tapir-rs/src/applications/authentication_app.rs

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)
}
}