Skip to content

Commit

Permalink
Derive the decryption key using the ctx
Browse files Browse the repository at this point in the history
As explained in [MS-OAPXBC] 3.1.5.1.3.3. The
decryption algorithm is incorrect in the Jwe
header when the response is a PrtV2, and instead
should be decrypted with aes_256_cbc. This is not
documented by Microsoft, but has just been
observed in practice.

Signed-off-by: David Mulder <[email protected]>
  • Loading branch information
dmulder committed Feb 1, 2024
1 parent 420f2ea commit d505a80
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 6 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ url = { version = "^2.2.2", features = ["serde"] }
uuid = { version = "^1.0.0", features = ["serde"] }
tracing = "^0.1.34"
hex = "0.4"
openssl-kdf = "0.4.2"


[dev-dependencies]
Expand Down
2 changes: 2 additions & 0 deletions src/compact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,8 @@ pub struct JweProtectedHeader {
skip_serializing_if = "Option::is_none"
)]
pub(crate) x5t_s256: Option<()>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) ctx: Option<String>,
// Don't allow extra header names?
}

Expand Down
2 changes: 1 addition & 1 deletion src/crypto/a256gcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const AUTH_TAG_LEN: usize = 16;

#[derive(Clone)]
pub struct JweA256GCMEncipher {
aes_key: [u8; KEY_LEN],
pub(crate) aes_key: [u8; KEY_LEN],
}

#[cfg(test)]
Expand Down
56 changes: 51 additions & 5 deletions src/crypto/ms_oapxbc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ use super::rsaes_oaep::{JweRSAOAEPDecipher, JweRSAOAEPEncipher};

use crate::jwe::JweBuilder;

use openssl::hash::MessageDigest;
use openssl::pkey::Private;
use openssl::pkey::Public;
use openssl::rsa::Rsa;
use openssl::symm::decrypt;
use openssl::symm::Cipher;
use openssl_kdf::{perform_kdf, KdfArgument, KdfKbMode, KdfMacType, KdfType};

use base64::{engine::general_purpose, Engine as _};

/// A [MS-OAPXBC] 3.2.5.1.2.2 yielded session key. This is used as a form of key agreement
/// for MS clients, where this key can now be used to encipher and decipher arbitrary
Expand All @@ -28,6 +34,11 @@ pub enum MsOapxbcSessionKey {
},
}

pub enum PrtVersion {
V2,
V3,
}

#[cfg(test)]
impl MsOapxbcSessionKey {
pub(crate) fn assert_key(&self, key: &[u8]) -> bool {
Expand Down Expand Up @@ -91,7 +102,7 @@ impl MsOapxbcSessionKey {

impl MsOapxbcSessionKey {
/// Given a JWE in compact form, decipher and authenticate its content.
pub fn decipher(&self, jwec: &JweCompact) -> Result<Jwe, JwtError> {
pub fn decipher(&self, jwec: &JweCompact, prt_vers: PrtVersion) -> Result<Jwe, JwtError> {
// Alg must be direct.
if jwec.header.alg != JweAlg::DIRECT {
return Err(JwtError::AlgorithmUnavailable);
Expand All @@ -104,10 +115,45 @@ impl MsOapxbcSessionKey {
return Err(JwtError::CipherUnavailable);
}

aes_key.decipher_inner(jwec).map(|payload| Jwe {
header: jwec.header.clone(),
payload,
})
// If a ctx is present in the header, derive the session key
let mut session_key = aes_key.clone();
if let Some(ctx) = &jwec.header.ctx {
let decoded_ctx = general_purpose::STANDARD
.decode(ctx)
.map_err(|_| JwtError::InvalidBase64)?;
let args = [
&KdfArgument::KbMode(KdfKbMode::Counter),
&KdfArgument::Mac(KdfMacType::Hmac(MessageDigest::sha256())),
&KdfArgument::Salt(b"AzureAD-SecureConversation"),
&KdfArgument::KbInfo(&decoded_ctx),
&KdfArgument::Key(&session_key.aes_key),
];
let derived_key =
perform_kdf(KdfType::KeyBased, &args, session_key.aes_key.len())
.map_err(|_| JwtError::InvalidKey)?;
session_key = JweA256GCMEncipher::try_from(derived_key.as_slice())?;
}

match prt_vers {
PrtVersion::V2 => {
let cipher = Cipher::aes_256_cbc();
let decrypted = decrypt(
cipher,
&session_key.aes_key,
Some(&jwec.iv),
&jwec.ciphertext,
)
.map_err(|_| JwtError::OpenSSLError)?;
Ok(Jwe {
header: jwec.header.clone(),
payload: decrypted,
})
}
PrtVersion::V3 => aes_key.decipher_inner(jwec).map(|payload| Jwe {
header: jwec.header.clone(),
payload,
}),
}
}
}
}
Expand Down

0 comments on commit d505a80

Please sign in to comment.