Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dilithium Assertions #44

Closed
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = []
Expand Down Expand Up @@ -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" }
45 changes: 44 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
28 changes: 23 additions & 5 deletions src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8, { MAX_KEY_MATERIAL_LENGTH }>;
// 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<u8, { MAX_SERIALIZED_KEY_LENGTH }>;
pub type SerializedKeyBytes = Vec<u8, { MAX_SERIALIZED_KEY_LENGTH }>;

// We don't implement serde to make sure nobody inadvertently still uses it
Expand Down Expand Up @@ -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,
}

Expand Down Expand Up @@ -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,
}
}

Expand All @@ -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),
})
}
Expand Down
5 changes: 4 additions & 1 deletion src/mechanisms/chacha8poly1305.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 })
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,7 @@ impl<P: Platform, D: Dispatch> Service<P, D> {
.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() {
Expand Down
4 changes: 2 additions & 2 deletions src/store/keystore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -181,7 +181,7 @@ impl<S: Store> Keystore for ClientKeystore<S> {

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)?;

Expand Down
195 changes: 195 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,198 @@ impl From<&str> for CoreContext {
// - Hardware feature
// - Mechanism
// - Profiles

impl Serialize for Id {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_bytes(&self.0.to_be_bytes())
}
}

impl<'de> Deserialize<'de> for Id {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, v: &[u8]) -> Result<Self::Value, E>
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<MAX_LABEL_LENGTH>,

// // 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<MAX_MEDIUM_DATA_LENGTH>;
pub type ShortData = Bytes<MAX_SHORT_DATA_LENGTH>;

pub type Message = Bytes<MAX_MESSAGE_LENGTH>;
pub type SerializedKey = Bytes<MAX_SERIALIZED_KEY_LENGTH>;

#[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<MAX_SIGNATURE_LENGTH>;

#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub enum SignatureSerialization {
Asn1Der,
// Cose,
Raw,
// Sec1,
}

pub type UserAttribute = Bytes<MAX_USER_ATTRIBUTE_LENGTH>;