chore: rename quicproquo → quicprochat in Rust workspace
Rename all crate directories, package names, binary names, proto package/module paths, ALPN strings, env var prefixes, config filenames, mDNS service names, and plugin ABI symbols from quicproquo/qpq to quicprochat/qpc.
This commit is contained in:
109
crates/quicprochat-core/src/keypackage.rs
Normal file
109
crates/quicprochat-core/src/keypackage.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
//! MLS KeyPackage generation and TLS serialisation.
|
||||
//!
|
||||
//! # Ciphersuite
|
||||
//!
|
||||
//! `MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519` (ciphersuite ID `0x0001`).
|
||||
//! This is the RECOMMENDED ciphersuite from RFC 9420 §17.1.
|
||||
//!
|
||||
//! # Single-use semantics
|
||||
//!
|
||||
//! Per RFC 9420 §10.1, each KeyPackage MUST be used at most once. The
|
||||
//! Authentication Service enforces this by atomically removing a package on
|
||||
//! fetch.
|
||||
//!
|
||||
//! # Wire format
|
||||
//!
|
||||
//! KeyPackages are TLS-encoded using `tls_codec` (same version as openmls).
|
||||
//! The resulting bytes are opaque to the quicprochat transport layer.
|
||||
|
||||
use openmls::prelude::{
|
||||
Ciphersuite, Credential, CredentialType, CredentialWithKey, CryptoConfig, KeyPackage,
|
||||
KeyPackageIn, TlsDeserializeTrait, TlsSerializeTrait,
|
||||
};
|
||||
use openmls_rust_crypto::OpenMlsRustCrypto;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::{error::CoreError, identity::IdentityKeypair};
|
||||
|
||||
/// The MLS ciphersuite used throughout quicprochat (RFC 9420 §17.1).
|
||||
pub const ALLOWED_CIPHERSUITE: Ciphersuite =
|
||||
Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
|
||||
|
||||
/// Wire value of the allowed ciphersuite (KeyPackage TLS encoding: version 2B, ciphersuite 2B).
|
||||
const ALLOWED_CIPHERSUITE_WIRE: u16 = 0x0001;
|
||||
|
||||
const CIPHERSUITE: Ciphersuite = ALLOWED_CIPHERSUITE;
|
||||
|
||||
/// Validates that the KeyPackage bytes use an allowed ciphersuite (Phase 2: ciphersuite allowlist).
|
||||
///
|
||||
/// Parses the TLS-encoded KeyPackage and rejects if the ciphersuite is not
|
||||
/// `MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519`. Does not verify signatures;
|
||||
/// the server uses this only to enforce policy before storing.
|
||||
pub fn validate_keypackage_ciphersuite(bytes: &[u8]) -> Result<(), CoreError> {
|
||||
if bytes.len() < 4 {
|
||||
return Err(CoreError::Mls("KeyPackage too short for version+ciphersuite".into()));
|
||||
}
|
||||
let cs_wire = u16::from_be_bytes([bytes[2], bytes[3]]);
|
||||
if cs_wire != ALLOWED_CIPHERSUITE_WIRE {
|
||||
return Err(CoreError::Mls(format!(
|
||||
"KeyPackage ciphersuite {:#06x} not in allowlist (only {:#06x} allowed)",
|
||||
cs_wire, ALLOWED_CIPHERSUITE_WIRE
|
||||
)));
|
||||
}
|
||||
// Optionally confirm full parse so we don't accept garbage that happens to have 0x0001 at offset 2.
|
||||
let mut cursor = bytes;
|
||||
let _kp = KeyPackageIn::tls_deserialize(&mut cursor)
|
||||
.map_err(|e| CoreError::Mls(format!("KeyPackage parse: {e:?}")))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate a fresh MLS KeyPackage for `identity` and serialise it.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `(tls_bytes, sha256_fingerprint)` where:
|
||||
/// - `tls_bytes` is the TLS-encoded KeyPackage blob, suitable for uploading.
|
||||
/// - `sha256_fingerprint` is the SHA-256 digest of `tls_bytes` for tamper detection.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`CoreError::Mls`] if openmls fails to create the KeyPackage or if
|
||||
/// TLS serialisation fails.
|
||||
pub fn generate_key_package(identity: &IdentityKeypair) -> Result<(Vec<u8>, Vec<u8>), CoreError> {
|
||||
let backend = OpenMlsRustCrypto::default();
|
||||
|
||||
// Build a BasicCredential using the raw Ed25519 public key bytes as the
|
||||
// MLS identity. Per RFC 9420, any byte string may serve as the identity.
|
||||
let credential = Credential::new(identity.public_key_bytes().to_vec(), CredentialType::Basic)
|
||||
.map_err(|e| CoreError::Mls(format!("{e:?}")))?;
|
||||
|
||||
// The `signature_key` in CredentialWithKey is the Ed25519 public key that
|
||||
// will be used to verify the KeyPackage's leaf node signature.
|
||||
// `SignaturePublicKey` implements `From<Vec<u8>>`.
|
||||
let credential_with_key = CredentialWithKey {
|
||||
credential,
|
||||
signature_key: identity.public_key_bytes().to_vec().into(),
|
||||
};
|
||||
|
||||
// `IdentityKeypair` implements `openmls_traits::signatures::Signer`
|
||||
// so it can be passed directly to the builder.
|
||||
let key_package = KeyPackage::builder()
|
||||
.build(
|
||||
CryptoConfig::with_default_version(CIPHERSUITE),
|
||||
&backend,
|
||||
identity,
|
||||
credential_with_key,
|
||||
)
|
||||
.map_err(|e| CoreError::Mls(format!("{e:?}")))?;
|
||||
|
||||
// TLS-encode the KeyPackage using the trait from the openmls prelude.
|
||||
// This uses tls_codec 0.3 (the same version openmls uses internally),
|
||||
// avoiding a duplicate-trait conflict with tls_codec 0.4.
|
||||
let tls_bytes = key_package
|
||||
.tls_serialize_detached()
|
||||
.map_err(|e| CoreError::Mls(format!("{e:?}")))?;
|
||||
|
||||
let fingerprint: Vec<u8> = Sha256::digest(&tls_bytes).to_vec();
|
||||
|
||||
Ok((tls_bytes, fingerprint))
|
||||
}
|
||||
Reference in New Issue
Block a user