dcaf 0.4.0

An implementation of the ACE-OAuth framework
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
/*
 * Copyright (c) 2022 The NAMIB Project Developers.
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
 * option. This file may not be copied, modified, or distributed
 * except according to those terms.
 *
 * SPDX-License-Identifier: MIT OR Apache-2.0
 */

//! Contains the [`ToCborMap`] trait with which data types from this crate can be (de)serialized.
//!
//! # Example
//! Let's say we want to serialize an [`AccessTokenRequest`]:
//! ```
//! # use std::error::Error;
//! # use ciborium_io::Write;
//! # use ciborium_io::Read;
//! # use dcaf::{AccessTokenRequest, ToCborMap};
//! # use dcaf::endpoints::token_req::AccessTokenRequestBuilderError;
//! # use crate::dcaf::constants::cbor_abbreviations::token::CLIENT_ID;
//! # #[cfg(feature = "std")] {
//! let request: AccessTokenRequest = AccessTokenRequest::builder().client_id("test").build()?;
//! let mut serialized = Vec::new();
//! request.serialize_into(&mut serialized)?;
//!
//! assert_eq!(serialized, vec![
//! 0xA1, // map(1)
//! 0x18, 0x18, // unsigned(24), where 24 is the constant identifying the "client_id" label
//! 0x64, // text(4)
//! 0x74, 0x65, 0x73, 0x74 // "test"
//! ]);
//! # }
//! # Ok::<(), Box<dyn Error>>(())
//! ```
//! If we then want to deserialize it again:
//! ```
//! # use ciborium_io::Read;
//! # use serde::de::value::Error;
//! # use dcaf::{AccessTokenRequest, ToCborMap};
//! let serialized = vec![0xA1, 0x18, 0x18, 0x64, 0x74, 0x65, 0x73, 0x74];
//! let request = AccessTokenRequest::deserialize_from(serialized.as_slice())?;
//! assert_eq!(request.client_id, Some("test".to_string()));
//! # Ok::<(), ciborium::de::Error<<&[u8] as Read>::Error>>(())
//! ```
//!
//! [`AccessTokenRequest`]: crate::AccessTokenRequest

use core::fmt::{Debug, Display, Formatter};
#[cfg(feature = "std")]
use std::any::type_name;

use ciborium::de::from_reader;
use ciborium::ser::into_writer;
use ciborium::value::{Integer, Value};
use ciborium_io::{Read, Write};
use erased_serde::Serialize as ErasedSerialize;

#[cfg(not(feature = "std"))]
use {alloc::boxed::Box, alloc::format, alloc::vec::Vec, core::any::type_name};

use crate::common::scope::Scope;
use crate::error::{TryFromCborMapError, ValueIsNotIntegerError};

/// Creates a CBOR map from integer keys to values, where the given values must have a `map`
/// method available (e.g. [`Option`]).
///
/// The macro has been adapted from
/// [a macro in ciborium's tests](https://github.com/enarx/ciborium/blob/main/ciborium/tests/macro.rs#L13)
///
/// # Example
/// The following code:
/// ```ignore
/// let map = cbor_map_vec! {
///     0 => Some("Test"),
///     1 => Some(42)
/// };
/// ```
/// Would create the following map (written in CBOR diagnostic notation):
/// ```text
/// {
///    0: "Test",
///    1: 42
/// }
/// ```
macro_rules! cbor_map_vec {
    ($($key:expr => $val:expr),* $(,)*) => {
         vec![$(
             (
                 $key as i128,
                 $val.map(|x| {
                         // `Box::<dyn ErasedSerialize>` would not work, see
                         // here for an explanation: https://stackoverflow.com/a/63550684
                         let a_box: Box<dyn ErasedSerialize> = Box::new(x);
                         a_box
                     })
             )
         ),*]
     };
     }

#[rustfmt::skip]
pub(crate) use cbor_map_vec;

