dcaf/
lib.rs

1/*
2 * Copyright (c) 2022 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//! An implementation of the [ACE-OAuth framework (RFC 9200)](https://www.rfc-editor.org/rfc/rfc9200).
13//!
14//! This crate implements the ACE-OAuth
15//! (Authentication and Authorization for Constrained Environments using the OAuth 2.0 Framework)
16//! framework as defined in [RFC 9200](https://www.rfc-editor.org/rfc/rfc9200).
17//! Key features include CBOR-(de-)serializable data models such as [`AccessTokenRequest`](crate::endpoints::token_req::AccessTokenRequest),
18//! as well as the possibility to create COSE encrypted/signed access tokens
19//! (as described in the standard) along with decryption/verification functions.
20//! Implementations of the cryptographic functions must be provided by the user by implementing
21//! [`CoseEncryptCipher`](crate::token::CoseEncryptCipher) or [`CoseSignCipher`](crate::token::CoseSignCipher).
22//!
23//! Note that actually transmitting the serialized values (e.g., via CoAP) or providing more complex
24//! features not mentioned in the ACE-OAuth RFC (e.g., a permission management system for
25//! the Authorization Server) is *out of scope* for this crate.
26//! This also applies to cryptographic functions, as mentioned in the previous paragraph.
27//!
28//! The name DCAF was chosen because eventually, it's planned for this crate to support
29//! functionality from the [Delegated CoAP Authentication and Authorization Framework (DCAF)](https://dcaf.science/)
30//! specified in [`draft-gerdes-ace-dcaf-authorize`](https://datatracker.ietf.org/doc/html/draft-gerdes-ace-dcaf-authorize-04)
31//! (which was specified prior to ACE-OAuth and inspired many design choices in it)---
32//! specifically, it's planned to support using a CAM (Client Authorization Manager)
33//! instead of just a SAM (Server Authorization Manager), as is done in ACE-OAuth.
34//! Compatibility with the existing [DCAF implementation in C](https://gitlab.informatik.uni-bremen.de/DCAF/dcaf)
35//! (which we'll call `libdcaf` to disambiguate from `dcaf` referring to this crate) is also an
36//! additional design goal, though the primary objective is still to support ACE-OAuth.
37//!
38//! As one of the possible use-cases for this crate is usage on constrained IoT devices,
39//! requirements are minimal---as such, while `alloc` is still needed, this crate offers
40//! `no_std` support by omitting the default `std` feature.
41//!
42//! # Usage
43//! ```toml
44//! [dependencies]
45//! dcaf = { version = "^0.3" }
46//! ```
47//! Or, if you plan to use this crate in a `no_std` environment:
48//! ```toml
49//! [dependencies]
50//! dcaf = { version = "^0.3", default-features = false }
51//! ```
52//!
53//! # Example
54//! As mentioned, the main features of this crate are ACE-OAuth data models and
55//! token creation/verification functions. We'll quickly introduce both of these here.
56//!
57//! ## Data models
58//! [For example](https://www.rfc-editor.org/rfc/rfc9200#figure-6),
59//! let's assume you (the client) want to request an access token from an Authorization Server.
60//! For this, you'd need to create an [`AccessTokenRequest`](crate::endpoints::token_req::AccessTokenRequest), which has to include at least a
61//! `client_id`. We'll also specify an audience, a scope (using [`TextEncodedScope`](crate::common::scope::TextEncodedScope)---note that
62//! [binary-encoded scopes](crate::common::scope::BinaryEncodedScope) or [AIF-encoded scopes](crate::common::scope::AifEncodedScope) would also work), as well as a
63//! [`ProofOfPossessionKey`](crate::common::cbor_values::ProofOfPossessionKey) (the key the access token should be bound to) in the `req_cnf` field.
64//!
65//! Creating, serializing and then de-serializing such a structure would look like this:
66//! ```
67//! # use std::error::Error;
68//! use dcaf::{AccessTokenRequest, ToCborMap, ProofOfPossessionKey, TextEncodedScope};
69//!
70//! # #[cfg(feature = "std")] {
71//! let request = AccessTokenRequest::builder()
72//!    .client_id("myclient")
73//!    .audience("valve242")
74//!    .scope(TextEncodedScope::try_from("read")?)
75//!    .req_cnf(ProofOfPossessionKey::KeyId(base64::decode("6kg0dXJM13U")?))
76//!    .build()?;
77//! let mut encoded = Vec::new();
78//! request.clone().serialize_into(&mut encoded)?;
79//! assert_eq!(AccessTokenRequest::deserialize_from(encoded.as_slice())?, request);
80//! # }
81//! # Ok::<(), Box<dyn Error>>(())
82//! ```
83//!
84//! ## Access Tokens
85//! Following up from the previous example, let's assume we now want to create a signed
86//! access token containing the existing `key`, as well as claims about the audience and issuer
87//! of the token, using an existing cipher of type `FakeCrypto`[^cipher]:
88//! ```
89//! # use ciborium::value::Value;
90//! # use coset::{AsCborValue, CoseKey, CoseKeyBuilder, Header, iana, Label, ProtectedHeader};
91//! # use coset::cwt::{ClaimsSetBuilder, Timestamp};
92//! # use coset::iana::{Algorithm, CwtClaimName};
93//! # use rand::{CryptoRng, RngCore};
94//! # use dcaf::{ToCborMap, sign_access_token, verify_access_token, CoseSignCipher};
95//! # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey};
96//! # use dcaf::common::cbor_values::ProofOfPossessionKey::PlainCoseKey;
97//! # use dcaf::error::{AccessTokenError, CoseCipherError};
98//! use dcaf::token::CoseCipher;
99//!
100//! # struct FakeCrypto {}
101//! #
102//! # #[derive(Clone, Copy)]
103//! # pub(crate) struct FakeRng;
104//! #
105//! # fn get_k_from_key(key: &CoseKey) -> Option<Vec<u8>> {
106//! #     const K_PARAM: i64 = iana::SymmetricKeyParameter::K as i64;
107//! #     for (label, value) in key.params.iter() {
108//! #         if let Label::Int(K_PARAM) = label {
109//! #             if let Value::Bytes(k_val) = value {
110//! #                 return Some(k_val.clone());
111//! #             }
112//! #         }
113//! #     }
114//! #     None
115//! # }
116//! #
117//! # impl RngCore for FakeRng {
118//! #     fn next_u32(&mut self) -> u32 {
119//! #         0
120//! #     }
121//! #
122//! #     fn next_u64(&mut self) -> u64 {
123//! #         0
124//! #     }
125//! #
126//! #     fn fill_bytes(&mut self, dest: &mut [u8]) {
127//! #         dest.fill(0);
128//! #     }
129//! #
130//! #     fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
131//! #         dest.fill(0);
132//! #         Ok(())
133//! #     }
134//! # }
135//! #
136//! # impl CryptoRng for FakeRng {}
137//! #
138//! # impl CoseCipher for FakeCrypto {
139//! #     type Error = String;
140//! #
141//! #     fn set_headers<RNG: RngCore + CryptoRng>(key: &CoseKey, unprotected_header: &mut Header, protected_header: &mut Header, rng: RNG) -> Result<(), CoseCipherError<Self::Error>> {
142//! #         // We have to later verify these headers really are used.
143//! #         if let Some(label) = unprotected_header
144//! #             .rest
145//! #             .iter()
146//! #             .find(|x| x.0 == Label::Int(47))
147//! #         {
148//! #             return Err(CoseCipherError::existing_header_label(&label.0));
149//! #         }
150//! #         if protected_header.alg != None {
151//! #             return Err(CoseCipherError::existing_header("alg"));
152//! #         }
153//! #         unprotected_header.rest.push((Label::Int(47), Value::Null));
154//! #         protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::Direct));
155//! #         Ok(())
156//! #     }
157//! # }
158//! #
159//! # /// Implements basic operations from the [`CoseSignCipher`](crate::token::CoseSignCipher) trait
160//! # /// without actually using any "real" cryptography.
161//! # /// This is purely to be used for testing and obviously offers no security at all.
162//! # impl CoseSignCipher for FakeCrypto {
163//! #     fn sign(
164//! #         key: &CoseKey,
165//! #         target: &[u8],
166//! #         unprotected_header: &Header,
167//! #         protected_header: &Header,
168//! #     ) -> Vec<u8> {
169//! #         // We simply append the key behind the data.
170//! #         let mut signature = target.to_vec();
171//! #         let k = get_k_from_key(key);
172//! #         signature.append(&mut k.expect("k must be present in key!"));
173//! #         signature
174//! #     }
175//! #
176//! #     fn verify(
177//! #         key: &CoseKey,
178//! #         signature: &[u8],
179//! #         signed_data: &[u8],
180//! #         unprotected_header: &Header,
181//! #         protected_header: &ProtectedHeader,
182//! #         unprotected_signature_header: Option<&Header>,
183//! #         protected_signature_header: Option<&ProtectedHeader>,
184//! #     ) -> Result<(), CoseCipherError<Self::Error>> {
185//! #         if signature
186//! #             == Self::sign(
187//! #             key,
188//! #             signed_data,
189//! #             unprotected_header,
190//! #             &protected_header.header,
191//! #         )
192//! #         {
193//! #             Ok(())
194//! #         } else {
195//! #             Err(CoseCipherError::VerificationFailure)
196//! #         }
197//! #     }
198//! # }
199//!
200//! let rng = FakeRng;
201//! let key = CoseKeyBuilder::new_symmetric_key(vec![1,2,3,4,5]).key_id(vec![0xDC, 0xAF]).build();
202//! let claims = ClaimsSetBuilder::new()
203//!      .audience(String::from("coaps://rs.example.com"))
204//!      .issuer(String::from("coaps://as.example.com"))
205//!      .claim(CwtClaimName::Cnf, key.clone().to_cbor_value()?)
206//!      .build();
207//! let token = sign_access_token::<FakeCrypto, FakeRng>(&key, claims, None, None, None, rng)?;
208//! assert!(verify_access_token::<FakeCrypto>(&key, &token, None).is_ok());
209//! # Ok::<(), AccessTokenError<String>>(())
210//! ```
211//!
212//! [^cipher]: Note that we are deliberately omitting details about the implementation of the
213//! `cipher` here, since such implementations won't be in the scope of this crate.
214//!
215//! # Provided Data Models
216//!
217//! ## Token Endpoint
218//! The most commonly used models will probably be the token endpoint's
219//! [`AccessTokenRequest`](crate::endpoints::token_req::AccessTokenRequest) and
220//! [`AccessTokenResponse`](crate::endpoints::token_req::AccessTokenResponse)
221//! described in [section 5.8 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8).
222//! In case of an error, an [`ErrorResponse`](crate::endpoints::token_req::ErrorResponse)
223//! should be used.
224//!
225//! After an initial Unauthorized Resource Request Message, an
226//! [`AuthServerRequestCreationHint`](crate::endpoints::creation_hint::AuthServerRequestCreationHint)
227//! can be used to provide additional information to the client, as described in
228//! [section 5.3 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.3).
229//!
230//! ## Common Data Types
231//! Some types used across multiple scenarios include:
232//! - [`Scope`](crate::common::scope::Scope) (as described in
233//!   [section 5.8.1 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.1)),
234//!   either as a [`TextEncodedScope`](crate::common::scope::TextEncodedScope),
235//!   a [`BinaryEncodedScope`](crate::common::scope::BinaryEncodedScope) or
236//!   an [`AifEncodedScope`](crate::common::scope::AifEncodedScope).
237//! - [`ProofOfPossessionKey`](crate::common::cbor_values::ProofOfPossessionKey) as specified in
238//!   [section 3.1 of RFC 8747](https://www.rfc-editor.org/rfc/rfc8747#section-3.1).
239//!   For example, this will be used in the access token's `cnf` claim.
240//! - While not really a data type, various constants representing values used in ACE-OAuth
241//!   are provided in the [`constants`](crate::common::constants) module.
242//!
243//! # Creating Access Tokens
244//! In order to create access tokens, you can use either [`encrypt_access_token`](crate::token::encrypt_access_token)
245//! or [`sign_access_token`](crate::token::sign_access_token),
246//! depending on whether you want the access token to be wrapped in a
247//! `COSE_Encrypt0` or `COSE_Sign1` structure. Support for a combination of both is planned for the
248//! future. In case you want to create a token intended for multiple recipients (each with their
249//! own key), you can use [`encrypt_access_token_multiple`](crate::token::encrypt_access_token_multiple)
250//! or [`sign_access_token_multiple`](crate::token::sign_access_token_multiple).
251//!
252//! Both functions take a [`ClaimsSet`](coset::cwt::ClaimsSet) containing the claims that
253//! shall be part of the access token, a key used to encrypt or sign the token,
254//! optional `aad` (additional authenticated data), un-/protected headers and a cipher (explained
255//! further below) identified by type parameter `T`.
256//! Note that if the headers you pass in set fields which the cipher wants to set as well,
257//! the function will fail with a `HeaderAlreadySet` error.
258//! The function will return a [`Result`](::core::result::Result) of the opaque
259//! [`ByteString`](crate::common::cbor_values::ByteString) containing the access token.
260//!
261//! # Verifying and Decrypting Access Tokens
262//! In order to verify or decrypt existing access tokens represented as [`ByteString`](crate::common::cbor_values::ByteString)s,
263//! use [`verify_access_token`](crate::token::verify_access_token) or
264//! [`decrypt_access_token`](crate::token::decrypt_access_token) respectively.
265//! In case the token was created for multiple recipients (each with their own key),
266//! use [`verify_access_token_multiple`](crate::token::verify_access_token_multiple)
267//! or [`decrypt_access_token_multiple`](crate::token::decrypt_access_token_multiple).
268//!
269//! Both functions take the access token, a `key` used to decrypt or verify, optional `aad`
270//! (additional authenticated data) and a cipher implementing cryptographic operations identified
271//! by type parameter `T`.
272//!
273//! [`decrypt_access_token`](crate::token::decrypt_access_token) will return a result containing
274//! the decrypted [`ClaimsSet`](coset::cwt::ClaimsSet).
275//! [`verify_access_token`](crate::token::verify_access_token) will return an empty result which
276//! indicates that the token was successfully verified---an [`Err`](::core::result::Result)
277//! would indicate failure.
278//!
279//! # Extracting Headers from an Access Token
280//! Regardless of whether a token was signed, encrypted, or MAC-tagged, you can extract its
281//! headers using [`get_token_headers`](crate::token::get_token_headers),
282//! which will return an option containing both
283//! unprotected and protected headers (or which will be [`None`](core::option::Option::None) in case
284//! the token is invalid).
285//!
286//! # COSE Cipher
287//! As mentioned before, cryptographic functions are outside the scope of this crate.
288//! For this reason, the various COSE cipher traits exist; namely,
289//! [`CoseEncryptCipher`](token::CoseEncryptCipher), [`CoseSignCipher`](token::CoseSignCipher),
290//! and [`CoseMacCipher`](token::CoseMacCipher), each implementing
291//! a corresponding COSE operation as specified in sections 4, 5, and 6 of
292//! [RFC 8152](https://www.rfc-editor.org/rfc/rfc8152).
293//! There are also the traits [`MultipleEncryptCipher`](token::MultipleEncryptCipher),
294//! [`MultipleSignCipher`](token::MultipleSignCipher), and
295//! [`MultipleMacCipher`](token::MultipleMacCipher),
296//! which are used for creating tokens intended for multiple recipients.
297//!
298//! Note that these ciphers *don't* need to wrap their results in, e.g.,
299//! a `Cose_Encrypt0` structure, as this part is already handled by this library
300//! (which uses [`coset`](coset))---only the cryptographic algorithms themselves need to be implemented
301//! (e.g., step 4 of "how to decrypt a message" in [section 5.3 of RFC 8152](https://www.rfc-editor.org/rfc/rfc8152#section-5.3)).
302//!
303//! When implementing any of the specific COSE ciphers, you'll also need to specify the type
304//! of the key (which must be convertible to a `CoseKey`) and implement a method which sets
305//! headers for the token, for example, the used algorithm, the key ID, an IV, and so on.
306
307#![deny(rustdoc::broken_intra_doc_links, clippy::pedantic)]
308#![warn(missing_docs, rustdoc::missing_crate_level_docs)]
309// These ones are a little too eager
310#![allow(
311    clippy::doc_markdown,
312    clippy::module_name_repetitions,
313    clippy::wildcard_imports,
314    clippy::type_complexity
315)]
316#![cfg_attr(not(feature = "std"), no_std)]
317#[macro_use]
318extern crate alloc;
319extern crate core;
320#[macro_use]
321extern crate derive_builder;
322
323#[doc(inline)]
324pub use common::cbor_map::ToCborMap;
325#[doc(inline)]
326pub use common::cbor_values::{ByteString, ProofOfPossessionKey};
327#[doc(inline)]
328pub use common::constants;
329#[doc(inline)]
330pub use common::scope::{
331    AifEncodedScope, BinaryEncodedScope, LibdcafEncodedScope, Scope, TextEncodedScope,
332};
333#[doc(inline)]
334pub use endpoints::creation_hint::AuthServerRequestCreationHint;
335#[doc(inline)]
336pub use endpoints::token_req::{
337    AccessTokenRequest, AccessTokenResponse, AceProfile, ErrorCode, ErrorResponse, GrantType,
338    TokenType,
339};
340#[doc(inline)]
341pub use token::{
342    decrypt_access_token, decrypt_access_token_multiple, encrypt_access_token,
343    encrypt_access_token_multiple, get_token_headers, sign_access_token,
344    sign_access_token_multiple, verify_access_token, verify_access_token_multiple,
345    CoseEncryptCipher, CoseMacCipher, CoseSignCipher, MultipleEncryptCipher, MultipleMacCipher,
346    MultipleSignCipher,
347};
348
349pub mod common;
350pub mod endpoints;
351pub mod error;
352pub mod token;