am 614b39f3: Merge "Refactor Android Keystore CipherSpi base class." into mnc-dev

* commit '614b39f3de7747e9e1cd00d8985ec6fa9b356217':
  Refactor Android Keystore CipherSpi base class.
This commit is contained in:
Alex Klyubin
2015-05-29 19:32:10 +00:00
committed by Android Git Automerger
6 changed files with 840 additions and 690 deletions

View File

@@ -77,17 +77,17 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider {
// javax.crypto.Cipher
putSymmetricCipherImpl("AES/ECB/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$ECB$NoPadding");
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$NoPadding");
putSymmetricCipherImpl("AES/ECB/PKCS7Padding",
PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$ECB$PKCS7Padding");
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$PKCS7Padding");
putSymmetricCipherImpl("AES/CBC/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CBC$NoPadding");
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$NoPadding");
putSymmetricCipherImpl("AES/CBC/PKCS7Padding",
PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CBC$PKCS7Padding");
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$PKCS7Padding");
putSymmetricCipherImpl("AES/CTR/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreCipherSpi$AES$CTR$NoPadding");
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding");
}
private void putMacImpl(String algorithm, String implClass) {

View File

@@ -1,685 +0,0 @@
/*
* 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.os.IBinder;
import android.security.KeyStore;
import android.security.KeyStoreException;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
import android.security.keystore.KeyProperties;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
/**
* Base class for {@link CipherSpi} providing Android KeyStore backed ciphers.
*
* @hide
*/
public abstract class AndroidKeyStoreCipherSpi extends CipherSpi
implements KeyStoreCryptoOperation {
public abstract static class AES extends AndroidKeyStoreCipherSpi {
protected AES(int keymasterBlockMode, int keymasterPadding, boolean ivUsed) {
super(KeymasterDefs.KM_ALGORITHM_AES,
keymasterBlockMode,
keymasterPadding,
16,
ivUsed);
}
public abstract static class ECB extends AES {
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);
}
}
}
public abstract static class CBC extends AES {
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);
}
}
}
public abstract static class CTR extends AES {
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 final KeyStore mKeyStore;
private final int mKeymasterAlgorithm;
private final int mKeymasterBlockMode;
private final int mKeymasterPadding;
private final int mBlockSizeBytes;
/** Whether this transformation requires an IV. */
private final boolean mIvRequired;
// Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
// doFinal finishes.
protected boolean mEncrypting;
private AndroidKeyStoreSecretKey mKey;
private SecureRandom mRng;
private boolean mFirstOperationInitiated;
private byte[] mIv;
/** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
private boolean mIvHasBeenUsed;
// Fields below must be reset after doFinal
private byte[] mAdditionalEntropyForBegin;
/**
* Token referencing this operation inside keystore service. It is initialized by
* {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and one some
* error conditions in between.
*/
private IBinder mOperationToken;
private long mOperationHandle;
private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer;
/**
* Encountered exception which could not be immediately thrown because it was encountered inside
* a method that does not throw checked exception. This exception will be thrown from
* {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and
* {@code engineDoFinal} start ignoring input data.
*/
private Exception mCachedException;
protected AndroidKeyStoreCipherSpi(
int keymasterAlgorithm,
int keymasterBlockMode,
int keymasterPadding,
int blockSizeBytes,
boolean ivUsed) {
mKeyStore = KeyStore.getInstance();
mKeymasterAlgorithm = keymasterAlgorithm;
mKeymasterBlockMode = keymasterBlockMode;
mKeymasterPadding = keymasterPadding;
mBlockSizeBytes = blockSizeBytes;
mIvRequired = ivUsed;
}
@Override
protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters();
try {
ensureKeystoreOperationInitialized();
} catch (InvalidAlgorithmParameterException e) {
throw new InvalidKeyException(e);
}
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
@Override
protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters(params);
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
@Override
protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters(params);
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeyException(
"Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
}
mKey = (AndroidKeyStoreSecretKey) key;
mRng = random;
mIv = null;
mFirstOperationInitiated = false;
if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) {
throw new UnsupportedOperationException(
"Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode);
}
mEncrypting = opmode == Cipher.ENCRYPT_MODE;
}
private void resetAll() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mOperationToken = null;
mKeyStore.abort(operationToken);
}
mEncrypting = false;
mKey = null;
mRng = null;
mFirstOperationInitiated = false;
mIv = null;
mIvHasBeenUsed = false;
mAdditionalEntropyForBegin = null;
mOperationToken = null;
mOperationHandle = 0;
mMainDataStreamer = null;
mCachedException = null;
}
private void resetWhilePreservingInitState() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mOperationToken = null;
mKeyStore.abort(operationToken);
}
mOperationHandle = 0;
mMainDataStreamer = null;
mAdditionalEntropyForBegin = null;
mCachedException = null;
}
private void ensureKeystoreOperationInitialized() throws InvalidKeyException,
InvalidAlgorithmParameterException {
if (mMainDataStreamer != null) {
return;
}
if (mCachedException != null) {
return;
}
if (mKey == null) {
throw new IllegalStateException("Not initialized");
}
if ((mEncrypting) && (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.");
}
KeymasterArguments keymasterInputArgs = new KeymasterArguments();
keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
KeymasterArguments keymasterOutputArgs = new KeymasterArguments();
OperationResult opResult = mKeyStore.begin(
mKey.getAlias(),
mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT,
true, // permit aborting this operation if keystore runs out of resources
keymasterInputArgs,
mAdditionalEntropyForBegin,
keymasterOutputArgs);
mAdditionalEntropyForBegin = null;
if (opResult == null) {
throw new KeyStoreConnectException();
}
// Store operation token and handle regardless of the error code returned by KeyStore to
// ensure that the operation gets aborted immediately if the code below throws an exception.
mOperationToken = opResult.token;
mOperationHandle = opResult.operationHandle;
// If necessary, throw an exception due to KeyStore operation having failed.
GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit(
mKeyStore, mKey, opResult.resultCode);
if (e != null) {
if (e instanceof InvalidKeyException) {
throw (InvalidKeyException) e;
} else if (e instanceof InvalidAlgorithmParameterException) {
throw (InvalidAlgorithmParameterException) e;
} else {
throw new ProviderException("Unexpected exception type", e);
}
}
if (mOperationToken == null) {
throw new ProviderException("Keystore returned null operation token");
}
if (mOperationHandle == 0) {
throw new ProviderException("Keystore returned invalid operation handle");
}
loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs);
mFirstOperationInitiated = true;
mIvHasBeenUsed = true;
mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
mKeyStore, opResult.token));
}
@Override
protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
if (mCachedException != null) {
return null;
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
mCachedException = e;
return null;
}
if (inputLen == 0) {
return null;
}
byte[] output;
try {
output = mMainDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
return null;
}
if (output.length == 0) {
return null;
}
return output;
}
@Override
protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException {
byte[] outputCopy = engineUpdate(input, inputOffset, inputLen);
if (outputCopy == null) {
return 0;
}
int outputAvailable = output.length - outputOffset;
if (outputCopy.length > outputAvailable) {
throw new ShortBufferException("Output buffer too short. Produced: "
+ outputCopy.length + ", available: " + outputAvailable);
}
System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
return outputCopy.length;
}
@Override
protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException {
if (mCachedException != null) {
throw (IllegalBlockSizeException)
new IllegalBlockSizeException().initCause(mCachedException);
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
byte[] output;
try {
output = mMainDataStreamer.doFinal(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH:
throw new IllegalBlockSizeException();
case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT:
throw new BadPaddingException();
case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
throw new AEADBadTagException();
default:
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
}
resetWhilePreservingInitState();
return output;
}
@Override
protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
BadPaddingException {
byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen);
if (outputCopy == null) {
return 0;
}
int outputAvailable = output.length - outputOffset;
if (outputCopy.length > outputAvailable) {
throw new ShortBufferException("Output buffer too short. Produced: "
+ outputCopy.length + ", available: " + outputAvailable);
}
System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
return outputCopy.length;
}
@Override
protected int engineGetBlockSize() {
return mBlockSizeBytes;
}
@Override
protected byte[] engineGetIV() {
return (mIv != null) ? mIv.clone() : null;
}
@Override
protected int engineGetOutputSize(int inputLen) {
return inputLen + 3 * engineGetBlockSize();
}
@Override
protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
// This should never be invoked because all algorithms registered with the AndroidKeyStore
// provide explicitly specify block mode.
throw new UnsupportedOperationException();
}
@Override
protected void engineSetPadding(String arg0) throws NoSuchPaddingException {
// This should never be invoked because all algorithms registered with the AndroidKeyStore
// provide explicitly specify padding mode.
throw new UnsupportedOperationException();
}
@Override
public void finalize() throws Throwable {
try {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mKeyStore.abort(operationToken);
}
} finally {
super.finalize();
}
}
@Override
public long getOperationHandle() {
return mOperationHandle;
}
// The methods below may need to be overridden by subclasses that use algorithm-specific
// parameters.
/**
* Returns algorithm-specific parameters used by this {@code CipherSpi} instance or {@code null}
* if no algorithm-specific parameters are used.
*
* <p>This implementation only handles the IV parameter.
*/
@Override
protected AlgorithmParameters engineGetParameters() {
if (!mIvRequired) {
return null;
}
if ((mIv != null) && (mIv.length > 0)) {
try {
AlgorithmParameters params =
AlgorithmParameters.getInstance(KeyProperties.KEY_ALGORITHM_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;
}
/**
* Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters
* may need to be stored to be reused after {@code doFinal}.
*
* <p>The default implementation only handles the IV parameters.
*
* @param params algorithm parameters.
*
* @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be
* automatically configured and thus {@code Cipher.init} needs to be invoked with
* explicitly provided parameters.
*/
protected 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 (!mEncrypting) {
// 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");
}
}
/**
* Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters
* may need to be stored to be reused after {@code doFinal}.
*
* <p>The default implementation only handles the IV parameters.
*
* @param params algorithm parameters.
*
* @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be
* automatically configured and thus {@code Cipher.init} needs to be invoked with
* explicitly provided parameters.
*/
protected 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 (!mEncrypting) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ ". Use IvParameterSpec or AlgorithmParameters to provide it.");
}
return;
}
IvParameterSpec ivSpec;
try {
ivSpec = params.getParameterSpec(IvParameterSpec.class);
} catch (InvalidParameterSpecException e) {
if (!mEncrypting) {
// 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");
}
}
/**
* Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters
* may need to be stored to be reused after {@code doFinal}.
*
* <p>The default implementation only handles the IV parameter.
*
* @throws InvalidKeyException if some/all of the parameters cannot be automatically configured
* and thus {@code Cipher.init} needs to be invoked with explicitly provided parameters.
*/
protected void initAlgorithmSpecificParameters() throws InvalidKeyException {
if (!mIvRequired) {
return;
}
// IV is used
if (!mEncrypting) {
throw new InvalidKeyException("IV required when decrypting"
+ ". Use IvParameterSpec or AlgorithmParameters to provide it.");
}
}
/**
* Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
*
* <p>The default implementation takes care of the IV.
*
* @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
* parameters.
*/
protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) {
if (!mFirstOperationInitiated) {
// First begin operation -- see if we need to provide additional entropy for IV
// generation.
if (mIvRequired) {
// IV is needed
if ((mIv == null) && (mEncrypting)) {
// IV was not provided by the caller and thus will be generated by keymaster.
// Mix in some additional entropy from the provided SecureRandom.
mAdditionalEntropyForBegin =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, mBlockSizeBytes);
}
}
}
if ((mIvRequired) && (mIv != null)) {
keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv);
}
}
/**
* Invoked by {@code engineInit} to obtain algorithm-specific parameters from the result of the
* Keymaster's {@code begin} operation. Some of these parameters may need to be reused after
* {@code doFinal} by {@link #addAlgorithmSpecificParametersToBegin(KeymasterArguments)}.
*
* <p>The default implementation only takes care of the IV.
*
* @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin}
* operation.
*/
protected void loadAlgorithmSpecificParametersFromBeginResult(
KeymasterArguments keymasterArgs) {
// NOTE: Keymaster doesn't always return an IV, even if it's used.
byte[] returnedIv = keymasterArgs.getBlob(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");
}
}
}
}

