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
use zeroize::{Zeroizing, ZeroizeOnDrop};
pub const CIPHER_KEY_LEN: usize = 32;
pub const TAG_SIZE: usize = 16;
const MAX_HASH_BLOCKLEN: usize = 128;
/// DH functions
///
/// Spec: 4.1. DH functions
pub trait DHKeypair<const DHLEN: usize>: Clone + ZeroizeOnDrop {
const NAME: &'static str;
/// Generates a new Diffie-Hellman key pair.
fn generate_keypair(out: &mut Option<Self>) where Self: Sized;
/// Get the public key
fn public_key(&self) -> [u8; DHLEN];
/// Performs a Diffie-Hellman calculation between the private key in key_pair and the public_key and
/// returns an output sequence of bytes of length DHLEN.
///
/// For security, the Gap-DH problem based on this function must be unsolvable by any practical
/// cryptanalytic adversary [2]. The public_key either encodes some value which is a generator in a large
/// prime-order group (which value may have multiple equivalent encodings), or is an invalid value.
///
/// Implementations must handle invalid public keys either by returning some output which is purely a function
/// of the public key and does not depend on the private key.
fn dh(&self, public_key: &[u8; DHLEN], output: &mut [u8; DHLEN]);
}
/// Cipher functions
///
/// Spec: 4.2. Cipher functions
pub trait Cipher {
fn name(&self) -> &str;
/// Encrypts plaintext using the cipher key k of 32 bytes and an 8-byte
/// unsigned integer nonce nwhich must be unique for the key k.
///
/// Encryption must be done with an "AEAD" encryption mode with the associated
/// data ad (using the terminology from [1]) and returns a ciphertext that is
/// the same size as the plaintext (in place) plus 16 bytes for authentication data.
///
/// The entire ciphertext must be indistinguishable from random if the key is
/// secret (note that this is an additional requirement that isn't necessarily
/// met by all AEAD schemes).
///
/// Deviation: Rather than return ciphertext, the buffer is encrypted in place and tag is written with the tag.
fn encrypt(&self, key: &[u8; CIPHER_KEY_LEN], n: u64, ad: &[u8], buf: &mut [u8], tag: &mut [u8; TAG_SIZE]);
/// Decrypts ciphertext in-place using a cipher key k of 32 bytes, an 8-byte unsigned
/// integer nonce n, and associated data ad.
///
/// The tag which was appended to the ciphertext is passed as `tag`
///
/// Returns true on success
fn decrypt<'a>(
&self,
key: &[u8; CIPHER_KEY_LEN],
n: u64,
ad: &[u8],
buf: &'a mut [u8]
) -> Option<&'a [u8]>;
/// Sets the key to a 32-byte cipher key as a pseudorandom function of k.
fn rekey(&self, key: &[u8; CIPHER_KEY_LEN], out: &mut [u8; CIPHER_KEY_LEN]) {
let mut tmp = [0; TAG_SIZE];
// If this function is not specifically defined for some set of cipher functions,
// then it defaults to returning the first 32 bytes from ENCRYPT(k, maxnonce, zerolen, zeros),
// where maxnonce equals 2^64-1, zerolen is a zero-length byte sequence, and zeros is a sequence of
// 32 bytes filled with zeros.
self.encrypt(key, u64::MAX, &[], &mut out[..], &mut tmp);
}
}
pub trait HashFunction<const L: usize>: ZeroizeOnDrop {
/// the hash function with an empty input
fn new() -> Self where Self: Sized;
/// the protocol name for this hash function
const NAME: &'static str;
/// Bytes that the hash function uses internally to divide its input for iterative processing.
/// This is needed to use the hash function with HMAC (BLOCKLEN is B in [3]).
const BLOCKLEN: usize;
/// concatinate self to the input of the hash function
fn update(&mut self, data: &[u8]);
/// write the hash output to `out`, zeroize internal state and reset to initial
fn finalize_reset(&mut self, out: &mut [u8; L]);
}
// Applies HMAC from [3] using the HASH() function. This function is only called as part of HKDF(), below.
fn hmac<const L: usize, H: HashFunction<L>>(
key: &[u8; L],
out: &mut [u8; L],
write_text: impl FnOnce(&mut H) -> ()
) {
const IPAD: u8 = 0x36;
const OPAD: u8 = 0x5C;
let mut hash = H::new();
let key_block = &mut Zeroizing::new([0; MAX_HASH_BLOCKLEN])[..H::BLOCKLEN];
// append zeros to the end of K to create a B byte string
key_block[..key.as_ref().len()].copy_from_slice(key.as_ref());
// out = H(K XOR ipad, text)
for p in key_block.iter_mut() {
*p ^= IPAD;
}
hash.update(&key_block);
(write_text)(&mut hash);
hash.finalize_reset(out);
// out = H(K XOR opad, out)
for p in key_block.iter_mut() {
*p ^= IPAD ^ OPAD;
}
hash.update(&key_block);
hash.update(out.as_ref());
hash.finalize_reset(out);
}
/// Takes a chaining_key byte sequence of length HASHLEN, and an input_key_material byte sequence with length either
/// zero bytes, 32 bytes, or DHLEN bytes. Returns a pair or triple of byte sequences each of length HASHLEN,
/// depending on whether num_outputs is two or three:
pub fn hkdf<const L: usize, H: HashFunction<L>>(
chaining_key: &[u8; L],
input_key_material: &[u8],
outputs: &mut [&mut [u8; L]],
) {
assert!(matches!(outputs.len(), 2..=3));
let mut temp_key = Zeroizing::new([0; L]);
hmac::<L, H>(chaining_key, &mut temp_key, |hash| {
hash.update(input_key_material)
});
let mut prev = &[][..];
for (i, output) in outputs.iter_mut().enumerate() {
hmac::<L, H>(&temp_key, *output, |hash| {
hash.update(prev);
hash.update(&[i as u8 + 1]);
});
prev = (*output).as_ref();
}
}