• 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

93.6
/src/token/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 methods for [encrypting](encrypt_access_token), [decrypting](decrypt_access_token),
13
//! [signing](sign_access_token) and [verifying](verify_access_token) access tokens.
14
//!
15
//! **NOTE: The APIs in this module might change in the future!**
16
//! This is because we plan to move much of the code here to the [`coset`]
17
//! library, since much of this just builds on COSE functionality and isn't ACE-OAuth specific.
18
//!
19
//! In order to use any of these methods, you will need to provide a cryptographic backend which
20
//! handles the cryptographic operations by implementing either [`EncryptCryptoBackend`],
21
//! [`MacCryptoBackend`](cose::MacCryptoBackend) or [`SignCryptoBackend`], depending on the intended
22
//! operation.
23
//!
24
//! Implementations for these traits may be found in the [`cose::crypto_impl`] module.
25
//!
26
//! If you plan to support `CoseEncrypt` or `CoseSign` rather than just `CoseEncrypt0` or
27
//! `CoseSign1` (i.e., if you have multiple recipients with separate keys), your backend might also
28
//! need to implement [`KeyDistributionCryptoBackend`].
29
//!
30
//! See the respective traits for details.
31
//!
32
//! # Creating Access Tokens
33
//! In order to create access tokens, you can use either [`encrypt_access_token`] or
34
//! [`sign_access_token`],
35
//! depending on whether you want the access token to be wrapped in a
36
//! `COSE_Encrypt0` or `COSE_Sign1` structure. In case you want to create a token intended for
37
//! multiple recipients (each with their own key), you can use
38
//! [`encrypt_access_token_multiple`] or [`sign_access_token_multiple`].
39
//!
40
//! Both functions take a [`ClaimsSet`] containing the claims that shall be part of the access
41
//! token, a key used to encrypt or sign the token, optional `aad` (additional authenticated data),
42
//! un-/protected headers and a cryptographic `backend` (as described in the [`cose`] module).
43
//!
44
//! Note that if the headers you pass in set fields to invalid values, an error will be returned.
45
//! For more information on how to set headers, see the [`cose`] module.
46
//!
47
//! The function will return a [`Result`] of the opaque [`ByteString`] containing the access token.
48
//!
49
//! # Verifying and Decrypting Access Tokens
50
//! In order to verify or decrypt existing access tokens represented as [`ByteString`]s, use
51
//! [`verify_access_token`] or [`decrypt_access_token`] respectively.
52
//! In case the token was created for multiple recipients (each with their own key),
53
//! use [`verify_access_token_multiple`] or [`decrypt_access_token_multiple`].
54
//!
55
//! Both functions take the access token, a `key_provider` that allows looking up keys that might be
56
//! used to decrypt or verify, optional `aad` (additional authenticated data) and a cryptographic
57
//! `backend` (as described in the [`cose`] module).
58
//!
59
//! [`decrypt_access_token`] will return a result containing the decrypted [`ClaimsSet`].
60
//! [`verify_access_token`] will return an empty result which indicates that the token was
61
//! successfully verified---an [`Err`](Result)
62
//! would indicate failure.
63
//!
64
//! # Extracting Headers from an Access Token
65
//! Regardless of whether a token was signed, encrypted, or MAC-tagged, you can extract its
66
//! headers using [`get_token_headers`], which will return an option containing both unprotected and
67
//! protected headers (or which will be [`None`] in case the token is invalid).
68
//!
69
//! # Example
70
//! The following shows how to create and encrypt an access token:
71
//! ```
72
//! use coset::{AsCborValue, CoseKeyBuilder, HeaderBuilder, iana};
1✔
73
//! use coset::cwt::ClaimsSetBuilder;
74
//! use coset::iana::CwtClaimName;
75
//! use dcaf::{decrypt_access_token, encrypt_access_token};
76
//! use dcaf::error::{AccessTokenError, CoseCipherError};
77
//! use dcaf::token::cose::crypto_impl::openssl::OpensslContext;
78
//! use dcaf::token::cose::{CryptoBackend, HeaderBuilderExt};
79
//!
80
//! let mut backend = OpensslContext::new();
81
//!
1✔
82
//! let mut key_data = vec![0; 32];
1✔
83
//! backend.generate_rand(key_data.as_mut_slice()).map_err(CoseCipherError::from)?;
1✔
84
//! let key = CoseKeyBuilder::new_symmetric_key(key_data).algorithm(iana::Algorithm::A256GCM).build();
1✔
85
//!
1✔
86
//! let unprotected_header = HeaderBuilder::new().gen_iv(&mut backend, iana::Algorithm::A256GCM)?.build();
87
//!
1✔
88
//! let claims = ClaimsSetBuilder::new()
89
//!      .audience(String::from("coaps://rs.example.com"))
1✔
90
//!      .issuer(String::from("coaps://as.example.com"))
1✔
91
//!      .claim(CwtClaimName::Cnf, key.clone().to_cbor_value()?)
1✔
92
//!      .build();
1✔
93
//!
1✔
94
//! let token = encrypt_access_token(&mut backend, &key, claims, &None, Some(unprotected_header), None)?;
95
//! assert!(decrypt_access_token(&mut backend, &key, &token, &None).is_ok());
1✔
96
//! # Ok::<(), AccessTokenError<<OpensslContext as CryptoBackend>::Error>>(())
1✔
97
//! ```
1✔
98

