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

Changes to error types and moved some functions into modules #13

Merged
merged 3 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub(crate) fn aes_encrypt_with_iv(
aad: associated_data,
},
)
.map_err(|e| Error::EncryptError(e.to_string()))?;
.map_err(|_| Error::EncryptError("Encryption failed.".to_string()))?;
Ok((iv, EncryptedDocument(encrypted_bytes)))
}

Expand All @@ -129,7 +129,11 @@ pub(crate) fn aes_decrypt_core(
aad: associated_data,
},
)
.map_err(|e| Error::DecryptError(e.to_string()))
.map_err(|_| {
Error::DecryptError(
"Decryption failed. Check the data and tenant ID are correct".to_string(),
)
})
}

#[cfg(test)]
Expand Down
145 changes: 18 additions & 127 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@ pub mod v3;
pub mod v4;
pub mod v5;

use self::icl_header_v4::V4DocumentHeader;
use icl_header_v4::v4document_header::{
signature_information::SignatureType, SignatureInformation, SignedPayload,
};
use protobuf::Message;
use std::fmt::{Display, Formatter, Result as DisplayResult};
use thiserror::Error;
use v5::key_id_header::KEY_ID_HEADER_LEN;

include!(concat!(env!("OUT_DIR"), "/mod.rs"));

Expand Down Expand Up @@ -48,129 +44,24 @@ pub enum Error {
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> DisplayResult {
match self {
Error::EdocTooShort(x) => write!(f, "EdocTooShort({x})"),
Error::HeaderParseErr(x) => write!(f, "HeaderParseErr({x})"),
Error::InvalidVersion(x) => write!(f, "InvalidVersion({x})"),
Error::NoIronCoreMagic => write!(f, "NoIronCoreMagic"),
Error::SpecifiedLengthTooLong(x) => write!(f, "SpecifiedLengthTooLong({x})"),
Error::ProtoSerializationErr(x) => write!(f, "ProtoSerializationErr({x})"),
Error::HeaderLengthOverflow(x) => write!(f, "HeaderLengthOverflow({x})"),
Error::EncryptError(x) => write!(f, "EncryptError({x})"),
Error::DecryptError(x) => write!(f, "DecryptError({x})"),
Error::KeyIdHeaderTooShort(x) => write!(f, "KeyIdHeaderTooShort({x})"),
Error::EdekTypeError(x) => write!(f, "EdekTypeError({x})"),
Error::PayloadTypeError(x) => write!(f, "PayloadTypeError({x})"),
Error::KeyIdHeaderMalformed(x) => write!(f, "KeyIdHeaderMalformed({x})"),
}
}
}

/// Creates a signed proto wrapper with a single edek wrapper in it using the signing key to do the signing.
pub fn create_signed_proto(
edek_wrappers: Vec<icl_header_v4::v4document_header::EdekWrapper>,
signing_key: aes::EncryptionKey,
) -> V4DocumentHeader {
let signed_payload = icl_header_v4::v4document_header::SignedPayload {
edeks: edek_wrappers,
..Default::default()
};
let signature_info = sign_header(signing_key, &signed_payload);
icl_header_v4::V4DocumentHeader {
signed_payload: Some(signed_payload).into(),
signature_info: Some(signature_info).into(),
..Default::default()
}
}

/// Sign the payload using the key.
pub fn sign_header(
key: aes::EncryptionKey,
header_payload: &SignedPayload,
) -> SignatureInformation {
//This unwrap can't actually ever happen because they create the coded stream with exactly the computed size before
//serializing.
let bytes = header_payload
.write_to_bytes()
.expect("Writing proto to bytes failed.");
let signature = signing::sign_hs256(key.0, &bytes);

SignatureInformation {
signature: signature.0.to_vec().into(),
signature_type: SignatureType::HS256.into(),
..Default::default()
}
}

