github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/packer/legacy/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/base64"
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  
    15  	"github.com/btcsuite/btcutil/base58"
    16  	chacha "golang.org/x/crypto/chacha20poly1305"
    17  	"golang.org/x/crypto/poly1305"
    18  
    19  	"github.com/hyperledger/aries-framework-go/pkg/doc/util/jwkkid"
    20  
    21  	"github.com/hyperledger/aries-framework-go/pkg/common/log"
    22  	"github.com/hyperledger/aries-framework-go/pkg/internal/cryptoutil"
    23  	"github.com/hyperledger/aries-framework-go/pkg/kms"
    24  	"github.com/hyperledger/aries-framework-go/pkg/kms/localkms"
    25  	"github.com/hyperledger/aries-framework-go/pkg/kms/webkms"
    26  )
    27  
    28  var logger = log.New("aries-framework/pkg/didcomm/packer/legacy")
    29  
    30  // Pack will encode the payload argument
    31  // Using the protocol defined by Aries RFC 0019.
    32  func (p *Packer) Pack(_ string, payload, sender []byte, recipientPubKeys [][]byte) ([]byte, error) {
    33  	var err error
    34  
    35  	if len(recipientPubKeys) == 0 {
    36  		return nil, errors.New("empty recipients keys, must have at least one recipient")
    37  	}
    38  
    39  	nonce := make([]byte, chacha.NonceSize)
    40  
    41  	_, err = p.randSource.Read(nonce)
    42  	if err != nil {
    43  		return nil, fmt.Errorf("pack: failed to generate random nonce: %w", err)
    44  	}
    45  
    46  	// cek (content encryption key) is a symmetric key, for chacha20, a symmetric cipher
    47  	cek := &[chacha.KeySize]byte{}
    48  
    49  	_, err = p.randSource.Read(cek[:])
    50  	if err != nil {
    51  		return nil, fmt.Errorf("pack: failed to generate cek: %w", err)
    52  	}
    53  
    54  	var recipients []recipient
    55  
    56  	recipients, err = p.buildRecipients(cek, sender, recipientPubKeys)
    57  	if err != nil {
    58  		return nil, fmt.Errorf("pack: failed to build recipients: %w", err)
    59  	}
    60  
    61  	header := protected{
    62  		Enc:        "chacha20poly1305_ietf",
    63  		Typ:        encodingType,
    64  		Alg:        "Authcrypt",
    65  		Recipients: recipients,
    66  	}
    67  
    68  	return p.buildEnvelope(nonce, payload, cek[:], &header)
    69  }
    70  
    71  func (p *Packer) buildEnvelope(nonce, payload, cek []byte, header *protected) ([]byte, error) {
    72  	protectedBytes, err := json.Marshal(header)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	protectedB64 := base64.URLEncoding.EncodeToString(protectedBytes)
    78  
    79  	chachaCipher, err := chacha.New(cek)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	// 	Additional data is b64encode(jsonencode(header))
    85  	symPld := chachaCipher.Seal(nil, nonce, payload, []byte(protectedB64))
    86  
    87  	// symPld has a length of len(pld) + poly1305.TagSize
    88  	// fetch the tag from the tail
    89  	tag := symPld[len(symPld)-poly1305.TagSize:]
    90  	// fetch the cipherText from the head (0:up to the trailing tag)
    91  	cipherText := symPld[0 : len(symPld)-poly1305.TagSize]
    92  
    93  	env := legacyEnvelope{
    94  		Protected:  protectedB64,
    95  		IV:         base64.URLEncoding.EncodeToString(nonce),
    96  		CipherText: base64.URLEncoding.EncodeToString(cipherText),
    97  		Tag:        base64.URLEncoding.EncodeToString(tag),
    98  	}
    99  
   100  	out, err := json.Marshal(env)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	return out, nil
   106  }
   107  
   108  func (p *Packer) buildRecipients(cek *[chacha.KeySize]byte, senderKey []byte, recPubKeys [][]byte) ([]recipient, error) { // nolint: lll
   109  	encodedRecipients := make([]recipient, 0)
   110  
   111  	for _, recKey := range recPubKeys {
   112  		rec, err := p.buildRecipient(cek, senderKey, recKey)
   113  		if err != nil {
   114  			logger.Warnf("buildRecipients: failed to build recipient: %w", err)
   115  
   116  			continue
   117  		}
   118  
   119  		encodedRecipients = append(encodedRecipients, *rec)
   120  	}
   121  
   122  	if len(encodedRecipients) == 0 {
   123  		return nil, fmt.Errorf("recipients keys are empty")
   124  	}
   125  
   126  	return encodedRecipients, nil
   127  }
   128  
   129  // buildRecipient encodes the necessary data for the recipient to decrypt the message
   130  // encrypting the CEK and sender Pub key.
   131  func (p *Packer) buildRecipient(cek *[chacha.KeySize]byte, senderKey, recKey []byte) (*recipient, error) { // nolint: lll
   132  	var nonce [24]byte
   133  
   134  	_, err := p.randSource.Read(nonce[:])
   135  	if err != nil {
   136  		return nil, fmt.Errorf("buildRecipient: failed to generate random nonce: %w", err)
   137  	}
   138  
   139  	senderKID, err := jwkkid.CreateKID(senderKey, kms.ED25519Type)
   140  	if err != nil {
   141  		return nil, fmt.Errorf("buildRecipient: failed to create KID for public key: %w", err)
   142  	}
   143  
   144  	recEncKey, err := cryptoutil.PublicEd25519toCurve25519(recKey)
   145  	if err != nil {
   146  		return nil, fmt.Errorf("buildRecipient: failed to convert public Ed25519 to Curve25519: %w", err)
   147  	}
   148  
   149  	box, err := newCryptoBox(p.kms)
   150  	if err != nil {
   151  		return nil, fmt.Errorf("buildRecipient: failed to create new CryptoBox: %w", err)
   152  	}
   153  
   154  	encCEK, err := box.Easy(cek[:], nonce[:], recEncKey, senderKID)
   155  	if err != nil {
   156  		return nil, fmt.Errorf("buildRecipient: failed to encrypt cek: %w", err)
   157  	}
   158  
   159  	// assumption: senderKey is ed25519
   160  	encSender, err := box.Seal([]byte(base58.Encode(senderKey)), recEncKey, p.randSource)
   161  	if err != nil {
   162  		return nil, fmt.Errorf("buildRecipient: failed to encrypt sender key: %w", err)
   163  	}
   164  
   165  	return &recipient{
   166  		EncryptedKey: base64.URLEncoding.EncodeToString(encCEK),
   167  		Header: recipientHeader{
   168  			KID:    base58.Encode(recKey), // recKey is the Ed25519 recipient pk in b58 encoding
   169  			Sender: base64.URLEncoding.EncodeToString(encSender),
   170  			IV:     base64.URLEncoding.EncodeToString(nonce[:]),
   171  		},
   172  	}, nil
   173  }
   174  
   175  func newCryptoBox(manager kms.KeyManager) (kms.CryptoBox, error) {
   176  	switch manager.(type) {
   177  	case *localkms.LocalKMS:
   178  		return localkms.NewCryptoBox(manager)
   179  	case *webkms.RemoteKMS:
   180  		return webkms.NewCryptoBox(manager)
   181  	default:
   182  		return localkms.NewCryptoBox(manager)
   183  	}
   184  }