Skip to content

Commit

Permalink
header: fix authentication when protected header is zero-length map
Browse files Browse the repository at this point in the history
COSE allows an empty protected header to be encoded as a zero-length
map, even though the standard encourages encoding empty protected
headers as a zero-length string (RFC 2119 SHOULD according to RFC 9052,
Section 3).

However, according to RFC 9052, Section 4.4, 5.3 and 6.3, even if the
header is encoded as a zero-length map, the structure used for
authentication should not include the empty map if the protected header
is empty ("if there are no protected attributes, a zero-length byte
string is used").

This commit ensures that this behavior is implemented in coset, which
previously did include the zero length map (encoded as h'a0') in
signature calculation.
This previously caused signature verification failures, e.g. when
verifying the CoseSign1 object provided in
https://github.com/cose-wg/Examples/blob/master/sign1-tests/sign-pass-03.json
using coset.
  • Loading branch information
pulsastrix committed Jul 28, 2024
1 parent 0520f66 commit ccb85ce
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/encrypt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ pub fn enc_structure_data(
) -> Vec<u8> {
let arr = vec![
Value::Text(context.text().to_owned()),
protected.cbor_bstr().expect("failed to serialize header"), // safe: always serializable
protected.to_be_authenticated().expect("failed to serialize header"), // safe: always serializable
Value::Bytes(external_aad.to_vec()),
];

Expand Down
13 changes: 13 additions & 0 deletions src/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,19 @@ impl ProtectedHeader {
))
}

pub fn to_be_authenticated(self) -> Result<Value> {
let result = self.cbor_bstr()?;
// protected header might have been encoded as a zero length map, only containing
// the byte 0xA0 (see RFC 9052, Section 3).
// However, this byte should not be included during authentication (RFC 9052, Section 4.4,
// 5.3 and 6.3), so we need to return an empty byte string here instead.
if result.eq(&Value::Bytes(vec![0xA0])) {
Ok(Value::Bytes(vec![]))
} else {
Ok(result)
}
}

/// Indicate whether the `ProtectedHeader` is empty.
pub fn is_empty(&self) -> bool {
self.header.is_empty()
Expand Down
2 changes: 1 addition & 1 deletion src/mac/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ pub fn mac_structure_data(
) -> Vec<u8> {
let arr = vec![
Value::Text(context.text().to_owned()),
protected.cbor_bstr().expect("failed to serialize header"), // safe: always serializable
protected.to_be_authenticated().expect("failed to serialize header"), // safe: always serializable
Value::Bytes(external_aad.to_vec()),
Value::Bytes(payload.to_vec()),
];
Expand Down
9 changes: 6 additions & 3 deletions src/sign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,13 +518,16 @@ pub fn sig_structure_data(
aad: &[u8],
payload: &[u8],
) -> Vec<u8> {

let mut arr = vec![
Value::Text(context.text().to_owned()),
body.cbor_bstr().expect("failed to serialize header"), // safe: always serializable
body.to_be_authenticated().expect("failed to serialize header"), // safe: always serializable
];
if let Some(sign) = sign {
arr.push(sign.cbor_bstr().expect("failed to serialize header")); // safe: always
// serializable
arr.push(
sign.to_be_authenticated()
.expect("failed to serialize header") // safe: always serializable
);
}
arr.push(Value::Bytes(aad.to_vec()));
arr.push(Value::Bytes(payload.to_vec()));
Expand Down

0 comments on commit ccb85ce

Please sign in to comment.