1✔
99
use crate::common::cbor_values::ByteString;
100
use crate::error::AccessTokenError;
101
use crate::token::cose::CryptoBackend;
102
pub use crate::token::cose::SignCryptoBackend;
103
use ciborium::value::Value;
104
use cose::util::generate_cek_for_alg;
105
use cose::AadProvider;
106
use cose::CoseRecipientBuilderExt;
107
use cose::KeyProvider;
108
use cose::{util::determine_algorithm, KeyDistributionCryptoBackend};
109
use cose::{
110
    CoseEncrypt0BuilderExt, CoseEncrypt0Ext, CoseEncryptBuilderExt, CoseEncryptExt,
111
    EncryptCryptoBackend,
112
};
113
use cose::{CoseSign1BuilderExt, CoseSign1Ext};
114
use cose::{CoseSignBuilderExt, CoseSignExt};
115
use coset::cwt::ClaimsSet;
116
use coset::{
117
    iana, Algorithm, AsCborValue, CborSerializable, CoseEncrypt, CoseEncrypt0, CoseEncrypt0Builder,
118
    CoseEncryptBuilder, CoseKey, CoseKeyBuilder, CoseRecipientBuilder, CoseSign, CoseSign1,
119
    CoseSign1Builder, CoseSignBuilder, CoseSignature, EncryptionContext, Header, HeaderBuilder,
120
    ProtectedHeader,
121
};
122

123
pub mod cose;
124
#[cfg(test)]
125
mod tests;
126

127
/// Encrypts the given `claims` with the given headers and `external_aad` using the
128
/// `key` and the cryptographic `backend`, returning the token as a serialized bytestring of the
129
/// [`CoseEncrypt0`] structure.
130
///
131
/// Note that this method will create a token intended for a single recipient.
132
/// If you wish to create a token for more than one recipient, use
133
/// [`encrypt_access_token_multiple`].
134
///
135
/// # Errors
136
/// Returns an error if the [`ClaimsSet`] could not be encoded or an error during signing
137
/// occurs (see [`CoseEncryptBuilderExt::try_encrypt`] for possible errors).
138
///
139
/// # Example
140
///
141
/// see the [module-level documentation](self) for an example
142
pub fn encrypt_access_token<T, AAD: AadProvider>(
4✔
143
    backend: &mut T,
4✔
144
    key: &CoseKey,
4✔
145
    claims: ClaimsSet,
4✔
146
    external_aad: &AAD,
4✔
147
    unprotected_header: Option<Header>,
4✔
148
    protected_header: Option<Header>,
4✔
149
) -> Result<ByteString, AccessTokenError<T::Error>>
4✔
150
where
4✔
151
    T: EncryptCryptoBackend + CryptoBackend,
4✔
152
{
4✔
153
    CoseEncrypt0Builder::new()
4✔
154
        .try_encrypt(
4✔
155
            backend,
4✔
156
            key,
4✔
157
            protected_header,
4✔
158
            unprotected_header,
4✔
159
            claims.to_vec()?.as_slice(),
4✔
160
            external_aad,
4✔
161
        )?
2✔
162
        .build()
2✔
163
        .to_vec()
2✔
164
        .map_err(AccessTokenError::from)
2✔
165
}
4✔
166

