1// Copyright (c) 2020, Google Inc. 2// 3// Permission to use, copy, modify, and/or distribute this software for any 4// purpose with or without fee is hereby granted, provided that the above 5// copyright notice and this permission notice appear in all copies. 6// 7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15// Package hpke implements Hybrid Public Key Encryption (HPKE). 16// 17// See RFC 9180. 18package hpke 19 20import ( 21 "crypto" 22 "crypto/aes" 23 "crypto/cipher" 24 "encoding/binary" 25 "errors" 26 "fmt" 27 28 "golang.org/x/crypto/chacha20poly1305" 29) 30 31// KEM scheme IDs. 32const ( 33 P256WithHKDFSHA256 uint16 = 0x0010 34 X25519WithHKDFSHA256 uint16 = 0x0020 35) 36 37// HPKE AEAD IDs. 38const ( 39 AES128GCM uint16 = 0x0001 40 AES256GCM uint16 = 0x0002 41 ChaCha20Poly1305 uint16 = 0x0003 42) 43 44// HPKE KDF IDs. 45const ( 46 HKDFSHA256 uint16 = 0x0001 47 HKDFSHA384 uint16 = 0x0002 48 HKDFSHA512 uint16 = 0x0003 49) 50 51// Internal constants. 52const ( 53 hpkeModeBase uint8 = 0 54 hpkeModePSK uint8 = 1 55) 56 57// GetHKDFHash returns the crypto.Hash that corresponds to kdf. If kdf is not 58// one the supported KDF IDs, returns an error. 59func GetHKDFHash(kdf uint16) (crypto.Hash, error) { 60 switch kdf { 61 case HKDFSHA256: 62 return crypto.SHA256, nil 63 case HKDFSHA384: 64 return crypto.SHA384, nil 65 case HKDFSHA512: 66 return crypto.SHA512, nil 67 } 68 return 0, fmt.Errorf("unknown KDF: %d", kdf) 69} 70 71type GenerateKeyPairFunc func() (public []byte, secret []byte, e error) 72 73// Context holds the HPKE state for a sender or a receiver. 74type Context struct { 75 kemID uint16 76 kdfID uint16 77 aeadID uint16 78 79 aead cipher.AEAD 80 81 key []byte 82 baseNonce []byte 83 seq uint64 84 exporterSecret []byte 85} 86 87// SetupBaseSenderX25519 corresponds to the spec's SetupBaseS(), but only 88// supports X25519. 89func SetupBaseSenderX25519(kdfID, aeadID uint16, publicKeyR, info []byte, ephemKeygen GenerateKeyPairFunc) (context *Context, enc []byte, err error) { 90 sharedSecret, enc, err := x25519Encap(publicKeyR, ephemKeygen) 91 if err != nil { 92 return nil, nil, err 93 } 94 context, err = keySchedule(hpkeModeBase, X25519WithHKDFSHA256, kdfID, aeadID, sharedSecret, info, nil, nil) 95 return 96} 97 98// SetupBaseReceiverX25519 corresponds to the spec's SetupBaseR(), but only 99// supports X25519. 100func SetupBaseReceiverX25519(kdfID, aeadID uint16, enc, secretKeyR, info []byte) (context *Context, err error) { 101 sharedSecret, err := x25519Decap(enc, secretKeyR) 102 if err != nil { 103 return nil, err 104 } 105 return keySchedule(hpkeModeBase, X25519WithHKDFSHA256, kdfID, aeadID, sharedSecret, info, nil, nil) 106} 107 108// SetupPSKSenderX25519 corresponds to the spec's SetupPSKS(), but only supports 109// X25519. 110func SetupPSKSenderX25519(kdfID, aeadID uint16, publicKeyR, info, psk, pskID []byte, ephemKeygen GenerateKeyPairFunc) (context *Context, enc []byte, err error) { 111 sharedSecret, enc, err := x25519Encap(publicKeyR, ephemKeygen) 112 if err != nil { 113 return nil, nil, err 114 } 115 context, err = keySchedule(hpkeModePSK, X25519WithHKDFSHA256, kdfID, aeadID, sharedSecret, info, psk, pskID) 116 return 117} 118 119// SetupPSKReceiverX25519 corresponds to the spec's SetupPSKR(), but only 120// supports X25519. 121func SetupPSKReceiverX25519(kdfID, aeadID uint16, enc, secretKeyR, info, psk, pskID []byte) (context *Context, err error) { 122 sharedSecret, err := x25519Decap(enc, secretKeyR) 123 if err != nil { 124 return nil, err 125 } 126 context, err = keySchedule(hpkeModePSK, X25519WithHKDFSHA256, kdfID, aeadID, sharedSecret, info, psk, pskID) 127 if err != nil { 128 return nil, err 129 } 130 return context, nil 131} 132 133func (c *Context) KEM() uint16 { return c.kemID } 134 135func (c *Context) KDF() uint16 { return c.kdfID } 136 137func (c *Context) AEAD() uint16 { return c.aeadID } 138 139func (c *Context) Overhead() int { return c.aead.Overhead() } 140 141func (c *Context) Seal(plaintext, additionalData []byte) []byte { 142 ciphertext := c.aead.Seal(nil, c.computeNonce(), plaintext, additionalData) 143 c.incrementSeq() 144 return ciphertext 145} 146 147func (c *Context) Open(ciphertext, additionalData []byte) ([]byte, error) { 148 plaintext, err := c.aead.Open(nil, c.computeNonce(), ciphertext, additionalData) 149 if err != nil { 150 return nil, err 151 } 152 c.incrementSeq() 153 return plaintext, nil 154} 155 156func (c *Context) Export(exporterContext []byte, length int) []byte { 157 suiteID := buildSuiteID(c.kemID, c.kdfID, c.aeadID) 158 kdfHash := getKDFHash(c.kdfID) 159 return labeledExpand(kdfHash, c.exporterSecret, suiteID, []byte("sec"), exporterContext, length) 160} 161 162func buildSuiteID(kemID, kdfID, aeadID uint16) []byte { 163 ret := make([]byte, 0, 10) 164 ret = append(ret, "HPKE"...) 165 ret = appendBigEndianUint16(ret, kemID) 166 ret = appendBigEndianUint16(ret, kdfID) 167 ret = appendBigEndianUint16(ret, aeadID) 168 return ret 169} 170 171func newAEAD(aeadID uint16, key []byte) (cipher.AEAD, error) { 172 if len(key) != expectedKeyLength(aeadID) { 173 return nil, errors.New("wrong key length for specified AEAD") 174 } 175 switch aeadID { 176 case AES128GCM, AES256GCM: 177 block, err := aes.NewCipher(key) 178 if err != nil { 179 return nil, err 180 } 181 aead, err := cipher.NewGCM(block) 182 if err != nil { 183 return nil, err 184 } 185 return aead, nil 186 case ChaCha20Poly1305: 187 aead, err := chacha20poly1305.New(key) 188 if err != nil { 189 return nil, err 190 } 191 return aead, nil 192 } 193 return nil, errors.New("unsupported AEAD") 194} 195 196func keySchedule(mode uint8, kemID, kdfID, aeadID uint16, sharedSecret, info, psk, pskID []byte) (*Context, error) { 197 // Verify the PSK inputs. 198 switch mode { 199 case hpkeModeBase: 200 if len(psk) > 0 || len(pskID) > 0 { 201 panic("unnecessary psk inputs were provided") 202 } 203 case hpkeModePSK: 204 if len(psk) == 0 || len(pskID) == 0 { 205 panic("missing psk inputs") 206 } 207 default: 208 panic("unknown mode") 209 } 210 211 kdfHash := getKDFHash(kdfID) 212 suiteID := buildSuiteID(kemID, kdfID, aeadID) 213 pskIDHash := labeledExtract(kdfHash, nil, suiteID, []byte("psk_id_hash"), pskID) 214 infoHash := labeledExtract(kdfHash, nil, suiteID, []byte("info_hash"), info) 215 216 keyScheduleContext := make([]byte, 0) 217 keyScheduleContext = append(keyScheduleContext, mode) 218 keyScheduleContext = append(keyScheduleContext, pskIDHash...) 219 keyScheduleContext = append(keyScheduleContext, infoHash...) 220 221 secret := labeledExtract(kdfHash, sharedSecret, suiteID, []byte("secret"), psk) 222 key := labeledExpand(kdfHash, secret, suiteID, []byte("key"), keyScheduleContext, expectedKeyLength(aeadID)) 223 224 aead, err := newAEAD(aeadID, key) 225 if err != nil { 226 return nil, err 227 } 228 229 baseNonce := labeledExpand(kdfHash, secret, suiteID, []byte("base_nonce"), keyScheduleContext, aead.NonceSize()) 230 exporterSecret := labeledExpand(kdfHash, secret, suiteID, []byte("exp"), keyScheduleContext, kdfHash.Size()) 231 232 return &Context{ 233 kemID: kemID, 234 kdfID: kdfID, 235 aeadID: aeadID, 236 aead: aead, 237 key: key, 238 baseNonce: baseNonce, 239 seq: 0, 240 exporterSecret: exporterSecret, 241 }, nil 242} 243 244func (c Context) computeNonce() []byte { 245 nonce := make([]byte, len(c.baseNonce)) 246 // Write the big-endian |c.seq| value at the *end* of |baseNonce|. 247 binary.BigEndian.PutUint64(nonce[len(nonce)-8:], c.seq) 248 // XOR the big-endian |seq| with |c.baseNonce|. 249 for i, b := range c.baseNonce { 250 nonce[i] ^= b 251 } 252 return nonce 253} 254 255func (c *Context) incrementSeq() { 256 c.seq++ 257 if c.seq == 0 { 258 panic("sequence overflow") 259 } 260} 261 262func expectedKeyLength(aeadID uint16) int { 263 switch aeadID { 264 case AES128GCM: 265 return 128 / 8 266 case AES256GCM: 267 return 256 / 8 268 case ChaCha20Poly1305: 269 return chacha20poly1305.KeySize 270 } 271 panic("unsupported AEAD") 272} 273 274func appendBigEndianUint16(b []byte, v uint16) []byte { 275 return append(b, byte(v>>8), byte(v)) 276} 277