Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update API surface to support Kanidm KeyObjects #28

Merged
merged 3 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading