Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
FeynmanZhou committed Nov 23, 2023
2 parents c4064db + 5de0d58 commit 0ee2de0
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 55 deletions.
2 changes: 1 addition & 1 deletion dir/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func LocalKeyPath(name string) (keyPath, certPath string) {
//
// items includes named-store and cert-file names.
// the directory follows the pattern of
// {NOTATION_CONFIG}/truststore/x509/{named-store}/{cert-file}
// {NOTATION_CONFIG}/truststore/x509/{store-type}/{named-store}/{cert-file}
func X509TrustStoreDir(items ...string) string {
pathItems := []string{TrustStoreDir, "x509"}
pathItems = append(pathItems, items...)
Expand Down
12 changes: 6 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ go 1.20

require (
github.com/go-ldap/ldap/v3 v3.4.6
github.com/notaryproject/notation-core-go v1.0.0
github.com/notaryproject/notation-core-go v1.0.1
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0-rc5
github.com/veraison/go-cose v1.1.0
golang.org/x/crypto v0.14.0
golang.org/x/mod v0.13.0
oras.land/oras-go/v2 v2.3.0
golang.org/x/crypto v0.15.0
golang.org/x/mod v0.14.0
oras.land/oras-go/v2 v2.3.1
)

require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/fxamacker/cbor/v2 v2.5.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sync v0.4.0 // indirect
)
24 changes: 12 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1L
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A=
Expand All @@ -15,8 +15,8 @@ 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.0 h1:FgOAihtFW4XU9JYyTzItg1xW3OaN4eCasw5Bp00Ydu4=
github.com/notaryproject/notation-core-go v1.0.0/go.mod h1:eoHFJ2e6b31GZO9hckCms5kfXvHLTySvJ1QwRLB9ZCk=
github.com/notaryproject/notation-core-go v1.0.1 h1:01doxjDERbd0vocLQrlJdusKrRLNNn50OJzp0c5I4Cw=
github.com/notaryproject/notation-core-go v1.0.1/go.mod h1:rayl8WlKgS4YxOZgDO0iGGB4Ef515ZFZUFaZDmsPXgE=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
Expand All @@ -36,12 +36,12 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
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.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
Expand All @@ -50,8 +50,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down Expand Up @@ -80,5 +80,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
oras.land/oras-go/v2 v2.3.0 h1:lqX1aXdN+DAmDTKjiDyvq85cIaI4RkIKp/PghWlAGIU=
oras.land/oras-go/v2 v2.3.0/go.mod h1:GeAwLuC4G/JpNwkd+bSZ6SkDMGaaYglt6YK2WvZP7uQ=
oras.land/oras-go/v2 v2.3.1 h1:lUC6q8RkeRReANEERLfH86iwGn55lbSWP20egdFHVec=
oras.land/oras-go/v2 v2.3.1/go.mod h1:5AQXVEu1X/FKp1F9DMOb5ZItZBOa0y5dha0yCm4NR9c=
25 changes: 12 additions & 13 deletions notation.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ type ValidationResult struct {
Error error
}

// VerificationOutcome encapsulates a signature blob's descriptor, its content,
// VerificationOutcome encapsulates a signature envelope blob, its content,
// the verification level and results for each verification type that was
// performed.
type VerificationOutcome struct {
Expand Down Expand Up @@ -347,12 +347,12 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
return ocispec.Descriptor{}, nil, ErrorSignatureRetrievalFailed{Msg: fmt.Sprintf("user input digest %s does not match the resolved digest %s", ref.Reference, artifactDescriptor.Digest.String())}
}

var verificationSucceeded bool
var verificationOutcomes []*VerificationOutcome
var verificationFailedErrorArray = []error{ErrorVerificationFailed{}}
errExceededMaxVerificationLimit := ErrorVerificationFailed{Msg: fmt.Sprintf("signature evaluation stopped. The configured limit of %d signatures to verify per artifact exceeded", verifyOpts.MaxSignatureAttempts)}
numOfSignatureProcessed := 0

var verificationFailedErr error = ErrorVerificationFailed{}

// get signature manifests
logger.Debug("Fetching signature manifests")
err = repo.ListSignatures(ctx, artifactDescriptor, func(signatureManifests []ocispec.Descriptor) error {
Expand Down Expand Up @@ -380,16 +380,15 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
logger.Error("Got nil outcome. Expecting non-nil outcome on verification failure")
return err
}

if _, ok := outcome.Error.(ErrorUserMetadataVerificationFailed); ok {
verificationFailedErr = outcome.Error
}

outcome.Error = fmt.Errorf("failed to verify signature with digest %v, %w", sigManifestDesc.Digest, outcome.Error)
verificationFailedErrorArray = append(verificationFailedErrorArray, outcome.Error)
continue
}
// at this point, the signature is verified successfully. Add
// it to the verificationOutcomes.
verificationOutcomes = append(verificationOutcomes, outcome)
// at this point, the signature is verified successfully
verificationSucceeded = true
// on success, verificationOutcomes only contains the
// succeeded outcome
verificationOutcomes = []*VerificationOutcome{outcome}
logger.Debugf("Signature verification succeeded for artifact %v with signature digest %v", artifactDescriptor.Digest, sigManifestDesc.Digest)

// early break on success
Expand All @@ -416,9 +415,9 @@ func Verify(ctx context.Context, verifier Verifier, repo registry.Repository, ve
}

// Verification Failed
if len(verificationOutcomes) == 0 {
if !verificationSucceeded {
logger.Debugf("Signature verification failed for all the signatures associated with artifact %v", artifactDescriptor.Digest)
return ocispec.Descriptor{}, verificationOutcomes, verificationFailedErr
return ocispec.Descriptor{}, verificationOutcomes, errors.Join(verificationFailedErrorArray...)
}

// Verification Succeeded
Expand Down
14 changes: 14 additions & 0 deletions plugin/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"context"
"errors"
"io/fs"
"os"
"path"

"github.com/notaryproject/notation-go/dir"
Expand Down Expand Up @@ -82,3 +83,16 @@ func (m *CLIManager) List(ctx context.Context) ([]string, error) {
})
return plugins, nil
}

