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
use super::*;
use crate::{
node_info::NodeContact,
packet::{ChallengeData, Packet, PacketHeader, PacketKind, MESSAGE_NONCE_LENGTH},
};
use enr::{CombinedKey, NodeId};
use zeroize::Zeroize;
#[derive(Zeroize, PartialEq)]
pub(crate) struct Keys {
/// The encryption key.
encryption_key: [u8; 16],
/// The decryption key.
decryption_key: [u8; 16],
}
/// A Session containing the encryption/decryption keys. These are kept individually for a given
/// node.
pub(crate) struct Session {
/// The current keys used to encrypt/decrypt messages.
keys: Keys,
/// If a new handshake is being established, the older keys are maintained as race
/// conditions in the handshake can give different views of which keys are canon.
/// The key that worked to decrypt our last message (or are freshly established) exist in
/// `keys` and previous keys are optionally stored in `old_keys`. We attempt to decrypt
/// messages with `keys` before optionally trying `old_keys`.
old_keys: Option<Keys>,
/// If we contacted this node without an ENR, i.e. via a multiaddr, during the session
/// establishment we request the nodes ENR. Once the ENR is received and verified, this session
/// becomes established.
///
/// This field holds the request_id associated with the ENR request.
pub awaiting_enr: Option<RequestId>,
/// Number of messages sent. Used to ensure the nonce used in message encryption is always
/// unique.
counter: u32,
}
impl Session {
pub fn new(keys: Keys) -> Self {
Session {
keys,
old_keys: None,
awaiting_enr: None,
counter: 0,
}
}
/// A new session has been established. Update this session based on the new session.
pub fn update(&mut self, new_session: Session) {
// Optimistically assume the new keys are canonical.
self.old_keys = Some(std::mem::replace(&mut self.keys, new_session.keys));
self.awaiting_enr = new_session.awaiting_enr;
}
/// Uses the current `Session` to encrypt a message. Encrypt packets with the current session
/// key if we are awaiting a response from AuthMessage.
pub(crate) fn encrypt_message(
&mut self,
src_id: NodeId,
message: &[u8],
) -> Result<Packet, Discv5Error> {
self.counter += 1;
// If the message nonce length is ever set below 4 bytes this will explode. The packet
// size constants shouldn't be modified.
let random_nonce: [u8; MESSAGE_NONCE_LENGTH - 4] = rand::random();
let mut message_nonce: MessageNonce = [0u8; crate::packet::MESSAGE_NONCE_LENGTH];
message_nonce[..4].copy_from_slice(&self.counter.to_be_bytes());
message_nonce[4..].copy_from_slice(&random_nonce);
// the authenticated data is the IV concatenated with the packet header
let iv: u128 = rand::random();
let header = PacketHeader {
message_nonce,
kind: PacketKind::Message { src_id },
};
let mut authenticated_data = iv.to_be_bytes().to_vec();
authenticated_data.extend_from_slice(&header.encode());
let cipher = crypto::encrypt_message(
&self.keys.encryption_key,
message_nonce,
message,
&authenticated_data,
)?;
// construct a packet from the header and the cipher text
Ok(Packet {
iv,
header,
message: cipher,
})
}
/// Decrypts an encrypted message. If a Session is already established, the original decryption
/// keys are tried first, upon failure, the new keys are attempted. If the new keys succeed,
/// the session keys are updated along with the Session state.
pub(crate) fn decrypt_message(
&mut self,
message_nonce: MessageNonce,
message: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, Discv5Error> {
// First try with the canonical keys.
let result_canon =
crypto::decrypt_message(&self.keys.decryption_key, message_nonce, message, aad);
// If decryption is fine, nothing more to do.
if result_canon.is_ok() {
return result_canon;
}
// If these keys did not work, try old_keys
if let Some(old_keys) = self.old_keys.take() {
let result =
crypto::decrypt_message(&old_keys.decryption_key, message_nonce, message, aad);
if result.is_ok() {
// rotate the keys
self.old_keys = Some(std::mem::replace(&mut self.keys, old_keys));
}
return result;
}
result_canon
}
/* Session Helper Functions */
/// Generates session keys from an authentication header. If the IP of the ENR does not match the
/// source IP address, we consider this session untrusted. The output returns a boolean which
/// specifies if the Session is trusted or not.
pub(crate) fn establish_from_challenge(
local_key: Arc<RwLock<CombinedKey>>,
local_id: &NodeId,
remote_id: &NodeId,
challenge: Challenge,
id_nonce_sig: &[u8],
ephem_pubkey: &[u8],
enr_record: Option<Enr>,
) -> Result<(Session, Enr), Discv5Error> {
// check and verify a potential ENR update
// Duplicate code here to avoid cloning an ENR
let remote_public_key = {
let enr = match (enr_record.as_ref(), challenge.remote_enr.as_ref()) {
(Some(new_enr), Some(known_enr)) => {
if new_enr.seq() > known_enr.seq() {
new_enr
} else {
known_enr
}
}
(Some(new_enr), None) => new_enr,
(None, Some(known_enr)) => known_enr,
(None, None) => {
warn!(
"Peer did not respond with their ENR. Session could not be established. Node: {}",
remote_id
);
return Err(Discv5Error::SessionNotEstablished);
}
};
enr.public_key()
};
// verify the auth header nonce
if !crypto::verify_authentication_nonce(
&remote_public_key,
ephem_pubkey,
&challenge.data,
local_id,
id_nonce_sig,
) {
return Err(Discv5Error::InvalidChallengeSignature(challenge));
}
// The keys are derived after the message has been verified to prevent potential extra work
// for invalid messages.
// generate session keys
let (decryption_key, encryption_key) = crypto::derive_keys_from_pubkey(
&local_key.read(),
local_id,
remote_id,
&challenge.data,
ephem_pubkey,
)?;
let keys = Keys {
encryption_key,
decryption_key,
};
// Takes ownership of the provided ENRs - Slightly annoying code duplication, but avoids
// cloning ENRs
let session_enr = match (enr_record, challenge.remote_enr) {
(Some(new_enr), Some(known_enr)) => {
if new_enr.seq() > known_enr.seq() {
new_enr
} else {
known_enr
}
}
(Some(new_enr), None) => new_enr,
(None, Some(known_enr)) => known_enr,
(None, None) => unreachable!("Checked in the first match above"),
};
Ok((Session::new(keys), session_enr))
}
/// Encrypts a message and produces an AuthMessage.
pub(crate) fn encrypt_with_header(
remote_contact: &NodeContact,
local_key: Arc<RwLock<CombinedKey>>,
updated_enr: Option<Enr>,
local_node_id: &NodeId,
challenge_data: &ChallengeData,
message: &[u8],
) -> Result<(Packet, Session), Discv5Error> {
// generate the session keys
let (encryption_key, decryption_key, ephem_pubkey) =
crypto::generate_session_keys(local_node_id, remote_contact, challenge_data)?;
let keys = Keys {
encryption_key,
decryption_key,
};
// construct the nonce signature
let sig = crypto::sign_nonce(
&local_key.read(),
challenge_data,
&ephem_pubkey,
&remote_contact.node_id(),
)
.map_err(|_| Discv5Error::Custom("Could not sign WHOAREYOU nonce"))?;
// build an authentication packet
let message_nonce: MessageNonce = rand::random();
let mut packet = Packet::new_authheader(
*local_node_id,
message_nonce,
sig,
ephem_pubkey,
updated_enr,
);
// Create the authenticated data for the new packet.
let mut authenticated_data = packet.iv.to_be_bytes().to_vec();
authenticated_data.extend_from_slice(&packet.header.encode());
// encrypt the message
let message_ciphertext =
crypto::encrypt_message(&encryption_key, message_nonce, message, &authenticated_data)?;
packet.message = message_ciphertext;
let session = Session::new(keys);
Ok((packet, session))
}
}