Skip to content

Commit

Permalink
Merge pull request #21 from kanidm/20240131-prt-hmac
Browse files Browse the repository at this point in the history
20240131 prt hmac
  • Loading branch information
dmulder authored Feb 7, 2024
2 parents 420f2ea + 313f605 commit 9a626aa
Show file tree
Hide file tree
Showing 9 changed files with 392 additions and 133 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ license = "MPL-2.0"
[features]
unsafe_release_without_verify = []
hsm-crypto = ["kanidm-hsm-crypto"]
msextensions = ["dep:openssl-kdf", "openssl", "hsm-crypto"]
default = ["openssl"]

[package.metadata.docs.rs]
Expand All @@ -28,6 +29,7 @@ base64 = "^0.21.5"
base64urlsafedata = "0.1.0"
kanidm-hsm-crypto = { version = "^0.1.4", optional = true }
openssl = { version = "^0.10.38", optional = true }
openssl-kdf = { version = "0.4.2", optional = true }
url = { version = "^2.2.2", features = ["serde"] }
uuid = { version = "^1.0.0", features = ["serde"] }
tracing = "^0.1.34"
Expand Down
9 changes: 7 additions & 2 deletions src/compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ pub struct ProtectedHeader {
skip_serializing_if = "Option::is_none"
)]
pub(crate) x5t_s256: Option<()>,
// Don't allow extra header names?
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) ctx: Option<String>,
}

/// A Compact JWS that is able to be verified or stringified for transmission
Expand Down Expand Up @@ -365,7 +366,8 @@ pub struct JweProtectedHeader {
skip_serializing_if = "Option::is_none"
)]
pub(crate) x5t_s256: Option<()>,
// Don't allow extra header names?
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) ctx: Option<String>,
}

