Skip to content

Commit 08d8622

Browse files
Rostyslav-Romanetsmamoreau-devolutionsCopilot
authored
fix(kerberos): enforce the sequence number of MIC token only for acce… (#772)
This PR fixes [#748](#748) by enforcing sequence number verification only on the acceptor side. The initiator verifies the MIC checksum without enforcing the sequence number. --------- Co-authored-by: Marc-André Moreau <mamoreau@devolutions.net> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent d87a57a commit 08d8622

8 files changed

Lines changed: 104 additions & 17 deletions

File tree

‎Cargo.lock‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ winscard = { workspace = true, optional = true }
129129
rsa = { version = "=0.10.0-rc.18", default-features = false }
130130
tracing = { version = "0.1", default-features = true }
131131
serde = "1"
132-
picky-krb = "0.12"
132+
picky-krb = "0.13"
133133
picky-asn1 = { version = "0.10", features = ["time_conversion"] }
134134
byteorder = "1.5"
135135
crypto-bigint = { version = "0.7", default-features = false, features = ["alloc"] }

‎crates/dpapi/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ num-traits = { version = "0.2", default-features = true }
2424
uuid = { version = "1.23", default-features = false, features = ["std"] }
2525
picky-asn1 = "0.10"
2626
picky-asn1-der = "0.5"
27-
picky-krb = "0.12"
27+
picky-krb = "0.13"
2828
picky-asn1-x509 = { version = "0.15", features = ["pkcs7"] }
2929
sha1 = { version = "0.11", default-features = false }
3030
sha2 = "0.11"

‎crates/kdc/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ description = "A minimal Rust implementation of the KDC (Key Distribution Center
1313
picky-asn1 = { version = "0.10", features = ["time_conversion"] }
1414
picky-asn1-der = "0.5"
1515
picky-asn1-x509 = "0.15"
16-
picky-krb = "0.12"
16+
picky-krb = "0.13"
1717
tracing = { version = "0.1", default-features = false }
1818
time = { version = "0.3", default-features = false }
1919

‎src/kerberos/mod.rs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,8 +767,18 @@ impl SspiEx for Kerberos {
767767
}
768768

769769
fn verify_mic_token(&mut self, token: &[u8], data: &[u8], _: crate::private::Sealed) -> Result<()> {
770+
// The sequence number verification is only enforced for the acceptor side.
771+
// RFC 1964 §1.2.1 / RFC 4121 make sequence-number checking optional and tied to
772+
// the `SEQUENCE_DETECT`/`REPLAY_DETECT` flags negotiated at context establishment.
773+
// This client does not negotiate those flags.
774+
// Additionally, Windows acceptor signs the SPNEGO mechListMIC with sequence number 0
775+
// regardless of the AP-REP sequence number, so enforcing this on the initiator side
776+
// breaks interop with the Windows implementation.
777+
let enforce_sequence_number = !self.is_client();
778+
770779
utils::validate_mic_token(
771780
self.is_client(),
781+
enforce_sequence_number,
772782
self.remote_seq_number.into(),
773783
token,
774784
&self.encryption_params,

‎src/kerberos/tests.rs‎

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use picky_krb::crypto::CipherSuite;
33
use picky_krb::crypto::aes::{AesSize, checksum_sha_aes};
44
use picky_krb::gss_api::MicToken;
55

6-
use crate::kerberos::{EncryptionParams, KerberosConfig, KerberosState, test_data};
7-
use crate::{EncryptionFlags, Kerberos, SecurityBufferFlags, SecurityBufferRef, Sspi, SspiEx};
6+
use crate::kerberos::{EncryptionParams, KerberosConfig, KerberosState, test_data, utils};
7+
use crate::{EncryptionFlags, ErrorKind, Kerberos, SecurityBufferFlags, SecurityBufferRef, Sspi, SspiEx};
88

99
#[test]
1010
fn stream_buffer_decryption() {
@@ -326,3 +326,35 @@ fn aes128_subkey_mic_uses_subkey_size_with_aes256_ticket_enctype() {
326326

327327
assert_mic_uses_aes_size(CipherSuite::Aes256CtsHmacSha196, AesSize::Aes128, &key);
328328
}
329+
330+
#[test]
331+
fn initiator_accepts_mic_token_with_invalid_seq_number() {
332+
let mut client = test_data::fake_client();
333+
client.remote_seq_number = 10;
334+
335+
let data = b"SPNEGO mechTypes";
336+
let sub_session_key = client.encryption_params.sub_session_key.clone().unwrap();
337+
let mic_token = utils::generate_mic_token(false, 0, data.to_vec(), &sub_session_key, &AesSize::Aes256).unwrap();
338+
339+
client
340+
.verify_mic_token(&mic_token, data, crate::private::Sealed)
341+
.expect("MIC verification should succeed with seq number 0");
342+
}
343+
344+
#[test]
345+
fn acceptor_rejects_mic_token_with_invalid_seq_number() {
346+
let mut server = test_data::fake_server();
347+
server.remote_seq_number = 10;
348+
349+
let data = b"SPNEGO mechTypes";
350+
let sub_session_key = server.encryption_params.sub_session_key.clone().unwrap();
351+
let mic_token = utils::generate_mic_token(true, 0, data.to_vec(), &sub_session_key, &AesSize::Aes256).unwrap();
352+
353+
assert_eq!(
354+
server
355+
.verify_mic_token(&mic_token, data, crate::private::Sealed)
356+
.unwrap_err()
357+
.error_type,
358+
ErrorKind::InvalidToken
359+
);
360+
}

‎src/kerberos/utils.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub(super) fn serialize_message<T: ?Sized + Serialize>(v: &T) -> Result<Vec<u8>>
2323

2424
pub(crate) fn validate_mic_token(
2525
is_client: bool,
26+
enforce_seq_number: bool,
2627
expected_seq_number: u64,
2728
raw_token: &[u8],
2829
params: &EncryptionParams,
@@ -33,7 +34,7 @@ pub(crate) fn validate_mic_token(
3334
let token = MicToken::decode(raw_token)?;
3435
let token_flags = token.flags;
3536

36-
if token.seq_num != expected_seq_number {
37+
if enforce_seq_number && token.seq_num != expected_seq_number {
3738
return Err(Error::new(
3839
ErrorKind::InvalidToken,
3940
format!(

‎src/pku2u/mod.rs‎

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,10 @@ impl Pku2u {
313313
crate::kerberos::utils::generate_mic_token(self.is_client(), sequence_number, data.to_vec(), key, &aes_size)
314314
}
315315

316-
fn verify_mic(&self, sequence_number: u64, token: &[u8], data: &[u8]) -> Result<()> {
316+
fn verify_mic(&self, sequence_number: u64, token: &[u8], data: &[u8], enforce_sequence_number: bool) -> Result<()> {
317317
crate::kerberos::utils::validate_mic_token(
318318
self.is_client(),
319+
enforce_sequence_number,
319320
sequence_number,
320321
token,
321322
&self.encryption_params,
@@ -972,7 +973,7 @@ impl Sspi for Pku2u {
972973
acc.extend_from_slice(buffer.data());
973974
acc
974975
});
975-
self.verify_mic(sequence_number.into(), &token, &data)?;
976+
self.verify_mic(sequence_number.into(), &token, &data, true)?;
976977

977978
Ok(0)
978979
}
@@ -1498,7 +1499,7 @@ impl SspiEx for Pku2u {
14981499
}
14991500

15001501
fn verify_mic_token(&mut self, token: &[u8], data: &[u8], _: crate::private::Sealed) -> Result<()> {
1501-
self.verify_mic(self.remote_gss_seq_number.into(), token, data)?;
1502+
self.verify_mic(self.remote_gss_seq_number.into(), token, data, true)?;
15021503
self.remote_gss_seq_number = self.remote_gss_seq_number.wrapping_add(1);
15031504
Ok(())
15041505
}
@@ -1734,8 +1735,7 @@ xFnLp2UBrhxA9GYrpJ5i0onRmexQnTVSl5DDq07s+3dbr9YAKjrg9IDZYqLbdwP1
17341735
);
17351736
}
17361737

1737-
#[test]
1738-
fn stream_buffer_decryption() {
1738+
fn minimal_pku2u_pair() -> (Pku2u, Pku2u) {
17391739
let session_key = vec![
17401740
137, 60, 120, 245, 164, 179, 76, 200, 242, 96, 57, 174, 111, 209, 90, 76, 58, 117, 55, 138, 81, 75, 110,
17411741
235, 80, 228, 14, 238, 76, 128, 139, 81,
@@ -1746,13 +1746,12 @@ xFnLp2UBrhxA9GYrpJ5i0onRmexQnTVSl5DDq07s+3dbr9YAKjrg9IDZYqLbdwP1
17461746
];
17471747

17481748
let mut rng = StdRng::try_from_rng(&mut SysRng).unwrap();
1749-
17501749
let (p2p_certificate, private_key) = test_p2p_identity();
17511750

17521751
let mut negoex_random = [0; RANDOM_ARRAY_SIZE];
17531752
rng.fill_bytes(&mut negoex_random);
17541753

1755-
let mut pku2u_server = Pku2u {
1754+
let server = Pku2u {
17561755
mode: Pku2uMode::Server,
17571756
config: Pku2uConfig {
17581757
p2p_certificate: p2p_certificate.clone(),
@@ -1793,14 +1792,14 @@ xFnLp2UBrhxA9GYrpJ5i0onRmexQnTVSl5DDq07s+3dbr9YAKjrg9IDZYqLbdwP1
17931792
let mut negoex_random = [0; RANDOM_ARRAY_SIZE];
17941793
rng.try_fill_bytes(&mut negoex_random).unwrap();
17951794

1796-
let mut pku2u_client = Pku2u {
1795+
let client = Pku2u {
17971796
mode: Pku2uMode::Client,
17981797
config: Pku2uConfig {
17991798
p2p_certificate: p2p_certificate.clone(),
18001799
private_key: private_key.into(),
18011800
client_hostname: "hostname".into(),
18021801
additional_credentials: Vec::new(),
1803-
trusted_client_certificates: vec![p2p_certificate.clone()],
1802+
trusted_client_certificates: vec![p2p_certificate],
18041803
trusted_server_certificates: Vec::new(),
18051804
},
18061805
state: Pku2uState::Final,
@@ -1831,6 +1830,51 @@ xFnLp2UBrhxA9GYrpJ5i0onRmexQnTVSl5DDq07s+3dbr9YAKjrg9IDZYqLbdwP1
18311830
ticket_encryption_key: None,
18321831
};
18331832

1833+
(client, server)
1834+
}
1835+
1836+
#[test]
1837+
fn verify_signature_rejects_invalid_sequence_number() {
1838+
let (mut client, mut server) = minimal_pku2u_pair();
1839+
1840+
let mut signature = vec![0; 64];
1841+
let mut signed_data = b"signed Pku2u message".to_vec();
1842+
let mut message = [
1843+
SecurityBufferRef::token_buf(&mut signature),
1844+
SecurityBufferRef::data_buf(&mut signed_data),
1845+
];
1846+
1847+
server.make_signature(0, &mut message, 42).unwrap();
1848+
1849+
assert_eq!(
1850+
client.verify_signature(&mut message, 7).unwrap_err().error_type,
1851+
ErrorKind::InvalidToken
1852+
);
1853+
}
1854+
1855+
#[test]
1856+
fn verify_mic_token_rejects_invalid_sequence_number() {
1857+
let (mut client, mut server) = minimal_pku2u_pair();
1858+
server.gss_seq_number = 7;
1859+
1860+
let mic = server
1861+
.generate_mic_token(b"SPNEGO mech list", crate::private::Sealed)
1862+
.unwrap();
1863+
1864+
assert_eq!(
1865+
client
1866+
.verify_mic_token(&mic, b"SPNEGO mech list", crate::private::Sealed)
1867+
.unwrap_err()
1868+
.error_type,
1869+
ErrorKind::InvalidToken
1870+
);
1871+
assert_eq!(client.remote_gss_seq_number, 0);
1872+
}
1873+
1874+
#[test]
1875+
fn stream_buffer_decryption() {
1876+
let (mut pku2u_client, mut pku2u_server) = minimal_pku2u_pair();
1877+
18341878
assert_eq!(PKG_NAME, "pku2u");
18351879
assert_eq!(PACKAGE_INFO.rpc_id, 31);
18361880
assert_eq!(PACKAGE_INFO.max_token_len, 12_000);

0 commit comments

Comments
 (0)