167
/// Encrypts the given `claims` with the given headers and `external_aad` for each recipient
168
/// by using the `keys` with the cryptographic `backend`, returning the token as a serialized
169
/// bytestring of the [`CoseEncrypt`] structure.
170
///
171
/// The Content Encryption Key (used to encrypt the actual claims) is randomly generated by the
172
/// given `backend`, whereas the given `keys` are used as Key Encryption Keys, that is,
173
/// they encrypt the Content Encryption Key for each recipient.
174
///
175
/// # Errors
176
/// Returns an error if the [`ClaimsSet`] could not be encoded or an error during signing
177
/// occurs (see [`CoseEncryptBuilderExt::try_encrypt`] and [`CoseRecipientBuilderExt::try_encrypt`] for
178
/// possible errors).
179
///
180
/// # Example
181
///
182
/// ```
183
/// use coset::{AsCborValue, CoseKeyBuilder, HeaderBuilder, iana};
1✔
184
/// use coset::cwt::ClaimsSetBuilder;
185
/// use coset::iana::CwtClaimName;
186
/// use dcaf::{decrypt_access_token, decrypt_access_token_multiple, encrypt_access_token, encrypt_access_token_multiple};
187
/// use dcaf::error::{AccessTokenError, CoseCipherError};
188
/// use dcaf::token::cose::crypto_impl::openssl::OpensslContext;
189
/// use dcaf::token::cose::{CryptoBackend, HeaderBuilderExt};
190
///
191
/// let mut backend = OpensslContext::new();
192
///
1✔
193
/// let mut key1_data = vec![0; 32];
1✔
194
/// backend.generate_rand(key1_data.as_mut_slice()).map_err(CoseCipherError::from)?;
1✔
195
/// let key1 = CoseKeyBuilder::new_symmetric_key(key1_data).algorithm(iana::Algorithm::A256KW).build();
1✔
196
///
1✔
197
/// let mut key2_data = vec![0; 32];
1✔
198
/// backend.generate_rand(key2_data.as_mut_slice()).map_err(CoseCipherError::from)?;
1✔
199
/// let key2 = CoseKeyBuilder::new_symmetric_key(key2_data).algorithm(iana::Algorithm::A256KW).build();
1✔
200
///
1✔
201
/// let unprotected_header = HeaderBuilder::new().gen_iv(&mut backend, iana::Algorithm::A256GCM)?.algorithm(iana::Algorithm::A256GCM).build();
202
///
1✔
203
/// let claims = ClaimsSetBuilder::new()
204
///      .audience(String::from("coaps://rs.example.com"))
1✔
205
///      .issuer(String::from("coaps://as.example.com"))
1✔
206
///      .claim(CwtClaimName::Cnf, key1.clone().to_cbor_value()?)
1✔
207
///      .build();
1✔
208
///
1✔
209
/// let keys = vec![key1.clone(), key2.clone()];
1✔
210
///
1✔
211
/// let token = encrypt_access_token_multiple(&mut backend, &keys, claims, &None, Some(unprotected_header), None)?;
212
/// assert!(decrypt_access_token_multiple(&mut backend, &key1, &token, &None).is_ok());
1✔
213
/// assert!(decrypt_access_token_multiple(&mut backend, &key2, &token, &None).is_ok());
1✔
214
/// # Ok::<(), AccessTokenError<<OpensslContext as CryptoBackend>::Error>>(())
1✔
215
/// ```
1✔
216
#[allow(clippy::missing_panics_doc)]
1✔
217
pub fn encrypt_access_token_multiple<'a, T, I, AAD: AadProvider>(
3✔
218
    backend: &mut T,
3✔
219
    keys: I,
3✔
220
    claims: ClaimsSet,
3✔
221
    external_aad: &AAD,
3✔
222
    unprotected_header: Option<Header>,
3✔
223
    protected_header: Option<Header>,
3✔
224
) -> Result<ByteString, AccessTokenError<T::Error>>
3✔
225
where
3✔
226
    T: EncryptCryptoBackend + KeyDistributionCryptoBackend,
3✔
227
    I: IntoIterator<Item = &'a CoseKey>,
3✔
228
    I::IntoIter: ExactSizeIterator,