/// Verify the signature inside the
pub fn verify_signature(key: aes::EncryptionKey, header: &V4DocumentHeader) -> bool {
match header.signature_info.signature_type.enum_value() {
Ok(SignatureType::NONE) => true,
Ok(SignatureType::HS256) => {
if let Ok(signature_bytes) = header.signature_info.signature.to_vec().try_into() {
signing::verify_hs256(
key.0,
//This unwrap can't actually ever happen because they create the coded stream with exactly the computed size before
//serializing.
&header
.signed_payload
.write_to_bytes()
.expect("Writing proto to bytes failed."),
&signing::Signature(signature_bytes),
)
} else {
false
Error::EdocTooShort(x) => write!(f, "EDOC too short. Found {x} bytes."),
Error::HeaderParseErr(x) => write!(f, "Header parse error: '{x}'"),
Error::InvalidVersion(x) => write!(f, "Invalid EDOC version: {x}"),
Error::NoIronCoreMagic => write!(f, "Missing IronCore Magic bytes in header."),
Error::SpecifiedLengthTooLong(x) => {
write!(f, "Header too short for specified length: {x} bytes")
}
Error::ProtoSerializationErr(x) => write!(f, "Protobuf serialization error: '{x}'"),
Error::HeaderLengthOverflow(x) => write!(f, "Header length too long: {x} bytes"),
Error::EncryptError(x) => write!(f, "{x}"),
Error::DecryptError(x) => write!(f, "{x}"),
Error::KeyIdHeaderTooShort(x) => write!(
f,
"Key ID header too short. Found: {x} bytes. Required: {KEY_ID_HEADER_LEN} bytes."
),
Error::EdekTypeError(x) => write!(f, "EDEK type error: '{x}'"),
Error::PayloadTypeError(x) => write!(f, "Payload type error: '{x}'"),
Error::KeyIdHeaderMalformed(x) => write!(f, "Malformed key ID header: '{x}'"),
}
_ => false,
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::signing::AES_KEY_LEN;
use crate::{
aes::EncryptionKey,
icl_header_v4::v4document_header::{
edek_wrapper::{Aes256GcmEncryptedDek, Edek},
EdekWrapper,
},
};

#[test]
fn sign_verify_roundtrip() {
let dek = EncryptionKey([100u8; AES_KEY_LEN]);
let aes_edek = Aes256GcmEncryptedDek {
ciphertext: [42u8; 1024].as_ref().into(),
..Default::default()
};

let edek_wrapper = EdekWrapper {
edek: Some(Edek::Aes256GcmEdek(aes_edek)),
..Default::default()
};

let signed_payload = SignedPayload {
edeks: vec![edek_wrapper],
..Default::default()
};

let mut header = V4DocumentHeader {
signed_payload: Some(signed_payload).into(),
..Default::default()
};

let sign_result = sign_header(dek, &header.signed_payload);

header.signature_info = Some(sign_result).into();
assert!(verify_signature(dek, &header));
}

#[test]
fn verify_known_good_sig_in_v4_header() {
let dek = EncryptionKey([100u8; AES_KEY_LEN]);
let bytes = hex_literal::hex!("0a240a2082e7f2abc390635636f59ea51f7736846d9b1e799f4e9b63733679a417a2c5cf10011289081286081a83081280082a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a");
let header = Message::parse_from_bytes(&bytes).unwrap();
assert!(verify_signature(dek, &header))
}
}
116 changes: 112 additions & 4 deletions src/v4/aes.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
// This file contains things related to the V4 AES edek, which is defined in the icl_v4_header.proto.

use crate::{
aes::{aes_decrypt_core, aes_encrypt, EncryptionKey},
create_signed_proto, icl_header_v4, Error,
aes::{self, aes_decrypt_core, aes_encrypt, EncryptionKey},
icl_header_v4::{
self,
v4document_header::{
signature_information::SignatureType, SignatureInformation, SignedPayload,
},
V4DocumentHeader,
},
signing, Error,
};
use bytes::Bytes;
use protobuf::Message;
use rand::{CryptoRng, RngCore};

type Result<T> = core::result::Result<T, crate::Error>;

/// If `maybe_dek` is None, generate a dek, otherwise use the one provided.
/// Encrypt the dek using the kek to make an aes edek. The provided id will be put into the Aes256GcmEncryptedDek.
/// Returns the dek and Aes256GcmEncryptedDek.
pub fn generate_aes_edek<R: CryptoRng + RngCore>(
fn generate_aes_edek<R: CryptoRng + RngCore>(
rng: &mut R,
kek: EncryptionKey,
maybe_dek: Option<EncryptionKey>,
Expand All @@ -36,6 +44,39 @@ pub fn generate_aes_edek<R: CryptoRng + RngCore>(
Ok((dek, aes_edek))
}

/// Sign the payload using the key.
fn sign_header(key: aes::EncryptionKey, header_payload: &SignedPayload) -> SignatureInformation {
//This unwrap can't actually ever happen because they create the coded stream with exactly the computed size before
//serializing.
let bytes = header_payload
.write_to_bytes()
.expect("Writing proto to bytes failed.");
let signature = signing::sign_hs256(key.0, &bytes);

SignatureInformation {
signature: signature.0.to_vec().into(),
signature_type: SignatureType::HS256.into(),
..Default::default()
}
}

/// Creates a signed proto wrapper with a single edek wrapper in it using the signing key to do the signing.
pub fn create_signed_proto(
edek_wrappers: Vec<icl_header_v4::v4document_header::EdekWrapper>,
signing_key: aes::EncryptionKey,
) -> V4DocumentHeader {
let signed_payload = icl_header_v4::v4document_header::SignedPayload {
edeks: edek_wrappers,
..Default::default()
};
let signature_info = sign_header(signing_key, &signed_payload);
icl_header_v4::V4DocumentHeader {
signed_payload: Some(signed_payload).into(),
signature_info: Some(signature_info).into(),
..Default::default()
}
}

/// If `maybe_dek` is None, generate a dek, otherwise use the one provided.
/// Encrypt the dek using the kek to make an aes edek. The provided id will be put into the Aes256GcmEdek.
/// The edek will be placed into a V4DocumentHeader and the signature will be computed.
Expand Down Expand Up @@ -79,10 +120,40 @@ pub fn decrypt_aes_edek(
.map(EncryptionKey)
}

/// Verify the signature inside the V4 header
pub fn verify_signature(key: aes::EncryptionKey, header: &V4DocumentHeader) -> bool {
match header.signature_info.signature_type.enum_value() {
Ok(SignatureType::NONE) => true,
Ok(SignatureType::HS256) => {
if let Ok(signature_bytes) = header.signature_info.signature.to_vec().try_into() {
signing::verify_hs256(
key.0,
//This unwrap can't actually ever happen because they create the coded stream with exactly the computed size before
//serializing.
&header
.signed_payload
.write_to_bytes()
.expect("Writing proto to bytes failed."),
&signing::Signature(signature_bytes),
)
} else {
false
}
}
_ => false,
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::verify_signature;
use crate::{
icl_header_v4::v4document_header::{
edek_wrapper::{Aes256GcmEncryptedDek, Edek},
EdekWrapper,
},
signing::AES_KEY_LEN,
};
use hex_literal::hex;
use protobuf::Message;
use rand::SeedableRng;
Expand Down Expand Up @@ -145,4 +216,41 @@ mod test {
// Verify fails because I messed the signature up in the proto_bytes
assert!(!verify_result)
}

#[test]
fn sign_verify_roundtrip() {
let dek = EncryptionKey([100u8; AES_KEY_LEN]);
let aes_edek = Aes256GcmEncryptedDek {
ciphertext: [42u8; 1024].as_ref().into(),
..Default::default()
};

let edek_wrapper = EdekWrapper {
edek: Some(Edek::Aes256GcmEdek(aes_edek)),
..Default::default()
};

let signed_payload = SignedPayload {
edeks: vec![edek_wrapper],
..Default::default()
};

let mut header = V4DocumentHeader {
signed_payload: Some(signed_payload).into(),
..Default::default()
};

let sign_result = sign_header(dek, &header.signed_payload);

header.signature_info = Some(sign_result).into();
assert!(verify_signature(dek, &header));
}

#[test]
fn verify_known_good_sig_in_v4_header() {
let dek = EncryptionKey([100u8; AES_KEY_LEN]);
let bytes = hex_literal::hex!("0a240a2082e7f2abc390635636f59ea51f7736846d9b1e799f4e9b63733679a417a2c5cf10011289081286081a83081280082a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a");
let header = Message::parse_from_bytes(&bytes).unwrap();
assert!(verify_signature(dek, &header))
}
}
2 changes: 1 addition & 1 deletion src/v5/key_id_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const DETERMINISTIC_PAYLOAD_TYPE_NUM: u8 = 0u8;
const VECTOR_METADATA_PAYLOAD_TYPE_NUM: u8 = 1u8;
const STANDARD_EDEK_PAYLOAD_TYPE_NUM: u8 = 2u8;

const KEY_ID_HEADER_LEN: usize = 6;
pub(crate) const KEY_ID_HEADER_LEN: usize = 6;

type Result<A> = std::result::Result<A, super::Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down
Loading