diff --git a/README.md b/README.md index 5d684a2..306197d 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ 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 @@ -246,9 +246,13 @@ JWT based on the the specified algorithm. If other headers need to be specified, `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 @@ -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(); } ``` diff --git a/src/ear.rs b/src/ear.rs index b987c82..dcb283d 100644 --- a/src/ear.rs +++ b/src/ear.rs @@ -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, 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, 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, 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, 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, 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), @@ -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); @@ -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, Error> { + fn sign_cose_with_header( + &self, + header: cose::headers::CoseHeader, + key: &cose::keys::CoseKey, + ) -> Result, Error> { let mut payload: Vec = 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() { @@ -543,6 +575,15 @@ pub fn new_jwt_header(alg: &Algorithm) -> Result { Ok(jwt::Header::new(alg_to_jwt_alg(alg)?)) } +#[inline] +pub fn new_cose_header(alg: &Algorithm) -> Result { + 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 { match alg { diff --git a/src/lib.rs b/src/lib.rs index a79df8a..67a813c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -238,7 +238,7 @@ //! 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 @@ -246,9 +246,13 @@ //! `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 @@ -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(); //! } //! ``` //! @@ -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;