General Upgrade, NEW: WebServers, Better Docs
Some checks failed
CI / checks (push) Failing after 5m18s
Some checks failed
CI / checks (push) Failing after 5m18s
This commit is contained in:
parent
5f11d476b6
commit
6e5c985719
122 changed files with 10309 additions and 5206 deletions
218
client/src/crypto.rs
Normal file
218
client/src/crypto.rs
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
use mtp_codec::{CommunicationType, CommunicationValue, DataType, DataValue, Version};
|
||||
use mtp_common::CommunicationError;
|
||||
|
||||
pub(crate) fn unexpected_response_type_error(
|
||||
context: &str,
|
||||
expected_type: mtp_codec::CommunicationTypeId,
|
||||
response: &CommunicationValue,
|
||||
) -> CommunicationError {
|
||||
CommunicationError::AuthenticationFailed(format!(
|
||||
"unexpected response type during {context}: expected {:?}, got {:?}; parsed {}",
|
||||
expected_type,
|
||||
response.get_type(),
|
||||
response
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn verify_host_challenge(
|
||||
challenge: &CommunicationValue,
|
||||
host_pk: &mtp_crypto::PublicKeyBundle,
|
||||
id: u64,
|
||||
server_challenge: u128,
|
||||
require_pq: bool,
|
||||
) -> Result<(), CommunicationError> {
|
||||
use mtp_crypto::{auth, verify_ed25519, verify_ml_dsa};
|
||||
|
||||
let sig = match challenge.get_data(DataType::Signature) {
|
||||
DataValue::Bytes(b) => b.clone(),
|
||||
_ => {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Missing host challenge signature".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
let pq_sig = match challenge.get_data(DataType::PqSignature) {
|
||||
DataValue::Bytes(b) => b.clone(),
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
let host_requires_pq = challenge.get_data(DataType::RequirePq) == &DataValue::BoolTrue;
|
||||
if host_requires_pq && host_pk.sig_pq_public_key.as_bytes().is_empty() {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Host requires post-quantum authentication but its PQ public key is absent".into(),
|
||||
));
|
||||
}
|
||||
if require_pq && pq_sig.is_empty() {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Host challenge is missing the required PQ signature".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let payload = auth::challenge_payload(id, server_challenge);
|
||||
verify_ed25519(&host_pk.sig_cl_public_key, &payload, &sig).map_err(|_| {
|
||||
CommunicationError::AuthenticationFailed("Host challenge signature invalid".into())
|
||||
})?;
|
||||
if !pq_sig.is_empty() && verify_ml_dsa(&host_pk.sig_pq_public_key, &payload, &pq_sig).is_err() {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Host challenge PQ signature invalid".into(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn verify_host_final(
|
||||
response: &CommunicationValue,
|
||||
host_pk: &mtp_crypto::PublicKeyBundle,
|
||||
id: u64,
|
||||
client_nonce: u128,
|
||||
server_challenge: u128,
|
||||
require_pq: bool,
|
||||
) -> Result<(), CommunicationError> {
|
||||
use mtp_crypto::{auth, verify_ed25519, verify_ml_dsa};
|
||||
|
||||
match response.get_data(DataType::ClientNonce) {
|
||||
DataValue::UnsignedNumber(n) if *n == client_nonce => {}
|
||||
_ => {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Nonce mismatch".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let sig = match response.get_data(DataType::Signature) {
|
||||
DataValue::Bytes(b) => b.clone(),
|
||||
_ => {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Missing signature".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
let pq_sig = match response.get_data(DataType::PqSignature) {
|
||||
DataValue::Bytes(b) => b.clone(),
|
||||
_ => vec![],
|
||||
};
|
||||
if require_pq && pq_sig.is_empty() {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Host confirmation is missing the required PQ signature".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let payload = auth::host_final_payload(id, client_nonce, server_challenge);
|
||||
verify_ed25519(&host_pk.sig_cl_public_key, &payload, &sig)
|
||||
.map_err(|_| CommunicationError::AuthenticationFailed("Host signature invalid".into()))?;
|
||||
if !pq_sig.is_empty() && verify_ml_dsa(&host_pk.sig_pq_public_key, &payload, &pq_sig).is_err() {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Host PQ signature invalid".into(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn check_connected(
|
||||
response: &CommunicationValue,
|
||||
reject_msg: &str,
|
||||
) -> Result<(), CommunicationError> {
|
||||
match response.get_data(DataType::Connected) {
|
||||
DataValue::BoolTrue => Ok(()),
|
||||
DataValue::BoolFalse => Err(CommunicationError::AuthenticationFailed(
|
||||
response
|
||||
.get_str(DataType::ErrorMessage)
|
||||
.unwrap_or(reject_msg)
|
||||
.into(),
|
||||
)),
|
||||
_ => Err(CommunicationError::AuthenticationFailed(
|
||||
"Invalid response".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn negotiated_version(
|
||||
response: &CommunicationValue,
|
||||
) -> Result<Version, CommunicationError> {
|
||||
match response.get_data(DataType::Version) {
|
||||
DataValue::Str(version) => Version::parse(version).ok_or_else(|| {
|
||||
CommunicationError::AuthenticationFailed(
|
||||
"Host returned an invalid negotiated protocol version".into(),
|
||||
)
|
||||
}),
|
||||
_ => Err(CommunicationError::AuthenticationFailed(
|
||||
"Host omitted the negotiated protocol version".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn signed_challenge_response(
|
||||
keys: &mtp_crypto::Keyring,
|
||||
proof_payload: &[u8],
|
||||
client_nonce: u128,
|
||||
) -> Result<CommunicationValue, CommunicationError> {
|
||||
use mtp_crypto::{Ed25519Signer, MlDsaSigner, SignatureScheme};
|
||||
|
||||
let signer = Ed25519Signer::new(&keys.sig_cl_secret_key)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
let signature = signer
|
||||
.sign(proof_payload)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
|
||||
let mut proof = CommunicationValue::new(CommunicationType::ChallengeResponse)
|
||||
.add_typed_default(
|
||||
DataType::ClientNonce,
|
||||
DataValue::UnsignedNumber(client_nonce),
|
||||
)
|
||||
.add_typed_default(DataType::Signature, DataValue::Bytes(signature));
|
||||
|
||||
if !keys.sig_pq_secret_key.as_bytes().is_empty() {
|
||||
let pq_signer = MlDsaSigner::new(&keys.sig_pq_secret_key, &keys.sig_pq_public_key)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
let pq_signature = pq_signer
|
||||
.sign(proof_payload)
|
||||
.map_err(|e| CommunicationError::Other(e.to_string()))?;
|
||||
proof = proof.add_typed_default(DataType::PqSignature, DataValue::Bytes(pq_signature));
|
||||
}
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
pub(crate) async fn receive_verified_challenge(
|
||||
receiver: &mtp_transport::Receiver,
|
||||
tm: &mtp_codec::TypeMap,
|
||||
host_public_key_bundle: &mtp_crypto::PublicKeyBundle,
|
||||
bound_id: u64,
|
||||
context: &str,
|
||||
require_pq: bool,
|
||||
client_has_pq_key: bool,
|
||||
) -> Result<u128, CommunicationError> {
|
||||
let challenge = receiver.receive().await?;
|
||||
let expected = CommunicationType::Challenge
|
||||
.try_to_id(tm)
|
||||
.ok_or_else(|| CommunicationError::Other("Challenge is absent from the type map".into()))?;
|
||||
if challenge.get_type() != expected {
|
||||
return Err(unexpected_response_type_error(
|
||||
context, expected, &challenge,
|
||||
));
|
||||
}
|
||||
|
||||
let server_challenge = match challenge.get_data(DataType::ServerNonce) {
|
||||
DataValue::UnsignedNumber(n) => *n,
|
||||
_ => {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Missing server challenge".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
if challenge.get_data(DataType::RequirePq) == &DataValue::BoolTrue && !client_has_pq_key {
|
||||
return Err(CommunicationError::AuthenticationFailed(
|
||||
"Host requires post-quantum authentication but the client PQ key is absent".into(),
|
||||
));
|
||||
}
|
||||
|
||||
verify_host_challenge(
|
||||
&challenge,
|
||||
host_public_key_bundle,
|
||||
bound_id,
|
||||
server_challenge,
|
||||
require_pq,
|
||||
)?;
|
||||
|
||||
Ok(server_challenge)
|
||||
}
|
||||
Loading…
Reference in a new issue