diff --git a/openam-authentication/openam-auth-webauthn/pom.xml b/openam-authentication/openam-auth-webauthn/pom.xml index a02ab5834d..a943da091b 100644 --- a/openam-authentication/openam-auth-webauthn/pom.xml +++ b/openam-authentication/openam-auth-webauthn/pom.xml @@ -31,7 +31,7 @@ jar - 0.9.14.RELEASE + 0.12.0.RELEASE diff --git a/openam-authentication/openam-auth-webauthn/src/main/java/jp/co/osstech/openam/authentication/modules/webauthn/WebAuthn4JValidatorImpl.java b/openam-authentication/openam-auth-webauthn/src/main/java/jp/co/osstech/openam/authentication/modules/webauthn/WebAuthn4JValidatorImpl.java index 5c992c7ffa..d9d404851d 100644 --- a/openam-authentication/openam-auth-webauthn/src/main/java/jp/co/osstech/openam/authentication/modules/webauthn/WebAuthn4JValidatorImpl.java +++ b/openam-authentication/openam-auth-webauthn/src/main/java/jp/co/osstech/openam/authentication/modules/webauthn/WebAuthn4JValidatorImpl.java @@ -11,19 +11,30 @@ * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * - * Copyright 2019 Open Source Solution Technology Corporation + * Copyright 2019-2020 Open Source Solution Technology Corporation */ package jp.co.osstech.openam.authentication.modules.webauthn; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + import com.sun.identity.authentication.spi.AuthLoginException; import com.sun.identity.shared.debug.Debug; - +import com.webauthn4j.WebAuthnManager; import com.webauthn4j.authenticator.Authenticator; import com.webauthn4j.authenticator.AuthenticatorImpl; +import com.webauthn4j.converter.exception.DataConversionException; import com.webauthn4j.converter.util.CborConverter; -import com.webauthn4j.data.WebAuthnAuthenticationContext; -import com.webauthn4j.data.WebAuthnRegistrationContext; +import com.webauthn4j.converter.util.ObjectConverter; +import com.webauthn4j.data.AuthenticationData; +import com.webauthn4j.data.AuthenticationParameters; +import com.webauthn4j.data.AuthenticationRequest; +import com.webauthn4j.data.RegistrationData; +import com.webauthn4j.data.RegistrationParameters; +import com.webauthn4j.data.RegistrationRequest; import com.webauthn4j.data.attestation.authenticator.AAGUID; import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; import com.webauthn4j.data.attestation.authenticator.COSEKey; @@ -35,10 +46,7 @@ import com.webauthn4j.server.ServerProperty; import com.webauthn4j.util.ArrayUtil; import com.webauthn4j.util.Base64UrlUtil; -import com.webauthn4j.validator.WebAuthnAuthenticationContextValidationResponse; -import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; -import com.webauthn4j.validator.WebAuthnRegistrationContextValidationResponse; -import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; +import com.webauthn4j.validator.exception.ValidationException; import jp.co.osstech.openam.core.rest.devices.services.webauthn.WebAuthnAuthenticator; @@ -47,6 +55,8 @@ */ public class WebAuthn4JValidatorImpl implements WebAuthnValidator { + private WebAuthnManager webAuthnManager = WebAuthnManager.createNonStrictWebAuthnManager(); + private Challenge generatedChallenge = new DefaultChallenge(); @Override @@ -64,42 +74,58 @@ public WebAuthnAuthenticator validateCreateResponse(WebAuthnValidatorConfig conf * flow. This time use webauthn4j library to END * START============================. */ - byte[] _attestationObjectBytes = Base64UrlUtil.decode(responseJson.getAttestationObject()); byte[] _clientDataJsonBytes = Base64UrlUtil.decode(responseJson.getClientDataJSON()); + byte[] _attestationObjectBytes = Base64UrlUtil.decode(responseJson.getAttestationObject()); + String _clientExtensionJSON = null; + Set _transports = null; Origin _origin = new Origin(config.getOrigin()); String _rpId = _origin.getHost(); byte[] _tokenBindingId = null; - - // rp:id = origin - ServerProperty serverProperty = new ServerProperty(_origin, _rpId, generatedChallenge, _tokenBindingId); - - WebAuthnRegistrationContext registrationContext = new WebAuthnRegistrationContext(_clientDataJsonBytes, - _attestationObjectBytes, serverProperty, config.isVerificationRequired()); - WebAuthnRegistrationContextValidator webAuthnRegistrationContextValidator = WebAuthnRegistrationContextValidator - .createNonStrictRegistrationContextValidator(); - WebAuthnRegistrationContextValidationResponse response = webAuthnRegistrationContextValidator - .validate(registrationContext); - - // CredentialId <-- AttestedCredentialData <--AuthenticationData - // <--AttestationObject - byte[] attestedCredentialIdBytes = ArrayUtil.clone(response.getAttestationObject().getAuthenticatorData() + ServerProperty _serverProperty = new ServerProperty(_origin, _rpId, generatedChallenge, _tokenBindingId); + + boolean _userVerificationRequired = config.isVerificationRequired(); + boolean _userPresenceRequired = true; + List _expectedExtensionIds = new ArrayList(); + _expectedExtensionIds.add("credProtect"); // Because of CTAPv2.1(draft!) Yubikey5 + GoogleChrome return "credProtect" extension when residentkey=true. + + RegistrationRequest _registrationRequest = new RegistrationRequest(_attestationObjectBytes, _clientDataJsonBytes, + _clientExtensionJSON, _transports); + RegistrationParameters _registrationParameters = new RegistrationParameters(_serverProperty, _userVerificationRequired, + _userPresenceRequired, _expectedExtensionIds); + + RegistrationData _registrationData; + try{ + _registrationData = webAuthnManager.parse(_registrationRequest); + webAuthnManager.validate(_registrationData, _registrationParameters); + } catch (DataConversionException e){ + // If you would like to handle WebAuthn data structure parse error, please catch DataConversionException + throw e; + } catch (ValidationException e){ + // If you would like to handle WebAuthn data validation error, please catch ValidationException + throw e; + } + + // CredentialId = RegistrationData --> AttestationObject --> AuthenticatorData --> + // AttstedCredentialData --> CredentialId + byte[] _attestedCredentialIdBytes = ArrayUtil.clone(_registrationData.getAttestationObject().getAuthenticatorData() .getAttestedCredentialData().getCredentialId()); - // COSEKey <-- AttestedCredentialData <--AuthenticationData - // <--AttestationObject - COSEKey coseKey = response.getAttestationObject().getAuthenticatorData() + // COSEKey = RegistrationData --> AttestationObject --> AuthenticatorData --> + // AttstedCredentialData --> COSEKey + COSEKey _coseKey = _registrationData.getAttestationObject().getAuthenticatorData() .getAttestedCredentialData().getCOSEKey(); - // Counter <--AuthenticationData <--AttestationObject - long attestedCounter = response.getAttestationObject().getAuthenticatorData().getSignCount(); + // Counter = RegistrationData --> AttestationObject --> AuthenticatorData --> Counter + long _attestedCounter = _registrationData.getAttestationObject().getAuthenticatorData().getSignCount(); - CborConverter _cborConverter = new CborConverter(); + ObjectConverter _objectConverter = new ObjectConverter(); + CborConverter _cborConverter = _objectConverter.getCborConverter(); WebAuthnAuthenticator amAuthenticator = new WebAuthnAuthenticator( - Base64UrlUtil.encodeToString(attestedCredentialIdBytes), - _cborConverter.writeValueAsBytes(coseKey), - new Long(attestedCounter), userHandleIdBytes); + Base64UrlUtil.encodeToString(_attestedCredentialIdBytes), + _cborConverter.writeValueAsBytes(_coseKey), + new Long(_attestedCounter), userHandleIdBytes); return amAuthenticator; @@ -120,43 +146,71 @@ public void validateGetResponse(WebAuthnValidatorConfig config, WebAuthnJsonCall * flow. Use webauthn4j library to END * START============================. */ - byte[] rawIdBytes = Base64UrlUtil.decode(responseJson.getRawId()); - byte[] authenticatorDataBytes = Base64UrlUtil.decode(responseJson.getAuthenticatorData()); - byte[] clientDataJsonBytes = Base64UrlUtil.decode(responseJson.getClientDataJSON()); - byte[] signatureBytes = Base64UrlUtil.decode(responseJson.getSignature()); + byte[] _rawId = Base64UrlUtil.decode(responseJson.getRawId()); + byte[] _userHandle = Base64UrlUtil.decode(responseJson.getUserHandle()); + byte[] _authenticatorDataBytes = Base64UrlUtil.decode(responseJson.getAuthenticatorData()); + byte[] _clientDataJsonBytes = Base64UrlUtil.decode(responseJson.getClientDataJSON()); + String _clientExtensionJSON = null; + byte[] _signatureBytes = Base64UrlUtil.decode(responseJson.getSignature()); Origin _origin = new Origin(config.getOrigin()); String _rpId = _origin.getHost(); - byte[] _tokenBindingId = null; /* now set tokenBindingId null */ - ServerProperty serverProperty = new ServerProperty(_origin, _rpId, generatedChallenge, _tokenBindingId); + byte[] _tokenBindingId = null; + ServerProperty _serverProperty = new ServerProperty(_origin, _rpId, generatedChallenge, _tokenBindingId); - WebAuthnAuthenticationContext authenticationContext = new WebAuthnAuthenticationContext(rawIdBytes, - clientDataJsonBytes, authenticatorDataBytes, signatureBytes, serverProperty, - config.isVerificationRequired()); + boolean _userVerificationRequired = config.isVerificationRequired(); + boolean _userPresenceRequired = true; + List _expectedExtensionIds = Collections.emptyList(); //OpenAM desn't use extension now. // OpenAM didn't store aaguid now. Use ZERO AAGUID. AAGUID _aaguid = AAGUID.ZERO; //credentialPublicKey was stored as COSEKey byte[] data at registration time. - CborConverter _cborConverter = new CborConverter(); - COSEKey coseKey = + ObjectConverter _objectConverter = new ObjectConverter(); + CborConverter _cborConverter = _objectConverter.getCborConverter(); + COSEKey _coseKey = _cborConverter.readValue(amAuthenticator.getPublicKey(), COSEKey.class); - final AttestedCredentialData storedAttestedCredentialData = + final AttestedCredentialData _storedAttestedCredentialData = new AttestedCredentialData(_aaguid, - Base64UrlUtil.decode(amAuthenticator.getCredentialID()), coseKey); - final AttestationStatement noneAttestationStatement = new NoneAttestationStatement(); - final long storedCounter = amAuthenticator.getSignCount(); - Authenticator authenticator = new AuthenticatorImpl(storedAttestedCredentialData, - noneAttestationStatement, storedCounter); - - WebAuthnAuthenticationContextValidator webAuthnAuthenticationContextValidator = new WebAuthnAuthenticationContextValidator(); - - WebAuthnAuthenticationContextValidationResponse response = webAuthnAuthenticationContextValidator - .validate(authenticationContext, authenticator); - - // Update counter - amAuthenticator.setSignCount(response.getAuthenticatorData().getSignCount()); + Base64UrlUtil.decode(amAuthenticator.getCredentialID()), _coseKey); + final AttestationStatement _noneAttestationStatement = new NoneAttestationStatement(); + final long _storedCounter = amAuthenticator.getSignCount(); + Authenticator _authenticator = new AuthenticatorImpl(_storedAttestedCredentialData, + _noneAttestationStatement, _storedCounter); + + AuthenticationRequest _authenticationRequest = + new AuthenticationRequest( + _rawId, + _userHandle, + _authenticatorDataBytes, + _clientDataJsonBytes, + _clientExtensionJSON, + _signatureBytes + ); + AuthenticationParameters _authenticationParameters = + new AuthenticationParameters( + _serverProperty, + _authenticator, + _userVerificationRequired, + _userPresenceRequired, + _expectedExtensionIds + ); + + AuthenticationData _authenticationData; + try{ + _authenticationData = webAuthnManager.parse(_authenticationRequest); + webAuthnManager.validate(_authenticationData, _authenticationParameters); + } catch (DataConversionException e){ + // If you would like to handle WebAuthn data structure parse error, please catch DataConversionException + throw e; + } catch (ValidationException e){ + // If you would like to handle WebAuthn data validation error, please catch ValidationException + throw e; + } + + // Update Counter = AuthenticationData --> AuthenticatorData --> Counter + amAuthenticator.setSignCount(_authenticationData.getAuthenticatorData().getSignCount()); } catch (Exception ex) { debug.error("WebAuthnValidator.validateGetResponse : Error validating response. User handle is {}",