Skip to content

Commit

Permalink
feat: allow symmetric encryption of access keys
Browse files Browse the repository at this point in the history
  • Loading branch information
sstelfox committed Feb 11, 2024
1 parent 9310c18 commit 5576cfb
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 53 deletions.
6 changes: 3 additions & 3 deletions src/codec/content_payload/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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);
Expand All @@ -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 }))
}
Expand Down
37 changes: 30 additions & 7 deletions src/codec/crypto/access_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -24,10 +26,10 @@ impl AccessKey {
&self,
rng: &mut impl CryptoRngCore,
verifying_key: &VerifyingKey,
) -> Result<LockedAccessKey, AccessKeyError<&[u8]>> {
) -> Result<AsymLockedAccessKey, AccessKeyError<&[u8]>> {
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);
Expand All @@ -42,18 +44,39 @@ impl AccessKey {

let key_id = verifying_key.key_id();

Ok(LockedAccessKey {
Ok(AsymLockedAccessKey {
dh_exchange_key,
nonce,
cipher_text: key_payload,
tag,
key_id,
})
}

pub fn lock_with(
&self,
rng: &mut impl CryptoRngCore,
encrypion_key: &AccessKey,
) -> Result<SymLockedAccessKey, AccessKeyError<&[u8]>> {
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)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AccessKey, LockedAccessKeyError<&[u8]>> {
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 {
Expand All @@ -67,7 +46,7 @@ impl LockedAccessKey {
Ok((input, access_key))
}

pub(crate) fn parse_many(input: &[u8], key_count: u8) -> IResult<&[u8], Vec<Self>> {
pub fn parse_many(input: &[u8], key_count: u8) -> IResult<&[u8], Vec<Self>> {
let (input, keys) = match count(Self::parse, key_count as usize)(input) {
Ok(res) => res,
Err(nom::Err::Incomplete(Needed::Size(_))) => {
Expand All @@ -81,22 +60,43 @@ impl LockedAccessKey {

Ok((input, keys))
}

pub fn unlock(&self, key: &SigningKey) -> Result<AccessKey, AsymLockedAccessKeyError<&[u8]>> {
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<I> {
pub enum AsymLockedAccessKeyError<I> {
#[error("crypto error: {0}")]
CryptoFailure(String),

#[error("decoding data failed: {0}")]
FormatFailure(#[from] nom::Err<nom::error::Error<I>>),

#[error("unspecified crypto error")]
CryptoFailure,

#[error("validation failed most likely due to the use of an incorrect key")]
IncorrectKey,
}

impl<I> From<chacha20poly1305::Error> for LockedAccessKeyError<I> {
fn from(_: chacha20poly1305::Error) -> Self {
LockedAccessKeyError::CryptoFailure
impl<I> From<chacha20poly1305::Error> for AsymLockedAccessKeyError<I> {
fn from(err: chacha20poly1305::Error) -> Self {
AsymLockedAccessKeyError::CryptoFailure(err.to_string())
}
}
8 changes: 4 additions & 4 deletions src/codec/crypto/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
40 changes: 40 additions & 0 deletions src/codec/crypto/sym_locked_access_key.rs
Original file line number Diff line number Diff line change
@@ -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<AccessKey, SymLockedAccessKeyError<&[u8]>> {
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<I> {
#[error("crypto error: {0}")]
CryptoFailure(String),

#[error("decoding data failed: {0}")]
FormatFailure(#[from] nom::Err<nom::error::Error<I>>),

#[error("validation failed most likely due to the use of an incorrect key")]
IncorrectKey,
}

impl<I> From<chacha20poly1305::Error> for SymLockedAccessKeyError<I> {
fn from(err: chacha20poly1305::Error) -> Self {
SymLockedAccessKeyError::CryptoFailure(err.to_string())
}
}

0 comments on commit 5576cfb

Please sign in to comment.