Skip to content

Commit e42e9fb

Browse files
committed
Better credProtect indirect exclusion handling
1 parent 95a139f commit e42e9fb

File tree

2 files changed

+59
-3
lines changed

2 files changed

+59
-3
lines changed

python_tests/ctap/test_credprotect.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,64 @@ def test_deviations_from_expectations(self, _, level, policy,
129129
level, policy, UserVerificationRequirement.DISCOURAGED,
130130
resident, discoverable_afterwards, usable_afterwards)
131131

132+
def test_strong_protected_creds_ignored_on_exclude_list_without_pin(self):
133+
policy = CredProtectExtension.POLICY.REQUIRED
134+
client = self.get_high_level_client(extensions=[CredProtectExtension])
135+
res = client.make_credential(options=self.get_high_level_make_cred_options(
136+
ResidentKeyRequirement.DISCOURAGED,
137+
{
138+
"credentialProtectionPolicy": policy
139+
}
140+
))
141+
self.basic_makecred_params['exclude_list'] = [{
142+
"type": "public-key",
143+
"id": res.attestation_object.auth_data.credential_data.credential_id
144+
}]
145+
146+
self.ctap2.make_credential(**self.basic_makecred_params)
147+
148+
def test_level_3_protected_creds_ignored_on_exclude_list_without_pin(self):
149+
policy = CredProtectExtension.POLICY.REQUIRED
150+
client = self.get_high_level_client(extensions=[CredProtectExtension])
151+
res = client.make_credential(options=self.get_high_level_make_cred_options(
152+
ResidentKeyRequirement.REQUIRED,
153+
{
154+
"credentialProtectionPolicy": policy
155+
}
156+
))
157+
self.basic_makecred_params['exclude_list'] = [{
158+
"type": "public-key",
159+
"id": res.attestation_object.auth_data.credential_data.credential_id
160+
}]
161+
162+
self.ctap2.make_credential(**self.basic_makecred_params)
163+
164+
def test_level_2_protected_creds_effective_on_exclude_list_without_pin(self):
165+
policy = CredProtectExtension.POLICY.OPTIONAL_WITH_LIST
166+
client = self.get_high_level_client(extensions=[CredProtectExtension])
167+
res = client.make_credential(options=self.get_high_level_make_cred_options(
168+
ResidentKeyRequirement.REQUIRED,
169+
{
170+
"credentialProtectionPolicy": policy
171+
}
172+
))
173+
self.basic_makecred_params['exclude_list'] = [{
174+
"type": "public-key",
175+
"id": res.attestation_object.auth_data.credential_data.credential_id
176+
}]
177+
178+
with self.assertRaises(CtapError) as e:
179+
self.ctap2.make_credential(**self.basic_makecred_params)
180+
181+
self.assertEqual(CtapError.ERR.CREDENTIAL_EXCLUDED, e.exception.code)
182+
132183

133184
class CredProtectRKVisTestCase(CredManagementBaseTestCase):
134185
@parameterized.expand([
135186
("Level 3", 3, CredProtectExtension.POLICY.REQUIRED),
136187
("Level 2", 2, CredProtectExtension.POLICY.OPTIONAL_WITH_LIST),
137188
("Level 1", 1, CredProtectExtension.POLICY.OPTIONAL),
138-
("Omitted", 0, None),
189+
("Omitted", 1, None),
139190
])
140191
def test_cred_protect_level_rk_visibility(self, _, level, policy):
141192
client = self.get_high_level_client(extensions=[CredProtectExtension],

src/main/java/us/q3q/fido2/FIDO2Applet.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -832,7 +832,7 @@ private void makeCredential(APDU apdu, short lc, byte[] buffer) {
832832

833833
if (checkCredential(buffer, credIdIdx, CREDENTIAL_ID_LEN,
834834
scratchRPIDHashBuffer, scratchRPIDHashOffset,
835-
scratchCredBuffer, scratchCredOffset, (short) -1, true)) {
835+
scratchCredBuffer, scratchCredOffset, (short) -1, pinAuthSuccess)) {
836836
// This credential is a valid non-discoverable cred.
837837
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CREDENTIAL_EXCLUDED);
838838
}
@@ -987,7 +987,12 @@ private void makeCredential(APDU apdu, short lc, byte[] buffer) {
987987
initSymmetricWrapperForRK(targetRKSlot, RK_IV_CRED_BLOB);
988988
symmetricWrap(residentKeyCredBlobs, (short)(targetRKSlot * MAX_CRED_BLOB_LEN), MAX_CRED_BLOB_LEN,
989989
residentKeyCredBlobs, (short) (targetRKSlot * MAX_CRED_BLOB_LEN));
990-
byte residentKeyFlagByte = (byte) (0x80 | credProtectLevel);
990+
byte effectiveCPLevel = credProtectLevel;
991+
if (effectiveCPLevel == 0) {
992+
// "default" creds are saved as level one
993+
effectiveCPLevel = 1;
994+
}
995+
byte residentKeyFlagByte = (byte) (0x80 | effectiveCPLevel);
991996
if (!foundMatchingRK) {
992997
// We're filling an empty slot
993998
numResidentCredentials++;

0 commit comments

Comments
 (0)