forked from cloudflare/circl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
hpke.go
271 lines (240 loc) · 7.74 KB
/
hpke.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// Package hpke implements the Hybrid Public Key Encryption (HPKE) standard
// specified by draft-irtf-cfrg-hpke-07.
//
// HPKE works for any combination of a public-key encapsulation mechanism
// (KEM), a key derivation function (KDF), and an authenticated encryption
// scheme with additional data (AEAD).
//
// Specification in
// https://datatracker.ietf.org/doc/draft-irtf-cfrg-hpke
//
// BUG(cjpatton): This package does not implement the "Export-Only" mode of the
// HPKE context. In particular, it does not recognize the AEAD codepoint
// reserved for this purpose (0xFFFF).
package hpke
import (
"crypto/rand"
"encoding"
"errors"
"io"
"github.com/cloudflare/circl/kem"
)
const versionLabel = "HPKE-v1"
// Context defines the capabilities of an HPKE context.
type Context interface {
encoding.BinaryMarshaler
// Export takes a context string exporterContext and a desired length (in
// bytes), and produces a secret derived from the internal exporter secret
// using the corresponding KDF Expand function. It panics if length is
// greater than 255*N bytes, where N is the size (in bytes) of the KDF's
// output.
Export(exporterContext []byte, length uint) []byte
// Suite returns the cipher suite corresponding to this context.
Suite() Suite
}
// Sealer encrypts a plaintext using an AEAD encryption.
type Sealer interface {
Context
// Seal takes a plaintext and associated data to produce a ciphertext.
// The nonce is handled by the Sealer and incremented after each call.
Seal(pt, aad []byte) (ct []byte, err error)
}
// Opener decrypts a ciphertext using an AEAD encryption.
type Opener interface {
Context
// Open takes a ciphertext and associated data to recover, if successful,
// the plaintext. The nonce is handled by the Opener and incremented after
// each call.
Open(ct, aad []byte) (pt []byte, err error)
}
// modeID represents an HPKE variant.
type modeID = uint8
const (
// modeBase to enable encryption to the holder of a given KEM private key.
modeBase modeID = 0x00
// modePSK extends the base mode by allowing the Receiver to authenticate
// that the sender possessed a given pre-shared key (PSK).
modePSK modeID = 0x01
// modeAuth extends the base mode by allowing the Receiver to authenticate
// that the sender possessed a given KEM private key.
modeAuth modeID = 0x02
// modeAuthPSK provides a combination of the PSK and Auth modes.
modeAuthPSK modeID = 0x03
)
// Suite is an HPKE cipher suite consisting of a KEM, KDF, and AEAD algorithm.
type Suite struct {
kemID KEM
kdfID KDF
aeadID AEAD
}
// NewSuite builds a Suite from a specified set of algorithms. Panics
// if an algorithm identifier is not valid.
func NewSuite(kemID KEM, kdfID KDF, aeadID AEAD) Suite {
s := Suite{kemID, kdfID, aeadID}
if !s.isValid() {
panic(ErrInvalidHPKESuite)
}
return s
}
type state struct {
Suite
modeID modeID
skS kem.PrivateKey
pkS kem.PublicKey
psk []byte
pskID []byte
info []byte
}
// Sender performs hybrid public-key encryption.
type Sender struct {
state
pkR kem.PublicKey
}
// NewSender creates a Sender with knowledge of the receiver's public-key.
func (suite Suite) NewSender(pkR kem.PublicKey, info []byte) (*Sender, error) {
return &Sender{
state: state{Suite: suite, info: info},
pkR: pkR,
}, nil
}
// Setup generates a new HPKE context used for Base Mode encryption.
// Returns the Sealer and corresponding encapsulated key.
func (s *Sender) Setup(rnd io.Reader) (enc []byte, seal Sealer, err error) {
s.modeID = modeBase
return s.allSetup(rnd)
}
// SetupAuth generates a new HPKE context used for Auth Mode encryption.
// Returns the Sealer and corresponding encapsulated key.
func (s *Sender) SetupAuth(rnd io.Reader, skS kem.PrivateKey) (
enc []byte, seal Sealer, err error,
) {
s.modeID = modeAuth
s.state.skS = skS
return s.allSetup(rnd)
}
// SetupPSK generates a new HPKE context used for PSK Mode encryption.
// Returns the Sealer and corresponding encapsulated key.
func (s *Sender) SetupPSK(rnd io.Reader, psk, pskID []byte) (
enc []byte, seal Sealer, err error,
) {
s.modeID = modePSK
s.state.psk = psk
s.state.pskID = pskID
return s.allSetup(rnd)
}
// SetupAuthPSK generates a new HPKE context used for Auth-PSK Mode encryption.
// Returns the Sealer and corresponding encapsulated key.
func (s *Sender) SetupAuthPSK(rnd io.Reader, skS kem.PrivateKey, psk, pskID []byte) (
enc []byte, seal Sealer, err error,
) {
s.modeID = modeAuthPSK
s.state.skS = skS
s.state.psk = psk
s.state.pskID = pskID
return s.allSetup(rnd)
}
// Receiver performs hybrid public-key decryption.
type Receiver struct {
state
skR kem.PrivateKey
enc []byte
}
// NewReceiver creates a Receiver with knowledge of a private key.
func (suite Suite) NewReceiver(skR kem.PrivateKey, info []byte) (
*Receiver, error,
) {
return &Receiver{state: state{Suite: suite, info: info}, skR: skR}, nil
}
// Setup generates a new HPKE context used for Base Mode encryption.
// Setup takes an encapsulated key and returns an Opener.
func (r *Receiver) Setup(enc []byte) (Opener, error) {
r.modeID = modeBase
r.enc = enc
return r.allSetup()
}
// SetupAuth generates a new HPKE context used for Auth Mode encryption.
// SetupAuth takes an encapsulated key and a public key, and returns an Opener.
func (r *Receiver) SetupAuth(enc []byte, pkS kem.PublicKey) (Opener, error) {
r.modeID = modeAuth
r.enc = enc
r.state.pkS = pkS
return r.allSetup()
}
// SetupPSK generates a new HPKE context used for PSK Mode encryption.
// SetupPSK takes an encapsulated key, and a pre-shared key; and returns an
// Opener.
func (r *Receiver) SetupPSK(enc, psk, pskID []byte) (Opener, error) {
r.modeID = modePSK
r.enc = enc
r.state.psk = psk
r.state.pskID = pskID
return r.allSetup()
}
// SetupAuthPSK generates a new HPKE context used for Auth-PSK Mode encryption.
// SetupAuthPSK takes an encapsulated key, a public key, and a pre-shared key;
// and returns an Opener.
func (r *Receiver) SetupAuthPSK(
enc, psk, pskID []byte, pkS kem.PublicKey,
) (Opener, error) {
r.modeID = modeAuthPSK
r.enc = enc
r.state.psk = psk
r.state.pskID = pskID
r.state.pkS = pkS
return r.allSetup()
}
func (s *Sender) allSetup(rnd io.Reader) ([]byte, Sealer, error) {
scheme := s.kemID.Scheme()
if rnd == nil {
rnd = rand.Reader
}
seed := make([]byte, scheme.EncapsulationSeedSize())
_, err := io.ReadFull(rnd, seed)
if err != nil {
return nil, nil, err
}
var enc, ss []byte
switch s.modeID {
case modeBase, modePSK:
enc, ss, err = scheme.EncapsulateDeterministically(s.pkR, seed)
case modeAuth, modeAuthPSK:
enc, ss, err = scheme.AuthEncapsulateDeterministically(s.pkR, s.skS, seed)
}
if err != nil {
return nil, nil, err
}
ctx, err := s.keySchedule(ss, s.info, s.psk, s.pskID)
if err != nil {
return nil, nil, err
}
return enc, &sealContext{ctx}, nil
}
func (r *Receiver) allSetup() (Opener, error) {
var err error
var ss []byte
scheme := r.kemID.Scheme()
switch r.modeID {
case modeBase, modePSK:
ss, err = scheme.Decapsulate(r.skR, r.enc)
case modeAuth, modeAuthPSK:
ss, err = scheme.AuthDecapsulate(r.skR, r.enc, r.pkS)
}
if err != nil {
return nil, err
}
ctx, err := r.keySchedule(ss, r.info, r.psk, r.pskID)
if err != nil {
return nil, err
}
return &openContext{ctx}, nil
}
var (
ErrInvalidHPKESuite = errors.New("hpke: invalid HPKE suite")
ErrInvalidKDF = errors.New("hpke: invalid KDF identifier")
ErrInvalidKEM = errors.New("hpke: invalid KEM identifier")
ErrInvalidAEAD = errors.New("hpke: invalid AEAD identifier")
ErrInvalidKEMPublicKey = errors.New("hpke: invalid KEM public key")
ErrInvalidKEMPrivateKey = errors.New("hpke: invalid KEM private key")
ErrInvalidKEMSharedSecret = errors.New("hpke: invalid KEM shared secret")
ErrAEADSeqOverflows = errors.New("hpke: AEAD sequence number overflows")
)