Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

APDU command and response chaining support #55

Open
wants to merge 7 commits into
base: Javacard_KM_41_AOSP_UPMERGE_0630
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 133 additions & 7 deletions Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe
public static final short MAX_LENGTH = (short) 0x2000;
private static final byte CLA_ISO7816_NO_SM_NO_CHAN = (byte) 0x80;
private static final short KM_HAL_VERSION = (short) 0x4000;
private static final short KM_HAL_CHAIN_VERSION = (short) 0x4080;
private static final short MAX_AUTH_DATA_SIZE = (short) 512;
private static final short DERIVE_KEY_INPUT_SIZE = (short) 256;
private static final short POWER_RESET_MASK_FLAG = (short) 0x4000;
Expand Down Expand Up @@ -115,6 +116,10 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe

private static final byte INS_END_KM_CMD = 0x7F;

private static final byte INS_GET_RESPONSE_CMD = (byte)0xC0;
private static final short SHORT_APDU_DATA_LEN = 255;
private static final short SHORT_RESPONSE_DATA_LEN = 256;

// Provision reporting status
private static final byte NOT_PROVISIONED = 0x00;
private static final byte PROVISION_STATUS_ATTESTATION_KEY = 0x01;
Expand Down Expand Up @@ -190,6 +195,9 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe
protected static short[] data;
protected static byte provisionStatus = NOT_PROVISIONED;

protected static boolean responseChainingEnabled = false;
protected static boolean cmdChainingEnabled = false;

/**
* Registers this applet.
*/
Expand Down Expand Up @@ -298,7 +306,8 @@ protected void validateApduHeader(APDU apdu) {
}

// Validate P1P2.
if (P1P2 != KMKeymasterApplet.KM_HAL_VERSION) {
if (P1P2 != KMKeymasterApplet.KM_HAL_VERSION
&& P1P2 != KMKeymasterApplet.KM_HAL_CHAIN_VERSION) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
}
Expand Down Expand Up @@ -336,7 +345,8 @@ public void process(APDU apdu) {
byte apduIns = apduBuffer[ISO7816.OFFSET_INS];

// Validate whether INS can be supported
if (!(apduIns > INS_BEGIN_KM_CMD && apduIns < INS_END_KM_CMD)) {
if (!(apduIns > INS_BEGIN_KM_CMD && apduIns < INS_END_KM_CMD)
&& (apduIns != INS_GET_RESPONSE_CMD)) {
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
bufferRef[0] = repository.getHeap();
Expand Down Expand Up @@ -410,6 +420,9 @@ public void process(APDU apdu) {
|| ((keymasterState == KMKeymasterApplet.IN_PROVISION_STATE)
&& isProvisioningComplete())) {
switch (apduIns) {
case INS_GET_RESPONSE_CMD:
processGetResponseCmd(apdu);
break;
case INS_GENERATE_KEY_CMD:
processGenerateKey(apdu);
break;
Expand Down Expand Up @@ -490,6 +503,11 @@ && isProvisioningComplete())) {
sendError(apdu, KMException.getReason());
exception.clear();
} catch (ISOException exp) {
if (exp.getReason() == ISO7816.SW_BYTES_REMAINING_00) {
ISOException.throwIt(ISO7816.SW_BYTES_REMAINING_00);
} else if (exp.getReason() == ISO7816.SW_LAST_COMMAND_EXPECTED) {
return;
}
sendError(apdu, mapISOErrorToKMError(exp.getReason()));
freeOperations();
} catch (CryptoException e) {
Expand All @@ -499,8 +517,11 @@ && isProvisioningComplete())) {
freeOperations();
sendError(apdu, KMError.GENERIC_UNKNOWN_ERROR);
} finally {
resetData();
repository.clean();
if (!responseChainingEnabled && !cmdChainingEnabled) {
resetData();
repository.clean();

}
}
}

Expand Down Expand Up @@ -585,8 +606,99 @@ public static void sendOutgoing(APDU apdu) {
}
// Send data
apdu.setOutgoing();
apdu.setOutgoingLength(bufferProp[BUF_LEN_OFFSET]);
apdu.sendBytesLong((byte[]) bufferRef[0], bufferProp[BUF_START_OFFSET], bufferProp[BUF_LEN_OFFSET]);

if (bufferProp[BUF_LEN_OFFSET] > SHORT_RESPONSE_DATA_LEN) {
// If response data length is more than 256 then split the response
// in to multiple chunk of responses of each 256 bytes length
apdu.setOutgoingLength(SHORT_RESPONSE_DATA_LEN);
apdu.sendBytesLong((byte[]) bufferRef[0], bufferProp[BUF_START_OFFSET],
SHORT_RESPONSE_DATA_LEN);

// Update response data offset and remaining length to be sent
bufferProp[BUF_START_OFFSET] = (short)(bufferProp[BUF_START_OFFSET] +
SHORT_RESPONSE_DATA_LEN);
bufferProp[BUF_LEN_OFFSET] = (short)(bufferProp[BUF_LEN_OFFSET] - SHORT_RESPONSE_DATA_LEN);

// Enable response chaining to send remaining response data
responseChainingEnabled = true;

// Throw an exception to indicate more response data exist for this command (sw1sw2=0x6100)
ISOException.throwIt(ISO7816.SW_BYTES_REMAINING_00);
} else {
// send last part of command response or the only chunk of response data
apdu.setOutgoingLength(bufferProp[BUF_LEN_OFFSET]);
apdu.sendBytesLong((byte[]) bufferRef[0], bufferProp[BUF_START_OFFSET],
bufferProp[BUF_LEN_OFFSET]);
responseChainingEnabled = false;
}
}

