mirror of
https://github.com/zerotier/zssp.git
synced 2026-05-22 16:28:40 -07:00
added better fingerprint support
This commit is contained in:
@@ -63,6 +63,7 @@ impl CryptoLayer for TestApplication {
|
||||
type SessionData = u128;
|
||||
|
||||
type IncomingPacketBuffer = Vec<u8>;
|
||||
type FingerprintData = ();
|
||||
}
|
||||
#[allow(unused)]
|
||||
impl ApplicationLayer<TestApplication> for &TestApplication {
|
||||
@@ -82,6 +83,7 @@ impl ApplicationLayer<TestApplication> for &TestApplication {
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
identity: &[u8],
|
||||
_: Option<&()>,
|
||||
) -> AcceptAction<TestApplication> {
|
||||
AcceptAction {
|
||||
session_data: Some(1),
|
||||
@@ -93,15 +95,16 @@ impl ApplicationLayer<TestApplication> for &TestApplication {
|
||||
fn restore_by_fingerprint(
|
||||
&mut self,
|
||||
ratchet_fingerprint: &[u8; RATCHET_SIZE],
|
||||
) -> Result<Option<RatchetState>, std::io::Error> {
|
||||
) -> Result<Option<(RatchetState, ())>, std::io::Error> {
|
||||
let ratchets = self.ratchets.lock().unwrap();
|
||||
Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned())
|
||||
Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned().map(|r| (r, ())))
|
||||
}
|
||||
|
||||
fn restore_by_identity(
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
session_data: &u128,
|
||||
_: Option<&()>,
|
||||
) -> Result<Option<RatchetStates>, std::io::Error> {
|
||||
let ratchets = self.ratchets.lock().unwrap();
|
||||
Ok(ratchets.peer_map.get(session_data).cloned())
|
||||
|
||||
@@ -53,6 +53,7 @@ impl AsRef<[u8]> for PooledVec {
|
||||
#[allow(unused)]
|
||||
impl DefaultCrypto for TestApplication {
|
||||
type SessionData = ();
|
||||
type LookupData = ();
|
||||
type IncomingPacketBuffer = PooledVec;
|
||||
}
|
||||
|
||||
@@ -76,6 +77,7 @@ impl ApplicationLayer<TestApplication> for &TestApplication {
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
identity: &[u8],
|
||||
_: Option<&()>,
|
||||
) -> AcceptAction<TestApplication> {
|
||||
AcceptAction {
|
||||
session_data: Some(()),
|
||||
@@ -87,7 +89,7 @@ impl ApplicationLayer<TestApplication> for &TestApplication {
|
||||
fn restore_by_fingerprint(
|
||||
&mut self,
|
||||
ratchet_fingerprint: &[u8; RATCHET_SIZE],
|
||||
) -> Result<Option<RatchetState>, std::io::Error> {
|
||||
) -> Result<Option<(RatchetState, ())>, std::io::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -95,6 +97,7 @@ impl ApplicationLayer<TestApplication> for &TestApplication {
|
||||
&mut self,
|
||||
remote_static_key: &CrateP384PublicKey,
|
||||
session_data: &(),
|
||||
_: Option<&()>,
|
||||
) -> Result<Option<RatchetStates>, std::io::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -153,6 +153,22 @@ pub trait CryptoLayer: Sized {
|
||||
/// each session.
|
||||
type SessionData;
|
||||
|
||||
/// Type for arbitrary opaque object that is attached to a new connection attempt if Alice sends
|
||||
/// us a ratchet fingerprint recognized by `restore_by_fingerprint`.
|
||||
///
|
||||
/// If Alice continues to connect
|
||||
/// with us, then this object will be passed to `check_accept_session` and `restore_by_identity`.
|
||||
/// This is useful if the ratchet fingerprint was derived from a one-time password, in which
|
||||
/// case `FingerprintData` can contain metadata regarding the one-time password. This can be
|
||||
/// used by `check_accept_session` and `restore_by_identity` to perform additional
|
||||
/// authentication checks, such as validating the one-time password as an invitation code.
|
||||
///
|
||||
/// `FingerprintData` can also be used with extreme caution to cache database resources that can
|
||||
/// speed up the expected future calls to `check_accept_session` and `restore_by_identity`.
|
||||
/// If this is done, the implementor is required in `check_accept_session` to verify that the
|
||||
/// cached resources in `FingerprintData` indeed belong to the specified remote peer.
|
||||
type FingerprintData;
|
||||
|
||||
/// Data type for incoming packet buffers.
|
||||
///
|
||||
/// This can be something like `Vec<u8>` or `Box<[u8]>` or it can be something like a pooled
|
||||
@@ -190,6 +206,9 @@ pub trait ApplicationLayer<Crypto: CryptoLayer>: Sized {
|
||||
/// If this function is configured to always return true, it means peers will not be able to
|
||||
/// connect to us unless they had a prior-established ratchet key with us. This is the best way
|
||||
/// for the paranoid to enforce a manual allow-list.
|
||||
///
|
||||
/// Corresponds to the "Hello Requires Recognized Ratchet, π_1" security flag of Transition
|
||||
/// Algorithm 2 within the ZSSP whitepaper.
|
||||
fn hello_requires_recognized_ratchet(&mut self) -> bool;
|
||||
/// This function is called if we, as Alice, attempted to open a session with Bob using a
|
||||
/// non-empty ratchet key, but Bob does not have this ratchet key and wants to downgrade
|
||||
@@ -206,15 +225,29 @@ pub trait ApplicationLayer<Crypto: CryptoLayer>: Sized {
|
||||
/// least one party is misconfigured and got their ratchet keys corrupted or lost, or Bob has
|
||||
/// been compromised and is being impersonated. An attacker must at least have Bob's private
|
||||
/// static key to be able to ask Alice to downgrade.
|
||||
///
|
||||
/// Corresponds to the "Initiator Disallows Downgrade, π_2" security flag of Transition
|
||||
/// Algorithm 3 within the ZSSP whitepaper.
|
||||
fn initiator_disallows_downgrade(&mut self, session: &Arc<Session<Crypto>>) -> bool;
|
||||
/// Function to accept sessions after final negotiation.
|
||||
/// The second argument is the identity that the remote peer sent us. The application
|
||||
/// must verify this identity is associated with the remote peer's static key.
|
||||
///
|
||||
/// The implementor must verify that three arguments, `remote_static_key`, `identity` and
|
||||
/// optionally `fingerprint_data` all belong to the same remote peer, using whatever definition
|
||||
/// of "same remote peer" that the upper protocol chooses.
|
||||
/// `fingerprint_data` is an opaque type that is only `Some` if Alice sent us a ratchet
|
||||
/// fingerprint that was successfully restored by `restore_by_fingerprint`.
|
||||
///
|
||||
/// To prevent desync, if this function specifies that we should connect, no other open session
|
||||
/// with the same remote peer must exist. Drop or call expire on any pre-existing sessions
|
||||
/// before returning.
|
||||
fn check_accept_session(&mut self, remote_static_key: &Crypto::PublicKey, identity: &[u8]) -> AcceptAction<Crypto>;
|
||||
///
|
||||
/// Corresponds to the **Accept** call of Transition Algorithm 4 within the ZSSP whitepaper.
|
||||
fn check_accept_session(
|
||||
&mut self,
|
||||
remote_static_key: &Crypto::PublicKey,
|
||||
identity: &[u8],
|
||||
fingerprint_data: Option<&Crypto::FingerprintData>,
|
||||
) -> AcceptAction<Crypto>;
|
||||
|
||||
/// Lookup a specific ratchet state based on its ratchet fingerprint.
|
||||
/// This function will be called whenever Alice attempts to connect to us with a non-empty
|
||||
@@ -222,10 +255,24 @@ pub trait ApplicationLayer<Crypto: CryptoLayer>: Sized {
|
||||
///
|
||||
/// If a ratchet state with a matching fingerprint could not be found, this function should
|
||||
/// return `Ok(None)`.
|
||||
///
|
||||
/// This function can also return an opaque `FingerprintData` object. If Alice continues to connect
|
||||
/// with us, then this object will be passed to `check_accept_session` and `restore_by_identity`.
|
||||
/// This is useful if the ratchet fingerprint was derived from a one-time password, in which
|
||||
/// case `FingerprintData` can contain metadata regarding the one-time password. This can be
|
||||
/// used by `check_accept_session` and `restore_by_identity` to perform additional
|
||||
/// authentication checks, such as validating the one-time password as an invitation code.
|
||||
///
|
||||
/// `FingerprintData` can also be used with extreme caution to cache database resources that can
|
||||
/// speed up the expected future calls to `check_accept_session` and `restore_by_identity`.
|
||||
/// If this is done, the implementor is required in `check_accept_session` to verify that the
|
||||
/// cached resources in `FingerprintData` indeed belong to the specified remote peer.
|
||||
///
|
||||
/// Corresponds to the **Restore** call of Transition Algorithm 2 within the ZSSP whitepaper.
|
||||
fn restore_by_fingerprint(
|
||||
&mut self,
|
||||
ratchet_fingerprint: &[u8; RATCHET_SIZE],
|
||||
) -> Result<Option<RatchetState>, std::io::Error>;
|
||||
) -> Result<Option<(RatchetState, Crypto::FingerprintData)>, std::io::Error>;
|
||||
/// Lookup the specific ratchet states based on the identity of the peer being communicated with.
|
||||
/// This function will be called whenever Alice attempts to open a session, or Bob attempts
|
||||
/// to verify Alice's identity.
|
||||
@@ -240,10 +287,13 @@ pub trait ApplicationLayer<Crypto: CryptoLayer>: Sized {
|
||||
/// This function is not responsible for deciding whether or not to connect to this remote peer.
|
||||
/// Filtering peers should be done by the caller to `Context::open` as well as by the
|
||||
/// function `ApplicationLayer::check_accept_session`.
|
||||
///
|
||||
/// Corresponds to the **Restore** call of Transition Algorithm 1 and 4 within the ZSSP whitepaper.
|
||||
fn restore_by_identity(
|
||||
&mut self,
|
||||
remote_static_key: &Crypto::PublicKey,
|
||||
session_data: &Crypto::SessionData,
|
||||
fingerprint_data: Option<&Crypto::FingerprintData>,
|
||||
) -> Result<Option<RatchetStates>, std::io::Error>;
|
||||
/// Atomically commit the update specified by `update_data` to storage, or return an error if
|
||||
/// the update could not be made.
|
||||
@@ -306,12 +356,18 @@ pub struct AcceptAction<Crypto: CryptoLayer> {
|
||||
pub session_data: Option<Crypto::SessionData>,
|
||||
/// Whether or not we will accept a connection with the remote peer when they do not have a
|
||||
/// ratchet key that we think they should have.
|
||||
///
|
||||
/// Corresponds to the "Responder Disallows Downgrade, π_3" security flag of Transition
|
||||
/// Algorithm 4 within the ZSSP whitepaper.
|
||||
pub responder_disallows_downgrade: bool,
|
||||
/// Whether or not to send an explicit rejection packet to the remote peer if we do not create
|
||||
/// a session with them.
|
||||
///
|
||||
/// This field will not be used if `session_data` is `Some` and the remote peer passes all other
|
||||
/// authentication checks.
|
||||
///
|
||||
/// Corresponds to the "Responder Silently Rejects, π_4" security flag of Transition
|
||||
/// Algorithm 4 within the ZSSP whitepaper.
|
||||
pub responder_silently_rejects: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ pub use openssl_sys;
|
||||
#[cfg(feature = "default-crypto")]
|
||||
pub trait DefaultCrypto {
|
||||
type SessionData;
|
||||
type LookupData;
|
||||
type IncomingPacketBuffer: AsMut<[u8]> + AsRef<[u8]>;
|
||||
}
|
||||
#[cfg(feature = "default-crypto")]
|
||||
@@ -54,5 +55,6 @@ impl<C: DefaultCrypto> crate::application::CryptoLayer for C {
|
||||
type Kem = CrateKyber1024PrivateKey;
|
||||
|
||||
type SessionData = C::SessionData;
|
||||
type FingerprintData = C::LookupData;
|
||||
type IncomingPacketBuffer = C::IncomingPacketBuffer;
|
||||
}
|
||||
|
||||
@@ -257,6 +257,7 @@ fn test_cache() {
|
||||
type Kem = CrateKyber1024PrivateKey;
|
||||
|
||||
type SessionData = ();
|
||||
type FingerprintData = ();
|
||||
type IncomingPacketBuffer = Vec<u8>;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ pub(crate) struct MutableState<Crypto: CryptoLayer> {
|
||||
/// Corresponds to State B_2 of the Zeta State Machine found in Section 4.1 - Definition 3.
|
||||
pub(crate) struct StateB2<Crypto: CryptoLayer> {
|
||||
ratchet_state: RatchetState,
|
||||
lookup_data: Option<Crypto::FingerprintData>,
|
||||
kid_send: NonZeroU32,
|
||||
pub kid_recv: NonZeroU32,
|
||||
pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>,
|
||||
@@ -331,7 +332,7 @@ pub(crate) fn trans_to_a1<Crypto: CryptoLayer, App: ApplicationLayer<Crypto>>(
|
||||
send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>),
|
||||
) -> Result<(Arc<Session<Crypto>>, Option<i64>), OpenError> {
|
||||
let RatchetStates { state1, state2 } = app
|
||||
.restore_by_identity(&s_remote, &session_data)
|
||||
.restore_by_identity(&s_remote, &session_data, None)
|
||||
.map_err(OpenError::StorageError)?
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -472,11 +473,13 @@ pub(crate) fn received_x1_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
return Err(fault!(FailedAuth, true));
|
||||
}
|
||||
|
||||
let mut lookup_data = None;
|
||||
let mut ratchet_state = None;
|
||||
while i + RATCHET_SIZE <= j {
|
||||
match app.restore_by_fingerprint((&x1[i..i + RATCHET_SIZE]).try_into().unwrap()) {
|
||||
Ok(None) => {}
|
||||
Ok(Some(rs)) => {
|
||||
Ok(Some((rs, data))) => {
|
||||
lookup_data = Some(data);
|
||||
ratchet_state = Some(rs);
|
||||
break;
|
||||
}
|
||||
@@ -551,6 +554,7 @@ pub(crate) fn received_x1_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
e_secret,
|
||||
noise,
|
||||
defrag: Mutex::new(Fragged::new()),
|
||||
lookup_data,
|
||||
}),
|
||||
app.time(),
|
||||
);
|
||||
@@ -838,7 +842,7 @@ pub(crate) fn received_x3_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_send, &mut kek_recv);
|
||||
let c = 0;
|
||||
|
||||
let action = app.check_accept_session(&s_remote, &x3[identity_start..identity_end]);
|
||||
let action = app.check_accept_session(&s_remote, &x3[identity_start..identity_end], zeta.lookup_data.as_ref());
|
||||
let responder_disallows_downgrade = action.responder_disallows_downgrade;
|
||||
let responder_silently_rejects = action.responder_silently_rejects;
|
||||
let create_reject = || {
|
||||
@@ -854,7 +858,7 @@ pub(crate) fn received_x3_trans<Crypto: CryptoLayer, App: ApplicationLayer<Crypt
|
||||
d
|
||||
};
|
||||
if let Some(session_data) = action.session_data {
|
||||
let result = app.restore_by_identity(&s_remote, &session_data);
|
||||
let result = app.restore_by_identity(&s_remote, &session_data, zeta.lookup_data.as_ref());
|
||||
match result {
|
||||
Ok(rss) => {
|
||||
let RatchetStates { state1, state2 } = rss.unwrap_or_default();
|
||||
|
||||
Reference in New Issue
Block a user