• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

namib-project / dcaf-rs / 11935120896

20 Nov 2024 02:11PM UTC coverage: 86.555% (+1.3%) from 85.242%
11935120896

Pull #27

github

web-flow
Merge d2b3d706b into 383248641
Pull Request #27: ci: update grcov to latest stable version

6116 of 7066 relevant lines covered (86.56%)

167.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.73
/src/common/cbor_values/mod.rs
1
/*
2
 * Copyright (c) 2022, 2024 The NAMIB Project Developers.
3
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4
 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5
 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6
 * option. This file may not be copied, modified, or distributed
7
 * except according to those terms.
8
 *
9
 * SPDX-License-Identifier: MIT OR Apache-2.0
10
 */
11

12
//! Contains various helper values for CBOR structures.
13
//!
14
//! For example, this contains an enum representing a [`ProofOfPossessionKey`].
15
//!
16
//! # Example
17
//! One of the main use cases of the [`ProofOfPossessionKey`]
18
//! is for representing a key in the `cnf` claim:
19
//! ```
20
//! # use dcaf::AccessTokenResponse;
1✔
21
//! # use dcaf::endpoints::token_req::AccessTokenResponseBuilderError;
22
//! # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey};
23
//! let response: AccessTokenResponse = AccessTokenResponse::builder()
24
//!     .access_token(vec![0xDC, 0xAF, 0xDC, 0xAF])
1✔
25
//!     .cnf(ProofOfPossessionKey::KeyId(vec![0x42])).build()?;
1✔
26
//! # Ok::<(), AccessTokenResponseBuilderError>(())
1✔
27
//! ```
1✔
28

1✔
29
use core::fmt::{Debug, Display, Formatter};
30
use core::ops::Deref;
31

32
use coset::{CoseEncrypt0, CoseKey};
33
use strum_macros::IntoStaticStr;
34

35
use {alloc::boxed::Box, alloc::format, alloc::vec, alloc::vec::Vec};
36

37
#[cfg(test)]
38
mod tests;
39

40
/// A type intended to be used as a CBOR bytestring, represented as a vector of bytes.
41
pub type ByteString = Vec<u8>;
42

43
/// A Key ID, represented as a [`ByteString`].
44
pub(crate) type KeyId = ByteString;
45

46
/// Wrapper around a type `T` which can be created from and turned into an [`i32`].
47
pub(crate) struct CborMapValue<T>(pub(crate) T)
48
where
49
    i32: Into<T>,
50
    T: Into<i32> + Copy;
51

52
/// A proof-of-possession key as specified by
53
/// [RFC 8747, section 3.1](https://datatracker.ietf.org/doc/html/rfc8747#section-3.1).
54
///
55
/// Can either be a COSE key, an encrypted COSE key, or simply a key ID.
56
/// As described in [RFC 9201](https://www.rfc-editor.org/rfc/rfc9201),
57
/// PoP keys are used for the `req_cnf` parameter in [`AccessTokenRequest`](crate::AccessTokenRequest),
58
/// as well as for the `cnf` and `rs_cnf` parameters in [`AccessTokenResponse`](crate::AccessTokenResponse).
59
///
60
/// # Example
61
/// We showcase creation of an [`AccessTokenRequest`](crate::AccessTokenRequest) in which we set `req_cnf` to a PoP key
62
/// with an ID of 0xDCAF which the access token shall be bound to:
63
/// ```
64
/// # use dcaf::AccessTokenRequest;
1✔
65
/// # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey};
66
/// # use dcaf::endpoints::token_req::AccessTokenRequestBuilderError;
67
/// let key = ProofOfPossessionKey::KeyId(vec![0xDC, 0xAF]);
68
/// let request: AccessTokenRequest = AccessTokenRequest::builder().client_id("test_client").req_cnf(key).build()?;
1✔
69
/// assert_eq!(request.req_cnf.unwrap().key_id().to_vec(), vec![0xDC, 0xAF]);
1✔
70
/// # Ok::<(), AccessTokenRequestBuilderError>(())
1✔
71
/// ```
1✔
72
#[derive(Debug, PartialEq, Clone, IntoStaticStr)]
1✔
73
#[allow(clippy::large_enum_variant)] // size difference of ~300 bytes is acceptable
74
pub enum ProofOfPossessionKey {
75
    /// An unencrypted [`CoseKey`] used to represent an asymmetric public key or
76
    /// (if the CWT it's contained in is encrypted) a symmetric key.
77
    ///
78
    /// For details, see [section 3.2 of RFC 8747](https://datatracker.ietf.org/doc/html/rfc8747#section-3.2).
79
    PlainCoseKey(CoseKey),
80

81
    /// An encrypted [`CoseKey`] used to represent a symmetric key.
82
    ///
83
    /// For details, see [section 3.3 of RFC 8747](https://datatracker.ietf.org/doc/html/rfc8747#section-3.3).
84
    EncryptedCoseKey(CoseEncrypt0),
85

86
    /// Key ID of the actual proof-of-possession key.
87
    ///
88
    /// Note that as described in [section 6 of RFC 8747](https://datatracker.ietf.org/doc/html/rfc8747#section-6),
89
    /// certain caveats apply when choosing to represent a proof-of-possession key by its Key ID.
90
    ///
91
    /// For details, see [section 3.4 of RFC 8747](https://datatracker.ietf.org/doc/html/rfc8747#section-3.4).
92
    KeyId(KeyId),
93
}
94

