From 057b44253bb1e61d7bbdce2ed0ba30160a1b13b8 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Fri, 13 Sep 2024 11:12:13 -0700 Subject: [PATCH] Include URI for CA verified timestamps (#270) Include the URI of the transparency log and timestamp authority for verified timestamps in the final verification result. The URIs are helpful for human readability of the result. When the WithoutAnyObserverTimestampsUnsafe verify policy is selected, there is no certificate authority, so the URI is left empty. Signed-off-by: Colleen Murphy --- pkg/root/trusted_root.go | 4 +++- pkg/verify/signed_entity.go | 6 +++--- pkg/verify/signed_entity_test.go | 1 + pkg/verify/tlog.go | 35 ++++++++++++-------------------- pkg/verify/tlog_test.go | 2 +- pkg/verify/tsa.go | 17 ++++++++++------ pkg/verify/tsa_test.go | 2 +- 7 files changed, 33 insertions(+), 34 deletions(-) diff --git a/pkg/root/trusted_root.go b/pkg/root/trusted_root.go index 3112aebb..7291960b 100644 --- a/pkg/root/trusted_root.go +++ b/pkg/root/trusted_root.go @@ -279,7 +279,9 @@ func ParseCertificateAuthority(certAuthority *prototrustroot.CertificateAuthorit } } - // TODO: Should we inspect/enforce ca.Subject and ca.Uri? + certificateAuthority.URI = certAuthority.Uri + + // TODO: Should we inspect/enforce ca.Subject? // TODO: Handle validity period (ca.ValidFor) return certificateAuthority, nil diff --git a/pkg/verify/signed_entity.go b/pkg/verify/signed_entity.go index 2ab826e9..b33fbd5f 100644 --- a/pkg/verify/signed_entity.go +++ b/pkg/verify/signed_entity.go @@ -667,7 +667,7 @@ func (v *SignedEntityVerifier) VerifyTransparencyLogInclusion(entity SignedEntit } for _, vts := range verifiedTlogTimestamps { - verifiedTimestamps = append(verifiedTimestamps, TimestampVerificationResult{Type: "Tlog", URI: "TODO", Timestamp: vts}) + verifiedTimestamps = append(verifiedTimestamps, TimestampVerificationResult{Type: "Tlog", URI: vts.URI, Timestamp: vts.Time}) } } @@ -692,7 +692,7 @@ func (v *SignedEntityVerifier) VerifyObserverTimestamps(entity SignedEntity, log return nil, err } for _, vts := range verifiedSignedTimestamps { - verifiedTimestamps = append(verifiedTimestamps, TimestampVerificationResult{Type: "TimestampAuthority", URI: "TODO", Timestamp: vts}) + verifiedTimestamps = append(verifiedTimestamps, TimestampVerificationResult{Type: "TimestampAuthority", URI: vts.URI, Timestamp: vts.Time}) } } @@ -719,7 +719,7 @@ func (v *SignedEntityVerifier) VerifyObserverTimestamps(entity SignedEntity, log // append all timestamps verifiedTimestamps = append(verifiedTimestamps, logTimestamps...) for _, vts := range verifiedSignedTimestamps { - verifiedTimestamps = append(verifiedTimestamps, TimestampVerificationResult{Type: "TimestampAuthority", URI: "TODO", Timestamp: vts}) + verifiedTimestamps = append(verifiedTimestamps, TimestampVerificationResult{Type: "TimestampAuthority", URI: vts.URI, Timestamp: vts.Time}) } } diff --git a/pkg/verify/signed_entity_test.go b/pkg/verify/signed_entity_test.go index ab04c37f..98ea657e 100644 --- a/pkg/verify/signed_entity_test.go +++ b/pkg/verify/signed_entity_test.go @@ -94,6 +94,7 @@ func TestEntitySignedByPublicGoodWithTlogVerifiesSuccessfully(t *testing.T) { assert.NotNil(t, res.Signature.Certificate) assert.Equal(t, "https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main", res.Signature.Certificate.SubjectAlternativeName) assert.NotEmpty(t, res.VerifiedTimestamps) + assert.Equal(t, "https://rekor.sigstore.dev", res.VerifiedTimestamps[0].URI) // verifies with integrated timestamp threshold too v, err = verify.NewSignedEntityVerifier(tr, verify.WithTransparencyLog(1), verify.WithIntegratedTimestamps(1)) diff --git a/pkg/verify/tlog.go b/pkg/verify/tlog.go index e91baafd..4dcacde1 100644 --- a/pkg/verify/tlog.go +++ b/pkg/verify/tlog.go @@ -21,7 +21,6 @@ import ( "encoding/hex" "errors" "fmt" - "time" rekorClient "github.com/sigstore/rekor/pkg/client" rekorGeneratedClient "github.com/sigstore/rekor/pkg/generated/client" @@ -43,7 +42,7 @@ const maxAllowedTlogEntries = 32 // that must be verified. // // If online is true, the log entry is verified against the Rekor server. -func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.TrustedMaterial, logThreshold int, trustIntegratedTime, online bool) ([]time.Time, error) { //nolint:revive +func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.TrustedMaterial, logThreshold int, trustIntegratedTime, online bool) ([]Timestamp, error) { //nolint:revive entries, err := entity.TlogEntries() if err != nil { return nil, err @@ -75,7 +74,7 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru return nil, err } - verifiedTimestamps := []time.Time{} + verifiedTimestamps := []Timestamp{} logEntriesVerified := 0 for _, entry := range entries { @@ -84,29 +83,29 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru return nil, err } + rekorLogs := trustedMaterial.RekorLogs() + keyID := entry.LogKeyID() + hex64Key := hex.EncodeToString([]byte(keyID)) + tlogVerifier, ok := trustedMaterial.RekorLogs()[hex64Key] + if !ok { + // skip entries the trust root cannot verify + continue + } if !online { if !entry.HasInclusionPromise() && !entry.HasInclusionProof() { return nil, fmt.Errorf("entry must contain an inclusion proof and/or promise") } if entry.HasInclusionPromise() { - err = tlog.VerifySET(entry, trustedMaterial.RekorLogs()) + err = tlog.VerifySET(entry, rekorLogs) if err != nil { // skip entries the trust root cannot verify continue } if trustIntegratedTime { - verifiedTimestamps = append(verifiedTimestamps, entry.IntegratedTime()) + verifiedTimestamps = append(verifiedTimestamps, Timestamp{Time: entry.IntegratedTime(), URI: tlogVerifier.BaseURL}) } } if entity.HasInclusionProof() { - keyID := entry.LogKeyID() - hex64Key := hex.EncodeToString([]byte(keyID)) - tlogVerifier, ok := trustedMaterial.RekorLogs()[hex64Key] - if !ok { - // skip entries the trust root cannot verify - continue - } - verifier, err := getVerifier(tlogVerifier.PublicKey, tlogVerifier.SignatureHashFunc) if err != nil { return nil, err @@ -119,14 +118,6 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru // DO NOT use timestamp with only an inclusion proof, because it is not signed metadata } } else { - keyID := entry.LogKeyID() - hex64Key := hex.EncodeToString([]byte(keyID)) - tlogVerifier, ok := trustedMaterial.RekorLogs()[hex64Key] - if !ok { - // skip entries the trust root cannot verify - continue - } - client, err := getRekorClient(tlogVerifier.BaseURL) if err != nil { return nil, err @@ -160,7 +151,7 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru } } if trustIntegratedTime { - verifiedTimestamps = append(verifiedTimestamps, entry.IntegratedTime()) + verifiedTimestamps = append(verifiedTimestamps, Timestamp{Time: entry.IntegratedTime(), URI: tlogVerifier.BaseURL}) } } // Ensure entry signature matches signature from bundle diff --git a/pkg/verify/tlog_test.go b/pkg/verify/tlog_test.go index dc16bf3c..be8d60bc 100644 --- a/pkg/verify/tlog_test.go +++ b/pkg/verify/tlog_test.go @@ -35,7 +35,7 @@ func TestTlogVerifier(t *testing.T) { entity, err := virtualSigstore.Attest("foo@example.com", "issuer", statement) assert.NoError(t, err) - var ts []time.Time + var ts []verify.Timestamp ts, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, true, false) assert.NoError(t, err) // 1 verified timestamp diff --git a/pkg/verify/tsa.go b/pkg/verify/tsa.go index 966fad20..2db892b0 100644 --- a/pkg/verify/tsa.go +++ b/pkg/verify/tsa.go @@ -28,9 +28,14 @@ import ( const maxAllowedTimestamps = 32 +type Timestamp struct { + Time time.Time + URI string +} + // VerifyTimestampAuthority verifies that the given entity has been timestamped // by a trusted timestamp authority and that the timestamp is valid. -func VerifyTimestampAuthority(entity SignedEntity, trustedMaterial root.TrustedMaterial) ([]time.Time, error) { //nolint:revive +func VerifyTimestampAuthority(entity SignedEntity, trustedMaterial root.TrustedMaterial) ([]Timestamp, error) { //nolint:revive signedTimestamps, err := entity.Timestamps() if err != nil { return nil, err @@ -62,7 +67,7 @@ func VerifyTimestampAuthority(entity SignedEntity, trustedMaterial root.TrustedM return nil, err } - verifiedTimestamps := []time.Time{} + verifiedTimestamps := []Timestamp{} for _, timestamp := range signedTimestamps { verifiedSignedTimestamp, err := verifySignedTimestamp(timestamp, signatureBytes, trustedMaterial, verificationContent) @@ -82,7 +87,7 @@ func VerifyTimestampAuthority(entity SignedEntity, trustedMaterial root.TrustedM // // The threshold parameter is the number of unique timestamps that must be // verified. -func VerifyTimestampAuthorityWithThreshold(entity SignedEntity, trustedMaterial root.TrustedMaterial, threshold int) ([]time.Time, error) { //nolint:revive +func VerifyTimestampAuthorityWithThreshold(entity SignedEntity, trustedMaterial root.TrustedMaterial, threshold int) ([]Timestamp, error) { //nolint:revive verifiedTimestamps, err := VerifyTimestampAuthority(entity, trustedMaterial) if err != nil { return nil, err @@ -93,7 +98,7 @@ func VerifyTimestampAuthorityWithThreshold(entity SignedEntity, trustedMaterial return verifiedTimestamps, nil } -func verifySignedTimestamp(signedTimestamp []byte, dsseSignatureBytes []byte, trustedMaterial root.TrustedMaterial, verificationContent VerificationContent) (time.Time, error) { +func verifySignedTimestamp(signedTimestamp []byte, dsseSignatureBytes []byte, trustedMaterial root.TrustedMaterial, verificationContent VerificationContent) (Timestamp, error) { certAuthorities := trustedMaterial.TimestampingAuthorities() // Iterate through TSA certificate authorities to find one that verifies @@ -124,8 +129,8 @@ func verifySignedTimestamp(signedTimestamp []byte, dsseSignatureBytes []byte, tr } // All above verification successful, so return nil - return timestamp.Time, nil + return Timestamp{Time: timestamp.Time, URI: ca.URI}, nil } - return time.Time{}, errors.New("unable to verify signed timestamps") + return Timestamp{}, errors.New("unable to verify signed timestamps") } diff --git a/pkg/verify/tsa_test.go b/pkg/verify/tsa_test.go index 40972fc2..3ebd4eac 100644 --- a/pkg/verify/tsa_test.go +++ b/pkg/verify/tsa_test.go @@ -60,7 +60,7 @@ func TestTimestampAuthorityVerifierWithoutThreshold(t *testing.T) { virtualSigstore2, err := ca.NewVirtualSigstore() assert.NoError(t, err) - var ts []time.Time + var ts []verify.Timestamp // expect one verified timestamp ts, err = verify.VerifyTimestampAuthority(entity, virtualSigstore)