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