#oauth #framework #ace

no-std dcaf

An implementation of the ACE-OAuth framework

5 releases (3 breaking)

0.4.0 Mar 27, 2023
0.3.1 Aug 11, 2022
0.3.0 Jun 14, 2022
0.2.0 Apr 5, 2022
0.1.0 Apr 2, 2022

#206 in Authentication

Download history 11/week @ 2024-02-19 8/week @ 2024-02-26 51/week @ 2024-04-01

51 downloads per month

MIT/Apache

300KB
4K SLoC

Crates.io Docs Coverage

dcaf-rs

An implementation of the ACE-OAuth framework (RFC 9200).

This crate implements the ACE-OAuth (Authentication and Authorization for Constrained Environments using the OAuth 2.0 Framework) framework as defined in RFC 9200. Key features include CBOR-(de-)serializable data models such as AccessTokenRequest, as well as the possibility to create COSE encrypted/signed access tokens (as described in the standard) along with decryption/verification functions. Implementations of the cryptographic functions must be provided by the user by implementing CoseEncryptCipher or CoseSignCipher.

Note that actually transmitting the serialized values (e.g., via CoAP) or providing more complex features not mentioned in the ACE-OAuth RFC (e.g., a permission management system for the Authorization Server) is out of scope for this crate. This also applies to cryptographic functions, as mentioned in the previous paragraph.

