| // Copyright 2024 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not | |
| // use this file except in compliance with the License. You may obtain a copy of | |
| // the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| // License for the specific language governing permissions and limitations under | |
| // the License. | |
| //! Defines the Crypto trait and related types. | |
| use crate::byte_array_wrapper; | |
| use crate::constants::*; | |
| use crate::error::DpeResult; | |
| use crate::memory::{Message, SizedMessage}; | |
| use zeroize::ZeroizeOnDrop; | |
| byte_array_wrapper!(MacKey, HASH_SIZE, "MAC key"); | |
| byte_array_wrapper!(EncryptionKey, ENCRYPTION_KEY_SIZE, "encryption key"); | |
| byte_array_wrapper!(DhPublicKey, DH_PUBLIC_KEY_SIZE, "DH public key"); | |
| byte_array_wrapper!(DhPrivateKey, DH_PRIVATE_KEY_SIZE, "DH private key"); | |
| byte_array_wrapper!(Hash, HASH_SIZE, "hash"); | |
| byte_array_wrapper!( | |
| SigningPublicKey, | |
| SIGNING_PUBLIC_KEY_SIZE, | |
| "signing public key" | |
| ); | |
| byte_array_wrapper!( | |
| SigningPrivateKey, | |
| SIGNING_PRIVATE_KEY_SIZE, | |
| "signing private key" | |
| ); | |
| byte_array_wrapper!( | |
| SealingPublicKey, | |
| SEALING_PUBLIC_KEY_SIZE, | |
| "sealing public key" | |
| ); | |
| byte_array_wrapper!( | |
| SealingPrivateKey, | |
| SEALING_PRIVATE_KEY_SIZE, | |
| "sealing private key" | |
| ); | |
| /// A session handshake message. | |
| pub(crate) type HandshakeMessage = SizedMessage<MAX_HANDSHAKE_MESSAGE_SIZE>; | |
| /// A session handshake payload. | |
| pub(crate) type HandshakePayload = SizedMessage<MAX_HANDSHAKE_PAYLOAD_SIZE>; | |
| /// A signature. | |
| pub(crate) type Signature = SizedMessage<MAX_SIGNATURE_SIZE>; | |
| /// A trait for committing previously staged changes. | |
| pub(crate) trait Commit { | |
| /// Commits a previously staged changes. When used with session cipher | |
| /// state, the staged changes are typically counter increments that result | |
| /// from encrypt or decrypt operations. | |
| fn commit(&mut self); | |
| } | |
| /// A trait for maintaining a counter. | |
| pub(crate) trait Counter { | |
| /// Returns the current counter value. | |
| fn n(&self) -> u64; | |
| /// Sets the counter value to `n`. | |
| fn set_n(&mut self, n: u64); | |
| } | |
| /// Provides cryptographic operations for encrypted sessions. | |
| pub(crate) trait SessionCrypto { | |
| /// A type to represent session cipher states. These are owned by and opaque | |
| /// to the caller in `new_session_handshake` and `derive_session_handshake`. | |
| type SessionCipherState: Commit + Counter; | |
| /// Performs a session responder handshake for a new session. | |
| /// | |
| /// # Parameters | |
| /// | |
| /// * `static_dh_key`: The DPE session identity, which the client is | |
| /// expected to already know. | |
| /// * `initiator_handshake`: The handshake message received from the client. | |
| /// * `payload`: The payload to include in the `responder_handshake`. | |
| /// * `responder_handshake`: Receives the handshake message to be sent back | |
| /// to the client. | |
| /// * `decrypt_cipher_state`: Receives cipher state for decrypting incoming | |
| /// session messages. This is intended to be passed to | |
| /// [`SessionCrypto::session_decrypt`]. | |
| /// * `encrypt_cipher_state`: Receives cipher state for encrypting outgoing | |
| /// session messages. This is intended to be passed to | |
| /// [`SessionCrypto::session_encrypt`]. | |
| /// * `psk_seed`: Receives a PSK seed that can be used to construct a PSK to | |
| /// be used when deriving a session (see | |
| /// [`SessionCrypto::derive_session_handshake`]). | |
| /// | |
| /// # Errors | |
| /// | |
| /// This method allows implementers to return an error but it is expected to | |
| /// be infallible. | |
| #[allow(clippy::too_many_arguments)] | |
| fn new_session_handshake( | |
| static_dh_key: &DhPrivateKey, | |
| initiator_handshake: &HandshakeMessage, | |
| payload: &HandshakePayload, | |
| responder_handshake: &mut HandshakeMessage, | |
| decrypt_cipher_state: &mut Self::SessionCipherState, | |
| encrypt_cipher_state: &mut Self::SessionCipherState, | |
| psk_seed: &mut Hash, | |
| ) -> DpeResult<()>; | |
| /// Performs a session responder handshake for a derived session. In | |
| /// contrast to a new session handshake, a derived session does not use a | |
| /// static key, but a pre-shared key (PSK) derived from an existing session. | |
| /// | |
| /// # Parameters | |
| /// | |
| /// * `psk`: A PSK derived from an existing session. | |
| /// * `initiator_handshake`: The handshake message received from the client. | |
| /// * `payload`: The payload to include in the `responder_handshake`. | |
| /// * `responder_handshake`: Receives the handshake message to be sent back | |
| /// to the client. | |
| /// * `decrypt_cipher_state`: Receives cipher state for decrypting incoming | |
| /// session messages. This is intended to be passed to | |
| /// [`SessionCrypto::session_decrypt`]. | |
| /// * `encrypt_cipher_state`: Receives cipher state for encrypting outgoing | |
| /// session messages. This is intended to be passed to | |
| /// [`SessionCrypto::session_encrypt`]. | |
| /// * `psk_seed`: Receives a PSK seed that can be used to construct a PSK to | |
| /// be used when deriving another session. | |
| /// | |
| /// # Errors | |
| /// | |
| /// This method allows implementers to return an error but it is expected to | |
| /// be infallible. | |
| #[allow(clippy::too_many_arguments)] | |
| fn derive_session_handshake( | |
| psk: &Hash, | |
| initiator_handshake: &HandshakeMessage, | |
| payload: &HandshakePayload, | |
| responder_handshake: &mut HandshakeMessage, | |
| decrypt_cipher_state: &mut Self::SessionCipherState, | |
| encrypt_cipher_state: &mut Self::SessionCipherState, | |
| psk_seed: &mut Hash, | |
| ) -> DpeResult<()>; | |
| /// Derives a PSK from session state: `psk_seed`, `decrypt_cipher_state`, | |
| /// and `encrypt_cipher_state`. The returned PSK is appropriate as an | |
| /// argument to [`derive_session_handshake`]. | |
| /// | |
| /// # Errors | |
| /// | |
| /// This method allows implementers to return an error but it is expected to | |
| /// be infallible. | |
| /// | |
| /// [`derive_session_handshake`]: #method.derive_session_handshake | |
| fn derive_psk_from_session( | |
| psk_seed: &Hash, | |
| decrypt_cipher_state: &Self::SessionCipherState, | |
| encrypt_cipher_state: &Self::SessionCipherState, | |
| ) -> DpeResult<Hash>; | |
| /// Encrypts an outgoing session message with the given `cipher_state`. The | |
| /// `in_place_buffer` both provides the plaintext message and receives the | |
| /// corresponding ciphertext. | |
| /// | |
| /// # Errors | |
| /// | |
| /// This method fails with an OutOfMemory error if the encryption overhead | |
| /// does not fit in the buffer. | |
| fn session_encrypt( | |
| cipher_state: &mut Self::SessionCipherState, | |
| in_place_buffer: &mut Message, | |
| ) -> DpeResult<()>; | |
| /// Decrypts an incoming session message with the given `cipher_state`. The | |
| /// `in_place_buffer` both provides the ciphertext message and receives the | |
| /// corresponding plaintext. | |
| /// | |
| /// # Errors | |
| /// | |
| /// This method fails with an InvalidArgument error if the ciphertext cannot | |
| /// be decrypted (e.g. if tag authentication fails). | |
| fn session_decrypt( | |
| cipher_state: &mut Self::SessionCipherState, | |
| in_place_buffer: &mut Message, | |
| ) -> DpeResult<()>; | |
| } | |
| /// Provides cryptographic operations. These operations are specifically for DPE | |
| /// concepts, defined by a DPE profile, and to be invoked by a DPE instance. | |
| pub(crate) trait Crypto { | |
| /// An associated [`SessionCrypto`] type. | |
| type S: SessionCrypto; | |
| /// Returns a hash of `input`. | |
| /// | |
| /// # Errors | |
| /// | |
| /// This method is infallible. | |
| fn hash(input: &[u8]) -> Hash; | |
| /// Returns a hash over all items in `iter`, in order. | |
| /// | |
| /// # Errors | |
| /// | |
| /// This method is infallible. | |
| fn hash_iter<'a>(iter: impl Iterator<Item = &'a [u8]>) -> Hash; | |
| /// Runs a key derivation function (KDF) to derive a key the length of the | |
| /// `derived_key` buffer. The inputs are interpreted as documented by the | |
| /// [HKDF](<https://datatracker.ietf.org/doc/html/rfc5869>) scheme. The | |
| /// implementation doesn't need to be HKDF specifically but needs to work | |
| /// with HKDF-style inputs. | |
| /// | |
| /// # Parameters | |
| /// | |
| /// * `kdf_ikm`: input keying material | |
| /// * `kdf_info`: HKDF-style info (optional) | |
| /// * `kdf_salt`: HKDF-style salt (optional) | |
| /// * `derived_key`: Receives the derived key | |
| /// | |
| /// # Errors | |
| /// | |
| /// Fails with an `InternalError` if `derived_key` is too large. | |
| fn kdf( | |
| kdf_ikm: &[u8], | |
| kdf_info: &[u8], | |
| kdf_salt: &[u8], | |
| derived_key: &mut [u8], | |
| ) -> DpeResult<()>; | |
| /// Derives an asymmetric key pair for signing from a given `seed`. | |
| /// | |
| /// # Errors | |
| /// | |
| /// This method allows implementers to return an error but it is expected to | |
| /// be infallible. | |
| fn signing_keypair_from_seed( | |
| seed: &Hash, | |
| ) -> DpeResult<(SigningPublicKey, SigningPrivateKey)>; | |
| /// Derives an asymmetric key pair for sealing from a given `seed`. | |
| /// | |
| /// # Errors | |
| /// | |
| /// This method allows implementers to return an error but it is expected to | |
| /// be infallible. | |
| fn sealing_keypair_from_seed( | |
| seed: &Hash, | |
| ) -> DpeResult<(SealingPublicKey, SealingPrivateKey)>; | |
| /// Computes a MAC over `data` using the given `key`. | |
| /// | |
| /// # Errors | |
| /// | |
| /// This method allows implementers to return an error but it is expected to | |
| /// be infallible. | |
| fn mac(key: &MacKey, data: &[u8]) -> DpeResult<Hash>; | |
| /// Generates a signature over `tbs` using the given `key`. | |
| /// | |
| /// # Errors | |
| /// | |
| /// This method allows implementers to return an error but it is expected to | |
| /// be infallible. | |
| fn sign(key: &SigningPrivateKey, tbs: &[u8]) -> DpeResult<Signature>; | |
| /// Encrypts data using the given `key` in a way that it can be decrypted by | |
| /// the `unseal` method with the same `key`. The `in_place_buffer` both | |
| /// provides the plaintext input and receives the ciphertext output. | |
| /// | |
| /// # Errors | |
| /// | |
| /// Fails with OutOfMemory if the ciphertext, including overhead, does not | |
| /// fit in the buffer. | |
| fn seal( | |
| key: &EncryptionKey, | |
| in_place_buffer: &mut Message, | |
| ) -> DpeResult<()>; | |
| /// Decrypts and authenticates data previously generated by the `seal` | |
| /// method using the given 'key'. The `in_place_buffer` both provides the | |
| /// ciphertext input and receives the plaintext output. | |
| /// | |
| /// # Errors | |
| /// | |
| /// Fails with InvalidArgument if authenticated decryption fails. | |
| fn unseal( | |
| key: &EncryptionKey, | |
| in_place_buffer: &mut Message, | |
| ) -> DpeResult<()>; | |
| /// Encrypts data using an asymmetric scheme and the given `public_key` in | |
| /// a way that it can be decrypted by the `unseal_asymmetric` method given | |
| /// the corresponding private key. While this method is useful for testing, | |
| /// a DPE does not use this during normal operation. The `in_place_buffer` | |
| /// both provides the plaintext input and receives the ciphertext output. | |
| /// | |
| /// # Errors | |
| /// | |
| /// Fails with OutOfMemory if the ciphertext, including overhead, does not | |
| /// fit in the buffer. | |
| fn seal_asymmetric( | |
| public_key: &SealingPublicKey, | |
| in_place_buffer: &mut Message, | |
| ) -> DpeResult<()>; | |
| /// Decrypts data using an asymmetric scheme and the give `key`. The | |
| /// `in_place_buffer` both provides the ciphertext input and receives the | |
| /// plaintext output. | |
| /// | |
| /// # Errors | |
| /// | |
| /// Fails with InvalidArgument if the ciphertext cannot be decrypted. | |
| fn unseal_asymmetric( | |
| key: &SealingPrivateKey, | |
| 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(()) | |
| } | |
| } | |
| } |