diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/keycloak/licenses.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/keycloak/licenses.xml index 319073d440..d71d718e5b 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/keycloak/licenses.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/keycloak/licenses.xml @@ -521,22 +521,22 @@ com.webauthn4j webauthnj4-core - 0.9.14.RELEASE + 0.10.2.RELEASE Apache Software License 2.0 - https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.9.14.RELEASE/LICENSE.txt + https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.10.2.RELEASE/LICENSE.txt com.webauthn4j webauthnj4-util - 0.9.14.RELEASE + 0.10.2.RELEASE Apache Software License 2.0 - https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.9.14.RELEASE/LICENSE.txt + https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.10.2.RELEASE/LICENSE.txt diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/rh-sso/licenses.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/rh-sso/licenses.xml index fd4472ffdf..f02d743619 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/rh-sso/licenses.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/rh-sso/licenses.xml @@ -521,22 +521,22 @@ com.webauthn4j webauthnj4-core - 0.9.14.RELEASE + 0.10.2.RELEASE Apache Software License 2.0 - https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.9.14.RELEASE/LICENSE.txt + https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.10.2.RELEASE/LICENSE.txt com.webauthn4j webauthnj4-util - 0.9.14.RELEASE + 0.10.2.RELEASE Apache Software License 2.0 - https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.9.14.RELEASE/LICENSE.txt + https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.10.2.RELEASE/LICENSE.txt diff --git a/pom.xml b/pom.xml index 2dc55d2565..8fb043d7ab 100755 --- a/pom.xml +++ b/pom.xml @@ -169,7 +169,7 @@ 2.2.0.RELEASE - 0.9.14.RELEASE + 0.10.2.RELEASE 2.0.0 diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnAuthenticator.java index e0eb158773..f5805e4e2f 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnAuthenticator.java @@ -16,13 +16,16 @@ package org.keycloak.authentication.authenticators.browser; -import com.webauthn4j.data.WebAuthnAuthenticationContext; +import com.webauthn4j.data.AuthenticationParameters; +import com.webauthn4j.data.AuthenticationRequest; import com.webauthn4j.data.client.Origin; import com.webauthn4j.data.client.challenge.Challenge; import com.webauthn4j.data.client.challenge.DefaultChallenge; import com.webauthn4j.server.ServerProperty; import com.webauthn4j.util.exception.WebAuthnException; + import org.jboss.logging.Logger; + import org.keycloak.WebAuthnConstants; import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.AuthenticationFlowError; @@ -177,17 +180,24 @@ public class WebAuthnAuthenticator implements Authenticator, CredentialValidator if (WebAuthnConstants.OPTION_REQUIRED.equals(userVerificationRequirement)) isUVFlagChecked = true; UserModel user = session.users().getUserById(userId, context.getRealm()); - WebAuthnAuthenticationContext authenticationContext = new WebAuthnAuthenticationContext( + + AuthenticationRequest authenticationRequest = new AuthenticationRequest( credentialId, - clientDataJSON, authenticatorData, - signature, + clientDataJSON, + signature + ); + + AuthenticationParameters authenticationParameters = new AuthenticationParameters( server, + null, // here authenticator cannot be fetched, set it afterwards in WebAuthnCredentialProvider.isValid() isUVFlagChecked - ); + ); WebAuthnCredentialModelInput cred = new WebAuthnCredentialModelInput(getCredentialType()); - cred.setAuthenticationContext(authenticationContext); + + cred.setAuthenticationRequest(authenticationRequest); + cred.setAuthenticationParameters(authenticationParameters); boolean result = false; try { diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java b/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java index 27e7f9bbd1..bbcc9c11e3 100644 --- a/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java @@ -19,6 +19,7 @@ package org.keycloak.authentication.requiredactions; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Base64; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -47,19 +48,19 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; import org.keycloak.models.WebAuthnPolicy; -import com.webauthn4j.converter.util.CborConverter; -import com.webauthn4j.converter.util.JsonConverter; -import com.webauthn4j.data.WebAuthnRegistrationContext; +import com.webauthn4j.WebAuthnManager; +import com.webauthn4j.converter.util.ObjectConverter; import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; import com.webauthn4j.data.attestation.statement.AttestationStatement; import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; import com.webauthn4j.data.client.Origin; import com.webauthn4j.data.client.challenge.Challenge; import com.webauthn4j.data.client.challenge.DefaultChallenge; +import com.webauthn4j.data.RegistrationRequest; +import com.webauthn4j.data.RegistrationData; +import com.webauthn4j.data.RegistrationParameters; import com.webauthn4j.server.ServerProperty; import com.webauthn4j.util.exception.WebAuthnException; -import com.webauthn4j.validator.WebAuthnRegistrationContextValidationResponse; -import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; import com.webauthn4j.validator.attestation.statement.androidkey.AndroidKeyAttestationStatementValidator; import com.webauthn4j.validator.attestation.statement.androidsafetynet.AndroidSafetyNetAttestationStatementValidator; import com.webauthn4j.validator.attestation.statement.none.NoneAttestationStatementValidator; @@ -204,19 +205,24 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis // check User Verification by considering a malicious user might modify the result of calling WebAuthn API boolean isUserVerificationRequired = policy.getUserVerificationRequirement().equals(WebAuthnConstants.OPTION_REQUIRED); + RegistrationRequest registrationRequest = new RegistrationRequest(attestationObject, clientDataJSON); + RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, isUserVerificationRequired); + + WebAuthnManager webAuthnManager = createWebAuthnManager(); try { - WebAuthnRegistrationContext registrationContext = new WebAuthnRegistrationContext(clientDataJSON, attestationObject, serverProperty, isUserVerificationRequired); - WebAuthnRegistrationContextValidator webAuthnRegistrationContextValidator = createWebAuthnRegistrationContextValidator(); - WebAuthnRegistrationContextValidationResponse response = webAuthnRegistrationContextValidator.validate(registrationContext); + // parse + RegistrationData registrationData = webAuthnManager.parse(registrationRequest); + // validate + webAuthnManager.validate(registrationData, registrationParameters); - showInfoAfterWebAuthnApiCreate(response); + showInfoAfterWebAuthnApiCreate(registrationData); - checkAcceptedAuthenticator(response, policy); + checkAcceptedAuthenticator(registrationData, policy); WebAuthnCredentialModelInput credential = new WebAuthnCredentialModelInput(getCredentialType()); - credential.setAttestedCredentialData(response.getAttestationObject().getAuthenticatorData().getAttestedCredentialData()); - credential.setCount(response.getAttestationObject().getAuthenticatorData().getSignCount()); + credential.setAttestedCredentialData(registrationData.getAttestationObject().getAuthenticatorData().getAttestedCredentialData()); + credential.setCount(registrationData.getAttestationObject().getAuthenticatorData().getSignCount()); // Save new webAuthn credential WebAuthnCredentialProvider webAuthnCredProvider = (WebAuthnCredentialProvider) this.session.getProvider(CredentialProvider.class, getCredentialProviderId()); @@ -245,8 +251,8 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis } } - private WebAuthnRegistrationContextValidator createWebAuthnRegistrationContextValidator() { - return new WebAuthnRegistrationContextValidator( + private WebAuthnManager createWebAuthnManager() { + return new WebAuthnManager( Arrays.asList( new NoneAttestationStatementValidator(), new PackedAttestationStatementValidator(), @@ -257,8 +263,10 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis ), this.certPathtrustValidator, new DefaultECDAATrustworthinessValidator(), new DefaultSelfAttestationTrustworthinessValidator(), - new JsonConverter(), - new CborConverter()); + Collections.emptyList(), // Custom Registration Validator is not supported + Collections.emptyList(), // Custom Authentication Validator is not supported + new ObjectConverter() + ); } private String stringifySignatureAlgorithms(List signatureAlgorithmsList) { @@ -304,7 +312,7 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis return sb.toString(); } - private void showInfoAfterWebAuthnApiCreate(WebAuthnRegistrationContextValidationResponse response) { + private void showInfoAfterWebAuthnApiCreate(RegistrationData response) { AttestedCredentialData attestedCredentialData = response.getAttestationObject().getAuthenticatorData().getAttestedCredentialData(); AttestationStatement attestationStatement = response.getAttestationObject().getAttestationStatement(); logger.debugv("createad key's algorithm = {0}", String.valueOf(attestedCredentialData.getCOSEKey().getAlgorithm().getValue())); @@ -312,7 +320,7 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis logger.debugv("attestation format = {0}", attestationStatement.getFormat()); } - private void checkAcceptedAuthenticator(WebAuthnRegistrationContextValidationResponse response, WebAuthnPolicy policy) throws Exception { + private void checkAcceptedAuthenticator(RegistrationData response, WebAuthnPolicy policy) throws Exception { String aaguid = response.getAttestationObject().getAuthenticatorData().getAttestedCredentialData().getAaguid().toString(); List acceptableAaguids = policy.getAcceptableAaguids(); boolean isAcceptedAuthenticator = false; diff --git a/services/src/main/java/org/keycloak/credential/AttestationStatementConverter.java b/services/src/main/java/org/keycloak/credential/AttestationStatementConverter.java index d045f1dbf6..750f6cc744 100644 --- a/services/src/main/java/org/keycloak/credential/AttestationStatementConverter.java +++ b/services/src/main/java/org/keycloak/credential/AttestationStatementConverter.java @@ -16,6 +16,7 @@ package org.keycloak.credential; +import com.webauthn4j.converter.util.ObjectConverter; import org.keycloak.common.util.Base64Url; import com.webauthn4j.converter.util.CborConverter; @@ -23,20 +24,20 @@ import com.webauthn4j.data.attestation.statement.AttestationStatement; public class AttestationStatementConverter { - private CborConverter converter; + private CborConverter cborConverter; - public AttestationStatementConverter(CborConverter converter) { - this.converter = converter; + public AttestationStatementConverter(ObjectConverter objectConverter) { + this.cborConverter = objectConverter.getCborConverter(); } public String convertToDatabaseColumn(AttestationStatement attribute) { AttestationStatementSerializationContainer container = new AttestationStatementSerializationContainer(attribute); - return Base64Url.encode(converter.writeValueAsBytes(container)); + return Base64Url.encode(cborConverter.writeValueAsBytes(container)); } public AttestationStatement convertToEntityAttribute(String dbData) { byte[] data = Base64Url.decode(dbData); - AttestationStatementSerializationContainer container = converter.readValue(data, AttestationStatementSerializationContainer.class); + AttestationStatementSerializationContainer container = cborConverter.readValue(data, AttestationStatementSerializationContainer.class); return container.getAttestationStatement(); } } diff --git a/services/src/main/java/org/keycloak/credential/CredentialPublicKeyConverter.java b/services/src/main/java/org/keycloak/credential/CredentialPublicKeyConverter.java index 3286d98383..d87a54b7e5 100644 --- a/services/src/main/java/org/keycloak/credential/CredentialPublicKeyConverter.java +++ b/services/src/main/java/org/keycloak/credential/CredentialPublicKeyConverter.java @@ -16,6 +16,7 @@ package org.keycloak.credential; +import com.webauthn4j.converter.util.ObjectConverter; import org.keycloak.common.util.Base64Url; import com.webauthn4j.converter.util.CborConverter; @@ -23,17 +24,17 @@ import com.webauthn4j.data.attestation.authenticator.COSEKey; public class CredentialPublicKeyConverter { - private CborConverter converter; + private CborConverter cborConverter; - public CredentialPublicKeyConverter(CborConverter converter) { - this.converter = converter; + public CredentialPublicKeyConverter(ObjectConverter objectConverter) { + this.cborConverter = objectConverter.getCborConverter(); } public String convertToDatabaseColumn(COSEKey credentialPublicKey) { - return Base64Url.encode(converter.writeValueAsBytes(credentialPublicKey)); + return Base64Url.encode(cborConverter.writeValueAsBytes(credentialPublicKey)); } public COSEKey convertToEntityAttribute(String s) { - return converter.readValue(Base64Url.decode(s), COSEKey.class); + return cborConverter.readValue(Base64Url.decode(s), COSEKey.class); } } diff --git a/services/src/main/java/org/keycloak/credential/WebAuthnCredentialModelInput.java b/services/src/main/java/org/keycloak/credential/WebAuthnCredentialModelInput.java index f0362fc558..87f94cbc0b 100644 --- a/services/src/main/java/org/keycloak/credential/WebAuthnCredentialModelInput.java +++ b/services/src/main/java/org/keycloak/credential/WebAuthnCredentialModelInput.java @@ -18,17 +18,18 @@ package org.keycloak.credential; import org.keycloak.common.util.Base64; -import com.webauthn4j.data.WebAuthnAuthenticationContext; +import com.webauthn4j.data.AuthenticationParameters; +import com.webauthn4j.data.AuthenticationRequest; import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; import com.webauthn4j.data.attestation.authenticator.COSEKey; import com.webauthn4j.data.attestation.statement.AttestationStatement; -import org.keycloak.models.credential.WebAuthnCredentialModel; public class WebAuthnCredentialModelInput implements CredentialInput { private AttestedCredentialData attestedCredentialData; private AttestationStatement attestationStatement; - private WebAuthnAuthenticationContext authenticationContext; + private AuthenticationParameters authenticationParameters; // not persisted because it can only be used on authentication operation. + private AuthenticationRequest authenticationRequest; // not persisted because it can only be used on authentication operation. private long count; private String credentialDBId; private final String credentialType; @@ -65,12 +66,20 @@ public class WebAuthnCredentialModelInput implements CredentialInput { return count; } - public WebAuthnAuthenticationContext getAuthenticationContext() { - return authenticationContext; + public AuthenticationParameters getAuthenticationParameters() { + return authenticationParameters; } - public void setAuthenticationContext(WebAuthnAuthenticationContext authenticationContext) { - this.authenticationContext = authenticationContext; + public void setAuthenticationParameters(AuthenticationParameters authenticationParameters) { + this.authenticationParameters = authenticationParameters; + } + + public AuthenticationRequest getAuthenticationRequest() { + return authenticationRequest; + } + + public void setAuthenticationRequest(AuthenticationRequest authenticationRequest) { + this.authenticationRequest = authenticationRequest; } public void setAttestedCredentialData(AttestedCredentialData attestedCredentialData) { @@ -127,10 +136,10 @@ public class WebAuthnCredentialModelInput implements CredentialInput { .append(credPubKey.getKeyType().name()) .append(","); } - if (authenticationContext != null) { + if (authenticationRequest != null) { // only set on Authentication sb.append("Credential Id = ") - .append(Base64.encodeBytes(authenticationContext.getCredentialId())) + .append(Base64.encodeBytes(authenticationRequest.getCredentialId())) .append(","); } if (sb.length() > 0) diff --git a/services/src/main/java/org/keycloak/credential/WebAuthnCredentialProvider.java b/services/src/main/java/org/keycloak/credential/WebAuthnCredentialProvider.java index 972a02525c..b9b06a8100 100644 --- a/services/src/main/java/org/keycloak/credential/WebAuthnCredentialProvider.java +++ b/services/src/main/java/org/keycloak/credential/WebAuthnCredentialProvider.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import com.webauthn4j.converter.util.ObjectConverter; import org.jboss.logging.Logger; import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory; import org.keycloak.common.util.Base64; @@ -29,15 +30,15 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; +import com.webauthn4j.WebAuthnManager; import com.webauthn4j.authenticator.Authenticator; import com.webauthn4j.authenticator.AuthenticatorImpl; -import com.webauthn4j.converter.util.CborConverter; +import com.webauthn4j.data.AuthenticationData; +import com.webauthn4j.data.AuthenticationParameters; import com.webauthn4j.data.attestation.authenticator.AAGUID; import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; import com.webauthn4j.data.attestation.authenticator.COSEKey; import com.webauthn4j.util.exception.WebAuthnException; -import com.webauthn4j.validator.WebAuthnAuthenticationContextValidationResponse; -import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; import org.keycloak.models.credential.WebAuthnCredentialModel; import org.keycloak.models.credential.dto.WebAuthnCredentialData; @@ -53,12 +54,12 @@ public class WebAuthnCredentialProvider implements CredentialProvider auths = getWebAuthnCredentialModelList(realm, user); - WebAuthnAuthenticationContextValidator webAuthnAuthenticationContextValidator = - new WebAuthnAuthenticationContextValidator(); + WebAuthnManager webAuthnManager = WebAuthnManager.createNonStrictWebAuthnManager(); // not special setting is needed for authentication's validation. + AuthenticationData authenticationData = null; + try { for (WebAuthnCredentialModelInput auth : auths) { byte[] credentialId = auth.getAttestedCredentialData().getCredentialId(); - if (Arrays.equals(credentialId, context.getAuthenticationContext().getCredentialId())) { + if (Arrays.equals(credentialId, context.getAuthenticationRequest().getCredentialId())) { Authenticator authenticator = new AuthenticatorImpl( auth.getAttestedCredentialData(), auth.getAttestationStatement(), auth.getCount() ); - // WebAuthnException is thrown if validation fails - WebAuthnAuthenticationContextValidationResponse response = - webAuthnAuthenticationContextValidator.validate( - context.getAuthenticationContext(), - authenticator); + // parse + authenticationData = webAuthnManager.parse(context.getAuthenticationRequest()); + // validate + AuthenticationParameters authenticationParameters = new AuthenticationParameters( + context.getAuthenticationParameters().getServerProperty(), + authenticator, + context.getAuthenticationParameters().isUserVerificationRequired() + ); + webAuthnManager.validate(authenticationData, authenticationParameters); - logger.debugv("response.getAuthenticatorData().getFlags() = {0}", response.getAuthenticatorData().getFlags()); + + logger.debugv("response.getAuthenticatorData().getFlags() = {0}", authenticationData.getAuthenticatorData().getFlags()); // update authenticator counter long count = auth.getCount(); diff --git a/services/src/main/java/org/keycloak/credential/WebAuthnCredentialProviderFactory.java b/services/src/main/java/org/keycloak/credential/WebAuthnCredentialProviderFactory.java index 761af41622..ba13af2137 100644 --- a/services/src/main/java/org/keycloak/credential/WebAuthnCredentialProviderFactory.java +++ b/services/src/main/java/org/keycloak/credential/WebAuthnCredentialProviderFactory.java @@ -19,14 +19,14 @@ package org.keycloak.credential; import org.keycloak.common.Profile; import org.keycloak.models.KeycloakSession; -import com.webauthn4j.converter.util.CborConverter; +import com.webauthn4j.converter.util.ObjectConverter; import org.keycloak.provider.EnvironmentDependentProviderFactory; public class WebAuthnCredentialProviderFactory implements CredentialProviderFactory, EnvironmentDependentProviderFactory { public static final String PROVIDER_ID = "keycloak-webauthn"; - private static CborConverter converter = new CborConverter(); + private static ObjectConverter converter = new ObjectConverter(); @Override public CredentialProvider create(KeycloakSession session) { diff --git a/services/src/main/java/org/keycloak/credential/WebAuthnPasswordlessCredentialProvider.java b/services/src/main/java/org/keycloak/credential/WebAuthnPasswordlessCredentialProvider.java index ac7308a8fb..d5b91c1ad9 100644 --- a/services/src/main/java/org/keycloak/credential/WebAuthnPasswordlessCredentialProvider.java +++ b/services/src/main/java/org/keycloak/credential/WebAuthnPasswordlessCredentialProvider.java @@ -18,7 +18,7 @@ package org.keycloak.credential; -import com.webauthn4j.converter.util.CborConverter; +import com.webauthn4j.converter.util.ObjectConverter; import org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.credential.WebAuthnCredentialModel; @@ -30,8 +30,8 @@ import org.keycloak.models.credential.WebAuthnCredentialModel; */ public class WebAuthnPasswordlessCredentialProvider extends WebAuthnCredentialProvider { - public WebAuthnPasswordlessCredentialProvider(KeycloakSession session, CborConverter converter) { - super(session, converter); + public WebAuthnPasswordlessCredentialProvider(KeycloakSession session, ObjectConverter objectConverter) { + super(session, objectConverter); } @Override diff --git a/services/src/main/java/org/keycloak/credential/WebAuthnPasswordlessCredentialProviderFactory.java b/services/src/main/java/org/keycloak/credential/WebAuthnPasswordlessCredentialProviderFactory.java index c317567a41..f621455918 100644 --- a/services/src/main/java/org/keycloak/credential/WebAuthnPasswordlessCredentialProviderFactory.java +++ b/services/src/main/java/org/keycloak/credential/WebAuthnPasswordlessCredentialProviderFactory.java @@ -18,7 +18,7 @@ package org.keycloak.credential; -import com.webauthn4j.converter.util.CborConverter; +import com.webauthn4j.converter.util.ObjectConverter; import org.keycloak.common.Profile; import org.keycloak.models.KeycloakSession; import org.keycloak.provider.EnvironmentDependentProviderFactory; @@ -30,11 +30,11 @@ public class WebAuthnPasswordlessCredentialProviderFactory implements Credential public static final String PROVIDER_ID = "keycloak-webauthn-passwordless"; - private static CborConverter converter = new CborConverter(); + private static ObjectConverter objectConverter = new ObjectConverter(); @Override public CredentialProvider create(KeycloakSession session) { - return new WebAuthnPasswordlessCredentialProvider(session, converter); + return new WebAuthnPasswordlessCredentialProvider(session, objectConverter); } @Override