/**
* Handles Command chaining, if 8th bit of P2 is set then it indicates command chaining.
* This method keeps track of total length of command data and first chunk offset
* After receiving last part of the command chain it prepares final data to be processed.
* @param apdu
* @param startOffset
* @param lengthTillNow
*/
public static void handleCommandChaining(APDU apdu, short startOffset, short lengthTillNow) {
byte[] srcBuffer = apdu.getBuffer();
// 8th bit of P2 will be set for command chaining.
short P1P2 = Util.getShort(srcBuffer, ISO7816.OFFSET_P1);
boolean cmdChainingApdu = (P1P2 == KMKeymasterApplet.KM_HAL_CHAIN_VERSION);

// If already command chaining started
if (cmdChainingEnabled) {
// If this APDU is not marked with command chaining bit in P2
// then it will be last part of the command, so prepare the final command data
// from chunks of data received in this command chain.
if (!cmdChainingApdu) {
// this will be the last APDU of this cmd chain
// Rearrange the command data received
short lastChunkOffset = bufferProp[BUF_START_OFFSET];
short lastChunkLen = bufferProp[BUF_LEN_OFFSET];

// Copy all chunks of data in order in contiguous bytes of memory.
// chucks of command data are stored on heap and heap grows backwards.
// reordering of these chunks is required to make it contiguous bytes of data to process.
// Allocate for total command-data to be used by the command
short totalLength = (short) (lengthTillNow + bufferProp[BUF_LEN_OFFSET]);
bufferProp[BUF_START_OFFSET] = repository.allocReclaimableMemory(totalLength);
bufferProp[BUF_LEN_OFFSET] = totalLength;

// Copy chunk by chunk in the received order to contiguous allocated memory
short index = bufferProp[BUF_START_OFFSET];
short length = SHORT_APDU_DATA_LEN;
while (startOffset > lastChunkOffset) {
Util.arrayCopyNonAtomic((byte[]) bufferRef[0], startOffset, (byte[]) bufferRef[0],
index, length);
index += length;
startOffset -= length;
}
// Handle last chunk of data which can be less than SHORT_APDU_DATA_LEN bytes.
if ((short) (index - bufferProp[BUF_START_OFFSET]) < bufferProp[BUF_LEN_OFFSET]) {
Util.arrayCopyNonAtomic((byte[]) bufferRef[0], lastChunkOffset, (byte[]) bufferRef[0],
index, lastChunkLen);
}
} else {
// This apdu is not the last part of this command chain,
// store first chunk offset and total length of bytes received till now
bufferProp[BUF_LEN_OFFSET] = (short) (lengthTillNow + bufferProp[BUF_LEN_OFFSET]);
bufferProp[BUF_START_OFFSET] = startOffset;
}
}

// Enable command chaining, if 8th bit of P2 is set
if (cmdChainingApdu) {
// if this command APDU is marked with command chaining (P1P2=0x4080),
// then enable to collect data from subsequent commands in this chain
cmdChainingEnabled = true;

// Throw an exception to indicate next chunk of command data is required.
ISOException.throwIt(ISO7816.SW_LAST_COMMAND_EXPECTED);
} else {
cmdChainingEnabled = false;
}
}

