From 50f5254eb0c2001a3ffcccc0ae771f8f6cec4b33 Mon Sep 17 00:00:00 2001 From: Keshav Deshpande Date: Fri, 8 May 2026 19:38:35 +0200 Subject: [PATCH] Refactor test class - OID4VCJWTIssuerEndpointTest See #48416 Signed-off-by: Keshav Deshpande --- .../oid4vc/OID4VCJWTIssuerEndpointTest.java | 1704 +++++------------ 1 file changed, 525 insertions(+), 1179 deletions(-) diff --git a/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCJWTIssuerEndpointTest.java b/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCJWTIssuerEndpointTest.java index 6da3f9779fdd..4c46ee29f495 100644 --- a/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCJWTIssuerEndpointTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCJWTIssuerEndpointTest.java @@ -17,7 +17,6 @@ package org.keycloak.tests.oid4vc; import java.io.IOException; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -29,39 +28,24 @@ import java.util.function.Function; import jakarta.ws.rs.BadRequestException; -import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.client.Invocation; import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.UriBuilder; import org.keycloak.TokenVerifier; import org.keycloak.VCFormat; -import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.common.VerificationException; import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Time; -import org.keycloak.constants.OID4VCIConstants; import org.keycloak.crypto.Algorithm; -import org.keycloak.crypto.ECDSASignatureSignerContext; -import org.keycloak.crypto.KeyWrapper; -import org.keycloak.jose.jwk.JWK; -import org.keycloak.jose.jwk.JWKBuilder; -import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSHeader; import org.keycloak.models.Constants; -import org.keycloak.models.RealmModel; import org.keycloak.models.oid4vci.CredentialScopeModel; -import org.keycloak.protocol.oid4vc.OID4VCLoginProtocolFactory; import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint; -import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProvider; import org.keycloak.protocol.oid4vc.issuance.credentialoffer.CredentialOfferState; import org.keycloak.protocol.oid4vc.issuance.credentialoffer.CredentialOfferStorage; -import org.keycloak.protocol.oid4vc.issuance.keybinding.AttestationValidatorUtil; import org.keycloak.protocol.oid4vc.model.Claim; import org.keycloak.protocol.oid4vc.model.ClaimDisplay; import org.keycloak.protocol.oid4vc.model.Claims; @@ -74,34 +58,34 @@ import org.keycloak.protocol.oid4vc.model.ErrorResponse; import org.keycloak.protocol.oid4vc.model.ErrorType; import org.keycloak.protocol.oid4vc.model.JwtProof; -import org.keycloak.protocol.oid4vc.model.KeyAttestationJwtBody; -import org.keycloak.protocol.oid4vc.model.NonceResponse; import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail; import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant; import org.keycloak.protocol.oid4vc.model.Proofs; import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration; import org.keycloak.protocol.oid4vc.model.VerifiableCredential; import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; -import org.keycloak.representations.AccessToken; import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.KeysMetadataRepresentation; import org.keycloak.representations.idm.OAuth2ErrorRepresentation; -import org.keycloak.services.CorsErrorResponseException; -import org.keycloak.services.ErrorResponseException; +import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.managers.AppAuthManager.BearerTokenAuthenticator; -import org.keycloak.services.resources.RealmsResource; import org.keycloak.testframework.annotations.KeycloakIntegrationTest; import org.keycloak.testframework.remote.runonserver.InjectRunOnServer; import org.keycloak.testframework.remote.runonserver.RunOnServerClient; import org.keycloak.testsuite.util.AccountHelper; import org.keycloak.testsuite.util.oauth.AccessTokenResponse; +import org.keycloak.testsuite.util.oauth.oid4vc.CredentialIssuerMetadataResponse; import org.keycloak.testsuite.util.oauth.oid4vc.CredentialOfferResponse; +import org.keycloak.testsuite.util.oauth.oid4vc.CredentialOfferUriResponse; +import org.keycloak.testsuite.util.oauth.oid4vc.Oid4vcCredentialResponse; +import org.keycloak.testsuite.util.oauth.oid4vc.Oid4vcNonceResponse; import org.keycloak.util.JsonSerialization; import com.fasterxml.jackson.core.type.TypeReference; import org.apache.http.HttpStatus; +import org.apache.http.entity.ContentType; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -111,7 +95,6 @@ import static org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProvider.ATTR_REQUEST_ENCRYPTION_REQUIRED; import static org.keycloak.tests.oid4vc.OID4VCProofTestUtils.generateJwtProof; import static org.keycloak.tests.oid4vc.OID4VCProofTestUtils.generateJwtProofWithClaims; -import static org.keycloak.tests.oid4vc.OID4VCProofTestUtils.generateJwtProofWithKidNoAttestation; import static org.keycloak.tests.oid4vc.OID4VCProofTestUtils.jwtProofs; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -140,48 +123,34 @@ public void logout() { void testGetCredentialOfferUriUnsupportedCredential() { String token = getBearerToken(oauth); - runOnServer.run(session -> { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - - OID4VCIssuerEndpoint oid4VCIssuerEndpoint = prepareIssuerEndpoint(session, authenticator); + CredentialOfferUriResponse response = oauth.oid4vc() + .credentialOfferUriRequest("inexistent-id") + .bearerToken(token) + .send(); - CorsErrorResponseException exception = assertThrows( - CorsErrorResponseException.class, - () -> oid4VCIssuerEndpoint.createCredentialOffer("inexistent-id") - ); - assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), exception.getResponse().getStatus(), "Should return BAD_REQUEST"); - }); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode(), + "Should return BAD_REQUEST"); } @Test void testGetCredentialOfferUriUnauthorized() { - runOnServer.run(session -> { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(null); - OID4VCIssuerEndpoint oid4VCIssuerEndpoint = prepareIssuerEndpoint(session, authenticator); + CredentialOfferUriResponse response = oauth.oid4vc() + .credentialOfferUriRequest("test-credential") + .send(); - CorsErrorResponseException exception = assertThrows( - CorsErrorResponseException.class, - () -> oid4VCIssuerEndpoint.createCredentialOffer("test-credential", true, "john") - ); - assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), exception.getResponse().getStatus(), "Should return BAD_REQUEST"); - }); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode(), + "Should return BAD_REQUEST"); } @Test void testGetCredentialOfferUriInvalidToken() { - runOnServer.run(session -> { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString("invalid-token"); - OID4VCIssuerEndpoint oid4VCIssuerEndpoint = prepareIssuerEndpoint(session, authenticator); + CredentialOfferUriResponse response = oauth.oid4vc() + .credentialOfferUriRequest("test-credential") + .bearerToken("invalid-token") + .send(); - CorsErrorResponseException exception = assertThrows( - CorsErrorResponseException.class, - () -> oid4VCIssuerEndpoint.createCredentialOffer("test-credential", true, "john") - ); - assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), exception.getResponse().getStatus(), "Should return BAD_REQUEST"); - }); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode(), + "Should return BAD_REQUEST"); } @Test @@ -192,95 +161,59 @@ public void testGetCredentialOfferURI() { String token = getBearerToken(oauth, client, scopeName); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - OID4VCIssuerEndpoint oid4VCIssuerEndpoint = prepareIssuerEndpoint(session, authenticator); - - Response response = oid4VCIssuerEndpoint.createCredentialOffer(credentialConfigurationId); + CredentialOfferUriResponse response = oauth.oid4vc() + .credentialOfferUriRequest(credentialConfigurationId) + .bearerToken(token) + .send(); - assertEquals(HttpStatus.SC_OK, response.getStatus(), "An offer uri should have been returned."); + assertEquals(HttpStatus.SC_OK, response.getStatusCode(), "An offer uri should have been returned."); - CredentialOfferURI credentialOfferURI = JsonSerialization.mapper.convertValue( - response.getEntity(), - CredentialOfferURI.class - ); + CredentialOfferURI credentialOfferURI = response.getCredentialOfferURI(); - assertNotNull(credentialOfferURI.getNonce(), "A nonce should be included."); - assertNotNull(credentialOfferURI.getIssuer(), "The issuer uri should be provided."); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + assertNotNull(credentialOfferURI.getNonce(), "A nonce should be included."); + assertNotNull(credentialOfferURI.getIssuer(), "The issuer uri should be provided."); } @Test public void testJwtVcIssuerWellKnownContainsDateHeader() { - String metadataUrl = oauth.getBaseUrl() + "/.well-known/jwt-vc-issuer/realms/" + oauth.getRealm(); - try (Client restClient = Keycloak.getClientProvider().newRestEasyClient(null, null, true)) { - WebTarget target = restClient.target(metadataUrl); - try (Response response = target.request().get()) { - assertEquals(Response.Status.OK.getStatusCode(), response.getStatus(), "JWT VC issuer metadata should return 200"); - String dateHeader = response.getHeaderString(HttpHeaders.DATE); - assertNotNull(dateHeader, "Date header must be present"); - assertDoesNotThrow(() -> ZonedDateTime.parse(dateHeader, DateTimeFormatter.RFC_1123_DATE_TIME), - "Date header must be RFC1123 formatted"); - } - } + CredentialIssuerMetadataResponse response = oauth.oid4vc() + .issuerMetadataRequest() + .send(); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode(), "JWT VC issuer metadata should return 200"); + String dateHeader = response.getHeader(HttpHeaders.DATE); + assertNotNull(dateHeader, "Date header must be present"); + assertDoesNotThrow(() -> ZonedDateTime.parse(dateHeader, DateTimeFormatter.RFC_1123_DATE_TIME), + "Date header must be RFC1123 formatted"); } @Test - public void testGetCredentialOfferUnauthorized() { - assertThrows(BadRequestException.class, () -> - withCausePropagation(() -> runOnServer.run(session -> { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(null); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); - Response response = issuerEndpoint.getCredentialOffer("nonce"); - assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getMediaType()); - })) - ); + public void testGetCredentialOfferWithUnknownNonce() { + CredentialOfferResponse response = oauth.oid4vc() + .credentialOfferRequest("some-nonce") + .send(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); } @Test public void testGetCredentialOfferWithoutNonce() { - String token = getBearerToken(oauth); - assertThrows(BadRequestException.class, () -> - withCausePropagation(() -> runOnServer.run(session -> { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); - issuerEndpoint.getCredentialOffer(null); - })) - ); + CredentialOfferResponse response = oauth.oid4vc() + .credentialOfferRequest("") + .send(); + + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatusCode(), + "Should return NOT_FOUND when nonce path segment is missing"); } @Test public void testGetCredentialOfferWithoutAPreparedOffer() { - String token = getBearerToken(oauth); - assertThrows(BadRequestException.class, () -> - withCausePropagation(() -> runOnServer.run(session -> { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); - issuerEndpoint.getCredentialOffer("unpreparedNonce"); - })) - ); - } + CredentialOfferResponse response = oauth.oid4vc() + .credentialOfferRequest("unpreparedNonce") + .send(); - @Test - public void testGetCredentialOfferWithABrokenNote() { - String token = getBearerToken(oauth); - assertThrows(BadRequestException.class, () -> - withCausePropagation(() -> runOnServer.run(session -> { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String nonce = prepareSessionCode(session, authenticator, "invalidNote").key(); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); - issuerEndpoint.getCredentialOffer(nonce); - })) - ); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode(), + "Should return BAD_REQUEST when nonce has no prepared offer"); } @Test @@ -303,76 +236,45 @@ public void testGetCredentialOffer() { // The cache transactions need to be committed }); - runOnServer.run(session -> { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); - Response credentialOfferResponse = issuerEndpoint.getCredentialOffer(nonce); + CredentialOfferResponse response = oauth.oid4vc() + .credentialOfferRequest(nonce) + .send(); - CredentialOfferStorage offerStorage = session.getProvider(CredentialOfferStorage.class); - offerStorage.getOfferStateByNonce(nonce); + assertEquals(HttpStatus.SC_OK, response.getStatusCode(), "The offer should have been returned."); - assertEquals(HttpStatus.SC_OK, credentialOfferResponse.getStatus(), "The offer should have been returned."); - Object credentialOfferEntity = credentialOfferResponse.getEntity(); - assertNotNull(credentialOfferEntity, "An actual offer should be in the response."); + CredentialsOffer retrievedCredentialsOffer = response.getCredentialsOffer(); + assertNotNull(retrievedCredentialsOffer, "An actual offer should be in the response."); - CredentialsOffer retrievedCredentialsOffer = JsonSerialization.mapper.convertValue(credentialOfferEntity, CredentialsOffer.class); - assertEquals("the-issuer", retrievedCredentialsOffer.getCredentialIssuer()); - assertEquals(new PreAuthorizedCodeGrant().setPreAuthorizedCode("the-code"), retrievedCredentialsOffer.getPreAuthorizedGrant()); - assertEquals(List.of("credential-configuration-id"), retrievedCredentialsOffer.getCredentialConfigurationIds()); - }); + assertEquals("the-issuer", retrievedCredentialsOffer.getCredentialIssuer()); + assertEquals(new PreAuthorizedCodeGrant().setPreAuthorizedCode("the-code"), retrievedCredentialsOffer.getPreAuthorizedGrant()); + assertEquals(List.of("credential-configuration-id"), retrievedCredentialsOffer.getCredentialConfigurationIds()); } // ----- requestCredential @Test public void testRequestCredentialUnauthorized() { - runOnServer.run(session -> { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(null); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); + CredentialRequest credentialRequest = new CredentialRequest() + .setCredentialIdentifier("test-credential"); - CredentialRequest credentialRequest = new CredentialRequest() - .setCredentialIdentifier("test-credential"); + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(credentialRequest) + .send(); - String requestPayload; - try { - requestPayload = JsonSerialization.writeValueAsString(credentialRequest); - } catch (IOException e) { - throw new RuntimeException(e); - } - - CorsErrorResponseException exception = assertThrows( - CorsErrorResponseException.class, - () -> issuerEndpoint.requestCredential(requestPayload) - ); - assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), exception.getResponse().getStatus(), "Should return BAD_REQUEST"); - }); + assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), response.getStatusCode(), "Should return UNAUTHORIZED"); } @Test public void testRequestCredentialInvalidToken() { - runOnServer.run(session -> { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString("token"); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); - - CredentialRequest credentialRequest = new CredentialRequest() - .setCredentialIdentifier("test-credential"); + CredentialRequest credentialRequest = new CredentialRequest() + .setCredentialIdentifier("test-credential"); - String requestPayload; - try { - requestPayload = JsonSerialization.writeValueAsString(credentialRequest); - } catch (IOException e) { - throw new RuntimeException(e); - } + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(credentialRequest) + .bearerToken("token") + .send(); - CorsErrorResponseException exception = assertThrows( - CorsErrorResponseException.class, - () -> issuerEndpoint.requestCredential(requestPayload) - ); - assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), exception.getResponse().getStatus(), "Should return BAD_REQUEST"); - }); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode(), "Should return BAD_REQUEST"); } @Test @@ -427,82 +329,59 @@ public void testRequestCredentialNoMatchingCredentialBuilder() throws Throwable public void testRequestCredentialUnsupportedCredential() { String token = getBearerToken(oauth); - assertThrows(BadRequestException.class, () -> - withCausePropagation(() -> runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); + CredentialRequest credentialRequest = new CredentialRequest() + .setCredentialIdentifier("no-such-credential"); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); - CredentialRequest credentialRequest = new CredentialRequest() - .setCredentialIdentifier("no-such-credential"); + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(credentialRequest) + .bearerToken(token) + .send(); - String requestPayload = JsonSerialization.writeValueAsString(credentialRequest); - issuerEndpoint.requestCredential(requestPayload); - } catch (IOException e) { - throw new RuntimeException(e); - } - })) - ); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertNotNull(response.getError()); } @Test public void testPlainJsonRequestWithCredentialResponseEncryptionIsRejected() { String token = getBearerToken(oauth); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); - CredentialIssuer issuerMetadata = new OID4VCIssuerWellKnownProvider(session).getIssuerMetadata(); + CredentialIssuer issuerMetadata = oauth.oid4vc().doIssuerMetadataRequest().getMetadata(); - CredentialResponseEncryption encryption = new CredentialResponseEncryption() - .setEnc("A256GCM") - .setJwk(issuerMetadata.getCredentialRequestEncryption().getJwks().getKeys()[0]); + CredentialResponseEncryption encryption = new CredentialResponseEncryption() + .setEnc("A256GCM") + .setJwk(issuerMetadata.getCredentialRequestEncryption().getJwks().getKeys()[0]); - CredentialRequest credentialRequest = new CredentialRequest() - .setCredentialResponseEncryption(encryption); + CredentialRequest credentialRequest = new CredentialRequest() + .setCredentialResponseEncryption(encryption); - String requestPayload = JsonSerialization.writeValueAsString(credentialRequest); + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(credentialRequest) + .bearerToken(token) + .send(); - try { - issuerEndpoint.requestCredential(requestPayload); - fail("Expected BadRequestException because credential_response_encryption requires encrypted request"); - } catch (BadRequestException e) { - ErrorResponse error = (ErrorResponse) e.getResponse().getEntity(); - assertEquals(ErrorType.INVALID_ENCRYPTION_PARAMETERS.getValue(), error.getError()); - assertTrue(error.getErrorDescription().contains("requires encrypted Credential Request")); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_ENCRYPTION_PARAMETERS.getValue(), response.getError()); + assertTrue(response.getErrorDescription().contains("requires encrypted Credential Request")); } @Test public void testEncryptedLookingPayloadWithJsonContentTypeIsRejected() { String token = getBearerToken(oauth); - runOnServer.run(session -> { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); + // Compact-JWE-like payload (5 segments) to trigger encrypted-payload detection. + // This is intentionally not decryptable; the test targets media-type enforcement first. + String jweLikePayload = "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoidGVzdCJ9.a.b.c.d"; - // Compact-JWE-like payload (5 segments) to trigger encrypted-payload detection. - // This is intentionally not decryptable; the test targets media-type enforcement first. - String jweLikePayload = "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoidGVzdCJ9.a.b.c.d"; + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(new CredentialRequest()) + .payload(jweLikePayload, ContentType.APPLICATION_JSON) + .bearerToken(token) + .send(); - try { - issuerEndpoint.requestCredential(jweLikePayload); - fail("Expected BadRequestException due to encrypted payload without application/jwt content type"); - } catch (BadRequestException e) { - ErrorResponse error = (ErrorResponse) e.getResponse().getEntity(); - // This payload is not valid JSON and not a decryptable JWE, so it is malformed request payload. - assertEquals(ErrorType.INVALID_CREDENTIAL_REQUEST.getValue(), error.getError()); - assertTrue(error.getErrorDescription().contains("Failed to parse JSON request")); - } - }); + // This payload is not valid JSON and not a decryptable JWE, so it is malformed request payload. + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_CREDENTIAL_REQUEST.getValue(), response.getError()); + assertTrue(response.getErrorDescription().contains("Failed to parse JSON request")); } @Test @@ -512,26 +391,17 @@ public void testRequestEncryptionRequiredRejectsUnencryptedJsonRequest() { // Force request encryption requirement for this test. setRealmAttributes(Map.of(ATTR_REQUEST_ENCRYPTION_REQUIRED, "true")); try { - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); + CredentialRequest credentialRequest = new CredentialRequest() + .setCredentialIdentifier("test-credential"); - CredentialRequest credentialRequest = new CredentialRequest() - .setCredentialIdentifier("test-credential"); - String requestPayload = JsonSerialization.writeValueAsString(credentialRequest); + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(credentialRequest) + .bearerToken(token) + .send(); - issuerEndpoint.requestCredential(requestPayload); - fail("Expected BadRequestException because request encryption is required"); - } catch (BadRequestException e) { - ErrorResponse error = (ErrorResponse) e.getResponse().getEntity(); - assertEquals(ErrorType.INVALID_ENCRYPTION_PARAMETERS.getValue(), error.getError()); - assertTrue(error.getErrorDescription().contains("Encryption is required")); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_ENCRYPTION_PARAMETERS.getValue(), response.getError()); + assertTrue(response.getErrorDescription().contains("Encryption is required")); } finally { // Restore default behavior for remaining tests. setRealmAttributes(Map.of(ATTR_REQUEST_ENCRYPTION_REQUIRED, "false")); @@ -625,26 +495,15 @@ public void testRequestCredentialWithNeitherIdSet() { final String scopeName = minimalJwtTypeCredentialScope.getName(); String token = getBearerToken(oauth, client, scopeName); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); + CredentialRequest credentialRequest = new CredentialRequest(); - CredentialRequest credentialRequest = new CredentialRequest(); + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(credentialRequest) + .bearerToken(token) + .send(); - try { - String requestPayload = JsonSerialization.writeValueAsString(credentialRequest); - issuerEndpoint.requestCredential(requestPayload); - fail("Expected BadRequestException due to missing credential identifier or configuration id"); - } catch (BadRequestException e) { - ErrorResponse error = (ErrorResponse) e.getResponse().getEntity(); - assertEquals(ErrorType.INVALID_CREDENTIAL_REQUEST.getValue(), error.getError()); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_CREDENTIAL_REQUEST.getValue(), response.getError()); } @Test @@ -865,364 +724,56 @@ public void testRequestMultipleCredentialsWithProofs() { String cNonce = getCNonce(); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - - String jwtProof1 = generateJwtProof(issuer, cNonce); - String jwtProof2 = generateJwtProof(issuer, cNonce); - Proofs proofs = new Proofs().setJwt(Arrays.asList(jwtProof1, jwtProof2)); - - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(proofs); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - String requestPayload = JsonSerialization.writeValueAsString(request); - - Response response = endpoint.requestCredential(requestPayload); - assertEquals(Response.Status.OK.getStatusCode(), response.getStatus(), "Response status should be OK"); - - CredentialResponse credentialResponse = JsonSerialization.mapper - .convertValue(response.getEntity(), CredentialResponse.class); - - assertNotNull(credentialResponse, "Credential response should not be null"); - assertNotNull(credentialResponse.getCredentials(), "Credentials array should not be null"); - assertEquals(2, credentialResponse.getCredentials().size(), "Should return 2 credentials due to two proofs"); - - // Validate each credential - for (CredentialResponse.Credential credential : credentialResponse.getCredentials()) { - assertNotNull(credential.getCredential(), "Credential should not be null"); - JsonWebToken jsonWebToken; - try { - jsonWebToken = TokenVerifier.create((String) credential.getCredential(), JsonWebToken.class).getToken(); - } catch (VerificationException e) { - fail("Failed to verify JWT: " + e.getMessage()); - return; - } - - assertNotNull(jsonWebToken, "A valid credential string should be returned"); - assertNotNull(jsonWebToken.getOtherClaims().get("vc"), "The credentials should include the vc claim"); - - VerifiableCredential vc = JsonSerialization.mapper.convertValue( - jsonWebToken.getOtherClaims().get("vc"), - VerifiableCredential.class - ); - - assertTrue(vc.getCredentialSubject().getClaims().containsKey("scope-name"), "The scope-name claim should be set"); - assertEquals(scopeName, vc.getCredentialSubject().getClaims().get("scope-name"), "The scope-name claim should match the scope"); - assertTrue(vc.getCredentialSubject().getClaims().containsKey("given_name"), "The given_name claim should be set"); - assertEquals("John", vc.getCredentialSubject().getClaims().get("given_name"), "The given_name claim should be John"); - assertTrue(vc.getCredentialSubject().getClaims().containsKey("email"), "The email claim should be set"); - assertEquals("john@email.cz", vc.getCredentialSubject().getClaims().get("email"), "The email claim should be john@email.cz"); - assertFalse(vc.getCredentialSubject().getClaims().containsKey("AnotherCredentialType"), "Only supported mappers should be evaluated"); - } - } catch (Exception e) { - throw new RuntimeException("Test failed due to: " + e.getMessage(), e); - } - }); - } - - @Test - public void testRequestCredentialWithKidProofWithoutKeyAttestation() { - final String scopeName = jwtTypeCredentialScope.getName(); - String credConfigId = jwtTypeCredentialScope.getAttributes().get(CredentialScopeModel.VC_CONFIGURATION_ID); - - CredentialIssuer credentialIssuer = getCredentialIssuerMetadata(); - OID4VCAuthorizationDetail authDetail = new OID4VCAuthorizationDetail(); - authDetail.setType(OPENID_CREDENTIAL); - authDetail.setCredentialConfigurationId(credConfigId); - authDetail.setLocations(List.of(credentialIssuer.getCredentialIssuer())); - - String authCode = getAuthorizationCode(oauth, client, "john", scopeName); - AccessTokenResponse tokenResponse = getBearerToken(oauth, authCode, authDetail); - String token = tokenResponse.getAccessToken(); - String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0); - String cNonce = getCNonce(); - - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - String kidOnlyJwtProof = generateJwtProofWithKidNoAttestation(issuer, cNonce); - - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(kidOnlyJwtProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - ErrorResponseException ex = assertThrows(ErrorResponseException.class, - () -> endpoint.requestCredential(requestPayload)); - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - - @Test - public void testRequestCredentialWithJwkKeyAttestationAccepted() { - Map requestContext = prepareJwtCredentialRequestContext(); - String token = requestContext.get("token"); - String credentialIdentifier = requestContext.get("credentialIdentifier"); - String cNonce = requestContext.get("cNonce"); - - runOnServer.run(session -> { - RealmModel realm = session.getContext().getRealm(); - String previousTrustedKeys = realm.getAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR); - try { - KeyWrapper attestationSigner = OID4VCProofTestUtils.newEcSigningKey("endpoint-attestation-jwk"); - JWK trustedAttestationJwk = JWKBuilder.create().ec(attestationSigner.getPublicKey()); - trustedAttestationJwk.setKeyId(attestationSigner.getKid()); - trustedAttestationJwk.setAlgorithm(attestationSigner.getAlgorithm()); - realm.setAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR, - JsonSerialization.writeValueAsString(List.of(trustedAttestationJwk))); - - KeyWrapper proofKey = OID4VCProofTestUtils.newEcSigningKey("endpoint-proof-jwk"); - JWK proofJwk = JWKBuilder.create().ec(proofKey.getPublicKey()); - proofJwk.setKeyId(proofKey.getKid()); - proofJwk.setAlgorithm(proofKey.getAlgorithm()); - String attestationJwt = OID4VCProofTestUtils.generateAttestationProof( - attestationSigner, cNonce, List.of(proofJwk), List.of("iso_18045_high"), - List.of("iso_18045_high"), null); + String issuer = credentialIssuer.getCredentialIssuer(); - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - String jwtProof = generateJwtProofWithEmbeddedAttestation( - proofKey, attestationJwt, cNonce, issuer, false); + String jwtProof1 = generateJwtProof(issuer, cNonce); + String jwtProof2 = generateJwtProof(issuer, cNonce); + Proofs proofs = new Proofs().setJwt(Arrays.asList(jwtProof1, jwtProof2)); - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(jwtProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); + CredentialRequest request = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier) + .setProofs(proofs); - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - Response response = endpoint.requestCredential(requestPayload); - assertSingleCredentialResponse(response); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (previousTrustedKeys != null) { - realm.setAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR, previousTrustedKeys); - } else { - realm.removeAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR); - } - } - }); - } - - @Test - public void testRequestCredentialWithKidKeyAttestationAccepted() { - Map requestContext = prepareJwtCredentialRequestContext(); - String token = requestContext.get("token"); - String credentialIdentifier = requestContext.get("credentialIdentifier"); - String cNonce = requestContext.get("cNonce"); - - runOnServer.run(session -> { - RealmModel realm = session.getContext().getRealm(); - String previousTrustedKeys = realm.getAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR); - try { - KeyWrapper attestationSigner = OID4VCProofTestUtils.newEcSigningKey("endpoint-attestation-kid"); - JWK trustedAttestationJwk = JWKBuilder.create().ec(attestationSigner.getPublicKey()); - trustedAttestationJwk.setKeyId(attestationSigner.getKid()); - trustedAttestationJwk.setAlgorithm(attestationSigner.getAlgorithm()); - realm.setAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR, - JsonSerialization.writeValueAsString(List.of(trustedAttestationJwk))); - - KeyWrapper proofKey = OID4VCProofTestUtils.newEcSigningKey("endpoint-proof-kid"); - JWK proofJwk = JWKBuilder.create().ec(proofKey.getPublicKey()); - proofJwk.setKeyId(proofKey.getKid()); - proofJwk.setAlgorithm(proofKey.getAlgorithm()); - String attestationJwt = OID4VCProofTestUtils.generateAttestationProof( - attestationSigner, cNonce, List.of(proofJwk), List.of("iso_18045_high"), - List.of("iso_18045_high"), null); - - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - String jwtProof = generateJwtProofWithEmbeddedAttestation( - proofKey, attestationJwt, cNonce, issuer, true); - - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(jwtProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - Response response = endpoint.requestCredential(requestPayload); - assertSingleCredentialResponse(response); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (previousTrustedKeys != null) { - realm.setAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR, previousTrustedKeys); - } else { - realm.removeAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR); - } - } - }); - } - - @Test - public void testRequestCredentialWithAttestationProofAccepted() { - Map requestContext = prepareJwtCredentialRequestContext(); - String token = requestContext.get("token"); - String credentialIdentifier = requestContext.get("credentialIdentifier"); - String cNonce = requestContext.get("cNonce"); - - runOnServer.run(session -> { - RealmModel realm = session.getContext().getRealm(); - String previousTrustedKeys = realm.getAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR); - try { - KeyWrapper attestationSigner = OID4VCProofTestUtils.newEcSigningKey("endpoint-attestation-proof-type"); - JWK trustedAttestationJwk = JWKBuilder.create().ec(attestationSigner.getPublicKey()); - trustedAttestationJwk.setKeyId(attestationSigner.getKid()); - trustedAttestationJwk.setAlgorithm(attestationSigner.getAlgorithm()); - realm.setAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR, - JsonSerialization.writeValueAsString(List.of(trustedAttestationJwk))); - - KeyWrapper proofKey = OID4VCProofTestUtils.newEcSigningKey("endpoint-proof-attestation-proof-type"); - JWK proofJwk = JWKBuilder.create().ec(proofKey.getPublicKey()); - proofJwk.setKeyId(proofKey.getKid()); - proofJwk.setAlgorithm(proofKey.getAlgorithm()); - String attestationJwt = OID4VCProofTestUtils.generateAttestationProof( - attestationSigner, cNonce, List.of(proofJwk), List.of("iso_18045_high"), - List.of("iso_18045_high"), null); - - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(request) + .bearerToken(token) + .send(); - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setAttestation(List.of(attestationJwt))); - String requestPayload = JsonSerialization.writeValueAsString(request); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode(), "Response status should be OK"); - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - Response response = endpoint.requestCredential(requestPayload); - assertSingleCredentialResponse(response); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (previousTrustedKeys != null) { - realm.setAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR, previousTrustedKeys); - } else { - realm.removeAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR); - } - } - }); - } + CredentialResponse credentialResponse = response.getCredentialResponse(); - @Test - public void testRequestCredentialWithKeyAttestationMismatchedProofKeyRejected() { - Map requestContext = prepareJwtCredentialRequestContext(); - String token = requestContext.get("token"); - String credentialIdentifier = requestContext.get("credentialIdentifier"); - String cNonce = requestContext.get("cNonce"); + assertNotNull(credentialResponse, "Credential response should not be null"); + assertNotNull(credentialResponse.getCredentials(), "Credentials array should not be null"); + assertEquals(2, credentialResponse.getCredentials().size(), "Should return 2 credentials due to two proofs"); - runOnServer.run(session -> { - RealmModel realm = session.getContext().getRealm(); - String previousTrustedKeys = realm.getAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR); + // Validate each credential + for (CredentialResponse.Credential credential : credentialResponse.getCredentials()) { + assertNotNull(credential.getCredential(), "Credential should not be null"); + JsonWebToken jsonWebToken; try { - KeyWrapper attestationSigner = OID4VCProofTestUtils.newEcSigningKey("endpoint-attestation-mismatch"); - JWK trustedAttestationJwk = JWKBuilder.create().ec(attestationSigner.getPublicKey()); - trustedAttestationJwk.setKeyId(attestationSigner.getKid()); - trustedAttestationJwk.setAlgorithm(attestationSigner.getAlgorithm()); - realm.setAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR, - JsonSerialization.writeValueAsString(List.of(trustedAttestationJwk))); - - KeyWrapper proofKey = OID4VCProofTestUtils.newEcSigningKey("endpoint-proof-used"); - KeyWrapper otherKey = OID4VCProofTestUtils.newEcSigningKey("endpoint-proof-attested"); - JWK otherJwk = JWKBuilder.create().ec(otherKey.getPublicKey()); - otherJwk.setKeyId(otherKey.getKid()); - otherJwk.setAlgorithm(otherKey.getAlgorithm()); - String attestationJwt = OID4VCProofTestUtils.generateAttestationProof( - attestationSigner, cNonce, List.of(otherJwk), List.of("iso_18045_high"), - List.of("iso_18045_high"), null); - - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - String jwtProof = generateJwtProofWithEmbeddedAttestation( - proofKey, attestationJwt, cNonce, issuer, false); - - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(jwtProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - ErrorResponseException ex = assertThrows(ErrorResponseException.class, - () -> endpoint.requestCredential(requestPayload)); - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - assertTrue(ex.getErrorDescription().contains("attested_keys")); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (previousTrustedKeys != null) { - realm.setAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR, previousTrustedKeys); - } else { - realm.removeAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR); - } + jsonWebToken = TokenVerifier.create((String) credential.getCredential(), JsonWebToken.class).getToken(); + } catch (VerificationException e) { + fail("Failed to verify JWT: " + e.getMessage()); + return; } - }); - } - - @Test - public void testRequestCredentialWithKeyAttestationMissingExpRejected() { - Map requestContext = prepareJwtCredentialRequestContext(); - String token = requestContext.get("token"); - String credentialIdentifier = requestContext.get("credentialIdentifier"); - String cNonce = requestContext.get("cNonce"); - runOnServer.run(session -> { - RealmModel realm = session.getContext().getRealm(); - String previousTrustedKeys = realm.getAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR); - try { - KeyWrapper attestationSigner = OID4VCProofTestUtils.newEcSigningKey("endpoint-attestation-no-exp"); - JWK trustedAttestationJwk = JWKBuilder.create().ec(attestationSigner.getPublicKey()); - trustedAttestationJwk.setKeyId(attestationSigner.getKid()); - trustedAttestationJwk.setAlgorithm(attestationSigner.getAlgorithm()); - realm.setAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR, - JsonSerialization.writeValueAsString(List.of(trustedAttestationJwk))); - - KeyWrapper proofKey = OID4VCProofTestUtils.newEcSigningKey("endpoint-proof-no-exp"); - JWK proofJwk = JWKBuilder.create().ec(proofKey.getPublicKey()); - proofJwk.setKeyId(proofKey.getKid()); - proofJwk.setAlgorithm(proofKey.getAlgorithm()); - String attestationJwtWithoutExp = generateAttestationProofWithoutExp( - attestationSigner, cNonce, List.of(proofJwk)); + assertNotNull(jsonWebToken, "A valid credential string should be returned"); + assertNotNull(jsonWebToken.getOtherClaims().get("vc"), "The credentials should include the vc claim"); - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - String jwtProof = generateJwtProofWithEmbeddedAttestation( - proofKey, attestationJwtWithoutExp, cNonce, issuer, false); + VerifiableCredential vc = JsonSerialization.mapper.convertValue( + jsonWebToken.getOtherClaims().get("vc"), + VerifiableCredential.class + ); - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(jwtProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - ErrorResponseException ex = assertThrows(ErrorResponseException.class, - () -> endpoint.requestCredential(requestPayload)); - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - assertTrue(ex.getErrorDescription().contains("Missing 'exp' claim")); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (previousTrustedKeys != null) { - realm.setAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR, previousTrustedKeys); - } else { - realm.removeAttribute(OID4VCIConstants.TRUSTED_KEYS_REALM_ATTR); - } - } - }); + assertTrue(vc.getCredentialSubject().getClaims().containsKey("scope-name"), "The scope-name claim should be set"); + assertEquals(scopeName, vc.getCredentialSubject().getClaims().get("scope-name"), "The scope-name claim should match the scope"); + assertTrue(vc.getCredentialSubject().getClaims().containsKey("given_name"), "The given_name claim should be set"); + assertEquals("John", vc.getCredentialSubject().getClaims().get("given_name"), "The given_name claim should be John"); + assertTrue(vc.getCredentialSubject().getClaims().containsKey("email"), "The email claim should be set"); + assertEquals("john@email.cz", vc.getCredentialSubject().getClaims().get("email"), "The email claim should be john@email.cz"); + assertFalse(vc.getCredentialSubject().getClaims().containsKey("AnotherCredentialType"), "Only supported mappers should be evaluated"); + } } @Test @@ -1242,28 +793,22 @@ public void testRequestCredentialWithHs256JwtProofRejected() { String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0); String cNonce = getCNonce(); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - String validJwtProof = generateJwtProof(issuer, cNonce); - String hs256JwtProof = withModifiedHeaderClaim(validJwtProof, "alg", "HS256"); + String issuer = credentialIssuer.getCredentialIssuer(); + String validJwtProof = generateJwtProof(issuer, cNonce); + String hs256JwtProof = withModifiedHeaderClaim(validJwtProof, "alg", "HS256"); - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(hs256JwtProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - ErrorResponseException ex = assertThrows(ErrorResponseException.class, - () -> endpoint.requestCredential(requestPayload)); - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - assertTrue(ex.getErrorDescription().contains("Proof signature algorithm not supported")); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + CredentialRequest request = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier) + .setProofs(new Proofs().setJwt(List.of(hs256JwtProof))); + + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(request) + .bearerToken(token) + .send(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_PROOF.getValue(), response.getError()); + assertTrue(response.getErrorDescription().contains("Proof signature algorithm not supported")); } @Test @@ -1283,27 +828,21 @@ public void testRequestCredentialWithPrivateJwkMaterialInHeaderRejected() { String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0); String cNonce = getCNonce(); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - String validJwtProof = generateJwtProof(issuer, cNonce); - String proofWithPrivateJwkMaterial = withPrivateJwkMaterialInHeader(validJwtProof); + String issuer = credentialIssuer.getCredentialIssuer(); + String validJwtProof = generateJwtProof(issuer, cNonce); + String proofWithPrivateJwkMaterial = withPrivateJwkMaterialInHeader(validJwtProof); - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(proofWithPrivateJwkMaterial))); - String requestPayload = JsonSerialization.writeValueAsString(request); + CredentialRequest request = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier) + .setProofs(new Proofs().setJwt(List.of(proofWithPrivateJwkMaterial))); - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - ErrorResponseException ex = assertThrows(ErrorResponseException.class, - () -> endpoint.requestCredential(requestPayload)); - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(request) + .bearerToken(token) + .send(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_PROOF.getValue(), response.getError()); } @Test @@ -1323,30 +862,23 @@ public void testRequestCredentialWithMissingIssuerInClientBoundFlowAllowed() { String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0); String cNonce = getCNonce(); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - String missingIssuerProof = generateJwtProofWithClaims(List.of(issuer), cNonce, null, null, null, null); + String issuer = credentialIssuer.getCredentialIssuer(); + String missingIssuerProof = generateJwtProofWithClaims(List.of(issuer), cNonce, null, null, null, null); - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(missingIssuerProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - Response response = endpoint.requestCredential(requestPayload); - assertEquals(Response.Status.OK.getStatusCode(), response.getStatus(), "Response status should be OK"); - CredentialResponse credentialResponse = JsonSerialization.mapper - .convertValue(response.getEntity(), CredentialResponse.class); - assertNotNull(credentialResponse, "Credential response should not be null"); - assertNotNull(credentialResponse.getCredentials(), "Credentials should not be null"); - assertEquals(1, credentialResponse.getCredentials().size(), "Expected exactly one credential"); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + CredentialRequest request = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier) + .setProofs(new Proofs().setJwt(List.of(missingIssuerProof))); + + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(request) + .bearerToken(token) + .send(); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode(), "Response status should be OK"); + CredentialResponse credentialResponse = response.getCredentialResponse(); + assertNotNull(credentialResponse, "Credential response should not be null"); + assertNotNull(credentialResponse.getCredentials(), "Credentials should not be null"); + assertEquals(1, credentialResponse.getCredentials().size(), "Expected exactly one credential"); } @Test @@ -1366,28 +898,22 @@ public void testRequestCredentialWithWrongIssuerInClientBoundFlowRejected() { String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0); String cNonce = getCNonce(); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - String wrongIssuerProof = generateJwtProofWithClaims( - List.of(issuer), cNonce, "wrong-client-id", null, null, null); + String issuer = credentialIssuer.getCredentialIssuer(); + String wrongIssuerProof = generateJwtProofWithClaims( + List.of(issuer), cNonce, "wrong-client-id", null, null, null); - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(wrongIssuerProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - ErrorResponseException ex = assertThrows(ErrorResponseException.class, - () -> endpoint.requestCredential(requestPayload)); - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - assertTrue(ex.getErrorDescription().contains("Issuer claim must be the client_id")); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + CredentialRequest request = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier) + .setProofs(new Proofs().setJwt(List.of(wrongIssuerProof))); + + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(request) + .bearerToken(token) + .send(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_PROOF.getValue(), response.getError()); + assertTrue(response.getErrorDescription().contains("Issuer claim must be the client_id")); } @Test @@ -1407,33 +933,27 @@ public void testRequestCredentialWithMultipleAudiencesRejected() { String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0); String cNonce = getCNonce(); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - String multiAudProof = generateJwtProofWithClaims( - List.of(issuer, "https://unrelated.example"), - cNonce, - OID4VCI_CLIENT_ID, - null, - null, - null); - - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(multiAudProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - ErrorResponseException ex = assertThrows(ErrorResponseException.class, - () -> endpoint.requestCredential(requestPayload)); - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - assertTrue(ex.getErrorDescription().contains("Audience claim must be single value")); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + String issuer = credentialIssuer.getCredentialIssuer(); + String multiAudProof = generateJwtProofWithClaims( + List.of(issuer, "https://unrelated.example"), + cNonce, + OID4VCI_CLIENT_ID, + null, + null, + null); + + CredentialRequest request = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier) + .setProofs(new Proofs().setJwt(List.of(multiAudProof))); + + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(request) + .bearerToken(token) + .send(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_PROOF.getValue(), response.getError()); + assertTrue(response.getErrorDescription().contains("Audience claim must be single value")); } @Test @@ -1453,34 +973,28 @@ public void testRequestCredentialWithFutureIatRejected() { String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0); String cNonce = getCNonce(); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - long now = System.currentTimeMillis() / 1000L; - String futureIatProof = generateJwtProofWithClaims( - List.of(issuer), - cNonce, - OID4VCI_CLIENT_ID, - now + 120, - null, - null); - - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(futureIatProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - ErrorResponseException ex = assertThrows(ErrorResponseException.class, - () -> endpoint.requestCredential(requestPayload)); - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - assertTrue(ex.getErrorDescription().contains("Proof iat is in the future")); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + String issuer = credentialIssuer.getCredentialIssuer(); + long now = System.currentTimeMillis() / 1000L; + String futureIatProof = generateJwtProofWithClaims( + List.of(issuer), + cNonce, + OID4VCI_CLIENT_ID, + now + 120, + null, + null); + + CredentialRequest request = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier) + .setProofs(new Proofs().setJwt(List.of(futureIatProof))); + + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(request) + .bearerToken(token) + .send(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_PROOF.getValue(), response.getError()); + assertTrue(response.getErrorDescription().contains("Proof iat is in the future")); } @Test @@ -1500,34 +1014,28 @@ public void testRequestCredentialWithExpiredExpRejected() { String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0); String cNonce = getCNonce(); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - long now = System.currentTimeMillis() / 1000L; - String expiredExpProof = generateJwtProofWithClaims( - List.of(issuer), - cNonce, - OID4VCI_CLIENT_ID, - now, - now - 1, - null); - - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(expiredExpProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - ErrorResponseException ex = assertThrows(ErrorResponseException.class, - () -> endpoint.requestCredential(requestPayload)); - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - assertTrue(ex.getErrorDescription().contains("Proof has expired")); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + String issuer = credentialIssuer.getCredentialIssuer(); + long now = System.currentTimeMillis() / 1000L; + String expiredExpProof = generateJwtProofWithClaims( + List.of(issuer), + cNonce, + OID4VCI_CLIENT_ID, + now, + now - 1, + null); + + CredentialRequest request = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier) + .setProofs(new Proofs().setJwt(List.of(expiredExpProof))); + + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(request) + .bearerToken(token) + .send(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_PROOF.getValue(), response.getError()); + assertTrue(response.getErrorDescription().contains("Proof has expired")); } @Test @@ -1547,34 +1055,28 @@ public void testRequestCredentialWithFutureNbfRejected() { String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0); String cNonce = getCNonce(); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - long now = System.currentTimeMillis() / 1000L; - String futureNbfProof = generateJwtProofWithClaims( - List.of(issuer), - cNonce, - OID4VCI_CLIENT_ID, - now, - null, - now + 120); - - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(futureNbfProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - ErrorResponseException ex = assertThrows(ErrorResponseException.class, - () -> endpoint.requestCredential(requestPayload)); - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - assertTrue(ex.getErrorDescription().contains("Proof is not yet valid")); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + String issuer = credentialIssuer.getCredentialIssuer(); + long now = System.currentTimeMillis() / 1000L; + String futureNbfProof = generateJwtProofWithClaims( + List.of(issuer), + cNonce, + OID4VCI_CLIENT_ID, + now, + null, + now + 120); + + CredentialRequest request = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier) + .setProofs(new Proofs().setJwt(List.of(futureNbfProof))); + + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(request) + .bearerToken(token) + .send(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_PROOF.getValue(), response.getError()); + assertTrue(response.getErrorDescription().contains("Proof is not yet valid")); } @Test @@ -1594,28 +1096,22 @@ public void testRequestCredentialWithTrustChainHeaderRejected() { String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0); String cNonce = getCNonce(); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - String validJwtProof = generateJwtProof(issuer, cNonce); - String trustChainProof = withModifiedHeaderClaim(validJwtProof, "trust_chain", List.of("dummy-trust-chain-entry")); + String issuer = credentialIssuer.getCredentialIssuer(); + String validJwtProof = generateJwtProof(issuer, cNonce); + String trustChainProof = withModifiedHeaderClaim(validJwtProof, "trust_chain", List.of("dummy-trust-chain-entry")); - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(trustChainProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - ErrorResponseException ex = assertThrows(ErrorResponseException.class, - () -> endpoint.requestCredential(requestPayload)); - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - assertTrue(ex.getErrorDescription().contains("trust_chain")); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + CredentialRequest request = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier) + .setProofs(new Proofs().setJwt(List.of(trustChainProof))); + + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(request) + .bearerToken(token) + .send(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_PROOF.getValue(), response.getError()); + assertTrue(response.getErrorDescription().contains("trust_chain")); } @Test @@ -1635,28 +1131,22 @@ public void testRequestCredentialWithKidAndJwkHeadersRejected() { String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0); String cNonce = getCNonce(); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - String issuer = OID4VCIssuerWellKnownProvider.getIssuer(session.getContext()); - String validJwtProof = generateJwtProof(issuer, cNonce); - String kidAndJwkProof = withModifiedHeaderClaim(validJwtProof, "kid", "some-kid"); + String issuer = credentialIssuer.getCredentialIssuer(); + String validJwtProof = generateJwtProof(issuer, cNonce); + String kidAndJwkProof = withModifiedHeaderClaim(validJwtProof, "kid", "some-kid"); - CredentialRequest request = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier) - .setProofs(new Proofs().setJwt(List.of(kidAndJwkProof))); - String requestPayload = JsonSerialization.writeValueAsString(request); - - OID4VCIssuerEndpoint endpoint = prepareIssuerEndpoint(session, authenticator); - ErrorResponseException ex = assertThrows(ErrorResponseException.class, - () -> endpoint.requestCredential(requestPayload)); - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - assertTrue(ex.getErrorDescription().contains("mutually exclusive")); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + CredentialRequest request = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier) + .setProofs(new Proofs().setJwt(List.of(kidAndJwkProof))); + + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(request) + .bearerToken(token) + .send(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.INVALID_PROOF.getValue(), response.getError()); + assertTrue(response.getErrorDescription().contains("mutually exclusive")); } @Test @@ -1670,154 +1160,139 @@ public void testGetJwtVcConfigFromMetadata() { String expectedNonceEndpoint = expectedIssuer + "/protocol/oid4vc/" + OID4VCIssuerEndpoint.NONCE_PATH; final String expectedAuthorizationServer = expectedIssuer; - runOnServer.run(session -> { - OID4VCIssuerWellKnownProvider oid4VCIssuerWellKnownProvider = new OID4VCIssuerWellKnownProvider(session); - CredentialIssuer credentialIssuer = oid4VCIssuerWellKnownProvider.getIssuerMetadata(); - - assertEquals(expectedIssuer, credentialIssuer.getCredentialIssuer(), "The correct issuer should be included."); - assertEquals(expectedCredentialsEndpoint, credentialIssuer.getCredentialEndpoint(), "The correct credentials endpoint should be included."); - assertEquals(expectedNonceEndpoint, credentialIssuer.getNonceEndpoint(), "The correct nonce endpoint should be included."); - assertEquals(1, credentialIssuer.getAuthorizationServers().size(), "Since the authorization server is equal to the issuer, just 1 should be returned."); - assertEquals(expectedAuthorizationServer, credentialIssuer.getAuthorizationServers().get(0), "The expected server should have been returned."); - assertTrue(credentialIssuer.getCredentialsSupported().containsKey(credentialConfigurationId), "The jwt_vc-credential should be supported."); - - SupportedCredentialConfiguration jwtVcConfig = credentialIssuer.getCredentialsSupported().get(credentialConfigurationId); - assertEquals(scopeName, jwtVcConfig.getScope(), "The jwt_vc-credential should offer type test-credential"); - assertEquals(VCFormat.JWT_VC, jwtVcConfig.getFormat(), "The jwt_vc-credential should be offered in the jwt_vc format."); - - Claims jwtVcClaims = jwtVcConfig.getCredentialMetadata() != null ? jwtVcConfig.getCredentialMetadata().getClaims() : null; - assertNotNull(jwtVcClaims, "The jwt_vc-credential can optionally provide a claims claim."); - assertEquals(8, jwtVcClaims.size()); - - { - Claim claim = jwtVcClaims.get(0); - assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.id"); - assertEquals("id", claim.getPath().get(1), "credentialSubject.id mapped correctly"); - assertFalse(claim.isMandatory(), "credentialSubject.id is not mandatory"); - assertNull(claim.getDisplay(), "credentialSubject.id has no display"); - } - { - Claim claim = jwtVcClaims.get(1); - assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.given_name"); - assertEquals("given_name", claim.getPath().get(1), "credentialSubject.given_name mapped correctly"); - assertFalse(claim.isMandatory(), "credentialSubject.given_name is not mandatory"); - assertNotNull(claim.getDisplay(), "credentialSubject.given_name has display"); - assertEquals(15, claim.getDisplay().size()); - for (ClaimDisplay givenNameDisplay : claim.getDisplay()) { - assertNotNull(givenNameDisplay.getName()); - assertNotNull(givenNameDisplay.getLocale()); - } - } - { - Claim claim = jwtVcClaims.get(2); - assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.family_name"); - assertEquals("family_name", claim.getPath().get(1), "credentialSubject.family_name mapped correctly"); - assertFalse(claim.isMandatory(), "credentialSubject.family_name is not mandatory"); - assertNotNull(claim.getDisplay(), "credentialSubject.family_name has display"); - assertEquals(15, claim.getDisplay().size()); - for (ClaimDisplay familyNameDisplay : claim.getDisplay()) { - assertNotNull(familyNameDisplay.getName()); - assertNotNull(familyNameDisplay.getLocale()); - } - } - { - Claim claim = jwtVcClaims.get(3); - assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.birthdate"); - assertEquals("birthdate", claim.getPath().get(1), "credentialSubject.birthdate mapped correctly"); - assertFalse(claim.isMandatory(), "credentialSubject.birthdate is not mandatory"); - assertNotNull(claim.getDisplay(), "credentialSubject.birthdate has display"); - assertEquals(15, claim.getDisplay().size()); - for (ClaimDisplay birthDateDisplay : claim.getDisplay()) { - assertNotNull(birthDateDisplay.getName()); - assertNotNull(birthDateDisplay.getLocale()); - } - } - { - Claim claim = jwtVcClaims.get(4); - assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.email"); - assertEquals("email", claim.getPath().get(1), "credentialSubject.email mapped correctly"); - assertFalse(claim.isMandatory(), "credentialSubject.email is not mandatory"); - assertNotNull(claim.getDisplay(), "credentialSubject.email has display"); - assertEquals(15, claim.getDisplay().size()); - for (ClaimDisplay birthDateDisplay : claim.getDisplay()) { - assertNotNull(birthDateDisplay.getName()); - assertNotNull(birthDateDisplay.getLocale()); - } + CredentialIssuer credentialIssuer = oauth.oid4vc().doIssuerMetadataRequest().getMetadata(); + + assertEquals(expectedIssuer, credentialIssuer.getCredentialIssuer(), "The correct issuer should be included."); + assertEquals(expectedCredentialsEndpoint, credentialIssuer.getCredentialEndpoint(), "The correct credentials endpoint should be included."); + assertEquals(expectedNonceEndpoint, credentialIssuer.getNonceEndpoint(), "The correct nonce endpoint should be included."); + assertEquals(1, credentialIssuer.getAuthorizationServers().size(), "Since the authorization server is equal to the issuer, just 1 should be returned."); + assertEquals(expectedAuthorizationServer, credentialIssuer.getAuthorizationServers().get(0), "The expected server should have been returned."); + assertTrue(credentialIssuer.getCredentialsSupported().containsKey(credentialConfigurationId), "The jwt_vc-credential should be supported."); + + SupportedCredentialConfiguration jwtVcConfig = credentialIssuer.getCredentialsSupported().get(credentialConfigurationId); + assertEquals(scopeName, jwtVcConfig.getScope(), "The jwt_vc-credential should offer type test-credential"); + assertEquals(VCFormat.JWT_VC, jwtVcConfig.getFormat(), "The jwt_vc-credential should be offered in the jwt_vc format."); + + Claims jwtVcClaims = jwtVcConfig.getCredentialMetadata() != null ? jwtVcConfig.getCredentialMetadata().getClaims() : null; + assertNotNull(jwtVcClaims, "The jwt_vc-credential can optionally provide a claims claim."); + assertEquals(8, jwtVcClaims.size()); + + { + Claim claim = jwtVcClaims.get(0); + assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.id"); + assertEquals("id", claim.getPath().get(1), "credentialSubject.id mapped correctly"); + assertFalse(claim.isMandatory(), "credentialSubject.id is not mandatory"); + assertNull(claim.getDisplay(), "credentialSubject.id has no display"); + } + { + Claim claim = jwtVcClaims.get(1); + assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.given_name"); + assertEquals("given_name", claim.getPath().get(1), "credentialSubject.given_name mapped correctly"); + assertFalse(claim.isMandatory(), "credentialSubject.given_name is not mandatory"); + assertNotNull(claim.getDisplay(), "credentialSubject.given_name has display"); + assertEquals(15, claim.getDisplay().size()); + for (ClaimDisplay givenNameDisplay : claim.getDisplay()) { + assertNotNull(givenNameDisplay.getName()); + assertNotNull(givenNameDisplay.getLocale()); } - { - Claim claim = jwtVcClaims.get(5); - assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.address_locality"); - assertEquals("address", claim.getPath().get(1), "credentialSubject.address.locality mapped correctly (parent claim in path)"); - assertEquals("locality", claim.getPath().get(2), "credentialSubject.address.locality mapped correctly (nested claim in path)"); - assertFalse(claim.isMandatory(), "credentialSubject.address.locality is not mandatory"); - assertNull(claim.getDisplay(), "credentialSubject.address.locality has no display"); + } + { + Claim claim = jwtVcClaims.get(2); + assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.family_name"); + assertEquals("family_name", claim.getPath().get(1), "credentialSubject.family_name mapped correctly"); + assertFalse(claim.isMandatory(), "credentialSubject.family_name is not mandatory"); + assertNotNull(claim.getDisplay(), "credentialSubject.family_name has display"); + assertEquals(15, claim.getDisplay().size()); + for (ClaimDisplay familyNameDisplay : claim.getDisplay()) { + assertNotNull(familyNameDisplay.getName()); + assertNotNull(familyNameDisplay.getLocale()); } - { - Claim claim = jwtVcClaims.get(6); - assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.address.street_address"); - assertEquals("address", claim.getPath().get(1), "credentialSubject.address.street_address mapped correctly (parent claim in path)"); - assertEquals("street_address", claim.getPath().get(2), "credentialSubject.address.street_address mapped correctly (nested claim in path)"); - assertFalse(claim.isMandatory(), "credentialSubject.address.street_address is not mandatory"); - assertNull(claim.getDisplay(), "credentialSubject.address.street_address has no display"); + } + { + Claim claim = jwtVcClaims.get(3); + assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.birthdate"); + assertEquals("birthdate", claim.getPath().get(1), "credentialSubject.birthdate mapped correctly"); + assertFalse(claim.isMandatory(), "credentialSubject.birthdate is not mandatory"); + assertNotNull(claim.getDisplay(), "credentialSubject.birthdate has display"); + assertEquals(15, claim.getDisplay().size()); + for (ClaimDisplay birthDateDisplay : claim.getDisplay()) { + assertNotNull(birthDateDisplay.getName()); + assertNotNull(birthDateDisplay.getLocale()); } - { - Claim claim = jwtVcClaims.get(7); - assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.scope-name"); - assertEquals("scope-name", claim.getPath().get(1), "credentialSubject.scope-name mapped correctly"); - assertFalse(claim.isMandatory(), "credentialSubject.scope-name is not mandatory"); - assertNull(claim.getDisplay(), "credentialSubject.scope-name has no display"); + } + { + Claim claim = jwtVcClaims.get(4); + assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.email"); + assertEquals("email", claim.getPath().get(1), "credentialSubject.email mapped correctly"); + assertFalse(claim.isMandatory(), "credentialSubject.email is not mandatory"); + assertNotNull(claim.getDisplay(), "credentialSubject.email has display"); + assertEquals(15, claim.getDisplay().size()); + for (ClaimDisplay birthDateDisplay : claim.getDisplay()) { + assertNotNull(birthDateDisplay.getName()); + assertNotNull(birthDateDisplay.getLocale()); } + } + { + Claim claim = jwtVcClaims.get(5); + assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.address_locality"); + assertEquals("address", claim.getPath().get(1), "credentialSubject.address.locality mapped correctly (parent claim in path)"); + assertEquals("locality", claim.getPath().get(2), "credentialSubject.address.locality mapped correctly (nested claim in path)"); + assertFalse(claim.isMandatory(), "credentialSubject.address.locality is not mandatory"); + assertNull(claim.getDisplay(), "credentialSubject.address.locality has no display"); + } + { + Claim claim = jwtVcClaims.get(6); + assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.address.street_address"); + assertEquals("address", claim.getPath().get(1), "credentialSubject.address.street_address mapped correctly (parent claim in path)"); + assertEquals("street_address", claim.getPath().get(2), "credentialSubject.address.street_address mapped correctly (nested claim in path)"); + assertFalse(claim.isMandatory(), "credentialSubject.address.street_address is not mandatory"); + assertNull(claim.getDisplay(), "credentialSubject.address.street_address has no display"); + } + { + Claim claim = jwtVcClaims.get(7); + assertEquals(CREDENTIAL_SUBJECT, claim.getPath().get(0), "Has credentialSubject.scope-name"); + assertEquals("scope-name", claim.getPath().get(1), "credentialSubject.scope-name mapped correctly"); + assertFalse(claim.isMandatory(), "credentialSubject.scope-name is not mandatory"); + assertNull(claim.getDisplay(), "credentialSubject.scope-name has no display"); + } - assertNotNull(jwtVcConfig.getCredentialDefinition(), "The jwt_vc-credential should offer credential_definition"); - assertNull(jwtVcConfig.getVct(), "JWT_VC credentials should not have vct"); - - assertTrue(jwtVcConfig.getCryptographicBindingMethodsSupported().contains(CredentialScopeModel.CRYPTOGRAPHIC_BINDING_METHODS_DEFAULT), - "The jwt_vc-credential should contain a cryptographic binding method supported named jwk"); - assertTrue(jwtVcConfig.getCredentialSigningAlgValuesSupported().contains("RS256"), - "The jwt_vc-credential should contain a credential signing algorithm named RS256"); - - assertTrue(credentialIssuer.getCredentialsSupported() - .get(credentialConfigurationId) - .getProofTypesSupported() - .getSupportedProofTypes() - .get("jwt") - .getSigningAlgorithmsSupported() - .contains("RS256"), - "The jwt_vc-credential should support a proof of type jwt with signing algorithm RS256"); - - String expectedDisplay = (jwtVcConfig.getCredentialMetadata() != null && jwtVcConfig.getCredentialMetadata().getDisplay() != null) - ? jwtVcConfig.getCredentialMetadata().getDisplay().get(0).getName() - : null; - assertEquals(credentialConfigurationId, expectedDisplay, "The jwt_vc-credential should display as Test Credential"); - }); + assertNotNull(jwtVcConfig.getCredentialDefinition(), "The jwt_vc-credential should offer credential_definition"); + assertNull(jwtVcConfig.getVct(), "JWT_VC credentials should not have vct"); + + assertTrue(jwtVcConfig.getCryptographicBindingMethodsSupported().contains(CredentialScopeModel.CRYPTOGRAPHIC_BINDING_METHODS_DEFAULT), + "The jwt_vc-credential should contain a cryptographic binding method supported named jwk"); + assertTrue(jwtVcConfig.getCredentialSigningAlgValuesSupported().contains("RS256"), + "The jwt_vc-credential should contain a credential signing algorithm named RS256"); + + assertTrue(credentialIssuer.getCredentialsSupported() + .get(credentialConfigurationId) + .getProofTypesSupported() + .getSupportedProofTypes() + .get("jwt") + .getSigningAlgorithmsSupported() + .contains("RS256"), + "The jwt_vc-credential should support a proof of type jwt with signing algorithm RS256"); + + String expectedDisplay = (jwtVcConfig.getCredentialMetadata() != null && jwtVcConfig.getCredentialMetadata().getDisplay() != null) + ? jwtVcConfig.getCredentialMetadata().getDisplay().get(0).getName() + : null; + assertEquals(credentialConfigurationId, expectedDisplay, "The jwt_vc-credential should display as Test Credential"); } @Test public void testRequestCredentialWithUnknownCredentialIdentifier() { String token = getBearerToken(oauth, client, jwtTypeCredentialScope.getName()); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); - - CredentialRequest credentialRequest = new CredentialRequest() - .setCredentialIdentifier("unknown-credential-identifier"); + CredentialRequest credentialRequest = new CredentialRequest() + .setCredentialIdentifier("unknown-credential-identifier"); - String requestPayload = JsonSerialization.writeValueAsString(credentialRequest); + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(credentialRequest) + .bearerToken(token) + .send(); - try { - issuerEndpoint.requestCredential(requestPayload); - fail("Expected BadRequestException due to unknown credential identifier"); - } catch (BadRequestException e) { - ErrorResponse error = (ErrorResponse) e.getResponse().getEntity(); - assertEquals(ErrorType.UNKNOWN_CREDENTIAL_IDENTIFIER.getValue(), error.getError()); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode()); + assertEquals(ErrorType.UNKNOWN_CREDENTIAL_IDENTIFIER.getValue(), response.getError()); } @Test @@ -1909,57 +1384,52 @@ public void testProofToProofsConversion() { List authDetailsResponse = tokenResponse.getOID4VCAuthorizationDetails(); String credentialIdentifier = authDetailsResponse.get(0).getCredentialIdentifiers().get(0); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); + try { + // Test 1: Create a request with single proof field - should be converted to proofs array + CredentialRequest requestWithProof = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier); - // Test 1: Create a request with single proof field - should be converted to proofs array - CredentialRequest requestWithProof = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier); + JwtProof singleProof = new JwtProof() + .setJwt("dummy-jwt") + .setProofType("jwt"); + requestWithProof.setProof(singleProof); - JwtProof singleProof = new JwtProof() - .setJwt("dummy-jwt") - .setProofType("jwt"); - requestWithProof.setProof(singleProof); + String requestPayload = JsonSerialization.writeValueAsString(requestWithProof); - String requestPayload = JsonSerialization.writeValueAsString(requestWithProof); + Oid4vcCredentialResponse response1 = oauth.oid4vc() + .credentialRequest(null) + .payload(requestPayload, ContentType.APPLICATION_JSON) + .bearerToken(token) + .send(); - try { - issuerEndpoint.requestCredential(requestPayload); - fail(); - } catch (ErrorResponseException ex) { - assertEquals(ErrorType.INVALID_PROOF.getValue(), ex.getError()); - assertEquals("Could not validate JWT proof", ex.getErrorDescription()); - } + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response1.getStatusCode()); + assertEquals(ErrorType.INVALID_PROOF.getValue(), response1.getError()); + assertEquals("Could not validate JWT proof", response1.getErrorDescription()); - // Test 2: Create a request with both proof and proofs fields - should fail validation - CredentialRequest requestWithBoth = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier); + // Test 2: Create a request with both proof and proofs fields - should fail validation + CredentialRequest requestWithBoth = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier); - requestWithBoth.setProof(singleProof); + requestWithBoth.setProof(singleProof); - Proofs proofsArray = new Proofs(); - proofsArray.setJwt(List.of("dummy-jwt")); - requestWithBoth.setProofs(proofsArray); + Proofs proofsArray = new Proofs(); + proofsArray.setJwt(List.of("dummy-jwt")); + requestWithBoth.setProofs(proofsArray); - String bothFieldsPayload = JsonSerialization.writeValueAsString(requestWithBoth); + String bothFieldsPayload = JsonSerialization.writeValueAsString(requestWithBoth); - try { - issuerEndpoint.requestCredential(bothFieldsPayload); - fail("Expected BadRequestException when both proof and proofs are provided"); - } catch (BadRequestException e) { - int statusCode = e.getResponse().getStatus(); - assertEquals(400, statusCode, "Expected HTTP 400 Bad Request"); - ErrorResponse error = (ErrorResponse) e.getResponse().getEntity(); - assertEquals(ErrorType.INVALID_CREDENTIAL_REQUEST.getValue(), error.getError()); - assertEquals("Both 'proof' and 'proofs' must not be present at the same time", error.getErrorDescription()); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + Oid4vcCredentialResponse response2 = oauth.oid4vc() + .credentialRequest(null) + .payload(bothFieldsPayload, ContentType.APPLICATION_JSON) + .bearerToken(token) + .send(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response2.getStatusCode(), "Expected HTTP 400 Bad Request"); + assertEquals(ErrorType.INVALID_CREDENTIAL_REQUEST.getValue(), response2.getError()); + assertEquals("Both 'proof' and 'proofs' must not be present at the same time", response2.getErrorDescription()); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Test @@ -1993,30 +1463,21 @@ public void testCredentialRequestWithOptionalClientScope() { List authDetailsResponse = tokenResponse.getOID4VCAuthorizationDetails(); String credentialIdentifier = authDetailsResponse.get(0).getCredentialIdentifiers().get(0); - runOnServer.run(session -> { - try { - BearerTokenAuthenticator authenticator = new BearerTokenAuthenticator(session); - authenticator.setTokenString(token); - OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator); - - CredentialRequest credentialRequest = new CredentialRequest() - .setCredentialIdentifier(credentialIdentifier); + CredentialRequest credentialRequest = new CredentialRequest() + .setCredentialIdentifier(credentialIdentifier); - String requestPayload = JsonSerialization.writeValueAsString(credentialRequest); - Response credentialResponse = issuerEndpoint.requestCredential(requestPayload); + Oid4vcCredentialResponse response = oauth.oid4vc() + .credentialRequest(credentialRequest) + .bearerToken(token) + .send(); - assertEquals(HttpStatus.SC_OK, credentialResponse.getStatus(), "The credential request should succeed for Optional client scope"); - assertNotNull(credentialResponse.getEntity(), "A credential should be returned"); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode(), "The credential request should succeed for Optional client scope"); + assertNotNull(response.getCredentialResponse(), "A credential response should be returned"); - CredentialResponse credentialResponseVO = JsonSerialization.mapper - .convertValue(credentialResponse.getEntity(), CredentialResponse.class); + CredentialResponse credentialResponseVO = response.getCredentialResponse(); - assertNotNull(credentialResponseVO.getCredentials(), "Credentials array should not be null"); - assertFalse(credentialResponseVO.getCredentials().isEmpty(), "Credentials array should not be empty"); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + assertNotNull(credentialResponseVO.getCredentials(), "Credentials array should not be null"); + assertFalse(credentialResponseVO.getCredentials().isEmpty(), "Credentials array should not be empty"); } @Test @@ -2080,10 +1541,10 @@ public void testCannotAssignOid4vciScopeWhenRealmDisabled() { oid4vciScope = registerOptionalClientScope(oid4vciScope); - runOnServer.run(session -> { - RealmModel realm = session.getContext().getRealm(); - realm.setVerifiableCredentialsEnabled(false); - }); + // Disable Verifiable Credentials for the realm + RealmRepresentation realmRep = testRealm.admin().toRepresentation(); + realmRep.setVerifiableCredentialsEnabled(false); + testRealm.admin().update(realmRep); try { ClientRepresentation testClient = testRealm.admin().clients().findByClientId(OID4VCI_CLIENT_ID).get(0); @@ -2098,10 +1559,10 @@ public void testCannotAssignOid4vciScopeWhenRealmDisabled() { error.getErrorDescription()); } } finally { - runOnServer.run(session -> { - RealmModel realm = session.getContext().getRealm(); - realm.setVerifiableCredentialsEnabled(true); - }); + // Re-enable Verifiable Credentials for the realm + realmRep = testRealm.admin().toRepresentation(); + realmRep.setVerifiableCredentialsEnabled(true); + testRealm.admin().update(realmRep); } } @@ -2349,46 +1810,15 @@ private String issueCredentialAndGetTyp(ClientScopeRepresentation clientScope, S } private String getCNonce() { - UriBuilder builder = UriBuilder.fromUri(keycloakUrls.getBase()); - URI oid4vcUri = RealmsResource.protocolUrl(builder) - .build(testRealm.getName(), OID4VCLoginProtocolFactory.PROTOCOL_ID); - String nonceUrl = String.format("%s/%s", oid4vcUri.toString(), OID4VCIssuerEndpoint.NONCE_PATH); - - String nonceResponseString; - - // request cNonce - try (Client restClient = Keycloak.getClientProvider().newRestEasyClient(null, null, true)) { - WebTarget nonceTarget = restClient.target(nonceUrl); - Invocation.Builder nonceInvocationBuilder = nonceTarget.request() - .header(HttpHeaders.AUTHORIZATION, null) - .header(HttpHeaders.COOKIE, null); - - try (Response response = nonceInvocationBuilder.post(null)) { - assertEquals(HttpStatus.SC_OK, response.getStatus()); - assertTrue(response.getMediaType().toString().startsWith(MediaType.APPLICATION_JSON_TYPE.toString())); - - nonceResponseString = parseResponse(response); - assertNotNull(nonceResponseString); - assertEquals("no-store", response.getHeaderString(HttpHeaders.CACHE_CONTROL)); - } - } + Oid4vcNonceResponse response = oauth.oid4vc() + .nonceRequest() + .send(); - NonceResponse nonceResponse; - try { - nonceResponse = JsonSerialization.readValue(nonceResponseString, NonceResponse.class); - } catch (IOException e) { - throw new RuntimeException(e); - } + assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + assertNotNull(response.getNonce()); + assertEquals("no-store", response.getHeader(HttpHeaders.CACHE_CONTROL)); - return nonceResponse.getNonce(); - } - - private static String parseResponse(Response response) { - try { - return response.readEntity(String.class); - } catch (Exception e) { - throw new RuntimeException(e); - } + return response.getNonce(); } private static String withModifiedHeaderClaim(String jwt, String claim, Object value) { @@ -2420,88 +1850,4 @@ private static String withPrivateJwkMaterialInHeader(String jwt) { } } - private static String generateJwtProofWithEmbeddedAttestation( - KeyWrapper proofKey, - String attestationJwt, - String cNonce, - String audience, - boolean useKidHeader - ) { - try { - JWK proofJwk = JWKBuilder.create().ec(proofKey.getPublicKey()); - proofJwk.setKeyId(proofKey.getKid()); - proofJwk.setAlgorithm(proofKey.getAlgorithm()); - - AccessToken token = new AccessToken(); - token.addAudience(audience); - token.setNonce(cNonce); - token.issuedNow(); - - Map header = Map.of( - "alg", proofKey.getAlgorithm(), - "typ", "openid4vci-proof+jwt", - "key_attestation", attestationJwt, - useKidHeader ? "kid" : "jwk", - useKidHeader ? proofKey.getKid() : proofJwk - ); - - return new JWSBuilder() { - @Override - protected String encodeHeader(String sigAlgName) { - try { - return Base64Url.encode(JsonSerialization.writeValueAsBytes(header)); - } catch (Exception e) { - throw new RuntimeException("Failed to encode JWT proof header", e); - } - } - }.jsonContent(token).sign(new ECDSASignatureSignerContext(proofKey)); - } catch (Exception e) { - throw new RuntimeException("Failed to generate JWT proof with key_attestation", e); - } - } - - private static String generateAttestationProofWithoutExp(KeyWrapper attestationKey, String nonce, List attestedKeys) { - KeyAttestationJwtBody body = new KeyAttestationJwtBody(); - body.setIat(System.currentTimeMillis() / 1000L); - body.setNonce(nonce); - body.setAttestedKeys(attestedKeys); - body.setKeyStorage(List.of("iso_18045_high")); - body.setUserAuthentication(List.of("iso_18045_high")); - - return new JWSBuilder() - .type(AttestationValidatorUtil.ATTESTATION_JWT_TYP) - .kid(attestationKey.getKid()) - .jsonContent(body) - .sign(new ECDSASignatureSignerContext(attestationKey)); - } - - private Map prepareJwtCredentialRequestContext() { - String scopeName = jwtTypeCredentialScope.getName(); - String credentialConfigurationId = jwtTypeCredentialScope.getAttributes().get(CredentialScopeModel.VC_CONFIGURATION_ID); - CredentialIssuer credentialIssuer = getCredentialIssuerMetadata(); - - OID4VCAuthorizationDetail authDetail = new OID4VCAuthorizationDetail(); - authDetail.setType(OPENID_CREDENTIAL); - authDetail.setCredentialConfigurationId(credentialConfigurationId); - authDetail.setLocations(List.of(credentialIssuer.getCredentialIssuer())); - - String authCode = getAuthorizationCode(oauth, client, "john", scopeName); - AccessTokenResponse tokenResponse = getBearerToken(oauth, authCode, authDetail); - String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0); - return Map.of( - "token", tokenResponse.getAccessToken(), - "credentialIdentifier", credentialIdentifier, - "cNonce", getCNonce() - ); - } - - private static void assertSingleCredentialResponse(Response response) { - assertEquals(Response.Status.OK.getStatusCode(), response.getStatus(), "Response status should be OK"); - CredentialResponse credentialResponse = JsonSerialization.mapper - .convertValue(response.getEntity(), CredentialResponse.class); - assertNotNull(credentialResponse); - assertNotNull(credentialResponse.getCredentials()); - assertEquals(1, credentialResponse.getCredentials().size()); - } - }