// Uninstall uninstalls a plugin on the system by its name
// If the plugin dir does not exist, os.ErrNotExist is returned.
func (m *CLIManager) Uninstall(ctx context.Context, name string) error {
pluginDirPath, err := m.pluginFS.SysPath(name)
if err != nil {
return err
}
if _, err := os.Stat(pluginDirPath); err != nil {
return err
}
return os.RemoveAll(pluginDirPath)
}
26 changes: 26 additions & 0 deletions plugin/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"context"
"encoding/json"
"io/fs"
"os"
"reflect"
"testing"
"testing/fstest"
Expand Down Expand Up @@ -87,6 +88,31 @@ func TestManager_List(t *testing.T) {
})
}

func TestManager_Uninstall(t *testing.T) {
executor = testCommander{stdout: metadataJSON(validMetadata)}
mgr := NewCLIManager(mockfs.NewSysFSWithRootMock(fstest.MapFS{}, "./testdata/plugins"))
if err := os.MkdirAll("./testdata/plugins/toUninstall", 0777); err != nil {
t.Fatalf("failed to create toUninstall dir: %v", err)
}
defer os.RemoveAll("./testdata/plugins/toUninstall")
pluginFile, err := os.Create("./testdata/plugins/toUninstall/toUninstall")
if err != nil {
t.Fatalf("failed to create toUninstall file: %v", err)
}
if err := pluginFile.Close(); err != nil {
t.Fatalf("failed to close toUninstall file: %v", err)
}
// test uninstall valid plugin
if err := mgr.Uninstall(context.Background(), "toUninstall"); err != nil {
t.Fatalf("Manager.Uninstall() err %v, want nil", err)
}
// test uninstall non-exist plugin
expectedErrorMsg := "stat testdata/plugins/non-exist: no such file or directory"
if err := mgr.Uninstall(context.Background(), "non-exist"); err == nil || err.Error() != expectedErrorMsg {
t.Fatalf("Manager.Uninstall() err %v, want %s", err, expectedErrorMsg)
}
}