/// A Compact JWE that is able to be deciphered or stringified for transmission
Expand Down Expand Up @@ -419,6 +421,9 @@ impl FromStr for JweCompact {
JwtError::InvalidBase64
})
.and_then(|bytes| {
// let hdr_str = String::from_utf8(bytes.to_vec()).unwrap();
// trace!(%hdr_str);

serde_json::from_slice(&bytes).map_err(|e| {
debug!(?e, "invalid header format - invalid json");
JwtError::InvalidHeaderFormat
Expand Down
86 changes: 47 additions & 39 deletions src/crypto/a128cbc_hs256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use openssl::sign::Signer;
use openssl::symm::{Cipher, Crypter, Mode};

const AES_KEY_LEN: usize = 16;
const HMAC_KEY_LEN: usize = 16;
pub const HMAC_KEY_LEN: usize = 16;
const HMAC_SIG_LEN: usize = 32;
const HMAC_TRUNC_SIG_LEN: usize = 16;

Expand Down Expand Up @@ -94,56 +94,64 @@ impl JweA128CBCHS256Decipher {

let cipher = Cipher::aes_128_cbc();

let block_size = cipher.block_size();
// The ciphertext needs pkcs7 padding, so we have to deal with that later.
let plaintext_len = jwec.ciphertext.len() + block_size;
let mut plaintext = vec![0; plaintext_len];

let mut decrypter = Crypter::new(cipher, Mode::Decrypt, &self.aes_cbc_key, Some(&jwec.iv))
.map_err(|ossl_err| {
debug!(?ossl_err);
JwtError::OpenSSLError
})?;

decrypter.pad(true);

let mut count = 0;
decipher(cipher, &self.aes_cbc_key, &jwec.ciphertext, &jwec.iv)
}
}

let mut idx = 0;
let mut cipher_boundary = idx + block_size;
let mut plaintext_boundary = count + (block_size * 2);
pub(crate) fn decipher(
cipher: Cipher,
aes_cbc_key: &[u8],
ciphertext: &[u8],
iv: &[u8],
) -> Result<Vec<u8>, JwtError> {
let block_size = cipher.block_size();
let plaintext_len = ciphertext.len() + block_size;
let mut plaintext = vec![0; plaintext_len];

let mut decrypter =
Crypter::new(cipher, Mode::Decrypt, aes_cbc_key, Some(iv)).map_err(|ossl_err| {
debug!(?ossl_err);
JwtError::OpenSSLError
})?;

// Only works because of CBC mode - cipher text will be block_size * N, and
// plaintext_len will be block_size * (N + 1).
//
// Unclear if padding is needed?
while idx < jwec.ciphertext.len() {
let cipher_chunk = &jwec.ciphertext[idx..cipher_boundary];
let mut_plaintext_chunk = &mut plaintext[count..plaintext_boundary];
decrypter.pad(true);

count += decrypter
.update(cipher_chunk, mut_plaintext_chunk)
.map_err(|ossl_err| {
debug!(?ossl_err);
JwtError::OpenSSLError
})?;
let mut count = 0;

idx += block_size;
cipher_boundary = idx + block_size;
plaintext_boundary = count + (block_size * 2);
}
let mut idx = 0;
let mut cipher_boundary = idx + block_size;
let mut plaintext_boundary = count + (block_size * 2);

// Only works because of CBC mode - cipher text will be block_size * N, and
// plaintext_len will be block_size * (N + 1).
//
// Unclear if padding is needed?
while idx < ciphertext.len() {
let cipher_chunk = &ciphertext[idx..cipher_boundary];
let mut_plaintext_chunk = &mut plaintext[count..plaintext_boundary];

count += decrypter
.finalize(mut_plaintext_chunk)
.update(cipher_chunk, mut_plaintext_chunk)
.map_err(|ossl_err| {
debug!(?ossl_err);
JwtError::OpenSSLError
})?;

plaintext.truncate(count);

Ok(plaintext)
idx += block_size;
cipher_boundary = idx + block_size;
plaintext_boundary = count + (block_size * 2);
}

let mut_plaintext_chunk = &mut plaintext[count..plaintext_boundary];

count += decrypter
.finalize(mut_plaintext_chunk)
.map_err(|ossl_err| {
debug!(?ossl_err);
JwtError::OpenSSLError
})?;

plaintext.truncate(count);

Ok(plaintext)
}
19 changes: 12 additions & 7 deletions src/crypto/a256gcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ pub struct JweA256GCMEncipher {
aes_key: [u8; KEY_LEN],
}

#[cfg(test)]
#[cfg(feature = "msextensions")]
impl JweA256GCMEncipher {
pub(crate) fn assert_key(&self, key: &[u8]) -> bool {
self.aes_key == key
pub(crate) fn raw_key(&self) -> [u8; KEY_LEN] {
self.aes_key
}
}

Expand All @@ -41,6 +41,15 @@ impl TryFrom<&[u8]> for JweA256GCMEncipher {
}
}

impl From<&[u8; 32]> for JweA256GCMEncipher {
fn from(r_aes_key: &[u8; 32]) -> Self {
let mut aes_key = [0; KEY_LEN];
aes_key.copy_from_slice(r_aes_key);

JweA256GCMEncipher { aes_key }
}
}

impl JweEncipherInnerK256 for JweA256GCMEncipher {}

impl JweEncipherInner for JweA256GCMEncipher {
Expand Down Expand Up @@ -102,10 +111,6 @@ impl JweEncipherInner for JweA256GCMEncipher {
}

impl JweA256GCMEncipher {
pub(crate) fn ms_oapxbc_key(&self) -> &[u8] {
&self.aes_key
}

pub fn key_len() -> usize {
KEY_LEN
}
Expand Down
82 changes: 47 additions & 35 deletions src/crypto/hs256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::fmt;
use std::hash::{Hash, Hasher};

use crate::compact::{JwaAlg, JwsCompact, ProtectedHeader};
use crate::jws::JwsCompactSign2Data;
use crate::traits::*;
use base64::{engine::general_purpose, Engine as _};

Expand Down Expand Up @@ -67,11 +68,55 @@ impl JwsHs256Signer {
})?;

let kid = hash::hash(digest, &kid)
.map(hex::encode)
.map(|out| {
let half = out.len() / 2;
hex::encode(out.split_at(half).0)
})
.map_err(|_| JwtError::OpenSSLError)?;

Ok(JwsHs256Signer { kid, skey, digest })
}

pub(crate) fn sign_inner<V: JwsSignable>(
&self,
jws: &V,
sign_data: JwsCompactSign2Data,
) -> Result<V::Signed, JwtError> {
let hdr_b64 = serde_json::to_vec(&sign_data.header)
.map_err(|e| {
debug!(?e);
JwtError::InvalidHeaderFormat
})
.map(|bytes| general_purpose::URL_SAFE_NO_PAD.encode(bytes))?;

let mut signer = sign::Signer::new(self.digest, &self.skey).map_err(|e| {
debug!(?e);
JwtError::OpenSSLError
})?;

signer
.update(hdr_b64.as_bytes())
.and_then(|_| signer.update(".".as_bytes()))
.and_then(|_| signer.update(sign_data.payload_b64.as_bytes()))
.map_err(|e| {
debug!(?e);
JwtError::OpenSSLError
})?;

let signature = signer.sign_to_vec().map_err(|e| {
debug!(?e);
JwtError::OpenSSLError
})?;

let jwsc = JwsCompact {
header: sign_data.header,
hdr_b64,
payload_b64: sign_data.payload_b64,
signature,
};

jws.post_process(jwsc)
}
}

impl TryFrom<&[u8]> for JwsHs256Signer {
Expand Down Expand Up @@ -117,40 +162,7 @@ impl JwsSigner for JwsHs256Signer {
// Let the signer update the header as required.
self.update_header(&mut sign_data.header)?;

let hdr_b64 = serde_json::to_vec(&sign_data.header)
.map_err(|e| {
debug!(?e);
JwtError::InvalidHeaderFormat
})
.map(|bytes| general_purpose::URL_SAFE_NO_PAD.encode(bytes))?;

let mut signer = sign::Signer::new(self.digest, &self.skey).map_err(|e| {
debug!(?e);
JwtError::OpenSSLError
})?;

signer
.update(hdr_b64.as_bytes())
.and_then(|_| signer.update(".".as_bytes()))
.and_then(|_| signer.update(sign_data.payload_b64.as_bytes()))
.map_err(|e| {
debug!(?e);
JwtError::OpenSSLError
})?;

let signature = signer.sign_to_vec().map_err(|e| {
debug!(?e);
JwtError::OpenSSLError
})?;

let jwsc = JwsCompact {
header: sign_data.header,
hdr_b64,
payload_b64: sign_data.payload_b64,
signature,
};

jws.post_process(jwsc)
self.sign_inner(jws, sign_data)
}
}

Expand Down
8 changes: 6 additions & 2 deletions src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ mod hs256;
mod rs256;
mod x509;

mod ms_oapxbc;
mod rsaes_oaep;

mod a128cbc_hs256;
Expand All @@ -25,6 +24,9 @@ mod ecdhes_a128kw;
#[cfg(feature = "hsm-crypto")]
mod tpm;

#[cfg(feature = "msextensions")]
mod ms_oapxbc;

pub use es256::{JwsEs256Signer, JwsEs256Verifier};
pub use hs256::JwsHs256Signer;
pub use rs256::{JwsRs256Signer, JwsRs256Verifier};
Expand All @@ -33,9 +35,11 @@ pub use x509::{JwsX509Verifier, JwsX509VerifierBuilder};
pub use a128kw::JweA128KWEncipher;
pub use a256kw::JweA256KWEncipher;
pub use ecdhes_a128kw::{JweEcdhEsA128KWDecipher, JweEcdhEsA128KWEncipher};
pub use ms_oapxbc::MsOapxbcSessionKey;
pub use rsaes_oaep::{JweRSAOAEPDecipher, JweRSAOAEPEncipher};

#[cfg(feature = "msextensions")]
pub use ms_oapxbc::MsOapxbcSessionKey;

#[cfg(feature = "hsm-crypto")]
pub use tpm::JwsTpmSigner;

Expand Down
Loading

0 comments on commit 9a626aa

Please sign in to comment.