3✔
229
{
3✔
230
    let mut result = CoseEncryptBuilder::new();
3✔
231
    let mut key_iter = keys.into_iter();
3✔
232
    let preset_algorithm =
3✔
233
        determine_algorithm(None, protected_header.as_ref(), unprotected_header.as_ref());
3✔
234

235
    let cek = if key_iter.len() == 1 && preset_algorithm.is_err() {
3✔
236
        let key = key_iter.next().unwrap();
×
237
        let ce_alg = determine_algorithm(
×
238
            Some(key),
×
239
            protected_header.as_ref(),
×
240
            unprotected_header.as_ref(),
×
241
        )?;
×
242
        let recipient_header = HeaderBuilder::new()
×
243
            .algorithm(iana::Algorithm::Direct)
×
244
            .key_id(key.key_id.clone())
×
245
            .build();
×
246
        result = result.add_recipient(
×
247
            CoseRecipientBuilder::new()
×
248
                .unprotected(recipient_header)
×
249
                .build(),
×
250
        );
×
251
        let mut cek_key = key.clone();
×
252
        cek_key.alg = Some(Algorithm::Assigned(ce_alg));
×
253
        cek_key
×
254
    } else {
255
        let ce_alg = preset_algorithm?;
3✔
256
        let cek_v = generate_cek_for_alg(backend, ce_alg)?;
3✔
257
        let cek = CoseKeyBuilder::new_symmetric_key(cek_v.clone()).build();
3✔
258

259
        for key in key_iter {
9✔
260
            // TODO allow manually setting headers for each recipient.
261
            let kek_alg = determine_algorithm(Some(key), None, None)?;
6✔
262
            let recipient_header = HeaderBuilder::new().algorithm(kek_alg).build();
6✔
263
            let recipient = CoseRecipientBuilder::new().try_encrypt(
6✔
264
                backend,
6✔
265
                key,
6✔
266
                EncryptionContext::EncRecipient,
6✔
267
                None,
6✔
268
                Some(recipient_header),
6✔
269
                cek_v.as_slice(),
6✔
270
                None,
6✔
271
            )?;
6✔
272

273
            result = result.add_recipient(recipient.build());
6✔
274
        }
275
        cek
3✔
276
    };
277

278
    result = result.try_encrypt(
3✔
279
        backend,
3✔
280
        &cek,
3✔
281
        protected_header,
3✔
282
        unprotected_header,
3✔
283
        claims.to_vec()?.as_slice(),
3✔
284
        external_aad,
3✔
285
    )?;
×
286
    result.build().to_vec().map_err(AccessTokenError::from)
3✔
287
}
3✔
288

289
/// Signs the given `claims` with the given headers and `external_aad` using the `key` and the
290
/// cryptographic `backend`, returning the token as a serialized byte string of the resulting
291
/// [`CoseSign1`] structure.
292
///
293
/// Note that this method will create a token with a single signature.
294
/// If you wish to create a token with multiple signatures, use [`sign_access_token_multiple`].
295
///
296
/// # Errors
297
/// Returns an error if the [`ClaimsSet`] could not be encoded or an error during signing
298
/// occurs (see [`CoseSign1BuilderExt::try_add_sign`](CoseSign1BuilderExt) for possible errors).
299
///
300
/// # Example
301
/// ```
302
/// use base64::Engine;
1✔
303
/// use coset::{AsCborValue, CoseKeyBuilder, HeaderBuilder, iana};
304
/// use coset::cwt::ClaimsSetBuilder;
305
/// use coset::iana::CwtClaimName;
306
/// use dcaf::{sign_access_token, verify_access_token};
307
/// use dcaf::error::{AccessTokenError, CoseCipherError};
308
/// use dcaf::token::cose::crypto_impl::openssl::OpensslContext;
309
/// use dcaf::token::cose::{CryptoBackend, HeaderBuilderExt};
310
///
311
/// let mut backend = OpensslContext::new();
312
///
1✔
313
/// let cose_ec2_key_x = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode("usWxHK2PmfnHKwXPS54m0kTcGJ90UiglWiGahtagnv8").unwrap();
1✔
314
/// let cose_ec2_key_y = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode("IBOL-C3BttVivg-lSreASjpkttcsz-1rb7btKLv8EX4").unwrap();
1✔
315
/// let cose_ec2_key_d = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode("V8kgd2ZBRuh2dgyVINBUqpPDr7BOMGcF22CQMIUHtNM").unwrap();
1✔
316
/// let key = CoseKeyBuilder::new_ec2_priv_key(
1✔
317
///                             iana::EllipticCurve::P_256,
1✔
318
///                             cose_ec2_key_x,
1✔
319
///                             cose_ec2_key_y,
1✔
320
///                             cose_ec2_key_d
1✔
321
///                 )
1✔
322
///                 .key_id("example_key".as_bytes().to_vec())
1✔
323
///                 .build();
1✔
324
///
1✔
325
/// let unprotected_header = HeaderBuilder::new().algorithm(iana::Algorithm::ES256).build();
1✔
326
///
1✔
327
/// let claims = ClaimsSetBuilder::new()
328
///      .audience(String::from("coaps://rs.example.com"))
1✔
329
///      .issuer(String::from("coaps://as.example.com"))
1✔
330
///      .claim(CwtClaimName::Cnf, key.clone().to_cbor_value()?)
1✔
331
///      .build();
1✔
332
///
1✔
333
/// let token = sign_access_token(&mut backend, &key, claims, &None, Some(unprotected_header), None)?;
334
/// assert!(verify_access_token(&mut backend, &key, &token, &None).is_ok());
1✔
335
/// # Ok::<(), AccessTokenError<<OpensslContext as CryptoBackend>::Error>>(())
1✔
336
/// ```
1✔
337
pub fn sign_access_token<T, AAD: AadProvider>(
5✔
338
    backend: &mut T,
6✔
339
    key: &CoseKey,
6✔
340
    claims: ClaimsSet,
6✔
341
    external_aad: &AAD,
6✔
342
    unprotected_header: Option<Header>,
6✔
343
    protected_header: Option<Header>,
6✔
344
) -> Result<ByteString, AccessTokenError<T::Error>>
6✔
345
where
6✔
346
    T: SignCryptoBackend,
