diff --git a/revocation/internal/chain/validate.go b/revocation/internal/chain/validate.go new file mode 100644 index 00000000..33e6418d --- /dev/null +++ b/revocation/internal/chain/validate.go @@ -0,0 +1,47 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package chain provides the method to validate the certificate chain for a +// specific purpose, including code signing and timestamping. +package chain + +import ( + "crypto/x509" + "fmt" + + "github.com/notaryproject/notation-core-go/revocation/purpose" + "github.com/notaryproject/notation-core-go/revocation/result" + coreX509 "github.com/notaryproject/notation-core-go/x509" +) + +// Validate checks the certificate chain for a specific purpose, including +// code signing and timestamping. +func Validate(certChain []*x509.Certificate, certChainPurpose purpose.Purpose) error { + switch certChainPurpose { + case purpose.CodeSigning: + // Since ValidateCodeSigningCertChain is using authentic signing time, + // signing time may be zero. + // Thus, it is better to pass nil here than fail for a cert's NotBefore + // being after zero time + if err := coreX509.ValidateCodeSigningCertChain(certChain, nil); err != nil { + return result.InvalidChainError{Err: err} + } + case purpose.Timestamping: + if err := coreX509.ValidateTimestampingCertChain(certChain); err != nil { + return result.InvalidChainError{Err: err} + } + default: + return result.InvalidChainError{Err: fmt.Errorf("unsupported certificate chain purpose %v", certChainPurpose)} + } + return nil +} diff --git a/revocation/internal/chain/validate_test.go b/revocation/internal/chain/validate_test.go new file mode 100644 index 00000000..e119d6b7 --- /dev/null +++ b/revocation/internal/chain/validate_test.go @@ -0,0 +1,60 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package chain + +import ( + "crypto/x509" + "testing" + + "github.com/notaryproject/notation-core-go/revocation/purpose" + "github.com/notaryproject/notation-core-go/testhelper" +) + +func TestValidate(t *testing.T) { + t.Run("unsupported_certificate_chain_purpose", func(t *testing.T) { + certChain := []*x509.Certificate{} + certChainPurpose := purpose.Purpose(-1) + err := Validate(certChain, certChainPurpose) + if err == nil { + t.Errorf("Validate() failed, expected error, got nil") + } + }) + + t.Run("invalid code signing certificate chain", func(t *testing.T) { + certChain := []*x509.Certificate{} + certChainPurpose := purpose.CodeSigning + err := Validate(certChain, certChainPurpose) + if err == nil { + t.Errorf("Validate() failed, expected error, got nil") + } + }) + + t.Run("invalid timestamping certificate chain", func(t *testing.T) { + certChain := []*x509.Certificate{} + certChainPurpose := purpose.Timestamping + err := Validate(certChain, certChainPurpose) + if err == nil { + t.Errorf("Validate() failed, expected error, got nil") + } + }) + + t.Run("valid code signing certificate chain", func(t *testing.T) { + certChain := testhelper.GetRevokableRSAChain(2) + certChainPurpose := purpose.CodeSigning + err := Validate([]*x509.Certificate{certChain[0].Cert, certChain[1].Cert}, certChainPurpose) + if err != nil { + t.Errorf("Validate() failed, expected nil, got %v", err) + } + }) +} diff --git a/revocation/internal/crl/crl.go b/revocation/internal/crl/crl.go index 82c6ce06..2f635216 100644 --- a/revocation/internal/crl/crl.go +++ b/revocation/internal/crl/crl.go @@ -42,8 +42,8 @@ type Options struct { // HTTPClient is the HTTP client used to download CRL HTTPClient *http.Client - // SigningTime is the time when the certificate's private key is - // used to sign the data. + // SigningTime is used to compare with the invalidity date during revocation + // check SigningTime time.Time } diff --git a/revocation/internal/ocsp/ocsp.go b/revocation/internal/ocsp/ocsp.go index 2bada88d..1c3071b5 100644 --- a/revocation/internal/ocsp/ocsp.go +++ b/revocation/internal/ocsp/ocsp.go @@ -31,9 +31,9 @@ import ( "sync" "time" + "github.com/notaryproject/notation-core-go/revocation/internal/chain" "github.com/notaryproject/notation-core-go/revocation/purpose" "github.com/notaryproject/notation-core-go/revocation/result" - coreX509 "github.com/notaryproject/notation-core-go/x509" "golang.org/x/crypto/ocsp" ) @@ -45,9 +45,8 @@ type Options struct { // values are CodeSigning and Timestamping. // When not provided, the default value is CodeSigning. CertChainPurpose purpose.Purpose - - SigningTime time.Time - HTTPClient *http.Client + SigningTime time.Time + HTTPClient *http.Client } const ( @@ -67,7 +66,7 @@ func CheckStatus(opts Options) ([]*result.CertRevocationResult, error) { return nil, result.InvalidChainError{Err: errors.New("chain does not contain any certificates")} } - if err := ValidateCertificateChain(opts.CertChain, opts.CertChainPurpose); err != nil { + if err := chain.Validate(opts.CertChain, opts.CertChainPurpose); err != nil { return nil, err } @@ -96,26 +95,6 @@ func CheckStatus(opts Options) ([]*result.CertRevocationResult, error) { return certResults, nil } -func ValidateCertificateChain(certChain []*x509.Certificate, certChainPurpose purpose.Purpose) error { - switch certChainPurpose { - case purpose.CodeSigning: - // Since ValidateCodeSigningCertChain is using authentic signing time, - // signing time may be zero. - // Thus, it is better to pass nil here than fail for a cert's NotBefore - // being after zero time - if err := coreX509.ValidateCodeSigningCertChain(certChain, nil); err != nil { - return result.InvalidChainError{Err: err} - } - case purpose.Timestamping: - if err := coreX509.ValidateTimestampingCertChain(certChain); err != nil { - return result.InvalidChainError{Err: err} - } - default: - return result.InvalidChainError{Err: fmt.Errorf("unsupported certificate chain purpose %v", certChainPurpose)} - } - return nil -} - // CertCheckStatus checks the revocation status of a certificate using OCSP func CertCheckStatus(cert, issuer *x509.Certificate, opts Options) *result.CertRevocationResult { if !Supported(cert) { diff --git a/revocation/revocation.go b/revocation/revocation.go index 1efec35d..1bd580a8 100644 --- a/revocation/revocation.go +++ b/revocation/revocation.go @@ -24,6 +24,7 @@ import ( "sync" "time" + "github.com/notaryproject/notation-core-go/revocation/internal/chain" "github.com/notaryproject/notation-core-go/revocation/internal/crl" "github.com/notaryproject/notation-core-go/revocation/internal/ocsp" "github.com/notaryproject/notation-core-go/revocation/purpose" @@ -131,15 +132,11 @@ func NewWithOptions(opts Options) (Validator, error) { // that contain the results and any errors that are encountered during the // process. // -// The certificate chain is expected to be in the order of leaf to root. -// // This function tries OCSP and falls back to CRL when: // - OCSP is not supported by the certificate // - OCSP returns an unknown status // -// When OCSP returns an unknown status, the function will try to check the -// certificate status using CRL and return certificate result with an -// result.OCSPFallbackError. +// NOTE: The certificate chain is expected to be in the order of leaf to root. func (r *revocation) Validate(certChain []*x509.Certificate, signingTime time.Time) ([]*result.CertRevocationResult, error) { return r.ValidateContext(context.Background(), ValidateContextOptions{ CertChain: certChain, @@ -147,16 +144,23 @@ func (r *revocation) Validate(certChain []*x509.Certificate, signingTime time.Ti }) } -// ValidateContext checks the revocation status for a certificate chain using -// OCSP and returns an array of CertRevocationResults that contain the results -// and any errors that are encountered during the process +// ValidateContext checks the revocation status for a certificate chain using OCSP and +// CRL if OCSP is not available. It returns an array of CertRevocationResults +// that contain the results and any errors that are encountered during the +// process. +// +// This function tries OCSP and falls back to CRL when: +// - OCSP is not supported by the certificate +// - OCSP returns an unknown status +// +// NOTE: The certificate chain is expected to be in the order of leaf to root. func (r *revocation) ValidateContext(ctx context.Context, validateContextOpts ValidateContextOptions) ([]*result.CertRevocationResult, error) { if len(validateContextOpts.CertChain) == 0 { return nil, result.InvalidChainError{Err: errors.New("chain does not contain any certificates")} } certChain := validateContextOpts.CertChain - if err := ocsp.ValidateCertificateChain(certChain, r.certChainPurpose); err != nil { + if err := chain.Validate(certChain, r.certChainPurpose); err != nil { return nil, err }