/**
Expand All @@ -596,15 +708,25 @@ public static void receiveIncoming(APDU apdu) {
byte[] srcBuffer = apdu.getBuffer();
short recvLen = apdu.setIncomingAndReceive();
short srcOffset = apdu.getOffsetCdata();

// For command chaining use case to track total length of data received and
// its first chunk offset
short lengthTillNow = bufferProp[BUF_LEN_OFFSET];
short startOffset = bufferProp[BUF_START_OFFSET];

// Get chunk of data from current APDU and allocate for it.
bufferProp[BUF_LEN_OFFSET] = apdu.getIncomingLength();
bufferProp[BUF_START_OFFSET] = repository.allocReclaimableMemory(bufferProp[BUF_LEN_OFFSET]);
short index = bufferProp[BUF_START_OFFSET];

// Receive the chunk of command data from current APDU on to allocated memory.
short index = bufferProp[BUF_START_OFFSET];
while (recvLen > 0 && ((short) (index - bufferProp[BUF_START_OFFSET]) < bufferProp[BUF_LEN_OFFSET])) {
Util.arrayCopyNonAtomic(srcBuffer, srcOffset, (byte[]) bufferRef[0], index, recvLen);
index += recvLen;
recvLen = apdu.receiveBytes(srcOffset);
}

handleCommandChaining(apdu, startOffset, lengthTillNow);
}

private void processGetHwInfoCmd(APDU apdu) {
Expand Down Expand Up @@ -3302,6 +3424,10 @@ private void processSetBootParamsCmd(APDU apdu) {
repository.initHmacNonce(scratchPad, (short) 0, KMRepository.HMAC_SEED_NONCE_SIZE);
}

private static void processGetResponseCmd(APDU apdu) {
sendOutgoing(apdu);
}

private static void processGenerateKey(APDU apdu) {
// Receive the incoming request fully from the master into buffer.
receiveIncoming(apdu);
Expand Down
99 changes: 98 additions & 1 deletion HAL/keymaster/4.1/JavacardKeymaster4Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
#include <cppbor_parse.h>
#include <CborConverter.h>
#include <Transport.h>

#include <sstream>
#include <iomanip>
#include <stdio.h>

#include <keymaster/key_blob_utils/software_keyblobs.h>
#include <keymaster/android_keymaster_utils.h>
#include <keymaster/wrapped_key.h>
Expand All @@ -45,6 +50,11 @@
// Cuttlefish build fingerprint substring.
#define CUTTLEFISH_FINGERPRINT_SS "aosp_cf_"

#define CMD_CHAINING_APDU_CLS 0x80
#define CMD_CHAINING_APDU_P1 0x40
#define CMD_CHAINING_APDU_P2 0x80
#define APDU_SHORT_LENGTH 255

#define APDU_CLS 0x80
#define APDU_P1 0x40
#define APDU_P2 0x00
Expand Down Expand Up @@ -136,7 +146,7 @@ static inline std::unique_ptr<se_transport::TransportFactory>& getTransportFacto
if (!isEmulator) {
std::string fingerprint = android::base::GetProperty(PROP_BUILD_FINGERPRINT, "");
if (!fingerprint.empty()) {
if (fingerprint.find(CUTTLEFISH_FINGERPRINT_SS, 0)) {
if (fingerprint.find(CUTTLEFISH_FINGERPRINT_SS, 0) != std::string::npos) {
isEmulator = true;
}
}
Expand Down Expand Up @@ -403,6 +413,57 @@ keyFormat, std::vector<uint8_t>& wrappedKeyDescription) {
return ErrorCode::OK;
}

/**
* Construct the command chaining for extended length (command data > APDU_SHORT_LENGTH) APDU
*/
ErrorCode constructAPDUCmdChain(Instruction& ins, std::vector<uint8_t>& inputData,
std::vector<std::vector<uint8_t>>& apduChain) {
if(inputData.size() >= USHRT_MAX) {
return (ErrorCode::INSUFFICIENT_BUFFER_SPACE);
}

uint8_t* cmdData = inputData.data();
size_t cmdDataLen = inputData.size();
size_t index = 0;

// Construct command chaining only if command data is of large length > APDU_SHORT_LENGTH
// otherwise signle short format APDU is created.
while((cmdDataLen - index) > APDU_SHORT_LENGTH) {
std::vector<uint8_t> apduOut;
apduOut.push_back(static_cast<uint8_t>(CMD_CHAINING_APDU_CLS)); //CLS
apduOut.push_back(static_cast<uint8_t>(ins)); //INS
apduOut.push_back(static_cast<uint8_t>(CMD_CHAINING_APDU_P1)); //P1
apduOut.push_back(static_cast<uint8_t>(CMD_CHAINING_APDU_P2)); //P2

apduOut.push_back(static_cast<uint8_t>(0xFF)); //Lc

std::vector<uint8_t> data((cmdData + index), (cmdData + index + APDU_SHORT_LENGTH));
apduOut.insert(apduOut.end(), std::begin(data), std::end(data));

apduChain.push_back(apduOut);

index += APDU_SHORT_LENGTH;
}

// Construct last APDU or the only APDU of the command
std::vector<uint8_t> apduOut;
apduOut.push_back(static_cast<uint8_t>(APDU_CLS)); //CLS
apduOut.push_back(static_cast<uint8_t>(ins)); //INS
apduOut.push_back(static_cast<uint8_t>(APDU_P1)); //P1
apduOut.push_back(static_cast<uint8_t>(APDU_P2)); //P2

if ((cmdDataLen - index) > 0) {
//apduOut.push_back(static_cast<uint8_t>(0x00)); //Lc
apduOut.push_back(static_cast<uint8_t>((cmdDataLen - index) & 0xFF));
std::vector<uint8_t> data((cmdData + index), (cmdData + cmdDataLen));
apduOut.insert(apduOut.end(), std::begin(data), std::end(data));
}
apduOut.push_back(static_cast<uint8_t>(0x00)); //Le

apduChain.push_back(apduOut);
return (ErrorCode::OK);//success
}

