Skip to content

Commit

Permalink
Updating APIs
Browse files Browse the repository at this point in the history
Signed-off-by: Pritesh Bandi <priteshbandi@gmail.com>
  • Loading branch information
priteshbandi committed Mar 3, 2024
1 parent b0b6c9b commit f58ed61
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 78 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
8 changes: 4 additions & 4 deletions notation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestSignBlobSuccess(t *testing.T) {
{"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, "", "", 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,7 +93,7 @@ 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)
}
Expand Down Expand Up @@ -128,7 +128,7 @@ func TestSignError(t *testing.T) {
ContentMediaType: tc.mtype,
}

_, 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")
}
Expand Down Expand Up @@ -414,7 +414,7 @@ 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(ctx context.Context, genDesc BlobDescriptorGenerator, opts SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
if s.fail {
return nil, nil, errors.New("expected SignBlob failure")
}
Expand Down
33 changes: 18 additions & 15 deletions signer/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ func (s *PluginSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts n
return nil, nil, err
}

logger.Debugf("Using plugin %v with capabilities %v to sign artifact %v in signature media type %v", metadata.Name, metadata.Capabilities, desc.Digest, opts.SignatureMediaType)
logger.Debugf("Using plugin %v with capabilities %v to sign oci artifact %v in signature media type %v", metadata.Name, metadata.Capabilities, desc.Digest, opts.SignatureMediaType)
if metadata.HasCapability(proto.CapabilitySignatureGenerator) {
// Get key info and validate.
logger.Debug("Invoking plugin's describe-key command")
ks, err := s.getKeySpec(ctx, mergedConfig)
if err != nil {
return nil, nil, err
Expand All @@ -100,7 +100,7 @@ func (s *PluginSigner) Sign(ctx context.Context, desc ocispec.Descriptor, opts n
}

// SignBlob signs the arbitrary data and returns the marshalled envelope.
func (s *PluginSigner) SignBlob(ctx context.Context, reader io.Reader, opts notation.SignBlobOptions) ([]byte, *signature.SignerInfo, error) {
func (s *PluginSigner) SignBlob(ctx context.Context, blobGenfunc notation.BlobDescriptorGenerator, opts notation.SignerSignOptions) ([]byte, *signature.SignerInfo, error) {
logger := log.GetLogger(ctx)
mergedConfig := s.mergeConfig(opts.PluginConfig)

Expand All @@ -110,30 +110,33 @@ func (s *PluginSigner) SignBlob(ctx context.Context, reader io.Reader, opts nota
return nil, nil, err
}

// Get key info and validate.
logger.Debug("Invoking plugin's describe-key command")
ks, err := s.getKeySpec(ctx, mergedConfig)
if err != nil {
return nil, nil, err
}

desc, err := getDescriptor(reader, opts.ContentMediaType, ks)
digestAlg, ok := algorithms[ks.SignatureAlgorithm().Hash()]
if !ok {
return nil, nil, fmt.Errorf("unknown hashing algo %v", ks.SignatureAlgorithm())
}

// get descriptor to sign
desc, err := blobGenfunc(digestAlg)
if err != nil {
return nil, nil, err
}

logger.Debugf("Using plugin %v with capabilities %v to sign artifact using descriptor %+v", metadata.Name, metadata.Capabilities, desc)
logger.Debugf("Using plugin %v with capabilities %v to sign blob using descriptor %+v", metadata.Name, metadata.Capabilities, desc)
if metadata.HasCapability(proto.CapabilitySignatureGenerator) {
return s.generateSignature(ctx, desc, opts.SignerSignOptions, ks, metadata)
return s.generateSignature(ctx, desc, opts, ks, metadata)
} else if metadata.HasCapability(proto.CapabilityEnvelopeGenerator) {
return s.generateSignatureEnvelope(ctx, desc, opts.SignerSignOptions)
return s.generateSignatureEnvelope(ctx, desc, opts)
}
return nil, nil, fmt.Errorf("plugin does not have signing capabilities")
}

func (s *PluginSigner) getKeySpec(ctx context.Context, config map[string]string) (signature.KeySpec, error) {
// Get key info and validate.
logger := log.GetLogger(ctx)
logger.Debug("Invoking plugin's describe-key command")
descKeyResp, err := s.describeKey(ctx, config)
if err != nil {
return signature.KeySpec{}, err
Expand All @@ -149,7 +152,7 @@ func (s *PluginSigner) getKeySpec(ctx context.Context, config map[string]string)
func (s *PluginSigner) generateSignature(ctx context.Context, desc ocispec.Descriptor, opts notation.SignerSignOptions, ks signature.KeySpec, metadata *proto.GetMetadataResponse) ([]byte, *signature.SignerInfo, error) {
logger := log.GetLogger(ctx)
logger.Debug("Generating signature by plugin")
genericSigner := genericSigner{
genericSigner := GenericSigner{
Signer: &pluginPrimitiveSigner{
ctx: ctx,
plugin: s.plugin,
Expand Down Expand Up @@ -207,17 +210,17 @@ func (s *PluginSigner) generateSignatureEnvelope(ctx context.Context, desc ocisp
return nil, nil, err
}

content := envContent.Payload.Content
cnt := envContent.Payload.Content
var signedPayload envelope.Payload
if err = json.Unmarshal(content, &signedPayload); err != nil {
if err = json.Unmarshal(cnt, &signedPayload); err != nil {
return nil, nil, fmt.Errorf("signed envelope payload can't be unmarshalled: %w", err)
}

if !isPayloadDescriptorValid(desc, signedPayload.TargetArtifact) {
return nil, nil, fmt.Errorf("during signing descriptor subject has changed from %+v to %+v", desc, signedPayload.TargetArtifact)
}

if unknownAttributes := areUnknownAttributesAdded(content); len(unknownAttributes) != 0 {
if unknownAttributes := areUnknownAttributesAdded(cnt); len(unknownAttributes) != 0 {
return nil, nil, fmt.Errorf("during signing, following unknown attributes were added to subject descriptor: %+q", unknownAttributes)
}

Expand Down
20 changes: 13 additions & 7 deletions signer/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/notaryproject/notation-go/internal/envelope"
"github.com/notaryproject/notation-go/plugin"
"github.com/notaryproject/notation-go/plugin/proto"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

Expand Down Expand Up @@ -70,6 +71,16 @@ type mockPlugin struct {
keySpec signature.KeySpec
}

func getDescriptorFunc(throwError bool) func(hashAlgo digest.Algorithm) (ocispec.Descriptor, error) {
return func(hashAlgo digest.Algorithm) (ocispec.Descriptor, error) {
if throwError {
return ocispec.Descriptor{}, errors.New("")
}
return validSignDescriptor, nil
}

}

func newMockPlugin(key crypto.PrivateKey, certs []*x509.Certificate, keySpec signature.KeySpec) *mockPlugin {
return &mockPlugin{
key: key,
Expand Down Expand Up @@ -314,13 +325,8 @@ func TestPluginSigner_SignBlob_Valid(t *testing.T) {
pluginSigner := PluginSigner{
plugin: newMockPlugin(keyCert.key, keyCert.certs, keySpec),
}
sOpts := notation.SignBlobOptions{
SignerSignOptions: notation.SignerSignOptions{
SignatureMediaType: envelopeType,
},
}

data, signerInfo, err := pluginSigner.SignBlob(context.Background(), strings.NewReader("some content"), sOpts)
validSignOpts.SignatureMediaType = envelopeType
data, signerInfo, err := pluginSigner.SignBlob(context.Background(), getDescriptorFunc(false), validSignOpts)
basicSignTest(t, &pluginSigner, envelopeType, data, signerInfo, err)
})
}
Expand Down
Loading

0 comments on commit f58ed61

Please sign in to comment.