Skip to main content

securedrop_protocol_minimal/
keys.rs

1mod newsroom;
2
3use rand_core::{CryptoRng, RngCore};
4
5use crate::sign::{
6    DomainTag, FpfOnNewsroom, JournalistEphemeralKey, JournalistLongTermKey, Signature, SigningKey,
7    VerifyingKey,
8};
9
10use crate::message::{MessageKeyPair, MessagePublicKey};
11use crate::metadata::{MetadataKeyPair, MetadataPublicKey};
12use crate::primitives::x25519::DHPrivateKey;
13use crate::primitives::x25519::DHPublicKey;
14use alloc::vec::Vec;
15
16use crate::constants::*;
17
18/// Generic KeyPair
19pub struct KeyPair<SK, PK> {
20    pub(crate) sk: SK,
21    pub(crate) pk: PK,
22}
23
24// silly name but include "fetch" for disambiguation with dh-akem.
25// eventually: ristretto255
26pub type DhFetchKeyPair = KeyPair<DHPrivateKey, DHPublicKey>;
27pub type SigningKeyPair = KeyPair<SigningKey, VerifyingKey>;
28
29/// The public half of an ephemeral key bundle together with the journalist's
30/// self-signature over it.
31pub type SignedKeyBundlePublic = (KeyBundlePublic, Signature<JournalistEphemeralKey>);
32
33/// The public keys that make up one ephemeral key bundle
34#[derive(Debug, Clone)]
35pub struct KeyBundlePublic {
36    /// SD-APKE ephemeral key `pk_{J,i}^{APKE_E} = (pk1, pk2)`.
37    pub apke_pk: MessagePublicKey,
38    /// SD-PKE ephemeral key, used for metadata protection.
39    pub metadata_pk: MetadataPublicKey,
40}
41
42impl KeyBundlePublic {
43    /// Serialize the bundle public keys in canonical byte order for signing.
44    ///
45    /// Layout: `pk_{J,i}^{APKE_E}(DHKEM) || pk_{J,i}^{APKE_E}(ML-KEM) || pk_{J,i}^{PKE_E}(X-Wing)`
46    pub fn as_bytes(&self) -> Vec<u8> {
47        let mut out = Vec::new();
48        out.extend(self.apke_pk.as_bytes());
49        out.extend(self.metadata_pk.as_bytes());
50        out
51    }
52}
53
54pub(crate) struct MessageKeyBundle {
55    pub(crate) apke: MessageKeyPair,
56    pub(crate) metadata_kp: MetadataKeyPair,
57}
58
59impl MessageKeyBundle {
60    pub fn new(apke: MessageKeyPair, metadata_kp: MetadataKeyPair) -> Self {
61        Self { apke, metadata_kp }
62    }
63
64    pub(crate) fn public(&self) -> KeyBundlePublic {
65        KeyBundlePublic {
66            apke_pk: self.apke.public_key().clone(),
67            metadata_pk: self.metadata_kp.public_key().clone(),
68        }
69    }
70}
71
72pub(crate) struct SignedMessageKeyBundle {
73    pub(crate) bundle: MessageKeyBundle,
74    pub(crate) selfsig: Signature<JournalistEphemeralKey>,
75}
76
77#[derive(Debug, Clone)]
78pub struct SignedLongtermPubKeyBytes(
79    pub [u8; LEN_DHKEM_ENCAPS_KEY + LEN_MLKEM_ENCAPS_KEY + LEN_DH_ITEM],
80);
81
82impl SignedLongtermPubKeyBytes {
83    /// Serialize long-term public keys into the canonical byte encoding.
84    ///
85    /// Byte layout (per spec ยง3.1): `pk_J^APKE || pk_J^fetch`
86    /// where `pk_J^APKE = pk_J^AKEM (DH-AKEM) || pk_J^PQ (ML-KEM)`
87    pub(crate) fn from_keys(reply_apke: &MessagePublicKey, fetch_pk: &DHPublicKey) -> Self {
88        let apke_bytes = reply_apke.as_bytes();
89        let fetch_bytes = fetch_pk.into_bytes();
90
91        let mut pubkey_bytes = [0u8; LEN_DHKEM_ENCAPS_KEY + LEN_MLKEM_ENCAPS_KEY + LEN_DH_ITEM];
92        pubkey_bytes[..apke_bytes.len()].copy_from_slice(&apke_bytes);
93        pubkey_bytes[apke_bytes.len()..].copy_from_slice(&fetch_bytes);
94
95        Self(pubkey_bytes)
96    }
97
98    /// Return the canonical byte encoding of the long-term public keys.
99    pub fn as_bytes(&self) -> &[u8] {
100        &self.0
101    }
102}
103
104#[derive(Clone, Debug)]
105pub struct Enrollment {
106    pub bundle: SignedLongtermPubKeyBytes,
107    pub selfsig: Signature<JournalistLongTermKey>,
108    pub keys: (VerifyingKey, DHPublicKey, MessagePublicKey),
109}
110
111// in memory session storage
112pub struct SessionStorage {
113    pub fpf_key: Option<VerifyingKey>,
114    pub nr_key: Option<VerifyingKey>,
115    pub fpf_signature: Option<Signature<FpfOnNewsroom>>,
116}
117
118/// A key pair for FPF (Freedom of the Press Foundation).
119pub struct FPFKeyPair {
120    sk: SigningKey,
121    vk: VerifyingKey,
122}
123
124impl core::fmt::Debug for FPFKeyPair {
125    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
126        f.debug_struct("FPFKeyPair")
127            .field("vk", &self.vk)
128            .finish_non_exhaustive()
129    }
130}
131
132impl FPFKeyPair {
133    /// Generate a new FPF key pair.
134    ///
135    /// # Errors
136    ///
137    /// Returns an error if the key generation fails.
138    pub fn new<R: RngCore + CryptoRng>(mut rng: R) -> Result<Self, anyhow::Error> {
139        let sk = SigningKey::new(&mut rng)?;
140        let vk = sk.vk;
141        Ok(Self { sk, vk })
142    }
143
144    /// Returns the verification key.
145    pub fn verifying_key(&self) -> VerifyingKey {
146        self.vk
147    }
148
149    /// Sign `msg` in domain `D` using the FPF signing key.
150    pub fn sign<D: DomainTag>(&self, msg: &[u8]) -> Signature<D> {
151        self.sk.sign(msg)
152    }
153}
154
155pub use newsroom::NewsroomKeyPair;