Skip to content

Commit

Permalink
fixup! chore: apply suggestions from code review (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
pulsastrix committed Aug 4, 2024
1 parent 0be5c67 commit a5c4a78
Show file tree
Hide file tree
Showing 15 changed files with 541 additions and 215 deletions.
86 changes: 69 additions & 17 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,73 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

This release reworks the access token API and fully implements extensions to the COSE structures provided by `coset`
that allow using pre-defined cryptographic backends.
This way, it is no longer required for applications to implement cryptographic operations and logic to verify that
COSE structures match the specification.

Note: The long-term plan is to integrate these extensions into `coset` at some point (assuming that they would be
interested in this code). This might come with minor API adjustments (e.g. by removing some parameters to builder
methods that already have their own builder method, but whose fields we can't access from outside of `coset`), but the
basic design of this module should now be quite stable
Applications can expect to only have to make minor changes in calls to these operations for the forseeable future.

### Added

- An entire new submodule `token/cose` that is responsible for easier handling of creation and verification of COSE
structures.
Instead of manually defining glue code to cryptographic libraries for each call and manually checking whether the COSE
structure is specification compliant, this module takes care of most of the validation and leaves the basic
cryptographic operations to reusable predefined cryptographic backends
- `CoseEncryptExt`/`CoseEncrypt0Ext`/`CoseSignExt`/`CoseSign1Ext`/`CoseMacExt`/`CoseMac0Ext`: Extension traits for
the
corresponding `coset` types that allow for easy verification of those COSE structures.
- `CoseEncryptBuilderExt`/`CoseEncrypt0BuilderExt`/`CoseSignBuilderExt`/`CoseSign1BuilderExt`/`CoseMacBuilderExt`/
`CoseMac0BuilderExt`: Extension traits for the corresponding `coset` types that allow for easy creation of those
COSE structures.
- `CoseRecipientExt`/`CoseRecipientBuilderExt`: Extensions for creating and decrypting COSE recipients, which might
be used in `CoseEncrypt` and `CoseMac` structures to encode data for multiple recipients at once.
- A cryptographic backend based on the `openssl` crate (a `RustCrypto` backend will be following soon).

### Changed

- `encrypt_access_token_multiple`/`decrypt_access_token_multiple`: Parameters have been adjusted to use the new COSE
extensions
- `sign_access_token_multiple` / `verify_access_token_multiple`: Parameters have been adjusted to use the new COSE
extensions
- `encrypt_access_token`/`decrypt_access_token`: Parameters have been adjusted to use the new COSE extensions
- `sign_access_token` / `verify_access_token`: Parameters have been adjusted to use the new COSE extensions
- `CoseCipher`/`CoseEncryptCipher`/`CoseSignCipher`/`CoseMacCipher`: Renamed to `CryptoBackend`/`EncryptCryptoBackend`/
`SignCryptoBackend`/`MacCryptoBackend` and fully reworked. These traits now only need to implement the basic
cryptographic operations, almost everything else (validation of header parameters, identifying suitable keys, ...) is
now handled by the `coset` extensions.

### Removed

- `MultipleEncryptCipher`/`MultipleSignCipher`: No longer required as the cryptographic backends no longer have to
handle those differently (this is taken care of by the `token/cose` module now.

## [0.4.0] --- 2023-03-27

This release mainly adds support for multiple token recipients, deals with the newly released RFCs,
and fixes `no_std` support.
and fixes `no_std` support.
Note that the cipher interfaces have been refactored in a major way.

### Added

- The `CoapOscore` profile has been added as an `AceProfile`.
- Support for multiple token recipients has been added. Specifically, the following new methods have been added:
- `encrypt_access_token_multiple` / `decrypt_access_token_multiple`: Creates a new access token encoded as a
`CoseEncrypt` rather than a `CoseEncrypt0`. The user passes in a vector of keys on encryption, these will then be
used as Key Encryption Keys. The Content Encryption Key is generated by the `MultipleEncryptCipher` required by
the function. On decryption, the correct recipient structure will be identified by the key ID of the passed-in key.
- `sign_access_token_multiple` / `decrypt_access_token_multiple`: Creates a new access token encoded as a `CoseSign`
rather than a `CoseSign1`. The user passes in a vector of keys when signing, and a recipient will be created
for each key. When verifying, the correct recipient structure will be identified by the key ID of the passed-in key.
- `encrypt_access_token_multiple` / `decrypt_access_token_multiple`: Creates a new access token encoded as a
`CoseEncrypt` rather than a `CoseEncrypt0`. The user passes in a vector of keys on encryption, these will then be
used as Key Encryption Keys. The Content Encryption Key is generated by the `MultipleEncryptCipher` required by
the function. On decryption, the correct recipient structure will be identified by the key ID of the passed-in
key.
- `sign_access_token_multiple` / `decrypt_access_token_multiple`: Creates a new access token encoded as a `CoseSign`
rather than a `CoseSign1`. The user passes in a vector of keys when signing, and a recipient will be created
for each key. When verifying, the correct recipient structure will be identified by the key ID of the passed-in
key.

### Changed

Expand Down Expand Up @@ -64,11 +114,11 @@ with `AifRestMethod` (using [`enumflags2`]).
`AifRestMethodSet` has been added too.
The reason for this is that this makes it possible to declare
*single* REST methods in a type-safe manner.
- Note that any existing usages of `AifRestMethodSet` now need to
be replaced with the new corresponding API calls.
- Variant names are now using `PascalCase` instead of `UPPER_CASE`.
- Use the type `AifRestMethod` for a single REST method and
`AifRestMethodSet` for a set of REST methods.
- Note that any existing usages of `AifRestMethodSet` now need to
be replaced with the new corresponding API calls.
- Variant names are now using `PascalCase` instead of `UPPER_CASE`.
- Use the type `AifRestMethod` for a single REST method and
`AifRestMethodSet` for a set of REST methods.
- The `derive_builder` dependency has been updated to 0.11.2.

### Fixed
Expand All @@ -84,13 +134,14 @@ This release focuses on introducing [AIF] and [libdcaf]-support.
- Support for scopes using the
[Authorization Information Format (AIF) for ACE](https://www.rfc-editor.org/rfc/rfc9237.html).
For this purpose, the following types have been added:
- `AifEncodedScope`, representing an AIF-encoded scope (surprise)
- `AifEncodedScopeElement`, a single element in an AIF-encoded scope
- `AifRestMethodSet`, encoding a set of REST methods
- Support for scopes used by the [libdcaf] implementation
- `AifEncodedScope`, representing an AIF-encoded scope (surprise)
- `AifEncodedScopeElement`, a single element in an AIF-encoded scope
- `AifRestMethodSet`, encoding a set of REST methods
- Support for scopes used by the [libdcaf] implementation
(a variant of AIF-encoded scopes).

### Fixed

- Binary-encoded scopes are now properly serialized.
- Some incorrect documentation regarding scopes has been corrected.

Expand All @@ -101,6 +152,7 @@ For more extensive documentation, consult the
[crate-level documentation](https://docs.rs/dcaf).

### Added

- CBOR de-/serializable model of the ACE-OAuth framework has been added:
- Binary- and text-encoded scopes
- Access token requests and responses
Expand Down
87 changes: 70 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ An implementation of the [ACE-OAuth framework (RFC 9200)](https://www.rfc-editor
This crate implements the ACE-OAuth
(Authentication and Authorization for Constrained Environments using the OAuth 2.0 Framework)
framework as defined in [RFC 9200](https://www.rfc-editor.org/rfc/rfc9200).
Key features include CBOR-(de-)serializable data models such as [`AccessTokenRequest`],
Key features include CBOR-(de-)serializable data models such as [`AccessTokenRequest`](https://docs.rs/dcaf/latest/dcaf/struct.AccessTokenRequest.html),
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
[`EncryptCryptoBackend`](token::cose::EncryptCryptoBackend) or
[`SignCryptoBackend`](token::cose::SignCryptoBackend).
[`EncryptCryptoBackend`](https://docs.rs/dcaf/latest/dcaf/token/cose/trait.EncryptCryptoBackend.html)
or [`SignCryptoBackend`](https://docs.rs/dcaf/latest/dcaf/token/cose/trait.SignCryptoBackend.html).

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
Expand Down Expand Up @@ -55,10 +55,13 @@ token creation/verification functions. We'll quickly introduce both of these her
### Data models
[For example](https://www.rfc-editor.org/rfc/rfc9200#figure-6),
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](BinaryEncodedScope) or [AIF-encoded scopes](AifEncodedScope) would also work), as well as a
[`ProofOfPossessionKey`] (the key the access token should be bound to) in the `req_cnf` field.
For this, you'd need to create an [`AccessTokenRequest`](https://docs.rs/dcaf/latest/dcaf/struct.AccessTokenRequest.html),
which has to include at least a `client_id`. We'll also specify an audience, a scope (using
[`TextEncodedScope`](https://docs.rs/dcaf/latest/dcaf/struct.TextEncodedScope.html)---note that
[binary-encoded scopes](https://docs.rs/dcaf/latest/dcaf/struct.BinaryEncodedScope.html) or
[AIF-encoded scopes](https://docs.rs/dcaf/latest/dcaf/struct.AifEncodedScope.html) would also
work), as well as a [`ProofOfPossessionKey`](https://docs.rs/dcaf/latest/dcaf/enum.ProofOfPossessionKey.html)
(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:
```rust
Expand All @@ -75,36 +78,86 @@ 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 the `openssl` cryptographic backend and the signing key `sign_key`:

```rust
use coset::{AsCborValue, CoseKeyBuilder, HeaderBuilder, iana};
use coset::cwt::ClaimsSetBuilder;
use coset::iana::CwtClaimName;
use dcaf::{sign_access_token, verify_access_token};
use dcaf::error::{AccessTokenError, CoseCipherError};
use dcaf::token::cose::crypto_impl::openssl::OpensslContext;
use dcaf::token::cose::{CryptoBackend, HeaderBuilderExt};

let mut backend = OpensslContext::new();

let sign_key = CoseKeyBuilder::new_ec2_priv_key(
iana::EllipticCurve::P_256,
cose_ec2_key_x, // X component of elliptic curve key
cose_ec2_key_y, // Y component of elliptic curve key
cose_ec2_key_d // D component of elliptic curve key
)
.key_id("sign_key".as_bytes().to_vec())
.build();

let mut key_data = vec![0; 32];
backend.generate_rand(key_data.as_mut_slice()).map_err(CoseCipherError::from)?;
let key = CoseKeyBuilder::new_symmetric_key(key_data).build();

let unprotected_header = HeaderBuilder::new().algorithm(iana::Algorithm::ES256).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(&mut backend, &key, claims, &None, Some(unprotected_header), None)?;
assert!(verify_access_token(&mut backend, &key, &token, &None).is_ok());
```

## 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](https://www.rfc-editor.org/rfc/rfc9200#section-5.8).
[`AccessTokenRequest`](https://docs.rs/dcaf/latest/dcaf/struct.AccessTokenRequest.html) and
[`AccessTokenResponse`](https://docs.rs/dcaf/latest/dcaf/struct.AccessTokenResponse.html)
described in [section 5.8 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8).
In case of an error, an [`ErrorResponse`] should be used.

After an initial Unauthorized Resource Request Message, an
[`AuthServerRequestCreationHint`]
[`AuthServerRequestCreationHint`](https://docs.rs/dcaf/latest/dcaf/struct.AuthServerRequestCreationHint.html)
can be used to provide additional information to the client, as described in
[section 5.3 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.3).

### Common Data Types
Some types used across multiple scenarios include:
- [`Scope`] (as described in
- [`Scope`](https://docs.rs/dcaf/latest/dcaf/enum.Scope.html) (as described in
[section 5.8.1 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.1)),
either as a [`TextEncodedScope`], a [`BinaryEncodedScope`] or an [`AifEncodedScope`].
- [`ProofOfPossessionKey`] as specified in
[section 3.1 of RFC 8747](https://www.rfc-editor.org/rfc/rfc8747#section-3.1).
either as a [`TextEncodedScope`](https://docs.rs/dcaf/latest/dcaf/struct.TextEncodedScope.html),
a [`BinaryEncodedScope`](https://docs.rs/dcaf/latest/dcaf/struct.BinaryEncodedScope.html) or
an [`AifEncodedScope`](https://docs.rs/dcaf/latest/dcaf/struct.AifEncodedScope.html).
- [`ProofOfPossessionKey`](https://docs.rs/dcaf/latest/dcaf/enum.ProofOfPossessionKey.html) as
specified in [section 3.1 of RFC 8747](https://www.rfc-editor.org/rfc/rfc8747#section-3.1).
For example, this will be used in the access token's `cnf` claim.
- While not really a data type, various constants representing values used in ACE-OAuth
are provided in the [`constants`] module.
are provided in the [`constants`](https://docs.rs/dcaf/latest/dcaf/constants/index.html) module.

## Token handling

This crate also provides some functionality regarding the encoding and decoding of access
tokens, especially of CBOR Web Tokens.
tokens, especially of CBOR Web Tokens (CWTs), which are based on the COSE specification
(RFC 9052).

Generation and validation of CWTs is supported for CWTs based on signed and encrypted
COSE objects. Additionally, helper methods are provided to more easily create and validate
COSE objects that are encrypted, signed or authenticated using MACs.

See the [token] module-level documentation for more information.
See the [token](https://docs.rs/dcaf/latest/dcaf/token/index.html) module-level documentation
for more information.

<!-- cargo-rdme end -->

Expand Down
13 changes: 12 additions & 1 deletion src/common/test_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ where
}
}

/// Parameters used for a [`MockCipher`] AEAD operation.
#[derive(Clone, Debug, PartialEq, Eq)]
struct MockCipherAeadParams {
key: Vec<u8>,
Expand All @@ -107,6 +108,7 @@ impl MockCipherAeadParams {
}
}

/// Parameters used for a [`MockCipher`] ECDSA operation.
#[derive(Clone, Debug, PartialEq)]
struct MockCipherEcdsaParams {
key: CoseKey,
Expand All @@ -128,7 +130,16 @@ impl MockCipherEcdsaParams {
}
}

pub struct MockCipher<R: CryptoRng + Rng> {
/// "Mocked" Implementation of a cryptographic backend that does not actually perform cryptographic
/// operations.
///
/// Instead, it stores the parameters passed to it during the creation of COSE objects and performs
/// a lookup of those parameters (alongside a random value returned during creation) when attempting
/// to authenticate or decrypt the object.
///
/// Due to the way this cryptographic backend works, created COSE objects can only be
/// "authenticated" by the `MockCipher` instance they were created with.
pub(crate) struct MockCipher<R: CryptoRng + Rng> {
rng: R,
aes_gcm_inputs: BTreeMap<Vec<u8>, (MockCipherAeadParams, Vec<u8>)>,
aes_kw_inputs: BTreeMap<Vec<u8>, (MockCipherAeadParams, Vec<u8>)>,
Expand Down
Loading

0 comments on commit a5c4a78

Please sign in to comment.