Skip to content

Commit f550963

Browse files
committed
Fix OCI verification with local cert - old bundle
`SigVerifier` is set on `CheckOpts` no matter whether the verifying material is a public key or a certificate. The `keyBytes` function used for comparing the verifier object was incorrectly assuming that `SigVerifier` being non-nil meant that a local certificate was not present and was returning the certificate's public key instead of the certificate. This change fixes the function to check for a certificate before returning the bytes from the public key. It is also possible to provide a public key as an argument when a certificate is in the signature bundle. It does not make sense to try to provide both, but this has been allowed and asserted in verify_blob_test.go. In this case, the comparer should ensure that the certificate and public key are related to each other. The alternative is to disallow providing a key as a command argument when a certificate is present in the signature, but this could be considered a breaking change. This change only applies to verifying images using the old signature format. For the new bundle format, the certificate verification goes through a different path. Signed-off-by: Colleen Murphy <[email protected]>
1 parent 2d110ab commit f550963

File tree

3 files changed

+92
-19
lines changed

3 files changed

+92
-19
lines changed

cmd/cosign/cli/verify/verify_blob_test.go

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,18 @@ func TestVerifyBlob(t *testing.T) {
162162
time.Now().Add(-time.Hour), leafPriv, rootCert, rootPriv)
163163
expiredLeafPem, _ := cryptoutils.MarshalCertificateToPEM(expiredLeafCert)
164164

165+
unrelatedPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
166+
if err != nil {
167+
t.Fatal(err)
168+
}
169+
unrelatedSigner, err := signature.LoadECDSASignerVerifier(unrelatedPriv, crypto.SHA256)
170+
if err != nil {
171+
t.Fatal(err)
172+
}
173+
unrelatedLeafCert, _ := test.GenerateLeafCertWithExpiration(identity, issuer,
174+
time.Now(), unrelatedPriv, rootCert, rootPriv)
175+
unrelatedCertPem, _ := cryptoutils.MarshalCertificateToPEM(unrelatedLeafCert)
176+
165177
// Make rekor signer
166178
rekorPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
167179
if err != nil {
@@ -178,18 +190,20 @@ func TestVerifyBlob(t *testing.T) {
178190
tmpRekorPubFile := writeBlobFile(t, td, string(pemRekor), "rekor_pub.key")
179191
t.Setenv("SIGSTORE_REKOR_PUBLIC_KEY", tmpRekorPubFile)
180192

181-
var makeSignature = func(blob []byte) string {
193+
var makeSignature = func(blob []byte, signer signature.SignerVerifier) string {
182194
sig, err := signer.SignMessage(bytes.NewReader(blob))
183195
if err != nil {
184196
t.Fatal(err)
185197
}
186198
return string(sig)
187199
}
188200
blobBytes := []byte("foo")
189-
blobSignature := makeSignature(blobBytes)
201+
blobSignature := makeSignature(blobBytes, signer)
190202

191203
otherBytes := []byte("bar")
192-
otherSignature := makeSignature(otherBytes)
204+
otherSignature := makeSignature(otherBytes, signer)
205+
206+
unrelatedSignature := makeSignature(blobBytes, unrelatedSigner)
193207

194208
// initialize timestamp for expired and unexpired certificates
195209
expiredTSAOpts := mock.TSAClientOptions{Time: time.Now().Add(-time.Hour), Message: []byte(blobSignature)}
@@ -300,18 +314,27 @@ func TestVerifyBlob(t *testing.T) {
300314
{
301315
name: "valid signature with public key - bad bundle cert mismatch",
302316
blob: blobBytes,
317+
signature: unrelatedSignature,
318+
key: pubKeyBytes,
319+
bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(unrelatedSignature),
320+
unrelatedCertPem, true),
321+
shouldErr: true,
322+
},
323+
{
324+
name: "valid signature with public key and bundle cert derived from public key",
325+
blob: blobBytes,
303326
signature: blobSignature,
304327
key: pubKeyBytes,
305328
bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature),
306329
unexpiredCertPem, true),
307-
shouldErr: true,
330+
shouldErr: false,
308331
},
309332
{
310333
name: "valid signature with public key - bad bundle signature mismatch",
311334
blob: blobBytes,
312335
signature: blobSignature,
313336
key: pubKeyBytes,
314-
bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes)),
337+
bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes, signer)),
315338
pubKeyBytes, true),
316339
shouldErr: true,
317340
},
@@ -365,21 +388,12 @@ func TestVerifyBlob(t *testing.T) {
365388
cert: unexpiredLeafCert,
366389
shouldErr: true,
367390
},
368-
{
369-
name: "valid signature with unexpired certificate - bad bundle cert mismatch",
370-
blob: blobBytes,
371-
signature: blobSignature,
372-
key: pubKeyBytes,
373-
bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature),
374-
unexpiredCertPem, true),
375-
shouldErr: true,
376-
},
377391
{
378392
name: "valid signature with unexpired certificate - bad bundle signature mismatch",
379393
blob: blobBytes,
380394
signature: blobSignature,
381395
cert: unexpiredLeafCert,
382-
bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes)),
396+
bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes, signer)),
383397
unexpiredCertPem, true),
384398
shouldErr: true,
385399
},

