Commit 57224772 authored by Josh Ji's avatar Josh Ji

code review, change version string, change aaguid, remove redundant flag in...

code review, change version string, change aaguid, remove redundant flag in the response of info command, rename project
parent c9d0b00a
#Mon Dec 06 04:06:59 CST 2021
gradle.version=7.1
newJavaCardApplet
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" /> <bytecodeTargetLevel target="11" />
</component> </component>
</project> </project>
\ No newline at end of file
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<component name="FrameworkDetectionExcludesConfiguration"> <component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" /> <file type="web" url="file://$PROJECT_DIR$" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
rootProject.name = 'newJavaCardApplet' rootProject.name = 'Fido2Applet'
/* /*
** **
** Copyright 2021, VivoKey Technologies ** Copyright 2021, VivoKey Technologies
** **
** Licensed under the Apache License, Version 2.0 (the "License"); ** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License. ** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at ** You may obtain a copy of the License at
** **
** http://www.apache.org/licenses/LICENSE-2.0 ** http://www.apache.org/licenses/LICENSE-2.0
** **
** Unless required by applicable law or agreed to in writing, software ** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS, ** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and ** See the License for the specific language governing permissions and
** limitations under the License. ** limitations under the License.
*/ */
package com.josh.vku2f; package com.josh.vku2f;
...@@ -32,1228 +32,1227 @@ import static com.josh.vku2f.ClientPINSubCommand.*; ...@@ -32,1228 +32,1227 @@ import static com.josh.vku2f.ClientPINSubCommand.*;
public class CTAP2 extends Applet implements ExtendedLength { public class CTAP2 extends Applet implements ExtendedLength {
// transient memory, clear on deselect // transient memory, clear on deselect
private byte[] dataBuffer; private byte[] dataBuffer;
private byte[] scratch; private byte[] scratch;
private final short[] tempVars; private final short[] tempVars;
private final short[] chainRam; private final short[] chainRam;
private final short[] outChainRam; private final short[] outChainRam;
private final boolean[] isChaining; private final boolean[] isChaining;
private final boolean[] isOutChaining; private final boolean[] isOutChaining;
//transient memory, clear on reset(power off) //transient memory, clear on reset(power off)
private final short[] nextAssertion; private final short[] nextAssertion;
private byte[] fidoInfo; private byte[] fidoInfo;
private final CBORDecoder cborDecoder; private final CBORDecoder cborDecoder;
private final CBOREncoder cborEncoder; private final CBOREncoder cborEncoder;
private final MessageDigest sha256MessageDigest; private final MessageDigest sha256MessageDigest;
private final AttestationKeyPair attestationKeyPair; private final AttestationKeyPair attestationKeyPair;
private boolean personalizeComplete; private boolean personalizeComplete;
private CredentialArray credentialArray; private CredentialArray credentialArray;
private AuthenticatorMakeCredential authenticatorMakeCredential; private AuthenticatorMakeCredential authenticatorMakeCredential;
private AuthenticatorGetAssertion authenticatorGetAssertion; private AuthenticatorGetAssertion authenticatorGetAssertion;
private StoredCredential tempCredential; private StoredCredential tempCredential;
private StoredCredential[] assertionCredentials; private StoredCredential[] assertionCredentials;
private final ClientPINCommand clientPINCommand; private final ClientPINCommand clientPINCommand;
private boolean isClientPinSet = false; private boolean isClientPinSet = false;
private final byte MAX_PIN_RETRIES = (byte) 0x08; private final byte MAX_PIN_RETRIES = (byte) 0x08;
private final byte MAX_UV_RETRIES = (byte) 0x08; private final byte MAX_UV_RETRIES = (byte) 0x08;
private byte pinRetries = MAX_PIN_RETRIES; private byte pinRetries = MAX_PIN_RETRIES;
private byte uvRetries = MAX_UV_RETRIES; private byte uvRetries = MAX_UV_RETRIES;
// private final KeyPair ecDhKeyPair; // private final KeyPair ecDhKeyPair;
// private final boolean[] ecDhSet; // private final boolean[] ecDhSet;
private PinUvAuthProtocolOne pinUvAuthProtocolOne; private PinUvAuthProtocolOne pinUvAuthProtocolOne;
private short pinLength = 0; private short pinLength = 0;
private byte[] pin; private byte[] pin;
private final byte[] currentStoredPIN = new byte[16]; // LEFT(SHA-256(pin), 16) private final byte[] currentStoredPIN = new byte[16]; // LEFT(SHA-256(pin), 16)
private final byte[] pinToken = new byte[32]; private final byte[] pinToken = new byte[32];
// INS // INS
public static final byte ISO_INS_GET_DATA = (byte) 0xC0; 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_NFCCTAP_MSG = (byte) 0x10;
public static final byte FIDO2_INS_DESELECT = (byte) 0x12; public static final byte FIDO2_INS_DESELECT = (byte) 0x12;
// FIDO Command // FIDO Command
public static final byte FIDO2_AUTHENTICATOR_MAKE_CREDENTIAL = (byte) 0x01; 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_ASSERTION = (byte) 0x02;
public static final byte FIDO2_AUTHENTICATOR_GET_NEXT_ASSERTION = (byte) 0x08; 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_GET_INFO = (byte) 0x04;
public static final byte FIDO2_AUTHENTICATOR_CLIENT_PIN = (byte) 0x06; public static final byte FIDO2_AUTHENTICATOR_CLIENT_PIN = (byte) 0x06;
public static final byte FIDO2_AUTHENTICATOR_RESET = (byte) 0x07; public static final byte FIDO2_AUTHENTICATOR_RESET = (byte) 0x07;
// Vendor specific - for attestation cert loading. // Vendor specific - for attestation cert loading.
public static final byte FIDO2_VENDOR_ATTEST_SIGN = (byte) 0x41; 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_ATTEST_LOADCERT = (byte) 0x42;
public static final byte FIDO2_VENDOR_PERSO_COMPLETE = (byte) 0x43; 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_ATTEST_GETPUB = (byte) 0x44;
public static final byte FIDO2_VENDOR_GET_CREDENTIAL_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_VENDOR_ATTEST_GETCERT = (byte) 0x4A;
//IDSecret //IDSecret
public final IDSecret idSecret; public final IDSecret idSecret;
public static final byte ID_SECRET_GET_PUKX_RX = (byte) 0x50; public static final byte ID_SECRET_GET_PUKX_RX = (byte) 0x50;
public static final byte ID_SECRET_GET_CX = (byte) 0x51; public static final byte ID_SECRET_GET_CX = (byte) 0x51;
public static final byte ID_SECRET_GET_PUKX_CX = (byte) 0x52; public static final byte ID_SECRET_GET_PUKX_CX = (byte) 0x52;
public static final byte ID_SECRET_DUMP_ALL = (byte) 0x5F; public static final byte ID_SECRET_DUMP_ALL = (byte) 0x5F;
// AAGUID - Authenticator Attestation Global Unique Identifier // AAGUID - Authenticator Attestation Global Unique Identifier
// this uniquely identifies the type of authenticator we have built. // this uniquely identifies the type of authenticator we have built.
// If you're reusing this code, please generate your own GUID and put it here - // If you're reusing this code, please generate your own GUID and put it here -
// this is unique to manufacturer and device model. // this is unique to manufacturer and device model.
public static final byte[] aaguid = {//yubikey 5c nfc aaguid public static final byte[] aaguid = {
(byte) 0x2f, (byte) 0xc0, (byte) 0x57, (byte) 0x9f, (byte) 0x81, (byte) 0x13, (byte) 0x47, (byte) 0xea, (byte) 't', (byte) 'e', (byte) 's', (byte) 't', (byte) 'a', (byte) 'a', (byte) 'g', (byte) 'u',
(byte) 0xb1, (byte) 0x16, (byte) 0xbb, (byte) 0x5a, (byte) 0x8d, (byte) 0xb9, (byte) 0x20, (byte) 0x2a, }; (byte) 'i', (byte) 'd', (byte) 'p', (byte) 'r', (byte) 'l', (byte) 'a', (byte) 'b', (byte) '_',};
private CTAP2() { private CTAP2() {
// 1210 bytes of a transient buffer for read-in and out // 1210 bytes of a transient buffer for read-in and out
// We advertise 1200 bytes supported, but 10 bytes for protocol nonsense // We advertise 1200 bytes supported, but 10 bytes for protocol nonsense
try { try {
dataBuffer = JCSystem.makeTransientByteArray((short) 1210, JCSystem.CLEAR_ON_DESELECT); dataBuffer = JCSystem.makeTransientByteArray((short) 1210, JCSystem.CLEAR_ON_DESELECT);
} catch (Exception e) { } catch (Exception e) {
dataBuffer = new byte[1210]; dataBuffer = new byte[1210];
} }
try { try {
scratch = JCSystem.makeTransientByteArray((short) 512, JCSystem.CLEAR_ON_DESELECT); scratch = JCSystem.makeTransientByteArray((short) 512, JCSystem.CLEAR_ON_DESELECT);
} catch (Exception e) { } catch (Exception e) {
scratch = new byte[512]; scratch = new byte[512];
} }
tempVars = JCSystem.makeTransientShortArray((short) 8, JCSystem.CLEAR_ON_DESELECT); tempVars = JCSystem.makeTransientShortArray((short) 8, JCSystem.CLEAR_ON_DESELECT);
// Create the CBOR decoder // Create the CBOR decoder
cborDecoder = new CBORDecoder(); cborDecoder = new CBORDecoder();
cborEncoder = new CBOREncoder(); cborEncoder = new CBOREncoder();
credentialArray = new CredentialArray((short) 5); credentialArray = new CredentialArray((short) 5);
sha256MessageDigest = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); sha256MessageDigest = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false);
attestationKeyPair = new AttestationKeyPair(); attestationKeyPair = new AttestationKeyPair();
nextAssertion = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); nextAssertion = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET);
personalizeComplete = false; personalizeComplete = false;
isChaining = JCSystem.makeTransientBooleanArray((short) 2, JCSystem.CLEAR_ON_DESELECT); isChaining = JCSystem.makeTransientBooleanArray((short) 2, JCSystem.CLEAR_ON_DESELECT);
chainRam = JCSystem.makeTransientShortArray((short) 4, JCSystem.CLEAR_ON_DESELECT); chainRam = JCSystem.makeTransientShortArray((short) 4, JCSystem.CLEAR_ON_DESELECT);
outChainRam = JCSystem.makeTransientShortArray((short) 4, JCSystem.CLEAR_ON_DESELECT); outChainRam = JCSystem.makeTransientShortArray((short) 4, JCSystem.CLEAR_ON_DESELECT);
isOutChaining = JCSystem.makeTransientBooleanArray((short) 2, JCSystem.CLEAR_ON_DESELECT); isOutChaining = JCSystem.makeTransientBooleanArray((short) 2, JCSystem.CLEAR_ON_DESELECT);
// ECPublicKey ecDhPub = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.ALG_TYPE_EC_FP_PUBLIC, // ECPublicKey ecDhPub = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.ALG_TYPE_EC_FP_PUBLIC,
// JCSystem.MEMORY_TYPE_TRANSIENT_RESET, KeyBuilder.LENGTH_EC_FP_256, false); // JCSystem.MEMORY_TYPE_TRANSIENT_RESET, KeyBuilder.LENGTH_EC_FP_256, false);
// ECPrivateKey ecDhPriv = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.ALG_TYPE_EC_FP_PRIVATE, // ECPrivateKey ecDhPriv = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.ALG_TYPE_EC_FP_PRIVATE,
// JCSystem.MEMORY_TYPE_TRANSIENT_RESET, KeyBuilder.LENGTH_EC_FP_256, false); // JCSystem.MEMORY_TYPE_TRANSIENT_RESET, KeyBuilder.LENGTH_EC_FP_256, false);
clientPINCommand = new ClientPINCommand(); clientPINCommand = new ClientPINCommand();
// ecDhKeyPair = new KeyPair(ecDhPub, ecDhPriv); // ecDhKeyPair = new KeyPair(ecDhPub, ecDhPriv);
// ecDhSet = JCSystem.makeTransientBooleanArray((short) 1, JCSystem.CLEAR_ON_RESET); // ecDhSet = JCSystem.makeTransientBooleanArray((short) 1, JCSystem.CLEAR_ON_RESET);
pinUvAuthProtocolOne = new PinUvAuthProtocolOne(); pinUvAuthProtocolOne = new PinUvAuthProtocolOne();
pinUvAuthProtocolOne.initialize(); pinUvAuthProtocolOne.initialize();
idSecret = new IDSecret(); idSecret = new IDSecret();
} }
public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException { public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException {
short offset = bOffset; short offset = bOffset;
offset += (short) (bArray[offset] + 1); // instance offset += (short) (bArray[offset] + 1); // instance
offset += (short) (bArray[offset] + 1); // privileges offset += (short) (bArray[offset] + 1); // privileges
final CTAP2 applet = new CTAP2(); final CTAP2 applet = new CTAP2();
try { try {
applet.register(bArray, (short) (bOffset + 1), bArray[bOffset]); applet.register(bArray, (short) (bOffset + 1), bArray[bOffset]);
} catch (Exception e) { } catch (Exception e) {
applet.register(); applet.register();
} }
} }
// main entry point // main entry point
public void process(APDU apdu) throws ISOException { public void process(APDU apdu) throws ISOException {
byte[] buffer = apdu.getBuffer(); byte[] buffer = apdu.getBuffer();
// return version String when selecting // return version String when selecting
if (selectingApplet()) { if (selectingApplet()) {
Util.arrayCopyNonAtomic(Utf8Strings.UTF8_U2F_V2, (short) 0, buffer, (short) 0, Util.arrayCopyNonAtomic(Utf8Strings.UTF8_FIDO_2_0, (short) 0, buffer, (short) 0,
(short) Utf8Strings.UTF8_U2F_V2.length); (short) Utf8Strings.UTF8_FIDO_2_0.length);
apdu.setOutgoingAndSend((short) 0, (short) Utf8Strings.UTF8_U2F_V2.length); apdu.setOutgoingAndSend((short) 0, (short) Utf8Strings.UTF8_FIDO_2_0.length);
return; return;
} }
// Check CLA // Check CLA
if (!apdu.isCommandChainingCLA() && apdu.isISOInterindustryCLA()) { if (!apdu.isCommandChainingCLA() && apdu.isISOInterindustryCLA()) {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
} }
JCSystem.requestObjectDeletion(); JCSystem.requestObjectDeletion();
switch (buffer[ISO7816.OFFSET_INS]) { switch (buffer[ISO7816.OFFSET_INS]) {
case ISO_INS_GET_DATA: // 0xC0 case ISO_INS_GET_DATA: // 0xC0
if (isOutChaining[0]) { if (isOutChaining[0]) {
getData(apdu); getData(apdu);
} else { } else {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
break; break;
case FIDO2_INS_NFCCTAP_MSG: // 0x10 case FIDO2_INS_NFCCTAP_MSG: // 0x10
handle(apdu); handle(apdu);
break; break;
case FIDO2_INS_DESELECT: // 0x12 case FIDO2_INS_DESELECT: // 0x12
// Appears to be a reset function in the FIDO2 spec, but never referenced // Appears to be a reset function in the FIDO2 spec, but never referenced
// anywhere // anywhere
ISOException.throwIt(ISO7816.SW_NO_ERROR); ISOException.throwIt(ISO7816.SW_NO_ERROR);
break; break;
default: default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
} }
} }
/** /**
* Handle the command chaining or extended APDU logic. * Handle the command chaining or extended APDU logic.
* *
* Due to the FIDO2 spec requiring support for both extended APDUs and command * Due to the FIDO2 spec requiring support for both extended APDUs and command
* chaining, we need to implement chaining here. * chaining, we need to implement chaining here.
* *
* I didn't want to pollute the logic over in the process function, and it makes * I didn't want to pollute the logic over in the process function, and it makes
* sense to do both here. * sense to do both here.
* *
* @param apdu apdu buffer * @param apdu apdu buffer
* @return length of data to be processed. 0 if command chaining is not finished. * @return length of data to be processed. 0 if command chaining is not finished.
*/ */
private short doApduIngestion(APDU apdu) { private short doApduIngestion(APDU apdu) {
byte[] buffer = apdu.getBuffer(); byte[] buffer = apdu.getBuffer();
// Receive the APDU // Receive the APDU
tempVars[4] = apdu.setIncomingAndReceive(); tempVars[4] = apdu.setIncomingAndReceive();
// Get true incoming data length // Get true incoming data length
tempVars[3] = apdu.getIncomingLength(); tempVars[3] = apdu.getIncomingLength();
// Check if the APDU is too big, we only handle 1200 byte // Check if the APDU is too big, we only handle 1200 byte
if (tempVars[3] > 1200) { if (tempVars[3] > 1200) {
returnError(apdu, CTAP2_ERR_REQUEST_TOO_LARGE); returnError(apdu, CTAP2_ERR_REQUEST_TOO_LARGE);
return 0; return 0;
} }
// Check what we need to do re APDU buffer, is it full (special case for 1 len) // 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 this is a command chaining APDU, swap to that logic
if (isCommandChainingCLA(apdu)) { if (isCommandChainingCLA(apdu)) {
// In the chaining // In the chaining
if (!isChaining[0]) { if (!isChaining[0]) {
// Must be first chaining APDU // Must be first chaining APDU
isChaining[0] = true; isChaining[0] = true;
// Prep the variables // Prep the variables
chainRam[0] = 0; chainRam[0] = 0;
} }
// Copy buffer // Copy buffer
chainRam[1] = tempVars[4]; chainRam[1] = tempVars[4];
// chainRam[0] is the current point in the buffer we start from // chainRam[0] is the current point in the buffer we start from
chainRam[0] = Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, chainRam[0], chainRam[1]); chainRam[0] = Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, chainRam[0], chainRam[1]);
return 0x00; return 0x00;
} else if (isChaining[0]) { } else if (isChaining[0]) {
// Must be the last of the chaining - make the copy and return the length. // Must be the last of the chaining - make the copy and return the length.
chainRam[1] = tempVars[4]; chainRam[1] = tempVars[4];
chainRam[0] = Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, chainRam[0], chainRam[1]); chainRam[0] = Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, chainRam[0], chainRam[1]);
isChaining[0] = false; isChaining[0] = false;
isChaining[1] = true; isChaining[1] = true;
return chainRam[0]; return chainRam[0];
} else if (tempVars[3] == 0x01) { } else if (tempVars[3] == 0x01) {
dataBuffer[0] = buffer[apdu.getOffsetCdata()]; dataBuffer[0] = buffer[apdu.getOffsetCdata()];
return 0x01; return 0x01;
} else if (apdu.getCurrentState() == APDU.STATE_FULL_INCOMING) { } else if (apdu.getCurrentState() == APDU.STATE_FULL_INCOMING) {
// We need to do no more // We need to do no more
// Read the entirety of the buffer into the inBuf // Read the entirety of the buffer into the inBuf
Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, (short) 0, tempVars[3]); Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, (short) 0, tempVars[3]);
return tempVars[4]; return tempVars[4];
} else { } else {
// The APDU needs a multi-stage copy // The APDU needs a multi-stage copy
// First, copy the current data buffer in // First, copy the current data buffer in
// Get the number of bytes in the data buffer that are the Lc, vars[5] will do // Get the number of bytes in the data buffer that are the Lc, vars[5] will do
tempVars[5] = tempVars[4]; tempVars[5] = tempVars[4];
// Make the copy, vars[3] is bytes remaining to get // Make the copy, vars[3] is bytes remaining to get
tempVars[4] = 0; tempVars[4] = 0;
while (tempVars[3] > 0) { while (tempVars[3] > 0) {
// Copy data // Copy data
tempVars[4] = Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, tempVars[4], tempVars[5]); tempVars[4] = Util.arrayCopyNonAtomic(buffer, apdu.getOffsetCdata(), dataBuffer, tempVars[4], tempVars[5]);
// Decrement vars[3] by the bytes copied // Decrement vars[3] by the bytes copied
tempVars[3] -= tempVars[5]; tempVars[3] -= tempVars[5];
// Pull more bytes // Pull more bytes
tempVars[5] = apdu.receiveBytes(apdu.getOffsetCdata()); tempVars[5] = apdu.receiveBytes(apdu.getOffsetCdata());
} }
// Now we're at the end, here, and the commands expect us to give them a data // 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. // 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]. // The commands use vars[3], so vars[4] will be fine to copy to vars[3].
return tempVars[4]; return tempVars[4];
} }
} }
private void handle(APDU apdu) { private void handle(APDU apdu) {
byte[] buffer = apdu.getBuffer(); byte[] buffer = apdu.getBuffer();
tempCredential = null; tempCredential = null;
authenticatorMakeCredential = null; authenticatorMakeCredential = null;
tempVars[3] = doApduIngestion(apdu); tempVars[3] = doApduIngestion(apdu);
if (tempVars[3] == 0) { if (tempVars[3] == 0) {
// If zero, we had no ISO error, but there might be a CTAP error to return. // If zero, we had no ISO error, but there might be a CTAP error to return.
// Throw either way. // Throw either way.
ISOException.throwIt(ISO7816.SW_NO_ERROR); ISOException.throwIt(ISO7816.SW_NO_ERROR);
return; return;
} }
// Need to grab the CTAP command byte // Need to grab the CTAP command byte
switch (dataBuffer[0]) { switch (dataBuffer[0]) {
case FIDO2_AUTHENTICATOR_MAKE_CREDENTIAL: //0x01 case FIDO2_AUTHENTICATOR_MAKE_CREDENTIAL: //0x01
authMakeCredential(apdu, tempVars[3]); authMakeCredential(apdu, tempVars[3]);
break; break;
case FIDO2_AUTHENTICATOR_GET_ASSERTION: // 0x02 case FIDO2_AUTHENTICATOR_GET_ASSERTION: // 0x02
authGetAssertion(apdu, tempVars[3]); authGetAssertion(apdu, tempVars[3]);
break; break;
case FIDO2_AUTHENTICATOR_GET_INFO: // x0x04 case FIDO2_AUTHENTICATOR_GET_INFO: // x0x04
authGetInfo(apdu); authGetInfo(apdu);
break; break;
case FIDO2_AUTHENTICATOR_CLIENT_PIN: // 0x06 case FIDO2_AUTHENTICATOR_CLIENT_PIN: // 0x06
clientPin(apdu, tempVars[3]); clientPin(apdu, tempVars[3]);
break; break;
case FIDO2_AUTHENTICATOR_RESET: //0x07 case FIDO2_AUTHENTICATOR_RESET: //0x07
// Need to finish doing this, we can, I mean, but I don't like it // Need to finish doing this, we can, I mean, but I don't like it
doReset(apdu); doReset(apdu);
break; break;
case FIDO2_AUTHENTICATOR_GET_NEXT_ASSERTION: // 0x08 case FIDO2_AUTHENTICATOR_GET_NEXT_ASSERTION: // 0x08
authGetNextAssertion(apdu, buffer); authGetNextAssertion(apdu, buffer);
break; break;
case FIDO2_VENDOR_ATTEST_SIGN: //0x41 case FIDO2_VENDOR_ATTEST_SIGN: //0x41
attestSignRaw(apdu, tempVars[3]); attestSignRaw(apdu, tempVars[3]);
break; break;
case FIDO2_VENDOR_ATTEST_LOADCERT: //0x42 case FIDO2_VENDOR_ATTEST_LOADCERT: //0x42
attestSetCert(apdu, tempVars[3]); attestSetCert(apdu, tempVars[3]);
break; break;
case FIDO2_VENDOR_PERSO_COMPLETE: //0x43 case FIDO2_VENDOR_PERSO_COMPLETE: //0x43
personalizationComplete(apdu); personalizationComplete(apdu);
break; break;
case FIDO2_VENDOR_ATTEST_GETPUB: //0x44 case FIDO2_VENDOR_ATTEST_GETPUB: //0x44
getAttestPublic(apdu); getAttestPublic(apdu);
break; break;
case FIDO2_VENDOR_GET_CREDENTIAL_COUNT: //0x45 case FIDO2_VENDOR_GET_CREDENTIAL_COUNT: //0x45
getCredentialCount(apdu); getCredentialCount(apdu);
break; break;
case ID_SECRET_GET_PUKX_RX: // 0x50 case ID_SECRET_GET_PUKX_RX: // 0x50
getPuKxRx(apdu, tempVars[3]); getPuKxRx(apdu, tempVars[3]);
break; break;
case ID_SECRET_GET_CX: // 0x51 case ID_SECRET_GET_CX: // 0x51
getCx(apdu, tempVars[3]); getCx(apdu, tempVars[3]);
break; break;
case ID_SECRET_GET_PUKX_CX: case ID_SECRET_GET_PUKX_CX:
getPuKxCx(apdu, tempVars[3]); getPuKxCx(apdu, tempVars[3]);
break; break;
case ID_SECRET_DUMP_ALL: // 0x5F case ID_SECRET_DUMP_ALL: // 0x5F
// apdu.setOutgoingAndSend((short)0, (short)4); // testing, for break point setting dumpIDSecret(apdu);
dumpIDSecret(apdu); break;
break; case FIDO2_VENDOR_ATTEST_GETCERT: //0x4a
case FIDO2_VENDOR_ATTEST_GETCERT: //0x4a getCert(apdu);
getCert(apdu); break;
break; default:
default: returnError(apdu, CTAP1_ERR_INVALID_COMMAND);
returnError(apdu, CTAP1_ERR_INVALID_COMMAND); }
}
}
}
private void personalizationComplete(APDU apdu) {
private void personalizationComplete(APDU apdu) { if (attestationKeyPair.isCertSet() && !personalizeComplete) {
if (attestationKeyPair.isCertSet() && !personalizeComplete) { personalizeComplete = true;
personalizeComplete = true; returnError(apdu, CTAP1_ERR_SUCCESS);
returnError(apdu, CTAP1_ERR_SUCCESS); } else {
} else { returnError(apdu, CTAP1_ERR_INVALID_COMMAND);
returnError(apdu, CTAP1_ERR_INVALID_COMMAND); }
} }
}
/**
/** * Gets the attestation public key.
* Gets the attestation public key. *
* * @param apdu apdu buffer
* @param apdu apdu buffer */
*/ private void getAttestPublic(APDU apdu) {
private void getAttestPublic(APDU apdu) { if (personalizeComplete) {
if (personalizeComplete) { returnError(apdu, CTAP1_ERR_INVALID_COMMAND);
returnError(apdu, CTAP1_ERR_INVALID_COMMAND); return;
return; }
} dataBuffer[0] = 0x00;
dataBuffer[0] = 0x00; tempVars[0] = (short) (attestationKeyPair.getPubkey(dataBuffer, (short) 1) + 1);
tempVars[0] = (short) (attestationKeyPair.getPubkey(dataBuffer, (short) 1) + 1); apdu.setOutgoing();
apdu.setOutgoing(); apdu.setOutgoingLength(tempVars[0]);
apdu.setOutgoingLength(tempVars[0]); apdu.sendBytesLong(dataBuffer, (short) 0, tempVars[0]);
apdu.sendBytesLong(dataBuffer, (short) 0, tempVars[0]); }
}
/**
/** get counter's value */ * get counter's value
private void getCredentialCount(APDU apdu){ */
Util.setShort(apdu.getBuffer(), (short)0x00, credentialArray.getCount()); private void getCredentialCount(APDU apdu) {
apdu.setOutgoingAndSend((short)0x00, (short)2); Util.setShort(apdu.getBuffer(), (short) 0x00, credentialArray.getCount());
} apdu.setOutgoingAndSend((short) 0x00, (short) 2);
}
/**
* for original framework purpose /**
* * for original framework purpose
* input: IDx String * <p>
* return: PuKx and Rx in CBOR form * input: IDx String
*/ * return: PuKx and Rx in CBOR form
private void getPuKxRx(APDU apdu, short dataLength){ */
// Done IDx have to get data from dataBuffer at index 1 private void getPuKxRx(APDU apdu, short dataLength) {
Util.arrayCopy(dataBuffer, (short)1, scratch, (short)0, (short)(dataLength-1)); // Done IDx have to get data from dataBuffer at index 1
idSecret.IDx = new DomString(scratch, (short)(dataLength-1) ); Util.arrayCopy(dataBuffer, (short) 1, scratch, (short) 0, (short) (dataLength - 1));
cborEncoder.init(dataBuffer, (short) 0, (short)1200); idSecret.IDx = new DomString(scratch, (short) (dataLength - 1));
cborEncoder.startArray((short)2); cborEncoder.init(dataBuffer, (short) 0, (short) 1200);
cborEncoder.encodeUInt32(idSecret.Rx, (short)0); cborEncoder.startArray((short) 2);
tempVars[0] = attestationKeyPair.getPubkey(scratch, (short)0); cborEncoder.encodeUInt32(idSecret.Rx, (short) 0);
cborEncoder.encodeByteString(scratch, (short)0, tempVars[0]); tempVars[0] = attestationKeyPair.getPubkey(scratch, (short) 0);
apdu.setOutgoing(); cborEncoder.encodeByteString(scratch, (short) 0, tempVars[0]);
apdu.setOutgoingLength(cborEncoder.getCurrentOffset()); apdu.setOutgoing();
apdu.sendBytesLong(dataBuffer, (short) 0, cborEncoder.getCurrentOffset()); apdu.setOutgoingLength(cborEncoder.getCurrentOffset());
} apdu.sendBytesLong(dataBuffer, (short) 0, cborEncoder.getCurrentOffset());
}
/**
* pending /**
*/ * pending
private void getCx(APDU apdu, short dataLength){ */
private void getCx(APDU apdu, short dataLength) {
}
}
/**
* for alternative framework purpose /**
* * for alternative framework purpose
* input: IDx , PuKp in CBOR form * <p>
* return: PuKx, encryptedCx in CBOR form * input: IDx , PuKp in CBOR form
*/ * return: PuKx, encryptedCx in CBOR form
private void getPuKxCx(APDU apdu, short dataLength){ */
cborDecoder.init(dataBuffer, (short)1, dataLength); private void getPuKxCx(APDU apdu, short dataLength) {
try{ cborDecoder.init(dataBuffer, (short) 1, dataLength);
cborDecoder.readMajorType(CBORBase.TYPE_ARRAY); try {
short length = cborDecoder.readTextString(scratch, (short)0); cborDecoder.readMajorType(CBORBase.TYPE_ARRAY);
idSecret.IDx = new DomString(scratch, length); short length = cborDecoder.readTextString(scratch, (short) 0);
cborDecoder.readByteString(scratch, (short)0); idSecret.IDx = new DomString(scratch, length);
Util.arrayCopy(scratch, (short)8, idSecret.PuKp, (short)1, (short)64); cborDecoder.readByteString(scratch, (short) 0);
}catch(UserException e){ Util.arrayCopy(scratch, (short) 8, idSecret.PuKp, (short) 1, (short) 64);
returnError(apdu, e.getReason()); } catch (UserException e) {
} returnError(apdu, e.getReason());
KeyAgreement keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH, false); }
keyAgreement.init(attestationKeyPair.getPrivate()); KeyAgreement keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH, false);
keyAgreement.generateSecret(idSecret.PuKp, (short)0, (short)65, idSecret.sharedSecret, (short)0); keyAgreement.init(attestationKeyPair.getPrivate());
keyAgreement.generateSecret(idSecret.PuKp, (short) 0, (short) 65, idSecret.sharedSecret, (short) 0);
idSecret.initAesKey();
idSecret.encryptCx(); idSecret.initAesKey();
idSecret.encryptCx();
cborEncoder.init(dataBuffer, (short)0, (short)1200);
cborEncoder.startArray((short)2); cborEncoder.init(dataBuffer, (short) 0, (short) 1200);
short length = attestationKeyPair.getPubkey(scratch, (short)0); cborEncoder.startArray((short) 2);
cborEncoder.encodeByteString(scratch, (short)0, length); short length = attestationKeyPair.getPubkey(scratch, (short) 0);
cborEncoder.encodeByteString(idSecret.encryptedCx, (short)0, (short)idSecret.encryptedCx.length); cborEncoder.encodeByteString(scratch, (short) 0, length);
cborEncoder.encodeByteString(idSecret.encryptedCx, (short) 0, (short) idSecret.encryptedCx.length);
//for test
idSecret.getHMAC(scratch, (short)0); //for test
idSecret.getHMAC(scratch, (short) 0);
apdu.setOutgoing();
apdu.setOutgoingLength(cborEncoder.getCurrentOffset()); apdu.setOutgoing();
apdu.sendBytesLong(dataBuffer, (short)0, cborEncoder.getCurrentOffset()); apdu.setOutgoingLength(cborEncoder.getCurrentOffset());
} apdu.sendBytesLong(dataBuffer, (short) 0, cborEncoder.getCurrentOffset());
}
/**
* dump secrets /**
*/ * dump secrets
private void dumpIDSecret(APDU apdu){ */
tempVars[0] = idSecret.dump(dataBuffer, cborEncoder); private void dumpIDSecret(APDU apdu) {
tempVars[0] = idSecret.dump(dataBuffer, cborEncoder);
// apdu.setOutgoing(); // apdu.setOutgoing();
// apdu.setOutgoingLength(tempVars[0]); // apdu.setOutgoingLength(tempVars[0]);
// apdu.sendBytesLong(dataBuffer, (short)0, tempVars[0]); // apdu.sendBytesLong(dataBuffer, (short)0, tempVars[0]);
sendLongChaining(apdu, tempVars[0]); sendLongChaining(apdu, tempVars[0]);
} }
/** /**
* Performs raw signatures, may only occur when personalisation is not complete. * Performs raw signatures, may only occur when personalisation is not complete.
* *
* @param apdu apdu buffer * @param apdu apdu buffer
* @param bufLen buffer length * @param bufLen buffer length
*/ */
public void attestSignRaw(APDU apdu, short bufLen) { public void attestSignRaw(APDU apdu, short bufLen) {
if (personalizeComplete) { if (personalizeComplete) {
returnError(apdu, CTAP1_ERR_INVALID_COMMAND); returnError(apdu, CTAP1_ERR_INVALID_COMMAND);
return; return;
} }
Util.arrayCopy(dataBuffer, (short) 1, scratch, (short) 0, (short) (bufLen - 1)); Util.arrayCopy(dataBuffer, (short) 1, scratch, (short) 0, (short) (bufLen - 1));
dataBuffer[0] = 0x00; dataBuffer[0] = 0x00;
tempVars[2] = attestationKeyPair.sign(scratch, (short) 0, tempVars[1], dataBuffer, (short) 1); tempVars[2] = attestationKeyPair.sign(scratch, (short) 0, tempVars[1], dataBuffer, (short) 1);
apdu.setOutgoing(); apdu.setOutgoing();
apdu.setOutgoingLength((short) (tempVars[2] + 1)); apdu.setOutgoingLength((short) (tempVars[2] + 1));
apdu.sendBytesLong(dataBuffer, (short) 0, (short) (tempVars[2] + 1)); apdu.sendBytesLong(dataBuffer, (short) 0, (short) (tempVars[2] + 1));
} }
public void attestSetCert(APDU apdu, short bufLen) { public void attestSetCert(APDU apdu, short bufLen) {
if (personalizeComplete) { if (personalizeComplete) {
returnError(apdu, CTAP1_ERR_INVALID_COMMAND); returnError(apdu, CTAP1_ERR_INVALID_COMMAND);
return; return;
} }
// We don't actually use any CBOR here, simplify copying // We don't actually use any CBOR here, simplify copying
attestationKeyPair.setCert(dataBuffer, (short) 1, (short) (bufLen - 1)); attestationKeyPair.setCert(dataBuffer, (short) 1, (short) (bufLen - 1));
MessageDigest dig = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); MessageDigest dig = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false);
short len = (short) (dig.doFinal(attestationKeyPair.x509cert, (short) 0, attestationKeyPair.x509len, dataBuffer, (short) 3) + 3); short len = (short) (dig.doFinal(attestationKeyPair.x509cert, (short) 0, attestationKeyPair.x509len, dataBuffer, (short) 3) + 3);
dataBuffer[0] = 0x00; dataBuffer[0] = 0x00;
Util.setShort(dataBuffer, (short) 1, attestationKeyPair.x509len); Util.setShort(dataBuffer, (short) 1, attestationKeyPair.x509len);
apdu.setOutgoing(); apdu.setOutgoing();
apdu.setOutgoingLength(len); apdu.setOutgoingLength(len);
apdu.sendBytesLong(dataBuffer, (short) 0, len); apdu.sendBytesLong(dataBuffer, (short) 0, len);
} }
public void authMakeCredential(APDU apdu, short bufLen) { public void authMakeCredential(APDU apdu, short bufLen) {
if(pinRetries < (short)1){ if (pinRetries < (short) 1) {
returnError(apdu, CTAP2_ERR_PIN_AUTH_BLOCKED); returnError(apdu, CTAP2_ERR_PIN_AUTH_BLOCKED);
return; return;
} }
// Init the decoder // Init the decoder
cborDecoder.init(dataBuffer, (short) 1, bufLen); cborDecoder.init(dataBuffer, (short) 1, bufLen);
// create a credential object // create a credential object
try { try {
authenticatorMakeCredential = new AuthenticatorMakeCredential(cborDecoder); authenticatorMakeCredential = new AuthenticatorMakeCredential(cborDecoder);
} catch (UserException e) { } catch (UserException e) {
returnError(apdu, e.getReason()); returnError(apdu, e.getReason());
return; return;
} }
// Create the actual credential // Create the actual credential
switch (authenticatorMakeCredential.getAlgorithm()) { switch (authenticatorMakeCredential.getAlgorithm()) {
case Signature.ALG_ECDSA_SHA_256: case Signature.ALG_ECDSA_SHA_256:
tempCredential = new StoredES256Credential(authenticatorMakeCredential); tempCredential = new StoredES256Credential(authenticatorMakeCredential);
break; break;
case Signature.ALG_RSA_SHA_256_PKCS1: case Signature.ALG_RSA_SHA_256_PKCS1:
tempCredential = new StoredRS256Credential(authenticatorMakeCredential); tempCredential = new StoredRS256Credential(authenticatorMakeCredential);
break; break;
case Signature.ALG_RSA_SHA_256_PKCS1_PSS: case Signature.ALG_RSA_SHA_256_PKCS1_PSS:
tempCredential = new StoredPS256Credential(authenticatorMakeCredential); tempCredential = new StoredPS256Credential(authenticatorMakeCredential);
break; break;
default: default:
returnError(apdu, CTAP2_ERR_UNSUPPORTED_ALGORITHM); returnError(apdu, CTAP2_ERR_UNSUPPORTED_ALGORITHM);
return; return;
} }
if (authenticatorMakeCredential.isResident()) { if (authenticatorMakeCredential.isResident()) {
idSecret.writeTempBuffer(pinUvAuthProtocolOne.authenticate(pinToken, authenticatorMakeCredential.getDataHash()), (short)0); idSecret.writeTempBuffer(pinUvAuthProtocolOne.authenticate(pinToken, authenticatorMakeCredential.getDataHash()), (short) 0);
idSecret.writeTempBuffer(authenticatorMakeCredential.getPinUvAuthParam(), (short)64); idSecret.writeTempBuffer(authenticatorMakeCredential.getPinUvAuthParam(), (short) 64);
// verify the pin UV Auth token // verify the pin UV Auth token
if(pinUvAuthProtocolOne.verify( if (pinUvAuthProtocolOne.verify(
pinToken, pinToken,
authenticatorMakeCredential.getDataHash(), authenticatorMakeCredential.getDataHash(),
authenticatorMakeCredential.getPinUvAuthParam() authenticatorMakeCredential.getPinUvAuthParam()
) )
){ ) {
pinRetries = MAX_PIN_RETRIES; pinRetries = MAX_PIN_RETRIES;
}else{ } else {
pinRetries--; pinRetries--;
returnError(apdu, CTAP2_ERR_PIN_AUTH_INVALID); returnError(apdu, CTAP2_ERR_PIN_AUTH_INVALID);
return; return;
} }
// Check if a credential exists on the excluded list // Check if a credential exists on the excluded list
if (authenticatorMakeCredential.isExclude() && isPresent(authenticatorMakeCredential.exclude)) { if (authenticatorMakeCredential.isExclude() && isPresent(authenticatorMakeCredential.exclude)) {
// Throw the error // Throw the error
returnError(apdu, CTAP2_ERR_CREDENTIAL_EXCLUDED); returnError(apdu, CTAP2_ERR_CREDENTIAL_EXCLUDED);
return; return;
} }
// Add the credential to the resident storage, overwriting if necessary // Add the credential to the resident storage, overwriting if necessary
addResident(apdu, tempCredential); addResident(apdu, tempCredential);
// Initialise the output buffer, for CBOR writing. // Initialise the output buffer, for CBOR writing.
// output buffer needs 0x00 as first byte as status code // output buffer needs 0x00 as first byte as status code
dataBuffer[0] = 0x00; dataBuffer[0] = 0x00;
cborEncoder.init(dataBuffer, (short) 1, (short) 1199); cborEncoder.init(dataBuffer, (short) 1, (short) 1199);
// Create a map in the buffer // Create a map in the buffer
tempVars[0] = cborEncoder.startMap((short) 3); // current offset tempVars[0] = cborEncoder.startMap((short) 3); // current offset
// Attestation stuff // Attestation stuff
// Attestation statement format : 0x01 // Attestation statement format : 0x01
cborEncoder.writeRawByte((byte) 0x01); cborEncoder.writeRawByte((byte) 0x01);
cborEncoder.encodeTextString(Utf8Strings.UTF8_PACKED, (short) 0, (short) 6); cborEncoder.encodeTextString(Utf8Strings.UTF8_PACKED, (short) 0, (short) 6);
// Put the authenticatorData identifier(0x02) there // Put the authenticatorData identifier(0x02) there
// Authenticator Data : 0x02 // Authenticator Data : 0x02
cborEncoder.writeRawByte((byte) 0x02); cborEncoder.writeRawByte((byte) 0x02);
// Allocate some space for the byte string // Allocate some space for the byte string
/** /**
* add extensions byte string length * add extensions byte string length
*/ */
// TODO fix the bug on extension byte string // TODO fix the bug on extension byte string
// tempVars[0] = cborEncoder.startByteString((short) (37 + tempCredential.getAttestedLen() + idSecret.getExtensionsLength() )); // tempVars[0] = cborEncoder.startByteString((short) (37 + tempCredential.getAttestedLen() + idSecret.getExtensionsLength() ));
// tempVars[0] = cborEncoder.startByteString((short) (37 + tempCredential.getAttestedLen() + Utf8Strings.UTF8_PRLab.length )); // tempVars[0] = cborEncoder.startByteString((short) (37 + tempCredential.getAttestedLen() + Utf8Strings.UTF8_PRLab.length ));
tempVars[0] = cborEncoder.startByteString((short) (37 + tempCredential.getAttestedLen() )); tempVars[0] = cborEncoder.startByteString((short) (37 + tempCredential.getAttestedLen()));
/** /**
* end * end
*/ */
// Stash where it begins // Stash where it begins
tempVars[7] = tempVars[0]; tempVars[7] = tempVars[0];
// Create the SHA256 hash of the RP ID // Create the SHA256 hash of the RP ID
tempCredential.rpEntity.getRp(scratch, (short) 0); tempCredential.rpEntity.getRp(scratch, (short) 0);
tempVars[0] += sha256MessageDigest.doFinal(scratch, (short) 0, tempCredential.rpEntity.getRpLen(), dataBuffer, tempVars[0]); tempVars[0] += sha256MessageDigest.doFinal(scratch, (short) 0, tempCredential.rpEntity.getRpLen(), dataBuffer, tempVars[0]);
// Set flags - User presence, user verified, attestation present // Set flags - User presence, user verified, attestation present
dataBuffer[tempVars[0]++] = (byte) 0x45; dataBuffer[tempVars[0]++] = (byte) 0x45;
// Set the signature counter // Set the signature counter
tempVars[0] += tempCredential.readCounter(dataBuffer, tempVars[0]); tempVars[0] += tempCredential.readCounter(dataBuffer, tempVars[0]);
// Read the credential details in // Read the credential details in
// Just note down where this starts for future ref // Just note down where this starts for future ref
tempVars[0] += tempCredential.getAttestedData(dataBuffer, tempVars[0]); tempVars[0] += tempCredential.getAttestedData(dataBuffer, tempVars[0]);
/** /**
* put extensions here * put extensions here
*/ */
// TODO fix the bug on this // TODO fix the bug on this
// tempVars[0] += idSecret.getExtensionsByteString(dataBuffer, tempVars[0]); // tempVars[0] += idSecret.getExtensionsByteString(dataBuffer, tempVars[0]);
// Util.arrayCopy(Utf8Strings.UTF8_PRLab, (short)0, dataBuffer, tempVars[0], (short)Utf8Strings.UTF8_PRLab.length); // Util.arrayCopy(Utf8Strings.UTF8_PRLab, (short)0, dataBuffer, tempVars[0], (short)Utf8Strings.UTF8_PRLab.length);
/** /**
* end extensions * end extensions
*/ */
// Generate and then attach the attestation. // Generate and then attach the attestation.
// Attestation Statement : 0x03 // Attestation Statement : 0x03
cborEncoder.writeRawByte((byte) 0x03); cborEncoder.writeRawByte((byte) 0x03);
// Start to build into the cbor array manually, to avoid arrayCopy // Start to build into the cbor array manually, to avoid arrayCopy
// Create a map with 3 things // Create a map with 3 things
// cborEncoder.startMap((short) 3); // cborEncoder.startMap((short) 3);
cborEncoder.startMap((short) 4); cborEncoder.startMap((short) 4);
// Add the alg label // Add the alg label
cborEncoder.encodeTextString(Utf8Strings.UTF8_ALG, (short) 0, (short) 3); cborEncoder.encodeTextString(Utf8Strings.UTF8_ALG, (short) 0, (short) 3);
// Add the actual algorithm - -7 is 6 as a negative // Add the actual algorithm - -7 is 6 as a negative
cborEncoder.encodeNegativeUInt8((byte) 0x06); cborEncoder.encodeNegativeUInt8((byte) 0x06);
// Add the actual signature, we should generate this // Add the actual signature, we should generate this
cborEncoder.encodeTextString(Utf8Strings.UTF8_SIG, (short) 0, (short) 3); cborEncoder.encodeTextString(Utf8Strings.UTF8_SIG, (short) 0, (short) 3);
// Generate the signature, can't do this directly unfortunately. // Generate the signature, can't do this directly unfortunately.
// We sign over the client data hash and the attested data. // We sign over the client data hash and the attested data.
// AuthenticatorData is first. We noted down where it begins and know how long // AuthenticatorData is first. We noted down where it begins and know how long
// it is. // it is.
attestationKeyPair.update(dataBuffer, tempVars[7], (short) (tempCredential.getAttestedLen() + 37)); attestationKeyPair.update(dataBuffer, tempVars[7], (short) (tempCredential.getAttestedLen() + 37));
// The client data hash is next, which we use to finish off the signature. // The client data hash is next, which we use to finish off the signature.
tempVars[4] = attestationKeyPair.sign(authenticatorMakeCredential.dataHash, (short) 0, (short) authenticatorMakeCredential.dataHash.length, scratch, (short) 0); tempVars[4] = attestationKeyPair.sign(authenticatorMakeCredential.dataHash, (short) 0, (short) authenticatorMakeCredential.dataHash.length, scratch, (short) 0);
// Create the byte string for the signature // Create the byte string for the signature
cborEncoder.encodeByteString(scratch, (short) 0, tempVars[4]); cborEncoder.encodeByteString(scratch, (short) 0, tempVars[4]);
// Set the x509 cert now // Set the x509 cert now
cborEncoder.encodeTextString(Utf8Strings.UTF8_X5C, (short) 0, (short) 3); cborEncoder.encodeTextString(Utf8Strings.UTF8_X5C, (short) 0, (short) 3);
// Supposedly we need an array here // Supposedly we need an array here
cborEncoder.startArray((short) 1); cborEncoder.startArray((short) 1);
cborEncoder.encodeByteString(attestationKeyPair.x509cert, (short) 0, attestationKeyPair.x509len); cborEncoder.encodeByteString(attestationKeyPair.x509cert, (short) 0, attestationKeyPair.x509len);
/** /**
* extension * extension
*/ */
// add extension label // add extension label
cborEncoder.encodeTextString(Utf8Strings.UTF8_EXTENSIONS, (short)0, (short)Utf8Strings.UTF8_EXTENSIONS.length); cborEncoder.encodeTextString(Utf8Strings.UTF8_EXTENSIONS, (short) 0, (short) Utf8Strings.UTF8_EXTENSIONS.length);
// add extension element // add extension element
cborEncoder.startArray((short)2); cborEncoder.startArray((short) 2);
// add HMAC // add HMAC
// cborEncoder.encodeTextString(Utf8Strings.UTF8_HMAC, (short)0, (short)Utf8Strings.UTF8_HMAC.length ); // cborEncoder.encodeTextString(Utf8Strings.UTF8_HMAC, (short)0, (short)Utf8Strings.UTF8_HMAC.length );
cborEncoder.encodeByteString(idSecret.hmac, (short)0, (short)idSecret.hmac.length); cborEncoder.encodeByteString(idSecret.hmac, (short) 0, (short) idSecret.hmac.length);
// // add Cx // // add Cx
// cborEncoder.encodeTextString(Utf8Strings.UTF8_Cx, (short)0, (short)Utf8Strings.UTF8_Cx.length); // cborEncoder.encodeTextString(Utf8Strings.UTF8_Cx, (short)0, (short)Utf8Strings.UTF8_Cx.length);
cborEncoder.encodeByteString(idSecret.encryptedCx, (short)0, (short)idSecret.encryptedCx.length); cborEncoder.encodeByteString(idSecret.encryptedCx, (short) 0, (short) idSecret.encryptedCx.length);
/** /**
* end extension * end extension
*/ */
// We're actually done, send this out
sendLongChaining(apdu, cborEncoder.getCurrentOffset());
// We're actually done, send this out
sendLongChaining(apdu, cborEncoder.getCurrentOffset()); } else {
// Non-resident credential
} else { // TODO - we currently force resident credentials
// Non-resident credential returnError(apdu, CTAP2_ERR_UNSUPPORTED_OPTION);
// TODO - we currently force resident credentials }
returnError(apdu, CTAP2_ERR_UNSUPPORTED_OPTION);
} }
} public void authGetAssertion(APDU apdu, short bufLen) {
nextAssertion[0] = (short) 0;
public void authGetAssertion(APDU apdu, short bufLen) { // Decode the CBOR array for the assertion
nextAssertion[0] = (short) 0; cborDecoder.init(dataBuffer, (short) 1, bufLen);
// Decode the CBOR array for the assertion try {
cborDecoder.init(dataBuffer, (short) 1, bufLen); authenticatorGetAssertion = new AuthenticatorGetAssertion(cborDecoder);
try { } catch (UserException e) {
authenticatorGetAssertion = new AuthenticatorGetAssertion(cborDecoder); returnError(apdu, e.getReason());
} catch (UserException e) { return;
returnError(apdu, e.getReason()); }
return; // Match the assertion to the credential
} // Get a list of matching credentials
// Match the assertion to the credential assertionCredentials = findCredentials(apdu, authenticatorGetAssertion);
// Get a list of matching credentials // Use the first one; this complies with both ideas - use the most recent match
assertionCredentials = findCredentials(apdu, authenticatorGetAssertion); // if no allow list, use any if an allowing list existed
// Use the first one; this complies with both ideas - use the most recent match if (assertionCredentials.length == 0 || assertionCredentials[0] == null) {
// if no allow list, use any if an allowing list existed returnError(apdu, CTAP2_ERR_NO_CREDENTIALS);
if (assertionCredentials.length == 0 || assertionCredentials[0] == null) { return;
returnError(apdu, CTAP2_ERR_NO_CREDENTIALS); }
return; // Create the authenticatorData to sign
} sha256MessageDigest.doFinal(authenticatorGetAssertion.rpId, (short) 0, (short) authenticatorGetAssertion.rpId.length, scratch, (short) 0);
// Create the authenticatorData to sign if (authenticatorGetAssertion.options[1]) {
sha256MessageDigest.doFinal(authenticatorGetAssertion.rpId, (short) 0, (short) authenticatorGetAssertion.rpId.length, scratch, (short) 0); scratch[32] = 0x05;
if (authenticatorGetAssertion.options[1]) { } else {
scratch[32] = 0x05; scratch[32] = 0x01;
} else { }
scratch[32] = 0x01;
} assertionCredentials[0].readCounter(scratch, (short) 33);
// Copy the hash in
assertionCredentials[0].readCounter(scratch, (short) 33); authenticatorGetAssertion.getHash(scratch, (short) 37);
// Copy the hash in // Create the output
authenticatorGetAssertion.getHash(scratch, (short) 37);
// Create the output // Status flags first
dataBuffer[0] = 0x00;
// Status flags first // Create the encoder
dataBuffer[0] = 0x00; cborEncoder.init(dataBuffer, (short) 1, (short) 1199);
// Create the encoder // Determine if we need 4 or 5 in the array
cborEncoder.init(dataBuffer, (short) 1, (short) 1199); if (assertionCredentials.length > 1) {
// Determine if we need 4 or 5 in the array doAssertionCommon(cborEncoder, (short) 5);
if (assertionCredentials.length > 1) { } else {
doAssertionCommon(cborEncoder, (short) 5); doAssertionCommon(cborEncoder, (short) 4);
} else { }
doAssertionCommon(cborEncoder, (short) 4); nextAssertion[0] = (short) 1;
} // Emit this as a response
nextAssertion[0] = (short) 1; sendLongChaining(apdu, cborEncoder.getCurrentOffset());
// Emit this as a response }
sendLongChaining(apdu, cborEncoder.getCurrentOffset());
} /**
* Get the next assertion in a list of multiple.
/** *
* Get the next assertion in a list of multiple. * @param apdu apdu buffer
* * @param buffer buffer
* @param apdu apdu buffer */
* @param buffer buffer private void authGetNextAssertion(APDU apdu, byte[] buffer) {
*/ // Confirm that we have more assertions to do
private void authGetNextAssertion(APDU apdu, byte[] buffer) { if (nextAssertion[0] != (short) 0 && nextAssertion[0] < assertionCredentials.length) {
// Confirm that we have more assertions to do // Create the authenticatorData to sign
if (nextAssertion[0] != (short) 0 && nextAssertion[0] < assertionCredentials.length) { sha256MessageDigest.doFinal(authenticatorGetAssertion.rpId, (short) 0, (short) authenticatorGetAssertion.rpId.length, scratch, (short) 0);
// Create the authenticatorData to sign if (authenticatorGetAssertion.options[1]) {
sha256MessageDigest.doFinal(authenticatorGetAssertion.rpId, (short) 0, (short) authenticatorGetAssertion.rpId.length, scratch, (short) 0); scratch[32] = 0x05;
if (authenticatorGetAssertion.options[1]) { } else {
scratch[32] = 0x05; scratch[32] = 0x01;
} else { }
scratch[32] = 0x01; assertionCredentials[nextAssertion[0]].readCounter(scratch, (short) 33);
} // Copy the hash in
assertionCredentials[nextAssertion[0]].readCounter(scratch, (short) 33); authenticatorGetAssertion.getHash(scratch, (short) 37);
// Copy the hash in // Create the output
authenticatorGetAssertion.getHash(scratch, (short) 37);
// Create the output // Status flags first
dataBuffer[0] = 0x00;
// Status flags first // Create the encoder
dataBuffer[0] = 0x00; cborEncoder.init(dataBuffer, (short) 1, (short) 1199);
// Create the encoder doAssertionCommon(cborEncoder, (short) 4);
cborEncoder.init(dataBuffer, (short) 1, (short) 1199);
doAssertionCommon(cborEncoder, (short) 4); nextAssertion[0]++;
// Emit this as a response
nextAssertion[0]++; sendLongChaining(apdu, cborEncoder.getCurrentOffset());
// Emit this as a response }
sendLongChaining(apdu, cborEncoder.getCurrentOffset()); }
}
} // Process the AuthenticatorClientPin feature
// Note: we only implement the keyAgreement bit
// Process the AuthenticatorClientPin feature public void clientPin(APDU apdu, short bufferLength) {
// Note: we only implement the keyAgreement bit try {
public void clientPin(APDU apdu, short bufferLength) { cborDecoder.init(dataBuffer, (short) 1, bufferLength);
try { // Start reading
cborDecoder.init(dataBuffer, (short) 1, bufferLength); clientPINCommand.decodeCommand(cborDecoder);
// Start reading
clientPINCommand.decodeCommand(cborDecoder); switch (clientPINCommand.getSubCommandCode()) {
case SUBCOMMAND_GET_PIN_RETRIES:
switch(clientPINCommand.getSubCommandCode()){ dataBuffer[0] = CTAP1_ERR_SUCCESS; // 0x00 : response success code
case SUBCOMMAND_GET_PIN_RETRIES: cborEncoder.init(dataBuffer, (short) 1, (short) (1199));
dataBuffer[0] = CTAP1_ERR_SUCCESS; // 0x00 : response success code cborEncoder.startMap((short) 1);
cborEncoder.init(dataBuffer, (short)1, (short)(1199)); cborEncoder.encodeUInt8(ClientPINResponse.PIN_RETRIES);
cborEncoder.startMap((short)1); cborEncoder.encodeUInt8(pinRetries);
cborEncoder.encodeUInt8(ClientPINResponse.PIN_RETRIES); sendLongChaining(apdu, cborEncoder.getCurrentOffset());
cborEncoder.encodeUInt8(pinRetries); break;
sendLongChaining(apdu, cborEncoder.getCurrentOffset()); case SUBCOMMAND_GET_KEY_AGREEMENT:
break; dataBuffer[0] = CTAP1_ERR_SUCCESS; // 0x00 : response success code
case SUBCOMMAND_GET_KEY_AGREEMENT: cborEncoder.init(dataBuffer, (short) 1, (short) 1199);
dataBuffer[0] = CTAP1_ERR_SUCCESS; // 0x00 : response success code // Start a map
cborEncoder.init(dataBuffer, (short) 1, (short) 1199); cborEncoder.startMap((short) 1);
// Start a map // Encode the COSE key identifier
cborEncoder.startMap((short) 1); cborEncoder.encodeUInt8((byte) 0x01);
// Encode the COSE key identifier // Start the COSE map
cborEncoder.encodeUInt8((byte) 0x01); cborEncoder.startMap((short) 5);
// Start the COSE map // Kty tag
cborEncoder.startMap((short) 5); cborEncoder.encodeUInt8((byte) 0x01);
// Kty tag // Kty value - EC2
cborEncoder.encodeUInt8((byte) 0x01); cborEncoder.encodeUInt8((byte) 0x02);
// Kty value - EC2 // Alg tag
cborEncoder.encodeUInt8((byte) 0x02); cborEncoder.encodeUInt8((byte) 0x03);
// Alg tag // Alg value - ES256 (-7, 6 in negative format)
cborEncoder.encodeUInt8((byte) 0x03); // Alg value - ECDH (-25, 24 in negative format)
// Alg value - ES256 (-7, 6 in negative format) cborEncoder.encodeNegativeUInt8((byte) 0x18);
// Alg value - ECDH (-25, 24 in negative format) // Crv tag - negative
cborEncoder.encodeNegativeUInt8((byte) 0x18); cborEncoder.encodeNegativeUInt8((byte) 0x00);
// Crv tag - negative // Crv value - P-256
cborEncoder.encodeNegativeUInt8((byte) 0x00); cborEncoder.encodeUInt8((byte) 0x01);
// Crv value - P-256 // X-coord tag
cborEncoder.encodeUInt8((byte) 0x01); cborEncoder.encodeNegativeUInt8((byte) 0x01);
// X-coord tag // X-coord value
cborEncoder.encodeNegativeUInt8((byte) 0x01); cborEncoder.encodeByteString(pinUvAuthProtocolOne.getPublicKey(), (short) 1, (short) 32); // the first byte is 0x04, it means the key is uncompressed
// X-coord value // Y-coord tag
cborEncoder.encodeByteString(pinUvAuthProtocolOne.getPublicKey(), (short) 1, (short) 32); // the first byte is 0x04, it means the key is uncompressed cborEncoder.encodeNegativeUInt8((byte) 0x02);
// Y-coord tag // Y-coord value
cborEncoder.encodeNegativeUInt8((byte) 0x02); cborEncoder.encodeByteString(pinUvAuthProtocolOne.getPublicKey(), (short) 33, (short) 32);
// Y-coord value // That's it
cborEncoder.encodeByteString(pinUvAuthProtocolOne.getPublicKey(), (short) 33, (short) 32); sendLongChaining(apdu, cborEncoder.getCurrentOffset());
// That's it break;
sendLongChaining(apdu, cborEncoder.getCurrentOffset()); case SUBCOMMAND_SET_PIN:
break; byte[] paddedPin = pinUvAuthProtocolOne.decrypt(
case SUBCOMMAND_SET_PIN: pinUvAuthProtocolOne.ecdh(clientPINCommand.getKeyAgreement()),
byte[] paddedPin = pinUvAuthProtocolOne.decrypt( clientPINCommand.getNewPinEnc()
pinUvAuthProtocolOne.ecdh(clientPINCommand.getKeyAgreement()), );
clientPINCommand.getNewPinEnc()
); for (short i = 0; i < (short) paddedPin.length; i++) {
if (paddedPin[i] == 0x00) {
for(short i = 0 ; i < (short)paddedPin.length ; i ++){ pinLength = i;
if(paddedPin[i] == 0x00) { break;
pinLength = i; }
break; }
}
} pin = new byte[pinLength];
pin = new byte[pinLength]; Util.arrayCopy(paddedPin, (short) 0, pin, (short) 0, (short) pin.length);
Util.arrayCopy(paddedPin, (short)0, pin, (short)0, (short)pin.length);
// idSecret.writeTempBuffer(pin, (short)0); // idSecret.writeTempBuffer(pin, (short)0);
byte[] hashedPin = pinUvAuthProtocolOne.hashPin(pin); byte[] hashedPin = pinUvAuthProtocolOne.hashPin(pin);
Util.arrayCopy(hashedPin, (short)0, currentStoredPIN, (short)0, (short)16); Util.arrayCopy(hashedPin, (short) 0, currentStoredPIN, (short) 0, (short) 16);
// idSecret.writeTempBuffer(currentStoredPIN, (short)10); // idSecret.writeTempBuffer(currentStoredPIN, (short)10);
isClientPinSet = true; isClientPinSet = true;
fidoInfo = null; fidoInfo = null;
pinRetries = MAX_PIN_RETRIES; pinRetries = MAX_PIN_RETRIES;
JCSystem.requestObjectDeletion(); JCSystem.requestObjectDeletion();
break; break;
case SUBCOMMAND_CHANGE_PIN: case SUBCOMMAND_CHANGE_PIN:
break; break;
case SUBCOMMAND_GET_PIN_TOKEN: case SUBCOMMAND_GET_PIN_TOKEN:
byte[] hashedPin_leftHalf; byte[] hashedPin_leftHalf;
byte[] sharedSecret = pinUvAuthProtocolOne.ecdh(clientPINCommand.getKeyAgreement()); byte[] sharedSecret = pinUvAuthProtocolOne.ecdh(clientPINCommand.getKeyAgreement());
hashedPin_leftHalf = pinUvAuthProtocolOne.decryptHashedPin( hashedPin_leftHalf = pinUvAuthProtocolOne.decryptHashedPin(
sharedSecret, clientPINCommand.getPinHashEnc() sharedSecret, clientPINCommand.getPinHashEnc()
); );
// idSecret.writeTempBuffer(hashedPin_leftHalf, (short)36); // idSecret.writeTempBuffer(hashedPin_leftHalf, (short)36);
for(short i = 0; i < (short)hashedPin_leftHalf.length ; i++){ for (short i = 0; i < (short) hashedPin_leftHalf.length; i++) {
if(hashedPin_leftHalf[i] != currentStoredPIN[i]){ if (hashedPin_leftHalf[i] != currentStoredPIN[i]) {
pinRetries--; pinRetries--;
UserException.throwIt(CTAP2_ERR_PIN_INVALID); UserException.throwIt(CTAP2_ERR_PIN_INVALID);
break; break;
} }
} }
RandomData r = Random.getInstance(); RandomData r = Random.getInstance();
r.nextBytes(pinToken, (short)0, (short)pinToken.length); r.nextBytes(pinToken, (short) 0, (short) pinToken.length);
// idSecret.writeTempBuffer(pinToken, (short) 0); // idSecret.writeTempBuffer(pinToken, (short) 0);
byte[] pinTokenEnc = pinUvAuthProtocolOne.encrypt(sharedSecret, pinToken); byte[] pinTokenEnc = pinUvAuthProtocolOne.encrypt(sharedSecret, pinToken);
dataBuffer[0] = CTAP1_ERR_SUCCESS; dataBuffer[0] = CTAP1_ERR_SUCCESS;
cborEncoder.init(dataBuffer, (short)1, (short)1199); cborEncoder.init(dataBuffer, (short) 1, (short) 1199);
cborEncoder.startMap((short)1); cborEncoder.startMap((short) 1);
cborEncoder.encodeUInt8((byte)0x02); cborEncoder.encodeUInt8((byte) 0x02);
cborEncoder.encodeByteString(pinTokenEnc, (short)0, (short)32); cborEncoder.encodeByteString(pinTokenEnc, (short) 0, (short) 32);
sendLongChaining(apdu, cborEncoder.getCurrentOffset()); sendLongChaining(apdu, cborEncoder.getCurrentOffset());
break; break;
case SUBCOMMAND_GET_PIN_UV_AUTH_TOKEN_UV: case SUBCOMMAND_GET_PIN_UV_AUTH_TOKEN_UV:
break; break;
case SUBCOMMAND_GET_UV_RETRIES: case SUBCOMMAND_GET_UV_RETRIES:
break; break;
case SUBCOMMAND_GET_PIN_UV_AUTH_TOKEN_PIN: case SUBCOMMAND_GET_PIN_UV_AUTH_TOKEN_PIN:
break; break;
} }
} catch (UserException e) { } catch (UserException e) {
returnError(apdu, e.getReason()); returnError(apdu, e.getReason());
} }
} }
private void addResident(APDU apdu, StoredCredential cred) { private void addResident(APDU apdu, StoredCredential cred) {
// Add a Discoverable Credential (resident) // Add a Discoverable Credential (resident)
try { try {
credentialArray.addCredential(cred); credentialArray.addCredential(cred);
} catch (UserException e) { } catch (UserException e) {
returnError(apdu, e.getReason()); returnError(apdu, e.getReason());
} }
} }
/** /**
* Finds all credentials scoped to the RpId, and optionally the allowList, in * Finds all credentials scoped to the RpId, and optionally the allowList, in
* assertion * assertion
* *
* @param apdu the APDU to send through for errors * @param apdu the APDU to send through for errors
* @param assertion the assertion CTAP object * @param assertion the assertion CTAP object
* @return an array of StoredCredential objects, null if none matched. * @return an array of StoredCredential objects, null if none matched.
*/ */
private StoredCredential[] findCredentials(APDU apdu, AuthenticatorGetAssertion assertion) { private StoredCredential[] findCredentials(APDU apdu, AuthenticatorGetAssertion assertion) {
StoredCredential[] list; StoredCredential[] list;
StoredCredential temp; StoredCredential temp;
if (assertion.hasAllow()) { if (assertion.hasAllow()) {
// Our list can be no bigger than the allowList // Our list can be no bigger than the allowList
list = new StoredCredential[(short) assertion.allow.length]; list = new StoredCredential[(short) assertion.allow.length];
tempVars[6] = 0; tempVars[6] = 0;
for (tempVars[7] = (short) (credentialArray.getLength() - 1); tempVars[7] >= 0; tempVars[7]--) { for (tempVars[7] = (short) (credentialArray.getLength() - 1); tempVars[7] >= 0; tempVars[7]--) {
temp = credentialArray.get(tempVars[7]); temp = credentialArray.get(tempVars[7]);
// Check if null or doesn't match rpId // Check if null or doesn't match rpId
if (temp != null && temp.rpEntity.checkId(assertion.rpId, (short) 0, (short) assertion.rpId.length)) { if (temp != null && temp.rpEntity.checkId(assertion.rpId, (short) 0, (short) assertion.rpId.length)) {
for (tempVars[5] = 0; tempVars[5] < (short) assertion.allow.length; tempVars[5]++) { for (tempVars[5] = 0; tempVars[5] < (short) assertion.allow.length; tempVars[5]++) {
// Check the list // Check the list
// Does length match? // Does length match?
if ((short) assertion.allow[tempVars[5]].id.length != (short) temp.credentialId.length) { if ((short) assertion.allow[tempVars[5]].id.length != (short) temp.credentialId.length) {
continue; continue;
} }
if (Util.arrayCompare(assertion.allow[tempVars[5]].id, (short) 0, temp.credentialId, (short) 0, if (Util.arrayCompare(assertion.allow[tempVars[5]].id, (short) 0, temp.credentialId, (short) 0,
(short) temp.credentialId.length) == 0) { (short) temp.credentialId.length) == 0) {
// Add it to the list // Add it to the list
list[tempVars[6]++] = temp; list[tempVars[6]++] = temp;
} }
} }
} }
} }
} else { } else {
// Old code path, works fine for me // Old code path, works fine for me
list = new StoredCredential[credentialArray.getLength()]; list = new StoredCredential[credentialArray.getLength()];
tempVars[6] = 0; tempVars[6] = 0;
for (tempVars[7] = (short) (credentialArray.getLength() - 1); tempVars[7] >= 0; tempVars[7]--) { for (tempVars[7] = (short) (credentialArray.getLength() - 1); tempVars[7] >= 0; tempVars[7]--) {
temp = credentialArray.get(tempVars[7]); temp = credentialArray.get(tempVars[7]);
// Check for null or doesn't match rpId // Check for null or doesn't match rpId
if (temp != null && temp.rpEntity.checkId(assertion.rpId, (short) 0, (short) assertion.rpId.length)) { if (temp != null && temp.rpEntity.checkId(assertion.rpId, (short) 0, (short) assertion.rpId.length)) {
// Then valid // Then valid
list[tempVars[6]++] = temp; list[tempVars[6]++] = temp;
} }
} }
} }
// Trim the list // Trim the list
StoredCredential[] ret = new StoredCredential[tempVars[6]]; StoredCredential[] ret = new StoredCredential[tempVars[6]];
// Trim // Trim
for (tempVars[7] = 0; tempVars[7] < tempVars[6]; tempVars[7]++) { for (tempVars[7] = 0; tempVars[7] < tempVars[6]; tempVars[7]++) {
ret[tempVars[7]] = list[tempVars[7]]; ret[tempVars[7]] = list[tempVars[7]];
} }
// Null out the unused stuff // Null out the unused stuff
JCSystem.requestObjectDeletion(); JCSystem.requestObjectDeletion();
return ret; return ret;
} }
/** /**
* Check if anything in the list is present * Check if anything in the list is present
* *
* @param list list * @param list list
* @return if is present * @return if is present
*/ */
private boolean isPresent(PublicKeyCredentialDescriptor[] list) { private boolean isPresent(PublicKeyCredentialDescriptor[] list) {
StoredCredential temp; StoredCredential temp;
for (tempVars[7] = (short) 0; tempVars[7] < credentialArray.getLength(); tempVars[7]++) { for (tempVars[7] = (short) 0; tempVars[7] < credentialArray.getLength(); tempVars[7]++) {
temp = credentialArray.get(tempVars[7]); temp = credentialArray.get(tempVars[7]);
if (temp == null) { if (temp == null) {
continue; continue;
} }
for (tempVars[6] = (short) 0; tempVars[6] < (short) list.length; tempVars[6]++) { for (tempVars[6] = (short) 0; tempVars[6] < (short) list.length; tempVars[6]++) {
if (temp.checkId(list[tempVars[6]].id, (short) 0, (short) list[tempVars[6]].id.length)) { if (temp.checkId(list[tempVars[6]].id, (short) 0, (short) list[tempVars[6]].id.length)) {
return true; return true;
} }
} }
} }
return false; return false;
} }
/** /**
* Reset the authenticator. This doesn't actually take much. * Reset the authenticator. This doesn't actually take much.
* checking. This is just so testing doesn't crap out. * checking. This is just so testing doesn't crap out.
*/ */
private void doReset(APDU apdu) { private void doReset(APDU apdu) {
// TODO: Implement Resetting // TODO: Implement Resetting
credentialArray = new CredentialArray((short) 5); credentialArray = new CredentialArray((short) 5);
JCSystem.requestObjectDeletion(); JCSystem.requestObjectDeletion();
returnError(apdu, CTAP1_ERR_SUCCESS); returnError(apdu, CTAP1_ERR_SUCCESS);
} }
/** /**
* Return an error via APDU - an error on the FIDO2 side is considered a success * Return an error via APDU - an error on the FIDO2 side is considered a success
* in APDU-land ,so we send a response. * in APDU-land ,so we send a response.
* *
* @param apdu shared APDU object * @param apdu shared APDU object
* @param err error code * @param err error code
*/ */
public void returnError(APDU apdu, byte err) { public void returnError(APDU apdu, byte err) {
byte[] buffer = apdu.getBuffer(); byte[] buffer = apdu.getBuffer();
buffer[0] = err; buffer[0] = err;
apdu.setOutgoingAndSend((short) 0, (short) 1); apdu.setOutgoingAndSend((short) 0, (short) 1);
} }
/** /**
* Return an error via APDU - an error on the FIDO2 side is considered a success * Return an error via APDU - an error on the FIDO2 side is considered a success
* in APDU-land ,so we send a response. * in APDU-land ,so we send a response.
* *
* @param apdu shared APDU object * @param apdu shared APDU object
* @param err error code * @param err error code
*/ */
public void returnError(APDU apdu, short err) { public void returnError(APDU apdu, short err) {
byte[] buffer = apdu.getBuffer(); byte[] buffer = apdu.getBuffer();
// Get the low byte of the error. // Get the low byte of the error.
Util.setShort(buffer, (short) 0, err); Util.setShort(buffer, (short) 0, err);
apdu.setOutgoingAndSend((short) 1, (short) 1); apdu.setOutgoingAndSend((short) 1, (short) 1);
} }
/** /**
* Get authenticator-specific information, and return it to the platform. * Get authenticator-specific information, and return it to the platform.
* *
* @param apdu apdu buffer * @param apdu apdu buffer
*/ */
private void authGetInfo(APDU apdu) { private void authGetInfo(APDU apdu) {
// Create the authenticator info if not present. // Create the authenticator info if not present.
if (fidoInfo == null) { if (fidoInfo == null) {
// Create the authGetInfo - 0x00 is success // Create the authGetInfo - 0x00 is success
dataBuffer[0] = 0x00; dataBuffer[0] = 0x00;
cborEncoder.init(dataBuffer, (short) 1, (short) 1199); cborEncoder.init(dataBuffer, (short) 1, (short) 1199);
cborEncoder.startMap((short) 8); cborEncoder.startMap((short) 7);
// 0x01, versions // 0x01, versions
cborEncoder.encodeUInt8((byte) 0x01); cborEncoder.encodeUInt8((byte) 0x01);
// Value is an array of strings // Value is an array of strings
cborEncoder.startArray((short) 2); cborEncoder.startArray((short) 1);
// Type 1, FIDO2 // Type 1, FIDO2
cborEncoder.encodeTextString(Utf8Strings.UTF8_FIDO2, (short) 0, (short) Utf8Strings.UTF8_FIDO2.length); cborEncoder.encodeTextString(Utf8Strings.UTF8_FIDO_2_0, (short) 0, (short) Utf8Strings.UTF8_FIDO_2_0.length);
cborEncoder.encodeTextString(Utf8Strings.UTF8_FIDO_2_1_PRE, (short) 0, (short) Utf8Strings.UTF8_FIDO_2_1_PRE.length); // cborEncoder.encodeTextString(Utf8Strings.UTF8_FIDO_2_1_PRE, (short) 0, (short) Utf8Strings.UTF8_FIDO_2_1_PRE.length);
// 0x02, Extensions // 0x02, Extensions
cborEncoder.encodeUInt8((byte) 0x02); // cborEncoder.encodeUInt8((byte) 0x02);
cborEncoder.startArray((short) 2); // cborEncoder.startArray((short) 2);
cborEncoder.encodeTextString(Utf8Strings.UTF8_credProtect, (short)0, (short)Utf8Strings.UTF8_credProtect.length); // cborEncoder.encodeTextString(Utf8Strings.UTF8_credProtect, (short)0, (short)Utf8Strings.UTF8_credProtect.length);
cborEncoder.encodeTextString(Utf8Strings.UTF8_hmac_secret, (short)0, (short)Utf8Strings.UTF8_hmac_secret.length); // cborEncoder.encodeTextString(Utf8Strings.UTF8_hmac_secret, (short)0, (short)Utf8Strings.UTF8_hmac_secret.length);
// cborEncoder.encodeTextString(Utf8Strings.UTF8_PRLab, (short)0, (short)Utf8Strings.UTF8_PRLab.length); // cborEncoder.encodeTextString(Utf8Strings.UTF8_PRLab, (short)0, (short)Utf8Strings.UTF8_PRLab.length);
// 0x03, AAGUID, // 0x03, AAGUID,
cborEncoder.encodeUInt8((byte) 0x03); cborEncoder.encodeUInt8((byte) 0x03);
cborEncoder.encodeByteString(aaguid, (short) 0, (short) 16); cborEncoder.encodeByteString(aaguid, (short) 0, (short) 16);
// 0x04, Options, // 0x04, Options,
cborEncoder.encodeUInt8((byte) 0x04); cborEncoder.encodeUInt8((byte) 0x04);
// Map of 3 // Map of 3
cborEncoder.startMap((short) 5); cborEncoder.startMap((short) 4);
// Rk // Rk
cborEncoder.encodeTextString(Utf8Strings.UTF8_RK, (short) 0, (short)Utf8Strings.UTF8_RK.length); cborEncoder.encodeTextString(Utf8Strings.UTF8_RK, (short) 0, (short) Utf8Strings.UTF8_RK.length);
cborEncoder.encodeBoolean(true); cborEncoder.encodeBoolean(true);
// UP // UP
cborEncoder.encodeTextString(Utf8Strings.UTF8_UP, (short) 0, (short)Utf8Strings.UTF8_UP.length); cborEncoder.encodeTextString(Utf8Strings.UTF8_UP, (short) 0, (short) Utf8Strings.UTF8_UP.length);
cborEncoder.encodeBoolean(true); cborEncoder.encodeBoolean(true);
// // UV // // UV
// cborEncoder.encodeTextString(Utf8Strings.UTF8_UV, (short) 0, (short)Utf8Strings.UTF8_UV.length); // cborEncoder.encodeTextString(Utf8Strings.UTF8_UV, (short) 0, (short)Utf8Strings.UTF8_UV.length);
// cborEncoder.encodeBoolean(true); // cborEncoder.encodeBoolean(true);
// plat // plat
cborEncoder.encodeTextString(Utf8Strings.UTF8_plat, (short) 0, (short)Utf8Strings.UTF8_plat.length); cborEncoder.encodeTextString(Utf8Strings.UTF8_plat, (short) 0, (short) Utf8Strings.UTF8_plat.length);
cborEncoder.encodeBoolean(false); cborEncoder.encodeBoolean(false);
// clientPin // clientPin
cborEncoder.encodeTextString(Utf8Strings.UTF8_CLIENT_PIN, (short) 0, (short)Utf8Strings.UTF8_CLIENT_PIN.length); cborEncoder.encodeTextString(Utf8Strings.UTF8_CLIENT_PIN, (short) 0, (short) Utf8Strings.UTF8_CLIENT_PIN.length);
cborEncoder.encodeBoolean(isClientPinSet); cborEncoder.encodeBoolean(isClientPinSet);
// credentialMgmtPreview // credentialMgmtPreview
cborEncoder.encodeTextString(Utf8Strings.UTF8_CREDENTIAL_MGMT_PREVIEW, (short) 0, (short)Utf8Strings.UTF8_CREDENTIAL_MGMT_PREVIEW.length); // cborEncoder.encodeTextString(Utf8Strings.UTF8_CREDENTIAL_MGMT_PREVIEW, (short) 0, (short)Utf8Strings.UTF8_CREDENTIAL_MGMT_PREVIEW.length);
cborEncoder.encodeBoolean(true); // cborEncoder.encodeBoolean(true);
// Max msg size, 0x05 // Max msg size, 0x05
cborEncoder.encodeUInt8((byte) 0x05); cborEncoder.encodeUInt8((byte) 0x05);
cborEncoder.encodeUInt16((short) 1200); cborEncoder.encodeUInt16((short) 1200);
// pin Protocols, 0x06 // pin Protocols, 0x06
cborEncoder.encodeUInt8((byte) 0x06); cborEncoder.encodeUInt8((byte) 0x06);
cborEncoder.startArray((short)0x01); cborEncoder.startArray((short) 0x01);
cborEncoder.encodeUInt8((byte) 0x01); cborEncoder.encodeUInt8((byte) 0x01);
// transports, 0x09 // transports, 0x09
cborEncoder.encodeUInt8((byte) 0x09); cborEncoder.encodeUInt8((byte) 0x09);
cborEncoder.startArray((short)0x02); cborEncoder.startArray((short) 0x01);
cborEncoder.encodeTextString(Utf8Strings.UTF8_nfc, (short) 0, (short)Utf8Strings.UTF8_nfc.length); cborEncoder.encodeTextString(Utf8Strings.UTF8_nfc, (short) 0, (short) Utf8Strings.UTF8_nfc.length);
cborEncoder.encodeTextString(Utf8Strings.UTF8_usb, (short) 0, (short)Utf8Strings.UTF8_usb.length); // cborEncoder.encodeTextString(Utf8Strings.UTF8_usb, (short) 0, (short)Utf8Strings.UTF8_usb.length);
// minPINLength, 0x0D // minPINLength, 0x0D
cborEncoder.encodeUInt8((byte) 0x0D); cborEncoder.encodeUInt8((byte) 0x0D);
cborEncoder.encodeUInt8((byte) 0x04); cborEncoder.encodeUInt8((byte) 0x04);
// Done // Done
JCSystem.beginTransaction(); JCSystem.beginTransaction();
fidoInfo = new byte[cborEncoder.getCurrentOffset()]; fidoInfo = new byte[cborEncoder.getCurrentOffset()];
Util.arrayCopy(dataBuffer, (short) 0, fidoInfo, (short) 0, cborEncoder.getCurrentOffset()); Util.arrayCopy(dataBuffer, (short) 0, fidoInfo, (short) 0, cborEncoder.getCurrentOffset());
JCSystem.commitTransaction(); JCSystem.commitTransaction();
} }
// Send it // Send it
Util.arrayCopyNonAtomic(fidoInfo, (short) 0, dataBuffer, (short) 0, (short) fidoInfo.length); Util.arrayCopyNonAtomic(fidoInfo, (short) 0, dataBuffer, (short) 0, (short) fidoInfo.length);
sendLongChaining(apdu, (short) fidoInfo.length); sendLongChaining(apdu, (short) fidoInfo.length);
} }
/** /**
* Covers the common assertion building process. * Covers the common assertion building process.
* *
* @param encoder CBOR Encoder * @param encoder CBOR Encoder
* @param mapLength Map Length * @param mapLength Map Length
*/ */
private void doAssertionCommon(CBOREncoder encoder, short mapLength) { private void doAssertionCommon(CBOREncoder encoder, short mapLength) {
// Determine if we need 4 or 5 in the array // Determine if we need 4 or 5 in the array
if (mapLength == 4) { if (mapLength == 4) {
encoder.startMap((short) 4); encoder.startMap((short) 4);
} else { } else {
encoder.startMap((short) 5); encoder.startMap((short) 5);
} }
// Tag 1, credential data // Tag 1, credential data
encoder.encodeUInt8((byte) 0x01); encoder.encodeUInt8((byte) 0x01);
// Start a map, which is all the PublicKeyCredentialDescriptor is // Start a map, which is all the PublicKeyCredentialDescriptor is
encoder.startMap((short) 2); encoder.startMap((short) 2);
// Put the id key // Put the id key
cborEncoder.encodeTextString(Utf8Strings.UTF8_ID, (short) 0, (short) 2); cborEncoder.encodeTextString(Utf8Strings.UTF8_ID, (short) 0, (short) 2);
// Put the value, which is a byte array // Put the value, which is a byte array
cborEncoder.encodeByteString(assertionCredentials[nextAssertion[0]].credentialId, (short) 0, cborEncoder.encodeByteString(assertionCredentials[nextAssertion[0]].credentialId, (short) 0,
(short) assertionCredentials[nextAssertion[0]].credentialId.length); (short) assertionCredentials[nextAssertion[0]].credentialId.length);
// Put the key for the type // Put the key for the type
cborEncoder.encodeTextString(Utf8Strings.UTF8_TYPE, (short) 0, (short) 4); cborEncoder.encodeTextString(Utf8Strings.UTF8_TYPE, (short) 0, (short) 4);
// Put the value // Put the value
cborEncoder.encodeTextString(Utf8Strings.UTF8_PUBLIC_KEY, (short) 0, (short) 10); cborEncoder.encodeTextString(Utf8Strings.UTF8_PUBLIC_KEY, (short) 0, (short) 10);
// Done with tag 1 // Done with tag 1
cborEncoder.encodeUInt8((byte) 0x02); cborEncoder.encodeUInt8((byte) 0x02);
// Tag 2, which is the Authenticator bindings data (turns out this is excluding // Tag 2, which is the Authenticator bindings data (turns out this is excluding
// the clientDataHash) // the clientDataHash)
cborEncoder.encodeByteString(scratch, (short) 0, (short) 37); cborEncoder.encodeByteString(scratch, (short) 0, (short) 37);
// Tag 3, the signature of said data // Tag 3, the signature of said data
// Put the tag in // Put the tag in
cborEncoder.encodeUInt8((byte) 0x03); cborEncoder.encodeUInt8((byte) 0x03);
// Turns out this is DER encoding, again // Turns out this is DER encoding, again
// Sign the data // Sign the data
tempVars[3] = assertionCredentials[nextAssertion[0]].performSignature(scratch, (short) 0, (short) 69, scratch, tempVars[3] = assertionCredentials[nextAssertion[0]].performSignature(scratch, (short) 0, (short) 69, scratch,
(short) 69); (short) 69);
// Create the ByteString to put it into // Create the ByteString to put it into
cborEncoder.encodeByteString(scratch, (short) 69, tempVars[3]); cborEncoder.encodeByteString(scratch, (short) 69, tempVars[3]);
// Tag 4, user details // Tag 4, user details
cborEncoder.encodeUInt8((byte) 0x04); cborEncoder.encodeUInt8((byte) 0x04);
// Start the PublicKeyCredentialUserEntity map // Start the PublicKeyCredentialUserEntity map
// If we have "UV" enabled, then we do all the info we have. // If we have "UV" enabled, then we do all the info we have.
if (authenticatorGetAssertion.options[1]) { if (authenticatorGetAssertion.options[1]) {
cborEncoder.startMap(assertionCredentials[nextAssertion[0]].userEntity.numData); cborEncoder.startMap(assertionCredentials[nextAssertion[0]].userEntity.numData);
// We need to check what we have for users // We need to check what we have for users
// Iterate over the bit flags // Iterate over the bit flags
boolean[] usrFlags = assertionCredentials[nextAssertion[0]].getPresentUser(); boolean[] usrFlags = assertionCredentials[nextAssertion[0]].getPresentUser();
// This actually // This actually
if (usrFlags[2]) { if (usrFlags[2]) {
// Has the 'displayName' tag // Has the 'displayName' tag
cborEncoder.encodeTextString(Utf8Strings.UTF8_DISPLAYNAME, (short) 0, (short) 11); cborEncoder.encodeTextString(Utf8Strings.UTF8_DISPLAYNAME, (short) 0, (short) 11);
cborEncoder.encodeTextString(assertionCredentials[nextAssertion[0]].userEntity.displayName.str, (short) 0, cborEncoder.encodeTextString(assertionCredentials[nextAssertion[0]].userEntity.displayName.str, (short) 0,
assertionCredentials[nextAssertion[0]].userEntity.displayName.len); assertionCredentials[nextAssertion[0]].userEntity.displayName.len);
} }
if (usrFlags[1]) { if (usrFlags[1]) {
// The 'id' tag // The 'id' tag
cborEncoder.encodeTextString(Utf8Strings.UTF8_ID, (short) 0, (short) 2); cborEncoder.encodeTextString(Utf8Strings.UTF8_ID, (short) 0, (short) 2);
cborEncoder.encodeByteString(assertionCredentials[nextAssertion[0]].userEntity.id, (short) 0, cborEncoder.encodeByteString(assertionCredentials[nextAssertion[0]].userEntity.id, (short) 0,
(short) assertionCredentials[nextAssertion[0]].userEntity.id.length); (short) assertionCredentials[nextAssertion[0]].userEntity.id.length);
} }
if (usrFlags[0]) { if (usrFlags[0]) {
// The 'name' // The 'name'
cborEncoder.encodeTextString(Utf8Strings.UTF8_NAME, (short) 0, (short) 4); cborEncoder.encodeTextString(Utf8Strings.UTF8_NAME, (short) 0, (short) 4);
cborEncoder.encodeTextString(assertionCredentials[nextAssertion[0]].userEntity.name.str, (short) 0, cborEncoder.encodeTextString(assertionCredentials[nextAssertion[0]].userEntity.name.str, (short) 0,
assertionCredentials[nextAssertion[0]].userEntity.name.len); assertionCredentials[nextAssertion[0]].userEntity.name.len);
} }
if (usrFlags[3]) { if (usrFlags[3]) {
// Has the 'icon' tag // Has the 'icon' tag
cborEncoder.encodeTextString(Utf8Strings.UTF8_ICON, (short) 0, (short) 4); cborEncoder.encodeTextString(Utf8Strings.UTF8_ICON, (short) 0, (short) 4);
cborEncoder.encodeTextString(assertionCredentials[nextAssertion[0]].userEntity.icon, (short) 0, cborEncoder.encodeTextString(assertionCredentials[nextAssertion[0]].userEntity.icon, (short) 0,
(short) assertionCredentials[nextAssertion[0]].userEntity.icon.length); (short) assertionCredentials[nextAssertion[0]].userEntity.icon.length);
} }
} else { } else {
// UV not enabled. Don't send extra info apart from the id field // UV not enabled. Don't send extra info apart from the id field
cborEncoder.startMap((short) 1); cborEncoder.startMap((short) 1);
cborEncoder.encodeTextString(Utf8Strings.UTF8_ID, (short) 0, (short) 2); cborEncoder.encodeTextString(Utf8Strings.UTF8_ID, (short) 0, (short) 2);
cborEncoder.encodeByteString(assertionCredentials[nextAssertion[0]].userEntity.id, (short) 0, cborEncoder.encodeByteString(assertionCredentials[nextAssertion[0]].userEntity.id, (short) 0,
(short) assertionCredentials[nextAssertion[0]].userEntity.id.length); (short) assertionCredentials[nextAssertion[0]].userEntity.id.length);
} }
// Done tag 4 // Done tag 4
if (mapLength == 5) { if (mapLength == 5) {
cborEncoder.encodeUInt8((byte) 0x05); cborEncoder.encodeUInt8((byte) 0x05);
cborEncoder.encodeUInt8((byte) assertionCredentials.length); cborEncoder.encodeUInt8((byte) assertionCredentials.length);
} }
} }
// There's only so many ways to do this. // There's only so many ways to do this.
public static boolean isCommandChainingCLA(APDU apdu) { public static boolean isCommandChainingCLA(APDU apdu) {
byte[] buf = apdu.getBuffer(); byte[] buf = apdu.getBuffer();
// return true if bit4 is 1 in CLA // return true if bit4 is 1 in CLA
return ((byte) (buf[0] & (byte) 0x10) == (byte) 0x10); return ((byte) (buf[0] & (byte) 0x10) == (byte) 0x10);
} }
/** /**
* Gets 256 or fewer bytes from inBuf. * Gets 256 or fewer bytes from inBuf.
* *
* @param apdu apdu buffer * @param apdu apdu buffer
*/ */
public void getData(APDU apdu) { public void getData(APDU apdu) {
if (outChainRam[0] > 256) { if (outChainRam[0] > 256) {
// More to go after this // More to go after this
outChainRam[0] -= 256; outChainRam[0] -= 256;
byte[] buf = apdu.getBuffer(); byte[] buf = apdu.getBuffer();
Util.arrayCopyNonAtomic(dataBuffer, outChainRam[1], buf, (short) 0, (short) 256); Util.arrayCopyNonAtomic(dataBuffer, outChainRam[1], buf, (short) 0, (short) 256);
apdu.setOutgoingAndSend((short) 0, (short) 256); apdu.setOutgoingAndSend((short) 0, (short) 256);
outChainRam[1] += 256; outChainRam[1] += 256;
if (outChainRam[0] > 255) { if (outChainRam[0] > 255) {
// At least 256 to go, so 256 more // At least 256 to go, so 256 more
ISOException.throwIt((short) 0x6100); ISOException.throwIt((short) 0x6100);
} else { } else {
// Less than, so say how many bytes are left. // Less than, so say how many bytes are left.
ISOException.throwIt(Util.makeShort((byte) 0x61, (byte) outChainRam[0])); ISOException.throwIt(Util.makeShort((byte) 0x61, (byte) outChainRam[0]));
} }
} else { } else {
// This is the last message // This is the last message
byte[] buf = apdu.getBuffer(); byte[] buf = apdu.getBuffer();
Util.arrayCopyNonAtomic(dataBuffer, outChainRam[1], buf, (short) 0, outChainRam[0]); Util.arrayCopyNonAtomic(dataBuffer, outChainRam[1], buf, (short) 0, outChainRam[0]);
apdu.setOutgoingAndSend((short) 0, outChainRam[0]); apdu.setOutgoingAndSend((short) 0, outChainRam[0]);
isOutChaining[0] = false; isOutChaining[0] = false;
outChainRam[0] = 0; outChainRam[0] = 0;
outChainRam[1] = 0; outChainRam[1] = 0;
ISOException.throwIt(ISO7816.SW_NO_ERROR); ISOException.throwIt(ISO7816.SW_NO_ERROR);
} }
} }
/** /**
* Set chaining flags to send dataLen bytes from inLen via chaining, if * Set chaining flags to send dataLen bytes from inLen via chaining, if
* necessary. * necessary.
* *
* @param apdu apdu buffer * @param apdu apdu buffer
*/ */
public void sendLongChaining(APDU apdu, short dataLen) { public void sendLongChaining(APDU apdu, short dataLen) {
if (dataLen > 256) { if (dataLen > 256) {
// Set the chaining boolean to 1 // Set the chaining boolean to 1
isOutChaining[0] = true; isOutChaining[0] = true;
// All the bytes are in inBuf already // All the bytes are in inBuf already
// Set the chaining remainder to dataLen minus 256 // Set the chaining remainder to dataLen minus 256
outChainRam[0] = (short) (dataLen - 256); outChainRam[0] = (short) (dataLen - 256);
// Send the first 256 bytes out // Send the first 256 bytes out
byte[] buf = apdu.getBuffer(); byte[] buf = apdu.getBuffer();
Util.arrayCopyNonAtomic(dataBuffer, (short) 0, buf, (short) 0, (short) 256); Util.arrayCopyNonAtomic(dataBuffer, (short) 0, buf, (short) 0, (short) 256);
apdu.setOutgoingAndSend((short) 0, (short) 256); apdu.setOutgoingAndSend((short) 0, (short) 256);
outChainRam[1] = 256; outChainRam[1] = 256;
// Throw the 61 xx // Throw the 61 xx
if (outChainRam[0] > 255) { if (outChainRam[0] > 255) {
// More than 255 (at least 256) to go, so 256 more // More than 255 (at least 256) to go, so 256 more
ISOException.throwIt((short) 0x6100); ISOException.throwIt((short) 0x6100);
} else { } else {
// Less than, so say how many bytes are left. // Less than, so say how many bytes are left.
ISOException.throwIt(Util.makeShort((byte) 0x61, (byte) outChainRam[0])); ISOException.throwIt(Util.makeShort((byte) 0x61, (byte) outChainRam[0]));
} }
} else { } else {
// Chaining not necessary, send in one go // Chaining not necessary, send in one go
isOutChaining[0] = false; isOutChaining[0] = false;
apdu.setOutgoing(); apdu.setOutgoing();
apdu.setOutgoingLength(dataLen); apdu.setOutgoingLength(dataLen);
apdu.sendBytesLong(dataBuffer, (short) 0, dataLen); apdu.sendBytesLong(dataBuffer, (short) 0, dataLen);
ISOException.throwIt(ISO7816.SW_NO_ERROR); ISOException.throwIt(ISO7816.SW_NO_ERROR);
} }
} }
private void getCert(APDU apdu) { private void getCert(APDU apdu) {
dataBuffer[0] = 0x00; dataBuffer[0] = 0x00;
tempVars[0] = (short) (attestationKeyPair.getCert(dataBuffer, (short) 1) + 1); tempVars[0] = (short) (attestationKeyPair.getCert(dataBuffer, (short) 1) + 1);
sendLongChaining(apdu, tempVars[0]); sendLongChaining(apdu, tempVars[0]);
} }
} }
...@@ -39,7 +39,7 @@ public class Utf8Strings { ...@@ -39,7 +39,7 @@ public class Utf8Strings {
public static final byte[] UTF8_X5C = {'x', '5', 'c'}; public static final byte[] UTF8_X5C = {'x', '5', 'c'};
public static final byte[] UTF8_PUBLIC_KEY = {'p', 'u', 'b', 'l', 'i', 'c', '-', 'k', 'e', 'y'}; public static final byte[] UTF8_PUBLIC_KEY = {'p', 'u', 'b', 'l', 'i', 'c', '-', 'k', 'e', 'y'};
public static final byte[] UTF8_U2F_V2 = {'U', '2', 'F', '_', 'V', '2'}; public static final byte[] UTF8_U2F_V2 = {'U', '2', 'F', '_', 'V', '2'};
public static final byte[] UTF8_FIDO2 = {'F', 'I', 'D', 'O', '_', '2', '_', '0'}; public static final byte[] UTF8_FIDO_2_0 = {'F', 'I', 'D', 'O', '_', '2', '_', '0'};
public static final byte[] UTF8_FIDO_2_1_PRE = {'F', 'I', 'D', 'O', '_', '2', '_', '1', '_', 'P', 'R', 'E'}; public static final byte[] UTF8_FIDO_2_1_PRE = {'F', 'I', 'D', 'O', '_', '2', '_', '1', '_', 'P', 'R', 'E'};
public static final byte[] UTF8_ICON = {'i', 'c', 'o', 'n'}; public static final byte[] UTF8_ICON = {'i', 'c', 'o', 'n'};
public static final byte[] UTF8_NULL = {'n', 'u', 'l', 'l'}; public static final byte[] UTF8_NULL = {'n', 'u', 'l', 'l'};
......
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