use crate::crypto::{ed25519_verify, Ed25519PublicKey, Ed25519Signature, X25519PublicKey};
use crate::engine::EngineState;
use crate::engine::{CommandId, CommandOutcome, Identify, IdentifyRejection};
use crate::identity::{
IdentityEncryptionPublicKey, IdentityHash, IdentitySigner, IdentitySigningPublicKey,
RemoteIdentity, IDENTITY_PUBLIC_KEY_LEN,
};
use crate::interfaces::InterfaceId;
use crate::routing::links::table::{LinkPhase, LinkRole};
use crate::routing::links::LinkId;
use crate::storage::StorageLayout;
use crate::wire::{
ContextFlag, DestinationType, IfacFlag, PacketType, PropagationType, WireContext,
WirePacketHeader, TRUNCATED_HASH_BYTE_LEN,
};
pub const IDENTIFY_PLAINTEXT_LEN: usize = IDENTITY_PUBLIC_KEY_LEN + Ed25519Signature::LEN;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IdentifyDispatch {
pub wire_bytes: usize,
pub fire_on: InterfaceId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IdentifyWriteError {
LinkVanished,
IdentityVanished,
BufferTooShort,
}
impl<S: StorageLayout> EngineState<S> {
pub fn ingest_identify(&self, id: CommandId, identify: Identify) -> CommandOutcome {
match self.links.phase_for(&identify.link_id) {
None => CommandOutcome::IdentifyRejected {
id,
rejection: IdentifyRejection::NoSuchLink,
},
Some(LinkPhase::Pending { .. } | LinkPhase::Handshake { .. }) => {
CommandOutcome::IdentifyRejected {
id,
rejection: IdentifyRejection::LinkNotActive,
}
}
Some(LinkPhase::Active {
role: LinkRole::Responder { .. },
..
}) => CommandOutcome::IdentifyRejected {
id,
rejection: IdentifyRejection::NotInitiator,
},
Some(LinkPhase::Active {
role: LinkRole::Initiator { .. },
..
}) => {
if self.held_identities.get(&identify.identity).is_none() {
CommandOutcome::IdentifyRejected {
id,
rejection: IdentifyRejection::IdentityNotHeld,
}
} else {
CommandOutcome::OwesIdentify { id, identify }
}
}
}
}
pub fn write_commanded_identify(
&self,
identify: &Identify,
iv: &[u8; 16],
buf: &mut [u8],
) -> Result<IdentifyDispatch, IdentifyWriteError> {
let Some(LinkPhase::Active {
key,
attached_interface,
..
}) = self.links.phase_for(&identify.link_id)
else {
return Err(IdentifyWriteError::LinkVanished);
};
let identity = self
.held_identities
.get(&identify.identity)
.ok_or(IdentifyWriteError::IdentityVanished)?;
let keys = identity.public_key_bytes();
let signed_data = identify_signed_data(&identify.link_id, &keys);
let signature = identity.sign(&signed_data);
let mut plaintext = [0u8; IDENTIFY_PLAINTEXT_LEN];
plaintext[..IDENTITY_PUBLIC_KEY_LEN].copy_from_slice(&keys);
plaintext[IDENTITY_PUBLIC_KEY_LEN..].copy_from_slice(&signature.0);
let header = WirePacketHeader {
ifac_flag: IfacFlag::Open,
context_flag: ContextFlag::Unset,
propagation: PropagationType::Broadcast,
destination_type: DestinationType::Link,
packet_type: PacketType::Data,
hops: 0,
transport_id: None,
address: identify.link_id.to_address(),
context: WireContext::LinkIdentify,
};
let header_len = header
.write(buf)
.map_err(|_| IdentifyWriteError::BufferTooShort)?;
let sealed = key
.seal(iv, &plaintext, &mut buf[header_len..])
.map_err(|_| IdentifyWriteError::BufferTooShort)?;
Ok(IdentifyDispatch {
wire_bytes: header_len + sealed,
fire_on: *attached_interface,
})
}
}
fn identify_signed_data(
link_id: &LinkId,
keys: &[u8; IDENTITY_PUBLIC_KEY_LEN],
) -> [u8; TRUNCATED_HASH_BYTE_LEN + IDENTITY_PUBLIC_KEY_LEN] {
let mut signed_data = [0u8; TRUNCATED_HASH_BYTE_LEN + IDENTITY_PUBLIC_KEY_LEN];
signed_data[..TRUNCATED_HASH_BYTE_LEN].copy_from_slice(link_id.as_bytes());
signed_data[TRUNCATED_HASH_BYTE_LEN..].copy_from_slice(keys);
signed_data
}
pub fn peer_identity_from(link_id: &LinkId, plaintext: &[u8]) -> Option<IdentityHash> {
if plaintext.len() != IDENTIFY_PLAINTEXT_LEN {
return None;
}
let keys: &[u8; IDENTITY_PUBLIC_KEY_LEN] =
plaintext[..IDENTITY_PUBLIC_KEY_LEN].try_into().ok()?;
let mut encryption = [0u8; X25519PublicKey::LEN];
encryption.copy_from_slice(&keys[..X25519PublicKey::LEN]);
let mut signing = [0u8; Ed25519PublicKey::LEN];
signing.copy_from_slice(&keys[X25519PublicKey::LEN..]);
let mut signature = [0u8; Ed25519Signature::LEN];
signature.copy_from_slice(&plaintext[IDENTITY_PUBLIC_KEY_LEN..]);
let signed_data = identify_signed_data(link_id, keys);
ed25519_verify(
&Ed25519PublicKey(signing),
&signed_data,
&Ed25519Signature(signature),
)
.ok()?;
let remote = RemoteIdentity::from_public_keys(
IdentityEncryptionPublicKey::new(X25519PublicKey(encryption)),
IdentitySigningPublicKey::new(Ed25519PublicKey(signing)),
);
Some(remote.identity_hash())
}