func metadataJSON(m proto.GetMetadataResponse) []byte {
d, err := json.Marshal(m)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions verifier/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func loadX509TrustStores(ctx context.Context, scheme signature.SigningScheme, po
case signature.SigningSchemeX509SigningAuthority:
typeToLoad = truststore.TypeSigningAuthority
default:
return nil, fmt.Errorf("unrecognized signing scheme %q", scheme)
return nil, truststore.TrustStoreError{Msg: fmt.Sprintf("error while loading the trust store, unrecognized signing scheme %q", scheme)}
}

processedStoreSet := set.New[string]()
Expand All @@ -71,7 +71,7 @@ func loadX509TrustStores(ctx context.Context, scheme signature.SigningScheme, po

storeType, name, found := strings.Cut(trustStore, ":")
if !found {
return nil, fmt.Errorf("trust policy statement %q is missing separator in trust store value %q. The required format is <TrustStoreType>:<TrustStoreName>", policy.Name, trustStore)
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 <TrustStoreType>:<TrustStoreName>", policy.Name, trustStore)}
}
if typeToLoad != truststore.Type(storeType) {
continue
Expand Down
54 changes: 54 additions & 0 deletions verifier/truststore/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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 truststore

// TrustStoreError is used when accessing specified trust store failed
type TrustStoreError struct {
Msg string
InnerError error
}

func (e TrustStoreError) Error() string {
if e.Msg != "" {
return e.Msg
}
if e.InnerError != nil {
return e.InnerError.Error()
}
return "unable to access the trust store"
}

func (e TrustStoreError) Unwrap() error {
return e.InnerError
}

// CertificateError is used when reading a certificate failed
type CertificateError struct {
Msg string
InnerError error
}

func (e CertificateError) Error() string {
if e.Msg != "" {
return e.Msg
}
if e.InnerError != nil {
return e.InnerError.Error()
}
return "unable to read the certificate"
}

func (e CertificateError) Unwrap() error {
return e.InnerError
}
25 changes: 13 additions & 12 deletions verifier/truststore/truststore.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,49 +64,50 @@ type x509TrustStore struct {
// GetCertificates returns certificates under storeType/namedStore
func (trustStore *x509TrustStore) GetCertificates(ctx context.Context, storeType Type, namedStore string) ([]*x509.Certificate, error) {
if !isValidStoreType(storeType) {
return nil, fmt.Errorf("unsupported store type: %s", storeType)
return nil, TrustStoreError{Msg: fmt.Sprintf("unsupported trust store type: %s", storeType)}
}
if !file.IsValidFileName(namedStore) {
return nil, errors.New("named store name needs to follow [a-zA-Z0-9_.-]+ format")
return nil, TrustStoreError{Msg: fmt.Sprintf("trust store name needs to follow [a-zA-Z0-9_.-]+ format, %s is invalid", namedStore)}
}
path, err := trustStore.trustStorefs.SysPath(dir.X509TrustStoreDir(string(storeType), namedStore))
if err != nil {
return nil, err
return nil, TrustStoreError{InnerError: err, Msg: fmt.Sprintf("failed to get path of trust store %s of type %s", namedStore, storeType)}
}
// throw error if path is not a directory or is a symlink or does not exist.
fileInfo, err := os.Lstat(path)
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("%q does not exist", path)
return nil, TrustStoreError{InnerError: err, Msg: fmt.Sprintf("the trust store %q of type %q does not exist", namedStore, storeType)}
}
return nil, err
return nil, TrustStoreError{InnerError: err, Msg: fmt.Sprintf("failed to access the trust store %q of type %q", namedStore, storeType)}
}
mode := fileInfo.Mode()
if !mode.IsDir() || mode&fs.ModeSymlink != 0 {
return nil, fmt.Errorf("%q is not a regular directory (symlinks are not supported)", path)
return nil, TrustStoreError{Msg: fmt.Sprintf("the trust store %s of type %s with path %s is not a regular directory (symlinks are not supported)", namedStore, storeType, path)}
}
files, err := os.ReadDir(path)
if err != nil {
return nil, err
return nil, TrustStoreError{InnerError: err, Msg: fmt.Sprintf("failed to access the trust store %q of type %q", namedStore, storeType)}
}

var certificates []*x509.Certificate
for _, file := range files {
joinedPath := filepath.Join(path, file.Name())
certFileName := file.Name()
joinedPath := filepath.Join(path, certFileName)
if file.IsDir() || file.Type()&fs.ModeSymlink != 0 {
return nil, fmt.Errorf("%q is not a regular file (directories or symlinks are not supported)", joinedPath)
return nil, CertificateError{Msg: fmt.Sprintf("trusted certificate %s in trust store %s of type %s is not a regular file (directories or symlinks are not supported)", certFileName, namedStore, storeType)}
}
certs, err := corex509.ReadCertificateFile(joinedPath)
if err != nil {
return nil, fmt.Errorf("error while reading certificates from %q: %w", joinedPath, err)
return nil, CertificateError{InnerError: err, Msg: fmt.Sprintf("failed to read the trusted certificate %s in trust store %s of type %s", certFileName, namedStore, storeType)}
}
if err := ValidateCertificates(certs); err != nil {
return nil, fmt.Errorf("error while validating certificates from %q: %w", joinedPath, err)
return nil, CertificateError{InnerError: err, Msg: fmt.Sprintf("failed to validate the trusted certificate %s in trust store %s of type %s", certFileName, namedStore, storeType)}
}
certificates = append(certificates, certs...)
}
if len(certificates) < 1 {
return nil, fmt.Errorf("trust store %q has no x509 certificates", path)
return nil, CertificateError{InnerError: fs.ErrNotExist, Msg: fmt.Sprintf("no x509 certificates were found in trust store %q of type %q", namedStore, storeType)}
}
return certificates, nil
}
Expand Down
Loading

0 comments on commit 0ee2de0

Please sign in to comment.