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
//! HPKE AEAD dispatcher: runtime selection of AES-128-GCM,
//! AES-256-GCM, ChaCha20-Poly1305, and the ExportOnly marker
//! (RFC 9180 §7.3).
use super::Error;
use crate::cipher::{Aes128, Aes128Gcm, Aes256, Aes256Gcm, ChaCha20Poly1305, Gcm};
use alloc::vec::Vec;
/// HPKE AEAD identifiers (RFC 9180 §7.3).
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum HpkeAead {
/// `0x0001` — AES-128-GCM.
Aes128Gcm,
/// `0x0002` — AES-256-GCM.
Aes256Gcm,
/// `0x0003` — ChaCha20-Poly1305.
ChaCha20Poly1305,
/// `0xFFFF` — Export-Only: `seal`/`open` are unsupported; only
/// [`SenderContext::export`](super::SenderContext::export) /
/// [`ReceiverContext::export`](super::ReceiverContext::export) are
/// available.
ExportOnly,
}
impl HpkeAead {
/// The IANA-assigned AEAD id.
pub const fn id(self) -> u16 {
match self {
HpkeAead::Aes128Gcm => 0x0001,
HpkeAead::Aes256Gcm => 0x0002,
HpkeAead::ChaCha20Poly1305 => 0x0003,
HpkeAead::ExportOnly => 0xFFFF,
}
}
/// `Nk`: AEAD key length in bytes.
pub const fn key_len(self) -> usize {
match self {
HpkeAead::Aes128Gcm => 16,
HpkeAead::Aes256Gcm => 32,
HpkeAead::ChaCha20Poly1305 => 32,
HpkeAead::ExportOnly => 0,
}
}
/// `Nn`: AEAD nonce length in bytes. Always 12 for the wired
/// algorithms (RFC 9180 §7.3).
pub const fn nonce_len(self) -> usize {
match self {
HpkeAead::Aes128Gcm | HpkeAead::Aes256Gcm | HpkeAead::ChaCha20Poly1305 => 12,
HpkeAead::ExportOnly => 0,
}
}
/// `Nt`: AEAD tag length in bytes. Always 16 for the wired
/// algorithms.
pub const fn tag_len(self) -> usize {
match self {
HpkeAead::ExportOnly => 0,
_ => 16,
}
}
/// Whether this AEAD supports `seal`/`open` (false for
/// [`HpkeAead::ExportOnly`]).
pub const fn is_export_only(self) -> bool {
matches!(self, HpkeAead::ExportOnly)
}
/// Encrypts `pt` under `key` and `nonce`, binding `aad`, returning
/// `ciphertext || tag`.
pub(crate) fn seal(
self,
key: &[u8],
nonce: &[u8],
aad: &[u8],
pt: &[u8],
) -> Result<Vec<u8>, Error> {
match self {
HpkeAead::Aes128Gcm => {
if key.len() != 16 || nonce.len() != 12 {
return Err(Error::AeadError);
}
let mut k = [0u8; 16];
k.copy_from_slice(key);
let mut buf = pt.to_vec();
let cipher = Aes128Gcm::new(Aes128::new(&k));
super::wipe(&mut k);
let tag = cipher.encrypt(nonce, aad, &mut buf);
buf.extend_from_slice(&tag);
Ok(buf)
}
HpkeAead::Aes256Gcm => {
if key.len() != 32 || nonce.len() != 12 {
return Err(Error::AeadError);
}
let mut k = [0u8; 32];
k.copy_from_slice(key);
let mut buf = pt.to_vec();
let cipher = Aes256Gcm::new(Aes256::new(&k));
super::wipe(&mut k);
let tag = cipher.encrypt(nonce, aad, &mut buf);
buf.extend_from_slice(&tag);
Ok(buf)
}
HpkeAead::ChaCha20Poly1305 => {
if key.len() != 32 || nonce.len() != 12 {
return Err(Error::AeadError);
}
let mut k = [0u8; 32];
k.copy_from_slice(key);
let mut n = [0u8; 12];
n.copy_from_slice(nonce);
let mut buf = pt.to_vec();
let cipher = ChaCha20Poly1305::new(&k);
super::wipe(&mut k);
let tag = cipher.encrypt(&n, aad, &mut buf);
buf.extend_from_slice(&tag);
Ok(buf)
}
HpkeAead::ExportOnly => Err(Error::ExportOnly),
}
}
/// Verifies the trailing 16-byte tag of `ct` against `aad` and, on
/// success, returns the decrypted plaintext.
pub(crate) fn open(
self,
key: &[u8],
nonce: &[u8],
aad: &[u8],
ct: &[u8],
) -> Result<Vec<u8>, Error> {
if self == HpkeAead::ExportOnly {
return Err(Error::ExportOnly);
}
let tag_len = self.tag_len();
if ct.len() < tag_len {
return Err(Error::AeadError);
}
let (body, tag) = ct.split_at(ct.len() - tag_len);
let mut tag_arr = [0u8; 16];
tag_arr.copy_from_slice(tag);
match self {
HpkeAead::Aes128Gcm => {
if key.len() != 16 || nonce.len() != 12 {
return Err(Error::AeadError);
}
let mut k = [0u8; 16];
k.copy_from_slice(key);
let mut buf = body.to_vec();
let cipher = Aes128Gcm::new(Aes128::new(&k));
super::wipe(&mut k);
cipher
.decrypt(nonce, aad, &mut buf, &tag_arr)
.map_err(|_| Error::AeadError)?;
Ok(buf)
}
HpkeAead::Aes256Gcm => {
if key.len() != 32 || nonce.len() != 12 {
return Err(Error::AeadError);
}
let mut k = [0u8; 32];
k.copy_from_slice(key);
let mut buf = body.to_vec();
let cipher = Aes256Gcm::new(Aes256::new(&k));
super::wipe(&mut k);
cipher
.decrypt(nonce, aad, &mut buf, &tag_arr)
.map_err(|_| Error::AeadError)?;
Ok(buf)
}
HpkeAead::ChaCha20Poly1305 => {
if key.len() != 32 || nonce.len() != 12 {
return Err(Error::AeadError);
}
let mut k = [0u8; 32];
k.copy_from_slice(key);
let mut n = [0u8; 12];
n.copy_from_slice(nonce);
let mut buf = body.to_vec();
let cipher = ChaCha20Poly1305::new(&k);
super::wipe(&mut k);
cipher
.decrypt(&n, aad, &mut buf, &tag_arr)
.map_err(|_| Error::AeadError)?;
Ok(buf)
}
HpkeAead::ExportOnly => Err(Error::ExportOnly),
}
}
// Suppress dead-code lint when only some entry points are exercised.
#[allow(dead_code)]
pub(crate) const _GCM_REUSE_HINT: Option<Gcm<Aes128>> = None;
}