KEYCLOAK-12696 Upgrade to webauthn4j 0.10.2.RELEASE

This commit is contained in:
Takashi Norimatsu 2020-02-19 09:05:58 +09:00 committed by Marek Posolda
parent 86089d40b8
commit fc58af1365
12 changed files with 111 additions and 75 deletions

View file

@ -521,22 +521,22 @@
<dependency> <dependency>
<groupId>com.webauthn4j</groupId> <groupId>com.webauthn4j</groupId>
<artifactId>webauthnj4-core</artifactId> <artifactId>webauthnj4-core</artifactId>
<version>0.9.14.RELEASE</version> <version>0.10.2.RELEASE</version>
<licenses> <licenses>
<license> <license>
<name>Apache Software License 2.0</name> <name>Apache Software License 2.0</name>
<url>https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.9.14.RELEASE/LICENSE.txt</url> <url>https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.10.2.RELEASE/LICENSE.txt</url>
</license> </license>
</licenses> </licenses>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.webauthn4j</groupId> <groupId>com.webauthn4j</groupId>
<artifactId>webauthnj4-util</artifactId> <artifactId>webauthnj4-util</artifactId>
<version>0.9.14.RELEASE</version> <version>0.10.2.RELEASE</version>
<licenses> <licenses>
<license> <license>
<name>Apache Software License 2.0</name> <name>Apache Software License 2.0</name>
<url>https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.9.14.RELEASE/LICENSE.txt</url> <url>https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.10.2.RELEASE/LICENSE.txt</url>
</license> </license>
</licenses> </licenses>
</dependency> </dependency>

View file

@ -521,22 +521,22 @@
<dependency> <dependency>
<groupId>com.webauthn4j</groupId> <groupId>com.webauthn4j</groupId>
<artifactId>webauthnj4-core</artifactId> <artifactId>webauthnj4-core</artifactId>
<version>0.9.14.RELEASE</version> <version>0.10.2.RELEASE</version>
<licenses> <licenses>
<license> <license>
<name>Apache Software License 2.0</name> <name>Apache Software License 2.0</name>
<url>https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.9.14.RELEASE/LICENSE.txt</url> <url>https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.10.2.RELEASE/LICENSE.txt</url>
</license> </license>
</licenses> </licenses>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.webauthn4j</groupId> <groupId>com.webauthn4j</groupId>
<artifactId>webauthnj4-util</artifactId> <artifactId>webauthnj4-util</artifactId>
<version>0.9.14.RELEASE</version> <version>0.10.2.RELEASE</version>
<licenses> <licenses>
<license> <license>
<name>Apache Software License 2.0</name> <name>Apache Software License 2.0</name>
<url>https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.9.14.RELEASE/LICENSE.txt</url> <url>https://raw.githubusercontent.com/webauthn4j/webauthn4j/0.10.2.RELEASE/LICENSE.txt</url>
</license> </license>
</licenses> </licenses>
</dependency> </dependency>

View file

@ -169,7 +169,7 @@
<spring-boot22.version>2.2.0.RELEASE</spring-boot22.version> <spring-boot22.version>2.2.0.RELEASE</spring-boot22.version>
<!-- webauthn support --> <!-- webauthn support -->
<webauthn4j.version>0.9.14.RELEASE</webauthn4j.version> <webauthn4j.version>0.10.2.RELEASE</webauthn4j.version>
<org.apache.kerby.kerby-asn1.version>2.0.0</org.apache.kerby.kerby-asn1.version> <org.apache.kerby.kerby-asn1.version>2.0.0</org.apache.kerby.kerby-asn1.version>
</properties> </properties>

View file

