From a67a25037f1f5dead191e901946c712b9703a6e1 Mon Sep 17 00:00:00 2001 From: JasonPowr Date: Fri, 8 May 2026 10:09:21 +0100 Subject: [PATCH] fix: add cosignv3 signiture support, Fix LogID encoding Signed-off-by: JasonPowr --- hack/gentestdata/gentestdata.go | 14 +++++++++-- pkg/apis/config/sigstore_keys.go | 7 +++++- .../trustroot/testdata/marshalledEntry.json | 4 +-- .../testdata/marshalledEntryFromMirrorFS.json | 4 +-- pkg/reconciler/trustroot/trustroot.go | 19 +++++++++++--- pkg/reconciler/trustroot/trustroot_test.go | 13 ++++++++-- pkg/webhook/validation.go | 6 +++++ pkg/webhook/validator.go | 20 ++++++++++++--- pkg/webhook/validator_test.go | 25 +++++++++++++------ 9 files changed, 88 insertions(+), 24 deletions(-) diff --git a/hack/gentestdata/gentestdata.go b/hack/gentestdata/gentestdata.go index 1797e3aea..6d2400f6c 100644 --- a/hack/gentestdata/gentestdata.go +++ b/hack/gentestdata/gentestdata.go @@ -22,8 +22,10 @@ import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" + "encoding/hex" "encoding/pem" "flag" + "fmt" "log" "math/big" "os" @@ -215,14 +217,22 @@ func populateLogIDs(sigstoreKeys *config.SigstoreKeys) error { if err != nil { return err } - sigstoreKeys.Tlogs[i].LogId = &config.LogID{KeyId: []byte(logID)} + logIDBytes, err := hex.DecodeString(logID) + if err != nil { + return fmt.Errorf("failed to decode tlog LogID: %w", err) + } + sigstoreKeys.Tlogs[i].LogId = &config.LogID{KeyId: logIDBytes} } for i := range sigstoreKeys.Ctlogs { logID, err := genLogID(sigstoreKeys.Ctlogs[i].PublicKey.RawBytes) if err != nil { return err } - sigstoreKeys.Ctlogs[i].LogId = &config.LogID{KeyId: []byte(logID)} + logIDBytes, err := hex.DecodeString(logID) + if err != nil { + return fmt.Errorf("failed to decode ctlog LogID: %w", err) + } + sigstoreKeys.Ctlogs[i].LogId = &config.LogID{KeyId: logIDBytes} } return nil } diff --git a/pkg/apis/config/sigstore_keys.go b/pkg/apis/config/sigstore_keys.go index f52488a9c..0e25d9240 100644 --- a/pkg/apis/config/sigstore_keys.go +++ b/pkg/apis/config/sigstore_keys.go @@ -21,6 +21,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" + "encoding/hex" "encoding/pem" "fmt" @@ -166,13 +167,17 @@ func ConvertTransparencyLogInstance(source v1alpha1.TransparencyLogInstance) (*p if err != nil { return nil, err } + logIDBytes, err := hex.DecodeString(logID) + if err != nil { + return nil, fmt.Errorf("failed to decode LogID: %w", err) + } return &pbtrustroot.TransparencyLogInstance{ BaseUrl: source.BaseURL.String(), HashAlgorithm: HashStringToHashAlgorithm(source.HashAlgorithm), PublicKey: pbpk, LogId: &pbcommon.LogId{ - KeyId: []byte(logID), + KeyId: logIDBytes, }, }, nil } diff --git a/pkg/reconciler/trustroot/testdata/marshalledEntry.json b/pkg/reconciler/trustroot/testdata/marshalledEntry.json index 1025d792c..56a8d8153 100644 --- a/pkg/reconciler/trustroot/testdata/marshalledEntry.json +++ b/pkg/reconciler/trustroot/testdata/marshalledEntry.json @@ -12,7 +12,7 @@ } }, "logId": { - "keyId": "M2E3ZWZjYTk3MGUzMGY2N2U2MTI0MzQ3MzZlMzVkNTRjMDYxZDM4OThlZmY1YjA2Mjc5ZmQ3MjIwNDM4OTY4NQ==" + "keyId": "On78qXDjD2fmEkNHNuNdVMBh04mO/1sGJ5/XIgQ4loU=" } } ], @@ -50,7 +50,7 @@ } }, "logId": { - "keyId": "ZDc0MGI4ZGQ2NGM5NjE4NTVlODk1M2Y0Mjc2MzY5ZjE4NGQ4MzM3MTZhNmE5OTA4NDRjOTIzYTkwODNkOTI5Ng==" + "keyId": "10C43WTJYYVeiVP0J2Np8YTYM3FqapkIRMkjqQg9kpY=" } } ], diff --git a/pkg/reconciler/trustroot/testdata/marshalledEntryFromMirrorFS.json b/pkg/reconciler/trustroot/testdata/marshalledEntryFromMirrorFS.json index c21b3647a..9227ae6ee 100644 --- a/pkg/reconciler/trustroot/testdata/marshalledEntryFromMirrorFS.json +++ b/pkg/reconciler/trustroot/testdata/marshalledEntryFromMirrorFS.json @@ -10,7 +10,7 @@ } }, "logId": { - "keyId": "M2E3ZWZjYTk3MGUzMGY2N2U2MTI0MzQ3MzZlMzVkNTRjMDYxZDM4OThlZmY1YjA2Mjc5ZmQ3MjIwNDM4OTY4NQ==" + "keyId": "On78qXDjD2fmEkNHNuNdVMBh04mO/1sGJ5/XIgQ4loU=" } } ], @@ -42,7 +42,7 @@ } }, "logId": { - "keyId": "ZDc0MGI4ZGQ2NGM5NjE4NTVlODk1M2Y0Mjc2MzY5ZjE4NGQ4MzM3MTZhNmE5OTA4NDRjOTIzYTkwODNkOTI5Ng==" + "keyId": "10C43WTJYYVeiVP0J2Np8YTYM3FqapkIRMkjqQg9kpY=" } } ] diff --git a/pkg/reconciler/trustroot/trustroot.go b/pkg/reconciler/trustroot/trustroot.go index 21491a402..26d7341ca 100644 --- a/pkg/reconciler/trustroot/trustroot.go +++ b/pkg/reconciler/trustroot/trustroot.go @@ -18,6 +18,7 @@ import ( "context" "crypto" "crypto/ecdsa" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -95,14 +96,22 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, trustroot *v1alpha1.Trus if !ok { return fmt.Errorf("public key %d is not ecdsa.PublicKey", i) } - sigstoreKeys.Tlogs[i].LogId = &config.LogID{KeyId: []byte(logID)} + logIDBytes, err := hex.DecodeString(logID) + if err != nil { + return fmt.Errorf("invalid rekor log ID encoding %d: %w", i, err) + } + sigstoreKeys.Tlogs[i].LogId = &config.LogID{KeyId: logIDBytes} } for i, ctlog := range sigstoreKeys.Ctlogs { _, logID, err := pemToKeyAndID(config.SerializePublicKey(ctlog.PublicKey)) if err != nil { return fmt.Errorf("invalid ctlog public key %d: %w", i, err) } - sigstoreKeys.Ctlogs[i].LogId = &config.LogID{KeyId: []byte(logID)} + logIDBytes, err := hex.DecodeString(logID) + if err != nil { + return fmt.Errorf("invalid ctlog log ID encoding %d: %w", i, err) + } + sigstoreKeys.Ctlogs[i].LogId = &config.LogID{KeyId: logIDBytes} } // See if the CM holding configs exists @@ -328,10 +337,14 @@ func genTransparencyLogInstance(baseURL string, pkBytes []byte) (*config.Transpa if err != nil { return nil, fmt.Errorf("failed to construct LogID: %w", err) } + logIDBytes, err := hex.DecodeString(logID) + if err != nil { + return nil, fmt.Errorf("failed to decode LogID: %w", err) + } return &config.TransparencyLogInstance{ BaseUrl: baseURL, HashAlgorithm: pbcommon.HashAlgorithm_SHA2_256, PublicKey: pbpk, - LogId: &pbcommon.LogId{KeyId: []byte(logID)}, + LogId: &pbcommon.LogId{KeyId: logIDBytes}, }, nil } diff --git a/pkg/reconciler/trustroot/trustroot_test.go b/pkg/reconciler/trustroot/trustroot_test.go index 4f404efe8..b2a5ea7dc 100644 --- a/pkg/reconciler/trustroot/trustroot_test.go +++ b/pkg/reconciler/trustroot/trustroot_test.go @@ -23,6 +23,7 @@ import ( "crypto/x509" "crypto/x509/pkix" _ "embed" + "encoding/hex" "encoding/pem" "fmt" "math/big" @@ -447,11 +448,19 @@ func makeConfigMapWithSigstoreKeys() *corev1.ConfigMap { if err != nil { panic("failed to convert test SigstoreKeys") } + rekorLogIDBytes, err := hex.DecodeString(rekorLogID) + if err != nil { + panic("failed to decode rekor log ID") + } for i := range c.Tlogs { - c.Tlogs[i].LogId = &config.LogID{KeyId: []byte(rekorLogID)} + c.Tlogs[i].LogId = &config.LogID{KeyId: rekorLogIDBytes} + } + ctfeLogIDBytes, err := hex.DecodeString(ctfeLogID) + if err != nil { + panic("failed to decode ctfe log ID") } for i := range c.Ctlogs { - c.Ctlogs[i].LogId = &config.LogID{KeyId: []byte(ctfeLogID)} + c.Ctlogs[i].LogId = &config.LogID{KeyId: ctfeLogIDBytes} } marshalled, err := resources.Marshal(c) if err != nil { diff --git a/pkg/webhook/validation.go b/pkg/webhook/validation.go index 76333c162..e092ce755 100644 --- a/pkg/webhook/validation.go +++ b/pkg/webhook/validation.go @@ -76,6 +76,12 @@ func validSignatures(ctx context.Context, ref name.Reference, checkOpts *cosign. return sigs, err } +func validBundleSignatures(ctx context.Context, ref name.Reference, checkOpts *cosign.CheckOpts) ([]oci.Signature, error) { + checkOpts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier + sigs, _, err := cosignVerifyAttestations(ctx, ref, checkOpts) + return sigs, err +} + func validAttestations(ctx context.Context, ref name.Reference, checkOpts *cosign.CheckOpts) ([]oci.Signature, error) { cfg := policycontrollerconfig.FromContextOrDefaults(ctx) if cfg.EnableOCI11 { diff --git a/pkg/webhook/validator.go b/pkg/webhook/validator.go index 5cd41d924..928360b5d 100644 --- a/pkg/webhook/validator.go +++ b/pkg/webhook/validator.go @@ -803,7 +803,13 @@ func ValidatePolicySignaturesForAuthority(ctx context.Context, ref name.Referenc case authority.Keyless != nil: if authority.Keyless.URL != nil { - sps, err := validSignatures(ctx, ref, checkOpts) + var sps []oci.Signature + var err error + if checkOpts.NewBundleFormat { + sps, err = validBundleSignatures(ctx, ref, checkOpts) + } else { + sps, err = validSignatures(ctx, ref, checkOpts) + } if err != nil { logging.FromContext(ctx).Errorf("failed validSignatures for authority %s with fulcio for %s: %v", name, ref.Name(), err) return nil, fmt.Errorf("signature keyless validation failed for authority %s for %s: %w", name, ref.Name(), err) @@ -813,7 +819,13 @@ func ValidatePolicySignaturesForAuthority(ctx context.Context, ref name.Referenc } return nil, fmt.Errorf("no Keyless URL specified") case authority.RFC3161Timestamp != nil: - sps, err := validSignatures(ctx, ref, checkOpts) + var sps []oci.Signature + var err error + if checkOpts.NewBundleFormat { + sps, err = validBundleSignatures(ctx, ref, checkOpts) + } else { + sps, err = validSignatures(ctx, ref, checkOpts) + } if err != nil { logging.FromContext(ctx).Errorf("failed validSignatures for authority %s with fulcio for %s: %v", name, ref.Name(), err) return nil, fmt.Errorf("signature TSA validation failed for authority %s for %s: %w", name, ref.Name(), err) @@ -1567,7 +1579,7 @@ func fulcioCertsFromAuthority(ctx context.Context, keylessRef *webhookcip.Keyles if err != nil { return nil, nil, nil, fmt.Errorf("unmarshaling public key %d failed: %w", i, err) } - ctlogKeys.Keys[string(ctlog.LogId.KeyId)] = cosign.TransparencyLogPubKey{ + ctlogKeys.Keys[hex.EncodeToString(ctlog.LogId.KeyId)] = cosign.TransparencyLogPubKey{ PubKey: pk, Status: tuf.Active, } @@ -1659,7 +1671,7 @@ func rekorKeysFromTrustRef(ctx context.Context, trustRootRef string) (*cosign.Tr if !ok { return nil, "", fmt.Errorf("public key %d is not ecdsa.PublicKey", i) } - retKeys.Keys[string(tlog.LogId.KeyId)] = cosign.TransparencyLogPubKey{ + retKeys.Keys[hex.EncodeToString(tlog.LogId.KeyId)] = cosign.TransparencyLogPubKey{ PubKey: pkecdsa, Status: tuf.Active, } diff --git a/pkg/webhook/validator_test.go b/pkg/webhook/validator_test.go index a7e1c77c7..1a3091a2d 100644 --- a/pkg/webhook/validator_test.go +++ b/pkg/webhook/validator_test.go @@ -22,6 +22,7 @@ import ( "crypto/ecdsa" "crypto/x509" "encoding/base64" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -134,6 +135,14 @@ aEcvjlCkgBCKXbrkumZV0m0dSlK1V1gxEiyQ8y6hk1MxJNe2AZrZUt7a4w== ctfeLogID = "39d1c085f7d5f3fe7a0de9e52a3ead14186891e52a9269d90de7990a30b55083" ) +func mustHexDecode(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + func TestValidatePodSpec(t *testing.T) { tag := name.MustParseReference("gcr.io/distroless/static:nonroot") // Resolved via crane digest on 2021/09/25 @@ -2998,7 +3007,7 @@ func TestFulcioCertsFromAuthority(t *testing.T) { CertChain: certChain, }}, Ctlogs: []*config.TransparencyLogInstance{{ - LogId: &config.LogID{KeyId: []byte(ctfeLogID)}, + LogId: &config.LogID{KeyId: mustHexDecode(ctfeLogID)}, PublicKey: pbpk, }}, } @@ -3106,7 +3115,7 @@ func TestRekorClientAndKeysFromAuthority(t *testing.T) { sk := config.SigstoreKeys{ Tlogs: []*config.TransparencyLogInstance{{ PublicKey: pbpk, - LogId: &config.LogID{KeyId: []byte(rekorLogID)}, + LogId: &config.LogID{KeyId: mustHexDecode(rekorLogID)}, BaseUrl: "rekor.example.com", }}, } @@ -3249,7 +3258,7 @@ func TestCheckOptsFromAuthority(t *testing.T) { skRekor := config.SigstoreKeys{ Tlogs: []*config.TransparencyLogInstance{{ PublicKey: pbpkRekor, - LogId: &config.LogID{KeyId: []byte("rekor-logid")}, + LogId: &config.LogID{KeyId: mustHexDecode(rekorLogID)}, BaseUrl: "rekor.example.com", }}, } @@ -3266,7 +3275,7 @@ func TestCheckOptsFromAuthority(t *testing.T) { CertChain: certChainPB, }}, Ctlogs: []*config.TransparencyLogInstance{{ - LogId: &config.LogID{KeyId: []byte(ctfeLogID)}, + LogId: &config.LogID{KeyId: mustHexDecode(ctfeLogID)}, PublicKey: pbpkCTFE, }}, } @@ -3274,7 +3283,7 @@ func TestCheckOptsFromAuthority(t *testing.T) { MediaType: "application/vnd.dev.sigstore.trustedroot+json;version=0.1", Tlogs: []*config.TransparencyLogInstance{{ PublicKey: pbpkRekor, - LogId: &config.LogID{KeyId: []byte("rekor-logid")}, + LogId: &config.LogID{KeyId: mustHexDecode(rekorLogID)}, BaseUrl: "rekor.example.com", HashAlgorithm: pbcommon.HashAlgorithm_SHA2_256, }}, @@ -3286,7 +3295,7 @@ func TestCheckOptsFromAuthority(t *testing.T) { CertChain: certChainPB, }}, Ctlogs: []*config.TransparencyLogInstance{{ - LogId: &config.LogID{KeyId: []byte(ctfeLogID)}, + LogId: &config.LogID{KeyId: mustHexDecode(ctfeLogID)}, PublicKey: pbpkCTFE, HashAlgorithm: pbcommon.HashAlgorithm_SHA2_256, }}, @@ -3349,7 +3358,7 @@ func TestCheckOptsFromAuthority(t *testing.T) { ctx: testCtx, wantClient: true, wantCheckOpts: &cosign.CheckOpts{ - RekorPubKeys: &cosign.TrustedTransparencyLogPubKeys{Keys: map[string]cosign.TransparencyLogPubKey{"rekor-logid": {PubKey: ecpk, Status: tuf.Active}}}, + RekorPubKeys: &cosign.TrustedTransparencyLogPubKeys{Keys: map[string]cosign.TransparencyLogPubKey{rekorLogID: {PubKey: ecpk, Status: tuf.Active}}}, }, }, { name: "trustroot found, Fulcio", @@ -3382,7 +3391,7 @@ func TestCheckOptsFromAuthority(t *testing.T) { wantCheckOpts: &cosign.CheckOpts{ RootCerts: roots, IntermediateCerts: intermediates, - RekorPubKeys: &cosign.TrustedTransparencyLogPubKeys{Keys: map[string]cosign.TransparencyLogPubKey{"rekor-logid": {PubKey: ecpk, Status: tuf.Active}}}, + RekorPubKeys: &cosign.TrustedTransparencyLogPubKeys{Keys: map[string]cosign.TransparencyLogPubKey{rekorLogID: {PubKey: ecpk, Status: tuf.Active}}}, Identities: []cosign.Identity{{ Issuer: "issuer", Subject: "subject",