diff --git a/python_tests/ctap/test_credprotect.py b/python_tests/ctap/test_credprotect.py index b05719e..42b7562 100644 --- a/python_tests/ctap/test_credprotect.py +++ b/python_tests/ctap/test_credprotect.py @@ -211,6 +211,40 @@ def test_low_sec_credprotect(self): 'up': False }) + def test_credprotect_three_creation_requires_pin_when_pin_set(self): + pin = secrets.token_hex(8) + ClientPin(self.ctap2).set_pin(pin) + + params = dict(self.basic_makecred_params) + params['extensions'] = { + "credProtect": CredProtectExtension.POLICY.REQUIRED + } + + with self.assertRaises(CtapError) as e: + self.ctap2.make_credential(**params) + + self.assertEqual(CtapError.ERR.PIN_REQUIRED, e.exception.code) + + +class MaximumComplianceCredProtectTestCase(CredProtectTestCase): + def setUp(self, install_params=None) -> None: + if install_params is None: + install_params = bytes([0xA2, 0x00, 0xF5, 0x01, 0xF5]) + super().setUp(install_params=install_params) + + def test_level_three_can_use_low_security_when_explicitly_enabled(self): + pin = secrets.token_hex(8) + ClientPin(self.ctap2).set_pin(pin) + + params = dict(self.basic_makecred_params) + params['extensions'] = { + "credProtect": CredProtectExtension.POLICY.REQUIRED + } + + res = self.ctap2.make_credential(**params) + self.assertEqual(CredProtectExtension.POLICY.REQUIRED, + res.auth_data.extensions.get('credProtect')) + class CredProtectDeletionTestCase(CredManagementBaseTestCase): @parameterized.expand([ diff --git a/src/main/java/us/q3q/fido2/FIDO2Applet.java b/src/main/java/us/q3q/fido2/FIDO2Applet.java index 137da2a..1b1b3c9 100644 --- a/src/main/java/us/q3q/fido2/FIDO2Applet.java +++ b/src/main/java/us/q3q/fido2/FIDO2Applet.java @@ -1068,7 +1068,8 @@ private void makeCredential(APDU apdu, short lc, byte[] buffer) { effectiveCredBlobLen = 0; } - final boolean lowSecForRK = USE_LOW_SECURITY_FOR_SOME_RKS && credProtectLevel < 3; + final boolean lowSecForRK = (USE_LOW_SECURITY_FOR_SOME_RKS && credProtectLevel < 3) + || LOW_SECURITY_MAXIMUM_COMPLIANCE; final boolean lowSecWasUsed = encodeCredentialID(apdu, (ECPrivateKey) ecKeyPair.getPrivate(), scratchRPIDHashBuffer, scratchRPIDHashOffset, scratchCredBuffer, scratchCredOffset, @@ -1119,7 +1120,8 @@ private void makeCredential(APDU apdu, short lc, byte[] buffer) { } } else { // Non-resident credProtect Level 3 creds still need to use the high security key (to require PIN auth) - final boolean credMayUseLowSecurityForDiscoverable = credProtectLevel < 3; + final boolean credMayUseLowSecurityForDiscoverable = credProtectLevel < 3 + || LOW_SECURITY_MAXIMUM_COMPLIANCE; encodeCredentialID(apdu, (ECPrivateKey) ecKeyPair.getPrivate(), scratchRPIDHashBuffer, scratchRPIDHashOffset, scratchCredBuffer, scratchCredOffset, @@ -1772,13 +1774,18 @@ private boolean encodeCredentialID(APDU apdu, ECPrivateKey privKey, byte[] rpIdHashBuffer, short rpIdHashOffset, byte[] outBuffer, short outOffset, short rkNum, boolean lowSecurity, byte credProtectLevel) { + final boolean lowSecurityExplicitlyRequested = lowSecurity; if (rkNum >= 0 && !USE_LOW_SECURITY_FOR_SOME_RKS) { lowSecurity = false; } - // use low security even for RKs if LOW_SECURITY_MAXIMUM_COMPLIANCE - if (LOW_SECURITY_MAXIMUM_COMPLIANCE) { + // use low security even for RKs if LOW_SECURITY_MAXIMUM_COMPLIANCE is explicitly requested + if (LOW_SECURITY_MAXIMUM_COMPLIANCE && (lowSecurity || credProtectLevel < 3)) { lowSecurity = true; } + // regardless of anything else, default to the high-sec key for credProtect-level-3 creds unless explicitly requested + if (credProtectLevel == 3 && !lowSecurityExplicitlyRequested) { + lowSecurity = false; + } // regardless of anything else, opportunistically use the high-sec key for credProtect-level-3 creds final byte pinProtocolInUse = transientStorage.getPinProtocolInUse(); if (credProtectLevel == 3 && (pinProtocolInUse == 1 || pinProtocolInUse == 2)) { @@ -6826,7 +6833,7 @@ private FIDO2Applet(byte[] array, short offset, byte length) { // set up parameters // first, defaults attestationSwitchingEnabled = false; - LOW_SECURITY_MAXIMUM_COMPLIANCE = true; + LOW_SECURITY_MAXIMUM_COMPLIANCE = false; FORCE_ALWAYS_UV = false; USE_LOW_SECURITY_FOR_SOME_RKS = true; PROTECT_AGAINST_MALICIOUS_RESETS = false;