diff --git a/build.gradle b/build.gradle
index bcaa38b..573d1e3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -27,5 +27,5 @@ allprojects {
ext {
compileSdkVersion = 29
- hwSdkVersionName = '4.4.0'
+ hwSdkVersionName = '4.5.0'
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/BooleanExtensionParameterValue.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/BooleanExtensionParameterValue.java
new file mode 100644
index 0000000..13fc2dc
--- /dev/null
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/BooleanExtensionParameterValue.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018-2021 Confidential Technologies GmbH
+ *
+ * You can purchase a commercial license at https://hwsecurity.dev.
+ * Buying such a license is mandatory as soon as you develop commercial
+ * activities involving this program without disclosing the source code
+ * of your own applications.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package de.cotech.hw.fido2.domain;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class BooleanExtensionParameterValue implements ExtensionParameter.ExtensionParameterValue {
+ public abstract boolean value();
+
+ public static BooleanExtensionParameterValue create(boolean value) {
+ return new AutoValue_BooleanExtensionParameterValue(value);
+ }
+}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/ByteArrayExtensionParameterValue.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/ByteArrayExtensionParameterValue.java
new file mode 100644
index 0000000..976b6b7
--- /dev/null
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/ByteArrayExtensionParameterValue.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018-2021 Confidential Technologies GmbH
+ *
+ * You can purchase a commercial license at https://hwsecurity.dev.
+ * Buying such a license is mandatory as soon as you develop commercial
+ * activities involving this program without disclosing the source code
+ * of your own applications.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package de.cotech.hw.fido2.domain;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class ByteArrayExtensionParameterValue implements ExtensionParameter.ExtensionParameterValue {
+ @SuppressWarnings("mutable")
+ public abstract byte[] value();
+
+ public static ByteArrayExtensionParameterValue create(byte[] value) {
+ return new AutoValue_ByteArrayExtensionParameterValue(value);
+ }
+}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/ExtensionParameter.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/ExtensionParameter.java
new file mode 100644
index 0000000..a23562f
--- /dev/null
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/ExtensionParameter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018-2021 Confidential Technologies GmbH
+ *
+ * You can purchase a commercial license at https://hwsecurity.dev.
+ * Buying such a license is mandatory as soon as you develop commercial
+ * activities involving this program without disclosing the source code
+ * of your own applications.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package de.cotech.hw.fido2.domain;
+import android.os.Parcelable;
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class ExtensionParameter implements Parcelable {
+
+ public interface ExtensionParameterValue extends Parcelable {
+ }
+
+ public abstract String key();
+ @SuppressWarnings("mutable")
+ public abstract ExtensionParameterValue value();
+
+ public static ExtensionParameter create(String key, ExtensionParameterValue value) {
+ return new AutoValue_ExtensionParameter(key, value);
+ }
+}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/HmacSecretExtensionParameterValue.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/HmacSecretExtensionParameterValue.java
new file mode 100644
index 0000000..7505f12
--- /dev/null
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/HmacSecretExtensionParameterValue.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018-2021 Confidential Technologies GmbH
+ *
+ * You can purchase a commercial license at https://hwsecurity.dev.
+ * Buying such a license is mandatory as soon as you develop commercial
+ * activities involving this program without disclosing the source code
+ * of your own applications.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package de.cotech.hw.fido2.domain;
+
+import androidx.annotation.Nullable;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class HmacSecretExtensionParameterValue implements ExtensionParameter.ExtensionParameterValue {
+ @SuppressWarnings("mutable")
+ public abstract byte[] salt1();
+
+ @Nullable
+ @SuppressWarnings("mutable")
+ public abstract byte[] salt2();
+
+ public static HmacSecretExtensionParameterValue create(byte[] salt1, @Nullable byte[] salt2) {
+ if (salt1.length != 32) {
+ throw new IllegalArgumentException("HMAC salt1 must be 32 bytes in length exactly");
+ }
+ if (salt2 != null && salt2.length != 32) {
+ throw new IllegalArgumentException("HMAC salt2 must be 32 bytes in length exactly");
+ }
+ return new AutoValue_HmacSecretExtensionParameterValue(salt1, salt2);
+ }
+}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/create/AuthenticatorData.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/create/AuthenticatorData.java
index 263e81a..01a59a6 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/create/AuthenticatorData.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/create/AuthenticatorData.java
@@ -27,6 +27,7 @@
import androidx.annotation.Nullable;
import com.google.auto.value.AutoValue;
+import de.cotech.hw.fido2.internal.cbor_java.model.Map;
@AutoValue
@@ -69,7 +70,7 @@ public abstract class AuthenticatorData {
// extensions variable (if present) Extension-defined authenticator data. This is a CBOR [RFC7049] map with extension identifiers as keys, and authenticator extension outputs as values. See §9 WebAuthn Extensions for details.
@Nullable
@SuppressWarnings("mutable")
- public abstract byte[] extensions();
+ public abstract Map extensions();
public boolean hasAttestedCredentialData() {
return (flags() & FLAG_ATTESTED_CREDENTIAL_DATA) != 0;
@@ -80,7 +81,7 @@ public boolean hasExtensionData() {
}
public static AuthenticatorData create(byte[] rpIdHash, byte flags, int sigCounter,
- AttestedCredentialData credentialData, byte[] extensions) {
+ AttestedCredentialData credentialData, Map extensions) {
return new AutoValue_AuthenticatorData(rpIdHash, flags, sigCounter, credentialData, extensions);
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/create/PublicKeyCredentialCreationOptions.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/create/PublicKeyCredentialCreationOptions.java
index daea5d6..f06d7db 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/create/PublicKeyCredentialCreationOptions.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/create/PublicKeyCredentialCreationOptions.java
@@ -31,6 +31,8 @@
import androidx.annotation.Nullable;
import com.google.auto.value.AutoValue;
+
+import de.cotech.hw.fido2.domain.ExtensionParameter;
import de.cotech.hw.fido2.domain.PublicKeyCredentialDescriptor;
import de.cotech.hw.fido2.domain.PublicKeyCredentialParameters;
import de.cotech.hw.fido2.domain.PublicKeyCredentialRpEntity;
@@ -50,6 +52,8 @@ public abstract class PublicKeyCredentialCreationOptions implements Parcelable {
@Nullable
public abstract List excludeCredentials();
public abstract AttestationConveyancePreference attestation();
+ @Nullable
+ public abstract List extensionParameters();
public static PublicKeyCredentialCreationOptions create(
PublicKeyCredentialRpEntity rp,
@@ -60,9 +64,23 @@ public static PublicKeyCredentialCreationOptions create(
AuthenticatorSelectionCriteria authenticatorSelection,
@Nullable List excludeCredentials,
AttestationConveyancePreference attestation
+ ) {
+ return create(rp, user, challenge, pubKeyCredParams, timeout, authenticatorSelection, excludeCredentials, attestation);
+ }
+
+ public static PublicKeyCredentialCreationOptions create(
+ PublicKeyCredentialRpEntity rp,
+ PublicKeyCredentialUserEntity user,
+ byte[] challenge,
+ List pubKeyCredParams,
+ @Nullable Long timeout,
+ AuthenticatorSelectionCriteria authenticatorSelection,
+ @Nullable List excludeCredentials,
+ AttestationConveyancePreference attestation,
+ @Nullable List extensionParameters
) {
return new AutoValue_PublicKeyCredentialCreationOptions(
rp, user, challenge, pubKeyCredParams, timeout,
- authenticatorSelection, excludeCredentials, attestation);
+ authenticatorSelection, excludeCredentials, attestation, extensionParameters);
}
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/get/AuthenticatorAssertionResponse.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/get/AuthenticatorAssertionResponse.java
index 200ebb2..c6be6ce 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/get/AuthenticatorAssertionResponse.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/get/AuthenticatorAssertionResponse.java
@@ -39,8 +39,11 @@ public abstract class AuthenticatorAssertionResponse extends AuthenticatorRespon
@Nullable
@SuppressWarnings("mutable")
public abstract byte[] userHandle();
+ @Nullable
+ @SuppressWarnings("mutable")
+ public abstract byte[] hmacSecretData();
- public static AuthenticatorAssertionResponse create(byte[] clientDataJson, byte[] authenticatorData, byte[] signature, @Nullable byte[] userHandle) {
- return new AutoValue_AuthenticatorAssertionResponse(clientDataJson, authenticatorData, signature, userHandle);
+ public static AuthenticatorAssertionResponse create(byte[] clientDataJson, byte[] authenticatorData, byte[] signature, @Nullable byte[] userHandle, @Nullable byte[] hmacSecretData) {
+ return new AutoValue_AuthenticatorAssertionResponse(clientDataJson, authenticatorData, signature, userHandle, hmacSecretData);
}
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/get/PublicKeyCredentialRequestOptions.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/get/PublicKeyCredentialRequestOptions.java
index 1d3cca5..6c7d046 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/get/PublicKeyCredentialRequestOptions.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/domain/get/PublicKeyCredentialRequestOptions.java
@@ -31,6 +31,8 @@
import androidx.annotation.Nullable;
import com.google.auto.value.AutoValue;
+
+import de.cotech.hw.fido2.domain.ExtensionParameter;
import de.cotech.hw.fido2.domain.PublicKeyCredentialDescriptor;
import de.cotech.hw.fido2.domain.UserVerificationRequirement;
@@ -47,6 +49,8 @@ public abstract class PublicKeyCredentialRequestOptions implements Parcelable {
public abstract List allowCredentials();
@Nullable
public abstract UserVerificationRequirement userVerification();
+ @Nullable
+ public abstract List extensionParameters();
public static PublicKeyCredentialRequestOptions create(
byte[] challenge,
@@ -54,8 +58,21 @@ public static PublicKeyCredentialRequestOptions create(
String rpId,
List allowCredentials,
UserVerificationRequirement userVerification
+ ) {
+ return create(challenge, timeout, rpId, allowCredentials, userVerification, null);
+ }
+
+ public static PublicKeyCredentialRequestOptions create(
+ byte[] challenge,
+ Long timeout,
+ String rpId,
+ List allowCredentials,
+ UserVerificationRequirement userVerification,
+ @Nullable
+ List extensionParameters
) {
return new AutoValue_PublicKeyCredentialRequestOptions(
- challenge, timeout, rpId, allowCredentials, userVerification);
+ challenge, timeout, rpId, allowCredentials, userVerification,
+ extensionParameters);
}
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/Ctap2CborSerializer.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/Ctap2CborSerializer.java
index c152be9..4560caa 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/Ctap2CborSerializer.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/Ctap2CborSerializer.java
@@ -30,6 +30,10 @@
import java.util.List;
import java.util.Map.Entry;
+import de.cotech.hw.fido2.domain.BooleanExtensionParameterValue;
+import de.cotech.hw.fido2.domain.ByteArrayExtensionParameterValue;
+import de.cotech.hw.fido2.domain.ExtensionParameter;
+import de.cotech.hw.fido2.domain.HmacSecretExtensionParameterValue;
import de.cotech.hw.fido2.internal.cbor_java.CborBuilder;
import de.cotech.hw.fido2.internal.cbor_java.CborDecoder;
import de.cotech.hw.fido2.internal.cbor_java.CborEncoder;
@@ -51,6 +55,8 @@
import de.cotech.hw.fido2.domain.PublicKeyCredentialUserEntity;
import de.cotech.hw.fido2.internal.ctap2.commands.makeCredential.AuthenticatorMakeCredential.AuthenticatorMakeCredentialOptions;
import de.cotech.hw.fido2.internal.ctap2.commands.rawCommand.RawCtap2Command;
+import de.cotech.hw.fido2.internal.pinauth.PinToken;
+import de.cotech.hw.util.Arrays;
class Ctap2CborSerializer {
@@ -126,6 +132,13 @@ private void writeToBuilder(CborBuilder cborBuilder2, AuthenticatorMakeCredentia
arrayBuilder.end();
}
// extensions 0x06 CBOR definite length map (CBOR major type 5).
+ List extensionParameters = amc.extensions();
+ if (extensionParameters != null) {
+ MapBuilder> mapBuilder = cborBuilder.putMap(0x06);
+ for (ExtensionParameter param : extensionParameters) {
+ writeToMap(mapBuilder, param);
+ }
+ }
// options 0x07 CBOR definite length map (CBOR major type 5).
AuthenticatorMakeCredentialOptions options = amc.options();
@@ -176,6 +189,13 @@ private void writeToBuilder(CborBuilder cborBuilder2, AuthenticatorGetAssertion
}
}
// extensions 0x04 CBOR definite length map (CBOR major type 5).
+ List extensionParameters = aga.extensions();
+ if (extensionParameters != null) {
+ MapBuilder> mapBuilder = cborBuilder.putMap(0x04);
+ for (ExtensionParameter param : extensionParameters) {
+ writeToMap(mapBuilder, param, aga);
+ }
+ }
// options 0x05 CBOR definite length map (CBOR major type 5).
// pinAuth 0x06 byte string (CBOR major type 2).
@@ -265,4 +285,53 @@ private void writeToMap(MapBuilder> mapBuilder, PublicKeyCredentialDescriptor
}
}
}
+
+ private void writeToMap(MapBuilder> mapBuilder, ExtensionParameter extensionParameters) {
+ Object value = extensionParameters.value();
+ if (value instanceof BooleanExtensionParameterValue) {
+ mapBuilder.put(extensionParameters.key(), ((BooleanExtensionParameterValue) value).value());
+ } else if (value instanceof ByteArrayExtensionParameterValue) {
+ mapBuilder.put(extensionParameters.key(), ((ByteArrayExtensionParameterValue) value).value());
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private void writeToMap(MapBuilder> mapBuilder, ExtensionParameter extensionParameters, AuthenticatorGetAssertion aga) {
+ // Handle parameters which require assertion-specific data here
+ Object value = extensionParameters.value();
+ if (value instanceof HmacSecretExtensionParameterValue) {
+ HmacSecretExtensionParameterValue param = (HmacSecretExtensionParameterValue) value;
+ MapBuilder> hmacParamsMap = mapBuilder.putMap(extensionParameters.key());
+
+ PinToken pinToken = aga.pinToken();
+ if (pinToken == null) {
+ throw new UnsupportedOperationException("An HMAC secret cannot be requested without PIN auth");
+ }
+
+ byte[] salts;
+ if (param.salt2() == null) {
+ salts = param.salt1();
+ } else {
+ salts = Arrays.concatenate(param.salt1(), param.salt2());
+ }
+
+ byte[] saltEnc = pinToken.encrypt(salts);
+
+ try {
+ hmacParamsMap.put(Ctap2CborConstants.CBOR_ONE, new CborDecoder(new ByteArrayInputStream(pinToken.platformKeyAgreementKey())).decodeNext());
+ } catch (CborException e) {
+ throw new IllegalArgumentException(e);
+ } // keyAgreement
+ hmacParamsMap.put(0x02, saltEnc); // saltEnc
+ hmacParamsMap.put(0x03, pinToken.authenticate(saltEnc)); // saltAuth
+ if (pinToken.pinProtocol().version() != 1) { // pinUvAuthProtocol
+ hmacParamsMap.put(0x04, pinToken.pinProtocol().version());
+ }
+
+ return;
+ }
+
+ writeToMap(mapBuilder, extensionParameters);
+ }
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/getAssertion/AuthenticatorGetAssertion.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/getAssertion/AuthenticatorGetAssertion.java
index 5880fe0..0d47fb8 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/getAssertion/AuthenticatorGetAssertion.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/getAssertion/AuthenticatorGetAssertion.java
@@ -29,10 +29,13 @@
import androidx.annotation.Nullable;
import com.google.auto.value.AutoValue;
+
+import de.cotech.hw.fido2.domain.ExtensionParameter;
import de.cotech.hw.fido2.domain.PublicKeyCredentialDescriptor;
import de.cotech.hw.fido2.internal.ctap2.Ctap2Command;
import de.cotech.hw.fido2.internal.ctap2.Ctap2ResponseFactory;
import de.cotech.hw.fido2.internal.ctap2.commands.getInfo.AuthenticatorOptions;
+import de.cotech.hw.fido2.internal.pinauth.PinToken;
@AutoValue
@@ -50,7 +53,7 @@ public abstract class AuthenticatorGetAssertion extends Ctap2Command extensions();
// options 0x05 CBOR definite length map (CBOR major type 5).
@Nullable
abstract AuthenticatorOptions options();
@@ -61,13 +64,26 @@ public abstract class AuthenticatorGetAssertion extends Ctap2Command allowCredentials, AuthenticatorOptions options) {
- return new AutoValue_AuthenticatorGetAssertion(COMMAND_GET_ASSERTION, rpId, clientDataHash, clientDataJson, allowCredentials, null, options, null, null);
+ return new AutoValue_AuthenticatorGetAssertion(COMMAND_GET_ASSERTION, rpId, clientDataHash, clientDataJson, allowCredentials, null, options, null, null, null);
+ }
+
+ public static AuthenticatorGetAssertion create(String rpId, byte[] clientDataHash, String clientDataJson, List allowCredentials, List extensionParameters, AuthenticatorOptions options) {
+ return new AutoValue_AuthenticatorGetAssertion(COMMAND_GET_ASSERTION, rpId, clientDataHash, clientDataJson, allowCredentials, extensionParameters, options, null, null, null);
+ }
+
+ public static AuthenticatorGetAssertion create(String rpId, byte[] clientDataHash, String clientDataJson, List allowCredentials, AuthenticatorOptions options, PinToken pinToken) {
+ byte[] pinAuth = pinToken.calculatePinAuth(clientDataHash);
+ return new AutoValue_AuthenticatorGetAssertion(COMMAND_GET_ASSERTION, rpId, clientDataHash, clientDataJson, allowCredentials, null, options, pinAuth, pinToken.pinProtocol().version(), pinToken);
}
- public static AuthenticatorGetAssertion create(String rpId, byte[] clientDataHash, String clientDataJson, List allowCredentials, AuthenticatorOptions options, byte[] pinAuth, Integer pinProtocol) {
- return new AutoValue_AuthenticatorGetAssertion(COMMAND_GET_ASSERTION, rpId, clientDataHash, clientDataJson, allowCredentials, null, options, pinAuth, pinProtocol);
+ public static AuthenticatorGetAssertion create(String rpId, byte[] clientDataHash, String clientDataJson, List allowCredentials, List extensionParameters, AuthenticatorOptions options, PinToken pinToken) {
+ byte[] pinAuth = pinToken.calculatePinAuth(clientDataHash);
+ return new AutoValue_AuthenticatorGetAssertion(COMMAND_GET_ASSERTION, rpId, clientDataHash, clientDataJson, allowCredentials, extensionParameters, options, pinAuth, pinToken.pinProtocol().version(), pinToken);
}
@Override
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/getAssertion/AuthenticatorGetAssertionResponse.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/getAssertion/AuthenticatorGetAssertionResponse.java
index 6ed0a05..72484a7 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/getAssertion/AuthenticatorGetAssertionResponse.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/getAssertion/AuthenticatorGetAssertionResponse.java
@@ -53,6 +53,10 @@ public abstract class AuthenticatorGetAssertionResponse extends Ctap2Response {
@SuppressWarnings("mutable")
public abstract byte[] clientDataJSON();
+ @Nullable
+ @SuppressWarnings("mutable")
+ public abstract byte[] hmacSecretData();
+
public static AuthenticatorGetAssertionResponse create(
@Nullable byte[] credential,
@@ -60,8 +64,9 @@ public static AuthenticatorGetAssertionResponse create(
byte[] signature,
@Nullable PublicKeyCredentialUserEntity user,
@Nullable Integer numberOfCredentials,
- byte[] clientDataJSON
+ byte[] clientDataJSON,
+ @Nullable byte[] hmacSecretData
) {
- return new AutoValue_AuthenticatorGetAssertionResponse(credential, authData, signature, user, numberOfCredentials, clientDataJSON);
+ return new AutoValue_AuthenticatorGetAssertionResponse(credential, authData, signature, user, numberOfCredentials, clientDataJSON, hmacSecretData);
}
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/getAssertion/AuthenticatorGetAssertionResponseFactory.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/getAssertion/AuthenticatorGetAssertionResponseFactory.java
index 1563e48..99c9f30 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/getAssertion/AuthenticatorGetAssertionResponseFactory.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/getAssertion/AuthenticatorGetAssertionResponseFactory.java
@@ -28,6 +28,7 @@
import java.io.IOException;
import java.util.List;
+import de.cotech.hw.fido2.domain.create.AuthenticatorData;
import de.cotech.hw.fido2.internal.cbor_java.CborDecoder;
import de.cotech.hw.fido2.internal.cbor_java.CborException;
import de.cotech.hw.fido2.internal.cbor_java.model.ByteString;
@@ -40,11 +41,14 @@
import de.cotech.hw.fido2.internal.cbor.CborUtils;
import de.cotech.hw.fido2.internal.ctap2.Ctap2CborConstants;
import de.cotech.hw.fido2.internal.ctap2.Ctap2ResponseFactory;
+import de.cotech.hw.fido2.internal.webauthn.AuthenticatorDataParser;
public class AuthenticatorGetAssertionResponseFactory implements
Ctap2ResponseFactory {
private final AuthenticatorGetAssertion authenticatorGetAssertion;
+ private static final AuthenticatorDataParser authenticatorDataParser = new AuthenticatorDataParser();
+ private static final UnicodeString HMAC_SECRET_EXTENSION_ID = new UnicodeString("hmac-secret");
AuthenticatorGetAssertionResponseFactory(AuthenticatorGetAssertion authenticatorGetAssertion) {
this.authenticatorGetAssertion = authenticatorGetAssertion;
@@ -76,13 +80,27 @@ private AuthenticatorGetAssertionResponse readAuthenticatorGetAssertionResponse(
PublicKeyCredentialUserEntity publicKeyCredentialUserEntity =
readPublicKeyCredentialUserEntity(user);
+ byte[] decryptedHmacSecretValue = null;
+ AuthenticatorData decodedAuthData = authenticatorDataParser.fromBytes(authData.getBytes());
+ if (decodedAuthData.hasExtensionData()) {
+ DataItem hmacSecretValue = decodedAuthData.extensions().get(HMAC_SECRET_EXTENSION_ID);
+ if (hmacSecretValue != null) {
+ if (hmacSecretValue.getMajorType() != MajorType.BYTE_STRING) {
+ throw new IOException("hmac-secret response is not a byte string");
+ }
+ byte[] hmacSecretBytes = ((ByteString) hmacSecretValue).getBytes();
+ decryptedHmacSecretValue = authenticatorGetAssertion.pinToken().decrypt(hmacSecretBytes);
+ }
+ }
+
return AuthenticatorGetAssertionResponse.create(
credential != null ? CborUtils.writeCborDataToBytes(credential) : null,
authData.getBytes(),
signature.getBytes(),
publicKeyCredentialUserEntity,
numberOfCredentials != null ? numberOfCredentials.getValue().intValue() : null,
- authenticatorGetAssertion.clientDataJson().getBytes()
+ authenticatorGetAssertion.clientDataJson().getBytes(),
+ decryptedHmacSecretValue
);
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/makeCredential/AuthenticatorMakeCredential.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/makeCredential/AuthenticatorMakeCredential.java
index 6ef783c..d87be87 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/makeCredential/AuthenticatorMakeCredential.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/makeCredential/AuthenticatorMakeCredential.java
@@ -29,6 +29,8 @@
import androidx.annotation.Nullable;
import com.google.auto.value.AutoValue;
+
+import de.cotech.hw.fido2.domain.ExtensionParameter;
import de.cotech.hw.fido2.internal.ctap2.Ctap2Command;
import de.cotech.hw.fido2.domain.PublicKeyCredentialDescriptor;
import de.cotech.hw.fido2.domain.PublicKeyCredentialParameters;
@@ -60,7 +62,8 @@ public abstract class AuthenticatorMakeCredential extends Ctap2Command excludeList();
// CBOR map of extension identifier → authenticator extension input values Optional Parameters to influence authenticator operation, as specified in [WebAuthN]. These parameters might be authenticator specific.
- // public abstract int extensions();
+ @Nullable
+ public abstract List extensions();
// Map of authenticator options Optional Parameters to influence authenticator operation, as specified in in the table below.
@Nullable
@@ -114,6 +117,15 @@ public static AuthenticatorMakeCredential create(byte[] clientDataHash, String c
List excludeList,
AuthenticatorMakeCredentialOptions options, byte[] pinAuth, Integer pinProtocol) {
return new AutoValue_AuthenticatorMakeCredential(Ctap2Command.COMMAND_MAKE_CREDENTIAL, clientDataHash, clientDataJson, rp, user,
- pubKeyCredParams, excludeList, options, pinAuth, pinProtocol);
+ pubKeyCredParams, excludeList, null, options, pinAuth, pinProtocol);
+ }
+
+ public static AuthenticatorMakeCredential create(byte[] clientDataHash, String clientDataJson,
+ PublicKeyCredentialRpEntity rp, PublicKeyCredentialUserEntity user, List pubKeyCredParams,
+ List excludeList,
+ List extensionParameters,
+ AuthenticatorMakeCredentialOptions options, byte[] pinAuth, Integer pinProtocol) {
+ return new AutoValue_AuthenticatorMakeCredential(Ctap2Command.COMMAND_MAKE_CREDENTIAL, clientDataHash, clientDataJson, rp, user,
+ pubKeyCredParams, excludeList, extensionParameters, options, pinAuth, pinProtocol);
}
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/makeCredential/AuthenticatorMakeCredentialResponseFactory.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/makeCredential/AuthenticatorMakeCredentialResponseFactory.java
index 6ff9fd0..7f003d0 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/makeCredential/AuthenticatorMakeCredentialResponseFactory.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/ctap2/commands/makeCredential/AuthenticatorMakeCredentialResponseFactory.java
@@ -68,4 +68,5 @@ public AuthenticatorMakeCredentialResponse createResponse(byte[] rawResponseData
throw new IOException(e);
}
}
+
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/json/JsonWebauthnOptionsParser.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/json/JsonWebauthnOptionsParser.java
index f84a158..fab0648 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/json/JsonWebauthnOptionsParser.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/json/JsonWebauthnOptionsParser.java
@@ -69,7 +69,7 @@ private PublicKeyCredentialCreationOptions fromJsonMakeCredential(JSONObject jso
publicKeyObject.optJSONObject("authenticatorSelection"));
return PublicKeyCredentialCreationOptions.create(rp, user, challenge, pubKeyCredParams, timeout,
- authenticatorSelection, null, attestationConveyancePreference);
+ authenticatorSelection, null, attestationConveyancePreference, null);
}
private List jsonToPubKeyCredParamsList(
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/operations/ctap1/AuthenticatorGetAssertionCtap1Operation.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/operations/ctap1/AuthenticatorGetAssertionCtap1Operation.java
index 0a40b76..187eb16 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/operations/ctap1/AuthenticatorGetAssertionCtap1Operation.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/operations/ctap1/AuthenticatorGetAssertionCtap1Operation.java
@@ -115,6 +115,7 @@ private PublicKeyCredential ctap1ResponseApduToWebauthnResponse(
authenticatorGetAssertion.clientDataJson().getBytes(),
authenticatorDataBytes,
u2fResponse.signature(),
+ null,
null
);
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/operations/ctap2/AuthenticatorGetAssertionOperation.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/operations/ctap2/AuthenticatorGetAssertionOperation.java
index 619ed1d..0174c75 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/operations/ctap2/AuthenticatorGetAssertionOperation.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/operations/ctap2/AuthenticatorGetAssertionOperation.java
@@ -46,6 +46,7 @@
import de.cotech.hw.fido2.exceptions.FidoSecurityError;
import de.cotech.hw.fido2.internal.Fido2AppletConnection;
import de.cotech.hw.fido2.internal.cbor.CborPublicKeyCredentialDescriptorParser;
+import de.cotech.hw.fido2.internal.cbor_java.model.UnicodeString;
import de.cotech.hw.fido2.internal.ctap2.Ctap2Exception;
import de.cotech.hw.fido2.internal.ctap2.CtapErrorResponse;
import de.cotech.hw.fido2.internal.ctap2.commands.getAssertion.AuthenticatorGetAssertion;
@@ -55,6 +56,7 @@
import de.cotech.hw.fido2.internal.pinauth.PinProtocolV1;
import de.cotech.hw.fido2.internal.pinauth.PinToken;
import de.cotech.hw.fido2.internal.utils.RelyingPartyIdUtils;
+import de.cotech.hw.fido2.internal.webauthn.AuthenticatorDataParser;
import de.cotech.hw.util.HashUtil;
import de.cotech.hw.util.HwTimber;
@@ -67,6 +69,7 @@ public class AuthenticatorGetAssertionOperation extends
private final PinProtocolV1 pinProtocolV1;
private final JsonCollectedClientDataSerializer jsonCollectedClientDataSerializer;
private final RelyingPartyIdUtils relyingPartyIdUtils;
+ private final AuthenticatorDataParser authenticatorDataParser = new AuthenticatorDataParser();
public AuthenticatorGetAssertionOperation(
CborPublicKeyCredentialDescriptorParser cborPublicKeyCredentialDescriptorParser,
@@ -96,6 +99,7 @@ public PublicKeyCredential performWebauthnSecurityKeyOperation(
try {
AuthenticatorGetAssertionResponse response =
fido2AppletConnection.ctap2CommunicateOrThrow(authenticatorGetAssertion);
+
return ctap2ResponseToWebauthnResponse(request, response);
} catch (Ctap2Exception e) {
switch (e.ctapErrorResponse.errorCode()) {
@@ -154,20 +158,19 @@ public AuthenticatorGetAssertion webauthnCommandToCtap2Command(
byte[] clientDataHash = HashUtil.sha256(clientDataJson);
if (pinToken != null) {
- byte[] pinAuth = pinProtocolV1.calculatePinAuth(pinToken, clientDataHash);
return AuthenticatorGetAssertion
- .create(rpId, clientDataHash, clientDataJson, options.allowCredentials(), null,
- pinAuth, PinProtocolV1.PIN_PROTOCOL);
+ .create(rpId, clientDataHash, clientDataJson, options.allowCredentials(), options.extensionParameters(), null,
+ pinToken);
} else {
- return AuthenticatorGetAssertion.create(rpId, clientDataHash, clientDataJson, options.allowCredentials(), null);
+ return AuthenticatorGetAssertion.create(rpId, clientDataHash, clientDataJson, options.allowCredentials(), options.extensionParameters(), null);
}
}
private PublicKeyCredential ctap2ResponseToWebauthnResponse(
- PublicKeyCredentialGet credentialCreate,
+ PublicKeyCredentialGet credentialGet,
AuthenticatorGetAssertionResponse response
) throws IOException {
- byte[] credential = determinePublicKeyCredentialId(credentialCreate, response);
+ byte[] credential = determinePublicKeyCredentialId(credentialGet, response);
PublicKeyCredentialUserEntity user = response.user();
AssertionCreationData assertionCreationData = AssertionCreationData.create(
@@ -185,7 +188,8 @@ private PublicKeyCredential ctap2ResponseToWebauthnResponse(
assertionCreationData.clientDataJSONResult(),
assertionCreationData.authenticatorDataResult(),
assertionCreationData.signatureResult(),
- assertionCreationData.userHandleResult()
+ assertionCreationData.userHandleResult(),
+ response.hmacSecretData()
);
return PublicKeyCredential
.create(assertionCreationData.credentialIdResult(), authenticatorResponse);
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/operations/ctap2/AuthenticatorMakeCredentialOperation.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/operations/ctap2/AuthenticatorMakeCredentialOperation.java
index a5caa27..79ba2f3 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/operations/ctap2/AuthenticatorMakeCredentialOperation.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/operations/ctap2/AuthenticatorMakeCredentialOperation.java
@@ -143,12 +143,12 @@ public AuthenticatorMakeCredential webauthnToCtap2Command(
}
if (pinToken != null) {
- byte[] pinAuth = pinProtocolV1.calculatePinAuth(pinToken, clientDataHash);
+ byte[] pinAuth = pinToken.calculatePinAuth(clientDataHash);
return AuthenticatorMakeCredential.create(clientDataHash, clientDataJson, rp, options.user(), options.pubKeyCredParams(),
- options.excludeCredentials(), authenticatorOptions, pinAuth, PinProtocolV1.PIN_PROTOCOL);
+ options.excludeCredentials(), options.extensionParameters(), authenticatorOptions, pinAuth, pinToken.pinProtocol().version());
} else {
return AuthenticatorMakeCredential.create(clientDataHash, clientDataJson, rp, options.user(), options.pubKeyCredParams(),
- options.excludeCredentials(), authenticatorOptions, null, null);
+ options.excludeCredentials(), options.extensionParameters(), authenticatorOptions, null, null);
}
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinAuthCryptoUtil.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinAuthCryptoUtil.java
index 70e8843..e19dfeb 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinAuthCryptoUtil.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinAuthCryptoUtil.java
@@ -67,6 +67,10 @@ public byte[] calculatePinAuth(byte[] pinToken, byte[] clientDataHash) {
return hmacSha256Left16Bytes(pinToken, clientDataHash);
}
+ public byte[] authenticate(byte[] secret, byte[] data) {
+ return hmacSha256Left16Bytes(secret, data);
+ }
+
/**
* Leftmost 16 bytes of an HMAC-SHA-256 operation.
*/
@@ -87,19 +91,37 @@ private byte[] hmacSha256(byte[] secret, byte[] data) {
}
}
- public byte[] calculatePinHashEnc(byte[] sharedSecret, String pin) throws IOException {
- byte[] pinHash = calculatePinHash(pin);
+ public byte[] encrypt(byte[] sharedSecret, byte[] data) {
try {
SecretKeySpec secretKey = new SecretKeySpec(sharedSecret, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(getIv());
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
- return cipher.doFinal(pinHash);
+ return cipher.doFinal(data);
} catch (IllegalBlockSizeException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | InvalidKeyException e) {
throw new IllegalStateException(e);
}
}
+ public byte[] decrypt(byte[] sharedSecret, byte[] data) throws IOException {
+ try {
+ SecretKeySpec secretKey = new SecretKeySpec(sharedSecret, "AES");
+ IvParameterSpec ivParameterSpec = new IvParameterSpec(new byte[16]);
+ Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
+ return cipher.doFinal(data);
+ } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
+ throw new IllegalStateException(e);
+ } catch (IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException | InvalidKeyException e) {
+ throw new IOException("Error decrypting data from authenticator", e);
+ }
+ }
+
+ public byte[] calculatePinHashEnc(byte[] sharedSecret, String pin) throws IOException {
+ byte[] pinHash = calculatePinHash(pin);
+ return encrypt(sharedSecret, pinHash);
+ }
+
@Keep
private byte[] getIv() {
// IV=0 as per CTAP2 specification
@@ -134,14 +156,8 @@ byte[] padPin(String pin) throws IOException {
public byte[] decryptPinToken(byte[] sharedSecret, byte[] pinTokenEnc) throws IOException {
try {
- SecretKeySpec secretKey = new SecretKeySpec(sharedSecret, "AES");
- IvParameterSpec ivParameterSpec = new IvParameterSpec(new byte[16]);
- Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
- cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
- return cipher.doFinal(pinTokenEnc);
- } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
- throw new IllegalStateException(e);
- } catch (IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException | InvalidKeyException e) {
+ return decrypt(sharedSecret, pinTokenEnc);
+ } catch (IOException e) {
throw new IOException("Error decrypting pinToken from authenticator", e);
}
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinProtocol.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinProtocol.java
new file mode 100644
index 0000000..e0cc8ae
--- /dev/null
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinProtocol.java
@@ -0,0 +1,20 @@
+package de.cotech.hw.fido2.internal.pinauth;
+
+import java.io.IOException;
+
+import de.cotech.hw.fido2.internal.Fido2AppletConnection;
+
+public interface PinProtocol {
+ PinToken clientPinAuthenticate(
+ Fido2AppletConnection fido2AppletConnection, String pin, boolean lastAttemptOk) throws IOException;
+
+ int version();
+
+ byte[] authenticate(PinToken pinToken, byte[] data);
+
+ byte[] encrypt(PinToken pinToken, byte[] data);
+
+ byte[] decrypt(PinToken pinToken, byte[] data) throws IOException;
+
+ byte[] calculatePinAuth(PinToken pinToken, byte[] data);
+}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinProtocolV1.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinProtocolV1.java
index bde6400..b1c5742 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinProtocolV1.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinProtocolV1.java
@@ -44,7 +44,7 @@
import de.cotech.hw.util.HwTimber;
-public class PinProtocolV1 {
+public class PinProtocolV1 implements PinProtocol {
public static final int PIN_PROTOCOL = 1;
private final PinAuthCryptoUtil pinAuthCryptoUtil;
@@ -53,6 +53,7 @@ public PinProtocolV1(PinAuthCryptoUtil pinAuthCryptoUtil) {
this.pinAuthCryptoUtil = pinAuthCryptoUtil;
}
+ @Override
public PinToken clientPinAuthenticate(
Fido2AppletConnection fido2AppletConnection, String pin, boolean lastAttemptOk) throws IOException {
AuthenticatorClientPin ctap2Command;
@@ -98,7 +99,7 @@ public PinToken clientPinAuthenticate(
.decryptPinToken(sharedSecret, authenticatorClientPinResponse.pinToken());
HwTimber.d("Authentication successful. pinToken is " +
Hex.encodeHexString(pinToken));
- return PinToken.create(pinToken);
+ return PinToken.create(this, pinToken, platformKeyAgreementKey, sharedSecret);
} catch (Ctap2Exception e) {
switch (e.ctapErrorResponse.errorCode()) {
case CtapErrorResponse.CTAP2_ERR_PIN_BLOCKED:
@@ -108,11 +109,14 @@ public PinToken clientPinAuthenticate(
throw new FidoClientPinInvalidException(retriesLeft);
}
throw e;
- } finally {
- Arrays.fill(sharedSecret, (byte) 0);
}
}
+ @Override
+ public int version() {
+ return PIN_PROTOCOL;
+ }
+
private int checkRetries(Fido2AppletConnection fido2AppletConnection)
throws IOException {
Ctap2Command ctap2Command = AuthenticatorClientPin.createGetRetries();
@@ -124,7 +128,22 @@ private int checkRetries(Fido2AppletConnection fido2AppletConnection)
return retries;
}
- public byte[] calculatePinAuth(PinToken pinToken, byte[] clientDataHash) {
- return pinAuthCryptoUtil.calculatePinAuth(pinToken.pinToken(), clientDataHash);
+ public byte[] calculatePinAuth(PinToken pinToken, byte[] data) {
+ return pinAuthCryptoUtil.calculatePinAuth(pinToken.pinToken(), data);
+ }
+
+ @Override
+ public byte[] authenticate(PinToken pinToken, byte[] data) {
+ return pinAuthCryptoUtil.authenticate(pinToken.sharedSecret(), data);
+ }
+
+ @Override
+ public byte[] encrypt(PinToken pinToken, byte[] data) {
+ return pinAuthCryptoUtil.encrypt(pinToken.sharedSecret(), data);
+ }
+
+ @Override
+ public byte[] decrypt(PinToken pinToken, byte[] data) throws IOException {
+ return pinAuthCryptoUtil.decrypt(pinToken.sharedSecret(), data);
}
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinToken.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinToken.java
index 3e50684..6a087d8 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinToken.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/pinauth/PinToken.java
@@ -27,13 +27,40 @@
import com.google.auto.value.AutoValue;
+import java.io.IOException;
+
@AutoValue
public abstract class PinToken {
+
+ public abstract PinProtocol pinProtocol();
+
@SuppressWarnings("mutable")
public abstract byte[] pinToken();
- public static PinToken create(byte[] pinToken) {
- return new AutoValue_PinToken(pinToken);
+ @SuppressWarnings("mutable")
+ public abstract byte[] platformKeyAgreementKey();
+
+ @SuppressWarnings("mutable")
+ abstract byte[] sharedSecret();
+
+ public byte[] authenticate(byte[] data) {
+ return pinProtocol().authenticate(this, data);
+ }
+
+ public byte[] calculatePinAuth(byte[] data) {
+ return pinProtocol().calculatePinAuth(this, data);
+ }
+
+ public byte[] encrypt(byte[] data) {
+ return pinProtocol().encrypt(this, data);
+ }
+
+ public byte[] decrypt(byte[] data) throws IOException {
+ return pinProtocol().decrypt(this, data);
+ }
+
+ public static PinToken create(PinProtocol pinProtocol, byte[] pinToken, byte[] platformKeyAgreementKey, byte[] sharedSecret) {
+ return new AutoValue_PinToken(pinProtocol, pinToken, platformKeyAgreementKey, sharedSecret);
}
}
diff --git a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/webauthn/AuthenticatorDataParser.java b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/webauthn/AuthenticatorDataParser.java
index 7477603..bcb88f1 100644
--- a/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/webauthn/AuthenticatorDataParser.java
+++ b/hwsecurity/fido2/src/main/java/de/cotech/hw/fido2/internal/webauthn/AuthenticatorDataParser.java
@@ -26,16 +26,20 @@
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import de.cotech.hw.fido2.domain.create.AuthenticatorData;
import de.cotech.hw.fido2.internal.cbor_java.CborDecoder;
+import de.cotech.hw.fido2.internal.cbor_java.CborEncoder;
import de.cotech.hw.fido2.internal.cbor_java.CborException;
import de.cotech.hw.fido2.internal.cbor_java.model.DataItem;
import de.cotech.hw.fido2.domain.create.AttestedCredentialData;
-import de.cotech.hw.fido2.domain.create.AuthenticatorData;
import de.cotech.hw.fido2.internal.cbor.CborUtils;
+import de.cotech.hw.fido2.internal.cbor_java.model.MajorType;
+import de.cotech.hw.fido2.internal.cbor_java.model.Map;
public class AuthenticatorDataParser {
@@ -44,7 +48,7 @@ public class AuthenticatorDataParser {
private static final int LENGTH_AAGUID = 16;
- AuthenticatorData fromBytes(byte[] bytes) throws IOException {
+ public AuthenticatorData fromBytes(byte[] bytes) throws IOException {
ByteBuffer buf = ByteBuffer.wrap(bytes).duplicate();
buf.order(ByteOrder.BIG_ENDIAN);
@@ -54,7 +58,7 @@ AuthenticatorData fromBytes(byte[] bytes) throws IOException {
int sigCounter = buf.getInt();
AttestedCredentialData attestedCredentialData = null;
- byte[] extensionData = null;
+ Map extensionData = null;
boolean hasAttestedCredentialData = (flags & AuthenticatorData.FLAG_ATTESTED_CREDENTIAL_DATA) != 0;
if (hasAttestedCredentialData) {
@@ -63,8 +67,7 @@ AuthenticatorData fromBytes(byte[] bytes) throws IOException {
boolean hasExtensionData = (flags & AuthenticatorData.FLAG_EXTENSION_DATA) != 0;
if (hasExtensionData) {
- extensionData = new byte[buf.remaining()];
- buf.get(extensionData);
+ extensionData = parseExtensionData(buf);
}
return AuthenticatorData.create(
@@ -74,8 +77,20 @@ AuthenticatorData fromBytes(byte[] bytes) throws IOException {
public byte[] toBytes(AuthenticatorData authenticatorData) {
byte[] attestedCredentialData = serializeAttestedCredentialData(authenticatorData.attestedCredentialData());
- byte[] extensionData = authenticatorData.extensions();
- int extensionDataLength = authenticatorData.hasExtensionData() && extensionData != null ? extensionData.length : 0;
+ byte[] extensionData = null;
+
+ if (authenticatorData.hasExtensionData()) {
+ try {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ new CborEncoder(outputStream).encode(authenticatorData.extensions());
+ extensionData = outputStream.toByteArray();
+ } catch (CborException e) {
+ // Failing to re-CBOR-encode a CBOR DataItem?
+ throw new IllegalStateException(e);
+ }
+ }
+
+ int extensionDataLength = extensionData != null ? extensionData.length : 0;
ByteBuffer result = ByteBuffer.allocate(
TOTAL_LENGTH_HEADER + attestedCredentialData.length + extensionDataLength);
@@ -108,6 +123,20 @@ private byte[] serializeAttestedCredentialData(AttestedCredentialData attestedCr
return result.array();
}
+ private static Map parseExtensionData(ByteBuffer buf) throws IOException {
+ try {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(
+ buf.array(), buf.arrayOffset() + buf.position(), buf.remaining());
+ DataItem dataItem = new CborDecoder(inputStream).decodeNext();
+ if (dataItem.getMajorType() != MajorType.MAP) {
+ throw new IOException("Extension data not a CBOR map!");
+ }
+ return (Map) dataItem;
+ } catch (CborException e) {
+ throw new IOException("Error reading CBOR-encoded credential data!", e);
+ }
+ }
+
private static AttestedCredentialData parseAttestedCredentialData(ByteBuffer buf) throws IOException {
byte[] aaguid = new byte[LENGTH_AAGUID];
buf.get(aaguid);
diff --git a/hwsecurity/fido2/src/test/java/de/cotech/hw/fido2/Fido2SecurityKeyTest.java b/hwsecurity/fido2/src/test/java/de/cotech/hw/fido2/Fido2SecurityKeyTest.java
index cff4680..b741953 100644
--- a/hwsecurity/fido2/src/test/java/de/cotech/hw/fido2/Fido2SecurityKeyTest.java
+++ b/hwsecurity/fido2/src/test/java/de/cotech/hw/fido2/Fido2SecurityKeyTest.java
@@ -267,4 +267,4 @@ public void makeCredential_withPin() throws Exception {
assertEquals("{\"type\":\"webauthn.create\",\"origin\":\"https:\\/\\/webauthn.hwsecurity.dev\",\"challenge\":\"GNxfVQfEVOoi9uU1W_jM-w\",\"hashAlgorithm\":\"SHA-256\"}",
new String(publicKeyCredential.response().clientDataJson()));
}
-}
\ No newline at end of file
+}