nodedb_wal/segment/decrypt.rs
1// SPDX-License-Identifier: Apache-2.0
2
3//! Segment-scoped payload decryption.
4//!
5//! Decryption belongs at the point records leave the WAL layer, not at each
6//! consumer. A record's AAD binds its ciphertext to the segment preamble it
7//! was written under, so only code that still holds the open segment knows the
8//! epoch needed to decrypt it. Pushing that knowledge outwards would mean every
9//! replay consumer re-deriving it — and any consumer that forgot would silently
10//! feed ciphertext into an engine decoder.
11//!
12//! [`SegmentDecryptor`] is therefore constructed once per segment — by the
13//! readers themselves, at open time — and applied to every record before it is
14//! handed out. A record that is marked encrypted with no key ring available is
15//! a hard [`WalError::EncryptedRecordWithoutKey`], never a passthrough and
16//! never a skip.
17
18use crate::crypto::KeyRing;
19use crate::error::{Result, WalError};
20use crate::preamble::{PREAMBLE_SIZE, SegmentPreamble};
21use crate::record::{RecordHeader, WalRecord};
22
23/// The per-segment inputs to the AAD: the epoch used to rebuild the nonce, and
24/// the preamble bytes that were prepended to the header at encryption time.
25struct SegmentAad {
26 epoch: [u8; 4],
27 preamble_bytes: [u8; PREAMBLE_SIZE],
28}
29
30/// Turns the records of one WAL segment back into plaintext.
31///
32/// Owns both the segment's AAD inputs and its key ring rather than borrowing
33/// them: a reader builds one at open time and keeps it for as long as the file
34/// handle lives, and borrowing the caller's ring would put a lifetime on every
35/// reader type that outlives the call that constructed it.
36pub struct SegmentDecryptor {
37 ring: Option<KeyRing>,
38 aad: Option<SegmentAad>,
39}
40
41impl SegmentDecryptor {
42 /// Build a decryptor for a segment from its preamble (absent on segments
43 /// written without encryption) and the replay key ring (absent when the
44 /// database is not configured for WAL encryption).
45 pub fn new(preamble: Option<&SegmentPreamble>, ring: Option<&KeyRing>) -> Self {
46 Self {
47 ring: ring.cloned(),
48 aad: preamble.map(|p| SegmentAad {
49 epoch: *p.epoch(),
50 preamble_bytes: p.to_bytes(),
51 }),
52 }
53 }
54
55 /// Return `record` as plaintext, with `ENCRYPTED_FLAG` cleared and its CRC
56 /// recomputed. Unencrypted records pass through untouched.
57 pub fn decrypt_record(&self, record: WalRecord) -> Result<WalRecord> {
58 if !record.is_encrypted() {
59 return Ok(record);
60 }
61 let (ring, aad) = self.require_keys(record.header.lsn)?;
62 record.into_decrypted(&aad.epoch, Some(&aad.preamble_bytes), Some(ring))
63 }
64
65 /// Return the plaintext for a payload that was read separately from its
66 /// header, as the lazy reader does. Unencrypted payloads pass through.
67 pub fn decrypt_payload(&self, header: &RecordHeader, payload: Vec<u8>) -> Result<Vec<u8>> {
68 let record = WalRecord {
69 header: *header,
70 payload,
71 };
72 if !record.is_encrypted() {
73 return Ok(record.payload);
74 }
75 let (ring, aad) = self.require_keys(header.lsn)?;
76 record.decrypt_payload_ring(&aad.epoch, Some(&aad.preamble_bytes), Some(ring))
77 }
78
79 /// Resolve the key ring and segment AAD, or explain which one is missing.
80 fn require_keys(&self, lsn: u64) -> Result<(&KeyRing, &SegmentAad)> {
81 const SITE: &str = "WAL segment replay";
82 let ring = match self.ring.as_ref() {
83 Some(ring) => ring,
84 None => {
85 let err = WalError::EncryptedRecordWithoutKey { lsn, context: SITE };
86 crate::diag::encrypted_record_without_key(&err, lsn, SITE);
87 return Err(err);
88 }
89 };
90 // An encrypted record can only exist in a segment whose preamble
91 // recorded the epoch it was encrypted under. A missing preamble means
92 // the segment's leading bytes are gone, not that the record is legible.
93 let aad = self.aad.as_ref().ok_or_else(|| WalError::CorruptRecord {
94 lsn,
95 detail: "encrypted record in a segment with no preamble — the epoch \
96 needed to decrypt it is unrecoverable"
97 .into(),
98 })?;
99 Ok((ring, aad))
100 }
101}