Skip to content

Commit 4008991

Browse files
committed
Allow forcing alwaysUv on easily
1 parent 0fe68cf commit 4008991

File tree

4 files changed

+46
-28
lines changed

4 files changed

+46
-28
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ system locally with [pam-u2f](https://github.com/Yubico/pam-u2f).
1313

1414
100% of the FIDO2 CTAP2.1 spec is covered, with the exception of features
1515
that aren't physically on an ordinary smartcard, such as biometrics or
16-
other on-board user verification. The implementation is not 100% standards
16+
other on-board user verification. The implementation is not 100.0% standards
1717
compliant, but you can expect very good results generally.
1818

1919
In order to run this outside a simulator, you will need

mds.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"attestationRootCertificates": [],
3535
"authenticatorGetInfo": {
3636
"versions": [ "FIDO_2_0", "FIDO_2_1", "FIDO_2_1_PRE" ],
37-
"extensions": [ "credBlob", "credProtect", "hmac-secret", "largeBlobKey" ],
37+
"extensions": [ "credBlob", "credProtect", "hmac-secret", "largeBlobKey", "minPinLength" ],
3838
"aaguid": "00000000000000000000000000000000",
3939
"options": {
4040
"ep": true,

python_tests/ctap/ctap_test.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -294,27 +294,42 @@ def get_high_level_client(self, extensions: Optional[list[Type[Ctap2Extension]]]
294294

295295
def get_high_level_make_cred_options(self,
296296
resident_key: ResidentKeyRequirement = ResidentKeyRequirement.DISCOURAGED,
297-
extensions=None, rp_id: Optional[str] = None,
297+
extensions=None, rp_id: Optional[str] = None, rp_stuff: Optional[dict] = None,
298+
user_stuff: Optional[dict] = None,
299+
client_data: Optional[bytes] = None,
300+
user_verification: Optional[UserVerificationRequirement] = None,
298301
user_id: Optional[bytes] = None) -> PublicKeyCredentialCreationOptions:
299302
if extensions is None:
300303
extensions = {}
301304

305+
if client_data is None:
306+
client_data = self.client_data
307+
302308
if rp_id is None:
303309
rp_id = self.rp_id
304310

305-
if user_id is None:
306-
user_id = self.basic_makecred_params['user']['id']
311+
if user_verification is None:
312+
user_verification = UserVerificationRequirement.DISCOURAGED
313+
314+
if rp_stuff is None:
315+
rp_stuff = {
316+
"name": "An RP Name",
317+
"id": rp_id
318+
}
319+
320+
if user_stuff is None:
321+
if user_id is None:
322+
user_id = self.basic_makecred_params['user']['id']
323+
324+
user_stuff = {
325+
"name": "Bob",
326+
"id": user_id
327+
}
307328

308329
return PublicKeyCredentialCreationOptions(
309-
rp=PublicKeyCredentialRpEntity(
310-
name="An RP Name",
311-
id=rp_id
312-
),
313-
user=PublicKeyCredentialUserEntity(
314-
name="Bob",
315-
id=user_id
316-
),
317-
challenge=self.client_data,
330+
rp=PublicKeyCredentialRpEntity(**rp_stuff),
331+
user=PublicKeyCredentialUserEntity(**user_stuff),
332+
challenge=client_data,
318333
pub_key_cred_params=[
319334
PublicKeyCredentialParameters(
320335
type=PublicKeyCredentialType.PUBLIC_KEY,
@@ -324,7 +339,7 @@ def get_high_level_make_cred_options(self,
324339
extensions=extensions,
325340
authenticator_selection=AuthenticatorSelectionCriteria(
326341
resident_key=resident_key,
327-
user_verification=UserVerificationRequirement.DISCOURAGED
342+
user_verification=user_verification
328343
)
329344
)
330345

@@ -375,10 +390,10 @@ class BasicAttestationTestCase(CTAPTestCase):
375390
aaguid: bytes
376391
cert: bytes
377392

378-
def install_attestation_cert(self):
393+
def install_attestation_cert(self, **kwargs):
379394
self.ctap2.send_cbor(
380395
self.VENDOR_COMMAND_SWITCH_ATT,
381-
args(self.gen_attestation_cert())
396+
args(self.gen_attestation_cert(**kwargs))
382397
)
383398

384399
def _short_to_bytes(self, b: int) -> list[int]:

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

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ public final class FIDO2Applet extends Applet implements ExtendedLength {
1717
*/
1818
private static final byte FIRMWARE_VERSION = 0x01;
1919
/**
20-
* If true, permit the creation of resident keys without a PIN being set or provided.
21-
* This is "normal" for a FIDO2 authenticator, but means keys on the device could be
22-
* accessed in the event of a software bug or hardware fault.
20+
* If true, default the `alwaysUv` option to on, and prevent disabling it.
2321
*/
24-
private static final boolean ALLOW_RESIDENT_KEY_CREATION_WITHOUT_PIN = true;
22+
private static final boolean FORCE_ALWAYS_UV = false;
2523
/**
2624
* If true, the authenticator will refuse to reset itself until the following three steps happen in order:
2725
* <p>
@@ -305,7 +303,7 @@ public final class FIDO2Applet extends Applet implements ExtendedLength {
305303
/**
306304
* Set to true when the use of a PIN is forced for all operations
307305
*/
308-
private boolean alwaysUv;
306+
private boolean alwaysUv = FORCE_ALWAYS_UV;
309307
/**
310308
* Everything that needs to be hot in RAM instead of stored to the flash. All goes away on deselect or reset!
311309
*/
@@ -787,10 +785,6 @@ private void makeCredential(APDU apdu, short lc, byte[] buffer) {
787785
// OR: PIN not set, but we've been asked not to do this without one
788786
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_REQUIRED);
789787
}
790-
if (!ALLOW_RESIDENT_KEY_CREATION_WITHOUT_PIN && transientStorage.hasRKOption()) {
791-
// Don't allow storing resident keys without a PIN set
792-
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_OPERATION_DENIED);
793-
}
794788
}
795789
loadWrappingKeyIfNoPIN();
796790

@@ -1376,10 +1370,16 @@ private void verifyPinAuth(APDU apdu, byte[] buffer, short readIdx,
13761370
if (len >= (byte) 0x40 && len <= (byte) 0x57) {
13771371
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_AUTH_INVALID);
13781372
}
1373+
if (len == 0x58) {
1374+
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_AUTH_INVALID);
1375+
}
13791376
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
13801377
}
13811378
} else {
13821379
if (len != 0x58) { // byte array, one-byte length
1380+
if (len >= 0x40 && len <= 0x57) {
1381+
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_AUTH_INVALID);
1382+
}
13831383
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
13841384
}
13851385
if (buffer[readIdx++] != desiredLength) {
@@ -3601,7 +3601,7 @@ private void handleLargeBlobs(APDU apdu, byte[] reqBuffer, short lc) {
36013601
}
36023602
}
36033603

3604-
if (pinSet) {
3604+
if (pinSet || alwaysUv) {
36053605
if (pinUvAuthIdx == -1) {
36063606
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_REQUIRED);
36073607
}
@@ -4277,6 +4277,9 @@ private void setMinPin(APDU apdu, byte[] buffer, short readIdx, short cmdParamsL
42774277
* @param apdu Request/response context object
42784278
*/
42794279
private void toggleAlwaysUv(APDU apdu) {
4280+
if (FORCE_ALWAYS_UV) {
4281+
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_OPERATION_DENIED);
4282+
}
42804283
alwaysUv = !alwaysUv;
42814284

42824285
apdu.getBuffer()[0] = FIDOConstants.CTAP2_OK;
@@ -5083,7 +5086,7 @@ private void authenticatorReset(APDU apdu) {
50835086
pinSet = false;
50845087
minPinLength = 4;
50855088
forcePinChange = false;
5086-
alwaysUv = false;
5089+
alwaysUv = FORCE_ALWAYS_UV;
50875090
pinRetryCounter.reset(pinIdx);
50885091
Util.arrayFillNonAtomic(largeBlobStore, (short) 0, LARGE_BLOB_STORE_MAX_SIZE, (byte) 0x00);
50895092
Util.arrayCopyNonAtomic(CannedCBOR.INITIAL_LARGE_BLOB_ARRAY, (short) 0,

0 commit comments

Comments
 (0)