Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #217 WebAuthn Authenticator Registration fail when Yubikey5+Chrome+ResidentKey #218

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion openam-authentication/openam-auth-webauthn/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<packaging>jar</packaging>

<properties>
<webauthn4j.version>0.9.14.RELEASE</webauthn4j.version>
<webauthn4j.version>0.12.0.RELEASE</webauthn4j.version>
</properties>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -47,6 +55,8 @@
*/
public class WebAuthn4JValidatorImpl implements WebAuthnValidator {

private WebAuthnManager webAuthnManager = WebAuthnManager.createNonStrictWebAuthnManager();

private Challenge generatedChallenge = new DefaultChallenge();

@Override
Expand All @@ -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<String> _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<String> _expectedExtensionIds = new ArrayList<String>();
_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;

Expand All @@ -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<String> _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 {}",
Expand Down