Skip to content

Commit

Permalink
Merge pull request #28 from kanidm/20240321-key-object-support
Browse files Browse the repository at this point in the history
Update API surface to support Kanidm KeyObjects
  • Loading branch information
yaleman authored Apr 10, 2024
2 parents 311929d + 670fdc7 commit bcee118
Show file tree
Hide file tree
Showing 19 changed files with 465 additions and 133 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "compact_jwt"
version = "0.3.5"
version = "0.4.0-dev"
edition = "2021"
authors = ["William Brown <[email protected]>"]
description = "Minimal implementation of JWT for OIDC and other applications"
Expand All @@ -27,7 +27,7 @@ serde = { version = "^1.0.136", features = ["derive"] }
serde_json = "^1.0.79"
base64 = "^0.21.5"
base64urlsafedata = "0.1.0"
kanidm-hsm-crypto = { version = "^0.1.6", optional = true }
kanidm-hsm-crypto = { version = "^0.2.0", optional = true }
# kanidm-hsm-crypto = { path = "../hsm-crypto", optional = true }
openssl = { version = "^0.10.38", optional = true }
openssl-kdf = { version = "0.4.2", optional = true }
Expand Down
29 changes: 23 additions & 6 deletions src/compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,6 @@ impl fmt::Debug for JwsCompact {
}

impl JwsCompact {
/// Get the KID used to sign this Jws if present
pub fn get_jwk_kid(&self) -> Option<&str> {
self.header.kid.as_deref()
}

/// Get the embedded Url for the Jwk that signed this Jws.
///
/// You MUST ensure this url uses HTTPS and you MUST ensure that your
Expand Down Expand Up @@ -261,6 +256,14 @@ impl JwsVerifiable for JwsCompact {
}
}

fn alg(&self) -> JwaAlg {
self.header.alg
}

fn kid(&self) -> Option<&str> {
self.header.kid.as_deref()
}

fn post_process(&self, value: Jws) -> Result<Self::Verified, JwtError> {
Ok(value)
}
Expand Down Expand Up @@ -384,9 +387,18 @@ pub struct JweCompact {
pub(crate) authentication_tag: Vec<u8>,
}

impl fmt::Debug for JweCompact {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JweCompact")
.field("header", &self.header)
.field("encrypted_payload_length", &self.ciphertext.len())
.finish()
}
}

