internal/signer/signer.go (102 lines of code) (raw):

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0 // // or in the "license" file accompanying this file. This file 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 signer provides functionality to generate signatures using AWS Signer // in accordance with the NotaryProject Plugin contract. package signer import ( "context" "errors" "fmt" "strings" "github.com/aws/aws-signer-notation-plugin/internal/client" "github.com/aws/aws-signer-notation-plugin/internal/logger" "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/aws/transport/http" "github.com/aws/aws-sdk-go-v2/service/signer" "github.com/aws/smithy-go" "github.com/notaryproject/notation-plugin-framework-go/plugin" ) const ( mediaTypeJwsEnvelope = "application/jose+json" errorMsgMalformedSigningProfileFmt = "%s is not a valid AWS Signer signing profile or signing profile version ARN." errorMSGExpiryPassed = "AWSSigner plugin doesn't support -e (--expiry) argument. Please use signing profile to set signature expiry." ) // Signer generates signature generated using AWS Signer. type Signer struct { awssigner client.Interface } // New returns Signer given an AWS Signer client. func New(s client.Interface) *Signer { return &Signer{awssigner: s} } // GenerateEnvelope generates signature envelope by calling AWS Signer func (s *Signer) GenerateEnvelope(ctx context.Context, request *plugin.GenerateEnvelopeRequest) (*plugin.GenerateEnvelopeResponse, error) { log := logger.GetLogger(ctx) log.Debug("validating request") if err := validate(request); err != nil { return nil, err } log.Debug("succeeded request validation") log.Debug("validating signing profile") signingProfileArn, err := arn.Parse(request.KeyID) if err != nil { return nil, plugin.NewValidationErrorf(errorMsgMalformedSigningProfileFmt, request.KeyID) } signingProfileName, err := getProfileName(signingProfileArn) if err != nil { return nil, err } log.Debug("succeeded signing profile validation") log.Debug("calling AWS Signer's SignPayload API") input := &signer.SignPayloadInput{ Payload: request.Payload, ProfileName: &signingProfileName, PayloadFormat: &request.PayloadType, ProfileOwner: &signingProfileArn.AccountID, } output, err := s.awssigner.SignPayload(ctx, input) if err != nil { log.Debugf("failed AWS Signer's SignPayload API call with error: %v", err) return nil, parseAwsError(err) } res := &plugin.GenerateEnvelopeResponse{ SignatureEnvelope: output.Signature, SignatureEnvelopeType: request.SignatureEnvelopeType, Annotations: output.Metadata} log.Debugf("succeeded AWS Signer's SignPayload API call. output: %s", res) return res, nil } func getProfileName(arn arn.ARN) (string, error) { //resource name will be in format /signing-profiles/ProfileName profileArnParts := strings.Split(arn.Resource, "/") if len(profileArnParts) != 3 { return "", plugin.NewValidationErrorf(errorMsgMalformedSigningProfileFmt, arn) } return profileArnParts[2], nil } func validate(request *plugin.GenerateEnvelopeRequest) error { if request.ExpiryDurationInSeconds != 0 { return plugin.NewError(plugin.ErrorCodeValidation, errorMSGExpiryPassed) } if request.ContractVersion != plugin.ContractVersion { return plugin.NewUnsupportedContractVersionError(request.ContractVersion) } if request.SignatureEnvelopeType != mediaTypeJwsEnvelope { return plugin.NewUnsupportedError(fmt.Sprintf("envelope type %q", request.SignatureEnvelopeType)) } return nil } // ParseAwsError converts error from SignPayload API to plugin error func parseAwsError(err error) *plugin.Error { var apiError smithy.APIError if errors.As(err, &apiError) { var re *http.ResponseError errMsgSuffix := "" if errors.As(err, &re) { errMsgSuffix = fmt.Sprintf(" RequestID: %s.", re.ServiceRequestID()) } errMsg := fmt.Sprintf("Failed to call AWSSigner. Error: %s.%s", apiError.ErrorMessage(), errMsgSuffix) switch apiError.ErrorCode() { case "NotFoundException", "ResourceNotFoundException", "ValidationException", "BadRequestException": return plugin.NewValidationError(errMsg) case "ThrottlingException": return plugin.NewError(plugin.ErrorCodeThrottled, errMsg) case "AccessDeniedException": return plugin.NewError(plugin.ErrorCodeAccessDenied, errMsg) default: return plugin.NewGenericError(errMsg) } } return plugin.NewGenericError(err.Error()) }