diff --git a/go.mod b/go.mod index 3b61b04a..9f2db601 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( github.com/go-ldap/ldap/v3 v3.4.4 github.com/notaryproject/notation-core-go v0.2.0-beta.1 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.0.2 + github.com/opencontainers/image-spec v1.1.0-rc2 github.com/oras-project/artifacts-spec v1.0.0-rc.2 github.com/veraison/go-cose v1.0.0-rc.1.0.20220824135457-9d2fab636b83 - oras.land/oras-go/v2 v2.0.0-rc.3 + oras.land/oras-go/v2 v2.0.0-rc.4 ) require ( @@ -17,7 +17,6 @@ require ( github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect - github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220620172159-4ab4752c3b86 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect diff --git a/go.sum b/go.sum index 685070c7..38b7b6f6 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 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/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= @@ -12,12 +12,10 @@ github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQA github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/notaryproject/notation-core-go v0.2.0-beta.1 h1:8tFxNycWCcPLti9ZYST5kjkX2wMXtX9YPvMjiBAQ1tA= github.com/notaryproject/notation-core-go v0.2.0-beta.1/go.mod h1:s8DZptmN1rZS0tBLTPt/w+d4o6eAcGWTYYJlXaJhQ4U= -github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220620172159-4ab4752c3b86 h1:Oumw+lPnO8qNLTY2mrqPJZMoGExLi/0h/DdikoLTXVU= -github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220620172159-4ab4752c3b86/go.mod h1:aA4vdXRS8E1TG7pLZOz85InHi3BiPdErh8IpJN6E0x4= 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.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= +github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/oras-project/artifacts-spec v1.0.0-rc.2 h1:9SMCNSxkJEHqWGDiMCuy6TXHgvjgwXGdXZZGXLKQvVE= github.com/oras-project/artifacts-spec v1.0.0-rc.2/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -43,5 +41,5 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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.0.0-rc.3 h1:O4GeIwJ9Ge7rbCkqa/M7DLrL55ww+ZEc+Rhc63OYitU= -oras.land/oras-go/v2 v2.0.0-rc.3/go.mod h1:PrY+cCglzK/DrQoJUtxbYVbL94ZHecVS3eJR01RglpE= +oras.land/oras-go/v2 v2.0.0-rc.4 h1:hg/R2znUQ1+qd43gRmL16VeX1GIZ8hQlLalBjYhhKSk= +oras.land/oras-go/v2 v2.0.0-rc.4/go.mod h1:YGHvWBGuqRlZgUyXUIoKsR3lcuCOb3DAtG0SEsEw1iY= diff --git a/internal/mock/mocks.go b/internal/mock/mocks.go index 7903b9f1..13a40dd1 100644 --- a/internal/mock/mocks.go +++ b/internal/mock/mocks.go @@ -6,9 +6,9 @@ import ( "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/internal/registry" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/plugin/manager" - "github.com/notaryproject/notation-go/registry" "github.com/opencontainers/go-digest" ) diff --git a/internal/registry/interface.go b/internal/registry/interface.go new file mode 100644 index 00000000..e8e621bc --- /dev/null +++ b/internal/registry/interface.go @@ -0,0 +1,28 @@ +package registry + +import ( + "context" + + "github.com/notaryproject/notation-go" + "github.com/opencontainers/go-digest" +) + +// SignatureRepository provides a storage for signatures +type SignatureRepository interface { + // ListSignatureManifests returns all signature manifests given the manifest digest + ListSignatureManifests(ctx context.Context, manifestDigest digest.Digest) ([]SignatureManifest, error) + + // GetBlob downloads the content of the specified digest's Blob + GetBlob(ctx context.Context, digest digest.Digest) ([]byte, error) + + // PutSignatureManifest creates and uploads an signature artifact linking the manifest and the signature + PutSignatureManifest(ctx context.Context, signature []byte, signatureMediaType string, manifest notation.Descriptor, annotations map[string]string) (notation.Descriptor, SignatureManifest, error) +} + +// Repository provides functions for verification and signing workflows +type Repository interface { + SignatureRepository + + // Resolve resolves a reference(tag or digest) to a manifest descriptor + Resolve(ctx context.Context, reference string) (notation.Descriptor, error) +} diff --git a/internal/registry/mediatype.go b/internal/registry/mediatype.go new file mode 100644 index 00000000..a7350d19 --- /dev/null +++ b/internal/registry/mediatype.go @@ -0,0 +1,4 @@ +package registry + +// ArtifactTypeNotation specifies the artifact type for a notation object. +const ArtifactTypeNotation = "application/vnd.cncf.notary.v2.signature" diff --git a/internal/registry/repository.go b/internal/registry/repository.go new file mode 100644 index 00000000..7efb4f5a --- /dev/null +++ b/internal/registry/repository.go @@ -0,0 +1,190 @@ +package registry + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + + "github.com/notaryproject/notation-go" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content" + "oras.land/oras-go/v2/registry" + "oras.land/oras-go/v2/registry/remote" +) + +const ( + maxBlobSizeLimit = 32 * 1024 * 1024 // 32 MiB + maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB +) + +type RepositoryClient struct { + remote.Repository +} + +type SignatureManifest struct { + Blob notation.Descriptor + Annotations map[string]string +} + +// NewRepositoryClient creates a new registry client. +func NewRepositoryClient(client remote.Client, ref registry.Reference, plainHTTP bool) *RepositoryClient { + return &RepositoryClient{ + Repository: remote.Repository{ + Client: client, + Reference: ref, + PlainHTTP: plainHTTP, + }, + } +} + +// Resolve resolves a reference(tag or digest) to a manifest descriptor +func (c *RepositoryClient) Resolve(ctx context.Context, reference string) (notation.Descriptor, error) { + desc, err := c.Repository.Resolve(ctx, reference) + if err != nil { + return notation.Descriptor{}, err + } + return notationDescriptorFromOCI(desc), nil +} + +// ListSignatureManifests returns all signature manifests given the manifest digest +func (c *RepositoryClient) ListSignatureManifests(ctx context.Context, manifestDigest digest.Digest) ([]SignatureManifest, error) { + var signatureManifests []SignatureManifest + if err := c.Repository.Referrers(ctx, ocispec.Descriptor{ + Digest: manifestDigest, + }, ArtifactTypeNotation, func(referrers []ocispec.Descriptor) error { + for _, desc := range referrers { + if desc.MediaType != artifactspec.MediaTypeArtifactManifest { + continue + } + artifact, err := c.getArtifactManifest(ctx, desc.Digest) + if err != nil { + return fmt.Errorf("failed to fetch manifest: %v: %v", desc.Digest, err) + } + if len(artifact.Blobs) == 0 { + continue + } + signatureManifests = append(signatureManifests, SignatureManifest{ + Blob: notationDescriptorFromArtifact(artifact.Blobs[0]), + Annotations: artifact.Annotations, + }) + } + return nil + }); err != nil { + return nil, err + } + return signatureManifests, nil +} + +// GetBlob downloads the content of the specified digest's Blob +func (c *RepositoryClient) GetBlob(ctx context.Context, digest digest.Digest) ([]byte, error) { + desc, err := c.Repository.Blobs().Resolve(ctx, digest.String()) + if err != nil { + return nil, err + } + if desc.Size > maxBlobSizeLimit { + return nil, fmt.Errorf("signature blob too large: %d", desc.Size) + } + return content.FetchAll(ctx, c.Repository.Blobs(), desc) +} + +// PutSignatureManifest creates and uploads an signature artifact linking the manifest and the signature +func (c *RepositoryClient) PutSignatureManifest(ctx context.Context, signature []byte, signatureMediaType string, subjectManifest notation.Descriptor, annotations map[string]string) (notation.Descriptor, SignatureManifest, error) { + signatureDesc, err := c.uploadSignature(ctx, signature, signatureMediaType) + if err != nil { + return notation.Descriptor{}, SignatureManifest{}, err + } + + manifestDesc, err := c.uploadSignatureManifest(ctx, ociDescriptorFromNotation(subjectManifest), signatureDesc, annotations) + if err != nil { + return notation.Descriptor{}, SignatureManifest{}, err + } + + signatureManifest := SignatureManifest{ + Blob: notationDescriptorFromOCI(signatureDesc), + Annotations: annotations, + } + return notationDescriptorFromOCI(manifestDesc), signatureManifest, nil +} + +func (c *RepositoryClient) getArtifactManifest(ctx context.Context, manifestDigest digest.Digest) (artifactspec.Manifest, error) { + repo := c.Repository + repo.ManifestMediaTypes = []string{ + artifactspec.MediaTypeArtifactManifest, + } + store := repo.Manifests() + desc, err := store.Resolve(ctx, manifestDigest.String()) + if err != nil { + return artifactspec.Manifest{}, err + } + if desc.Size > maxManifestSizeLimit { + return artifactspec.Manifest{}, fmt.Errorf("manifest too large: %d", desc.Size) + } + manifestJSON, err := content.FetchAll(ctx, store, desc) + if err != nil { + return artifactspec.Manifest{}, err + } + + var manifest artifactspec.Manifest + err = json.Unmarshal(manifestJSON, &manifest) + if err != nil { + return artifactspec.Manifest{}, err + } + return manifest, nil +} + +// uploadSignature uploads the signature to the registry +func (c *RepositoryClient) uploadSignature(ctx context.Context, signature []byte, signatureMediaType string) (ocispec.Descriptor, error) { + desc := ocispec.Descriptor{ + MediaType: signatureMediaType, + Digest: digest.FromBytes(signature), + Size: int64(len(signature)), + } + if err := c.Repository.Blobs().Push(ctx, desc, bytes.NewReader(signature)); err != nil { + return ocispec.Descriptor{}, err + } + return desc, nil +} + +// uploadSignatureManifest uploads the signature manifest to the registry +func (c *RepositoryClient) uploadSignatureManifest(ctx context.Context, subjectManifest, signatureDesc ocispec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) { + opts := oras.PackOptions{ + Subject: &subjectManifest, + ManifestAnnotations: annotations, + } + + return oras.Pack( + ctx, + c.Repository.Manifests(), + ArtifactTypeNotation, + []ocispec.Descriptor{signatureDesc}, + opts, + ) +} + +func ociDescriptorFromNotation(desc notation.Descriptor) ocispec.Descriptor { + return ocispec.Descriptor{ + MediaType: desc.MediaType, + Digest: desc.Digest, + Size: desc.Size, + } +} + +func notationDescriptorFromArtifact(desc artifactspec.Descriptor) notation.Descriptor { + return notation.Descriptor{ + MediaType: desc.MediaType, + Digest: desc.Digest, + Size: desc.Size, + } +} + +func notationDescriptorFromOCI(desc ocispec.Descriptor) notation.Descriptor { + return notation.Descriptor{ + MediaType: desc.MediaType, + Digest: desc.Digest, + Size: desc.Size, + } +} diff --git a/registry/interface.go b/registry/interface.go index e8e621bc..9fb4452d 100644 --- a/registry/interface.go +++ b/registry/interface.go @@ -1,28 +1,27 @@ +// Package registry provides access to signatures in a registry package registry import ( "context" - "github.com/notaryproject/notation-go" - "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) -// SignatureRepository provides a storage for signatures -type SignatureRepository interface { - // ListSignatureManifests returns all signature manifests given the manifest digest - ListSignatureManifests(ctx context.Context, manifestDigest digest.Digest) ([]SignatureManifest, error) - - // GetBlob downloads the content of the specified digest's Blob - GetBlob(ctx context.Context, digest digest.Digest) ([]byte, error) +// Repository provides registry functionalities for storage and retrieval +// of signature. +type Repository interface { + // Resolve resolves a reference(tag or digest) to a manifest descriptor + Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) - // PutSignatureManifest creates and uploads an signature artifact linking the manifest and the signature - PutSignatureManifest(ctx context.Context, signature []byte, signatureMediaType string, manifest notation.Descriptor, annotations map[string]string) (notation.Descriptor, SignatureManifest, error) -} + // ListSignatures returns signature manifests filtered by fn given the + // artifact manifest descriptor + ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error -// Repository provides functions for verification and signing workflows -type Repository interface { - SignatureRepository + // FetchSignatureBlob returns signature envelope blob and descriptor for + // given signature manifest descriptor + FetchSignatureBlob(ctx context.Context, desc ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) - // Resolve resolves a reference(tag or digest) to a manifest descriptor - Resolve(ctx context.Context, reference string) (notation.Descriptor, error) + // PushSignature creates and uploads an signature manifest along with its + // linked signature envelope blob. + PushSignature(ctx context.Context, blob []byte, mediaType string, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error) } diff --git a/registry/repository.go b/registry/repository.go index 37f38e14..a153060b 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -1,19 +1,15 @@ package registry import ( - "bytes" "context" "encoding/json" + "errors" "fmt" - "github.com/notaryproject/notation-go" - "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/registry" - "oras.land/oras-go/v2/registry/remote" ) const ( @@ -21,178 +17,101 @@ const ( maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB ) -type RepositoryClient struct { - remote.Repository +// repositoryClient implements Repository +type repositoryClient struct { + registry.Repository } -type SignatureManifest struct { - Blob notation.Descriptor - Annotations map[string]string -} - -// NewRepositoryClient creates a new registry client. -func NewRepositoryClient(client remote.Client, ref registry.Reference, plainHTTP bool) *RepositoryClient { - return &RepositoryClient{ - Repository: remote.Repository{ - Client: client, - Reference: ref, - PlainHTTP: plainHTTP, - }, +// NewRepository returns a new Repository +func NewRepository(repo registry.Repository) Repository { + return &repositoryClient{ + Repository: repo, } } // Resolve resolves a reference(tag or digest) to a manifest descriptor -func (c *RepositoryClient) Resolve(ctx context.Context, reference string) (notation.Descriptor, error) { - desc, err := c.Repository.Resolve(ctx, reference) - if err != nil { - return notation.Descriptor{}, err - } - return notationDescriptorFromOCI(desc), nil +func (c *repositoryClient) Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) { + return c.Repository.Manifests().Resolve(ctx, reference) } -// ListSignatureManifests returns all signature manifests given the manifest digest -func (c *RepositoryClient) ListSignatureManifests(ctx context.Context, manifestDigest digest.Digest) ([]SignatureManifest, error) { - var signatureManifests []SignatureManifest - if err := c.Repository.Referrers(ctx, ocispec.Descriptor{ - Digest: manifestDigest, - }, ArtifactTypeNotation, func(referrers []artifactspec.Descriptor) error { - for _, desc := range referrers { - if desc.MediaType != artifactspec.MediaTypeArtifactManifest { - continue - } - artifact, err := c.getArtifactManifest(ctx, desc.Digest) - if err != nil { - return fmt.Errorf("failed to fetch manifest: %v: %v", desc.Digest, err) - } - if len(artifact.Blobs) == 0 { - continue - } - signatureManifests = append(signatureManifests, SignatureManifest{ - Blob: notationDescriptorFromArtifact(artifact.Blobs[0]), - Annotations: artifact.Annotations, - }) - } - return nil - }); err != nil { - return nil, err +// ListSignatures returns signature manifests filtered by fn given the +// artifact manifest descriptor +func (c *repositoryClient) ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error { + // TODO: remove this part once oras v2.0.0-rc.5 is released + // https://github.com/notaryproject/notation-go/issues/195 + refFinder, ok := c.Repository.(registry.ReferrerFinder) + if !ok { + return errors.New("repo is not a registry.ReferrerFinder") } - return signatureManifests, nil + return refFinder.Referrers(ctx, desc, ArtifactTypeNotation, fn) } -// GetBlob downloads the content of the specified digest's Blob -func (c *RepositoryClient) GetBlob(ctx context.Context, digest digest.Digest) ([]byte, error) { - desc, err := c.Repository.Blobs().Resolve(ctx, digest.String()) +// FetchSignatureBlob returns signature envelope blob and descriptor given +// signature manifest descriptor +func (c *repositoryClient) FetchSignatureBlob(ctx context.Context, desc ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) { + sigManifest, err := c.getSignatureManifest(ctx, desc) if err != nil { - return nil, err + return nil, ocispec.Descriptor{}, err } - if desc.Size > maxBlobSizeLimit { - return nil, fmt.Errorf("signature blob too large: %d", desc.Size) + if len(sigManifest.Blobs) != 1 { + return nil, ocispec.Descriptor{}, fmt.Errorf("signature manifest requries exactly one signature envelope blob, got %d", len(sigManifest.Blobs)) } - return content.FetchAll(ctx, c.Repository.Blobs(), desc) + sigDesc := sigManifest.Blobs[0] + if sigDesc.Size > maxBlobSizeLimit { + return nil, ocispec.Descriptor{}, fmt.Errorf("signature blob too large: %d bytes", sigDesc.Size) + } + sigBlob, err := content.FetchAll(ctx, c.Repository.Blobs(), sigDesc) + if err != nil { + return nil, ocispec.Descriptor{}, err + } + return sigBlob, sigDesc, nil } -// PutSignatureManifest creates and uploads an signature artifact linking the manifest and the signature -func (c *RepositoryClient) PutSignatureManifest(ctx context.Context, signature []byte, signatureMediaType string, subjectManifest notation.Descriptor, annotations map[string]string) (notation.Descriptor, SignatureManifest, error) { - signatureDesc, err := c.uploadSignature(ctx, signature, signatureMediaType) +// PushSignature creates and uploads an signature manifest along with its +// linked signature envelope blob. Upon successful, PushSignature returns +// signature envelope blob and manifest descriptors. +func (c *repositoryClient) PushSignature(ctx context.Context, blob []byte, mediaType string, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error) { + blobDesc, err = oras.PushBytes(ctx, c.Repository.Blobs(), mediaType, blob) if err != nil { - return notation.Descriptor{}, SignatureManifest{}, err + return ocispec.Descriptor{}, ocispec.Descriptor{}, err } - manifestDesc, err := c.uploadSignatureManifest(ctx, artifactDescriptorFromNotation(subjectManifest), signatureDesc, annotations) + manifestDesc, err = c.uploadSignatureManifest(ctx, subject, blobDesc, annotations) if err != nil { - return notation.Descriptor{}, SignatureManifest{}, err + return ocispec.Descriptor{}, ocispec.Descriptor{}, err } - signatureManifest := SignatureManifest{ - Blob: notationDescriptorFromArtifact(signatureDesc), - Annotations: annotations, - } - return notationDescriptorFromOCI(manifestDesc), signatureManifest, nil + return blobDesc, manifestDesc, nil } -func (c *RepositoryClient) getArtifactManifest(ctx context.Context, manifestDigest digest.Digest) (artifactspec.Manifest, error) { - repo := c.Repository - repo.ManifestMediaTypes = []string{ - artifactspec.MediaTypeArtifactManifest, - } - store := repo.Manifests() - desc, err := store.Resolve(ctx, manifestDigest.String()) - if err != nil { - return artifactspec.Manifest{}, err +// getSignatureManifest returns signature manifest given signature manifest +// descriptor +func (c *repositoryClient) getSignatureManifest(ctx context.Context, sigManifestDesc ocispec.Descriptor) (*ocispec.Artifact, error) { + if sigManifestDesc.MediaType != ocispec.MediaTypeArtifactManifest { + return nil, fmt.Errorf("sigManifestDesc.MediaType requires %q, got %q", ocispec.MediaTypeArtifactManifest, sigManifestDesc.MediaType) } - if desc.Size > maxManifestSizeLimit { - return artifactspec.Manifest{}, fmt.Errorf("manifest too large: %d", desc.Size) + if sigManifestDesc.Size > maxManifestSizeLimit { + return nil, fmt.Errorf("manifest too large: %d bytes", sigManifestDesc.Size) } - manifestJSON, err := content.FetchAll(ctx, store, desc) + manifestJSON, err := content.FetchAll(ctx, c.Repository.Manifests(), sigManifestDesc) if err != nil { - return artifactspec.Manifest{}, err + return nil, err } - var manifest artifactspec.Manifest - err = json.Unmarshal(manifestJSON, &manifest) + var sigManifest ocispec.Artifact + err = json.Unmarshal(manifestJSON, &sigManifest) if err != nil { - return artifactspec.Manifest{}, err - } - return manifest, nil -} - -// uploadSignature uploads the signature to the registry -func (c *RepositoryClient) uploadSignature(ctx context.Context, signature []byte, signatureMediaType string) (artifactspec.Descriptor, error) { - desc := ocispec.Descriptor{ - MediaType: signatureMediaType, - Digest: digest.FromBytes(signature), - Size: int64(len(signature)), - } - if err := c.Repository.Blobs().Push(ctx, desc, bytes.NewReader(signature)); err != nil { - return artifactspec.Descriptor{}, err + return nil, err } - return artifactDescriptorFromOCI(desc), nil + return &sigManifest, nil } // uploadSignatureManifest uploads the signature manifest to the registry -func (c *RepositoryClient) uploadSignatureManifest(ctx context.Context, subjectManifest, signatureDesc artifactspec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) { - opts := oras.PackArtifactOptions{ - Subject: &subjectManifest, +func (c *repositoryClient) uploadSignatureManifest(ctx context.Context, subject, blobDesc ocispec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) { + opts := oras.PackOptions{ + Subject: &subject, ManifestAnnotations: annotations, } - return oras.PackArtifact( - ctx, - c.Repository.Manifests(), - ArtifactTypeNotation, - []artifactspec.Descriptor{signatureDesc}, - opts, - ) -} - -func artifactDescriptorFromNotation(desc notation.Descriptor) artifactspec.Descriptor { - return artifactspec.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, - } -} - -func notationDescriptorFromArtifact(desc artifactspec.Descriptor) notation.Descriptor { - return notation.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, - } -} - -func artifactDescriptorFromOCI(desc ocispec.Descriptor) artifactspec.Descriptor { - return artifactspec.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, - } -} - -func notationDescriptorFromOCI(desc ocispec.Descriptor) notation.Descriptor { - return notation.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, - } + return oras.Pack(ctx, c.Repository.Manifests(), ArtifactTypeNotation, []ocispec.Descriptor{blobDesc}, opts) } diff --git a/registry/repository_test.go b/registry/repository_test.go index 97917b2b..71100909 100644 --- a/registry/repository_test.go +++ b/registry/repository_test.go @@ -11,9 +11,8 @@ import ( "strings" "testing" - "github.com/notaryproject/notation-go" "github.com/opencontainers/go-digest" - artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote" ) @@ -106,15 +105,17 @@ const ( ) type args struct { - ctx context.Context - reference string - remoteClient remote.Client - plainHttp bool - digest digest.Digest - annotations map[string]string - subjectManifest notation.Descriptor - signature []byte - signatureMediaType string + ctx context.Context + reference string + remoteClient remote.Client + plainHttp bool + digest digest.Digest + annotations map[string]string + subjectManifest ocispec.Descriptor + signature []byte + signatureMediaType string + signatureManifestDesc ocispec.Descriptor + artifactManifestDesc ocispec.Descriptor } type mockRemoteClient struct { @@ -296,25 +297,11 @@ func (c mockRemoteClient) Do(req *http.Request) (*http.Response, error) { } } -func TestNewRepositoryClient_constructCorrectly(t *testing.T) { - remoteClient := mockRemoteClient{} - ref := registry.Reference{} - plainHttp := false - client := NewRepositoryClient(remoteClient, ref, plainHttp) - - if client.Client != remoteClient || - client.PlainHTTP != plainHttp || - client.Reference != ref { - t.Fatalf("Expect client with remoteClient: %v, plainHTTP: %v, ref: %v, got: %+v", - remoteClient, plainHttp, ref, client.Client) - } -} - func TestResolve(t *testing.T) { tests := []struct { name string args args - expect notation.Descriptor + expect ocispec.Descriptor expectErr bool }{ { @@ -325,30 +312,16 @@ func TestResolve(t *testing.T) { remoteClient: mockRemoteClient{}, plainHttp: false, }, - expect: notation.Descriptor{}, + expect: ocispec.Descriptor{}, expectErr: true, }, - { - name: "succeed to resolve", - args: args{ - ctx: context.Background(), - reference: validReference, - remoteClient: mockRemoteClient{}, - plainHttp: false, - }, - expect: notation.Descriptor{ - MediaType: mediaType, - Digest: validDigestWithAlgo, - }, - expectErr: false, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { args := tt.args ref, _ := registry.ParseReference(args.reference) - client := NewRepositoryClient(args.remoteClient, ref, args.plainHttp) + client := newRepositoryClient(args.remoteClient, ref, args.plainHttp) res, err := client.Resolve(args.ctx, args.reference) if (err != nil) != tt.expectErr { t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) @@ -360,7 +333,7 @@ func TestResolve(t *testing.T) { } } -func TestGetBlob(t *testing.T) { +func TestFetchSignatureBlob(t *testing.T) { tests := []struct { name string args args @@ -376,7 +349,10 @@ func TestGetBlob(t *testing.T) { reference: validReference, remoteClient: mockRemoteClient{}, plainHttp: false, - digest: digest.Digest(invalidDigest), + signatureManifestDesc: ocispec.Descriptor{ + MediaType: ocispec.MediaTypeArtifactManifest, + Digest: digest.Digest(invalidDigest), + }, }, }, { @@ -388,19 +364,10 @@ func TestGetBlob(t *testing.T) { reference: validReference, remoteClient: mockRemoteClient{}, plainHttp: false, - digest: digest.Digest(validDigestWithAlgo3), - }, - }, - { - name: "succeed to get", - expect: []byte(validBlob), - expectErr: false, - args: args{ - ctx: context.Background(), - reference: validReference6, - remoteClient: mockRemoteClient{}, - plainHttp: false, - digest: digest.Digest(validDigestWithAlgo6), + signatureManifestDesc: ocispec.Descriptor{ + MediaType: ocispec.MediaTypeArtifactManifest, + Digest: digest.Digest(validDigestWithAlgo3), + }, }, }, } @@ -409,8 +376,8 @@ func TestGetBlob(t *testing.T) { t.Run(tt.name, func(t *testing.T) { args := tt.args ref, _ := registry.ParseReference(args.reference) - client := NewRepositoryClient(args.remoteClient, ref, args.plainHttp) - res, err := client.GetBlob(args.ctx, args.digest) + client := newRepositoryClient(args.remoteClient, ref, args.plainHttp) + res, _, err := client.FetchSignatureBlob(args.ctx, args.signatureManifestDesc) if (err != nil) != tt.expectErr { t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) } @@ -421,25 +388,13 @@ func TestGetBlob(t *testing.T) { } } -func TestListSignatureManifests(t *testing.T) { +func TestListSignatures(t *testing.T) { tests := []struct { name string args args - expect []SignatureManifest + expect []interface{} expectErr bool }{ - { - name: "wrong mediaType", - expectErr: false, - expect: nil, - args: args{ - ctx: context.Background(), - reference: validReference, - remoteClient: mockRemoteClient{}, - plainHttp: false, - digest: digest.Digest(validDigest7), - }, - }, { name: "failed to fetch content", expectErr: true, @@ -452,56 +407,34 @@ func TestListSignatureManifests(t *testing.T) { digest: digest.Digest(invalidDigest), }, }, - { - name: "succeed to list", - expectErr: false, - expect: []SignatureManifest{ - { - Blob: notation.Descriptor{ - Digest: validDigestWithAlgo9, - Size: 90, - }, - }, - }, - args: args{ - ctx: context.Background(), - reference: validReference, - remoteClient: mockRemoteClient{}, - plainHttp: false, - digest: digest.Digest(validDigest8), - }, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { args := tt.args ref, _ := registry.ParseReference(args.reference) - client := NewRepositoryClient(args.remoteClient, ref, args.plainHttp) + client := newRepositoryClient(args.remoteClient, ref, args.plainHttp) - res, err := client.ListSignatureManifests(args.ctx, args.digest) + err := client.ListSignatures(args.ctx, args.artifactManifestDesc, nil) if (err != nil) != tt.expectErr { t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) } - if !reflect.DeepEqual(res, tt.expect) { - t.Errorf("expect %+v, got %+v", tt.expect, res) - } }) } } -func TestPutSignatureManifest(t *testing.T) { +func TestPushSignature(t *testing.T) { tests := []struct { name string args args - expectDes notation.Descriptor - expectManifest SignatureManifest + expectDes ocispec.Descriptor + expectManifest ocispec.Descriptor expectErr bool }{ { name: "failed to upload signature", expectErr: true, - expectDes: notation.Descriptor{}, - expectManifest: SignatureManifest{}, + expectDes: ocispec.Descriptor{}, + expectManifest: ocispec.Descriptor{}, args: args{ reference: referenceWithInvalidHost, signature: make([]byte, 0), @@ -512,69 +445,13 @@ func TestPutSignatureManifest(t *testing.T) { { name: "failed to upload signature manifest", expectErr: true, - expectDes: notation.Descriptor{}, - expectManifest: SignatureManifest{}, - args: args{ - reference: validReference, - signature: make([]byte, 0), - ctx: context.Background(), - remoteClient: mockRemoteClient{}, - }, - }, - { - name: "succeed to put signature manifest with jws media type", - expectErr: false, - expectDes: notation.Descriptor{ - MediaType: artifactspec.MediaTypeArtifactManifest, - Digest: digest.Digest(validDigestWithAlgo4), - Size: 369, - }, - expectManifest: SignatureManifest{ - Annotations: map[string]string{ - artifactspec.AnnotationArtifactCreated: validTimestamp, - }, - Blob: notation.Descriptor{ - MediaType: joseTag, - Digest: validDigestWithAlgo5, - }, - }, + expectDes: ocispec.Descriptor{}, + expectManifest: ocispec.Descriptor{}, args: args{ reference: validReference, signature: make([]byte, 0), ctx: context.Background(), remoteClient: mockRemoteClient{}, - annotations: map[string]string{ - artifactspec.AnnotationArtifactCreated: validTimestamp, - }, - signatureMediaType: joseTag, - }, - }, - { - name: "succeed to put signature manifest with cose media type", - expectErr: false, - expectDes: notation.Descriptor{ - MediaType: artifactspec.MediaTypeArtifactManifest, - Digest: digest.Digest(validDigestWithAlgo10), - Size: 364, - }, - expectManifest: SignatureManifest{ - Annotations: map[string]string{ - artifactspec.AnnotationArtifactCreated: validTimestamp, - }, - Blob: notation.Descriptor{ - MediaType: coseTag, - Digest: validDigestWithAlgo5, - }, - }, - args: args{ - reference: validReference, - signature: make([]byte, 0), - ctx: context.Background(), - remoteClient: mockRemoteClient{}, - annotations: map[string]string{ - artifactspec.AnnotationArtifactCreated: validTimestamp, - }, - signatureMediaType: coseTag, }, }, } @@ -582,18 +459,26 @@ func TestPutSignatureManifest(t *testing.T) { t.Run(tt.name, func(t *testing.T) { args := tt.args ref, _ := registry.ParseReference(args.reference) - client := NewRepositoryClient(args.remoteClient, ref, args.plainHttp) + client := newRepositoryClient(args.remoteClient, ref, args.plainHttp) - des, manifest, err := client.PutSignatureManifest(args.ctx, args.signature, args.signatureMediaType, args.subjectManifest, args.annotations) + des, _, err := client.PushSignature(args.ctx, args.signature, args.signatureMediaType, args.subjectManifest, args.annotations) if (err != nil) != tt.expectErr { t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) } if !reflect.DeepEqual(des, tt.expectDes) { t.Errorf("expect descriptor: %+v, got %+v", tt.expectDes, des) } - if !reflect.DeepEqual(manifest, tt.expectManifest) { - t.Errorf("expect manifest: %+v, got %+v", tt.expectManifest, manifest) - } }) } } + +// newRepositoryClient creates a new repository client. +func newRepositoryClient(client remote.Client, ref registry.Reference, plainHTTP bool) *repositoryClient { + return &repositoryClient{ + Repository: &remote.Repository{ + Client: client, + Reference: ref, + PlainHTTP: plainHTTP, + }, + } +} diff --git a/verification/verifier.go b/verification/verifier.go index 860b4b98..35c46b89 100644 --- a/verification/verifier.go +++ b/verification/verifier.go @@ -7,9 +7,9 @@ import ( "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/internal/registry" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/plugin/manager" - "github.com/notaryproject/notation-go/registry" ) type Verifier struct { diff --git a/verification/verifier_helpers.go b/verification/verifier_helpers.go index a9a1415d..b6825f97 100644 --- a/verification/verifier_helpers.go +++ b/verification/verifier_helpers.go @@ -10,8 +10,8 @@ import ( "time" "github.com/notaryproject/notation-core-go/signature" + "github.com/notaryproject/notation-go/internal/registry" "github.com/notaryproject/notation-go/plugin" - "github.com/notaryproject/notation-go/registry" sig "github.com/notaryproject/notation-go/signature" ) diff --git a/verification/verifier_test.go b/verification/verifier_test.go index b2b31879..ede639f3 100644 --- a/verification/verifier_test.go +++ b/verification/verifier_test.go @@ -11,9 +11,9 @@ import ( "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/internal/mock" + "github.com/notaryproject/notation-go/internal/registry" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/plugin/manager" - "github.com/notaryproject/notation-go/registry" _ "github.com/notaryproject/notation-core-go/signature/cose" _ "github.com/notaryproject/notation-core-go/signature/jws"