From 2b8c8e3e81e9f6c77eb4b8a1b5a6bd25c3c2a7b5 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Mon, 15 Jul 2024 17:39:26 +0200 Subject: [PATCH] improve FIDO integration tests --- .../yubico/yubikit/testing/DeviceTests.java | 5 +- .../fido/Ctap2ConfigInstrumentedTests.java | 80 ++++++++++++------- .../Ctap2SessionResetInstrumentedTests.java | 3 + .../yubikit/testing/fido/FidoTests.java | 19 ++++- .../fido/UvDiscouragedInstrumentedTests.java | 7 +- .../testing/AlwaysManualTestCategory.java | 20 +++++ .../yubico/yubikit/testing/TestActivity.java | 22 +++-- .../fido/BasicWebAuthnClientTests.java | 32 ++------ .../testing/fido/Ctap2BioEnrollmentTests.java | 3 +- .../testing/fido/Ctap2ClientPinTests.java | 34 +------- .../testing/fido/Ctap2ConfigTests.java | 19 ++++- .../fido/Ctap2CredentialManagementTests.java | 3 +- .../testing/fido/Ctap2SessionTests.java | 3 - .../fido/EnterpriseAttestationTests.java | 5 +- .../yubikit/testing/fido/FidoTestUtils.java | 66 ++++++++++++--- .../yubico/yubikit/testing/fido/TestData.java | 3 +- 16 files changed, 204 insertions(+), 120 deletions(-) create mode 100644 testing-android/src/main/java/com/yubico/yubikit/testing/AlwaysManualTestCategory.java diff --git a/testing-android/src/androidTest/java/com/yubico/yubikit/testing/DeviceTests.java b/testing-android/src/androidTest/java/com/yubico/yubikit/testing/DeviceTests.java index 9a946c95..e2504c46 100644 --- a/testing-android/src/androidTest/java/com/yubico/yubikit/testing/DeviceTests.java +++ b/testing-android/src/androidTest/java/com/yubico/yubikit/testing/DeviceTests.java @@ -21,18 +21,21 @@ import com.yubico.yubikit.testing.openpgp.OpenPgpTests; import com.yubico.yubikit.testing.piv.PivTests; +import org.junit.experimental.categories.Categories; import org.junit.runner.RunWith; import org.junit.runners.Suite; /** * All integration tests. + * Note that the YubiKey applications will be reset several times. + *

+ * FIDO integration tests should be ran separately. */ @RunWith(Suite.class) @Suite.SuiteClasses({ PivTests.class, OpenPgpTests.class, OathTests.class, - FidoTests.class }) public class DeviceTests { } \ No newline at end of file diff --git a/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/Ctap2ConfigInstrumentedTests.java b/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/Ctap2ConfigInstrumentedTests.java index fece1025..733a4c84 100644 --- a/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/Ctap2ConfigInstrumentedTests.java +++ b/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/Ctap2ConfigInstrumentedTests.java @@ -16,43 +16,67 @@ package com.yubico.yubikit.testing.fido; +import com.yubico.yubikit.testing.AlwaysManualTestCategory; import com.yubico.yubikit.testing.framework.FidoInstrumentedTests; import org.junit.Test; +import org.junit.experimental.categories.Categories; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; /** - * NOTE: Run the testcases in this suite manually one by one. See test case documentation - * and reset the FIDO application where needed. + * Config tests. + *

+ * These tests will change FIDO2 application configuration through authenticatorConfig. As these changes + * are irreversible. + *

+ * Read documentation for each tests for more information. */ -public class Ctap2ConfigInstrumentedTests extends FidoInstrumentedTests { +@RunWith(Categories.class) +@Suite.SuiteClasses(Ctap2ConfigInstrumentedTests.ConfigTests.class) +@Categories.ExcludeCategory(AlwaysManualTestCategory.class) +public class Ctap2ConfigInstrumentedTests { - @Test - public void testReadWriteEnterpriseAttestation() throws Throwable { - withCtap2Session(Ctap2ConfigTests::testReadWriteEnterpriseAttestation); - } + public static class ConfigTests extends FidoInstrumentedTests { + @Test + public void testReadWriteEnterpriseAttestation() throws Throwable { + withCtap2Session(Ctap2ConfigTests::testReadWriteEnterpriseAttestation); + } - @Test - public void testToggleAlwaysUv() throws Throwable { - withCtap2Session(Ctap2ConfigTests::testToggleAlwaysUv); - } + /** + * Toggles the {@code alwaysUv} option to opposite value. It is not possible to set this + * option to `false` on a FIPS approved YubiKey. + * + * @throws Throwable if an error occurs + */ + @Test + @Category(AlwaysManualTestCategory.class) + public void testToggleAlwaysUv() throws Throwable { + withCtap2Session(Ctap2ConfigTests::testToggleAlwaysUv); + } - /** - * Reset the FIDO application after calling this test case. - * - * @throws Throwable on any error - */ - @Test - public void testSetForcePinChange() throws Throwable { - withCtap2Session(Ctap2ConfigTests::testSetForcePinChange); - } + /** + * Sets the {@code forcePinChange} flag, verifies that and then changes the PIN twice so + * that the device uses the {@code TestUtil.PIN}. + * + * @throws Throwable if an error occurs + */ + @Test + public void testSetForcePinChange() throws Throwable { + withCtap2Session(Ctap2ConfigTests::testSetForcePinChange); + } - /** - * Reset the FIDO application after calling this test case. - * - * @throws Throwable on any error - */ - @Test - public void testSetMinPinLength() throws Throwable { - withCtap2Session(Ctap2ConfigTests::testSetMinPinLength); + /** + * Changes the {@code minPinLength} value. This change is irreversible and after running + * this test, the YubiKey should be reset. + * + * @throws Throwable if an error occurs + */ + @Test + @Category(AlwaysManualTestCategory.class) + public void testSetMinPinLength() throws Throwable { + withCtap2Session(Ctap2ConfigTests::testSetMinPinLength); + } } } diff --git a/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/Ctap2SessionResetInstrumentedTests.java b/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/Ctap2SessionResetInstrumentedTests.java index 51217e27..c1492bda 100644 --- a/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/Ctap2SessionResetInstrumentedTests.java +++ b/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/Ctap2SessionResetInstrumentedTests.java @@ -16,9 +16,11 @@ package com.yubico.yubikit.testing.fido; +import com.yubico.yubikit.testing.AlwaysManualTestCategory; import com.yubico.yubikit.testing.framework.FidoInstrumentedTests; import org.junit.Test; +import org.junit.experimental.categories.Category; /** * Tests FIDO Reset. @@ -32,6 +34,7 @@ */ public class Ctap2SessionResetInstrumentedTests extends FidoInstrumentedTests { @Test + @Category(AlwaysManualTestCategory.class) public void testReset() throws Throwable { withCtap2Session(Ctap2SessionTests::testReset); } diff --git a/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/FidoTests.java b/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/FidoTests.java index a0de1c12..b5ca07bf 100644 --- a/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/FidoTests.java +++ b/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/FidoTests.java @@ -16,20 +16,31 @@ package com.yubico.yubikit.testing.fido; +import com.yubico.yubikit.testing.AlwaysManualTestCategory; + +import org.junit.experimental.categories.Categories; import org.junit.runner.RunWith; import org.junit.runners.Suite; -@RunWith(Suite.class) +/** + * Setup YubiKey before running the integration tests: + *

+ */ +@RunWith(Categories.class) @Suite.SuiteClasses({ BasicWebAuthnClientInstrumentedTests.class, - Ctap2BioEnrollmentInstrumentedTests.class, Ctap2ClientPinInstrumentedTests.class, - Ctap2ConfigInstrumentedTests.class, Ctap2CredentialManagementInstrumentedTests.class, Ctap2SessionInstrumentedTests.class, - Ctap2SessionResetInstrumentedTests.class, EnterpriseAttestationInstrumentedTests.class, UvDiscouragedInstrumentedTests.class, + Ctap2ConfigInstrumentedTests.class, + Ctap2BioEnrollmentInstrumentedTests.class, + Ctap2SessionResetInstrumentedTests.class, }) +@Categories.ExcludeCategory(AlwaysManualTestCategory.class) public class FidoTests { } diff --git a/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/UvDiscouragedInstrumentedTests.java b/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/UvDiscouragedInstrumentedTests.java index f8b077fd..2c7ed0e2 100644 --- a/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/UvDiscouragedInstrumentedTests.java +++ b/testing-android/src/androidTest/java/com/yubico/yubikit/testing/fido/UvDiscouragedInstrumentedTests.java @@ -17,14 +17,17 @@ package com.yubico.yubikit.testing.fido; import com.yubico.yubikit.fido.client.PinRequiredClientError; +import com.yubico.yubikit.testing.AlwaysManualTestCategory; import com.yubico.yubikit.testing.framework.FidoInstrumentedTests; import org.junit.Test; +import org.junit.experimental.categories.Category; public class UvDiscouragedInstrumentedTests extends FidoInstrumentedTests { @Test + @Category(AlwaysManualTestCategory.class) public void testMakeCredentialGetAssertion() throws Throwable { - withCtap2Session(BasicWebAuthnClientTests::testUvDiscouragedMakeCredentialGetAssertionWithoutPin); + withCtap2Session(BasicWebAuthnClientTests::testUvDiscouragedMakeCredentialGetAssertionNoPin); } /** @@ -32,7 +35,7 @@ public void testMakeCredentialGetAssertion() throws Throwable { * Expected to fail with PinRequiredClientError */ @Test(expected = PinRequiredClientError.class) - public void testMakeCredentialGetAssertionOnProtected() throws Throwable { + public void testMakeCredentialGetAssertionOnProtectedWithPin() throws Throwable { withCtap2Session(BasicWebAuthnClientTests::testUvDiscouragedMakeCredentialGetAssertionWithPin); } } diff --git a/testing-android/src/main/java/com/yubico/yubikit/testing/AlwaysManualTestCategory.java b/testing-android/src/main/java/com/yubico/yubikit/testing/AlwaysManualTestCategory.java new file mode 100644 index 00000000..7c40dfbc --- /dev/null +++ b/testing-android/src/main/java/com/yubico/yubikit/testing/AlwaysManualTestCategory.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2024 Yubico. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.yubico.yubikit.testing; + +public interface AlwaysManualTestCategory { +} diff --git a/testing-android/src/main/java/com/yubico/yubikit/testing/TestActivity.java b/testing-android/src/main/java/com/yubico/yubikit/testing/TestActivity.java index 3d776502..8899489d 100644 --- a/testing-android/src/main/java/com/yubico/yubikit/testing/TestActivity.java +++ b/testing-android/src/main/java/com/yubico/yubikit/testing/TestActivity.java @@ -79,6 +79,20 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { @Override protected void onResume() { super.onResume(); + startNfcDiscovery(); + } + + @Override + protected void onPause() { + stopNfcDiscovery(); + super.onPause(); + } + + private void stopNfcDiscovery() { + yubiKitManager.stopNfcDiscovery(this); + } + + private void startNfcDiscovery() { try { yubiKitManager.startNfcDiscovery(new NfcConfiguration().timeout(1000 * 60 * 5), this, sessionQueue::add); } catch (NfcNotAvailable e) { @@ -90,12 +104,6 @@ protected void onResume() { } } - @Override - protected void onPause() { - yubiKitManager.stopNfcDiscovery(this); - super.onPause(); - } - private void setBusy(boolean busy) { runOnUiThread(() -> { if (busy) { @@ -148,6 +156,8 @@ public synchronized void returnSession(YubiKeyDevice device) throws InterruptedE ((NfcYubiKeyDevice) device).remove(lock::release); } else { lock.release(); + stopNfcDiscovery(); + startNfcDiscovery(); } lock.acquire(); diff --git a/testing/src/main/java/com/yubico/yubikit/testing/fido/BasicWebAuthnClientTests.java b/testing/src/main/java/com/yubico/yubikit/testing/fido/BasicWebAuthnClientTests.java index c9ba3034..1aee7758 100644 --- a/testing/src/main/java/com/yubico/yubikit/testing/fido/BasicWebAuthnClientTests.java +++ b/testing/src/main/java/com/yubico/yubikit/testing/fido/BasicWebAuthnClientTests.java @@ -77,11 +77,6 @@ public static void testMakeCredentialGetAssertionTokenUvOnly(Ctap2Session sessio } public static void testMakeCredentialGetAssertion(Ctap2Session session) throws Throwable { - - // // Ctap2ClientPinTests.ensureDefaultPinSet(session); - - char[] pin = TestData.PIN; - BasicWebAuthnClient webauthn = new BasicWebAuthnClient(session); List deleteCredIds = new ArrayList<>(); @@ -100,7 +95,7 @@ public static void testMakeCredentialGetAssertion(Ctap2Session session) throws T TestData.CLIENT_DATA_JSON_CREATE, creationOptionsNonRk, Objects.requireNonNull(creationOptionsNonRk.getRp().getId()), - pin, + TestData.PIN, null, null ); @@ -123,7 +118,7 @@ public static void testMakeCredentialGetAssertion(Ctap2Session session) throws T TestData.CLIENT_DATA_JSON_CREATE, creationOptionsRk, Objects.requireNonNull(creationOptionsRk.getRp().getId()), - pin, + TestData.PIN, null, null ); @@ -148,7 +143,7 @@ public static void testMakeCredentialGetAssertion(Ctap2Session session) throws T TestData.CLIENT_DATA_JSON_GET, requestOptions, TestData.RP_ID, - pin, + TestData.PIN, null ); AuthenticatorAssertionResponse response = (AuthenticatorAssertionResponse) credential.getResponse(); @@ -168,7 +163,7 @@ public static void testUvDiscouragedMakeCredentialGetAssertionWithPin(Ctap2Sessi testUvDiscouragedMakeCredentialGetAssertion(session); } - public static void testUvDiscouragedMakeCredentialGetAssertionWithoutPin(Ctap2Session session) throws Throwable { + public static void testUvDiscouragedMakeCredentialGetAssertionNoPin(Ctap2Session session) throws Throwable { assumeFalse("Device has no PIN set", Boolean.TRUE.equals(session.getCachedInfo().getOptions().get("clientPin"))); testUvDiscouragedMakeCredentialGetAssertion(session); @@ -286,9 +281,6 @@ private static void testUvDiscouragedMakeCredentialGetAssertion(Ctap2Session ses } public static void testGetAssertionMultipleUsersRk(Ctap2Session session) throws Throwable { - - // Ctap2ClientPinTests.ensureDefaultPinSet(session); - BasicWebAuthnClient webauthn = new BasicWebAuthnClient(session); List deleteCredIds = new ArrayList<>(); @@ -367,8 +359,6 @@ public static void testGetAssertionMultipleUsersRk(Ctap2Session session) throws public static void testGetAssertionWithAllowList(Ctap2Session session) throws Throwable { - // Ctap2ClientPinTests.ensureDefaultPinSet(session); - BasicWebAuthnClient webauthn = new BasicWebAuthnClient(session); // Make 2 new credentials @@ -457,8 +447,6 @@ public static void testGetAssertionWithAllowList(Ctap2Session session) throws Th public static void testMakeCredentialWithExcludeList(Ctap2Session session) throws Throwable { - // Ctap2ClientPinTests.ensureDefaultPinSet(session); - BasicWebAuthnClient webauthn = new BasicWebAuthnClient(session); List excludeList = new ArrayList<>(); @@ -525,7 +513,6 @@ public static void testMakeCredentialWithExcludeList(Ctap2Session session) throw } public static void testMakeCredentialKeyAlgorithms(Ctap2Session session) throws Throwable { - // Ctap2ClientPinTests.ensureDefaultPinSet(session); BasicWebAuthnClient webauthn = new BasicWebAuthnClient(session); List allCredParams = Arrays.asList( TestData.PUB_KEY_CRED_PARAMS_ES256, @@ -601,18 +588,14 @@ public static void testMakeCredentialKeyAlgorithms(Ctap2Session session) throws } public static void testClientPinManagement(Ctap2Session session) throws Throwable { - // Ctap2ClientPinTests.ensureDefaultPinSet(session); - BasicWebAuthnClient webauthn = new BasicWebAuthnClient(session); assertTrue(webauthn.isPinSupported()); assertTrue(webauthn.isPinConfigured()); - char[] otherPin = "123123".toCharArray(); - - webauthn.changePin(TestData.PIN, otherPin); + webauthn.changePin(TestData.PIN, TestData.OTHER_PIN); try { - webauthn.changePin(TestData.PIN, otherPin); + webauthn.changePin(TestData.PIN, TestData.OTHER_PIN); fail("Wrong PIN was accepted"); } catch (ClientError e) { assertThat(e.getErrorCode(), equalTo(ClientError.Code.BAD_REQUEST)); @@ -621,14 +604,13 @@ public static void testClientPinManagement(Ctap2Session session) throws Throwabl is(CtapException.ERR_PIN_INVALID)); } - webauthn.changePin(otherPin, TestData.PIN); + webauthn.changePin(TestData.OTHER_PIN, TestData.PIN); } public static void testClientCredentialManagement(Ctap2Session session) throws Throwable { assumeTrue("Credential management not supported", CredentialManagement.isSupported(session.getCachedInfo())); - // Ctap2ClientPinTests.ensureDefaultPinSet(session); BasicWebAuthnClient webauthn = new BasicWebAuthnClient(session); PublicKeyCredentialCreationOptions creationOptions = getCreateOptions(null, true, Collections.singletonList(TestData.PUB_KEY_CRED_PARAMS_ES256), diff --git a/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2BioEnrollmentTests.java b/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2BioEnrollmentTests.java index ce5c9ed7..3a6ec960 100755 --- a/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2BioEnrollmentTests.java +++ b/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2BioEnrollmentTests.java @@ -16,7 +16,6 @@ package com.yubico.yubikit.testing.fido; -import static com.yubico.yubikit.testing.fido.Ctap2ClientPinTests.ensureDefaultPinSet; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -42,7 +41,7 @@ public class Ctap2BioEnrollmentTests { public static void testFingerprintEnrollment(Ctap2Session session) throws Throwable { - assumeTrue(" Bio enrollment not supported", + assumeTrue("Bio enrollment not supported", BioEnrollment.isSupported(session.getCachedInfo())); final FingerprintBioEnrollment fingerprintBioEnrollment = fpBioEnrollment(session); diff --git a/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2ClientPinTests.java b/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2ClientPinTests.java index 0820504d..8a848f3a 100755 --- a/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2ClientPinTests.java +++ b/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2ClientPinTests.java @@ -21,48 +21,20 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.fail; -import com.yubico.yubikit.core.application.CommandException; import com.yubico.yubikit.core.fido.CtapException; import com.yubico.yubikit.fido.ctap.ClientPin; import com.yubico.yubikit.fido.ctap.Ctap2Session; -import java.io.IOException; -import java.util.Objects; - public class Ctap2ClientPinTests { - /** - * Attempts to set (or verify) the default PIN, or fails. - */ - static void ensureDefaultPinSet(Ctap2Session session) throws IOException, CommandException { - - Ctap2Session.InfoData info = session.getInfo(); - - ClientPin pin = new ClientPin(session, TestData.PIN_UV_AUTH_PROTOCOL); - boolean pinSet = Objects.requireNonNull((Boolean) info.getOptions().get("clientPin")); - - if (!pinSet) { - pin.setPin(TestData.PIN); - } else { - pin.getPinToken( - TestData.PIN, - ClientPin.PIN_PERMISSION_MC | ClientPin.PIN_PERMISSION_GA, - "localhost"); - } - } - public static void testClientPin(Ctap2Session session) throws Throwable { - char[] otherPin = "12312312".toCharArray(); - Integer permissions = ClientPin.PIN_PERMISSION_MC | ClientPin.PIN_PERMISSION_GA; String permissionRpId = "localhost"; - // ensureDefaultPinSet(session); - ClientPin pin = new ClientPin(session, TestData.PIN_UV_AUTH_PROTOCOL); assertThat(pin.getPinUvAuth().getVersion(), is(TestData.PIN_UV_AUTH_PROTOCOL.getVersion())); assertThat(pin.getPinRetries().getCount(), is(8)); - pin.changePin(TestData.PIN, otherPin); + pin.changePin(TestData.PIN, TestData.OTHER_PIN); try { pin.getPinToken(TestData.PIN, permissions, permissionRpId); fail("Wrong PIN was accepted"); @@ -72,8 +44,8 @@ public static void testClientPin(Ctap2Session session) throws Throwable { } assertThat(pin.getPinRetries().getCount(), is(7)); - assertThat(pin.getPinToken(otherPin, permissions, permissionRpId), notNullValue()); + assertThat(pin.getPinToken(TestData.OTHER_PIN, permissions, permissionRpId), notNullValue()); assertThat(pin.getPinRetries().getCount(), is(8)); - pin.changePin(otherPin, TestData.PIN); + pin.changePin(TestData.OTHER_PIN, TestData.PIN); } } diff --git a/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2ConfigTests.java b/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2ConfigTests.java index 241b01be..f7b2d077 100644 --- a/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2ConfigTests.java +++ b/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2ConfigTests.java @@ -16,11 +16,14 @@ package com.yubico.yubikit.testing.fido; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; import static java.lang.Boolean.TRUE; @@ -34,7 +37,6 @@ public class Ctap2ConfigTests { static Config getConfig(Ctap2Session session) throws IOException, CommandException { - // Ctap2ClientPinTests.ensureDefaultPinSet(session); ClientPin clientPin = new ClientPin(session, TestData.PIN_UV_AUTH_PROTOCOL); byte[] pinToken = clientPin.getPinToken(TestData.PIN, ClientPin.PIN_PERMISSION_ACFG, null); return new Config(session, TestData.PIN_UV_AUTH_PROTOCOL, pinToken); @@ -60,10 +62,23 @@ public static void testToggleAlwaysUv(Ctap2Session session) throws Throwable { public static void testSetForcePinChange(Ctap2Session session) throws Throwable { assumeTrue("authenticatorConfig not supported", Config.isSupported(session.getCachedInfo())); - assertFalse(session.getInfo().getForcePinChange()); + assumeFalse("Force PIN change already set. Reset key and retry", session.getInfo().getForcePinChange()); Config config = getConfig(session); config.setMinPinLength(null, null, true); assertTrue(session.getInfo().getForcePinChange()); + + // set a new PIN + ClientPin pin = new ClientPin(session, TestData.PIN_UV_AUTH_PROTOCOL); + assertThat(pin.getPinUvAuth().getVersion(), is(TestData.PIN_UV_AUTH_PROTOCOL.getVersion())); + assertThat(pin.getPinRetries().getCount(), is(8)); + + pin.changePin(TestData.PIN, TestData.OTHER_PIN); + assertFalse(session.getInfo().getForcePinChange()); + + // set to a default PIN + pin.changePin(TestData.OTHER_PIN, TestData.PIN); + assertFalse(session.getInfo().getForcePinChange()); + } public static void testSetMinPinLength(Ctap2Session session) throws Throwable { diff --git a/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2CredentialManagementTests.java b/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2CredentialManagementTests.java index 917c87a6..d4871bd2 100755 --- a/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2CredentialManagementTests.java +++ b/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2CredentialManagementTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Yubico. + * Copyright (C) 2020-2024 Yubico. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,7 +58,6 @@ private static CredentialManagement setupCredentialManagement( assumeTrue("Credential management not supported", CredentialManagement.isSupported(session.getCachedInfo())); - // Ctap2ClientPinTests.ensureDefaultPinSet(session); ClientPin clientPin = new ClientPin(session, TestData.PIN_UV_AUTH_PROTOCOL); return new CredentialManagement( diff --git a/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2SessionTests.java b/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2SessionTests.java index 758fac8d..239ae70e 100644 --- a/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2SessionTests.java +++ b/testing/src/main/java/com/yubico/yubikit/testing/fido/Ctap2SessionTests.java @@ -16,7 +16,6 @@ package com.yubico.yubikit.testing.fido; -import static com.yubico.yubikit.testing.fido.Ctap2ClientPinTests.ensureDefaultPinSet; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; @@ -95,8 +94,6 @@ private static void doTestCancelCborCommand( assumeTrue("Not a USB connection", TestData.TRANSPORT_USB); - // ensureDefaultPinSet(session); - ClientPin pin = new ClientPin(session, TestData.PIN_UV_AUTH_PROTOCOL); byte[] pinToken = pin.getPinToken(TestData.PIN, ClientPin.PIN_PERMISSION_MC, TestData.RP.getId()); byte[] pinAuth = pin.getPinUvAuth().authenticate(pinToken, TestData.CLIENT_DATA_HASH); diff --git a/testing/src/main/java/com/yubico/yubikit/testing/fido/EnterpriseAttestationTests.java b/testing/src/main/java/com/yubico/yubikit/testing/fido/EnterpriseAttestationTests.java index 9dcd3ac6..d02d6bd8 100644 --- a/testing/src/main/java/com/yubico/yubikit/testing/fido/EnterpriseAttestationTests.java +++ b/testing/src/main/java/com/yubico/yubikit/testing/fido/EnterpriseAttestationTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Yubico. + * Copyright (C) 2020-2024 Yubico. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,6 @@ import javax.annotation.Nullable; -@SuppressWarnings("unchecked") public class EnterpriseAttestationTests { static void enableEp(Ctap2Session session) @@ -66,7 +65,6 @@ static void enableEp(Ctap2Session session) public static void testSupportedPlatformManagedEA(Ctap2Session session) throws Throwable { assumeTrue("Enterprise attestation not supported", session.getCachedInfo().getOptions().containsKey("ep")); - // // Ctap2ClientPinTests.ensureDefaultPinSet(session); enableEp(session); BasicWebAuthnClient webauthn = new BasicWebAuthnClient(session); webauthn.getUserAgentConfiguration().setEpSupportedRpIds(Collections.singletonList(TestData.RP_ID)); @@ -183,6 +181,7 @@ private static PublicKeyCredentialCreationOptions getCredentialCreationOptions( /** * Helper method which extracts AuthenticatorAttestationResponse from the credential */ + @SuppressWarnings("unchecked") private static Map getAttestationObject(AuthenticatorResponse response) { AuthenticatorAttestationResponse authenticatorAttestationResponse = (AuthenticatorAttestationResponse) response; diff --git a/testing/src/main/java/com/yubico/yubikit/testing/fido/FidoTestUtils.java b/testing/src/main/java/com/yubico/yubikit/testing/fido/FidoTestUtils.java index 2e6601c2..6a266591 100644 --- a/testing/src/main/java/com/yubico/yubikit/testing/fido/FidoTestUtils.java +++ b/testing/src/main/java/com/yubico/yubikit/testing/fido/FidoTestUtils.java @@ -16,8 +16,10 @@ package com.yubico.yubikit.testing.fido; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import com.yubico.yubikit.core.Transport; @@ -26,15 +28,23 @@ import com.yubico.yubikit.core.application.CommandException; import com.yubico.yubikit.core.fido.FidoConnection; import com.yubico.yubikit.core.smartcard.SmartCardConnection; +import com.yubico.yubikit.fido.client.BasicWebAuthnClient; +import com.yubico.yubikit.fido.client.ClientError; +import com.yubico.yubikit.fido.client.CredentialManager; +import com.yubico.yubikit.fido.ctap.ClientPin; import com.yubico.yubikit.fido.ctap.Config; import com.yubico.yubikit.fido.ctap.Ctap2Session; import com.yubico.yubikit.fido.ctap.PinUvAuthProtocol; +import com.yubico.yubikit.fido.webauthn.PublicKeyCredentialDescriptor; +import com.yubico.yubikit.fido.webauthn.PublicKeyCredentialUserEntity; import com.yubico.yubikit.management.Capability; import com.yubico.yubikit.management.DeviceInfo; import com.yubico.yubikit.management.ManagementSession; import java.io.IOException; import java.util.List; +import java.util.Map; +import java.util.Objects; public class FidoTestUtils { public static void verifyAndSetup( @@ -66,15 +76,8 @@ public static void verifyAndSetup( TestData.PIN_UV_AUTH_PROTOCOL = pinUvAuthProtocol; TestData.TRANSPORT_USB = device.getTransport() == Transport.USB; -// // cannot reset over neither transport -// -// if (!TestData.TRANSPORT_USB) { -// // only reset FIDO over NFC -// session.reset(null); -// } - - // always set a PIN - Ctap2ClientPinTests.ensureDefaultPinSet(session); + // validate PIN + verifyOrSetPin(session); if (isFidoFipsCapable && Boolean.FALSE.equals(session.getInfo().getOptions().get("alwaysUv"))) { @@ -83,6 +86,7 @@ public static void verifyAndSetup( config.toggleAlwaysUv(); } + managementSession = getManagementSession(connection); deviceInfo = managementSession.getDeviceInfo(); TestData.FIPS_APPROVED = (deviceInfo.getFipsApproved() & Capability.FIDO2.bit) == Capability.FIDO2.bit; @@ -93,8 +97,10 @@ public static void verifyAndSetup( assertNotNull(deviceInfo); assertTrue("Device not FIDO FIPS approved as expected", TestData.FIPS_APPROVED); } - } + // remove existing credentials + deleteExistingCredentials(session); + } } private static boolean supportsPinUvAuthProtocol( @@ -142,4 +148,44 @@ private static ManagementSession getManagementSession(YubiKeyConnection connecti return session; } + + private static void deleteExistingCredentials(Ctap2Session session) + throws IOException, CommandException, ClientError { + final BasicWebAuthnClient webauthn = new BasicWebAuthnClient(session); + final CredentialManager credentialManager = webauthn.getCredentialManager(TestData.PIN); + final List rpIds = credentialManager.getRpIdList(); + for (String rpId : rpIds) { + Map credentials + = credentialManager.getCredentials(rpId); + for (PublicKeyCredentialDescriptor credential : credentials.keySet()) { + credentialManager.deleteCredential(credential); + } + } + assertEquals("Failed to remove all credentials", 0, credentialManager.getCredentialCount()); + } + + /** + * Attempts to set (or verify) the default PIN, or fails. + */ + private static void verifyOrSetPin(Ctap2Session session) throws IOException, CommandException { + + Ctap2Session.InfoData info = session.getInfo(); + + ClientPin pin = new ClientPin(session, TestData.PIN_UV_AUTH_PROTOCOL); + boolean pinSet = Objects.requireNonNull((Boolean) info.getOptions().get("clientPin")); + + try { + if (!pinSet) { + pin.setPin(TestData.PIN); + } else { + pin.getPinToken( + TestData.PIN, + ClientPin.PIN_PERMISSION_MC | ClientPin.PIN_PERMISSION_GA, + "localhost"); + } + } catch (CommandException e) { + fail("YubiKey cannot be used for test, failed to set/verify PIN. Please reset " + + "and try again."); + } + } } diff --git a/testing/src/main/java/com/yubico/yubikit/testing/fido/TestData.java b/testing/src/main/java/com/yubico/yubikit/testing/fido/TestData.java index d0e6512d..69d5b8b1 100755 --- a/testing/src/main/java/com/yubico/yubikit/testing/fido/TestData.java +++ b/testing/src/main/java/com/yubico/yubikit/testing/fido/TestData.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Yubico. + * Copyright (C) 2020-2024 Yubico. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,7 @@ public ClientData(String type, String origin, byte[] challenge, String androidPa public static boolean FIPS_APPROVED = false; public static final char[] PIN = "11234567".toCharArray(); + public static final char[] OTHER_PIN = "11231234".toCharArray(); public static final String RP_ID = "example.com"; public static final String RP_NAME = "Example Company";