diff --git a/fido/src/main/java/com/yubico/yubikit/fido/client/BasicWebAuthnClient.java b/fido/src/main/java/com/yubico/yubikit/fido/client/BasicWebAuthnClient.java index f21c196b..97c54b83 100644 --- a/fido/src/main/java/com/yubico/yubikit/fido/client/BasicWebAuthnClient.java +++ b/fido/src/main/java/com/yubico/yubikit/fido/client/BasicWebAuthnClient.java @@ -25,9 +25,7 @@ import com.yubico.yubikit.fido.ctap.PinUvAuthDummyProtocol; import com.yubico.yubikit.fido.ctap.PinUvAuthProtocolV1; import com.yubico.yubikit.fido.webauthn.AttestationObject; -import com.yubico.yubikit.fido.webauthn.AttestedCredentialData; import com.yubico.yubikit.fido.webauthn.AuthenticatorAttestationResponse; -import com.yubico.yubikit.fido.webauthn.AuthenticatorData; import com.yubico.yubikit.fido.webauthn.AuthenticatorSelectionCriteria; import com.yubico.yubikit.fido.webauthn.PublicKeyCredential; import com.yubico.yubikit.fido.webauthn.PublicKeyCredentialCreationOptions; @@ -40,10 +38,10 @@ import java.io.Closeable; import java.io.IOException; -import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -340,74 +338,89 @@ protected Ctap2Session.CredentialData ctapMakeCredential( @Nullable CommandState state ) throws IOException, CommandException, ClientError { - if (options.getExtensions() != null) { - throw new ClientError(ClientError.Code.CONFIGURATION_UNSUPPORTED, "Extensions not supported"); - } - - Map rp = options.getRp().toMap(); - String rpId = options.getRp().getId(); - if (rpId == null) { - ((Map) rp).put("id", effectiveDomain); - } else if (!(effectiveDomain.equals(rpId) || effectiveDomain.endsWith("." + rpId))) { - throw new ClientError(ClientError.Code.BAD_REQUEST, "RP ID is not valid for effective domain"); - } - - byte[] pinUvAuthParam = null; - int pinUvAuthProtocol = 0; + byte[] pinToken = null; + try { + if (options.getExtensions() != null) { + throw new ClientError( + ClientError.Code.CONFIGURATION_UNSUPPORTED, + "Extensions not supported"); + } - Map ctapOptions = new HashMap<>(); - AuthenticatorSelectionCriteria authenticatorSelection = options.getAuthenticatorSelection(); - if (authenticatorSelection != null) { - String residentKeyRequirement = authenticatorSelection.getResidentKey(); - if (ResidentKeyRequirement.REQUIRED.equals(residentKeyRequirement) || - (ResidentKeyRequirement.PREFERRED.equals(residentKeyRequirement) && - (pinSupported || uvSupported) - ) - ) { - ctapOptions.put(OPTION_RESIDENT_KEY, true); + Map rp = options.getRp().toMap(); + String rpId = options.getRp().getId(); + if (rpId == null) { + ((Map) rp).put("id", effectiveDomain); + } else if (!(effectiveDomain.equals(rpId) || effectiveDomain.endsWith("." + rpId))) { + throw new ClientError( + ClientError.Code.BAD_REQUEST, + "RP ID is not valid for effective domain"); } - if (getCtapUv(authenticatorSelection.getUserVerification(), pin != null)) { - ctapOptions.put(OPTION_USER_VERIFICATION, true); + + byte[] pinUvAuthParam = null; + int pinUvAuthProtocol = 0; + + Map ctapOptions = new HashMap<>(); + AuthenticatorSelectionCriteria authenticatorSelection = + options.getAuthenticatorSelection(); + if (authenticatorSelection != null) { + String residentKeyRequirement = authenticatorSelection.getResidentKey(); + if (ResidentKeyRequirement.REQUIRED.equals(residentKeyRequirement) || + (ResidentKeyRequirement.PREFERRED.equals(residentKeyRequirement) && + (pinSupported || uvSupported) + ) + ) { + ctapOptions.put(OPTION_RESIDENT_KEY, true); + } + if (getCtapUv(authenticatorSelection.getUserVerification(), pin != null)) { + ctapOptions.put(OPTION_USER_VERIFICATION, true); + } + } else { + if (getCtapUv(UserVerificationRequirement.PREFERRED, pin != null)) { + ctapOptions.put(OPTION_USER_VERIFICATION, true); + } } - } else { - if (getCtapUv(UserVerificationRequirement.PREFERRED, pin != null)) { - ctapOptions.put(OPTION_USER_VERIFICATION, true); + + if (pin != null) { + pinToken = clientPin.getPinToken(pin); + pinUvAuthParam = clientPin.getPinUvAuth().authenticate(pinToken, clientDataHash); + pinUvAuthProtocol = clientPin.getPinUvAuth().getVersion(); + } else if (pinConfigured && !ctapOptions.containsKey(OPTION_USER_VERIFICATION)) { + throw new PinRequiredClientError(); } - } - if (pin != null) { - byte[] pinToken = clientPin.getPinToken(pin); - pinUvAuthParam = clientPin.getPinUvAuth().authenticate(pinToken, clientDataHash); - pinUvAuthProtocol = clientPin.getPinUvAuth().getVersion(); - } else if (pinConfigured && !ctapOptions.containsKey(OPTION_USER_VERIFICATION)) { - throw new PinRequiredClientError(); - } + final List excludeCredentials = + removeUnsupportedCredentials( + options.getExcludeCredentials() + ); - final List excludeCredentials = removeUnsupportedCredentials( - options.getExcludeCredentials() - ); + final Map user = CredentialManager.credentialUserEntityToMap( + options.getUser() + ); - final Map user = CredentialManager.credentialUserEntityToMap(options.getUser()); + List> pubKeyCredParams = new ArrayList<>(); + for (PublicKeyCredentialParameters param : options.getPubKeyCredParams()) { + if (isPublicKeyCredentialTypeSupported(param.getType())) { + pubKeyCredParams.add(param.toMap()); + } + } - List> pubKeyCredParams = new ArrayList<>(); - for (PublicKeyCredentialParameters param : options.getPubKeyCredParams()) { - if (isPublicKeyCredentialTypeSupported(param.getType())) { - pubKeyCredParams.add(param.toMap()); + return ctap.makeCredential( + clientDataHash, + rp, + user, + pubKeyCredParams, + getCredentialList(excludeCredentials), + null, + ctapOptions.isEmpty() ? null : ctapOptions, + pinUvAuthParam, + pinUvAuthProtocol, + state + ); + } finally { + if (pinToken != null) { + Arrays.fill(pinToken, (byte) 0); } } - - return ctap.makeCredential( - clientDataHash, - rp, - user, - pubKeyCredParams, - getCredentialList(excludeCredentials), - null, - ctapOptions.isEmpty() ? null : ctapOptions, - pinUvAuthParam, - pinUvAuthProtocol, - state - ); } /** @@ -454,9 +467,10 @@ protected List ctapGetAssertions( byte[] pinUvAuthParam = null; int pinUvAuthProtocol = 0; + byte[] pinToken = null; try { if (pin != null) { - byte[] pinToken = clientPin.getPinToken(pin); + pinToken = clientPin.getPinToken(pin); pinUvAuthParam = clientPin.getPinUvAuth().authenticate(pinToken, clientDataHash); pinUvAuthProtocol = clientPin.getPinUvAuth().getVersion(); } @@ -480,6 +494,10 @@ protected List ctapGetAssertions( throw new PinInvalidClientError(e, clientPin.getPinRetries()); } throw ClientError.wrapCtapException(e); + } finally { + if (pinToken != null) { + Arrays.fill(pinToken, (byte) 0); + } } } diff --git a/fido/src/main/java/com/yubico/yubikit/fido/ctap/ClientPin.java b/fido/src/main/java/com/yubico/yubikit/fido/ctap/ClientPin.java index 7ba29bc7..53fd2738 100755 --- a/fido/src/main/java/com/yubico/yubikit/fido/ctap/ClientPin.java +++ b/fido/src/main/java/com/yubico/yubikit/fido/ctap/ClientPin.java @@ -124,8 +124,11 @@ public int getPinRetries() throws IOException, CommandException { */ public byte[] getPinToken(char[] pin) throws IOException, CommandException { Pair, byte[]> pair = getSharedSecret(); + byte[] pinHash = null; try { - byte[] pinHash = Arrays.copyOf(MessageDigest.getInstance("SHA-256").digest(preparePin(pin, false)), PIN_HASH_LEN); + pinHash = Arrays.copyOf( + MessageDigest.getInstance("SHA-256").digest(preparePin(pin, false)), + PIN_HASH_LEN); byte[] pinHashEnc = pinUvAuth.encrypt(pair.second, pinHash); Logger.debug(logger, "Getting PIN token"); @@ -144,6 +147,10 @@ public byte[] getPinToken(char[] pin) throws IOException, CommandException { } catch (NoSuchAlgorithmException e) { Logger.error(logger, "Failure getting PIN token: ", e); throw new IllegalStateException(e); + } finally { + if (pinHash != null) { + Arrays.fill(pinHash, (byte) 0); + } } } @@ -178,12 +185,18 @@ public void setPin(char[] pin) throws IOException, CommandException { * @throws IOException A communication error in the transport layer. * @throws CommandException A communication in the protocol layer. */ - public void changePin(char[] currentPin, char[] newPin) throws IOException, CommandException { + public void changePin(char[] currentPin, char[] newPin) + throws IOException, CommandException + { byte[] newPinBytes = preparePin(newPin, true); Pair, byte[]> pair = getSharedSecret(); + byte[] pinHash = null; try { - byte[] pinHash = Arrays.copyOf(MessageDigest.getInstance("SHA-256").digest(preparePin(currentPin, false)), PIN_HASH_LEN); + pinHash = Arrays.copyOf( + MessageDigest.getInstance("SHA-256").digest(preparePin(currentPin, false)), + PIN_HASH_LEN + ); byte[] pinHashEnc = pinUvAuth.encrypt(pair.second, pinHash); byte[] newPinEnc = pinUvAuth.encrypt(pair.second, newPinBytes); @@ -207,6 +220,10 @@ public void changePin(char[] currentPin, char[] newPin) throws IOException, Comm } catch (NoSuchAlgorithmException e) { Logger.error(logger, "Failure changing PIN: ", e); throw new IllegalStateException(e); + } finally { + if (pinHash != null) { + Arrays.fill(pinHash, (byte) 0); + } } } @@ -215,13 +232,15 @@ public void changePin(char[] currentPin, char[] newPin) throws IOException, Comm */ static byte[] preparePin(char[] pin, boolean pad) { if (pin.length < MIN_PIN_LEN) { - throw new IllegalArgumentException("PIN must be at least " + MIN_PIN_LEN + " characters"); + throw new IllegalArgumentException( + "PIN must be at least " + MIN_PIN_LEN + " characters"); } ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(pin)); try { int byteLen = byteBuffer.limit() - byteBuffer.position(); if (byteLen > MAX_PIN_LEN) { - throw new IllegalArgumentException("PIN must be no more than " + MAX_PIN_LEN + " bytes"); + throw new IllegalArgumentException( + "PIN must be no more than " + MAX_PIN_LEN + " bytes"); } byte[] pinBytes = new byte[pad ? PIN_BUFFER_LEN : byteLen]; System.arraycopy(byteBuffer.array(), byteBuffer.position(), pinBytes, 0, byteLen);