Skip to content

Commit 3ade80c

Browse files
Haydencmurphy
andauthored
Fix bundle verify path for old bundle/trusted root (#4624)
Ensure the bundle signature and key are compared to the rekor entry every time, not just when trusted root is used. Sigstore-go's VerifySET does not do this full comparison, we'd need to go through one of the more comprehensive Verify* functions to get this level of verification from the library. Signed-off-by: Colleen Murphy <colleenmurphy@google.com> Co-authored-by: Colleen Murphy <cmurphy@users.noreply.github.com>
1 parent c4e6a78 commit 3ade80c

File tree

2 files changed

+109
-29
lines changed

2 files changed

+109
-29
lines changed

pkg/cosign/verify.go

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,36 @@ func VerifyBundle(sig oci.Signature, co *CheckOpts) (bool, error) {
12021202
return false, errors.New("no trusted rekor public keys provided")
12031203
}
12041204

1205+
if err := compareSigs(bundle.Payload.Body.(string), sig); err != nil {
1206+
return false, err
1207+
}
1208+
1209+
if err := comparePublicKey(bundle.Payload.Body.(string), sig, co); err != nil {
1210+
return false, err
1211+
}
1212+
1213+
payload, err := sig.Payload()
1214+
if err != nil {
1215+
return false, fmt.Errorf("reading payload: %w", err)
1216+
}
1217+
signature, err := sig.Base64Signature()
1218+
if err != nil {
1219+
return false, fmt.Errorf("reading base64signature: %w", err)
1220+
}
1221+
1222+
alg, bundlehash, err := bundleHash(bundle.Payload.Body.(string), signature)
1223+
if err != nil {
1224+
return false, fmt.Errorf("computing bundle hash: %w", err)
1225+
}
1226+
h := sha256.Sum256(payload)
1227+
payloadHash := hex.EncodeToString(h[:])
1228+
1229+
if alg != "sha256" {
1230+
return false, fmt.Errorf("unexpected algorithm: %q", alg)
1231+
} else if bundlehash != payloadHash {
1232+
return false, fmt.Errorf("matching bundle to payload: bundle=%q, payload=%q", bundlehash, payloadHash)
1233+
}
1234+
12051235
if co.TrustedMaterial != nil {
12061236
payload := bundle.Payload
12071237
logID, err := hex.DecodeString(payload.LogID)
@@ -1216,6 +1246,7 @@ func VerifyBundle(sig oci.Signature, co *CheckOpts) (bool, error) {
12161246
if err := tlog.VerifySET(entry, co.TrustedMaterial.RekorLogs()); err != nil {
12171247
return false, fmt.Errorf("verifying bundle with trusted root: %w", err)
12181248
}
1249+
12191250
return true, nil
12201251
}
12211252
// Make sure all the rekorPubKeys are ecsda.PublicKeys
@@ -1225,14 +1256,6 @@ func VerifyBundle(sig oci.Signature, co *CheckOpts) (bool, error) {
12251256
}
12261257
}
12271258

1228-
if err := compareSigs(bundle.Payload.Body.(string), sig); err != nil {
1229-
return false, err
1230-
}
1231-
1232-
if err := comparePublicKey(bundle.Payload.Body.(string), sig, co); err != nil {
1233-
return false, err
1234-
}
1235-
12361259
pubKey, ok := co.RekorPubKeys.Keys[bundle.Payload.LogID]
12371260
if !ok {
12381261
return false, &VerificationFailure{
@@ -1247,27 +1270,6 @@ func VerifyBundle(sig oci.Signature, co *CheckOpts) (bool, error) {
12471270
fmt.Fprintf(os.Stderr, "**Info** Successfully verified Rekor entry using an expired verification key\n")
12481271
}
12491272

1250-
payload, err := sig.Payload()
1251-
if err != nil {
1252-
return false, fmt.Errorf("reading payload: %w", err)
1253-
}
1254-
signature, err := sig.Base64Signature()
1255-
if err != nil {
1256-
return false, fmt.Errorf("reading base64signature: %w", err)
1257-
}
1258-
1259-
alg, bundlehash, err := bundleHash(bundle.Payload.Body.(string), signature)
1260-
if err != nil {
1261-
return false, fmt.Errorf("computing bundle hash: %w", err)
1262-
}
1263-
h := sha256.Sum256(payload)
1264-
payloadHash := hex.EncodeToString(h[:])
1265-
1266-
if alg != "sha256" {
1267-
return false, fmt.Errorf("unexpected algorithm: %q", alg)
1268-
} else if bundlehash != payloadHash {
1269-
return false, fmt.Errorf("matching bundle to payload: bundle=%q, payload=%q", bundlehash, payloadHash)
1270-
}
12711273
return true, nil
12721274
}
12731275

pkg/cosign/verify_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,84 @@ func TestVerifyImageSignatureWithSigVerifierAndRekorTSA(t *testing.T) {
731731
}
732732
}
733733

734+
func TestVerifyImageSignatureWithMismatchedBundleAndTrustedRoot(t *testing.T) {
735+
ctx := context.Background()
736+
var ca root.FulcioCertificateAuthority
737+
rootCert, rootKey, _ := test.GenerateRootCa()
738+
ca.Root = rootCert
739+
sv, _, err := signature.NewECDSASignerVerifier(elliptic.P256(), rand.Reader, crypto.SHA256)
740+
if err != nil {
741+
t.Fatalf("creating signer: %v", err)
742+
}
743+
744+
leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey)
745+
pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw})
746+
747+
rootPool := x509.NewCertPool()
748+
rootPool.AddCert(rootCert)
749+
750+
payload := []byte{1, 2, 3, 4}
751+
h := sha256.Sum256(payload)
752+
signature1, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256)
753+
754+
// Create a fake bundle
755+
pe, _ := proposedEntries(base64.StdEncoding.EncodeToString(signature1), payload, pemLeaf)
756+
entry, _ := rtypes.UnmarshalEntry(pe[0])
757+
leaf, _ := entry.Canonicalize(ctx)
758+
rekorBundle := CreateTestBundle(ctx, t, sv, leaf)
759+
pemBytes, _ := cryptoutils.MarshalPublicKeyToPEM(sv.Public())
760+
rekorPubKeys := NewTrustedTransparencyLogPubKeys()
761+
rekorPubKeys.AddTransparencyLogPubKey(pemBytes, tuf.Active)
762+
763+
tlogs := make(map[string]*root.TransparencyLog)
764+
for k, v := range rekorPubKeys.Keys {
765+
tlogs[k] = &root.TransparencyLog{PublicKey: v.PubKey, HashFunc: crypto.SHA256, ValidityPeriodStart: time.Now().Add(-1 * time.Minute)}
766+
}
767+
768+
trustedRoot, err := root.NewTrustedRoot(root.TrustedRootMediaType01, []root.CertificateAuthority{&ca}, nil, nil, tlogs)
769+
if err != nil {
770+
t.Fatal(err)
771+
}
772+
773+
// Create a different bundle for a different signature
774+
signature2, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256)
775+
pe2, _ := proposedEntries(base64.StdEncoding.EncodeToString(signature2), payload, pemLeaf)
776+
entry2, _ := rtypes.UnmarshalEntry(pe2[0])
777+
leaf2, _ := entry2.Canonicalize(ctx)
778+
rekorBundle2 := CreateTestBundle(ctx, t, sv, leaf2)
779+
780+
opts := []static.Option{static.WithCertChain(pemLeaf, []byte{}), static.WithBundle(rekorBundle2)}
781+
// Create a signed entity for the original signature but with the wrong bundle for that signature
782+
ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature1), opts...)
783+
784+
_, err = VerifyImageSignature(context.TODO(), ociSig, v1.Hash{},
785+
&CheckOpts{
786+
RootCerts: rootPool,
787+
IgnoreSCT: true,
788+
Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}},
789+
TrustedMaterial: trustedRoot})
790+
if err == nil || !strings.Contains(err.Error(), "signature in bundle does not match signature being verified") {
791+
t.Fatalf("expected error for mismatched signature and bundle, got %v", err)
792+
}
793+
794+
// Create a signed entity with a different key from the bundle
795+
leafCert2, _, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey)
796+
pemLeaf2 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert2.Raw})
797+
798+
opts = []static.Option{static.WithCertChain(pemLeaf2, []byte{}), static.WithBundle(rekorBundle)}
799+
ociSig, _ = static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature1), opts...)
800+
801+
_, err = VerifyImageSignature(context.TODO(), ociSig, v1.Hash{},
802+
&CheckOpts{
803+
RootCerts: rootPool,
804+
IgnoreSCT: true,
805+
Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}},
806+
TrustedMaterial: trustedRoot})
807+
if err == nil || !strings.Contains(err.Error(), "error verifying bundle: comparing public key PEMs") {
808+
t.Fatal(err)
809+
}
810+
}
811+
734812
func TestValidateAndUnpackCertSuccess(t *testing.T) {
735813
subject := "email@email"
736814
oidcIssuer := "https://accounts.google.com"

0 commit comments

Comments
 (0)