github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/jose/jws.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package jose
     8  
     9  import (
    10  	"encoding/base64"
    11  	"errors"
    12  	"fmt"
    13  	"strings"
    14  
    15  	"github.com/go-jose/go-jose/v3/json"
    16  )
    17  
    18  const (
    19  	jwsPartsCount    = 3
    20  	jwsHeaderPart    = 0
    21  	jwsPayloadPart   = 1
    22  	jwsSignaturePart = 2
    23  )
    24  
    25  // JSONWebSignature defines JSON Web Signature (https://tools.ietf.org/html/rfc7515)
    26  type JSONWebSignature struct {
    27  	ProtectedHeaders   Headers
    28  	UnprotectedHeaders Headers
    29  	Payload            []byte
    30  
    31  	signature   []byte
    32  	joseHeaders Headers
    33  }
    34  
    35  // SignatureVerifier makes verification of JSON Web Signature.
    36  type SignatureVerifier interface {
    37  	// Verify verifies JWS based on the signing input.
    38  	Verify(joseHeaders Headers, payload, signingInput, signature []byte) error
    39  }
    40  
    41  // SignatureVerifierFunc is a function wrapper for SignatureVerifier.
    42  type SignatureVerifierFunc func(joseHeaders Headers, payload, signingInput, signature []byte) error
    43  
    44  // Verify verifies JWS signature.
    45  func (s SignatureVerifierFunc) Verify(joseHeaders Headers, payload, signingInput, signature []byte) error {
    46  	return s(joseHeaders, payload, signingInput, signature)
    47  }
    48  
    49  // DefaultSigningInputVerifier is a SignatureVerifier that generates the signing input
    50  // from the given headers and payload, instead of using the signing input parameter.
    51  type DefaultSigningInputVerifier func(joseHeaders Headers, payload, signingInput, signature []byte) error
    52  
    53  // Verify verifies JWS signature.
    54  func (s DefaultSigningInputVerifier) Verify(joseHeaders Headers, payload, _, signature []byte) error {
    55  	signingInputData, err := signingInput(joseHeaders, "", payload)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	return s(joseHeaders, payload, signingInputData, signature)
    61  }
    62  
    63  // CompositeAlgSigVerifier defines composite signature verifier based on the algorithm
    64  // taken from JOSE header alg.
    65  type CompositeAlgSigVerifier struct {
    66  	verifierByAlg map[string]SignatureVerifier
    67  }
    68  
    69  // AlgSignatureVerifier defines verifier for particular signature algorithm.
    70  type AlgSignatureVerifier struct {
    71  	Alg      string
    72  	Verifier SignatureVerifier
    73  }
    74  
    75  // NewCompositeAlgSigVerifier creates a new CompositeAlgSigVerifier.
    76  func NewCompositeAlgSigVerifier(v AlgSignatureVerifier, vOther ...AlgSignatureVerifier) *CompositeAlgSigVerifier {
    77  	verifierByAlg := make(map[string]SignatureVerifier, 1+len(vOther))
    78  	verifierByAlg[v.Alg] = v.Verifier
    79  
    80  	for _, v := range vOther {
    81  		verifierByAlg[v.Alg] = v.Verifier
    82  	}
    83  
    84  	return &CompositeAlgSigVerifier{
    85  		verifierByAlg: verifierByAlg,
    86  	}
    87  }
    88  
    89  // Verify verifiers JWS signature.
    90  func (v *CompositeAlgSigVerifier) Verify(joseHeaders Headers, payload, signingInput, signature []byte) error {
    91  	alg, ok := joseHeaders.Algorithm()
    92  	if !ok {
    93  		return errors.New("'alg' JOSE header is not present")
    94  	}
    95  
    96  	verifier, ok := v.verifierByAlg[alg]
    97  	if !ok {
    98  		return fmt.Errorf("no verifier found for %s algorithm", alg)
    99  	}
   100  
   101  	return verifier.Verify(joseHeaders, payload, signingInput, signature)
   102  }
   103  
   104  // Signer defines JWS Signer interface. It makes signing of data and provides custom JWS headers relevant to the signer.
   105  type Signer interface {
   106  	// Sign signs.
   107  	Sign(data []byte) ([]byte, error)
   108  
   109  	// Headers provides JWS headers. "alg" header must be provided (see https://tools.ietf.org/html/rfc7515#section-4.1)
   110  	Headers() Headers
   111  }
   112  
   113  // NewJWS creates JSON Web Signature.
   114  func NewJWS(protectedHeaders, unprotectedHeaders Headers, payload []byte, signer Signer) (*JSONWebSignature, error) {
   115  	headers := mergeHeaders(protectedHeaders, signer.Headers())
   116  	jws := &JSONWebSignature{
   117  		ProtectedHeaders:   headers,
   118  		UnprotectedHeaders: unprotectedHeaders,
   119  		Payload:            payload,
   120  		joseHeaders:        headers,
   121  	}
   122  
   123  	signature, err := sign(jws.joseHeaders, payload, signer)
   124  	if err != nil {
   125  		return nil, fmt.Errorf("sign JWS: %w", err)
   126  	}
   127  
   128  	jws.signature = signature
   129  
   130  	return jws, nil
   131  }
   132  
   133  // SerializeCompact makes JWS Compact Serialization (https://tools.ietf.org/html/rfc7515#section-7.1)
   134  func (s JSONWebSignature) SerializeCompact(detached bool) (string, error) {
   135  	byteHeaders, err := json.Marshal(s.joseHeaders)
   136  	if err != nil {
   137  		return "", fmt.Errorf("marshal JWS JOSE Headers: %w", err)
   138  	}
   139  
   140  	b64Headers := base64.RawURLEncoding.EncodeToString(byteHeaders)
   141  
   142  	b64Payload := ""
   143  	if !detached {
   144  		b64Payload = base64.RawURLEncoding.EncodeToString(s.Payload)
   145  	}
   146  
   147  	b64Signature := base64.RawURLEncoding.EncodeToString(s.signature)
   148  
   149  	return fmt.Sprintf("%s.%s.%s",
   150  		b64Headers,
   151  		b64Payload,
   152  		b64Signature), nil
   153  }
   154  
   155  // Signature returns a copy of JWS signature.
   156  func (s JSONWebSignature) Signature() []byte {
   157  	if s.signature == nil {
   158  		return nil
   159  	}
   160  
   161  	sCopy := make([]byte, len(s.signature))
   162  	copy(sCopy, s.signature)
   163  
   164  	return sCopy
   165  }
   166  
   167  func mergeHeaders(h1, h2 Headers) Headers {
   168  	h := make(Headers, len(h1)+len(h2))
   169  
   170  	for k, v := range h2 {
   171  		h[k] = v
   172  	}
   173  
   174  	for k, v := range h1 {
   175  		h[k] = v
   176  	}
   177  
   178  	return h
   179  }
   180  
   181  func sign(joseHeaders Headers, payload []byte, signer Signer) ([]byte, error) {
   182  	err := checkJWSHeaders(joseHeaders)
   183  	if err != nil {
   184  		return nil, fmt.Errorf("check JOSE headers: %w", err)
   185  	}
   186  
   187  	sigInput, err := signingInput(joseHeaders, "", payload)
   188  	if err != nil {
   189  		return nil, fmt.Errorf("prepare JWS verification data: %w", err)
   190  	}
   191  
   192  	signature, err := signer.Sign(sigInput)
   193  	if err != nil {
   194  		return nil, fmt.Errorf("sign JWS verification data: %w", err)
   195  	}
   196  
   197  	return signature, nil
   198  }
   199  
   200  // jwsParseOpts holds options for the JWS Parsing.
   201  type jwsParseOpts struct {
   202  	detachedPayload []byte
   203  }
   204  
   205  // JWSParseOpt is the JWS Parser option.
   206  type JWSParseOpt func(opts *jwsParseOpts)
   207  
   208  // WithJWSDetachedPayload option is for definition of JWS detached payload.
   209  func WithJWSDetachedPayload(payload []byte) JWSParseOpt {
   210  	return func(opts *jwsParseOpts) {
   211  		opts.detachedPayload = payload
   212  	}
   213  }
   214  
   215  // ParseJWS parses serialized JWS. Currently only JWS Compact Serialization parsing is supported.
   216  func ParseJWS(jws string, verifier SignatureVerifier, opts ...JWSParseOpt) (*JSONWebSignature, error) {
   217  	pOpts := &jwsParseOpts{}
   218  
   219  	for _, opt := range opts {
   220  		opt(pOpts)
   221  	}
   222  
   223  	if strings.HasPrefix(jws, "{") {
   224  		// TODO support JWS JSON serialization format
   225  		//  https://github.com/hyperledger/aries-framework-go/issues/1331
   226  		return nil, errors.New("JWS JSON serialization is not supported")
   227  	}
   228  
   229  	return parseCompacted(jws, verifier, pOpts)
   230  }
   231  
   232  // IsCompactJWS checks weather input is a compact JWS (based on https://tools.ietf.org/html/rfc7516#section-9)
   233  func IsCompactJWS(s string) bool {
   234  	parts := strings.Split(s, ".")
   235  
   236  	return len(parts) == jwsPartsCount
   237  }
   238  
   239  func parseCompacted(jwsCompact string, verifier SignatureVerifier, opts *jwsParseOpts) (*JSONWebSignature, error) {
   240  	parts := strings.Split(jwsCompact, ".")
   241  	if len(parts) != jwsPartsCount {
   242  		return nil, errors.New("invalid JWS compact format")
   243  	}
   244  
   245  	joseHeaders, err := parseCompactedHeaders(parts)
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  
   250  	payload, err := parseCompactedPayload(parts[jwsPayloadPart], opts)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  
   255  	sInput, err := signingInput(joseHeaders, parts[jwsHeaderPart], payload)
   256  	if err != nil {
   257  		return nil, fmt.Errorf("build signing input: %w", err)
   258  	}
   259  
   260  	signature, err := base64.RawURLEncoding.DecodeString(parts[jwsSignaturePart])
   261  	if err != nil {
   262  		return nil, fmt.Errorf("decode base64 signature: %w", err)
   263  	}
   264  
   265  	err = verifier.Verify(joseHeaders, payload, sInput, signature)
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  
   270  	return &JSONWebSignature{
   271  		ProtectedHeaders: joseHeaders,
   272  		Payload:          payload,
   273  		signature:        signature,
   274  		joseHeaders:      joseHeaders,
   275  	}, nil
   276  }
   277  
   278  func parseCompactedPayload(jwsPayload string, opts *jwsParseOpts) ([]byte, error) {
   279  	if len(opts.detachedPayload) > 0 {
   280  		return opts.detachedPayload, nil
   281  	}
   282  
   283  	payload, err := base64.RawURLEncoding.DecodeString(jwsPayload)
   284  	if err != nil {
   285  		return nil, fmt.Errorf("decode base64 payload: %w", err)
   286  	}
   287  
   288  	return payload, nil
   289  }
   290  
   291  func parseCompactedHeaders(parts []string) (Headers, error) {
   292  	headersBytes, err := base64.RawURLEncoding.DecodeString(parts[jwsHeaderPart])
   293  	if err != nil {
   294  		return nil, fmt.Errorf("decode base64 header: %w", err)
   295  	}
   296  
   297  	var joseHeaders Headers
   298  
   299  	err = json.Unmarshal(headersBytes, &joseHeaders)
   300  	if err != nil {
   301  		return nil, fmt.Errorf("unmarshal JSON headers: %w", err)
   302  	}
   303  
   304  	err = checkJWSHeaders(joseHeaders)
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  
   309  	return joseHeaders, nil
   310  }
   311  
   312  func signingInput(headers Headers, header string, payload []byte) ([]byte, error) {
   313  	headersBytes, err := json.Marshal(headers)
   314  	if err != nil {
   315  		return nil, fmt.Errorf("serialize JWS headers: %w", err)
   316  	}
   317  
   318  	hBase64 := true
   319  
   320  	if b64, ok := headers[HeaderB64Payload]; ok {
   321  		if hBase64, ok = b64.(bool); !ok {
   322  			return nil, errors.New("invalid b64 header")
   323  		}
   324  	}
   325  
   326  	// Will pass original header string for validation
   327  	headersStr := header
   328  
   329  	if headersStr == "" {
   330  		headersStr = base64.RawURLEncoding.EncodeToString(headersBytes)
   331  	}
   332  
   333  	var payloadStr string
   334  
   335  	if hBase64 {
   336  		payloadStr = base64.RawURLEncoding.EncodeToString(payload)
   337  	} else {
   338  		payloadStr = string(payload)
   339  	}
   340  
   341  	return []byte(fmt.Sprintf("%s.%s", headersStr, payloadStr)), nil
   342  }
   343  
   344  func checkJWSHeaders(headers Headers) error {
   345  	if _, ok := headers[HeaderAlgorithm]; !ok {
   346  		return fmt.Errorf("%s JWS header is not defined", HeaderAlgorithm)
   347  	}
   348  
   349  	return nil
   350  }
   351  
   352  func convertMapToValue(vOriginToBeMap, vDest interface{}) error {
   353  	if _, ok := vOriginToBeMap.(map[string]interface{}); !ok {
   354  		return errors.New("expected value to be a map")
   355  	}
   356  
   357  	mBytes, err := json.Marshal(vOriginToBeMap)
   358  	if err != nil {
   359  		return err
   360  	}
   361  
   362  	return json.Unmarshal(mBytes, vDest)
   363  }