From 5dbf1067736633cf97fff097fc1d839cef236141 Mon Sep 17 00:00:00 2001 From: petreeftime Date: Tue, 9 Mar 2021 18:58:08 +0200 Subject: [PATCH] sign: add support for CBOR tags (#10) Add the capability to tag COSE Sign1 objects with tag 18: https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml, and read back tagged objects. Signed-off-by: Petre Eftime --- Cargo.toml | 2 +- src/error.rs | 4 ++ src/sign.rs | 123 +++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 119 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c5cb4d5..20dbd99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/awslabs/aws-nitro-enclaves-cose" description = "This library aims to provide a safe Rust implementation of COSE, with COSE Sign1 currently implemented." [dependencies] -serde_cbor = "0.11" +serde_cbor = { version="0.11", features = ["tags"] } serde_repr = "0.1" serde_bytes = "0.11" serde_with = "1.5" diff --git a/src/error.rs b/src/error.rs index 08e2e07..c42b3cc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,6 +22,8 @@ pub enum COSEError { SpecificationError(String), /// Error while serializing or deserializing structures. SerializationError(CborError), + /// Tag is missing or incorrect + TagError(Option), } impl fmt::Display for COSEError { @@ -33,6 +35,8 @@ impl fmt::Display for COSEError { COSEError::UnverifiedSignature => write!(f, "Unverified signature"), COSEError::SpecificationError(e) => write!(f, "Specification error: {}", e), COSEError::SerializationError(e) => write!(f, "Serialization error: {}", e), + COSEError::TagError(Some(tag)) => write!(f, "Tag {} was not expected", tag), + COSEError::TagError(None) => write!(f, "Expected tag is missing"), } } } diff --git a/src/sign.rs b/src/sign.rs index 2752381..d1e3cf7 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -341,23 +341,48 @@ impl COSESign1 { )) } - /// Serializes the structure for transport / storage. `tagged` is currently unused, but it - /// will be used to set the #6.18 tag on the object as allowed by the spec. - pub fn as_bytes(&self, _tagged: bool) -> Result, COSEError> { - let bytes = serde_cbor::to_vec(&self).map_err(COSEError::SerializationError)?; - Ok(bytes) + /// Serializes the structure for transport / storage. If `tagged` is true, the optional #6.18 + /// tag is added to the output. + pub fn as_bytes(&self, tagged: bool) -> Result, COSEError> { + let bytes = if tagged { + serde_cbor::to_vec(&serde_cbor::tags::Tagged::new(Some(18), &self)) + } else { + serde_cbor::to_vec(&self) + }; + bytes.map_err(COSEError::SerializationError) } /// This function deserializes the structure, but doesn't check the contents for correctness - /// at all. + /// at all. Accepts untagged structures or structures with tag 18. pub fn from_bytes(bytes: &[u8]) -> Result { - let cosesign1: Self = + let cosesign1: serde_cbor::tags::Tagged = serde_cbor::from_slice(bytes).map_err(COSEError::SerializationError)?; - let protected = cosesign1.0.as_slice(); + match cosesign1.tag { + None | Some(18) => (), + Some(tag) => return Err(COSEError::TagError(Some(tag))), + } + let protected = cosesign1.value.0.as_slice(); let _: HeaderMap = serde_cbor::from_slice(protected).map_err(COSEError::SerializationError)?; - Ok(cosesign1) + Ok(cosesign1.value) + } + + /// This function deserializes the structure, but doesn't check the contents for correctness + /// at all. Accepts structures with tag 18. + pub fn from_bytes_tagged(bytes: &[u8]) -> Result { + let cosesign1: serde_cbor::tags::Tagged = + serde_cbor::from_slice(bytes).map_err(COSEError::SerializationError)?; + + match cosesign1.tag { + Some(18) => (), + other => return Err(COSEError::TagError(other)), + } + + let protected = cosesign1.value.0.as_slice(); + let _: HeaderMap = + serde_cbor::from_slice(protected).map_err(COSEError::SerializationError)?; + Ok(cosesign1.value) } /// This checks the signature included in the structure against the given public key and @@ -685,6 +710,7 @@ mod tests { // This output was validated against COSE-C implementation let cose_doc = COSESign1::from_bytes(&[ + 0xd9, 0x00, 0x12, /* tag 18 */ 0x84, /* Protected: {1: -7} */ 0x43, 0xA1, 0x01, 0x26, /* Unprotected: {4: '11'} */ 0xA1, 0x04, 0x42, 0x31, 0x31, /* payload: */ @@ -793,6 +819,24 @@ mod tests { ); } + #[test] + fn cose_sign1_ec256_text_tagged() { + let (ec_private, ec_public) = generate_ec256_test_key(); + let mut map = HeaderMap::new(); + map.insert(CborValue::Integer(4), CborValue::Bytes(b"11".to_vec())); + + let cose_doc1 = COSESign1::new(TEXT, &map, &ec_private).unwrap(); + let tagged_bytes = cose_doc1.as_bytes(true).unwrap(); + // Tag 6.18 should be present + assert_eq!(tagged_bytes[0], 6 << 5 | 18); + let cose_doc2 = COSESign1::from_bytes(&tagged_bytes).unwrap(); + + assert_eq!( + cose_doc1.get_payload(None).unwrap(), + cose_doc2.get_payload(Some(&ec_public)).unwrap() + ); + } + #[test] fn cose_sign1_ec384_text() { let (ec_private, ec_public) = generate_ec384_test_key(); @@ -922,4 +966,65 @@ mod tests { assert!(cose_doc.get_payload(Some(&ec_public)).is_err()); } + + #[test] + fn cose_sign1_ec256_invalid_tag() { + let cose_doc = COSESign1::from_bytes(&[ + 0xd3, /* tag 19 */ + 0x84, /* Protected: {1: -7} */ + 0x43, 0xA1, 0x01, 0x26, /* Unprotected: {4: '11'} */ + 0xA1, 0x04, 0x42, 0x31, 0x31, /* payload: */ + 0x58, 0x75, 0x49, 0x74, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0x72, 0x75, 0x74, + 0x68, 0x20, 0x75, 0x6E, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6C, 0x6C, 0x79, 0x20, + 0x61, 0x63, 0x6B, 0x6E, 0x6F, 0x77, 0x6C, 0x65, 0x64, 0x67, 0x65, 0x64, 0x2C, 0x20, + 0x74, 0x68, 0x61, 0x74, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, + 0x6D, 0x61, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x70, 0x6F, 0x73, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6F, 0x6E, 0x20, 0x6F, 0x66, 0x20, 0x61, 0x20, 0x67, 0x6F, 0x6F, 0x64, 0x20, + 0x66, 0x6F, 0x72, 0x74, 0x75, 0x6E, 0x65, 0x2C, 0x20, 0x6D, 0x75, 0x73, 0x74, 0x20, + 0x62, 0x65, 0x20, 0x69, 0x6E, 0x20, 0x77, 0x61, 0x6E, 0x74, 0x20, 0x6F, 0x66, 0x20, + 0x61, 0x20, 0x77, 0x69, 0x66, 0x65, 0x2E, /* Signature - length 32 x 2 */ + 0x58, 0x40, /* R: */ + 0x6E, 0x6D, 0xF6, 0x54, 0x89, 0xEA, 0x3B, 0x01, 0x88, 0x33, 0xF5, 0xFC, 0x4F, 0x84, + 0xF8, 0x1B, 0x4D, 0x5E, 0xFD, 0x5A, 0x09, 0xD5, 0xC6, 0x2F, 0x2E, 0x92, 0x38, 0x5D, + 0xCE, 0x31, 0xE2, 0xD1, /* S: */ + 0x5A, 0x53, 0xA9, 0xF0, 0x75, 0xE8, 0xFB, 0x39, 0x66, 0x9F, 0xCD, 0x4E, 0xB5, 0x22, + 0xC8, 0x5C, 0x92, 0x77, 0x45, 0x2F, 0xA8, 0x57, 0xF5, 0xFE, 0x37, 0x9E, 0xDD, 0xEF, + 0x0F, 0xAB, 0x3C, 0xDD, + ]); + + match cose_doc.unwrap_err() { + COSEError::TagError(Some(19)) => (), + _ => panic!(), + } + } + + #[test] + fn cose_sign1_ec256_missing_tag() { + let cose_doc = COSESign1::from_bytes_tagged(&[ + 0x84, /* Protected: {1: -7} */ + 0x43, 0xA1, 0x01, 0x26, /* Unprotected: {4: '11'} */ + 0xA1, 0x04, 0x42, 0x31, 0x31, /* payload: */ + 0x58, 0x75, 0x49, 0x74, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0x72, 0x75, 0x74, + 0x68, 0x20, 0x75, 0x6E, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6C, 0x6C, 0x79, 0x20, + 0x61, 0x63, 0x6B, 0x6E, 0x6F, 0x77, 0x6C, 0x65, 0x64, 0x67, 0x65, 0x64, 0x2C, 0x20, + 0x74, 0x68, 0x61, 0x74, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, + 0x6D, 0x61, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x70, 0x6F, 0x73, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6F, 0x6E, 0x20, 0x6F, 0x66, 0x20, 0x61, 0x20, 0x67, 0x6F, 0x6F, 0x64, 0x20, + 0x66, 0x6F, 0x72, 0x74, 0x75, 0x6E, 0x65, 0x2C, 0x20, 0x6D, 0x75, 0x73, 0x74, 0x20, + 0x62, 0x65, 0x20, 0x69, 0x6E, 0x20, 0x77, 0x61, 0x6E, 0x74, 0x20, 0x6F, 0x66, 0x20, + 0x61, 0x20, 0x77, 0x69, 0x66, 0x65, 0x2E, /* Signature - length 32 x 2 */ + 0x58, 0x40, /* R: */ + 0x6E, 0x6D, 0xF6, 0x54, 0x89, 0xEA, 0x3B, 0x01, 0x88, 0x33, 0xF5, 0xFC, 0x4F, 0x84, + 0xF8, 0x1B, 0x4D, 0x5E, 0xFD, 0x5A, 0x09, 0xD5, 0xC6, 0x2F, 0x2E, 0x92, 0x38, 0x5D, + 0xCE, 0x31, 0xE2, 0xD1, /* S: */ + 0x5A, 0x53, 0xA9, 0xF0, 0x75, 0xE8, 0xFB, 0x39, 0x66, 0x9F, 0xCD, 0x4E, 0xB5, 0x22, + 0xC8, 0x5C, 0x92, 0x77, 0x45, 0x2F, 0xA8, 0x57, 0xF5, 0xFE, 0x37, 0x9E, 0xDD, 0xEF, + 0x0F, 0xAB, 0x3C, 0xDD, + ]); + + match cose_doc.unwrap_err() { + COSEError::TagError(None) => (), + _ => panic!(), + } + } }