From 1dd1894182f5d15cf5a6f4b53438d62a1d867593 Mon Sep 17 00:00:00 2001 From: Subrahmanya Manikanta Venkateswarlu Bhamidipati Kameswara Sri Date: Thu, 21 Oct 2021 13:34:41 +0000 Subject: [PATCH 1/3] 1. Corrected the HardwareAuthToken and VerificationToken validation. 2. Able to generate key when both FINGERPRINT and BIOMETRIC were passed as USER_AUTH types. 3. USER_SECURE_ID and NO_AUTH_REQUIRED Mutual exclusive. --- .../android/javacard/keymaster/KMUtils.java | 29 +- .../android/javacard/keymaster/KMUtils.java | 30 +- Applet/README.md | 2 + .../android/javacard/keymaster/KMEnumTag.java | 3 +- .../android/javacard/keymaster/KMInteger.java | 9 + .../javacard/keymaster/KMIntegerArrayTag.java | 13 +- .../javacard/keymaster/KMKeymasterApplet.java | 164 ++--- .../javacard/keymaster/KMOperationState.java | 69 ++- .../4.1/JavacardKeymaster4Device.cpp | 558 ++++++++++-------- .../include/JavacardKeymaster4Device.h | 35 +- ProvisioningTool/src/provision.cpp | 13 + 11 files changed, 578 insertions(+), 347 deletions(-) diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java b/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java index c2b5c7f3..49ddd16d 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java @@ -303,6 +303,14 @@ public static byte compare(byte[] buf, short lhs, short rhs) { return KMInteger.unsignedByteArrayCompare(buf, lhs, buf, rhs, (short) 8); } + public static void shiftLeft(byte[] buf, short start, short count) { + short index = 0; + while (index < count) { + shiftLeft(buf, start); + index++; + } + } + public static void shiftLeft(byte[] buf, short start) { byte index = 7; byte carry = 0; @@ -343,7 +351,9 @@ public static void add(byte[] buf, short op1, short op2, short result) { byte carry = 0; short tmp; while (index >= 0) { - tmp = (short) (buf[(short) (op1 + index)] + buf[(short) (op2 + index)] + carry); + tmp = + (short) ((buf[(short) (op1 + index)] & 0xFF) + + (buf[(short) (op2 + index)] & 0xFF) + carry); carry = 0; if (tmp > 255) { carry = 1; // max unsigned byte value is 255 @@ -409,4 +419,19 @@ public static short getLeapYrIndex(boolean from2020, short yrsCount) { return -1; } -} \ No newline at end of file + //i * 1000 = (i << 9) + (i << 8) + (i << 7) + (i << 6) + (i << 5) + ( i << 3) + public static void convertToMilliseconds(byte[] buf, short inputOff, short outputOff, + short scratchPadOff) { + byte[] shiftPos = {9, 8, 7, 6, 5, 3}; + short index = 0; + while (index < (short) (shiftPos.length)) { + Util.arrayCopyNonAtomic(buf, inputOff, buf, scratchPadOff, (short) 8); + shiftLeft(buf, scratchPadOff, shiftPos[index]); + Util.arrayCopyNonAtomic(buf, outputOff, buf, (short) (scratchPadOff + 8), (short) 8); + add(buf, scratchPadOff, (short) (8 + scratchPadOff), (short) (16 + scratchPadOff)); + Util.arrayCopyNonAtomic(buf, (short) (scratchPadOff + 16), buf, outputOff, (short) 8); + Util.arrayFillNonAtomic(buf, scratchPadOff, (short) 24, (byte) 0); + index++; + } + } +} diff --git a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMUtils.java b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMUtils.java index c2b5c7f3..88b7b4d1 100644 --- a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMUtils.java +++ b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMUtils.java @@ -303,6 +303,14 @@ public static byte compare(byte[] buf, short lhs, short rhs) { return KMInteger.unsignedByteArrayCompare(buf, lhs, buf, rhs, (short) 8); } + public static void shiftLeft(byte[] buf, short start, short count) { + short index = 0; + while (index < count) { + shiftLeft(buf, start); + index++; + } + } + public static void shiftLeft(byte[] buf, short start) { byte index = 7; byte carry = 0; @@ -343,7 +351,9 @@ public static void add(byte[] buf, short op1, short op2, short result) { byte carry = 0; short tmp; while (index >= 0) { - tmp = (short) (buf[(short) (op1 + index)] + buf[(short) (op2 + index)] + carry); + tmp = + (short) ((buf[(short) (op1 + index)] & 0xFF) + + (buf[(short) (op2 + index)] & 0xFF) + carry); carry = 0; if (tmp > 255) { carry = 1; // max unsigned byte value is 255 @@ -409,4 +419,20 @@ public static short getLeapYrIndex(boolean from2020, short yrsCount) { return -1; } -} \ No newline at end of file + // i * 1000 = (i << 9) + (i << 8) + (i << 7) + (i << 6) + (i << 5) + ( i << 3) + public static void convertToMilliseconds(byte[] buf, short inputOff, short outputOff, + short scratchPadOff) { + byte[] shiftPos = {9, 8, 7, 6, 5, 3}; + short index = 0; + while (index < (short) (shiftPos.length)) { + Util.arrayCopyNonAtomic(buf, inputOff, buf, scratchPadOff, (short) 8); + shiftLeft(buf, scratchPadOff, shiftPos[index]); + Util.arrayCopyNonAtomic(buf, outputOff, buf, (short) (scratchPadOff + 8), (short) 8); + add(buf, scratchPadOff, (short) (8 + scratchPadOff), (short) (16 + scratchPadOff)); + Util.arrayCopyNonAtomic(buf, (short) (scratchPadOff + 16), buf, outputOff, (short) 8); + Util.arrayFillNonAtomic(buf, scratchPadOff, (short) 24, (byte) 0); + index++; + } + } + +} diff --git a/Applet/README.md b/Applet/README.md index 76af5b05..064fc9d9 100644 --- a/Applet/README.md +++ b/Applet/README.md @@ -17,3 +17,5 @@ which serves to intermediate between Android Keystore and this applet. - Install Javacard 3.0.5 classic sdk. - set JC_HOME_SIMULATOR environment variable to the installed sdk. - Give ant build from Applet folder. +- Download [gpapi-upgrade.jar](https://globalplatform.wpengine.com/specs-library/globalplatform-card-api-org-globalplatform-upgrade-v1/) and copy inside lib folder of both AndroidSEProvider and JCardSimProvider to resolve the compilation errors. + diff --git a/Applet/src/com/android/javacard/keymaster/KMEnumTag.java b/Applet/src/com/android/javacard/keymaster/KMEnumTag.java index 7493aa3d..0e45414e 100644 --- a/Applet/src/com/android/javacard/keymaster/KMEnumTag.java +++ b/Applet/src/com/android/javacard/keymaster/KMEnumTag.java @@ -106,8 +106,7 @@ public static void create() { new byte[]{RSA, DES, EC, AES, HMAC}, new byte[]{P_224, P_256, P_384, P_521}, new byte[]{STANDALONE, REQUIRES_FILE_SYSTEM}, - new byte[]{USER_AUTH_NONE, PASSWORD, FINGERPRINT, (byte) (PASSWORD & FINGERPRINT), - ANY}, + new byte[]{USER_AUTH_NONE, PASSWORD, FINGERPRINT, BOTH, ANY}, new byte[]{GENERATED, DERIVED, IMPORTED, UNKNOWN, SECURELY_IMPORTED}, new byte[]{SOFTWARE, TRUSTED_ENVIRONMENT, STRONGBOX} }; diff --git a/Applet/src/com/android/javacard/keymaster/KMInteger.java b/Applet/src/com/android/javacard/keymaster/KMInteger.java index aee6f9d5..fd080198 100644 --- a/Applet/src/com/android/javacard/keymaster/KMInteger.java +++ b/Applet/src/com/android/javacard/keymaster/KMInteger.java @@ -147,6 +147,15 @@ public short value(byte[] dest, short destOff) { return length(); } + public short toLittleEndian(byte[] dest, short destOff) { + short index = (short) (length() - 1); + while (index >= 0) { + dest[destOff++] = heap[(short) (instanceTable[KM_INTEGER_OFFSET] + TLV_HEADER_SIZE + index)]; + index--; + } + return length(); + } + public short getShort() { return Util.getShort(heap, (short) (instanceTable[KM_INTEGER_OFFSET] + TLV_HEADER_SIZE + 2)); } diff --git a/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java b/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java index e292c5e6..558e44e2 100644 --- a/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java +++ b/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java @@ -46,7 +46,7 @@ public static short exp(short tagType) { if (!validateTagType(tagType)) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } - short arrPtr = KMArray.exp(KMType.INTEGER_TYPE); + short arrPtr = KMArray.exp(KMInteger.exp()); short ptr = instance(TAG_TYPE, (short) 6); Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); @@ -134,6 +134,17 @@ private static boolean validateTagType(short tagType) { return (tagType == ULONG_ARRAY_TAG) || (tagType == UINT_ARRAY_TAG); } + public boolean contains(short tagValue) { + short index = 0; + while (index < length()) { + if (KMInteger.compare(tagValue, get(index)) == 0) { + return true; + } + index++; + } + return false; + } + public static boolean contains(short tagId, short tagValue, short params) { short tag = KMKeyParameters.findTag(KMType.UINT_ARRAY_TAG, tagId, params); diff --git a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java index 0949177f..29413a45 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -1968,7 +1968,7 @@ private void authorizeUpdateFinishOperation(KMOperationState op, byte[] scratchP if (KMInteger.compare(data[OP_HANDLE], tmpVariables[0]) != 0) { KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); } - authenticateUser(); + authTokenMatches(op.getUserSecureId(), op.getAuthType(), scratchPad); } } @@ -2015,7 +2015,7 @@ private void validateVerificationToken(KMOperationState op, short verToken, byte validateVerificationToken(verToken, scratchPad); // validate operation handle. ptr = KMVerificationToken.cast(verToken).getChallenge(); - if (op.getHandle() != KMInteger.cast(ptr).getShort()) { + if (KMInteger.compare(ptr, op.getHandle()) != 0) { KMException.throwIt(KMError.VERIFICATION_FAILED); } } @@ -2529,10 +2529,7 @@ private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) authorizeDigest(op); authorizePadding(op); authorizeBlockModeAndMacLength(op); - if (!validateHwToken(data[HW_TOKEN], scratchPad)) { - data[HW_TOKEN] = KMType.INVALID_VALUE; - } - authorizeUserSecureIdAuthTimeout(op); + authorizeUserSecureIdAuthTimeout(op, scratchPad); authorizeDeviceUnlock(data[HW_TOKEN]); // Authorize Caller Nonce - if caller nonce absent in key char and nonce present in // key params then fail if it is not a Decrypt operation @@ -2729,55 +2726,83 @@ private void beginSignVerifyOperation(KMOperationState op) { } } - private void authorizeUserSecureIdAuthTimeout(KMOperationState op) { + private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratchPad) { short authTime; + short authType; // Authorize User Secure Id and Auth timeout - tmpVariables[0] = + short userSecureIdPtr = KMKeyParameters.findTag(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, data[HW_PARAMETERS]); - if (tmpVariables[0] != KMType.INVALID_VALUE) { - tmpVariables[0] = + if (userSecureIdPtr != KMType.INVALID_VALUE) { + // Authentication required. + if (KMType.INVALID_VALUE != + KMKeyParameters.findTag(KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, data[HW_PARAMETERS])) { + // Key has both USER_SECURE_ID and NO_AUTH_REQUIRED + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // store authenticator type + if(KMType.INVALID_VALUE == + (authType = KMEnumTag.getValue(KMType.USER_AUTH_TYPE, data[HW_PARAMETERS]))) { + // Authentication required, but no auth type found. + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + + short authTimeoutTagPtr = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT, data[HW_PARAMETERS]); - if (tmpVariables[0] != KMType.INVALID_VALUE) { - // check if hw token is empty - mac should not be empty. - if (data[HW_TOKEN] == KMType.INVALID_VALUE) { - KMException.throwIt(KMError.INVALID_MAC_LENGTH); - } - authTime = KMIntegerTag.cast(tmpVariables[0]).getValue(); + if (authTimeoutTagPtr != KMType.INVALID_VALUE) { // authenticate user - authenticateUser(); + authTokenMatches(userSecureIdPtr, authType, scratchPad); + + authTime = KMIntegerTag.cast(authTimeoutTagPtr).getValue(); // set the one time auth op.setOneTimeAuthReqd(true); // set the authentication time stamp in operation state - authTime = addIntegers(authTime, KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp()); + authTime = + addAuthTimeToTimeStamp(authTime, + KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp(), scratchPad); op.setAuthTime( KMInteger.cast(authTime).getBuffer(), KMInteger.cast(authTime).getStartOff()); // auth time validation will happen in update or finish op.setAuthTimeoutValidated(false); } else { // auth per operation required + // store user secure id and authType in OperationState. + op.setUserSecureId(userSecureIdPtr); + op.setAuthType((byte) authType); + // set flags op.setOneTimeAuthReqd(false); op.setAuthPerOperationReqd(true); } } } - private void authenticateUser() { - tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getUserId(); - if (KMInteger.cast(tmpVariables[0]).isZero()) { - tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getAuthenticatorId(); - if (KMInteger.cast(tmpVariables[0]).isZero()) { - KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); - } + private boolean isHwAuthTokenContainsMatchingSecureId(short hwAuthToken, + short secureUserIdsObj) { + short secureUserId = KMHardwareAuthToken.cast(hwAuthToken).getUserId(); + if (!KMInteger.cast(secureUserId).isZero()) { + if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(secureUserId)) + return true; + } + + short authenticatorId = KMHardwareAuthToken.cast(hwAuthToken).getAuthenticatorId(); + if (!KMInteger.cast(authenticatorId).isZero()) { + if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(authenticatorId)) + return true; + } + return false; + } + + private void authTokenMatches(short userSecureIdsPtr, short authType, + byte[] scratchPad) { + if (!validateHwToken(data[HW_TOKEN], scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); } - // check user secure id - if (!KMIntegerArrayTag.contains(KMType.USER_SECURE_ID, tmpVariables[0], data[HW_PARAMETERS])) { + if (!isHwAuthTokenContainsMatchingSecureId(data[HW_TOKEN], userSecureIdsPtr)) { KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); } // check auth type - tmpVariables[1] = KMEnumTag.getValue(KMType.USER_AUTH_TYPE, data[HW_PARAMETERS]); tmpVariables[2] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); tmpVariables[2] = KMEnum.cast(tmpVariables[2]).getVal(); - if (((byte) tmpVariables[2] & (byte) tmpVariables[1]) == 0) { + if (((byte) tmpVariables[2] & (byte) authType) == 0) { KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); } } @@ -2795,18 +2820,15 @@ private boolean validateHwToken(short hwToken, byte[] scratchPad) { len = 1; // concatenate challenge - 8 bytes ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); - KMInteger.cast(ptr) - .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); len += 8; // concatenate user id - 8 bytes ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); - KMInteger.cast(ptr) - .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); len += 8; // concatenate authenticator id - 8 bytes ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); - KMInteger.cast(ptr) - .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); len += 8; // concatenate authenticator type - 4 bytes ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); @@ -3079,6 +3101,12 @@ private void importTDESKey(byte[] scratchPad) { data[KEY_BLOB] = KMArray.instance((short) 4); } + private void validateAesKeySize(short keySizeBits) { + if (keySizeBits != 128 && keySizeBits != 256) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + private void importAESKey(byte[] scratchPad) { // Get Key tmpVariables[0] = KMArray.instance((short) 1); @@ -3093,17 +3121,18 @@ private void importAESKey(byte[] scratchPad) { // create 128 or 256 bit AES key tmpVariables[4] = 0; // index in scratchPad for update params // check the keysize tag if present in key parameters. - tmpVariables[2] = + short keysize = KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); - if (tmpVariables[2] != KMType.INVALID_VALUE) { - if (tmpVariables[2] != 128 && tmpVariables[2] != 256) { - KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); - } + + if (keysize != KMType.INVALID_VALUE) { + validateAesKeySize(keysize); } else { // add the key size to scratchPad - tmpVariables[5] = KMInteger.uint_16(KMByteBlob.cast(data[SECRET]).length()); - tmpVariables[6] = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, tmpVariables[5]); - Util.setShort(scratchPad, tmpVariables[4], tmpVariables[6]); + keysize = (short) ( 8 * KMByteBlob.cast(data[SECRET]).length()); + validateAesKeySize(keysize); + keysize = KMInteger.uint_16(keysize); + short keysizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, tmpVariables[4], keysizeTag); tmpVariables[4] += 2; } // Check whether key can be created @@ -3929,38 +3958,29 @@ private static void sendError(APDU apdu, short err) { sendOutgoing(apdu); } - private short addIntegers(short num1, short num2) { - short buf = repository.alloc((short) 24); - byte[] scratchPad = repository.getHeap(); - Util.arrayFillNonAtomic(scratchPad, buf, (short) 24, (byte) 0); + private short addAuthTimeToTimeStamp(short authTime, short timeStamp, byte[] scratchPad) { + // Convert authTime to milliseconds + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 40, (byte) 0); Util.arrayCopyNonAtomic( - KMInteger.cast(num1).getBuffer(), - KMInteger.cast(num1).getStartOff(), + KMInteger.cast(authTime).getBuffer(), + KMInteger.cast(authTime).getStartOff(), scratchPad, - (short) (buf + 8 - KMInteger.cast(num1).length()), - KMInteger.cast(num1).length()); + (short) (8 - KMInteger.cast(authTime).length()), + KMInteger.cast(authTime).length()); + KMUtils.convertToMilliseconds(scratchPad, (short) 0, (short) 8, (short) 16); + + // Copy timestamp to scratchpad + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 8, (byte) 0); Util.arrayCopyNonAtomic( - KMInteger.cast(num2).getBuffer(), - KMInteger.cast(num2).getStartOff(), + KMInteger.cast(timeStamp).getBuffer(), + KMInteger.cast(timeStamp).getStartOff(), scratchPad, - (short) (buf + 16 - KMInteger.cast(num2).length()), - KMInteger.cast(num2).length()); - add(scratchPad, buf, (short) (buf + 8), (short) (buf + 16)); - return KMInteger.uint_64(scratchPad, (short) (buf + 16)); - } - - private void add(byte[] buf, short op1, short op2, short result) { - byte index = 7; - byte carry = 0; - short tmp; - while (index >= 0) { - tmp = (short) (buf[(short) (op1 + index)] + buf[(short) (op2 + index)] + carry); - carry = 0; - if (tmp > 255) { - carry = 1; // max unsigned byte value is 255 - } - buf[(short) (result + index)] = (byte) (tmp & (byte) 0xFF); - index--; - } + (short) (8 - KMInteger.cast(timeStamp).length()), + KMInteger.cast(timeStamp).length()); + + // add authTime in millis to timestamp. + Util.arrayFillNonAtomic(scratchPad, (short) 16, (short) 8, (byte) 0); + KMUtils.add(scratchPad, (short) 0, (short) 8, (short) 16); + return KMInteger.uint_64(scratchPad, (short) 16); } } diff --git a/Applet/src/com/android/javacard/keymaster/KMOperationState.java b/Applet/src/com/android/javacard/keymaster/KMOperationState.java index d8705de4..85a346e6 100644 --- a/Applet/src/com/android/javacard/keymaster/KMOperationState.java +++ b/Applet/src/com/android/javacard/keymaster/KMOperationState.java @@ -16,6 +16,8 @@ package com.android.javacard.keymaster; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; import javacard.framework.JCSystem; import javacard.framework.Util; @@ -27,7 +29,7 @@ */ public class KMOperationState { - public static final byte MAX_DATA = 20; + public static final byte MAX_DATA = 63; private static final byte OPERATION = 0; private static final byte TRUE = 1; private static final byte FALSE = 0; @@ -38,18 +40,23 @@ public class KMOperationState { private static final byte BLOCKMODE = 3; private static final byte DIGEST = 4; private static final byte FLAGS = 5; + private static final byte AUTH_TYPE = 6; // short type - private static final byte KEY_SIZE = 6; - private static final byte MAC_LENGTH = 8; + private static final byte KEY_SIZE = 7; + private static final byte MAC_LENGTH = 9; // Handle - currently this is short - private static final byte OP_HANDLE = 10; + private static final byte OP_HANDLE = 11; // Auth time 64 bits - private static final byte AUTH_TIME = 12; + private static final byte AUTH_TIME = 13; + // Secure user ids 5 * 8 = 40 bytes ( Considering Maximum 5 SECURE USER IDs) + // First two bytes are reserved to store number of secure ids. SO total 42 bytes. + private static final byte USER_SECURE_ID = 21; // Flag masks private static final byte AUTH_PER_OP_REQD = 1; private static final byte SECURE_USER_ID_REQD = 2; private static final byte AUTH_TIMEOUT_VALIDATED = 4; private static final byte AES_GCM_UPDATE_ALLOWED = 8; + private static final byte MAX_SECURE_USER_IDS = 5; // Object References private byte[] data; @@ -165,6 +172,58 @@ public void setAuthTime(byte[] timeBuf, short start) { dataUpdated(); } + public void setAuthType(byte authType) { + data[AUTH_TYPE] = authType; + dataUpdated(); + } + + public short getAuthType() { + return data[AUTH_TYPE]; + } + + public short getUserSecureId() { + short offset = USER_SECURE_ID; + short length = Util.getShort(data, USER_SECURE_ID); + if (length == 0) { + return KMType.INVALID_VALUE; + } + short arrObj = KMArray.instance(length); + short index = 0; + short obj; + offset = (short) (2 + USER_SECURE_ID); + while (index < length) { + obj = KMInteger.instance(data, (short) (offset + index * 8), (short) 8); + KMArray.cast(arrObj).add(index, obj); + index++; + } + return KMIntegerArrayTag.instance(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, arrObj); + } + + public void setUserSecureId(short integerArrayPtr) { + short length = KMIntegerArrayTag.cast(integerArrayPtr).length(); + if (length > MAX_SECURE_USER_IDS) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + Util.arrayFillNonAtomic(data, USER_SECURE_ID, (short) (MAX_SECURE_USER_IDS * 8) , (byte) 0); + short index = 0; + short obj; + short offset = USER_SECURE_ID; + Util.setShort(data, offset, length); + offset += 2; + while (index < length) { + obj = KMIntegerArrayTag.cast(integerArrayPtr).get(index); + Util.arrayCopy( + KMInteger.cast(obj).getBuffer(), + KMInteger.cast(obj).getStartOff(), + data, + (short) (8 - KMInteger.cast(obj).length() + offset + 8 * index), + KMInteger.cast(obj).length() + ); + index++; + } + dataUpdated(); + } + public void setOneTimeAuthReqd(boolean flag) { if (flag) { data[FLAGS] = (byte) (data[FLAGS] | SECURE_USER_ID_REQD); diff --git a/HAL/keymaster/4.1/JavacardKeymaster4Device.cpp b/HAL/keymaster/4.1/JavacardKeymaster4Device.cpp index 87daddbc..46a12557 100644 --- a/HAL/keymaster/4.1/JavacardKeymaster4Device.cpp +++ b/HAL/keymaster/4.1/JavacardKeymaster4Device.cpp @@ -63,10 +63,11 @@ namespace javacard { static std::unique_ptr pTransportFactory = nullptr; constexpr size_t kOperationTableSize = 4; -/* Key is the newly generated operation handle. Value is a pair with first element having - * original operation handle and second element represents SW or SB operation. +/* + * Key is the operation handle generated by either SoftKM or StrongboxKM and + * value is either PUBLIC_OPERATION or PRIVATE_OPERATION */ -std::map> operationTable; +std::map operationTable; struct KM_AUTH_LIST_Delete { void operator()(KM_AUTH_LIST* p) { KM_AUTH_LIST_free(p); } @@ -198,61 +199,31 @@ static T translateExtendedErrorsToHalErrors(T& errorCode) { return err; } -/* Generate new operation handle */ -static ErrorCode generateOperationHandle(uint64_t& oprHandle) { - std::map>::iterator it; - do { - keymaster_error_t err = GenerateRandom(reinterpret_cast(&oprHandle), (size_t)sizeof(oprHandle)); - if (err != KM_ERROR_OK) { - return legacy_enum_conversion(err); - } - it = operationTable.find(oprHandle); - } while (it != operationTable.end()); - return ErrorCode::OK; -} - -/* Create a new operation handle entry in operation table.*/ -static ErrorCode createOprHandleEntry(uint64_t origOprHandle, uint64_t keymasterSrc, uint64_t& newOperationHandle) { - ErrorCode errorCode = ErrorCode::OK; - if (ErrorCode::OK != (errorCode = generateOperationHandle(newOperationHandle))) { - return errorCode; - } - operationTable[newOperationHandle] = std::make_pair(origOprHandle, keymasterSrc); - return errorCode; -} - -/* Get original operation handle generated by softkeymaster/strongboxkeymaster. */ -static ErrorCode getOrigOperationHandle(uint64_t halGeneratedOperationHandle, uint64_t& origOprHandle) { - std::map>::iterator it = operationTable.find(halGeneratedOperationHandle); - if (it == operationTable.end()) { - return ErrorCode::INVALID_OPERATION_HANDLE; +/* Returns true if operation handle exists, otherwise false */ +static inline bool isOperationHandleExists(uint64_t opHandle) { + if (operationTable.end() == operationTable.find(opHandle)) { + return false; } - origOprHandle = it->second.first; - return ErrorCode::OK; + return true; } -/* Tells if the operation handle belongs to strongbox keymaster. */ -static bool isStrongboxOperation(uint64_t halGeneratedOperationHandle) { - std::map>::iterator it = operationTable.find(halGeneratedOperationHandle); +static inline OperationType getOperationType(uint64_t operationHandle) { + auto it = operationTable.find(operationHandle); if (it == operationTable.end()) { - return false; + return OperationType::UNKNOWN; } - return (SB_KM_OPR == it->second.second); -} - -/* Delete the operation handle entry from operation table. */ -static void deleteOprHandleEntry(uint64_t halGeneratedOperationHandle) { - operationTable.erase(halGeneratedOperationHandle); + return it->second; } /* Clears all the strongbox operation handle entries from operation table */ static void clearStrongboxOprHandleEntries(const std::unique_ptr& oprCtx) { - LOG(INFO) << "Secure Element reset or applet upgrade detected. Removing existing operation handles"; + LOG(INFO) + << "Secure Element reset or applet upgrade detected. Removing existing operation handles"; auto it = operationTable.begin(); while (it != operationTable.end()) { - if (it->second.second == SB_KM_OPR) { //Strongbox operation + if (it->second == OperationType::PRIVATE_OPERATION) { // Strongbox operation LOG(INFO) << "operation handle: " << it->first << " is removed"; - oprCtx->clearOperationData(it->second.first); + oprCtx->clearOperationData(it->first); it = operationTable.erase(it); } else { ++it; @@ -1010,53 +981,85 @@ Return JavacardKeymaster4Device::destroyAttestationIds() { Return JavacardKeymaster4Device::begin(KeyPurpose purpose, const hidl_vec& keyBlob, const hidl_vec& inParams, const HardwareAuthToken& authToken, begin_cb _hidl_cb) { ErrorCode errorCode = ErrorCode::UNKNOWN_ERROR; - hidl_vec outParams; uint64_t operationHandle = 0; - hidl_vec resultParams; - uint64_t generatedOpHandle = 0; - - if(keyBlob.size() == 0) { - LOG(ERROR) << "Error in INS_BEGIN_OPERATION_CMD, keyblob size is 0"; - _hidl_cb(ErrorCode::INVALID_ARGUMENT, resultParams, operationHandle); - return Void(); - } - /* Asymmetric public key operations like RSA Verify, RSA Encrypt, ECDSA verify - * are handled by softkeymaster. + OperationType operType = OperationType::PRIVATE_OPERATION; + hidl_vec outParams; + LOG(DEBUG) << "INS_BEGIN_OPERATION_CMD purpose: " << (int32_t)purpose; + /* + * Asymmetric public key operations are processed inside softkeymaster and private + * key operations are processed inside strongbox keymaster. + * All symmetric key operations are processed inside strongbox keymaster. + * If the purpose is either ENCRYPT / VERIFY then the operation type is set + * to public operation and in case if the key turned out to be a symmetric key then + * handleBeginOperation() function fallbacks to private key operation. */ LOG(DEBUG) << "INS_BEGIN_OPERATION_CMD purpose: " << (int32_t)purpose; if (KeyPurpose::ENCRYPT == purpose || KeyPurpose::VERIFY == purpose) { - BeginOperationRequest request(softKm_->message_version()); - request.purpose = legacy_enum_conversion(purpose); - request.SetKeyMaterial(keyBlob.data(), keyBlob.size()); - request.additional_params.Reinitialize(KmParamSet(inParams)); - - BeginOperationResponse response(softKm_->message_version()); - /* For Symmetric key operation, the BeginOperation returns KM_ERROR_INCOMPATIBLE_ALGORITHM error. */ - softKm_->BeginOperation(request, &response); - errorCode = legacy_enum_conversion(response.error); - LOG(DEBUG) << "INS_BEGIN_OPERATION_CMD softkm BeginOperation status: " << (int32_t) errorCode; - if (errorCode != ErrorCode::OK) - LOG(ERROR) << "INS_BEGIN_OPERATION_CMD error in softkm BeginOperation status: " << (int32_t) errorCode; - - if (response.error == KM_ERROR_OK) { - resultParams = kmParamSet2Hidl(response.output_params); - } - if (response.error != KM_ERROR_INCOMPATIBLE_ALGORITHM) { /*Incompatible algorithm could be handled by JavaCard*/ - errorCode = legacy_enum_conversion(response.error); - /* Create a new operation handle and add a entry inside the operation table map with - * key - new operation handle - * value - hal generated operation handle. - */ - if (errorCode == ErrorCode::OK) { - errorCode = createOprHandleEntry(response.op_handle, SW_KM_OPR, generatedOpHandle); - if (errorCode != ErrorCode::OK) - LOG(ERROR) << "INS_BEGIN_OPERATION_CMD error while creating new operation handle: " << (int32_t) errorCode; + operType = OperationType::PUBLIC_OPERATION; + } + errorCode = handleBeginOperation(purpose, keyBlob, inParams, authToken, outParams, + operationHandle, operType); + if (errorCode == ErrorCode::OK && isOperationHandleExists(operationHandle)) { + LOG(DEBUG) << "Operation handle " << operationHandle + << "already exists" + "in the opertion table. so aborting this opertaion."; + // abort the operation. + errorCode = abortOperation(operationHandle, operType); + if (errorCode == ErrorCode::OK) { + // retry begin to get an another operation handle. + errorCode = handleBeginOperation(purpose, keyBlob, inParams, authToken, outParams, + operationHandle, operType); + if (errorCode == ErrorCode::OK && isOperationHandleExists(operationHandle)) { + errorCode = ErrorCode::UNKNOWN_ERROR; + LOG(ERROR) << "INS_BEGIN_OPERATION_CMD: Failed in begin operation as the" + "operation handle already exists in the operation table." + << (int32_t)errorCode; + // abort the operation. + auto abortErr = abortOperation(operationHandle, operType); + if (abortErr != ErrorCode::OK) { + LOG(ERROR) << "Fail to abort the operation."; + errorCode = abortErr; + } } - _hidl_cb(errorCode, resultParams, generatedOpHandle); - return Void(); } } + // Create an entry inside the operation table for the new operation + // handle. + if (ErrorCode::OK == errorCode) operationTable[operationHandle] = operType; + + _hidl_cb(errorCode, outParams, operationHandle); + return Void(); +} +ErrorCode JavacardKeymaster4Device::handleBeginPublicKeyOperation( + KeyPurpose purpose, const hidl_vec& keyBlob, const hidl_vec& inParams, + hidl_vec& outParams, uint64_t& operationHandle) { + BeginOperationRequest request(softKm_->message_version()); + request.purpose = legacy_enum_conversion(purpose); + request.SetKeyMaterial(keyBlob.data(), keyBlob.size()); + request.additional_params.Reinitialize(KmParamSet(inParams)); + + BeginOperationResponse response(softKm_->message_version()); + /* For Symmetric key operation, the BeginOperation returns + * KM_ERROR_INCOMPATIBLE_ALGORITHM error. */ + softKm_->BeginOperation(request, &response); + ErrorCode errorCode = legacy_enum_conversion(response.error); + LOG(DEBUG) << "INS_BEGIN_OPERATION_CMD softkm BeginOperation status: " << (int32_t)errorCode; + if (ErrorCode::OK == errorCode) { + outParams = kmParamSet2Hidl(response.output_params); + operationHandle = response.op_handle; + } else { + LOG(ERROR) << "INS_BEGIN_OPERATION_CMD error in softkm BeginOperation status: " + << (int32_t)errorCode; + } + return errorCode; +} + +ErrorCode JavacardKeymaster4Device::handleBeginPrivateKeyOperation( + KeyPurpose purpose, const hidl_vec& keyBlob, const hidl_vec& inParams, + const HardwareAuthToken& authToken, hidl_vec& outParams, + uint64_t& operationHandle) { + ErrorCode errorCode = ErrorCode::UNKNOWN_ERROR; cppbor::Array array; std::vector cborOutData; std::unique_ptr item; @@ -1071,78 +1074,107 @@ Return JavacardKeymaster4Device::begin(KeyPurpose purpose, const hidl_vec< cborConverter_.addHardwareAuthToken(array, authToken); std::vector cborData = array.encode(); - // keyCharacteristics.hardwareEnforced is required to store algorithm, digest and padding values in operationInfo - // structure. To retrieve keyCharacteristics.hardwareEnforced, call getKeyCharacateristics. - // By calling getKeyCharacateristics also helps in finding a corrupted keyblob. + // keyCharacteristics.hardwareEnforced is required to store algorithm, digest + // and padding values in operationInfo structure. To retrieve + // keyCharacteristics.hardwareEnforced, call getKeyCharacateristics. By + // calling getKeyCharacateristics also helps in finding a corrupted keyblob. hidl_vec applicationId; hidl_vec applicationData; - if(getTag(inParams, Tag::APPLICATION_ID, param)) { + if (getTag(inParams, Tag::APPLICATION_ID, param)) { applicationId = param.blob; } - if(getTag(inParams, Tag::APPLICATION_DATA, param)) { + if (getTag(inParams, Tag::APPLICATION_DATA, param)) { applicationData = param.blob; } - //Call to getKeyCharacteristics. + // Call to getKeyCharacteristics. getKeyCharacteristics(keyBlob, applicationId, applicationData, - [&](ErrorCode error, KeyCharacteristics keyChars) { - errorCode = error; - keyCharacteristics = keyChars; - }); - LOG(DEBUG) << "INS_BEGIN_OPERATION_CMD getKeyCharacteristics status: " << (int32_t) errorCode; - - if(errorCode == ErrorCode::OK) { + [&](ErrorCode error, KeyCharacteristics keyChars) { + errorCode = error; + keyCharacteristics = keyChars; + }); + LOG(DEBUG) << "INS_BEGIN_OPERATION_CMD StrongboxKM getKeyCharacteristics status: " + << (int32_t)errorCode; + + if (errorCode == ErrorCode::OK) { errorCode = ErrorCode::UNKNOWN_ERROR; - if(getTag(keyCharacteristics.hardwareEnforced, Tag::ALGORITHM, param)) { + if (getTag(keyCharacteristics.hardwareEnforced, Tag::ALGORITHM, param)) { errorCode = sendData(Instruction::INS_BEGIN_OPERATION_CMD, cborData, cborOutData); - if(errorCode == ErrorCode::OK) { - //Skip last 2 bytes in cborData, it contains status. - std::tie(item, errorCode) = decodeData(cborConverter_, std::vector(cborOutData.begin(), cborOutData.end()-2), - true, oprCtx_); + if (errorCode == ErrorCode::OK) { + // Skip last 2 bytes in cborData, it contains status. + std::tie(item, errorCode) = + decodeData(cborConverter_, + std::vector(cborOutData.begin(), cborOutData.end() - 2), + true, oprCtx_); if (item != nullptr) { - if(!cborConverter_.getKeyParameters(item, 1, outParams) || - !cborConverter_.getUint64(item, 2, operationHandle)) { + if (!cborConverter_.getKeyParameters(item, 1, outParams) || + !cborConverter_.getUint64(item, 2, operationHandle)) { errorCode = ErrorCode::UNKNOWN_ERROR; outParams.setToExternal(nullptr, 0); operationHandle = 0; - LOG(ERROR) << "INS_BEGIN_OPERATION_CMD: error in converting cbor data, status: " << (int32_t) errorCode; + LOG(ERROR) << "INS_BEGIN_OPERATION_CMD: error in converting cbor " + "data, status: " + << (int32_t)errorCode; } else { /* Store the operationInfo */ - oprCtx_->setOperationInfo(operationHandle, purpose, param.f.algorithm, inParams); + oprCtx_->setOperationInfo(operationHandle, purpose, param.f.algorithm, + inParams); } } } } else { - LOG(ERROR) << "INS_BEGIN_OPERATION_CMD couldn't find algorithm tag: " << (int32_t)Tag::ALGORITHM; + LOG(ERROR) << "INS_BEGIN_OPERATION_CMD couldn't find algorithm tag: " + << (int32_t)Tag::ALGORITHM; } } else { - LOG(ERROR) << "INS_BEGIN_OPERATION_CMD error in getKeyCharacteristics status: " << (int32_t) errorCode; + LOG(ERROR) << "INS_BEGIN_OPERATION_CMD error in getKeyCharacteristics status: " + << (int32_t)errorCode; } - /* Create a new operation handle and add a entry inside the operation table map with - * key - new operation handle - * value - hal generated operation handle. - */ - if (ErrorCode::OK == errorCode) - errorCode = createOprHandleEntry(operationHandle, SB_KM_OPR, generatedOpHandle); + return errorCode; +} - _hidl_cb(errorCode, outParams, generatedOpHandle); - return Void(); +ErrorCode JavacardKeymaster4Device::handleBeginOperation( + KeyPurpose purpose, const hidl_vec& keyBlob, const hidl_vec& inParams, + const HardwareAuthToken& authToken, hidl_vec& outParams, + uint64_t& operationHandle, OperationType& operType) { + ErrorCode errorCode = ErrorCode::UNKNOWN_ERROR; + if (operType == OperationType::PUBLIC_OPERATION) { + errorCode = + handleBeginPublicKeyOperation(purpose, keyBlob, inParams, outParams, operationHandle); + + // For Symmetric operations handleBeginPublicKeyOperation function + // returns INCOMPATIBLE_ALGORITHM error. Based on this error + // condition it fallbacks to private key operation. + if (errorCode == ErrorCode::INCOMPATIBLE_ALGORITHM) { + operType = OperationType::PRIVATE_OPERATION; + } + } + + if (operType == OperationType::PRIVATE_OPERATION) { + errorCode = handleBeginPrivateKeyOperation(purpose, keyBlob, inParams, authToken, outParams, + operationHandle); + } + return errorCode; } -Return JavacardKeymaster4Device::update(uint64_t halGeneratedOprHandle, const hidl_vec& inParams, const hidl_vec& input, const HardwareAuthToken& authToken, const VerificationToken& verificationToken, update_cb _hidl_cb) { +Return +JavacardKeymaster4Device::update(uint64_t operationHandle, const hidl_vec& inParams, + const hidl_vec& input, const HardwareAuthToken& authToken, + const VerificationToken& verificationToken, update_cb _hidl_cb) { ErrorCode errorCode = ErrorCode::UNKNOWN_ERROR; uint32_t inputConsumed = 0; hidl_vec outParams; hidl_vec output; - uint64_t operationHandle; UpdateOperationResponse response(softKm_->message_version()); - if (ErrorCode::OK != (errorCode = getOrigOperationHandle(halGeneratedOprHandle, operationHandle))) { - LOG(ERROR) << " Operation handle is invalid. This could happen if invalid operation handle is passed or if" - << " secure element reset occurred."; - _hidl_cb(errorCode, inputConsumed, outParams, output); + OperationType operType = getOperationType(operationHandle); + if (OperationType::UNKNOWN == operType) { // operation handle not found + LOG(ERROR) << " Operation handle is invalid. This could happen if invalid operation handle " + "is passed or if" + << " secure element reset occurred."; + _hidl_cb(ErrorCode::INVALID_OPERATION_HANDLE, inputConsumed, outParams, output); return Void(); } - if (!isStrongboxOperation(halGeneratedOprHandle)) { + if (OperationType::PUBLIC_OPERATION == operType) { /* SW keymaster (Public key operation) */ LOG(DEBUG) << "INS_UPDATE_OPERATION_CMD - swkm operation "; UpdateOperationRequest request(softKm_->message_version()); @@ -1153,14 +1185,14 @@ Return JavacardKeymaster4Device::update(uint64_t halGeneratedOprHandle, co softKm_->UpdateOperation(request, &response); errorCode = legacy_enum_conversion(response.error); LOG(DEBUG) << "INS_UPDATE_OPERATION_CMD - swkm update operation status: " - << (int32_t) errorCode; + << (int32_t)errorCode; if (response.error == KM_ERROR_OK) { inputConsumed = response.input_consumed; outParams = kmParamSet2Hidl(response.output_params); output = kmBuffer2hidlVec(response.output); } else { - LOG(ERROR) << "INS_UPDATE_OPERATION_CMD - error swkm update operation status: " - << (int32_t) errorCode; + LOG(ERROR) << "INS_UPDATE_OPERATION_CMD - error swkm update operation status: " + << (int32_t)errorCode; } } else { /* Strongbox Keymaster operation */ @@ -1173,20 +1205,24 @@ Return JavacardKeymaster4Device::update(uint64_t halGeneratedOprHandle, co std::unique_ptr item; std::vector cborOutData; std::vector asn1ParamsVerified; - // For symmetic ciphers only block aligned data is send to javacard Applet to reduce the number of calls to - //javacard. If the input message is less than block size then it is buffered inside the HAL. so in case if + // For symmetic ciphers only block aligned data is send to javacard Applet to reduce the + // number of calls to + // javacard. If the input message is less than block size then it is buffered inside the + // HAL. so in case if // after buffering there is no data to send to javacard don't call javacard applet. - //For AES GCM operations, even though the input length is 0(which is not block aligned), if there is - //ASSOCIATED_DATA present in KeyParameters. Then we need to make a call to javacard Applet. - if(data.size() == 0 && !findTag(inParams, Tag::ASSOCIATED_DATA)) { - //Return OK, since this is not error case. + // For AES GCM operations, even though the input length is 0(which is not block + // aligned), if there is ASSOCIATED_DATA present in KeyParameters. Then we need to make + // a call to javacard Applet. + if (data.size() == 0 && !findTag(inParams, Tag::ASSOCIATED_DATA)) { + // Return OK, since this is not error case. LOG(DEBUG) << "sendDataCallback: data size is zero"; return ErrorCode::OK; } - if(ErrorCode::OK != (errorCode = encodeParametersVerified(verificationToken, asn1ParamsVerified))) { + if (ErrorCode::OK != + (errorCode = encodeParametersVerified(verificationToken, asn1ParamsVerified))) { LOG(ERROR) << "sendDataCallback: error in encodeParametersVerified status: " - << (int32_t) errorCode; + << (int32_t)errorCode; return errorCode; } @@ -1200,65 +1236,77 @@ Return JavacardKeymaster4Device::update(uint64_t halGeneratedOprHandle, co errorCode = sendData(Instruction::INS_UPDATE_OPERATION_CMD, cborData, cborOutData); - if(errorCode == ErrorCode::OK) { - //Skip last 2 bytes in cborData, it contains status. - std::tie(item, errorCode) = decodeData(cborConverter_, std::vector(cborOutData.begin(), cborOutData.end()-2), - true, oprCtx_); + if (errorCode == ErrorCode::OK) { + // Skip last 2 bytes in cborData, it contains status. + std::tie(item, errorCode) = + decodeData(cborConverter_, + std::vector(cborOutData.begin(), cborOutData.end() - 2), + true, oprCtx_); if (item != nullptr) { /*Ignore inputConsumed from javacard SE since HAL consumes all the input */ - //cborConverter_.getUint64(item, 1, inputConsumed); - //This callback function may gets called multiple times so parse and get the outParams only once. - //Otherwise there can be chance of duplicate entries in outParams. Use tempOut to collect all the - //cipher text and finally copy it to the output. getBinaryArray function appends the new cipher text - //at the end of the tempOut(std::vector). - if((outParams.size() == 0 && !cborConverter_.getKeyParameters(item, 2, outParams)) || - !cborConverter_.getBinaryArray(item, 3, tempOut)) { + // cborConverter_.getUint64(item, 1, inputConsumed); + // This callback function may gets called multiple times so parse and get the + // outParams only once. Otherwise there can be chance of duplicate entries in + // outParams. Use tempOut to collect all the cipher text and finally copy it to + // the output. getBinaryArray function appends the new cipher text at the end of + // the tempOut(std::vector). + if ((outParams.size() == 0 && + !cborConverter_.getKeyParameters(item, 2, outParams)) || + !cborConverter_.getBinaryArray(item, 3, tempOut)) { outParams.setToExternal(nullptr, 0); tempOut.clear(); errorCode = ErrorCode::UNKNOWN_ERROR; - LOG(ERROR) << "sendDataCallback: INS_UPDATE_OPERATION_CMD: error while converting cbor data, status: " << (int32_t) errorCode; + LOG(ERROR) << "sendDataCallback: INS_UPDATE_OPERATION_CMD: error while " + "converting cbor data, status: " + << (int32_t)errorCode; } } } return errorCode; }; - if(ErrorCode::OK == (errorCode = oprCtx_->update(operationHandle, std::vector(input), - sendDataCallback))) { + if (ErrorCode::OK == + (errorCode = + oprCtx_->update(operationHandle, std::vector(input), sendDataCallback))) { /* Consumed all the input */ inputConsumed = input.size(); output = tempOut; } - LOG(DEBUG) << "Update operation status: " << (int32_t) errorCode; - if(ErrorCode::OK != errorCode) { - LOG(ERROR) << "Error in update operation, status: " << (int32_t) errorCode; - abort(halGeneratedOprHandle); + LOG(DEBUG) << "Update operation status: " << (int32_t)errorCode; + if (ErrorCode::OK != errorCode) { + LOG(ERROR) << "Error in update operation, status: " << (int32_t)errorCode; + abort(operationHandle); } } - if(ErrorCode::OK != errorCode) { + if (ErrorCode::OK != errorCode) { /* Delete the entry from operation table. */ - LOG(ERROR) << "Delete entry from operation table, status: " << (int32_t) errorCode; - deleteOprHandleEntry(halGeneratedOprHandle); + LOG(ERROR) << "Delete entry from operation table, status: " << (int32_t)errorCode; + operationTable.erase(operationHandle); } _hidl_cb(errorCode, inputConsumed, outParams, output); return Void(); } -Return JavacardKeymaster4Device::finish(uint64_t halGeneratedOprHandle, const hidl_vec& inParams, const hidl_vec& input, const hidl_vec& signature, const HardwareAuthToken& authToken, const VerificationToken& verificationToken, finish_cb _hidl_cb) { +Return +JavacardKeymaster4Device::finish(uint64_t operationHandle, const hidl_vec& inParams, + const hidl_vec& input, const hidl_vec& signature, + const HardwareAuthToken& authToken, + const VerificationToken& verificationToken, finish_cb _hidl_cb) { ErrorCode errorCode = ErrorCode::UNKNOWN_ERROR; - uint64_t operationHandle; hidl_vec outParams; hidl_vec output; FinishOperationResponse response(softKm_->message_version()); + OperationType operType = getOperationType(operationHandle); - if (ErrorCode::OK != (errorCode = getOrigOperationHandle(halGeneratedOprHandle, operationHandle))) { - LOG(ERROR) << " Operation handle is invalid. This could happen if invalid operation handle is passed or if" - << " secure element reset occurred."; - _hidl_cb(errorCode, outParams, output); + if (OperationType::UNKNOWN == operType) { // operation handle not found + LOG(ERROR) << " Operation handle is invalid. This could happen if invalid operation handle " + "is passed or if" + << " secure element reset occurred."; + _hidl_cb(ErrorCode::INVALID_OPERATION_HANDLE, outParams, output); return Void(); } - if (!isStrongboxOperation(halGeneratedOprHandle)) { + if (OperationType::PUBLIC_OPERATION == operType) { /* SW keymaster (Public key operation) */ LOG(DEBUG) << "FINISH - swkm operation "; FinishOperationRequest request(softKm_->message_version()); @@ -1270,13 +1318,13 @@ Return JavacardKeymaster4Device::finish(uint64_t halGeneratedOprHandle, co softKm_->FinishOperation(request, &response); errorCode = legacy_enum_conversion(response.error); - LOG(DEBUG) << "FINISH - swkm operation, status: " << (int32_t) errorCode; + LOG(DEBUG) << "FINISH - swkm operation, status: " << (int32_t)errorCode; if (response.error == KM_ERROR_OK) { outParams = kmParamSet2Hidl(response.output_params); output = kmBuffer2hidlVec(response.output); } else { - LOG(ERROR) << "Error in finish operation, status: " << (int32_t) errorCode; + LOG(ERROR) << "Error in finish operation, status: " << (int32_t)errorCode; } } else { /* Strongbox Keymaster operation */ @@ -1286,8 +1334,8 @@ Return JavacardKeymaster4Device::finish(uint64_t halGeneratedOprHandle, co * may be called multiple times if the input data is larger than MAX_ALLOWED_INPUT_SIZE. * This callback function decides whether to call update/finish instruction based on the * input received from the OperationContext through finish variable. - * if finish variable is false update instruction is called, if it is true finish instruction - * is called. + * if finish variable is false update instruction is called, if it is true finish + * instruction is called. */ auto sendDataCallback = [&](std::vector& data, bool finish) -> ErrorCode { cppbor::Array array; @@ -1297,23 +1345,26 @@ Return JavacardKeymaster4Device::finish(uint64_t halGeneratedOprHandle, co int keyParamPos, outputPos; std::vector asn1ParamsVerified; - if(ErrorCode::OK != (errorCode = encodeParametersVerified(verificationToken, asn1ParamsVerified))) { - LOG(ERROR) << "sendDataCallback: Error in encodeParametersVerified, status: " << (int32_t) errorCode; + if (ErrorCode::OK != + (errorCode = encodeParametersVerified(verificationToken, asn1ParamsVerified))) { + LOG(ERROR) << "sendDataCallback: Error in encodeParametersVerified, status: " + << (int32_t)errorCode; return errorCode; } - //In case if there is ASSOCIATED_DATA present in the keyparams, then make sure it is either passed with - //update call or finish call. Don't send ASSOCIATED_DATA in both update and finish calls. aadTag is used to - //check if ASSOCIATED_DATA is already sent in update call. If addTag is true then skip ASSOCIATED_DATA from - //keyparams in finish call. - // Convert input data to cbor format + // In case if there is ASSOCIATED_DATA present in the keyparams, then make sure it is + // either passed with update call or finish call. Don't send ASSOCIATED_DATA in both + // update and finish calls. aadTag is used to check if ASSOCIATED_DATA is already sent + // in update call. If addTag is true then skip ASSOCIATED_DATA from keyparams in finish + // call. + // Convert input data to cbor format array.add(operationHandle); - if(finish) { + if (finish) { std::vector finishParams; LOG(DEBUG) << "sendDataCallback: finish operation"; - if(aadTag) { - for(int i = 0; i < inParams.size(); i++) { - if(inParams[i].tag != Tag::ASSOCIATED_DATA) + if (aadTag) { + for (int i = 0; i < inParams.size(); i++) { + if (inParams[i].tag != Tag::ASSOCIATED_DATA) finishParams.push_back(inParams[i]); } } else { @@ -1327,7 +1378,7 @@ Return JavacardKeymaster4Device::finish(uint64_t halGeneratedOprHandle, co outputPos = 2; } else { LOG(DEBUG) << "sendDataCallback: update operation"; - if(findTag(inParams, Tag::ASSOCIATED_DATA)) { + if (findTag(inParams, Tag::ASSOCIATED_DATA)) { aadTag = true; } cborConverter_.addKeyparameters(array, inParams); @@ -1341,52 +1392,74 @@ Return JavacardKeymaster4Device::finish(uint64_t halGeneratedOprHandle, co std::vector cborData = array.encode(); errorCode = sendData(ins, cborData, cborOutData); - if(errorCode == ErrorCode::OK) { - //Skip last 2 bytes in cborData, it contains status. - std::tie(item, errorCode) = decodeData(cborConverter_, std::vector(cborOutData.begin(), cborOutData.end()-2), - true, oprCtx_); + if (errorCode == ErrorCode::OK) { + // Skip last 2 bytes in cborData, it contains status. + std::tie(item, errorCode) = + decodeData(cborConverter_, + std::vector(cborOutData.begin(), cborOutData.end() - 2), + true, oprCtx_); if (item != nullptr) { - //There is a change that this finish callback may gets called multiple times if the input data size - //is larger the MAX_ALLOWED_INPUT_SIZE (Refer OperationContext) so parse and get the outParams only - //once. Otherwise there can be chance of duplicate entries in outParams. Use tempOut to collect all - //the cipher text and finally copy it to the output. getBinaryArray function appends the new cipher - //text at the end of the tempOut(std::vector). - if((outParams.size() == 0 && !cborConverter_.getKeyParameters(item, keyParamPos, outParams)) || - !cborConverter_.getBinaryArray(item, outputPos, tempOut)) { + // There is a change that this finish callback may gets called multiple times if + // the input data size is larger the MAX_ALLOWED_INPUT_SIZE (Refer + // OperationContext) so parse and get the outParams only once. Otherwise there + // can be chance of duplicate entries in outParams. Use tempOut to collect all + // the cipher text and finally copy it to the output. getBinaryArray function + // appends the new cipher text at the end of the tempOut(std::vector). + if ((outParams.size() == 0 && + !cborConverter_.getKeyParameters(item, keyParamPos, outParams)) || + !cborConverter_.getBinaryArray(item, outputPos, tempOut)) { outParams.setToExternal(nullptr, 0); tempOut.clear(); errorCode = ErrorCode::UNKNOWN_ERROR; - LOG(ERROR) << "sendDataCallback: error while converting cbor data in operation: " << (int32_t)ins << " decodeData, status: " << (int32_t) errorCode; + LOG(ERROR) + << "sendDataCallback: error while converting cbor data in operation: " + << (int32_t)ins << " decodeData, status: " << (int32_t)errorCode; } } } return errorCode; }; - if(ErrorCode::OK == (errorCode = oprCtx_->finish(operationHandle, std::vector(input), - sendDataCallback))) { + if (ErrorCode::OK == + (errorCode = + oprCtx_->finish(operationHandle, std::vector(input), sendDataCallback))) { output = tempOut; } if (ErrorCode::OK != errorCode) { - LOG(ERROR) << "Error in finish operation, status: " << (int32_t) errorCode; - abort(halGeneratedOprHandle); + LOG(ERROR) << "Error in finish operation, status: " << (int32_t)errorCode; + abort(operationHandle); } } /* Delete the entry from operation table. */ - deleteOprHandleEntry(halGeneratedOprHandle); + operationTable.erase(operationHandle); oprCtx_->clearOperationData(operationHandle); - LOG(DEBUG) << "finish operation, status: " << (int32_t) errorCode; + LOG(DEBUG) << "finish operation, status: " << (int32_t)errorCode; _hidl_cb(errorCode, outParams, output); return Void(); } -Return JavacardKeymaster4Device::abort(uint64_t halGeneratedOprHandle) { +ErrorCode JavacardKeymaster4Device::abortPrivateKeyOperation(uint64_t operationHandle) { ErrorCode errorCode = ErrorCode::UNKNOWN_ERROR; - uint64_t operationHandle; - if (ErrorCode::OK != (errorCode = getOrigOperationHandle(halGeneratedOprHandle, operationHandle))) { - LOG(ERROR) << " Operation handle is invalid. This could happen if invalid operation handle is passed or if" - << " secure element reset occurred."; - return errorCode; + cppbor::Array array; + std::unique_ptr item; + std::vector cborOutData; + + /* Convert input data to cbor format */ + array.add(operationHandle); + std::vector cborData = array.encode(); + + errorCode = sendData(Instruction::INS_ABORT_OPERATION_CMD, cborData, cborOutData); + + if (errorCode == ErrorCode::OK) { + // Skip last 2 bytes in cborData, it contains status. + std::tie(item, errorCode) = decodeData( + cborConverter_, std::vector(cborOutData.begin(), cborOutData.end() - 2), true, + oprCtx_); } + return errorCode; +} + +ErrorCode JavacardKeymaster4Device::abortPublicKeyOperation(uint64_t operationHandle) { + ErrorCode errorCode = ErrorCode::UNKNOWN_ERROR; AbortOperationRequest request(softKm_->message_version()); request.op_handle = operationHandle; @@ -1394,72 +1467,35 @@ Return JavacardKeymaster4Device::abort(uint64_t halGeneratedOprHandle softKm_->AbortOperation(request, &response); errorCode = legacy_enum_conversion(response.error); - LOG(DEBUG) << "swkm abort operation, status: " << (int32_t) errorCode; - if (response.error == KM_ERROR_INVALID_OPERATION_HANDLE) { - cppbor::Array array; - std::unique_ptr item; - std::vector cborOutData; - - /* Convert input data to cbor format */ - array.add(operationHandle); - std::vector cborData = array.encode(); - - errorCode = sendData(Instruction::INS_ABORT_OPERATION_CMD, cborData, cborOutData); - - if(errorCode == ErrorCode::OK) { - //Skip last 2 bytes in cborData, it contains status. - std::tie(item, errorCode) = decodeData(cborConverter_, std::vector(cborOutData.begin(), cborOutData.end()-2), - true, oprCtx_); - } - } - /* Delete the entry on this operationHandle */ - oprCtx_->clearOperationData(operationHandle); - deleteOprHandleEntry(halGeneratedOprHandle); return errorCode; } -// Methods from ::android::hardware::keymaster::V4_1::IKeymasterDevice follow. -Return<::android::hardware::keymaster::V4_1::ErrorCode> JavacardKeymaster4Device::deviceLocked(bool passwordOnly, const VerificationToken& verificationToken) { - cppbor::Array array; - std::unique_ptr item; - std::vector cborOutData; - ::android::hardware::keymaster::V4_1::ErrorCode errorCode = ::android::hardware::keymaster::V4_1::ErrorCode::UNKNOWN_ERROR; - std::vector asn1ParamsVerified; - ErrorCode ret = ErrorCode::UNKNOWN_ERROR; - - if(ErrorCode::OK != (ret = encodeParametersVerified(verificationToken, asn1ParamsVerified))) { - LOG(DEBUG) << "INS_DEVICE_LOCKED_CMD: Error in encodeParametersVerified, status: " << (int32_t) errorCode; - return errorCode; - } +ErrorCode JavacardKeymaster4Device::abortOperation(uint64_t operationHandle, + OperationType operType) { + if (operType == OperationType::UNKNOWN) return ErrorCode::UNKNOWN_ERROR; - /* Convert input data to cbor format */ - array.add(passwordOnly); - cborConverter_.addVerificationToken(array, verificationToken, asn1ParamsVerified); - std::vector cborData = array.encode(); - - ret = sendData(Instruction::INS_DEVICE_LOCKED_CMD, cborData, cborOutData); - - if(ret == ErrorCode::OK) { - //Skip last 2 bytes in cborData, it contains status. - std::tie(item, errorCode) = decodeData<::android::hardware::keymaster::V4_1::ErrorCode>( - cborConverter_, std::vector(cborOutData.begin(), cborOutData.end()-2), true, oprCtx_); + if (OperationType::PUBLIC_OPERATION == operType) { + return abortPublicKeyOperation(operationHandle); + } else { + return abortPrivateKeyOperation(operationHandle); } - return errorCode; } -Return<::android::hardware::keymaster::V4_1::ErrorCode> JavacardKeymaster4Device::earlyBootEnded() { - std::unique_ptr item; - std::string message; - std::vector cborOutData; - std::vector cborInput; - ::android::hardware::keymaster::V4_1::ErrorCode errorCode = ::android::hardware::keymaster::V4_1::ErrorCode::UNKNOWN_ERROR; - - ErrorCode ret = sendData(Instruction::INS_EARLY_BOOT_ENDED_CMD, cborInput, cborOutData); +Return JavacardKeymaster4Device::abort(uint64_t operationHandle) { + ErrorCode errorCode = ErrorCode::UNKNOWN_ERROR; + OperationType operType = getOperationType(operationHandle); + if (OperationType::UNKNOWN == operType) { // operation handle not found + LOG(ERROR) << " Operation handle is invalid. This could happen if invalid " + "operation handle is passed or if" + << " secure element reset occurred."; + return ErrorCode::INVALID_OPERATION_HANDLE; + } - if(ret == ErrorCode::OK) { - //Skip last 2 bytes in cborData, it contains status. - std::tie(item, errorCode) = decodeData<::android::hardware::keymaster::V4_1::ErrorCode>( - cborConverter_, std::vector(cborOutData.begin(), cborOutData.end()-2), true, oprCtx_); + errorCode = abortOperation(operationHandle, operType); + if (errorCode == ErrorCode::OK) { + /* Delete the entry on this operationHandle */ + oprCtx_->clearOperationData(operationHandle); + operationTable.erase(operationHandle); } return errorCode; } diff --git a/HAL/keymaster/include/JavacardKeymaster4Device.h b/HAL/keymaster/include/JavacardKeymaster4Device.h index c8ada383..e34a2aee 100644 --- a/HAL/keymaster/include/JavacardKeymaster4Device.h +++ b/HAL/keymaster/include/JavacardKeymaster4Device.h @@ -54,6 +54,14 @@ using ::android::hardware::keymaster::V4_0::Tag; using V41ErrorCode = ::android::hardware::keymaster::V4_1::ErrorCode; +enum class OperationType { + /* Public operations are processed inside softkeymaster */ + PUBLIC_OPERATION = 0, + /* Private operations are processed inside strongbox */ + PRIVATE_OPERATION = 1, + UNKNOWN = 2, +}; + class JavacardKeymaster4Device : public IKeymasterDevice { public: @@ -87,8 +95,31 @@ class JavacardKeymaster4Device : public IKeymasterDevice { protected: CborConverter cborConverter_; - -private: + + private: + ErrorCode handleBeginPublicKeyOperation(KeyPurpose purpose, const hidl_vec& keyBlob, + const hidl_vec& inParams, + hidl_vec& outParams, + uint64_t& operationHandle); + + ErrorCode handleBeginPrivateKeyOperation(KeyPurpose purpose, const hidl_vec& keyBlob, + const hidl_vec& inParams, + const HardwareAuthToken& authToken, + hidl_vec& outParams, + uint64_t& operationHandle); + + ErrorCode handleBeginOperation(KeyPurpose purpose, const hidl_vec& keyBlob, + const hidl_vec& inParams, + const HardwareAuthToken& authToken, + hidl_vec& outParams, uint64_t& operationHandle, + OperationType& operType); + + ErrorCode abortOperation(uint64_t operationHandle, OperationType operType); + + ErrorCode abortPublicKeyOperation(uint64_t operationHandle); + + ErrorCode abortPrivateKeyOperation(uint64_t operationHandle); + std::unique_ptr<::keymaster::AndroidKeymaster> softKm_; std::unique_ptr oprCtx_; bool isEachSystemPropertySet; diff --git a/ProvisioningTool/src/provision.cpp b/ProvisioningTool/src/provision.cpp index 2007e08e..69ac3621 100644 --- a/ProvisioningTool/src/provision.cpp +++ b/ProvisioningTool/src/provision.cpp @@ -26,6 +26,7 @@ #include #include +#define SE_POWER_RESET_STATUS_FLAG (1 << 30) enum ProvisionStatus { NOT_PROVISIONED = 0x00, PROVISION_STATUS_ATTESTATION_KEY = 0x01, @@ -112,6 +113,17 @@ int getUint64(const std::unique_ptr &item, const uint32_t pos, uint64_t &v return SUCCESS; } + +uint64_t unmaskPowerResetFlag(uint64_t errorCode) { + bool isSeResetOccurred = (0 != (errorCode & SE_POWER_RESET_STATUS_FLAG)); + + if (isSeResetOccurred) { + printf("\n Secure element reset happened\n"); + errorCode &= ~SE_POWER_RESET_STATUS_FLAG; + } + return errorCode; +} + int provisionData(std::shared_ptr& pSocket, std::string apdu, std::vector& response) { if (SUCCESS != sendData(pSocket, apdu, response)) { return FAILURE; @@ -128,6 +140,7 @@ int provisionData(std::shared_ptr& pSocket, std::string apdu, s const Uint* uintVal = item.get()->asUint(); err = uintVal->value(); } + err = unmaskPowerResetFlag(err); if (err != 0) { printf("\n Failed with error:%ld", err); return FAILURE; From 7a5f77f70ac37be299e2fad6889d998ef2215952 Mon Sep 17 00:00:00 2001 From: Subrahmanya Manikanta Venkateswarlu Bhamidipati Kameswara Sri Date: Fri, 22 Oct 2021 17:22:09 +0000 Subject: [PATCH 2/3] 1. Store AUTH_TIMEOUT_MILLIS in keyblob. 2. Validate Tokens in littleEndian and BigEndian formats. --- .../android/javacard/keymaster/KMArray.java | 6 + .../javacard/keymaster/KMIntegerTag.java | 4 +- .../javacard/keymaster/KMKeyParameters.java | 70 +++++ .../javacard/keymaster/KMKeymasterApplet.java | 267 +++++++++++++----- .../android/javacard/keymaster/KMType.java | 2 + 5 files changed, 280 insertions(+), 69 deletions(-) diff --git a/Applet/src/com/android/javacard/keymaster/KMArray.java b/Applet/src/com/android/javacard/keymaster/KMArray.java index bfa09269..adf61723 100644 --- a/Applet/src/com/android/javacard/keymaster/KMArray.java +++ b/Applet/src/com/android/javacard/keymaster/KMArray.java @@ -92,6 +92,12 @@ public void add(short index, short objPtr) { (short) (instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + ARRAY_HEADER_SIZE + (short) (index * 2)), objPtr); } + + public void deleteLastEntry() { + short len = length(); + Util.setShort(heap, (short) (instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2), (short) (len -1)); + } + public short get(short index) { short len = length(); diff --git a/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java b/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java index 6ddec4bd..c4bab026 100644 --- a/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java +++ b/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java @@ -48,7 +48,9 @@ public class KMIntegerTag extends KMTag { ACTIVE_DATETIME, ORIGINATION_EXPIRE_DATETIME, USAGE_EXPIRE_DATETIME, - CREATION_DATETIME + CREATION_DATETIME, + // Custom tag. + AUTH_TIMEOUT_MILLIS }; private KMIntegerTag() { diff --git a/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java b/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java index 0ef85ae4..ef1e1e00 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java +++ b/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java @@ -26,6 +26,9 @@ * arrayPtr is a pointer to array with any KMTag subtype instances. */ public class KMKeyParameters extends KMType { + + + private static short[] customTags; private static KMKeyParameters prototype; @@ -211,6 +214,9 @@ public static short makeHwEnforced(short keyParamsPtr, byte origin, .instance(KMType.UINT_TAG, KMType.BOOT_PATCH_LEVEL, bootPatchObjPtr); Util.setShort(scratchPad, arrInd, bootPatchTag); arrInd += 2; + // Add custom tags at the end of the array. So it becomes easy to + // delete them when sending key characteristics back to HAL. + arrInd = addCustomTags(keyParamsPtr, scratchPad, arrInd); return createKeyParameters(scratchPad, (short) (arrInd / 2)); } @@ -318,4 +324,68 @@ public static short createKeyParameters(byte[] ptrArr, short len) { } return KMKeyParameters.instance(arrPtr); } + + private static short[] getCustomTags() { + if (customTags == null) { + customTags = new short[] { + KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, + }; + } + return customTags; + } + + public static short addCustomTags(short keyParams, byte[] scratchPad, short offset) { + short[] customTags = getCustomTags(); + short index = 0; + short tagPtr; + short len = (short) customTags.length; + short tagType; + while (index < len) { + tagType = customTags[(short) (index + 1)]; + switch(tagType) { + case KMType.AUTH_TIMEOUT_MILLIS: + short authTimeOutTag = + KMKeyParameters.cast(keyParams).findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT); + if (authTimeOutTag != KMType.INVALID_VALUE) { + tagPtr = createAuthTimeOutMillisTag(authTimeOutTag, scratchPad, offset); + Util.setShort(scratchPad, offset, tagPtr); + offset += 2; + } + break; + default: + break; + } + index += 2; + } + return offset; + } + + public void deleteCustomTags() { + short arrPtr = getVals(); + short[] customTags = getCustomTags(); + short index = (short) (customTags.length - 1); + short obj; + while (index >= 0) { + obj = findTag(customTags[(short) (index - 1)], customTags[index]); + if (obj != KMType.INVALID_VALUE) { + KMArray.cast(arrPtr).deleteLastEntry(); + } + index -= 2; + } + } + + public static short createAuthTimeOutMillisTag(short authTimeOutTag, byte[] scratchPad, short offset) { + short authTime = KMIntegerTag.cast(authTimeOutTag).getValue(); + Util.arrayFillNonAtomic(scratchPad, offset, (short) 40, (byte) 0); + Util.arrayCopyNonAtomic( + KMInteger.cast(authTime).getBuffer(), + KMInteger.cast(authTime).getStartOff(), + scratchPad, + (short) (offset + 8 - KMInteger.cast(authTime).length()), + KMInteger.cast(authTime).length()); + KMUtils.convertToMilliseconds(scratchPad, offset, (short) (offset + 8), (short) (offset + 16)); + return KMIntegerTag.instance(KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, + KMInteger.uint_64(scratchPad, (short) (offset + 8))); + } + } diff --git a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java index 29413a45..a7ec86a8 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -966,6 +966,9 @@ private void processGetKeyCharacteristicsCmd(APDU apdu) { parseEncryptedKeyBlob(scratchPad); // Check Version and Patch Level checkVersionAndPatchLevel(scratchPad); + // Remove custom tags from key characteristics + short hwParams = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getHardwareEnforced(); + KMKeyParameters.cast(hwParams).deleteCustomTags(); // make response. tmpVariables[0] = KMArray.instance((short) 2); KMArray.cast(tmpVariables[0]).add((short) 0, buildErrorStatus(KMError.OK)); @@ -1953,7 +1956,13 @@ private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratch private void authorizeUpdateFinishOperation(KMOperationState op, byte[] scratchPad) { // If one time user Authentication is required if (op.isSecureUserIdReqd() && !op.isAuthTimeoutValidated()) { - validateVerificationToken(op, data[VERIFICATION_TOKEN], scratchPad); + // Validate Verification Token. + validateVerificationToken(data[VERIFICATION_TOKEN], scratchPad); + // validate operation handle. + short ptr = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getChallenge(); + if (KMInteger.compare(ptr, op.getHandle()) != 0) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } tmpVariables[0] = op.getAuthTime(); tmpVariables[2] = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getTimestamp(); if (tmpVariables[2] == KMType.INVALID_VALUE) { @@ -2005,48 +2014,65 @@ private void authorizeDeviceUnlock(short hwToken) { } } - private void validateVerificationToken(KMOperationState op, short verToken, byte[] scratchPad) { - // CBOR Encoding is always big endian and Java is big endian - short ptr = KMVerificationToken.cast(verToken).getMac(); - // If mac length is zero then token is empty. - if (KMByteBlob.cast(ptr).length() == 0) { - return; - } - validateVerificationToken(verToken, scratchPad); - // validate operation handle. - ptr = KMVerificationToken.cast(verToken).getChallenge(); - if (KMInteger.compare(ptr, op.getHandle()) != 0) { - KMException.throwIt(KMError.VERIFICATION_FAILED); + private boolean verifyVerificationTokenMacInBigEndian(short verToken, short key, byte[] scratchPad) { + // concatenation length will be 37 + length of verified parameters list - which + // is typically + // empty + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + // Add "Auth Verification" - 17 bytes. + Util.arrayCopy(authVerification, (short) 0, scratchPad, (short) 0, (short) authVerification.length); + short len = (short) authVerification.length; + // concatenate challenge - 8 bytes + short ptr = KMVerificationToken.cast(verToken).getChallenge(); + KMInteger.cast(ptr).value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // concatenate timestamp -8 bytes + ptr = KMVerificationToken.cast(verToken).getTimestamp(); + KMInteger.cast(ptr).value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // concatenate security level - 4 bytes + ptr = KMVerificationToken.cast(verToken).getSecurityLevel(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + len += 4; + // concatenate Parameters verified - blob of encoded data. + ptr = KMVerificationToken.cast(verToken).getParametersVerified(); + if (KMByteBlob.cast(ptr).length() != 0) { + len += KMByteBlob.cast(ptr).getValues(scratchPad, (short) 0); } + // hmac the data + ptr = KMVerificationToken.cast(verToken).getMac(); + + return seProvider.hmacVerify( + KMByteBlob.cast(key).getBuffer(), + KMByteBlob.cast(key).getStartOff(), + KMByteBlob.cast(key).length(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); } - private void validateVerificationToken(short verToken, byte[] scratchPad) { - short ptr = KMVerificationToken.cast(verToken).getMac(); - short len; - // If mac length is zero then token is empty. - if (KMByteBlob.cast(ptr).length() == 0) { - return; - } - // concatenation length will be 37 + length of verified parameters list - which is typically + private boolean verifyVerificationTokenMacInLittleEndian(short verToken, short key, byte[] scratchPad) { + // concatenation length will be 37 + length of verified parameters list - which + // is typically // empty Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); // Add "Auth Verification" - 17 bytes. - Util.arrayCopy( - authVerification, (short) 0, scratchPad, (short) 0, (short) authVerification.length); - len = (short) authVerification.length; + Util.arrayCopy(authVerification, (short) 0, scratchPad, (short) 0, (short) authVerification.length); + short len = (short) authVerification.length; // concatenate challenge - 8 bytes - ptr = KMVerificationToken.cast(verToken).getChallenge(); - KMInteger.cast(ptr) - .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + short ptr = KMVerificationToken.cast(verToken).getChallenge(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); len += 8; // concatenate timestamp -8 bytes ptr = KMVerificationToken.cast(verToken).getTimestamp(); - KMInteger.cast(ptr) - .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); len += 8; // concatenate security level - 4 bytes ptr = KMVerificationToken.cast(verToken).getSecurityLevel(); - scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + scratchPad[len] = KMEnum.cast(ptr).getVal(); len += 4; // concatenate Parameters verified - blob of encoded data. ptr = KMVerificationToken.cast(verToken).getParametersVerified(); @@ -2055,21 +2081,31 @@ private void validateVerificationToken(short verToken, byte[] scratchPad) { } // hmac the data ptr = KMVerificationToken.cast(verToken).getMac(); + + return seProvider.hmacVerify( + KMByteBlob.cast(key).getBuffer(), + KMByteBlob.cast(key).getStartOff(), + KMByteBlob.cast(key).length(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private void validateVerificationToken(short verToken, byte[] scratchPad) { + short ptr = KMVerificationToken.cast(verToken).getMac(); + // If mac length is zero then token is empty. + if (KMByteBlob.cast(ptr).length() == 0) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } short key = repository.getComputedHmacKey(); - boolean verified = - seProvider.hmacVerify( - KMByteBlob.cast(key).getBuffer(), - KMByteBlob.cast(key).getStartOff(), - KMByteBlob.cast(key).length(), - scratchPad, - (short) 0, - len, - KMByteBlob.cast(ptr).getBuffer(), - KMByteBlob.cast(ptr).getStartOff(), - KMByteBlob.cast(ptr).length()); - if (!verified) { - KMException.throwIt(KMError.VERIFICATION_FAILED); + if (!verifyVerificationTokenMacInLittleEndian(verToken, key, scratchPad) && + !verifyVerificationTokenMacInBigEndian(verToken, key, scratchPad)) { + // Throw Exception if none of the combination works. + KMException.throwIt(KMError.VERIFICATION_FAILED); } } @@ -2752,12 +2788,17 @@ private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratc // authenticate user authTokenMatches(userSecureIdPtr, authType, scratchPad); + authTimeoutTagPtr = + KMKeyParameters.findTag(KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, data[HW_PARAMETERS]); + if (authTimeoutTagPtr == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } authTime = KMIntegerTag.cast(authTimeoutTagPtr).getValue(); // set the one time auth op.setOneTimeAuthReqd(true); // set the authentication time stamp in operation state authTime = - addAuthTimeToTimeStamp(authTime, + addIntegers(authTime, KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp(), scratchPad); op.setAuthTime( KMInteger.cast(authTime).getBuffer(), KMInteger.cast(authTime).getStartOff()); @@ -2793,9 +2834,7 @@ private boolean isHwAuthTokenContainsMatchingSecureId(short hwAuthToken, private void authTokenMatches(short userSecureIdsPtr, short authType, byte[] scratchPad) { - if (!validateHwToken(data[HW_TOKEN], scratchPad)) { - KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); - } + validateHwToken(data[HW_TOKEN], scratchPad); if (!isHwAuthTokenContainsMatchingSecureId(data[HW_TOKEN], userSecureIdsPtr)) { KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); } @@ -2806,20 +2845,14 @@ private void authTokenMatches(short userSecureIdsPtr, short authType, KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); } } - - private boolean validateHwToken(short hwToken, byte[] scratchPad) { - // CBOR Encoding is always big endian - short ptr = KMHardwareAuthToken.cast(hwToken).getMac(); - short len; - // If mac length is zero then token is empty. - if (KMByteBlob.cast(ptr).length() == 0) { - return false; - } + + private boolean verifyHwTokenMacAsPerSpecification(short hwToken, short key, byte[] scratchPad) { + short len = 0; // add 0 Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); len = 1; // concatenate challenge - 8 bytes - ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); + short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); KMInteger.cast(ptr).toLittleEndian(scratchPad, len); len += 8; // concatenate user id - 8 bytes @@ -2836,12 +2869,97 @@ private boolean validateHwToken(short hwToken, byte[] scratchPad) { len += 4; // concatenate timestamp -8 bytes ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); + KMInteger.cast(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + + return seProvider.hmacVerify( + KMByteBlob.cast(key).getBuffer(), + KMByteBlob.cast(key).getStartOff(), + KMByteBlob.cast(key).length(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private boolean verifyHwTokenMacInBigEndian(short hwToken, short key, byte[] scratchPad) { + short len = 0; + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + len = 1; + // concatenate challenge - 8 bytes + short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); + KMInteger.cast(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // concatenate user id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); + KMInteger.cast(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // concatenate authenticator type - 4 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + len += 4; + // concatenate timestamp -8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); KMInteger.cast(ptr) .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); len += 8; - // hmac the data + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); - short key = repository.getComputedHmacKey(); + + return seProvider.hmacVerify( + KMByteBlob.cast(key).getBuffer(), + KMByteBlob.cast(key).getStartOff(), + KMByteBlob.cast(key).length(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + + } + + private boolean verifyHwTokenMacInLittleEndian(short hwToken, short key, byte[] scratchPad) { + short len = 0; + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + len = 1; + // concatenate challenge - 8 bytes + short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += 8; + // concatenate user id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += 8; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += 8; + // concatenate authenticator type - 4 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); + //scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + scratchPad[len] = KMEnum.cast(ptr).getVal(); + len += 4; + // concatenate timestamp -8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += 8; + + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + return seProvider.hmacVerify( KMByteBlob.cast(key).getBuffer(), KMByteBlob.cast(key).getStartOff(), @@ -2854,6 +2972,23 @@ private boolean validateHwToken(short hwToken, byte[] scratchPad) { KMByteBlob.cast(ptr).length()); } + private void validateHwToken(short hwToken, byte[] scratchPad) { + // CBOR Encoding is always big endian + short ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + // If mac length is zero then token is empty. + if (KMByteBlob.cast(ptr).length() == 0) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + short key = repository.getComputedHmacKey(); + + if (!verifyHwTokenMacInLittleEndian(hwToken, key, scratchPad) && + !verifyHwTokenMacInBigEndian(hwToken, key, scratchPad) && + !verifyHwTokenMacAsPerSpecification(hwToken, key, scratchPad)) { + // Throw Exception if none of the combination works. + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + } + private void processImportKeyCmd(APDU apdu) { // Receive the incoming request fully from the master into buffer. receiveIncoming(apdu); @@ -3958,28 +4093,24 @@ private static void sendError(APDU apdu, short err) { sendOutgoing(apdu); } - private short addAuthTimeToTimeStamp(short authTime, short timeStamp, byte[] scratchPad) { - // Convert authTime to milliseconds - Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 40, (byte) 0); + private short addIntegers(short authTime, short timeStamp, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 24, (byte) 0); Util.arrayCopyNonAtomic( KMInteger.cast(authTime).getBuffer(), KMInteger.cast(authTime).getStartOff(), scratchPad, - (short) (8 - KMInteger.cast(authTime).length()), - KMInteger.cast(authTime).length()); - KMUtils.convertToMilliseconds(scratchPad, (short) 0, (short) 8, (short) 16); + (short) (8 - KMInteger.cast(timeStamp).length()), + KMInteger.cast(timeStamp).length()); // Copy timestamp to scratchpad - Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 8, (byte) 0); Util.arrayCopyNonAtomic( KMInteger.cast(timeStamp).getBuffer(), KMInteger.cast(timeStamp).getStartOff(), scratchPad, - (short) (8 - KMInteger.cast(timeStamp).length()), + (short) (16 - KMInteger.cast(timeStamp).length()), KMInteger.cast(timeStamp).length()); // add authTime in millis to timestamp. - Util.arrayFillNonAtomic(scratchPad, (short) 16, (short) 8, (byte) 0); KMUtils.add(scratchPad, (short) 0, (short) 8, (short) 16); return KMInteger.uint_64(scratchPad, (short) 16); } diff --git a/Applet/src/com/android/javacard/keymaster/KMType.java b/Applet/src/com/android/javacard/keymaster/KMType.java index f571275c..00704df2 100644 --- a/Applet/src/com/android/javacard/keymaster/KMType.java +++ b/Applet/src/com/android/javacard/keymaster/KMType.java @@ -186,6 +186,8 @@ public abstract class KMType { public static final short USERID = 0x01F5; // Auth Timeout public static final short AUTH_TIMEOUT = 0x01F9; + // Auth Timeout in Milliseconds + public static final short AUTH_TIMEOUT_MILLIS = 0x7FFF; // OS Version public static final short OS_VERSION = 0x02C1; // OS Patch Level From 76f2f1d3b9caa2f28479665214371cdd7476b970 Mon Sep 17 00:00:00 2001 From: Subrahmanya Manikanta Venkateswarlu Bhamidipati Kameswara Sri Date: Tue, 26 Oct 2021 16:24:26 +0000 Subject: [PATCH 3/3] Added missed functions in 4.1 --- .../4.1/JavacardKeymaster4Device.cpp | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/HAL/keymaster/4.1/JavacardKeymaster4Device.cpp b/HAL/keymaster/4.1/JavacardKeymaster4Device.cpp index 46a12557..ffca0856 100644 --- a/HAL/keymaster/4.1/JavacardKeymaster4Device.cpp +++ b/HAL/keymaster/4.1/JavacardKeymaster4Device.cpp @@ -1500,6 +1500,52 @@ Return JavacardKeymaster4Device::abort(uint64_t operationHandle) { return errorCode; } +// Methods from ::android::hardware::keymaster::V4_1::IKeymasterDevice follow. +Return<::android::hardware::keymaster::V4_1::ErrorCode> JavacardKeymaster4Device::deviceLocked(bool passwordOnly, const VerificationToken& verificationToken) { + cppbor::Array array; + std::unique_ptr item; + std::vector cborOutData; + ::android::hardware::keymaster::V4_1::ErrorCode errorCode = ::android::hardware::keymaster::V4_1::ErrorCode::UNKNOWN_ERROR; + std::vector asn1ParamsVerified; + ErrorCode ret = ErrorCode::UNKNOWN_ERROR; + + if(ErrorCode::OK != (ret = encodeParametersVerified(verificationToken, asn1ParamsVerified))) { + LOG(DEBUG) << "INS_DEVICE_LOCKED_CMD: Error in encodeParametersVerified, status: " << (int32_t) errorCode; + return errorCode; + } + + /* Convert input data to cbor format */ + array.add(passwordOnly); + cborConverter_.addVerificationToken(array, verificationToken, asn1ParamsVerified); + std::vector cborData = array.encode(); + + ret = sendData(Instruction::INS_DEVICE_LOCKED_CMD, cborData, cborOutData); + + if(ret == ErrorCode::OK) { + //Skip last 2 bytes in cborData, it contains status. + std::tie(item, errorCode) = decodeData<::android::hardware::keymaster::V4_1::ErrorCode>( + cborConverter_, std::vector(cborOutData.begin(), cborOutData.end()-2), true, oprCtx_); + } + return errorCode; +} + +Return<::android::hardware::keymaster::V4_1::ErrorCode> JavacardKeymaster4Device::earlyBootEnded() { + std::unique_ptr item; + std::string message; + std::vector cborOutData; + std::vector cborInput; + ::android::hardware::keymaster::V4_1::ErrorCode errorCode = ::android::hardware::keymaster::V4_1::ErrorCode::UNKNOWN_ERROR; + + ErrorCode ret = sendData(Instruction::INS_EARLY_BOOT_ENDED_CMD, cborInput, cborOutData); + + if(ret == ErrorCode::OK) { + //Skip last 2 bytes in cborData, it contains status. + std::tie(item, errorCode) = decodeData<::android::hardware::keymaster::V4_1::ErrorCode>( + cborConverter_, std::vector(cborOutData.begin(), cborOutData.end()-2), true, oprCtx_); + } + return errorCode; +} + } // javacard } // namespace V4_1 } // namespace keymaster