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
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use super::schedule;
use crate::crypto::awslc::DecryptKey;
use once_cell::sync::OnceCell;
use s2n_quic_core::varint::VarInt;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug)]
pub struct State {
current_id: AtomicU64,
pub(super) stateless_reset: [u8; 16],
control_secret: OnceCell<DecryptKey>,
}
impl State {
pub fn new(stateless_reset: [u8; 16]) -> Self {
Self {
current_id: AtomicU64::new(0),
stateless_reset,
control_secret: Default::default(),
}
}
pub fn next_key_id(&self) -> VarInt {
let id = self
.current_id
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
VarInt::try_from(current + 1)
.ok()
// Make sure we can always +1. This is a useful property for StaleKey packets
// which send a minimum *not yet seen* ID. In practice it shouldn't matter
// since we are assuming we can't hit 2^62, but this helps localize handling
// that edge to this code.
.filter(|id| *id != VarInt::MAX)
.map(|id| *id)
});
let id = id.expect("2^62 integer incremented per-path will not wrap");
// The atomic will not be incremented (i.e., would have panic'd above) if we do not fit
// into a VarInt.
VarInt::try_from(id).unwrap()
}
#[inline]
pub fn control_secret(&self, secret: &schedule::Secret) -> &DecryptKey {
self.control_secret.get_or_init(|| secret.control_opener())
}
/// Update the sender for a received stale key packet.
///
/// This increments the current ID we are sending at to at least the ID provided in the packet.
///
/// Note that this packet can be replayed without detection, we must deal with authenticated
/// but arbitrarily old IDs here. In the future we may want to guard against advancing too
/// quickly (e.g., due to bit flips), but for now we ignore that problem.
pub(super) fn update_for_stale_key(&self, min_key_id: VarInt) {
// Update the key to the new minimum to start at.
self.current_id.fetch_max(*min_key_id, Ordering::Relaxed);
}
}
#[test]
#[should_panic = "2^62 integer incremented"]
fn sender_does_not_wrap() {
let state = State::new([0; 16]);
assert_eq!(*state.next_key_id(), 0);
state.current_id.store((1 << 62) - 3, Ordering::Relaxed);
assert_eq!(*state.next_key_id(), (1 << 62) - 3);
assert_eq!(*state.next_key_id(), (1 << 62) - 2);
assert_eq!(*state.next_key_id(), (1 << 62) - 1);
// should panic
state.next_key_id();
}
#[test]
fn update_restarts_sequence() {
let state = State::new([0; 16]);
assert_eq!(*state.next_key_id(), 0);
state.update_for_stale_key(VarInt::new(3).unwrap());
// Update should start at the minimum trusted key ID on the other side.
assert_eq!(*state.next_key_id(), 3);
}