From 5576cfb07640567b21ff5dc2e86a2cd8039843af Mon Sep 17 00:00:00 2001 From: Sam Stelfox Date: Sun, 11 Feb 2024 10:36:38 -0500 Subject: [PATCH] feat: allow symmetric encryption of access keys --- src/codec/content_payload/mod.rs | 6 +- src/codec/crypto/access_key.rs | 37 +++++++-- ...ccess_key.rs => asym_locked_access_key.rs} | 78 +++++++++---------- src/codec/crypto/mod.rs | 8 +- src/codec/crypto/sym_locked_access_key.rs | 40 ++++++++++ 5 files changed, 116 insertions(+), 53 deletions(-) rename src/codec/crypto/{locked_access_key.rs => asym_locked_access_key.rs} (62%) create mode 100644 src/codec/crypto/sym_locked_access_key.rs diff --git a/src/codec/content_payload/mod.rs b/src/codec/content_payload/mod.rs index 1147625..bb8ba6e 100644 --- a/src/codec/content_payload/mod.rs +++ b/src/codec/content_payload/mod.rs @@ -14,7 +14,7 @@ use nom::error::{Error as NomError, ErrorKind}; use nom::number::streaming::le_u8; use nom::{Err, IResult}; -use crate::codec::crypto::{AccessKey, LockedAccessKey, SigningKey}; +use crate::codec::crypto::{AccessKey, AsymLockedAccessKey, SigningKey}; pub enum ContentPayload { Private { access_key: AccessKey }, @@ -24,7 +24,7 @@ pub enum ContentPayload { impl ContentPayload { pub fn parse_private<'a>(input: &'a [u8], key: &SigningKey) -> IResult<&'a [u8], Self> { let (input, key_count) = le_u8(input)?; - let (input, locked_keys) = LockedAccessKey::parse_many(input, key_count)?; + let (input, locked_keys) = AsymLockedAccessKey::parse_many(input, key_count)?; let key_id = key.key_id(); let relevant_keys = locked_keys.into_iter().filter(|k| k.key_id == key_id); @@ -42,7 +42,7 @@ impl ContentPayload { None => return Err(Err::Failure(NomError::new(input, ErrorKind::Verify))), }; - // todo(sstelfox): implement the reset + // todo(sstelfox): implement the rest Ok((input, ContentPayload::Private { access_key })) } diff --git a/src/codec/crypto/access_key.rs b/src/codec/crypto/access_key.rs index 2be64a3..5a74e56 100644 --- a/src/codec/crypto/access_key.rs +++ b/src/codec/crypto/access_key.rs @@ -4,11 +4,13 @@ use nom::AsBytes; use rand::Rng; use crate::codec::crypto::{ - AuthenticationTag, LockedAccessKey, Nonce, VerifyingKey, SYMMETRIC_KEY_LENGTH, + AsymLockedAccessKey, AuthenticationTag, Nonce, SymLockedAccessKey, VerifyingKey, }; +const ACCESS_KEY_LENGTH: usize = 32; + #[derive(Clone)] -pub struct AccessKey([u8; SYMMETRIC_KEY_LENGTH]); +pub struct AccessKey([u8; ACCESS_KEY_LENGTH]); impl AccessKey { #[allow(dead_code)] @@ -24,10 +26,10 @@ impl AccessKey { &self, rng: &mut impl CryptoRngCore, verifying_key: &VerifyingKey, - ) -> Result> { + ) -> Result> { let (dh_exchange_key, shared_secret) = verifying_key.ephemeral_dh_exchange(rng); - let mut key_payload = [0u8; SYMMETRIC_KEY_LENGTH]; + let mut key_payload = [0u8; ACCESS_KEY_LENGTH]; key_payload.copy_from_slice(&self.0); let chacha_key = ChaChaKey::from_slice(&shared_secret); @@ -42,7 +44,7 @@ impl AccessKey { let key_id = verifying_key.key_id(); - Ok(LockedAccessKey { + Ok(AsymLockedAccessKey { dh_exchange_key, nonce, cipher_text: key_payload, @@ -50,10 +52,31 @@ impl AccessKey { key_id, }) } + + pub fn lock_with( + &self, + rng: &mut impl CryptoRngCore, + encrypion_key: &AccessKey, + ) -> Result> { + let mut key_payload = self.0; + + let cipher = XChaCha20Poly1305::new(encrypion_key.chacha_key()); + let nonce = Nonce::generate(rng); + let raw_tag = cipher.encrypt_in_place_detached(&nonce, &[], &mut key_payload)?; + + let mut tag_bytes = [0u8; AuthenticationTag::size()]; + tag_bytes.copy_from_slice(raw_tag.as_bytes()); + let _tag = AuthenticationTag::from(tag_bytes); + unimplemented!() + } + + pub const fn size() -> usize { + ACCESS_KEY_LENGTH + } } -impl From<[u8; SYMMETRIC_KEY_LENGTH]> for AccessKey { - fn from(key: [u8; SYMMETRIC_KEY_LENGTH]) -> Self { +impl From<[u8; ACCESS_KEY_LENGTH]> for AccessKey { + fn from(key: [u8; ACCESS_KEY_LENGTH]) -> Self { Self(key) } } diff --git a/src/codec/crypto/locked_access_key.rs b/src/codec/crypto/asym_locked_access_key.rs similarity index 62% rename from src/codec/crypto/locked_access_key.rs rename to src/codec/crypto/asym_locked_access_key.rs index b8aa3a6..9d0b433 100644 --- a/src/codec/crypto/locked_access_key.rs +++ b/src/codec/crypto/asym_locked_access_key.rs @@ -4,56 +4,35 @@ use nom::multi::count; use nom::sequence::tuple; use nom::{IResult, Needed}; -use crate::codec::crypto::{ - AccessKey, AuthenticationTag, KeyId, Nonce, SigningKey, VerifyingKey, SYMMETRIC_KEY_LENGTH, -}; +use crate::codec::crypto::{AccessKey, AuthenticationTag, KeyId, Nonce, SigningKey, VerifyingKey}; const ACCESS_KEY_RECORD_LENGTH: usize = KeyId::size() + VerifyingKey::size() + Nonce::size() - + SYMMETRIC_KEY_LENGTH + + AccessKey::size() + AuthenticationTag::size(); -pub struct LockedAccessKey { +pub struct AsymLockedAccessKey { pub(crate) key_id: KeyId, pub(crate) dh_exchange_key: VerifyingKey, pub(crate) nonce: Nonce, - pub(crate) cipher_text: [u8; SYMMETRIC_KEY_LENGTH], + pub(crate) cipher_text: [u8; AccessKey::size()], pub(crate) tag: AuthenticationTag, } -impl LockedAccessKey { - pub(crate) fn unlock( - &self, - key: &SigningKey, - ) -> Result> { - if self.key_id != key.verifying_key().key_id() { - return Err(LockedAccessKeyError::IncorrectKey); - } - - let shared_secret = key.dh_exchange(&self.dh_exchange_key); - let mut key_payload = self.cipher_text.to_vec(); - - XChaCha20Poly1305::new(ChaChaKey::from_slice(&shared_secret)) - .decrypt_in_place_detached(&self.nonce, &[], &mut key_payload, &self.tag) - .map_err(|_| LockedAccessKeyError::CryptoFailure)?; - - let mut key = [0u8; SYMMETRIC_KEY_LENGTH]; - key.copy_from_slice(&key_payload); - - Ok(AccessKey::from(key)) - } - - pub(crate) fn parse(input: &[u8]) -> IResult<&[u8], Self> { +impl AsymLockedAccessKey { + pub fn parse(input: &[u8]) -> IResult<&[u8], Self> { let (input, (key_id, dh_exchange_key, nonce, raw_cipher_text, tag)) = tuple(( KeyId::parse, VerifyingKey::parse, Nonce::parse, - take(SYMMETRIC_KEY_LENGTH), + // This is NOT being parsed into the target data type yet as its still encrypted. We'll + // construct it when the contents are valid. + take(AccessKey::size()), AuthenticationTag::parse, ))(input)?; - let mut cipher_text = [0u8; SYMMETRIC_KEY_LENGTH]; + let mut cipher_text = [0u8; AccessKey::size()]; cipher_text.copy_from_slice(raw_cipher_text); let access_key = Self { @@ -67,7 +46,7 @@ impl LockedAccessKey { Ok((input, access_key)) } - pub(crate) fn parse_many(input: &[u8], key_count: u8) -> IResult<&[u8], Vec> { + pub fn parse_many(input: &[u8], key_count: u8) -> IResult<&[u8], Vec> { let (input, keys) = match count(Self::parse, key_count as usize)(input) { Ok(res) => res, Err(nom::Err::Incomplete(Needed::Size(_))) => { @@ -81,22 +60,43 @@ impl LockedAccessKey { Ok((input, keys)) } + + pub fn unlock(&self, key: &SigningKey) -> Result> { + if self.key_id != key.verifying_key().key_id() { + return Err(AsymLockedAccessKeyError::IncorrectKey); + } + + let shared_secret = key.dh_exchange(&self.dh_exchange_key); + let mut key_payload = self.cipher_text.to_vec(); + + XChaCha20Poly1305::new(ChaChaKey::from_slice(&shared_secret)).decrypt_in_place_detached( + &self.nonce, + &[], + &mut key_payload, + &self.tag, + )?; + + let mut key = [0u8; AccessKey::size()]; + key.copy_from_slice(&key_payload); + + Ok(AccessKey::from(key)) + } } #[derive(Debug, thiserror::Error)] -pub(crate) enum LockedAccessKeyError { +pub enum AsymLockedAccessKeyError { + #[error("crypto error: {0}")] + CryptoFailure(String), + #[error("decoding data failed: {0}")] FormatFailure(#[from] nom::Err>), - #[error("unspecified crypto error")] - CryptoFailure, - #[error("validation failed most likely due to the use of an incorrect key")] IncorrectKey, } -impl From for LockedAccessKeyError { - fn from(_: chacha20poly1305::Error) -> Self { - LockedAccessKeyError::CryptoFailure +impl From for AsymLockedAccessKeyError { + fn from(err: chacha20poly1305::Error) -> Self { + AsymLockedAccessKeyError::CryptoFailure(err.to_string()) } } diff --git a/src/codec/crypto/mod.rs b/src/codec/crypto/mod.rs index 14a705d..86e8802 100644 --- a/src/codec/crypto/mod.rs +++ b/src/codec/crypto/mod.rs @@ -1,23 +1,23 @@ mod access_key; +mod asym_locked_access_key; mod authentication_tag; mod encrypted_stream; mod fingerprint; mod key_id; -mod locked_access_key; mod nonce; mod signature; mod signing_key; +mod sym_locked_access_key; mod verifying_key; pub use access_key::AccessKey; +pub use asym_locked_access_key::AsymLockedAccessKey; pub use authentication_tag::AuthenticationTag; pub use encrypted_stream::EncryptingWriter; pub use fingerprint::Fingerprint; pub use key_id::KeyId; -pub use locked_access_key::LockedAccessKey; pub use nonce::Nonce; pub use signature::Signature; pub use signing_key::SigningKey; +pub use sym_locked_access_key::SymLockedAccessKey; pub use verifying_key::VerifyingKey; - -pub(crate) const SYMMETRIC_KEY_LENGTH: usize = 32; diff --git a/src/codec/crypto/sym_locked_access_key.rs b/src/codec/crypto/sym_locked_access_key.rs new file mode 100644 index 0000000..8d1dc6c --- /dev/null +++ b/src/codec/crypto/sym_locked_access_key.rs @@ -0,0 +1,40 @@ +use crate::codec::crypto::{AccessKey, AuthenticationTag, Nonce}; +use chacha20poly1305::{AeadInPlace, KeyInit, XChaCha20Poly1305}; + +pub struct SymLockedAccessKey { + pub(crate) nonce: Nonce, + pub(crate) cipher_text: [u8; AccessKey::size()], + pub(crate) tag: AuthenticationTag, +} + +impl SymLockedAccessKey { + pub fn unlock( + &self, + decryption_key: &AccessKey, + ) -> Result> { + let mut key_payload = self.cipher_text; + + let cipher = XChaCha20Poly1305::new(decryption_key.chacha_key()); + cipher.decrypt_in_place_detached(&self.nonce, &[], &mut key_payload, &self.tag)?; + + Ok(AccessKey::from(key_payload)) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum SymLockedAccessKeyError { + #[error("crypto error: {0}")] + CryptoFailure(String), + + #[error("decoding data failed: {0}")] + FormatFailure(#[from] nom::Err>), + + #[error("validation failed most likely due to the use of an incorrect key")] + IncorrectKey, +} + +impl From for SymLockedAccessKeyError { + fn from(err: chacha20poly1305::Error) -> Self { + SymLockedAccessKeyError::CryptoFailure(err.to_string()) + } +}