Repository navigation
9.0.0-RC2
The new validation API (CredentialValidator, RelyingPartyValidator, IssuerMetadataValidator) reports every check
of a credential, presentation, relying party, or credential issuer in a ValidationReport, decided by a configurable
ValidationPolicy covering trust anchors (e.g. from LoTEs), status lists, timeliness, and WRPAC/WRPRC registration, and
replaces the legacy validators. The OAuth 2.0, OpenID4VCI, and OpenID4VP clients are split into protocol clients
returning HttpExchanges for any HTTP stack, with the Ktor clients built on them, and servers return
PreparedHttpResponses to write out unchanged. Presentation Exchange and other APIs deprecated in 8.0.0 are removed,
and many fixes align VC-K with OpenID4VP 1.0, OpenID4VCI 1.0, ETSI TS 119 475 and TS 119 602, and ISO/IEC 18013-5, so
check the entries marked BREAKING when migrating.
- Removals:
- Remove code elements deprecated in 8.0.0: Presentation Exchange types, the module
dif-data-classeswith its
compatibility surface, and others
- Remove code elements deprecated in 8.0.0: Presentation Exchange types, the module
- Validation (package
at.asitplus.wallet.lib.validationinvck):- Add
CredentialValidator, validating credentials (CredentialValidationInput) and presentations
(PresentationValidationInputin aPresentationContext) under aValidationPolicy(TrustPolicy,
StatusPolicy, time leeway,requireTimeliness), returning aValidationReport: a tree of checks
(CheckOutcome: passed, failed with its cause, blocked, or not applicable) whosedecisionfollows from the
policy, next to the validated value, which is present only if the report is accepted. Trust anchors are
configured per credential type (CredentialTrustAnchors,CredentialTrustScopes, adapters for LoTEs and
TrustAnchorProvider), status with aStatusValidatorand aStatusListTokenResolver, signatures with a
SignatureCheck - Verify mdoc documents authenticated with a
deviceMac(ISO/IEC 18013-5:2021, 9.1.3.5) with the reader's
ephemeral private key, the newPresentationContext.eReaderKey, which was rejected as not supported before.
Without that key, the device authentication of such a document is blocked. A document with both a device
signature and a device MAC fails - Add
PresentationContext.forMdoc(sessionTranscript, eReaderKey), the context of mdoc device authentication,
e.g. of an ISO/IEC 18013-5 proximity reader, without challenge and audience. A blank challenge or audience of a
context is never answered, so presentations of other formats are rejected with it - Add
HolderAgent.storeCredentialWithReport(credential, policy, renewalInfo), and the parameter
credentialValidatorofHolderAgent, which validates every credential before storing it, and orders stored
credentials ingetValidCredentialsByPriority(), demoting only verified problems. Deprecate the constructor
takingvalidator,validatorVcJws,validatorSdJwt,validatorMdoc(which no longer take effect), and
trustedIssuers(adapted as anchors for every credential type) - Add
OpenId4VciClient.decodeCredentialResponse(response, request, representation), decoding every credential
of a (batch) response into aReceivedCredential, also one that can not be decoded, and
ReceivedCredential.prepareCredentialStorage(holder, scheme, policy, renewalInfo), validating it for storage by
theHolderAgent, which reports a credential that can not be decoded as rejected - Add
ValidationPolicy.requiredClaims(RequiredClaimsPolicy.Skipby default, orRequireMandatory) and the
checkCredentialChecks.requiredClaims(RequiredClaimsValidation, failing withMissingRequiredClaimException): whether a
credential received on its own contains every claim the metadata of its signed type describe asmandatory
(draft-ietf-oauth-sd-jwt-vc-19, 5.6.3), resolved by the newrequiredClaimsResolverofCredentialValidator
(RequiredClaimsResolver.Defaultuses the credential metadata registries and schemes ofAttributeIndex, and
blocks the check for a type without metadata). Not applicable to presented credentials - Add
CredentialValidationResult.unacceptedCredentialandValidationReport.isIntactCredential(): the
credential whatever the decision, if only checks a policy may relax are not satisfied (issuer trust, timeliness,
required claims, status), so that a wallet can store it after the user accepts the report - Add
HolderAgent.prepareCredentialStorage(credential, policy, renewalInfo), validating a received credential
without storing it, e.g. as the last step of issuance, returning aPreparedCredentialStorageto keep while the
user looks at the report, thenHolderAgent.storeAccepted(prepared), storing only an accepted credential, as
storeCredentialWithReportdoes, orHolderAgent.storeAfterUserConsent(prepared), storing also a rejected but
intact credential.CredentialStorageResult.storedEntrymay thus be present for a rejected report - Add
HolderAgent.prepareCredentialStorage(input, scheme, policy, renewalInfo), validating an encoded credential
(CredentialValidationInput), e.g. as received from a credential issuer, and decoding it only once: the
credential to store is the validated one, e.g. an mdoc with exactly the bytes the issuer signed. Add
HolderAgent.rejectUndecodable(cause, policy, renewalInfo)for a credential that can not be decoded from its
transfer encoding, so that it is reported, but never stored.PreparedCredentialStorage.credentialisnull
for a credential prepared from its encoding that is not intact - Add
RelyingPartyValidatorwithRelyingPartyTrustAnchors(WRPAC and WRPRC anchors, status list signers of
WRPRCs,PreRegisteredClients,VerifierAttesters, andClientIdentifierTrustfor client identifier
prefixes this library does not evaluate), validating the WRPAC, WRPRCs, and the client identifier prefix of a
request in one report;IsoMdocDcApi.validateRelyingParty(validator, policy)invck-openid. An unregistered
request fails with anUnregisteredRequestException.RelyingPartyValidator.validate(deviceRequest, sessionTranscript, policy)
validates the reader of a proximity presentation, e.g. over BLE or NFC (ISO/IEC 18013-5:2021, 9.1.4): of
severalreaderAuthAllsignatures, the report is about the first whose WRPAC is accepted and links to every
WRPRC, and a request that authenticates only some of its document requests failsrequestDatawith an
UnsupportedWrpRequestException, while itssignaturepasses - Add
ValidationPolicy.registration(RegistrationPolicy.Requireby default, orValidateIfPresent): whether
a relying party has to transport WRPRCs, so that a wallet can accept mdoc readers outside of the EUDI Wallet
ecosystem, e.g. the wallet apps of other wallet providers, trusted through
RelyingPartyTrustAnchors.accessCertificates. Without WRPRCs,registrationis not applicable, the reader
certificate need not carry the identifier of a WRPAC, andValidatedRelyingParty.identifierisnull - Add
ReaderAuthenticationVerifier.authenticate(request, transcript), returning anAuthenticatedReaderwith the
chain of everyreaderAuthAllsignature that verifies, and the document requests without reader
authentication (ISO/IEC DIS 18013-5:2025, 12.5.4). Deprecate itsinvoke, which returns only the first chain.
AreaderAuthAllthat does not verify fails as such, instead of falling back to thereaderAuthof document
requests - Add
AuthorizationRequestValidator.validate(request, policy), and
OpenId4VpHolder.startAuthorizationResponsePreparation(input, policy)and itsDcApiHolderoverload,
returning the report of the request next to the preparation state, also for a rejected request; also
OpenId4VpProtocolClient.prepareAuthorizationResponse(input, policy), loading a request object by reference,
and inOpenId4VpKtorHolderstartAuthorizationResponsePreparation(input, policy), its overload for parsed
requests, andprepareDcApiRequest(request, policy).
AuthorizationRequestValidation.relyingPartyandDcApiRequestValidation.relyingPartycarry the
RelyingPartyValidationResultof a request with a WRPRC, or of every Annex C request, validated like a
proximity request, whose report isDcApiRequestValidation.validationReport, andRelyingPartyValidationResult.untrustedRegistrations
the payloads of its WRPRCs whose parsing passed, whatever the decision, to show them next to a warning instead of
parsing them with the deprecatedWrpAuthenticationRequestValidator.
OpenId4VpHolder,OpenId4VpKtorHolder, andDcApiHoldertake arelyingPartyValidator; deprecate
RelyingPartyTrustand the constructors takingrelyingPartyTrust(mapped bytoRelyingPartyValidator();
RelyingPartyTrust.Customno longer takes effect) or the unusedkeyMaterial - Behavior changes, also through the legacy entry points, which delegate to the new validation:
- With
requireTimelinessand a status policy other thanSkip, an expired, not yet valid, revoked, or
status-unresolvable credential rejects its presentation and can't be stored - With
TrustPolicy.RequireAuthorizedSigner, missing anchors reject; with a status policy other than
Skip, a missing status list token resolver rejects a credential that has a status claim - A
jwknext to anx5cwith a different key fails the signature - SD-JWT:
typdc+sd-jwtandkb+jwtare enforced,vc+sd-jwtis rejected; unreferenced, duplicate, or
malformed disclosures and claim collisions fail (RFC 9901, 7.1); the KB-JWTiathas to be within the
leeway - mdoc: storage checks item digests, the
docTypeagainst the scheme, and the device key against the
holder key; a missing MSO fails - VP-JWT:
expandnbfare checked; a VP with one rejected VC is rejected - Credential issuers, status list tokens, key attestations, and client attestations are trusted through an
anchor that issued their signing certificate, possibly through intermediate certificates transported with
it, or one that is the signing certificate, as a LoTE may list it (ETSI TS 119 602 V1.1.1, 6.6.3, H.3; ETSI
TS 119 472-3 V1.1.1, AUTH-REQ-PROC-4.4.3-01). Otherwise, a transported anchor or a self-signed signer fails
(OpenID4VC HAIP 1.0, 4.5.1, 6.1, 6.1.1), and so does a transported certificate that did not issue the one
before it - WRPRC errors are reported instead of being turned into
false, and one clock is used for certificate
chains; a WRPRC no longer needsalgES256 or to lackkid, which ETSI TS 119 475 does not require - A signed authorization request whose signature can not be verified, as no key is established for its
client identifier prefix and it asserts none, is rejected (OpenID4VP 1.0, 5.9.3) - An ISO mdoc request without any reader authentication, and an unsigned request, are reported as unsigned:
thesignatureandparsingof the WRPAC are not applicable,requestDatafails with an
UnsupportedWrpRequestException, and their WRPRCs are not evaluated, while a reader authentication that does
not verify fails thesignature, so that a wallet can tell an unauthenticated reader from a forged one - A relying party reports its WRPRCs in the new named check
registrationofRelyingPartyChecks, required
unlessValidationPolicy.registrationisValidateIfPresent: a missing WRPRC (MissingRegistrationCertificateException) or one that can not be parsed
(InvalidRegistrationCertificateException) failsregistrationinstead ofrequestData, which keeps the
extraction of the request, e.g. itsclient_idand DCQL query, and fails for unsigned requests, whose
registrationis blocked
- With
- Deprecate the superseded validation API, to be removed in the next major release:
Validator,
ValidatorVcJws,ValidatorSdJwt,ValidatorMdoc,VerifierAgent, and the presentation methods ofVerifier
(useCredentialValidator);VcJwsInputValidator,SdJwtInputValidator,MdocInputValidatorand their
results,CredentialTimelinessValidator,CredentialFreshnessSummary,CredentialWrapper, and
ValidatiorVcJwsValidationException(see thetimelinessandstatuschecks of the report);
TokenStatusResolver,TokenStatusResolverImpl,TokenStatusResolverNoop,toTokenStatusResolver(),
TokenStatusResolver.resolve(),TokenStatusValidatorwith its functions, andTokenStatusValidationResult
(useStatusValidatorwith aStatusListTokenResolver, which stays);issuerJwsVerifier,
issuerCoseVerifier, andVerifyStatusListTokenHAIP(use trust anchors ofCredentialValidator);
WrpacValidator,WrprcValidator,WrpAuthenticationRequestValidator,WrpChainValidator, their results,
andIsoMdocDcApi.validateWrpAuthenticationRequest()(useRelyingPartyValidator) - BREAKING:
VcJwsTimelinessValidator,SdJwtTimelinessValidator,MdocTimelinessValidator, and
MobileSecurityObjectTimelinessValidatorare objects, as they hold no state and take the time per call
(TimeScope): callVcJwsTimelinessValidator(vcJws, timeScope)instead of
VcJwsTimelinessValidator()(vcJws, timeScope);MdocTimelinessValidatorno longer takes a
MobileSecurityObjectTimelinessValidator - Add
ValidationReport.namedChecks, the checks of a report by name with theRequirementits policy put on each
(NamedCheck.satisfiedtells whether it rejects the report), recorded when the report is created, so that a
report can be displayed as it has been decided, including the checks a relaxed policy tolerates; the decision
rules are derived from the same lists. Check names are field names, so renaming one is a breaking change noted
here; every check of trust carries itsTrustValidation,timelinessis always the validity of the artifact
(the time of a holder proof isproofTimeorpopTime), and status parts start withstatus. - Make
CheckOutcome.cause()andValidationReport.firstRejection()public, to display why a check did not pass
and why a report has been rejected - Move the holder binding of standalone credentials into
ValidationPolicy.holderBinding(default
HolderBindingPolicy.None):CredentialValidator.validate(input, policy, context),
CredentialValidator.rejectUndecodable(cause, policy), andValidationReport.credential(checks, policy, evaluatedAt)no longer take aHolderBindingPolicy. In a presentation, the presentation decides the holder
binding of its credentials;HolderAgent.storeCredentialWithReportandHolderAgent.prepareCredentialStorage
always require the holder's key PresentationValidationResult.rejection()takes the requirements from the report instead of a policy. Rejection
messages name the check, e.g. "The issuerSignature check did not pass", and disclosed items by index- Add
CredentialToJsonConverter.toJsonElement(ValidatedCredential),ValidatedCredential.credentialIdentifier,
andValidatedPresentation.credentials, e.g. to display the claims of accepted presentations ValidatedPresentation.SdJwt.keyBindingis the signed key binding JWT (JwsCompactTyped<KeyBindingJws>)
instead of its payload, andValidatedPresentation.SdJwt.sdJwtSignedis the SD-JWT as presented, with that key
binding JWT.VerifyPresentationResult.SuccessSdJwt.sdJwtSignedis the latter, so it contains no key binding JWT
the request did not require, as such a key binding JWT is not checkedStatusValidationrequires mechanisms for a passedagreement- Reduce certificate trust-check allocations by reusing the BasicConstraints OID and encoding the top certificate once when comparing it with trust anchors.
- Known limitations:
- Certificate paths: trust is evaluated by walking the transported chain, without RFC 5280 path building,
pathLenConstraintorkeyUsage, and without certificate revocation (CRL, OCSP). So a chain missing an
intermediate certificate is rejected, a directly listed self-signed signer certificate is accepted, and a
revoked WRPAC or signer certificate is not detected - ZK mdoc presentations are validated like plain mdocs, their proofs are not verified
- Certificate paths: trust is evaluated by walking the transported chain, without RFC 5280 path building,
- Add
- Subject credential store:
- BREAKING: Implementations of
SubjectCredentialStoreimplement the singlestoreCredential(entry: StoreEntry),
persisting a readyStoreEntry.HolderAgentstores every credential through it, with its renewal info. - Deprecate the per-format
storeCredential(vc, ...),storeCredential(vc, ..., disclosures, ...), and
storeCredential(issuerSigned, ...)ofSubjectCredentialStore, which now build theStoreEntryand call
storeCredential(entry): implementations no longer need to override them - Add
StoreEntry.fromPreparedCredential(prepared), the entry of aPreparedCredentialStoragebuilt from what
its validation verified, also for a rejected but intact credential, andStoreEntry.Vc.fromString(...),
StoreEntry.SdJwt.fromString(...), andStoreEntry.Iso.fromString(...)(base64url as in OpenID4VCI, or
base64), parsing a serialized credential without validating it
- BREAKING: Implementations of
- Trusted relying parties:
- Fix JSON serialization of
JsonClaimReferenceasSingleClaimReference, e.g. inRequestDataValidity, which produced invalid JSON sinceNormalizedJsonPathbecame a list in JsonPath4K 4.0.0. It is now serialized as an object holdingnormalizedJsonPath - BREAKING:
JsonClaimReferencenow wrapspath: DCQLClaimsPathPointer, preserving wildcards during WRPRC authorization. The concrete-path
constructor is retained; deprecatenormalizedJsonPath, which cannot represent wildcards. Its JSON member name
remainsnormalizedJsonPath - WRPRC authorization exempts only the root
vctclaim; nested claims namedvctmust be registered WrprcValidatorno longer drops the cause when a registration certificate can not be validated, e.g. for a missing or unparseable certificate chain:WrprcValidationResult.certificateValidationResultsholds aKmmResultfor each certificate. DeprecateWrprcValidationResult.certificateValidation, which maps such certificates tonullWrprcValidatorvalidates each credential request on its own, so a request that can not be validated (e.g. a DCQL claims path with anullsegment) no longer fails the whole validation:WrprcValidationResult.requestDataValidationResultsholds aKmmResultfor each request. DeprecateWrprcValidationResult.requestDataValidation, which maps such requests to an invalid credential type without attributes,WrprcValidator.validateRequest()in favor ofvalidateCredentialRequests(), and theWrprcValidationResultconstructor taking the old typesWrpAuthenticationRequestValidatortells apart why it can not extract a WRPRC, with exceptions extendingIllegalArgumentException:MissingRegistrationCertificateExceptionif the request contains none,InvalidRegistrationCertificateExceptionwith the parsing error as cause if it can not be parsed, andUnsupportedWrpRequestExceptionfor requests it can not validate, e.g. unsigned onesWrpAuthenticationRequestValidatorrejects requests with more than oneregistration_certentry inverifier_info, also if only one of them can be parsed, and ISO requests that carry aneuWrprcin only some of theirDocRequestsWrpAuthenticationRequestValidatorrejects aregistration_certentry inverifier_infowhose data is a JSON object instead of a string- Add
WrpRegistrationCertificateValidation.tokenStatus, which holds the status of the WRPRC from its status list, or why it could not be obtained, so that a revoked or suspended WRPRC can be told apart from one whose status is unknown
- Fix JSON serialization of
- Trust List filtering:
- BREAKING:
LoteProfile.fromSchemeIdentifier()returnsnullfor a credential identifier without a known list, instead ofLoteProfile.EAA, so thatLoTETrustAnchorProviderno longer authorizes every Pub-EAA provider for arbitrary credential types. Identifiers match case-sensitively, and the mDLdocTypeorg.iso.18013.5.1.mDLmatches exactly instead of by prefix - BREAKING: Remove
additionalAnchorsofLoTETrustAnchorProvider, which was added to the anchors of every credential type and every list, and so merged trust sources across types. Trust further sources for a type with aCredentialTrustScopeinstead
- BREAKING:
- Status lists:
- Add
StatusListCwt.encodeForPublication()to encode a CWT as a tagged COSE_Sign1 (CBOR tag 18), as required by Token Status List draft 21. Generic COSE serialization remains unchanged.
- Add
- DCQL:
- Claim paths preserve numeric object keys as strings and reject invalid or overflowing indices
DCQLClaimsPathPointer.queryprocesses claims path pointers as OpenID4VP 1.0, 7.1.1 specifies: if any selected element is not an object for a name component, or not an array for an index ornullcomponent, the result is empty, i.e. the claim does not match, instead of continuing with the other selected elements; other options ofclaim_setsthat do not request the claim may still match- Reject queries that OpenID4VP 1.0 does not allow: empty
valuesof a claims query, emptyvct_values, empty arrays intype_valuesor in theoptionsofcredential_sets,optionsthat reference an unknown Credential Query andclaim_setsthat reference an unknown claims query; a claims query that points to the same claim again is ignored when matching - Match the
valuesof a claims query against the JSON value of an ISO mdoc data element as OpenID4VP 1.0, 6.3 requires, converted withCredentialToJsonConverter, so that a full-date or date-time matches its string, e.g."1990-01-01"forbirth_date, a byte string its base64url encoding, and a value of a scheme-specific type the JSON encoding of the serializer registered for its data element withCborCredentialSerializer, e.g. the integer code ofIsoSexEnum, or else of its registeredJsonValueEncoder; such elements matched no value before. AddCredentialToJsonConverter.toJsonElementOrNull, which returns null instead of thetoString()of a value of a type without a known encoding.DCQLCredentialClaimStructure.IsoMdocStructureholds the JSON values of the data elements (Map<String, Map<String, JsonElement>>instead ofAny), withJsonNullfor an element without a JSON value, andDCQLClaimsQueryResult.IsoMdocResult.claimValueis that JSON value HolderDCQLQueryMatchingResult.toDefaultSubmissionselects the first option of a required credential set whose queries all match a credential, instead of always the first option, which produced an empty submission when no credential matched it; it fails if no option of a required credential set is satisfiable- Add request-side X.509 DCQL support for ETSI TS 119 432 Annex A.6:
CredentialFormatEnum.X509,
DCQLX509CredentialQuery, andX509MetadataQueryimplementingDCQLCredentialMetadataAndValidityConstraints.
- QES approvals:
- Bind each approval transaction to one selected DCQL credential submission, allowing separate approvals for distinct queries without adding approvals to unrelated identity presentations
- Treat multiple
credential_idsas alternatives and reject missing, empty, or unknown query references in OpenID4VP transaction data withinvalid_transaction_data - Add optional
PresentationRequestParameters.qesApprovalTransactionDatafor selecting approval data independently of the general SD-JWT transaction-data integrity hashes
- Credential schemes:
- Add
IsoMdocCredentialScheme.isoQualifiedClaimDescriptionsinvck, the claim descriptions of a scheme with their
paths qualified by its ISO namespace, as the ISO mdoc credential configurations ofDefaultCredentialSchemeMapperuse - Add
IsoMdocCredentialScheme.isoClaimPathsAreQualified(defaultfalse); whentrue, preserve all claim paths
without adding the scheme namespace. ISO schemes derived from type metadata set it totrue: their paths must
include their namespace, including whenvck.isoNamespaceis absent or another namespace is used
- Add
- OpenID for Verifiable Credential Issuance issuer:
- BREAKING:
ProofValidatortakes aKeyAttestationValidator(invck) instead ofverifyKeyAttestationSignature
andstatusListTokenResolver: configure it with the anchors of the wallet providers, whose certificate in
x5chas to sign key attestations (EUDI TS3, 2.2.2.2), and a status list token resolver for
key_storage_status. Without anchors, key attestations are rejected, as before - Add
ProofValidator.validateProofs(params, policy), reporting every proof with its key attestation;
validateProofExtractSubjectPublicKeysdelegates to it. A key attestation embedded in ajwtproof needs an
exp, and its optionalnoncemust be a server-providedc_nonce(OpenID4VCI 1.0, F.1, D.1), ajwtproof
at most one ofkid,jwk, andx5c; a key attestation no longer needsuser_authentication, and may
containiss(EUDI TS3, 2.3.2)
- BREAKING:
- OpenID for Verifiable Credential Issuance client:
- Add
WalletService.createCredential(metadata, credentialConfigurationId, ...)to request exactly one credential
by itscredential_configuration_id - Add
CredentialIssuanceResult.Success.reissuingStoreEntryId, the stored credential the new ones replace, as
passed tostartProvisioningWithAuthRequestReturningResultand kept in theProvisioningContext - Add
CredentialIssuanceResult.Success.prepareCredentialStorage(holder, policy), validating the received
credentials with their renewal info byHolderAgent.prepareCredentialStorage, for the wallet to show the reports
and store them withHolderAgent.storeAccepted, orHolderAgent.storeAfterUserConsentafter the user accepts a
rejected report, e.g. of a credential missing claims its type requires (ValidationPolicy.requiredClaims). A
credential that can not be decoded is reported as rejected instead of failing the issuance - BREAKING:
CredentialIssuanceResult.Successholds the credentialsissuedfor each requested credential
configuration asIssuedCredentials: thereceivedCredentials(ReceivedCredential), not validated yet, the
schemethey have been requested as, and their renewal inforefreshToken, instead ofcredentialsand
refreshToken. Deprecatecredentials, which decodes the credentials of all configurations without validating
them, and fails for a credential that can not be decoded, andrefreshToken, which is the one of the first
configuration - Add
OpenId4VciProtocolClient.loadIssuerMetadata(credentialIssuer, policy)and
OpenId4VciKtorClient.loadIssuerMetadata(credentialIssuer, policy), asking for signed metadata
(application/jwt) over unsigned ones (OpenID4VCI 1.0, 12.2.2), and returningLoadedIssuerMetadata:Signed
with theIssuerMetadataValidationof the new constructor parameterissuerMetadataValidator, also if
rejected, orUnsigned, which the wallet may reject, e.g. as the EUDI Wallet ecosystem requires signed metadata - Add
CredentialIdentifierInfo.issuer, theValidatedCredentialIssuerof signed metadata, which is now
serializable, so that it is kept inProvisioningContextduring the authorization in the browser; add
parseCredentialMetadata(loaded: LoadedIssuerMetadata), setting it, and failing for rejected signed metadata loadIssuerMetadata(credentialIssuer)andloadCredentialMetadata(host)ask forapplication/json, and fail
for metadata whosecredential_issueris not the requested identifier, whose data "MUST NOT be used"
(OpenID4VCI 1.0, 12.2.4)- Add
IssuerMetadataValidatorinvck, validating signed credential issuer metadata (OpenID4VCI 1.0, 12.2.3)
for the identifier it was requested for, signed with the access certificate (WRPAC) of the provider inx5c
(ETSI TS 119 472-3 V1.1.1, 4.2), whose chain leads to an anchor of the WRPAC providers. It returns a report, and
the metadata with the signing WRPAC if accepted; a policy not requiring trust lets a wallet warn about an
untrusted provider instead of rejecting it - The metadata have to carry
issuer_infoholding its registration certificate (registration_cert),
which is validated as for relying parties, has to be issued to the identifier of the WRPAC, and has to register
the provider with an entitlement of attestation providers (ETSI TS 119 472-3 V1.1.1, 4.2.3;
ETSI TS 119 475 V1.2.1, A.2). Accepted metadata come with aValidatedCredentialIssuer - Add
IssuerMetadataValidation.untrustedMetadataandLoadedIssuerMetadata.Signed.untrustedMetadata, the
metadata whatever the decision if their signature and subject passed, and
parseCredentialMetadataIgnoringTrust(loaded)inOpenId4VciProtocolClientandOpenId4VciKtorClient, which
parses them without a validated credential issuer, so that a wallet can continue after warning the user, e.g.
about an untrusted WRPAC or a rejected WRPRC IssuerMetadataValidatorkeeps the requirements of the EUDI Wallet ecosystem out ofparsingandsignature:
the signature is verified with the key the metadata assert, thex5cleaf or ajwk; metadata without a WRPAC
inx5c, or with one carrying no identifier, failtrust;issuer_infois checked by the new named check
registrationofIssuerMetadataChecks, required by every policy, which fails without exactly one
registration_cert.IssuerMetadataValidation.untrustedRegistration, also onLoadedIssuerMetadata.Signed,
carries its payload whatever the decision. As the WRPRC has to be linked to the identifier of a WRPAC, metadata
without a WRPAC are rejected under every policy, and accepted metadata always come with anissuer- Add
IssuerMetadata.issuerInfowithIssuerInfo, whosedatais a JSON element, and
OpenIdConstants.IssuerInfo - Add
OpenId4VciServer.issuerInfo, served in the signed metadata - Renamed
WalletService(invck-openid) toOpenId4VciClient - Renamed
OpenId4VciClient(invck-openid-ktor) toOpenId4VciKtorClient - Renamed
CredentialIssuer(invck-openid) toOpenId4VciServer - Add
OpenId4VciProtocolClientinvck-openid, returningHttpExchanges for the requests to the credential issuer
(metadata, nonce, credential), built onOAuth2ProtocolClient, which handles DPoP for the credential issuer with
the credential issuer's own nonces.credentialRequestdecodes the response intoReceivedCredentials, see
OpenId4VciClient.decodeCredentialResponse - Move
CredentialIdentifierInfotovck-openid(at.asitplus.wallet.lib.oidvci), deprecate the typealias left
invck-openid-ktor; the serialized form is unchanged OpenId4VciKtorClientsends the requests ofOpenId4VciProtocolClientandOAuth2ProtocolClientin the order of each
flow, keeping its API- Fix: Credential requests use only DPoP nonces of the credential issuer, never the one of the authorization server
from the token response, as nonces are only accepted by the server that issued them (RFC 9449 9.) - Add
OpenId4VciProtocolClient.loadCredentialOfferandOpenId4VciClient.loadCredentialOfferto load a credential
offer passed by value or by reference; the resource atcredential_offer_urimust be the JSON-encoded offer, so
another offer URL or a redirect in its place is rejected - Deprecate
WalletService.parseCredentialOffer, which retrieved offers passed by reference with the
remoteResourceRetrieverofOpenId4VciClient; that constructor parameter is only used by the deprecated method - Deprecate
OpenId4VciClient.parseCredentialResponse, which decodes credentials without validating them: use
decodeCredentialResponseandReceivedCredential.prepareCredentialStorage. The overloads without the
originatingCredentialRequestalso accept a plain response even if the request asked for an encrypted one - Add
OpenId4VciProtocolClient.createCredential, whose proof JWTs name theclient_idof itsOAuth2Clientas the
issuer (OID4VCI 1.0 Appendix F.1);OpenId4VciClient.createCredentialtakes it as the new parameterclientId - Deprecate
OpenId4VciClient.clientId, which only remains the default forOpenId4VciClient.createCredential;
theclient_idbelongs toOAuth2Client - Add
ProofCreator, creating theproofsof a credential request of one proof type (OpenID4VCI 1.0, Appendix F),
withJwtProofCreator(jwt, F.1, signed with itskeyMaterial, with a key attestation if the issuer requires
one) andAttestationProofCreator(attestation, F.3).OpenId4VciClienttakesproofCreatorsand uses the
first proof created, in their order, for each credential request: if one fails, e.g. as it can not load a key
attestation the issuer requires, the next one is asked. The request fails if none creates a proof although the
issuer listsproof_types_supported, asproofsare required then (8.2) - Deprecate the constructor of
OpenId4VciClienttakingkeyMaterial,loadKeyAttestation,
selectProofJwtKeyBinding, andclock, which creates aJwtProofCreatorof them, followed by an
AttestationProofCreatorifloadKeyAttestationis set, as before - Extend
KeyAttestationInputto contain the requirements of the issuer asKeyAttestationRequired
from issuer metadata, and deprecateKeyAttestationInput.preferredKeyStorageStatusPeriod, use
requirements?.preferredTtlinstead - Add
OpenId4VciClient.prepareCredentialRequests(tokenResponse, metadata, credentialFormats, ...), preparing one
PreparedCredentialRequestper credential the access token grants, each with the configuration of its
credential_configuration_id(OpenID4VCI 1.0, 5.1.1), or of itsscope, which its proof and the decoding of its
response depend on. Overloads prepare every credential the token grants, or the given
credentialConfigurationIds. Create each request withcreateCredential(request, metadata, clientNonce, ...)
right before sending it, with a proof for its own configuration and its ownc_nonce, so that credentials of
different formats can be requested with one access token, also from a credential issuer that accepts each
c_nonceonly once - Fix:
OpenId4VciClient.createCredential(tokenResponse, metadata, credentialFormat, ...)creates requests only for
the credentials ofcredentialFormatthe token grants, instead of for every credential it grants with a proof
forcredentialFormat, and fails if it grants none of them. Authorization details of the token response without
acredential_configuration_id, which is REQUIRED (5.1.1), are rejected - Add overloads of
OpenId4VciKtorClient.startProvisioningWithAuthRequestReturningResultand
OpenId4VciKtorClient.loadCredentialWithOfferReturningResulttaking severalCredentialIdentifierInfos of one
credential issuer, requesting all of them with one authorization and one token, e.g. as a pre-authorized code can
usually be used only once. They are requested withscopeif every one of them has one, else with
authorization_details(OpenID4VCI 1.0, 12.2.4).ProvisioningContext.credentialskeeps them during the
authorization in the browser; a context stored by an earlier version requests itscredential - Fix:
OpenId4VciKtorClientrequests a freshc_noncefor each credential request, as the credential issuer may
accept each one only once (13.8), and sends a request once more with a new one if the credential issuer rejects it
withinvalid_nonce(8.3.1.2), seeOpenId4VciProtocolClient.isInvalidNonce. Each credential is requested and
decoded with its own configuration, so that credentials of different formats can be issued in one flow - Fix: Proof JWTs of
OpenId4VciKtorClientname theclient_idof the OAuth 2.0 client that requested the token,
instead of the one ofOpenId4VciClient, which differed when the OAuth 2.0 client was passed separately - Add a constructor to
OpenId4VciKtorClienttaking the app's ktorHttpClient, theOAuth2Client(the only
source of the client ID), theOpenId4VciClientand aClientAttestation, so that each value is passed once.
Deprecate the constructor takingengine,cookiesStorage,httpClientConfigand anOAuth2KtorClient, whose
HTTP configuration it silently preferred over its own
- Add
- OAuth 2.0 authorization server:
- BREAKING:
AttestationBasedClientAuthenticationServicetakes aClientAttestationValidator(invck) instead
ofverifyJwsObject: configure it with the anchors of the wallet providers, which have to issue the
certificate inx5csigning client attestations (EUDI TS3, 2.2.1.2), and a status list token resolver for
client_status. Without anchors, client attestations are rejected; before, the default accepted any client
attestation verifying with the key it carried - Add
AttestationBasedClientAuthenticationService.validate(httpRequest, clientId, validatedClientKey, policy),
reporting the client attestation with its proof of possession;authenticateClientdelegates to it. The
challenge is consumed only if the client attestation is accepted,client_status.statusis resolved if a
status list token resolver is configured, and an expiredclient_statusfails withuse_fresh_attestation
instead ofinvalid_client. A client attestation no longer needsiat, a client attestation PoP needsaud
also withoutissuerIdentifier(draft-ietf-oauth-attestation-based-client-auth-10, 5.1, 5.2)
- BREAKING:
- OAuth 2.0 client:
- Add
OAuth2ProtocolClientinvck-openid, implementing the client side of OAuth 2.0 (PAR, JAR, token requests,
token introspection, userinfo) including DPoP and attestation-based client authentication, without sending
requests itself: each call returns anHttpExchange, whose requests (ProtocolRequest) callers send with any
HTTP stack.ProtocolRequest.Retryablemarks the requests that are sent again when the server asks for a DPoP
nonce or an attestation challenge, with theirattempt. ThePreparedHttpRequestof each request carries its
bodyasHttpBody, with thecontentTypeto send asContent-Type; itsheadersnever containContent-Type OAuth2KtorClientsends the requests ofOAuth2ProtocolClient, keeping its API- Move
TokenResponseWithDpopNonce,LoadInstanceAttestationInputandOpenUrlForAuthnRequesttovck-openid
(at.asitplus.wallet.lib.oauth2, the latter two nested inOAuth2ProtocolClient), deprecate the typealiases
left invck-openid-ktor - Deprecate
OAuth2KtorClient.callTokenIntrospectionwith the parameterstoken(never used) andretryCount
(now ignored), replace with the overload without them - Fix: A retried token introspection request passes
issuerMetadatatoloadInstanceAttestation RemoteOAuth2AuthorizationServerAdapterloads the authorization server metadata and the user info through
OAuth2ProtocolClient; the DPoP proof for the userinfo endpoint uses the nonce the userinfo endpoint provided,
instead of the nonce of the token responseOAuth2ProtocolClientkeeps DPoP nonces from responses of the authorization server (for requests with client
authentication) apart from those of resource servers (for requests with an access token), even on the same
origin, as RFC 9449 9. requires;OAuth2KtorClient.applyTokenfalls back to the latest nonce of the resource
server at that origin- Add
ClientAttestationinvck-openid(at.asitplus.wallet.lib.oauth2), holding the instance attestation loader
together with the key it attests;OAuth2ProtocolClienttakes it as one parameter, and signs DPoP proofs with
its key by default (EUDI TS3 binds the DPoP key to the Wallet Instance Attestation), else with an ephemeral key - Add constructors to
OAuth2KtorClientandRemoteOAuth2AuthorizationServerAdaptertaking the app's ktor
HttpClient, whose copy without following redirects sends the requests, and aClientAttestation; the DPoP key
defaults to the attested key, so that refresh tokens remain usable after the app restarts. Deprecate the
constructors takingengine,cookiesStorageandhttpClientConfig - Add
OAuth2KtorClient.clientAttestation, deprecateOAuth2KtorClient.loadInstanceAttestation - Form-encoded requests (PAR, token, token introspection) are sent as
application/x-www-form-urlencodedwithout
charsetin theContent-Type, as the media type defines no such parameter - Change:
BuildDPoPHeader/BuildClientAttestationJwt/BuildClientAttestationPoPJwtget a parameterissuedAtthat is set without applying the clock skew, so callers need to set it on their own
- Add
- Token introspection with JWT responses (RFC 9701):
- BREAKING: Remove
TokenIntrospectionRequest.responseFormatandTokenIntrospectionRequest.ResponseFormat, as
no specification defines the form parameterresponse_format: a JWT response is requested with the header
Accept: application/token-introspection+jwt(RFC 9701, 4) - BREAKING:
TokenIntrospectionJwtResponseholds the signed JWT (JwsCompactTyped<TokenIntrospectionJwtPayload>)
and is no longer serialized as{"jwt": …};toHttpResponse()sends the JWT itself as body with
Content-Type: application/token-introspection+jwt(RFC 9701, 5) - Add
TokenIntrospectionJwtPayload, with the claimsiss,aud,iatandtoken_introspectionthat RFC 9701, 5
requires, instead of the members of the introspection response at the top level of the JWT - BREAKING: The constructor parameter
signIntrospectionJwtofSimpleAuthorizationServicesigns a
TokenIntrospectionJwtPayload SimpleAuthorizationService.tokenIntrospectionanswers with a JWT when theAcceptheader of the request asks for
application/token-introspection+jwt, with the authorization server asissand the authenticated client, i.e.
the resource server, asaud; without client authentication it refuses such requests withinvalid_client
(status 400, RFC 9701, 5)- BREAKING:
verifyTokenIntrospectionJwtofOAuth2ProtocolClient,OAuth2KtorClientand
RemoteOAuth2AuthorizationServerAdapteris aVerifyJwsObjectFun?,nullby default. When set, token
introspection requests JWT responses, and accepts only those withtyptoken-introspection+jwt, a verified
signature, the authorization server asissand theclient_idof the client asaud; whennull, it requests
JSON responses. Before, a JWT was accepted even when not requested, and by default without verifying it - Add
MediaTypes.Application.TOKEN_INTROSPECTION_JWT - BREAKING:
TokenIntrospectionResponse.audienceis aSet<String>?, asaudmay be a single string or an array
(RFC 7662, 2.2; RFC 7519, 4.1.3): responses with an array failed to parse.TokenIntrospectionJwtPayload.audience
is aSet<String>, and the client accepts JWT responses that list itsclient_idinaud. Add
JwtAudienceSerializer, which decodes both forms and encodes a single audience as string - Fix:
RemoteOAuth2AuthorizationServerAdapter.getTokenInfosendstoken_type_hintaccess_token, instead of the
scheme of theAuthorizationheader (DPoP,Bearer), or the token itself for a header without scheme, which
are no token type hints (RFC 7009, 4.1.2). AddOpenIdConstants.TOKEN_TYPE_HINT_ACCESS_TOKENand
TOKEN_TYPE_HINT_REFRESH_TOKEN - Add
TokenIntrospectionResponse.confirmationClaim(cnf) andTokenInfo.confirmationClaim:
SimpleAuthorizationService.tokenIntrospectionconveys the binding of a DPoP-bound access token ascnfwith the
JWK SHA-256 thumbprint injkt, andtoken_typeDPoP(RFC 9449, 6.2), and
RemoteOAuth2AuthorizationServerAdapter.getTokenInfopasses it on, so that a resource server can check that the
DPoP proof of a request is signed with that key (RFC 9449, 7.1)
- BREAKING: Remove
- HTTP error handling:
- Add
HttpErrorResponseExceptionandProblemDetailsinvck-openid(packageat.asitplus.wallet.lib), so
OAuth 2.0 errors and RFC 9457 problem details of non-success responses are available without a ktor client.
The exception carriesstatusandheadersinstead of a ktorHttpResponse - Add
OAuth2Error?.dpopNonce(Headers)andOAuth2Error?.attestationChallenge(Headers)invck-openid - BREAKING:
HttpErrorResponseExceptioninvck-openid-ktoris a deprecated typealias of the new class, and no
longer a ktorResponseException, so catching either name catches every error response, also of exchanges sent
without ktor. Itsresponseand the constructors taking anHttpResponseare removed, usestatus,headers
andresponseBody; it is still anIllegalStateException - Deprecate the typealiases
HttpErrorResponseExceptionandProblemDetailsinvck-openid-ktor, replace with
the classes fromvck-openid - Deprecate
HttpErrorResponseException.dpopNonce()andHttpErrorResponseException.attestationChallenge()in
vck-openid-ktor, replace withoauth2Error.dpopNonce(headers)andoauth2Error.attestationChallenge(headers)
- Add
- Server-side HTTP responses:
- Add
PreparedHttpResponseinvck-openid(packageat.asitplus.wallet.lib), a response with status, headers and
encoded body, to be written out unchanged with any HTTP server stack. Thebodyis anHttpBody, with the
contentTypeto write out asContent-Type; theheadersnever containContent-Type - Add
OAuth2Exception.toHttpResponse()for errors of authorization server endpoints: status 400, withDPoP-Nonce
foruse_dpop_nonceandOAuth-Client-Attestation-Challengeforuse_attestation_challenge - Add
OAuth2Exception.toResourceServerHttpResponse(authorizationHeader)for errors of endpoints accessed with an
access token (credential, userinfo): status 401 withWWW-Authenticateforinvalid_token,invalid_dpop_proof
anduse_dpop_nonce(the latter withDPoP-Nonce), as RFC 6750 3. and RFC 9449 7.1 and 9. require, else 400;
use_attestation_challengecomes withOAuth-Client-Attestation-Challengehere, too - Add
toHttpResponse()for the results of the authorization server:OAuth2AuthorizationServerMetadata,
AttestationChallengeResponse(withCache-Control: no-store),ResponseWithDpopNonceof PAR (status 201),
token (withCache-Control: no-storeandPragma: no-cache) and userinfo (each withDPoP-Nonce),
AuthenticationResponseResult.Redirect(302 withLocation) andTokenIntrospectionResult - Add
ResponseWithDpopNonce.attestationChallenge:SimpleAuthorizationService.parWithDpopNonceand
tokenWithDpopNonceprovide a fresh attestation challenge with attestation-based client authentication, which
toHttpResponse()sends asOAuth-Client-Attestation-Challenge(attestation-based client authentication,
draft 10, 6.2);OAuth2ProtocolClientuses it for the next request, instead of fetching one from the challenge
endpoint - Add
OpenId4VciServer.metadataHttpResponse(acceptHeader), servingsignedMetadata()asapplication/jwtwhen
theAcceptheader listsapplication/jwtwith a quality not below the one ofapplication/json, else
metadataasapplication/json(OID4VCI 1.0, 12.2.2), withVary: Accept - Add
toHttpResponse()forOpenId4VciServer.Nonce(withCache-Control: no-storeandDPoP-Nonce) and
OpenId4VciServer.CredentialResponse(application/json, orapplication/jwtwhen encrypted, each with
Cache-Control: no-store); errors of the credential endpoint convert with
toResourceServerHttpResponse(authorizationHeader) - Add
CreatedRequest.loadRequestObjectHttpResponse(params)invck-openid, serving the request object at the
verifier'srequest_uriasapplication/oauth-authz-req+jwt(OpenID4VP 1.0, 5.10.1) - Add
directPostHttpResponse(redirectUri)invck-openid(at.asitplus.wallet.lib.openid), the answer of the
verifier's response endpoint fordirect_postanddirect_post.jwt: a JSON object with the optional
redirect_uri(OpenID4VP 1.0, 8.2), withCache-Control: no-store
- Add
- OpenID for Verifiable Presentations client:
- Renamed
OpenId4VpWallet(invck-openid) toOpenId4VpKtorHolder - Add
OpenId4VpProtocolClientinvck-openid, returningHttpExchanges for the requests to the verifier:
prepareAuthorizationResponsefetches the request object fromrequest_uriwhen needed,
sendAuthorizationResponseposts authorization responses and error responses for the response modes
direct_postanddirect_post.jwt - Deprecate
OpenId4VpHolder.startAuthorizationResponsePreparationandOpenId4VpHolder.createAuthnResponse
taking the request asString, which fetch the request object with theremoteResourceRetriever, replace with
OpenId4VpProtocolClient.prepareAuthorizationResponse, followed byOpenId4VpHolder.finalizeAuthorizationResponse
for the latter - BREAKING:
OpenId4VpHolderno longer fetches the verifier's JWK Set from ajwks_uriinclient_metadata, which
OpenID4VP 1.0 does not define (5.1 definesjwksonly, and requires other metadata parameters to be ignored);
the keys come fromjwks, or else fromlookupJsonWebKeysForClient - Fix (security): The
redirect_uriin the verifier's answer to a posted authorization response must be an absolute
httpsURI, else sending the response fails, although the verifier has already processed it. Before, any value,
e.g. ajavascript:orintent:URI, or a custom app scheme, was returned for the wallet to open - The
redirect_uriis only read from the JSON body of the verifier's answer (OpenID4VP 1.0, 8.2); aLocation
header is ignored OpenId4VpKtorHolderfetches request objects and sends authorization responses withOpenId4VpProtocolClient,
keeping its API; forrequest_uri_method=post, the form is now sent withoutcharsetin theContent-Type, as
the authorization response- Move
OpenId4VpSuccesstovck-openid(at.asitplus.wallet.lib.openid), deprecate the typealias left in
vck-openid-ktor - Deprecate
OpenId4VpKtorHolder.FormDataContentPlain, which is no longer used - Add a constructor to
OpenId4VpKtorHoldertaking the app's ktorHttpClient, whose copy without following
redirects sends the requests, and every parameter ofOpenId4VpHolder, with the holder asholder: Holder
instead ofholderAgent: HolderAgent. Deprecate the constructor takingengineandhttpClientConfig - The deprecated
OpenId4VpHoldermethods ofOpenId4VpKtorHolder.openId4VpHolderfetch request objects as
OpenId4VpProtocolClientdoes, i.e. the form posted torequest_urihas nocharseteither - For
direct_post.jwt, an error response that can't be encrypted, e.g. as the verifier passed no suitable key,
is sent without encryption, as OpenID4VP 1.0, 8.3.1 allows; before, creating it failed, so that
OpenId4VpKtorHoldersent nothing at all. A presentation is never sent without encryption - Fix: Error responses over the Digital Credentials API follow OpenID4VP 1.0, A.4:
datais an object with the
single propertyerror, never encrypted, also fordc_api.jwt; before, the whole error includingstateand
description was passed inresponse, encrypted fordc_api.jwt, which verifiers could not process
- Renamed
- OpenID for Verifiable Presentations verifier:
- Add
error,errorDescriptionanderrorUritoAuthenticationResponseParameters, so that authorization error
responses keep their parameters when decoded from a form post (OpenID4VP 1.0, 8.2), the payload of an encrypted
response (8.3.1), or thedataof a Digital Credentials API response (A.4) - BREAKING (binary): The constructor and
copyofAuthenticationResponseParametersgain these trailing
parameters; calls with named or leading positional arguments still compile - Fix: A response violating the encryption its request requires (
direct_post.jwt,dc_api.jwt) consumes the
request's nonce and ephemeral encryption key along with the request; before, the nonce stayed valid and the key
stayed stored until evicted OpenId4VpVerifierandDcApiVerifiershare one lifecycle for responses: request, nonce and ephemeral
encryption key are consumed before any validation that can fail, and the key is removed also for responses that
were not encrypted- A Digital Credentials API response to
OpenId4VpVerifier, or a response to a signed DC API request from an
origin not in itsexpected_origins, now failsvalidateAuthnResponseinstead of only its
vpTokenValidationResult; the request is consumed either way - BREAKING:
AuthnResponseResultis a sealed interface with the subtypesSuccess(with the non-null
vpTokenResult, formerlyvpTokenValidationResult, andrequest) andError(with the wallet'serroras
OAuth2Error, andrequest);requestis non-null. Migrate
result.vpTokenValidationResult?.getOrThrow()to awhenover both subtypes, e.g.
is AuthnResponseResult.Success -> result.vpTokenResult.getOrThrow()and
is AuthnResponseResult.Error -> showError(result.error) - Fix:
OpenId4VpVerifierandDcApiVerifierprocess authorization error responses of the wallet (OpenID4VP 1.0,
8.2, A.4) asAuthnResponseResult.Error, ending the request like a presentation, instead of reporting them as
invalid presentations (direct_post, DC API) or failing (direct_post.jwt). Answer them with
directPostHttpResponse()like presentations - Accept plaintext authorization error responses for
direct_post.jwt(OpenID4VP 1.0, 8.3.1) anddc_api.jwt
(A.4), as OpenID4VP allows them although OpenID4VC HAIP requires encrypted responses; an error carries no
credential, and a plaintextvp_tokenis still rejected - Reject authorization error responses violating RFC 6749, 4.1.2.1 (
errorblank or with illegal characters,
illegal characters inerror_descriptionorerror_uri, either of them withouterror), combiningerror
withvp_tokenorcode, and responses passing parameters next to an encodedresponse - Fix:
DcApiVerifierreturns the newIso180137AnnexCErrorwith the status of an ISO/IEC 18013-7 Annex C device
response with an error status (ISO/IEC 18013-5, 10.3.6), which is the only error Annex C can convey, instead of
failing as for a malformed response; a device response with an error status and documents still fails.
BREAKING:Iso180137AnnexCWrapperandIso180137AnnexCErrorimplement the new sealedIso180137AnnexCResult,
a further subtype ofDcApiResponseResult - Add
EphemeralEncryptionKeyService.discardKey(identifier), removing a key without decoding it OpenId4VpVerifierandDcApiVerifiervalidate every presentation with aCredentialValidatorand report the
whole response as aValidationReport, configured with the new constructor parameterscredentialValidator
andpresentationPolicy; the default policy requires authorized issuers, so without trust anchors every
presentation is rejected in the report.VpTokenValidationResultDCQL.presentationValidationsholds the
validated presentations by credential query- BREAKING:
AuthnResponseResult.SuccessandIso180137AnnexCWrapperhave the non-nullvalidationReport, and
Iso180137AnnexCWrapperthe validatedpresentationValidations, both without defaults in their constructors - BREAKING (behavior):
OpenId4VpVerifierandDcApiVerifiervalidate every presentation once, with
credentialValidatorunderpresentationPolicy, and project the legacy results from that validation:
an accepted presentation becomes itsVerifyPresentationResult, with theCredentialFreshnessSummaryof its
report, a rejected one fails with the cause of its first rejecting check
(PresentationValidationResult.rejection(policy)), andsubmissionRequirementsValidationResultis the
submissionRequirementscheck of the report. So what the legacy verifier accepted with a summary that is not
fresh, e.g. expired, revoked, or with a status that can not be resolved, fails wheneverpresentationPolicy
requires it, and by default every presentation fails without trust anchors. A status that the policy does not
resolve is reported asTokenStatusValidationResult.Rejectedinstead ofValid. Transaction data can no longer
be skipped throughValidatorSdJwt(verifyTransactionData = false). Annex Cdocumentshold the accepted
documents only: a rejected document no longer failsvalidateAuthnResponse, while a document of a type that
has not been requested still does - Add
AuthnResponseResult.Success.presentationValidations; deprecateAuthnResponseResult.Success.vpTokenResult,
VpTokenValidationResultDCQL.credentialQueryResponseValidations,submissionRequirementsValidationResult, and
presentationResults, andIso180137AnnexCWrapper.documents, in favor ofvalidationReportand
presentationValidations - Deprecate the constructors of
OpenId4VpVerifierandDcApiVerifiertakingverifierand
verifyCoseSignature, which no longer take effect, and their propertyverifier; the new primary
constructors omit both. DeprecateNonceChallengeVerifier.verifier, its constructor taking it, and the
presentation methods ofChallengeSession, in favor ofCredentialValidatorwithChallengeSession.context() MapStore.removeis documented to be atomic: of concurrent calls for one key, at most one returns the value, as
DefaultMapStoredoes; custom stores passed to the verifiers need to guarantee this, so that a request is
answered at most once
- Add
- OpenID data classes:
- BREAKING:
VerifierInfo.datais aVerifierInfo.DataofStringDataorObjectDatainstead ofString, as OpenID4VP 1.0 allows a string or a JSON object.VerifierInfocan still be created with aString, or with aJsonObject - Moved
Base64URLTransactionDataSerializerto openid-data-classes - BREAKING: Make
IssuerMetadata.supportedCredentialConfigurationsnon-null without a default, as
credential_configurations_supportedis REQUIRED in OID4VCI; issuer metadata without it now fails to deserialize
- BREAKING:
- ETSI data classes:
- BREAKING: Align LoTE digital-identity JSON member names and representations with TS 119 602 Annex A.1:
PublicKeyValueis aJsonWebKey, whileSubjectKeyIdentifierandOtherIdare strings; absent
x509Certificatesis nownull. Other-list pointers carryLoTEQualifierscontainingLoTEQualifierobjects, including a required MIME type - BREAKING: Policy and legal-notice arrays must use one alternative consistently. Open service, trusted-entity and
associated-body extensions preserve their JSONcontent - Accept a plain string as
LoTELegalNotice, as the Annex A.1 JSON schema declares and published lists use it,
in the newPolicyOrLegalNoticeItem.legalNoticeText - Allow an explicitly null
NextUpdatefor closed lists while keeping that member required; accept optional
certificateencodingandspecRefmetadata without discarding the certificate - Reject empty required arrays, empty digital identities, null certificate wire entries, missing scheme-operator
and associated-body website contacts, and timestamps without a four-digit year. Trusted-entity contacts need not
contain both an email address and a website, as published lists contain only one of them. Other
ListAndSchemeInformationrequirements remain unchanged - BREAKING:
WrpClaim.pathis a claims path pointer ofWrpClaimPathSegments (a name,nullfor all elements
of an array, or an index), andWrpClaim.valuesa list ofWrpClaimValues (a string, integer, or boolean), as
ETSI TS 119 475 V1.2.1, B.2.10 refers to OpenID4VP for them;WrpClaim.named(...)creates a path of names. A
WRPRC withnullor an index in a path could not be parsed before. Registered paths authorize requested claims
by coverage: a registered ancestor covers descendants, andnullcovers an index ornull. JSON and CWT
decode typed segments and values; empty paths and
indices aboveUInt.MAX_VALUEare rejected. kotlinx-serialization CBOR also reads an empty map asnull - BREAKING: Align
WrpPayloadwith the multiplicities of ETSI TS 119 475 V1.2.1, Annex B and Tables 7 to 10:
policyIdhas no default, so it is always encoded and a WRPRC withoutpolicy_idno longer passes as
carrying the WRPRC policy (6.1.3);supportUriandsupervisoryAuthorityare required,infoUriis
optional;purpose,credentialsandprovidesAttestationsarenullwhen absent, as only service
providers or attestation providers carry them;WrpCredential.claimisnullwhen absent;
WrpIntermediary.subis required. Emptysrv_description,entitlements,purpose,credentials,
claimand claimvaluesare rejected. AddWrpConstants.ENTITLEMENTS(A.2) - BREAKING: Model the WRPRC fields for which the register allows several values (ETSI TS 119 475 V1.2.1,
Annex B) as lists:WrpPayload.subjectLegalNames,infoUris,supportUris, and
WrpSupervisoryAuthority.emails,phones,uris. They are always encoded as arrays, and also decoded from a
single string, as in Annex C, in JSON and CWT; empty arrays are rejected. DeprecatesubjectLegalName,
infoUri,supportUri,email,phone, anduri, which return the first entry.privacy_policystays a
single string, since Table 7 maps it from the singlepolicyURIof a Policy (B.2.8) - WRPRC validation no longer requires
name(B.2.1), but requires an entitlement of A.2 (GEN-5.2.4-03),
provides_attestationsonly for attestation providers (B.2.1), andpurposefor presentation (GEN-5.2.4-06) - WRPRC linkage also accepts the WRPAC of the intermediary named in
intermediary.sub, who presents the WRPRC of
the relying party it acts for (ETSI TS 119 475 V1.2.1, 4.5, Table 10), for requests and issuer metadata;
act.subof GEN-5.2.4-09 contradicts Table 10 and is not supported. AddValidatedRelyingParty.intermediated
andValidatedCredentialIssuer.intermediated, telling whether the WRPAC is the one of that intermediary - An ISO mdoc request whose WRPRCs identify different relying parties fails
registration, as the WRPAC of an
intermediary is associated to one relying party (CIR (EU) 2025/848, Annex I, point 16, as amended by CIR (EU)
2026/1730) - BREAKING: Remove
DocRequestInfo.euWrpRegistrarInfoand the classesWrpRegistrarInfoandWrpIdentifier:
CIR (EU) 2026/1731, Annex XII voids the registrar information of ETSI TS 119 472-2 V1.2.1 (ISO/IEC
18013-REQ-08 to -11), so an ISO mdoc request carries onlyeuWrprc - BREAKING: Model
srv_descriptionasWrpPayload.serviceDescription, the localized versions of the description
of the one service a WRPRC is issued for (ARF Annex 2, RPRC_09), encoded as an array ofWrpLangStrings; it is
also decoded from an array of arrays (ETSI TS 119 475 V1.2.1, B.2.1, Annex C), in JSON and CWT, into the
descriptions of all its arrays; arrays mixed with objects are rejected. DeprecatesrvDescription, which returns
serviceDescriptionas the only array
- BREAKING: Align LoTE digital-identity JSON member names and representations with TS 119 602 Annex A.1:
- ISO mdoc data classes:
- Encode issuer-signed items with the bytes they were decoded from (
ByteStringWrapper.serialized) instead of
re-encoding their value, so items an issuer encoded differently, e.g. in another key order like the EUDI
reference issuer, keep matching their digests (ISO/IEC 18013-5:2021, 9.1.2.5).serializedof items from
IssuerSignedList.fromIssuerSignedItemsholds the encodedIssuerSignedItemlike a decoded item, no longer the
tag-24-wrappedIssuerSignedItemBytes - Add
IssuerSigned.fromIssuerSignedLists(...)andStoreEntry.Iso.discloseIssuedItem(...); mdoc presentations
disclose items with the bytes they were issued with - Serialize
DocRequest.itemsRequestandDeviceRequest.deviceRequestInfoagain with the bytes they were decoded
from (ByteStringWrapper.serialized) instead of encoding their value anew, using the new
ReserializableByteStringSerializer, asreaderAuthandreaderAuthAllsign exactly these bytes
(ISO/IEC 18013-5:2021, 9.1.4). A request serialized again, e.g. to pass it on within a wallet, no longer verified
if its verifier encoded it in another key order, e.g.systemSpecsbeforezkRequiredin azkRequest - BREAKING:
SessionTranscriptis a sealed interface with one subtype per handover:SessionTranscript.Qrand
SessionTranscript.Nfc(bothSessionTranscript.Proximity, withdeviceEngagementBytesand
eReaderKeyBytes),SessionTranscript.DcApi, andSessionTranscript.OpenId4Vp, each with itshandover. Its
newSessionTranscriptSerializerdecodes every kind of handover by its shape, also an NFC transcript, which could
not be decoded before, and re-encodes the canonical encoding to the same bytes. The properties
deviceEngagementBytesOid,eReaderKeyBytesOid,nfcHandover,dcapiHandover, andopenId4VpHandoverare
removed; deprecateSessionTranscript.forQr,forNfc,forDcApi, andforOpenIdin favor of the constructors
of the subtypes. The encoding is unchanged
- Encode issuer-signed items with the bytes they were decoded from (
- CSC data classes:
- BREAKING: Split CSC API into a new
apisubfolder and add thedatamodelpackage - Implement CSC Data Model 1.0.0
- BREAKING: Split CSC API into a new
- Java interop:
- Java could not instantiate data classes whose constructor takes a
kotlin.time.Durationor an
unsigned integer, because the Kotlin compiler hides such constructors from Java. Add Java-safe constructors, and
accessors for the hidden properties, taking durations in seconds (…Seconds) or milliseconds (…Milliseconds), and
unsigned integers asLong(…AsLong, values outside ofUIntare rejected):KeyAttestationRequired(preferredTtlSeconds),TokenResponseParameters(expiresSeconds,
intervalSeconds),CredentialResponseParameters(intervalSeconds),PushedAuthenticationResponseParameters
(expiresSeconds), and the accessorIssuerMetadata.preferredClientStatusPeriodSecondsValidationPolicyandTimeScope(timeLeewayMilliseconds), whose Java constructors take the leeway at a
different position to keep their JVM signatures distinct- CSC
KeyParameters(lenAsLong) andCredentialInfo(multisignAsLong) EuPidCredential(sexAsLong) andMobileDrivingLicence(heightAsLong,weightAsLong,ageInYearsAsLong,
ageBirthYearAsLong)
- Java could not instantiate data classes whose constructor takes a
- ISO mDoc Zero-Knowledge Proofs:
- In
IsoMdocZkBackend.generate()add atimestampto pass as the creation timestamp of the proof
- In