Migrates all MLS code in quicprochat-core from OpenMLS 0.5 to 0.8: - StorageProvider replaces OpenMlsKeyStore (keystore.rs full rewrite) - HybridCryptoProvider updated for new OpenMlsProvider trait - Group operations updated for new API signatures - MLS state persistence via MemoryStorage serialization - tls_codec 0.3 → 0.4, openmls_traits/rust_crypto 0.2 → 0.5
104 lines
4.1 KiB
Rust
104 lines
4.1 KiB
Rust
//! 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::{
|
|
BasicCredential, Ciphersuite, CredentialWithKey, KeyPackage, KeyPackageIn,
|
|
};
|
|
use openmls_rust_crypto::OpenMlsRustCrypto;
|
|
use tls_codec::{Deserialize as TlsDeserializeTrait, Serialize as TlsSerializeTrait};
|
|
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: openmls::prelude::Credential =
|
|
BasicCredential::new(identity.public_key_bytes().to_vec()).into();
|
|
|
|
// 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_bundle = KeyPackage::builder()
|
|
.build(CIPHERSUITE, &backend, identity, credential_with_key)
|
|
.map_err(|e| CoreError::Mls(format!("{e:?}")))?;
|
|
|
|
// TLS-encode the KeyPackage.
|
|
let tls_bytes = key_package_bundle
|
|
.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))
|
|
}
|