blob: 1f1d36fa44051566c0f8ceac827308324b8a8023 [file] [log] [blame]
/*
* Copyright (C) 2015 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 android.security.keystore;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import javax.crypto.CipherSpi;
import javax.crypto.spec.IvParameterSpec;
/**
* Base class for Android Keystore unauthenticated AES {@link CipherSpi} implementations.
*
* @hide
*/
class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
abstract static class ECB extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
protected ECB(int keymasterPadding) {
super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false);
}
public static class NoPadding extends ECB {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
}
public static class PKCS7Padding extends ECB {
public PKCS7Padding() {
super(KeymasterDefs.KM_PAD_PKCS7);
}
}
}
abstract static class CBC extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
protected CBC(int keymasterPadding) {
super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true);
}
public static class NoPadding extends CBC {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
}
public static class PKCS7Padding extends CBC {
public PKCS7Padding() {
super(KeymasterDefs.KM_PAD_PKCS7);
}
}
}
abstract static class CTR extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
protected CTR(int keymasterPadding) {
super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true);
}
public static class NoPadding extends CTR {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
}
}
private static final int BLOCK_SIZE_BYTES = 16;
private final int mKeymasterBlockMode;
private final int mKeymasterPadding;
/** Whether this transformation requires an IV. */
private final boolean mIvRequired;
private byte[] mIv;
/** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
private boolean mIvHasBeenUsed;
AndroidKeyStoreUnauthenticatedAESCipherSpi(
int keymasterBlockMode,
int keymasterPadding,
boolean ivRequired) {
mKeymasterBlockMode = keymasterBlockMode;
mKeymasterPadding = keymasterPadding;
mIvRequired = ivRequired;
}
@Override
protected final void resetAll() {
mIv = null;
mIvHasBeenUsed = false;
super.resetAll();
}
@Override
protected final void resetWhilePreservingInitState() {
super.resetWhilePreservingInitState();
}
@Override
protected final void initKey(int opmode, Key key) throws InvalidKeyException {
if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeyException(
"Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
}
if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException(
"Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
KeyProperties.KEY_ALGORITHM_AES + " supported");
}
setKey((AndroidKeyStoreSecretKey) key);
}
@Override
protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {
if (!mIvRequired) {
return;
}
// IV is used
if (!isEncrypting()) {
throw new InvalidKeyException("IV required when decrypting"
+ ". Use IvParameterSpec or AlgorithmParameters to provide it.");
}
}
@Override
protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
if (!mIvRequired) {
if (params != null) {
throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
}
return;
}
// IV is used
if (params == null) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException(
"IvParameterSpec must be provided when decrypting");
}
return;
}
if (!(params instanceof IvParameterSpec)) {
throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported");
}
mIv = ((IvParameterSpec) params).getIV();
if (mIv == null) {
throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec");
}
}
@Override
protected final void initAlgorithmSpecificParameters(AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
if (!mIvRequired) {
if (params != null) {
throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
}
return;
}
// IV is used
if (params == null) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ ". Use IvParameterSpec or AlgorithmParameters to provide it.");
}
return;
}
if (!"AES".equalsIgnoreCase(params.getAlgorithm())) {
throw new InvalidAlgorithmParameterException(
"Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm()
+ ". Supported: AES");
}
IvParameterSpec ivSpec;
try {
ivSpec = params.getParameterSpec(IvParameterSpec.class);
} catch (InvalidParameterSpecException e) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ ", but not found in parameters: " + params, e);
}
mIv = null;
return;
}
mIv = ivSpec.getIV();
if (mIv == null) {
throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters");
}
}
@Override
protected final int getAdditionalEntropyAmountForBegin() {
if ((mIvRequired) && (mIv == null) && (isEncrypting())) {
// IV will need to be generated
return BLOCK_SIZE_BYTES;
}
return 0;
}
@Override
protected final int getAdditionalEntropyAmountForFinish() {
return 0;
}
@Override
protected final void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs) {
if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) {
// IV is being reused for encryption: this violates security best practices.
throw new IllegalStateException(
"IV has already been used. Reusing IV in encryption mode violates security best"
+ " practices.");
}
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
keymasterArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
if ((mIvRequired) && (mIv != null)) {
keymasterArgs.addBytes(KeymasterDefs.KM_TAG_NONCE, mIv);
}
}
@Override
protected final void loadAlgorithmSpecificParametersFromBeginResult(
@NonNull KeymasterArguments keymasterArgs) {
mIvHasBeenUsed = true;
// NOTE: Keymaster doesn't always return an IV, even if it's used.
byte[] returnedIv = keymasterArgs.getBytes(KeymasterDefs.KM_TAG_NONCE, null);
if ((returnedIv != null) && (returnedIv.length == 0)) {
returnedIv = null;
}
if (mIvRequired) {
if (mIv == null) {
mIv = returnedIv;
} else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
throw new ProviderException("IV in use differs from provided IV");
}
} else {
if (returnedIv != null) {
throw new ProviderException(
"IV in use despite IV not being used by this transformation");
}
}
}
@Override
protected final int engineGetBlockSize() {
return BLOCK_SIZE_BYTES;
}
@Override
protected final int engineGetOutputSize(int inputLen) {
return inputLen + 3 * BLOCK_SIZE_BYTES;
}
@Override
protected final byte[] engineGetIV() {
return ArrayUtils.cloneIfNotEmpty(mIv);
}
@Nullable
@Override
protected final AlgorithmParameters engineGetParameters() {
if (!mIvRequired) {
return null;
}
if ((mIv != null) && (mIv.length > 0)) {
try {
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(mIv));
return params;
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(
"Failed to obtain AES AlgorithmParameters", e);
} catch (InvalidParameterSpecException e) {
throw new ProviderException(
"Failed to initialize AES AlgorithmParameters with an IV",
e);
}
}
return null;
}
}