Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## 0.4.0 - TBD

- Breaking change: add `Unassigned(i64)` variant to `RegisteredLabel<t>` and
`RegisteredLabelWithPrivateRange<T>`, to allow use of values that are not yet IANA-assigned.
- Breaking change: remove `CoseError::UnregisteredIanaValue` and
`CoseError::UnregisteredIanaNonPrivateValue` variants.
- Breaking change: alter type of `crit` field in `Header` to support private-use labels (in accordance with
[9052 §3.1](https://datatracker.ietf.org/doc/html/rfc9052#name-common-cose-header-paramete)).

Expand Down
3 changes: 2 additions & 1 deletion examples/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ fn main() -> Result<(), CoseError> {
let aad = b"this is additional data";

// Build a `CoseSign1` object.
let protected = coset::HeaderBuilder::new()
let mut protected = coset::HeaderBuilder::new()
.algorithm(iana::Algorithm::ES256)
.key_id(b"11".to_vec())
.build();
protected.alg = Some(coset::Algorithm::PrivateUse(-49));
let sign1 = coset::CoseSign1Builder::new()
.protected(protected)
.payload(pt.to_vec())
Expand Down
78 changes: 46 additions & 32 deletions src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ pub enum CoseError {
OutOfRangeIntegerValue,
/// Unexpected CBOR item encountered (got, want).
UnexpectedItem(&'static str, &'static str),
/// Unrecognized value in IANA-controlled range (with no private range).
UnregisteredIanaValue,
/// Unrecognized value in neither IANA-controlled range nor private range.
UnregisteredIanaNonPrivateValue,
}

/// Crate-specific Result type
Expand Down Expand Up @@ -107,10 +103,6 @@ impl CoseError {
CoseError::ExtraneousData => write!(f, "extraneous data in CBOR input"),
CoseError::OutOfRangeIntegerValue => write!(f, "out of range integer value"),
CoseError::UnexpectedItem(got, want) => write!(f, "got {got}, expected {want}"),
CoseError::UnregisteredIanaValue => write!(f, "expected recognized IANA value"),
CoseError::UnregisteredIanaNonPrivateValue => {
write!(f, "expected value in IANA or private use range")
}
}
}
}
Expand Down Expand Up @@ -291,6 +283,7 @@ impl AsCborValue for Label {
/// where the allowed integer values are governed by IANA.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum RegisteredLabel<T: EnumI64> {
Unassigned(i64),
Assigned(T),
Text(String),
}
Expand All @@ -306,16 +299,25 @@ impl<T: EnumI64> CborSerializable for RegisteredLabel<T> {}
/// Manual implementation of [`Ord`] to ensure that CBOR canonical ordering is respected.
impl<T: EnumI64> Ord for RegisteredLabel<T> {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(RegisteredLabel::Assigned(i1), RegisteredLabel::Assigned(i2)) => {
Label::Int(i1.to_i64()).cmp(&Label::Int(i2.to_i64()))
let left: i64 = match self {
RegisteredLabel::Assigned(i1) => i1.to_i64(),
RegisteredLabel::Unassigned(i1) => *i1,
RegisteredLabel::Text(t1) => {
return match other {
RegisteredLabel::Assigned(_i2) => Ordering::Greater,
RegisteredLabel::Unassigned(_i2) => Ordering::Greater,
RegisteredLabel::Text(t2) => t1.len().cmp(&t2.len()).then(t1.cmp(t2)),
};
}
(RegisteredLabel::Assigned(_i1), RegisteredLabel::Text(_t2)) => Ordering::Less,
(RegisteredLabel::Text(_t1), RegisteredLabel::Assigned(_i2)) => Ordering::Greater,
(RegisteredLabel::Text(t1), RegisteredLabel::Text(t2)) => {
t1.len().cmp(&t2.len()).then(t1.cmp(t2))
}
}
};
// The `self`/`left` value is an integer if we reach here.

let right: i64 = match other {
RegisteredLabel::Assigned(i2) => i2.to_i64(),
RegisteredLabel::Unassigned(i2) => *i2,
RegisteredLabel::Text(_t2) => return Ordering::Less,
};
Label::Int(left).cmp(&Label::Int(right))
}
}

