diff --git a/Cargo.toml b/Cargo.toml index 9122a570fa5..5d9d51b4900 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ salty = { version = "0.3.0", features = ["cose"] } p384 = { version = "0.13.0", optional = true, default-features = false, features = ["sha384", "ecdh", "ecdsa"] } p521 = { version = "0.13.3", optional = true, default-features = false, features = ["sha512", "ecdh", "ecdsa"] } ecdsa = { version = "0.16.9", optional = true, default-features = false } +pqcrypto-dilithium = { version = "0.5.0", optional = true } [dev-dependencies] # Testing @@ -89,6 +90,12 @@ verbose-tests = ["littlefs2/ll-assertions"] verbose-lfs = ["littlefs2/ll-assertions", "littlefs2/ll-trace"] virt = ["std"] +# If any PQC algorithm is set, it sets the general PQC feature +backend-dilithium2 = ["dep:pqcrypto-dilithium", "cosey/backend-dilithium2"] +backend-dilithium3 = ["dep:pqcrypto-dilithium", "cosey/backend-dilithium3"] +backend-dilithium5 = ["dep:pqcrypto-dilithium", "cosey/backend-dilithium5"] + + log-all = [] log-none = [] log-info = [] @@ -155,3 +162,6 @@ test-attestation-cert-ids = [] [package.metadata.docs.rs] features = ["serde-extensions", "virt"] rustdoc-args = ["--cfg", "docsrs"] + +[patch.crates-io] +cosey = { path = "../cosey" } diff --git a/src/config.rs b/src/config.rs index 0899ce7d33e..76f46d70b74 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,6 +7,9 @@ pub use trussed_core::config::{ SERDE_EXTENSION_REQUEST_LENGTH, }; +pub const MAX_SHORT_DATA_LENGTH: usize = 128; +pub const MAX_MEDIUM_DATA_LENGTH: usize = 256; + cfg_if::cfg_if! { if #[cfg(test)] { pub const MAX_SERVICE_CLIENTS: usize = 6; @@ -39,7 +42,47 @@ cfg_if::cfg_if! { } } -// must be MAX_KEY_MATERIAL_LENGTH + 4 +// For the PQC algorithms, public and private key are generated at the same time and stored together as +// the private key. Then in the derive call, it just pulls the public key from the private key store +// and re-saves it as a public-only key. Therefore, the max material length is both keys together, plus +// the PKCS8 DER encoding overhead (31 bytes). +cfg_if::cfg_if! { + if #[cfg(feature = "backend-dilithium5")] { + pub const MAX_SIGNATURE_LENGTH: usize = pqcrypto_dilithium::ffi::PQCLEAN_DILITHIUM5_CLEAN_CRYPTO_BYTES; + pub const MAX_KEY_MATERIAL_LENGTH: usize = pqcrypto_dilithium::ffi::PQCLEAN_DILITHIUM5_CLEAN_CRYPTO_PUBLICKEYBYTES + + pqcrypto_dilithium::ffi::PQCLEAN_DILITHIUM5_CLEAN_CRYPTO_SECRETKEYBYTES + + 31; + //pub const MAX_MESSAGE_LENGTH: usize = MAX_KEY_MATERIAL_LENGTH; + pub const MAX_FIDO_WRAPPED_KEY_LENGTH: usize = MAX_SERIALIZED_KEY_LENGTH + 57; + } else if #[cfg(feature = "backend-dilithium3")] { + pub const MAX_SIGNATURE_LENGTH: usize = pqcrypto_dilithium::ffi::PQCLEAN_DILITHIUM3_CLEAN_CRYPTO_BYTES; + pub const MAX_KEY_MATERIAL_LENGTH: usize = pqcrypto_dilithium::ffi::PQCLEAN_DILITHIUM3_CLEAN_CRYPTO_PUBLICKEYBYTES + + pqcrypto_dilithium::ffi::PQCLEAN_DILITHIUM3_CLEAN_CRYPTO_SECRETKEYBYTES + + 31; + //pub const MAX_MESSAGE_LENGTH: usize = MAX_KEY_MATERIAL_LENGTH; + pub const MAX_FIDO_WRAPPED_KEY_LENGTH: usize = MAX_SERIALIZED_KEY_LENGTH + 57; + } else if #[cfg(feature = "backend-dilithium2")] { + pub const MAX_SIGNATURE_LENGTH: usize = pqcrypto_dilithium::ffi::PQCLEAN_DILITHIUM2_CLEAN_CRYPTO_BYTES; + pub const MAX_KEY_MATERIAL_LENGTH: usize = pqcrypto_dilithium::ffi::PQCLEAN_DILITHIUM2_CLEAN_CRYPTO_PUBLICKEYBYTES + + pqcrypto_dilithium::ffi::PQCLEAN_DILITHIUM2_CLEAN_CRYPTO_SECRETKEYBYTES + + 31; + //pub const MAX_MESSAGE_LENGTH: usize = MAX_KEY_MATERIAL_LENGTH; + pub const MAX_FIDO_WRAPPED_KEY_LENGTH: usize = MAX_SERIALIZED_KEY_LENGTH + 57; + } else { + // Default from before addition of PQC + pub const MAX_SIGNATURE_LENGTH: usize = 512 * 2; + // FIXME: Value from https://stackoverflow.com/questions/5403808/private-key-length-bytes for Rsa2048 Private key + pub const MAX_KEY_MATERIAL_LENGTH: usize = 1160 * 2 + 72; + //pub const MAX_MESSAGE_LENGTH: usize = 1024; + pub const MAX_FIDO_WRAPPED_KEY_LENGTH: usize = 128; + } +} + +// Must be MAX_KEY_MATERIAL_LENGTH + 4 +// Note that this is not the serialized key material (e.g. serialized PKCS#8), but +// the internal Trussed serialization that adds flags and such pub const MAX_SERIALIZED_KEY_LENGTH: usize = MAX_KEY_MATERIAL_LENGTH + 4; +// 30 bytes are added by CBOR serialization of a FullCredential +pub const MAX_MESSAGE_LENGTH: usize = MAX_FIDO_WRAPPED_KEY_LENGTH + 30 + 2031 + 32 + 37; // TODO: update this to be different pub const USER_ATTRIBUTE_NUMBER: u8 = 37; diff --git a/src/key.rs b/src/key.rs index b071d324a9c..dc67a9805b6 100644 --- a/src/key.rs +++ b/src/key.rs @@ -6,12 +6,11 @@ use serde::{de::Visitor, ser::SerializeMap, Deserialize, Serialize}; use zeroize::Zeroize; pub use crate::Bytes; -use crate::{ - config::{MAX_KEY_MATERIAL_LENGTH, MAX_SERIALIZED_KEY_LENGTH}, - Error, -}; +use crate::{config::MAX_SERIALIZED_KEY_LENGTH, Error}; -pub type Material = Vec; +// Keys are often stored in serialized format (e.g. PKCS#8 used by the RSA backend), +// so material max length must be serialized max length. +pub type Material = Vec; pub type SerializedKeyBytes = Vec; // We don't implement serde to make sure nobody inadvertently still uses it @@ -76,6 +75,13 @@ pub enum Kind { BrainpoolP384R1, BrainpoolP512R1, X255, + // Post-quantum cryptography algorithms + #[cfg(feature = "backend-dilithium2")] + Dilithium2, + #[cfg(feature = "backend-dilithium3")] + Dilithium3, + #[cfg(feature = "backend-dilithium5")] + Dilithium5, Secp256k1, } @@ -223,6 +229,12 @@ impl Kind { Kind::BrainpoolP384R1 => 13, Kind::BrainpoolP512R1 => 14, Kind::Secp256k1 => 15, + #[cfg(feature = "backend-dilithium2")] + Kind::Dilithium2 => 16, + #[cfg(feature = "backend-dilithium3")] + Kind::Dilithium3 => 17, + #[cfg(feature = "backend-dilithium5")] + Kind::Dilithium5 => 18, } } @@ -243,6 +255,12 @@ impl Kind { 13 => Kind::BrainpoolP384R1, 14 => Kind::BrainpoolP512R1, 15 => Kind::Secp256k1, + #[cfg(feature = "backend-dilithium2")] + 16 => Kind::Dilithium2, + #[cfg(feature = "backend-dilithium3")] + 17 => Kind::Dilithium3, + #[cfg(feature = "backend-dilithium5")] + 18 => Kind::Dilithium5, _ => return Err(Error::InvalidSerializedKey), }) } diff --git a/src/mechanisms/chacha8poly1305.rs b/src/mechanisms/chacha8poly1305.rs index c19460decf2..10617931d5c 100644 --- a/src/mechanisms/chacha8poly1305.rs +++ b/src/mechanisms/chacha8poly1305.rs @@ -179,7 +179,9 @@ impl WrapKey for super::Chacha8Poly1305 { // TODO: need to check both secret and private keys let serialized_key = keystore.load_key(key::Secrecy::Secret, None, &request.key)?; - let message = Message::from_slice(&serialized_key.serialize()).unwrap(); + let serialized = serialized_key.serialize(); + let message = Message::from_slice(&serialized).unwrap(); + debug_now!("Serialized key length: {}", &serialized.len()); let encryption_request = request::Encrypt { mechanism: Mechanism::Chacha8Poly1305, @@ -193,6 +195,7 @@ impl WrapKey for super::Chacha8Poly1305 { let wrapped_key = crate::postcard_serialize_bytes(&encryption_reply).map_err(|_| Error::CborError)?; + debug_now!("Wrapped key length: {}", wrapped_key.len()); Ok(reply::WrapKey { wrapped_key }) } } diff --git a/src/service.rs b/src/service.rs index 291d9132c16..4fc7489501f 100644 --- a/src/service.rs +++ b/src/service.rs @@ -939,7 +939,7 @@ impl Service { .platform .user_interface() .set_status(ui::Status::Processing); - // #[cfg(test)] println!("service got request: {:?}", &request); + info_now!("service got request: {:?}", &request); // resources.currently_serving = ep.client_id.clone(); let reply_result = if ep.backends.is_empty() { diff --git a/src/store/keystore.rs b/src/store/keystore.rs index 91849b1ffdf..bdcec651f05 100644 --- a/src/store/keystore.rs +++ b/src/store/keystore.rs @@ -2,7 +2,7 @@ use littlefs2_core::{path, PathBuf}; use rand_chacha::ChaCha8Rng; use crate::{ - config::MAX_KEY_MATERIAL_LENGTH, + config::MAX_SERIALIZED_KEY_LENGTH, error::{Error, Result}, key, store::{self, Store}, @@ -181,7 +181,7 @@ impl Keystore for ClientKeystore { let location = self.location(secrecy, id).ok_or(Error::NoSuchKey)?; - let bytes: Bytes<{ MAX_KEY_MATERIAL_LENGTH }> = store::read(self.store, location, &path)?; + let bytes: Bytes<{ MAX_SERIALIZED_KEY_LENGTH }> = store::read(self.store, location, &path)?; let key = key::Key::try_deserialize(&bytes)?; diff --git a/src/types.rs b/src/types.rs index 24e8a5d55e4..56dbedc68d1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -126,3 +126,198 @@ impl From<&str> for CoreContext { // - Hardware feature // - Mechanism // - Profiles + +impl Serialize for Id { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_bytes(&self.0.to_be_bytes()) + } +} + +impl<'de> Deserialize<'de> for Id { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct ValueVisitor<'de>(PhantomData<&'de ()>); + + impl<'de> serde::de::Visitor<'de> for ValueVisitor<'de> { + type Value = Id; + + fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + formatter.write_str("16 bytes") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + if v.len() != 16 { + return Err(E::invalid_length(v.len(), &self)); + } + Ok(Id(u128::from_be_bytes(v.try_into().unwrap()))) + } + } + + deserializer.deserialize_bytes(ValueVisitor(PhantomData)) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub enum Location { + Volatile, + Internal, + External, +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[non_exhaustive] +pub struct StorageAttributes { + // each object must have a unique ID + // unique_id: UniqueId, + + // description of object + // label: String, + + // // cryptoki: token (vs session) object + // persistent: bool, + pub persistence: Location, + + /// Wether a the result of an [`agree`](crate::client::CryptoClient::agree) can be serialized + /// with [`serialize_key`](crate::client::CryptoClient::serialize_key) + pub serializable: bool, + // cryptoki: user must be logged in + // private: bool, + + // modifiable: bool, + // copyable: bool, + // destroyable: bool, +} + +impl StorageAttributes { + pub fn set_persistence(mut self, persistence: Location) -> Self { + self.persistence = persistence; + self + } + + pub fn set_serializable(mut self, serializable: bool) -> Self { + self.serializable = serializable; + self + } +} + +impl StorageAttributes { + // pub fn new(unique_id: UniqueId) -> Self { + pub fn new() -> Self { + Self { + // unique_id, + // label: String::new(), + // persistent: false, + persistence: Location::Volatile, + serializable: false, + // modifiable: true, + // copyable: true, + // destroyable: true, + } + } +} + +impl Default for StorageAttributes { + fn default() -> Self { + Self::new() + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[non_exhaustive] +pub enum Mechanism { + Aes256Cbc, + Chacha8Poly1305, + Ed255, + HmacBlake2s, + HmacSha1, + HmacSha256, + HmacSha512, + // P256XSha256, + P256, + P256Prehashed, + P384, + P384Prehashed, + P521, + P521Prehashed, + BrainpoolP256R1, + BrainpoolP256R1Prehashed, + BrainpoolP384R1, + BrainpoolP384R1Prehashed, + BrainpoolP512R1, + BrainpoolP512R1Prehashed, + // clients can also do hashing by themselves + Sha256, + Tdes, + Totp, + Trng, + X255, + /// Used to serialize the output of a diffie-hellman + SharedSecret, + + /// Exposes the Raw RSA encryption/decryption primitive. Be aware this is dangerous. + /// Not having any padding can allow an attacker to obtain plaintexts and forge signatures. + /// It should only be used if absolutely necessary. + Rsa2048Raw, + /// Exposes the Raw RSA encryption/decryption primitive. Be aware this is dangerous. + /// Not having any padding can allow an attacker to obtain plaintexts and forge signatures. + /// It should only be used if absolutely necessary. + Rsa3072Raw, + /// Exposes the Raw RSA encryption/decryption primitive. Be aware this is dangerous. + /// Not having any padding can allow an attacker to obtain plaintexts and forge signatures. + /// It should only be used if absolutely necessary. + Rsa4096Raw, + + Rsa2048Pkcs1v15, + Rsa3072Pkcs1v15, + Rsa4096Pkcs1v15, + + // Post-quantum cryptography algorithms + #[cfg(feature = "backend-dilithium2")] + Dilithium2, + #[cfg(feature = "backend-dilithium3")] + Dilithium3, + #[cfg(feature = "backend-dilithium5")] + Dilithium5, +} + +pub type MediumData = Bytes; +pub type ShortData = Bytes; + +pub type Message = Bytes; +pub type SerializedKey = Bytes; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub enum KeySerialization { + // Asn1Der, + Cose, + // Der, + EcdhEsHkdf256, + Raw, + Sec1, + /// Used by backends implementing RSA. + /// + /// Since RSA keys have multiple parts, and that the [`SerializeKey`](crate::api::Reply::SerializeKey) and + /// [`UnsafeInjectKey`](crate::api::Request::UnsafeInjectKey) have only transfer one byte array, the RSA key is serialized with postcard + RsaParts, + Pkcs8Der, +} + +pub type Signature = Bytes; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub enum SignatureSerialization { + Asn1Der, + // Cose, + Raw, + // Sec1, +} + +pub type UserAttribute = Bytes;