6✔
347
{
6✔
348
    CoseSign1Builder::new()
6✔
349
        .payload(claims.to_vec()?)
6✔
350
        .try_sign(
6✔
351
            backend,
6✔
352
            key,
6✔
353
            protected_header,
6✔
354
            unprotected_header,
6✔
355
            external_aad,
6✔
356
        )?
6✔
357
        .build()
6✔
358
        .to_vec()
6✔
359
        .map_err(AccessTokenError::from)
6✔
360
}
6✔
361

362
/// Signs the given `claims` with the given headers and `external_aad` for each recipient
363
/// by using the `keys` with the cryptographic `backend`, returning the token as a serialized
364
/// bytestring of the [`CoseSign`] structure.
365
///
366
/// For each key in `keys`, another signature will be added, created with that respective key.
367
/// The given headers will be used for the [`CoseSign`] structure as a whole, not for each
368
/// individual signature.
369
///
370
/// # Errors
371
/// Returns an error if the [`ClaimsSet`] could not be encoded or an error during signing
372
/// occurs (see [`CoseSignBuilderExt::try_add_sign`] for possible errors).
373
///
374
/// # Example
375
/// ```
376
/// use base64::Engine;
1✔
377
/// use coset::{AsCborValue, CoseKeyBuilder, CoseSignatureBuilder, HeaderBuilder, iana};
378
/// use coset::cwt::ClaimsSetBuilder;
379
/// use coset::iana::CwtClaimName;
380
/// use dcaf::{sign_access_token, sign_access_token_multiple, verify_access_token, verify_access_token_multiple};
381
/// use dcaf::error::{AccessTokenError, CoseCipherError};
382
/// use dcaf::token::cose::crypto_impl::openssl::OpensslContext;
383
/// use dcaf::token::cose::{CryptoBackend, HeaderBuilderExt};
384
///
385
/// let mut backend = OpensslContext::new();
386
///
1✔
387
/// let cose_ec2_key1_x = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode("usWxHK2PmfnHKwXPS54m0kTcGJ90UiglWiGahtagnv8").unwrap();
1✔
388
/// let cose_ec2_key1_y = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode("IBOL-C3BttVivg-lSreASjpkttcsz-1rb7btKLv8EX4").unwrap();
1✔
389
/// let cose_ec2_key1_d = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode("V8kgd2ZBRuh2dgyVINBUqpPDr7BOMGcF22CQMIUHtNM").unwrap();
1✔
390
/// let key1 = CoseKeyBuilder::new_ec2_priv_key(
1✔
391
///                             iana::EllipticCurve::P_256,
1✔
392
///                             cose_ec2_key1_x,
1✔
393
///                             cose_ec2_key1_y,
1✔
394
///                             cose_ec2_key1_d
1✔
395
///                 )
1✔
396
///                 .key_id("example_key".as_bytes().to_vec())
1✔
397
///                 .build();
1✔
398
/// let cose_ec2_key2_x = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode("kTJyP2KSsBBhnb4kjWmMF7WHVsY55xUPgb7k64rDcjatChoZ1nvjKmYmPh5STRKc").unwrap();
1✔
399
/// let cose_ec2_key2_y = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode("mM0weMVU2DKsYDxDJkEP9hZiRZtB8fPfXbzINZj_fF7YQRynNWedHEyzAJOX2e8s").unwrap();
1✔
400
/// let cose_ec2_key2_d = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode("ok3Nq97AXlpEusO7jIy1FZATlBP9PNReMU7DWbkLQ5dU90snHuuHVDjEPmtV0fTo").unwrap();
1✔
401
/// let key2 = CoseKeyBuilder::new_ec2_priv_key(
1✔
402
///                             iana::EllipticCurve::P_384,
1✔
403
///                             cose_ec2_key2_x,
1✔
404
///                             cose_ec2_key2_y,
1✔
405
///                             cose_ec2_key2_d
1✔
406
///                 )
1✔
407
///                 .key_id("example_key2".as_bytes().to_vec())
1✔
408
///                 .build();
1✔
409
///
1✔
410
/// let unprotected_sig1_header = HeaderBuilder::new().algorithm(iana::Algorithm::ES256).build();
1✔
411
/// let unprotected_sig2_header = HeaderBuilder::new().algorithm(iana::Algorithm::ES384).build();
1✔
412
///
1✔
413
/// let sig1 = CoseSignatureBuilder::new().unprotected(unprotected_sig1_header).build();
1✔
414
/// let sig2 = CoseSignatureBuilder::new().unprotected(unprotected_sig2_header).build();
1✔
415
///
1✔
416
/// let claims = ClaimsSetBuilder::new()
417
///      .audience(String::from("coaps://rs.example.com"))
1✔
418
///      .issuer(String::from("coaps://as.example.com"))
1✔
419
///      .claim(CwtClaimName::Cnf, key1.clone().to_cbor_value()?)
1✔
420
///      .build();
1✔
421
///
1✔
422
/// let keys = vec![(&key1, sig1), (&key2, sig2)];
1✔
423
///
1✔
424
/// let token = sign_access_token_multiple(&mut backend, keys, claims, &None, None, None)?;
425
/// assert!(verify_access_token_multiple(&mut backend, &key1, &token, &None).is_ok());
1✔
426
/// assert!(verify_access_token_multiple(&mut backend, &key2, &token, &None).is_ok());
1✔
427
/// # Ok::<(), AccessTokenError<<OpensslContext as CryptoBackend>::Error>>(())
1✔
428
/// ```
1✔
429
pub fn sign_access_token_multiple<'a, T, I, AAD: AadProvider>(
2✔
430
    backend: &mut T,
