diff --git a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java index 6f39f4cd..0ffdba2e 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -401,6 +401,9 @@ public void process(APDU apdu) { case INS_IMPORT_KEY_CMD: processImportKeyCmd(apdu); break; + case INS_IMPORT_WRAPPED_KEY_CMD: + processImportWrappedKeyCmd(apdu); + break; case INS_BEGIN_IMPORT_WRAPPED_KEY_CMD: processBeginImportWrappedKeyCmd(apdu); break; @@ -524,7 +527,7 @@ private short deviceLockedCmd(APDU apdu){ // passwordOnly KMArray.cast(cmd).add((short) 0, KMInteger.exp()); // verification token - KMArray.cast(cmd).add((short) 1, KMVerificationToken.exp()); + KMArray.cast(cmd).add((short) 1, specification.getKMVerificationTokenExp()); return receiveIncoming(apdu, cmd); } @@ -1648,15 +1651,17 @@ private void processAbortOperationCmd(APDU apdu) { } private short finishOperationCmd(APDU apdu){ - short cmd = KMArray.instance((short) 6); + short cmd = KMArray.instance((short) 7); KMArray.cast(cmd).add((short) 0, KMInteger.exp());//op handle - KMArray.cast(cmd).add((short) 1, KMByteBlob.exp());// input data - KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // signature + short keyParam = KMKeyParameters.exp(); + KMArray.cast(cmd).add((short) 1, keyParam);// Key Parameters + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp());// input data + KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // signature short authToken = KMHardwareAuthToken.exp(); - KMArray.cast(cmd).add((short) 3, authToken); // auth token - short verToken = KMVerificationToken.exp(); - KMArray.cast(cmd).add((short) 4, verToken); // time stamp token - KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); //confirmation token + KMArray.cast(cmd).add((short) 4, authToken); // auth token + short verToken = specification.getKMVerificationTokenExp(); + KMArray.cast(cmd).add((short) 5, verToken); // time stamp token + KMArray.cast(cmd).add((short) 6, KMByteBlob.exp()); //confirmation token return receiveIncoming(apdu, cmd); } @@ -1664,11 +1669,13 @@ private void processFinishOperationCmd(APDU apdu) { short cmd = finishOperationCmd(apdu); byte[] scratchPad = apdu.getBuffer(); data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); - data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); - data[SIGNATURE] = KMArray.cast(cmd).get((short) 2); - data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3); - data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 4); - data[CONFIRMATION_TOKEN] = KMArray.cast(cmd).get((short) 5); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 1); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 2); + data[SIGNATURE] = KMArray.cast(cmd).get((short) 3); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 4); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 5); + data[CONFIRMATION_TOKEN] = KMArray.cast(cmd).get((short) 6); + // Check Operation Handle KMOperationState op = findOperation(data[OP_HANDLE]); if (op == null) { @@ -1689,7 +1696,11 @@ private void processFinishOperationCmd(APDU apdu) { finishDecryptOperation(op, scratchPad); break; case KMType.AGREE_KEY: - finishKeyAgreementOperation(op, scratchPad); + if(specification.isKeyAgreementSupported()) { + finishKeyAgreementOperation(op, scratchPad); + } else { + KMException.throwIt(KMError.UNIMPLEMENTED); + } break; } if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) { @@ -1699,9 +1710,12 @@ private void processFinishOperationCmd(APDU apdu) { releaseOperation(op); // make response - short resp = KMArray.instance((short) 2); + short keyParam = KMArray.instance((short) 0); + keyParam = KMKeyParameters.instance(keyParam); + short resp = KMArray.instance((short) 3); KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]); + KMArray.cast(resp).add((short) 1, keyParam); + KMArray.cast(resp).add((short) 2, data[OUTPUT_DATA]); sendOutgoing(apdu, resp); } @@ -2018,7 +2032,7 @@ private boolean verifyVerificationTokenMacInBigEndian(short verToken, byte[] scr scratchPad[(short) (len + 3)] = TRUSTED_ENVIRONMENT; len += 4; // hmac the data - ptr = KMVerificationToken.cast(verToken).getMac(); + ptr = specification.getMacFromVerificationToken(verToken); return seProvider.hmacVerify( seProvider.getComputedHmacKey(), @@ -2031,7 +2045,7 @@ private boolean verifyVerificationTokenMacInBigEndian(short verToken, byte[] scr } private void validateVerificationToken(short verToken, byte[] scratchPad) { - short ptr = KMVerificationToken.cast(verToken).getMac(); + short ptr = specification.getMacFromVerificationToken(verToken); // If mac length is zero then token is empty. if (KMByteBlob.cast(ptr).length() == 0) { KMException.throwIt(KMError.INVALID_MAC_LENGTH); @@ -2043,14 +2057,16 @@ private void validateVerificationToken(short verToken, byte[] scratchPad) { } private short updateOperationCmd(APDU apdu){ - short cmd = KMArray.instance((short) 4); + short cmd = KMArray.instance((short) 5); // Arguments + short keyParams = KMKeyParameters.exp(); KMArray.cast(cmd).add((short) 0, KMInteger.exp()); - KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 1, keyParams); + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); short authToken = KMHardwareAuthToken.exp(); - KMArray.cast(cmd).add((short) 2, authToken); - short verToken = KMVerificationToken.exp(); - KMArray.cast(cmd).add((short) 3, verToken); + KMArray.cast(cmd).add((short) 3, authToken); + short verToken = specification.getKMVerificationTokenExp(); + KMArray.cast(cmd).add((short) 4, verToken); return receiveIncoming(apdu, cmd); } @@ -2058,9 +2074,10 @@ private void processUpdateOperationCmd(APDU apdu) { short cmd = updateOperationCmd(apdu); byte[] scratchPad = apdu.getBuffer(); data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); - data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); - data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2); - data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 1); //will be passed as INVALID_VALUE from keymint HAL + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 2); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 4); // Input data must be present even if it is zero length. if (data[INPUT_DATA] == KMType.INVALID_VALUE) { @@ -2075,7 +2092,7 @@ private void processUpdateOperationCmd(APDU apdu) { } // authorize the update operation authorizeUpdateFinishOperation(op, scratchPad); - + short inputConsumed = 0; if (op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY) { // update the data. op.getOperation() @@ -2098,6 +2115,10 @@ private void processUpdateOperationCmd(APDU apdu) { if (op.getAlgorithm() == KMType.AES) { blockSize = AES_BLOCK_SIZE; if (op.getBlockMode() == KMType.GCM) { + // data[KEY_PARAMETERS] will be invalid for keymint + if(data[KEY_PARAMETERS] != KMType.INVALID_VALUE) { + updateAAD(op, KMType.INVALID_VALUE, (byte) 0x00); + } // if input data present if (len > 0) { // no more future updateAAD allowed if input data present. @@ -2112,6 +2133,7 @@ private void processUpdateOperationCmd(APDU apdu) { // Otherwise just update the data. // HAL consumes all the input and maintains a buffered data inside it. So the // applet sends the inputConsumed length as same as the input length. + inputConsumed = len; try { len = op.getOperation() @@ -2136,9 +2158,13 @@ private void processUpdateOperationCmd(APDU apdu) { // Persist if there are any updates. //op.persist(); // make response - short resp = KMArray.instance((short) 2); + short resp = KMArray.instance((short) 4); KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]); + short keyParm = KMKeyParameters.instance(KMArray.instance((short) 0)); + KMArray.cast(resp).add((short) 2, keyParm); // will be ignored in keymint HAL + KMArray.cast(resp).add((short) 3, KMInteger.uint_16(inputConsumed)); // will be ignored in keymint HAL + sendOutgoing(apdu, resp); } @@ -2148,11 +2174,54 @@ private short updateAadOperationCmd(APDU apdu){ KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); short authToken = KMHardwareAuthToken.exp(); KMArray.cast(cmd).add((short) 2, authToken); - short verToken = KMVerificationToken.exp(); + short verToken = specification.getKMVerificationTokenExp(); KMArray.cast(cmd).add((short) 3, verToken); return receiveIncoming(apdu, cmd); } + //update operation should send 0x00 for finish variable, where as finish operation + // should send 0x01 for finish variable. + private void updateAAD(KMOperationState op, short associatedData, byte finish) { + + short aData = associatedData; + if(associatedData == KMType.INVALID_VALUE) { + // Is input data absent + if (data[INPUT_DATA] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Update can be called either to update auth data, update input data or both. + // But if it is called for neither then return error. + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short tag = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.ASSOCIATED_DATA, data[KEY_PARAMETERS]); + // For Finish operation the input data can be zero length and associated data can be + // INVALID_VALUE + // For update operation either input data or associated data should be present. + if (tag == KMType.INVALID_VALUE && len <= 0 && finish == 0x00) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + // Check if associated data is present and update aad still allowed by the operation. + if (tag != KMType.INVALID_VALUE) { + // If allowed the update the aad + if (!op.isAesGcmUpdateAllowed()) { + KMException.throwIt(KMError.INVALID_TAG); + } + aData = KMByteTag.cast(tmpVariables[1]).getValue(); + } + } + try { + if(aData != KMType.INVALID_VALUE) { + op.getOperation() + .updateAAD( + KMByteBlob.cast(aData).getBuffer(), + KMByteBlob.cast(aData).getStartOff(), + KMByteBlob.cast(aData).length()); + } + } catch(CryptoException exp){ + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } + private void processUpdateAadOperationCmd(APDU apdu) { short cmd = updateAadOperationCmd(apdu); byte[] scratchPad = apdu.getBuffer(); @@ -2185,15 +2254,8 @@ private void processUpdateAadOperationCmd(APDU apdu) { } // authorize the update operation authorizeUpdateFinishOperation(op, scratchPad); - try { - op.getOperation() - .updateAAD( - KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), - KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), - KMByteBlob.cast(data[INPUT_DATA]).length()); - }catch(CryptoException exp){ - KMException.throwIt(KMError.UNKNOWN_ERROR); - } + updateAAD(op, data[INPUT_DATA], (byte)0x00); + // make response short resp = KMArray.instance((short) 1); KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); @@ -2236,6 +2298,8 @@ private void processBeginOperationCmd(APDU apdu) { parseEncryptedKeyBlob(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad); KMTag.assertPresence(data[SB_PARAMETERS],KMType.ENUM_TAG,KMType.ALGORITHM,KMError.UNSUPPORTED_ALGORITHM); short algorithm = KMEnumTag.getValue(KMType.ALGORITHM,data[SB_PARAMETERS]); + + //TODO should be removed for keymint // If Blob usage tag is present in key characteristics then it should be standalone. if(KMTag.isPresent(data[SB_PARAMETERS],KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ)){ if(KMEnumTag.getValue(KMType.BLOB_USAGE_REQ, data[SB_PARAMETERS]) != KMType.STANDALONE){ @@ -2273,7 +2337,11 @@ private void processBeginOperationCmd(APDU apdu) { beginCipherOperation(op); break; case KMType.AGREE_KEY: - beginKeyAgreementOperation(op); + if(specification.isKeyAgreementSupported()) { + beginKeyAgreementOperation(op); + } else { + KMException.throwIt(KMError.UNIMPLEMENTED); + } break; default: KMException.throwIt(KMError.UNIMPLEMENTED); @@ -3028,6 +3096,7 @@ private short importKeyCmd(APDU apdu){ KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); //issuer return receiveIncoming(apdu, cmd); } + private void processImportKeyCmd(APDU apdu) { // Receive the incoming request fully from the master into buffer. short cmd = importKeyCmd(apdu); @@ -3045,6 +3114,154 @@ private void processImportKeyCmd(APDU apdu) { importKey(apdu, keyFmt, scratchPad); } + private short importWrappedKeyCmd(APDU apdu){ + short cmd = KMArray.instance((short) 12); + // Arguments + short params = KMKeyParameters.exp(); + short bBlob = KMByteBlob.exp(); + KMArray.cast(cmd).add((short) 0, params); // Key Params of wrapped key + KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); // Key Format + KMArray.cast(cmd).add((short) 2, bBlob); // Wrapped Import Key Blob + KMArray.cast(cmd).add((short) 3, bBlob); // Auth Tag + KMArray.cast(cmd).add((short) 4, bBlob); // IV - Nonce + KMArray.cast(cmd).add((short) 5, bBlob); // Encrypted Transport Key + KMArray.cast(cmd).add((short) 6, bBlob); // Wrapping Key KeyBlob + KMArray.cast(cmd).add((short) 7, bBlob); // Masking Key + KMArray.cast(cmd).add((short) 8, params); // Un-wrapping Params + KMArray.cast(cmd) + .add((short) 9, bBlob); // Wrapped Key ASSOCIATED AUTH DATA + KMArray.cast(cmd).add((short) 10, KMInteger.exp()); // Password Sid + KMArray.cast(cmd).add((short) 11, KMInteger.exp()); // Biometric Sid + return receiveIncoming(apdu, cmd); + } + + private void processImportWrappedKeyCmd(APDU apdu) { + + byte[] scratchPad = apdu.getBuffer(); + short cmd = importWrappedKeyCmd(apdu); + + // Step -0 - check whether the key format and algorithm supported + // read algorithm + tmpVariables[0] = KMArray.cast(cmd).get((short) 0); + tmpVariables[1] = KMEnumTag.getValue(KMType.ALGORITHM, tmpVariables[0]); + // read key format + tmpVariables[2] = KMArray.cast(cmd).get((short) 1); + byte keyFormat = KMEnum.cast(tmpVariables[2]).getVal(); + if ((tmpVariables[1] == KMType.RSA || tmpVariables[1] == KMType.EC) + && (keyFormat != KMType.PKCS8)) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + + // Step -1 parse the wrapping key blob + // read wrapping key blob + data[KEY_BLOB] = KMArray.cast(cmd).get((short) 6); + // read un wrapping key params + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 8); + // Read App Id and App Data if any from un wrapping key params + data[APP_ID] = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, data[KEY_PARAMETERS]); + data[APP_DATA] = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, data[KEY_PARAMETERS]); + if (data[APP_ID] != KMTag.INVALID_VALUE) { + data[APP_ID] = KMByteTag.cast(data[APP_ID]).getValue(); + } + if (data[APP_DATA] != KMTag.INVALID_VALUE) { + data[APP_DATA] = KMByteTag.cast(data[APP_DATA]).getValue(); + } + // parse the wrapping key blob + parseEncryptedKeyBlob(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad); + // check whether the wrapping key is RSA with purpose KEY_WRAP, padding RSA_OAEP and Digest + // SHA2_256. + if (KMEnumTag.getValue(KMType.ALGORITHM, data[HW_PARAMETERS]) != KMType.RSA) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM); + } + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (!KMEnumArrayTag.contains(KMType.PADDING, KMType.RSA_OAEP, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + if (!KMEnumArrayTag.contains(KMType.PURPOSE, KMType.WRAP_KEY, data[HW_PARAMETERS])) { + KMException.throwIt((KMError.INCOMPATIBLE_PURPOSE)); + } + + // Step 2 - decrypt the encrypted transport key - 32 bytes AES-GCM key + // create rsa decipher + // read encrypted transport key from args + tmpVariables[0] = KMArray.cast(cmd).get((short) 5); + // Decrypt the transport key + tmpVariables[1] = + seProvider.rsaDecipherOAEP256( + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length(), + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).length(), + scratchPad, + (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[1]); + + // Step 3 - XOR the decrypted AES-GCM key with with masking key + // read masking key + tmpVariables[0] = KMArray.cast(cmd).get((short) 7); + tmpVariables[1] = KMByteBlob.cast(tmpVariables[0]).length(); + // Length of masking key and transport key must be same. + if (tmpVariables[1] != KMByteBlob.cast(data[SECRET]).length()) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + tmpVariables[2] = 0; // index + // Xor every byte of masking and key and store the result in data[SECRET] + while (tmpVariables[2] < tmpVariables[1]) { + tmpVariables[3] = + (short) (((short) KMByteBlob.cast(tmpVariables[0]).get(tmpVariables[2])) & 0x00FF); + tmpVariables[4] = + (short) (((short) KMByteBlob.cast(data[SECRET]).get(tmpVariables[2])) & 0x00FF); + KMByteBlob.cast(data[SECRET]) + .add(tmpVariables[2], (byte) (tmpVariables[3] ^ tmpVariables[4])); + tmpVariables[2]++; + } + + // Step 4 - AES-GCM decrypt the wrapped key + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 2); + data[AUTH_DATA] = KMArray.cast(cmd).get((short) 9); + data[AUTH_TAG] = KMArray.cast(cmd).get((short) 3); + data[NONCE] = KMArray.cast(cmd).get((short) 4); + Util.arrayFillNonAtomic( + scratchPad, (short) 0, KMByteBlob.cast(data[INPUT_DATA]).length(), (byte) 0); + + if (!seProvider.aesGCMDecrypt( + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length(), + KMByteBlob.cast(data[AUTH_DATA]).getBuffer(), + KMByteBlob.cast(data[AUTH_DATA]).getStartOff(), + KMByteBlob.cast(data[AUTH_DATA]).length(), + KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), + KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), + KMByteBlob.cast(data[AUTH_TAG]).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + + // Step 5 - Import decrypted key + data[ORIGIN] = KMType.SECURELY_IMPORTED; + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0); + // create key blob array + data[IMPORTED_KEY_BLOB] = KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(data[INPUT_DATA]).length()); + importKey(apdu, keyFormat, scratchPad); + } + private void validateImportKey(short params, short keyFmt){ // Rollback protection not supported KMTag.assertAbsence(params, KMType.BOOL_TAG, KMType.ROLLBACK_RESISTANCE, KMError.ROLLBACK_RESISTANCE_UNAVAILABLE); @@ -3089,7 +3306,9 @@ private void importKey(APDU apdu, short keyFmt, byte[] scratchPad) { break; } makeKeyCharacteristics( scratchPad); - generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS],scratchPad); + if(specification.isAttestSupportedInImport()) { + generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS],scratchPad); + } createEncryptedKeyBlob(scratchPad); // prepare the response short resp = KMArray.instance((short) 4); @@ -4533,24 +4752,21 @@ public static short generateBcc(boolean testMode, byte[] scratchPad) { } private void updateTrustedConfirmationOperation(KMOperationState op) { - if (op.isTrustedConfirmationRequired()) { - op.getTrustedConfirmationSigner().update(KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), - KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), KMByteBlob.cast(data[INPUT_DATA]).length()); - } + if (op.isTrustedConfirmationRequired()) { + op.getTrustedConfirmationSigner().update(KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), KMByteBlob.cast(data[INPUT_DATA]).length()); + } } private void finishTrustedConfirmationOperation(KMOperationState op) { // Perform trusted confirmation if required - if (op.isTrustedConfirmationRequired()) { - if (0 == KMByteBlob.cast(data[CONFIRMATION_TOKEN]).length()) { - KMException.throwIt(KMError.NO_USER_CONFIRMATION); - } - + if (op.isTrustedConfirmationRequired()) { + short confToken = specification.getConfirmationToken(data[CONFIRMATION_TOKEN], data[KEY_PARAMETERS]); boolean verified = op.getTrustedConfirmationSigner().verify(KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), KMByteBlob.cast(data[INPUT_DATA]).length(), - KMByteBlob.cast(data[CONFIRMATION_TOKEN]).getBuffer(), - KMByteBlob.cast(data[CONFIRMATION_TOKEN]).getStartOff(), - KMByteBlob.cast(data[CONFIRMATION_TOKEN]).length()); + KMByteBlob.cast(confToken).getBuffer(), + KMByteBlob.cast(confToken).getStartOff(), + KMByteBlob.cast(confToken).length()); if (!verified) { KMException.throwIt(KMError.NO_USER_CONFIRMATION); } diff --git a/Applet/src/com/android/javacard/keymaster/KMKeymasterSpecification.java b/Applet/src/com/android/javacard/keymaster/KMKeymasterSpecification.java index 808435e1..860eb5f8 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeymasterSpecification.java +++ b/Applet/src/com/android/javacard/keymaster/KMKeymasterSpecification.java @@ -156,6 +156,11 @@ public short getIssuer() { return 0; } + @Override + public boolean isKeyAgreementSupported() { + return false; + } + private short getProvisionedCertificateData(KMSEProvider kmseProvider, byte dataType) { short len = seProvider.getProvisionedDataLength(dataType); if (len == 0) { @@ -169,4 +174,28 @@ private short getProvisionedCertificateData(KMSEProvider kmseProvider, byte data return ptr; } + @Override + public short getConfirmationToken(short confToken, short keyParams) { + short cToken = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.CONFIRMATION_TOKEN, keyParams); + if (cToken == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.NO_USER_CONFIRMATION); + } + return KMByteTag.cast(cToken).getValue(); + } + + @Override + public short getKMVerificationTokenExp() { + return KMVerificationToken.exp2(); + } + + @Override + public short getMacFromVerificationToken(short verToken) { + return KMVerificationToken.cast(verToken).getMac((short)0x04); + } + + @Override + public boolean isAttestSupportedInImport() { + return false; + } } diff --git a/Applet/src/com/android/javacard/keymaster/KMKeymintSpecification.java b/Applet/src/com/android/javacard/keymaster/KMKeymintSpecification.java index 814063e6..c7ded89c 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeymintSpecification.java +++ b/Applet/src/com/android/javacard/keymaster/KMKeymintSpecification.java @@ -174,4 +174,32 @@ public short getNotBefore(short params) { public short getIssuer() { return 0; } + + @Override + public boolean isKeyAgreementSupported() { + return true; + } + + @Override + public short getConfirmationToken(short confToken, short keyParams) { + if (0 == KMByteBlob.cast(confToken).length()) { + KMException.throwIt(KMError.NO_USER_CONFIRMATION); + } + return confToken; + } + + @Override + public short getKMVerificationTokenExp() { + return KMVerificationToken.exp1(); + } + + @Override + public short getMacFromVerificationToken(short verToken) { + return KMVerificationToken.cast(verToken).getMac((short)0x02); + } + + @Override + public boolean isAttestSupportedInImport() { + return true; + } } diff --git a/Applet/src/com/android/javacard/keymaster/KMSpecification.java b/Applet/src/com/android/javacard/keymaster/KMSpecification.java index 976f9e75..fe350859 100644 --- a/Applet/src/com/android/javacard/keymaster/KMSpecification.java +++ b/Applet/src/com/android/javacard/keymaster/KMSpecification.java @@ -32,4 +32,14 @@ KMAttestationCert makeCommonCert(short swParams, short hwParams, short keyParams short getNotBefore(short params); short getIssuer(); + + boolean isKeyAgreementSupported(); + + short getConfirmationToken(short confToken, short keyParams); + + short getKMVerificationTokenExp(); + + short getMacFromVerificationToken(short verToken); + + boolean isAttestSupportedInImport(); } diff --git a/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java b/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java index 65eb2dcc..f10f049e 100644 --- a/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java +++ b/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java @@ -31,19 +31,34 @@ public class KMVerificationToken extends KMType { public static final byte CHALLENGE = 0x00; public static final byte TIMESTAMP = 0x01; - public static final byte MAC = 0x02; + public static final byte PARAMETERS_VERIFIED = 0x02; + public static final byte SECURITY_LEVEL = 0x03; + public static final byte MAC1 = 0x02; + public static final byte MAC2 = 0x04; private static KMVerificationToken prototype; private KMVerificationToken() { } - public static short exp() { + public static short exp1() { short arrPtr = KMArray.instance((short) 3); KMArray arr = KMArray.cast(arrPtr); arr.add(CHALLENGE, KMInteger.exp()); arr.add(TIMESTAMP, KMInteger.exp()); - arr.add(MAC, KMByteBlob.exp()); + arr.add(MAC1, KMByteBlob.exp()); + return instance(arrPtr); + } + + public static short exp2() { + short arrPtr = KMArray.instance((short) 5); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.exp()); + arr.add(TIMESTAMP, KMInteger.exp()); + //arr.add(PARAMETERS_VERIFIED, KMKeyParameters.exp()); + arr.add(PARAMETERS_VERIFIED, KMByteBlob.exp()); + arr.add(SECURITY_LEVEL, KMEnum.instance(KMType.HARDWARE_TYPE)); + arr.add(MAC2, KMByteBlob.exp()); return instance(arrPtr); } @@ -55,18 +70,28 @@ private static KMVerificationToken proto(short ptr) { return prototype; } - public static short instance() { + public static short instance1() { short arrPtr = KMArray.instance((short) 3); KMArray arr = KMArray.cast(arrPtr); arr.add(CHALLENGE, KMInteger.uint_16((short) 0)); arr.add(TIMESTAMP, KMInteger.uint_16((short) 0)); - arr.add(MAC, KMByteBlob.instance((short) 0)); + arr.add(MAC1, KMByteBlob.instance((short) 0)); return instance(arrPtr); } - + public static short instance2() { + short arrPtr = KMArray.instance((short) 5); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.uint_16((short) 0)); + arr.add(TIMESTAMP, KMInteger.uint_16((short) 0)); + arr.add(PARAMETERS_VERIFIED, KMByteBlob.instance((short) 0)); + arr.add(SECURITY_LEVEL, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); + arr.add(MAC2, KMByteBlob.instance((short) 0)); + return instance(arrPtr); + } + public static short instance(short vals) { KMArray arr = KMArray.cast(vals); - if (arr.length() != 3) { + if (arr.length() != 3 && arr.length() != 5) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } short ptr = KMType.instance(VERIFICATION_TOKEN_TYPE, (short) 2); @@ -116,15 +141,8 @@ public void setTimestamp(short vals) { KMArray.cast(arrPtr).add(TIMESTAMP, vals); } - public short getMac() { + public short getMac(short macIndex) { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(MAC); + return KMArray.cast(arrPtr).get(macIndex); } - - public void setMac(short vals) { - KMByteBlob.cast(vals); - short arrPtr = getVals(); - KMArray.cast(arrPtr).add(MAC, vals); - } - } diff --git a/Keymaster_HAL/keymaster/4.1/JavacardKeymaster4Device.cpp b/Keymaster_HAL/keymaster/4.1/JavacardKeymaster4Device.cpp index 8391956d..9ed3f539 100644 --- a/Keymaster_HAL/keymaster/4.1/JavacardKeymaster4Device.cpp +++ b/Keymaster_HAL/keymaster/4.1/JavacardKeymaster4Device.cpp @@ -462,7 +462,7 @@ Return JavacardKeymaster4Device::getHardwareInfo(getHardwareInfo_cb _hidl_ //Skip last 2 bytes in cborData, it contains status. std::tie(item, ret) = decodeData(cborConverter_, std::vector(resp.begin(), resp.end()-2), false, oprCtx_); - if (item != nullptr) { + if (ret == ErrorCode::OK) { std::vector temp; if(!cborConverter_.getUint64(item, 0, securityLevel) || !cborConverter_.getBinaryArray(item, 1, jcKeymasterName) || @@ -494,7 +494,7 @@ Return JavacardKeymaster4Device::getHmacSharingParameters(getHmacSharingPa //Skip last 2 bytes in cborData, it contains status. std::tie(item, errorCode) = decodeData(cborConverter_, std::vector(cborData.begin(), cborData.end()-2), true, oprCtx_); - if (item != nullptr) { + if (errorCode == ErrorCode::OK) { if(!cborConverter_.getHmacSharingParameters(item, 1, hmacSharingParameters)) { LOG(ERROR) << "javacard strongbox : Failed to convert cbor data of INS_GET_HMAC_SHARING_PARAM_CMD"; errorCode = ErrorCode::UNKNOWN_ERROR; @@ -555,7 +555,7 @@ Return JavacardKeymaster4Device::computeSharedHmac(const hidl_vec(cborOutData.begin(), cborOutData.end()-2), true, oprCtx_); - if (item != nullptr) { + if (errorCode == ErrorCode::OK) { std::vector bstr; if(!cborConverter_.getBinaryArray(item, 1, bstr)) { LOG(ERROR) << "INS_COMPUTE_SHARED_HMAC_CMD: failed to convert cbor sharing check value"; @@ -626,7 +626,7 @@ Return JavacardKeymaster4Device::generateKey(const hidl_vec& //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 (errorCode == ErrorCode::OK) { if(!cborConverter_.getBinaryArray(item, 1, keyBlob) || !cborConverter_.getKeyCharacteristics(item, 2, keyCharacteristics)) { //Clear the buffer. @@ -649,6 +649,7 @@ Return JavacardKeymaster4Device::importKey(const hidl_vec& k std::vector cborOutData; ErrorCode errorCode = ErrorCode::UNKNOWN_ERROR; KeyCharacteristics keyCharacteristics; + std::vector emptyParameters; cppbor::Array subArray; // Send earlyBootEnded if there is any pending earlybootEnded event. handleSendEarlyBootEndedEvent(); @@ -662,6 +663,9 @@ Return JavacardKeymaster4Device::importKey(const hidl_vec& k array.add(static_cast(keyFormat)); //javacard accepts only RAW. array.add(std::vector(keyData)); + array.add(std::vector()); //dummy attest key blob + cborConverter_.addKeyparameters(array, emptyParameters); //empty attest key param + array.add(std::vector()); //dummy issuer std::vector cborData = array.encode(); errorCode = sendData(Instruction::INS_IMPORT_KEY_CMD, cborData, cborOutData); @@ -670,7 +674,7 @@ Return JavacardKeymaster4Device::importKey(const hidl_vec& k //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 (errorCode == ErrorCode::OK) { if(!cborConverter_.getBinaryArray(item, 1, keyBlob) || !cborConverter_.getKeyCharacteristics(item, 2, keyCharacteristics)) { //Clear the buffer. @@ -728,7 +732,7 @@ Return JavacardKeymaster4Device::importWrappedKey(const hidl_vec& //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 (errorCode == ErrorCode::OK) { if(!cborConverter_.getBinaryArray(item, 1, keyBlob) || !cborConverter_.getKeyCharacteristics(item, 2, keyCharacteristics)) { //Clear the buffer. @@ -761,7 +765,7 @@ Return JavacardKeymaster4Device::getKeyCharacteristics(const hidl_vec(cborOutData.begin(), cborOutData.end()-2), true, oprCtx_); - if (item != nullptr) { + if (errorCode == ErrorCode::OK) { if(!cborConverter_.getKeyCharacteristics(item, 1, keyCharacteristics)) { keyCharacteristics.softwareEnforced.setToExternal(nullptr, 0); keyCharacteristics.hardwareEnforced.setToExternal(nullptr, 0); @@ -836,7 +840,7 @@ Return JavacardKeymaster4Device::attestKey(const hidl_vec& keyToA //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 (errorCode == ErrorCode::OK) { if(!cborConverter_.getMultiBinaryArray(item, 1, temp)) { errorCode = ErrorCode::UNKNOWN_ERROR; LOG(ERROR) << "INS_ATTEST_KEY_CMD: error in converting cbor data, status: " << (int32_t) errorCode; @@ -849,7 +853,7 @@ Return JavacardKeymaster4Device::attestKey(const hidl_vec& keyToA std::tie(item, errorCode) = decodeData(cborConverter_, std::vector(cborOutData.begin(), cborOutData.end()-2), true, oprCtx_); - if (item != nullptr) { + if (errorCode == ErrorCode::OK) { std::vector chain; if(!cborConverter_.getBinaryArray(item, 1, chain)) { errorCode = ErrorCode::UNKNOWN_ERROR; @@ -890,7 +894,7 @@ Return JavacardKeymaster4Device::upgradeKey(const hidl_vec& keyBl //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 (errorCode == ErrorCode::OK) { if(!cborConverter_.getBinaryArray(item, 1, upgradedKeyBlob)) { errorCode = ErrorCode::UNKNOWN_ERROR; LOG(ERROR) << "INS_UPGRADE_KEY_CMD: error in converting cbor data, status: " << (int32_t) errorCode; @@ -1078,7 +1082,7 @@ ErrorCode JavacardKeymaster4Device::handleBeginPrivateKeyOperation( decodeData(cborConverter_, std::vector(cborOutData.begin(), cborOutData.end() - 2), true, oprCtx_); - if (item != nullptr) { + if (errorCode == ErrorCode::OK) { if (!cborConverter_.getKeyParameters(item, 1, outParams) || !cborConverter_.getUint64(item, 2, operationHandle)) { errorCode = ErrorCode::UNKNOWN_ERROR; @@ -1215,7 +1219,7 @@ JavacardKeymaster4Device::update(uint64_t operationHandle, const hidl_vec(cborOutData.begin(), cborOutData.end() - 2), true, oprCtx_); - if (item != nullptr) { + if (errorCode == ErrorCode::OK) { /*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 @@ -1225,7 +1229,7 @@ JavacardKeymaster4Device::update(uint64_t operationHandle, const hidl_vec). if ((outParams.size() == 0 && !cborConverter_.getKeyParameters(item, 2, outParams)) || - !cborConverter_.getBinaryArray(item, 3, tempOut)) { + !cborConverter_.getBinaryArray(item, 1, tempOut)) { outParams.setToExternal(nullptr, 0); tempOut.clear(); errorCode = ErrorCode::UNKNOWN_ERROR; @@ -1317,6 +1321,7 @@ JavacardKeymaster4Device::finish(uint64_t operationHandle, const hidl_vec cborOutData; int keyParamPos, outputPos; std::vector asn1ParamsVerified; + const hidl_vec confToken = {}; //dummy if (ErrorCode::OK != (errorCode = encodeParametersVerified(verificationToken, asn1ParamsVerified))) { @@ -1358,10 +1363,11 @@ JavacardKeymaster4Device::finish(uint64_t operationHandle, const hidl_vec(confToken)); std::vector cborData = array.encode(); errorCode = sendData(ins, cborData, cborOutData); @@ -1371,7 +1377,7 @@ JavacardKeymaster4Device::finish(uint64_t operationHandle, const hidl_vec(cborOutData.begin(), cborOutData.end() - 2), true, oprCtx_); - if (item != nullptr) { + if (errorCode == ErrorCode::OK) { // 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 diff --git a/Keymaster_HAL/keymaster/include/CborConverter.h b/Keymaster_HAL/keymaster/include/CborConverter.h index 45855244..70b3e766 100644 --- a/Keymaster_HAL/keymaster/include/CborConverter.h +++ b/Keymaster_HAL/keymaster/include/CborConverter.h @@ -59,16 +59,10 @@ class CborConverter std::tie(item, pos, message) = parse(response); if(item != nullptr && hasErrorCode) { - if(MajorType::ARRAY == getType(item)) { - if(!getErrorCode(item, 0, errorCode)) - item = nullptr; - } else if (MajorType::UINT == getType(item)) { - uint64_t err; - if(getUint64(item, err)) { - errorCode = static_cast(err); - } - item = nullptr; /*Already read the errorCode. So no need of sending item to client */ - } + if(MajorType::ARRAY == getType(item)) { + if(!getErrorCode(item, 0, errorCode)) + return {nullptr, T::UNKNOWN_ERROR}; + } } return {std::move(item), errorCode}; }