Expand All @@ -329,10 +331,11 @@ impl<T: EnumI64> AsCborValue for RegisteredLabel<T> {
fn from_cbor_value(value: Value) -> Result<Self> {
match value {
Value::Integer(i) => {
if let Some(a) = T::from_i64(i.try_into()?) {
let i: i64 = i.try_into()?;
if let Some(a) = T::from_i64(i) {
Ok(RegisteredLabel::Assigned(a))
} else {
Err(CoseError::UnregisteredIanaValue)
Ok(RegisteredLabel::Unassigned(i))
}
}
Value::Text(t) => Ok(RegisteredLabel::Text(t)),
Expand All @@ -342,6 +345,7 @@ impl<T: EnumI64> AsCborValue for RegisteredLabel<T> {

fn to_cbor_value(self) -> Result<Value> {
Ok(match self {
RegisteredLabel::Unassigned(e) => Value::from(e),
RegisteredLabel::Assigned(e) => Value::from(e.to_i64()),
RegisteredLabel::Text(t) => Value::Text(t),
})
Expand All @@ -354,6 +358,7 @@ impl<T: EnumI64> AsCborValue for RegisteredLabel<T> {
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum RegisteredLabelWithPrivate<T: EnumI64 + WithPrivateRange> {
PrivateUse(i64),
Unassigned(i64),
Assigned(T),
Text(String),
}
Expand All @@ -369,18 +374,26 @@ impl<T: EnumI64 + WithPrivateRange> CborSerializable for RegisteredLabelWithPriv
/// Manual implementation of [`Ord`] to ensure that CBOR canonical ordering is respected.
impl<T: EnumI64 + WithPrivateRange> Ord for RegisteredLabelWithPrivate<T> {
fn cmp(&self, other: &Self) -> Ordering {
use RegisteredLabelWithPrivate::{Assigned, PrivateUse, Text};
match (self, other) {
(Assigned(i1), Assigned(i2)) => Label::Int(i1.to_i64()).cmp(&Label::Int(i2.to_i64())),
(Assigned(i1), PrivateUse(i2)) => Label::Int(i1.to_i64()).cmp(&Label::Int(*i2)),
(PrivateUse(i1), Assigned(i2)) => Label::Int(*i1).cmp(&Label::Int(i2.to_i64())),
(PrivateUse(i1), PrivateUse(i2)) => Label::Int(*i1).cmp(&Label::Int(*i2)),
(Assigned(_i1), Text(_t2)) => Ordering::Less,
(PrivateUse(_i1), Text(_t2)) => Ordering::Less,
(Text(_t1), Assigned(_i2)) => Ordering::Greater,
(Text(_t1), PrivateUse(_i2)) => Ordering::Greater,
(Text(t1), Text(t2)) => t1.len().cmp(&t2.len()).then(t1.cmp(t2)),
}
use RegisteredLabelWithPrivate::{Assigned, PrivateUse, Text, Unassigned};
let left: i64 = match self {
Assigned(i1) => i1.to_i64(),
Unassigned(i1) | PrivateUse(i1) => *i1,
Text(t1) => {
return match other {
Assigned(_i2) => Ordering::Greater,
Unassigned(_i2) | PrivateUse(_i2) => Ordering::Greater,
Text(t2) => t1.len().cmp(&t2.len()).then(t1.cmp(t2)),
};
}
};
// The `self`/`left` value is an integer if we reach here.

let right: i64 = match other {
Assigned(i2) => i2.to_i64(),
Unassigned(i2) | PrivateUse(i2) => *i2,
Text(_t2) => return Ordering::Less,
};
Label::Int(left).cmp(&Label::Int(right))
}
}

Expand All @@ -400,7 +413,7 @@ impl<T: EnumI64 + WithPrivateRange> AsCborValue for RegisteredLabelWithPrivate<T
} else if T::is_private(i) {
Ok(RegisteredLabelWithPrivate::PrivateUse(i))
} else {
Err(CoseError::UnregisteredIanaNonPrivateValue)
Ok(RegisteredLabelWithPrivate::Unassigned(i))
}
}
Value::Text(t) => Ok(RegisteredLabelWithPrivate::Text(t)),
Expand All @@ -410,6 +423,7 @@ impl<T: EnumI64 + WithPrivateRange> AsCborValue for RegisteredLabelWithPrivate<T
fn to_cbor_value(self) -> Result<Value> {
Ok(match self {
RegisteredLabelWithPrivate::PrivateUse(i) => Value::from(i),
RegisteredLabelWithPrivate::Unassigned(i) => Value::from(i),
RegisteredLabelWithPrivate::Assigned(i) => Value::from(i.to_i64()),
RegisteredLabelWithPrivate::Text(t) => Value::Text(t),
})
Expand Down
12 changes: 6 additions & 6 deletions src/common/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ fn test_registered_label_encode() {
(RegisteredLabel::Assigned(iana::Algorithm::A192GCM), "02"),
(RegisteredLabel::Assigned(iana::Algorithm::EdDSA), "27"),
(RegisteredLabel::Text("abc".to_owned()), "63616263"),
(RegisteredLabel::Unassigned(9), "09"),
(RegisteredLabel::Unassigned(-20_000), "394e1f"),
];

for (i, (label, label_data)) in tests.iter().enumerate() {
Expand Down Expand Up @@ -247,8 +249,6 @@ fn test_registered_label_decode_fail() {
let tests = [
("43010203", "expected int/tstr"),
("", "decode CBOR failure: Io(EndOfFile"),
("09", "expected recognized IANA value"),
("394e1f", "expected recognized IANA value"),
];
for (label_data, err_msg) in tests.iter() {
let data = hex::decode(label_data).unwrap();
Expand All @@ -266,7 +266,7 @@ iana_registry! {

impl WithPrivateRange for TestPrivateLabel {
fn is_private(i: i64) -> bool {
i > 10 || i < 1000
!(-50_000..=10).contains(&i)
}
}

Expand All @@ -286,14 +286,16 @@ fn test_registered_label_with_private_encode() {
"3a0001116f",
),
(RegisteredLabelWithPrivate::PrivateUse(11), "0b"),
(RegisteredLabelWithPrivate::Unassigned(9), "09"),
(RegisteredLabelWithPrivate::Unassigned(-20_000), "394e1f"),
];

for (i, (label, label_data)) in tests.iter().enumerate() {
let got = label.clone().to_vec().unwrap();
assert_eq!(*label_data, hex::encode(&got), "case {i}");

let got = RegisteredLabelWithPrivate::from_slice(&got).unwrap();
assert_eq!(*label, got);
assert_eq!(*label, got, "case {i}: {label_data}");
}
}

Expand Down Expand Up @@ -353,8 +355,6 @@ fn test_registered_label_with_private_decode_fail() {
let tests = [
("43010203", "expected int/tstr"),
("", "decode CBOR failure: Io(EndOfFile"),
("09", "expected value in IANA or private use range"),
("394e1f", "expected value in IANA or private use range"),
];
for (label_data, err_msg) in tests.iter() {
let data = hex::decode(label_data).unwrap();
Expand Down
23 changes: 13 additions & 10 deletions src/context/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,19 @@ fn test_context_encode() {
"41", "03", // 1-bstr
),
),
(
CoseKdfContext {
algorithm_id: Algorithm::Unassigned(8),
..CoseKdfContext::default()
},
concat!(
"84", // 4-tuple
"08", // int : unassigned value
"83", "f6f6f6", // 3-tuple: [nil, nil, nil]
"83", "f6f6f6", // 3-tuple: [nil, nil, nil]
"82", "0040", // 2-tuple: [0, 0-bstr]
),
),
];
for (i, (key, key_data)) in tests.iter().enumerate() {
let got = key.clone().to_vec().unwrap();
Expand Down Expand Up @@ -238,16 +251,6 @@ fn test_context_decode_fail() {
),
"decode CBOR failure: Io(EndOfFile",
),
(
concat!(
"84", // 4-tuple
"08", // int : unassigned value
"83", "f6f6f6", // 3-tuple: [nil, nil, nil]
"83", "f6f6f6", // 3-tuple: [nil, nil, nil]
"82", "0040", // 2-tuple: [0, 0-bstr]
),
"expected value in IANA or private use range",
),
(
concat!(
"84", // 4-tuple
Expand Down
4 changes: 3 additions & 1 deletion src/cwt/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ fn test_cwt_encode() {
let got = claims.clone().to_vec().unwrap();
assert_eq!(*claims_data, hex::encode(&got), "case {i}");

let got = ClaimsSet::from_slice(&got).unwrap();
let got = ClaimsSet::from_slice(&got).unwrap_or_else(|e| {
panic!("deserialize failed on case {}, {}: {:?}", i, claims_data, e)
});
assert_eq!(*claims, got);
}
}
Expand Down
34 changes: 20 additions & 14 deletions src/header/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,26 @@ fn test_header_encode() {
"3a00010000", // crit => 1-arr [-65537]
),
),
(
Header {
content_type: Some(ContentType::Unassigned(1542)),
..Default::default()
},
concat!(
"a1", // 1-map
"03", "19", "0606", // 3 (content-type) => unassigned value 1542
),
),
(
Header {
alg: Some(Algorithm::Unassigned(8)),
..Default::default()
},
concat!(
"a1", // 1-map
"01", "08", // 1 (alg) => unassigned value 8
),
),
];
for (i, (header, header_data)) in tests.iter().enumerate() {
let got = header.clone().to_vec().unwrap();
Expand Down Expand Up @@ -213,13 +233,6 @@ fn test_header_decode_fail() {
),
"extraneous data in CBOR input",
),
(
concat!(
"a1", // 1-map
"01", "08", // 1 (alg) => invalid value
),
"expected value in IANA or private use range",
),
(
concat!(
"a1", // 1-map
Expand Down Expand Up @@ -255,13 +268,6 @@ fn test_header_decode_fail() {
),
"expected int/tstr",
),
(
concat!(
"a1", // 1-map
"03", "19", "0606", // 3 (content-type) => invalid value 1542
),
"expected recognized IANA value",
),
(
concat!(
"a1", // 1-map
Expand Down
Loading
Loading