95
impl ProofOfPossessionKey {
96
    /// Returns the key ID of this PoP key, cloning it if necessary.
97
    /// Note that the returned key ID may be empty if no key ID was present in the key.
98
    ///
99
    /// # Example
100
    /// ```
101
    /// # use coset::CoseKeyBuilder;
1✔
102
    /// # use dcaf::common::cbor_values::ProofOfPossessionKey;
103
    /// let key = CoseKeyBuilder::new_symmetric_key(vec![0; 5]).key_id(vec![0xDC, 0xAF]).build();
104
    /// let pop_key = ProofOfPossessionKey::from(key);
1✔
105
    /// assert_eq!(pop_key.key_id().to_vec(), vec![0xDC, 0xAF]);
1✔
106
    /// ```
1✔
107
    #[must_use]
1✔
108
    pub fn key_id(&self) -> &KeyId {
168✔
109
        match self {
168✔
110
            ProofOfPossessionKey::PlainCoseKey(k) => &k.key_id,
83✔
111
            ProofOfPossessionKey::KeyId(k) => k,
83✔
112
            ProofOfPossessionKey::EncryptedCoseKey(k) => {
2✔
113
                if k.protected.header.key_id.is_empty() {
2✔
114
                    &k.unprotected.key_id
1✔
115
                } else {
116
                    &k.protected.header.key_id
1✔
117
                }
118
            }
119
        }
120
    }
168✔
121
}
122

123
impl<T> Deref for CborMapValue<T>
124
where
125
    T: From<i32> + Into<i32> + Copy,
126
{
127
    type Target = T;
128

129
    fn deref(&self) -> &Self::Target {
×
130
        &self.0
×
131
    }
×
132
}
133

134
impl<T> Display for CborMapValue<T>
135
where
136
    i32: Into<T>,
137
    T: Into<i32> + Copy + Display,
138
{
139
    fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
×
140
        write!(f, "{}", self.0)
×
141
    }
×
142
}
143