@ -16,13 +16,16 @@
package org.keycloak.authentication.authenticators.browser; 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.Origin;
import com.webauthn4j.data.client.challenge.Challenge; import com.webauthn4j.data.client.challenge.Challenge;
import com.webauthn4j.data.client.challenge.DefaultChallenge; import com.webauthn4j.data.client.challenge.DefaultChallenge;
import com.webauthn4j.server.ServerProperty; import com.webauthn4j.server.ServerProperty;
import com.webauthn4j.util.exception.WebAuthnException; import com.webauthn4j.util.exception.WebAuthnException;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.WebAuthnConstants; import org.keycloak.WebAuthnConstants;
import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError; import org.keycloak.authentication.AuthenticationFlowError;
@ -177,17 +180,24 @@ public class WebAuthnAuthenticator implements Authenticator, CredentialValidator
if (WebAuthnConstants.OPTION_REQUIRED.equals(userVerificationRequirement)) isUVFlagChecked = true; if (WebAuthnConstants.OPTION_REQUIRED.equals(userVerificationRequirement)) isUVFlagChecked = true;
UserModel user = session.users().getUserById(userId, context.getRealm()); UserModel user = session.users().getUserById(userId, context.getRealm());
WebAuthnAuthenticationContext authenticationContext = new WebAuthnAuthenticationContext(
AuthenticationRequest authenticationRequest = new AuthenticationRequest(
credentialId, credentialId,
clientDataJSON,
authenticatorData, authenticatorData,
signature, clientDataJSON,
signature
);
AuthenticationParameters authenticationParameters = new AuthenticationParameters(
server, server,
null, // here authenticator cannot be fetched, set it afterwards in WebAuthnCredentialProvider.isValid()
isUVFlagChecked isUVFlagChecked
); );
WebAuthnCredentialModelInput cred = new WebAuthnCredentialModelInput(getCredentialType()); WebAuthnCredentialModelInput cred = new WebAuthnCredentialModelInput(getCredentialType());
cred.setAuthenticationContext(authenticationContext);
cred.setAuthenticationRequest(authenticationRequest);
cred.setAuthenticationParameters(authenticationParameters);
boolean result = false; boolean result = false;
try { try {

View file

@ -19,6 +19,7 @@ package org.keycloak.authentication.requiredactions;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -47,19 +48,19 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.WebAuthnPolicy; import org.keycloak.models.WebAuthnPolicy;
import com.webauthn4j.converter.util.CborConverter; import com.webauthn4j.WebAuthnManager;
import com.webauthn4j.converter.util.JsonConverter; import com.webauthn4j.converter.util.ObjectConverter;
import com.webauthn4j.data.WebAuthnRegistrationContext;
import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData;
import com.webauthn4j.data.attestation.statement.AttestationStatement; import com.webauthn4j.data.attestation.statement.AttestationStatement;
import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier;
import com.webauthn4j.data.client.Origin; import com.webauthn4j.data.client.Origin;
import com.webauthn4j.data.client.challenge.Challenge; import com.webauthn4j.data.client.challenge.Challenge;
import com.webauthn4j.data.client.challenge.DefaultChallenge; 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.server.ServerProperty;
import com.webauthn4j.util.exception.WebAuthnException; 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.androidkey.AndroidKeyAttestationStatementValidator;
import com.webauthn4j.validator.attestation.statement.androidsafetynet.AndroidSafetyNetAttestationStatementValidator; import com.webauthn4j.validator.attestation.statement.androidsafetynet.AndroidSafetyNetAttestationStatementValidator;
import com.webauthn4j.validator.attestation.statement.none.NoneAttestationStatementValidator; 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 // check User Verification by considering a malicious user might modify the result of calling WebAuthn API
boolean isUserVerificationRequired = policy.getUserVerificationRequirement().equals(WebAuthnConstants.OPTION_REQUIRED); boolean isUserVerificationRequired = policy.getUserVerificationRequirement().equals(WebAuthnConstants.OPTION_REQUIRED);
RegistrationRequest registrationRequest = new RegistrationRequest(attestationObject, clientDataJSON);
RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, isUserVerificationRequired);
WebAuthnManager webAuthnManager = createWebAuthnManager();
try { try {
WebAuthnRegistrationContext registrationContext = new WebAuthnRegistrationContext(clientDataJSON, attestationObject, serverProperty, isUserVerificationRequired); // parse
WebAuthnRegistrationContextValidator webAuthnRegistrationContextValidator = createWebAuthnRegistrationContextValidator(); RegistrationData registrationData = webAuthnManager.parse(registrationRequest);
WebAuthnRegistrationContextValidationResponse response = webAuthnRegistrationContextValidator.validate(registrationContext); // validate
webAuthnManager.validate(registrationData, registrationParameters);
showInfoAfterWebAuthnApiCreate(response); showInfoAfterWebAuthnApiCreate(registrationData);
checkAcceptedAuthenticator(response, policy); checkAcceptedAuthenticator(registrationData, policy);
WebAuthnCredentialModelInput credential = new WebAuthnCredentialModelInput(getCredentialType()); WebAuthnCredentialModelInput credential = new WebAuthnCredentialModelInput(getCredentialType());
credential.setAttestedCredentialData(response.getAttestationObject().getAuthenticatorData().getAttestedCredentialData()); credential.setAttestedCredentialData(registrationData.getAttestationObject().getAuthenticatorData().getAttestedCredentialData());
credential.setCount(response.getAttestationObject().getAuthenticatorData().getSignCount()); credential.setCount(registrationData.getAttestationObject().getAuthenticatorData().getSignCount());
// Save new webAuthn credential // Save new webAuthn credential
WebAuthnCredentialProvider webAuthnCredProvider = (WebAuthnCredentialProvider) this.session.getProvider(CredentialProvider.class, getCredentialProviderId()); WebAuthnCredentialProvider webAuthnCredProvider = (WebAuthnCredentialProvider) this.session.getProvider(CredentialProvider.class, getCredentialProviderId());
@ -245,8 +251,8 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
} }
} }
private WebAuthnRegistrationContextValidator createWebAuthnRegistrationContextValidator() { private WebAuthnManager createWebAuthnManager() {
return new WebAuthnRegistrationContextValidator( return new WebAuthnManager(
Arrays.asList( Arrays.asList(
new NoneAttestationStatementValidator(), new NoneAttestationStatementValidator(),
new PackedAttestationStatementValidator(), new PackedAttestationStatementValidator(),
@ -257,8 +263,10 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
), this.certPathtrustValidator, ), this.certPathtrustValidator,
new DefaultECDAATrustworthinessValidator(), new DefaultECDAATrustworthinessValidator(),
new DefaultSelfAttestationTrustworthinessValidator(), new DefaultSelfAttestationTrustworthinessValidator(),
new JsonConverter(), Collections.emptyList(), // Custom Registration Validator is not supported
new CborConverter()); Collections.emptyList(), // Custom Authentication Validator is not supported
new ObjectConverter()
);
} }
private String stringifySignatureAlgorithms(List<String> signatureAlgorithmsList) { private String stringifySignatureAlgorithms(List<String> signatureAlgorithmsList) {
@ -304,7 +312,7 @@ public class WebAuthnRegister implements RequiredActionProvider, CredentialRegis
return sb.toString(); return sb.toString();
} }
private void showInfoAfterWebAuthnApiCreate(WebAuthnRegistrationContextValidationResponse response) { private void showInfoAfterWebAuthnApiCreate(RegistrationData response) {
AttestedCredentialData attestedCredentialData = response.getAttestationObject().getAuthenticatorData().getAttestedCredentialData(); AttestedCredentialData attestedCredentialData = response.getAttestationObject().getAuthenticatorData().getAttestedCredentialData();
AttestationStatement attestationStatement = response.getAttestationObject().getAttestationStatement(); AttestationStatement attestationStatement = response.getAttestationObject().getAttestationStatement();
logger.debugv("createad key's algorithm = {0}", String.valueOf(attestedCredentialData.getCOSEKey().getAlgorithm().getValue())); 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()); 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(); String aaguid = response.getAttestationObject().getAuthenticatorData().getAttestedCredentialData().getAaguid().toString();
List<String> acceptableAaguids = policy.getAcceptableAaguids(); List<String> acceptableAaguids = policy.getAcceptableAaguids();
boolean isAcceptedAuthenticator = false; boolean isAcceptedAuthenticator = false;

View file

@ -16,6 +16,7 @@
package org.keycloak.credential; package org.keycloak.credential;
import com.webauthn4j.converter.util.ObjectConverter;
import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Base64Url;
import com.webauthn4j.converter.util.CborConverter; import com.webauthn4j.converter.util.CborConverter;
@ -23,20 +24,20 @@ import com.webauthn4j.data.attestation.statement.AttestationStatement;
public class AttestationStatementConverter { public class AttestationStatementConverter {
private CborConverter converter; private CborConverter cborConverter;
public AttestationStatementConverter(CborConverter converter) { public AttestationStatementConverter(ObjectConverter objectConverter) {
this.converter = converter; this.cborConverter = objectConverter.getCborConverter();
} }
public String convertToDatabaseColumn(AttestationStatement attribute) { public String convertToDatabaseColumn(AttestationStatement attribute) {
AttestationStatementSerializationContainer container = new AttestationStatementSerializationContainer(attribute); AttestationStatementSerializationContainer container = new AttestationStatementSerializationContainer(attribute);
return Base64Url.encode(converter.writeValueAsBytes(container)); return Base64Url.encode(cborConverter.writeValueAsBytes(container));
} }
public AttestationStatement convertToEntityAttribute(String dbData) { public AttestationStatement convertToEntityAttribute(String dbData) {
byte[] data = Base64Url.decode(dbData); byte[] data = Base64Url.decode(dbData);
AttestationStatementSerializationContainer container = converter.readValue(data, AttestationStatementSerializationContainer.class); AttestationStatementSerializationContainer container = cborConverter.readValue(data, AttestationStatementSerializationContainer.class);
return container.getAttestationStatement(); return container.getAttestationStatement();
} }
} }

View file

@ -16,6 +16,7 @@
package org.keycloak.credential; package org.keycloak.credential;
import com.webauthn4j.converter.util.ObjectConverter;
import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Base64Url;
import com.webauthn4j.converter.util.CborConverter; import com.webauthn4j.converter.util.CborConverter;
@ -23,17 +24,17 @@ import com.webauthn4j.data.attestation.authenticator.COSEKey;
public class CredentialPublicKeyConverter { public class CredentialPublicKeyConverter {
private CborConverter converter; private CborConverter cborConverter;
public CredentialPublicKeyConverter(CborConverter converter) { public CredentialPublicKeyConverter(ObjectConverter objectConverter) {
this.converter = converter; this.cborConverter = objectConverter.getCborConverter();
} }
public String convertToDatabaseColumn(COSEKey credentialPublicKey) { public String convertToDatabaseColumn(COSEKey credentialPublicKey) {
return Base64Url.encode(converter.writeValueAsBytes(credentialPublicKey)); return Base64Url.encode(cborConverter.writeValueAsBytes(credentialPublicKey));
} }
public COSEKey convertToEntityAttribute(String s) { public COSEKey convertToEntityAttribute(String s) {
return converter.readValue(Base64Url.decode(s), COSEKey.class); return cborConverter.readValue(Base64Url.decode(s), COSEKey.class);
} }
} }

View file

@ -18,17 +18,18 @@ package org.keycloak.credential;
import org.keycloak.common.util.Base64; 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.AttestedCredentialData;
import com.webauthn4j.data.attestation.authenticator.COSEKey; import com.webauthn4j.data.attestation.authenticator.COSEKey;
import com.webauthn4j.data.attestation.statement.AttestationStatement; import com.webauthn4j.data.attestation.statement.AttestationStatement;
import org.keycloak.models.credential.WebAuthnCredentialModel;
public class WebAuthnCredentialModelInput implements CredentialInput { public class WebAuthnCredentialModelInput implements CredentialInput {
private AttestedCredentialData attestedCredentialData; private AttestedCredentialData attestedCredentialData;
private AttestationStatement attestationStatement; 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 long count;
private String credentialDBId; private String credentialDBId;
private final String credentialType; private final String credentialType;
@ -65,12 +66,20 @@ public class WebAuthnCredentialModelInput implements CredentialInput {
return count; return count;
} }
public WebAuthnAuthenticationContext getAuthenticationContext() { public AuthenticationParameters getAuthenticationParameters() {
return authenticationContext; return authenticationParameters;
} }
public void setAuthenticationContext(WebAuthnAuthenticationContext authenticationContext) { public void setAuthenticationParameters(AuthenticationParameters authenticationParameters) {
this.authenticationContext = authenticationContext; this.authenticationParameters = authenticationParameters;
}
public AuthenticationRequest getAuthenticationRequest() {
return authenticationRequest;
}
public void setAuthenticationRequest(AuthenticationRequest authenticationRequest) {
this.authenticationRequest = authenticationRequest;
} }
public void setAttestedCredentialData(AttestedCredentialData attestedCredentialData) { public void setAttestedCredentialData(AttestedCredentialData attestedCredentialData) {
@ -127,10 +136,10 @@ public class WebAuthnCredentialModelInput implements CredentialInput {
.append(credPubKey.getKeyType().name()) .append(credPubKey.getKeyType().name())
.append(","); .append(",");
} }
if (authenticationContext != null) { if (authenticationRequest != null) {
// only set on Authentication // only set on Authentication
sb.append("Credential Id = ") sb.append("Credential Id = ")
.append(Base64.encodeBytes(authenticationContext.getCredentialId())) .append(Base64.encodeBytes(authenticationRequest.getCredentialId()))
.append(","); .append(",");
} }
if (sb.length() > 0) if (sb.length() > 0)

View file

@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.webauthn4j.converter.util.ObjectConverter;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory; import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory;
import org.keycloak.common.util.Base64; import org.keycloak.common.util.Base64;
@ -29,15 +30,15 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import com.webauthn4j.WebAuthnManager;
import com.webauthn4j.authenticator.Authenticator; import com.webauthn4j.authenticator.Authenticator;
import com.webauthn4j.authenticator.AuthenticatorImpl; 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.AAGUID;
import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData;
import com.webauthn4j.data.attestation.authenticator.COSEKey; import com.webauthn4j.data.attestation.authenticator.COSEKey;
import com.webauthn4j.util.exception.WebAuthnException; 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.WebAuthnCredentialModel;
import org.keycloak.models.credential.dto.WebAuthnCredentialData; import org.keycloak.models.credential.dto.WebAuthnCredentialData;
@ -53,12 +54,12 @@ public class WebAuthnCredentialProvider implements CredentialProvider<WebAuthnCr
private CredentialPublicKeyConverter credentialPublicKeyConverter; private CredentialPublicKeyConverter credentialPublicKeyConverter;
private AttestationStatementConverter attestationStatementConverter; private AttestationStatementConverter attestationStatementConverter;
public WebAuthnCredentialProvider(KeycloakSession session, CborConverter converter) { public WebAuthnCredentialProvider(KeycloakSession session, ObjectConverter objectConverter) {
this.session = session; this.session = session;
if (credentialPublicKeyConverter == null) if (credentialPublicKeyConverter == null)
credentialPublicKeyConverter = new CredentialPublicKeyConverter(converter); credentialPublicKeyConverter = new CredentialPublicKeyConverter(objectConverter);
if (attestationStatementConverter == null) if (attestationStatementConverter == null)
attestationStatementConverter = new AttestationStatementConverter(converter); attestationStatementConverter = new AttestationStatementConverter(objectConverter);
} }
private UserCredentialStore getCredentialStore() { private UserCredentialStore getCredentialStore() {
@ -163,26 +164,32 @@ public class WebAuthnCredentialProvider implements CredentialProvider<WebAuthnCr
WebAuthnCredentialModelInput context = WebAuthnCredentialModelInput.class.cast(input); WebAuthnCredentialModelInput context = WebAuthnCredentialModelInput.class.cast(input);
List<WebAuthnCredentialModelInput> auths = getWebAuthnCredentialModelList(realm, user); List<WebAuthnCredentialModelInput> auths = getWebAuthnCredentialModelList(realm, user);
WebAuthnAuthenticationContextValidator webAuthnAuthenticationContextValidator = WebAuthnManager webAuthnManager = WebAuthnManager.createNonStrictWebAuthnManager(); // not special setting is needed for authentication's validation.
new WebAuthnAuthenticationContextValidator(); AuthenticationData authenticationData = null;
try { try {
for (WebAuthnCredentialModelInput auth : auths) { for (WebAuthnCredentialModelInput auth : auths) {
byte[] credentialId = auth.getAttestedCredentialData().getCredentialId(); byte[] credentialId = auth.getAttestedCredentialData().getCredentialId();
if (Arrays.equals(credentialId, context.getAuthenticationContext().getCredentialId())) { if (Arrays.equals(credentialId, context.getAuthenticationRequest().getCredentialId())) {
Authenticator authenticator = new AuthenticatorImpl( Authenticator authenticator = new AuthenticatorImpl(
auth.getAttestedCredentialData(), auth.getAttestedCredentialData(),
auth.getAttestationStatement(), auth.getAttestationStatement(),
auth.getCount() auth.getCount()
); );
// WebAuthnException is thrown if validation fails // parse
WebAuthnAuthenticationContextValidationResponse response = authenticationData = webAuthnManager.parse(context.getAuthenticationRequest());
webAuthnAuthenticationContextValidator.validate( // validate
context.getAuthenticationContext(), AuthenticationParameters authenticationParameters = new AuthenticationParameters(
authenticator); 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 // update authenticator counter
long count = auth.getCount(); long count = auth.getCount();

View file

@ -19,14 +19,14 @@ package org.keycloak.credential;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import com.webauthn4j.converter.util.CborConverter; import com.webauthn4j.converter.util.ObjectConverter;
import org.keycloak.provider.EnvironmentDependentProviderFactory; import org.keycloak.provider.EnvironmentDependentProviderFactory;
public class WebAuthnCredentialProviderFactory implements CredentialProviderFactory<WebAuthnCredentialProvider>, EnvironmentDependentProviderFactory { public class WebAuthnCredentialProviderFactory implements CredentialProviderFactory<WebAuthnCredentialProvider>, EnvironmentDependentProviderFactory {
public static final String PROVIDER_ID = "keycloak-webauthn"; public static final String PROVIDER_ID = "keycloak-webauthn";
private static CborConverter converter = new CborConverter(); private static ObjectConverter converter = new ObjectConverter();
@Override @Override
public CredentialProvider create(KeycloakSession session) { public CredentialProvider create(KeycloakSession session) {

View file

@ -18,7 +18,7 @@
package org.keycloak.credential; 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.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.credential.WebAuthnCredentialModel; import org.keycloak.models.credential.WebAuthnCredentialModel;
@ -30,8 +30,8 @@ import org.keycloak.models.credential.WebAuthnCredentialModel;
*/ */
public class WebAuthnPasswordlessCredentialProvider extends WebAuthnCredentialProvider { public class WebAuthnPasswordlessCredentialProvider extends WebAuthnCredentialProvider {
public WebAuthnPasswordlessCredentialProvider(KeycloakSession session, CborConverter converter) { public WebAuthnPasswordlessCredentialProvider(KeycloakSession session, ObjectConverter objectConverter) {
super(session, converter); super(session, objectConverter);
} }
@Override @Override

View file

@ -18,7 +18,7 @@
package org.keycloak.credential; package org.keycloak.credential;
import com.webauthn4j.converter.util.CborConverter; import com.webauthn4j.converter.util.ObjectConverter;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.EnvironmentDependentProviderFactory; import org.keycloak.provider.EnvironmentDependentProviderFactory;
@ -30,11 +30,11 @@ public class WebAuthnPasswordlessCredentialProviderFactory implements Credential
public static final String PROVIDER_ID = "keycloak-webauthn-passwordless"; public static final String PROVIDER_ID = "keycloak-webauthn-passwordless";
private static CborConverter converter = new CborConverter(); private static ObjectConverter objectConverter = new ObjectConverter();
@Override @Override
public CredentialProvider create(KeycloakSession session) { public CredentialProvider create(KeycloakSession session) {
return new WebAuthnPasswordlessCredentialProvider(session, converter); return new WebAuthnPasswordlessCredentialProvider(session, objectConverter);
} }
@Override @Override