Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1769,7 +1769,7 @@ private VCIssuanceContext prepareCredential(AuthenticationManager.AuthResult aut
VCIssuanceContext vcIssuanceContext = getVCToSign(protocolMappers, credentialConfig, authResult, authDetail, credentialRequestVO, credentialScopeModel, eventBuilder);

// Enforce key binding prior to signing if necessary
enforceKeyBindingIfProofProvided(vcIssuanceContext);
enforceKeyBindingIfProofProvided(vcIssuanceContext, eventBuilder);

return vcIssuanceContext;
}
Expand Down Expand Up @@ -1893,7 +1893,7 @@ private VCIssuanceContext getVCToSign(List<OID4VCMapper> protocolMappers, Suppor
/**
* Enforce key binding: Validate proof and bind associated key to credential in issuance context.
*/
private void enforceKeyBindingIfProofProvided(VCIssuanceContext vcIssuanceContext) {
private void enforceKeyBindingIfProofProvided(VCIssuanceContext vcIssuanceContext, EventBuilder eventBuilder) {
Proofs proofs = vcIssuanceContext.getCredentialRequest().getProofs();
if (proofs == null) {
LOGGER.debugf("No proofs provided, skipping key binding");
Expand All @@ -1902,11 +1902,11 @@ private void enforceKeyBindingIfProofProvided(VCIssuanceContext vcIssuanceContex

// Validate each proof type that is present
for (String proofType : proofs.getPresentProofTypes()) {
validateProofs(vcIssuanceContext, proofType);
validateProofs(vcIssuanceContext, proofType, eventBuilder);
}
}

private void validateProofs(VCIssuanceContext vcIssuanceContext, String proofType) {
private void validateProofs(VCIssuanceContext vcIssuanceContext, String proofType, EventBuilder eventBuilder) {
ProofValidator proofValidator = session.getProvider(ProofValidator.class, proofType);
if (proofValidator == null) {
throw new BadRequestException(String.format("Unable to validate proofs of type %s", proofType));
Expand All @@ -1920,6 +1920,8 @@ private void validateProofs(VCIssuanceContext vcIssuanceContext, String proofTyp
vcIssuanceContext.getCredentialBody().addKeyBinding(jwks.get(0));
}
} catch (VCIssuerException e) {
eventBuilder.detail(Details.REASON, e.getMessage())
.error(e.getErrorType().getValue());
switch (e.getErrorType()) {
case INVALID_NONCE:
throw new ErrorResponseException(INVALID_NONCE.getValue(), e.getMessage(), Response.Status.BAD_REQUEST);
Expand Down Expand Up @@ -1957,8 +1959,10 @@ private CredentialBuilder findCredentialBuilder(SupportedCredentialConfiguration
private void validateRequestedClaimsArePresent(Map<String, Object> allClaims, SupportedCredentialConfiguration credentialConfig,
UserModel user, OID4VCAuthorizationDetail authzDetail, String scope, EventBuilder eventBuilder) {
// Protocol mappers from configuration
Map<List<Object>, ClaimsDescription> claimsConfig = credentialConfig.getCredentialMetadata().getClaims()
Map<List<Object>, ClaimsDescription> claimsConfig = Optional.ofNullable(credentialConfig.getCredentialMetadata())
.map(metadata -> metadata.getClaims())
.stream()
.flatMap(claims -> claims.stream())
.map(claim -> {
List<Object> pathObj = new ArrayList<>(claim.getPath());
return new ClaimsDescription(pathObj, claim.isMandatory());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.Time;
import org.keycloak.crypto.Algorithm;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.models.Constants;
import org.keycloak.models.oid4vci.CredentialScopeModel;
Expand Down Expand Up @@ -73,6 +75,7 @@
import org.keycloak.representations.idm.oid4vc.UserVerifiableCredentialRepresentation;
import org.keycloak.services.managers.AppAuthManager.BearerTokenAuthenticator;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.EventAssertion;
import org.keycloak.testframework.remote.runonserver.InjectRunOnServer;
import org.keycloak.testframework.remote.runonserver.RunOnServerClient;
import org.keycloak.testsuite.util.AccountHelper;
Expand Down Expand Up @@ -1041,6 +1044,7 @@ public void testRequestCredentialWithFutureIatRejected() {
.setCredentialIdentifier(credentialIdentifier)
.setProofs(new Proofs().setJwt(List.of(futureIatProof)));

events.clear();
Oid4vcCredentialResponse response = oauth.oid4vc()
.credentialRequest(request)
.bearerToken(token)
Expand All @@ -1049,6 +1053,11 @@ public void testRequestCredentialWithFutureIatRejected() {
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"));
EventAssertion.assertError(events.poll())
.type(EventType.VERIFIABLE_CREDENTIAL_REQUEST_ERROR)
.clientId(OID4VCI_CLIENT_ID)
.error(ErrorType.INVALID_PROOF.getValue())
.details(Details.REASON, "Proof iat is in the future beyond allowed clock skew");
}

@Test
Expand Down Expand Up @@ -1496,6 +1505,7 @@ public void testCredentialRequestWithOptionalClientScope() {
VCFormat.JWT_VC,
null, null
);
optionalScope.setProtocolMappers(List.of());

optionalScope = registerOptionalClientScope(optionalScope);
ClientRepresentation testClient = testRealm.admin().clients().findByClientId(OID4VCI_CLIENT_ID).get(0);
Expand Down Expand Up @@ -1526,6 +1536,7 @@ public void testCredentialRequestWithOptionalClientScope() {
CredentialRequest credentialRequest = new CredentialRequest()
.setCredentialIdentifier(credentialIdentifier);

events.clear();
Oid4vcCredentialResponse response = oauth.oid4vc()
.credentialRequest(credentialRequest)
.bearerToken(token)
Expand All @@ -1538,6 +1549,10 @@ public void testCredentialRequestWithOptionalClientScope() {

assertNotNull(credentialResponseVO.getCredentials(), "Credentials array should not be null");
assertFalse(credentialResponseVO.getCredentials().isEmpty(), "Credentials array should not be empty");
EventAssertion.assertSuccess(events.poll())
.type(EventType.VERIFIABLE_CREDENTIAL_REQUEST)
.clientId(OID4VCI_CLIENT_ID)
.details(Details.CREDENTIAL_TYPE, configId);
}

@Test
Expand Down
Loading