xref: /aosp_15_r20/external/boringssl/src/ssl/test/runner/hpke/hpke.go (revision 8fb009dc861624b67b6cdb62ea21f0f22d0c584b)
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