Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: update revocation #215

Merged
merged 25 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 17 additions & 23 deletions revocation/ocsp/ocsp.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,23 @@ import (
"sync"
"time"

"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"
)

// Purpose is an enum for purpose of the certificate chain whose OCSP status
// is checked
type Purpose int

const (
// PurposeCodeSigning means the certificate chain is a code signing chain
PurposeCodeSigning Purpose = iota

// PurposeTimestamping means the certificate chain is a timestamping chain
PurposeTimestamping
)

// Options specifies values that are needed to check OCSP revocation
type Options struct {
CertChain []*x509.Certificate
CertChainPurpose Purpose // default value is `PurposeCodeSigning`
SigningTime time.Time
HTTPClient *http.Client
CertChain []*x509.Certificate

// CertChainPurpose is the purpose of the certificate chain. Supported
// values are CodeSigning and Timestamping.
// When not provided, the default value is CodeSigning.
CertChainPurpose purpose.Purpose

SigningTime time.Time
HTTPClient *http.Client
}

const (
Expand All @@ -73,21 +67,21 @@ func CheckStatus(opts Options) ([]*result.CertRevocationResult, error) {
return nil, result.InvalidChainError{Err: errors.New("chain does not contain any certificates")}
}

// Validate cert chain structure
// Since this 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
switch opts.CertChainPurpose {
case PurposeCodeSigning:
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(opts.CertChain, nil); err != nil {
return nil, result.InvalidChainError{Err: err}
}
case PurposeTimestamping:
case purpose.Timestamping:
if err := coreX509.ValidateTimestampingCertChain(opts.CertChain); err != nil {
return nil, result.InvalidChainError{Err: err}
}
default:
return nil, result.InvalidChainError{Err: fmt.Errorf("unknown certificate chain purpose %v", opts.CertChainPurpose)}
return nil, result.InvalidChainError{Err: fmt.Errorf("unsupported certificate chain purpose %v", opts.CertChainPurpose)}
}

certResults := make([]*result.CertRevocationResult, len(opts.CertChain))
Expand Down
26 changes: 20 additions & 6 deletions revocation/ocsp/ocsp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"testing"
"time"

"github.com/notaryproject/notation-core-go/revocation/purpose"
"github.com/notaryproject/notation-core-go/revocation/result"
"github.com/notaryproject/notation-core-go/testhelper"
"golang.org/x/crypto/ocsp"
Expand Down Expand Up @@ -228,9 +229,10 @@ func TestCheckStatusForChain(t *testing.T) {
t.Run("check non-revoked chain", func(t *testing.T) {
client := testhelper.MockClient(testChain, []ocsp.ResponseStatus{ocsp.Good}, nil, true)
opts := Options{
CertChain: revokableChain,
SigningTime: time.Now(),
HTTPClient: client,
CertChain: revokableChain,
CertChainPurpose: purpose.CodeSigning,
SigningTime: time.Now(),
HTTPClient: client,
}

certResults, err := CheckStatus(opts)
Expand Down Expand Up @@ -535,7 +537,7 @@ func TestCheckStatusErrors(t *testing.T) {
t.Run("check codesigning cert with PurposeTimestamping", func(t *testing.T) {
opts := Options{
CertChain: okChain,
CertChainPurpose: PurposeTimestamping,
CertChainPurpose: purpose.Timestamping,
SigningTime: time.Now(),
HTTPClient: http.DefaultClient,
}
Expand All @@ -548,15 +550,27 @@ func TestCheckStatusErrors(t *testing.T) {
}
})

t.Run("check with default CertChainPurpose", func(t *testing.T) {
opts := Options{
CertChain: okChain,
SigningTime: time.Now(),
HTTPClient: http.DefaultClient,
}
_, err := CheckStatus(opts)
if err != nil {
t.Fatal(err)
}
})

t.Run("check with unknwon CertChainPurpose", func(t *testing.T) {
opts := Options{
CertChain: okChain,
CertChainPurpose: 2,
CertChainPurpose: -1,
SigningTime: time.Now(),
HTTPClient: http.DefaultClient,
}
certResults, err := CheckStatus(opts)
if err == nil || err.Error() != "invalid chain: expected chain to be correct and complete: unknown certificate chain purpose 2" {
if err == nil || err.Error() != "invalid chain: expected chain to be correct and complete: unsupported certificate chain purpose -1" {
t.Errorf("Expected CheckStatus to fail with %v, but got: %v", timestampSigningCertErr, err)
}
if certResults != nil {
Expand Down
28 changes: 28 additions & 0 deletions revocation/purpose/purpose.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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 purpose provides purposes of the certificate chain whose revocation
// status is checked
package purpose

// Purpose is an enum for purpose of the certificate chain whose revocation
// status is checked
type Purpose int

const (
// CodeSigning means the certificate chain is a code signing chain
CodeSigning Purpose = iota

// Timestamping means the certificate chain is a timestamping chain
Timestamping
)
94 changes: 81 additions & 13 deletions revocation/revocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,49 +16,100 @@
package revocation

import (
"context"
"crypto/x509"
"errors"
"fmt"
"net/http"
"time"

"github.com/notaryproject/notation-core-go/revocation/ocsp"
"github.com/notaryproject/notation-core-go/revocation/purpose"
"github.com/notaryproject/notation-core-go/revocation/result"
)

// Revocation is an interface that specifies methods used for revocation checking
// Revocation is an interface that specifies methods used for revocation checking.
//
// Deprecated: Revocation exists for backwards compatibility and should not be used.
// To perform revocation check, use [Validator].
type Revocation interface {
// Validate 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
Validate(certChain []*x509.Certificate, signingTime time.Time) ([]*result.CertRevocationResult, error)
}

// ValidateContextOptions provides configuration options for revocation checks
type ValidateContextOptions struct {
// CertChain denotes the certificate chain whose revocation status is
// been validated. REQUIRED.
CertChain []*x509.Certificate
Two-Hearts marked this conversation as resolved.
Show resolved Hide resolved

Two-Hearts marked this conversation as resolved.
Show resolved Hide resolved
// AuthenticSigningTime denotes the authentic signing time of the signature.
// It is used to compare with the InvalidityDate during revocation check.
// OPTIONAL.
//
// Reference: https://github.com/notaryproject/specifications/blob/v1.0.0/specs/trust-store-trust-policy.md#revocation-checking-with-ocsp
AuthenticSigningTime time.Time
}

// Validator is an interface that provides revocation checking with
// context
type Validator interface {
// ValidateContext checks the revocation status given caller provided options
// and returns an array of CertRevocationResults that contain the results
// and any errors that are encountered during the process
ValidateContext(ctx context.Context, validateContextOpts ValidateContextOptions) ([]*result.CertRevocationResult, error)
}

// revocation is an internal struct used for revocation checking
type revocation struct {
httpClient *http.Client
certChainPurpose ocsp.Purpose
certChainPurpose purpose.Purpose
}

// New constructs a revocation object for code signing certificate chain
// New constructs a revocation object for code signing certificate chain.
//
// Deprecated: New exists for backwards compatibility and should not be used.
// To create a revocation object, use [NewWithOptions].
func New(httpClient *http.Client) (Revocation, error) {
if httpClient == nil {
return nil, errors.New("invalid input: a non-nil httpClient must be specified")
}
return &revocation{
httpClient: httpClient,
certChainPurpose: ocsp.PurposeCodeSigning,
certChainPurpose: purpose.CodeSigning,
}, nil
}

// NewTimestamp contructs a revocation object for timestamping certificate
// chain
func NewTimestamp(httpClient *http.Client) (Revocation, error) {
if httpClient == nil {
return nil, errors.New("invalid input: a non-nil httpClient must be specified")
// Options specifies values that are needed to check revocation
type Options struct {
// OCSPHTTPClient is the HTTP client for OCSP request. If not provided,
// a default *http.Client with timeout of 2 seconds will be used.
// OPTIONAL.
OCSPHTTPClient *http.Client

// CertChainPurpose is the purpose of the certificate chain. Supported
Two-Hearts marked this conversation as resolved.
Show resolved Hide resolved
// values are CodeSigning and Timestamping. Default value is CodeSigning.
// OPTIONAL.
CertChainPurpose purpose.Purpose
}

// NewWithOptions constructs a Validator with the specified options
func NewWithOptions(opts Options) (Validator, error) {
if opts.OCSPHTTPClient == nil {
opts.OCSPHTTPClient = &http.Client{Timeout: 2 * time.Second}
}

switch opts.CertChainPurpose {
case purpose.CodeSigning, purpose.Timestamping:
default:
return nil, fmt.Errorf("unsupported certificate chain purpose %v", opts.CertChainPurpose)
}

return &revocation{
httpClient: httpClient,
certChainPurpose: ocsp.PurposeTimestamping,
httpClient: opts.OCSPHTTPClient,
certChainPurpose: opts.CertChainPurpose,
}, nil
}

Expand All @@ -69,10 +120,27 @@ func NewTimestamp(httpClient *http.Client) (Revocation, error) {
// TODO: add CRL support
// https://github.com/notaryproject/notation-core-go/issues/125
func (r *revocation) Validate(certChain []*x509.Certificate, signingTime time.Time) ([]*result.CertRevocationResult, error) {
return r.ValidateContext(context.Background(), ValidateContextOptions{
CertChain: certChain,
AuthenticSigningTime: signingTime,
})
}

// 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
//
// TODO: add CRL support
// https://github.com/notaryproject/notation-core-go/issues/125
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")}
}

return ocsp.CheckStatus(ocsp.Options{
CertChain: certChain,
CertChain: validateContextOpts.CertChain,
CertChainPurpose: r.certChainPurpose,
SigningTime: signingTime,
SigningTime: validateContextOpts.AuthenticSigningTime,
HTTPClient: r.httpClient,
})

Expand Down
Loading
Loading