Skip to content

Commit

Permalink
feat: allow specifying COSE header when signing
Browse files Browse the repository at this point in the history
- Add alternative versions of signing methods that take a
  cose::headers::CoseHeader instead of an algorithm.
- Add new_cose_header helper function that creates a new jwt::Header
  from an Algorithm.

Signed-off-by: Sergei Trofimov <[email protected]>
  • Loading branch information
setrofim committed Nov 6, 2024
1 parent fa2839f commit 4034ae0
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 22 deletions.
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,17 +238,21 @@ let exp2 = match ear2.extensions.get_by_name("exp").unwrap() {
assert!(SystemTime::now().duration_since(UNIX_EPOCH).unwrap() < exp2);
```

# JWT headers
# JWT/CWT headers

When signing with `sign_jwt_pem`/`sign_jwk_der`, only the `alg` header is set in the resulting
JWT based on the the specified algorithm. If other headers need to be specified, then
`sign_jwt_pem_with_header` and `sign_jwk_der_with_header` can be used instead; these take a
`jwt::Header` instead of an algorithm. A new header can be created from an algorithm using
`new_jwt_header`.

The same goes when signing as COSE_Sign1Message. Only `alg` header is set by default, however,
`_with_header` signing methods can be used to specify a custom `cose::headers::CoseHeader`,
which can be reating from an algorithm using `new_cwt_header`.

```rust
use std::collections::BTreeMap;
use ear::{Ear, VerifierID, Algorithm, Appraisal, Extensions, new_jwt_header};
use ear::{Ear, VerifierID, Algorithm, Appraisal, Extensions, new_jwt_header, new_cose_header};

const SIGNING_KEY: &str = "-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPp4XZRnRHSMhGg0t
Expand All @@ -271,11 +275,20 @@ fn main() {
extensions: Extensions::new(),
};

let mut header = new_jwt_header(&Algorithm::ES256).unwrap();
// JWT

let mut jwt_header = new_jwt_header(&Algorithm::ES256).unwrap();
// set additional header(s)
jwt_header.kid = Some("key-ident".to_string());

let signed_jwt = token.sign_jwt_pem_with_header(&jwt_header, SIGNING_KEY.as_bytes()).unwrap();
// CWT

let mut cwt_header = new_cose_header(&Algorithm::ES256).unwrap();
// set additional header(s)
header.kid = Some("key-ident".to_string());
cwt_header.kid("key-ident".as_bytes().to_vec(), true, false);

let signed = token.sign_jwt_pem_with_header(&header, SIGNING_KEY.as_bytes()).unwrap();
let signed_cwt = token.sign_cose_pem_with_header(cwt_header, SIGNING_KEY.as_bytes()).unwrap();
}
```

Expand Down
65 changes: 53 additions & 12 deletions src/ear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,28 +263,52 @@ impl Ear {

/// Encode the EAR as a COSE token, signing it with the specified PEM-encoded key
pub fn sign_cose_pem(&self, alg: Algorithm, key: &[u8]) -> Result<Vec<u8>, Error> {
self.sign_cose_bytes(alg, key, KeyFormat::PEM)
let header = new_cose_header(&alg)?;
self.sign_cose_bytes_with_header(header, key, KeyFormat::PEM)
}

/// Encode the EAR as a COSE token, signing it with the specified DER-encoded key
pub fn sign_cose_der(&self, alg: Algorithm, key: &[u8]) -> Result<Vec<u8>, Error> {
self.sign_cose_bytes(alg, key, KeyFormat::DER)
let header = new_cose_header(&alg)?;
self.sign_cose_bytes_with_header(header, key, KeyFormat::DER)
}

fn sign_cose_bytes(
/// Encode the EAR as a COSE token with the specified header, signing it with the specified
/// PEM-encoded key
pub fn sign_cose_pem_with_header(
&self,
alg: Algorithm,
header: cose::headers::CoseHeader,
key: &[u8],
) -> Result<Vec<u8>, Error> {
self.sign_cose_bytes_with_header(header, key, KeyFormat::PEM)
}

/// Encode the EAR as a COSE token with the specified header, signing it with the specified
/// DER-encoded key
pub fn sign_cose_der_with_header(
&self,
header: cose::headers::CoseHeader,
key: &[u8],
) -> Result<Vec<u8>, Error> {
self.sign_cose_bytes_with_header(header, key, KeyFormat::DER)
}

fn sign_cose_bytes_with_header(
&self,
header: cose::headers::CoseHeader,
key: &[u8],
key_fmt: KeyFormat,
) -> Result<Vec<u8>, Error> {
let cose_alg = alg_to_cose(&alg)?;
let cose_alg = header
.alg
.ok_or(Error::SignError("alg header must be set".to_string()))?;

let mut cose_key = cose::keys::CoseKey::new();
cose_key.alg(cose_alg);
cose_key.key_ops(vec![cose::keys::KEY_OPS_SIGN]);

match alg {
Algorithm::ES256 | Algorithm::ES384 | Algorithm::PS512 => {
match cose_alg {
cose::algs::ES256 | cose::algs::ES384 | cose::algs::PS512 => {
let ec_key = match key_fmt {
KeyFormat::PEM => ec::EcKey::private_key_from_pem(key),
KeyFormat::DER => ec::EcKey::private_key_from_der(key),
Expand Down Expand Up @@ -320,7 +344,7 @@ impl Ear {
cose_key.y(y_ref.to_vec());
cose_key.d(ec_key.private_key().to_vec());
}
Algorithm::EdDSA => {
cose::algs::EDDSA => {
cose_key.kty(cose::keys::OKP);
cose_key.crv(cose::keys::ED25519);

Expand All @@ -337,20 +361,28 @@ impl Ear {
cose_key.d(raw[..32].to_vec());
cose_key.x(raw[32..].to_vec());
}
_ => return Err(Error::SignError(format!("algorithm {alg:?} not supported"))),
_ => {
return Err(Error::SignError(format!(
"algorithm {cose_alg:?} not supported"
)))
}
};

self.sign_cose(cose_alg, &cose_key)
self.sign_cose_with_header(header, &cose_key)
}

fn sign_cose(&self, alg: i32, key: &cose::keys::CoseKey) -> Result<Vec<u8>, Error> {
fn sign_cose_with_header(
&self,
header: cose::headers::CoseHeader,
key: &cose::keys::CoseKey,
) -> Result<Vec<u8>, Error> {
let mut payload: Vec<u8> = Vec::new();
ciborium::ser::into_writer(self, &mut payload)
.map_err(|e| Error::SignError(e.to_string()))?;

let mut sign1 = CoseMessage::new_sign();
sign1.payload(payload);
sign1.header.alg(alg, true, false);
sign1.add_header(header);

if let Some(a) = key.alg {
if a != sign1.header.alg.unwrap() {
Expand Down Expand Up @@ -543,6 +575,15 @@ pub fn new_jwt_header(alg: &Algorithm) -> Result<jwt::Header, Error> {
Ok(jwt::Header::new(alg_to_jwt_alg(alg)?))
}

#[inline]
pub fn new_cose_header(alg: &Algorithm) -> Result<cose::headers::CoseHeader, Error> {
let cose_alg = alg_to_cose(alg)?;
let mut header = cose::headers::CoseHeader::new();
header.alg(cose_alg, true, false);

Ok(header)
}

#[inline]
fn alg_to_jwt_alg(alg: &Algorithm) -> Result<jwt::Algorithm, Error> {
match alg {
Expand Down
24 changes: 19 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,17 +238,21 @@
//! assert!(SystemTime::now().duration_since(UNIX_EPOCH).unwrap() < exp2);
//! ```
//!
//! # JWT headers
//! # JWT/CWT headers
//!
//! When signing with `sign_jwt_pem`/`sign_jwk_der`, only the `alg` header is set in the resulting
//! JWT based on the the specified algorithm. If other headers need to be specified, then
//! `sign_jwt_pem_with_header` and `sign_jwk_der_with_header` can be used instead; these take a
//! `jwt::Header` instead of an algorithm. A new header can be created from an algorithm using
//! `new_jwt_header`.
//!
//! The same goes when signing as COSE_Sign1Message. Only `alg` header is set by default, however,
//! `_with_header` signing methods can be used to specify a custom `cose::headers::CoseHeader`,
//! which can be reating from an algorithm using `new_cwt_header`.
//!
//! ```
//! use std::collections::BTreeMap;
//! use ear::{Ear, VerifierID, Algorithm, Appraisal, Extensions, new_jwt_header};
//! use ear::{Ear, VerifierID, Algorithm, Appraisal, Extensions, new_jwt_header, new_cose_header};
//!
//! const SIGNING_KEY: &str = "-----BEGIN PRIVATE KEY-----
//! MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPp4XZRnRHSMhGg0t
Expand All @@ -271,11 +275,20 @@
//! extensions: Extensions::new(),
//! };
//!
//! let mut header = new_jwt_header(&Algorithm::ES256).unwrap();
//! // JWT
//!
//! let mut jwt_header = new_jwt_header(&Algorithm::ES256).unwrap();
//! // set additional header(s)
//! jwt_header.kid = Some("key-ident".to_string());
//!
//! let signed_jwt = token.sign_jwt_pem_with_header(&jwt_header, SIGNING_KEY.as_bytes()).unwrap();
//! // CWT
//!
//! let mut cwt_header = new_cose_header(&Algorithm::ES256).unwrap();
//! // set additional header(s)
//! header.kid = Some("key-ident".to_string());
//! cwt_header.kid("key-ident".as_bytes().to_vec(), true, false);
//!
//! let signed = token.sign_jwt_pem_with_header(&header, SIGNING_KEY.as_bytes()).unwrap();
//! let signed_cwt = token.sign_cose_pem_with_header(cwt_header, SIGNING_KEY.as_bytes()).unwrap();
//! }
//! ```
//!
Expand Down Expand Up @@ -303,6 +316,7 @@ mod trust;
pub use self::algorithm::Algorithm;
pub use self::appraisal::Appraisal;
pub use self::base64::Bytes;
pub use self::ear::new_cose_header;
pub use self::ear::new_jwt_header;
pub use self::ear::Ear;
pub use self::error::Error;
Expand Down

0 comments on commit 4034ae0

Please sign in to comment.