/// Provides methods to serialize a type into a CBOR map bytestring and back.
///
/// This provides methods to [`serialize_into`](ToCborMap::serialize_into) and
/// [`deserialize_from`](ToCborMap::deserialize_from) CBOR, which is the
/// recommended way to serialize and deserialize any types implementing [`ToCborMap`] in this crate.
/// *While other methods are provided as well, it's recommended for clients of this library not to
/// use them, as they are mostly intended for internal use and as such may have an unstable API.*
///
/// # Example
/// The following showcases how to serialize a type implementing `ToCborMap`
/// using the example of an [`AuthServerRequestCreationHint`](crate::AuthServerRequestCreationHint):
/// ```
/// # use ciborium_io::Write;
/// # use dcaf::AuthServerRequestCreationHint;
/// # use dcaf::common::cbor_map::ToCborMap;
/// let hint = AuthServerRequestCreationHint::default();
/// let mut serialized: Vec<u8> = Vec::new();
/// hint.serialize_into(&mut serialized)?;
/// # Ok::<(), ciborium::ser::Error<<Vec<u8> as Write>::Error>>(())
/// ```
/// From the serialized bytestring, just call [`deserialize_from`](ToCborMap::deserialize_from)
/// on the struct you want to deserialize into:
/// ```
/// # use ciborium_io::Read;
/// # use dcaf::AuthServerRequestCreationHint;
/// # use dcaf::common::cbor_map::ToCborMap;
/// # let hint = AuthServerRequestCreationHint::default();
/// # let mut serialized: Vec<u8> = Vec::new();
/// # hint.clone().serialize_into(&mut serialized).expect("couldn't serialize hint");
/// let deserialized = AuthServerRequestCreationHint::deserialize_from(serialized.as_slice())?;
/// assert_eq!(hint, deserialized);
/// # Ok::<(), ciborium::de::Error<<&[u8] as Read>::Error>>(())
/// ```
pub trait ToCborMap: private::Sealed {
    /// Serializes this type as a CBOR map bytestring into the given `writer`.
    ///
    /// # Example
    /// The following showcases how to serialize a type implementing `ToCborMap`
    /// using the example of an [`AuthServerRequestCreationHint`](crate::AuthServerRequestCreationHint):
    /// ```
    /// # use ciborium_io::Write;
    /// # use dcaf::AuthServerRequestCreationHint;
    /// # use dcaf::common::cbor_map::ToCborMap;
    /// let hint = AuthServerRequestCreationHint::default();
    /// let mut serialized: Vec<u8> = Vec::new();
    /// hint.serialize_into(&mut serialized)?;
    /// assert_eq!(serialized, vec![0xA0]);  // 0xA0 == Empty CBOR map.
    /// # Ok::<(), ciborium::ser::Error<<Vec<u8> as Write>::Error>>(())
    /// ```
    ///
    /// # Errors
    /// - When serialization of this value failed, e.g. due to malformed input.
    /// - When the output couldn't be put inside the given `writer`.
    fn serialize_into<W>(self, writer: W) -> Result<(), ciborium::ser::Error<W::Error>>
    where
        Self: Sized,
        W: Write,
        W::Error: Debug,
    {
        into_writer(&CborMap(self), writer)
    }

    /// Deserializes from the given `reader` --- which is expected to be an instance of this type,
    /// represented as a CBOR map bytestring --- into an instance of this type.
    ///
    /// # Example
    /// Assuming `serialized` holds an empty CBOR map, we expect it to deserialize to the default
    /// value of a type (this obviously only holds for types which implement [`Default`].)
    /// Here, we show this using [`AuthServerRequestCreationHint`](crate::AuthServerRequestCreationHint) as an example:
    /// ```
    /// # use ciborium_io::Read;
    /// # use dcaf::AuthServerRequestCreationHint;
    /// # use dcaf::common::cbor_map::ToCborMap;
    /// let serialized = vec![0xA0];
    /// let deserialized = AuthServerRequestCreationHint::deserialize_from(serialized.as_slice())?;
    /// assert_eq!(deserialized, AuthServerRequestCreationHint::default());
    /// # Ok::<(), ciborium::de::Error<<&[u8] as Read>::Error>>(())
    /// ```
    ///
    /// # Errors
    /// - When deserialization of the bytestring failed, e.g. when the given `reader` does not
    ///   contain a valid CBOR map or deserializes to a different type than this one.
    /// - When the input couldn't be read from the given `reader`.
    fn deserialize_from<R>(reader: R) -> Result<Self, ciborium::de::Error<R::Error>>
    where
        Self: Sized,
        R: Read,
        R::Error: Debug,
    {
        from_reader(reader).map(|x: CborMap<Self>| x.0)
    }

