Commit 0bd67b09 authored by Josh Ji's avatar Josh Ji

code review

parent 20eb478d
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -31,58 +31,66 @@ import static com.josh.vku2f.ClientPINSubCommand.*;
public class CTAP2 extends Applet implements ExtendedLength {
private final CBORDecoder cborDecoder;
private final CBOREncoder cborEncoder;
// transient memory, clear on deselect
private byte[] dataBuffer;
private byte[] scratch;
private byte[] info;
private final short[] tempVars;
private final short[] chainRam;
private final short[] outChainRam;
private final boolean[] isChaining;
private final boolean[] isOutChaining;
//transient memory, clear on reset
private final short[] nextAssertion;
private byte[] fidoInfo;
private final CBORDecoder cborDecoder;
private final CBOREncoder cborEncoder;
private final MessageDigest sha256MessageDigest;
private final AttestationKeyPair attestationKeyPair;
private boolean personalizeComplete;
private CredentialArray credentialArray;
private AuthenticatorMakeCredential authenticatorMakeCredential;
private AuthenticatorGetAssertion authenticatorGetAssertion;
private final ClientPINCommand clientPINCommand;
private StoredCredential tempCredential;
private StoredCredential[] assertionCredentials;
private byte MAX_PIN_RETRIES = (byte) 0x08;
private byte MAX_UV_RETRIES = (byte) 0x08;
private final ClientPINCommand clientPINCommand;
private final byte MAX_PIN_RETRIES = (byte) 0x08;
private final byte MAX_UV_RETRIES = (byte) 0x08;
private byte pinRetries;
private byte uvRetries;
// private final KeyPair ecDhKeyPair;
// private final boolean[] ecDhSet;
private PinUvAuthProtocolOne pinUvAuthProtocolOne;
private final boolean[] isChaining;
private final boolean[] isOutChaining;
private boolean personalizeComplete;
private StoredCredential tempCredential;
private StoredCredential[] assertionCredentials;
public static final byte ISO_INS_GET_DATA = (byte) 0xC0;
public static final byte FIDO2_INS_NFCCTAP_MSG = (byte) 0x10;
// INS
public static final byte ISO_INS_GET_DATA = (byte) 0xC0;
public static final byte FIDO2_INS_NFCCTAP_MSG = (byte) 0x10;
public static final byte FIDO2_INS_DESELECT = (byte) 0x12;
// FIDO Command
public static final byte FIDO2_AUTHENTICATOR_MAKE_CREDENTIAL = (byte) 0x01;
public static final byte FIDO2_AUTHENTICATOR_GET_ASSERTION = (byte) 0x02;
public static final byte FIDO2_AUTHENTICATOR_GET_NEXT_ASSERTION = (byte) 0x08;
public static final byte FIDO2_AUTHENTICATOR_GET_INFO = (byte) 0x04;
public static final byte FIDO2_AUTHENTICATOR_CLIENT_PIN = (byte) 0x06;
public static final byte FIDO2_AUTHENTICATOR_RESET = (byte) 0x07;
// Vendor specific - for attestation cert loading.
public static final byte FIDO2_VENDOR_ATTEST_SIGN = (byte) 0x41;
public static final byte FIDO2_VENDOR_ATTEST_LOADCERT = (byte) 0x42;
public static final byte FIDO2_VENDOR_PERSO_COMPLETE = (byte) 0x43;
public static final byte FIDO2_VENDOR_ATTEST_GETPUB = (byte) 0x44;
public static final byte FIDO2_VENDOR_GET_COUNT = (byte) 0x45;
public static final byte FIDO2_VENDOR_GET_CREDENTIAL_COUNT = (byte) 0x45;
public static final byte FIDO2_VENDOR_ATTEST_GETCERT = (byte) 0x4A;
public static final byte FIDO2_DESELECT = 0x12;
//PRLAB
public static final byte PRLAB_GET_PUKX_RX = (byte) 0x50;
public static final byte PRLAB_GET_CX = (byte) 0x51;
// AAGUID - Authenticator Attestation Global Unique Identifier
// this uniquely identifies the type of authenticator we have built.
......@@ -129,6 +137,134 @@ public class CTAP2 extends Applet implements ExtendedLength {
}
public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException {
short offset = bOffset;
offset += (short) (bArray[offset] + 1); // instance
offset += (short) (bArray[offset] + 1); // privileges
final CTAP2 applet = new CTAP2();
try {
applet.register(bArray, (short) (bOffset + 1), bArray[bOffset]);
} catch (Exception e) {
applet.register();
}
}
// main entry point
public void process(APDU apdu) throws ISOException {
byte[] buffer = apdu.getBuffer();
// return FIDO2 String when selecting
if (selectingApplet()) {
Util.arrayCopyNonAtomic(Utf8Strings.UTF8_FIDO2, (short) 0, buffer, (short) 0,
(short) Utf8Strings.UTF8_FIDO2.length);
apdu.setOutgoingAndSend((short) 0, (short) Utf8Strings.UTF8_FIDO2.length);
return;
}
// Check CLA
if (!apdu.isCommandChainingCLA() && apdu.isISOInterindustryCLA()) {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
JCSystem.requestObjectDeletion();
switch (buffer[ISO7816.OFFSET_INS]) {
case ISO_INS_GET_DATA: // 0xC0
if (isOutChaining[0]) {
getData(apdu);
} else {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
break;
case FIDO2_INS_NFCCTAP_MSG: // 0x10
handle(apdu);
break;
case FIDO2_INS_DESELECT: // 0x12
// Appears to be a reset function in the FIDO2 spec, but never referenced
// anywhere
ISOException.throwIt(ISO7816.SW_NO_ERROR);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
/**
* Handle the command chaining or extended APDU logic.
*
* Due to the FIDO2 spec requiring support for both extended APDUs and command
* chaining, we need to implement chaining here.
*
* I didn't want to pollute the logic over in the process function, and it makes
* sense to do both here.
*
* @param apdu apdu buffer
* @return length of data to be processed. 0 if command chaining is not finished.
*/
private short doApduIngestion(APDU apdu) {
byte[] buffer = apdu.getBuffer();
// Receive the APDU
tempVars[4] = apdu.setIncomingAndReceive();
// Get true incoming data length
tempVars[3] = apdu.getIncomingLength();
// Check if the APDU is too big, we only handle 1200 byte
if (tempVars[3] > 1200) {
returnError(apdu, CTAP2_ERR_REQUEST_TOO_LARGE);
return 0;
}
// Check what we need to do re APDU buffer, is it full (special case for 1 len)
// If this is a command chaining APDU, swap to that logic
if (isCommandChainingCLA(apdu)) {
// In the chaining
if (!isChaining[0]) {
// Must be first chaining APDU
isChaining[0] = true;
// Prep the variables
chainRam[0] = 0;
}
// Copy buffer
chainRam[1] = tempVars[4];
// chainRam[0] is the current point in the buffer we start from
chainRam[0] = Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, chainRam[0], chainRam[1]);
return 0x00;
} else if (isChaining[0]) {
// Must be the last of the chaining - make the copy and return the length.
chainRam[1] = tempVars[4];
chainRam[0] = Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, chainRam[0], chainRam[1]);
isChaining[0] = false;
isChaining[1] = true;
return chainRam[0];
} else if (tempVars[3] == 0x01) {
dataBuffer[0] = buffer[apdu.getOffsetCdata()];
return 0x01;
} else if (apdu.getCurrentState() == APDU.STATE_FULL_INCOMING) {
// We need to do no more
// Read the entirety of the buffer into the inBuf
Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, (short) 0, tempVars[3]);
return tempVars[4];
} else {
// The APDU needs a multi-stage copy
// First, copy the current data buffer in
// Get the number of bytes in the data buffer that are the Lc, vars[5] will do
tempVars[5] = tempVars[4];
// Make the copy, vars[3] is bytes remaining to get
tempVars[4] = 0;
while (tempVars[3] > 0) {
// Copy data
tempVars[4] = Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, tempVars[4], tempVars[5]);
// Decrement vars[3] by the bytes copied
tempVars[3] -= tempVars[5];
// Pull more bytes
tempVars[5] = apdu.receiveBytes(apdu.getOffsetCdata());
}
// Now we're at the end, here, and the commands expect us to give them a data
// length. Turns out Le bytes aren't anywhere to be found here.
// The commands use vars[3], so vars[4] will be fine to copy to vars[3].
return tempVars[4];
}
}
public void handle(APDU apdu) {
byte[] buffer = apdu.getBuffer();
tempCredential = null;
......@@ -142,42 +278,48 @@ public class CTAP2 extends Applet implements ExtendedLength {
}
// Need to grab the CTAP command byte
switch (dataBuffer[0]) {
case FIDO2_AUTHENTICATOR_MAKE_CREDENTIAL:
case FIDO2_AUTHENTICATOR_MAKE_CREDENTIAL: //0x01
authMakeCredential(apdu, tempVars[3]);
break;
case FIDO2_AUTHENTICATOR_GET_ASSERTION:
case FIDO2_AUTHENTICATOR_GET_ASSERTION: // 0x02
authGetAssertion(apdu, tempVars[3]);
break;
case FIDO2_AUTHENTICATOR_GET_INFO:
case FIDO2_AUTHENTICATOR_GET_INFO: // x0x04
authGetInfo(apdu);
break;
case FIDO2_AUTHENTICATOR_GET_NEXT_ASSERTION:
case FIDO2_AUTHENTICATOR_CLIENT_PIN: // 0x06
clientPin(apdu, tempVars[3]);
break;
case FIDO2_AUTHENTICATOR_RESET: //0x07
// Need to finish doing this, we can, I mean, but I don't like it
doReset(apdu);
break;
case FIDO2_AUTHENTICATOR_GET_NEXT_ASSERTION: // 0x08
authGetNextAssertion(apdu, buffer);
break;
case FIDO2_VENDOR_ATTEST_SIGN: //0x41
attestSignRaw(apdu, tempVars[3]);
break;
case FIDO2_AUTHENTICATOR_CLIENT_PIN:
clientPin(apdu, tempVars[3]);
break;
case FIDO2_VENDOR_ATTEST_LOADCERT: //0x42
attestSetCert(apdu, tempVars[3]);
break;
case FIDO2_VENDOR_PERSO_COMPLETE: //0x43
persoComplete(apdu);
personalizationComplete(apdu);
break;
case FIDO2_VENDOR_ATTEST_GETPUB: //0x44
getAttestPublic(apdu);
break;
case FIDO2_VENDOR_ATTEST_GETCERT: //0x4a
getCert(apdu);
case FIDO2_VENDOR_GET_CREDENTIAL_COUNT: //0x45
getCredentialCount(apdu);
break;
case FIDO2_VENDOR_GET_COUNT: //0x45
getCount(apdu);
case PRLAB_GET_PUKX_RX: // 0x50
getPuKxRx(apdu);
break;
case FIDO2_AUTHENTICATOR_RESET: //0x07
// Need to finish doing this, we can, I mean, but I don't like it
doReset(apdu);
case PRLAB_GET_CX: // 0x51
getCx(apdu);
break;
case FIDO2_VENDOR_ATTEST_GETCERT: //0x4a
getCert(apdu);
break;
default:
returnError(apdu, CTAP1_ERR_INVALID_COMMAND);
......@@ -185,7 +327,7 @@ public class CTAP2 extends Applet implements ExtendedLength {
}
public void persoComplete(APDU apdu) {
public void personalizationComplete(APDU apdu) {
if (attestationKeyPair.isCertSet() && !personalizeComplete) {
personalizeComplete = true;
returnError(apdu, CTAP1_ERR_SUCCESS);
......@@ -212,12 +354,23 @@ public class CTAP2 extends Applet implements ExtendedLength {
}
/** get counter's value */
public void getCount(APDU apdu){
short count = credentialArray.getCount();
apdu.setOutgoing();
apdu.setOutgoingLength((short)2);
Util.setShort(dataBuffer,(short)0, count);
apdu.sendBytesLong(dataBuffer,(short)0,(short)2);
public void getCredentialCount(APDU apdu){
Util.setShort(apdu.getBuffer(), (short)0x00, credentialArray.getCount());
apdu.setOutgoingAndSend((short)0x00, (short)2);
}
/**
*
*/
private void getPuKxRx(APDU apdu){
}
/**
*
*/
private void getCx(APDU apdu){
}
/**
......@@ -646,7 +799,7 @@ public class CTAP2 extends Applet implements ExtendedLength {
*/
public void authGetInfo(APDU apdu) {
// Create the authenticator info if not present.
if (info == null) {
if (fidoInfo == null) {
// Create the authGetInfo - 0x00 is success
dataBuffer[0] = 0x00;
cborEncoder.init(dataBuffer, (short) 1, (short) 1199);
......@@ -678,13 +831,13 @@ public class CTAP2 extends Applet implements ExtendedLength {
cborEncoder.encodeUInt16((short) 1200);
// Done
JCSystem.beginTransaction();
info = new byte[cborEncoder.getCurrentOffset()];
Util.arrayCopy(dataBuffer, (short) 0, info, (short) 0, cborEncoder.getCurrentOffset());
fidoInfo = new byte[cborEncoder.getCurrentOffset()];
Util.arrayCopy(dataBuffer, (short) 0, fidoInfo, (short) 0, cborEncoder.getCurrentOffset());
JCSystem.commitTransaction();
}
// Send it
Util.arrayCopyNonAtomic(info, (short) 0, dataBuffer, (short) 0, (short) info.length);
sendLongChaining(apdu, (short) info.length);
Util.arrayCopyNonAtomic(fidoInfo, (short) 0, dataBuffer, (short) 0, (short) fidoInfo.length);
sendLongChaining(apdu, (short) fidoInfo.length);
}
/**
......@@ -782,88 +935,12 @@ public class CTAP2 extends Applet implements ExtendedLength {
}
// There's only so many ways to do this.
static boolean isCommandChainingCLA(APDU apdu) {
public static boolean isCommandChainingCLA(APDU apdu) {
byte[] buf = apdu.getBuffer();
// return true if bit4 is 1 in CLA
return ((byte) (buf[0] & (byte) 0x10) == (byte) 0x10);
}
/**
* Handle the command chaining or extended APDU logic.
*
* Due to the FIDO2 spec requiring support for both extended APDUs and command
* chaining, we need to implement chaining here.
*
* I didn't want to pollute the logic over in the process function, and it makes
* sense to do both here.
*
* @param apdu apdu buffer
* @return length of data to be processed. 0 if command chaining is not finished.
*/
private short doApduIngestion(APDU apdu) {
byte[] buffer = apdu.getBuffer();
// Receive the APDU
tempVars[4] = apdu.setIncomingAndReceive();
// Get true incoming data length
tempVars[3] = apdu.getIncomingLength();
// Check if the APDU is too big, we only handle 1200 byte
if (tempVars[3] > 1200) {
returnError(apdu, CTAP2_ERR_REQUEST_TOO_LARGE);
return 0;
}
// Check what we need to do re APDU buffer, is it full (special case for 1 len)
// If this is a command chaining APDU, swap to that logic
if (isCommandChainingCLA(apdu)) {
// In the chaining
if (!isChaining[0]) {
// Must be first chaining APDU
isChaining[0] = true;
// Prep the variables
chainRam[0] = 0;
}
// Copy buffer
chainRam[1] = tempVars[4];
// chainRam[0] is the current point in the buffer we start from
chainRam[0] = Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, chainRam[0], chainRam[1]);
return 0x00;
} else if (isChaining[0]) {
// Must be the last of the chaining - make the copy and return the length.
chainRam[1] = tempVars[4];
chainRam[0] = Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, chainRam[0], chainRam[1]);
isChaining[0] = false;
isChaining[1] = true;
return chainRam[0];
} else if (tempVars[3] == 0x01) {
dataBuffer[0] = buffer[apdu.getOffsetCdata()];
return 0x01;
} else if (apdu.getCurrentState() == APDU.STATE_FULL_INCOMING) {
// We need to do no more
// Read the entirety of the buffer into the inBuf
Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, (short) 0, tempVars[3]);
return tempVars[4];
} else {
// The APDU needs a multi-stage copy
// First, copy the current data buffer in
// Get the number of bytes in the data buffer that are the Lc, vars[5] will do
tempVars[5] = tempVars[4];
// Make the copy, vars[3] is bytes remaining to get
tempVars[4] = 0;
while (tempVars[3] > 0) {
// Copy data
tempVars[4] = Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, tempVars[4], tempVars[5]);
// Decrement vars[3] by the bytes copied
tempVars[3] -= tempVars[5];
// Pull more bytes
tempVars[5] = apdu.receiveBytes(apdu.getOffsetCdata());
}
// Now we're at the end, here, and the commands expect us to give them a data
// length. Turns out Le bytes aren't anywhere to be found here.
// The commands use vars[3], so vars[4] will be fine to copy to vars[3].
return tempVars[4];
}
}
/**
* Gets 256 or fewer bytes from inBuf.
*
......@@ -932,68 +1009,10 @@ public class CTAP2 extends Applet implements ExtendedLength {
}
}
/**
* Checks if chaining is set for U2FApplet
*
* @return if it is chaining
*/
public boolean isChaining() {
return isOutChaining[0];
}
private void getCert(APDU apdu) {
dataBuffer[0] = 0x00;
tempVars[0] = (short) (attestationKeyPair.getCert(dataBuffer, (short) 1) + 1);
sendLongChaining(apdu, tempVars[0]);
}
public void process(APDU apdu) throws ISOException {
byte[] buffer = apdu.getBuffer();
if (selectingApplet()) {
Util.arrayCopyNonAtomic(Utf8Strings.UTF8_FIDO2, (short) 0, buffer, (short) 0,
(short) Utf8Strings.UTF8_FIDO2.length);
apdu.setOutgoingAndSend((short) 0, (short) Utf8Strings.UTF8_FIDO2.length);
return;
}
if (!apdu.isCommandChainingCLA() && apdu.isISOInterindustryCLA()) {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
JCSystem.requestObjectDeletion();
switch (buffer[ISO7816.OFFSET_INS]) {
case ISO_INS_GET_DATA:
if (isChaining()) {
getData(apdu);
} else {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
break;
case FIDO2_INS_NFCCTAP_MSG: // 0x10
handle(apdu);
break;
case FIDO2_DESELECT:
// Appears to be a reset function in the FIDO2 spec, but never referenced
// anywhere
ISOException.throwIt(ISO7816.SW_NO_ERROR);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException {
short offset = bOffset;
offset += (short) (bArray[offset] + 1); // instance
offset += (short) (bArray[offset] + 1); // privileges
final CTAP2 applet = new CTAP2();
try {
applet.register(bArray, (short) (bOffset + 1), bArray[bOffset]);
} catch (Exception e) {
applet.register();
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment