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 }