The name DCAF was chosen because eventually, it's planned for this crate to support functionality from the Delegated CoAP Authentication and Authorization Framework (DCAF) specified in draft-gerdes-ace-dcaf-authorize (which was specified prior to ACE-OAuth and inspired many design choices in it)--- specifically, it's planned to support using a CAM (Client Authorization Manager) instead of just a SAM (Server Authorization Manager), as is done in ACE-OAuth. Compatibility with the existing DCAF implementation in C (which we'll call libdcaf to disambiguate from dcaf referring to this crate) is also an additional design goal, though the primary objective is still to support ACE-OAuth.

As one of the possible use-cases for this crate is usage on constrained IoT devices, requirements are minimal---as such, while alloc is still needed, this crate offers no_std support by omitting the default std feature.

Usage

[dependencies]
dcaf = { version = "^0.3" }

Or, if you plan to use this crate in a no_std environment:

[dependencies]
dcaf = { version = "^0.3", default-features = false }

Example

As mentioned, the main features of this crate are ACE-OAuth data models and token creation/verification functions. We'll quickly introduce both of these here.

Data models

For example, let's assume you (the client) want to request an access token from an Authorization Server. For this, you'd need to create an AccessTokenRequest, which has to include at least a client_id. We'll also specify an audience, a scope (using TextEncodedScope---note that binary-encoded scopes or AIF-encoded scopes would also work), as well as a ProofOfPossessionKey (the key the access token should be bound to) in the req_cnf field.

Creating, serializing and then de-serializing such a structure would look like this:

use dcaf::{AccessTokenRequest, ToCborMap, ProofOfPossessionKey, TextEncodedScope};

let request = AccessTokenRequest::builder()
   .client_id("myclient")
   .audience("valve242")
   .scope(TextEncodedScope::try_from("read")?)
   .req_cnf(ProofOfPossessionKey::KeyId(base64::decode("6kg0dXJM13U")?))
   .build()?;
let mut encoded = Vec::new();
request.clone().serialize_into(&mut encoded)?;
assert_eq!(AccessTokenRequest::deserialize_from(encoded.as_slice())?, request);

Access Tokens

Following up from the previous example, let's assume we now want to create a signed access token containing the existing key, as well as claims about the audience and issuer of the token, using an existing cipher of type FakeCrypto[^cipher]:

use dcaf::token::CoseCipher;


let rng = FakeRng;
let key = CoseKeyBuilder::new_symmetric_key(vec![1,2,3,4,5]).key_id(vec![0xDC, 0xAF]).build();
let claims = ClaimsSetBuilder::new()
     .audience(String::from("coaps://rs.example.com"))
     .issuer(String::from("coaps://as.example.com"))
     .claim(CwtClaimName::Cnf, key.clone().to_cbor_value()?)
     .build();
let token = sign_access_token::<FakeCrypto, FakeRng>(&key, claims, None, None, None, rng)?;
assert!(verify_access_token::<FakeCrypto>(&key, &token, None).is_ok());

[^cipher]: Note that we are deliberately omitting details about the implementation of the cipher here, since such implementations won't be in the scope of this crate.

Provided Data Models

Token Endpoint

The most commonly used models will probably be the token endpoint's AccessTokenRequest and AccessTokenResponse described in section 5.8 of RFC 9200. In case of an error, an ErrorResponse should be used.

After an initial Unauthorized Resource Request Message, an AuthServerRequestCreationHint can be used to provide additional information to the client, as described in section 5.3 of RFC 9200.

Common Data Types

Some types used across multiple scenarios include:

Creating Access Tokens

In order to create access tokens, you can use either encrypt_access_token or sign_access_token, depending on whether you want the access token to be wrapped in a COSE_Encrypt0 or COSE_Sign1 structure. Support for a combination of both is planned for the future. In case you want to create a token intended for multiple recipients (each with their own key), you can use encrypt_access_token_multiple or sign_access_token_multiple.

Both functions take a ClaimsSet containing the claims that shall be part of the access token, a key used to encrypt or sign the token, optional aad (additional authenticated data), un-/protected headers and a cipher (explained further below) identified by type parameter T. Note that if the headers you pass in set fields which the cipher wants to set as well, the function will fail with a HeaderAlreadySet error. The function will return a Result of the opaque ByteString containing the access token.

Verifying and Decrypting Access Tokens

In order to verify or decrypt existing access tokens represented as ByteStrings, use verify_access_token or decrypt_access_token respectively. In case the token was created for multiple recipients (each with their own key), use verify_access_token_multiple or decrypt_access_token_multiple.

Both functions take the access token, a key used to decrypt or verify, optional aad (additional authenticated data) and a cipher implementing cryptographic operations identified by type parameter T.

decrypt_access_token will return a result containing the decrypted ClaimsSet. verify_access_token will return an empty result which indicates that the token was successfully verified---an Err would indicate failure.

Extracting Headers from an Access Token

Regardless of whether a token was signed, encrypted, or MAC-tagged, you can extract its headers using get_token_headers, which will return an option containing both unprotected and protected headers (or which will be None in case the token is invalid).

COSE Cipher

As mentioned before, cryptographic functions are outside the scope of this crate. For this reason, the various COSE cipher traits exist; namely, CoseEncryptCipher, CoseSignCipher, and CoseMacCipher, each implementing a corresponding COSE operation as specified in sections 4, 5, and 6 of RFC 8152. There are also the traits MultipleEncryptCipher, MultipleSignCipher, and MultipleMacCipher, which are used for creating tokens intended for multiple recipients.

Note that these ciphers don't need to wrap their results in, e.g., a Cose_Encrypt0 structure, as this part is already handled by this library (which uses coset)---only the cryptographic algorithms themselves need to be implemented (e.g., step 4 of "how to decrypt a message" in section 5.3 of RFC 8152).

When implementing any of the specific COSE ciphers, you'll also need to specify the type of the key (which must be convertible to a CoseKey) and implement a method which sets headers for the token, for example, the used algorithm, the key ID, an IV, and so on.

Changelog

You can find a list of changes in CHANGELOG.md.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Maintainers

This project is currently maintained by the following developers:

Name Email Address GitHub Username
Falko Galperin falko1@uni-bremen.de @falko17
Hugo Hakim Damer hdamer@uni-bremen.de @pulsastrix

Dependencies

~3MB
~62K SLoC