View File

@@ -0,0 +1,534 @@
/*
* 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.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.security.KeyStore;
import android.security.KeyStoreException;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
/**
* Base class for {@link CipherSpi} implementations of Android KeyStore backed ciphers.
*
* @hide
*/
abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation {
private final KeyStore mKeyStore;
// Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
// doFinal finishes.
private boolean mEncrypting;
private AndroidKeyStoreKey mKey;
private SecureRandom mRng;
/**
* Token referencing this operation inside keystore service. It is initialized by
* {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some error
* conditions in between.
*/
private IBinder mOperationToken;
private long mOperationHandle;
private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer;
/**
* Encountered exception which could not be immediately thrown because it was encountered inside
* a method that does not throw checked exception. This exception will be thrown from
* {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and
* {@code engineDoFinal} start ignoring input data.
*/
private Exception mCachedException;
AndroidKeyStoreCipherSpiBase() {
mKeyStore = KeyStore.getInstance();
}
@Override
protected final void engineInit(int opmode, Key key, SecureRandom random)
throws InvalidKeyException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters();
try {
ensureKeystoreOperationInitialized();
} catch (InvalidAlgorithmParameterException e) {
throw new InvalidKeyException(e);
}
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
@Override
protected final void engineInit(int opmode, Key key, AlgorithmParameters params,
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters(params);
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
@Override
protected final void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
init(opmode, key, random);
initAlgorithmSpecificParameters(params);
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) {
throw new UnsupportedOperationException(
"Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode);
}
mEncrypting = opmode == Cipher.ENCRYPT_MODE;
initKey(opmode, key);
if (mKey == null) {
throw new ProviderException("initKey did not initialize the key");
}
mRng = random;
}
/**
* Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new
* cipher instance.
*
* <p>Subclasses storing additional state should override this method, reset the additional
* state, and then chain to superclass.
*/
@CallSuper
protected void resetAll() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mOperationToken = null;
mKeyStore.abort(operationToken);
}
mEncrypting = false;
mKey = null;
mRng = null;
mOperationToken = null;
mOperationHandle = 0;
mMainDataStreamer = null;
mCachedException = null;
}
/**
* Resets this cipher while preserving the initialized state. This must be equivalent to
* rolling back the cipher's state to just after the most recent {@code engineInit} completed
* successfully.
*
* <p>Subclasses storing additional post-init state should override this method, reset the
* additional state, and then chain to superclass.
*/
@CallSuper
protected void resetWhilePreservingInitState() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mOperationToken = null;
mKeyStore.abort(operationToken);
}
mOperationHandle = 0;
mMainDataStreamer = null;
mCachedException = null;
}
private void ensureKeystoreOperationInitialized() throws InvalidKeyException,
InvalidAlgorithmParameterException {
if (mMainDataStreamer != null) {
return;
}
if (mCachedException != null) {
return;
}
if (mKey == null) {
throw new IllegalStateException("Not initialized");
}
KeymasterArguments keymasterInputArgs = new KeymasterArguments();
addAlgorithmSpecificParametersToBegin(keymasterInputArgs);
byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, getAdditionalEntropyAmountForBegin());
KeymasterArguments keymasterOutputArgs = new KeymasterArguments();
OperationResult opResult = mKeyStore.begin(
mKey.getAlias(),
mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT,
true, // permit aborting this operation if keystore runs out of resources
keymasterInputArgs,
additionalEntropy,
keymasterOutputArgs);
if (opResult == null) {
throw new KeyStoreConnectException();
}
// Store operation token and handle regardless of the error code returned by KeyStore to
// ensure that the operation gets aborted immediately if the code below throws an exception.
mOperationToken = opResult.token;
mOperationHandle = opResult.operationHandle;
// If necessary, throw an exception due to KeyStore operation having failed.
GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit(
mKeyStore, mKey, opResult.resultCode);
if (e != null) {
if (e instanceof InvalidKeyException) {
throw (InvalidKeyException) e;
} else if (e instanceof InvalidAlgorithmParameterException) {
throw (InvalidAlgorithmParameterException) e;
} else {
throw new ProviderException("Unexpected exception type", e);
}
}
if (mOperationToken == null) {
throw new ProviderException("Keystore returned null operation token");
}
if (mOperationHandle == 0) {
throw new ProviderException("Keystore returned invalid operation handle");
}
loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs);
mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
mKeyStore, opResult.token));
}
@Override
protected final byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
if (mCachedException != null) {
return null;
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
mCachedException = e;
return null;
}
if (inputLen == 0) {
return null;
}
byte[] output;
try {
output = mMainDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
return null;
}
if (output.length == 0) {
return null;
}
return output;
}
@Override
protected final int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException {
byte[] outputCopy = engineUpdate(input, inputOffset, inputLen);
if (outputCopy == null) {
return 0;
}
int outputAvailable = output.length - outputOffset;
if (outputCopy.length > outputAvailable) {
throw new ShortBufferException("Output buffer too short. Produced: "
+ outputCopy.length + ", available: " + outputAvailable);
}
System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
return outputCopy.length;
}
@Override
protected final int engineUpdate(ByteBuffer input, ByteBuffer output)
throws ShortBufferException {
return super.engineUpdate(input, output);
}
@Override
protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
super.engineUpdateAAD(input, inputOffset, inputLen);
}
@Override
protected final void engineUpdateAAD(ByteBuffer src) {
super.engineUpdateAAD(src);
}
@Override
protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException {
if (mCachedException != null) {
throw (IllegalBlockSizeException)
new IllegalBlockSizeException().initCause(mCachedException);
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
byte[] output;
try {
output = mMainDataStreamer.doFinal(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH:
throw new IllegalBlockSizeException();
case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT:
throw new BadPaddingException();
case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
throw new AEADBadTagException();
default:
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
}
resetWhilePreservingInitState();
return output;
}
@Override
protected final int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
BadPaddingException {
byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen);
if (outputCopy == null) {
return 0;
}
int outputAvailable = output.length - outputOffset;
if (outputCopy.length > outputAvailable) {
throw new ShortBufferException("Output buffer too short. Produced: "
+ outputCopy.length + ", available: " + outputAvailable);
}
System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length);
return outputCopy.length;
}
@Override
protected final int engineDoFinal(ByteBuffer input, ByteBuffer output)
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
return super.engineDoFinal(input, output);
}
@Override
protected final byte[] engineWrap(Key key)
throws IllegalBlockSizeException, InvalidKeyException {
return super.engineWrap(key);
}
@Override
protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException {
return super.engineUnwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType);
}
@Override
protected final void engineSetMode(String mode) throws NoSuchAlgorithmException {
// This should never be invoked because all algorithms registered with the AndroidKeyStore
// provide explicitly specify block mode.
throw new UnsupportedOperationException();
}
@Override
protected final void engineSetPadding(String arg0) throws NoSuchPaddingException {
// This should never be invoked because all algorithms registered with the AndroidKeyStore
// provide explicitly specify padding mode.
throw new UnsupportedOperationException();
}
@Override
protected final int engineGetKeySize(Key key) throws InvalidKeyException {
throw new UnsupportedOperationException();
}
@CallSuper
@Override
public void finalize() throws Throwable {
try {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mKeyStore.abort(operationToken);
}
} finally {
super.finalize();
}
}
@Override
public final long getOperationHandle() {
return mOperationHandle;
}
protected final void setKey(@NonNull AndroidKeyStoreKey key) {
mKey = key;
}
/**
* Returns {@code true} if this cipher is initialized for encryption, {@code false} if this
* cipher is initialized for decryption.
*/
protected final boolean isEncrypting() {
return mEncrypting;
}
@NonNull
protected final KeyStore getKeyStore() {
return mKeyStore;
}
// The methods below need to be implemented by subclasses.
/**
* Initializes this cipher with the provided key.
*
* @throws InvalidKeyException if the {@code key} is not suitable for this cipher in the
* specified {@code opmode}.
*
* @see #setKey(AndroidKeyStoreKey)
*/
protected abstract void initKey(int opmode, @Nullable Key key) throws InvalidKeyException;
/**
* Returns algorithm-specific parameters used by this cipher or {@code null} if no
* algorithm-specific parameters are used.
*/
@Nullable
@Override
protected abstract AlgorithmParameters engineGetParameters();
/**
* Invoked by {@code engineInit} to initialize algorithm-specific parameters when no additional
* initialization parameters were provided.
*
* @throws InvalidKeyException if this cipher cannot be configured based purely on the provided
* key and needs additional parameters to be provided to {@code Cipher.init}.
*/
protected abstract void initAlgorithmSpecificParameters() throws InvalidKeyException;
/**
* Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional
* parameters were provided.
*
* @param params additional algorithm parameters or {@code null} if not specified.
*
* @throws InvalidAlgorithmParameterException if there is insufficient information to configure
* this cipher or if the provided parameters are not suitable for this cipher.
*/
protected abstract void initAlgorithmSpecificParameters(
@Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException;
/**
* Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional
* parameters were provided.
*
* @param params additional algorithm parameters or {@code null} if not specified.
*
* @throws InvalidAlgorithmParameterException if there is insufficient information to configure
* this cipher or if the provided parameters are not suitable for this cipher.
*/
protected abstract void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
throws InvalidAlgorithmParameterException;
/**
* Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
* {@code begin} operation.
*
* <p>For decryption, this should be {@code 0} because decryption should not be consuming any
* entropy. For encryption, this value should match (or exceed) the amount of Shannon entropy of
* the ciphertext produced by this cipher assuming the key, the plaintext, and all explicitly
* provided parameters to {@code Cipher.init} are known. For example, for AES CBC encryption
* with an explicitly provided IV this should be {@code 0}, whereas for the case where IV is
* generated by the KeyStore's {@code begin} operation this should be {@code 16}. For RSA with
* OAEP this should be the size of the OAEP hash output. For RSA with PKCS#1 padding this should
* be the size of the padding string or could be raised (for simplicity) to the size of the
* modulus.
*/
protected abstract int getAdditionalEntropyAmountForBegin();
/**
* Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
*
* @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific
* parameters.
*/
protected abstract void addAlgorithmSpecificParametersToBegin(
@NonNull KeymasterArguments keymasterArgs);
/**
* Invoked to obtain algorithm-specific parameters from the result of the KeyStore's
* {@code begin} operation.
*
* <p>Some parameters, such as IV, are not required to be provided to {@code Cipher.init}. Such
* parameters, if not provided, must be generated by KeyStore and returned to the user of
* {@code Cipher} and potentially reused after {@code doFinal}.
*
* @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin}
* operation.
*/
protected abstract void loadAlgorithmSpecificParametersFromBeginResult(
@NonNull KeymasterArguments keymasterArgs);
}

View File

@@ -0,0 +1,292 @@
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;
}
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 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.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
keymasterArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode);
keymasterArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding);
if ((mIvRequired) && (mIv != null)) {
keymasterArgs.addBlob(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.getBlob(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;
}
}

View File

@@ -32,6 +32,10 @@ public abstract class ArrayUtils {
return ((array != null) && (array.length > 0)) ? array.clone() : array;
}
public static byte[] cloneIfNotEmpty(byte[] array) {
return ((array != null) && (array.length > 0)) ? array.clone() : array;
}
public static byte[] concat(byte[] arr1, byte[] arr2) {
return concat(arr1, 0, (arr1 != null) ? arr1.length : 0,
arr2, 0, (arr2 != null) ? arr2.length : 0);

View File

@@ -19,6 +19,8 @@ package android.security.keystore;
import android.security.KeyStore;
import android.security.keymaster.KeymasterDefs;
import libcore.util.EmptyArray;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -94,6 +96,9 @@ abstract class KeyStoreCryptoOperationUtils {
* RNG.
*/
static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) {
if (sizeBytes <= 0) {
return EmptyArray.BYTE;
}
if (rng == null) {
rng = getRng();
}