reach_core/wire/
mod.rs

1// SPDX-FileCopyrightText: 2023—2025 eaon <eaon@posteo.net>
2// SPDX-FileCopyrightText: 2024 Michael Goldenberg <m@mgoldenberg.net>
3// SPDX-License-Identifier: EUPL-1.2
4
5#[cfg(debug_assertions)]
6use rand_core::CryptoRngCore;
7use reach_proc_macros::{communicable, prosted};
8
9#[cfg(debug_assertions)]
10use ecdh_omr::Decoy;
11use reach_aliases::*;
12use reach_signatures::{Digestible, Signatures, Verifiable, Verifies};
13
14#[cfg(feature = "websocket")]
15use crate::macros::from_generic_websocket_error;
16#[cfg(any(feature = "attestant", feature = "server"))]
17use crate::storage::Storable;
18use crate::{communication::*, error, traits::*};
19
20#[cfg(any(feature = "reaching", feature = "reachable", feature = "server"))]
21mod macros;
22#[cfg(any(feature = "reaching", feature = "reachable", feature = "server"))]
23pub(crate) use macros::*;
24
25#[cfg(any(feature = "reaching", feature = "reachable", feature = "server"))]
26mod envelope;
27#[cfg(any(feature = "reaching", feature = "reachable", feature = "server"))]
28pub use envelope::*;
29
30#[cfg(any(feature = "reaching", feature = "reachable", feature = "server"))]
31mod message_vault;
32#[cfg(any(feature = "reaching", feature = "reachable", feature = "server"))]
33pub use message_vault::*;
34
35mod reach;
36pub use reach::*;
37
38mod v0 {
39    include!(concat!(env!("OUT_DIR"), "/reach.wire_v0.rs"));
40
41    pub(crate) mod communication {
42        include!(concat!(env!("OUT_DIR"), "/reach.wire_communication_v0.rs"));
43    }
44}
45
46pub(crate) mod proto {
47    use super::v0;
48
49    #[cfg(any(feature = "reaching", feature = "reachable", feature = "server"))]
50    pub(crate) use v0::{
51        CredentialVault, Envelope, EnvelopeId, EnvelopeSeed, MessageVault, MessageVaultId,
52        MessageVaultSeed, Reach, ReachableSignedKeys, RemoveEnvelopeIdHint, SealedEnvelopeId,
53        SealedMessageVaultId,
54    };
55
56    #[cfg(any(feature = "reachable", feature = "server"))]
57    pub(crate) use v0::{EnvelopeIds, RemoveReachablePublicKeys};
58
59    #[cfg(any(feature = "attestant", feature = "server"))]
60    pub(crate) use v0::RemoveReachableVerifyingKeys;
61
62    #[cfg(any(feature = "attestant", feature = "reachable", feature = "server"))]
63    pub(crate) use v0::{AuthenticationAssurance, AuthenticationChallenge};
64
65    pub(crate) use v0::{
66        AttestantVerifyingKeys, ReachablePublicKeys, ReachableVerifyingKeys, Salts,
67        SharedPublicKeys, SharedVerifyingKeys,
68    };
69
70    pub use v0::communication::{Request, Response};
71}
72
73pub use proto::{Request, Response};
74
75impl CommunicableType for Request {}
76impl CommunicableType for Response {}
77
78#[cfg(feature = "websocket")]
79from_generic_websocket_error!(Response);
80
81#[derive(Debug, Clone, Copy)]
82pub enum ErrorResponse {
83    Unsupported,
84    Internal,
85    Decode,
86}
87
88impl From<ErrorResponse> for Response {
89    fn from(value: ErrorResponse) -> Self {
90        match value {
91            ErrorResponse::Unsupported => Response::ErrorUnsupported,
92            ErrorResponse::Internal => Response::ErrorInternal,
93            ErrorResponse::Decode => Response::ErrorDecode,
94        }
95    }
96}
97
98impl ErrorSubset for Response {
99    type Error = ErrorResponse;
100}
101
102pub struct ReachCommunication {}
103
104impl CommunicableTypes for ReachCommunication {
105    type Req = Request;
106    type Resp = Response;
107    type Variant = RemoteCommunication;
108}
109
110#[cfg(any(feature = "reaching", feature = "reachable", feature = "server"))]
111mod reaching_reachable_server {
112    use super::*;
113
114    #[derive(Debug)]
115    pub struct Init(pub u8);
116
117    impl Communicable<Request> for Init {
118        fn to_communication(&self) -> Communication<Request> {
119            Communication::from(Request::Init, vec![self.0], None)
120        }
121
122        fn try_from_communication(
123            communication: &Communication<Request>,
124        ) -> Result<Self, error::DecodeError> {
125            if communication.r#type != Request::Init || communication.inner.len() != 1 {
126                return Err(error::DecodeError);
127            }
128
129            Ok(Init(communication.inner[0]))
130        }
131    }
132
133    impl MatchResponse<Request> for Init {
134        type Resp = ReachOk;
135    }
136}
137
138#[cfg(any(feature = "reaching", feature = "reachable", feature = "server"))]
139pub use reaching_reachable_server::*;
140
141#[cfg(any(feature = "reachable", feature = "attestant", feature = "server"))]
142mod reachable_attestant_server {
143    use super::*;
144
145    pub struct InitAuthenticatedSession(pub u8);
146
147    impl Communicable<Request> for InitAuthenticatedSession {
148        fn to_communication(&self) -> Communication<Request> {
149            Communication::from(Request::InitAuthenticatedSession, vec![self.0], None)
150        }
151
152        fn try_from_communication(
153            communication: &Communication<Request>,
154        ) -> Result<Self, error::DecodeError> {
155            if communication.r#type != Request::InitAuthenticatedSession
156                || communication.inner.len() != 1
157            {
158                return Err(error::DecodeError);
159            }
160
161            Ok(InitAuthenticatedSession(communication.inner[0]))
162        }
163    }
164
165    impl MatchResponse<Request> for InitAuthenticatedSession {
166        type Resp = AuthenticationChallenge;
167    }
168}
169
170#[cfg(any(feature = "reachable", feature = "attestant", feature = "server"))]
171pub use reachable_attestant_server::*;
172
173#[cfg(any(feature = "reachable", feature = "server"))]
174pub struct ListEnvelopeIds;
175
176#[cfg(any(feature = "reachable", feature = "server"))]
177pub struct GetSharedSecretKeys;
178
179#[derive(Debug)]
180#[communicable(Response::Ok, marker)]
181pub struct ReachOk;
182
183#[prosted(proto::Salts, Decode, Encode, ProstTraits)]
184#[derive(Debug, Clone, Copy)]
185pub struct Salts {
186    pub attestant_identity: ThreeTwo,
187    pub shared_secret: ThreeTwo,
188    pub reaching_static: ThreeTwo,
189    pub verifying_keys_mac: ThreeTwo,
190    pub reaching_current: ThreeTwo,
191    pub reaching_previous: ThreeTwo,
192    pub attestant_ec_signature: Ed25519Signature,
193    pub attestant_pq_signature: FnDsaSignature,
194}
195
196#[cfg(any(feature = "attestant", feature = "server"))]
197impl Storable for Salts {}
198
199#[cfg(debug_assertions)]
200impl Decoy for Salts {
201    fn random_decoy(csprng: &mut impl CryptoRngCore) -> Salts {
202        let mut attestant_pq_signature = reach_signatures::FN_DSA_SIGNATURE_EMPTY;
203        csprng.fill_bytes(&mut attestant_pq_signature);
204
205        Salts {
206            attestant_identity: ThreeTwo::random_from_rng(csprng),
207            shared_secret: ThreeTwo::random_from_rng(csprng),
208            reaching_static: ThreeTwo::random_from_rng(csprng),
209            verifying_keys_mac: ThreeTwo::random_from_rng(csprng),
210            reaching_current: ThreeTwo::random_from_rng(csprng),
211            reaching_previous: ThreeTwo::random_from_rng(csprng),
212            attestant_ec_signature: Ed25519Signature::from_bytes(&SixFour::random_from_rng(csprng)),
213            attestant_pq_signature,
214        }
215    }
216}
217
218impl Digestible for Salts {
219    fn hashable_bytes(&self) -> Vec<Box<dyn AsRef<[u8]> + '_>> {
220        vec![
221            Box::new(&self.attestant_identity),
222            Box::new(&self.shared_secret),
223            Box::new(&self.reaching_static),
224            Box::new(&self.verifying_keys_mac),
225        ]
226    }
227}
228
229impl Signatures for Salts {
230    fn ec_signature(&self) -> &Ed25519Signature {
231        &self.attestant_ec_signature
232    }
233
234    fn pq_signature(&self) -> &FnDsaSignature {
235        &self.attestant_pq_signature
236    }
237}
238
239impl Verifies<Salts> for AttestantVerifyingKeys {}
240
241impl Verifiable for Salts {}