Skip to content

Commit

Permalink
Add additional header validation for payload
Browse files Browse the repository at this point in the history
Signed-off-by: Jonathan Donas <jddonas@amazon.com>
  • Loading branch information
jondonas committed Oct 31, 2022
1 parent 82ec47d commit af0a272
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 13 deletions.
42 changes: 30 additions & 12 deletions signature/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (s *pluginSigner) Sign(ctx context.Context, desc notation.Descriptor, opts
)
}
if metadata.HasCapability(plugin.CapabilitySignatureGenerator) {
return s.generateSignature(ctx, desc, opts)
return s.generateSignature(ctx, desc, opts, metadata)
} else if metadata.HasCapability(plugin.CapabilityEnvelopeGenerator) {
return s.generateSignatureEnvelope(ctx, desc, opts)
}
Expand Down Expand Up @@ -95,7 +95,7 @@ func (s *pluginSigner) describeKey(ctx context.Context, config map[string]string
return resp, nil
}

func (s *pluginSigner) generateSignature(ctx context.Context, desc notation.Descriptor, opts notation.SignOptions) ([]byte, error) {
func (s *pluginSigner) generateSignature(ctx context.Context, desc notation.Descriptor, opts notation.SignOptions, metadata *plugin.Metadata) ([]byte, error) {
config := s.mergeConfig(opts.PluginConfig)
// Get key info.
key, err := s.describeKey(ctx, config)
Expand Down Expand Up @@ -123,6 +123,7 @@ func (s *pluginSigner) generateSignature(ctx context.Context, desc notation.Desc
}
extProvider.prepareSigning(config, ks)
}
signingAgentId := fmt.Sprintf("Signing Agent: %s - Signing Plugin Name: %s - Signing Plugin Version: %s", notation.SigningAgent, metadata.Name, metadata.Version)
signReq := &signature.SignRequest{
Payload: signature.Payload{
ContentType: notation.MediaTypePayloadV1,
Expand All @@ -132,7 +133,7 @@ func (s *pluginSigner) generateSignature(ctx context.Context, desc notation.Desc
SigningTime: time.Now(),
ExtendedSignedAttributes: nil,
SigningScheme: signature.SigningSchemeX509,
SigningAgent: notation.SigningAgent, // TODO: include external signing plugin's name and version. https://github.com/notaryproject/notation-go/issues/80
SigningAgent: signingAgentId,
}

if !opts.Expiry.IsZero() {
Expand Down Expand Up @@ -220,25 +221,42 @@ func (s *pluginSigner) generateSignatureEnvelope(ctx context.Context, desc notat
return nil, err
}

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

// TODO: Verify plugin didnot add any additional top level payload attributes. https://github.com/notaryproject/notation-go/issues/80
if !descriptorPartialEqual(desc, signedPayload.TargetArtifact) {
if !isPayloadDescriptorValid(content, desc, signedPayload.TargetArtifact) {
return nil, errors.New("descriptor subject has changed")
}

return resp.SignatureEnvelope, nil
}

// descriptorPartialEqual checks if the both descriptors point to the same resource
// and that newDesc hasn't replaced or overridden existing annotations.
func descriptorPartialEqual(original, newDesc notation.Descriptor) bool {
if !original.Equal(newDesc) {
return false
}
func isPayloadDescriptorValid(originalContent []byte, originalDesc, newDesc notation.Descriptor) bool {
return originalDesc.Equal(newDesc) &&
!isDescriptorAttributeAdded(originalContent) &&
descriptorAnnotationsPartialEqual(originalDesc, newDesc)
}

// isDescriptorAttributeAdded checks that the unmarshalled json content does not have any unexpected keys added
func isDescriptorAttributeAdded(content []byte) bool {
var targetArtifactMap map[string]interface{}
_ = json.Unmarshal(content, &targetArtifactMap)
descriptor := targetArtifactMap["targetArtifact"].(map[string]interface{})

// Explicitly remove expected keys to check if any are left over
delete(descriptor, "mediaType")
delete(descriptor, "digest")
delete(descriptor, "size")
delete(descriptor, "annotations")

return len(targetArtifactMap) != 1 || len(descriptor) != 0
}

// descriptorAnnotationsPartialEqual checks that newDesc hasn't replaced or overridden existing annotations.
func descriptorAnnotationsPartialEqual(original, newDesc notation.Descriptor) bool {
// Plugins may append additional annotations but not replace/override existing.
for k, v := range original.Annotations {
if v2, ok := newDesc.Annotations[k]; !ok || v != v2 {
Expand Down
1 change: 0 additions & 1 deletion signature/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
)

// NewSignerFromFiles creates a signer from key, certificate files
// TODO: Add tests for this method. https://github.com/notaryproject/notation-go/issues/80
func NewSignerFromFiles(keyPath, certPath, envelopeMediaType string) (notation.Signer, error) {
if keyPath == "" {
return nil, errors.New("key path not specified")
Expand Down

0 comments on commit af0a272

Please sign in to comment.