Basic Crypto
This commit is contained in:
parent
02f94993c7
commit
94f2280570
10 changed files with 568 additions and 79 deletions
|
|
@ -4,21 +4,21 @@
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub enum CommunicationType {
|
pub enum CommunicationType {
|
||||||
ping = 0x01,
|
ping = 0x0001,
|
||||||
message_send = 0x02,
|
message_send = 0x0002,
|
||||||
update = 0x03,
|
update = 0x0003,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommunicationType {
|
impl CommunicationType {
|
||||||
pub fn as_number(self) -> u8 {
|
pub fn as_number(self) -> u16 {
|
||||||
self as u8
|
self as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_number(n: u8) -> Self {
|
pub fn from_number(n: u16) -> Self {
|
||||||
match n {
|
match n {
|
||||||
0x01 => CommunicationType::ping,
|
0x0001 => CommunicationType::ping,
|
||||||
0x02 => CommunicationType::message_send,
|
0x0002 => CommunicationType::message_send,
|
||||||
0x03 => CommunicationType::update,
|
0x0003 => CommunicationType::update,
|
||||||
_ => CommunicationType::ping,
|
_ => CommunicationType::ping,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ impl CommunicationValue {
|
||||||
/*
|
/*
|
||||||
* Frame format (strict new format):
|
* Frame format (strict new format):
|
||||||
* [4 bytes u32 total_length] // number of bytes after this field
|
* [4 bytes u32 total_length] // number of bytes after this field
|
||||||
* [1 byte communication_type]
|
* [2 bytes u16 communication_type]
|
||||||
* [1 byte flags]
|
* [1 byte flags]
|
||||||
* [optional 4 bytes id] // if flags bit2 set
|
* [optional 4 bytes id] // if flags bit2 set
|
||||||
* [optional 6 bytes sender] // if flags bit0 set
|
* [optional 6 bytes sender] // if flags bit0 set
|
||||||
|
|
@ -110,7 +110,7 @@ impl CommunicationValue {
|
||||||
flags |= 0b0000_0100;
|
flags |= 0b0000_0100;
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.push(self.comm_type.0);
|
let _ = payload.write_u16::<BigEndian>(self.comm_type.0);
|
||||||
payload.push(flags);
|
payload.push(flags);
|
||||||
|
|
||||||
if has_id {
|
if has_id {
|
||||||
|
|
@ -148,7 +148,7 @@ impl CommunicationValue {
|
||||||
|
|
||||||
let frame_end = 4 + total_len;
|
let frame_end = 4 + total_len;
|
||||||
|
|
||||||
let comm_type_num = cursor.read_u8().ok()?;
|
let comm_type_num = cursor.read_u16::<BigEndian>().ok()?;
|
||||||
let comm_type = CommTypeId(comm_type_num);
|
let comm_type = CommTypeId(comm_type_num);
|
||||||
|
|
||||||
let flags = cursor.read_u8().ok()?;
|
let flags = cursor.read_u8().ok()?;
|
||||||
|
|
@ -216,13 +216,13 @@ mod tests {
|
||||||
let cv = CommunicationValue::new(CommTypeId(1)).with_id(0);
|
let cv = CommunicationValue::new(CommTypeId(1)).with_id(0);
|
||||||
let bytes = cv.to_bytes();
|
let bytes = cv.to_bytes();
|
||||||
|
|
||||||
// [u32 len][type][flags]...
|
// [u32 len][u16 type][flags]...
|
||||||
assert!(bytes.len() >= 6);
|
assert!(bytes.len() >= 7);
|
||||||
let mut c = Cursor::new(bytes.as_slice());
|
let mut c = Cursor::new(bytes.as_slice());
|
||||||
let total_len = c.read_u32::<BigEndian>().expect("read len");
|
let total_len = c.read_u32::<BigEndian>().expect("read len");
|
||||||
assert_eq!(total_len as usize + 4, bytes.len());
|
assert_eq!(total_len as usize + 4, bytes.len());
|
||||||
|
|
||||||
let typ = c.read_u8().expect("read type");
|
let typ = c.read_u16::<BigEndian>().expect("read type");
|
||||||
assert_eq!(typ, 1);
|
assert_eq!(typ, 1);
|
||||||
|
|
||||||
let flags = c.read_u8().expect("read flags");
|
let flags = c.read_u8().expect("read flags");
|
||||||
|
|
@ -242,7 +242,7 @@ mod tests {
|
||||||
let total_len = c.read_u32::<BigEndian>().expect("len");
|
let total_len = c.read_u32::<BigEndian>().expect("len");
|
||||||
assert_eq!(total_len as usize + 4, bytes.len());
|
assert_eq!(total_len as usize + 4, bytes.len());
|
||||||
|
|
||||||
let typ = c.read_u8().expect("type");
|
let typ = c.read_u16::<BigEndian>().expect("type");
|
||||||
assert_eq!(typ, 2);
|
assert_eq!(typ, 2);
|
||||||
|
|
||||||
let flags = c.read_u8().expect("flags");
|
let flags = c.read_u8().expect("flags");
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ use std::io::Cursor;
|
||||||
|
|
||||||
use mtp_type_map::DataTypeId;
|
use mtp_type_map::DataTypeId;
|
||||||
|
|
||||||
|
#[cfg(feature = "crypto")]
|
||||||
|
use mtp_crypto::{AeadDecrypt, AeadEncrypt};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum DataKind {
|
pub enum DataKind {
|
||||||
Bool,
|
Bool,
|
||||||
|
|
@ -44,7 +47,7 @@ pub enum DataValue {
|
||||||
Container(Vec<(DataTypeId, DataValue)>),
|
Container(Vec<(DataTypeId, DataValue)>),
|
||||||
|
|
||||||
#[cfg(feature = "crypto")]
|
#[cfg(feature = "crypto")]
|
||||||
EncryptedContainer(Vec<(DataTypeId, DataValue)>),
|
EncryptedContainer(Vec<u8>),
|
||||||
|
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
@ -52,26 +55,36 @@ pub enum DataValue {
|
||||||
impl DataValue {
|
impl DataValue {
|
||||||
/*
|
/*
|
||||||
* Container format:
|
* Container format:
|
||||||
* [2 bytes u16 entry_count] // length of the container
|
* [2 bytes u16 entry_count] // number of entries
|
||||||
* [1 byte kind] // DataValue kind marker
|
* [1 byte kind] // DataValue kind marker
|
||||||
* [if kind == BOOL_TRUE or BOOL_FALSE:]
|
* [if kind == BOOL_TRUE or BOOL_FALSE:]
|
||||||
* [1 byte key] // DataTypes discriminant
|
* [2 bytes u16 key] // DataTypeId discriminant
|
||||||
* [else:]
|
* [else:]
|
||||||
* [2 bytes u16 payload_len] // length of the value payload
|
* [4 bytes u32 payload_len] // length of the value payload
|
||||||
* [1 byte key] // DataTypes discriminant
|
* [2 bytes u16 key] // DataTypeId discriminant
|
||||||
* [payload_len bytes payload] // value data (interpreted based on kind)
|
* [payload_len bytes payload] // value data (interpreted based on kind)
|
||||||
*
|
*
|
||||||
|
* Array format (same as container but no keys):
|
||||||
|
* [2 bytes u16 entry_count]
|
||||||
|
* for each entry:
|
||||||
|
* [1 byte kind]
|
||||||
|
* [if kind == BOOL_TRUE or BOOL_FALSE:]
|
||||||
|
* (no payload)
|
||||||
|
* [else:]
|
||||||
|
* [4 bytes u32 payload_len]
|
||||||
|
* [payload_len bytes payload]
|
||||||
|
*
|
||||||
* Kind markers:
|
* Kind markers:
|
||||||
* 0x01 => BoolTrue
|
* 0x01 => BoolTrue
|
||||||
* 0x02 => BoolFalse
|
* 0x02 => BoolFalse
|
||||||
* 0x03 => Signed Number (i64, 8 bytes big-endian)
|
* 0x03 => Signed Number (i128, 16 bytes big-endian)
|
||||||
* 0x04 => Unsigned Number (u64, 8 bytes big-endian)
|
* 0x04 => Unsigned Number (u128, 16 bytes big-endian)
|
||||||
* 0x05 => Float (1 byte exponent, 3 bytes mantissa)
|
* 0x05 => Float (1 byte exponent, 4 bytes mantissa)
|
||||||
* 0x06 => Str (UTF-8 bytes)
|
* 0x06 => Str (UTF-8 bytes)
|
||||||
* 0x07 => Bytes
|
* 0x07 => Bytes
|
||||||
* 0x08 => Array (nested container format)
|
* 0x08 => Array
|
||||||
* 0x09 => Container (nested container format)
|
* 0x09 => Container
|
||||||
* 0x0A => EncryptedContainer (nested container format)
|
* 0x0A => EncryptedContainer (4 bytes u32 len + encrypted bytes)
|
||||||
* 0x0B => Null
|
* 0x0B => Null
|
||||||
*/
|
*/
|
||||||
const KIND_BOOL_TRUE: u8 = 0x01;
|
const KIND_BOOL_TRUE: u8 = 0x01;
|
||||||
|
|
@ -184,13 +197,49 @@ impl DataValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "crypto")]
|
#[cfg(feature = "crypto")]
|
||||||
pub fn as_encrypted_container(&self) -> Option<Vec<(DataTypeId, DataValue)>> {
|
pub fn as_encrypted_container(&self) -> Option<Vec<u8>> {
|
||||||
match self {
|
match self {
|
||||||
DataValue::EncryptedContainer(c) => Some(c.clone()),
|
DataValue::EncryptedContainer(c) => Some(c.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decrypt an `EncryptedContainer` in-place, replacing it with the
|
||||||
|
/// deserialized `Container`. Returns `None` if decryption or
|
||||||
|
/// deserialization fails.
|
||||||
|
#[cfg(feature = "crypto")]
|
||||||
|
pub fn decrypt_into_container(
|
||||||
|
&mut self,
|
||||||
|
cipher: &impl AeadDecrypt,
|
||||||
|
aad: &[u8],
|
||||||
|
) -> Option<()> {
|
||||||
|
let data = self.as_encrypted_container()?;
|
||||||
|
let plaintext = cipher.decrypt(&data, aad).ok()?;
|
||||||
|
let dv = DataValue::from_bytes(&plaintext)?;
|
||||||
|
match dv {
|
||||||
|
DataValue::Container(entries) => {
|
||||||
|
*self = DataValue::Container(entries);
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt a `Container` into an `EncryptedContainer` in-place.
|
||||||
|
/// Returns `None` if the value is not a `Container` or encryption fails.
|
||||||
|
#[cfg(feature = "crypto")]
|
||||||
|
pub fn encrypt_container(
|
||||||
|
&mut self,
|
||||||
|
cipher: &impl AeadEncrypt,
|
||||||
|
aad: &[u8],
|
||||||
|
) -> Option<()> {
|
||||||
|
let entries = self.as_container()?;
|
||||||
|
let plaintext = DataValue::Container(entries).to_bytes();
|
||||||
|
let ct = cipher.encrypt(&plaintext, aad).ok()?;
|
||||||
|
*self = DataValue::EncryptedContainer(ct);
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_map(&self) -> Option<BTreeMap<DataTypeId, DataValue>> {
|
pub fn as_map(&self) -> Option<BTreeMap<DataTypeId, DataValue>> {
|
||||||
match self {
|
match self {
|
||||||
DataValue::Container(c) => {
|
DataValue::Container(c) => {
|
||||||
|
|
@ -258,8 +307,7 @@ impl DataValue {
|
||||||
buf.push(kind);
|
buf.push(kind);
|
||||||
|
|
||||||
if kind == Self::KIND_BOOL_TRUE || kind == Self::KIND_BOOL_FALSE {
|
if kind == Self::KIND_BOOL_TRUE || kind == Self::KIND_BOOL_FALSE {
|
||||||
let key = u8::try_from(key.0).unwrap_or(0);
|
let _ = buf.write_u16::<BigEndian>(key.0);
|
||||||
buf.push(key);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,16 +316,10 @@ impl DataValue {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let len_u16 = match u16::try_from(payload.len()) {
|
if buf.write_u32::<BigEndian>(payload.len() as u32).is_err() {
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => return false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if buf.write_u16::<BigEndian>(len_u16).is_err() {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let key = u8::try_from(key.0).unwrap_or(0);
|
let _ = buf.write_u16::<BigEndian>(key.0);
|
||||||
buf.push(key);
|
|
||||||
buf.extend_from_slice(&payload);
|
buf.extend_from_slice(&payload);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
@ -313,12 +355,7 @@ impl DataValue {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let len_u16 = match u16::try_from(payload.len()) {
|
if buf.write_u32::<BigEndian>(payload.len() as u32).is_err() {
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => return false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if buf.write_u16::<BigEndian>(len_u16).is_err() {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
buf.extend_from_slice(&payload);
|
buf.extend_from_slice(&payload);
|
||||||
|
|
@ -368,9 +405,8 @@ impl DataValue {
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
#[cfg(feature = "crypto")]
|
#[cfg(feature = "crypto")]
|
||||||
DataValue::EncryptedContainer(entries) => {
|
DataValue::EncryptedContainer(data) => {
|
||||||
let bytes = Self::encode_container(entries);
|
buf.extend_from_slice(data);
|
||||||
buf.extend_from_slice(&bytes);
|
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -418,7 +454,7 @@ impl DataValue {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = cursor.read_u16::<BigEndian>().ok()? as usize;
|
let len = cursor.read_u32::<BigEndian>().ok()? as usize;
|
||||||
let key = DataTypeId(cursor.read_u16::<BigEndian>().ok()?);
|
let key = DataTypeId(cursor.read_u16::<BigEndian>().ok()?);
|
||||||
|
|
||||||
let start = cursor.position() as usize;
|
let start = cursor.position() as usize;
|
||||||
|
|
@ -457,7 +493,7 @@ impl DataValue {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = cursor.read_u16::<BigEndian>().ok()? as usize;
|
let len = cursor.read_u32::<BigEndian>().ok()? as usize;
|
||||||
let start = cursor.position() as usize;
|
let start = cursor.position() as usize;
|
||||||
let end = start.checked_add(len)?;
|
let end = start.checked_add(len)?;
|
||||||
if end > cursor.get_ref().len() {
|
if end > cursor.get_ref().len() {
|
||||||
|
|
@ -510,6 +546,17 @@ impl DataValue {
|
||||||
cursor.set_position(end as u64);
|
cursor.set_position(end as u64);
|
||||||
Some(DataValue::Str(s))
|
Some(DataValue::Str(s))
|
||||||
}
|
}
|
||||||
|
Self::KIND_BYTES => {
|
||||||
|
let len = payload_len?;
|
||||||
|
let start = cursor.position() as usize;
|
||||||
|
let end = start.checked_add(len)?;
|
||||||
|
if end > cursor.get_ref().len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let b = cursor.get_ref()[start..end].to_vec();
|
||||||
|
cursor.set_position(end as u64);
|
||||||
|
Some(DataValue::Bytes(b))
|
||||||
|
}
|
||||||
Self::KIND_ARRAY => {
|
Self::KIND_ARRAY => {
|
||||||
let start = cursor.position() as usize;
|
let start = cursor.position() as usize;
|
||||||
let len = payload_len?;
|
let len = payload_len?;
|
||||||
|
|
@ -542,25 +589,19 @@ impl DataValue {
|
||||||
}
|
}
|
||||||
#[cfg(feature = "crypto")]
|
#[cfg(feature = "crypto")]
|
||||||
Self::KIND_ENCRYPTED_CONTAINER => {
|
Self::KIND_ENCRYPTED_CONTAINER => {
|
||||||
let start = cursor.position() as usize;
|
|
||||||
let len = payload_len?;
|
let len = payload_len?;
|
||||||
|
let start = cursor.position() as usize;
|
||||||
let end = start.checked_add(len)?;
|
let end = start.checked_add(len)?;
|
||||||
if end > cursor.get_ref().len() {
|
if end > cursor.get_ref().len() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut inner = Cursor::new(&cursor.get_ref()[start..end]);
|
let data = cursor.get_ref()[start..end].to_vec();
|
||||||
let c = Self::try_read_container(&mut inner)?;
|
|
||||||
if inner.position() as usize != len {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
cursor.set_position(end as u64);
|
cursor.set_position(end as u64);
|
||||||
if let DataValue::Container(entries) = c {
|
Some(DataValue::EncryptedContainer(data))
|
||||||
Some(DataValue::EncryptedContainer(entries))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Self::KIND_NULL => Some(DataValue::Null),
|
Self::KIND_NULL => Some(DataValue::Null),
|
||||||
|
#[cfg(not(feature = "crypto"))]
|
||||||
|
0x0A => None,
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -660,7 +701,7 @@ impl Hash for DataValue {
|
||||||
c.hash(state);
|
c.hash(state);
|
||||||
}
|
}
|
||||||
Null => {
|
Null => {
|
||||||
6u8.hash(state);
|
7u8.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub enum RegistryError {
|
pub enum RegistryError {
|
||||||
ReservedCommId(u8, String),
|
ReservedCommId(u16, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
@ -9,7 +9,7 @@ pub enum CodecError {
|
||||||
UnknownVersion,
|
UnknownVersion,
|
||||||
UnknownCommunicationType(String),
|
UnknownCommunicationType(String),
|
||||||
UnknownDataType(String),
|
UnknownDataType(String),
|
||||||
ReservedCommunicationType(u8),
|
ReservedCommunicationType(u16),
|
||||||
InvalidEncoding,
|
InvalidEncoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,21 @@ edition = "2024"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chacha20poly1305 = { version = "0.10", optional = true }
|
chacha20poly1305 = { version = "0.10", optional = true }
|
||||||
aes-gcm = { version = "0.10", optional = true }
|
aes-gcm = { version = "0.10", optional = true }
|
||||||
ed25519-dalek = { version = "2.1", optional = true, features = ["pkcs8", "pem"] }
|
ed25519-dalek = { version = "2.2", optional = true, features = [
|
||||||
hkdf = { version = "0.12", optional = true }
|
"pkcs8",
|
||||||
sha2 = { version = "0.10", optional = true }
|
"pem",
|
||||||
zeroize = { version = "1.7", features = ["derive"] }
|
] }
|
||||||
|
hkdf = { version = "0.13", optional = true }
|
||||||
|
sha2 = { version = "0.11", optional = true }
|
||||||
|
zeroize = { version = "1.9", features = ["derive"] }
|
||||||
rand_core = { version = "0.6", features = ["getrandom"] }
|
rand_core = { version = "0.6", features = ["getrandom"] }
|
||||||
getrandom = "0.2"
|
getrandom = "0.4.3"
|
||||||
mlkem-tls = { version = "0.2", optional = true }
|
mlkem-tls = { version = "0.2", optional = true }
|
||||||
ml-dsa = { version = "0.0.4", optional = true }
|
ml-dsa = { version = "0.1.1", optional = true }
|
||||||
|
serde = { version = "1", optional = true, features = ["derive"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["chacha20poly1305", "ed25519-dalek", "hkdf", "sha2"]
|
default = ["chacha20poly1305", "ed25519-dalek", "hkdf", "sha2"]
|
||||||
full = ["chacha20poly1305", "aes-gcm", "ed25519-dalek", "hkdf", "sha2"]
|
full = ["chacha20poly1305", "aes-gcm", "ed25519-dalek", "hkdf", "sha2"]
|
||||||
pqc = ["mlkem-tls", "ml-dsa"]
|
pqc = ["mlkem-tls", "ml-dsa"]
|
||||||
|
serde = ["dep:serde"]
|
||||||
|
|
|
||||||
235
crypto/src/helper.rs
Normal file
235
crypto/src/helper.rs
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
use crate::error::CryptoError;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "mlkem-tls", feature = "chacha20poly1305", feature = "hkdf"))]
|
||||||
|
use crate::aead::{AeadDecrypt, AeadEncrypt, ChaCha20Poly1305};
|
||||||
|
#[cfg(all(feature = "mlkem-tls", feature = "chacha20poly1305", feature = "hkdf"))]
|
||||||
|
use crate::kdf::derive_encryption_key;
|
||||||
|
#[cfg(all(feature = "mlkem-tls", feature = "chacha20poly1305", feature = "hkdf"))]
|
||||||
|
use crate::kem::HybridKem;
|
||||||
|
#[cfg(all(feature = "mlkem-tls", feature = "chacha20poly1305", feature = "hkdf"))]
|
||||||
|
use crate::keypair::{Keyring, PublicKeyBundle};
|
||||||
|
#[cfg(all(feature = "mlkem-tls", feature = "chacha20poly1305", feature = "hkdf"))]
|
||||||
|
use rand_core::RngCore;
|
||||||
|
|
||||||
|
pub struct RecipientEntry {
|
||||||
|
pub kem_ciphertext: Vec<u8>,
|
||||||
|
pub encrypted_key: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A payload encrypted for multiple recipients.
|
||||||
|
*
|
||||||
|
* Any recipient who possesses the corresponding `KemPrivateKey` can decrypt the message.
|
||||||
|
*/
|
||||||
|
pub struct MultiEncryptedMessage {
|
||||||
|
pub recipients: Vec<RecipientEntry>,
|
||||||
|
pub nonce: [u8; 24],
|
||||||
|
pub ciphertext: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MultiEncryptedMessage {
|
||||||
|
/*
|
||||||
|
* Serialize into a compact byte vector.
|
||||||
|
*
|
||||||
|
* Format:
|
||||||
|
* - `num_recipients: u16`
|
||||||
|
* - for each recipient:
|
||||||
|
* - `kem_ct_len: u16` | `kem_ciphertext`
|
||||||
|
* - `ek_len: u16` | `encrypted_key`
|
||||||
|
* - `nonce: 24 bytes`
|
||||||
|
* - `ciphertext` (remaining)
|
||||||
|
*/
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
out.extend_from_slice(&(self.recipients.len() as u16).to_be_bytes());
|
||||||
|
for r in &self.recipients {
|
||||||
|
out.extend_from_slice(&(r.kem_ciphertext.len() as u16).to_be_bytes());
|
||||||
|
out.extend_from_slice(&r.kem_ciphertext);
|
||||||
|
out.extend_from_slice(&(r.encrypted_key.len() as u16).to_be_bytes());
|
||||||
|
out.extend_from_slice(&r.encrypted_key);
|
||||||
|
}
|
||||||
|
out.extend_from_slice(&self.nonce);
|
||||||
|
out.extend_from_slice(&self.ciphertext);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize from bytes produced by `to_bytes`.
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
|
||||||
|
let mut offset = 0;
|
||||||
|
let read_u16 = |off: &mut usize| -> Result<u16, CryptoError> {
|
||||||
|
let v = u16::from_be_bytes(
|
||||||
|
bytes
|
||||||
|
.get(*off..*off + 2)
|
||||||
|
.ok_or(CryptoError::DecryptionFailed)?
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
*off += 2;
|
||||||
|
Ok(v)
|
||||||
|
};
|
||||||
|
|
||||||
|
let num = read_u16(&mut offset)? as usize;
|
||||||
|
let mut recipients = Vec::with_capacity(num);
|
||||||
|
for _ in 0..num {
|
||||||
|
let klen = read_u16(&mut offset)? as usize;
|
||||||
|
let kem_ct = bytes
|
||||||
|
.get(offset..offset + klen)
|
||||||
|
.ok_or(CryptoError::DecryptionFailed)?
|
||||||
|
.to_vec();
|
||||||
|
offset += klen;
|
||||||
|
|
||||||
|
let elen = read_u16(&mut offset)? as usize;
|
||||||
|
let enc_key = bytes
|
||||||
|
.get(offset..offset + elen)
|
||||||
|
.ok_or(CryptoError::DecryptionFailed)?
|
||||||
|
.to_vec();
|
||||||
|
offset += elen;
|
||||||
|
|
||||||
|
recipients.push(RecipientEntry {
|
||||||
|
kem_ciphertext: kem_ct,
|
||||||
|
encrypted_key: enc_key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let nonce: [u8; 24] = bytes
|
||||||
|
.get(offset..offset + 24)
|
||||||
|
.ok_or(CryptoError::DecryptionFailed)?
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| CryptoError::DecryptionFailed)?;
|
||||||
|
offset += 24;
|
||||||
|
|
||||||
|
let ciphertext = bytes
|
||||||
|
.get(offset..)
|
||||||
|
.ok_or(CryptoError::DecryptionFailed)?
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
recipients,
|
||||||
|
nonce,
|
||||||
|
ciphertext,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Encrypt `plaintext` for every recipient in `entities`.
|
||||||
|
*
|
||||||
|
* Internally generates a fresh content-encryption key, encrypts the payload
|
||||||
|
* with ChaCha20-Poly1305, then KEM-encapsulates and wraps the key for each
|
||||||
|
* recipient. The returned `MultiEncryptedMessage` can be decrypted by any
|
||||||
|
* entity whose keyring contains the corresponding private KEM key.
|
||||||
|
*
|
||||||
|
* Requires the `pqc` and `chacha20poly1305` features.
|
||||||
|
*/
|
||||||
|
#[cfg(all(feature = "mlkem-tls", feature = "chacha20poly1305", feature = "hkdf"))]
|
||||||
|
pub fn encrypt_multi(
|
||||||
|
plaintext: &[u8],
|
||||||
|
aad: &[u8],
|
||||||
|
entities: &[PublicKeyBundle],
|
||||||
|
) -> Result<MultiEncryptedMessage, CryptoError> {
|
||||||
|
let mut cek = [0u8; 32];
|
||||||
|
rand_core::OsRng.fill_bytes(&mut cek);
|
||||||
|
|
||||||
|
let cipher = ChaCha20Poly1305::new(cek);
|
||||||
|
let encrypted_payload = cipher.encrypt(plaintext, aad)?;
|
||||||
|
|
||||||
|
let nonce: [u8; 24] = encrypted_payload[..24]
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| CryptoError::EncryptionFailed)?;
|
||||||
|
let ciphertext = encrypted_payload[24..].to_vec();
|
||||||
|
|
||||||
|
let mut recipients = Vec::with_capacity(entities.len());
|
||||||
|
for entity in entities {
|
||||||
|
let enc = HybridKem::encapsulate(&entity.kem_public_key)?;
|
||||||
|
let wrap_key = derive_encryption_key(
|
||||||
|
&enc.shared_secret,
|
||||||
|
b"mtp-multi-key-wrap",
|
||||||
|
b"multi-recipient",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let wrap_cipher = ChaCha20Poly1305::new(wrap_key);
|
||||||
|
let encrypted_key = wrap_cipher.encrypt(&cek, b"")?;
|
||||||
|
|
||||||
|
recipients.push(RecipientEntry {
|
||||||
|
kem_ciphertext: enc.ciphertext,
|
||||||
|
encrypted_key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(MultiEncryptedMessage {
|
||||||
|
recipients,
|
||||||
|
nonce,
|
||||||
|
ciphertext,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decrypt a `MultiEncryptedMessage` using the recipient's `Keyring`.
|
||||||
|
*
|
||||||
|
* Tries each `RecipientEntry` until one succeeds with the given keyring's
|
||||||
|
* KEM secret key. Returns the original plaintext.
|
||||||
|
*/
|
||||||
|
#[cfg(all(feature = "mlkem-tls", feature = "chacha20poly1305", feature = "hkdf"))]
|
||||||
|
pub fn decrypt_multi(
|
||||||
|
msg: &MultiEncryptedMessage,
|
||||||
|
aad: &[u8],
|
||||||
|
keyring: &Keyring,
|
||||||
|
) -> Result<Vec<u8>, CryptoError> {
|
||||||
|
for entry in &msg.recipients {
|
||||||
|
let ss = match HybridKem::decapsulate(&keyring.kem_secret_key, &entry.kem_ciphertext) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
let wrap_key = derive_encryption_key(&ss, b"mtp-multi-key-wrap", b"multi-recipient")?;
|
||||||
|
let wrap_cipher = ChaCha20Poly1305::new(wrap_key);
|
||||||
|
let cek = match wrap_cipher.decrypt(&entry.encrypted_key, b"") {
|
||||||
|
Ok(k) => k,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
let cek_arr: [u8; 32] = cek.try_into().map_err(|_| CryptoError::DecryptionFailed)?;
|
||||||
|
|
||||||
|
let mut full_ct = Vec::with_capacity(24 + msg.ciphertext.len());
|
||||||
|
full_ct.extend_from_slice(&msg.nonce);
|
||||||
|
full_ct.extend_from_slice(&msg.ciphertext);
|
||||||
|
|
||||||
|
let data_cipher = ChaCha20Poly1305::new(cek_arr);
|
||||||
|
return data_cipher.decrypt(&full_ct, aad);
|
||||||
|
}
|
||||||
|
Err(CryptoError::DecryptionFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify an Ed25519 signature against a public key.
|
||||||
|
#[cfg(feature = "ed25519-dalek")]
|
||||||
|
pub fn verify_ed25519_sig(
|
||||||
|
public_key: &crate::keypair::SignaturePublicKey,
|
||||||
|
msg: &[u8],
|
||||||
|
signature: &[u8],
|
||||||
|
) -> Result<(), CryptoError> {
|
||||||
|
crate::sign::verify_ed25519(public_key, msg, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify an ML-DSA signature against a public key.
|
||||||
|
#[cfg(feature = "ml-dsa")]
|
||||||
|
pub fn verify_ml_dsa_sig(
|
||||||
|
public_key: &crate::keypair::SignaturePqPublicKey,
|
||||||
|
msg: &[u8],
|
||||||
|
signature: &[u8],
|
||||||
|
) -> Result<(), CryptoError> {
|
||||||
|
crate::sign::verify_ml_dsa(public_key, msg, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verify both an Ed25519 and ML-DSA signature (dual) against
|
||||||
|
* the public keys in a `PublicKeyBundle`.
|
||||||
|
*/
|
||||||
|
#[cfg(all(feature = "ed25519-dalek", feature = "ml-dsa"))]
|
||||||
|
pub fn verify_dual_sig(
|
||||||
|
public_keys: &crate::keypair::PublicKeyBundle,
|
||||||
|
msg: &[u8],
|
||||||
|
ed25519_sig: &[u8],
|
||||||
|
mldsa_sig: &[u8],
|
||||||
|
) -> Result<(), CryptoError> {
|
||||||
|
verify_ed25519_sig(&public_keys.sig_cl_public_key, msg, ed25519_sig)?;
|
||||||
|
verify_ml_dsa_sig(&public_keys.sig_pq_public_key, msg, mldsa_sig)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(transparent))]
|
||||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct EncryptionPrivateKey(Vec<u8>);
|
pub struct EncryptionPrivateKey(Vec<u8>);
|
||||||
|
|
||||||
|
|
@ -19,6 +21,8 @@ impl From<Vec<u8>> for EncryptionPrivateKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(transparent))]
|
||||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct SignaturePrivateKey(Vec<u8>);
|
pub struct SignaturePrivateKey(Vec<u8>);
|
||||||
|
|
||||||
|
|
@ -38,6 +42,8 @@ impl From<Vec<u8>> for SignaturePrivateKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(transparent))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EncryptionPublicKey(Vec<u8>);
|
pub struct EncryptionPublicKey(Vec<u8>);
|
||||||
|
|
||||||
|
|
@ -57,6 +63,8 @@ impl From<Vec<u8>> for EncryptionPublicKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(transparent))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SignaturePublicKey(Vec<u8>);
|
pub struct SignaturePublicKey(Vec<u8>);
|
||||||
|
|
||||||
|
|
@ -102,6 +110,8 @@ impl KeyGroup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(transparent))]
|
||||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct KemPrivateKey(Vec<u8>);
|
pub struct KemPrivateKey(Vec<u8>);
|
||||||
|
|
||||||
|
|
@ -121,6 +131,8 @@ impl From<Vec<u8>> for KemPrivateKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(transparent))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct KemPublicKey(Vec<u8>);
|
pub struct KemPublicKey(Vec<u8>);
|
||||||
|
|
||||||
|
|
@ -140,6 +152,8 @@ impl From<Vec<u8>> for KemPublicKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(transparent))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SignaturePqPublicKey(Vec<u8>);
|
pub struct SignaturePqPublicKey(Vec<u8>);
|
||||||
|
|
||||||
|
|
@ -159,6 +173,8 @@ impl From<Vec<u8>> for SignaturePqPublicKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(transparent))]
|
||||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct SignaturePqPrivateKey(Vec<u8>);
|
pub struct SignaturePqPrivateKey(Vec<u8>);
|
||||||
|
|
||||||
|
|
@ -178,6 +194,7 @@ impl From<Vec<u8>> for SignaturePqPrivateKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(ZeroizeOnDrop)]
|
#[derive(ZeroizeOnDrop)]
|
||||||
pub struct Keyring {
|
pub struct Keyring {
|
||||||
#[zeroize(skip)]
|
#[zeroize(skip)]
|
||||||
|
|
@ -209,4 +226,180 @@ impl Keyring {
|
||||||
sig_cl_secret_key,
|
sig_cl_secret_key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
sig_pq_public_key: sig_pq_pk,
|
||||||
|
sig_pq_secret_key: sig_pq_sk,
|
||||||
|
sig_cl_public_key: sig_cl_pk,
|
||||||
|
sig_cl_secret_key: sig_cl_sk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PublicKeyBundle {
|
||||||
|
pub kem_public_key: KemPublicKey,
|
||||||
|
pub sig_pq_public_key: SignaturePqPublicKey,
|
||||||
|
pub sig_cl_public_key: SignaturePublicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PublicKeyBundle {
|
||||||
|
pub fn new(
|
||||||
|
kem_public_key: KemPublicKey,
|
||||||
|
sig_pq_public_key: SignaturePqPublicKey,
|
||||||
|
sig_cl_public_key: SignaturePublicKey,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
kem_public_key,
|
||||||
|
sig_pq_public_key,
|
||||||
|
sig_cl_public_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bytes(&self) -> Vec<u8> {
|
||||||
|
let kem = self.kem_public_key.as_bytes();
|
||||||
|
let pq = self.sig_pq_public_key.as_bytes();
|
||||||
|
let cl = self.sig_cl_public_key.as_bytes();
|
||||||
|
|
||||||
|
let mut out = Vec::with_capacity(kem.len() + pq.len() + cl.len() + 6);
|
||||||
|
out.extend_from_slice(&(kem.len() as u16).to_be_bytes());
|
||||||
|
out.extend_from_slice(kem);
|
||||||
|
out.extend_from_slice(&(pq.len() as u16).to_be_bytes());
|
||||||
|
out.extend_from_slice(pq);
|
||||||
|
out.extend_from_slice(&(cl.len() as u16).to_be_bytes());
|
||||||
|
out.extend_from_slice(cl);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::error::CryptoError> {
|
||||||
|
use crate::error::CryptoError;
|
||||||
|
let mut offset = 0;
|
||||||
|
|
||||||
|
let kem_len = u16::from_be_bytes(
|
||||||
|
bytes
|
||||||
|
.get(offset..offset + 2)
|
||||||
|
.ok_or(CryptoError::InvalidKeyLength)?
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
) as usize;
|
||||||
|
offset += 2;
|
||||||
|
let kem = KemPublicKey::new(
|
||||||
|
bytes
|
||||||
|
.get(offset..offset + kem_len)
|
||||||
|
.ok_or(CryptoError::InvalidKeyLength)?
|
||||||
|
.to_vec(),
|
||||||
|
);
|
||||||
|
offset += kem_len;
|
||||||
|
|
||||||
|
let pq_len = u16::from_be_bytes(
|
||||||
|
bytes
|
||||||
|
.get(offset..offset + 2)
|
||||||
|
.ok_or(CryptoError::InvalidKeyLength)?
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
) as usize;
|
||||||
|
offset += 2;
|
||||||
|
let pq = SignaturePqPublicKey::new(
|
||||||
|
bytes
|
||||||
|
.get(offset..offset + pq_len)
|
||||||
|
.ok_or(CryptoError::InvalidKeyLength)?
|
||||||
|
.to_vec(),
|
||||||
|
);
|
||||||
|
offset += pq_len;
|
||||||
|
|
||||||
|
let cl_len = u16::from_be_bytes(
|
||||||
|
bytes
|
||||||
|
.get(offset..offset + 2)
|
||||||
|
.ok_or(CryptoError::InvalidKeyLength)?
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
) as usize;
|
||||||
|
offset += 2;
|
||||||
|
let cl = SignaturePublicKey::new(
|
||||||
|
bytes
|
||||||
|
.get(offset..offset + cl_len)
|
||||||
|
.ok_or(CryptoError::InvalidKeyLength)?
|
||||||
|
.to_vec(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
kem_public_key: kem,
|
||||||
|
sig_pq_public_key: pq,
|
||||||
|
sig_cl_public_key: cl,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
.unwrap(),
|
||||||
|
) 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)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,14 @@ pub mod sign;
|
||||||
#[cfg(feature = "mlkem-tls")]
|
#[cfg(feature = "mlkem-tls")]
|
||||||
pub mod kem;
|
pub mod kem;
|
||||||
|
|
||||||
|
pub mod helper;
|
||||||
|
|
||||||
pub use aead::{AeadCipher, AeadDecrypt, AeadEncrypt};
|
pub use aead::{AeadCipher, AeadDecrypt, AeadEncrypt};
|
||||||
pub use error::CryptoError;
|
pub use error::CryptoError;
|
||||||
pub use keypair::{
|
pub use keypair::{
|
||||||
EncryptionPrivateKey, EncryptionPublicKey, KemPrivateKey, KemPublicKey, KeyGroup, Keyring,
|
EncryptionPrivateKey, EncryptionPublicKey, KemPrivateKey, KemPublicKey, KeyGroup, Keyring,
|
||||||
SignaturePqPrivateKey, SignaturePqPublicKey, SignaturePrivateKey, SignaturePublicKey,
|
PublicKeyBundle, SignaturePqPrivateKey, SignaturePqPublicKey, SignaturePrivateKey,
|
||||||
|
SignaturePublicKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "chacha20poly1305")]
|
#[cfg(feature = "chacha20poly1305")]
|
||||||
|
|
@ -44,3 +47,15 @@ pub use kdf::{derive_encryption_key, hkdf_expand, hkdf_extract};
|
||||||
|
|
||||||
#[cfg(feature = "mlkem-tls")]
|
#[cfg(feature = "mlkem-tls")]
|
||||||
pub use kem::HybridKem;
|
pub use kem::HybridKem;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "mlkem-tls", feature = "chacha20poly1305", feature = "hkdf"))]
|
||||||
|
pub use helper::{decrypt_multi, encrypt_multi, MultiEncryptedMessage, RecipientEntry};
|
||||||
|
|
||||||
|
#[cfg(feature = "ed25519-dalek")]
|
||||||
|
pub use helper::verify_ed25519_sig;
|
||||||
|
|
||||||
|
#[cfg(feature = "ml-dsa")]
|
||||||
|
pub use helper::verify_ml_dsa_sig;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "ed25519-dalek", feature = "ml-dsa"))]
|
||||||
|
pub use helper::verify_dual_sig;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct CommTypeId(pub u8);
|
pub struct CommTypeId(pub u16);
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
|
@ -15,7 +15,7 @@ pub struct DataTypeId(pub u16);
|
||||||
* across all versions for version negotiation & security.
|
* across all versions for version negotiation & security.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub const INTERNAL_COMM_RESERVED: std::ops::Range<u8> = 0..16;
|
pub const INTERNAL_COMM_RESERVED: std::ops::Range<u16> = 0..16;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum InternalCommType {
|
pub enum InternalCommType {
|
||||||
|
|
@ -27,7 +27,7 @@ pub enum InternalCommType {
|
||||||
|
|
||||||
impl InternalCommType {
|
impl InternalCommType {
|
||||||
pub fn as_id(self) -> CommTypeId {
|
pub fn as_id(self) -> CommTypeId {
|
||||||
CommTypeId(self as u8)
|
CommTypeId(self as u16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,15 +37,15 @@ pub struct Version(pub u16, pub u16, pub u16);
|
||||||
|
|
||||||
pub struct Registry {
|
pub struct Registry {
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
comm_name_to_id: HashMap<String, u8>,
|
comm_name_to_id: HashMap<String, u16>,
|
||||||
comm_id_to_name: HashMap<u8, String>,
|
comm_id_to_name: HashMap<u16, String>,
|
||||||
data_name_to_id: HashMap<String, u16>,
|
data_name_to_id: HashMap<String, u16>,
|
||||||
data_id_to_name: HashMap<u16, String>,
|
data_id_to_name: HashMap<u16, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RegistryConfig {
|
pub struct RegistryConfig {
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
pub communication_types: HashMap<String, u8>,
|
pub communication_types: HashMap<String, u16>,
|
||||||
pub data_types: HashMap<String, u16>,
|
pub data_types: HashMap<String, u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct CommTypeId(pub u8);
|
pub struct CommTypeId(pub u16);
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct DataTypeId(pub u16);
|
pub struct DataTypeId(pub u16);
|
||||||
|
|
@ -14,12 +14,12 @@ pub struct Version(pub u16);
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TypeMap {
|
pub struct TypeMap {
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
pub comm_types: HashMap<String, u8>,
|
pub comm_types: HashMap<String, u16>,
|
||||||
pub data_types: HashMap<String, u16>,
|
pub data_types: HashMap<String, u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeMap {
|
impl TypeMap {
|
||||||
pub fn comm_id(&self, name: &str) -> Option<u8> {
|
pub fn comm_id(&self, name: &str) -> Option<u16> {
|
||||||
self.comm_types.get(name).copied()
|
self.comm_types.get(name).copied()
|
||||||
}
|
}
|
||||||
pub fn data_id(&self, name: &str) -> Option<u16> {
|
pub fn data_id(&self, name: &str) -> Option<u16> {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue