1use serde::{Deserialize, Serialize};
19
20pub const COSE_SIGN1_TAG: u64 = 18;
22
23pub const COSE_ALG_PARAM: i32 = 1;
25
26pub mod alg {
28 pub const EDDSA: i32 = -8;
30 pub const ES256: i32 = -7;
32 pub const ED25519: i32 = -19;
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
38pub struct CoseSign1 {
39 pub protected_bytes: Vec<u8>,
41 pub unprotected_bytes: Vec<u8>,
43 pub payload: Vec<u8>,
45 pub signature: Vec<u8>,
47}
48
49#[derive(Debug, thiserror::Error)]
51pub enum CoseError {
52 #[error("cbor encoding error: {0}")]
54 Encode(String),
55 #[error("cbor decoding error: {0}")]
57 Decode(String),
58}
59
60impl CoseSign1 {
61 pub fn new(algorithm: i32, payload: &[u8], signature: &[u8]) -> Result<Self, CoseError> {
64 let protected = encode_protected_header(algorithm)?;
65 Ok(Self {
66 protected_bytes: protected,
67 unprotected_bytes: Vec::new(),
68 payload: payload.to_vec(),
69 signature: signature.to_vec(),
70 })
71 }
72
73 pub fn encode(&self) -> Result<Vec<u8>, CoseError> {
75 encode_cose_sign1(self)
76 }
77
78 pub fn decode(bytes: &[u8]) -> Result<Self, CoseError> {
80 decode_cose_sign1(bytes)
81 }
82
83 pub fn algorithm(&self) -> Result<i32, CoseError> {
85 decode_algorithm(&self.protected_bytes)
86 }
87}
88
89const CBOR_MAJOR_UNSIGNED: u8 = 0;
92const CBOR_MAJOR_BYTE_STRING: u8 = 2;
93const CBOR_MAJOR_ARRAY: u8 = 4;
94const CBOR_MAJOR_MAP: u8 = 5;
95
96fn cbor_unsigned_int(n: u64) -> Vec<u8> {
97 if n <= 23 {
98 vec![(CBOR_MAJOR_UNSIGNED << 5) | n as u8]
99 } else if n <= u8::MAX as u64 {
100 let mut v = vec![(CBOR_MAJOR_UNSIGNED << 5) | 24];
101 v.push(n as u8);
102 v
103 } else if n <= u16::MAX as u64 {
104 let mut v = vec![(CBOR_MAJOR_UNSIGNED << 5) | 25];
105 v.extend_from_slice(&(n as u16).to_be_bytes());
106 v
107 } else if n <= u32::MAX as u64 {
108 let mut v = vec![(CBOR_MAJOR_UNSIGNED << 5) | 26];
109 v.extend_from_slice(&(n as u32).to_be_bytes());
110 v
111 } else {
112 let mut v = vec![(CBOR_MAJOR_UNSIGNED << 5) | 27];
113 v.extend_from_slice(&n.to_be_bytes());
114 v
115 }
116}
117
118fn cbor_negative_int(n: i64) -> Vec<u8> {
119 let unsigned = (-(n as i128) - 1) as u64;
122 if unsigned <= 23 {
123 vec![(1u8 << 5) | unsigned as u8]
124 } else if unsigned <= u8::MAX as u64 {
125 let mut v = vec![(1u8 << 5) | 24];
126 v.push(unsigned as u8);
127 v
128 } else if unsigned <= u16::MAX as u64 {
129 let mut v = vec![(1u8 << 5) | 25];
130 v.extend_from_slice(&(unsigned as u16).to_be_bytes());
131 v
132 } else if unsigned <= u32::MAX as u64 {
133 let mut v = vec![(1u8 << 5) | 26];
134 v.extend_from_slice(&(unsigned as u32).to_be_bytes());
135 v
136 } else {
137 let mut v = vec![(1u8 << 5) | 27];
138 v.extend_from_slice(&unsigned.to_be_bytes());
139 v
140 }
141}
142
143fn cbor_byte_string(bytes: &[u8]) -> Vec<u8> {
144 let len = bytes.len();
145 let mut v = vec![(CBOR_MAJOR_BYTE_STRING << 5)];
146 if len <= 23 {
147 v[0] |= len as u8;
148 v.extend_from_slice(bytes);
149 } else if len <= u8::MAX as usize {
150 v[0] |= 24;
151 v.push(len as u8);
152 v.extend_from_slice(bytes);
153 } else if len <= u16::MAX as usize {
154 v[0] |= 25;
155 v.extend_from_slice(&(len as u16).to_be_bytes());
156 v.extend_from_slice(bytes);
157 } else {
158 v[0] |= 26;
159 v.extend_from_slice(&(len as u32).to_be_bytes());
160 v.extend_from_slice(bytes);
161 }
162 v
163}
164
165fn cbor_array_header(count: usize) -> Vec<u8> {
166 let mut v = vec![(CBOR_MAJOR_ARRAY << 5)];
167 if count <= 23 {
168 v[0] |= count as u8;
169 } else if count <= u8::MAX as usize {
170 v[0] |= 24;
171 v.push(count as u8);
172 } else {
173 v[0] |= 25;
174 v.extend_from_slice(&(count as u16).to_be_bytes());
175 }
176 v
177}
178
179fn cbor_map_header(count: usize) -> Vec<u8> {
180 let mut v = vec![(CBOR_MAJOR_MAP << 5)];
181 if count <= 23 {
182 v[0] |= count as u8;
183 } else {
184 v[0] |= 24;
185 v.push(count as u8);
186 }
187 v
188}
189
190fn encode_protected_header(alg: i32) -> Result<Vec<u8>, CoseError> {
191 let mut buf = cbor_map_header(1);
192 buf.extend(cbor_unsigned_int(COSE_ALG_PARAM as u64));
193 if alg < 0 {
194 buf.extend(cbor_negative_int(alg as i64));
195 } else {
196 buf.extend(cbor_unsigned_int(alg as u64));
197 }
198 Ok(buf)
199}
200
201fn encode_cose_sign1(cose: &CoseSign1) -> Result<Vec<u8>, CoseError> {
202 let mut out = vec![];
203 out.push(0xC0 | COSE_SIGN1_TAG as u8);
206
207 out.extend(cbor_array_header(4));
209
210 out.extend(cbor_byte_string(&cose.protected_bytes));
212 out.extend(cbor_byte_string(&cose.unprotected_bytes));
214 out.extend(cbor_byte_string(&cose.payload));
216 out.extend(cbor_byte_string(&cose.signature));
218
219 Ok(out)
220}
221
222struct CborReader<'a> {
225 bytes: &'a [u8],
226 pos: usize,
227}
228
229impl<'a> CborReader<'a> {
230 fn new(bytes: &'a [u8]) -> Self {
231 Self { bytes, pos: 0 }
232 }
233
234 fn read_u8(&mut self) -> Option<u8> {
235 self.bytes.get(self.pos).map(|&b| {
236 self.pos += 1;
237 b
238 })
239 }
240
241 fn read_bytes(&mut self, n: usize) -> Option<&'a [u8]> {
242 let end = self.pos.checked_add(n)?;
246 if end > self.bytes.len() {
247 return None;
248 }
249 let result = &self.bytes[self.pos..end];
250 self.pos = end;
251 Some(result)
252 }
253
254 fn check_count(&self, count: usize) -> Option<()> {
260 if count > self.bytes.len() - self.pos {
261 None
262 } else {
263 Some(())
264 }
265 }
266
267 fn read(&mut self) -> Option<CborValue<'a>> {
268 let initial = self.read_u8()?;
269 let major = (initial & 0xE0) >> 5;
270 let info = initial & 0x1F;
271
272 match major {
273 0 => {
274 let n = self.read_uint(info)?;
275 Some(CborValue::Unsigned(n))
276 }
277 1 => {
278 let n = self.read_uint(info)?;
279 Some(CborValue::Negative(n))
280 }
281 2 => {
282 let len = self.read_uint(info)? as usize;
283 let bytes = self.read_bytes(len)?;
284 Some(CborValue::Bytes(bytes))
285 }
286 4 => {
287 let count = self.read_uint(info)? as usize;
288 self.check_count(count)?;
289 let mut items = Vec::with_capacity(count);
290 for _ in 0..count {
291 items.push(self.read()?);
292 }
293 Some(CborValue::Array(items))
294 }
295 5 => {
296 let count = self.read_uint(info)? as usize;
297 self.check_count(count)?;
298 let mut entries = Vec::with_capacity(count);
299 for _ in 0..count {
300 let k = self.read()?;
301 let v = self.read()?;
302 entries.push((k, v));
303 }
304 Some(CborValue::Map(entries))
305 }
306 6 => {
307 let _tag = self.read_uint(info)?;
309 self.read()
310 }
311 _ => None,
312 }
313 }
314
315 fn read_uint(&mut self, info: u8) -> Option<u64> {
316 match info {
317 n if n <= 23 => Some(n as u64),
318 24 => Some(self.read_u8()? as u64),
319 25 => {
320 let bytes = self.read_bytes(2)?;
321 Some(u16::from_be_bytes(bytes.try_into().ok()?) as u64)
322 }
323 26 => {
324 let bytes = self.read_bytes(4)?;
325 Some(u32::from_be_bytes(bytes.try_into().ok()?) as u64)
326 }
327 27 => {
328 let bytes = self.read_bytes(8)?;
329 Some(u64::from_be_bytes(bytes.try_into().ok()?))
330 }
331 _ => None,
332 }
333 }
334}
335
336#[derive(Debug, Clone)]
337enum CborValue<'a> {
338 Unsigned(u64),
339 Negative(u64),
340 Bytes(&'a [u8]),
341 Array(Vec<CborValue<'a>>),
342 Map(Vec<(CborValue<'a>, CborValue<'a>)>),
343}
344
345fn decode_cose_sign1(bytes: &[u8]) -> Result<CoseSign1, CoseError> {
346 let mut reader = CborReader::new(bytes);
349 let array_value = reader
350 .read()
351 .ok_or_else(|| CoseError::Decode("empty".into()))?;
352 decode_cose_sign1_array(&array_value, bytes)
353}
354
355fn decode_cose_sign1_array(value: &CborValue<'_>, _bytes: &[u8]) -> Result<CoseSign1, CoseError> {
356 let items = match value {
357 CborValue::Array(v) => v,
358 _ => return Err(CoseError::Decode("expected array".into())),
359 };
360 if items.len() != 4 {
361 return Err(CoseError::Decode("expected 4 elements".into()));
362 }
363 let protected = match &items[0] {
364 CborValue::Bytes(b) => b.to_vec(),
365 _ => return Err(CoseError::Decode("protected must be bstr".into())),
366 };
367 let unprotected = match &items[1] {
368 CborValue::Bytes(b) => b.to_vec(),
369 _ => return Err(CoseError::Decode("unprotected must be bstr".into())),
370 };
371 let payload = match &items[2] {
372 CborValue::Bytes(b) => b.to_vec(),
373 _ => return Err(CoseError::Decode("payload must be bstr".into())),
374 };
375 let signature = match &items[3] {
376 CborValue::Bytes(b) => b.to_vec(),
377 _ => return Err(CoseError::Decode("signature must be bstr".into())),
378 };
379 Ok(CoseSign1 {
380 protected_bytes: protected,
381 unprotected_bytes: unprotected,
382 payload,
383 signature,
384 })
385}
386
387fn decode_algorithm(protected_bytes: &[u8]) -> Result<i32, CoseError> {
388 let mut reader = CborReader::new(protected_bytes);
389 let map = reader
390 .read()
391 .ok_or_else(|| CoseError::Decode("empty protected header".into()))?;
392 let entries = match map {
393 CborValue::Map(e) => e,
394 _ => return Err(CoseError::Decode("protected header must be a map".into())),
395 };
396 for (k, v) in entries {
397 if let CborValue::Unsigned(key) = k {
398 if key == COSE_ALG_PARAM as u64 {
399 match v {
400 CborValue::Unsigned(n) => return Ok(n as i32),
401 CborValue::Negative(n) => {
402 let neg = -((n as i64) + 1);
404 return Ok(neg as i32);
405 }
406 _ => {}
407 }
408 }
409 }
410 }
411 Err(CoseError::Decode("algorithm not found".into()))
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
419 fn adversarial_u64_max_length_is_error_not_panic() {
420 let evil = [0x5B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
424 let result = std::panic::catch_unwind(|| CoseSign1::decode(&evil));
425 assert!(
426 result.is_ok(),
427 "decoder must not panic on adversarial length"
428 );
429 assert!(result.unwrap().is_err(), "u64::MAX length must not decode");
430 }
431
432 #[test]
433 fn adversarial_huge_length_at_offset_is_error_not_panic() {
434 let mut evil = vec![0x83, 0x01]; evil.extend_from_slice(&[0x5B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE]);
438 let result = std::panic::catch_unwind(|| CoseSign1::decode(&evil));
439 assert!(result.is_ok(), "decoder must not panic");
440 assert!(result.unwrap().is_err());
441 }
442
443 #[test]
444 fn truncated_input_is_error_not_panic() {
445 for len in 0..8 {
446 let truncated = &b"\xD2\x84\x43\x01\x02\x03\x04\xA0"[..len];
447 assert!(CoseSign1::decode(truncated).is_err());
448 }
449 }
450
451 #[test]
452 fn create_and_extract_algorithm() {
453 let cose = CoseSign1::new(alg::ES256, b"payload", b"signature").unwrap();
454 assert_eq!(cose.algorithm().unwrap(), alg::ES256);
455 }
456
457 #[test]
458 fn round_trip_preserves_payload_and_signature() {
459 let original = CoseSign1::new(alg::ED25519, b"my payload", b"my sig").unwrap();
460 let encoded = original.encode().unwrap();
461 let decoded = CoseSign1::decode(&encoded).unwrap();
462 assert_eq!(decoded.payload, b"my payload");
463 assert_eq!(decoded.signature, b"my sig");
464 }
465
466 #[test]
467 fn cbor_unsigned_encoding_zero() {
468 assert_eq!(cbor_unsigned_int(0), vec![0x00]);
469 }
470
471 #[test]
472 fn cbor_unsigned_encoding_small() {
473 assert_eq!(cbor_unsigned_int(23), vec![23]);
474 }
475
476 #[test]
477 fn cbor_unsigned_encoding_one_byte() {
478 assert_eq!(cbor_unsigned_int(200), vec![24, 200]);
479 }
480
481 #[test]
482 fn cbor_byte_string_empty() {
483 assert_eq!(cbor_byte_string(b""), vec![0x40]);
484 }
485
486 #[test]
487 fn cbor_byte_string_short() {
488 assert_eq!(cbor_byte_string(b"abc"), vec![0x43, b'a', b'b', b'c']);
489 }
490
491 #[test]
492 fn negative_algorithm_encoding() {
493 let cose = CoseSign1::new(alg::EDDSA, b"", b"").unwrap();
494 assert_eq!(cose.algorithm().unwrap(), alg::EDDSA);
495 }
496
497 #[test]
498 fn decode_empty_payload() {
499 let cose = CoseSign1::new(alg::ES256, b"", b"sig").unwrap();
500 let encoded = cose.encode().unwrap();
501 let decoded = CoseSign1::decode(&encoded).unwrap();
502 assert!(decoded.payload.is_empty());
503 }
504
505 #[test]
506 fn decode_large_signature() {
507 let sig = vec![0xAA; 256];
508 let cose = CoseSign1::new(alg::ES256, b"msg", &sig).unwrap();
509 let encoded = cose.encode().unwrap();
510 let decoded = CoseSign1::decode(&encoded).unwrap();
511 assert_eq!(decoded.signature, sig);
512 }
513}