pkg/cosign/verify.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -945,15 +945,23 @@ func keyBytes(sig oci.Signature, co *CheckOpts) ([]byte, error) {
945945
if err != nil {
946946
return nil, err
947947
}
948-
// We have a public key.
948+
var pub crypto.PublicKey
949949
if co.SigVerifier != nil {
950-
pub, err := co.SigVerifier.PublicKey(co.PKOpts...)
950+
pub, err = co.SigVerifier.PublicKey(co.PKOpts...)
951951
if err != nil {
952952
return nil, err
953953
}
954-
return cryptoutils.MarshalPublicKeyToPEM(pub)
955954
}
956-
return cryptoutils.MarshalCertificateToPEM(cert)
955+
if cert != nil && co.SigVerifier != nil {
956+
if err := cryptoutils.EqualKeys(cert.PublicKey, pub); err != nil {
957+
return nil, fmt.Errorf("both public key and certificate were provided but did not match")
958+
}
959+
}
960+
961+
if cert != nil {
962+
return cryptoutils.MarshalCertificateToPEM(cert)
963+
}
964+
return cryptoutils.MarshalPublicKeyToPEM(pub)
957965
}
958966

959967
// VerifyBlobSignature verifies a blob signature.

pkg/cosign/verify_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,57 @@ func TestVerifyImageSignatureWithNoChain(t *testing.T) {
326326
t.Fatalf("expected verified=true, got verified=false")
327327
}
328328
}
329+
330+
func TestVerifyImageSignatureWithKeyAndCert(t *testing.T) {
331+
ctx := context.Background()
332+
rootCert, rootKey, _ := test.GenerateRootCa()
333+
sv, _, err := signature.NewECDSASignerVerifier(elliptic.P256(), rand.Reader, crypto.SHA256)
334+
if err != nil {
335+
t.Fatalf("creating signer: %v", err)
336+
}
337+
338+
leafCert, privKey, _ := test.GenerateLeafCert("[email protected]", "oidc-issuer", rootCert, rootKey)
339+
pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw})
340+
341+
rootPool := x509.NewCertPool()
342+
rootPool.AddCert(rootCert)
343+
344+
payload := []byte{1, 2, 3, 4}
345+
h := sha256.Sum256(payload)
346+
sig, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256)
347+
348+
// Create a fake bundle
349+
pe, _ := proposedEntries(base64.StdEncoding.EncodeToString(sig), payload, pemLeaf)
350+
entry, _ := rtypes.UnmarshalEntry(pe[0])
351+
leaf, _ := entry.Canonicalize(ctx)
352+
rekorBundle := CreateTestBundle(ctx, t, sv, leaf)
353+
pemBytes, _ := cryptoutils.MarshalPublicKeyToPEM(sv.Public())
354+
rekorPubKeys := NewTrustedTransparencyLogPubKeys()
355+
rekorPubKeys.AddTransparencyLogPubKey(pemBytes, tuf.Active)
356+
357+
opts := []static.Option{static.WithCertChain(pemLeaf, []byte{}), static.WithBundle(rekorBundle)}
358+
ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(sig), opts...)
359+
360+
leafSV, err := signature.LoadECDSASignerVerifier(privKey, crypto.SHA256)
361+
if err != nil {
362+
t.Fatal(err)
363+
}
364+
365+
verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{},
366+
&CheckOpts{
367+
SigVerifier: leafSV,
368+
RootCerts: rootPool,
369+
IgnoreSCT: true,
370+
Identities: []Identity{{Subject: "[email protected]", Issuer: "oidc-issuer"}},
371+
RekorPubKeys: &rekorPubKeys})
372+
if err != nil {
373+
t.Fatalf("unexpected error %v", err)
374+
}
375+
if verified == false {
376+
t.Fatalf("expected verified=true, got verified=false")
377+
}
378+
}
379+
329380
func TestVerifyImageSignatureWithInvalidPublicKeyType(t *testing.T) {
330381
ctx := context.Background()
331382
rootCert, rootKey, _ := test.GenerateRootCa()

0 commit comments

Comments
 (0)