diff options
Diffstat (limited to 'ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java')
-rw-r--r-- | ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java new file mode 100644 index 0000000..8059e44 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java @@ -0,0 +1,415 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.KeyAgreement; +import javacard.security.PrivateKey; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; + +/** + * This class contains the actual implementation of all the crypto operations. It internally uses + * the Javacard crypto library to perform the operations. + */ +public class KMOperationImpl implements KMOperation { + + private static final byte ALG_TYPE_OFFSET = 0x00; + private static final byte PADDING_OFFSET = 0x01; + private static final byte PURPOSE_OFFSET = 0x02; + private static final byte BLOCK_MODE_OFFSET = 0x03; + private static final byte MAC_LENGTH_OFFSET = 0x04; + private final byte[] EMPTY = {}; + // This will hold the length of the buffer stored inside the + // Java Card after the GCM update operation. + private static final byte AES_GCM_UPDATE_LEN_OFFSET = 0x05; + private static final byte PARAMETERS_LENGTH = 6; + private short[] parameters; + // Either one of Cipher/Signature instance is stored. + private Object[] operationInst; + + public KMOperationImpl() { + parameters = JCSystem.makeTransientShortArray(PARAMETERS_LENGTH, JCSystem.CLEAR_ON_RESET); + operationInst = JCSystem.makeTransientObjectArray((short) 2, JCSystem.CLEAR_ON_RESET); + reset(); + } + + public short getPurpose() { + return parameters[PURPOSE_OFFSET]; + } + + public void setPurpose(short mode) { + parameters[PURPOSE_OFFSET] = mode; + } + + public short getMacLength() { + return parameters[MAC_LENGTH_OFFSET]; + } + + public void setMacLength(short macLength) { + parameters[MAC_LENGTH_OFFSET] = macLength; + } + + public short getPaddingAlgorithm() { + return parameters[PADDING_OFFSET]; + } + + public void setPaddingAlgorithm(short alg) { + parameters[PADDING_OFFSET] = alg; + } + + public void setBlockMode(short mode) { + parameters[BLOCK_MODE_OFFSET] = mode; + } + + public short getBlockMode() { + return parameters[BLOCK_MODE_OFFSET]; + } + + public short getAlgorithmType() { + return parameters[ALG_TYPE_OFFSET]; + } + + public void setAlgorithmType(short cipherAlg) { + parameters[ALG_TYPE_OFFSET] = cipherAlg; + } + + public void setCipher(Cipher cipher) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = cipher; + } + + public void setSignature(Signature signer) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = signer; + } + + public void setKeyAgreement(KeyAgreement keyAgreement) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = keyAgreement; + } + + public boolean isResourceMatches(Object object, byte resourceType) { + return operationInst[resourceType] == object; + } + + public void setKeyObject(KMKeyObject keyObject) { + operationInst[KMPoolManager.RESOURCE_TYPE_KEY] = keyObject; + } + + public KMKeyObject getKeyObject() { + return (KMKeyObject) operationInst[KMPoolManager.RESOURCE_TYPE_KEY]; + } + + private void reset() { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = null; + operationInst[KMPoolManager.RESOURCE_TYPE_KEY] = null; + parameters[MAC_LENGTH_OFFSET] = KMType.INVALID_VALUE; + parameters[AES_GCM_UPDATE_LEN_OFFSET] = 0; + parameters[BLOCK_MODE_OFFSET] = KMType.INVALID_VALUE; + parameters[PURPOSE_OFFSET] = KMType.INVALID_VALUE; + parameters[ALG_TYPE_OFFSET] = KMType.INVALID_VALUE; + parameters[PADDING_OFFSET] = KMType.INVALID_VALUE; + } + + private byte mapPurpose(short purpose) { + switch (purpose) { + case KMType.ENCRYPT: + return Cipher.MODE_ENCRYPT; + case KMType.DECRYPT: + return Cipher.MODE_DECRYPT; + case KMType.SIGN: + return Signature.MODE_SIGN; + case KMType.VERIFY: + return Signature.MODE_VERIFY; + } + return -1; + } + + private void initSymmetricCipher(Key key, byte[] ivBuffer, short ivStart, short ivLength) { + Cipher symmCipher = (Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + byte cipherAlg = symmCipher.getAlgorithm(); + switch (cipherAlg) { + case Cipher.ALG_AES_BLOCK_128_CBC_NOPAD: + case Cipher.ALG_AES_CTR: + symmCipher.init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, ivLength); + break; + case Cipher.ALG_AES_BLOCK_128_ECB_NOPAD: + case Cipher.ALG_DES_ECB_NOPAD: + symmCipher.init(key, mapPurpose(getPurpose())); + break; + case Cipher.ALG_DES_CBC_NOPAD: + // Consume only 8 bytes of iv. the random number for iv is of 16 bytes. + // While sending back the iv, send only 8 bytes. + symmCipher.init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, (short) 8); + break; + case AEADCipher.ALG_AES_GCM: + ((AEADCipher) symmCipher).init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, ivLength); + break; + default: // This should never happen + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + private void initRsa(Key key, short digest) { + if (KMType.SIGN == getPurpose()) { + byte mode; + if (getPaddingAlgorithm() == KMType.PADDING_NONE + || (getPaddingAlgorithm() == KMType.RSA_PKCS1_1_5_SIGN && digest == KMType.DIGEST_NONE)) { + mode = Cipher.MODE_DECRYPT; + } else { + mode = Signature.MODE_SIGN; + } + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).init((PrivateKey) key, mode); + } else { // RSA Cipher + ((Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init((PrivateKey) key, mapPurpose(getPurpose())); + } + } + + private void initEc(Key key) { + if (KMType.AGREE_KEY == getPurpose()) { + ((KeyAgreement) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).init((PrivateKey) key); + } else { + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init((PrivateKey) key, mapPurpose(getPurpose())); + } + } + + public void init(Key key, short digest, byte[] buf, short start, short length) { + switch (getAlgorithmType()) { + case KMType.AES: + case KMType.DES: + initSymmetricCipher(key, buf, start, length); + break; + case KMType.HMAC: + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init(key, mapPurpose(getPurpose())); + break; + case KMType.RSA: + initRsa(key, digest); + break; + case KMType.EC: + initEc(key); + break; + default: // This should never happen + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + @Override + public short update( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + short len = + ((Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .update(inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + if (parameters[ALG_TYPE_OFFSET] == KMType.AES && parameters[BLOCK_MODE_OFFSET] == KMType.GCM) { + // Every time Block size data is stored as intermediate result. + parameters[AES_GCM_UPDATE_LEN_OFFSET] += (short) (inputDataLength - len); + } + return len; + } + + @Override + public short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength) { + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .update(inputDataBuf, inputDataStart, inputDataLength); + return 0; + } + + private short finishKeyAgreement( + byte[] publicKey, short start, short len, byte[] output, short outputStart) { + return ((KeyAgreement) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .generateSecret(publicKey, start, len, output, outputStart); + } + + private short finishCipher( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLen, + byte[] outputDataBuf, + short outputDataStart) { + short len = 0; + try { + byte[] tmpArray = KMAndroidSEProvider.getInstance().tmpArray; + Cipher cipher = (Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + short cipherAlg = parameters[ALG_TYPE_OFFSET]; + short blockMode = parameters[BLOCK_MODE_OFFSET]; + short mode = parameters[PURPOSE_OFFSET]; + short macLength = parameters[MAC_LENGTH_OFFSET]; + short padding = parameters[PADDING_OFFSET]; + + if (cipherAlg == KMType.AES && blockMode == KMType.GCM) { + if (mode == KMType.DECRYPT) { + inputDataLen = (short) (inputDataLen - macLength); + } + } else if ((cipherAlg == KMType.DES || cipherAlg == KMType.AES) + && padding == KMType.PKCS7 + && mode == KMType.ENCRYPT) { + byte blkSize = 16; + byte paddingBytes; + short inputlen = inputDataLen; + if (cipherAlg == KMType.DES) { + blkSize = 8; + } + // padding bytes + if (inputlen % blkSize == 0) { + paddingBytes = blkSize; + } else { + paddingBytes = (byte) (blkSize - (inputlen % blkSize)); + } + // final len with padding + inputlen = (short) (inputlen + paddingBytes); + // intermediate buffer to copy input data+padding + // fill in the padding + Util.arrayFillNonAtomic(tmpArray, (short) 0, inputlen, paddingBytes); + // copy the input data + Util.arrayCopyNonAtomic(inputDataBuf, inputDataStart, tmpArray, (short) 0, inputDataLen); + inputDataBuf = tmpArray; + inputDataLen = inputlen; + inputDataStart = 0; + } + len = + cipher.doFinal( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + if ((cipherAlg == KMType.AES || cipherAlg == KMType.DES) + && padding == KMType.PKCS7 + && mode == KMType.DECRYPT) { + byte blkSize = 16; + if (cipherAlg == KMType.DES) { + blkSize = 8; + } + if (len > 0) { + // verify if padding is corrupted. + byte paddingByte = outputDataBuf[(short) (outputDataStart + len - 1)]; + // padding byte always should be <= block size + if ((short) paddingByte > blkSize || (short) paddingByte <= 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + for (short j = 1; j <= paddingByte; ++j) { + if (outputDataBuf[(short) (outputDataStart + len - j)] != paddingByte) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + len = (short) (len - (short) paddingByte); // remove the padding bytes + } + } else if (cipherAlg == KMType.AES && blockMode == KMType.GCM) { + if (mode == KMType.ENCRYPT) { + len += + ((AEADCipher) cipher) + .retrieveTag(outputDataBuf, (short) (outputDataStart + len), macLength); + } else { + boolean verified = + ((AEADCipher) cipher) + .verifyTag( + inputDataBuf, (short) (inputDataStart + inputDataLen), macLength, macLength); + if (!verified) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + } + } finally { + KMAndroidSEProvider.getInstance().clean(); + } + return len; + } + + @Override + public short finish( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLen, + byte[] outputDataBuf, + short outputDataStart) { + if (parameters[PURPOSE_OFFSET] == KMType.AGREE_KEY) { + return finishKeyAgreement( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + } else { + return finishCipher( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + } + } + + @Override + public short sign( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart) { + return ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .sign(inputDataBuf, inputDataStart, inputDataLength, signBuf, signStart); + } + + @Override + public boolean verify( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart, + short signLength) { + return ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .verify(inputDataBuf, inputDataStart, inputDataLength, signBuf, signStart, signLength); + } + + @Override + public void abort() { + // Few simulators does not reset the Hmac signer instance on init so as + // a workaround to reset the hmac signer instance in case of abort/failure of the operation + // the corresponding sign / verify function is called. + if (operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] != null) { + if ((parameters[PURPOSE_OFFSET] == KMType.SIGN || parameters[PURPOSE_OFFSET] == KMType.VERIFY) + && (((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).getAlgorithm() + == Signature.ALG_HMAC_SHA_256)) { + Signature signer = (Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + try { + if (parameters[PURPOSE_OFFSET] == KMType.SIGN) { + signer.sign(EMPTY, (short) 0, (short) 0, EMPTY, (short) 0); + } else { + signer.verify(EMPTY, (short) 0, (short) 0, EMPTY, (short) 0, (short) 0); + } + } catch (Exception e) { + // Ignore. + } + } + } + reset(); + } + + @Override + public void updateAAD(byte[] dataBuf, short dataStart, short dataLength) { + ((AEADCipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .updateAAD(dataBuf, dataStart, dataLength); + } + + @Override + public short getAESGCMOutputSize(short dataSize, short macLength) { + if (parameters[PURPOSE_OFFSET] == KMType.ENCRYPT) { + return (short) (parameters[AES_GCM_UPDATE_LEN_OFFSET] + dataSize + macLength); + } else { + return (short) (parameters[AES_GCM_UPDATE_LEN_OFFSET] + dataSize - macLength); + } + } +} |