Skip to main content

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}