ErrorCode constructApduMessage(Instruction& ins, std::vector<uint8_t>& inputData, std::vector<uint8_t>& apduOut) {
apduOut.push_back(static_cast<uint8_t>(APDU_CLS)); //CLS
apduOut.push_back(static_cast<uint8_t>(ins)); //INS
Expand Down Expand Up @@ -437,6 +498,8 @@ uint16_t getStatus(std::vector<uint8_t>& inputData) {
return (inputData.at(inputData.size()-2) << 8) | (inputData.at(inputData.size()-1));
}

/*
//TODO this method is kept just for reference to support extended length format
ErrorCode sendData(Instruction ins, std::vector<uint8_t>& inData, std::vector<uint8_t>& response) {
ErrorCode ret = ErrorCode::UNKNOWN_ERROR;
std::vector<uint8_t> apdu;
Expand All @@ -460,6 +523,40 @@ ErrorCode sendData(Instruction ins, std::vector<uint8_t>& inData, std::vector<ui
}
LOG(DEBUG) << "sendData cmd: " << (int32_t)ins << " status: " << (int32_t)ErrorCode::OK;
return (ErrorCode::OK);//success
}*/

ErrorCode sendData(Instruction ins, std::vector<uint8_t>& inData, std::vector<uint8_t>& response) {
ErrorCode ret = ErrorCode::UNKNOWN_ERROR;
std::vector<std::vector<uint8_t>> apduChain;

// Constructs short length format APDUs based on the size of command data
ret = constructAPDUCmdChain(ins, inData, apduChain);
if(ret != ErrorCode::OK) {
LOG(ERROR) << "error in constructApduMessage cmd: " << (int32_t)ins << " status: " << (int32_t)ret;
return ret;
}

// Send all APDUs in the command chain and get its response.
auto it = apduChain.begin();
while (it != apduChain.end()) {
std::vector<uint8_t> apdu = *it;
response.clear();

if(!getTransportFactoryInstance()->sendData(apdu.data(), apdu.size(), response)) {
LOG(ERROR) << "error in sendData cmd: " << (int32_t)ins << " status: "
<< (int32_t)ErrorCode::SECURE_HW_COMMUNICATION_FAILED;
return (ErrorCode::SECURE_HW_COMMUNICATION_FAILED);
}

// Response size should be greater than 2. Cbor output data followed by two bytes of APDU status.
if((response.size() < 2) || (getStatus(response) != APDU_RESP_STATUS_OK)) {
LOG(ERROR) << "error in sendData cmd: " << (int32_t)ins << " status: " << getStatus(response);
return (ErrorCode::UNKNOWN_ERROR);
}
++it;
}
LOG(DEBUG) << "sendData cmd: " << (int32_t)ins << " status: " << (int32_t)ErrorCode::OK;
return (ErrorCode::OK);//success
}

/**
Expand Down
Loading