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

     1  /*
     2  Copyright Avast Software. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package anoncryt
     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/common/log"
    20  	"github.com/hyperledger/aries-framework-go/pkg/internal/cryptoutil"
    21  )
    22  
    23  var logger = log.New("aries-framework/pkg/didcomm/packer/legacy/anoncrypt")
    24  
    25  // Pack will encode the payload argument
    26  // Using the protocol defined by Aries RFC 0019.
    27  func (p *Packer) Pack(_ string, payload, _ []byte, recipientPubKeys [][]byte) ([]byte, error) {
    28  	var err error
    29  
    30  	if len(recipientPubKeys) == 0 {
    31  		return nil, errors.New("empty recipients keys, must have at least one recipient")
    32  	}
    33  
    34  	nonce := make([]byte, chacha.NonceSize)
    35  
    36  	_, err = p.randSource.Read(nonce)
    37  	if err != nil {
    38  		return nil, fmt.Errorf("pack: failed to generate random nonce: %w", err)
    39  	}
    40  
    41  	// cek (content encryption key) is a symmetric key, for chacha20, a symmetric cipher
    42  	cek := &[chacha.KeySize]byte{}
    43  
    44  	_, err = p.randSource.Read(cek[:])
    45  	if err != nil {
    46  		return nil, fmt.Errorf("pack: failed to generate cek: %w", err)
    47  	}
    48  
    49  	var recipients []recipient
    50  
    51  	recipients, err = p.buildRecipients(cek, recipientPubKeys)
    52  	if err != nil {
    53  		return nil, fmt.Errorf("pack: failed to build recipients: %w", err)
    54  	}
    55  
    56  	header := protected{
    57  		Enc:        anonCryptEncType,
    58  		Typ:        encodingType,
    59  		Alg:        anonCrypt,
    60  		Recipients: recipients,
    61  	}
    62  
    63  	return p.buildEnvelope(nonce, payload, cek[:], &header)
    64  }
    65  
    66  func (p *Packer) buildEnvelope(nonce, payload, cek []byte, header *protected) ([]byte, error) {
    67  	protectedBytes, err := json.Marshal(header)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	protectedB64 := base64.URLEncoding.EncodeToString(protectedBytes)
    73  
    74  	chachaCipher, err := chacha.New(cek)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	// 	Additional data is b64encode(jsonencode(header))
    80  	symPld := chachaCipher.Seal(nil, nonce, payload, []byte(protectedB64))
    81  
    82  	// symPld has a length of len(pld) + poly1305.TagSize
    83  	// fetch the tag from the tail
    84  	tag := symPld[len(symPld)-poly1305.TagSize:]
    85  	// fetch the cipherText from the head (0:up to the trailing tag)
    86  	cipherText := symPld[0 : len(symPld)-poly1305.TagSize]
    87  
    88  	env := legacyEnvelope{
    89  		Protected:  protectedB64,
    90  		IV:         base64.URLEncoding.EncodeToString(nonce),
    91  		CipherText: base64.URLEncoding.EncodeToString(cipherText),
    92  		Tag:        base64.URLEncoding.EncodeToString(tag),
    93  	}
    94  
    95  	out, err := json.Marshal(env)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	return out, nil
   101  }
   102  
   103  func (p *Packer) buildRecipients(cek *[chacha.KeySize]byte, recPubKeys [][]byte) ([]recipient, error) {
   104  	encodedRecipients := make([]recipient, 0)
   105  
   106  	for _, recKey := range recPubKeys {
   107  		rec, err := p.buildRecipient(cek, recKey)
   108  		if err != nil {
   109  			logger.Warnf("buildRecipients: failed to build recipient: %w", err)
   110  
   111  			continue
   112  		}
   113  
   114  		encodedRecipients = append(encodedRecipients, *rec)
   115  	}
   116  
   117  	if len(encodedRecipients) == 0 {
   118  		return nil, fmt.Errorf("recipients keys are empty")
   119  	}
   120  
   121  	return encodedRecipients, nil
   122  }
   123  
   124  // buildRecipient encodes the necessary data for the recipient to decrypt the message
   125  // encrypting the CEK.
   126  func (p *Packer) buildRecipient(cek *[chacha.KeySize]byte, recKey []byte) (*recipient, error) {
   127  	recEncKey, err := cryptoutil.PublicEd25519toCurve25519(recKey)
   128  	if err != nil {
   129  		return nil, fmt.Errorf("buildRecipient: failed to convert public Ed25519 to Curve25519: %w", err)
   130  	}
   131  
   132  	box, err := newCryptoBox(p.kms)
   133  	if err != nil {
   134  		return nil, fmt.Errorf("buildRecipient: failed to create new CryptoBox: %w", err)
   135  	}
   136  
   137  	encCEK, err := box.Seal(cek[:], recEncKey, p.randSource)
   138  	if err != nil {
   139  		return nil, fmt.Errorf("buildRecipient: failed to encrypt cek: %w", err)
   140  	}
   141  
   142  	return &recipient{
   143  		EncryptedKey: base64.URLEncoding.EncodeToString(encCEK),
   144  		Header: recipientHeader{
   145  			KID: base58.Encode(recKey), // recKey is the Ed25519 recipient pk in b58 encoding
   146  		},
   147  	}, nil
   148  }