🌟 [Major]: JWT module rewrite covering the full JWS half of JOSE#26
Draft
Marius Storhaug (MariusStorhaug) wants to merge 12 commits into
Draft
🌟 [Major]: JWT module rewrite covering the full JWS half of JOSE#26Marius Storhaug (MariusStorhaug) wants to merge 12 commits into
Marius Storhaug (MariusStorhaug) wants to merge 12 commits into
Conversation
…t, Get-Jwt*, JwtKey converters
…JWK round-trip, and algorithm-confusion coverage
… claim handling on PowerShell
Super-linter summary
Super-linter detected linting errors For more information, see the GitHub Actions workflow run Powered by Super-linter MARKDOWNPOWERSHELL |
…WS surface with curve-OID enforcement
This was referenced May 12, 2026
Super-linter summary
Super-linter detected linting errors For more information, see the GitHub Actions workflow run Powered by Super-linter MARKDOWNNATURAL_LANGUAGEPOWERSHELL |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Jwt module is rewritten end to end against the JOSE specs. The v1 surface (
-PayloadJson,-Cert,-Secret, string-returningGet-JwtHeader/Get-JwtPayload) is replaced with a typed object model and a public surface that now covers every JWS algorithm in RFC 7518 §3, JWK + JWKS handling per RFC 7517, and RFC 7638 thumbprints. All cryptography usesSystem.Security.Cryptographyfrom .NET — no third-party dependencies. The module now requires PowerShell 7.6.Breaking Changes
The v1 cmdlet shapes are gone. See the README for the full migration table; the highlights:
New-Jwt -Header '{...}' -PayloadJson '{...}' -SecretNew-Jwt -Payload @{...} -Algorithm HS256 -Key $secretNew-Jwt -Cert $cert ...$rsa = $cert.GetRSAPrivateKey(); New-Jwt -Key $rsaTest-Jwt -Cert $cert ...Test-Jwt -Key $rsa ...(or-Key $jwk)Get-JwtHeader/Get-JwtPayloadreturned strings[JwtHeader]/[JwtPayload]Verify-JwtSignaturealiasTest-JwtNew: Full RFC 7518 §3 JWS algorithm matrix
New-JwtandTest-Jwtnow support every JWS algorithm in the spec:HS256,HS384,HS512RS256,RS384,RS512PS256,PS384,PS512ES256(P-256),ES384(P-384),ES512(P-521)none(rejected byTest-Jwtunless-AllowUnsignedis supplied)The ECDSA family enforces the curve OID required by the algorithm — supplying a P-256 key for
ES384(or vice versa) is refused before any signature work happens. HMAC keys are similarly refused for asymmetric algorithms, blocking the classic algorithm-confusion attack.New: Typed JWT object model
ConvertFrom-Jwtreturns a[Jwt]with strongly-typedHeaderandPayload, preservingEncodedHeader/EncodedPayloadfor byte-exact round-tripping. Registered claims (iss,sub,aud,exp,nbf,iat,jti) are first-class properties on[JwtPayload]; everything else lives inAdditionalFields(anOrderedDictionary).New: External signing flow
New-Jwt -Unsignedproduces an unsigned token whoseSigningInput()can be handed to an external signer (HSM, Azure Key Vault, etc.). The base64url signature is then assigned back onto$token.Signatureto complete the JWT:New: JWK, JWKS, and RFC 7638 thumbprints
The full JSON Web Key surface is now in the module:
ConvertTo-JwtKeyRSA/ECDsa/byte[]into a[JwtKey](JWK)ConvertFrom-JwtKey[JwtKey]back into a .NET keyConvertTo-JwtKeySet[JwtKey]into a[JwtKeySet](JWKS)ConvertFrom-JwtKeySet[JwtKeySet]Get-JwtKeyFromSet[JwtKey]bykidGet-JwtKeyThumbprintTypical "fetch the issuer's JWKS, look up the kid from the token header, verify" flow:
The RFC 7638 reference vector matches exactly: a thumbprint computed with
Get-JwtKeyThumbprintover the spec's example RSA key producesNzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs.New: Structured validation report
Test-Jwt -Detailedreturns a per-check report covering algorithm, signature, expiration, not-before, issuer, and audience — useful for auditing why a token was rejected:New: Base64Url helpers as public surface
ConvertTo-Base64UrlString/ConvertFrom-Base64UrlString(and the[JwtBase64Url]class) are kept on the public surface — they're standard JOSE building blocks (RFC 4648 §5) that are useful for hand-constructing tokens, debugging, and external signing flows.Fixed: Order-preserving JSON output
JWT serialization now preserves the order of dictionary input. Passing
[ordered]@{}toNew-Jwt -Payloadproduces a JWT whose claim order matches the input — necessary for byte-exact reproduction of reference vectors and for any consumer that hashes the encoded payload.Roadmap
The v2 release covers the JWS half of JOSE end to end (RFC 7515 / 7517 / 7518 §3 / 7519 / 7638). The following are explicitly out of scope and tracked as follow-up issues:
Protect-Jwt/Unprotect-Jwtplus the full key-management and content-encryption matrix. Not in scope for v2 because the surface is large and the AES-CBC-HMAC mode requires careful constant-time MAC-then-decrypt to avoid padding-oracle bugs.Ed25519andEd448over theOKPkey type. Blocked on first-party Ed25519 support inSystem.Security.Cryptography.RSA1_5key wrap. Spec-listed but Bleichenbacher-vulnerable; will not be implemented.Technical Details
src/classes/public/:Jwt,JwtHeader,JwtPayload,JwtKey,JwtKeySet,JwtBase64Url. Headers and payloads useOrderedDictionaryforAdditionalFieldsand an explicit_keyOrderlist to keep registered claims in input order during serialization.Resolve-JwtKey(private): per-algorithm-family key validation. ECDSA branch enforces the required curve via OID comparison onparams.Curve.Oid.Value(1.2.840.10045.3.1.7,1.3.132.0.34,1.3.132.0.35). HMAC branch refuses RSA/EC inputs to block algorithm-confusion attacks.Test-JwtSignature(private): switch-on-prefix dispatch; PSS padding for^PS, PKCS#1 v1.5 for^RS, constant-time compare for HMAC.Get-JwtClaim: now uses a stable[object]::new()sentinel instead of[AutomationNull]for missing-value detection, so-ErrorIfMissingactually emits errors (AutomationNullunwraps to$nullon assignment, which silently broke the previous check).JwtKeyconstructor accepts[IDictionary](incl.[ordered]@{}) to preserve field order from JWKS JSON parsed viaConvertFrom-Json -AsHashtable.Implementation plan progress
All four implementation-plan tasks from #13 are complete:
[Jwt],[JwtHeader],[JwtPayload]) — doneTest-Jwtwith detailed reporting, claim checks, algorithm-confusion blocks) — done