diff --git a/go.mod b/go.mod index 6a78eeab..54bcabc6 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/go-ldap/ldap/v3 v3.4.6 github.com/notaryproject/notation-core-go v1.0.3-0.20240325061945-807a3386734e github.com/notaryproject/notation-plugin-framework-go v1.0.0 + github.com/notaryproject/tspclient-go v0.0.0-20240122083733-a373599795a2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/veraison/go-cose v1.1.0 @@ -23,3 +24,7 @@ require ( github.com/x448/float16 v0.8.4 // indirect golang.org/x/sync v0.6.0 // indirect ) + +replace github.com/notaryproject/notation-core-go => github.com/Two-Hearts/notation-core-go v0.0.0-20240418044922-14d05519328b + +replace github.com/notaryproject/tspclient-go => github.com/Two-Hearts/tspclient-go v0.0.0-20240410033505-94c3b3def019 diff --git a/go.sum b/go.sum index 725505f1..70efd74f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Two-Hearts/notation-core-go v0.0.0-20240418044922-14d05519328b h1:a27dSIOWWw7rJSD2ebmWUYjwhhhPnIaMGlqPl9OuCmI= +github.com/Two-Hearts/notation-core-go v0.0.0-20240418044922-14d05519328b/go.mod h1:o3qDLatecAi3cQKnlnTk32mJNoNWovGFMiToV5n8KW4= +github.com/Two-Hearts/tspclient-go v0.0.0-20240410033505-94c3b3def019 h1:7SJ4FlWTmpXssu5J+XI7Fzn50tPsagFMEJSWmqv8nLU= +github.com/Two-Hearts/tspclient-go v0.0.0-20240410033505-94c3b3def019/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -15,8 +19,6 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/notaryproject/notation-core-go v1.0.3-0.20240325061945-807a3386734e h1:GdPnC0iJ2gIhed529oaVXtzWUTyDafmOUah/07uEQVo= -github.com/notaryproject/notation-core-go v1.0.3-0.20240325061945-807a3386734e/go.mod h1:HsaLU1gXhal0p5a0noBFEZxs2NIDCqdFgx4mD4DmlmY= github.com/notaryproject/notation-plugin-framework-go v1.0.0 h1:6Qzr7DGXoCgXEQN+1gTZWuJAZvxh3p8Lryjn5FaLzi4= github.com/notaryproject/notation-plugin-framework-go v1.0.0/go.mod h1:RqWSrTOtEASCrGOEffq0n8pSg2KOgKYiWqFWczRSics= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= diff --git a/notation.go b/notation.go index 8cb566a8..34ddda88 100644 --- a/notation.go +++ b/notation.go @@ -59,6 +59,9 @@ type SignerSignOptions struct { // SigningAgent sets the signing agent name SigningAgent string + + // TSA denotes the TSA server URL + TSAServerURL string } // Signer is a generic interface for signing an OCI artifact. diff --git a/signer/signer.go b/signer/signer.go index 05d0ea8c..af693d36 100644 --- a/signer/signer.go +++ b/signer/signer.go @@ -122,6 +122,7 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts SigningTime: time.Now(), SigningScheme: signature.SigningSchemeX509, SigningAgent: signingAgentId, + TSAServerURL: opts.TSAServerURL, } // Add expiry only if ExpiryDuration is not zero @@ -135,6 +136,7 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts logger.Debugf(" Expiry: %v", signReq.Expiry) logger.Debugf(" SigningScheme: %v", signReq.SigningScheme) logger.Debugf(" SigningAgent: %v", signReq.SigningAgent) + logger.Debugf(" TSAServerURL: %v", signReq.TSAServerURL) // perform signing sigEnv, err := signature.NewEnvelope(opts.SignatureMediaType) @@ -142,9 +144,14 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts return nil, nil, err } + var timestampErr *signature.TimestampError sig, err := sigEnv.Sign(signReq) if err != nil { - return nil, nil, err + if !errors.As(err, ×tampErr) { + return nil, nil, err + } + // warn on timestamping error, but do not fail the signing process + logger.Warnf("Failed to timestamp the signature. Error: %v", timestampErr) } envContent, err := sigEnv.Verify() @@ -154,8 +161,6 @@ func (s *GenericSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts if err := envelope.ValidatePayloadContentType(&envContent.Payload); err != nil { return nil, nil, err } - - // TODO: re-enable timestamping https://github.com/notaryproject/notation-go/issues/78 return sig, &envContent.SignerInfo, nil } diff --git a/verifier/helpers.go b/verifier/helpers.go index d432f0c1..43ddb7ce 100644 --- a/verifier/helpers.go +++ b/verifier/helpers.go @@ -57,31 +57,7 @@ func loadX509TrustStores(ctx context.Context, scheme signature.SigningScheme, po default: return nil, truststore.TrustStoreError{Msg: fmt.Sprintf("error while loading the trust store, unrecognized signing scheme %q", scheme)} } - - processedStoreSet := set.New[string]() - var certificates []*x509.Certificate - for _, trustStore := range policy.TrustStores { - if processedStoreSet.Contains(trustStore) { - // we loaded this trust store already - continue - } - - storeType, name, found := strings.Cut(trustStore, ":") - if !found { - return nil, truststore.TrustStoreError{Msg: fmt.Sprintf("error while loading the trust store, trust policy statement %q is missing separator in trust store value %q. The required format is :", policy.Name, trustStore)} - } - if typeToLoad != truststore.Type(storeType) { - continue - } - - certs, err := x509TrustStore.GetCertificates(ctx, typeToLoad, name) - if err != nil { - return nil, err - } - certificates = append(certificates, certs...) - processedStoreSet.Add(trustStore) - } - return certificates, nil + return loadX509TrustStoresWithType(ctx, typeToLoad, policy, x509TrustStore) } // isCriticalFailure checks whether a VerificationResult fails the entire @@ -154,3 +130,41 @@ func getVerificationPluginMinVersion(signerInfo *signature.SignerInfo) (string, } return version, nil } + +func loadX509TSATrustStores(ctx context.Context, scheme signature.SigningScheme, policy *trustpolicy.TrustPolicy, x509TrustStore truststore.X509TrustStore) ([]*x509.Certificate, error) { + var typeToLoad truststore.Type + switch scheme { + case signature.SigningSchemeX509: + typeToLoad = truststore.TypeTSA + default: + return nil, truststore.TrustStoreError{Msg: fmt.Sprintf("error while loading the TSA trust store, signing scheme must be notary.x509, but got %s", scheme)} + } + return loadX509TrustStoresWithType(ctx, typeToLoad, policy, x509TrustStore) +} + +func loadX509TrustStoresWithType(ctx context.Context, trustStoreType truststore.Type, policy *trustpolicy.TrustPolicy, x509TrustStore truststore.X509TrustStore) ([]*x509.Certificate, error) { + processedStoreSet := set.New[string]() + var certificates []*x509.Certificate + for _, trustStore := range policy.TrustStores { + if processedStoreSet.Contains(trustStore) { + // we loaded this trust store already + continue + } + + storeType, name, found := strings.Cut(trustStore, ":") + if !found { + return nil, truststore.TrustStoreError{Msg: fmt.Sprintf("error while loading the trust store, trust policy statement %q is missing separator in trust store value %q. The required format is :", policy.Name, trustStore)} + } + if trustStoreType != truststore.Type(storeType) { + continue + } + + certs, err := x509TrustStore.GetCertificates(ctx, trustStoreType, name) + if err != nil { + return nil, err + } + certificates = append(certificates, certs...) + processedStoreSet.Add(trustStore) + } + return certificates, nil +} diff --git a/verifier/trustpolicy/trustpolicy.go b/verifier/trustpolicy/trustpolicy.go index b0aaede5..e5297e78 100644 --- a/verifier/trustpolicy/trustpolicy.go +++ b/verifier/trustpolicy/trustpolicy.go @@ -134,7 +134,7 @@ var ( } ) -var supportedPolicyVersions = []string{"1.0"} +var supportedPolicyVersions = []string{"1.0", "1.1"} // Document represents a trustPolicy.json document type Document struct { @@ -156,6 +156,9 @@ type TrustPolicy struct { // SignatureVerification setting for this policy statement SignatureVerification SignatureVerification `json:"signatureVerification"` + // TimestampVerification setting for this policy statement + TimestampVerification *TimestampVerification `json:"timestampVerification,omitempty"` + // TrustStores this policy statement uses TrustStores []string `json:"trustStores,omitempty"` @@ -163,12 +166,24 @@ type TrustPolicy struct { TrustedIdentities []string `json:"trustedIdentities,omitempty"` } +// String returns print out of TrustPolicy as a string +func (t *TrustPolicy) String() string { + return fmt.Sprintf("Name: %s, RegistryScopes: %v, SignatureVerification: %+v, TimestampVerification: %+v, TrustStores: %v, TrustedIdentities: %v", t.Name, t.RegistryScopes, t.SignatureVerification, t.TimestampVerification, t.TrustStores, t.TrustedIdentities) +} + // SignatureVerification represents verification configuration in a trust policy type SignatureVerification struct { VerificationLevel string `json:"level"` Override map[ValidationType]ValidationAction `json:"override,omitempty"` } +// TimestampVerification represents timestamp countersignature verification +// configuration in a trust policy +type TimestampVerification struct { + Enable bool `json:"enable,omitempty"` + ExpiryRelaxed bool `json:"expiryRelaxed,omitempty"` +} + // Validate validates a policy document according to its version's rule set. // if any rule is violated, returns an error func (policyDoc *Document) Validate() error { @@ -185,7 +200,6 @@ func (policyDoc *Document) Validate() error { return fmt.Errorf("trust policy document uses unsupported version %q", policyDoc.Version) } - // Validate the policy according to 1.0 rules if len(policyDoc.TrustPolicies) == 0 { return errors.New("trust policy document can not have zero trust policy statements") } @@ -206,6 +220,11 @@ func (policyDoc *Document) Validate() error { return fmt.Errorf("trust policy statement %q has invalid signatureVerification: %w", statement.Name, err) } + // Verify timestamp verification is valid + if statement.TimestampVerification != nil && policyDoc.Version != "1.1" { + return fmt.Errorf("trust policy document version must be 1.1 to support timestamp verification, but got %q", policyDoc.Version) + } + // Any signature verification other than "skip" needs a trust store and // trusted identities if verificationLevel.Name == "skip" { @@ -394,6 +413,7 @@ func (t *TrustPolicy) clone() *TrustPolicy { return &TrustPolicy{ Name: t.Name, SignatureVerification: t.SignatureVerification, + TimestampVerification: t.TimestampVerification, RegistryScopes: append([]string(nil), t.RegistryScopes...), TrustedIdentities: append([]string(nil), t.TrustedIdentities...), TrustStores: append([]string(nil), t.TrustStores...), diff --git a/verifier/truststore/truststore.go b/verifier/truststore/truststore.go index c98b2c6c..067f5e63 100644 --- a/verifier/truststore/truststore.go +++ b/verifier/truststore/truststore.go @@ -36,12 +36,14 @@ type Type string const ( TypeCA Type = "ca" TypeSigningAuthority Type = "signingAuthority" + TypeTSA Type = "tsa" ) var ( Types = []Type{ TypeCA, TypeSigningAuthority, + TypeTSA, } ) diff --git a/verifier/verifier.go b/verifier/verifier.go index e6436050..1207c099 100644 --- a/verifier/verifier.go +++ b/verifier/verifier.go @@ -31,6 +31,7 @@ import ( "github.com/notaryproject/notation-core-go/revocation" revocationresult "github.com/notaryproject/notation-core-go/revocation/result" "github.com/notaryproject/notation-core-go/signature" + nx509 "github.com/notaryproject/notation-core-go/x509" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/internal/envelope" @@ -43,6 +44,7 @@ import ( "github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/notaryproject/notation-go/verifier/truststore" pluginframework "github.com/notaryproject/notation-plugin-framework-go/plugin" + "github.com/notaryproject/tspclient-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -286,7 +288,7 @@ func (v *verifier) processSignature(ctx context.Context, sigBlob []byte, envelop // verify authentic timestamp logger.Debug("Validating authentic timestamp") - authenticTimestampResult := verifyAuthenticTimestamp(outcome) + authenticTimestampResult := verifyAuthenticTimestamp(ctx, trustPolicy, v.trustStore, outcome) outcome.VerificationResults = append(outcome.VerificationResults, authenticTimestampResult) logVerificationResult(logger, authenticTimestampResult) if isCriticalFailure(authenticTimestampResult) { @@ -514,51 +516,176 @@ func verifyExpiry(outcome *notation.VerificationOutcome) *notation.ValidationRes } } -func verifyAuthenticTimestamp(outcome *notation.VerificationOutcome) *notation.ValidationResult { - invalidTimestamp := false - var err error +func verifyAuthenticTimestamp(ctx context.Context, trustPolicy *trustpolicy.TrustPolicy, x509TrustStore truststore.X509TrustStore, outcome *notation.VerificationOutcome) *notation.ValidationResult { + logger := log.GetLogger(ctx) + // under signing scheme notary.x509 if signerInfo := outcome.EnvelopeContent.SignerInfo; signerInfo.SignedAttributes.SigningScheme == signature.SigningSchemeX509 { - // TODO verify RFC3161 TSA signature if present (not in RC1) - // https://github.com/notaryproject/notation-go/issues/78 + var requireTimestampVerification bool + var invalidCertificate *x509.Certificate + for _, cert := range signerInfo.CertificateChain { + if time.Now().Before(cert.NotBefore) || time.Now().After(cert.NotAfter) { + // found at least one cert that current time is not in its + // validity period; require timestamp to continue this step + requireTimestampVerification = true + invalidCertificate = cert + break + } + } + if !requireTimestampVerification { // this step is a success + return ¬ation.ValidationResult{ + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } if len(signerInfo.UnsignedAttributes.TimestampSignature) == 0 { - // if there is no TSA signature, then every certificate should be - // valid at the time of verification - now := time.Now() - for _, cert := range signerInfo.CertificateChain { - if now.Before(cert.NotBefore) { - invalidTimestamp = true - err = fmt.Errorf("certificate %q is not valid yet, it will be valid from %q", cert.Subject, cert.NotBefore.Format(time.RFC1123Z)) - break + // if there is no timestamp token, fail this step + return ¬ation.ValidationResult{ + Error: fmt.Errorf("current time is not in certificate %q validity period [%q, %q] and no timestamp token was found in the signature envelope", invalidCertificate.Subject, invalidCertificate.NotBefore.Format(time.RFC1123Z), invalidCertificate.NotAfter.Format(time.RFC1123Z)), + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } + if trustPolicy.TimestampVerification == nil || !trustPolicy.TimestampVerification.Enable { + // if timestamp verification is disabled by trust policy + return ¬ation.ValidationResult{ + Error: fmt.Errorf("current time is not in certificate %q validity period [%q, %q] and timestamp verification is disabled by trust policy", invalidCertificate.Subject, invalidCertificate.NotBefore.Format(time.RFC1123Z), invalidCertificate.NotAfter.Format(time.RFC1123Z)), + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } + trustTSACerts, err := loadX509TSATrustStores(ctx, outcome.EnvelopeContent.SignerInfo.SignedAttributes.SigningScheme, trustPolicy, x509TrustStore) + if err != nil { + return ¬ation.ValidationResult{ + Error: err, + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } + if len(trustTSACerts) < 1 { + return ¬ation.ValidationResult{ + Error: errors.New("no trusted TSA root certificate was found in the trust store"), + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } + signedToken, err := tspclient.ParseSignedToken(ctx, signerInfo.UnsignedAttributes.TimestampSignature) + if err != nil { + return ¬ation.ValidationResult{ + Error: err, + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } + roots := x509.NewCertPool() + for _, cert := range trustTSACerts { + roots.AddCert(cert) + } + timestampVerifyOpts := x509.VerifyOptions{ + Roots: roots, + } + // get the timestamp token info + info, err := signedToken.Info() + if err != nil { + return ¬ation.ValidationResult{ + Error: err, + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } + // validate the info + ts, accuracy, err := info.Timestamp(signerInfo.Signature) + if err != nil { + return ¬ation.ValidationResult{ + Error: err, + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } + if trustPolicy.TimestampVerification.ExpiryRelaxed { + timestampVerifyOpts.CurrentTime = ts + } + // verify the timestamp countersignature + tsaCertChain, err := signedToken.Verify(ctx, timestampVerifyOpts) + if err != nil { + return ¬ation.ValidationResult{ + Error: err, + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } + // validate timestamp certificate chain + if err := nx509.ValidateTimeStampingCertChain(tsaCertChain, nil); err != nil { + return ¬ation.ValidationResult{ + Error: err, + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } + // get the timestamp token time range + timeStampLowerLimit := ts.Add(-accuracy) + timeStampUpperLimit := ts.Add(accuracy) + logger.Infof("timestamp token time range: [%v, %v]", timeStampLowerLimit, timeStampUpperLimit) + // TSA certificate chain revocation check + certResults, err := revocation.ValidateTimestampCertChain(tsaCertChain, timeStampUpperLimit) + if err != nil { + logger.Debug("error while checking revocation status, err: %s", err.Error()) + return ¬ation.ValidationResult{ + Error: err, + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } + finalResult, problematicCertSubject := revocationFinalResult(certResults, tsaCertChain, logger) + switch finalResult { + case revocationresult.ResultOK: + logger.Debug("no verification impacting errors encountered while checking TSA certificate chain revocation, status is OK") + case revocationresult.ResultRevoked: + return ¬ation.ValidationResult{ + Error: fmt.Errorf("TSA certificate with subject %q is revoked", problematicCertSubject), + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + default: + // revocationresult.ResultUnknown + return ¬ation.ValidationResult{ + Error: fmt.Errorf("TSA certificate with subject %q revocation status is unknown", problematicCertSubject), + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } + } + // timestamp validation core process + for _, cert := range signerInfo.CertificateChain { + if timeStampLowerLimit.Before(cert.NotBefore) { + return ¬ation.ValidationResult{ + Error: fmt.Errorf("timestamp lower limit %q is before certificate %q validity period, it will be valid from %q", timeStampLowerLimit.Format(time.RFC1123Z), cert.Subject, cert.NotBefore.Format(time.RFC1123Z)), + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], } - if now.After(cert.NotAfter) { - invalidTimestamp = true - err = fmt.Errorf("certificate %q is not valid anymore, it was expired at %q", cert.Subject, cert.NotAfter.Format(time.RFC1123Z)) - break + } + if timeStampUpperLimit.After(cert.NotAfter) { + return ¬ation.ValidationResult{ + Error: fmt.Errorf("timestamp upper limit %q is after certificate %q validity period, it was expired at %q", timeStampUpperLimit.Format(time.RFC1123Z), cert.Subject, cert.NotAfter.Format(time.RFC1123Z)), + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], } } } } else if signerInfo.SignedAttributes.SigningScheme == signature.SigningSchemeX509SigningAuthority { + // under signing scheme notary.x509.signingAuthority authenticSigningTime := signerInfo.SignedAttributes.SigningTime - // TODO use authenticSigningTime from signerInfo - // https://github.com/notaryproject/notation-core-go/issues/38 for _, cert := range signerInfo.CertificateChain { if authenticSigningTime.Before(cert.NotBefore) || authenticSigningTime.After(cert.NotAfter) { - invalidTimestamp = true - err = fmt.Errorf("certificate %q was not valid when the digital signature was produced at %q", cert.Subject, authenticSigningTime.Format(time.RFC1123Z)) - break + return ¬ation.ValidationResult{ + Error: fmt.Errorf("certificate %q was not valid when the digital signature was produced at %q", cert.Subject, authenticSigningTime.Format(time.RFC1123Z)), + Type: trustpolicy.TypeAuthenticTimestamp, + Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], + } } } } - if invalidTimestamp { - return ¬ation.ValidationResult{ - Error: err, - Type: trustpolicy.TypeAuthenticTimestamp, - Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], - } - } - + // this step is a success return ¬ation.ValidationResult{ Type: trustpolicy.TypeAuthenticTimestamp, Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeAuthenticTimestamp], @@ -593,6 +720,23 @@ func verifyRevocation(outcome *notation.VerificationOutcome, r revocation.Revoca Type: trustpolicy.TypeRevocation, Action: outcome.VerificationLevel.Enforcement[trustpolicy.TypeRevocation], } + finalResult, problematicCertSubject := revocationFinalResult(certResults, outcome.EnvelopeContent.SignerInfo.CertificateChain, logger) + switch finalResult { + case revocationresult.ResultOK: + logger.Debug("no verification impacting errors encountered while checking revocation, status is OK") + case revocationresult.ResultRevoked: + result.Error = fmt.Errorf("signing certificate with subject %q is revoked", problematicCertSubject) + default: + // revocationresult.ResultUnknown + result.Error = fmt.Errorf("signing certificate with subject %q revocation status is unknown", problematicCertSubject) + } + + return result +} + +// revocationFinalResult returns the final revocation result and problematic +// certificate subject if the final result is not ResultOK +func revocationFinalResult(certResults []*revocationresult.CertRevocationResult, certChain []*x509.Certificate, logger log.Logger) (revocationresult.Result, string) { finalResult := revocationresult.ResultUnknown numOKResults := 0 var problematicCertSubject string @@ -600,14 +744,14 @@ func verifyRevocation(outcome *notation.VerificationOutcome, r revocation.Revoca var revokedCertSubject string for i := len(certResults) - 1; i >= 0; i-- { if len(certResults[i].ServerResults) > 0 && certResults[i].ServerResults[0].Error != nil { - logger.Debugf("error for certificate #%d in chain with subject %v for server %q: %v", (i + 1), outcome.EnvelopeContent.SignerInfo.CertificateChain[i].Subject.String(), certResults[i].ServerResults[0].Server, certResults[i].ServerResults[0].Error) + logger.Debugf("error for certificate #%d in chain with subject %v for server %q: %v", (i + 1), certChain[i].Subject.String(), certResults[i].ServerResults[0].Server, certResults[i].ServerResults[0].Error) } if certResults[i].Result == revocationresult.ResultOK || certResults[i].Result == revocationresult.ResultNonRevokable { numOKResults++ } else { finalResult = certResults[i].Result - problematicCertSubject = outcome.EnvelopeContent.SignerInfo.CertificateChain[i].Subject.String() + problematicCertSubject = certChain[i].Subject.String() if certResults[i].Result == revocationresult.ResultRevoked { revokedFound = true revokedCertSubject = problematicCertSubject @@ -621,18 +765,7 @@ func verifyRevocation(outcome *notation.VerificationOutcome, r revocation.Revoca if numOKResults == len(certResults) { finalResult = revocationresult.ResultOK } - - switch finalResult { - case revocationresult.ResultOK: - logger.Debug("no verification impacting errors encountered while checking revocation, status is OK") - case revocationresult.ResultRevoked: - result.Error = fmt.Errorf("signing certificate with subject %q is revoked", problematicCertSubject) - default: - // revocationresult.ResultUnknown - result.Error = fmt.Errorf("signing certificate with subject %q revocation status is unknown", problematicCertSubject) - } - - return result + return finalResult, problematicCertSubject } func executePlugin(ctx context.Context, installedPlugin pluginframework.VerifyPlugin, trustPolicy *trustpolicy.TrustPolicy, capabilitiesToVerify []pluginframework.Capability, envelopeContent *signature.EnvelopeContent, pluginConfig map[string]string) (*pluginframework.VerifySignatureResponse, error) {