bitwarden_crypto/signing/
cose.rs

1//! This file contains helper functions to aid in COSE deserialization
2
3use coset::{
4    CoseKey, Label, ProtectedHeader, RegisteredLabel,
5    iana::{EllipticCurve, EnumI64, OkpKeyParameter},
6};
7
8use super::SigningNamespace;
9use crate::{
10    CryptoError, KEY_ID_SIZE,
11    cose::SIGNING_NAMESPACE,
12    error::{EncodingError, SignatureError},
13    keys::KeyId,
14};
15
16/// Helper function to extract the namespace from a `ProtectedHeader`. The namespace is a custom
17/// header set on the protected headers of the signature object.
18pub(super) fn namespace(
19    protected_header: &ProtectedHeader,
20) -> Result<SigningNamespace, CryptoError> {
21    let namespace = protected_header
22        .header
23        .rest
24        .iter()
25        .find_map(|(key, value)| {
26            if let Label::Int(key) = key {
27                if *key == SIGNING_NAMESPACE {
28                    return value.as_integer();
29                }
30            }
31            None
32        })
33        .ok_or(SignatureError::InvalidNamespace)?;
34
35    SigningNamespace::try_from(i128::from(namespace))
36}
37
38/// Helper function to extract the content type from a `ProtectedHeader`. The content type is a
39/// standardized header set on the protected headers of the signature object. Currently we only
40/// support registered values, but PrivateUse values are also allowed in the COSE specification.
41pub(super) fn content_type(
42    protected_header: &ProtectedHeader,
43) -> Result<coset::iana::CoapContentFormat, CryptoError> {
44    protected_header
45        .header
46        .content_type
47        .as_ref()
48        .and_then(|ct| match ct {
49            RegisteredLabel::Assigned(content_format) => Some(*content_format),
50            _ => None,
51        })
52        .ok_or_else(|| SignatureError::InvalidSignature.into())
53}
54
55/// Helper function to extract the key ID from a `CoseKey`. The key ID is a standardized header
56/// and always set in bitwarden-crypto generated encrypted messages or signatures.
57pub(super) fn key_id(cose_key: &CoseKey) -> Result<KeyId, EncodingError> {
58    let key_id: [u8; KEY_ID_SIZE] = cose_key
59        .key_id
60        .as_slice()
61        .try_into()
62        .map_err(|_| EncodingError::InvalidValue("key id length"))?;
63    let key_id: KeyId = key_id.into();
64    Ok(key_id)
65}
66
67/// Helper function to parse a ed25519 signing key from a `CoseKey`.
68pub(super) fn ed25519_signing_key(
69    cose_key: &CoseKey,
70) -> Result<ed25519_dalek::SigningKey, EncodingError> {
71    // https://www.rfc-editor.org/rfc/rfc9053.html#name-octet-key-pair
72    let d = okp_d(cose_key)?;
73    let crv = okp_curve(cose_key)?;
74    if crv == EllipticCurve::Ed25519.to_i64().into() {
75        Ok(ed25519_dalek::SigningKey::from_bytes(
76            d.try_into()
77                .map_err(|_| EncodingError::InvalidCoseEncoding)?,
78        ))
79    } else {
80        Err(EncodingError::UnsupportedValue("OKP curve"))
81    }
82}
83
84/// Helper function to parse a ed25519 verifying key from a `CoseKey`.
85pub(super) fn ed25519_verifying_key(
86    cose_key: &CoseKey,
87) -> Result<ed25519_dalek::VerifyingKey, EncodingError> {
88    // https://www.rfc-editor.org/rfc/rfc9053.html#name-octet-key-pair
89    let x = okp_x(cose_key)?;
90    let crv = okp_curve(cose_key)?;
91    if crv == EllipticCurve::Ed25519.to_i64().into() {
92        ed25519_dalek::VerifyingKey::from_bytes(
93            x.try_into()
94                .map_err(|_| EncodingError::InvalidValue("ed25519 OKP verifying key"))?,
95        )
96        .map_err(|_| EncodingError::InvalidValue("ed25519 OKP verifying key"))
97    } else {
98        Err(EncodingError::UnsupportedValue("OKP curve"))
99    }
100}
101
102/// Helper function to parse the private key `d` from a `CoseKey`.
103fn okp_d(cose_key: &CoseKey) -> Result<&[u8], EncodingError> {
104    // https://www.rfc-editor.org/rfc/rfc9053.html#name-octet-key-pair
105    cose_key
106        .params
107        .iter()
108        .find_map(|(key, value)| match key {
109            Label::Int(i) if OkpKeyParameter::from_i64(*i) == Some(OkpKeyParameter::D) => {
110                value.as_bytes().map(|v| v.as_slice())
111            }
112            _ => None,
113        })
114        .ok_or(EncodingError::MissingValue("OKP private key"))
115}
116
117/// Helper function to parse the public key `x` from a `CoseKey`.
118fn okp_x(cose_key: &CoseKey) -> Result<&[u8], EncodingError> {
119    // https://www.rfc-editor.org/rfc/rfc9053.html#name-octet-key-pair
120    cose_key
121        .params
122        .iter()
123        .find_map(|(key, value)| match key {
124            Label::Int(i) if OkpKeyParameter::from_i64(*i) == Some(OkpKeyParameter::X) => {
125                value.as_bytes().map(|v| v.as_slice())
126            }
127            _ => None,
128        })
129        .ok_or(EncodingError::MissingValue("OKP public key"))
130}
131
132/// Helper function to parse the OKP curve from a `CoseKey`.
133fn okp_curve(cose_key: &CoseKey) -> Result<i128, EncodingError> {
134    // https://www.rfc-editor.org/rfc/rfc9053.html#name-octet-key-pair
135    cose_key
136        .params
137        .iter()
138        .find_map(|(key, value)| match key {
139            Label::Int(i) if OkpKeyParameter::from_i64(*i) == Some(OkpKeyParameter::Crv) => {
140                value.as_integer().map(i128::from)
141            }
142            _ => None,
143        })
144        .ok_or(EncodingError::MissingValue("OKP curve"))
145}