144
/// Contains various `From`, `TryFrom` and other conversion methods for types of the parent module.
145
mod conversion {
146
    use ciborium::value::Value;
147
    use coset::{AsCborValue, CoseEncrypt0, CoseKey};
148
    use erased_serde::Serialize as ErasedSerialize;
149
    use serde::de::Error;
150
    use serde::{Deserialize, Deserializer, Serialize, Serializer};
151

152
    use crate::common::cbor_map::ToCborMap;
153
    use crate::error::{TryFromCborMapError, WrongSourceTypeError};
154

155
    use super::*;
156

157
    impl<T> Serialize for CborMapValue<T>
158
    where
159
        T: From<i32> + Into<i32> + Copy,
160
    {
161
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
497✔
162
        where
497✔
163
            S: Serializer,
497✔
164
        {
497✔
165
            let cbor_value: i32 = self.0.into();
497✔
166
            Value::from(cbor_value).serialize(serializer)
497✔
167
        }
497✔
168
    }
169

170
    impl<'de, T> Deserialize<'de> for CborMapValue<T>
171
    where
172
        T: From<i32> + Into<i32> + Copy,
173
    {
174
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
×
175
        where
×
176
            D: Deserializer<'de>,
×
177
        {
×
178
            if let Ok(Value::Integer(i)) = Value::deserialize(deserializer) {
×
179
                Ok(CborMapValue(
180
                    i32::try_from(i)
×
181
                        .map_err(|_| Error::custom("CBOR map key too high for i32"))?
×
182
                        .into(),
×
183
                ))
184
            } else {
185
                Err(Error::custom("CBOR map value must be an Integer"))
×
186
            }
187
        }
×
188
    }
189

190
    impl ToCborMap for ProofOfPossessionKey {
191
        fn to_cbor_map(&self) -> Vec<(i128, Option<Box<dyn ErasedSerialize + '_>>)> {
419✔
192
            // The fact that we have to clone this is a little unfortunate.
419✔
193
            match self {
419✔
194
                Self::PlainCoseKey(key) => {
250✔
195
                    let x: i128 = 1;
250✔
196
                    vec![(
250✔
197
                        x,
250✔
198
                        Some(Box::new(key.clone().to_cbor_value().expect("Invalid key"))),
250✔
199
                    )]
250✔
200
                }
201
                Self::EncryptedCoseKey(enc) => {
2✔
202
                    let x: i128 = 2;
2✔
203
                    vec![(
2✔
204
                        x,
2✔
205
                        Some(Box::new(
2✔
206
                            (*enc).clone().to_cbor_value().expect("Invalid key"),
2✔
207
                        )),
2✔
208
                    )]
2✔
209
                }
210
                Self::KeyId(kid) => {
167✔
211
                    let x: i128 = 3;
167✔
212
                    vec![(x, Some(Box::new(Value::Bytes(kid.clone()))))]
167✔
213
                }
214
            }
215
        }
419✔
216

217
        fn try_from_cbor_map(map: Vec<(i128, Value)>) -> Result<Self, TryFromCborMapError>
264✔
218
        where
264✔
219
            Self: Sized + ToCborMap,
264✔
220
        {
264✔
221
            if map.len() != 1 {
264✔
222
                Err(TryFromCborMapError::from_message(
2✔
223
                    "given CBOR map must contain exactly one element",
2✔
224
                ))
2✔
225
            } else if let Some(entry) = map.into_iter().next() {
262✔
226
                match entry {
87✔
227
                    (1, x) => CoseKey::from_cbor_value(x)
171✔
228
                        .map(ProofOfPossessionKey::PlainCoseKey)
171✔
229
                        .map_err(|x| {
171✔
230
                            TryFromCborMapError::from_message(format!(
1✔
231
                                "couldn't create CoseKey from CBOR value: {x}"
1✔
232
                            ))
1✔
233
                        }),
171✔
234
                    (2, x) => CoseEncrypt0::from_cbor_value(x)
3✔
235
                        .map(ProofOfPossessionKey::EncryptedCoseKey)
3✔
236
                        .map_err(|x| {
3✔
237
                            TryFromCborMapError::from_message(format!(
1✔
238
                                "couldn't create CoseEncrypt0 from CBOR value: {x}"
1✔
239
                            ))
1✔
240
                        }),
3✔
241
                    (3, Value::Bytes(x)) => Ok(ProofOfPossessionKey::KeyId(x)),
86✔
242
                    (x, _) => Err(TryFromCborMapError::unknown_field(u8::try_from(x)?)),
2✔
243
                }
244
            } else {
245
                unreachable!(
×
246
                    "we have previously verified that map.len() == 1, \
×
247
                so map.into_iter().next() must return a next element"
×
248
                )
×
249
            }
250
        }
264✔
251
    }
252

253
    impl From<CoseKey> for ProofOfPossessionKey {
254
        fn from(key: CoseKey) -> Self {
167✔
255
            ProofOfPossessionKey::PlainCoseKey(key)
167✔
256
        }
167✔
257
    }
258

259
    impl From<ByteString> for ProofOfPossessionKey {
260
        fn from(kid: ByteString) -> Self {
1✔
261
            ProofOfPossessionKey::KeyId(kid)
1✔
262
        }
1✔
263
    }
264

265
    impl From<CoseEncrypt0> for ProofOfPossessionKey {
266
        fn from(enc: CoseEncrypt0) -> Self {
1✔
267
            ProofOfPossessionKey::EncryptedCoseKey(enc)
1✔
268
        }
1✔
269
    }
270

271
    impl TryFrom<ProofOfPossessionKey> for CoseKey {
272
        type Error = WrongSourceTypeError<ProofOfPossessionKey>;
273

274
        fn try_from(
2✔
275
            value: ProofOfPossessionKey,
2✔
276
        ) -> Result<Self, WrongSourceTypeError<ProofOfPossessionKey>> {
2✔
277
            if let ProofOfPossessionKey::PlainCoseKey(key) = value {
2✔
278
                Ok(key)
1✔
279
            } else {
280
                Err(WrongSourceTypeError::new("PlainCoseKey", value.into()))
1✔
281
            }
282
        }
2✔
283
    }
284

285
    impl TryFrom<ProofOfPossessionKey> for CoseEncrypt0 {
286
        type Error = WrongSourceTypeError<ProofOfPossessionKey>;
287

288
        fn try_from(
3✔
289
            value: ProofOfPossessionKey,
3✔
290
        ) -> Result<Self, WrongSourceTypeError<ProofOfPossessionKey>> {
3✔
291
            if let ProofOfPossessionKey::EncryptedCoseKey(key) = value {
3✔
292
                Ok(key)
2✔
293
            } else {
294
                Err(WrongSourceTypeError::new("EncryptedCoseKey", value.into()))
1✔
295
            }
296
        }
3✔
297
    }
298

299
    impl TryFrom<ProofOfPossessionKey> for KeyId {
300
        type Error = WrongSourceTypeError<ProofOfPossessionKey>;
301

302
        fn try_from(
2✔
303
            value: ProofOfPossessionKey,
2✔
304
        ) -> Result<Self, WrongSourceTypeError<ProofOfPossessionKey>> {
2✔
305
            if let ProofOfPossessionKey::KeyId(kid) = value {
2✔
306
                Ok(kid)
1✔
307
            } else {
308
                Err(WrongSourceTypeError::new("KeyId", value.into()))
1✔
309
            }
310
        }
2✔
311
    }
312
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc