github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/packer/authcrypt/pack.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package authcrypt
     8  
     9  import (
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"strings"
    14  
    15  	"github.com/google/tink/go/keyset"
    16  
    17  	"github.com/hyperledger/aries-framework-go/pkg/common/log"
    18  	cryptoapi "github.com/hyperledger/aries-framework-go/pkg/crypto"
    19  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/packer"
    20  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/transport"
    21  	"github.com/hyperledger/aries-framework-go/pkg/doc/jose"
    22  	"github.com/hyperledger/aries-framework-go/pkg/doc/jose/kid/resolver"
    23  	"github.com/hyperledger/aries-framework-go/pkg/kms"
    24  )
    25  
    26  // Package authcrypt includes a Packer implementation to build and parse JWE messages using Authcrypt. It allows sending
    27  // messages between parties with non-repudiation messages, ie the sender identity is revealed (and therefore
    28  // authenticated) to the recipient(s). The assumption of using this package is that public keys exchange has previously
    29  // occurred between the sender and the recipient(s).
    30  
    31  var logger = log.New("aries-framework/pkg/didcomm/packer/authcrypt")
    32  
    33  // Packer represents an Authcrypt Pack/Unpacker that outputs/reads Aries envelopes.
    34  type Packer struct {
    35  	kms           kms.KeyManager
    36  	encAlg        jose.EncAlg
    37  	cryptoService cryptoapi.Crypto
    38  	kidResolvers  []resolver.KIDResolver
    39  }
    40  
    41  // New will create a Packer instance to 'AuthCrypt' payloads for a given sender and list of recipients keys using
    42  // DIDComm typ V2 value (default envelope 'typ' protected header).
    43  // It opens thirdPartyKS store (or fetch cached one) that contains third party keys. This store must be
    44  // pre-populated with the sender key required by a recipient to Unpack a JWE envelope. It is not needed by the sender
    45  // (as the sender packs the envelope with its own key).
    46  // The returned Packer contains all the information required to pack and unpack payloads.
    47  func New(ctx packer.Provider, encAlg jose.EncAlg) (*Packer, error) {
    48  	err := validateEncAlg(encAlg)
    49  	if err != nil {
    50  		return nil, fmt.Errorf("authcrypt: %w", err)
    51  	}
    52  
    53  	k := ctx.KMS()
    54  	if k == nil {
    55  		return nil, errors.New("authcrypt: failed to create packer because KMS is empty")
    56  	}
    57  
    58  	c := ctx.Crypto()
    59  	if c == nil {
    60  		return nil, errors.New("authcrypt: failed to create packer because crypto service is empty")
    61  	}
    62  
    63  	vdrReg := ctx.VDRegistry()
    64  	if vdrReg == nil {
    65  		return nil, errors.New("authcrypt: failed to create packer because vdr registry is empty")
    66  	}
    67  
    68  	var kidResolvers []resolver.KIDResolver
    69  
    70  	kidResolvers = append(kidResolvers, &resolver.DIDKeyResolver{}, &resolver.DIDDocResolver{VDRRegistry: vdrReg})
    71  
    72  	return &Packer{
    73  		kms:           k,
    74  		encAlg:        encAlg,
    75  		cryptoService: c,
    76  		kidResolvers:  kidResolvers,
    77  	}, nil
    78  }
    79  
    80  func validateEncAlg(alg jose.EncAlg) error {
    81  	switch alg {
    82  	// authcrypt supports AES-CBC+HMAC-SHA algorithms only, anoncrypt supports the same and AES256-GCM.
    83  	case jose.A128CBCHS256, jose.A192CBCHS384ALG, jose.A256CBCHS384, jose.A256CBCHS512, jose.XC20P:
    84  		return nil
    85  	default:
    86  		return fmt.Errorf("unsupported content encrytpion algorithm: %v", alg)
    87  	}
    88  }
    89  
    90  // Pack will encode the payload argument with contentType argument
    91  // Using the protocol defined by the Authcrypt message of Aries RFC 0334
    92  // with the following arguments:
    93  // payload: the payload message that will be protected
    94  // senderID: the key id of the sender (stored in the KMS)
    95  // recipientsPubKeys: public keys.
    96  func (p *Packer) Pack( // nolint:gocyclo
    97  	contentType string, payload, senderID []byte, recipientsPubKeys [][]byte) ([]byte, error) {
    98  	if len(recipientsPubKeys) == 0 {
    99  		return nil, fmt.Errorf("authcrypt Pack: empty recipientsPubKeys")
   100  	}
   101  
   102  	recECKeys, aad, err := unmarshalRecipientKeys(recipientsPubKeys)
   103  	if err != nil {
   104  		return nil, fmt.Errorf("authcrypt Pack: failed to convert recipient keys: %w", err)
   105  	}
   106  
   107  	senderKID := string(senderID)
   108  	skid := senderKID
   109  
   110  	if idx := strings.Index(senderKID, "."); idx > 0 {
   111  		senderKID = senderKID[:idx] // senderKID is the kms kid.
   112  		skid = skid[idx+1:]         // skid as did:key or didDoc.KeyAgreement[].VerificationMethod.ID value.
   113  	}
   114  
   115  	kh, err := p.kms.Get(senderKID)
   116  	if err != nil {
   117  		return nil, fmt.Errorf("authcrypt Pack: failed to get sender key from KMS: %w", err)
   118  	}
   119  
   120  	sKH, ok := kh.(*keyset.Handle)
   121  	if !ok {
   122  		sKH = nil
   123  	}
   124  
   125  	jweEncrypter, err := jose.NewJWEEncrypt(p.encAlg, p.EncodingType(), contentType, skid,
   126  		sKH, recECKeys, p.cryptoService)
   127  	if err != nil {
   128  		return nil, fmt.Errorf("authcrypt Pack: failed to new JWEEncrypt instance: %w", err)
   129  	}
   130  
   131  	jwe, err := jweEncrypter.EncryptWithAuthData(payload, aad)
   132  	if err != nil {
   133  		return nil, fmt.Errorf("authcrypt Pack: failed to encrypt payload: %w", err)
   134  	}
   135  
   136  	mPh, err := json.Marshal(jwe.ProtectedHeaders)
   137  	if err != nil {
   138  		return nil, fmt.Errorf("authcrypt Pack: %w", err)
   139  	}
   140  
   141  	logger.Debugf("protected headers: %s", mPh)
   142  
   143  	var s string
   144  
   145  	if len(recipientsPubKeys) == 1 {
   146  		s, err = jwe.CompactSerialize(json.Marshal)
   147  	} else {
   148  		s, err = jwe.FullSerialize(json.Marshal)
   149  	}
   150  
   151  	if err != nil {
   152  		return nil, fmt.Errorf("authcrypt Pack: failed to serialize JWE message: %w", err)
   153  	}
   154  
   155  	return []byte(s), nil
   156  }
   157  
   158  func unmarshalRecipientKeys(keys [][]byte) ([]*cryptoapi.PublicKey, []byte, error) {
   159  	var (
   160  		pubKeys []*cryptoapi.PublicKey
   161  		aad     []byte
   162  	)
   163  
   164  	for _, key := range keys {
   165  		var ecKey *cryptoapi.PublicKey
   166  
   167  		err := json.Unmarshal(key, &ecKey)
   168  		if err != nil {
   169  			return nil, nil, err
   170  		}
   171  
   172  		pubKeys = append(pubKeys, ecKey)
   173  	}
   174  
   175  	return pubKeys, aad, nil
   176  }
   177  
   178  // Unpack will decode the envelope using a standard format. Using either did:key KID resolver or the
   179  // DIDDoc.KeyAgreement[].VerificationMethod.ID resolver which are set in p.resolvers.
   180  func (p *Packer) Unpack(envelope []byte) (*transport.Envelope, error) {
   181  	// TODO validate `typ` and `cty` values
   182  	jwe, _, _, err := deserializeEnvelope(envelope)
   183  	if err != nil {
   184  		return nil, fmt.Errorf("failed to deserialize envelope: %w", err)
   185  	}
   186  
   187  	for i := range jwe.Recipients {
   188  		var (
   189  			recKey *cryptoapi.PublicKey
   190  			pt     []byte
   191  			recKID string
   192  			env    *transport.Envelope
   193  		)
   194  
   195  		recKey, recKID, err = p.pubKey(i, jwe)
   196  		if err != nil {
   197  			return nil, fmt.Errorf("authcrypt Unpack: %w", err)
   198  		}
   199  
   200  		// verify recKey.KID is found in kms to prove ownership of key.
   201  		_, err = p.kms.Get(recKey.KID)
   202  		if err != nil {
   203  			if errors.Is(err, kms.ErrKeyNotFound) {
   204  				retriesMsg := ""
   205  
   206  				if i < len(jwe.Recipients) {
   207  					retriesMsg = ", will try another recipient"
   208  				}
   209  
   210  				logger.Debugf("authcrypt Unpack: recipient keyID not found in KMS: %v%s", recKey.KID, retriesMsg)
   211  
   212  				continue
   213  			}
   214  
   215  			return nil, fmt.Errorf("authcrypt Unpack: failed to get key from kms: %w", err)
   216  		}
   217  
   218  		jweDecrypter := jose.NewJWEDecrypt(p.kidResolvers, p.cryptoService, p.kms)
   219  
   220  		pt, err = jweDecrypter.Decrypt(jwe)
   221  		if err != nil {
   222  			return nil, fmt.Errorf("authcrypt Unpack: failed to decrypt JWE envelope: %w", err)
   223  		}
   224  
   225  		env, err = p.buildEnvelope(recKey, recKID, pt, jwe)
   226  		if err != nil {
   227  			return nil, fmt.Errorf("authcrypt Unpack: %w", err)
   228  		}
   229  
   230  		return env, nil
   231  	}
   232  
   233  	return nil, fmt.Errorf("authcrypt Unpack: no matching recipient in envelope")
   234  }
   235  
   236  func (p *Packer) buildEnvelope(recKey *cryptoapi.PublicKey, recKID string, message []byte,
   237  	jwe *jose.JSONWebEncryption) (*transport.Envelope, error) {
   238  	// set original recKID in recKey to keep original source of key (ie not the KMS kid)
   239  	recKey.KID = recKID
   240  
   241  	ecdh1puPubKeyByes, err := json.Marshal(recKey)
   242  	if err != nil {
   243  		return nil, fmt.Errorf("buildEnvelope: failed to marshal recipient public key: %w", err)
   244  	}
   245  
   246  	mSenderPubKey, err := p.extractSenderKey(jwe)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	return &transport.Envelope{
   252  		Message: message,
   253  		FromKey: mSenderPubKey,
   254  		ToKey:   ecdh1puPubKeyByes,
   255  	}, nil
   256  }
   257  
   258  func (p *Packer) extractSenderKey(jwe *jose.JSONWebEncryption) ([]byte, error) {
   259  	var (
   260  		senderKey     *cryptoapi.PublicKey
   261  		mSenderPubKey []byte
   262  		err           error
   263  	)
   264  
   265  	skidHeader, ok := jwe.ProtectedHeaders["skid"]
   266  	if ok { //nolint:nestif
   267  		skid, ok := skidHeader.(string)
   268  		if ok {
   269  			for _, r := range p.kidResolvers {
   270  				senderKey, err = r.Resolve(skid)
   271  				if err != nil {
   272  					logger.Debugf("authcrypt Unpack: unpack successful, but resolving sender key failed [%v] "+
   273  						"using %T resolver, skipping it.", err.Error(), r)
   274  				}
   275  
   276  				if senderKey != nil {
   277  					logger.Debugf("authcrypt Unpack: unpack successful with resolving sender key success "+
   278  						"using %T resolver, will be using resolved senderKey for skid: %v", r, skid)
   279  					break
   280  				}
   281  			}
   282  
   283  			if senderKey != nil {
   284  				// original senderKey.KID is a kms kid. The agent unpacking the envelope doesn't have this kid in kms.
   285  				// Set value as skid to keep trace of the origin of the key (didDoc.keyAgreement[].VerificationMehod.ID)
   286  				senderKey.KID = skid
   287  				mSenderPubKey, err = json.Marshal(senderKey)
   288  
   289  				if err != nil {
   290  					return nil, fmt.Errorf("authcrypt Unpack: failed to marshal sender public key: %w", err)
   291  				}
   292  			} else {
   293  				logger.Debugf("authcrypt Unpack: senderKey not resolved, skipping FromKey in envelope")
   294  			}
   295  		}
   296  	}
   297  
   298  	return mSenderPubKey, nil
   299  }
   300  
   301  func deserializeEnvelope(envelope []byte) (*jose.JSONWebEncryption, string, string, error) {
   302  	jwe, err := jose.Deserialize(string(envelope))
   303  	if err != nil {
   304  		return nil, "", "", fmt.Errorf("authcrypt Unpack: failed to deserialize JWE message: %w", err)
   305  	}
   306  
   307  	typ, _ := jwe.ProtectedHeaders.Type()
   308  	cty, _ := jwe.ProtectedHeaders.ContentType()
   309  
   310  	return jwe, typ, cty, nil
   311  }
   312  
   313  // pubKey will resolve recipient kid at i and returns the corresponding full public key with KID as the kms KID value.
   314  func (p *Packer) pubKey(i int, jwe *jose.JSONWebEncryption) (*cryptoapi.PublicKey, string, error) {
   315  	var (
   316  		kid         string
   317  		kidResolver resolver.KIDResolver
   318  	)
   319  
   320  	if i == 0 && len(jwe.Recipients) == 1 { // compact serialization, recipient headers are in jwe.ProtectedHeaders
   321  		var ok bool
   322  
   323  		kid, ok = jwe.ProtectedHeaders.KeyID()
   324  		if !ok {
   325  			return nil, "", fmt.Errorf("single recipient missing 'KID' in jwe.ProtectHeaders")
   326  		}
   327  	} else {
   328  		kid = jwe.Recipients[i].Header.KID
   329  	}
   330  
   331  	keySource := "did:key"
   332  
   333  	switch {
   334  	case strings.HasPrefix(kid, keySource):
   335  		kidResolver = p.kidResolvers[0]
   336  	case strings.Index(kid, "#") > 0:
   337  		kidResolver = p.kidResolvers[1]
   338  		keySource = "didDoc.KeyAgreement[].VerificationMethod.ID"
   339  	default:
   340  		return nil, "", fmt.Errorf("invalid kid format, must be a did:key or a DID doc verificaitonMethod ID")
   341  	}
   342  
   343  	// recipient kid header is the did:Key or KeyAgreement.ID, extract the public key and build a kms kid.
   344  	recKey, err := kidResolver.Resolve(kid)
   345  	if err != nil {
   346  		return nil, "", fmt.Errorf("failed to resolve recipient key from %s value: %w", keySource, err)
   347  	}
   348  
   349  	return recKey, kid, nil
   350  }
   351  
   352  // EncodingType for didcomm.
   353  func (p *Packer) EncodingType() string {
   354  	return transport.MediaTypeV2EncryptedEnvelope
   355  }