darkbio_crypto/cose/types.rs
1// crypto-rs: cryptography primitives and wrappers
2// Copyright 2025 Dark Bio AG. All rights reserved.
3//
4// Use of this source code is governed by a BSD-style
5// license that can be found in the LICENSE file.
6
7//! COSE structure types with CBOR serialization.
8
9use crate::cbor::Cbor;
10use crate::{xdsa, xhpke};
11
12/// Private COSE header label for Unix timestamp.
13pub const HEADER_TIMESTAMP: i64 = -70002;
14
15/// Protected header for [`CoseSign1`].
16#[derive(Debug, Clone, PartialEq, Eq, Cbor)]
17pub struct SigProtectedHeader {
18 /// Algorithm identifier (COSE header label 1)
19 #[cbor(key = 1)]
20 pub algorithm: i64,
21 /// Critical headers that must be understood (COSE header label 2)
22 #[cbor(key = 2)]
23 pub crit: CritHeader,
24 /// Key identifier - signer's fingerprint (COSE header label 4)
25 #[cbor(key = 4)]
26 pub kid: xdsa::Fingerprint,
27 /// Unix timestamp in seconds (private header label)
28 #[cbor(key = -70002)]
29 pub timestamp: i64,
30}
31
32/// Critical headers list per RFC 9052.
33/// Implementations must reject messages with unknown crit labels.
34#[derive(Debug, Clone, PartialEq, Eq, Cbor)]
35#[cbor(array)]
36pub struct CritHeader {
37 /// HeaderTimestamp label - must be understood
38 pub timestamp: i64,
39}
40
41/// Protected header for [`CoseEncrypt0`].
42#[derive(Debug, Clone, PartialEq, Eq, Cbor)]
43pub struct EncProtectedHeader {
44 /// Algorithm identifier (COSE header label 1)
45 #[cbor(key = 1)]
46 pub algorithm: i64,
47 /// Key identifier - recipient's fingerprint (COSE header label 4)
48 #[cbor(key = 4)]
49 pub kid: xhpke::Fingerprint,
50}
51
52/// Empty unprotected header map (for [`CoseSign1`]).
53#[derive(Debug, Clone, PartialEq, Eq, Cbor)]
54pub struct EmptyHeader {}
55
56/// Unprotected header containing the encapsulated key (for [`CoseEncrypt0`]).
57#[derive(Debug, Clone, PartialEq, Eq, Cbor)]
58pub struct EncapKeyHeader {
59 /// Encapsulated key (COSE header label -4)
60 #[cbor(key = -4)]
61 pub encap_key: Vec<u8>,
62}
63
64/// COSE_Sign1 structure per RFC 9052 Section 4.2.
65///
66/// ```text
67/// COSE_Sign1 = [
68/// protected: bstr,
69/// unprotected: header_map,
70/// payload: bstr / null,
71/// signature: bstr
72/// ]
73/// ```
74#[derive(Debug, Clone, PartialEq, Eq, Cbor)]
75#[cbor(array)]
76pub struct CoseSign1 {
77 /// Protected header (CBOR-encoded map, wrapped as bstr)
78 pub protected: Vec<u8>,
79 /// Unprotected header map (empty for signatures)
80 pub unprotected: EmptyHeader,
81 /// Payload bytes (null for detached payload)
82 pub payload: Option<Vec<u8>>,
83 /// Signature (fixed size for xDSA)
84 pub signature: xdsa::Signature,
85}
86
87/// COSE_Encrypt0 structure per RFC 9052 Section 5.2.
88///
89/// ```text
90/// COSE_Encrypt0 = [
91/// protected: bstr,
92/// unprotected: header_map,
93/// ciphertext: bstr
94/// ]
95/// ```
96#[derive(Debug, Clone, PartialEq, Eq, Cbor)]
97#[cbor(array)]
98pub struct CoseEncrypt0 {
99 /// Protected header (CBOR-encoded map, wrapped as bstr)
100 pub protected: Vec<u8>,
101 /// Unprotected header map (contains encapsulated key)
102 pub unprotected: EncapKeyHeader,
103 /// Ciphertext bytes
104 pub ciphertext: Vec<u8>,
105}
106
107/// Sig_structure for computing signatures per RFC 9052 Section 4.4.
108///
109/// ```text
110/// Sig_structure = [
111/// context: "Signature1",
112/// body_protected: bstr,
113/// external_aad: bstr,
114/// payload: bstr
115/// ]
116/// ```
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub struct SigStructure<'a> {
119 /// Fixed context string, `Signature1` for [`CoseSign1`].
120 pub context: &'static str,
121 /// The protected header, already CBOR encoded.
122 pub protected: &'a [u8],
123 /// Data authenticated but not transmitted, the domain and the caller's
124 /// message.
125 pub external_aad: &'a [u8],
126 /// The signed payload bytes. This crate's detached convention uses an empty
127 /// payload and authenticates the caller's message through `external_aad`.
128 pub payload: &'a [u8],
129}
130
131impl crate::cbor::Encode for SigStructure<'_> {
132 fn encode_cbor_to(&self, buf: &mut Vec<u8>) -> Result<(), crate::cbor::Error> {
133 crate::cbor::encode_array_header_to(buf, 4);
134 self.context.encode_cbor_to(buf)?;
135 self.protected.encode_cbor_to(buf)?;
136 self.external_aad.encode_cbor_to(buf)?;
137 self.payload.encode_cbor_to(buf)?;
138 Ok(())
139 }
140}
141
142/// Enc_structure for computing AAD per RFC 9052 Section 5.3.
143///
144/// ```text
145/// Enc_structure = [
146/// context: "Encrypt0",
147/// protected: bstr,
148/// external_aad: bstr
149/// ]
150/// ```
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub struct EncStructure<'a> {
153 /// Fixed context string, `Encrypt0` for [`CoseEncrypt0`].
154 pub context: &'static str,
155 /// The protected header, already CBOR encoded.
156 pub protected: &'a [u8],
157 /// Data authenticated but not transmitted, the caller's message.
158 pub external_aad: &'a [u8],
159}
160
161impl crate::cbor::Encode for EncStructure<'_> {
162 fn encode_cbor_to(&self, buf: &mut Vec<u8>) -> Result<(), crate::cbor::Error> {
163 crate::cbor::encode_array_header_to(buf, 3);
164 self.context.encode_cbor_to(buf)?;
165 self.protected.encode_cbor_to(buf)?;
166 self.external_aad.encode_cbor_to(buf)?;
167 Ok(())
168 }
169}