Skip to content

Commit

Permalink
Updating APIs
Browse files Browse the repository at this point in the history
Signed-off-by: Pritesh Bandi <[email protected]>
  • Loading branch information
priteshbandi committed Mar 3, 2024
1 parent b0b6c9b commit 6532bba
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 138 deletions.
95 changes: 61 additions & 34 deletions notation.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import (
orasRegistry "oras.land/oras-go/v2/registry"

"github.com/notaryproject/notation-core-go/signature"
"github.com/notaryproject/notation-core-go/signature/cose"
"github.com/notaryproject/notation-core-go/signature/jws"
"github.com/notaryproject/notation-go/internal/envelope"
"github.com/notaryproject/notation-go/log"
"github.com/notaryproject/notation-go/registry"
Expand All @@ -44,7 +46,7 @@ var reservedAnnotationPrefixes = [...]string{"io.cncf.notary"}
// SignerSignOptions contains parameters for Signer.Sign.
type SignerSignOptions struct {
// SignatureMediaType is the envelope type of the signature.
// Currently both `application/jose+json` and `application/cose` are
// Currently, both `application/jose+json` and `application/cose` are
// supported.
SignatureMediaType string

Expand All @@ -59,19 +61,34 @@ type SignerSignOptions struct {
SigningAgent string
}

// Signer is a generic interface for signing an artifact.
// Signer is a generic interface for signing an OCI artifact.
// The interface allows signing with local or remote keys,
// and packing in various signature formats.
type Signer interface {
// Sign signs the artifact described by its descriptor,
// Sign signs the OCI artifact described by its descriptor,
// and returns the signature and SignerInfo.
Sign(ctx context.Context, desc ocispec.Descriptor, opts SignerSignOptions) ([]byte, *signature.SignerInfo, error)
}

// SignBlobOptions contains parameters for notation.SignBlob.
type SignBlobOptions struct {
SignerSignOptions

ContentMediaType string
// UserMetadata contains key-value pairs that are added to the signature
// payload
UserMetadata map[string]string
}

type BlobDescriptorGenerator func(digest.Algorithm) (ocispec.Descriptor, error)

// BlobSigner is a generic interface for signing arbitrary data.
// The interface allows signing with local or remote keys,
// and packing in various signature formats.
type BlobSigner interface {
// SignBlob signs the artifact described by its descriptor,
// and returns the signature and SignerInfo.
SignBlob(ctx context.Context, reader io.Reader, opts SignBlobOptions) ([]byte, *signature.SignerInfo, error)
// SignBlob signs the descriptor returned by genDesc ,
// and returns the signature and SignerInfo
SignBlob(ctx context.Context, genDesc BlobDescriptorGenerator, opts SignerSignOptions) ([]byte, *signature.SignerInfo, error)
}

// signerAnnotation facilitates return of manifest annotations by signers
Expand All @@ -94,20 +111,10 @@ type SignOptions struct {
UserMetadata map[string]string
}

type SignBlobOptions struct {
SignerSignOptions

ContentMediaType string

// UserMetadata contains key-value pairs that are added to the signature
// payload
UserMetadata map[string]string
}

// Sign signs the artifact and push the signature to the Repository.
// Sign signs the OCI artifact and push the signature to the Repository.
// The descriptor of the sign content is returned upon successful signing.
func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts SignOptions) (ocispec.Descriptor, error) {
// sanity checks
// sanity check
if err := validate(signer, signOpts.SignerSignOptions); err != nil {
return ocispec.Descriptor{}, err
}
Expand Down Expand Up @@ -165,41 +172,61 @@ func Sign(ctx context.Context, signer Signer, repo registry.Repository, signOpts
return targetDesc, nil
}

func SignBlob(ctx context.Context, signer BlobSigner, reader io.Reader, signOpts SignBlobOptions) ([]byte, error) {
// SignBlob signs the arbitrary data and returns the signature
func SignBlob(ctx context.Context, signer BlobSigner, reader io.Reader, signBlobOpts SignBlobOptions) ([]byte, *signature.SignerInfo, error) {
// sanity checks
if err := validate(signer, signOpts.SignerSignOptions); err != nil {
return nil, err
if err := validate(signer, signBlobOpts.SignerSignOptions); err != nil {
return nil, nil, err
}

if reader == nil {
return nil, errors.New("reader cannot be nil")
return nil, nil, errors.New("reader cannot be nil")
}
if signOpts.ContentMediaType != "" {
if _, _, err := mime.ParseMediaType(signOpts.ContentMediaType); err != nil {
return nil, fmt.Errorf("invalid content media-type '%s': %v", signOpts.ContentMediaType, err)
}

if signBlobOpts.ContentMediaType == "" {
return nil, nil, errors.New("content media-type cannot be empty")
}

sig, _, err := signer.SignBlob(ctx, reader, signOpts)
if err != nil {
return nil, err
if _, _, err := mime.ParseMediaType(signBlobOpts.ContentMediaType); err != nil {
return nil, nil, fmt.Errorf("invalid content media-type '%s': %v", signBlobOpts.ContentMediaType, err)
}

getDescFun := func(hashAlgo digest.Algorithm) (ocispec.Descriptor, error) {
h := hashAlgo.Hash()
bytes, err := io.Copy(hashAlgo.Hash(), reader)
if err != nil {
return ocispec.Descriptor{}, err
}

targetDesc := ocispec.Descriptor{
MediaType: signBlobOpts.ContentMediaType,
Digest: digest.NewDigest(hashAlgo, h),
Size: bytes,
}
return addUserMetadataToDescriptor(ctx, targetDesc, signBlobOpts.UserMetadata)
}

return sig, nil
return signer.SignBlob(ctx, getDescFun, signBlobOpts.SignerSignOptions)
}

func validate(signer any, signOpts SignerSignOptions) error {
if signer == nil {
return errors.New("signer cannot be nil")
}
if signOpts.ExpiryDuration < 0 {
return fmt.Errorf("expiry duration cannot be a negative value")
return errors.New("expiry duration cannot be a negative value")
}
if signOpts.ExpiryDuration%time.Second != 0 {
return fmt.Errorf("expiry duration supports minimum granularity of seconds")
return errors.New("expiry duration supports minimum granularity of seconds")
}
if _, _, err := mime.ParseMediaType(signOpts.SignatureMediaType); err != nil {
return fmt.Errorf("invalid signature media-type '%s': %v", signOpts.SignatureMediaType, err)
if signOpts.SignatureMediaType == "" {
return errors.New("signature media-type cannot be empty")
}

if !(signOpts.SignatureMediaType == jws.MediaTypeEnvelope || signOpts.SignatureMediaType == cose.MediaTypeEnvelope) {
return fmt.Errorf("invalid signature media-type '%s'", signOpts.SignatureMediaType)
}

return nil
}

Expand Down
51 changes: 31 additions & 20 deletions notation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/notaryproject/notation-go/plugin"
"github.com/notaryproject/notation-go/registry"
"github.com/notaryproject/notation-go/verifier/trustpolicy"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

Expand Down Expand Up @@ -75,9 +76,7 @@ func TestSignBlobSuccess(t *testing.T) {
{"expiryInHours", 24 * time.Hour, "video/mp4", "", nil, nil},
{"oneSecondExpiry", 1 * time.Second, "video/mp4", "", nil, nil},
{"zeroExpiry", 0, "video/mp4", "", nil, nil},
//{"invalidContentMediaType", 1 * time.Second, "zap/zop/sop", nil, nil},
{"validContentType", 1 * time.Second, "video/mp4", "", nil, nil},
{"emptyContentType", 1 * time.Second, "", "", nil, nil},
{"emptyContentType", 1 * time.Second, "video/mp4", "someDummyAgent", map[string]string{"hi": "hello"}, map[string]string{"bye": "tata"}},
}
for _, tc := range testCases {
Expand All @@ -93,44 +92,51 @@ func TestSignBlobSuccess(t *testing.T) {
ContentMediaType: tc.mtype,
}

_, err := SignBlob(context.Background(), &dummySigner{}, reader, opts)
_, _, err := SignBlob(context.Background(), &dummySigner{}, reader, opts)
if err != nil {
b.Fatalf("Sign failed with error: %v", err)
}
})
}
}

func TestSignError(t *testing.T) {
func TestSignBlobError(t *testing.T) {
reader := strings.NewReader("some content")
testCases := []struct {
name string
signer BlobSigner
dur time.Duration
rdr io.Reader
mtype string
name string
signer BlobSigner
dur time.Duration
rdr io.Reader
sigMType string
ctMType string
errMsg string
}{
{"negativeExpiry", &dummySigner{}, -1 * time.Second, nil, "video/mp4"},
{"milliSecExpiry", &dummySigner{}, 1 * time.Millisecond, nil, "video/mp4"},
{"invalidContentMediaType", &dummySigner{}, 1 * time.Second, reader, "video/mp4/zoping"},
{"nilReader", &dummySigner{}, 1 * time.Second, nil, "video/mp4"},
{"nilSigner", nil, 1 * time.Second, reader, "video/mp4"},
{"signerError", &dummySigner{fail: true}, 1 * time.Second, reader, "video/mp4"},
{"negativeExpiry", &dummySigner{}, -1 * time.Second, nil, "video/mp4", jws.MediaTypeEnvelope, "expiry duration cannot be a negative value"},
{"milliSecExpiry", &dummySigner{}, 1 * time.Millisecond, nil, "video/mp4", jws.MediaTypeEnvelope, "expiry duration supports minimum granularity of seconds"},
{"invalidContentMediaType", &dummySigner{}, 1 * time.Second, reader, "video/mp4/zoping", jws.MediaTypeEnvelope, "invalid content media-type 'video/mp4/zoping': mime: unexpected content after media subtype"},
{"emptyContentMediaType", &dummySigner{}, 1 * time.Second, reader, "", jws.MediaTypeEnvelope, "content media-type cannot be empty"},
{"invalidSignatureMediaType", &dummySigner{}, 1 * time.Second, reader, "", "", "content media-type cannot be empty"},
{"nilReader", &dummySigner{}, 1 * time.Second, nil, "video/mp4", jws.MediaTypeEnvelope, "reader cannot be nil"},
{"nilSigner", nil, 1 * time.Second, reader, "video/mp4", jws.MediaTypeEnvelope, "signer cannot be nil"},
{"signerError", &dummySigner{fail: true}, 1 * time.Second, reader, "video/mp4", jws.MediaTypeEnvelope, "expected SignBlob failure"},
}
for _, tc := range testCases {
t.Run(tc.name, func(b *testing.T) {
t.Run(tc.name, func(t *testing.T) {
opts := SignBlobOptions{
SignerSignOptions: SignerSignOptions{
SignatureMediaType: jws.MediaTypeEnvelope,
ExpiryDuration: tc.dur,
PluginConfig: nil,
},
ContentMediaType: tc.mtype,
ContentMediaType: tc.sigMType,
}

_, err := SignBlob(context.Background(), tc.signer, tc.rdr, opts)
_, _, err := SignBlob(context.Background(), tc.signer, tc.rdr, opts)
if err == nil {
b.Error("expected error but didnt found")
t.Fatalf("expected error but didnt found")
}
if err.Error() != tc.errMsg {
t.Fatalf("expected err message to be '%s' but found '%s'", tc.errMsg, err.Error())
}
})
}
Expand Down Expand Up @@ -414,11 +420,16 @@ func (s *dummySigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts Si
}, nil
}

func (s *dummySigner) SignBlob(ctx context.Context, reader io.Reader, opts SignBlobOptions) ([]byte, *signature.SignerInfo, error) {
func (s *dummySigner) SignBlob(_ context.Context, descGenFunc BlobDescriptorGenerator, _ SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
if s.fail {
return nil, nil, errors.New("expected SignBlob failure")
}

_, err := descGenFunc(digest.SHA384)
if err != nil {
return nil, nil, err
}

return []byte("ABC"), &signature.SignerInfo{
SignedAttributes: signature.SignedAttributes{
SigningTime: time.Now(),
Expand Down
Loading

0 comments on commit 6532bba

Please sign in to comment.