diff --git a/go.mod b/go.mod index e5c3db8b3..b96e43dbd 100644 --- a/go.mod +++ b/go.mod @@ -34,8 +34,8 @@ require ( github.com/golang/protobuf v1.5.4 github.com/google/go-containerregistry v0.20.2 github.com/gorilla/mux v1.8.1 - github.com/notaryproject/notation-core-go v1.1.0 - github.com/notaryproject/notation-go v1.2.1 + github.com/notaryproject/notation-core-go v1.2.0-rc.1 + github.com/notaryproject/notation-go v1.3.0-rc.1 github.com/notaryproject/notation-plugin-framework-go v1.0.0 github.com/open-policy-agent/cert-controller v0.8.0 github.com/open-policy-agent/frameworks/constraint v0.0.0-20230411224310-3f237e2710fa @@ -241,7 +241,7 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.28.0 golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect - golang.org/x/mod v0.20.0 // indirect + golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sys v0.26.0 // indirect diff --git a/go.sum b/go.sum index dbdaf454c..42ab1552e 100644 --- a/go.sum +++ b/go.sum @@ -546,10 +546,10 @@ github.com/mozillazg/docker-credential-acr-helper v0.3.0/go.mod h1:cZlu3tof523uj github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/notaryproject/notation-core-go v1.1.0 h1:xCybcONOKcCyPNihJUSa+jRNsyQFNkrk0eJVVs1kWeg= -github.com/notaryproject/notation-core-go v1.1.0/go.mod h1:+6AOh41JPrnVLbW/19SJqdhVHwKgIINBO/np0e7nXJA= -github.com/notaryproject/notation-go v1.2.1 h1:fbCMBcvg1xttrisd5CyM60QDectGYYF701Us0M3cKN8= -github.com/notaryproject/notation-go v1.2.1/go.mod h1:re9V+TfuNRaUq5e3NuNcCJN53++sL2KbnJrjGyOUpgE= +github.com/notaryproject/notation-core-go v1.2.0-rc.1 h1:VMFlG+9a1JoNAQ3M96g8iqCq0cDRtE7XBaiTD8Ouvqw= +github.com/notaryproject/notation-core-go v1.2.0-rc.1/go.mod h1:b/70rA4OgOHlg0A7pb8zTWKJadFO6781zS3a37KHEJQ= +github.com/notaryproject/notation-go v1.3.0-rc.1 h1:pm9tdUy2tWYqlwyRDZyKXgLwAscDATPUYv0ul2RK/Iw= +github.com/notaryproject/notation-go v1.3.0-rc.1/go.mod h1:W4o45yolX4Q+3PKlcpGleLLXEKWHa3BshEqw/JX5c6I= 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/notaryproject/tspclient-go v0.2.0 h1:g/KpQGmyk/h7j60irIRG1mfWnibNOzJ8WhLqAzuiQAQ= @@ -825,8 +825,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/pkg/verifier/notation/notation.go b/pkg/verifier/notation/notation.go index e825db11d..52fdcbda6 100644 --- a/pkg/verifier/notation/notation.go +++ b/pkg/verifier/notation/notation.go @@ -36,6 +36,8 @@ import ( "github.com/ratify-project/ratify/pkg/verifier/factory" "github.com/ratify-project/ratify/pkg/verifier/types" + "github.com/notaryproject/notation-core-go/revocation" + "github.com/notaryproject/notation-core-go/revocation/purpose" _ "github.com/notaryproject/notation-core-go/signature/cose" // register COSE signature _ "github.com/notaryproject/notation-core-go/signature/jws" // register JWS signature "github.com/notaryproject/notation-go" @@ -103,8 +105,7 @@ func (f *notationPluginVerifierFactory) Create(_ string, verifierConfig config.V if err != nil { return nil, re.ErrorCodePluginInitFailure.WithDetail("Failed to create the Notation Verifier").WithError(err) } - - verifyService, err := getVerifierService(conf, pluginDirectory) + verifyService, err := getVerifierService(conf, pluginDirectory, NewRevocationFactoryImpl()) if err != nil { return nil, re.ErrorCodePluginInitFailure.WithDetail("Failed to create the Notation Verifier").WithError(err) } @@ -176,12 +177,40 @@ func (v *notationPluginVerifier) Verify(ctx context.Context, return verifier.NewVerifierResult("", v.name, v.verifierType, "Notation signature verification success", true, nil, extensions), nil } -func getVerifierService(conf *NotationPluginVerifierConfig, pluginDirectory string) (notation.Verifier, error) { +func getVerifierService(conf *NotationPluginVerifierConfig, pluginDirectory string, revocationFactory RevocationFactory) (notation.Verifier, error) { store, err := newTrustStore(conf.VerificationCerts, conf.VerificationCertStores) if err != nil { return nil, err } - verifier, err := notationVerifier.New(&conf.TrustPolicyDoc, store, NewRatifyPluginManager(pluginDirectory)) + + // revocation check using corecrl from notation-core-go and crl from notation-go + // This is the implementation for revocation check from notation cli to support crl and cache configurations + // removed timeout + // Related PR: notaryproject/notation#1043 + // Related File: https://github.com/notaryproject/notation/commits/main/cmd/notation/verify.go5 + crlFetcher, err := revocationFactory.NewFetcher() + if err != nil { + return nil, err + } + revocationCodeSigningValidator, err := revocationFactory.NewValidator(revocation.Options{ + CRLFetcher: crlFetcher, + CertChainPurpose: purpose.CodeSigning, + }) + if err != nil { + return nil, err + } + revocationTimestampingValidator, err := revocationFactory.NewValidator(revocation.Options{ + CRLFetcher: crlFetcher, + CertChainPurpose: purpose.Timestamping, + }) + if err != nil { + return nil, err + } + + verifier, err := notationVerifier.NewWithOptions(&conf.TrustPolicyDoc, store, NewRatifyPluginManager(pluginDirectory), notationVerifier.VerifierOptions{ + RevocationCodeSigningValidator: revocationCodeSigningValidator, + RevocationTimestampingValidator: revocationTimestampingValidator, + }) if err != nil { return nil, re.ErrorCodePluginInitFailure.WithDetail("Failed to create the Notation Verifier").WithError(err) } diff --git a/pkg/verifier/notation/notation_test.go b/pkg/verifier/notation/notation_test.go index dc34a1f9d..bf2fa4abb 100644 --- a/pkg/verifier/notation/notation_test.go +++ b/pkg/verifier/notation/notation_test.go @@ -18,10 +18,14 @@ import ( "crypto/x509" "crypto/x509/pkix" "fmt" + "net/http" paths "path/filepath" "reflect" "testing" + "github.com/notaryproject/notation-core-go/revocation" + corecrl "github.com/notaryproject/notation-core-go/revocation/crl" + "github.com/notaryproject/notation-core-go/revocation/purpose" sig "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-go" "github.com/opencontainers/go-digest" @@ -558,3 +562,102 @@ func TestNormalizeLegacyCertStore(t *testing.T) { }) } } +func TestGetVerifierService(t *testing.T) { + tests := []struct { + name string + conf *NotationPluginVerifierConfig + pluginDir string + RevocationFactory RevocationFactory + expectErr bool + errContent error + }{ + { + name: "failed to create CRL fetcher", + conf: &NotationPluginVerifierConfig{ + VerificationCerts: []string{defaultCertDir}, + }, + pluginDir: "", + RevocationFactory: mockRevocationFactory{httpClient: &http.Client{}, failFetcher: true}, + expectErr: true, + errContent: nil, + }, + { + name: "failed to create file cache", + conf: &NotationPluginVerifierConfig{ + VerificationCerts: []string{defaultCertDir}, + }, + pluginDir: "", + RevocationFactory: mockRevocationFactory{httpClient: &http.Client{}, failFileCache: true}, + expectErr: true, + errContent: nil, + }, + { + name: "failed to create code signing validator", + conf: &NotationPluginVerifierConfig{ + VerificationCerts: []string{defaultCertDir}, + }, + pluginDir: "", + RevocationFactory: mockRevocationFactory{httpClient: &http.Client{}, failCodeSigningValidator: true}, + expectErr: true, + errContent: fmt.Errorf("failed to create code signing validator"), + }, + { + name: "failed to create timestamping validator", + conf: &NotationPluginVerifierConfig{ + VerificationCerts: []string{defaultCertDir}, + }, + pluginDir: "", + RevocationFactory: mockRevocationFactory{httpClient: &http.Client{}, failTimestampingValidator: true}, + expectErr: true, + errContent: fmt.Errorf("failed to create timestamping validator"), + }, + { + name: "failed to create verifier", + conf: &NotationPluginVerifierConfig{ + VerificationCerts: []string{defaultCertDir}, + }, + pluginDir: "", + RevocationFactory: mockRevocationFactory{httpClient: &http.Client{}, failVerifier: true}, + expectErr: true, + errContent: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := getVerifierService(tt.conf, tt.pluginDir, tt.RevocationFactory) + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if tt.errContent != nil && err.Error() != tt.errContent.Error() { + t.Errorf("error = %v, expectErr = %v, content = %v", err, tt.expectErr, tt.errContent) + } + }) + } +} + +type mockRevocationFactory struct { + failFetcher bool + failFileCache bool + failCodeSigningValidator bool + failTimestampingValidator bool + failVerifier bool + httpClient *http.Client +} + +func (m mockRevocationFactory) NewFetcher() (corecrl.Fetcher, error) { + if m.failFetcher { + return nil, fmt.Errorf("failed to create fetcher") + } + return corecrl.NewHTTPFetcher(m.httpClient) +} + +func (m mockRevocationFactory) NewValidator(opts revocation.Options) (revocation.Validator, error) { + if m.failCodeSigningValidator && opts.CertChainPurpose == purpose.CodeSigning { + return nil, fmt.Errorf("failed to create code signing validator") + } + if m.failTimestampingValidator && opts.CertChainPurpose == purpose.Timestamping { + return nil, fmt.Errorf("failed to create timestamping validator") + } + return revocation.NewWithOptions(opts) +} diff --git a/pkg/verifier/notation/notationrevocationfactory.go b/pkg/verifier/notation/notationrevocationfactory.go new file mode 100644 index 000000000..47cc35606 --- /dev/null +++ b/pkg/verifier/notation/notationrevocationfactory.go @@ -0,0 +1,63 @@ +// Copyright The Ratify 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 notation + +import ( + "net/http" + + "github.com/notaryproject/notation-core-go/revocation" + corecrl "github.com/notaryproject/notation-core-go/revocation/crl" + "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/verifier/crl" +) + +type RevocationFactoryImpl struct { + cacheRoot string + httpClient *http.Client +} + +// NewRevocationFactoryImpl returns a new NewRevocationFactoryImpl instance +func NewRevocationFactoryImpl() RevocationFactory { + return &RevocationFactoryImpl{ + cacheRoot: dir.PathCRLCache, + httpClient: &http.Client{}, + } +} + +// NewFetcher returns a new fetcher instance +func (f *RevocationFactoryImpl) NewFetcher() (corecrl.Fetcher, error) { + crlFetcher, err := corecrl.NewHTTPFetcher(f.httpClient) + if err != nil { + return nil, err + } + crlFetcher.Cache, err = newFileCache(f.cacheRoot) + if err != nil { + return nil, err + } + return crlFetcher, nil +} + +// NewValidator returns a new validator instance +func (f *RevocationFactoryImpl) NewValidator(opts revocation.Options) (revocation.Validator, error) { + return revocation.NewWithOptions(opts) +} + +// newFileCache returns a new file cache instance +func newFileCache(root string) (*crl.FileCache, error) { + cacheRoot, err := dir.CacheFS().SysPath(root) + if err != nil { + return nil, err + } + return crl.NewFileCache(cacheRoot) +} diff --git a/pkg/verifier/notation/notationrevocationfactory_test.go b/pkg/verifier/notation/notationrevocationfactory_test.go new file mode 100644 index 000000000..b5355f83c --- /dev/null +++ b/pkg/verifier/notation/notationrevocationfactory_test.go @@ -0,0 +1,103 @@ +// Copyright The Ratify 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 notation + +import ( + "net/http" + "runtime" + "testing" + + "github.com/notaryproject/notation-core-go/revocation" + "github.com/stretchr/testify/assert" +) + +func TestNewRevocationFactoryImpl(t *testing.T) { + factory := NewRevocationFactoryImpl() + assert.NotNil(t, factory) +} + +func TestNewFetcher(t *testing.T) { + tests := []struct { + name string + cacheRoot string + httpClient *http.Client + wantErr bool + }{ + { + name: "valid fetcher", + cacheRoot: "/valid/path", + httpClient: &http.Client{}, + wantErr: false, + }, + { + name: "invalid fetcher with nil httpClient", + cacheRoot: "/valid/path", + httpClient: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + factory := &RevocationFactoryImpl{ + cacheRoot: tt.cacheRoot, + httpClient: tt.httpClient, + } + + fetcher, err := factory.NewFetcher() + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, fetcher) + } + }) + } +} + +func TestNewValidator(t *testing.T) { + factory := &RevocationFactoryImpl{} + opts := revocation.Options{} + + validator, err := factory.NewValidator(opts) + assert.NoError(t, err) + assert.NotNil(t, validator) +} +func TestNewFileCache(t *testing.T) { + tests := []struct { + name string + cacheRoot string + wantErr bool + }{ + { + name: "valid cache root", + cacheRoot: "/valid/path", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } + cache, err := newFileCache(tt.cacheRoot) + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, cache) + } else { + assert.NoError(t, err) + assert.NotNil(t, cache) + } + }) + } +} diff --git a/pkg/verifier/notation/revocationfactory.go b/pkg/verifier/notation/revocationfactory.go new file mode 100644 index 000000000..7860ec2a7 --- /dev/null +++ b/pkg/verifier/notation/revocationfactory.go @@ -0,0 +1,27 @@ +// Copyright The Ratify 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 notation + +import ( + "github.com/notaryproject/notation-core-go/revocation" + corecrl "github.com/notaryproject/notation-core-go/revocation/crl" +) + +type RevocationFactory interface { + // NewFetcher returns a new fetcher instance + NewFetcher() (corecrl.Fetcher, error) + + // NewValidator returns a new validator instance + NewValidator(revocation.Options) (revocation.Validator, error) +}