Skip to content

Releases: gladjohn/token-binding-showcase

v1.6.0 - AKV SDK does the real bound flow (not bearer)

Choose a tag to compare

@gladjohn gladjohn released this 30 Jun 14:55

Fix: the Azure Key Vault SDK path (option 7) was acquiring a plain BEARER token, because public Azure.Identity 1.21.0 has no PoP/mTLS API (confirmed by reflection). It now uses the binding-enabled preview packages from the azure-sdk public dev feed:
Azure.Core 1.60.0-alpha.20260622.10 (bound-token pipeline)
Azure.Identity.Broker 1.8.0-beta.1 (pulls the preview Azure.Identity)
Azure.Security.KeyVault.Secrets 4.12.0-beta.1

The binding-enabled ManagedIdentity* types ship in the Azure.Identity namespace inside Azure.Core and clash with the Azure.Identity that Microsoft.Identity.Web pulls in, so the project binds to them via an extern alias (azcore). Verified: option 7 now routes through ImdsV2 GetCsrMetadataAsync (the bound flow), not the v1 bearer endpoint. A NuGet.config wires the dev feed.

v1.5.0 - built-in attacker replay (option 9)

Choose a tag to compare

@gladjohn gladjohn released this 30 Jun 13:21

Option 9 - Attacker replay: paste a stolen token and the exe calls Key Vault with it as a plain bearer (no binding cert). Decodes the token to show whether it is bound (cnf claim), then: 200 = EXFILTRATED (bearer accepted), 401 = BLOCKED (bound token rejected), 403 = RBAC. No separate tool needed - run the same exe on the attacker VM.

v1.4.2 - target tokenbindingdemo vault

Choose a tag to compare

@gladjohn gladjohn released this 30 Jun 13:04

Demo now targets Key Vault 'tokenbindingdemo' (https://tokenbindingdemo.vault.azure.net/) with secret 'tbsecret'. Override via appsettings.json or env vars as before.

v1.4.1 - classic bearer path now calls Key Vault

Choose a tag to compare

@gladjohn gladjohn released this 30 Jun 12:54

Option 4 (Classic MSI bearer) now also calls Key Vault with the plain bearer token (Authorization: Bearer, no binding cert): 200 = secret read with a bearer (the exposure, shown red), 401 = the vault enforces token binding and rejects it, 403 = RBAC. Token is still printed in full for cross-VM replay.

v1.4.0 - KeyGuard vs software key + certs in CurrentUser\My

Choose a tag to compare

@gladjohn gladjohn released this 30 Jun 12:35

New key/cert demos that make the KeyGuard value obvious:

  • 2 - Non-KeyGuard (software) key: 2A create, 2B list, 2C export -> SUCCEEDS (private key pulled out in the clear, shown in red).
  • 3 - Certs from both keys: mints a self-signed cert from each into CurrentUser\My. Open certmgr.msc -> Personal -> Certificates to compare: 'DemoKeyGuard-NonExportable' (private key NON-exportable) vs 'DemoSoftware-Exportable' (private key exportable).

Menu: 1 KeyGuard key, 2 Non-KeyGuard key, 3 Certs from both, 4 Classic bearer, 5 MSAL, 6 Identity Web, 7 AKV SDK, 8 all bound, 0 exit. Requires a VBS/KeyGuard VM for option 1 and the KeyGuard cert.

v1.3.0 - KeyGuard key demo (create / list / non-exportable)

Choose a tag to compare

@gladjohn gladjohn released this 30 Jun 12:14

New: option 1 - KeyGuard key demo (the hardware-protected key behind token binding):

  • 1A create a VBS-isolated CNG RSA-2048 key named 'DemoKeyGuardKey' (distinct from MSAL's own key)
  • 1B list it (shows export policy = None, KeyGuard-protected = True via the 'Virtual Iso' property)
  • 1C try to export the private key -> BLOCKED (the key never leaves the VBS enclave)

Menu renumbered: 1 KeyGuard, 2 Classic bearer, 3 MSAL, 4 Identity Web, 5 AKV SDK, 6 all bound, 0 exit.
Targets net8.0-windows for the CNG/KeyGuard APIs. Requires a Trusted Launch / Confidential VM with VBS+KeyGuard.

v1.2.0 - nicer display + per-path context

Choose a tag to compare

@gladjohn gladjohn released this 30 Jun 12:03

Presentation upgrade (no functional change):

  • Framed banner with an intro and a config summary.
  • Cleaner, aligned menu.
  • Each path now prints a section header + a 'what's happening' context block explaining the flow, and a takeaway line after it runs.
  • ASCII-safe styling so it renders correctly on any Windows console.

Same single-file self-contained win-x64 build; same paths and behavior.

v1.1.2 - Identity Web sends x-ms-tokenboundauth header

Choose a tag to compare

@gladjohn gladjohn released this 30 Jun 00:50

Bug fix: the Microsoft Identity Web path (IDownstreamApi) was returning 401 from Key Vault because it didn't send the token-bound opt-in header. It now adds 'x-ms-tokenboundauth: true' via CustomizeHttpRequestMessage, matching the MSAL path. Option 3 should now return 200 on a supported VM.

v1.1.1 - fix Identity Web Build() twice in looping menu

Choose a tag to compare

@gladjohn gladjohn released this 29 Jun 23:27

Bug fix: the Microsoft Identity Web path threw 'You shouldn't call Build() twice' when run more than once in the looping menu. TokenAcquirerFactory is now built once and reused. Also corrected the path numbers shown in each demo header.

v1.1.0 - bearer option, looping menu, full token print

Choose a tag to compare

@gladjohn gladjohn released this 29 Jun 23:10

Adds three things from feedback:

  • Looping menu - the app stays open between calls (choose 0 to exit).
  • Classic MSI (option 1) - a basic bearer token from the local IMDS endpoint (the stealable "before").
  • Full token printing - the complete access token is printed so you can copy it and replay it from another VM (bearer succeeds; bound mtls_pop is rejected).

Self-contained single-file win-x64 build (no .NET install needed). Pre-configured with non-secret IDs (UAMI 71d22d68-..., region westcentralus, vault msidlabs, secret mytbsecret).

.\TokenBindingShowcase.exe        # interactive menu (loops)
.\TokenBindingShowcase.exe 5      # all bound paths