| /* |
| * Copyright (C) 2019 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" BASIS, |
| * 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 org.conscrypt; |
| |
| import java.security.InvalidAlgorithmParameterException; |
| import java.security.InvalidKeyException; |
| import java.security.SecureRandom; |
| import java.security.spec.AlgorithmParameterSpec; |
| import javax.crypto.BadPaddingException; |
| import javax.crypto.IllegalBlockSizeException; |
| import javax.crypto.ShortBufferException; |
| import javax.crypto.spec.IvParameterSpec; |
| import org.conscrypt.NativeRef.EVP_CIPHER_CTX; |
| |
| @Internal |
| public abstract class OpenSSLEvpCipher extends OpenSSLCipher { |
| /** |
| * Native pointer for the OpenSSL EVP_CIPHER context. |
| */ |
| private final EVP_CIPHER_CTX cipherCtx = new EVP_CIPHER_CTX( |
| NativeCrypto.EVP_CIPHER_CTX_new()); |
| |
| /** |
| * Whether the cipher has processed any data yet. EVP_CIPHER doesn't |
| * like calling "doFinal()" in decryption mode without processing any |
| * updates. |
| */ |
| private boolean calledUpdate; |
| |
| /** |
| * The block size of the current mode. |
| */ |
| private int modeBlockSize; |
| |
| public OpenSSLEvpCipher(Mode mode, Padding padding) { |
| super(mode, padding); |
| } |
| |
| @Override |
| void engineInitInternal(byte[] encodedKey, AlgorithmParameterSpec params, |
| SecureRandom random) throws InvalidKeyException, |
| InvalidAlgorithmParameterException { |
| byte[] iv; |
| if (params instanceof IvParameterSpec) { |
| IvParameterSpec ivParams = (IvParameterSpec) params; |
| iv = ivParams.getIV(); |
| } else { |
| iv = null; |
| } |
| |
| final long cipherType = NativeCrypto.EVP_get_cipherbyname(getCipherName( |
| encodedKey.length, mode)); |
| if (cipherType == 0) { |
| throw new InvalidAlgorithmParameterException("Cannot find name for key length = " |
| + (encodedKey.length * 8) + " and mode = " + mode); |
| } |
| |
| final boolean encrypting = isEncrypting(); |
| |
| final int expectedIvLength = NativeCrypto.EVP_CIPHER_iv_length(cipherType); |
| if (iv == null && expectedIvLength != 0) { |
| if (!encrypting) { |
| throw new InvalidAlgorithmParameterException("IV must be specified in " + mode |
| + " mode"); |
| } |
| |
| iv = new byte[expectedIvLength]; |
| if (random != null) { |
| random.nextBytes(iv); |
| } else { |
| NativeCrypto.RAND_bytes(iv); |
| } |
| } else if (expectedIvLength == 0 && iv != null) { |
| throw new InvalidAlgorithmParameterException("IV not used in " + mode + " mode"); |
| } else if (iv != null && iv.length != expectedIvLength) { |
| throw new InvalidAlgorithmParameterException("expected IV length of " |
| + expectedIvLength + " but was " + iv.length); |
| } |
| |
| this.iv = iv; |
| |
| if (supportsVariableSizeKey()) { |
| NativeCrypto.EVP_CipherInit_ex(cipherCtx, cipherType, null, null, encrypting); |
| NativeCrypto.EVP_CIPHER_CTX_set_key_length(cipherCtx, encodedKey.length); |
| NativeCrypto.EVP_CipherInit_ex(cipherCtx, 0, encodedKey, iv, isEncrypting()); |
| } else { |
| NativeCrypto.EVP_CipherInit_ex(cipherCtx, cipherType, encodedKey, iv, encrypting); |
| } |
| |
| // OpenSSL only supports PKCS5 Padding. |
| NativeCrypto |
| .EVP_CIPHER_CTX_set_padding(cipherCtx, getPadding() == Padding.PKCS5PADDING); |
| modeBlockSize = NativeCrypto.EVP_CIPHER_CTX_block_size(cipherCtx); |
| calledUpdate = false; |
| } |
| |
| @Override |
| int updateInternal(byte[] input, int inputOffset, int inputLen, byte[] output, |
| int outputOffset, int maximumLen) throws ShortBufferException { |
| final int intialOutputOffset = outputOffset; |
| |
| final int bytesLeft = output.length - outputOffset; |
| if (bytesLeft < maximumLen) { |
| throw new ShortBufferException("output buffer too small during update: " |
| + bytesLeft + " < " + maximumLen); |
| } |
| |
| outputOffset += NativeCrypto.EVP_CipherUpdate(cipherCtx, output, outputOffset, input, |
| inputOffset, inputLen); |
| |
| calledUpdate = true; |
| |
| return outputOffset - intialOutputOffset; |
| } |
| |
| @Override |
| int doFinalInternal(byte[] output, int outputOffset, int maximumLen) |
| throws IllegalBlockSizeException, BadPaddingException, ShortBufferException { |
| /* Remember this so we can tell how many characters were written. */ |
| final int initialOutputOffset = outputOffset; |
| |
| /* |
| * If we're decrypting and haven't had any input, we should return |
| * null. Otherwise OpenSSL will complain if we call final. |
| */ |
| if (!isEncrypting() && !calledUpdate) { |
| return 0; |
| } |
| |
| /* Allow OpenSSL to pad if necessary and clean up state. */ |
| final int bytesLeft = output.length - outputOffset; |
| final int writtenBytes; |
| if (bytesLeft >= maximumLen) { |
| writtenBytes = NativeCrypto.EVP_CipherFinal_ex(cipherCtx, output, outputOffset); |
| } else { |
| final byte[] lastBlock = new byte[maximumLen]; |
| writtenBytes = NativeCrypto.EVP_CipherFinal_ex(cipherCtx, lastBlock, 0); |
| if (writtenBytes > bytesLeft) { |
| throw new ShortBufferException("buffer is too short: " + writtenBytes + " > " |
| + bytesLeft); |
| } else if (writtenBytes > 0) { |
| System.arraycopy(lastBlock, 0, output, outputOffset, writtenBytes); |
| } |
| } |
| outputOffset += writtenBytes; |
| |
| reset(); |
| |
| return outputOffset - initialOutputOffset; |
| } |
| |
| @Override |
| int getOutputSizeForFinal(int inputLen) { |
| if (modeBlockSize == 1) { |
| return inputLen; |
| } else { |
| final int buffered = NativeCrypto.get_EVP_CIPHER_CTX_buf_len(cipherCtx); |
| |
| if (getPadding() == Padding.NOPADDING) { |
| return buffered + inputLen; |
| } else { |
| final boolean finalUsed = NativeCrypto.get_EVP_CIPHER_CTX_final_used(cipherCtx); |
| // There is an additional buffer containing the possible final block. |
| int totalLen = inputLen + buffered + (finalUsed ? modeBlockSize : 0); |
| // Extra block for remainder bytes plus padding. |
| // In case it's encrypting and there are no remainder bytes, add an extra block |
| // consisting only of padding. |
| totalLen += ((totalLen % modeBlockSize != 0) || isEncrypting()) |
| ? modeBlockSize : 0; |
| // The minimum multiple of {@code modeBlockSize} that can hold all the bytes. |
| return totalLen - (totalLen % modeBlockSize); |
| } |
| } |
| } |
| |
| @Override |
| int getOutputSizeForUpdate(int inputLen) { |
| return getOutputSizeForFinal(inputLen); |
| } |
| |
| /** |
| * Returns the OpenSSL cipher name for the particular {@code keySize} |
| * and cipher {@code mode}. |
| */ |
| abstract String getCipherName(int keySize, Mode mode); |
| |
| /** |
| * Reset this Cipher instance state to process a new chunk of data. |
| */ |
| private void reset() { |
| NativeCrypto.EVP_CipherInit_ex(cipherCtx, 0, encodedKey, iv, isEncrypting()); |
| calledUpdate = false; |
| } |
| |
| } |