# Changelog Release 9.0.0: 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 `HttpExchange`s for any HTTP stack, with the Ktor clients built on them, and servers return `PreparedHttpResponse`s 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-classes` with its compatibility surface, and others - Validation (package `at.asitplus.wallet.lib.validation` in `vck`): - Add `CredentialValidator`, validating credentials (`CredentialValidationInput`) and presentations (`PresentationValidationInput` in a `PresentationContext`) under a `ValidationPolicy` (`TrustPolicy`, `StatusPolicy`, time leeway, `requireTimeliness`), returning a `ValidationReport`: a tree of checks (`CheckOutcome`: passed, failed with its cause, blocked, or not applicable) whose `decision` follows 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 a `StatusValidator` and a `StatusListTokenResolver`, 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 new `PresentationContext.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 `credentialValidator` of `HolderAgent`, which validates every credential before storing it, and orders stored credentials in `getValidCredentialsByPriority()`, demoting only verified problems. Deprecate the constructor taking `validator`, `validatorVcJws`, `validatorSdJwt`, `validatorMdoc` (which no longer take effect), and `trustedIssuers` (adapted as anchors for every credential type) - Add the parameter `orderingValidator` of `HolderAgent`, `credentialValidator` by default, which orders stored credentials when matching them against a presentation request, e.g. with a status list token resolver that returns cached tokens only, so that matching and creating a presentation never wait for status lists to be retrieved, which they did for every stored credential, also those the request does not ask for - Add `OpenId4VciClient.decodeCredentialResponse(response, request, representation)`, decoding every credential of a (batch) response into a `ReceivedCredential`, also one that can not be decoded, and `ReceivedCredential.prepareCredentialStorage(holder, scheme, policy, renewalInfo)`, validating it for storage by the `HolderAgent`, which reports a credential that can not be decoded as rejected - Add `ValidationPolicy.requiredClaims` (`RequiredClaimsPolicy.Skip` by default, or `RequireMandatory`) and the check `CredentialChecks.requiredClaims` (`RequiredClaimsValidation`, failing with `MissingRequiredClaimException`): whether a credential received on its own contains every claim the metadata of its signed type describe as `mandatory` (draft-ietf-oauth-sd-jwt-vc-19, 5.6.3), resolved by the new `requiredClaimsResolver` of `CredentialValidator` (`RequiredClaimsResolver.Default` uses the credential metadata registries and schemes of `AttributeIndex`, and blocks the check for a type without metadata). Not applicable to presented credentials - Add `CredentialValidationResult.unacceptedCredential` and `ValidationReport.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 a `PreparedCredentialStorage` to keep while the user looks at the report, then `HolderAgent.storeAccepted(prepared)`, storing only an accepted credential, as `storeCredentialWithReport` does, or `HolderAgent.storeAfterUserConsent(prepared)`, storing also a rejected but intact credential. `CredentialStorageResult.storedEntry` may 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.credential` is `null` for a credential prepared from its encoding that is not intact - Add `RelyingPartyValidator` with `RelyingPartyTrustAnchors` (WRPAC and WRPRC anchors, status list signers of WRPRCs, `PreRegisteredClients`, `VerifierAttesters`, and `ClientIdentifierTrust` for 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)` in `vck-openid`. An unregistered request fails with an `UnregisteredRequestException`. `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 several `readerAuthAll` signatures, 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 fails `requestData` with an `UnsupportedWrpRequestException`, while its `signature` passes - Add `ValidationPolicy.registration` (`RegistrationPolicy.Require` by default, or `ValidateIfPresent`): 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, `registration` is not applicable, the reader certificate need not carry the identifier of a WRPAC, and `ValidatedRelyingParty.identifier` is `null` - Add `ReaderAuthenticationVerifier.authenticate(request, transcript)`, returning an `AuthenticatedReader` with the chain of every `readerAuthAll` signature that verifies, and the document requests without reader authentication (ISO/IEC DIS 18013-5:2025, 12.5.4). Deprecate its `invoke`, which returns only the first chain. A `readerAuthAll` that does not verify fails as such, instead of falling back to the `readerAuth` of document requests - Add `AuthorizationRequestValidator.validate(request, policy)`, and `OpenId4VpHolder.startAuthorizationResponsePreparation(input, policy)` and its `DcApiHolder` overload, 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 in `OpenId4VpKtorHolder` `startAuthorizationResponsePreparation(input, policy)`, its overload for parsed requests, and `prepareDcApiRequest(request, policy)`. `AuthorizationRequestValidation.relyingParty` and `DcApiRequestValidation.relyingParty` carry the `RelyingPartyValidationResult` of a request with a WRPRC, or of every Annex C request, validated like a proximity request, whose report is `DcApiRequestValidation.validationReport`, and `RelyingPartyValidationResult.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 deprecated `WrpAuthenticationRequestValidator`. `OpenId4VpHolder`, `OpenId4VpKtorHolder`, and `DcApiHolder` take a `relyingPartyValidator`; deprecate `RelyingPartyTrust` and the constructors taking `relyingPartyTrust` (mapped by `toRelyingPartyValidator()`; `RelyingPartyTrust.Custom` no longer takes effect) or the unused `keyMaterial` - Behavior changes, also through the legacy entry points, which delegate to the new validation: - With `requireTimeliness` and a status policy other than `Skip`, 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 `jwk` next to an `x5c` with a different key fails the signature - SD-JWT: `typ` `dc+sd-jwt` and `kb+jwt` are enforced, `vc+sd-jwt` is rejected; unreferenced, duplicate, or malformed disclosures and claim collisions fail (RFC 9901, 7.1); the KB-JWT `iat` has to be within the leeway - mdoc: storage checks item digests, the `docType` against the scheme, and the device key against the holder key; a missing MSO fails - VP-JWT: `exp` and `nbf` are 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), regardless of the certificates transported with it. An anchor transported as the last certificate of the chain is ignored, as is the case for `requireTrustedSigningCertificate`. 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 needs `alg` ES256 or to lack `kid`, 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: the `signature` and `parsing` of the WRPAC are not applicable, `requestData` fails with an `UnsupportedWrpRequestException`, and their WRPRCs are not evaluated, while a reader authentication that does not verify fails the `signature`, so that a wallet can tell an unauthenticated reader from a forged one - A relying party reports its WRPRCs in the new named check `registration` of `RelyingPartyChecks`, required unless `ValidationPolicy.registration` is `ValidateIfPresent`: a missing WRPRC (`MissingRegistrationCertificateException`) or one that can not be parsed (`InvalidRegistrationCertificateException`) fails `registration` instead of `requestData`, which keeps the extraction of the request, e.g. its `client_id` and DCQL query, and fails for unsigned requests, whose `registration` is blocked - Deprecate the superseded validation API, to be removed in the next major release: `Validator`, `ValidatorVcJws`, `ValidatorSdJwt`, `ValidatorMdoc`, `VerifierAgent`, and the presentation methods of `Verifier` (use `CredentialValidator`); `VcJwsInputValidator`, `SdJwtInputValidator`, `MdocInputValidator` and their results, `CredentialTimelinessValidator`, `CredentialFreshnessSummary`, `CredentialWrapper`, and `ValidatiorVcJwsValidationException` (see the `timeliness` and `status` checks of the report); `TokenStatusResolver`, `TokenStatusResolverImpl`, `TokenStatusResolverNoop`, `toTokenStatusResolver()`, `TokenStatusResolver.resolve()`, `TokenStatusValidator` with its functions, and `TokenStatusValidationResult` (use `StatusValidator` with a `StatusListTokenResolver`, which stays); `issuerJwsVerifier`, `issuerCoseVerifier`, and `VerifyStatusListTokenHAIP` (use trust anchors of `CredentialValidator`); `WrpacValidator`, `WrprcValidator`, `WrpAuthenticationRequestValidator`, `WrpChainValidator`, their results, and `IsoMdocDcApi.validateWrpAuthenticationRequest()` (use `RelyingPartyValidator`) - BREAKING: `VcJwsTimelinessValidator`, `SdJwtTimelinessValidator`, `MdocTimelinessValidator`, and `MobileSecurityObjectTimelinessValidator` are objects, as they hold no state and take the time per call (`TimeScope`): call `VcJwsTimelinessValidator(vcJws, timeScope)` instead of `VcJwsTimelinessValidator()(vcJws, timeScope)`; `MdocTimelinessValidator` no longer takes a `MobileSecurityObjectTimelinessValidator` - Add `ValidationReport.namedChecks`, the checks of a report by name with the `Requirement` its policy put on each (`NamedCheck.satisfied` tells 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 its `TrustValidation`, `timeliness` is always the validity of the artifact (the time of a holder proof is `proofTime` or `popTime`), and status parts start with `status.` - Make `CheckOutcome.cause()` and `ValidationReport.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)`, and `ValidationReport.credential(checks, policy, evaluatedAt)` no longer take a `HolderBindingPolicy`. In a presentation, the presentation decides the holder binding of its credentials; `HolderAgent.storeCredentialWithReport` and `HolderAgent.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`, and `ValidatedPresentation.credentials`, e.g. to display the claims of accepted presentations - `ValidatedPresentation.SdJwt.keyBinding` is the signed key binding JWT (`JwsCompactTyped`) instead of its payload, and `ValidatedPresentation.SdJwt.sdJwtSigned` is the SD-JWT as presented, with that key binding JWT. `VerifyPresentationResult.SuccessSdJwt.sdJwtSigned` is the latter, so it contains no key binding JWT the request did not require, as such a key binding JWT is not checked - `StatusValidation` requires mechanisms for a passed `agreement` - 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, `pathLenConstraint` or `keyUsage`, 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 - Subject credential store: - BREAKING: Implementations of `SubjectCredentialStore` implement the single `storeCredential(entry: StoreEntry)`, persisting a ready `StoreEntry`. `HolderAgent` stores every credential through it, with its renewal info. - Deprecate the per-format `storeCredential(vc, ...)`, `storeCredential(vc, ..., disclosures, ...)`, and `storeCredential(issuerSigned, ...)` of `SubjectCredentialStore`, which now build the `StoreEntry` and call `storeCredential(entry)`: implementations no longer need to override them - Add `StoreEntry.fromPreparedCredential(prepared)`, the entry of a `PreparedCredentialStorage` built from what its validation verified, also for a rejected but intact credential, and `StoreEntry.Vc.fromString(...)`, `StoreEntry.SdJwt.fromString(...)`, and `StoreEntry.Iso.fromString(...)` (base64url as in OpenID4VCI, or base64), parsing a serialized credential without validating it - Trusted relying parties: - Fix JSON serialization of `JsonClaimReference` as `SingleClaimReference`, e.g. in `RequestDataValidity`, which produced invalid JSON since `NormalizedJsonPath` became a list in JsonPath4K 4.0.0. It is now serialized as an object holding `normalizedJsonPath` - BREAKING: `JsonClaimReference` now wraps `path: DCQLClaimsPathPointer`, preserving wildcards during WRPRC authorization. The concrete-path constructor is retained; deprecate `normalizedJsonPath`, which cannot represent wildcards. Its JSON member name remains `normalizedJsonPath` - WRPRC authorization exempts only the root `vct` claim; nested claims named `vct` must be registered - `WrprcValidator` no longer drops the cause when a registration certificate can not be validated, e.g. for a missing or unparseable certificate chain: `WrprcValidationResult.certificateValidationResults` holds a `KmmResult` for each certificate. Deprecate `WrprcValidationResult.certificateValidation`, which maps such certificates to `null` - `WrprcValidator` validates each credential request on its own, so a request that can not be validated (e.g. a DCQL claims path with a `null` segment) no longer fails the whole validation: `WrprcValidationResult.requestDataValidationResults` holds a `KmmResult` for each request. Deprecate `WrprcValidationResult.requestDataValidation`, which maps such requests to an invalid credential type without attributes, `WrprcValidator.validateRequest()` in favor of `validateCredentialRequests()`, and the `WrprcValidationResult` constructor taking the old types - `WrpAuthenticationRequestValidator` tells apart why it can not extract a WRPRC, with exceptions extending `IllegalArgumentException`: `MissingRegistrationCertificateException` if the request contains none, `InvalidRegistrationCertificateException` with the parsing error as cause if it can not be parsed, and `UnsupportedWrpRequestException` for requests it can not validate, e.g. unsigned ones - `WrpAuthenticationRequestValidator` rejects requests with more than one `registration_cert` entry in `verifier_info`, also if only one of them can be parsed, and ISO requests that carry an `euWrprc` in only some of their `DocRequest`s - `WrpAuthenticationRequestValidator` rejects a `registration_cert` entry in `verifier_info` whose data is a JSON object instead of a string - `RelyingPartyValidator`, `IssuerMetadataValidator`, and `WrpAuthenticationRequestValidator` accept a `registration_cert` in `verifier_info` or `issuer_info` given as the base64url encoding of the compact JWS (ETSI TS 119 472-2 V1.1.1, OIDFVP-HAIP_COMMON_RO_REQ-09), which could not be parsed before, as well as the compact JWS itself - 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 - Trust List filtering: - BREAKING: `LoteProfile.fromSchemeIdentifier()` returns `null` for a credential identifier without a known list, instead of `LoteProfile.EAA`, so that `LoTETrustAnchorProvider` no longer authorizes every Pub-EAA provider for arbitrary credential types. Identifiers match case-sensitively, and the mDL `docType` `org.iso.18013.5.1.mDL` matches exactly instead of by prefix - BREAKING: Remove `additionalAnchors` of `LoTETrustAnchorProvider`, 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 a `CredentialTrustScope` instead - 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. - `StatusValidator` keeps what it verified of the last 32 status list tokens, by identity: their media type, signature, and decompressed list, so that validating many credentials referencing one token parses it, verifies its signature, and decompresses it once. Its signer, subject, and time are still checked on every validation. `StatusListCwt.parsedPayload` is decoded once, and an identifier list is indexed once per token instead of encoding every identifier for every lookup - DCQL: - Claim paths preserve numeric object keys as strings and reject invalid or overflowing indices - `DCQLClaimsPathPointer.query` processes 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 or `null` component, the result is empty, i.e. the claim does not match, instead of continuing with the other selected elements; other options of `claim_sets` that do not request the claim may still match - Reject queries that OpenID4VP 1.0 does not allow: empty `values` of a claims query, empty `vct_values`, empty arrays in `type_values` or in the `options` of `credential_sets`, `options` that reference an unknown Credential Query and `claim_sets` that reference an unknown claims query; a claims query that points to the same claim again is ignored when matching - Match the `values` of a claims query against the JSON value of an ISO mdoc data element as OpenID4VP 1.0, 6.3 requires, converted with `CredentialToJsonConverter`, so that a full-date or date-time matches its string, e.g. `"1990-01-01"` for `birth_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 with `CborCredentialSerializer`, e.g. the integer code of `IsoSexEnum`, or else of its registered `JsonValueEncoder`; such elements matched no value before. Add `CredentialToJsonConverter.toJsonElementOrNull`, which returns null instead of the `toString()` of a value of a type without a known encoding. `DCQLCredentialClaimStructure.IsoMdocStructure` holds the JSON values of the data elements (`Map>` instead of `Any`), with `JsonNull` for an element without a JSON value, and `DCQLClaimsQueryResult.IsoMdocResult.claimValue` is that JSON value - `HolderDCQLQueryMatchingResult.toDefaultSubmission` selects 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`, and `X509MetadataQuery` implementing `DCQLCredentialMetadataAndValidityConstraints`. - 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_ids` as alternatives and reject missing, empty, or unknown query references in OpenID4VP transaction data with `invalid_transaction_data` - Add optional `PresentationRequestParameters.qesApprovalTransactionData` for selecting approval data independently of the general SD-JWT transaction-data integrity hashes - Credential schemes: - Add `IsoMdocCredentialScheme.isoQualifiedClaimDescriptions` in `vck`, the claim descriptions of a scheme with their paths qualified by its ISO namespace, as the ISO mdoc credential configurations of `DefaultCredentialSchemeMapper` use - Add `IsoMdocCredentialScheme.isoClaimPathsAreQualified` (default `false`); when `true`, preserve all claim paths without adding the scheme namespace. ISO schemes derived from type metadata set it to `true`: their paths must include their namespace, including when `vck.isoNamespace` is absent or another namespace is used - OpenID for Verifiable Credential Issuance issuer: - BREAKING: `ProofValidator` takes a `KeyAttestationValidator` (in `vck`) instead of `verifyKeyAttestationSignature` and `statusListTokenResolver`: configure it with the anchors of the wallet providers, whose certificate in `x5c` has 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; `validateProofExtractSubjectPublicKeys` delegates to it. A key attestation embedded in a `jwt` proof needs an `exp`, and its optional `nonce` must be a server-provided `c_nonce` (OpenID4VCI 1.0, F.1, D.1), a `jwt` proof at most one of `kid`, `jwk`, and `x5c`; a key attestation no longer needs `user_authentication`, and may contain `iss` (EUDI TS3, 2.3.2) - Fix: `DefaultCredentialSchemeMapper` advertises the `cryptographic_binding_methods_supported` of OpenID4VCI 1.0, 12.2.4 only: `cose_key` for ISO mdoc, which no longer advertises `jwk`, and `jwk` for SD-JWT VC and VC-JWT, which no longer advertise `urn:ietf:params:oauth:jwk-thumbprint`. Deprecate `OpenIdConstants.URN_TYPE_JWK_THUMBPRINT`, which is no binding method - OpenID for Verifiable Credential Issuance client: - Add `WalletService.createCredential(metadata, credentialConfigurationId, ...)` to request exactly one credential by its `credential_configuration_id` - Add `CredentialIssuanceResult.Success.reissuingStoreEntryId`, the stored credential the new ones replace, as passed to `startProvisioningWithAuthRequestReturningResult` and kept in the `ProvisioningContext` - Add `CredentialIssuanceResult.Success.prepareCredentialStorage(holder, policy)`, validating the received credentials with their renewal info by `HolderAgent.prepareCredentialStorage`, for the wallet to show the reports and store them with `HolderAgent.storeAccepted`, or `HolderAgent.storeAfterUserConsent` after 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.Success` holds the credentials `issued` for each requested credential configuration as `IssuedCredentials`: the `receivedCredentials` (`ReceivedCredential`), not validated yet, the `scheme` they have been requested as, and their renewal info `refreshToken`, instead of `credentials` and `refreshToken`. Deprecate `credentials`, which decodes the credentials of all configurations without validating them, and fails for a credential that can not be decoded, and `refreshToken`, 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 returning `LoadedIssuerMetadata`: `Signed` with the `IssuerMetadataValidation` of the new constructor parameter `issuerMetadataValidator`, also if rejected, or `Unsigned`, which the wallet may reject, e.g. as the EUDI Wallet ecosystem requires signed metadata - Add `CredentialIdentifierInfo.issuer`, the `ValidatedCredentialIssuer` of signed metadata, which is now serializable, so that it is kept in `ProvisioningContext` during the authorization in the browser; add `parseCredentialMetadata(loaded: LoadedIssuerMetadata)`, setting it, and failing for rejected signed metadata - `loadIssuerMetadata(credentialIssuer)` and `loadCredentialMetadata(host)` ask for `application/json`, and fail for metadata whose `credential_issuer` is not the requested identifier, whose data "MUST NOT be used" (OpenID4VCI 1.0, 12.2.4) - Add `IssuerMetadataValidator` in `vck`, 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 in `x5c` (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_info` holding 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 a `ValidatedCredentialIssuer` - Add `IssuerMetadataValidation.untrustedMetadata` and `LoadedIssuerMetadata.Signed.untrustedMetadata`, the metadata whatever the decision if their signature and subject passed, and `parseCredentialMetadataIgnoringTrust(loaded)` in `OpenId4VciProtocolClient` and `OpenId4VciKtorClient`, 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 - `IssuerMetadataValidator` keeps the requirements of the EUDI Wallet ecosystem out of `parsing` and `signature`: the signature is verified with the key the metadata assert, the `x5c` leaf or a `jwk`; metadata without a WRPAC in `x5c`, or with one carrying no identifier, fail `trust`; `issuer_info` is checked by the new named check `registration` of `IssuerMetadataChecks`, required by every policy, which fails without exactly one `registration_cert`. `IssuerMetadataValidation.untrustedRegistration`, also on `LoadedIssuerMetadata.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 an `issuer` - Add `IssuerMetadata.issuerInfo` with `IssuerInfo`, whose `data` is a JSON element, and `OpenIdConstants.IssuerInfo` - Add `OpenId4VciServer.issuerInfo`, served in the signed metadata - Renamed `WalletService` (in `vck-openid`) to `OpenId4VciClient` - Renamed `OpenId4VciClient` (in `vck-openid-ktor`) to `OpenId4VciKtorClient` - Renamed `CredentialIssuer` (in `vck-openid`) to `OpenId4VciServer` - Add `OpenId4VciProtocolClient` in `vck-openid`, returning `HttpExchange`s for the requests to the credential issuer (metadata, nonce, credential), built on `OAuth2ProtocolClient`, which handles DPoP for the credential issuer with the credential issuer's own nonces. `credentialRequest` decodes the response into `ReceivedCredential`s, see `OpenId4VciClient.decodeCredentialResponse` - Move `CredentialIdentifierInfo` to `vck-openid` (`at.asitplus.wallet.lib.oidvci`), deprecate the typealias left in `vck-openid-ktor`; the serialized form is unchanged - `OpenId4VciKtorClient` sends the requests of `OpenId4VciProtocolClient` and `OAuth2ProtocolClient` in 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.loadCredentialOffer` and `OpenId4VciClient.loadCredentialOffer` to load a credential offer passed by value or by reference; the resource at `credential_offer_uri` must 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 `remoteResourceRetriever` of `OpenId4VciClient`; that constructor parameter is only used by the deprecated method - Deprecate `OpenId4VciClient.parseCredentialResponse`, which decodes credentials without validating them: use `decodeCredentialResponse` and `ReceivedCredential.prepareCredentialStorage`. The overloads without the originating `CredentialRequest` also accept a plain response even if the request asked for an encrypted one - Add `OpenId4VciProtocolClient.createCredential`, whose proof JWTs name the `client_id` of its `OAuth2Client` as the issuer (OID4VCI 1.0 Appendix F.1); `OpenId4VciClient.createCredential` takes it as the new parameter `clientId` - Deprecate `OpenId4VciClient.clientId`, which only remains the default for `OpenId4VciClient.createCredential`; the `client_id` belongs to `OAuth2Client` - Add `ProofCreator`, creating the `proofs` of a credential request of one proof type (OpenID4VCI 1.0, Appendix F), with `JwtProofCreator` (`jwt`, F.1, signed with its `keyMaterial`, with a key attestation if the issuer requires one) and `AttestationProofCreator` (`attestation`, F.3). `OpenId4VciClient` takes `proofCreators` and 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 lists `proof_types_supported`, as `proofs` are required then (8.2) - Deprecate the constructor of `OpenId4VciClient` taking `keyMaterial`, `loadKeyAttestation`, `selectProofJwtKeyBinding`, and `clock`, which creates a `JwtProofCreator` of them, followed by an `AttestationProofCreator` if `loadKeyAttestation` is set, as before - Extend `KeyAttestationInput` to contain the requirements of the issuer as `KeyAttestationRequired` from issuer metadata, and deprecate `KeyAttestationInput.preferredKeyStorageStatusPeriod`, use `requirements?.preferredTtl` instead - Add `OpenId4VciClient.prepareCredentialRequests(tokenResponse, metadata, credentialFormats, ...)`, preparing one `PreparedCredentialRequest` per credential the access token grants, each with the configuration of its `credential_configuration_id` (OpenID4VCI 1.0, 5.1.1), or of its `scope`, 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 with `createCredential(request, metadata, clientNonce, ...)` right before sending it, with a proof for its own configuration and its own `c_nonce`, so that credentials of different formats can be requested with one access token, also from a credential issuer that accepts each `c_nonce` only once - Fix: `OpenId4VciClient.createCredential(tokenResponse, metadata, credentialFormat, ...)` creates requests only for the credentials of `credentialFormat` the token grants, instead of for every credential it grants with a proof for `credentialFormat`, and fails if it grants none of them. Authorization details of the token response without a `credential_configuration_id`, which is REQUIRED (5.1.1), are rejected - Add overloads of `OpenId4VciKtorClient.startProvisioningWithAuthRequestReturningResult` and `OpenId4VciKtorClient.loadCredentialWithOfferReturningResult` taking several `CredentialIdentifierInfo`s 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 with `scope` if every one of them has one, else with `authorization_details` (OpenID4VCI 1.0, 12.2.4). `ProvisioningContext.credentials` keeps them during the authorization in the browser; a context stored by an earlier version requests its `credential` - Fix: `OpenId4VciKtorClient` requests a fresh `c_nonce` for 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 with `invalid_nonce` (8.3.1.2), see `OpenId4VciProtocolClient.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 `OpenId4VciKtorClient` name the `client_id` of the OAuth 2.0 client that requested the token, instead of the one of `OpenId4VciClient`, which differed when the OAuth 2.0 client was passed separately - Add a constructor to `OpenId4VciKtorClient` taking the app's ktor `HttpClient`, the `OAuth2Client` (the only source of the client ID), the `OpenId4VciClient` and a `ClientAttestation`, so that each value is passed once. Deprecate the constructor taking `engine`, `cookiesStorage`, `httpClientConfig` and an `OAuth2KtorClient`, whose HTTP configuration it silently preferred over its own - OAuth 2.0 authorization server: - BREAKING: `AttestationBasedClientAuthenticationService` takes a `ClientAttestationValidator` (in `vck`) instead of `verifyJwsObject`: configure it with the anchors of the wallet providers, which have to issue the certificate in `x5c` signing 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; `authenticateClient` delegates to it. The challenge is consumed only if the client attestation is accepted, `client_status.status` is resolved if a status list token resolver is configured, and an expired `client_status` fails with `use_fresh_attestation` instead of `invalid_client`. A client attestation no longer needs `iat`, a client attestation PoP needs `aud` also without `issuerIdentifier` (draft-ietf-oauth-attestation-based-client-auth-10, 5.1, 5.2) - Fix: A pushed authorization request passing a request object in `request` is rejected, if the request object's `client_id` differs from the `client_id` parameter (RFC 9101, 6.3) - OAuth 2.0 client: - Add `OAuth2ProtocolClient` in `vck-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 an `HttpExchange`, whose requests (`ProtocolRequest`) callers send with any HTTP stack. `ProtocolRequest.Retryable` marks the requests that are sent again when the server asks for a DPoP nonce or an attestation challenge, with their `attempt`. The `PreparedHttpRequest` of each request carries its `body` as `HttpBody`, with the `contentType` to send as `Content-Type`; its `headers` never contain `Content-Type` - `OAuth2KtorClient` sends the requests of `OAuth2ProtocolClient`, keeping its API - Move `TokenResponseWithDpopNonce`, `LoadInstanceAttestationInput` and `OpenUrlForAuthnRequest` to `vck-openid` (`at.asitplus.wallet.lib.oauth2`, the latter two nested in `OAuth2ProtocolClient`), deprecate the typealiases left in `vck-openid-ktor` - Deprecate `OAuth2KtorClient.callTokenIntrospection` with the parameters `token` (never used) and `retryCount` (now ignored), replace with the overload without them - Fix: A retried token introspection request passes `issuerMetadata` to `loadInstanceAttestation` - `RemoteOAuth2AuthorizationServerAdapter` loads 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 response - `OAuth2ProtocolClient` keeps 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.applyToken` falls back to the latest nonce of the resource server at that origin - Add `ClientAttestation` in `vck-openid` (`at.asitplus.wallet.lib.oauth2`), holding the instance attestation loader together with the key it attests; `OAuth2ProtocolClient` takes 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 `OAuth2KtorClient` and `RemoteOAuth2AuthorizationServerAdapter` taking the app's ktor `HttpClient`, whose copy without following redirects sends the requests, and a `ClientAttestation`; the DPoP key defaults to the attested key, so that refresh tokens remain usable after the app restarts. Deprecate the constructors taking `engine`, `cookiesStorage` and `httpClientConfig` - Add `OAuth2KtorClient.clientAttestation`, deprecate `OAuth2KtorClient.loadInstanceAttestation` - Form-encoded requests (PAR, token, token introspection) are sent as `application/x-www-form-urlencoded` without `charset` in the `Content-Type`, as the media type defines no such parameter - Change: `BuildDPoPHeader`/`BuildClientAttestationJwt`/`BuildClientAttestationPoPJwt` get a parameter `issuedAt` that is set without applying the clock skew, so callers need to set it on their own - Token introspection with JWT responses (RFC 9701): - BREAKING: Remove `TokenIntrospectionRequest.responseFormat` and `TokenIntrospectionRequest.ResponseFormat`, as no specification defines the form parameter `response_format`: a JWT response is requested with the header `Accept: application/token-introspection+jwt` (RFC 9701, 4) - BREAKING: `TokenIntrospectionJwtResponse` holds the signed JWT (`JwsCompactTyped`) 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 claims `iss`, `aud`, `iat` and `token_introspection` that RFC 9701, 5 requires, instead of the members of the introspection response at the top level of the JWT - BREAKING: The constructor parameter `signIntrospectionJwt` of `SimpleAuthorizationService` signs a `TokenIntrospectionJwtPayload` - `SimpleAuthorizationService.tokenIntrospection` answers with a JWT when the `Accept` header of the request asks for `application/token-introspection+jwt`, with the authorization server as `iss` and the authenticated client, i.e. the resource server, as `aud`; without client authentication it refuses such requests with `invalid_client` (status 400, RFC 9701, 5) - BREAKING: `verifyTokenIntrospectionJwt` of `OAuth2ProtocolClient`, `OAuth2KtorClient` and `RemoteOAuth2AuthorizationServerAdapter` is a `VerifyJwsObjectFun?`, `null` by default. When set, token introspection requests JWT responses, and accepts only those with `typ` `token-introspection+jwt`, a verified signature, the authorization server as `iss` and the `client_id` of the client as `aud`; when `null`, 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.audience` is a `Set?`, as `aud` may 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 a `Set`, and the client accepts JWT responses that list its `client_id` in `aud`. Add `JwtAudienceSerializer`, which decodes both forms and encodes a single audience as string - Fix: `RemoteOAuth2AuthorizationServerAdapter.getTokenInfo` sends `token_type_hint` `access_token`, instead of the scheme of the `Authorization` header (`DPoP`, `Bearer`), or the token itself for a header without scheme, which are no token type hints (RFC 7009, 4.1.2). Add `OpenIdConstants.TOKEN_TYPE_HINT_ACCESS_TOKEN` and `TOKEN_TYPE_HINT_REFRESH_TOKEN` - Add `TokenIntrospectionResponse.confirmationClaim` (`cnf`) and `TokenInfo.confirmationClaim`: `SimpleAuthorizationService.tokenIntrospection` conveys the binding of a DPoP-bound access token as `cnf` with the JWK SHA-256 thumbprint in `jkt`, and `token_type` `DPoP` (RFC 9449, 6.2), and `RemoteOAuth2AuthorizationServerAdapter.getTokenInfo` passes 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) - HTTP error handling: - Add `HttpErrorResponseException` and `ProblemDetails` in `vck-openid` (package `at.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 carries `status` and `headers` instead of a ktor `HttpResponse` - Add `OAuth2Error?.dpopNonce(Headers)` and `OAuth2Error?.attestationChallenge(Headers)` in `vck-openid` - BREAKING: `HttpErrorResponseException` in `vck-openid-ktor` is a deprecated typealias of the new class, and no longer a ktor `ResponseException`, so catching either name catches every error response, also of exchanges sent without ktor. Its `response` and the constructors taking an `HttpResponse` are removed, use `status`, `headers` and `responseBody`; it is still an `IllegalStateException` - Deprecate the typealiases `HttpErrorResponseException` and `ProblemDetails` in `vck-openid-ktor`, replace with the classes from `vck-openid` - Deprecate `HttpErrorResponseException.dpopNonce()` and `HttpErrorResponseException.attestationChallenge()` in `vck-openid-ktor`, replace with `oauth2Error.dpopNonce(headers)` and `oauth2Error.attestationChallenge(headers)` - Server-side HTTP responses: - Add `PreparedHttpResponse` in `vck-openid` (package `at.asitplus.wallet.lib`), a response with status, headers and encoded body, to be written out unchanged with any HTTP server stack. The `body` is an `HttpBody`, with the `contentType` to write out as `Content-Type`; the `headers` never contain `Content-Type` - Add `OAuth2Exception.toHttpResponse()` for errors of authorization server endpoints: status 400, with `DPoP-Nonce` for `use_dpop_nonce` and `OAuth-Client-Attestation-Challenge` for `use_attestation_challenge` - Add `OAuth2Exception.toResourceServerHttpResponse(authorizationHeader)` for errors of endpoints accessed with an access token (credential, userinfo): status 401 with `WWW-Authenticate` for `invalid_token`, `invalid_dpop_proof` and `use_dpop_nonce` (the latter with `DPoP-Nonce`), as RFC 6750 3. and RFC 9449 7.1 and 9. require, else 400; `use_attestation_challenge` comes with `OAuth-Client-Attestation-Challenge` here, too - Add `toHttpResponse()` for the results of the authorization server: `OAuth2AuthorizationServerMetadata`, `AttestationChallengeResponse` (with `Cache-Control: no-store`), `ResponseWithDpopNonce` of PAR (status 201), token (with `Cache-Control: no-store` and `Pragma: no-cache`) and userinfo (each with `DPoP-Nonce`), `AuthenticationResponseResult.Redirect` (302 with `Location`) and `TokenIntrospectionResult` - Add `ResponseWithDpopNonce.attestationChallenge`: `SimpleAuthorizationService.parWithDpopNonce` and `tokenWithDpopNonce` provide a fresh attestation challenge with attestation-based client authentication, which `toHttpResponse()` sends as `OAuth-Client-Attestation-Challenge` (attestation-based client authentication, draft 10, 6.2); `OAuth2ProtocolClient` uses it for the next request, instead of fetching one from the challenge endpoint - Add `OpenId4VciServer.metadataHttpResponse(acceptHeader)`, serving `signedMetadata()` as `application/jwt` when the `Accept` header lists `application/jwt` with a quality not below the one of `application/json`, else `metadata` as `application/json` (OID4VCI 1.0, 12.2.2), with `Vary: Accept` - Add `toHttpResponse()` for `OpenId4VciServer.Nonce` (with `Cache-Control: no-store` and `DPoP-Nonce`) and `OpenId4VciServer.CredentialResponse` (`application/json`, or `application/jwt` when encrypted, each with `Cache-Control: no-store`); errors of the credential endpoint convert with `toResourceServerHttpResponse(authorizationHeader)` - Add `CreatedRequest.loadRequestObjectHttpResponse(params)` in `vck-openid`, serving the request object at the verifier's `request_uri` as `application/oauth-authz-req+jwt` (OpenID4VP 1.0, 5.10.1) - Add `directPostHttpResponse(redirectUri)` in `vck-openid` (`at.asitplus.wallet.lib.openid`), the answer of the verifier's response endpoint for `direct_post` and `direct_post.jwt`: a JSON object with the optional `redirect_uri` (OpenID4VP 1.0, 8.2), with `Cache-Control: no-store` - OpenID for Verifiable Presentations client: - Renamed `OpenId4VpWallet` (in `vck-openid`) to `OpenId4VpKtorHolder` - Add `OpenId4VpProtocolClient` in `vck-openid`, returning `HttpExchange`s for the requests to the verifier: `prepareAuthorizationResponse` fetches the request object from `request_uri` when needed, `sendAuthorizationResponse` posts authorization responses and error responses for the response modes `direct_post` and `direct_post.jwt` - Deprecate `OpenId4VpHolder.startAuthorizationResponsePreparation` and `OpenId4VpHolder.createAuthnResponse` taking the request as `String`, which fetch the request object with the `remoteResourceRetriever`, replace with `OpenId4VpProtocolClient.prepareAuthorizationResponse`, followed by `OpenId4VpHolder.finalizeAuthorizationResponse` for the latter - BREAKING: `OpenId4VpHolder` no longer fetches the verifier's JWK Set from a `jwks_uri` in `client_metadata`, which OpenID4VP 1.0 does not define (5.1 defines `jwks` only, and requires other metadata parameters to be ignored); the keys come from `jwks`, or else from `lookupJsonWebKeysForClient` - Fix (security): The `redirect_uri` in the verifier's answer to a posted authorization response must be an absolute `https` URI, else sending the response fails, although the verifier has already processed it. Before, any value, e.g. a `javascript:` or `intent:` URI, or a custom app scheme, was returned for the wallet to open - Fix (security): A request object passed in `request` or `request_uri` must carry the same `client_id` as the request it is passed in (RFC 9101, 6.3), else parsing fails with `invalid_request`. Before, the `client_id` the wallet was launched with was not compared, so it could name another verifier than the one that signed the request object and receives the response - The `redirect_uri` is only read from the JSON body of the verifier's answer (OpenID4VP 1.0, 8.2); a `Location` header is ignored - `OpenId4VpKtorHolder` fetches request objects and sends authorization responses with `OpenId4VpProtocolClient`, keeping its API; for `request_uri_method=post`, the form is now sent without `charset` in the `Content-Type`, as the authorization response - Move `OpenId4VpSuccess` to `vck-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 `OpenId4VpKtorHolder` taking the app's ktor `HttpClient`, whose copy without following redirects sends the requests, and every parameter of `OpenId4VpHolder`, with the holder as `holder: Holder` instead of `holderAgent: HolderAgent`. Deprecate the constructor taking `engine` and `httpClientConfig` - The deprecated `OpenId4VpHolder` methods of `OpenId4VpKtorHolder.openId4VpHolder` fetch request objects as `OpenId4VpProtocolClient` does, i.e. the form posted to `request_uri` has no `charset` either - 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 `OpenId4VpKtorHolder` sent nothing at all. A presentation is never sent without encryption - Fix: Error responses over the Digital Credentials API follow OpenID4VP 1.0, A.4: `data` is an object with the single property `error`, never encrypted, also for `dc_api.jwt`; before, the whole error including `state` and description was passed in `response`, encrypted for `dc_api.jwt`, which verifiers could not process - `OpenId4VpKtorHolder` logs the `client_id`, response mode, and `response_uri` or `redirect_uri` of a request instead of the whole request, including its request object and `client_metadata`, and the URL of a posted response instead of the response - OpenID for Verifiable Presentations verifier: - Add `error`, `errorDescription` and `errorUri` to `AuthenticationResponseParameters`, 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 the `data` of a Digital Credentials API response (A.4) - BREAKING (binary): The constructor and `copy` of `AuthenticationResponseParameters` gain 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 - `OpenId4VpVerifier` and `DcApiVerifier` share 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 its `expected_origins`, now fails `validateAuthnResponse` instead of only its `vpTokenValidationResult`; the request is consumed either way - BREAKING: `AuthnResponseResult` is a sealed interface with the subtypes `Success` (with the non-null `vpTokenResult`, formerly `vpTokenValidationResult`, and `request`) and `Error` (with the wallet's `error` as `OAuth2Error`, and `request`); `request` is non-null. Migrate `result.vpTokenValidationResult?.getOrThrow()` to a `when` over both subtypes, e.g. `is AuthnResponseResult.Success -> result.vpTokenResult.getOrThrow()` and `is AuthnResponseResult.Error -> showError(result.error)` - Fix: `OpenId4VpVerifier` and `DcApiVerifier` process authorization error responses of the wallet (OpenID4VP 1.0, 8.2, A.4) as `AuthnResponseResult.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) and `dc_api.jwt` (A.4), as OpenID4VP allows them although OpenID4VC HAIP requires encrypted responses; an error carries no credential, and a plaintext `vp_token` is still rejected - Reject authorization error responses violating RFC 6749, 4.1.2.1 (`error` blank or with illegal characters, illegal characters in `error_description` or `error_uri`, either of them without `error`), combining `error` with `vp_token` or `code`, and responses passing parameters next to an encoded `response` - Fix: `DcApiVerifier` returns the new `Iso180137AnnexCError` with 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: `Iso180137AnnexCWrapper` and `Iso180137AnnexCError` implement the new sealed `Iso180137AnnexCResult`, a further subtype of `DcApiResponseResult` - Add `EphemeralEncryptionKeyService.discardKey(identifier)`, removing a key without decoding it - `OpenId4VpVerifier` and `DcApiVerifier` validate every presentation with a `CredentialValidator` and report the whole response as a `ValidationReport`, configured with the new constructor parameters `credentialValidator` and `presentationPolicy`; the default policy requires authorized issuers, so without trust anchors every presentation is rejected in the report. `VpTokenValidationResultDCQL.presentationValidations` holds the validated presentations by credential query - BREAKING: `AuthnResponseResult.Success` and `Iso180137AnnexCWrapper` have the non-null `validationReport`, and `Iso180137AnnexCWrapper` the validated `presentationValidations`, both without defaults in their constructors - BREAKING (behavior): `OpenId4VpVerifier` and `DcApiVerifier` validate every presentation once, with `credentialValidator` under `presentationPolicy`, and project the legacy results from that validation: an accepted presentation becomes its `VerifyPresentationResult`, with the `CredentialFreshnessSummary` of its report, a rejected one fails with the cause of its first rejecting check (`PresentationValidationResult.rejection(policy)`), and `submissionRequirementsValidationResult` is the `submissionRequirements` check 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 whenever `presentationPolicy` requires it, and by default every presentation fails without trust anchors. A status that the policy does not resolve is reported as `TokenStatusValidationResult.Rejected` instead of `Valid`. Transaction data can no longer be skipped through `ValidatorSdJwt(verifyTransactionData = false)`. Annex C `documents` hold the accepted documents only: a rejected document no longer fails `validateAuthnResponse`, while a document of a type that has not been requested still does - Add `AuthnResponseResult.Success.presentationValidations`; deprecate `AuthnResponseResult.Success.vpTokenResult`, `VpTokenValidationResultDCQL.credentialQueryResponseValidations`, `submissionRequirementsValidationResult`, and `presentationResults`, and `Iso180137AnnexCWrapper.documents`, in favor of `validationReport` and `presentationValidations` - Deprecate the constructors of `OpenId4VpVerifier` and `DcApiVerifier` taking `verifier` and `verifyCoseSignature`, which no longer take effect, and their property `verifier`; the new primary constructors omit both. Deprecate `NonceChallengeVerifier.verifier`, its constructor taking it, and the presentation methods of `ChallengeSession`, in favor of `CredentialValidator` with `ChallengeSession.context()` - `MapStore.remove` is documented to be atomic: of concurrent calls for one key, at most one returns the value, as `DefaultMapStore` does; custom stores passed to the verifiers need to guarantee this, so that a request is answered at most once - OpenID data classes: - BREAKING: `VerifierInfo.data` is a `VerifierInfo.Data` of `StringData` or `ObjectData` instead of `String`, as OpenID4VP 1.0 allows a string or a JSON object. `VerifierInfo` can still be created with a `String`, or with a `JsonObject` - Moved `Base64URLTransactionDataSerializer` to openid-data-classes - BREAKING: Make `IssuerMetadata.supportedCredentialConfigurations` non-null without a default, as `credential_configurations_supported` is REQUIRED in OID4VCI; issuer metadata without it now fails to deserialize - ETSI data classes: - BREAKING: Align LoTE digital-identity JSON member names and representations with TS 119 602 Annex A.1: `PublicKeyValue` is a `JsonWebKey`, while `SubjectKeyIdentifier` and `OtherId` are strings; absent `x509Certificates` is now `null`. Other-list pointers carry `LoTEQualifiers` containing `LoTEQualifier` objects, 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 JSON `content` - Accept a plain string as `LoTELegalNotice`, as the Annex A.1 JSON schema declares and published lists use it, in the new `PolicyOrLegalNoticeItem.legalNoticeText` - Allow an explicitly null `NextUpdate` for closed lists while keeping that member required; accept optional certificate `encoding` and `specRef` metadata 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 `ListAndSchemeInformation` requirements remain unchanged - BREAKING: `WrpClaim.path` is a claims path pointer of `WrpClaimPathSegment`s (a name, `null` for all elements of an array, or an index), and `WrpClaim.values` a list of `WrpClaimValue`s (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 with `null` or an index in a path could not be parsed before. Registered paths authorize requested claims by coverage: a registered ancestor covers descendants, and `null` covers an index or `null`. JSON and CWT decode typed segments and values; empty paths and indices above `UInt.MAX_VALUE` are rejected. kotlinx-serialization CBOR also reads an empty map as `null` - BREAKING: Align `WrpPayload` with the multiplicities of ETSI TS 119 475 V1.2.1, Annex B and Tables 7 to 10: `policyId` has no default, so it is always encoded and a WRPRC without `policy_id` no longer passes as carrying the WRPRC policy (6.1.3); `supportUri` and `supervisoryAuthority` are required, `infoUri` is optional; `purpose`, `credentials` and `providesAttestations` are `null` when absent, as only service providers or attestation providers carry them; `WrpCredential.claim` is `null` when absent; `WrpIntermediary.sub` is required. Empty `srv_description`, `entitlements`, `purpose`, `credentials`, `claim` and claim `values` are rejected. Add `WrpConstants.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. Deprecate `subjectLegalName`, `infoUri`, `supportUri`, `email`, `phone`, and `uri`, which return the first entry. `privacy_policy` stays a single string, since Table 7 maps it from the single `policyURI` of 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_attestations` only for attestation providers (B.2.1), and `purpose` for 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.sub` of GEN-5.2.4-09 contradicts Table 10 and is not supported. Add `ValidatedRelyingParty.intermediated` and `ValidatedCredentialIssuer.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.euWrpRegistrarInfo` and the classes `WrpRegistrarInfo` and `WrpIdentifier`: 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 only `euWrprc` - BREAKING: Model `srv_description` as `WrpPayload.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 of `WrpLangString`s; 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. Deprecate `srvDescription`, which returns `serviceDescription` as the only array - 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). `serialized` of items from `IssuerSignedList.fromIssuerSignedItems` holds the encoded `IssuerSignedItem` like a decoded item, no longer the tag-24-wrapped `IssuerSignedItemBytes` - Add `IssuerSigned.fromIssuerSignedLists(...)` and `StoreEntry.Iso.discloseIssuedItem(...)`; mdoc presentations disclose items with the bytes they were issued with - Serialize `DocRequest.itemsRequest` and `DeviceRequest.deviceRequestInfo` again with the bytes they were decoded from (`ByteStringWrapper.serialized`) instead of encoding their value anew, using the new `ReserializableByteStringSerializer`, as `readerAuth` and `readerAuthAll` sign 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. `systemSpecs` before `zkRequired` in a `zkRequest` - BREAKING: `SessionTranscript` is a sealed interface with one subtype per handover: `SessionTranscript.Qr` and `SessionTranscript.Nfc` (both `SessionTranscript.Proximity`, with `deviceEngagementBytes` and `eReaderKeyBytes`), `SessionTranscript.DcApi`, and `SessionTranscript.OpenId4Vp`, each with its `handover`. Its new `SessionTranscriptSerializer` decodes 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`, and `openId4VpHandover` are removed; deprecate `SessionTranscript.forQr`, `forNfc`, `forDcApi`, and `forOpenId` in favor of the constructors of the subtypes. The encoding is unchanged - CSC data classes: - BREAKING: Split CSC API into a new `api` subfolder and add the `datamodel` package - Implement CSC Data Model 1.0.0 - Java interop: - Java could not instantiate data classes whose constructor takes a `kotlin.time.Duration` or 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 as `Long` (`…AsLong`, values outside of `UInt` are rejected): - `KeyAttestationRequired` (`preferredTtlSeconds`), `TokenResponseParameters` (`expiresSeconds`, `intervalSeconds`), `CredentialResponseParameters` (`intervalSeconds`), `PushedAuthenticationResponseParameters` (`expiresSeconds`), and the accessor `IssuerMetadata.preferredClientStatusPeriodSeconds` - `ValidationPolicy` and `TimeScope` (`timeLeewayMilliseconds`), whose Java constructors take the leeway at a different position to keep their JVM signatures distinct - CSC `KeyParameters` (`lenAsLong`) and `CredentialInfo` (`multisignAsLong`) - `EuPidCredential` (`sexAsLong`) and `MobileDrivingLicence` (`heightAsLong`, `weightAsLong`, `ageInYearsAsLong`, `ageBirthYearAsLong`) - ISO mDoc Zero-Knowledge Proofs: - In `IsoMdocZkBackend.generate()` add a `timestamp` to pass as the creation timestamp of the proof Release 8.0.0: New features: - Generate holder-side ISO mDoc zero-knowledge proofs through pluggable backends, including the new `vck-longfellow` module. - Present mDocs through direct ISO 18013-5 Device Retrieval, including matching and multi-document responses. - Use encrypted OpenID4VP authorization requests and request-specific keys for encrypted presentation responses. - Encrypt OpenID4VCI credential requests and responses with keys bound to each credential request. - Configure trust for credential issuers and relying parties, including WRPAC and WRPRC validation for EU wallet presentations. - Filter EU Lists of Trusted Entities by profile and provide trust anchors for issuers and status list signers. - Use OAuth 2.0 attestation-based client authentication Draft 10, combined DPoP proofs, and transaction codes in pre-authorized issuance flows. Technical changes and migration notes: - Build: - Upgrade the Android JVM target to 17 for compatibility with `vck-longfellow` - Upgrade to the 20260828 conventions plugin and AGP 9 - Migrate Android library targets to the new Kotlin Multiplatform Android library plugin API - Remove the conventions plugin submodule and version catalog workarounds - Remove obsolete build hacks - ETSI data classes: - Normalize decoded RFC 5646 language tags to lowercase instead of rejecting non-lowercase input - Add `WalletRelyingParty` ETSI data classes for `WRPAC` and `WRPRC` validation - ISO mdoc data classes: - BREAKING: Update `ZkDocumentData.timestamp` type to `Instant` instead of `DateTime` to conform to upcoming ISO-18013-5 draft - Add support for correctly (de-)serializing RFC9360-conformant single-chain cbor-encoded `ZkDocumentData` - Add missing equality overrides for `ZkDocumentData`, `ZkSignedItem` - Openid data classes: - Add fields `eUWrprc` and `euWrpRegistrarInfo` to data class `DocRequestInfo` - ISO mDoc Zero-Knowledge Proofs: - Add the `ZkRequest`-based ISO mDoc ZK presentation path and convert DCQL ZK metadata into `ZkRequest` for holder-side proof generation - Add the `IsoMdocZkBackend`, `IsoMdocZkBackendRegistry`, `IsoMdocZkEngine`, and `IsoMdocZkProof` extension layer for application- and library-provided proving systems - Add holder-side ZK proof generation and optional plain-mDoc fallback through `VerifiablePresentationFactory` - Add backend routing for ISO mDoc ZK system specifications, including application-defined proving systems and backend-specific parameter serializers - BREAKING: Rename `ZkSystemSpec.zkSystemId` to `id` while retaining `zkSystemId` on the wire - Rename `ZkMetadata.IsoMdocZk.zkInfo` to `zkRequest`, change DCQL ZK numeric parameters from `Int` to `Long`, and update the ZK integration APIs to use `ZkSystemSpec` - Remove the unused `ZkInfo` and `ZkSystem` preparation abstractions - Add `vck-longfellow` module containing an implementation of a registerable `Longfellow-ZK` backend - Credentials: - In `SubjectCredentialStore.StoreEntry` make the `schemeIdentifier` non-nullable. Deserialization of old previously stored entries need to be handled by calling applications. - Derive SD-JWT Digital Credentials API identifiers from the JWT ID or serialized credential instead of the subject - Preserve and validate every status mechanism when a credential's `status` object contains both `status_list` and `identifier_list`; combined values are exposed through `StatusListInfo.tokenStatusInfo` while the 7.0.1 `RevocationListInfo` properties and singleton behavior remain compatible - Verifiable Presentations: - Compute ISO mDoc `DeviceAuthentication` signatures automatically using the `calcIsoSessionTranscript` callback instead of requiring `calcIsoDeviceSignaturePlain` - Replace `PresentationRequestParameters.calcIsoDeviceSignaturePlain` with the `PresentationRequestParameters.calcIsoSessionTranscript` callback to return a nullable `SessionTranscript`. DeviceSignature and DeviceAuth is now calculated based on the Transcript. - Add optional `signDeviceAuthDetached` parameter to `HolderAgent` and `VerifiablePresentationFactory` to centralize ISO mDoc device authentication within the holder - OpenID for Verifiable Presentations: - Deprecate Presentation Exchange APIs in favor of DCQL, since OpenID4VP 1.0 only supports DCQL - Implement direct presentation requests and responses according to ISO 18013-5 Device Retrieval with new subtypes `CredentialPresentationRequest.IsoDeviceRetrieval`, `CredentialPresentation.IsoDeviceRetrievalPresentation`, `IsoDeviceRetrievalMatchingResult` for `CredentialMatchingResult`, `HolderIsoDeviceRetrievalQueryMatchingResult` for `HolderPresentationRequestMatchingResult`, and `PresentationResponseParameters.DeviceRetrievalParameters` - Add `DeviceRequestCredentialDisclosure`, `IsoDeviceRetrievalQueryMatchingResult`, `IsoDeviceRetrievalCredentialMatch`, and `IsoDeviceRetrievalClaimMatch` for ISO submission and matching details - Match every `DocRequest` against stored mdocs, requiring all requested data elements while preserving repeated docTypes and multiple matching credentials; validate explicit submissions and create a single, possibly multi-document `DeviceResponse` - Keep direct ISO Device Retrieval responses separate from OpenID4VP `vp_token` responses, and fix JSON round trips for ISO presentation requests - Migrate ISO/IEC 18013-7 Annex C holder and iOS pre-request matching from Presentation Exchange to ISO Device Retrieval; replace `IosDcApiMdocPreRequestSummary.toDifInputDescriptors()` with `toDeviceRequest()` - Add `Holder.matchPresentationRequestAgainstCredentialStore()` as a protocol-neutral matching entry point and move `CredentialMatchingResult` with its `DCQLMatchingResult`, `IsoDeviceRetrievalMatchingResult`, and deprecated `PresentationExchangeMatchingResult` subtypes from `vck-openid` to `vck` - Use correct digest for validating KB JWT of SD-JWT VC - Extract format-specific submission resolution, validation, and response creation from `HolderAgent` into an internal presentation response coordinator - Deprecate format specific methods in `Holder`, all to be replaced with `matchPresentationRequestAgainstCredentialStore()`: `matchInputDescriptorsAgainstCredentialStoreV2()`, `matchDeviceRetrievalAgainstCredentialStore()`, `evaluateInputDescriptorAgainstCredential()`, `matchDCQLQueryAgainstCredentialStoreV2()` - Match non-fully specified COSE algorithms with fully specified ones - Reject credential presentations containing any invalid issuer signed items according to ISO 18013-5 9.3.1 Inspection procedure for issuer data authentication - Correctly validate credential submissions for DCQL queries - Add `VerifierInfo` to `OpenId4VpRequestOptions` - Do not accept responses for the same `state` twice for OpenID4VP flows - Do not accept responses for the same `externalId` twice for DCAPI flows - Supply an ephemeral response encryption key specific to each authentication request in its client metadata, as required by OpenID4VP 1.0, Section 8.3, and OpenID4VC HAIP. Keys are identified by the `kid` of the JWE, and also used as the HPKE recipient key for ISO/IEC 18013-7 Annex C requests, where they are identified by the request's `state` - Add `EphemeralEncryptionKeyService` holding these keys in a `MapStore`, PKCS#8 PEM encoded, so that applications running several instances can synchronize them, along with the parameter `ephemeralEncryptionKeyService` on `OpenId4VpVerifier` and `DcApiVerifier`. Every key decrypts at most one response - Add `DecryptJweWithEphemeralKey`, resolving the decryption key from the `kid` header of the JWE - In `OpenId4VpVerifier` and `DcApiVerifier` the `decryptionKeyMaterial` is now nullable and defaults to `null`: it is only advertised in `metadataWithEncryption`, i.e. for client identifier schemes that distribute it out-of-band, which is not conformant to HAIP. `metadata` carries no key to encrypt responses to at all - In `OpenId4VpVerifier` and `DcApiVerifier` remove the `decryptJwe` function from the list of constructor arguments - Consume the request's nonce when validating an authentication response, regardless of the validation result, i.e. authentication responses are not retryable - Support requesting and serving encrypted authorization requests as per OpenID4VP 1.0, Section 5.10 - Add `jwks`, `request_object_encryption_alg_values_supported` and `request_object_encryption_enc_values_supported` to `OAuth2AuthorizationServerMetadata` - Fix decoding of form-encoded parameters whose value is a JSON string, e.g. `wallet_metadata` posted to the request URI endpoint - Send `wallet_metadata` and `wallet_nonce` only when fetching the request object with `request_uri_method=post` - Terminate request processing if the request object does not carry back the `wallet_nonce` we sent, as required by OpenID4VP 1.0, Section 5.10.1 - Reject a JAR request whose request object can not be retrieved from `request_uri`, that carries neither `request` nor `request_uri`, or whose request object nests another `request`/`request_uri` (RFC 9101, Section 6.2), with `invalid_request` in `RequestParser` - Retain the underlying payload deserialization failure as the cause of `invalid_request` when parsing a structurally valid signed request object - In `OpenId4VpHolder` reject requests that are not authorization requests, e.g. RQES signature requests, with `invalid_request` instead of failing with a `ClassCastException` during request validation - Pass `requireEncryptedRequests = true` to `OpenId4VpHolder` to reject a plain request object served at a `request_uri` fetched with POST - Add `decryptedFrom` to `RequestParametersFrom.Jws` and `RequestParametersFrom.Json`, holding the header of the JWE a request was decrypted from, and `requestWasEncrypted` to `AuthorizationResponsePreparationState` - Reject request objects that are not a JWS (and optionally encrypted) per RFC 9101, Section 4 - Reject request objects that do not carry `typ: oauth-authz-req+jwt` as required by OpenID4VP 1.0, Section 5, also for signed and multi-signed requests over the DC API - Deprecate `CreationOptions.RequestByReference` at error level: it serves an unsigned request object by reference, which OpenID4VP 1.0, Section 5.10.1 forbids, instead use `CreationOptions.SignedRequestByReference` - OpenID for Verifiable Credential Issuance: - Preserve credential-offer retrieval failures instead of masking them with a JSON parsing error - Rework validation of key attestation statements - In `ProofValidator` replace the unreleased `keyAttestationIssuer` with `verifyKeyAttestationSignature` to accept key attestations of trusted wallet providers, e.g. with `VerifyJwsObjectTrustedCertificate`; key attestations are rejected unless a trusted verifier is configured - Sign metadata in `CredentialIssuer.signedMetadata()` as per OpenID4VCI 1.0, Section 12.2.3, i.e. with `typ` set to `openidvci-issuer-metadata+jwt` and the claims `sub` and `iat`, added as `subject`, `issuedAt` and `expiration` to `IssuerMetadata` - Add `displayProperties` to `CredentialIssuer`, to include them in both `metadata` and `signedMetadata()` - Make sure a `nonce` provided by the credential issuer can only be used for one request to the credential endpoint - Security fix: encrypt the credential request whenever it carries `credential_response_encryption`, as required by OpenID4VCI 1.0, i.e. *"Credential Request encryption MUST be used if the `credential_response_encryption` parameter is included, to prevent it being substituted by an attacker"*. `WalletEncryptionService` previously sent its response encryption key in a plain request unless request encryption was required by either side, and `CredentialIssuer` accepted such requests - In `WalletEncryptionService`, if the issuer publishes no key to encrypt the request with, `credential_response_encryption` is omitted, or the request fails if the issuer requires response encryption - Announce a response encryption key specific to each credential request, with a `kid` for the issuer to echo in the JWE header. Replace `WalletEncryptionService.decryptionKeyMaterial` with `ephemeralEncryptionKeyService`, which may hold a `MapStore` to synchronize these keys between the wallet component creating the request and the one receiving the response - Bind encrypted credential responses to their originating `CredentialRequest`, reject encryption downgrades and response/request key swaps, and consume a response key only after authenticated decryption. Parsing an encrypted response now requires passing the originating request to `WalletService.parseCredentialResponse` - Negotiate credential response `alg` and `enc` strictly against issuer metadata: omit optional encryption when no combination is shared, fail when encryption is required, and reject algorithms the issuer did not advertise - `IssuerEncryptionService` advertises `credential_response_encryption` whenever it can encrypt responses, and `credential_request_encryption` whenever it can decrypt requests, instead of only when some encryption is required, so that clients can opt into encryption. Requiring response encryption now also declares `credential_request_encryption.encryption_required`, since the client's key may only be sent in an encrypted request - Select the issuer's credential request encryption key by its intended use instead of taking the first key of the set, and use the `alg` of the client's response encryption key for the JWE, as required by OpenID4VCI 1.0 - Signature verification: - `VerifyJwsObject` and `VerifyCoseSignature` now only ever use the key material asserted by the JWS header resp. the COSE headers, and make no trust decision - Add `VerifyJwsObjectTrusted` and `VerifyCoseSignatureTrusted`, where the supplied keys are the only accepted signers - Note that neither builds a certificate path to a trust anchor, they compare public keys - In `ValidatorSdJwt.verifyVpSdJwt()` reject presentations whose SD-JWT carries no `cnf`: the key binding JWT used to be verified against a key asserted in its own header in that case, which proves nothing about the holder - Delegate ISO mDoc device authentication calculation directly to the holder during OpenID4VP and DCAPI presentation creation - Trusted issuers: - Accept an exactly listed, CA-issued end-entity certificate as a direct credential signer, without requiring it to be self-signed - Add `TrustedCertificates`, supplying the certificates of the parties to trust, e.g. extracted from an ETSI trust list with `LoTEFilterService` - Add `VerifyJwsObjectTrustedCertificate` and `VerifyCoseSignatureTrustedCertificate`, requiring the certificate transported with a credential to be signed by one of the trusted issuer certificates. - The certificate of the trust anchor must be known out-of-band, so no trusted certificate may be transported with the credential, and the signing certificate must not be self-signed. - Add a `trustedIssuers` parameter to `VerifierAgent` and `HolderAgent`, which builds validators enforcing it for issuer signatures on SD-JWT, VC-JWS and mdoc credentials. - Use `issuerJwsVerifier()` and `issuerCoseVerifier()` to wire it into validators directly, and pass `VerifyJwsObjectTrustedCertificate` as `verifyJwsObjectIntegrity` of `TokenStatusResolverImpl` resp. as `verifyJwsObject` of `ClientAuthenticationService` for status list tokens and client attestations, which are separate trust domains - In `VerifyStatusListTokenHAIP` implement the certificate checks. Replace `trustStoreLookup` with `trustedIssuers`, and deprecate the now unused `TrustStoreLookup`. - In `VerifyStatusListTokenHAIP` fix the check that the signing certificate must not be self-signed - Trusted relying parties: - Add `RelyingPartyTrust`, a sealed interface with one subtype per client identifier scheme, configuring how a wallet establishes trust in the relying party sending an authorization request, and a `relyingPartyTrust` parameter taking a `Set` of them on `OpenId4VpHolder` and on `OpenId4VpWallet`. Several sources of the same kind form a union, i.e. trust established by any one of them is enough, so e.g. a trust list may sit next to a locally pinned certificate. Passing `null` trusts every relying party, an empty set trusts none - `AuthorizationRequestValidator` now verifies the signature of a signed request object, for `JwsCompact` as well as for multi-signed `JwsGeneral` requests. - For `x509_san_dns` and `x509_hash`, validate the `x5c` chain against `RelyingPartyTrust.Certificates` - Implement the `verifier_attestation` client identifier scheme - Enforce the `pre-registered` client identifier scheme against `RelyingPartyTrust.PreRegisteredClients` - Requests using a scheme for which no trust material is configured are rejected. This includes `entity_id` and `did`, which are handed to `RelyingPartyTrust.Custom` and rejected when none is configured, so that a relying party cannot bypass the configured trust anchors by naming itself with a scheme this library does not evaluate natively. Only `redirect_uri` is not covered, as it forbids signed requests anyway - `RequestParser` no longer verifies anything and lost its `requestObjectJwsVerifier` parameter, so parsing a request is purely parsing. Consequently `RequestParametersSigned.verified` is removed, with the `verified` property of `Jws`, `OpenId4VpDcApiSigned` and `OpenId4VpDcApiMultiSigned` and its serialized form. Stored JSON still carrying `"verified"` deserializes fine, as unknown keys are ignored - Add `WrprcValidator`, `WrpacValidator`, `WrpAuthenticationRequestValidator` and `WrpChainValidator` to validate WRPAC and WRPRC during presentation. - Verify ISO mdoc `readerAuth`/`readerAuthAll` against the session transcript before accepting a WRPAC from the request. ISO mdoc validation now uses a suspend `WrpAuthenticationRequestValidator.invoke` overload requiring a `SessionTranscript`; `IsoMdocDcApi.validateWrpAuthenticationRequest()` calculates it from the DC API request. Remove the unauthenticated `DeviceRequest.extractCertificateChain()` helper. - `DcApiVerifier` signs `readerAuth` of every ISO 18013-7 Annex C document request with its key material, when that has a certificate, transporting the chain of the certificate-based client identifier scheme. Pass the CBOR-encoded WRPRC in the new `OpenId4VpRequestOptions.euWrprc` to set it in the `DocRequestInfo` of every document request. - Trust List filtering: - Replace `LoTEServiceType`/`LoTEFilterCriteria` with `LoteProfile`, a sealed class defining PID, mDL, WRPAC, WALLET, and EAA profiles with built-in matching against scheme type, status approach, community rules URIs, and country code - Add support for LoTEs with issuance and revocation certificates - Add `LoTEStage`, enumerating the base URLs of the European Commission's development, acceptance, and production trust infrastructure, so that applications can fetch the lists of any stage - Replace the hardcoded acceptance URL in `LoteProfile.fetchUrl` with the relative `LoteProfile.fileName`, resolved against a base URL by `LoteProfile.fetchUrl(baseUrl)` or `LoteProfile.fetchUrl(stage)` - Replace `LoteProfile.defaultUrls` with `LoteProfile.entries` and `LoteProfile.fetchUrls()`, which take the stages or the base URL to fetch from - Add `TrustAnchorProvider` which lets apps supply trust anchors per credential type (vct/doctype) or per `LoteProfile`, for issuers as well as for status list signers (JWT and CWT) - Form-url-encoded parameters: - Preserve opaque string parameters when decoding polymorphic requests, and ignore unknown object parameters before parsing their values as JSON - Use `RequestParametersSerializer.decodeFormParameters()` to decode form parameters whose concrete request type is determined by their parameter names - Extract the sketch in `SerializerSketch.kt` of `vck-openid` into a documented API in `FormUrlEncoding.kt`, covered by `FormUrlEncodingTest` - Move it from `at.asitplus.wallet.lib.oidvci` in `vck-openid` to `at.asitplus.openid` in `openid-data-classes`, next to the parameter classes it encodes, since it is specific to neither issuance nor presentation. The previous declarations remain as deprecated forwarders - Rename `Parameters` to `FormParameters`, to disambiguate it from `io.ktor.http.Parameters` - Replace `String.decodeFromPostBody()` and `String.decodeFromUrlQuery()`, which were two names for the same thing, with `String.decodeFromFormUrlEncoded()` - Add `Url.decodeFromQuery()`, `Url.decodeFromFragment()` and `Url.decodeFromFragmentOrQuery()`, which read the parameters off a URL instead of leaving that to callers - Add `T.encodeToFormUrlEncoded()` and `String.toFormParameters()` - Deprecate `FormParameters.decodeFromUrlQuery()`: its receiver is already decoded in nearly all call sites, so it decoded percent-encoding a second time and mangled every value containing a percent sign. This fixes parsing credential offers, authentication requests and authentication responses carrying such values - Split the payload with `io.ktor.http.parseQueryString()` instead of by hand, which drops names without a value instead of throwing, and reads `+` as a space in URL queries too - OAuth 2.0: - Update implementation of [OAuth 2.0 Attestation-Based Client Authentication](https://www.ietf.org/archive/id/draft-ietf-oauth-attestation-based-client-auth-10.html) to Draft 10 from 2026-07-06 - Support DPoP combined mode, advertised with `dpop_combined` in `client_attestation_pop_methods_supported` to combine client authentication with DPoP proofs from RFC 9449 - Support DPoP combined mode on the client side in `OAuth2KtorClient` - Add method `attestationChallenge()` to `SimpleAuthorizationService` to deliver challenges for Attestation-Based Client Authentication - Support `use_client_attestation` errors and parse challenges from `OAuth-Client-Attestation-Challenge` in `OAuth2KtorClient` - Strengthen validation of DPoP proofs from [OAuth 2.0 Demonstrating Proof of Possession (DPoP)](https://datatracker.ietf.org/doc/html/rfc9449) - Validate the access token in `SimpleAuthorizationService.getUserInfo()` before returning any user info, i.e. it now behaves the same as `userInfo()`. Previously the token was only parsed, so neither its signature, nor its expiration, nor the DPoP proof were verified - Compare scopes as space-delimited values instead of substrings, when issuing access tokens in `SimpleAuthorizationService` and when authorizing credential requests in `CredentialIssuer` - Authorize several credential requests with a single JWT access token - Add `TokenService.validateAccessToken()`, which validates the access token and resolves the user info stored at issuance - Bind client authentication to internal state to prevent client mismatches in subsequent requests - Add `dpopKeyMaterial` as explicit constructor argument to `OAuth2KtorClient` - Enforce `require_pushed_authorization_requests` in `SimpleAuthorizationService.authorize()` as mandated by [RFC 9126](https://datatracker.ietf.org/doc/html/rfc9126), Section 4 - Require PKCE with `code_challenge_method=S256` on every authorization request, as mandated by OpenID4VC HAIP, and advertise it in `code_challenge_methods_supported` - Make `CodeService.provideCode()` and `CodeService.verifyAndRemove()` suspending functions, and implement `DefaultCodeService` on top of `NonceService`, so codes are stored thread-safely and expire. - Keep authorization codes, pre-authorized codes and `issuer_state` values in separate stores in `SimpleAuthorizationService` - Make [OAuth 2.0 Token Exchange](https://datatracker.ietf.org/doc/html/rfc8693) opt-in with the new `supportTokenExchange` constructor argument of `SimpleAuthorizationService`, defaulting to `false`, and only advertise the grant in `grant_types_supported` when it is enabled. - Add `OAuth2Exception.UnsupportedGrantType` for the `unsupported_grant_type` error of [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-5.2) - Restrict pre-authorized codes to the credential configuration IDs of the offer they belong to, tracked in the new `ClientAuthRequest.configurationIds`. - Support OID4VCI transaction codes for the pre-authorized code flow, with the new `transactionCode` parameters of `SimpleAuthorizationService.offerWithPreAuthnForUserForSchemes()` and `providePreAuthorizedCode()`. - Return `null` from `CredentialAuthorizationServiceStrategy.filterScope()` when no requested scope is supported to trigger `invalid_scope` error - Grant only the scopes the authorization server accepted: `SimpleAuthorizationService` keeps the filtered scope with the authorization code - Implement `validScopes()` and `filterScope()` in `QtspAuthorizationServiceStrategy` for the CSC scopes `service` and `credential`, which the delegate `CredentialAuthorizationServiceStrategy` does not know - Deprecations: - Remove code deprecated in 7.0.0, e.g. various `Iso180137AnnexC*` and related classes - Deprecate all classes used for Presentation Exchange requests and so on, e.g., `CredentialPresentationRequest.PresentationExchangeRequest` or `PresentationExchangeCredentialDisclosure` or `CredentialPresentation.PresentationExchangePresentation` - Deprecate member `invalidItems` in `IsoDocumentParsed`, method `ValidatorMdoc.verifyDocument()` will throw instead of filling invalid items - In `ProofValidator` deprecate constructor argument `verifyAttestationProof`, replace with `statusListTokenResolver` and `verifyKeyAttestationSignature` - In `NonceChallengeVerifier` deprecate `verifyPresentationSdJwt()`, `verifyPresentationVcJwt()` and `verifyPresentationIsoMdoc()`, which take the challenge from the presentation itself, to be replaced with `consumeChallenge()` and the returned `ChallengeSession` - `NonceChallengeVerifier` does not implement `Verifier` and `NonceService` anymore, so a presentation cannot be verified without accounting for the challenge it answers; use the `ChallengeSession` from `consumeChallenge()`, or the properties `verifier` for challenge-free verification and `nonceService` for raw nonce access - Deprecate passing `publicKeyLookup` to `VerifyJwsObject` and `VerifyCoseSignature`, callers are rerouted to the trusted variants, use `VerifyJwsObjectTrusted` resp. `VerifyCoseSignatureTrusted` explicitly, or drop the parameter to keep verifying against the asserted key - Deprecate `PresentationRequestParameters.calcIsoDeviceSignaturePlain` in favor of computing device signatures automatically via `calcIsoSessionTranscript` - Deprecate the `NonceChallengeVerifier.createPresentationRequest()` overload accepting `calcIsoDeviceSignaturePlain` - Deprecate `signDeviceAuthDetached` parameter in `buildEncryptedResponse()`, `OpenId4VpHolder`, and `Iso180137AnnexCHolder`, as device authentication signature functions are now managed internally by the holder - Deprecate `TrustStoreLookup`, as trusted certificates are not selected per signed object, replaced by `TrustedCertificates` - Deprecate `RequestObjectJwsVerifier` and the `requestObjectJwsVerifier` parameter of `OpenId4VpHolder`, superseded by `RelyingPartyTrust`. **It is no longer invoked**: verifying a request object needs to know where to load the relying party's key from, which depends on the client identifier scheme, so it moved to `AuthorizationRequestValidator`. Supplying one now logs a warning and has no effect, so migrate to `relyingPartyTrust` — `RelyingPartyTrust.PreRegisteredClients` for the pre-registered case, `RelyingPartyTrust.Custom` for schemes this library does not evaluate natively. To be removed in 9.0.0 - Deprecate `ClientAuthenticationService.verifyClientAttestationJwt`, which never validated the `x5c` it required; pass `VerifyJwsObjectTrustedCertificate` with the trusted wallet provider certificates as `verifyJwsObject` instead - Deprecate constructor in `RequestInfo` taking single values for some HTTP headers, replace with constructor taking in all HTTP headers - Deprecate constructor parameter `enforceClientAuthentication` in `AttestationBasedClientAuthenticationService` as the new default is `true`. Callers might use a `NoopClientAuthenticationService` - Deprecate `TokenService.readUserInfo()`, since it does not prove possession of the key the access token is bound to. Replace with `TokenService.validateAccessToken()`, which validates the access token including the DPoP proof and returns the user info in `ValidatedAccessToken.userInfoExtended` - Deprecate constructor parameter `signDpop` in `OAuth2KtorClient`, replace by setting `dpopKeyMaterial` - Deprecate `LoTEFilterCriteria` and `LoTEServiceType`, replace by `LoteProfile` - Deprecate `LoTEFilterService.extractTrustedCertificates()`, replace by `LoTEFilterService.extractIssuanceCertificates()` and `LoTEFilterService.extractRevocationCertificates()` - Refactorings: - In `MdocInputValidator` and `ValidatorMdoc` replace `verifyCoseSignatureWithKey` with a `VerifyCoseSignatureFun`, which resolves the issuer key from the COSE headers itself. Consequently `MdocInputValidator.invoke()` and `ValidatorMdoc.verifyIsoCred()` lose their `issuerKey` parameter, `MdocInputValidationSummary.IntegrityValidationSummary.IntegrityValidationResult` loses its `issuerKey` property, and `IntegrityNotValidated` is removed - `ValidatorMdoc.verifyDocument()` no longer extracts the issuer certificate itself, but delegates to `MdocInputValidator` like the credential path does - Remove `requestObjectVerified` from `AuthorizationResponsePreparationState`: request verification is enforced in `AuthorizationRequestValidator`, which every holder flow passes through, so the flag had no remaining job. It could never be satisfied for signed DC API requests anyway, since nothing ever set it - In ISO data classes like `DeviceResponse`, `DeviceRequest`, `MobileSecurityObject` replace the String `version` with a typed `parsedVersion` from [kotlin-semver](https://github.com/z4kn4fein/kotlin-semver) - In ISO data class `MobileSecurityObject` replace the String `digestAlgorithm` with a typed `digest` from Signum - In `CredentialToBeIssued.Iso` add a property to specify the digest algorithm to be used in the MSO - Add method `getCertificateChain` to class `KeyStoreMaterial` - In `NonceChallengeVerifier` add `consumeChallenge()`, which consumes the challenge of the request an authentication response refers to and returns a `NonceChallengeVerifier.ChallengeSession` to verify all presentations of that response with, as used by `OpenId4VpVerifier` and `DcApiVerifier` - Add main constructor to `RequestInfo` that takes in all HTTP headers for future extensions to client authentication methods - Rename existing `ClientAuthenticationService` to `AttestationBasedClientAuthenticationService` and extract an interface - Return the validated token from `validateAccessToken()` in `TokenVerificationService` and `OAuth2AuthorizationServerAdapter` as `ValidatedAccessToken`, and move `validCredentialIdentifiers` from `TokenInfo` to `ValidatedAccessToken` - Authorize credential requests in `CredentialIssuer` from the `ValidatedAccessToken` - In `EncryptJwe` remove `keyMaterial` as it always relies on ephemeral keys embedded in the JWE header - Add `SingleClaimReference` (moved from Valera) - Additional Java-Safe APIs for `IssuerMetadata`, `ClaimDescrption`, `StatusIssuer`, `ReferencedTokenStore` - Dependencies: - Update to [Signum 3.26.0](https://github.com/a-sit-plus/signum/releases/tag/3.26.0) for HPKE support - Add `etsi-data-classes` as api dependency to `openid-data-classes` - Bouncy Castle 1.86 on the JVM - JsonPath4K 4.0.0 Release 7.0.0: - Credential definitions: - Move `CredentialScheme` out of `ConstantIndex` - Provide type alias for `CredentialRepresentation` - Introduce typed sub-interfaces of `CredentialScheme`: `VcJwtCredentialScheme`, `SdJwtCredentialScheme` and `IsoMdocCredentialScheme` - That implies changes to `CredentialToBeIssued`, `IssuedCredential`, `StoreCredentialInput` and methods in `SubjectCredentialStore` - In `CredentialScheme` deprecate `claimNames` (list of strings), to be replaced with `claimDescriptions` (set of typed descriptions) - In `CredentialScheme` deprecate `schemaUri`, clients should use the identifiers for each credential representation instead - In `StoreEntry` deprecate property `scheme` and add suspending function `resolveScheme()` to replace it - Add `UnknownCredentialScheme` so that the `scheme` property in several methods and classes is not null - Import data classes and data element strings from credentials into this library for [EU PID](https://github.com/a-sit-plus/eu-pid-credential), [EU PID in SD-JWT](https://github.com/a-sit-plus/eu-pid-credential-sdjwt/) and [Mobile Driving Licence](https://github.com/a-sit-plus/mobile-driving-licence-credential/) - Document usage of remote metadata retrieval - Make JSON and ISO CBOR serializer registration safe for concurrent extension-library initialization - OpenID for Verifiable Presentations: - Compare signed DC API `expected_origins` values to the provided origin as exact strings and add a configurable holder-side origin-scheme allowlist - Support non-web Android Digital Credentials API origins starting with `android:apk-key-hash:` for OpenID4VP; ISO18013-7 mdoc presentations require authority-based origins and reject opaque Android application origins - Fix SD-JWT presentation validation for Digital Credentials API responses by checking the key binding JWT audience against the request origin (`origin:`) instead of the verifier client identifier - Fix DCQL matching for credential queries without `claims`: selectively disclosable credentials now return an explicit mandatory-claims-only result, while non-selectively disclosable credentials still return all claims - Fix disclosure of SD-JWT claims from foreign issuers: match disclosure digests against the originally serialized disclosures instead of re-serializing them, since digests are computed over the exact bytes (RFC 9901, section 4.2.3), e.g. failing for disclosures serialized with whitespace - Extend `DCQLCredentialQueryMatchingResult` by case `AllMandatoryClaimsMatchingResult` - Consolidate interface of `OpenId4VpVerifier`: All clients should use `createAuthnRequest()`, so we deprecate methods `submitAuthnRequest()` or `createAuthnRequestAsSignedRequestObject()` - Extract `DcApiVerifier` as a pendant to `OpenId4VpVerifier` which handles DCAPI requests only, deprecating `Iso180137AnnexCVerifier` - Move `CreationOptions` and `CreatedRequest` to upper level (`at.asitplus.wallet.lib.openid`) instead of nesting in `OpenId4VpVerifier` - Digital Credentials API: - Add `DcApiHolder` as the unified wallet-side entry point for OpenID4VP and ISO/IEC 18013-7 Annex C requests received through the Digital Credentials API - Add platform response codecs for Android JSON and iOS ISO/IEC 18013-7 Annex C bytes without introducing platform dependencies - Add request-option conversion helpers that combine a selected DC API protocol with trusted platform metadata into `RequestParametersFrom.DcApiRequest` - Add the iOS-specific `IosDcApiMdocPreRequestSummary` model for pre-request credential matching and consistency checks against the full Annex C request - BREAKING: Remove the `origin` property from Digital Credentials API response models - Verifier: - Add `NonceChallengeVerifier`, a thin `Verifier` wrapper that creates presentation challenges from a `NonceService` and verifies SD-JWT/VC-JWT presentations against the embedded challenge - Move OpenID4VP request nonce handling out of `VerifierAgent` and consume nonces after successful response validation to prevent replay - Deprecate abstract base class `AbstractMdocVerifier` - Extract `MdocDeviceSignatureVerifier` from `AbstractMdocVerifier` - Extract `VpTokenValidator` from common code in `OpenId4VpVerifier` and `DcApiVerifier` - OpenID for Verifiable Credential Issuance: - Wallet does not send any proofs when the issuer doesn't [support any proof types](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-12.2.4-2.11.2.5.1) - Update Wallet Instance Attestation and Key Attestation to [EUDI Wallet TS3 1.5.2](https://github.com/eu-digital-identity-wallet/eudi-doc-standards-and-technical-specifications/blob/main/docs/technical-specifications/ts3-wallet-unit-attestation.md) from 2026-05-26 - In `IssuerAgent` introduce constructor parameter `statusListAgent` to decouple creation of status elements from issuing credentials - Rework `IssuerCredentialStore` by moving some functionality to `ReferencedTokenStore` - Status claims for identifier lists from ISO 18013-5 contain the certificate of the status list issuer - JVM interoperability: - Add `@JvmOverloads` to public API constructors with default parameters across the published modules - Provide methods to use non-negative `Long` values for status list indices and accompanying API - Preserve RFC 3986 port and IPvFuture syntax without artificial `ULong` limits - Refactorings: - `OpenId4VpHolder.getMatchingCredentials()` returns `KmmResult` instead of `Result` - In `SdJwtInputValidationResult` transport error during integrity validation in `integrityValidationResult` instead of `isIntegrityGood` - `vck-openid-ktor` HTTP clients throw `HttpErrorResponseException` for non-success responses, preserving OAuth errors, RFC 9457 problem details, and the raw response body - Add `ClaimToBeIssued(OpenId4VciClaimsPathPointer, value)` shorthand and Java-safe `ClaimToBeIssued.fromPath(List, value)` for creating nested claims from string path segments. - Trust Evaluation: - Add `LoTEFilterService` for extracting trust list certificates from `LoTE` based on `ServiceTypeIdentifier` - Add signature and time validity checks of certificate against the trust list - Add JAdES B-B validation (Used when fetching LoTE) - Add `issuer` property in `StoreEntry`, for evaluation of trust against trust list - Deprecations: - Remove code deprecated in 6.0.0, e.g. various `DCAPIWallet*` and related classes, `vckJsonSerializer` - In `OpenId4VpWallet` deprecate `sendAuthnErrorResponse()` with parameter of type `RequestParametersFrom`, use parameter of type `AuthorizationResponsePreparationState` instead - Dependencies: - Update to [Signum 3.24.0](https://github.com/a-sit-plus/signum/releases/tag/3.24.0) for HPKE support Release 6.0.0: - JWS: - BREAKING: Replace `JwsSigned` with `JwsCompact` and `JwsCompactTyped` in signing, verification, OpenID request/response, OAuth 2.0 DPoP/client attestation, OID4VCI proof, JWT VC, status list JWT, and SD-JWT APIs - BREAKING: Refactor `RequestParametersFromSigned.jwsSigned` from `JwsSigned` to `JWS` to allow multisigned use-cases - Remove `JwsSignedSerializer`, use `JwsCompactStringSerializer` - SD-JWT: - BREAKING CHANGE: Removed dot-notation shorthand for nested claims in `ClaimToBeIssued`. Claims with dots in their names (e.g. `address.region`) are now issued as flat claims with a literal dot in the key. Use a `Collection` in `value` to create nested structures. - Change: `String.toDigest()` annotated with `@Throws` - Change: `Digest.toIanaName()` annotated with `@Throws` - Change: `SdJwtDecoded` throws if payload is not a valid `JsonObject` - Change: `SdJwtSigned` now stores the issuer JWS as `JwsCompact` and key binding JWS as `JwsCompactTyped` - Deprecate `SdJwtSigned.getPayloadAsVerifiableCredentialSdJwt()` and `SdJwtSigned.getPayloadAsJsonObject()`, use `SdJwtSigned.jws.getPayload<...>()` - OpenID for Verifiable Presentations: - BREAKING: Integrate DC API request wrappers into `RequestParametersFrom` as `OpenId4VpDcApiUnsigned`, `OpenId4VpDcApiSigned`, `OpenId4VpDcApiMultiSigned`, and `IsoMdocDcApi`; DC API metadata such as `protocol`, `credentialIds`, `callingPackageName`, and `dcApiCallingOrigin` is now represented directly on `RequestParametersFrom.DcApiRequest` - Change: Signed and multisigned DC API requests are now rejected unless `expected_origins` is set, as required by OpenID4VP for signed requests over the Digital Credentials API - Fix: Unsigned DC API requests are no longer rejected when a `client_id` is present; per [OpenID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-request) the Wallet MUST ignore any `client_id` parameter in an unsigned request - Add `attributePaths` and `optionalAttributePaths` to `RequestOptionsCredential` for requesting literal claim names containing dots with `DCQLClaimsPathPointer`, while keeping the deprecated string attributes as nested dot-notation shorthand. ISO mdoc requests also accept explicit namespace/name paths and prefix single claim names with the credential scheme namespace. - Change: `RequestInfo.dpop`/`RequestInfo.clientAttestation`/`RequestInfo.clientAttestationDpop` now `JwsCompactTyped` instead of `String` - Change: `BuildDPoPHeader`/`BuildClientAttestationJwt`/`BuildClientAttestationPoPJwt` objects now return `JwsCompactTyped` instead of `String` - Change `JarRequestParameter.clientId` from optional to mandatory to enforce RFC9101 definition. - Digital Credentials API: - Deprecate `DCAPIWalletRequest` in favor of `RequestParametersFrom.DcApiRequest`; compatibility type aliases are provided for the old wallet request names - BREAKING: Refactor `DigitalCredentialGetRequest.OpenId4Vp` - Renamed `request` to `data` to reflect serial name - Introduced `SignedDataElement` `MultiSignedDataElement` wrapper to keep serialization shape - OpenID for Verifiable Credential Issuance: - Update Wallet Instance Attestation and Key Attestation to [EUDI Wallet TS3](https://github.com/eu-digital-identity-wallet/eudi-doc-standards-and-technical-specifications/blob/main/docs/technical-specifications/ts3-wallet-unit-attestation.md) from 2026-05-08 - Add `WalletService.KeyAttestationInput` - Add `OAuth2KtorClient.LoadInstanceAttestationInput` - Update `loadInstanceAttestation` and `loadKeyAttestation` to use input parameter - JWT proof creation only loads/attaches a key attestation when issuer metadata requires it - Reject Wallet Instance Attestations, attestation proofs, JWT proofs, and Key Attestations that use signing algorithms outside the TS3 ES256/ES384/ES512 set - Change: Add typed subclasses for `SupportedCredentialFormat` for every credential representation - Change: In `SupportedCredentialFormat` replace `List` with `OpenId4VciClaimsPathPointer` for claim definitions - Deprecations: - Remove code deprecated in 5.12.0, e.g. `CredentialSubject` as base class for JWT VC - Deprecate `vckJsonSerializer`, should be replaced with `joseCompliantSerializer` (Signum) - Deprecate `signDpop` in `OAuth2KtorClient` because DPoP need same key as instance attestation [EUDI Wallet TS3](https://github.com/eu-digital-identity-wallet/eudi-doc-standards-and-technical-specifications/blob/main/docs/technical-specifications/ts3-wallet-unit-attestation.md) - New modules: - `etsi-data-classes` implements list of trusted entities from [ETSI TS 119 602](https://www.etsi.org/deliver/etsi_ts/119600_119699/119602/01.01.01_60/ts_119602v010101p.pdf) - `sd-jwt-type-metadata` implements SD-JWT VC Type Metadata from [draft-ietf-oauth-sd-jwt-vc-16](https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/): - `SdJwtTypeMetadataDocument`: stores and verifies raw document bytes for W3C SRI integrity checks (integrity is computed over the original response bytes, not re-serialized JSON) - `KtorSdJwtTypeMetadataDocumentRetriever`: HTTP retrieval with two-tier caching (static/indefinite for integrity-pinned documents; Cache-Control–based TTL otherwise); integrity-pinned lookups bypass the dynamic cache and vice versa; integrity and `vct` are validated before a fetched document enters the static cache - `DelegatingSdJwtTypeMetadataDocumentResolver`: resolves full inheritance chains, merging display and claim metadata from all ancestors - `rfc3986-uri-syntax` implements [RFC 3986 URI Syntax](https://datatracker.ietf.org/doc/html/rfc3986) - Dependencies: - Update to [Signum 3.23.0](https://github.com/a-sit-plus/signum/releases/tag/3.23.0) - Update to [Supreme 0.14.0](https://github.com/a-sit-plus/signum/pull/451) - Update to Ktor 3.5.0 - Update Bouncy Castle 1.84 - Update to `kotlinx.coroutines` 1.11.0 - Matrix testing Release 5.12.0: - W3C JWT VC: - Presentation validation: Now verifies that the subject field contains the VP issuer's public key (VC holder's public key). - Replaced `CredentialSubject` abstract class with `JsonElement` for W3C VC `credentialSubject` field. Polymorphic deserialization using `type` discriminator is unreliable since W3C Data Model Spec 1.1 doesn't guarantee this field's presence. - Deprecate `LibraryInitializer.registerExtensionLibrary` overloads that take a `SerializersModule`; use the overloads without it. - Digital Credentials API: - Add issuance data classes: `CredentialCreationOptions`, `DigitalCredentialCreationOptions`, `DigitalCredentialCreateRequest`, `DigitalCredentialOfferReturn`, and `DigitalCredentialOfferReturnData`. These classes are based on a preliminary specification and are subject to change. - Add `CredentialRequestOptions.create()` method which automatically sets `mediation` to required and takes the list of requests, make the default constructor private. - Change: `DCAPIWalletRequest` now exposes and serializes `credentialIds`; deprecated single-ID constructors keep the old call shape available. - ISO mdoc: - Preserve `Document.errors` in parsed ISO document results instead of failing validation - Add data classes from ISO/IEC 18013-5 from 2026 update - BREAKING Change: Return type of `Iso180137AnnexCVerifier.validateResponse` from `Iso180137AnnexCResponseResult` to reworked `KmmResult` - OpenID for Verifiable Presentations: - Change: Executing unsatisfiable DCQL queries no longer throws on matching, only on submission. - Change: `Holder.matchInputDescriptorsAgainstCredentialStoreV2` now accepts `filterByIds: Collection?` for multi-credential DC API selections. - Change: Update DCQLClaimsQuery and DCQLCredentialQuery to OpenID4VP 1.0 - Change: Do not fail when only matching credentials without submitting a presentation - Allow issuance and verification of `IdentifierList` Revocation Mechanism - Change: Don't send response on user-initiated signature cancellation - BREAKING CHANGE: The result type from `verifyAuthnResponse`, `AuthnResponseResult` has been reworked to a data class - DCQL: Add custom credential types and proper satisfaction evaluation - Add: DCQL submission requirements validation - Add `VerifierMetadataMode` for `OpenId4VpRequestOptions` to provide them out-of-band when necessary, e.g. for Age Verification - OpenID for Verifiable Credential Issuance: - Moved the class `RefreshTokenInfo` from `OpenId4VciClient` to `SubjectCredentialStore.kt` and renamed it to `CredentialRenewalInfo` to better describe its role in the renewal process. Kept `RefreshTokenInfo` in the original package for backward compatibility - Added `CredentialRenewalInfo` to `SubjectCredentialStore.StoreEntry` - Added support for refresh tokens in BearerTokenService - Change: When no cryptographic holder binding is required, present raw W3C Verifiable Credentials - Change: When no cryptographic holder binding is required and no holder binding is available in SdJwt credentials, still accept those credentials - Change: OpenId4VPRequestOptions now transports a presentation request directly instead of credentials and presentation mechanism - Change: Return type of `Verifier.verifyPresentationSdJwt` from `VerifyPresentationResult` to `KmmResult` - Change: Return type of `Verifier.verifyPresentationVcJwt` from `VerifyPresentationResult` to `KmmResult` - Change: Return type of `Verifier.verifyPresentationIsoMdoc` from `VerifyPresentationResult` to `KmmResult` - Add: `Verifier.verifyUnsignedVcJws` - Add: `AuthnResponseResult.SuccessUnsigned` - Add: `CreatePresentationResult.VcJws` - Rename: `CreatePresentationResult.Signed` to `CreatePresentationResult.VpJws` - Add method `loadUnitAttestationPop` to `WalletService` - Add data class `LoadUnitAttestationPopInput` to `WalletService` - Deprecate `OAuth2KtorClient` methods `loadClientAttestationJwt` and `signClientAttestationPop`, point to `loadInstanceAttestation` and `loadInstanceAttestationPop` - Deprecate `WalletService` method `loadKeyAttestation`, point to `loadUnitAttestationPop` - Change method `ProofValidator.verifyAttestationProof` to suspend - Add member `statusListTokenResolver` to `CredentialIssuer` - Add member `preferredTtl` to `KeyAttestationRequired` - OAuth 2.0: - In `SimpleAuthorizationService` implement [JWT Response for OAuth Token Introspection](https://datatracker.ietf.org/doc/html/rfc9701/) - In `SimpleAuthorizationService` deprecate `credentialOffer*` methods to prevent configuration identifier mismatches - In `SimpleAuthorizationService` add `offer*` methods to take pairs of credential schemes and representations - SD-JWT: - Fix presentation of nested claims with the last name segment being present in structures with different names (e.g. `country` in `place_of_birth` and `address`) - Dependencies: - Update to [Signum 3.21.0](https://github.com/a-sit-plus/signum/releases/tag/3.21.0) fixing CBOR parsing and tolerating cursed X.509 certificate encodings - Remove code elements deprecated in 5.11.0