impl JweCompact {
/// Get the KID used to encipher this Jwe if present
pub fn get_jwk_kid(&self) -> Option<&str> {
pub fn kid(&self) -> Option<&str> {
self.header.kid.as_deref()
}

Expand All @@ -402,6 +414,11 @@ impl JweCompact {
pub fn get_jwk_pubkey(&self) -> Option<&Jwk> {
self.header.jwk.as_ref()
}

/// Return the CEK Algorithm and the inner encryption type.
pub fn get_alg_enc(&self) -> (JweAlg, JweEnc) {
(self.header.alg, self.header.enc)
}
}

impl FromStr for JweCompact {
Expand Down
7 changes: 5 additions & 2 deletions src/crypto/a128gcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub(crate) const KEY_LEN: usize = 16;
const IV_LEN: usize = 12;
const AUTH_TAG_LEN: usize = 16;

/// A JWE inner encipher and decipher for AES 128 GCM. This is the recommended inner
/// encryption algorithm to use.
#[derive(Clone)]
pub struct JweA128GCMEncipher {
aes_key: [u8; KEY_LEN],
Expand Down Expand Up @@ -93,11 +95,12 @@ impl JweEncipherInner for JweA128GCMEncipher {
}

impl JweA128GCMEncipher {
/// The required length for this cipher key
pub fn key_len() -> usize {
16
KEY_LEN
}

pub fn decipher_inner(&self, jwec: &JweCompact) -> Result<Vec<u8>, JwtError> {
pub(crate) fn decipher_inner(&self, jwec: &JweCompact) -> Result<Vec<u8>, JwtError> {
aes_gcm_decipher(
Cipher::aes_128_gcm(),
&jwec.ciphertext,
Expand Down
102 changes: 90 additions & 12 deletions src/crypto/a128kw.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use crate::compact::{JweAlg, JweCompact, JweProtectedHeader};
use crate::jwe::Jwe;
use crate::traits::*;
use crate::JwtError;
use crate::{JwtError, KID_LEN};

use openssl::aes::{unwrap_key, wrap_key, AesKey};
use openssl::hash;
use openssl::rand::rand_bytes;

use std::fmt;

// Do I need some inner type to handle the enc bit?

pub(crate) const KEY_LEN: usize = 16;
Expand All @@ -16,13 +19,32 @@ const KW_EXTRA: usize = 8;
/// shared keys, or are creating encrypted service tokens.
#[derive(Clone)]
pub struct JweA128KWEncipher {
/// Mark that the key is ephemeral and shall not be exported.
is_ephemeral: bool,
/// If the KID should be embedded during signing
sign_option_embed_kid: bool,
/// The KID of this encryption key. This is a truncated sha256 digest.
kid: String,
/// The wrapping key.
wrap_key: [u8; KEY_LEN],
}

impl fmt::Debug for JweA128KWEncipher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JweA128KWEncipher")
.field("kid", &self.kid)
.finish()
}
}

impl JweEncipherOuter for JweA128KWEncipher {
/// See [JweEncipherOuter]
fn set_header_alg(&self, hdr: &mut JweProtectedHeader) -> Result<(), JwtError> {
hdr.alg = JweAlg::A128KW;
if self.sign_option_embed_kid {
hdr.kid = Some(self.kid.clone());
}

Ok(())
}

Expand Down Expand Up @@ -55,16 +77,46 @@ impl JweEncipherOuter for JweA128KWEncipher {
}

impl JweA128KWEncipher {
/// Generate an ephemeral outer key.
pub fn generate_ephemeral() -> Result<Self, JwtError> {
/// Create a new direct A128KW Encipher. The key will be randomly generated.
pub fn new() -> Result<Self, JwtError> {
let mut wrap_key = [0; KEY_LEN];

rand_bytes(&mut wrap_key).map_err(|ossl_err| {
debug!(?ossl_err);
JwtError::OpenSSLError
})?;

Ok(JweA128KWEncipher { wrap_key })
Self::try_from(wrap_key)
}

/// Get the Key ID of this encipher
pub fn kid(&self) -> &str {
self.kid.as_str()
}

/// If this key is not ephemeral, expose the bytes of the AES wrap key. This
/// allows the key to be persisted.
pub fn key_bytes(&self) -> Result<[u8; KEY_LEN], JwtError> {
if self.is_ephemeral {
Err(JwtError::UnableToReleaseKey)
} else {
Ok(self.wrap_key)
}
}

/// Enable or disable setting the KID into the header of any encrypted JWE's
pub fn set_sign_option_embed_kid(mut self, value: bool) -> Self {
self.sign_option_embed_kid = value;
self
}

/// Generate an ephemeral outer key.
pub fn generate_ephemeral() -> Result<Self, JwtError> {
Self::new().map(|mut key| {
key.is_ephemeral = true;
key.sign_option_embed_kid = false;
key
})
}

/// Given a JWE, encipher its content to a compact form.
Expand Down Expand Up @@ -105,28 +157,54 @@ impl JweA128KWEncipher {
payload,
})
}

pub(crate) fn load_ephemeral(wrap_key: [u8; KEY_LEN]) -> Self {
JweA128KWEncipher {
is_ephemeral: true,
sign_option_embed_kid: false,
kid: String::default(),
wrap_key,
}
}
}

impl From<[u8; KEY_LEN]> for JweA128KWEncipher {
fn from(wrap_key: [u8; KEY_LEN]) -> JweA128KWEncipher {
JweA128KWEncipher { wrap_key }
impl TryFrom<[u8; KEY_LEN]> for JweA128KWEncipher {
type Error = JwtError;

fn try_from(wrap_key: [u8; KEY_LEN]) -> Result<Self, Self::Error> {
let digest = hash::MessageDigest::sha256();

let kid = hash::hash(digest, &wrap_key)
.map(|hashout| {
let mut s = hex::encode(hashout);
s.truncate(KID_LEN);
s
})
.map_err(|_| JwtError::OpenSSLError)?;

Ok(JweA128KWEncipher {
is_ephemeral: false,
sign_option_embed_kid: true,
kid,
wrap_key,
})
}
}

impl TryFrom<Vec<u8>> for JweA128KWEncipher {
impl TryFrom<&[u8]> for JweA128KWEncipher {
type Error = JwtError;

fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() != KEY_LEN {
// Wrong key size.
return Err(JwtError::InvalidKey);
}

let mut wrap_key = [0; KEY_LEN];

wrap_key.copy_from_slice(&value);
wrap_key.copy_from_slice(value);

Ok(JweA128KWEncipher { wrap_key })
Self::try_from(wrap_key)
}
}

Expand Down Expand Up @@ -181,7 +259,7 @@ mod tests {
assert!(jwec.get_jwk_pubkey().is_none());

let a128kw_encipher =
JweA128KWEncipher::try_from(a128kw_key).expect("Unable to create encipher");
JweA128KWEncipher::try_from(a128kw_key.as_slice()).expect("Unable to create encipher");

let released = a128kw_encipher
.decipher(&jwec)
Expand Down
4 changes: 3 additions & 1 deletion src/crypto/a256gcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub(crate) const KEY_LEN: usize = 32;
const IV_LEN: usize = 12;
const AUTH_TAG_LEN: usize = 16;

/// A JWE inner encipher and decipher for AES 256 GCM.
#[derive(Clone)]
pub struct JweA256GCMEncipher {
aes_key: [u8; KEY_LEN],
Expand Down Expand Up @@ -111,11 +112,12 @@ impl JweEncipherInner for JweA256GCMEncipher {
}

impl JweA256GCMEncipher {
/// The required length for this cipher key
pub fn key_len() -> usize {
KEY_LEN
}

pub fn decipher_inner(&self, jwec: &JweCompact) -> Result<Vec<u8>, JwtError> {
pub(crate) fn decipher_inner(&self, jwec: &JweCompact) -> Result<Vec<u8>, JwtError> {
super::a128gcm::aes_gcm_decipher(
Cipher::aes_256_gcm(),
&jwec.ciphertext,
Expand Down
10 changes: 5 additions & 5 deletions src/crypto/ecdhes_a128kw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ impl JweEncipherOuter for JweEcdhEsA128KWEncipher {

derive_key(&self.priv_key, &self.peer_public_key).and_then(|wrapping_key|
// use A128KW with the derived wrap key as usual.
JweA128KWEncipher::from(wrapping_key)
JweA128KWEncipher::load_ephemeral(wrapping_key)
.wrap_key(key_to_wrap))
}
}
Expand Down Expand Up @@ -228,10 +228,10 @@ impl JweEcdhEsA128KWDecipher {
}
};

derive_key(&self.priv_key, &ephemeral_public_key).and_then(|wrapping_key|
// use A128KW with the derived wrap key as usual.
JweA128KWEncipher::from(wrapping_key)
.decipher(jwec))
derive_key(&self.priv_key, &ephemeral_public_key).and_then(|wrapping_key| {
// use A128KW with the derived wrap key as usual.
JweA128KWEncipher::load_ephemeral(wrapping_key).decipher(jwec)
})
}
}

Expand Down
Loading

0 comments on commit bcee118

Please sign in to comment.