Skip to content

Commit b92ea6c

Browse files
committed
More standards compliance edge cases
1 parent dc92d86 commit b92ea6c

File tree

2 files changed

+89
-39
lines changed

2 files changed

+89
-39
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,13 @@ public abstract class CannedCBOR {
3838
0x50, // byte string, 16 bytes long
3939
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // aaguid
4040
0x04, // map key: options
41-
(byte) 0xA6, // map: six entries
41+
(byte) 0xA7, // map: seven entries
4242
0x62, // string: two bytes long
4343
0x72, 0x6B, // rk
4444
(byte) 0xF5, // true
45+
0x62, // string: two bytes long
46+
0x75, 0x70, // up
47+
(byte) 0xF5, // true
4548
0x68, // string - eight bytes long
4649
0x61, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x55, 0x76, // alwaysUv
4750
(byte) 0xF5, // true

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

Lines changed: 85 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,7 @@ private void makeCredential(APDU apdu, short lc, short readIdx) {
571571

572572
numExcludeListEntries = (short)(excludeListTypeVal - 0x80);
573573
excludeListStartIdx = (short)(readIdx + 2);
574+
readIdx++;
574575
} else if (bufferMem[readIdx] == 0x06) { // extensions
575576
if ((bufferMem[++readIdx] & 0xA0) != 0xA0) {
576577
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
@@ -694,7 +695,7 @@ private void makeCredential(APDU apdu, short lc, short readIdx) {
694695
continue;
695696
}
696697

697-
if (checkCredential(bufferMem, credIdIdx, credIdLen,
698+
if (checkCredential(bufferMem, credIdIdx, CREDENTIAL_ID_LEN,
698699
scratch, scratchRPIDHashOffset,
699700
scratch, excludeListCredSpace)) {
700701
// This cred decodes valid and is for the given RPID - fail early.
@@ -1620,6 +1621,13 @@ private void getAssertion(APDU apdu, short lc, short readIdx, short firstCredIdx
16201621
continue;
16211622
}
16221623

1624+
if (CTAP_2_1_GETASSERTION_WITH_ALLOW_LIST && startOfMatchingPubKeyCredData != -1) {
1625+
// In CTAP2.1, we are done, as we're supposed to treat the match we already made as the only match
1626+
// we're just continuing iteration to throw exceptions when we encounter invalid allowList entries
1627+
// AFTER the valid one
1628+
continue;
1629+
}
1630+
16231631
if (startOfMatchingPubKeyCredData == (short) -1 || firstCredIdx == 0) {
16241632
// We need to check all the creds in the list if we're starting iteration...
16251633
// ... in order to count the number of matches (for CTAP2.0 only!)
@@ -1632,11 +1640,6 @@ private void getAssertion(APDU apdu, short lc, short readIdx, short firstCredIdx
16321640
startOfMatchingPubKeyCredData = beforeReadIdx;
16331641
matchingPubKeyCredDataLen = (short) (blockReadIdx - startOfMatchingPubKeyCredData);
16341642
loadPrivateScratchIntoAttester();
1635-
1636-
if (CTAP_2_1_GETASSERTION_WITH_ALLOW_LIST) {
1637-
// In CTAP2.1, we are done, as we're supposed to treat this match as the only match
1638-
break;
1639-
}
16401643
}
16411644
}
16421645
}
@@ -2265,10 +2268,10 @@ private void setupChainedResponse(short offset, short remaining) {
22652268
transientStorage.setOutgoingContinuation(offset, remaining);
22662269
if (remaining >= 256) {
22672270
// at least ANOTHER full chunk remains
2268-
throwException(ISO7816.SW_BYTES_REMAINING_00);
2271+
throwException(ISO7816.SW_BYTES_REMAINING_00, false);
22692272
} else {
22702273
// exactly one more chunk remains
2271-
throwException((short) (ISO7816.SW_BYTES_REMAINING_00 + remaining));
2274+
throwException((short) (ISO7816.SW_BYTES_REMAINING_00 + remaining), false);
22722275
}
22732276
}
22742277

@@ -2417,14 +2420,14 @@ private short consumeMapAndGetID(APDU apdu, short readIdx, short lc, boolean byt
24172420
boolean foundType = false;
24182421
boolean correctType = false;
24192422
transientStorage.readyStoredVars();
2420-
short mapDef = bufferMem[readIdx++];
2423+
short mapDef = ub(bufferMem[readIdx++]);
24212424
if (readIdx >= lc) {
24222425
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_INVALID_CBOR);
24232426
}
24242427
short mapEntryCount = 0;
24252428
if ((mapDef & 0xF0) == 0xA0) {
24262429
mapEntryCount = (short) (mapDef & 0x0F);
2427-
} else if ((mapDef & 0xF0) == 0xB0) {
2430+
} else if ((mapDef & 0xF0) == 0xB0 && mapDef < ub((byte) 0xB8)) {
24282431
mapEntryCount = (short) ((mapDef & 0x0F) + 16);
24292432
} else if (mapDef == (byte) 0xB8) {
24302433
mapEntryCount = ub(bufferMem[readIdx++]);
@@ -2436,7 +2439,7 @@ private short consumeMapAndGetID(APDU apdu, short readIdx, short lc, boolean byt
24362439
}
24372440

24382441
for (short i = 0; i < mapEntryCount; i++) {
2439-
short keyDef = bufferMem[readIdx++];
2442+
short keyDef = ub(bufferMem[readIdx++]);
24402443
short keyLen = 0;
24412444
if (readIdx >= lc) {
24422445
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_INVALID_CBOR);
@@ -2447,7 +2450,7 @@ private short consumeMapAndGetID(APDU apdu, short readIdx, short lc, boolean byt
24472450
if (readIdx >= lc) {
24482451
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_INVALID_CBOR);
24492452
}
2450-
} else if (keyDef >= 0x61 && keyDef < 0x78) {
2453+
} else if (keyDef >= 0x60 && keyDef < 0x78) {
24512454
keyLen = (short)(keyDef - 0x60);
24522455
} else {
24532456
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
@@ -2484,7 +2487,9 @@ private short consumeMapAndGetID(APDU apdu, short readIdx, short lc, boolean byt
24842487
}
24852488
if (isType && valDef == 0x58) {
24862489
// heh, literally "unexpected type" here
2487-
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
2490+
// don't throw an exception, since the spec allows all sorts of "types", we just ignore them
2491+
foundType = true;
2492+
correctType = false;
24882493
}
24892494
valLen = ub(bufferMem[readIdx++]);
24902495
if (isId) {
@@ -2493,15 +2498,20 @@ private short consumeMapAndGetID(APDU apdu, short readIdx, short lc, boolean byt
24932498
if (readIdx >= lc) {
24942499
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_INVALID_CBOR);
24952500
}
2496-
} else if (valDef >= 0x61 && valDef < 0x78) {
2501+
} else if (valDef >= 0x60 && valDef < 0x78) {
24972502
if (isId && byteString) {
24982503
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
24992504
}
25002505
valLen = (short) (valDef - 0x60);
2501-
} else if (valDef >= 0x41 && valDef < 0x58) {
2502-
if ((isId && !byteString) || isType) {
2506+
} else if (valDef >= 0x40 && valDef < 0x58) {
2507+
if (isId && !byteString) {
25032508
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
25042509
}
2510+
if (isType) {
2511+
// byte strings aren't valid for public-key types...
2512+
foundType = true;
2513+
correctType = false;
2514+
}
25052515
valLen = (short) (valDef - 0x40);
25062516
} else {
25072517
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
@@ -2512,11 +2522,11 @@ private short consumeMapAndGetID(APDU apdu, short readIdx, short lc, boolean byt
25122522
transientStorage.setStoredVars(idPos, valLen);
25132523
}
25142524

2515-
if (isType && checkTypePublicKey) {
2525+
if (!foundType && isType && checkTypePublicKey) {
25162526
foundType = true;
25172527
correctType = valLen == (short) CannedCBOR.PUBLIC_KEY_TYPE.length
2518-
|| Util.arrayCompare(bufferMem, readIdx,
2519-
CannedCBOR.PUBLIC_KEY_TYPE, (short) 0, (short) CannedCBOR.PUBLIC_KEY_TYPE.length) == 0;
2528+
&& Util.arrayCompare(bufferMem, readIdx,
2529+
CannedCBOR.PUBLIC_KEY_TYPE, (short) 0, valLen) == 0;
25202530
}
25212531

25222532
readIdx += valLen;
@@ -2529,16 +2539,19 @@ private short consumeMapAndGetID(APDU apdu, short readIdx, short lc, boolean byt
25292539
sendErrorByte(apdu, FIDOConstants.CTAP1_ERR_INVALID_PARAMETER);
25302540
}
25312541

2532-
if (checkTypePublicKey && !foundType) {
2533-
// entirely missing "type" field
2534-
sendErrorByte(apdu, FIDOConstants.CTAP1_ERR_INVALID_PARAMETER);
2535-
}
2542+
if (checkTypePublicKey) {
2543+
if (!foundType) {
2544+
// entirely missing "type" field
2545+
sendErrorByte(apdu, FIDOConstants.CTAP1_ERR_INVALID_PARAMETER);
2546+
}
25362547

2537-
if (checkTypePublicKey && foundType && !correctType) {
2538-
// We found an ID entry, but it's not of type "public-key" - treat it as if we found nothing
2539-
transientStorage.readyStoredVars();
2548+
if (!correctType) {
2549+
// We found an ID entry, but it's not of type "public-key" - treat it as if we found nothing
2550+
transientStorage.readyStoredVars();
2551+
}
25402552
}
25412553

2554+
25422555
return readIdx;
25432556
}
25442557

@@ -3007,7 +3020,7 @@ private void handleDeleteCred(APDU apdu, short readOffset, short lc) {
30073020
// we want ANOTHER RK, not this one...
30083021
continue;
30093022
}
3010-
if (!residentKeyValidity[i]) {
3023+
if (!residentKeyValidity[otherRKIdx]) {
30113024
// deleted keys need not apply
30123025
continue;
30133026
}
@@ -3503,7 +3516,12 @@ private void sendAuthInfo(APDU apdu) {
35033516
* @param swCode Two-byte status code - may be SW_NO_ERROR if desired
35043517
*/
35053518
private void throwException(short swCode) {
3506-
transientStorage.clearIterationPointers();
3519+
throwException(swCode, true);
3520+
}
3521+
private void throwException(short swCode, boolean clearIteration) {
3522+
if (clearIteration) {
3523+
transientStorage.clearIterationPointers();
3524+
}
35073525
scratchRelease();
35083526
ecPrivateKey.clearKey();
35093527
ecKeyPair.getPrivate().clearKey();
@@ -3586,12 +3604,16 @@ private void handleClientPinChange(APDU apdu, short readIdx, short lc, byte pinP
35863604
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_NOT_SET);
35873605
}
35883606

3589-
short scratchOff = scratchAlloc((short) (PIN_PAD_LENGTH + 16)); // 16 bytes for PIN partial hash
3607+
// 32 bytes for PIN hash (for PIN protocol 2) and 16 for IV on padded PIN
3608+
short scratchOff = scratchAlloc((short) (PIN_PAD_LENGTH + 48));
35903609

35913610
readIdx = handlePinSetPreamble(apdu, readIdx, lc, scratch, scratchOff, true, pinProtocol);
35923611

35933612
short wrappedPinLocation = readIdx;
35943613
readIdx += PIN_PAD_LENGTH;
3614+
if (pinProtocol == 2) {
3615+
readIdx += 16; // IV for PIN pad
3616+
}
35953617

35963618
if (bufferMem[readIdx++] != 0x06) { // pinHashEnc
35973619
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_MISSING_PARAMETER);
@@ -3641,6 +3663,12 @@ private void handleClientPinGetToken(APDU apdu, short readIdx, short lc, byte pi
36413663
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_NOT_SET);
36423664
}
36433665

3666+
if (transientStorage.getPinTriesSinceReset() == PIN_TRIES_PER_RESET) {
3667+
// Proceed no further: PIN is blocked until authenticator is powered off and on again
3668+
// NO TOKEN FOR YOU. NEXT!
3669+
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_AUTH_BLOCKED);
3670+
}
3671+
36443672
if (bufferMem[readIdx++] != 0x03) { // map key: keyAgreement
36453673
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_MISSING_PARAMETER);
36463674
}
@@ -3897,6 +3925,10 @@ private void testAndReadyPIN(APDU apdu, byte[] buf, short off, byte pinProtocol,
38973925

38983926
// BAD PIN
38993927
forceInitKeyAgreementKey();
3928+
if (transientStorage.getPinTriesSinceReset() == PIN_TRIES_PER_RESET) {
3929+
// You've gone and done it now. Need to power the authenticator off and on again to try again.
3930+
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_AUTH_BLOCKED);
3931+
}
39003932
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_INVALID);
39013933
}
39023934

@@ -3980,15 +4012,30 @@ private short handlePinSetPreamble(APDU apdu, short readIdx, short lc, byte[] ou
39804012
if (bufferMem[readAheadIdx++] != 0x06) { // pinHashEnc
39814013
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_REQUIRED);
39824014
}
3983-
if (bufferMem[readAheadIdx++] != 0x50) { // byte array, 16 bytes long
3984-
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
3985-
}
3986-
Util.arrayCopyNonAtomic(bufferMem, readAheadIdx,
3987-
outBuf, (short)(outOffset + bLen), (short) 16);
4015+
if (pinProtocol == 1) {
4016+
if (bufferMem[readAheadIdx++] != 0x50) { // byte array, 16 bytes long
4017+
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
4018+
}
4019+
Util.arrayCopyNonAtomic(bufferMem, readAheadIdx,
4020+
outBuf, (short)(outOffset + bLen), (short) 16);
39884021

3989-
hmacSha256(apdu, sharedSecretVerifyKey, (short) 0,
3990-
outBuf, outOffset, (short)(bLen + 16),
3991-
outBuf, outOffset, false);
4022+
hmacSha256(apdu, sharedSecretVerifyKey, (short) 0,
4023+
outBuf, outOffset, (short)(bLen + 16),
4024+
outBuf, outOffset, false);
4025+
} else {
4026+
if (bufferMem[readAheadIdx++] != 0x58) { // byte string, one-byte length
4027+
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
4028+
}
4029+
if (bufferMem[readAheadIdx++] != 0x20) { // 32: sixteen bytes of IV and 16 bytes of hash
4030+
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
4031+
}
4032+
Util.arrayCopyNonAtomic(bufferMem, readAheadIdx,
4033+
outBuf, (short)(outOffset + bLen), (short) 32);
4034+
4035+
hmacSha256(apdu, sharedSecretVerifyKey, (short) 0,
4036+
outBuf, outOffset, (short)(bLen + 32),
4037+
outBuf, outOffset, false);
4038+
}
39924039
} else {
39934040
hmacSha256(apdu, sharedSecretVerifyKey, (short) 0,
39944041
bufferMem, readIdx, bLen,
@@ -4032,7 +4079,7 @@ private void handleClientPinInitialSet(APDU apdu, short readIdx, short lc, byte
40324079
break;
40334080
}
40344081
}
4035-
if (realPinLength < 4) {
4082+
if (realPinLength < 4 || realPinLength > 63) {
40364083
sendErrorByte(apdu, FIDOConstants.CTAP2_ERR_PIN_POLICY_VIOLATION);
40374084
}
40384085

0 commit comments

Comments
 (0)