Skip to content

Commit ee9e6c6

Browse files
chore(release): prepare for publishing
1 parent f740f00 commit ee9e6c6

7 files changed

Lines changed: 535 additions & 162 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,276 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88

9+
## [[0.23.1](https://github.com/Devolutions/sspi-rs/compare/sspi-v0.23.0...sspi-v0.23.1)] - 2026-10-27
10+
11+
### <!-- 0 -->Security
12+
13+
- Add support for IAKerb extension for Kerberos client ([#751](https://github.com/Devolutions/sspi-rs/issues/751)) ([a738671298](https://github.com/Devolutions/sspi-rs/commit/a7386712984473147e9d94636859452b98a6fc8f))
14+
15+
This PR adds support for the IAKERB extension according to the [IAKERB
16+
specification](https://datatracker.ietf.org/doc/html/draft-ietf-kitten-iakerb-03).
17+
18+
The main changes are focused on the `Kerberos` state machine.
19+
Previously, the `Preauthentication` state handled the entire KDC message
20+
exchange withing a single async `intialize_security_context` call. This
21+
approach does not work with IAKERB, where KDC messages are exchanged via
22+
a server that acts as a proxy. The `Preauthentication` state has
23+
therefore been split into multiple states, allowing the state machine to
24+
save `Kerberos` state between KDC message exchanges.
25+
26+
Additionaly, the `AS` and `TGS` exchanges have been extracted into
27+
separate state machines. These new state machines are KDC
28+
transport-agnostic and can be used with both external KDC and an IAKERB
29+
proxy.
30+
31+
## What is IAKERB
32+
IAKERB extends Kerberos to support scenarios where the client cannot
33+
directly access the KDC. Instead, KDC messages are encapsulated in
34+
GSS-API tokens and exchanged through an IAKERB proxy. The server
35+
forwards these messages to the LocalKDC, allowing the client to obtain
36+
the required Kerberos tickets without direct network access to the KDC.
37+
38+
Microsoft recently introduced IAKERB support in Windows Insider builds
39+
as part of its effort to reduce NTLM dependency:
40+
https://techcommunity.microsoft.com/blog/windows-itpro-blog/reducing-ntlm-dependency-iakerb-and-localkdc-in-windows-insider-preview/4524615.
41+
42+
## Related PRs
43+
* picky-rs: https://github.com/Devolutions/picky-rs/pull/531
44+
* IronRDP: https://github.com/Devolutions/IronRDP/pull/1987
45+
46+
### <!-- 4 -->Bug Fixes
47+
48+
- Drop local qualifier before a qualified name ([#767](https://github.com/Devolutions/sspi-rs/issues/767)) ([d87a57a704](https://github.com/Devolutions/sspi-rs/commit/d87a57a7042cbefd1f794a9c3657b33d3cee8037))
49+
50+
- Enforce the sequence number of MIC token only for acce… ([#772](https://github.com/Devolutions/sspi-rs/issues/772)) ([08d8622794](https://github.com/Devolutions/sspi-rs/commit/08d862279488dcdd0030050d7047bef85686a57a))
51+
52+
This PR fixes [#748](https://github.com/Devolutions/sspi-rs/issues/748)
53+
by enforcing sequence number verification only on the acceptor side. The
54+
initiator verifies the MIC checksum without enforcing the sequence
55+
number.
56+
57+
---------
58+
59+
- Interoperate with MIT and Heimdal KDCs ([#768](https://github.com/Devolutions/sspi-rs/issues/768)) ([cf91195338](https://github.com/Devolutions/sspi-rs/commit/cf91195338f62a8b01ca5024e06f97c150e03225))
60+
61+
## Summary
62+
63+
Two client fixes for password logons against MIT and Heimdal KDCs, found
64+
while using `sspi` for SASL/GSSAPI binds to Active Directory and
65+
MIT/Heimdal realms. Each currently makes every such logon fail.
66+
67+
- **Reuse the pre-authentication salt for AS-REP decryption.** MIT and
68+
Heimdal send PA-ETYPE-INFO2 only in `KDC_ERR_PREAUTH_REQUIRED`, not in
69+
the AS-REP. The client then derived the AS-REP key with an empty salt
70+
and failed with `Bad integrity: calculated hmac is different than
71+
provided`. The AS exchange now keeps the pre-authentication salt in the
72+
PA-DATA options, and the client falls back to it when the AS-REP has
73+
none.
74+
- **Accept an AS-REP enc-part tagged `EncTGSRepPart`.** The MIT KDC
75+
encodes the AS-REP enc-part with APPLICATION 26 for backwards
76+
compatibility, and the client failed with `Expected Application number
77+
tag 25 but got: 26`. It now accepts both tags.
78+
79+
### About the `EncTGSRepPart` fallback
80+
81+
I know this fallback was removed during the review of #681 ("We should
82+
not expect `EncTgsRepPart` during AS exchange"). The MIT KDC however
83+
always sends it, so without the fallback no AS exchange with an MIT KDC
84+
can succeed:
85+
86+
- MIT krb5,
87+
[`src/lib/krb5/asn.1/asn1_k_encode.c`](https://github.com/krb5/krb5/blob/master/src/lib/krb5/asn.1/asn1_k_encode.c):
88+
"For backwards compatibility, we encode both EncASRepPart and
89+
EncTGSRepPart with application tag 26. On decode, we accept either app
90+
tag". The encoder is `MAKE_ENCODER(encode_krb5_enc_kdc_rep_part,
91+
enc_tgs_rep_part)`, used for both reply types.
92+
- [RFC 4120 section
93+
5.4.2](https://www.rfc-editor.org/rfc/rfc4120#section-5.4.2): "Some
94+
implementations unconditionally send an encrypted EncTGSRepPart
95+
(application tag number 26) in this field regardless of whether the
96+
reply is a AS-REP or a TGS-REP. In the interest of compatibility,
97+
implementors MAY relax the check on the tag number of the decrypted
98+
ENC-PART."
99+
100+
The fallback only applies after the enc-part decrypted with the AS-REP
101+
key usage, and the TGS path is unchanged.
102+
103+
## Review and validation
104+
105+
- The test `KdcMock` gets two options reproducing each KDC behavior
106+
(`omit_as_rep_etype_info2`, `tgs_rep_enc_part_in_as_rep`), each covered
107+
by an end-to-end client/server authentication test. Without the fix,
108+
each test fails with the error quoted above.
109+
- Also verified against real MIT and Heimdal KDCs (LDAP SASL/GSSAPI
110+
binds).
111+
- `cargo test -q -p sspi -p kdc --features network_client,__test-data`
112+
- `cargo check -q -p sspi --all-targets --features
113+
network_client,dns_resolver,scard,__test-data` (`--all-features` also
114+
enables `tsssp`, which only builds on Windows)
115+
- `cargo clippy -q -p sspi -p kdc --features network_client,__test-data
116+
--lib --tests -- -D warnings -A unreachable-pub -A
117+
clippy::non-ascii-literal`
118+
- `cargo clippy --manifest-path ffi/Cargo.toml -- -D warnings -D
119+
clippy::print_stdout`
120+
- `cargo fmt --all -- --check`
121+
122+
- Complete AS-REP interoperability follow-ups ([#777](https://github.com/Devolutions/sspi-rs/issues/777)) ([e7ec8e45fd](https://github.com/Devolutions/sspi-rs/commit/e7ec8e45fdd83ef91cd1d90a381892a3557ef8fc))
123+
124+
## Summary
125+
126+
Follow up on [#768](https://github.com/Devolutions/sspi-rs/pull/768),
127+
based on freshly fetched `origin/master` at
128+
`[[cf91195338f62a8b01ca5024e06f97c150e03225](https://github.com/Devolutions/sspi-rs/commit/cf91195338f62a8b01ca5024e06f97c150e03225)](https://github.com/Devolutions/sspi-rs/commit/[cf91195338f62a8b01ca5024e06f97c150e03225](https://github.com/Devolutions/sspi-rs/commit/cf91195338f62a8b01ca5024e06f97c150e03225))`, including that merged PR.
129+
130+
- Carry the negotiated preauthentication salt out of the AS exchange for
131+
password changes and password-backed U2U acceptor TGT acquisition, using
132+
the same reply-salt selection helper as the direct/IAKerb client.
133+
- Distinguish an absent PA-ETYPE-INFO2 hint, an omitted salt field, and
134+
an explicitly empty salt. Retain the existing public extractor
135+
signatures.
136+
- Share authenticated AS-REP decryption between password/keytab and the
137+
actual PKINIT-facing PKU2U extractors, accepting application tags 25 and
138+
26 without weakening decryption or nonce handling.
139+
- Add 27 deterministic regressions, reusing the KDC mocks and
140+
direct/IAKerb flows. Fix the reported test-target Clippy findings and
141+
the RFC section-title capitalization.
142+
143+
## Salt semantics
144+
145+
RFC 4120 section 4, “Encryption and Checksum Specifications”, defines
146+
the default salt as the realm followed by principal components without
147+
separators; section 5.2.7.5, “PA-ETYPE-INFO2”, defines the optional salt
148+
field.
149+
150+
| Reply information | Salt used |
151+
| --- | --- |
152+
| PA-ETYPE-INFO2 absent | Salt used for preauthentication |
153+
| Entry present, salt field absent | Default from the reply client realm
154+
and principal components |
155+
| Salt explicitly empty | Empty salt, even when preauthentication used a
156+
different salt |
157+
| Salt explicitly nonempty | Reply-provided salt, taking precedence over
158+
preauthentication |
159+
160+
The preauthentication path also distinguishes an absent hint from a
161+
present entry requesting the principal's default salt. Existing
162+
explicit-salt/tag-25 behavior and the IAKerb state machine are retained.
163+
164+
## Requirement coverage
165+
166+
| Requirement | Deterministic evidence |
167+
| --- | --- |
168+
| Password-change salt fallback |
169+
`kerberos_change_password_uses_negotiated_salt_without_as_rep_hint`
170+
invokes the actual password-change consumer and decrypts/asserts the
171+
outgoing ticket, authenticator, and exact new-password bytes |
172+
| Password-backed U2U acceptor TGT acquisition |
173+
`kerberos_u2u_server_acquires_tgt_with_password_without_as_rep_hint`
174+
starts without a preinstalled ticket-decryption key, acquires the
175+
acceptor TGT, and completes authentication plus encrypted application
176+
traffic |
177+
| Combined missing reply hint and tag 26 |
178+
`kerberos_auth_without_as_rep_etype_info2_with_tgs_rep_enc_part_in_as_rep`
179+
|
180+
| The same combination through IAKerb |
181+
`iakerb_auth_without_as_rep_etype_info2_with_tgs_rep_enc_part_in_as_rep`,
182+
including cookies/transcript and encrypted application traffic |
183+
| Explicit-empty and custom reply salts override a different preauth
184+
salt | `kerberos_auth_with_explicit_empty_as_rep_salt`,
185+
`kerberos_auth_with_custom_as_rep_salt`,
186+
`extract_encryption_params_from_as_rep_with_salt_preserves_explicit_reply_salt`
187+
|
188+
| Omitted reply salt field uses the reply-principal default |
189+
`kerberos_auth_with_missing_as_rep_salt_field`,
190+
`extract_encryption_params_from_as_rep_with_salt_defaults_missing_reply_salt_field`
191+
assert case-preserving, separator-free realm/multicomponent-principal
192+
concatenation |
193+
| Missing versus empty preauth salt |
194+
`extract_preauth_salt_distinguishes_absent_default_empty_and_custom`,
195+
`kerberos_auth_with_missing_preauth_salt_field`,
196+
`kerberos_auth_with_explicit_empty_preauth_salt` |
197+
| Password/keytab and shared decoder tag compatibility |
198+
`kerberos_password_extractor_returns_expected_session_key_for_both_application_tags`,
199+
`kerberos_derived_key_extractor_returns_expected_session_key_for_both_application_tags`,
200+
`decrypt_as_rep_preserves_fields_for_both_application_tags` |
201+
| Actual PKINIT-facing key-only and key+nonce decoding |
202+
`pku2u_public_key_extractor_returns_expected_session_key_for_both_application_tags`,
203+
`pku2u_key_and_nonce_extractor_preserves_nonce_boundaries` |
204+
| Invalid tags, wrong keys, tampering, and nonce overflow still fail |
205+
`kerberos_session_key_extractors_reject_unsupported_application_tag`,
206+
`pku2u_session_key_extractors_reject_unsupported_application_tag`,
207+
`kerberos_session_key_extractors_reject_wrong_key_and_tampered_ciphertext`,
208+
`pku2u_session_key_extractors_reject_wrong_key_and_tampered_ciphertext`,
209+
`pku2u_nonce_extractor_rejects_nonce_above_u32_max` |
210+
| Public compatibility wrappers and malformed metadata |
211+
`extract_salt_from_krb_error_preserves_legacy_optional_string_contract`,
212+
`extract_encryption_params_from_as_rep_preserves_one_argument_api`,
213+
`salt_extractors_reject_invalid_challenges`,
214+
`reply_encryption_params_reject_malformed_or_missing_values` |
215+
| Cleanup | RFC title is `KRB_KDC_REP definition`; checked IAKerb
216+
`usize::try_from` conversion and structured trace logging in the common
217+
test helper; strict Clippy passes |
218+
219+
## Validation
220+
221+
Local Windows validation with VsDevShell initialized in each build
222+
process; `AWS_LC_SYS_NO_ASM=true`, `CMAKE_GENERATOR=Ninja`, and
223+
`SSPI_RS_IS_RUNNING_TESTS=true` for tests:
224+
225+
- `cargo test --locked -q -p sspi --lib --features
226+
network_client,dns_resolver,scard,tsssp,__test-data`: **307 passed, 0
227+
failed**.
228+
- `cargo test --locked -q -p sspi --test sspi --features
229+
network_client,dns_resolver,scard,tsssp,__test-data
230+
client_server::kerberos:: -- --test-threads=1`: **22 passed, 0 failed**.
231+
- Scoped extractor runs: **16 Kerberos tests** and **7 PKU2U tests**
232+
passed. The affected password-change and U2U tests were also rerun after
233+
the final Clippy cleanup.
234+
- `cargo clippy --locked -q -p sspi --lib --test sspi --features
235+
network_client,dns_resolver,scard,tsssp,__test-data -- -D warnings -D
236+
clippy::print_stdout`: passed.
237+
- `cargo check --locked -q -p sspi --no-default-features`: passed.
238+
- `cargo fmt --all -- --check` and `git diff --check`: passed; style
239+
review passed.
240+
- Standalone `tools/wasm-testcompile` fresh dependency resolution
241+
selected **picky-krb 0.13.0**; both `cargo check` and `cargo build` with
242+
`--locked --target wasm32-unknown-unknown` passed. The full `check.ps1`
243+
import scan was not run (`wasm2wat` is unavailable).
244+
245+
Formatting emits the existing stable-rustfmt warnings for nightly-only
246+
options. Cargo emits nonfatal incremental-cache access-denied notes;
247+
WASM compilation also emits the existing `encode_digest` dead-code
248+
warning.
249+
250+
## Investigated without changes
251+
252+
- **WASM dependency failure:** current master already requires
253+
`picky-krb = "0.13"` (raised in #772), which excludes the older 0.12.4
254+
release lacking `InvalidMechanismOid`. Fresh resolution and an actual
255+
standalone wasm32 build succeeded. No additional pin, lockfile change,
256+
or external workspace dependency is warranted.
257+
- **Dependency-fetch hang:** the reported historical macOS winscard
258+
timeout remains an infrastructure symptom, not a reproduced source
259+
failure. The historical failed-run jobs API returned HTTP 502, so its
260+
detailed logs could not be revalidated here. The later run
261+
[39320794389](https://github.com/Devolutions/sspi-rs/actions/runs/39320794389)
262+
has successful WASM and Windows/macOS/Linux winscard jobs; the
263+
RustCrypto/KDFs dependency is already revision-locked. No speculative
264+
timeout/retry infrastructure or dependency update was added.
265+
266+
No live Windows/MIT/Heimdal KDC validation was performed. Explicit-empty
267+
reply salt is a theoretical deterministic case, not a claimed real-world
268+
reproduction. Password-change coverage verifies the encrypted outgoing
269+
request, not a completed password update on a KDC.
270+
271+
- Decode PKU2U AuthPack as Diffie-Hellman ([#786](https://github.com/Devolutions/sspi-rs/issues/786)) ([f740f00451](https://github.com/Devolutions/sspi-rs/commit/f740f004515f6c96f831a6056aab236233f63927))
272+
273+
Use the Diffie-Hellman AuthPack alias introduced by picky-krb 0.13.1 so
274+
fresh dependency resolutions compile.
275+
Raise the minimum version to keep the decoder type available.
276+
277+
278+
9279
## [[0.23.0](https://github.com/Devolutions/sspi-rs/compare/sspi-v0.22.1...sspi-v0.23.0)] - 2026-10-01
10280

11281
### <!-- 1 -->Features

0 commit comments

Comments
 (0)