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
//! Continuations sealed with a key only this service holds.
//!
//! A sealed token is one format byte, a 24-byte nonce drawn for that token,
//! and the token's bytes sealed with XChaCha20-Poly1305 under the format byte,
//! written as lowercase hex. The caller who holds it can neither read nor
//! alter what is inside. The key is drawn from the operating system when the
//! service is made and lives only in its memory, so a token from another
//! service, or from before a restart, does not open.
use chacha20poly1305::aead::rand_core::RngCore;
use chacha20poly1305::aead::{Aead, KeyInit, OsRng, Payload};
use chacha20poly1305::{XChaCha20Poly1305, XNonce};
use super::codec;
use crate::serve::storage::Failure;
/// The first byte of every sealed token, and the data it is sealed under.
const FORMAT: u8 = 2;
const NONCE_BYTES: usize = 24;
/// This service's key for its continuations; never written, logged or shown.
pub struct Seal {
cipher: XChaCha20Poly1305,
}
/// A token that does not open, for every reason alike.
fn unopened() -> Failure {
Failure::new(
"query_continuation_invalid",
"the continuation was not made by this service as it runs now; the service may have \
restarted, so ask again from the first page",
)
}
impl Seal {
/// A key drawn from the operating system's randomness.
///
/// # Errors
///
/// When the operating system gives no randomness.
pub fn new() -> Result<Self, Failure> {
let mut key = [0_u8; 32];
OsRng.try_fill_bytes(&mut key).map_err(|error| {
Failure::new(
"query_seal_unavailable",
format!("no randomness for the continuation key: {error}"),
)
})?;
let cipher = XChaCha20Poly1305::new(&key.into());
key.fill(0);
Ok(Self { cipher })
}
/// `plain` sealed under a nonce drawn for it, as hex.
///
/// # Errors
///
/// When no nonce can be drawn or the bytes cannot be sealed.
pub fn seal(&self, plain: &[u8]) -> Result<String, Failure> {
let mut nonce = [0_u8; NONCE_BYTES];
OsRng
.try_fill_bytes(&mut nonce)
.map_err(|error| Failure::new("query_seal_unavailable", error.to_string()))?;
let payload = Payload {
msg: plain,
aad: &[FORMAT],
};
let sealed = self
.cipher
.encrypt(&XNonce::from(nonce), payload)
.map_err(|_| Failure::new("query_seal_unavailable", "the token could not be sealed"))?;
let mut token = Vec::with_capacity(1 + NONCE_BYTES + sealed.len());
token.push(FORMAT);
token.extend_from_slice(&nonce);
token.extend_from_slice(&sealed);
Ok(codec::encode(&token))
}
/// The bytes `token` was sealed from.
///
/// # Errors
///
/// `query_continuation_invalid` for a token that is not canonical hex, is
/// cut, altered, of another format, or sealed by another key.
pub fn open(&self, token: &str) -> Result<Vec<u8>, Failure> {
let bytes = codec::decode(token).map_err(|_| unopened())?;
let (format, rest) = bytes.split_first().ok_or_else(unopened)?;
if *format != FORMAT {
return Err(unopened());
}
let (nonce, sealed) = rest.split_at_checked(NONCE_BYTES).ok_or_else(unopened)?;
let payload = Payload {
msg: sealed,
aad: &[FORMAT],
};
self.cipher
.decrypt(XNonce::from_slice(nonce), payload)
.map_err(|_| unopened())
}
}