2✔
431
    keys: I,
2✔
432
    claims: ClaimsSet,
2✔
433
    external_aad: &AAD,
2✔
434
    unprotected_header: Option<Header>,
2✔
435
    protected_header: Option<Header>,
2✔
436
) -> Result<ByteString, AccessTokenError<T::Error>>
2✔
437
where
2✔
438
    T: SignCryptoBackend,
2✔
439
    I: IntoIterator<Item = (&'a CoseKey, CoseSignature)>,
2✔
440
{
2✔
441
    let mut builder = CoseSignBuilder::new().payload(claims.to_vec()?);
2✔
442
    if let Some(unprotected) = unprotected_header {
2✔
443
        builder = builder.unprotected(unprotected);
1✔
444
    }
2✔
445
    if let Some(protected) = protected_header {
2✔
446
        builder = builder.protected(protected);
1✔
447
    }
2✔
448
    for (key, signature) in keys {
6✔
449
        builder = builder.try_add_sign::<T, &CoseKey, _>(backend, &key, signature, external_aad)?;
4✔
450
    }
451

452
    builder.build().to_vec().map_err(AccessTokenError::from)
2✔
453
}
2✔
454

455
/// Returns the headers of the given signed ([`CoseSign1`] / [`CoseSign`]),
456
/// MAC tagged (`CoseMac0` / `CoseMac`), or encrypted ([`CoseEncrypt0`] / [`CoseEncrypt`])
457
/// access token.
458
///
459
/// When the given `token` is none of those structures mentioned above, `None` is returned.
460
///
461
/// # Example
462
/// For example, say you have an access token saved in `token` and want to look at its headers:
463
/// ```
464
/// # use dcaf::common::cbor_values::ByteString;
1✔
465
/// # use dcaf::token::get_token_headers;
466
/// # let token = vec![
467
/// # 0x84, 0x4b, 0xa2, 0x1, 0x25, 0x4, 0x46, 0x84, 0x9b, 0x57, 0x86, 0x45, 0x7c, 0xa2, 0x5, 0x4d,
1✔
468
/// # 0x63, 0x68, 0x98, 0x99, 0x4f, 0xf0, 0xec, 0x7b, 0xfc, 0xf6, 0xd3, 0xf9, 0x5b, 0x18, 0x2f, 0xf6,
1✔
469
/// # 0x58, 0x20, 0xa1, 0x8, 0xa3, 0x1, 0x4, 0x2, 0x46, 0x84, 0x9b, 0x57, 0x86, 0x45, 0x7c, 0x20,
1✔
470
/// # 0x51, 0x84, 0x9b, 0x57, 0x86, 0x45, 0x7c, 0x14, 0x91, 0xbe, 0x3a, 0x76, 0xdc, 0xea, 0x6c, 0x42,
1✔
471
/// # 0x71, 0x8, 0x58, 0x40, 0x84, 0x6a, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x31,
1✔
472
/// # 0x4b, 0xa2, 0x1, 0x25, 0x4, 0x46, 0x84, 0x9b, 0x57, 0x86, 0x45, 0x7c, 0x45, 0x1, 0x2, 0x3, 0x4,
1✔
473
/// # 0x5, 0x58, 0x20, 0xa1, 0x8, 0xa3, 0x1, 0x4, 0x2, 0x46, 0x84, 0x9b, 0x57, 0x86, 0x45, 0x7c, 0x20,
1✔
474
/// # 0x51, 0x84, 0x9b, 0x57, 0x86, 0x45, 0x7c, 0x14, 0x91, 0xbe, 0x3a, 0x76, 0xdc, 0xea, 0x6c, 0x42,
1✔
475
/// # 0x71, 0x8];
1✔
476
/// if let Some((unprotected_header, protected_header)) = get_token_headers(&token) {
1✔
477
///   assert_eq!(protected_header.header.key_id, vec![0x84, 0x9b, 0x57, 0x86, 0x45, 0x7c])
1✔
478
/// } else {
1✔
479
///   unreachable!("Example token should be valid.")
480
/// }
×
481
/// ```
482
#[must_use]
1✔
483
pub fn get_token_headers(token: &ByteString) -> Option<(Header, ProtectedHeader)> {
95✔
484
    let value: Option<Value> = ciborium::de::from_reader(token.as_slice()).ok();
95✔
485
    // All of COSE_Encrypt(0), COSE_Sign(1), COSE_Mac(0) are an array with headers first
486
    match value {
94✔
487
        Some(Value::Array(x)) => {
90✔
488
            let mut iter = x.into_iter();
90✔
489
            let protected = iter
90✔
490
                .next()
90✔
491
                .map(ProtectedHeader::from_cbor_bstr)
90✔
492
                .and_then(Result::ok);
90✔
493
            let unprotected = iter
90✔
494
                .next()
90✔
495
                .map(Header::from_cbor_value)
90✔
496
                .and_then(Result::ok);
90✔
497
            if let (Some(u), Some(p)) = (unprotected, protected) {
90✔
498
                Some((u, p))
90✔
499
            } else {
500
                None
×
501
            }
502
        }
503
        Some(_) | None => None,
5✔
504
    }
505
}
95✔
506

507
/// Verifies the given `token` and `external_aad` using keys from the given `key_provider` and the
508
/// cryptographic `backend`, returning an error in case it could not be verified.
509
///
510
/// This method should be used when the given `token` is a [`CoseSign1`] rather than
511
/// [`CoseSign`] (i.e., if it is intended for a single recipient). In case the token is an
512
/// instance of the latter, use [`verify_access_token_multiple`] instead.
513
///
514
/// # Errors
515
/// Returns an error if the [`CoseSign1`] structure could not be parsed, an error during verification
516
/// occurs (see [`CoseSign1Ext::try_verify`] for possible errors) or the contained payload is not a
517
/// valid [`ClaimsSet`].
518
///
519
/// # Example
520
///
521
/// For an example, see the documentation of [`sign_access_token`].
522
pub fn verify_access_token<T, CKP, AAD: AadProvider>(
6✔
523
    backend: &mut T,
6✔
524
    key_provider: &CKP,
6✔
525
    token: &ByteString,
6✔
526
    external_aad: &AAD,
6✔
527
) -> Result<(), AccessTokenError<T::Error>>
6✔
528
where
6✔
529
    T: SignCryptoBackend,
6✔
530
    CKP: KeyProvider,
6✔
531
{
6✔
532
    let sign = CoseSign1::from_slice(token.as_slice()).map_err(AccessTokenError::CoseError)?;
6✔
533
    sign.try_verify(backend, key_provider, external_aad)
6✔
534
        .map_err(AccessTokenError::from)
6✔
535
}
6✔
536

537
/// Verifies the given `token` and `external_aad` using keys from the given `key_provider` and the
538
/// cryptographic `backend`, returning an error in case it could not be verified.
539
///
540
/// This method should be used when the given `token` is a [`CoseSign`] rather than
541
/// [`CoseSign1`] (i.e., if it is intended for a multiple recipients). In case the token is an
542
/// instance of the latter, use [`verify_access_token`] instead.
543
///
544
/// # Errors
545
/// Returns an error if the [`CoseSign`] structure could not be parsed, an error during verification
546
/// occurs (see [`CoseSignExt::try_verify`] for possible errors) or the contained payload is not a
547
/// valid [`ClaimsSet`].
548
pub fn verify_access_token_multiple<T, CKP, AAD: AadProvider>(
6✔
549
    backend: &mut T,
6✔
550
    key_provider: &CKP,
6✔
551
    token: &ByteString,
6✔
552
    external_aad: &AAD,
6✔
553
) -> Result<(), AccessTokenError<T::Error>>
6✔
554
where
6✔
555
    T: SignCryptoBackend,
6✔
556
    CKP: KeyProvider,
6✔
557
{
6✔
558
    let sign = CoseSign::from_slice(token.as_slice()).map_err(AccessTokenError::CoseError)?;
6✔
559
    sign.try_verify(backend, key_provider, external_aad)?;
6✔
560
    Ok(())
4✔
561
}
6✔
562

563
/// Decrypts the given `token` and `external_aad` using keys from the given `key_provider` and the
564
/// cryptographic `backend`, returning the decrypted [`ClaimsSet`].
565
///
566
/// This method should be used when the given `token` is a [`CoseEncrypt0`] rather than
567
/// [`CoseEncrypt`] (i.e., if it is intended for a single recipient). In case the token is an
568
/// instance of the latter, use [`decrypt_access_token_multiple`] instead.
569
///
570
/// # Errors
571
/// Returns an error if the [`CoseEncrypt0`] structure could not be parsed, an error during
572
/// decryption occurs (see [`CoseEncrypt0Ext::try_decrypt_with_recipients`](CoseEncrypt0Ext) for
573
/// possible errors) or the decrypted payload is not a valid [`ClaimsSet`].
574
///
575
/// # Example
576
///
577
/// For an example, see the documentation of [`encrypt_access_token`].
578
pub fn decrypt_access_token<T, CKP, AAD: AadProvider>(
2✔
579
    backend: &mut T,
2✔
580
    key_provider: &CKP,
2✔
581
    token: &ByteString,
2✔
582
    external_aad: &AAD,
2✔
583
) -> Result<ClaimsSet, AccessTokenError<T::Error>>
2✔
584
where
2✔
585
    T: EncryptCryptoBackend,
2✔
586
    CKP: KeyProvider,
2✔
587
{
2✔
588
    let encrypt = CoseEncrypt0::from_slice(token.as_slice()).map_err(AccessTokenError::from)?;
2✔
589
    let result = encrypt.try_decrypt(backend, key_provider, external_aad)?;
2✔
590
    ClaimsSet::from_slice(result.as_slice()).map_err(AccessTokenError::from)
2✔
591
}
2✔
592

593
/// Decrypts the given `token` and `external_aad` using keys from the given `key_provider` and the
594
/// cryptographic `backend`, returning the decrypted [`ClaimsSet`].
595
///
596
/// Note that the given `kek` must have an associated `kid` (key ID) field when converted
597
/// to a COSE key, as the recipient inside the [`CoseEncrypt`] is identified in this way.
598
///
599
/// This method should be used when the given `token` is a [`CoseEncrypt`] rather than
600
/// [`CoseEncrypt0`] (i.e., if it is intended for multiple recipients). In case the token is an
601
/// instance of the latter, use [`decrypt_access_token`] instead.
602
///
603
/// # Errors
604
/// Returns an error if the [`CoseEncrypt`] structure could not be parsed, an error during decryption
605
/// occurs (see [`CoseEncryptExt::try_decrypt_with_recipients`] for possible errors) or the decrypted
606
/// payload is not a valid [`ClaimsSet`].
607
pub fn decrypt_access_token_multiple<T, CKP, AAD: AadProvider>(
7✔
608
    backend: &mut T,
7✔
609
    key_provider: &CKP,
7✔
610
    token: &ByteString,
7✔
611
    external_aad: &AAD,
7✔
612
) -> Result<ClaimsSet, AccessTokenError<T::Error>>
7✔
613
where
7✔
614
    T: EncryptCryptoBackend + KeyDistributionCryptoBackend,
7✔
615
    CKP: KeyProvider,
7✔
616
{
7✔
617
    let encrypt = CoseEncrypt::from_slice(token.as_slice()).map_err(AccessTokenError::from)?;
7✔
618
    let result = encrypt.try_decrypt_with_recipients(backend, key_provider, external_aad)?;
7✔
619
    ClaimsSet::from_slice(result.as_slice()).map_err(AccessTokenError::from)
5✔
620
}
7✔
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