blob: 997014b6d29783d41d385afb33f9f6b7bf4040e1 [file] [log] [blame]
// Copyright 2021 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
//
// http://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.
//
////////////////////////////////////////////////////////////////////////////////
use super::*;
use crate::{
cbor::value::Value, iana, util::expect_err, CborSerializable, ContentType, CoseKeyBuilder,
CoseRecipientBuilder, CoseSignatureBuilder, HeaderBuilder, TaggedCborSerializable,
};
use alloc::{
string::{String, ToString},
vec,
vec::Vec,
};
#[test]
fn test_cose_recipient_decode() {
let tests: Vec<(CoseRecipient, &'static str)> = vec![
(
CoseRecipientBuilder::new().build(),
concat!(
"83", // 3-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"f6", // null
),
),
(
CoseRecipientBuilder::new().ciphertext(vec![]).build(),
concat!(
"83", // 3-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"40", // 0-bstr
),
),
(
CoseRecipientBuilder::new()
.ciphertext(vec![])
.add_recipient(CoseRecipientBuilder::new().build())
.build(),
concat!(
"84", // 4-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"40", // 0-bstr
"81", // 1-tuple
"83", // 3-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"f6", // null
),
),
];
for (i, (recipient, recipient_data)) in tests.iter().enumerate() {
let got = recipient.clone().to_vec().unwrap();
assert_eq!(*recipient_data, hex::encode(&got), "case {}", i);
let mut got = CoseRecipient::from_slice(&got).unwrap();
got.protected.original_data = None;
for recip in &mut got.recipients {
recip.protected.original_data = None;
}
assert_eq!(*recipient, got);
}
}
#[test]
fn test_cose_recipient_decode_fail() {
let tests = [
(
concat!(
"a2", // 2-map (should be tuple)
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"4161", // 1-bstr
"40", // 0-bstr
),
"expected array",
),
(
concat!(
"82", // 2-tuple (should be 4-tuple)
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
),
"expected array with 3 or 4 items",
),
(
concat!(
"84", // 4-tuple
"80", // 0-tuple (should be bstr)
"a0", // 0-map
"40", // 0-bstr
"80", // 0-arr
),
"expected bstr",
),
(
concat!(
"84", // 4-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"40", // 0-bstr (should be map)
"40", // 0-bstr
"80", // 0-arr
),
"expected map",
),
(
concat!(
"84", // 4-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"60", // 0-tstr (should be bstr)
"80", // 0-arr
),
"expected bstr",
),
(
concat!(
"84", // 4-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"40", // 0-bstr
"40", // 0-bstr
),
"expected array",
),
];
for (recipient_data, err_msg) in tests.iter() {
let data = hex::decode(recipient_data).unwrap();
let result = CoseRecipient::from_slice(&data);
expect_err(result, err_msg);
}
}
#[test]
fn test_cose_encrypt_decode() {
let tests: Vec<(CoseEncrypt, &'static str)> = vec![
(
CoseEncryptBuilder::new().build(),
concat!(
"84", // 4-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"f6", // null
"80", // 0-tuple
),
),
(
CoseEncryptBuilder::new().ciphertext(vec![]).build(),
concat!(
"84", // 4-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"40", // 0-bstr
"80", // 0-tuple
),
),
];
for (i, (encrypt, encrypt_data)) in tests.iter().enumerate() {
let got = encrypt.clone().to_vec().unwrap();
assert_eq!(*encrypt_data, hex::encode(&got), "case {}", i);
let mut got = CoseEncrypt::from_slice(&got).unwrap();
got.protected.original_data = None;
assert_eq!(*encrypt, got);
}
}
#[test]
fn test_cose_encrypt_decode_fail() {
let tests = [
(
concat!(
"a2", // 2-map (should be tuple)
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"4161", // 1-bstr
"40", // 0-bstr
),
"expected array",
),
(
concat!(
"83", // 3-tuple (should be 4-tuple)
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"40", // 0-bstr
),
"expected array with 4 items",
),
(
concat!(
"84", // 4-tuple
"80", // 0-tuple (should be bstr)
"a0", // 0-map
"40", // 0-bstr
"80", // 0-arr
),
"expected bstr",
),
(
concat!(
"84", // 4-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"40", // 0-bstr (should be map)
"40", // 0-bstr
"80", // 0-arr
),
"expected map",
),
(
concat!(
"84", // 4-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"60", // 0-tstr (should be bstr)
"80", // 0-arr
),
"expected bstr",
),
(
concat!(
"84", // 4-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"40", // 0-bstr
"40", // 0-bstr
),
"expected array",
),
];
for (encrypt_data, err_msg) in tests.iter() {
let data = hex::decode(encrypt_data).unwrap();
let result = CoseEncrypt::from_slice(&data);
expect_err(result, err_msg);
}
}
#[test]
fn test_rfc8152_cose_encrypt_decode() {
// COSE_Encrypt structures from RFC 8152 section C.3.
let tests: Vec<(CoseEncrypt, &'static str)> = vec![
(
CoseEncryptBuilder::new()
.protected(
HeaderBuilder::new()
.algorithm(iana::Algorithm::A128GCM)
.build(),
)
.unprotected(
HeaderBuilder::new()
.iv(hex::decode("c9cf4df2fe6c632bf7886413").unwrap())
.build(),
)
.ciphertext(
hex::decode(
"7adbe2709ca818fb415f1e5df66f4e1a51053ba6d65a1a0c52a357da7a644b8070a151b0",
)
.unwrap(),
)
.add_recipient(
CoseRecipientBuilder::new()
.protected(
HeaderBuilder::new()
.algorithm(iana::Algorithm::ECDH_ES_HKDF_256)
.build(),
)
.unprotected(
HeaderBuilder::new()
.value(iana::HeaderAlgorithmParameter::EphemeralKey as i64,
CoseKeyBuilder::new_ec2_pub_key_y_sign(iana::EllipticCurve::P_256,
hex::decode("98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280").unwrap(),
true)
.build().to_cbor_value().unwrap())
.key_id(b"[email protected]".to_vec())
.build(),
)
.ciphertext(vec![])
.build(),
)
.build(),
// Note: contents of maps have been re-ordered from the RFC to canonical ordering.
concat!(
"d860",
"84", "43", "a10101",
"a1", "05", "4c", "c9cf4df2fe6c632bf7886413",
"5824", "7adbe2709ca818fb415f1e5df66f4e1a51053ba6d65a1a0c52a357da7a644b8070a151b0",
"81",
"83",
"44", "a1013818",
"a2",
"04",
"5824", "6d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65",
"20",
"a4",
"01", "02",
"20", "01",
"21", "5820", "98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280",
"22", "f5",
"40",
),
),
(
CoseEncryptBuilder::new()
.protected(HeaderBuilder::new().algorithm(iana::Algorithm::AES_CCM_16_64_128).build())
.unprotected(HeaderBuilder::new().iv(hex::decode("89f52f65a1c580933b5261a76c").unwrap()).build())
.ciphertext(hex::decode("753548a19b1307084ca7b2056924ed95f2e3b17006dfe931b687b847").unwrap())
.add_recipient(CoseRecipientBuilder::new()
.protected(HeaderBuilder::new().algorithm(iana::Algorithm::Direct_HKDF_SHA_256).build())
.unprotected(
HeaderBuilder::new()
.key_id(b"our-secret".to_vec())
.value(iana::HeaderAlgorithmParameter::Salt as i64,
Value::Bytes(b"aabbccddeeffgghh".to_vec()))
.build())
.ciphertext(vec![])
.build())
.build(),
// Note: contents of maps have been re-ordered from the RFC to canonical ordering.
concat!(
"d860",
"84",
"43",
"a1010a",
"a1",
"05",
"4d", "89f52f65a1c580933b5261a76c",
"581c", "753548a19b1307084ca7b2056924ed95f2e3b17006dfe931b687b847",
"81",
"83",
"43",
"a10129",
"a2",
"04", "4a", "6f75722d736563726574",
"33", "50", "61616262636364646565666667676868",
"40",
),
),
(
CoseEncryptBuilder::new()
.protected(HeaderBuilder::new().algorithm(iana::Algorithm::A128GCM).build())
.unprotected(HeaderBuilder::new()
.iv(hex::decode("c9cf4df2fe6c632bf7886413").unwrap())
.add_counter_signature(CoseSignatureBuilder::new()
.protected(HeaderBuilder::new().algorithm(iana::Algorithm::ES512).build())
.unprotected(HeaderBuilder::new().key_id(b"[email protected]".to_vec()).build())
.signature(hex::decode("00929663c8789bb28177ae28467e66377da12302d7f9594d2999afa5dfa531294f8896f2b6cdf1740014f4c7f1a358e3a6cf57f4ed6fb02fcf8f7aa989f5dfd07f0700a3a7d8f3c604ba70fa9411bd10c2591b483e1d2c31de003183e434d8fba18f17a4c7e3dfa003ac1cf3d30d44d2533c4989d3ac38c38b71481cc3430c9d65e7ddff").unwrap())
.build())
.build())
.ciphertext(hex::decode("7adbe2709ca818fb415f1e5df66f4e1a51053ba6d65a1a0c52a357da7a644b8070a151b0").unwrap())
.add_recipient(CoseRecipientBuilder::new()
.protected(
HeaderBuilder::new()
.algorithm(iana::Algorithm::ECDH_ES_HKDF_256)
.build())
.unprotected(
HeaderBuilder::new()
.value(iana::HeaderAlgorithmParameter::EphemeralKey as i64,
CoseKeyBuilder::new_ec2_pub_key_y_sign(iana::EllipticCurve::P_256,
hex::decode("98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280").unwrap(),
true)
.build().to_cbor_value().unwrap())
.key_id(b"[email protected]".to_vec())
.build())
.ciphertext(vec![])
.build())
.build(),
// Note: contents of maps have been re-ordered from the RFC to canonical ordering.
concat!(
"d860",
"84",
"43",
"a10101",
"a2",
"05",
"4c", "c9cf4df2fe6c632bf7886413",
"07",
"83",
"44",
"a1013823",
"a1",
"04",
"581e", "62696c626f2e62616767696e7340686f626269746f6e2e6578616d706c65",
"5884",
"00929663c8789bb28177ae28467e66377da12302d7f9594d2999afa5dfa531294f8896f2b6cdf1740014f4c7f1a358e3a6cf57f4ed6fb02fcf8f7aa989f5dfd07f0700a3a7d8f3c604ba70fa9411bd10c2591b483e1d2c31de003183e434d8fba18f17a4c7e3dfa003ac1cf3d30d44d2533c4989d3ac38c38b71481cc3430c9d65e7ddff",
"5824",
"7adbe2709ca818fb415f1e5df66f4e1a51053ba6d65a1a0c52a357da7a644b8070a151b0",
"81",
"83",
"44", "a1013818",
"a2",
"04",
"5824", "6d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65",
"20",
"a4",
"01",
"02",
"20",
"01",
"21",
"5820", "98f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280",
"22",
"f5",
"40",
),
),
(
CoseEncryptBuilder::new()
.protected(HeaderBuilder::new().algorithm(iana::Algorithm::A128GCM).build())
.unprotected(HeaderBuilder::new().iv(hex::decode("02d1f7e6f26c43d4868d87ce").unwrap()).build())
.ciphertext(hex::decode("64f84d913ba60a76070a9a48f26e97e863e28529d8f5335e5f0165eee976b4a5f6c6f09d").unwrap())
.add_recipient(CoseRecipientBuilder::new()
.protected(HeaderBuilder::new().algorithm(iana::Algorithm::ECDH_SS_A128KW).build())
.unprotected(HeaderBuilder::new()
.key_id(b"[email protected]".to_vec())
.value(
iana::HeaderAlgorithmParameter::StaticKeyId as i64,
Value::Bytes(b"[email protected]".to_vec())
)
.value(
iana::HeaderAlgorithmParameter::PartyUNonce as i64,
Value::Bytes(hex::decode("0101").unwrap())
)
.build())
.ciphertext(hex::decode("41e0d76f579dbd0d936a662d54d8582037de2e366fde1c62").unwrap())
.build())
.build(),
// Note: contents of maps have been re-ordered from the RFC to canonical ordering.
concat!(
"d860",
"84",
"43",
"a10101",
"a1",
"05",
"4c", "02d1f7e6f26c43d4868d87ce",
"5824", "64f84d913ba60a76070a9a48f26e97e863e28529d8f5335e5f0165eee976b4a5f6c6f09d",
"81",
"83",
"44", "a101381f",
"a3",
"04",
"5824", "6d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65",
"22",
"5821", "706572656772696e2e746f6f6b407475636b626f726f7567682e6578616d706c65",
"35",
"42",
"0101",
"5818", "41e0d76f579dbd0d936a662d54d8582037de2e366fde1c62",
),
),
];
for (i, (encrypt, encrypt_data)) in tests.iter().enumerate() {
let got = encrypt.clone().to_tagged_vec().unwrap();
assert_eq!(*encrypt_data, hex::encode(&got), "case {}", i);
let mut got = CoseEncrypt::from_tagged_slice(&got).unwrap();
got.protected.original_data = None;
for recip in &mut got.recipients {
recip.protected.original_data = None;
}
for sig in &mut got.unprotected.counter_signatures {
sig.protected.original_data = None;
}
assert_eq!(*encrypt, got);
}
}
#[test]
fn test_cose_encrypt0_decode() {
let tests: Vec<(CoseEncrypt0, &'static str)> = vec![
(
CoseEncrypt0Builder::new().build(),
concat!(
"83", // 3-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"f6", // null
),
),
(
CoseEncrypt0Builder::new().ciphertext(vec![]).build(),
concat!(
"83", // 3-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"40", // 0-bstr
),
),
];
for (i, (encrypt, encrypt_data)) in tests.iter().enumerate() {
let got = encrypt.clone().to_vec().unwrap();
assert_eq!(*encrypt_data, hex::encode(&got), "case {}", i);
let mut got = CoseEncrypt0::from_slice(&got).unwrap();
got.protected.original_data = None;
assert_eq!(*encrypt, got);
}
}
#[test]
fn test_cose_encrypt0_decode_fail() {
let tests = [
(
concat!(
"a2", // 2-map (should be tuple)
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"4100", // 1-bstr
"40", // 0-bstr
),
"expected array",
),
(
concat!(
"82", // 2-tuple (should be 3-tuple)
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
),
"expected array with 3 items",
),
(
concat!(
"83", // 3-tuple
"80", // 0-tuple (should be bstr)
"a0", // 0-map
"40", // 0-bstr
),
"expected bstr",
),
(
concat!(
"83", // 3-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"40", // 0-bstr (should be map)
"40", // 0-bstr
),
"expected map",
),
(
concat!(
"83", // 3-tuple
"40", // 0-bstr (special case for empty protected headers, rather than 41a0)
"a0", // 0-map
"60", // 0-tstr (should be bstr)
),
"expected bstr",
),
];
for (encrypt_data, err_msg) in tests.iter() {
let data = hex::decode(encrypt_data).unwrap();
let result = CoseEncrypt0::from_slice(&data);
expect_err(result, err_msg);
}
}
#[test]
fn test_rfc8152_cose_encrypt0_decode() {
// COSE_Encrypt0 structures from RFC 8152 section C.4.
let tests: Vec<(CoseEncrypt0, &'static str)> = vec![
(
CoseEncrypt0Builder::new()
.protected(
HeaderBuilder::new()
.algorithm(iana::Algorithm::AES_CCM_16_64_128)
.build(),
)
.unprotected(
HeaderBuilder::new()
.iv(hex::decode("89f52f65a1c580933b5261a78c").unwrap())
.build(),
)
.ciphertext(
hex::decode("5974e1b99a3a4cc09a659aa2e9e7fff161d38ce71cb45ce460ffb569")
.unwrap(),
)
.build(),
concat!(
"d0",
"83",
"43",
"a1010a",
"a1",
"05",
"4d",
"89f52f65a1c580933b5261a78c",
"581c",
"5974e1b99a3a4cc09a659aa2e9e7fff161d38ce71cb45ce460ffb569",
),
),
(
CoseEncrypt0Builder::new()
.protected(
HeaderBuilder::new()
.algorithm(iana::Algorithm::AES_CCM_16_64_128)
.build(),
)
.unprotected(
HeaderBuilder::new()
.partial_iv(hex::decode("61a7").unwrap())
.build(),
)
.ciphertext(
hex::decode("252a8911d465c125b6764739700f0141ed09192de139e053bd09abca")
.unwrap(),
)
.build(),
concat!(
"d0",
"83",
"43",
"a1010a",
"a1",
"06",
"42",
"61a7",
"581c",
"252a8911d465c125b6764739700f0141ed09192de139e053bd09abca",
),
),
];
for (i, (encrypt, encrypt_data)) in tests.iter().enumerate() {
let got = encrypt.clone().to_tagged_vec().unwrap();
assert_eq!(*encrypt_data, hex::encode(&got), "case {}", i);
let mut got = CoseEncrypt0::from_tagged_slice(&got).unwrap();
got.protected.original_data = None;
assert_eq!(*encrypt, got);
}
}
struct FakeCipher {}
impl FakeCipher {
fn encrypt(&self, plaintext: &[u8], additional_data: &[u8]) -> Result<Vec<u8>, String> {
let mut result = vec![];
result.extend_from_slice(&(plaintext.len() as u32).to_be_bytes());
result.extend_from_slice(plaintext);
result.extend_from_slice(additional_data);
Ok(result)
}
fn decrypt(&self, ciphertext: &[u8], additional_data: &[u8]) -> Result<Vec<u8>, String> {
if ciphertext.len() < 4 {
return Err("not long enough".to_owned());
}
let pt_len =
u32::from_be_bytes([ciphertext[0], ciphertext[1], ciphertext[2], ciphertext[3]])
as usize;
let pt = &ciphertext[4..4 + pt_len];
let recovered_aad = &ciphertext[4 + pt_len..];
if recovered_aad != additional_data {
return Err("aad doesn't match".to_owned());
}
Ok(pt.to_vec())
}
fn fail_encrypt(&self, _plaintext: &[u8], _additional_data: &[u8]) -> Result<Vec<u8>, String> {
Err("failed".to_string())
}
}
#[test]
fn test_cose_recipient_roundtrip() {
let pt = b"This is the plaintext";
let external_aad = b"This is the external aad";
let cipher = FakeCipher {};
for context in &[
EncryptionContext::EncRecipient,
EncryptionContext::MacRecipient,
EncryptionContext::RecRecipient,
] {
let protected = HeaderBuilder::new()
.algorithm(iana::Algorithm::ES256)
.key_id(b"11".to_vec())
.build();
let mut recipient = CoseRecipientBuilder::new()
.protected(protected)
.create_ciphertext(*context, pt, external_aad, |pt, aad| {
cipher.encrypt(pt, aad).unwrap()
})
.build();
let recovered_pt = recipient
.decrypt(*context, external_aad, |ct, aad| cipher.decrypt(ct, aad))
.unwrap();
assert_eq!(&pt[..], recovered_pt);
// Changing an unprotected header leaves the ciphertext decipherable.
recipient.unprotected.content_type = Some(ContentType::Text("text/plain".to_owned()));
assert!(recipient
.decrypt(*context, external_aad, |ct, aad| {
cipher.decrypt(ct, aad)
})
.is_ok());
// Providing a different `aad` means the ciphertext won't validate.
assert!(recipient
.decrypt(*context, b"not aad", |ct, aad| { cipher.decrypt(ct, aad) })
.is_err());
// Changing a protected header invalidates the ciphertext.
recipient.protected = ProtectedHeader::default();
assert!(recipient
.decrypt(*context, external_aad, |ct, aad| {
cipher.decrypt(ct, aad)
})
.is_err());
}
}
#[test]
fn test_cose_recipient_noncanonical() {
let pt = b"aa";
let aad = b"bb";
let cipher = FakeCipher {};
let context = EncryptionContext::EncRecipient;
// Build an empty protected header from a non-canonical input of 41a0 rather than 40.
let protected = ProtectedHeader::from_cbor_bstr(Value::Bytes(vec![0xa0])).unwrap();
assert_eq!(protected.header, Header::default());
assert_eq!(protected.original_data, Some(vec![0xa0]));
let mut recipient = CoseRecipient {
protected: protected.clone(),
..Default::default()
};
let internal_aad = crate::encrypt::enc_structure_data(context, protected, aad);
recipient.ciphertext = Some(cipher.encrypt(pt, &internal_aad).unwrap());
// Deciphering the ciphertext should still succeed, because the `ProtectedHeader`
// includes the wire data and uses it for building the decryption input.
let recovered_pt = recipient
.decrypt(context, aad, |ct, aad| cipher.decrypt(ct, aad))
.unwrap();
assert_eq!(&pt[..], recovered_pt);
// However, if we attempt to build the same decryption inputs by hand (thus not including the
// non-canonical wire data)...
let recreated_recipient = CoseRecipientBuilder::new()
.ciphertext(recipient.ciphertext.unwrap())
.build();
// ...then the transplanted cipher text will not decipher, because the re-building of the
// inputs will use the canonical encoding of the protected header, which is not what was
// originally used for the input.
assert!(recreated_recipient
.decrypt(context, aad, |ct, aad| cipher.decrypt(ct, aad))
.is_err());
}
#[test]
fn test_cose_recipient_result() {
let pt = b"This is the plaintext";
let external_aad = b"This is the external aad";
let cipher = FakeCipher {};
let protected = HeaderBuilder::new()
.algorithm(iana::Algorithm::ES256)
.key_id(b"11".to_vec())
.build();
let _recipient = CoseRecipientBuilder::new()
.protected(protected.clone())
.try_create_ciphertext(
EncryptionContext::EncRecipient,
pt,
external_aad,
|pt, aad| cipher.encrypt(pt, aad),
)
.unwrap()
.build();
let status = CoseRecipientBuilder::new()
.protected(protected)
.try_create_ciphertext(
EncryptionContext::EncRecipient,
pt,
external_aad,
|pt, aad| cipher.fail_encrypt(pt, aad),
);
expect_err(status, "failed");
}
#[test]
#[should_panic]
fn test_cose_recipient_missing_ciphertext() {
let external_aad = b"This is the external aad";
let cipher = FakeCipher {};
let recipient = CoseRecipient::default();
// No ciphertext has been set, do decryption will panic.
let _result = recipient.decrypt(EncryptionContext::EncRecipient, external_aad, |ct, aad| {
cipher.decrypt(ct, aad)
});
}
#[test]
#[should_panic]
fn test_cose_recipient_builder_invalid_context() {
let pt = b"This is the plaintext";
let external_aad = b"This is the external aad";
let cipher = FakeCipher {};
// Can't use a non-recipient context.
let _recipient = CoseRecipientBuilder::new()
.create_ciphertext(
EncryptionContext::CoseEncrypt,
pt,
external_aad,
|pt, aad| cipher.encrypt(pt, aad).unwrap(),
)
.build();
}
#[test]
#[should_panic]
fn test_cose_recipient_decrypt_invalid_context() {
let pt = b"This is the plaintext";
let external_aad = b"This is the external aad";
let cipher = FakeCipher {};
let recipient = CoseRecipientBuilder::new()
.create_ciphertext(
EncryptionContext::EncRecipient,
pt,
external_aad,
|pt, aad| cipher.encrypt(pt, aad).unwrap(),
)
.build();
// Can't use a non-recipient context.
let _result = recipient.decrypt(EncryptionContext::CoseEncrypt, external_aad, |ct, aad| {
cipher.decrypt(ct, aad)
});
}
#[test]
fn test_cose_encrypt_roundtrip() {
let pt = b"This is the plaintext";
let external_aad = b"This is the external aad";
let cipher = FakeCipher {};
let protected = HeaderBuilder::new()
.algorithm(iana::Algorithm::ES256)
.key_id(b"11".to_vec())
.build();
let mut encrypt = CoseEncryptBuilder::new()
.protected(protected)
.create_ciphertext(pt, external_aad, |pt, aad| cipher.encrypt(pt, aad).unwrap())
.build();
let recovered_pt = encrypt
.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
.unwrap();
assert_eq!(&pt[..], recovered_pt);
// Changing an unprotected header leaves the ciphertext decipherable.
encrypt.unprotected.content_type = Some(ContentType::Text("text/plain".to_owned()));
assert!(encrypt
.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
.is_ok());
// Providing a different `aad` means the signature won't validate.
assert!(encrypt
.decrypt(b"not aad", |ct, aad| cipher.decrypt(ct, aad))
.is_err());
// Changing a protected header invalidates the ciphertext.
encrypt.protected = ProtectedHeader::default();
assert!(encrypt
.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
.is_err());
}
#[test]
fn test_cose_encrypt_noncanonical() {
let pt = b"aa";
let external_aad = b"bb";
let cipher = FakeCipher {};
// Build an empty protected header from a non-canonical input of 41a0 rather than 40.
let protected = ProtectedHeader::from_cbor_bstr(Value::Bytes(vec![0xa0])).unwrap();
assert_eq!(protected.header, Header::default());
assert_eq!(protected.original_data, Some(vec![0xa0]));
let mut encrypt = CoseEncrypt {
protected: protected.clone(),
..Default::default()
};
let aad = enc_structure_data(
EncryptionContext::CoseEncrypt,
protected.clone(),
external_aad,
);
encrypt.ciphertext = Some(cipher.encrypt(pt, &aad).unwrap());
// Deciphering the ciphertext should still succeed, because the `ProtectedHeader`
// includes the wire data and uses it for building the decryption input.
let recovered_pt = encrypt
.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
.unwrap();
assert_eq!(&pt[..], recovered_pt);
// However, if we attempt to build the same decryption inputs by hand (thus not including the
// non-canonical wire data)...
let recreated_encrypt = CoseEncryptBuilder::new()
.protected(protected.header)
.ciphertext(encrypt.ciphertext.unwrap())
.build();
// ...then the transplanted cipher text will not decipher, because the re-building of the
// inputs will use the canonical encoding of the protected header, which is not what was
// originally used for the input.
assert!(recreated_encrypt
.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
.is_err());
}
#[test]
fn test_cose_encrypt_status() {
let pt = b"This is the plaintext";
let external_aad = b"This is the external aad";
let cipher = FakeCipher {};
let protected = HeaderBuilder::new()
.algorithm(iana::Algorithm::ES256)
.key_id(b"11".to_vec())
.build();
let _encrypt = CoseEncryptBuilder::new()
.protected(protected.clone())
.try_create_ciphertext(pt, external_aad, |pt, aad| cipher.encrypt(pt, aad))
.unwrap()
.build();
let status = CoseEncryptBuilder::new()
.protected(protected)
.try_create_ciphertext(pt, external_aad, |pt, aad| cipher.fail_encrypt(pt, aad));
expect_err(status, "failed");
}
#[test]
#[should_panic]
fn test_cose_encrypt_missing_ciphertext() {
let external_aad = b"This is the external aad";
let cipher = FakeCipher {};
let encrypt = CoseEncrypt::default();
// No ciphertext has been set, do decryption will panic.
let _result = encrypt.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad));
}
#[test]
fn test_cose_encrypt0_roundtrip() {
let pt = b"This is the plaintext";
let external_aad = b"This is the external aad";
let cipher = FakeCipher {};
let protected = HeaderBuilder::new()
.algorithm(iana::Algorithm::ES256)
.key_id(b"11".to_vec())
.build();
let mut encrypt = CoseEncrypt0Builder::new()
.protected(protected)
.create_ciphertext(pt, external_aad, |pt, aad| cipher.encrypt(pt, aad).unwrap())
.build();
let recovered_pt = encrypt
.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
.unwrap();
assert_eq!(&pt[..], recovered_pt);
// Changing an unprotected header leaves the ciphertext decipherable.
encrypt.unprotected.content_type = Some(ContentType::Text("text/plain".to_owned()));
assert!(encrypt
.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
.is_ok());
// Providing a different `aad` means the ciphertext won't decrypt.
assert!(encrypt
.decrypt(b"not aad", |ct, aad| cipher.decrypt(ct, aad))
.is_err());
// Changing a protected header invalidates the ciphertext.
encrypt.protected = ProtectedHeader::default();
assert!(encrypt
.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
.is_err());
}
#[test]
fn test_cose_encrypt0_noncanonical() {
let pt = b"aa";
let external_aad = b"bb";
let cipher = FakeCipher {};
// Build an empty protected header from a non-canonical input of 41a0 rather than 40.
let protected = ProtectedHeader::from_cbor_bstr(Value::Bytes(vec![0xa0])).unwrap();
assert_eq!(protected.header, Header::default());
assert_eq!(protected.original_data, Some(vec![0xa0]));
let mut encrypt = CoseEncrypt0 {
protected: protected.clone(),
..Default::default()
};
let aad = enc_structure_data(
EncryptionContext::CoseEncrypt0,
protected.clone(),
external_aad,
);
encrypt.ciphertext = Some(cipher.encrypt(pt, &aad).unwrap());
// Deciphering the ciphertext should still succeed, because the `ProtectedHeader`
// includes the wire data and uses it for building the decryption input.
let recovered_pt = encrypt
.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
.unwrap();
assert_eq!(&pt[..], recovered_pt);
// However, if we attempt to build the same decryption inputs by hand (thus not including the
// non-canonical wire data)...
let recreated_encrypt = CoseEncrypt0Builder::new()
.protected(protected.header)
.ciphertext(encrypt.ciphertext.unwrap())
.build();
// ...then the transplanted cipher text will not decipher, because the re-building of the
// inputs will use the canonical encoding of the protected header, which is not what was
// originally used for the input.
assert!(recreated_encrypt
.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad))
.is_err());
}
#[test]
fn test_cose_encrypt0_status() {
let pt = b"This is the plaintext";
let external_aad = b"This is the external aad";
let cipher = FakeCipher {};
let protected = HeaderBuilder::new()
.algorithm(iana::Algorithm::ES256)
.key_id(b"11".to_vec())
.build();
let _encrypt = CoseEncrypt0Builder::new()
.protected(protected.clone())
.try_create_ciphertext(pt, external_aad, |pt, aad| cipher.encrypt(pt, aad))
.unwrap()
.build();
let status = CoseEncrypt0Builder::new()
.protected(protected)
.try_create_ciphertext(pt, external_aad, |pt, aad| cipher.fail_encrypt(pt, aad));
expect_err(status, "failed");
}
#[test]
#[should_panic]
fn test_cose_encrypt0_missing_ciphertext() {
let external_aad = b"This is the external aad";
let cipher = FakeCipher {};
let encrypt = CoseEncrypt0::default();
// No ciphertext has been set, do decryption will panic.
let _result = encrypt.decrypt(external_aad, |ct, aad| cipher.decrypt(ct, aad));
}