Skip to content

Commit 9dd4833

Browse files
committed
Standards compliance work
1 parent 96fa62b commit 9dd4833

File tree

3 files changed

+85
-41
lines changed

3 files changed

+85
-41
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,9 @@ public abstract class CannedCBOR {
103103
0x62, // string - two bytes long
104104
0x69, 0x64, // id
105105
};
106+
107+
static final byte[] PUBLIC_KEY_TYPE = {
108+
0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79
109+
// p u b l i c - k e y
110+
};
106111
}

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

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,14 @@ private short checkIfPubKeyBlockSupported(APDU apdu, short readIdx, short lc) {
11521152
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
11531153
}
11541154
short typeValLen = (short) (bufferMem[readIdx] & 0x0F);
1155+
if (typeValLen != CannedCBOR.PUBLIC_KEY_TYPE.length) {
1156+
// not the same length as type "public-key": can't be a match
1157+
transientStorage.resetFoundKeyMatch();
1158+
} else if (Util.arrayCompare(bufferMem, (short)(readIdx + 1),
1159+
CannedCBOR.PUBLIC_KEY_TYPE, (short) 0, typeValLen) != 0) {
1160+
// Not of type "public-key", although same length
1161+
transientStorage.resetFoundKeyMatch();
1162+
}
11551163
readIdx += typeValLen + 1;
11561164
if (readIdx >= lc) {
11571165
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_INVALID_CBOR);
@@ -2507,7 +2515,22 @@ public void process(APDU apdu) throws ISOException {
25072515

25082516
byte[] apduBytes = apdu.getBuffer();
25092517

2510-
if (apduBytes[ISO7816.OFFSET_CLA] == 0x00 && apduBytes[ISO7816.OFFSET_INS] == (byte) 0xC0) {
2518+
byte cla = apduBytes[ISO7816.OFFSET_CLA];
2519+
byte ins = apduBytes[ISO7816.OFFSET_INS];
2520+
2521+
if (cla == (byte) 0x80 && ins == 0x12 && apduBytes[ISO7816.OFFSET_P1] == 0x01
2522+
&& apduBytes[ISO7816.OFFSET_P2] == 0x00) {
2523+
// Explicit disable command (NFCCTAP_CONTROL end CTAP_MSG). Turn off, and stay off.
2524+
transientStorage.disableAuthenticator();
2525+
}
2526+
2527+
if (transientStorage.authenticatorDisabled()) {
2528+
return;
2529+
}
2530+
2531+
if ((cla == 0x00 || cla == (byte) 0x80)
2532+
&& ins == (byte) 0xC0) {
2533+
// continue outgoing response from buffer
25112534
if (transientStorage.getOutgoingContinuationRemaining() == 0) {
25122535
throwException(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
25132536
}
@@ -2539,6 +2562,7 @@ public void process(APDU apdu) throws ISOException {
25392562
}
25402563

25412564
if (apdu.isCommandChainingCLA()) {
2565+
// Incoming chained request
25422566
short amtRead = apdu.setIncomingAndReceive();
25432567

25442568
short lc = apdu.getIncomingLength();
@@ -2552,7 +2576,7 @@ public void process(APDU apdu) throws ISOException {
25522576
return;
25532577
}
25542578

2555-
if (apduBytes[ISO7816.OFFSET_CLA] != (byte)0x80) {
2579+
if (apduBytes[ISO7816.OFFSET_CLA] != (byte) 0x80) {
25562580
throwException(ISO7816.SW_CLA_NOT_SUPPORTED);
25572581
}
25582582

@@ -2568,7 +2592,7 @@ public void process(APDU apdu) throws ISOException {
25682592
short lc = apdu.getIncomingLength();
25692593

25702594
if (amtRead == 0) {
2571-
throwException(ISO7816.SW_UNKNOWN);
2595+
throwException(ISO7816.SW_DATA_INVALID);
25722596
}
25732597

25742598
short incomingOffset = 1;
@@ -2726,7 +2750,7 @@ private void credManagementSubcommand(APDU apdu, short lc, short readIdx) {
27262750
short scratchAmt = (short)(1 + subCommandParamsLen);
27272751
short scratchOff = scratchAlloc(scratchAmt);
27282752
scratch[scratchOff] = bufferMem[subcommandIdx];
2729-
if (subCommandParamsLen > CREDENTIAL_ID_LEN + MAX_USER_ID_LENGTH + 30) {
2753+
if (subCommandParamsLen > CREDENTIAL_ID_LEN + MAX_USER_ID_LENGTH + 35) {
27302754
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_REQUEST_TOO_LARGE);
27312755
}
27322756
if (subCommandParamsLen > 0) {
@@ -2827,29 +2851,33 @@ private void handleUserUpdate(APDU apdu, short readOffset, short lc) {
28272851
// Don't need to decrypt creds, just byte-compare them
28282852
if (Util.arrayCompare(residentKeyData, (short)(i * CREDENTIAL_ID_LEN),
28292853
bufferMem, credIdIdx, CREDENTIAL_ID_LEN) == 0) {
2830-
// Credential matches - check user ID
2854+
// Matching cred.
2855+
// We need to extract the credential to check that our PIN token WOULD have permission
2856+
short scratchExtractedCred = scratchAlloc(CREDENTIAL_ID_LEN);
2857+
symmetricUnwrapper.doFinal(residentKeyData, (short)(i * CREDENTIAL_ID_LEN), CREDENTIAL_ID_LEN,
2858+
scratch, scratchExtractedCred
2859+
);
2860+
if (permissionsRpId[0] != 0x00) {
2861+
if (Util.arrayCompare(scratch, (short)(scratchExtractedCred + 32),
2862+
permissionsRpId, (short) 1, RP_HASH_LEN) != 0) {
2863+
// permissions RP ID in use, but doesn't match RP of this credential
2864+
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_AUTH_INVALID);
2865+
}
2866+
}
2867+
2868+
// Now that we have permission, check the user ID
28312869
symmetricUnwrapper.doFinal(residentKeyUserIds, (short)(i * MAX_USER_ID_LENGTH), MAX_USER_ID_LENGTH,
28322870
scratch, uidOffset);
28332871
if (Util.arrayCompare(scratch, uidOffset,
2834-
bufferMem,userIdIdx,userIdLen) == 0) {
2872+
bufferMem, userIdIdx, userIdLen) == 0) {
28352873
// Matches both credential and user ID - it's a hit.
2836-
// No actual updating work to do here because we don't store anything other than the ID...
2837-
2838-
// ... but we need to extract the credential to check that our PIN token WOULD have permission
2839-
short scratchExtractedCred = scratchAlloc(CREDENTIAL_ID_LEN);
2840-
symmetricUnwrapper.doFinal(residentKeyData, (short)(i * CREDENTIAL_ID_LEN), CREDENTIAL_ID_LEN,
2841-
scratch, scratchExtractedCred
2842-
);
2843-
if (permissionsRpId[0] != 0x00) {
2844-
if (Util.arrayCompare(scratch, (short)(scratchExtractedCred + 32),
2845-
permissionsRpId, (short) 1, RP_HASH_LEN) != 0) {
2846-
// permissions RP ID in use, but doesn't match RP of this credential
2847-
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_AUTH_INVALID);
2848-
}
2849-
}
2874+
// No actual updating work to do here because we don't store anything other than the ID
28502875

28512876
foundHit = true;
28522877
break;
2878+
} else {
2879+
// matches credential ID, but doesn't match user ID
2880+
sendErrorByte(apdu, FIDOConstants.CTAP1_ERR_INVALID_PARAMETER);
28532881
}
28542882
}
28552883
}
@@ -3505,7 +3533,7 @@ private void handleClientPinChange(APDU apdu, short readIdx, short lc, byte pinP
35053533
break;
35063534
}
35073535
}
3508-
if (realPinLength < 4) {
3536+
if (realPinLength < 4 || realPinLength > 63) {
35093537
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_POLICY_VIOLATION);
35103538
}
35113539

@@ -3789,7 +3817,7 @@ private void testAndReadyPIN(APDU apdu, byte[] buf, short off, byte pinProtocol,
37893817

37903818
// BAD PIN
37913819
forceInitKeyAgreementKey();
3792-
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_AUTH_INVALID);
3820+
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_INVALID);
37933821
}
37943822

37953823
/**

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

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,50 +17,50 @@ public class TransientStorage {
1717
/**
1818
* Used for storing found lengths in searches
1919
*/
20-
private static final short IDX_TEMP_BUF_IDX_LEN = 1;
20+
private static final byte IDX_TEMP_BUF_IDX_LEN = 1;
2121
/**
2222
* When writing an overlong response using chained APDUs, stores the position we're up to in the outgoing buffer
2323
*/
24-
private static final short IDX_CONTINUATION_OUTGOING_WRITE_OFFSET = 2;
24+
private static final byte IDX_CONTINUATION_OUTGOING_WRITE_OFFSET = 2;
2525
/**
2626
* When writing an overlong response using chained APDUs, stores the remaining bytes in the outgoing buffer
2727
*/
28-
private static final short IDX_CONTINUATION_OUTGOING_REMAINING = 3;
28+
private static final byte IDX_CONTINUATION_OUTGOING_REMAINING = 3;
2929
/**
3030
* When reading an overlong incoming request using chained APDUs, stores the fill level of the incoming buffer
3131
*/
32-
private static final short IDX_CHAINING_INCOMING_READ_OFFSET = 4;
32+
private static final byte IDX_CHAINING_INCOMING_READ_OFFSET = 4;
3333
/**
3434
* When reading incoming request chains, which FIDO2 command the request represents (pulled from the first packet)
3535
*/
36-
private static final short IDX_STORED_COMMAND_BYTE = 5;
36+
private static final byte IDX_STORED_COMMAND_BYTE = 5;
3737
/**
3838
* How full the scratch buffer is
3939
*/
40-
private static final short IDX_SCRATCH_ALLOC_SIZE = 6;
40+
private static final byte IDX_SCRATCH_ALLOC_SIZE = 6;
4141
/**
4242
* Index of next credential to consider when iterating through RPs with credManagement commands
4343
*/
44-
private static final short IDX_RP_ITERATION_POINTER = 7;
44+
private static final byte IDX_RP_ITERATION_POINTER = 7;
4545
/**
4646
* Index of next credential to consider when iterating through creds with credManagement commands
4747
*/
48-
private static final short IDX_CRED_ITERATION_POINTER = 8;
48+
private static final byte IDX_CRED_ITERATION_POINTER = 8;
4949
/**
5050
* Index of next credential (in either allowList or resident keys) to consider when iterating through
5151
* assertions with getNextAssertion commands
5252
*/
53-
private static final short IDX_ASSERT_ITERATION_POINTER = 9;
53+
private static final byte IDX_ASSERT_ITERATION_POINTER = 9;
5454
/**
5555
* Two vars for the price of one!
5656
* In the upper 8 bits, a permissions bitfield for the currently set PIN token
5757
* In the lower 8 bits, which PIN protocol is currently in use, as at the time the pinToken was sent to the platform
5858
*/
59-
private static final short IDX_PIN_PROTOCOL_IN_USE = 10;
59+
private static final byte IDX_PIN_PROTOCOL_IN_USE = 10;
6060
/**
6161
* Total number of in-memory short variables
6262
*/
63-
private static final short NUM_TEMP_SHORTS = 11;
63+
private static final byte NUM_TEMP_SHORTS = 11;
6464

6565
/**
6666
* array of per-reset booleans used internally
@@ -69,35 +69,39 @@ public class TransientStorage {
6969
/**
7070
* set when authenticator key initialized
7171
*/
72-
private static final short BOOL_IDX_RESET_PLATFORM_KEY_SET = 0;
72+
private static final byte BOOL_IDX_RESET_PLATFORM_KEY_SET = 0;
7373
/**
7474
* set if platform supports authenticator-compatible key
7575
*/
76-
private static final short BOOL_IDX_RESET_FOUND_KEY_MATCH = 1;
76+
private static final byte BOOL_IDX_RESET_FOUND_KEY_MATCH = 1;
7777
/**
7878
* set if the "up" (User Presence) option is enabled
7979
*/
80-
private static final short BOOL_IDX_OPTION_UP = 2;
80+
private static final byte BOOL_IDX_OPTION_UP = 2;
8181
/**
8282
* set if the "uv" (User Validation) option is enabled
8383
*/
84-
private static final short BOOL_IDX_OPTION_UV = 3;
84+
private static final byte BOOL_IDX_OPTION_UV = 3;
8585
/**
8686
* set if the "rk" (Resident Key) option is enabled
8787
*/
88-
private static final short BOOL_IDX_OPTION_RK = 4;
88+
private static final byte BOOL_IDX_OPTION_RK = 4;
8989
/**
9090
* set if chaining responses should come from the getNextAssertion buffer
9191
*/
92-
private static final short BOOL_IDX_RESPONSE_FROM_SCRATCH = 5;
92+
private static final byte BOOL_IDX_RESPONSE_FROM_SCRATCH = 5;
9393
/**
9494
* For reset "protection" feature, checks if a reset request has been received since the last authenticator powerup
9595
*/
96-
private static final short BOOL_IDX_RESET_RECEIVED_SINCE_POWERON = 6;
96+
private static final byte BOOL_IDX_RESET_RECEIVED_SINCE_POWERON = 6;
97+
/**
98+
* Set to true when the authenticator app is fully disabled until next reselect
99+
*/
100+
private static final byte BOOL_IDX_AUTHENTICATOR_DISABLED = 7;
97101
/**
98102
* number of booleans total in array
99103
*/
100-
private static final short NUM_RESET_BOOLS = 7;
104+
private static final byte NUM_RESET_BOOLS = 8;
101105

102106
/**
103107
* Pin-retries-since-reset counter, which must be cleared on RESET, not on deselect
@@ -117,7 +121,14 @@ public void fullyReset() {
117121
for (short s = 0; s < tempBools.length; s++) {
118122
tempBools[s] = false;
119123
}
124+
}
125+
126+
public boolean authenticatorDisabled() {
127+
return tempBools[BOOL_IDX_AUTHENTICATOR_DISABLED];
128+
}
120129

130+
public void disableAuthenticator() {
131+
tempBools[BOOL_IDX_AUTHENTICATOR_DISABLED] = true;
121132
}
122133

123134
public void clearIterationPointers() {

0 commit comments

Comments
 (0)