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

fix: properly map SSPI flags to Kerberos GSSAPI flags #189

Merged
merged 8 commits into from
Dec 13, 2023
133 changes: 131 additions & 2 deletions src/kerberos/client/generators.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::str::FromStr;

use bitflags;
use md5::{Digest, Md5};
use picky_asn1::bit_string::BitString;
use picky_asn1::date::GeneralizedTime;
Expand Down Expand Up @@ -345,7 +346,100 @@ pub fn generate_tgs_req(options: GenerateTgsReqOptions) -> Result<TgsReq> {
#[derive(Debug)]
pub struct ChecksumOptions {
pub checksum_type: Vec<u8>,
pub checksum_value: Vec<u8>,
pub checksum_value: ChecksumValues,
}

#[derive(Debug)]
pub struct ChecksumValues {
inner: Vec<u8>, // use named fields for future extensibility, this is a temporary solution
CBenoit marked this conversation as resolved.
Show resolved Hide resolved
}

impl Default for ChecksumValues {
fn default() -> Self {
Self {
inner: AUTHENTICATOR_DEFAULT_CHECKSUM.to_vec(),
}
}
}

impl From<ChecksumValues> for Vec<u8> {
fn from(val: ChecksumValues) -> Self {
val.inner
}
}

impl From<[u8; 24]> for ChecksumValues {
fn from(bytes: [u8; 24]) -> Self {
ChecksumValues {
inner: Vec::from(bytes),
}
}
}

impl ChecksumValues {
pub(crate) fn set_flags(&mut self, flags: GssFlags) {
let flag_bits = flags.bits();
let flag_bytes = flag_bits.to_le_bytes();
self.inner[20..24].copy_from_slice(&flag_bytes);
}

pub(crate) fn into_inner(self) -> Vec<u8> {
self.inner
}
}

bitflags::bitflags! {
// https://datatracker.ietf.org/doc/html/rfc4121#section-4.1.1.1, this is crucial for LDAPS
#[derive(Debug,Clone,Copy)]
pub(crate) struct GssFlags: u32 {
const GSS_C_DELEG_FLAG = 0b00000001;
const GSS_C_MUTUAL_FLAG = 0b00000010;
const GSS_C_REPLAY_FLAG = 0b00000100;
const GSS_C_SEQUENCE_FLAG = 0b00001000;
const GSS_C_CONF_FLAG = 0b00010000;
const GSS_C_INTEG_FLAG = 0b00100000;
const GSS_C_ANON_FLAG = 0b01000000;
const GSS_C_PROT_READY_FLAG = 0b10000000;
}
}

impl From<ClientRequestFlags> for GssFlags {
/*
the semantics of some of the flags of SSPI are I believe one to one mapped to the GSS flags
*/
fn from(value: ClientRequestFlags) -> Self {
let mut flags = GssFlags::empty();

if value.contains(ClientRequestFlags::DELEGATE) {
flags |= GssFlags::GSS_C_DELEG_FLAG;
}

if value.contains(ClientRequestFlags::MUTUAL_AUTH) {
flags |= GssFlags::GSS_C_MUTUAL_FLAG;
}

if value.contains(ClientRequestFlags::REPLAY_DETECT) {
flags |= GssFlags::GSS_C_REPLAY_FLAG;
}

if value.contains(ClientRequestFlags::SEQUENCE_DETECT) {
flags |= GssFlags::GSS_C_SEQUENCE_FLAG;
}

if value.contains(ClientRequestFlags::CONFIDENTIALITY) {
flags |= GssFlags::GSS_C_CONF_FLAG;
}

if value.contains(ClientRequestFlags::INTEGRITY) {
flags |= GssFlags::GSS_C_INTEG_FLAG;
}

if value.contains(ClientRequestFlags::NO_INTEGRITY) {
flags &= !GssFlags::GSS_C_INTEG_FLAG;
}

flags
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -396,9 +490,10 @@ pub fn generate_authenticator(options: GenerateAuthenticatorOptions) -> Result<A

let cksum = if let Some(ChecksumOptions {
checksum_type,
mut checksum_value,
checksum_value,
}) = checksum
{
let mut checksum_value = checksum_value.into_inner();
if checksum_type == AUTHENTICATOR_CHECKSUM_TYPE && channel_bindings.is_some() {
if checksum_value.len() < 20 {
return Err(Error::new(
Expand Down Expand Up @@ -637,3 +732,37 @@ pub fn generate_krb_priv_request(
krb_priv,
})
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_set_flags() {
let mut checksum_values = ChecksumValues::default();
let flags = GssFlags::GSS_C_MUTUAL_FLAG | GssFlags::GSS_C_REPLAY_FLAG;
checksum_values.set_flags(flags);
let expected_bytes = flags.bits().to_le_bytes();
assert_eq!(checksum_values.inner[20..24], expected_bytes);
}

#[test]
fn test_default() {
// ensure backwards compatibility
let checksum_values = ChecksumValues::default();
assert_eq!(checksum_values.into_inner(), AUTHENTICATOR_DEFAULT_CHECKSUM);
}

#[test]
fn test_flag_for_sign_and_seal() {
let mut checksum_values = ChecksumValues::default();
let flags = GssFlags::GSS_C_MUTUAL_FLAG
| GssFlags::GSS_C_REPLAY_FLAG
| GssFlags::GSS_C_SEQUENCE_FLAG
| GssFlags::GSS_C_CONF_FLAG
| GssFlags::GSS_C_INTEG_FLAG;
checksum_values.set_flags(flags);
let expected_bytes = [0x3E, 0x00, 0x00, 0x00];
assert_eq!(checksum_values.inner[20..24], expected_bytes);
}
}
14 changes: 11 additions & 3 deletions src/kerberos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ use self::client::extractors::{
use self::client::generators::{
generate_ap_req, generate_as_req, generate_as_req_kdc_body, generate_krb_priv_request, generate_neg_ap_req,
generate_neg_token_init, generate_pa_datas_for_as_req, generate_tgs_req, get_client_principal_name_type,
get_client_principal_realm, ChecksumOptions, EncKey, GenerateAsPaDataOptions, GenerateAsReqOptions,
GenerateAuthenticatorOptions, AUTHENTICATOR_DEFAULT_CHECKSUM,
get_client_principal_realm, ChecksumOptions, ChecksumValues, EncKey, GenerateAsPaDataOptions, GenerateAsReqOptions,
GenerateAuthenticatorOptions,
};
use self::config::KerberosConfig;
use self::pa_datas::AsReqPaDataOptions;
Expand Down Expand Up @@ -871,16 +871,24 @@ impl<'a> Kerberos {
.unwrap_or(&DEFAULT_ENCRYPTION_TYPE);
let authenticator_sub_key = generate_random_symmetric_key(enc_type, &mut OsRng);

// the original flag is
// GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG
// we want to be able to turn of sign and seal, so we leave confidentiality and integrity flags out
let flags = builder.context_requirements.into();
let mut checksum_value = ChecksumValues::default();
checksum_value.set_flags(flags);

let authenticator_options = GenerateAuthenticatorOptions {
kdc_rep: &tgs_rep.0,
seq_num: Some(seq_num),
sub_key: Some(EncKey {
key_type: enc_type.clone(),
key_value: authenticator_sub_key,
}),

checksum: Some(ChecksumOptions {
checksum_type: AUTHENTICATOR_CHECKSUM_TYPE.to_vec(),
checksum_value: AUTHENTICATOR_DEFAULT_CHECKSUM.to_vec(),
checksum_value,
}),
channel_bindings: self.channel_bindings.as_ref(),
extensions: Vec::new(),
Expand Down
3 changes: 2 additions & 1 deletion src/pku2u/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,10 @@ pub fn generate_authenticator(options: GenerateAuthenticatorOptions) -> Result<A

let cksum = if let Some(ChecksumOptions {
checksum_type,
mut checksum_value,
checksum_value,
}) = checksum
{
let mut checksum_value = checksum_value.into_inner();
if checksum_type == AUTHENTICATOR_CHECKSUM_TYPE && channel_bindings.is_some() {
if checksum_value.len() < 20 {
return Err(Error::new(
Expand Down
2 changes: 1 addition & 1 deletion src/pku2u/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ impl Pku2u {
}),
checksum: Some(ChecksumOptions {
checksum_type: AUTHENTICATOR_CHECKSUM_TYPE.to_vec(),
checksum_value: AUTHENTICATOR_DEFAULT_CHECKSUM.to_vec(),
checksum_value: AUTHENTICATOR_DEFAULT_CHECKSUM.into(),
}),
channel_bindings: None,
extensions: vec![generate_authenticator_extension(
Expand Down