reach_core/
traits.rs

1// SPDX-FileCopyrightText: 2023—2025 eaon <eaon@posteo.net>
2// SPDX-License-Identifier: EUPL-1.2
3
4use rand_core::CryptoRngCore;
5#[cfg(any(feature = "reachable", feature = "server", feature = "attestant"))]
6use sha3::{Digest, Sha3_512};
7
8use reach_aliases::*;
9#[cfg(feature = "reachable")]
10use reach_signatures::Signatures;
11#[cfg(any(feature = "reachable", feature = "server", feature = "attestant"))]
12use reach_signatures::Verifier;
13#[cfg(any(feature = "reachable", feature = "server"))]
14use reach_signatures::verify_digest;
15#[cfg(any(feature = "reachable", feature = "attestant"))]
16use reach_signatures::{Digestible, Sign, VerifyingKeys};
17
18use crate::error;
19#[cfg(any(feature = "reachable", feature = "server", feature = "attestant"))]
20use crate::wire::{AuthenticationAssurance, AuthenticationChallenge};
21
22pub trait RandomFromRng {
23    fn random_from_rng(csprng: &mut impl CryptoRngCore) -> Self;
24}
25
26macro_rules! byte_array_random_from_rng {
27    ($length:tt) => {
28        impl RandomFromRng for [u8; $length] {
29            fn random_from_rng(csprng: &mut impl CryptoRngCore) -> Self {
30                let mut byte_array = [0u8; $length];
31                csprng.fill_bytes(&mut byte_array);
32
33                byte_array
34            }
35        }
36    };
37}
38
39byte_array_random_from_rng!(16);
40byte_array_random_from_rng!(24);
41byte_array_random_from_rng!(32);
42byte_array_random_from_rng!(64);
43
44#[cfg(any(feature = "reaching", feature = "reachable"))]
45mod reaching_reachable {
46    use std::ops::Deref;
47
48    use crate::ParticipantType;
49
50    use super::*;
51
52    pub trait ParticipantPublicKeys {
53        fn ec_public_key(&self) -> &X25519Public;
54        fn pq_public_key(&self) -> &MlKemPublic;
55        fn participant_type(&self) -> ParticipantType;
56    }
57
58    impl ParticipantPublicKeys for Box<&dyn ParticipantPublicKeys> {
59        fn ec_public_key(&self) -> &X25519Public {
60            self.deref().ec_public_key()
61        }
62
63        fn pq_public_key(&self) -> &MlKemPublic {
64            self.deref().pq_public_key()
65        }
66
67        fn participant_type(&self) -> ParticipantType {
68            self.deref().participant_type()
69        }
70    }
71}
72
73#[cfg(any(feature = "reaching", feature = "reachable"))]
74pub use reaching_reachable::*;
75
76pub trait ProstEncode
77where
78    <Self as ProstEncode>::EncodedType: ProstMessage + for<'a> From<&'a Self>,
79{
80    type EncodedType;
81
82    fn encode<B>(&self, buf: &mut B) -> Result<(), error::EncodeError>
83    where
84        B: bytes::BufMut,
85    {
86        Self::EncodedType::from(self)
87            .encode(buf)
88            .map_err(error::EncodeError::from)
89    }
90
91    fn encode_to_vec(&self) -> Vec<u8> {
92        Self::EncodedType::from(self).encode_to_vec()
93    }
94}
95
96pub trait ProstEncodeOwned: Sized
97where
98    <Self as ProstEncodeOwned>::EncodedType: ProstMessage + From<Self>,
99{
100    type EncodedType;
101
102    fn encode<B>(self, buf: &mut B) -> Result<(), error::EncodeError>
103    where
104        B: bytes::BufMut,
105    {
106        Self::EncodedType::from(self)
107            .encode(buf)
108            .map_err(error::EncodeError::from)
109    }
110
111    fn encode_to_vec(self) -> Vec<u8> {
112        Self::EncodedType::from(self).encode_to_vec()
113    }
114}
115
116pub trait ProstDecode
117where
118    Self: Sized + TryFrom<Self::EncodedType, Error = error::DecodeError>,
119    <Self as ProstDecode>::EncodedType: ProstMessage + Default,
120{
121    type EncodedType;
122
123    fn decode<M>(message: M) -> Result<Self, error::DecodeError>
124    where
125        M: AsRef<[u8]>,
126    {
127        Self::try_from(Self::EncodedType::decode(message.as_ref())?)
128    }
129}
130
131#[cfg(any(feature = "reachable", feature = "attestant"))]
132pub trait AssureAuthentication {
133    fn assure_authentication<V>(
134        &self,
135        verifier: &V,
136        authentication_challenge: &AuthenticationChallenge,
137        csprng: &mut impl CryptoRngCore,
138    ) -> AuthenticationAssurance
139    where
140        V: Verifier + Digestible,
141        Self: Sign + VerifyingKeys<V>,
142    {
143        let epoch_timeframe = time::OffsetDateTime::now_utc().unix_timestamp() / 30;
144
145        let mut hasher = <Sha3_512 as Digest>::new();
146        hasher.update(authentication_challenge.authentication_challenge.as_slice());
147        hasher.update(epoch_timeframe.to_le_bytes().as_slice());
148
149        let (ec_signature, pq_signature) =
150            self.sign_digest(b"Authentication Assurance", hasher, csprng);
151
152        AuthenticationAssurance {
153            authenticator_id: verifier.finalized_digest(),
154            ec_signature,
155            pq_signature,
156        }
157    }
158}
159
160#[cfg(any(feature = "reachable", feature = "server"))]
161pub trait AuditAuthenticationAssurance {
162    fn audit_authentication_assurance(
163        &self,
164        authentication_assurance: &AuthenticationAssurance,
165        authentication_challenge: &AuthenticationChallenge,
166    ) -> bool
167    where
168        Self: Verifier,
169    {
170        let epoch_timeframe = time::OffsetDateTime::now_utc().unix_timestamp() / 30;
171
172        // Signatures ought to be valid for at least 30 seconds, with this approach (that is similar
173        // to TOTP) signatures are valid for a maximum of 60 seconds.
174        for i in 0..2 {
175            let mut hasher = <Sha3_512 as Digest>::new();
176            hasher.update(authentication_challenge.authentication_challenge);
177            hasher.update((epoch_timeframe + i).to_le_bytes());
178
179            if verify_digest(
180                self,
181                b"Authentication Assurance",
182                hasher,
183                authentication_assurance.ec_signature(),
184                authentication_assurance.pq_signature(),
185            ) {
186                return true;
187            }
188        }
189
190        false
191    }
192}