Compare commits

...
Author SHA1 Message Date
Alex Emmet
04d6240452 Merge remote-tracking branch 'refs/remotes/origin/master'
Some checks failed
CI / rustfmt (push) Successful in 17s
CI / wasm build (push) Successful in 1m27s
CI / clippy (push) Failing after 1m32s
CI / example (push) Successful in 1m46s
CI / test (push) Successful in 2m7s
CI / duplicate code (push) Failing after 35s
CI / web client (push) Failing after 35s
CI / cargo-machete (push) Successful in 1m27s
CI / cargo-deny (push) Failing after 2m41s
2026-06-28 14:43:09 +02:00
Alex Emmet
daf2f940d5 [Add] Key de-/serialization 2026-06-28 14:42:56 +02:00
4 changed files with 505 additions and 126 deletions

37
Cargo.lock generated
View file

@ -49,7 +49,7 @@ dependencies = [
"nom",
"num-traits",
"rusticata-macros",
"thiserror",
"thiserror 2.0.18",
"time",
]
@ -1020,7 +1020,7 @@ version = "0.1.0"
dependencies = [
"quinn",
"rustls",
"thiserror",
"thiserror 2.0.18",
"wtransport",
]
@ -1038,6 +1038,7 @@ dependencies = [
"rand_core 0.6.4",
"serde",
"sha2 0.11.0",
"thiserror 1.0.69",
"zeroize",
]
@ -1331,7 +1332,7 @@ dependencies = [
"rustc-hash",
"rustls",
"socket2",
"thiserror",
"thiserror 2.0.18",
"tokio",
"tracing",
"web-time",
@ -1353,7 +1354,7 @@ dependencies = [
"rustls",
"rustls-pki-types",
"slab",
"thiserror",
"thiserror 2.0.18",
"tinyvec",
"tracing",
"web-time",
@ -1852,13 +1853,33 @@ dependencies = [
"syn",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
"thiserror-impl 2.0.18",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
@ -2377,7 +2398,7 @@ dependencies = [
"rustls-pki-types",
"sha2 0.11.0",
"socket2",
"thiserror",
"thiserror 2.0.18",
"time",
"tokio",
"tracing",
@ -2394,7 +2415,7 @@ checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11"
dependencies = [
"httlib-huffman",
"octets",
"thiserror",
"thiserror 2.0.18",
"url",
]
@ -2425,7 +2446,7 @@ dependencies = [
"oid-registry",
"ring",
"rusticata-macros",
"thiserror",
"thiserror 2.0.18",
"time",
]

View file

@ -16,6 +16,7 @@ ed25519-dalek = { version = "2.2", optional = true, features = [
hkdf = { version = "0.13", optional = true }
sha2 = { version = "0.11", optional = true }
zeroize = { version = "1.9", features = ["derive"] }
thiserror = "1"
rand_core = { version = "0.6", features = ["getrandom"] }
getrandom = "0.4.3"
mlkem-tls = { version = "0.2", optional = true }

View file

@ -1,38 +1,31 @@
use std::fmt;
use thiserror::Error;
#[derive(Debug, Clone)]
#[derive(Error, Debug, Clone)]
pub enum CryptoError {
#[error("encryption failed")]
EncryptionFailed,
#[error("decryption failed")]
DecryptionFailed,
#[error("invalid key length")]
InvalidKeyLength,
#[error("invalid nonce length")]
InvalidNonceLength,
#[error("invalid signature")]
InvalidSignature,
#[error("signing failed")]
SigningFailed,
#[error("verification failed")]
VerificationFailed,
#[error("key generation failed")]
KeyGenerationFailed,
#[error("KDF error")]
KdfError,
#[error("KEM encapsulation failed")]
KemEncapsulationFailed,
#[error("KEM decapsulation failed")]
KemDecapsulationFailed,
#[error("unknown algorithm")]
UnknownAlgorithm,
#[error("invalid hex encoding")]
InvalidHex,
}
impl fmt::Display for CryptoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CryptoError::EncryptionFailed => write!(f, "encryption failed"),
CryptoError::DecryptionFailed => write!(f, "decryption failed"),
CryptoError::InvalidKeyLength => write!(f, "invalid key length"),
CryptoError::InvalidNonceLength => write!(f, "invalid nonce length"),
CryptoError::InvalidSignature => write!(f, "invalid signature"),
CryptoError::SigningFailed => write!(f, "signing failed"),
CryptoError::VerificationFailed => write!(f, "verification failed"),
CryptoError::KeyGenerationFailed => write!(f, "key generation failed"),
CryptoError::KdfError => write!(f, "KDF error"),
CryptoError::KemEncapsulationFailed => write!(f, "KEM encapsulation failed"),
CryptoError::KemDecapsulationFailed => write!(f, "KEM decapsulation failed"),
CryptoError::UnknownAlgorithm => write!(f, "unknown algorithm"),
}
}
}
impl std::error::Error for CryptoError {}

View file

@ -1,5 +1,8 @@
use std::fmt;
use zeroize::{Zeroize, ZeroizeOnDrop};
// --- Private key types ---
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Zeroize, ZeroizeOnDrop)]
@ -9,12 +12,26 @@ impl EncryptionPrivateKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl fmt::Debug for EncryptionPrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EncryptionPrivateKey")
.field("len", &self.0.len())
.field("data", &"[REDACTED]")
.finish()
}
}
impl AsRef<[u8]> for EncryptionPrivateKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for EncryptionPrivateKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
@ -27,6 +44,8 @@ impl From<&[u8]> for EncryptionPrivateKey {
}
}
// ---
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Zeroize, ZeroizeOnDrop)]
@ -36,12 +55,26 @@ impl SignaturePrivateKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl fmt::Debug for SignaturePrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SignaturePrivateKey")
.field("len", &self.0.len())
.field("data", &"[REDACTED]")
.finish()
}
}
impl AsRef<[u8]> for SignaturePrivateKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for SignaturePrivateKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
@ -54,59 +87,7 @@ impl From<&[u8]> for SignaturePrivateKey {
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Clone)]
pub struct EncryptionPublicKey(Vec<u8>);
impl EncryptionPublicKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for EncryptionPublicKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
impl From<&[u8]> for EncryptionPublicKey {
fn from(bytes: &[u8]) -> Self {
Self(bytes.to_vec())
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Clone)]
pub struct SignaturePublicKey(Vec<u8>);
impl SignaturePublicKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for SignaturePublicKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
impl From<&[u8]> for SignaturePublicKey {
fn from(bytes: &[u8]) -> Self {
Self(bytes.to_vec())
}
}
// ---
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
@ -117,12 +98,26 @@ impl KemPrivateKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl fmt::Debug for KemPrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("KemPrivateKey")
.field("len", &self.0.len())
.field("data", &"[REDACTED]")
.finish()
}
}
impl AsRef<[u8]> for KemPrivateKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for KemPrivateKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
@ -135,6 +130,179 @@ impl From<&[u8]> for KemPrivateKey {
}
}
// ---
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct SignaturePqPrivateKey(Vec<u8>);
impl SignaturePqPrivateKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl fmt::Debug for SignaturePqPrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SignaturePqPrivateKey")
.field("len", &self.0.len())
.field("data", &"[REDACTED]")
.finish()
}
}
impl AsRef<[u8]> for SignaturePqPrivateKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for SignaturePqPrivateKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
impl From<&[u8]> for SignaturePqPrivateKey {
fn from(bytes: &[u8]) -> Self {
Self(bytes.to_vec())
}
}
// --- Public key types ---
fn bytes_to_hex(bytes: &[u8]) -> String {
bytes
.iter()
.fold(String::with_capacity(bytes.len() * 2), |mut s, b| {
use fmt::Write;
let _ = write!(s, "{:02x}", b);
s
})
}
fn hex_to_bytes(s: &str) -> Result<Vec<u8>, crate::error::CryptoError> {
if s.len() % 2 != 0 {
return Err(crate::error::CryptoError::InvalidHex);
}
(0..s.len())
.step_by(2)
.map(|i| {
u8::from_str_radix(&s[i..i + 2], 16).map_err(|_| crate::error::CryptoError::InvalidHex)
})
.collect()
}
// ---
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Clone)]
pub struct EncryptionPublicKey(Vec<u8>);
impl EncryptionPublicKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn to_hex(&self) -> String {
bytes_to_hex(&self.0)
}
pub fn from_hex(s: &str) -> Result<Self, crate::error::CryptoError> {
hex_to_bytes(s).map(Self)
}
}
impl fmt::Debug for EncryptionPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "EncryptionPublicKey({})", self.to_hex())
}
}
impl AsRef<[u8]> for EncryptionPublicKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for EncryptionPublicKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
impl From<&[u8]> for EncryptionPublicKey {
fn from(bytes: &[u8]) -> Self {
Self(bytes.to_vec())
}
}
impl From<&EncryptionPublicKey> for Vec<u8> {
fn from(key: &EncryptionPublicKey) -> Vec<u8> {
key.0.clone()
}
}
// ---
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Clone)]
pub struct SignaturePublicKey(Vec<u8>);
impl SignaturePublicKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn to_hex(&self) -> String {
bytes_to_hex(&self.0)
}
pub fn from_hex(s: &str) -> Result<Self, crate::error::CryptoError> {
hex_to_bytes(s).map(Self)
}
}
impl fmt::Debug for SignaturePublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SignaturePublicKey({})", self.to_hex())
}
}
impl AsRef<[u8]> for SignaturePublicKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for SignaturePublicKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
impl From<&[u8]> for SignaturePublicKey {
fn from(bytes: &[u8]) -> Self {
Self(bytes.to_vec())
}
}
impl From<&SignaturePublicKey> for Vec<u8> {
fn from(key: &SignaturePublicKey) -> Vec<u8> {
key.0.clone()
}
}
// ---
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Clone)]
@ -144,10 +312,27 @@ impl KemPublicKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn to_hex(&self) -> String {
bytes_to_hex(&self.0)
}
pub fn from_hex(s: &str) -> Result<Self, crate::error::CryptoError> {
hex_to_bytes(s).map(Self)
}
}
impl fmt::Debug for KemPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "KemPublicKey({})", self.to_hex())
}
}
impl AsRef<[u8]> for KemPublicKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for KemPublicKey {
@ -162,6 +347,14 @@ impl From<&[u8]> for KemPublicKey {
}
}
impl From<&KemPublicKey> for Vec<u8> {
fn from(key: &KemPublicKey) -> Vec<u8> {
key.0.clone()
}
}
// ---
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Clone)]
@ -171,10 +364,27 @@ impl SignaturePqPublicKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn to_hex(&self) -> String {
bytes_to_hex(&self.0)
}
pub fn from_hex(s: &str) -> Result<Self, crate::error::CryptoError> {
hex_to_bytes(s).map(Self)
}
}
impl fmt::Debug for SignaturePqPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SignaturePqPublicKey({})", self.to_hex())
}
}
impl AsRef<[u8]> for SignaturePqPublicKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for SignaturePqPublicKey {
@ -189,32 +399,13 @@ impl From<&[u8]> for SignaturePqPublicKey {
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct SignaturePqPrivateKey(Vec<u8>);
impl SignaturePqPrivateKey {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
impl From<&SignaturePqPublicKey> for Vec<u8> {
fn from(key: &SignaturePqPublicKey) -> Vec<u8> {
key.0.clone()
}
}
impl From<Vec<u8>> for SignaturePqPrivateKey {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
impl From<&[u8]> for SignaturePqPrivateKey {
fn from(bytes: &[u8]) -> Self {
Self(bytes.to_vec())
}
}
// --- Keyring ---
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(ZeroizeOnDrop)]
@ -252,12 +443,9 @@ impl Keyring {
#[cfg(all(feature = "mlkem-tls", feature = "ml-dsa", feature = "ed25519-dalek"))]
pub fn generate() -> Self {
let (kem_sk, kem_pk) = crate::kem::HybridKem::generate_keypair();
let (ed_signer, sig_cl_sk, sig_cl_pk) = crate::sign::Ed25519Signer::generate();
let (_ml_signer, sig_pq_sk, sig_pq_pk) = crate::sign::MlDsaSigner::generate();
drop(ed_signer);
Self {
kem_public_key: kem_pk,
kem_secret_key: kem_sk,
@ -267,8 +455,98 @@ impl Keyring {
sig_cl_secret_key: sig_cl_sk,
}
}
pub fn public_key_bundle(&self) -> PublicKeyBundle {
PublicKeyBundle {
kem_public_key: self.kem_public_key.clone(),
sig_pq_public_key: self.sig_pq_public_key.clone(),
sig_cl_public_key: self.sig_cl_public_key.clone(),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let fields: &[&[u8]] = &[
self.kem_public_key.as_bytes(),
self.kem_secret_key.as_bytes(),
self.sig_pq_public_key.as_bytes(),
self.sig_pq_secret_key.as_bytes(),
self.sig_cl_public_key.as_bytes(),
self.sig_cl_secret_key.as_bytes(),
];
let mut out = Vec::new();
for f in fields {
out.extend_from_slice(&(f.len() as u16).to_be_bytes());
out.extend_from_slice(f);
}
out
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::error::CryptoError> {
use crate::error::CryptoError;
let mut offset = 0;
let read_key = |offset: &mut usize| -> Result<Vec<u8>, CryptoError> {
let len = u16::from_be_bytes(
bytes
.get(*offset..*offset + 2)
.ok_or(CryptoError::InvalidKeyLength)?
.try_into()
.expect("slice is 2 bytes, verified above"),
) as usize;
*offset += 2;
let key = bytes
.get(*offset..*offset + len)
.ok_or(CryptoError::InvalidKeyLength)?
.to_vec();
*offset += len;
Ok(key)
};
Ok(Self {
kem_public_key: KemPublicKey::new(read_key(&mut offset)?),
kem_secret_key: KemPrivateKey::new(read_key(&mut offset)?),
sig_pq_public_key: SignaturePqPublicKey::new(read_key(&mut offset)?),
sig_pq_secret_key: SignaturePqPrivateKey::new(read_key(&mut offset)?),
sig_cl_public_key: SignaturePublicKey::new(read_key(&mut offset)?),
sig_cl_secret_key: SignaturePrivateKey::new(read_key(&mut offset)?),
})
}
}
impl TryFrom<&[u8]> for Keyring {
type Error = crate::error::CryptoError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::from_bytes(bytes)
}
}
impl From<&Keyring> for Vec<u8> {
fn from(keyring: &Keyring) -> Vec<u8> {
keyring.to_bytes()
}
}
impl fmt::Debug for Keyring {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Keyring")
.field("kem_public_key", &self.kem_public_key)
.field("kem_secret_key", &self.kem_secret_key)
.field("sig_pq_public_key", &self.sig_pq_public_key)
.field("sig_pq_secret_key", &self.sig_pq_secret_key)
.field("sig_cl_public_key", &self.sig_cl_public_key)
.field("sig_cl_secret_key", &self.sig_cl_secret_key)
.finish()
}
}
// --- PublicKeyBundle ---
/// Ed25519 public key is always 32 bytes.
pub const SIG_CL_PUBLIC_KEY_LEN: usize = 32;
/// ML-DSA-65 public key is always 1952 bytes.
pub const SIG_PQ_PUBLIC_KEY_LEN: usize = 1952;
/// Hybrid X25519 + ML-KEM-768 public key (32 + 1184 bytes).
pub const KEM_PUBLIC_KEY_LEN: usize = 1216;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone)]
pub struct PublicKeyBundle {
@ -290,6 +568,21 @@ impl PublicKeyBundle {
}
}
#[cfg(all(feature = "ed25519-dalek", feature = "ml-dsa", feature = "mlkem-tls"))]
pub fn validate(&self) -> Result<(), crate::error::CryptoError> {
use crate::error::CryptoError;
if self.sig_cl_public_key.as_bytes().len() != SIG_CL_PUBLIC_KEY_LEN {
return Err(CryptoError::InvalidKeyLength);
}
if self.sig_pq_public_key.as_bytes().len() != SIG_PQ_PUBLIC_KEY_LEN {
return Err(CryptoError::InvalidKeyLength);
}
if self.kem_public_key.as_bytes().len() != KEM_PUBLIC_KEY_LEN {
return Err(CryptoError::InvalidKeyLength);
}
Ok(())
}
pub fn as_bytes(&self) -> Vec<u8> {
let kem = self.kem_public_key.as_bytes();
let pq = self.sig_pq_public_key.as_bytes();
@ -354,66 +647,137 @@ impl PublicKeyBundle {
}
}
impl Keyring {
pub fn public_key_bundle(&self) -> PublicKeyBundle {
PublicKeyBundle {
kem_public_key: self.kem_public_key.clone(),
sig_pq_public_key: self.sig_pq_public_key.clone(),
sig_cl_public_key: self.sig_cl_public_key.clone(),
}
}
/*
* Serialize the full keyring (all six keys) into a byte vector.
*
* Format: for each key, a 2-byte length prefix followed by the key bytes,
* in the order: kem_pk, kem_sk, sig_pq_pk, sig_pq_sk, sig_cl_pk, sig_cl_sk.
*/
pub fn to_bytes(&self) -> Vec<u8> {
let fields: &[&[u8]] = &[
self.kem_public_key.as_bytes(),
self.kem_secret_key.as_bytes(),
self.sig_pq_public_key.as_bytes(),
self.sig_pq_secret_key.as_bytes(),
self.sig_cl_public_key.as_bytes(),
self.sig_cl_secret_key.as_bytes(),
];
let mut out = Vec::new();
for f in fields {
out.extend_from_slice(&(f.len() as u16).to_be_bytes());
out.extend_from_slice(f);
}
out
}
/// Deserialize a full keyring from bytes produced by [`Keyring::to_bytes`].
pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::error::CryptoError> {
use crate::error::CryptoError;
let mut offset = 0;
let read_key = |offset: &mut usize| -> Result<Vec<u8>, CryptoError> {
let len = u16::from_be_bytes(
bytes
.get(*offset..*offset + 2)
.ok_or(CryptoError::InvalidKeyLength)?
.try_into()
.expect("slice is 2 bytes, verified above"),
) as usize;
*offset += 2;
let key = bytes
.get(*offset..*offset + len)
.ok_or(CryptoError::InvalidKeyLength)?
.to_vec();
*offset += len;
Ok(key)
};
Ok(Self {
kem_public_key: KemPublicKey::new(read_key(&mut offset)?),
kem_secret_key: KemPrivateKey::new(read_key(&mut offset)?),
sig_pq_public_key: SignaturePqPublicKey::new(read_key(&mut offset)?),
sig_pq_secret_key: SignaturePqPrivateKey::new(read_key(&mut offset)?),
sig_cl_public_key: SignaturePublicKey::new(read_key(&mut offset)?),
sig_cl_secret_key: SignaturePrivateKey::new(read_key(&mut offset)?),
})
impl TryFrom<&[u8]> for PublicKeyBundle {
type Error = crate::error::CryptoError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::from_bytes(bytes)
}
}
impl From<&PublicKeyBundle> for Vec<u8> {
fn from(bundle: &PublicKeyBundle) -> Vec<u8> {
bundle.as_bytes()
}
}
impl fmt::Debug for PublicKeyBundle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PublicKeyBundle")
.field("kem_public_key", &self.kem_public_key)
.field("sig_pq_public_key", &self.sig_pq_public_key)
.field("sig_cl_public_key", &self.sig_cl_public_key)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn public_key_bundle_roundtrip() {
let kem = KemPublicKey::new(vec![1u8; 32]);
let pq = SignaturePqPublicKey::new(vec![2u8; 64]);
let cl = SignaturePublicKey::new(vec![3u8; 32]);
let bundle = PublicKeyBundle::new(kem, pq, cl);
let bytes = bundle.as_bytes();
let recovered = PublicKeyBundle::from_bytes(&bytes).unwrap();
assert_eq!(
bundle.kem_public_key.as_bytes(),
recovered.kem_public_key.as_bytes()
);
assert_eq!(
bundle.sig_pq_public_key.as_bytes(),
recovered.sig_pq_public_key.as_bytes()
);
assert_eq!(
bundle.sig_cl_public_key.as_bytes(),
recovered.sig_cl_public_key.as_bytes()
);
}
#[test]
fn public_key_bundle_try_from_roundtrip() {
let bundle = PublicKeyBundle::new(
KemPublicKey::new(vec![0xABu8; 48]),
SignaturePqPublicKey::new(vec![0xCDu8; 96]),
SignaturePublicKey::new(vec![0xEFu8; 32]),
);
let bytes: Vec<u8> = Vec::from(&bundle);
let recovered = PublicKeyBundle::try_from(bytes.as_slice()).unwrap();
assert_eq!(bundle.as_bytes(), recovered.as_bytes());
}
#[test]
fn keyring_roundtrip() {
let keyring = Keyring::new(
KemPublicKey::new(vec![1u8; 32]),
KemPrivateKey::new(vec![2u8; 32]),
SignaturePqPublicKey::new(vec![3u8; 64]),
SignaturePqPrivateKey::new(vec![4u8; 64]),
SignaturePublicKey::new(vec![5u8; 32]),
SignaturePrivateKey::new(vec![6u8; 32]),
);
let bytes = keyring.to_bytes();
let recovered = Keyring::from_bytes(&bytes).unwrap();
assert_eq!(
keyring.kem_public_key.as_bytes(),
recovered.kem_public_key.as_bytes()
);
assert_eq!(
keyring.kem_secret_key.as_bytes(),
recovered.kem_secret_key.as_bytes()
);
assert_eq!(
keyring.sig_cl_public_key.as_bytes(),
recovered.sig_cl_public_key.as_bytes()
);
}
#[test]
fn keyring_try_from_roundtrip() {
let keyring = Keyring::new(
KemPublicKey::new(vec![0u8; 16]),
KemPrivateKey::new(vec![1u8; 16]),
SignaturePqPublicKey::new(vec![2u8; 16]),
SignaturePqPrivateKey::new(vec![3u8; 16]),
SignaturePublicKey::new(vec![4u8; 16]),
SignaturePrivateKey::new(vec![5u8; 16]),
);
let bytes: Vec<u8> = Vec::from(&keyring);
let recovered = Keyring::try_from(bytes.as_slice()).unwrap();
assert_eq!(keyring.to_bytes(), recovered.to_bytes());
}
#[test]
fn hex_roundtrip() {
let key = KemPublicKey::new(vec![0xDE, 0xAD, 0xBE, 0xEF]);
let hex = key.to_hex();
assert_eq!(hex, "deadbeef");
let recovered = KemPublicKey::from_hex(&hex).unwrap();
assert_eq!(key.as_bytes(), recovered.as_bytes());
}
#[test]
fn hex_invalid_returns_error() {
assert!(KemPublicKey::from_hex("xyz").is_err());
assert!(KemPublicKey::from_hex("abc").is_err()); // odd length
}
#[test]
fn debug_private_key_redacted() {
let key = KemPrivateKey::new(vec![0u8; 32]);
let s = format!("{:?}", key);
assert!(s.contains("REDACTED"));
assert!(!s.contains("00"));
}
#[test]
fn debug_public_key_shows_hex() {
let key = KemPublicKey::new(vec![0xABu8; 4]);
let s = format!("{:?}", key);
assert!(s.contains("abababab"));
}
}