    /// Converts this type into a CBOR map from integer keys to serializable values
    /// (which may be empty).
    ///
    /// **NOTE: This is not intended for users of this crate!**
    #[doc(hidden)]
    fn to_cbor_map(&self) -> Vec<(i128, Option<Box<dyn ErasedSerialize + '_>>)>;

    /// Tries to create an instance of this type from the given vector, which represents a CBOR map
    /// from integers to CBOR values.
    ///
    /// **NOTE: This is not intended for users of this crate!**
    ///
    /// # Errors
    /// - When the given CBOR map can't be converted to this trait.
    #[doc(hidden)]
    fn try_from_cbor_map(map: Vec<(i128, Value)>) -> Result<Self, TryFromCborMapError>
    where
        Self: Sized + ToCborMap;

    /// Converts this type to a CBOR serializable [`Value`] using [`to_cbor_map`](ToCborMap::to_cbor_map).
    ///
    /// # Panics
    /// - When the integers in the map from [`to_cbor_map`](ToCborMap::to_cbor_map) are too high to fit into a
    ///   [`Value::Integer`].
    /// - When a CBOR map value can't be serialized.
    ///
    /// Note that both of these would imply a programming mistake on account of `dcaf-rs`,
    /// not its users.
    ///
    /// # Example
    /// For example, to serialize a proof-of-possession key into a [`Value`] so we can then
    /// represent it inside a [`ClaimsSet`](coset::cwt::ClaimsSet) (to use it in an access token):
    /// ```
    /// # use coset::cwt::ClaimsSetBuilder;
    /// # use coset::iana::CwtClaimName;
    /// # use dcaf::ToCborMap;
    /// # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey};
    /// let key = ProofOfPossessionKey::KeyId(vec![0xDC, 0xAF]);
    /// let claims = ClaimsSetBuilder::new()
    ///     .claim(CwtClaimName::Cnf, key.to_ciborium_value())
    ///     .build();
    /// ```
    fn to_ciborium_value(&self) -> Value {
        Value::Map(
            self.to_cbor_map()
                .into_iter()
                .filter(|x| x.1.is_some())
                .map(|x| {
                    (
                        Value::Integer(x.0.try_into().expect("CBOR key value too high")),
                        Value::serialized(&x.1).expect("Invalid CBOR map value"),
                    )
                })
                .collect(),
        )
    }

    /// Converts the given vector representing
    /// "a CBOR map from serializable keys to serializable values" (`Vec<(Value, Value)>`)
    /// into a similar vector which represents
    /// "a CBOR map from integer keys to serializable values" (`Vec<(i128, Value)>`).
    ///
    /// This obviously requires that the given `map` is such a CBOR map with integer keys,
    /// otherwise an error will be returned.
    ///
    /// **NOTE: This is not intended for users of this crate!**
    ///
    /// # Errors
    /// - When a key from the given CBOR `map` is not an integer.
    #[doc(hidden)]
    fn cbor_map_from_int(
        map: Vec<(Value, Value)>,
    ) -> Result<Vec<(i128, Value)>, ValueIsNotIntegerError> {
        // We want to convert (Value, Value) to (i128, Value), assuming that the first
        // Value is always a Value::Integer.
        map.into_iter()
            .map(|x| (x.0.as_integer().map(i128::from), x.1))
            .map(|x| match x {
                (None, _) => Err(ValueIsNotIntegerError),
                (Some(x), y) => Ok((x, y)),
            })
            .collect::<Result<Vec<(i128, Value)>, ValueIsNotIntegerError>>()
    }
}

/// Decodes the given specific `scope` into the general [`Scope`] type.
///
/// # Errors
/// - If `scope` is not a valid scope.
pub(crate) fn decode_scope(scope: Value) -> Result<Scope, TryFromCborMapError> {
    Scope::try_from(scope)
        .map_err(|e| TryFromCborMapError::from_message(format!("couldn't decode scope: {e}")))
}

/// Decodes the given `number` Integer into a more specific integer of type `T`.
///
/// # Errors
/// - If `number` can't be a valid instance of type `T`.
pub(crate) fn decode_number<T>(number: Integer, name: &str) -> Result<T, TryFromCborMapError>
where
    T: TryFrom<Integer>,
{
    match T::try_from(number) {
        Ok(i) => Ok(i),
        Err(_) => Err(TryFromCborMapError::from_message(format!(
            "{name} must be a valid {}",
            type_name::<T>()
        ))),
    }
}

/// Decodes the given general CBOR `map` into a CBOR map from integers to values.
/// See [`ToCborMap::cbor_map_from_int`] for details.
///
/// # Errors
/// - If `map` is not a valid CBOR map with integer keys.
pub(crate) fn decode_int_map<T>(
    map: Vec<(Value, Value)>,
    name: &str,
) -> Result<Vec<(i128, Value)>, TryFromCborMapError>
where
    T: ToCborMap,
{
    T::cbor_map_from_int(map).map_err(|_| {
        TryFromCborMapError::from_message(format!(
            "{name} is not a valid CBOR map with integer keys"
        ))
    })
}

/// Convenience struct so we can implement a foreign trait on all structs we intend to
/// (de)serialize as CBOR maps.
///
/// This should always be invisible to clients of this crate.
#[derive(Debug, PartialEq, Eq, Hash)]
struct CborMap<T>(T)
where
    T: ToCborMap;

impl<T> Display for CborMap<T>
where
    T: ToCborMap + Display,
{
    fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Contains definitions according to C-SEALED, which turns [`ToCborMap`] into a sealed trait.
mod private {
    use crate::common::cbor_values::ProofOfPossessionKey;
    use crate::endpoints::creation_hint::AuthServerRequestCreationHint;
    use crate::endpoints::token_req::{AccessTokenRequest, AccessTokenResponse, ErrorResponse};

    /// Sealed trait according to C-SEALED.
    pub trait Sealed {}

    impl Sealed for AuthServerRequestCreationHint {}

    impl Sealed for AccessTokenRequest {}

    impl Sealed for AccessTokenResponse {}

    impl Sealed for ErrorResponse {}

    impl Sealed for ProofOfPossessionKey {}
}

/// Contains methods to convert `CborMap` structs (so actually, types implementing `ToCborMap`)
/// into CBOR and back.
mod conversion {
    #[cfg(not(feature = "std"))]
    use alloc::vec::Vec;

    use ciborium::value::Value;
    use serde::de::{Error, Unexpected};
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    use crate::common::cbor_map::{CborMap, ToCborMap};

    impl<T> From<T> for CborMap<T>
    where
        T: ToCborMap,
    {
        fn from(value: T) -> Self {
            CborMap(value)
        }
    }

    impl<T> Serialize for CborMap<T>
    where
        T: ToCborMap,
    {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            Serialize::serialize(&self.0.to_ciborium_value(), serializer)
        }
    }

    impl<'de, T> Deserialize<'de> for CborMap<T>
    where
        T: ToCborMap,
    {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: Deserializer<'de>,
        {
            match Value::deserialize(deserializer)? {
                Value::Map(map) => {
                    let map: Vec<(i128, Value)> =
                        T::cbor_map_from_int(map).map_err(D::Error::custom)?;
                    ToCborMap::try_from_cbor_map(map)
                        .map(CborMap)
                        .map_err(D::Error::custom)
                }
                _ => Err(D::Error::invalid_type(
                    Unexpected::Other("unknown type"),
                    &"a CBOR map",
                )),
            }
        }
    }
}