diff --git a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java index 0949177f..0fbd562d 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -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; @@ -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; @@ -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. */ @@ -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); } } @@ -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(); @@ -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; @@ -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) { @@ -499,8 +517,11 @@ && isProvisioningComplete())) { freeOperations(); sendError(apdu, KMError.GENERIC_UNKNOWN_ERROR); } finally { - resetData(); - repository.clean(); + if (!responseChainingEnabled && !cmdChainingEnabled) { + resetData(); + repository.clean(); + + } } } @@ -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; + } } /** @@ -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) { @@ -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); diff --git a/HAL/keymaster/4.1/JavacardKeymaster4Device.cpp b/HAL/keymaster/4.1/JavacardKeymaster4Device.cpp index 87daddbc..0eae0b6c 100644 --- a/HAL/keymaster/4.1/JavacardKeymaster4Device.cpp +++ b/HAL/keymaster/4.1/JavacardKeymaster4Device.cpp @@ -21,6 +21,11 @@ #include #include #include + +#include +#include +#include + #include #include #include @@ -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 @@ -136,7 +146,7 @@ static inline std::unique_ptr& 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; } } @@ -403,6 +413,57 @@ keyFormat, std::vector& wrappedKeyDescription) { return ErrorCode::OK; } +/** + * Construct the command chaining for extended length (command data > APDU_SHORT_LENGTH) APDU + */ +ErrorCode constructAPDUCmdChain(Instruction& ins, std::vector& inputData, + std::vector>& 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 apduOut; + apduOut.push_back(static_cast(CMD_CHAINING_APDU_CLS)); //CLS + apduOut.push_back(static_cast(ins)); //INS + apduOut.push_back(static_cast(CMD_CHAINING_APDU_P1)); //P1 + apduOut.push_back(static_cast(CMD_CHAINING_APDU_P2)); //P2 + + apduOut.push_back(static_cast(0xFF)); //Lc + + std::vector 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 apduOut; + apduOut.push_back(static_cast(APDU_CLS)); //CLS + apduOut.push_back(static_cast(ins)); //INS + apduOut.push_back(static_cast(APDU_P1)); //P1 + apduOut.push_back(static_cast(APDU_P2)); //P2 + + if ((cmdDataLen - index) > 0) { + //apduOut.push_back(static_cast(0x00)); //Lc + apduOut.push_back(static_cast((cmdDataLen - index) & 0xFF)); + std::vector data((cmdData + index), (cmdData + cmdDataLen)); + apduOut.insert(apduOut.end(), std::begin(data), std::end(data)); + } + apduOut.push_back(static_cast(0x00)); //Le + + apduChain.push_back(apduOut); + return (ErrorCode::OK);//success +} + ErrorCode constructApduMessage(Instruction& ins, std::vector& inputData, std::vector& apduOut) { apduOut.push_back(static_cast(APDU_CLS)); //CLS apduOut.push_back(static_cast(ins)); //INS @@ -437,6 +498,8 @@ uint16_t getStatus(std::vector& 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& inData, std::vector& response) { ErrorCode ret = ErrorCode::UNKNOWN_ERROR; std::vector apdu; @@ -460,6 +523,40 @@ ErrorCode sendData(Instruction ins, std::vector& inData, std::vector& inData, std::vector& response) { + ErrorCode ret = ErrorCode::UNKNOWN_ERROR; + std::vector> 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 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 } /** diff --git a/HAL/keymaster/4.1/SocketTransport.cpp b/HAL/keymaster/4.1/SocketTransport.cpp index e060262f..c9cb7306 100644 --- a/HAL/keymaster/4.1/SocketTransport.cpp +++ b/HAL/keymaster/4.1/SocketTransport.cpp @@ -62,6 +62,66 @@ bool SocketTransport::openConnection() { return true; } +std::vector SocketTransport::getGetResponseCmdApdu() { + std::vector apduOut; + apduOut.push_back(static_cast(0x80)); //CLS + apduOut.push_back(static_cast(0xC0)); //INS + apduOut.push_back(static_cast(0x40)); //P1 + apduOut.push_back(static_cast(0x00)); //P2 + apduOut.push_back(static_cast(0x00)); + + return apduOut; +} + +bool SocketTransport::handleChainGetResponse(std::vector& output) { + if (output.size() < 2) { + return false; + } + + uint16_t apduStatus = (output.at(output.size()-2) << 8) | (output.at(output.size()-1)); + if (apduStatus != 0x6100) { + return false; + } + + uint8_t buffer[MAX_RECV_BUFFER_SIZE]; + + // Holds complete reponse data, as and when new data is received, it get + // appended to this container + std::vector responseChain; + + std::vector getResponseApdu = getGetResponseCmdApdu(); + + // more response data is pending to receive + while (apduStatus == 0x6100) { + responseChain.insert(std::end(responseChain), std::begin(output), std::end(output)-2); + + if (send(mSocket, getResponseApdu.data(), getResponseApdu.size() , MSG_NOSIGNAL)< 0) { + LOG(ERROR) << "Failed to send data over socket err: " << errno; + return false; + } + + size_t valRead = read( mSocket , buffer, MAX_RECV_BUFFER_SIZE); + if(0 > valRead) { + LOG(ERROR) << "Failed to read data from socket - getResponse."; + } + output.clear(); + for(size_t i = 0; i < valRead; i++) { + output.push_back(buffer[i]); + } + + apduStatus = (output.at(output.size()-2) << 8) | (output.at(output.size()-1)); + // Last response chunk + if (apduStatus != 0x6100) { + responseChain.insert(std::end(responseChain), std::begin(output), std::end(output)); + output.clear(); + // Set the output with total data received till now for this command. + output.insert(std::end(output), std::begin(responseChain), std::end(responseChain)); + return true; + } + } + return false; +} + bool SocketTransport::sendData(const uint8_t* inData, const size_t inLen, std::vector& output) { uint8_t buffer[MAX_RECV_BUFFER_SIZE]; int count = 1; @@ -78,7 +138,7 @@ bool SocketTransport::sendData(const uint8_t* inData, const size_t inLen, std::v return false; } - if (send(mSocket, inData, inLen , 0)< 0) { + if (send(mSocket, inData, inLen , MSG_NOSIGNAL)< 0) { static int connectionResetCnt = 0; /* To avoid loop */ if (ECONNRESET == errno && connectionResetCnt == 0) { //Connection reset. Try open socket and then sendData. @@ -98,6 +158,12 @@ bool SocketTransport::sendData(const uint8_t* inData, const size_t inLen, std::v for(size_t i = 0; i < valRead; i++) { output.push_back(buffer[i]); } + + uint16_t apduStatus = (output.at(output.size()-2) << 8) | (output.at(output.size()-1)); + if (apduStatus == 0x6100) { + return handleChainGetResponse(output); + } + return true; } diff --git a/HAL/keymaster/include/Transport.h b/HAL/keymaster/include/Transport.h index c6674dca..e43844ae 100644 --- a/HAL/keymaster/include/Transport.h +++ b/HAL/keymaster/include/Transport.h @@ -101,6 +101,9 @@ class SocketTransport : public ITransport { */ bool isConnected() override; private: + + std::vector getGetResponseCmdApdu(); + bool handleChainGetResponse(std::vector& output); /** * Socket instance. */