use crate::cache::{CacheEntry, RetainedSecrets, SharedSecretStore};
use crate::crypto::{
build_confirm_message, compute_dh_shared, compute_hvi, compute_key_id, compute_preshared_key,
compute_total_hash, derive_retained_secret, derive_s0_full, derive_session_secrets,
generate_dh_keypair, match_retained_secret_ids, open_confirm_message,
retained_secret_ids_for_role, sas_render, select_matching_shared_secrets, DhPublicKey,
DhSecretKey, RetainedSecretMatches, SecretIdRole, SessionSecrets, SharedSecretSelection,
};
use crate::wire::*;
use std::collections::HashSet;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Role {
Initiator,
Responder,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HandshakeState {
Discovery,
HelloSent,
HelloReceived,
CommitSent,
CommitReceived,
DhPart1Sent,
DhPart1Received,
DhPart2Sent,
Confirm1Sent,
Confirm1Received,
Confirm2Sent,
Secure,
Clear,
Failed,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RetransmitKind {
Hello,
PostHello,
Ping,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RetransmitSchedule {
pub initial_ms: u64,
pub cap_ms: u64,
pub attempts: usize,
}
impl RetransmitSchedule {
pub fn hello() -> Self {
Self {
initial_ms: 50,
cap_ms: 200,
attempts: 20,
}
}
pub fn post_hello() -> Self {
Self {
initial_ms: 150,
cap_ms: 1200,
attempts: 10,
}
}
pub fn ping() -> Self {
Self::hello()
}
pub fn at(&self, attempt: usize) -> Option<u64> {
if attempt >= self.attempts {
return None;
}
let doubled = self.initial_ms.saturating_mul(1u64 << attempt.min(20));
Some(doubled.min(self.cap_ms))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EngineEvent {
Send(Message),
Negotiated(NegotiatedAlgorithms),
SasReady(String),
SecureOn,
SecureOff,
Error(ErrorCode),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RetransmitAck {
HelloAckOrCommit,
DhPart1OrConfirm1,
Confirm1,
Conf2AckOrSrtp,
ClearAck,
ErrorAck,
RelayAck,
PingAck,
}
pub fn retransmit_ack_for(message: &Message) -> Option<RetransmitAck> {
match message {
Message::Hello(_) => Some(RetransmitAck::HelloAckOrCommit),
Message::Commit(_) => Some(RetransmitAck::DhPart1OrConfirm1),
Message::DhPart2(_) => Some(RetransmitAck::Confirm1),
Message::Confirm2(_) => Some(RetransmitAck::Conf2AckOrSrtp),
Message::GoClear(_) => Some(RetransmitAck::ClearAck),
Message::Error(_) => Some(RetransmitAck::ErrorAck),
Message::SasRelay(_) => Some(RetransmitAck::RelayAck),
Message::Ping(_) => Some(RetransmitAck::PingAck),
_ => None,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RetransmitState {
pub message: Message,
pub schedule: RetransmitSchedule,
pub attempt: usize,
}
impl RetransmitState {
pub fn new(message: Message) -> Option<Self> {
let schedule = match message {
Message::Hello(_) | Message::Ping(_) => RetransmitSchedule::hello(),
Message::Commit(_)
| Message::DhPart2(_)
| Message::Confirm2(_)
| Message::GoClear(_)
| Message::Error(_)
| Message::SasRelay(_) => RetransmitSchedule::post_hello(),
_ => return None,
};
Some(Self {
message,
schedule,
attempt: 0,
})
}
pub fn next_delay_ms(&self) -> Option<u64> {
self.schedule.at(self.attempt)
}
pub fn advance(&mut self) {
self.attempt += 1;
}
pub fn is_exhausted(&self) -> bool {
self.next_delay_ms().is_none()
}
pub fn stops_on(&self, inbound: &Message, saw_valid_srtp: bool) -> bool {
match (&self.message, inbound) {
(Message::Hello(_), Message::HelloAck | Message::Commit(_)) => true,
(Message::Commit(_), Message::DhPart1(_) | Message::Confirm1(_)) => true,
(Message::DhPart2(_), Message::Confirm1(_)) => true,
(Message::Confirm2(_), Message::Conf2Ack) => true,
(Message::Confirm2(_), _) if saw_valid_srtp => true,
(Message::GoClear(_), Message::ClearAck) => true,
(Message::Error(_), Message::ErrorAck) => true,
(Message::SasRelay(_), Message::RelayAck) => true,
(Message::Ping(_), Message::PingAck(_)) => true,
_ => false,
}
}
}
pub struct ZrtpEngine<S> {
role: Role,
state: HandshakeState,
local_hello: Hello,
remote_hello: Option<Hello>,
negotiated: Option<NegotiatedAlgorithms>,
last_commit: Option<Commit>,
local_dh_secret: Option<DhSecretKey>,
pending_dhpart2: Option<DhPart2>,
last_sas: Option<String>,
retained_secrets: Option<RetainedSecrets>,
session_secrets: Option<SessionSecrets>,
total_hash: Option<Vec<u8>>,
peer_confirms_cache_expiry: Option<u32>,
transcript_messages: Vec<Vec<u8>>,
seen_commit_nonces: HashSet<Vec<u8>>,
hvi_verified: Option<bool>,
store: S,
}
impl<S: SharedSecretStore> ZrtpEngine<S> {
pub fn new(role: Role, local_hello: Hello, store: S) -> Self {
Self {
role,
state: HandshakeState::Discovery,
local_hello,
remote_hello: None,
negotiated: None,
last_commit: None,
local_dh_secret: None,
pending_dhpart2: None,
last_sas: None,
retained_secrets: None,
session_secrets: None,
total_hash: None,
peer_confirms_cache_expiry: None,
transcript_messages: Vec::new(),
seen_commit_nonces: HashSet::new(),
hvi_verified: None,
store,
}
}
pub fn role(&self) -> Role {
self.role
}
pub fn state(&self) -> HandshakeState {
self.state
}
pub fn local_hello(&self) -> &Hello {
&self.local_hello
}
pub fn remote_hello(&self) -> Option<&Hello> {
self.remote_hello.as_ref()
}
pub fn negotiated(&self) -> Option<&NegotiatedAlgorithms> {
self.negotiated.as_ref()
}
pub fn last_commit(&self) -> Option<&Commit> {
self.last_commit.as_ref()
}
pub fn last_sas(&self) -> Option<&str> {
self.last_sas.as_deref()
}
pub fn session_secrets(&self) -> Option<&SessionSecrets> {
self.session_secrets.as_ref()
}
pub fn retained_secrets(&self) -> Option<&RetainedSecrets> {
self.retained_secrets.as_ref()
}
pub fn total_hash(&self) -> Option<&[u8]> {
self.total_hash.as_deref()
}
pub fn start(&mut self) -> Vec<EngineEvent> {
self.state = HandshakeState::HelloSent;
let msg = Message::Hello(self.local_hello.clone());
self.push_transcript(&msg);
vec![EngineEvent::Send(msg)]
}
pub fn receive(&mut self, message: &Message) -> Result<Vec<EngineEvent>, Error> {
let mut events = Vec::new();
match message {
Message::Hello(remote) => {
self.push_transcript(message);
self.remote_hello = Some(remote.clone());
self.retained_secrets = self
.store
.load(self.local_hello.zid, remote.zid)?
.map(|e| e.secrets);
let negotiated = negotiate_algorithms(&self.local_hello, remote)?;
self.negotiated = Some(negotiated.clone());
events.push(EngineEvent::Negotiated(negotiated));
self.state = HandshakeState::HelloReceived;
events.push(EngineEvent::Send(Message::HelloAck));
if self.role == Role::Initiator
&& matches!(
self.state,
HandshakeState::HelloSent | HandshakeState::HelloReceived
)
{
self.precompute_initiator_dhpart2()?;
let commit = self.make_default_commit()?;
self.last_commit = Some(commit.clone());
self.state = HandshakeState::CommitSent;
self.push_transcript(&Message::Commit(commit.clone()));
events.push(EngineEvent::Send(Message::Commit(commit)));
}
}
Message::HelloAck => {
if self.role == Role::Initiator && self.state == HandshakeState::HelloSent {
if self.remote_hello.is_none() {
return Ok(events);
}
self.precompute_initiator_dhpart2()?;
let commit = self.make_default_commit()?;
self.last_commit = Some(commit.clone());
self.state = HandshakeState::CommitSent;
self.push_transcript(&Message::Commit(commit.clone()));
events.push(EngineEvent::Send(Message::Commit(commit)));
}
}
Message::Commit(commit) => {
self.push_transcript(message);
commit.validate()?;
self.validate_commit_material(commit)?;
self.seen_commit_nonces
.insert(Self::commit_nonce_bytes(commit));
self.last_commit = Some(commit.clone());
self.state = HandshakeState::CommitReceived;
match KeyAgreement::from(commit.key_agreement) {
KeyAgreement::Multistream | KeyAgreement::Preshared => {
self.derive_cached_session(commit)?;
self.state = HandshakeState::Confirm1Sent;
let confirm = self.make_confirm(true)?;
self.push_transcript(&Message::Confirm1(confirm.clone()));
events.push(EngineEvent::Send(Message::Confirm1(confirm)));
}
_ => {
self.state = HandshakeState::DhPart1Sent;
let dh1 = self.make_dhpart1(commit)?;
self.push_transcript(&Message::DhPart1(dh1.clone()));
events.push(EngineEvent::Send(Message::DhPart1(dh1)));
}
}
}
Message::DhPart1(dh1) => {
self.push_transcript(message);
self.validate_dhpart1_secret_ids(dh1)?;
self.state = HandshakeState::DhPart1Received;
let dh2 = self.make_dhpart2()?;
self.push_transcript(&Message::DhPart2(dh2.clone()));
events.push(EngineEvent::Send(Message::DhPart2(dh2)));
if let Some(sas) = self.compute_sas_from_remote(&dh1.pvr)? {
self.last_sas = Some(sas.clone());
events.push(EngineEvent::SasReady(sas));
}
self.state = HandshakeState::DhPart2Sent;
let confirm = self.make_confirm(true)?;
self.push_transcript(&Message::Confirm1(confirm.clone()));
events.push(EngineEvent::Send(Message::Confirm1(confirm)));
}
Message::DhPart2(dh2) => {
self.push_transcript(message);
self.validate_dhpart2_secret_ids(dh2)?;
self.verify_hvi_if_needed(dh2)?;
if let Some(sas) = self.compute_sas_from_remote(&dh2.pvi)? {
self.last_sas = Some(sas.clone());
events.push(EngineEvent::SasReady(sas));
}
self.state = HandshakeState::Confirm1Sent;
let confirm = self.make_confirm(true)?;
self.push_transcript(&Message::Confirm1(confirm.clone()));
events.push(EngineEvent::Send(Message::Confirm1(confirm)));
}
Message::Confirm1(confirm) => {
self.push_transcript(message);
let content = self.verify_confirm(confirm, true)?;
self.peer_confirms_cache_expiry = Some(content.cache_expiration_interval);
self.state = HandshakeState::Confirm1Received;
self.state = HandshakeState::Confirm2Sent;
let confirm2 = self.make_confirm(false)?;
self.push_transcript(&Message::Confirm2(confirm2.clone()));
events.push(EngineEvent::Send(Message::Confirm2(confirm2)));
}
Message::Confirm2(confirm) => {
self.push_transcript(message);
let content = self.verify_confirm(confirm, false)?;
self.peer_confirms_cache_expiry = Some(content.cache_expiration_interval);
self.state = HandshakeState::Secure;
events.push(EngineEvent::Send(Message::Conf2Ack));
events.push(EngineEvent::SecureOn);
self.update_retained_secrets_after_confirm()?;
}
Message::GoClear(_) => {
self.state = HandshakeState::Clear;
events.push(EngineEvent::Send(Message::ClearAck));
events.push(EngineEvent::SecureOff);
}
Message::Error(err) => {
self.state = HandshakeState::Failed;
events.push(EngineEvent::Send(Message::ErrorAck));
events.push(EngineEvent::Error(err.code));
}
Message::SasRelay(_) => {
events.push(EngineEvent::Send(Message::RelayAck));
}
Message::Ping(ping) => {
events.push(EngineEvent::Send(Message::PingAck(PingAck {
version: ping.version,
local_endpoint_hash: [0u8; 8],
remote_endpoint_hash: ping.endpoint_hash,
received_ping_ssrc: 0,
})));
}
Message::Conf2Ack if self.state == HandshakeState::Confirm2Sent => {
self.state = HandshakeState::Secure;
events.push(EngineEvent::SecureOn);
self.update_retained_secrets_after_confirm()?;
}
Message::Conf2Ack => {}
Message::ErrorAck
| Message::ClearAck
| Message::RelayAck
| Message::PingAck(_)
| Message::Unknown(_) => {}
}
Ok(events)
}
pub fn observe_srtp_authenticated(&mut self) -> Result<Vec<EngineEvent>, Error> {
let mut events = Vec::new();
if self.state == HandshakeState::Confirm2Sent {
self.state = HandshakeState::Secure;
self.update_retained_secrets_after_confirm()?;
events.push(EngineEvent::SecureOn);
}
Ok(events)
}
pub fn persist_verified_secrets(
&mut self,
remote_zid: [u8; 12],
rs1: Vec<u8>,
rs2: Vec<u8>,
expiration_interval: u32,
) -> Result<(), Error> {
self.store.store(CacheEntry {
local_zid: self.local_hello.zid,
remote_zid,
secrets: crate::cache::RetainedSecrets {
rs1,
rs2,
verified: true,
expiration_interval,
},
})
}
fn push_transcript(&mut self, message: &Message) {
self.transcript_messages.push(message.encode());
}
fn transcript_slices(&self) -> Vec<&[u8]> {
self.transcript_messages
.iter()
.map(|m| m.as_slice())
.collect()
}
fn commit_nonce_bytes(commit: &Commit) -> Vec<u8> {
match &commit.nonce {
CommitNonce::Hvi(v) => v.to_vec(),
CommitNonce::MultistreamNonce(v) => v.to_vec(),
CommitNonce::PresharedNonce { nonce, key_id } => {
let mut out = nonce.to_vec();
out.extend_from_slice(key_id);
out
}
}
}
fn make_default_commit(&self) -> Result<Commit, Error> {
let negotiated = self.negotiated.clone().unwrap_or(NegotiatedAlgorithms {
hash: HashAlgorithm::Sha256,
cipher: CipherAlgorithm::Aes128,
auth_tag: AuthTagType::HmacSha1_32,
key_agreement: KeyAgreement::Dh3072,
sas: SasType::B32,
});
Ok(Commit {
hash_image_h2: [0x42; 32],
zid: self.local_hello.zid,
hash: negotiated.hash.as_fourcc(),
cipher: negotiated.cipher.as_fourcc(),
auth_tag: negotiated.auth_tag.as_fourcc(),
key_agreement: negotiated.key_agreement.as_fourcc(),
sas: negotiated.sas.as_fourcc(),
nonce: match negotiated.key_agreement {
KeyAgreement::Multistream => CommitNonce::MultistreamNonce([0x11; 16]),
KeyAgreement::Preshared => {
let retained = self
.retained_secrets
.as_ref()
.ok_or(Error::InvalidField("retained secrets"))?;
let preshared_key =
compute_preshared_key(negotiated.hash, &retained.rs1, None, None)?;
let key_id = compute_key_id(negotiated.hash, &preshared_key)?;
CommitNonce::PresharedNonce {
nonce: [0x11; 16],
key_id,
}
}
_ => {
let dh2 = self
.pending_dhpart2
.as_ref()
.ok_or(Error::InvalidField("pending dhpart2"))?;
let remote_hello = self
.remote_hello
.as_ref()
.ok_or(Error::InvalidField("remote hello"))?;
let hvi = compute_hvi(
negotiated.hash,
&Message::DhPart2(dh2.clone()).encode(),
&Message::Hello(remote_hello.clone()).encode(),
)?;
CommitNonce::Hvi(hvi)
}
},
mac: [0x33; 8],
})
}
fn make_dhpart1(&mut self, commit: &Commit) -> Result<DhPart1, Error> {
let ka = KeyAgreement::from(commit.key_agreement);
let (secret, public) = generate_dh_keypair(ka)?;
let pvr = self.public_value_bytes(public);
self.local_dh_secret = Some(secret);
let ids = retained_secret_ids_for_role(
self.negotiated
.as_ref()
.ok_or(Error::InvalidField("negotiated"))?
.hash,
SecretIdRole::Responder,
&self.local_hello.hash_image_h3,
self.retained_secrets.as_ref().map(|s| s.rs1.as_slice()),
self.retained_secrets.as_ref().map(|s| s.rs2.as_slice()),
None,
None,
)?;
Ok(DhPart1 {
hash_image_h1: [0x44; 32],
rs1_idr: ids.rs1,
rs2_idr: ids.rs2,
auxsecret_idr: ids.aux,
pbxsecret_idr: ids.pbx,
pvr,
mac: [0xbb; 8],
})
}
fn make_dhpart2(&mut self) -> Result<DhPart2, Error> {
if let Some(precomputed) = self.pending_dhpart2.take() {
return Ok(precomputed);
}
let commit = self
.last_commit
.as_ref()
.ok_or(Error::InvalidField("commit"))?;
let ka = KeyAgreement::from(commit.key_agreement);
let (secret, public) = generate_dh_keypair(ka)?;
let pvi = self.public_value_bytes(public);
self.local_dh_secret = Some(secret);
let ids = retained_secret_ids_for_role(
self.negotiated
.as_ref()
.ok_or(Error::InvalidField("negotiated"))?
.hash,
SecretIdRole::Initiator,
&self.local_hello.hash_image_h3,
self.retained_secrets.as_ref().map(|s| s.rs1.as_slice()),
self.retained_secrets.as_ref().map(|s| s.rs2.as_slice()),
None,
None,
)?;
Ok(DhPart2 {
hash_image_h1: [0x55; 32],
rs1_idi: ids.rs1,
rs2_idi: ids.rs2,
auxsecret_idi: ids.aux,
pbxsecret_idi: ids.pbx,
pvi,
mac: [0xdd; 8],
})
}
fn precompute_initiator_dhpart2(&mut self) -> Result<(), Error> {
let negotiated = self
.negotiated
.as_ref()
.ok_or(Error::InvalidField("negotiated"))?;
if !matches!(
negotiated.key_agreement,
KeyAgreement::Dh2048
| KeyAgreement::Dh3072
| KeyAgreement::EcP256
| KeyAgreement::EcP384
| KeyAgreement::EcP521
) {
return Ok(());
}
if self.pending_dhpart2.is_some() {
return Ok(());
}
let ka = negotiated.key_agreement;
let (secret, public) = generate_dh_keypair(ka)?;
let pvi = self.public_value_bytes(public);
self.local_dh_secret = Some(secret);
let ids = retained_secret_ids_for_role(
negotiated.hash,
SecretIdRole::Initiator,
&self.local_hello.hash_image_h3,
self.retained_secrets.as_ref().map(|s| s.rs1.as_slice()),
self.retained_secrets.as_ref().map(|s| s.rs2.as_slice()),
None,
None,
)?;
self.pending_dhpart2 = Some(DhPart2 {
hash_image_h1: [0x55; 32],
rs1_idi: ids.rs1,
rs2_idi: ids.rs2,
auxsecret_idi: ids.aux,
pbxsecret_idi: ids.pbx,
pvi,
mac: [0xdd; 8],
});
Ok(())
}
fn public_value_bytes(&self, public: DhPublicKey) -> Vec<u8> {
match public {
DhPublicKey::EcP256(v)
| DhPublicKey::EcP384(v)
| DhPublicKey::EcP521(v)
| DhPublicKey::Modp2048(v)
| DhPublicKey::Modp3072(v) => v,
DhPublicKey::Unsupported => vec![],
}
}
fn public_value_from_bytes(&self, ka: KeyAgreement, bytes: &[u8]) -> DhPublicKey {
match ka {
KeyAgreement::EcP256 => DhPublicKey::EcP256(bytes.to_vec()),
KeyAgreement::EcP384 => DhPublicKey::EcP384(bytes.to_vec()),
KeyAgreement::EcP521 => DhPublicKey::EcP521(bytes.to_vec()),
KeyAgreement::Dh2048 => DhPublicKey::Modp2048(bytes.to_vec()),
KeyAgreement::Dh3072 => DhPublicKey::Modp3072(bytes.to_vec()),
_ => DhPublicKey::Unsupported,
}
}
fn validate_dhpart1_secret_ids(&self, dh1: &DhPart1) -> Result<(), Error> {
self.validate_secret_ids(
SecretIdRole::Responder,
&dh1.rs1_idr,
&dh1.rs2_idr,
&dh1.auxsecret_idr,
&dh1.pbxsecret_idr,
)
}
fn validate_dhpart2_secret_ids(&self, dh2: &DhPart2) -> Result<(), Error> {
self.validate_secret_ids(
SecretIdRole::Initiator,
&dh2.rs1_idi,
&dh2.rs2_idi,
&dh2.auxsecret_idi,
&dh2.pbxsecret_idi,
)
}
fn validate_secret_ids(
&self,
role: SecretIdRole,
rs1: &[u8; 8],
rs2: &[u8; 8],
aux: &[u8; 8],
pbx: &[u8; 8],
) -> Result<(), Error> {
let negotiated = self
.negotiated
.as_ref()
.ok_or(Error::InvalidField("negotiated"))?;
let h3 = match role {
SecretIdRole::Responder => {
&self
.remote_hello
.as_ref()
.ok_or(Error::InvalidField("remote hello"))?
.hash_image_h3
}
SecretIdRole::Initiator => &self.local_hello.hash_image_h3,
};
let expected = retained_secret_ids_for_role(
negotiated.hash,
role,
h3,
self.retained_secrets.as_ref().map(|s| s.rs1.as_slice()),
self.retained_secrets.as_ref().map(|s| s.rs2.as_slice()),
None,
None,
)?;
if *rs1 != expected.rs1
|| *rs2 != expected.rs2
|| *aux != expected.aux
|| *pbx != expected.pbx
{
return Err(Error::InvalidField("retained secret id mismatch"));
}
Ok(())
}
pub fn retained_secret_matches_dhpart1(
&self,
dh1: &DhPart1,
) -> Result<RetainedSecretMatches, Error> {
let negotiated = self
.negotiated
.as_ref()
.ok_or(Error::InvalidField("negotiated"))?;
let expected = retained_secret_ids_for_role(
negotiated.hash,
SecretIdRole::Responder,
&self
.remote_hello
.as_ref()
.ok_or(Error::InvalidField("remote hello"))?
.hash_image_h3,
self.retained_secrets.as_ref().map(|s| s.rs1.as_slice()),
self.retained_secrets.as_ref().map(|s| s.rs2.as_slice()),
None,
None,
)?;
Ok(match_retained_secret_ids(
&expected,
&dh1.rs1_idr,
&dh1.rs2_idr,
&dh1.auxsecret_idr,
&dh1.pbxsecret_idr,
))
}
pub fn retained_secret_matches_dhpart2(
&self,
dh2: &DhPart2,
) -> Result<RetainedSecretMatches, Error> {
let negotiated = self
.negotiated
.as_ref()
.ok_or(Error::InvalidField("negotiated"))?;
let expected = retained_secret_ids_for_role(
negotiated.hash,
SecretIdRole::Initiator,
&self.local_hello.hash_image_h3,
self.retained_secrets.as_ref().map(|s| s.rs1.as_slice()),
self.retained_secrets.as_ref().map(|s| s.rs2.as_slice()),
None,
None,
)?;
Ok(match_retained_secret_ids(
&expected,
&dh2.rs1_idi,
&dh2.rs2_idi,
&dh2.auxsecret_idi,
&dh2.pbxsecret_idi,
))
}
pub fn shared_secret_selection_from_dhpart1<'a>(
&'a self,
dh1: &DhPart1,
) -> Result<SharedSecretSelection<'a>, Error> {
let matches = self.retained_secret_matches_dhpart1(dh1)?;
let rs1_or_rs2 = matches.rs1 || matches.rs2;
let retained = self.retained_secrets.as_ref();
Ok(select_matching_shared_secrets(
retained.map(|s| s.rs1.as_slice()),
retained.map(|s| s.rs2.as_slice()),
None,
None,
&matches,
matches.rs1,
!matches.rs1 && rs1_or_rs2,
))
}
pub fn shared_secret_selection_from_dhpart2<'a>(
&'a self,
dh2: &DhPart2,
) -> Result<SharedSecretSelection<'a>, Error> {
let matches = self.retained_secret_matches_dhpart2(dh2)?;
let rs1_or_rs2 = matches.rs1 || matches.rs2;
let retained = self.retained_secrets.as_ref();
Ok(select_matching_shared_secrets(
retained.map(|s| s.rs1.as_slice()),
retained.map(|s| s.rs2.as_slice()),
None,
None,
&matches,
matches.rs1,
!matches.rs1 && rs1_or_rs2,
))
}
fn compute_sas_from_remote(
&mut self,
remote_public_value: &[u8],
) -> Result<Option<String>, Error> {
let negotiated = match &self.negotiated {
Some(v) => v,
None => return Ok(None),
};
let local_secret = match &self.local_dh_secret {
Some(v) => v,
None => return Ok(None),
};
match negotiated.key_agreement {
KeyAgreement::Preshared | KeyAgreement::Multistream => return Ok(None),
_ => {}
}
let remote = self
.remote_hello
.as_ref()
.ok_or(Error::InvalidField("remote hello"))?;
let (initiator_hello, responder_hello, initiator_zid, responder_zid) = match self.role {
Role::Initiator => (
&self.local_hello,
remote,
&self.local_hello.zid,
&remote.zid,
),
Role::Responder => (
remote,
&self.local_hello,
&remote.zid,
&self.local_hello.zid,
),
};
let remote_public =
self.public_value_from_bytes(negotiated.key_agreement, remote_public_value);
let shared = compute_dh_shared(local_secret, &remote_public)?;
let retained = self.retained_secrets.as_ref();
let s0 = derive_s0_full(
negotiated.hash,
&shared,
&[
&initiator_hello.hash_image_h3,
&responder_hello.hash_image_h3,
],
initiator_zid,
responder_zid,
retained,
)?;
let session = derive_session_secrets(
negotiated.hash,
negotiated.cipher,
&s0,
initiator_zid,
responder_zid,
)?;
let transcript = self.transcript_slices();
self.total_hash = Some(compute_total_hash(negotiated.hash, &transcript)?);
self.session_secrets = Some(session.clone());
let sas = sas_render(negotiated.sas, session.sas_value)?;
Ok(Some(sas))
}
fn derive_cached_session(&mut self, commit: &Commit) -> Result<(), Error> {
let negotiated = self
.negotiated
.as_ref()
.ok_or(Error::InvalidField("negotiated"))?;
let remote = self
.remote_hello
.as_ref()
.ok_or(Error::InvalidField("remote hello"))?;
let retained = self
.retained_secrets
.as_ref()
.ok_or(Error::InvalidField("retained secrets"))?;
let (initiator_hello, responder_hello, initiator_zid, responder_zid) = match self.role {
Role::Initiator => (
&self.local_hello,
remote,
&self.local_hello.zid,
&remote.zid,
),
Role::Responder => (
remote,
&self.local_hello,
&remote.zid,
&self.local_hello.zid,
),
};
let s0 = derive_s0_full(
negotiated.hash,
&[],
&[
&initiator_hello.hash_image_h3,
&responder_hello.hash_image_h3,
&commit.hash_image_h2,
],
initiator_zid,
responder_zid,
Some(retained),
)?;
let transcript = self.transcript_slices();
self.total_hash = Some(compute_total_hash(negotiated.hash, &transcript)?);
self.session_secrets = Some(derive_session_secrets(
negotiated.hash,
negotiated.cipher,
&s0,
initiator_zid,
responder_zid,
)?);
Ok(())
}
fn make_confirm(&mut self, from_initiator: bool) -> Result<Confirm, Error> {
if self.session_secrets.is_none() {
let commit = self
.last_commit
.clone()
.ok_or(Error::InvalidField("commit"))?;
self.derive_cached_session(&commit)?;
if self.session_secrets.is_none() {
return Err(Error::InvalidField("session secrets"));
}
}
let negotiated = self
.negotiated
.clone()
.ok_or(Error::InvalidField("negotiated"))?;
let session = self.session_secrets.as_ref().unwrap();
let content = ConfirmContent {
h0: [0x11; 32],
signature_length_words: 0,
flags: ConfirmFlags::default(),
cache_expiration_interval: 0,
signature_type: None,
signature: vec![],
};
let (confirm_key, mac_key) = if from_initiator {
(
&session.confirm_key_initiator,
&session.confirm_mac_key_initiator,
)
} else {
(
&session.confirm_key_responder,
&session.confirm_mac_key_responder,
)
};
let confirm = build_confirm_message(
negotiated.hash,
negotiated.cipher,
confirm_key,
mac_key,
&content,
)?;
self.last_sas = Some(sas_render(negotiated.sas, session.sas_value)?);
Ok(confirm)
}
fn verify_confirm(
&mut self,
confirm: &Confirm,
from_initiator: bool,
) -> Result<ConfirmContent, Error> {
let negotiated = self
.negotiated
.as_ref()
.ok_or(Error::InvalidField("negotiated"))?;
let session = self
.session_secrets
.as_ref()
.ok_or(Error::InvalidField("session secrets"))?;
let (confirm_key, mac_key) = if from_initiator {
(
&session.confirm_key_initiator,
&session.confirm_mac_key_initiator,
)
} else {
(
&session.confirm_key_responder,
&session.confirm_mac_key_responder,
)
};
let content = open_confirm_message(
negotiated.hash,
negotiated.cipher,
confirm_key,
mac_key,
confirm,
)?;
Ok(content)
}
fn validate_commit_material(&self, commit: &Commit) -> Result<(), Error> {
let nonce_key = Self::commit_nonce_bytes(commit);
if self.seen_commit_nonces.contains(&nonce_key) {
return Err(Error::InvalidField("nonce reuse"));
}
match (&commit.nonce, KeyAgreement::from(commit.key_agreement)) {
(CommitNonce::Hvi(hvi), KeyAgreement::Dh2048)
| (CommitNonce::Hvi(hvi), KeyAgreement::Dh3072)
| (CommitNonce::Hvi(hvi), KeyAgreement::EcP256)
| (CommitNonce::Hvi(hvi), KeyAgreement::EcP384)
| (CommitNonce::Hvi(hvi), KeyAgreement::EcP521)
if hvi == &[0u8; 32] =>
{
return Err(Error::InvalidField("zero hvi"));
}
(CommitNonce::PresharedNonce { key_id, .. }, KeyAgreement::Preshared) => {
let retained = self
.retained_secrets
.as_ref()
.ok_or(Error::InvalidField("retained secrets"))?;
let negotiated = self
.negotiated
.as_ref()
.ok_or(Error::InvalidField("negotiated"))?;
let preshared_key =
compute_preshared_key(negotiated.hash, &retained.rs1, None, None)?;
let expected = compute_key_id(negotiated.hash, &preshared_key)?;
if &expected != key_id {
return Err(Error::InvalidField("key id"));
}
}
_ => {}
}
Ok(())
}
pub fn resolve_commit_contention(
local_hello: &Hello,
local_commit: &Commit,
remote_hello: &Hello,
remote_commit: &Commit,
) -> Result<bool, Error> {
let local_ka = KeyAgreement::from(local_commit.key_agreement);
let remote_ka = KeyAgreement::from(remote_commit.key_agreement);
if (local_ka == KeyAgreement::Multistream) ^ (remote_ka == KeyAgreement::Multistream) {
return Err(Error::InvalidField("multistream contention"));
}
let local_is_dh = matches!(
local_ka,
KeyAgreement::Dh2048
| KeyAgreement::Dh3072
| KeyAgreement::EcP256
| KeyAgreement::EcP384
| KeyAgreement::EcP521
);
let remote_is_dh = matches!(
remote_ka,
KeyAgreement::Dh2048
| KeyAgreement::Dh3072
| KeyAgreement::EcP256
| KeyAgreement::EcP384
| KeyAgreement::EcP521
);
if local_is_dh && remote_ka == KeyAgreement::Preshared {
return Ok(true);
}
if local_ka == KeyAgreement::Preshared && remote_is_dh {
return Ok(false);
}
if local_ka == KeyAgreement::Preshared && remote_ka == KeyAgreement::Preshared {
return Ok(!local_hello.mitm_capable || remote_hello.mitm_capable);
}
let local_val: Vec<u8> = match &local_commit.nonce {
CommitNonce::Hvi(v) => v.to_vec(),
CommitNonce::MultistreamNonce(v) => v.to_vec(),
CommitNonce::PresharedNonce { nonce, .. } => nonce.to_vec(),
};
let remote_val: Vec<u8> = match &remote_commit.nonce {
CommitNonce::Hvi(v) => v.to_vec(),
CommitNonce::MultistreamNonce(v) => v.to_vec(),
CommitNonce::PresharedNonce { nonce, .. } => nonce.to_vec(),
};
Ok(local_val > remote_val)
}
fn update_retained_secrets_after_confirm(&mut self) -> Result<(), Error> {
let negotiated = self
.negotiated
.as_ref()
.ok_or(Error::InvalidField("negotiated"))?;
if negotiated.key_agreement == KeyAgreement::Multistream {
return Ok(());
}
let remote = self
.remote_hello
.as_ref()
.ok_or(Error::InvalidField("remote hello"))?;
let session = self
.session_secrets
.as_ref()
.ok_or(Error::InvalidField("session secrets"))?;
let total_hash = self.total_hash.clone().unwrap_or_else(|| vec![0u8; 32]);
let new_rs1 = derive_retained_secret(
negotiated.hash,
&session.s0,
&self.local_hello.zid,
&remote.zid,
&total_hash,
)?;
let previous = self.retained_secrets.clone();
let (rs2, verified) = match (negotiated.key_agreement, previous) {
(KeyAgreement::Preshared, Some(prev)) => (prev.rs2, prev.verified),
(_, Some(prev)) => (prev.rs1, prev.verified),
_ => (Vec::new(), false),
};
let secrets = RetainedSecrets {
rs1: new_rs1,
rs2,
verified,
expiration_interval: self.peer_confirms_cache_expiry.unwrap_or(0),
};
self.retained_secrets = Some(secrets.clone());
self.store.store(CacheEntry {
local_zid: self.local_hello.zid,
remote_zid: remote.zid,
secrets,
})?;
Ok(())
}
fn verify_hvi_if_needed(&mut self, dh2: &DhPart2) -> Result<(), Error> {
if self.role != Role::Responder {
return Ok(());
}
let negotiated = self
.negotiated
.as_ref()
.ok_or(Error::InvalidField("negotiated"))?;
let commit = match self.last_commit.as_ref() {
Some(v) => v,
None => return Ok(()),
};
let expected = match &commit.nonce {
CommitNonce::Hvi(v) => v,
_ => return Ok(()),
};
if !matches!(
negotiated.key_agreement,
KeyAgreement::Dh2048
| KeyAgreement::Dh3072
| KeyAgreement::EcP256
| KeyAgreement::EcP384
| KeyAgreement::EcP521
) {
return Ok(());
}
let hello_bytes = Message::Hello(self.local_hello.clone()).encode();
let dh2_bytes = Message::DhPart2(dh2.clone()).encode();
let actual = compute_hvi(negotiated.hash, &dh2_bytes, &hello_bytes)?;
self.hvi_verified = Some(actual == *expected);
if actual != *expected {
return Err(Error::InvalidField("hvi"));
}
Ok(())
}
}