securedrop_protocol_minimal/
keys.rs1mod 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::dh_akem::DH_AKEM_PUBLIC_KEY_LEN;
13use crate::primitives::mlkem::MLKEM768_PUBLIC_KEY_LEN;
14use crate::primitives::x25519::{DH_PUBLIC_KEY_LEN, DHPrivateKey, DHPublicKey, DHSharedSecret};
15use crate::primitives::xwing::XWING_PUBLIC_KEY_LEN;
16use alloc::string::String;
17use alloc::vec::Vec;
18use serde::de::Error as _;
19use serde::{Deserialize, Serialize};
20
21pub struct KeyPair<SK, PK> {
23 pub(crate) sk: SK,
24 pub(crate) pk: PK,
25}
26
27pub type DhFetchKeyPair = KeyPair<DHPrivateKey, DHPublicKey>;
30pub type SigningKeyPair = KeyPair<SigningKey, VerifyingKey>;
31
32pub type SignedKeyBundlePublic = (KeyBundlePublic, Signature<JournalistEphemeralKey>);
35
36#[derive(Debug, Clone)]
38#[cfg_attr(not(hax), derive(Serialize, Deserialize))]
39pub struct KeyBundlePublic {
40 pub apke_pk: MessagePublicKey,
42 pub metadata_pk: MetadataPublicKey,
44}
45
46impl KeyBundlePublic {
47 pub fn as_bytes(&self) -> Vec<u8> {
51 let mut out = Vec::new();
52 out.extend_from_slice(&self.apke_pk.as_bytes());
53 out.extend_from_slice(&self.metadata_pk.as_bytes());
54 out
55 }
56}
57
58pub(crate) struct MessageKeyBundle {
59 pub(crate) apke: MessageKeyPair,
60 pub(crate) metadata_kp: MetadataKeyPair,
61}
62
63impl MessageKeyBundle {
64 pub fn new(apke: MessageKeyPair, metadata_kp: MetadataKeyPair) -> Self {
65 Self { apke, metadata_kp }
66 }
67
68 pub(crate) fn public(&self) -> KeyBundlePublic {
69 KeyBundlePublic {
70 apke_pk: self.apke.public_key().clone(),
71 metadata_pk: self.metadata_kp.public_key().clone(),
72 }
73 }
74}
75
76pub(crate) struct SignedMessageKeyBundle {
77 pub(crate) bundle: MessageKeyBundle,
78 pub(crate) selfsig: Signature<JournalistEphemeralKey>,
79}
80
81#[derive(Debug, Clone)]
82pub struct SignedLongtermPubKeyBytes(
83 pub [u8; DH_AKEM_PUBLIC_KEY_LEN + MLKEM768_PUBLIC_KEY_LEN + DH_PUBLIC_KEY_LEN],
84);
85
86impl SignedLongtermPubKeyBytes {
87 pub(crate) fn from_keys(reply_apke: &MessagePublicKey, fetch_pk: &DHPublicKey) -> Self {
92 let apke_bytes = reply_apke.as_bytes();
93 let fetch_bytes = fetch_pk.into_bytes();
94
95 let mut pubkey_bytes =
96 [0u8; DH_AKEM_PUBLIC_KEY_LEN + MLKEM768_PUBLIC_KEY_LEN + DH_PUBLIC_KEY_LEN];
97 pubkey_bytes[..apke_bytes.len()].copy_from_slice(&apke_bytes);
98 pubkey_bytes[apke_bytes.len()..].copy_from_slice(&fetch_bytes);
99
100 Self(pubkey_bytes)
101 }
102
103 pub fn as_bytes(&self) -> &[u8] {
105 &self.0
106 }
107}
108
109#[cfg_attr(hax, hax_lib::exclude)]
110impl Serialize for SignedLongtermPubKeyBytes {
111 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
112 ser.serialize_str(&hex::encode(self.0))
113 }
114}
115
116#[cfg_attr(hax, hax_lib::exclude)]
117impl<'de> Deserialize<'de> for SignedLongtermPubKeyBytes {
118 fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
119 let s = String::deserialize(de)?;
120 let mut bytes = [0u8; DH_AKEM_PUBLIC_KEY_LEN + MLKEM768_PUBLIC_KEY_LEN + DH_PUBLIC_KEY_LEN];
121 hex::decode_to_slice(s.trim(), &mut bytes).map_err(D::Error::custom)?;
122 Ok(Self(bytes))
123 }
124}
125
126#[derive(Clone, Debug)]
127#[cfg_attr(not(hax), derive(Serialize, Deserialize))]
128pub struct Enrollment {
129 pub bundle: SignedLongtermPubKeyBytes,
130 pub selfsig: Signature<JournalistLongTermKey>,
131 pub keys: (VerifyingKey, DHPublicKey, MessagePublicKey),
132}
133
134pub struct SessionStorage {
136 pub fpf_key: Option<VerifyingKey>,
137 pub nr_key: Option<VerifyingKey>,
138 pub fpf_signature: Option<Signature<FpfOnNewsroom>>,
139}
140
141pub struct FPFKeyPair {
143 sk: SigningKey,
144 vk: VerifyingKey,
145}
146
147#[cfg_attr(hax, hax_lib::exclude)]
150impl core::fmt::Debug for FPFKeyPair {
151 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
152 f.debug_struct("FPFKeyPair")
153 .field("vk", &self.vk)
154 .finish_non_exhaustive()
155 }
156}
157
158impl FPFKeyPair {
159 pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, anyhow::Error> {
165 let sk = SigningKey::new(rng)?;
166 let vk = sk.vk;
167 Ok(Self { sk, vk })
168 }
169
170 pub fn verifying_key(&self) -> VerifyingKey {
172 self.vk
173 }
174
175 pub fn sign<D: DomainTag>(&self, msg: &[u8]) -> Signature<D> {
177 self.sk.sign(msg)
178 }
179
180 pub fn as_bytes(&self) -> [u8; 32] {
182 self.sk.as_bytes()
183 }
184
185 pub fn from_bytes(seed: [u8; 32]) -> Self {
187 let sk = SigningKey::from_seed(seed);
188 let vk = sk.vk;
189 Self { sk, vk }
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use proptest::prelude::*;
197
198 proptest! {
199 #[test]
200 fn fpf_keypair_seed_roundtrip(seed: [u8; 32]) {
201 let kp = FPFKeyPair::from_bytes(seed);
202 prop_assert_eq!(kp.as_bytes(), seed);
203 let kp2 = FPFKeyPair::from_bytes(kp.as_bytes());
204 prop_assert_eq!(
205 kp.verifying_key().into_bytes(),
206 kp2.verifying_key().into_bytes()
207 );
208 }
209 }
210}
211
212pub use newsroom::NewsroomKeyPair;