Add test implementations of the Crypto and Dice traits
Change-Id: Ifda7b5a4b04555b364ee36c51649a30f4f147341
Reviewed-on: https://pigweed-review.googlesource.com/c/open-dice/+/220232
Reviewed-by: Andrew Scull <ascull@google.com>
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
Commit-Queue: Darren Krahn <dkrahn@google.com>
Reviewed-by: Marshall Pierce <marshallpierce@google.com>
diff --git a/dpe-rs/src/commands_test.rs b/dpe-rs/src/commands_test.rs
index aabedd1..3a8e0e8 100644
--- a/dpe-rs/src/commands_test.rs
+++ b/dpe-rs/src/commands_test.rs
@@ -17,13 +17,11 @@
use crate::args::{ArgMap, ArgMapExt, ArgTypeMap, ArgTypeSelector};
use crate::commands::{handle_command_message, DeriveContextOptions, DpeCore};
-use crate::crypto::Hash;
use crate::crypto::{
HandshakeMessage, SealingPublicKey, Signature, SigningPublicKey,
};
-use crate::dice::{Certificate, DiceInput, InternalInputType};
use crate::dice::{
- DiceInputAuthority, DiceInputCode, DiceInputConfig, DiceInputMode,
+ test::get_fake_dice_input, Certificate, DiceInput, InternalInputType,
};
use crate::encode::{CommandSelector, ContextHandle, LocalityId, SessionId};
use crate::encode_test::{
@@ -838,17 +836,6 @@
}
}
-fn get_fake_dice_input<'a>() -> DiceInput<'a> {
- let hash = Hash::from_slice(&[0; 64]).unwrap();
- DiceInput {
- code: DiceInputCode::CodeHash(hash.clone()),
- config: DiceInputConfig::ConfigInlineValue(hash.clone()),
- authority: Some(DiceInputAuthority::AuthorityHash(hash.clone())),
- mode: DiceInputMode::NotInitialized,
- hidden: Some(hash.clone()),
- }
-}
-
#[test]
#[allow(unused_results)]
fn fake_commands() {
diff --git a/dpe-rs/src/crypto.rs b/dpe-rs/src/crypto.rs
index 4dc6e98..b127a6d 100644
--- a/dpe-rs/src/crypto.rs
+++ b/dpe-rs/src/crypto.rs
@@ -319,3 +319,223 @@
in_place_buffer: &mut Message,
) -> DpeResult<()>;
}
+
+#[cfg(test)]
+pub(crate) mod test {
+ use super::*;
+ use crate::cbor::{
+ cbor_decoder_from_message, cbor_encoder_from_message,
+ encode_bytes_prefix, DecoderExt,
+ };
+ use crate::error::ErrCode;
+ use crate::memory::SmallMessage;
+ use crate::noise::test::SessionCryptoForTesting;
+ use aes_gcm_siv::{AeadInPlace, Aes256GcmSiv, KeyInit};
+ use ed25519_dalek::Signer;
+ use hkdf::Hkdf;
+ use hmac::{Hmac, Mac};
+ use hpke::{
+ aead::AeadTag, aead::AesGcm256, kdf::HkdfSha512, kem::Kem,
+ kem::X25519HkdfSha256, Deserializable, OpModeR, OpModeS, Serializable,
+ };
+ use log::{debug, error};
+ use rand_chacha::ChaCha12Rng;
+ use rand_core::SeedableRng;
+ use sha2::{Digest, Sha512};
+
+ pub(crate) type HmacSha512 = Hmac<Sha512>;
+
+ impl From<aes_gcm_siv::Error> for ErrCode {
+ fn from(err: aes_gcm_siv::Error) -> Self {
+ error!("Decrypt error: {:?}", err);
+ ErrCode::InvalidArgument
+ }
+ }
+
+ impl From<hpke::HpkeError> for ErrCode {
+ fn from(err: hpke::HpkeError) -> Self {
+ error!("Hpke error: {:?}", err);
+ ErrCode::InvalidArgument
+ }
+ }
+
+ #[derive(Clone, Default, Debug, Eq, PartialEq, Hash)]
+ pub(crate) struct CryptoForTesting {
+ pub(crate) noise: SessionCryptoForTesting,
+ }
+
+ impl Crypto for CryptoForTesting {
+ type S = SessionCryptoForTesting;
+
+ fn hash(input: &[u8]) -> Hash {
+ Hash::from_slice(Sha512::digest(input).as_slice()).unwrap()
+ }
+
+ fn hash_iter<'a>(iter: impl Iterator<Item = &'a [u8]>) -> Hash {
+ let mut hasher = Sha512::new();
+ for input in iter {
+ hasher.update(input);
+ }
+ Hash::from_slice(hasher.finalize().as_slice()).unwrap()
+ }
+
+ fn kdf(
+ kdf_ikm: &[u8],
+ kdf_info: &[u8],
+ kdf_salt: &[u8],
+ derived_key: &mut [u8],
+ ) -> DpeResult<()> {
+ Hkdf::<Sha512>::new(Some(kdf_salt), kdf_ikm)
+ .expand(kdf_info, derived_key)
+ .unwrap();
+ Ok(())
+ }
+
+ fn signing_keypair_from_seed(
+ seed: &Hash,
+ ) -> DpeResult<(SigningPublicKey, SigningPrivateKey)> {
+ let mut key_bytes: ed25519_dalek::SecretKey = Default::default();
+ key_bytes.copy_from_slice(&seed.as_slice()[..32]);
+ let sk = ed25519_dalek::SigningKey::from_bytes(&key_bytes);
+ Ok((
+ SigningPublicKey::from_array(sk.verifying_key().as_bytes()),
+ SigningPrivateKey::from_array(sk.as_bytes()),
+ ))
+ }
+
+ fn sealing_keypair_from_seed(
+ seed: &Hash,
+ ) -> DpeResult<(SealingPublicKey, SealingPrivateKey)> {
+ let (private_key, public_key) =
+ <X25519HkdfSha256 as Kem>::derive_keypair(seed.as_slice());
+ Ok((
+ SealingPublicKey::from_slice(public_key.to_bytes().as_slice())?,
+ SealingPrivateKey::from_slice(
+ private_key.to_bytes().as_slice(),
+ )?,
+ ))
+ }
+
+ fn mac(key: &MacKey, data: &[u8]) -> DpeResult<Hash> {
+ let mut hmac =
+ <HmacSha512 as Mac>::new_from_slice(key.as_slice()).unwrap();
+ hmac.update(data);
+ Ok(Hash::from_slice(hmac.finalize().into_bytes().as_slice())
+ .unwrap())
+ }
+
+ fn sign(key: &SigningPrivateKey, tbs: &[u8]) -> DpeResult<Signature> {
+ let sk = ed25519_dalek::SigningKey::from_bytes(key.as_array());
+ let sig = sk.sign(tbs).to_bytes();
+ Ok(Signature::from_slice(&sig).unwrap())
+ }
+
+ fn seal(
+ key: &EncryptionKey,
+ in_place_buffer: &mut Message,
+ ) -> DpeResult<()> {
+ const TAG_LENGTH: usize = 16;
+ let cipher = Aes256GcmSiv::new(key.as_slice().into());
+ let nonce = Default::default();
+ // Make space to append the tag.
+ let required_buffer_size = in_place_buffer.len() + TAG_LENGTH;
+ in_place_buffer
+ .vec
+ .resize_default(required_buffer_size)
+ .map_err(|_| ErrCode::OutOfMemory)
+ .unwrap();
+ cipher
+ .encrypt_in_place(&nonce, &[], &mut in_place_buffer.vec)
+ .unwrap();
+ Ok(())
+ }
+
+ fn unseal(
+ key: &EncryptionKey,
+ in_place_buffer: &mut Message,
+ ) -> DpeResult<()> {
+ const TAG_LENGTH: usize = 16;
+ let cipher = Aes256GcmSiv::new(key.as_slice().into());
+ let nonce = Default::default();
+ cipher.decrypt_in_place(&nonce, &[], &mut in_place_buffer.vec)?;
+ let plaintext_len = in_place_buffer.len() - TAG_LENGTH;
+ in_place_buffer.vec.truncate(plaintext_len);
+ Ok(())
+ }
+
+ fn seal_asymmetric(
+ public_key: &SealingPublicKey,
+ in_place_buffer: &mut Message,
+ ) -> DpeResult<()> {
+ let mut rng = <ChaCha12Rng as SeedableRng>::from_seed([0xFF; 32]);
+ let kem_public_key =
+ <X25519HkdfSha256 as Kem>::PublicKey::from_bytes(
+ public_key.as_slice(),
+ )?;
+ let (encapped_key, aead_tag) =
+ hpke::single_shot_seal_in_place_detached::<
+ AesGcm256,
+ HkdfSha512,
+ X25519HkdfSha256,
+ _,
+ >(
+ &OpModeS::Base,
+ &kem_public_key,
+ &[],
+ in_place_buffer.vec.as_mut(),
+ &[],
+ &mut rng,
+ )?;
+ let mut prefix = SmallMessage::new();
+ let mut encoder = cbor_encoder_from_message(&mut prefix);
+ let _ = encoder.bytes(encapped_key.to_bytes().as_slice())?;
+ let _ = encoder.bytes(aead_tag.to_bytes().as_slice())?;
+ encode_bytes_prefix(&mut prefix, in_place_buffer.len())?;
+ debug!(
+ "seal_asymmetric: h={}, d={}",
+ prefix.len(),
+ in_place_buffer.len()
+ );
+ in_place_buffer.insert_prefix(prefix.as_slice())?;
+ Ok(())
+ }
+
+ fn unseal_asymmetric(
+ private_key: &SealingPrivateKey,
+ in_place_buffer: &mut Message,
+ ) -> DpeResult<()> {
+ let mut decoder = cbor_decoder_from_message(in_place_buffer);
+ let encapped_key =
+ <X25519HkdfSha256 as Kem>::EncappedKey::from_bytes(
+ decoder.bytes()?,
+ )?;
+ let tag = AeadTag::from_bytes(decoder.bytes()?)?;
+ // Leave only the ciphertext in in_place_buffer.
+ let sealed_data_position = decoder.decode_bytes_prefix()?;
+ debug!(
+ "unseal_asymmetric: h={}, d={}",
+ sealed_data_position,
+ in_place_buffer.len() - sealed_data_position
+ );
+ in_place_buffer.remove_prefix(sealed_data_position)?;
+ let kem_private_key =
+ <X25519HkdfSha256 as Kem>::PrivateKey::from_bytes(
+ private_key.as_slice(),
+ )?;
+ hpke::single_shot_open_in_place_detached::<
+ AesGcm256,
+ HkdfSha512,
+ X25519HkdfSha256,
+ >(
+ &OpModeR::Base,
+ &kem_private_key,
+ &encapped_key,
+ &[],
+ in_place_buffer.vec.as_mut(),
+ &[],
+ &tag,
+ )?;
+ Ok(())
+ }
+ }
+}
diff --git a/dpe-rs/src/dice.rs b/dpe-rs/src/dice.rs
index 2264d37..a113402 100644
--- a/dpe-rs/src/dice.rs
+++ b/dpe-rs/src/dice.rs
@@ -207,3 +207,272 @@
additional_input: &[u8],
) -> DpeResult<Certificate>;
}
+
+#[cfg(test)]
+pub(crate) mod test {
+ use super::*;
+ use crate::constants::SIGNING_PUBLIC_KEY_SIZE;
+ use crate::crypto::test::CryptoForTesting;
+ use crate::crypto::Crypto;
+ use crate::error::ErrCode;
+
+ #[derive(Default)]
+ pub(crate) struct DiceForTesting;
+ impl Dice for DiceForTesting {
+ fn dice(
+ &self,
+ cdi_sign: &Cdi,
+ cdi_seal: &Cdi,
+ // Note: this implementation is only useful for testing, it doesn't
+ // actually use the input values.
+ _inputs: &DiceInput,
+ _internal_inputs: &[InternalInputType],
+ is_export: bool,
+ ) -> DpeResult<(Cdi, Cdi)> {
+ let export_tag = if is_export { "export".as_bytes() } else { &[] };
+ let new_cdi_sign = Cdi::from_slice(
+ &CryptoForTesting::hash_iter(
+ [cdi_sign.as_slice(), export_tag].into_iter(),
+ )
+ .as_slice()[0..DICE_CDI_SIZE],
+ )?;
+ let new_cdi_seal = Cdi::from_slice(
+ &CryptoForTesting::hash_iter(
+ [cdi_seal.as_slice(), export_tag].into_iter(),
+ )
+ .as_slice()[0..DICE_CDI_SIZE],
+ )?;
+ Ok((new_cdi_sign, new_cdi_seal))
+ }
+
+ fn derive_eca_key_pair(
+ &self,
+ cdi_sign: &Cdi,
+ ) -> DpeResult<(SigningPublicKey, SigningPrivateKey)> {
+ let kdf_info = CryptoForTesting::hash(&[]);
+ let kdf_salt =
+ CryptoForTesting::hash("Key_Pair_25519_ECA".as_bytes());
+ let kdf_ikm = cdi_sign.as_slice();
+ let mut seed: Hash = Default::default();
+ CryptoForTesting::kdf(
+ kdf_ikm,
+ &kdf_info.as_slice(),
+ &kdf_salt.as_slice(),
+ seed.as_mut_slice(),
+ )?;
+ CryptoForTesting::signing_keypair_from_seed(&seed)
+ }
+
+ fn derive_signing_key_pair(
+ &self,
+ cdi_sign: &Cdi,
+ label: &[u8],
+ ) -> DpeResult<(SigningPublicKey, SigningPrivateKey)> {
+ let kdf_info = CryptoForTesting::hash(label);
+ let kdf_salt =
+ CryptoForTesting::hash("Key_Pair_25519_Sign".as_bytes());
+ let kdf_ikm = cdi_sign.as_slice();
+ let mut seed: Hash = Default::default();
+ CryptoForTesting::kdf(
+ kdf_ikm,
+ &kdf_info.as_slice(),
+ &kdf_salt.as_slice(),
+ seed.as_mut_slice(),
+ )?;
+ CryptoForTesting::signing_keypair_from_seed(&seed)
+ }
+
+ fn derive_sealing_key_pair(
+ &self,
+ cdi_seal: &Cdi,
+ label: &[u8],
+ unseal_policy: &[u8],
+ ) -> DpeResult<(SealingPublicKey, SealingPrivateKey)> {
+ let kdf_info =
+ CryptoForTesting::hash_iter([label, unseal_policy].into_iter());
+ let kdf_salt =
+ CryptoForTesting::hash("Key_Pair_25519_Seal".as_bytes());
+ let kdf_ikm = cdi_seal.as_slice();
+ let mut seed: Hash = Default::default();
+ CryptoForTesting::kdf(
+ kdf_ikm,
+ &kdf_info.as_slice(),
+ &kdf_salt.as_slice(),
+ seed.as_mut_slice(),
+ )?;
+ CryptoForTesting::sealing_keypair_from_seed(&seed)
+ }
+
+ fn derive_mac_key(
+ &self,
+ cdi_sign: &Cdi,
+ label: &[u8],
+ ) -> DpeResult<MacKey> {
+ let kdf_info = CryptoForTesting::hash(label);
+ let kdf_salt = CryptoForTesting::hash("Key_HMAC_Sign".as_bytes());
+ let kdf_ikm = cdi_sign.as_slice();
+ let mut key: MacKey = Default::default();
+ CryptoForTesting::kdf(
+ kdf_ikm,
+ &kdf_info.as_slice(),
+ &kdf_salt.as_slice(),
+ key.as_mut_slice(),
+ )?;
+ Ok(key)
+ }
+
+ fn derive_sealing_key(
+ &self,
+ cdi_seal: &Cdi,
+ label: &[u8],
+ unseal_policy: &[u8],
+ ) -> DpeResult<EncryptionKey> {
+ let kdf_info =
+ CryptoForTesting::hash_iter([label, unseal_policy].into_iter());
+ let kdf_salt = CryptoForTesting::hash("Key_AES_Seal".as_bytes());
+ let kdf_ikm = cdi_seal.as_slice();
+ let mut key: EncryptionKey = Default::default();
+ CryptoForTesting::kdf(
+ kdf_ikm,
+ &kdf_info.as_slice(),
+ &kdf_salt.as_slice(),
+ key.as_mut_slice(),
+ )?;
+ Ok(key)
+ }
+
+ fn create_certificate_info(
+ &self,
+ inputs: &DiceInput,
+ _internal_inputs: &[InternalInputType],
+ ) -> DpeResult<CertificateInfo> {
+ let mut info: CertificateInfo = Default::default();
+ let content = match &inputs.code {
+ DiceInputCode::CodeHash(h) => h.as_slice(),
+ DiceInputCode::CodeDescriptor(d) => d,
+ };
+ info.0
+ .extend_from_slice(content)
+ .map_err(|_| ErrCode::OutOfMemory)
+ .unwrap();
+ Ok(info)
+ }
+
+ fn create_eca_certificate(
+ &self,
+ issuer_key_pair: &(SigningPublicKey, SigningPrivateKey),
+ subject_public_key: &SigningPublicKey,
+ certificate_info: &CertificateInfo,
+ additional_certificate_info: &CertificateInfoList,
+ _is_export: bool,
+ ) -> DpeResult<Certificate> {
+ let mut new_eca_certificate: Certificate = Default::default();
+ new_eca_certificate
+ .0
+ .extend_from_slice(issuer_key_pair.0.as_slice())
+ .unwrap();
+ new_eca_certificate
+ .0
+ .extend_from_slice(subject_public_key.as_slice())
+ .unwrap();
+ new_eca_certificate
+ .0
+ .extend_from_slice(certificate_info.0.as_slice())
+ .unwrap();
+ for info in &additional_certificate_info.0 {
+ new_eca_certificate
+ .0
+ .extend_from_slice(info.0.as_slice())
+ .unwrap();
+ }
+ let signature = CryptoForTesting::sign(
+ &issuer_key_pair.1,
+ new_eca_certificate.0.as_slice(),
+ )?;
+ new_eca_certificate
+ .0
+ .extend_from_slice(signature.as_slice())
+ .unwrap();
+ Ok(new_eca_certificate)
+ }
+
+ fn create_leaf_certificate(
+ &self,
+ issuer_key_pair: &(SigningPublicKey, SigningPrivateKey),
+ subject_public_key: &SigningPublicKey,
+ certificate_info: &CertificateInfoList,
+ additional_input: &[u8],
+ ) -> DpeResult<Certificate> {
+ let mut new_leaf_certificate: Certificate = Default::default();
+ new_leaf_certificate
+ .0
+ .extend_from_slice(issuer_key_pair.0.as_slice())
+ .unwrap();
+ new_leaf_certificate
+ .0
+ .extend_from_slice(subject_public_key.as_slice())
+ .unwrap();
+ new_leaf_certificate.0.extend_from_slice(additional_input).unwrap();
+ for info in &certificate_info.0 {
+ new_leaf_certificate
+ .0
+ .extend_from_slice(info.0.as_slice())
+ .unwrap();
+ }
+ let signature = CryptoForTesting::sign(
+ &issuer_key_pair.1,
+ new_leaf_certificate.0.as_slice(),
+ )?;
+ new_leaf_certificate
+ .0
+ .extend_from_slice(signature.as_slice())
+ .unwrap();
+ Ok(new_leaf_certificate)
+ }
+ }
+
+ pub(crate) fn get_fake_dice_input() -> DiceInput<'static> {
+ let hash = Hash::from_slice(&[0; 64]).unwrap();
+ DiceInput {
+ code: DiceInputCode::CodeHash(hash.clone()),
+ config: DiceInputConfig::ConfigInlineValue(hash.clone()),
+ authority: Some(DiceInputAuthority::AuthorityHash(hash.clone())),
+ mode: DiceInputMode::NotInitialized,
+ hidden: Some(hash.clone()),
+ }
+ }
+
+ // Checks fake certs as constructed by DiceForTesting methods
+ pub(crate) fn check_cert(
+ cert: &Certificate,
+ expected_issuer: Option<&SigningPublicKey>,
+ expected_subject: &SigningPublicKey,
+ expected_cert_info: &CertificateInfoList,
+ expected_additional_info: &[u8],
+ ) -> () {
+ if let Some(issuer) = expected_issuer {
+ assert!(cert.0.starts_with(issuer.as_slice()));
+ let (tbs, sig) =
+ cert.0.split_at(cert.0.len() - SIGNING_PUBLIC_KEY_SIZE * 2);
+ let key = ed25519_dalek::VerifyingKey::try_from(issuer.as_slice())
+ .unwrap();
+ key.verify_strict(
+ tbs,
+ &ed25519_dalek::Signature::try_from(sig).unwrap(),
+ )
+ .unwrap();
+ }
+ assert!(cert.0[SIGNING_PUBLIC_KEY_SIZE..]
+ .starts_with(expected_subject.as_slice()));
+ assert!(cert.0[SIGNING_PUBLIC_KEY_SIZE * 2..]
+ .starts_with(expected_additional_info));
+ let mut cert_slice = &cert.0
+ [SIGNING_PUBLIC_KEY_SIZE * 2 + expected_additional_info.len()..];
+ for info in &expected_cert_info.0 {
+ assert!(cert_slice.starts_with(info.0.as_slice()));
+ cert_slice = &cert_slice[info.0.len()..];
+ }
+ // Only the signature is left
+ assert!(cert_slice.len() == SIGNING_PUBLIC_KEY_SIZE * 2);
+ }
+}
diff --git a/dpe-rs/src/noise.rs b/dpe-rs/src/noise.rs
index 7eaa89f..5f4f70c 100644
--- a/dpe-rs/src/noise.rs
+++ b/dpe-rs/src/noise.rs
@@ -522,21 +522,23 @@
}
#[cfg(test)]
-mod tests {
+pub(crate) mod test {
use super::*;
- struct DepsForTesting {}
+ pub(crate) struct DepsForTesting {}
impl NoiseCryptoDeps for DepsForTesting {
type Cipher = noise_rust_crypto::Aes256Gcm;
type DH = noise_rust_crypto::X25519;
type Hash = noise_rust_crypto::Sha512;
}
- type SessionCryptoForTesting = NoiseSessionCrypto<DepsForTesting>;
+ pub(crate) type SessionCryptoForTesting =
+ NoiseSessionCrypto<DepsForTesting>;
- type SessionClientForTesting = SessionClient<DepsForTesting>;
+ pub(crate) type SessionClientForTesting = SessionClient<DepsForTesting>;
- type CipherStateForTesting = NoiseCipherState<noise_rust_crypto::Aes256Gcm>;
+ pub(crate) type CipherStateForTesting =
+ NoiseCipherState<noise_rust_crypto::Aes256Gcm>;
#[test]
fn end_to_end_session() {