208 lines
9.9 KiB
Rust
208 lines
9.9 KiB
Rust
use crate::acns::tor::validation::public_key_to_hostname;
|
|
use crate::applications::authentication_app::AuthenticationAppError::NotAuthenticatedError;
|
|
use crate::connections::{Capability, Connection, ConnectionInterface, InboundConnection, OutboundConnection};
|
|
use crate::primitives::identity::Identity;
|
|
use crate::primitives::transcript::Transcript;
|
|
use ed25519_dalek::PublicKey;
|
|
use integer_encoding::FixedInt;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use sha3::Digest;
|
|
use std::sync::Arc;
|
|
use subtle::ConstantTimeEq;
|
|
|
|
pub struct AuthenicationApp {
|
|
identity: Arc<Identity>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum AuthenticationAppError {
|
|
NotAuthenticatedError,
|
|
}
|
|
|
|
pub const AUTHENTICATION_CAPABILITY: Capability = Capability("AuthenticationCapability");
|
|
|
|
struct AuthenticationSession<Direction> {
|
|
long_term_identity: Arc<Identity>,
|
|
ephemeral_identity: Identity,
|
|
local_auth_message_json: String,
|
|
remote_auth_message_json: String,
|
|
|
|
remote_long_term_identity: ed25519_dalek::PublicKey,
|
|
remote_ephemeral_identity: ed25519_dalek::PublicKey,
|
|
|
|
challenge: [u8; 64],
|
|
|
|
conn: Connection<Direction>,
|
|
}
|
|
|
|
impl<Direction> AuthenticationSession<Direction>
|
|
where
|
|
Direction: Clone,
|
|
{
|
|
pub fn new_outbound(conn: Connection<OutboundConnection>, long_term_identity: Arc<Identity>) -> AuthenticationSession<OutboundConnection> {
|
|
let ephemeral_identity = Identity::initialize_ephemeral_identity();
|
|
let mut auth_session = AuthenticationSession {
|
|
long_term_identity,
|
|
ephemeral_identity,
|
|
local_auth_message_json: "".to_string(),
|
|
remote_auth_message_json: "".to_string(),
|
|
remote_long_term_identity: Default::default(),
|
|
remote_ephemeral_identity: Default::default(),
|
|
conn,
|
|
challenge: [0; 64],
|
|
};
|
|
auth_session.identity_exchange();
|
|
auth_session
|
|
}
|
|
|
|
pub fn new_inbound(conn: Connection<InboundConnection>, long_term_identity: Arc<Identity>) -> AuthenticationSession<InboundConnection> {
|
|
let ephemeral_identity = Identity::initialize_ephemeral_identity();
|
|
let mut auth_session = AuthenticationSession {
|
|
long_term_identity,
|
|
ephemeral_identity,
|
|
local_auth_message_json: "".to_string(),
|
|
remote_auth_message_json: "".to_string(),
|
|
remote_long_term_identity: Default::default(),
|
|
remote_ephemeral_identity: Default::default(),
|
|
conn,
|
|
challenge: [0; 64],
|
|
};
|
|
auth_session.identity_exchange();
|
|
auth_session
|
|
}
|
|
|
|
/// exchange identities with the remote parties
|
|
/// Not that if network connections fail during this empty buffers will be returned which will
|
|
/// cause subsequent steps to fail
|
|
fn identity_exchange(&mut self) {
|
|
let auth_message = AuthMessage {
|
|
longterm_public_key: base64::encode(self.long_term_identity.public_key()),
|
|
ephemeral_public_key: base64::encode(self.ephemeral_identity.public_key().to_bytes()),
|
|
};
|
|
self.local_auth_message_json = serde_json::to_string(&auth_message).unwrap_or_default();
|
|
match self.conn.send(&self.local_auth_message_json) {
|
|
Ok(()) => match self.conn.expect() {
|
|
Ok(buffer) => {
|
|
self.remote_auth_message_json = String::from_utf8(buffer).unwrap_or_default();
|
|
let remote_auth_message: AuthMessage = serde_json::from_str(self.remote_auth_message_json.as_str()).unwrap_or(AuthMessage {
|
|
longterm_public_key: "".to_string(),
|
|
ephemeral_public_key: "".to_string(),
|
|
});
|
|
self.remote_long_term_identity = PublicKey::from_bytes(base64::decode(&remote_auth_message.longterm_public_key).unwrap_or_default().as_slice()).unwrap_or_default();
|
|
self.remote_ephemeral_identity = PublicKey::from_bytes(base64::decode(&remote_auth_message.ephemeral_public_key).unwrap_or_default().as_slice()).unwrap_or_default();
|
|
}
|
|
_ => {}
|
|
},
|
|
_ => {
|
|
// we fall out of the exchange..if the network fails then the protocol will also fail, as nothing will be instantiated...
|
|
}
|
|
};
|
|
}
|
|
|
|
/// generate the message for the local challenge based a new transcript hash.
|
|
fn generate_challenge_message(&self) -> Vec<u8> {
|
|
let mut msg = vec![];
|
|
let mut len = [0u8; 2];
|
|
((self.challenge.len() + self.long_term_identity.hostname().as_bytes().len()) as u16).encode_fixed(&mut len);
|
|
msg.extend_from_slice(len.as_slice());
|
|
msg.extend_from_slice(self.challenge.as_slice());
|
|
msg.extend_from_slice(self.long_term_identity.hostname().as_bytes());
|
|
msg
|
|
}
|
|
|
|
/// check the challenge from the remote
|
|
fn check_remote_challenge(&mut self) -> Result<Connection<Direction>, AuthenticationAppError> {
|
|
match self.conn.send_encrypted(self.generate_challenge_message()) {
|
|
Ok(()) => {
|
|
let remote_challenge = self.conn.expect_encrypted();
|
|
let mut cmp_challenge = vec![];
|
|
cmp_challenge.extend_from_slice(self.challenge.as_slice());
|
|
cmp_challenge.extend_from_slice(public_key_to_hostname(&self.remote_long_term_identity).as_bytes());
|
|
if remote_challenge.ct_eq(cmp_challenge.as_slice()).unwrap_u8() == 1 {
|
|
self.conn.set_hostname(&public_key_to_hostname(&self.remote_long_term_identity));
|
|
self.conn.set_capability(&AUTHENTICATION_CAPABILITY);
|
|
return Ok(self.conn.try_clone());
|
|
}
|
|
self.conn.shutdown();
|
|
Err(NotAuthenticatedError)
|
|
}
|
|
Err(_err) => Err(NotAuthenticatedError),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AuthenticationSession<OutboundConnection> {
|
|
pub fn generate_challenge(&mut self, transcript: &mut Transcript) -> Result<Connection<OutboundConnection>, AuthenticationAppError> {
|
|
let l2e = self.long_term_identity.edh(self.remote_ephemeral_identity);
|
|
let e2l = self.ephemeral_identity.edh(self.remote_long_term_identity);
|
|
let e2e = self.ephemeral_identity.edh(self.remote_ephemeral_identity);
|
|
|
|
let mut shared_secret = vec![];
|
|
// outbound calculates shared secret as Sha256(l2e:e2l:e2e)
|
|
shared_secret.extend_from_slice(l2e.as_bytes());
|
|
shared_secret.extend_from_slice(e2l.as_bytes());
|
|
shared_secret.extend_from_slice(e2e.as_bytes());
|
|
let key = sha3::Sha3_256::digest(shared_secret.as_slice());
|
|
|
|
// transcript is updated using appropriate values for outbound and inbound hostname and initial messages
|
|
transcript.new_protocol("auth-app");
|
|
transcript.add_to_transcript("outbound-hostname", self.long_term_identity.hostname().as_bytes());
|
|
transcript.add_to_transcript("inbound-hostname", public_key_to_hostname(&self.remote_long_term_identity).as_bytes());
|
|
transcript.add_to_transcript("outbound-challenge", self.local_auth_message_json.as_bytes());
|
|
transcript.add_to_transcript("inbound-challenge", self.remote_auth_message_json.as_bytes());
|
|
self.challenge = transcript.commit_to_transcript("3dh-auth-challenge");
|
|
self.conn.enable_encryption(key.to_vec());
|
|
self.check_remote_challenge()
|
|
}
|
|
}
|
|
|
|
impl AuthenticationSession<InboundConnection> {
|
|
pub fn generate_challenge(&mut self, transcript: &mut Transcript) -> Result<Connection<InboundConnection>, AuthenticationAppError> {
|
|
let l2e = self.long_term_identity.edh(self.remote_ephemeral_identity);
|
|
let e2l = self.ephemeral_identity.edh(self.remote_long_term_identity);
|
|
let e2e = self.ephemeral_identity.edh(self.remote_ephemeral_identity);
|
|
|
|
// inbound calculates shared secret as Sha256(e2l:l2e:e2e)
|
|
let mut shared_secret = vec![];
|
|
shared_secret.extend_from_slice(e2l.as_bytes());
|
|
shared_secret.extend_from_slice(l2e.as_bytes());
|
|
shared_secret.extend_from_slice(e2e.as_bytes());
|
|
let key = sha3::Sha3_256::digest(shared_secret.as_slice());
|
|
|
|
// transcript is updated using appropriate values for outbound and inbound hostname and initial messages
|
|
transcript.new_protocol("auth-app");
|
|
transcript.add_to_transcript("outbound-hostname", public_key_to_hostname(&self.remote_long_term_identity).as_bytes());
|
|
transcript.add_to_transcript("inbound-hostname", self.long_term_identity.hostname().as_bytes());
|
|
transcript.add_to_transcript("outbound-challenge", self.remote_auth_message_json.as_bytes());
|
|
transcript.add_to_transcript("inbound-challenge", self.local_auth_message_json.as_bytes());
|
|
self.challenge = transcript.commit_to_transcript("3dh-auth-challenge");
|
|
self.conn.enable_encryption(key.to_vec());
|
|
self.check_remote_challenge()
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
struct AuthMessage {
|
|
#[serde(rename(serialize = "LongTermPublicKey", deserialize = "LongTermPublicKey"))]
|
|
longterm_public_key: String,
|
|
#[serde(rename(serialize = "EphemeralPublicKey", deserialize = "EphemeralPublicKey"))]
|
|
ephemeral_public_key: String,
|
|
}
|
|
|
|
impl AuthenicationApp {
|
|
pub fn new(identity: Arc<Identity>) -> AuthenicationApp {
|
|
AuthenicationApp { identity }
|
|
}
|
|
|
|
pub fn run_outbound(&mut self, conn: Connection<OutboundConnection>, transcript: &mut Transcript) -> Result<Connection<OutboundConnection>, AuthenticationAppError> {
|
|
let mut auth_session = AuthenticationSession::<OutboundConnection>::new_outbound(conn, self.identity.clone());
|
|
auth_session.generate_challenge(transcript)
|
|
}
|
|
|
|
pub fn run_inbound(&mut self, conn: Connection<InboundConnection>, transcript: &mut Transcript) -> Result<Connection<InboundConnection>, AuthenticationAppError> {
|
|
let mut auth_session = AuthenticationSession::<InboundConnection>::new_inbound(conn, self.identity.clone());
|
|
auth_session.generate_challenge(transcript)
|
|
}
|
|
}
|