reachable_node/server/
mod.rs1use axum::{body::Body, http::HeaderValue, response::Response};
5use hyper::{StatusCode, header};
6use include_dir::{Dir, include_dir};
7use rand_core::CryptoRngCore;
8
9use reach_aliases::*;
10use reach_core::{AuditAuthenticationAssurance, RandomFromRng, error, storage::*, wire::*};
11use reach_signatures::Verifier;
12
13pub mod memory;
14use memory::Sealable;
15
16pub mod net;
17use net::SessionState;
18
19pub const DIST_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../reaching-app/dist");
20
21pub fn from_included_dir<A>(directory: &'static Dir<'_>, path: A) -> Response
22where
23 A: AsRef<str>,
24{
25 let content_type = mime_guess::from_path(path.as_ref()).first_or_octet_stream();
26 let path = path.as_ref().trim_start_matches('/');
27
28 match directory.get_file(path) {
29 None => Response::builder()
30 .status(StatusCode::NOT_FOUND)
31 .body(Body::empty())
32 .unwrap(),
33 Some(file) => Response::builder()
34 .status(StatusCode::OK)
35 .header(
36 header::CONTENT_TYPE,
37 HeaderValue::from_str(content_type.essence_str()).unwrap(),
38 )
39 .body(Body::from(file.contents()))
40 .unwrap(),
41 }
42}
43
44#[macro_export]
45#[doc(hidden)]
46macro_rules! _included {
47 ($included_dir:tt, $included_path:tt) => {
48 || async { reachable_node::server::from_included_dir(&$included_dir, $included_path) }
49 };
50
51 ($included_dir:tt) => {
52 |axum::extract::Path(path): axum::extract::Path<String>| async {
53 reachable_node::server::from_included_dir(&$included_dir, path)
54 }
55 };
56}
57
58#[doc(inline)]
59pub use _included as included;
60
61impl Sealable<SealedEnvelopeId> for EnvelopeId {}
62impl Sealable<SealedMessageVaultId> for MessageVaultId {}
63
64type AddableEnvelopeMaterial<'a> = (
65 SealedEnvelopeId,
66 Vec<(BlindedPublicKey, OneSix)>,
67 Envelope,
68 &'a MessageVaultId,
69);
70
71pub fn assign_envelope_id(envelope_seed: EnvelopeSeed, id: EnvelopeId) -> Envelope {
72 let EnvelopeSeed {
73 blinded_public_keys: _,
74 credential_vaults,
75 nonce,
76 message_vault_passport_ciphertext,
77 } = envelope_seed;
78
79 Envelope {
80 id,
81 credential_vaults,
82 nonce,
83 message_vault_passport_ciphertext,
84 }
85}
86
87pub fn assign_message_vault_id(
88 message_vault_seed: MessageVaultSeed,
89 id: MessageVaultId,
90) -> MessageVault {
91 MessageVault {
92 id,
93 message_ciphertext: message_vault_seed.message_ciphertext,
94 }
95}
96
97pub fn update_reaching_salts(salts: &mut Salts, csprng: &mut impl CryptoRngCore) {
98 salts.reaching_previous = salts.reaching_current;
99 salts.reaching_current = ThreeTwo::random_from_rng(csprng);
100}
101
102pub fn process_envelope_seed<'a>(
103 envelope_seed: EnvelopeSeed,
104 message_vault_id: &'a MessageVaultId,
105 shared_public_keys: &'_ SharedPublicKeys,
106 salts: &'_ Salts,
107 csprng: &mut impl CryptoRngCore,
108) -> Result<AddableEnvelopeMaterial<'a>, error::CryptError> {
109 let envelope_id = EnvelopeId::random_from_rng(csprng);
110 let sealed_envelope_id = envelope_id.seal(shared_public_keys, salts, csprng)?;
111 let blinded_public_key_removal_token_pairs = envelope_seed
112 .blinded_public_keys
113 .clone()
114 .into_iter()
115 .map(|blinded_public_key| (blinded_public_key, OneSix::random_from_rng(csprng)))
116 .collect();
117 let envelope = assign_envelope_id(envelope_seed, envelope_id);
118
119 Ok((
120 sealed_envelope_id,
121 blinded_public_key_removal_token_pairs,
122 envelope,
123 message_vault_id,
124 ))
125}
126
127type AddableMessageVaultMaterial = (SealedMessageVaultId, MessageVault);
128
129pub fn process_message_vault_seed(
130 message_vault_seed: MessageVaultSeed,
131 shared_public_keys: &SharedPublicKeys,
132 salts: &Salts,
133 csprng: &mut impl CryptoRngCore,
134) -> Result<AddableMessageVaultMaterial, error::CryptError> {
135 let message_vault_id = MessageVaultId::random_from_rng(csprng);
136 let sealed_message_vault_id = message_vault_id.seal(shared_public_keys, salts, csprng)?;
137
138 Ok((
139 sealed_message_vault_id,
140 assign_message_vault_id(message_vault_seed, message_vault_id),
141 ))
142}
143
144pub trait SessionAuthentication {
145 const AUTHENTICATED_SESSION_STATE: SessionState;
146
147 fn attempt_session_authentication(
148 &self,
149 authentication_assurance: &AuthenticationAssurance,
150 authentication_challenge: &AuthenticationChallenge,
151 ) -> SessionState
152 where
153 Self: Verifier + AuditAuthenticationAssurance,
154 {
155 if self.audit_authentication_assurance(authentication_assurance, authentication_challenge) {
156 return Self::AUTHENTICATED_SESSION_STATE;
157 }
158
159 SessionState::Ready
160 }
161}
162
163impl SessionAuthentication for ReachableVerifyingKeys {
164 const AUTHENTICATED_SESSION_STATE: SessionState = SessionState::AuthenticatedReachable;
165}
166
167impl SessionAuthentication for AttestantAuthenticationKeys {
168 const AUTHENTICATED_SESSION_STATE: SessionState = SessionState::AuthenticatedAttestant;
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 use ecdh_omr::Decoy;
176 use reach_harness::seeded_rng;
177
178 #[test]
179 fn salts_update() {
180 let mut seeded_rng = seeded_rng(0);
181 let mut salts = Salts::random_decoy(&mut seeded_rng);
182 let expiring_secret_keys_salt = salts.reaching_current;
183 update_reaching_salts(&mut salts, &mut seeded_rng);
184
185 assert_eq!(expiring_secret_keys_salt, salts.reaching_previous);
186 assert_ne!(expiring_secret_keys_salt, salts.reaching_current);
187 }
188}