fix(verifier): accept padded base64 vp_token and stop panicking on malformed VPs - #114
fix(verifier): accept padded base64 vp_token and stop panicking on malformed VPs#114vramperez wants to merge 5 commits into
Conversation
…lformed VPs
decodeVpString only decoded raw(unpadded) base64url, so a client emitting
padded base64 - java's Base64.getUrlEncoder() or python's b64encode() - made
the decode fail and the raw, still-encoded string was passed on. That string
matches no DCQL shape and reaches ParseWithSdJwt, where strings.Split(token,
".")[1] panics on a string without a '.', turning a malformed request into an
empty HTTP 500 instead of a 400.
Accept all four base64 variants in decodeVpString. Tokens that are not base64
at all(compact JWTs, SD-JWTs, plain json dcql responses) are unaffected, since
'.', '~', '{', '"' and ':' are part of no base64 alphabet.
ParseWithSdJwt now reuses the existing extractJWTPayload helper, which checks
the segment count and no longer discards the decode error. Its three remaining
unchecked type assertions - the holder, the verifiableCredential array and its
entries - are guarded as well, since they panic on the same unauthenticated
endpoint.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mortega5
left a comment
There was a problem hiding this comment.
Nice fix overall — the panic-to-400 conversion and the multi-variant base64 handling are well covered by tests.
One thing worth confirming before merge: holder becomes mandatory here — if the claim is missing or not a string, the whole presentation is rejected with ErrorPresentationNoHolder.
That's stricter than the rest of the codebase, where holder is treated as optional:
parseJSONLDPresentationjust skips settingholderwhen it's absent, no error.generateJWTinverifier.go:1351explicitly handles an empty holder as valid:if holder != "" { jwtBuilder.Subject(holder) }.
Since the stated goal here was to stop the panic (turning an unchecked type assertion into a checked one), was making the field strictly required also intentional? If not, it might be worth mirroring the JSON-LD path and defaulting to an empty holder instead of rejecting the whole presentation — otherwise this could reject legitimate SD-JWT/DCQL presentations from wallets that don't send a holder claim (common, since holder binding for SD-JWT VC is normally done via the KB-JWT rather than this field).
VCVerifier/verifier/presentation_parser.go
Lines 552 to 557 in 7718557
| holder, ok := vp[common.VPKeyHolder].(string) | ||
| if !ok { | ||
| logging.Log().Warn("VP does not contain a string holder") | ||
| return nil, ErrorPresentationNoHolder | ||
| } | ||
| presentation.Holder = holder |
There was a problem hiding this comment.
This makes holder mandatory (missing/non-string → ErrorPresentationNoHolder, rejecting the whole presentation), which is stricter than the rest of the codebase:
parseJSONLDPresentationtreatsholderas optional and just skips setting it when absent.generateJWT(verifier.go:1351) explicitly handles an empty holder as valid:if holder != "" { jwtBuilder.Subject(holder) }.
The stated goal here was to stop the panic (unchecked type assertion → checked one) — was requiring the field also intentional? If not, defaulting to an empty holder (like the JSON-LD path) instead of rejecting the presentation might be safer, since wallets that don't send holder for SD-JWT/DCQL presentations (holder binding is normally done via the KB-JWT) would otherwise be rejected.
VCVerifier/verifier/presentation_parser.go
Lines 552 to 557 in 7718557
…ifier into fix/vp-token-base64-padding
decodeVpString only decoded raw(unpadded) base64url, so a client emitting padded base64 - java's Base64.getUrlEncoder() or python's b64encode() - made the decode fail and the raw, still-encoded string was passed on. That string matches no DCQL shape and reaches ParseWithSdJwt, where strings.Split(token, ".")[1] panics on a string without a '.', turning a malformed request into an empty HTTP 500 instead of a 400.
Accept all four base64 variants in decodeVpString. Tokens that are not base64 at all(compact JWTs, SD-JWTs, plain json dcql responses) are unaffected, since '.', '~', '{', '"' and ':' are part of no base64 alphabet.
ParseWithSdJwt now reuses the existing extractJWTPayload helper, which checks the segment count and no longer discards the decode error. Its three remaining unchecked type assertions - the holder, the verifiableCredential array and its entries - are guarded as well, since they panic on the same unauthenticated endpoint.