Merge changes I9731d978,I9e325782,I441a4d4d,I86a85e48,I9268fd66, ... am: aeb15e8592 am: 788f03de26

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1471565

Change-Id: I495aa0430b4ddd2137a8b07af7dbe56ffe9a49d3
This commit is contained in:
Treehugger Robot
2020-11-17 00:10:48 +00:00
committed by Automerger Merge Worker
39 changed files with 9232 additions and 7 deletions

View File

@@ -42742,6 +42742,12 @@ package android.security.identity {
package android.security.keystore {
public class BackendBusyException extends java.security.ProviderException {
ctor public BackendBusyException();
ctor public BackendBusyException(@NonNull String);
ctor public BackendBusyException(@NonNull String, @NonNull Throwable);
}
public class KeyExpiredException extends java.security.InvalidKeyException {
ctor public KeyExpiredException();
ctor public KeyExpiredException(String);

View File

@@ -40910,6 +40910,12 @@ package android.security.identity {
package android.security.keystore {
public class BackendBusyException extends java.security.ProviderException {
ctor public BackendBusyException();
ctor public BackendBusyException(@NonNull String);
ctor public BackendBusyException(@NonNull String, @NonNull Throwable);
}
public class KeyExpiredException extends java.security.InvalidKeyException {
ctor public KeyExpiredException();
ctor public KeyExpiredException(String);

View File

@@ -73,6 +73,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.Provider;
import java.security.Security;
import java.util.Optional;
/**
* Startup class for the zygote process.
@@ -225,7 +226,17 @@ public class ZygoteInit {
// AndroidKeyStoreProvider.install() manipulates the list of JCA providers to insert
// preferred providers. Note this is not done via security.properties as the JCA providers
// are not on the classpath in the case of, for example, raw dalvikvm runtimes.
AndroidKeyStoreProvider.install();
// TODO b/171305684 This code is used to conditionally enable the installation of the
// Keystore 2.0 provider to enable teams adjusting to Keystore 2.0 at their own
// pace. This code will be removed when all calling code was adjusted to
// Keystore 2.0.
Optional<Boolean> keystore2_enabled =
android.sysprop.Keystore2Properties.keystore2_enabled();
if (keystore2_enabled.isPresent() && keystore2_enabled.get()) {
android.security.keystore2.AndroidKeyStoreProvider.install();
} else {
AndroidKeyStoreProvider.install();
}
Log.i(TAG, "Installed AndroidKeyStoreProvider in "
+ (SystemClock.uptimeMillis() - startTime) + "ms.");
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);

View File

@@ -0,0 +1,33 @@
/*
* 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" 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;
import android.os.RemoteException;
/**
* This is a Producer of {@code R} that is expected to throw a {@link RemoteException}.
*
* It is used by Keystore2 service wrappers to handle and convert {@link RemoteException}
* and {@link android.os.ServiceSpecificException} into {@link KeyStoreException}.
*
* @hide
* @param <R>
*/
@FunctionalInterface
interface CheckedRemoteRequest<R> {
R execute() throws RemoteException;
}

View File

@@ -0,0 +1,277 @@
/*
* 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" 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;
import android.annotation.NonNull;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.system.keystore2.IKeystoreService;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyEntryResponse;
import android.system.keystore2.ResponseCode;
import android.util.Log;
import java.util.Calendar;
/**
* @hide This should not be made public in its present form because it
* assumes that private and secret key bytes are available and would
* preclude the use of hardware crypto.
*/
public class KeyStore2 {
private static final String TAG = "KeyStore";
private static final int RECOVERY_GRACE_PERIOD_MS = 50;
/**
* Keystore operation creation may fail
*
* Keystore used to work under the assumption that the creation of cryptographic operations
* always succeeds. However, the KeyMint backend has only a limited number of operation slots.
* In order to keep up the appearance of "infinite" operation slots, the Keystore daemon
* would prune least recently used operations if there is no available operation slot.
* As a result, good operations could be terminated prematurely.
*
* This opens AndroidKeystore up to denial-of-service and unintended livelock situations.
* E.g.: if multiple apps wake up at the same time, e.g., due to power management optimizations,
* and attempt to perform crypto operations, they start terminating each others operations
* without making any progress.
*
* To break out of livelocks and to discourage DoS attempts we have changed the pruning
* strategy such that it prefers clients that use few operation slots and only briefly.
* As a result we can, almost, guarantee that single operations that don't linger inactive
* for more than 5 seconds will conclude unhampered by the pruning strategy. "Almost",
* because there are operations related to file system encryption that can prune even
* these operations, but those are extremely rare.
*
* As a side effect of this new pruning strategy operation creation can now fail if the
* client has a lower pruning power than all of the existing operations.
*
* Pruning strategy
*
* To find a suitable candidate we compute the malus for the caller and each existing
* operation. The malus is the inverse of the pruning power (caller) or pruning
* resistance (existing operation). For the caller to be able to prune an operation it must
* find an operation with a malus higher than its own.
*
* For more detail on the pruning strategy consult the implementation at
* https://android.googlesource.com/platform/system/security/+/refs/heads/master/keystore2/src/operation.rs
*
* For older SDK version, KeyStore2 will poll the Keystore daemon for a free operation
* slot. So to applications, targeting earlier SDK versions, it will still look like cipher and
* signature object initialization always succeeds, however, it may take longer to get an
* operation.
*
* All SDK version benefit from fairer operation slot scheduling and a better chance to
* successfully conclude an operation.
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
static final long KEYSTORE_OPERATION_CREATION_MAY_FAIL = 169897160L;
// Never use mBinder directly, use KeyStore2.getService() instead or better yet
// handleRemoteExceptionWithRetry which retries connecting to Keystore once in case
// of a remote exception.
private IKeystoreService mBinder;
@FunctionalInterface
interface CheckedRemoteRequest<R> {
R execute(IKeystoreService service) throws RemoteException;
}
private <R> R handleRemoteExceptionWithRetry(@NonNull CheckedRemoteRequest<R> request)
throws KeyStoreException {
IKeystoreService service = getService(false /* retryLookup */);
boolean firstTry = true;
while (true) {
try {
return request.execute(service);
} catch (ServiceSpecificException e) {
Log.e(TAG, "KeyStore exception", e);
throw new KeyStoreException(e.errorCode, "");
} catch (RemoteException e) {
if (firstTry) {
Log.w(TAG, "Looks like we may have lost connection to the Keystore "
+ "daemon.");
Log.w(TAG, "Retrying after giving Keystore "
+ RECOVERY_GRACE_PERIOD_MS + "ms to recover.");
interruptedPreservingSleep(RECOVERY_GRACE_PERIOD_MS);
service = getService(true /* retry Lookup */);
firstTry = false;
} else {
Log.e(TAG, "Cannot connect to Keystore daemon.", e);
throw new KeyStoreException(ResponseCode.SYSTEM_ERROR, "");
}
}
}
}
private KeyStore2() {
mBinder = null;
}
public static KeyStore2 getInstance() {
return new KeyStore2();
}
private synchronized IKeystoreService getService(boolean retryLookup) {
if (mBinder == null || retryLookup) {
mBinder = IKeystoreService.Stub.asInterface(ServiceManager
.getService("android.system.keystore2"));
}
return mBinder;
}
void delete(KeyDescriptor descriptor) throws KeyStoreException {
handleRemoteExceptionWithRetry((service) -> {
service.deleteKey(descriptor);
return 0;
});
}
/**
* List all entries in the keystore for in the given namespace.
*/
public KeyDescriptor[] list(int domain, long namespace) throws KeyStoreException {
return handleRemoteExceptionWithRetry((service) -> service.listEntries(domain, namespace));
}
/**
* Create a grant that allows the grantee identified by {@code granteeUid} to use
* the key specified by {@code descriptor} withint the restrictions given by
* {@code accessVectore}.
* @see IKeystoreService#grant(KeyDescriptor, int, int) for more details.
* @param descriptor
* @param granteeUid
* @param accessVector
* @return
* @throws KeyStoreException
* @hide
*/
public KeyDescriptor grant(KeyDescriptor descriptor, int granteeUid, int accessVector)
throws KeyStoreException {
return handleRemoteExceptionWithRetry(
(service) -> service.grant(descriptor, granteeUid, accessVector)
);
}
/**
* Destroys a grant.
* @see IKeystoreService#ungrant(KeyDescriptor, int) for more details.
* @param descriptor
* @param granteeUid
* @throws KeyStoreException
* @hide
*/
public void ungrant(KeyDescriptor descriptor, int granteeUid)
throws KeyStoreException {
handleRemoteExceptionWithRetry((service) -> {
service.ungrant(descriptor, granteeUid);
return 0;
});
}
/**
* Retrieves a key entry from the keystore backend.
* @see IKeystoreService#getKeyEntry(KeyDescriptor) for more details.
* @param descriptor
* @return
* @throws KeyStoreException
* @hide
*/
public KeyEntryResponse getKeyEntry(@NonNull KeyDescriptor descriptor)
throws KeyStoreException {
return handleRemoteExceptionWithRetry((service) -> service.getKeyEntry(descriptor));
}
/**
* Get the security level specific keystore interface from the keystore daemon.
* @see IKeystoreService#getSecurityLevel(int) for more details.
* @param securityLevel
* @return
* @throws KeyStoreException
* @hide
*/
public KeyStoreSecurityLevel getSecurityLevel(int securityLevel)
throws KeyStoreException {
return handleRemoteExceptionWithRetry((service) ->
new KeyStoreSecurityLevel(
service.getSecurityLevel(securityLevel)
)
);
}
/**
* Update the subcomponents of a key entry designated by the key descriptor.
* @see IKeystoreService#updateSubcomponent(KeyDescriptor, byte[], byte[]) for more details.
* @param key
* @param publicCert
* @param publicCertChain
* @throws KeyStoreException
* @hide
*/
public void updateSubcomponents(@NonNull KeyDescriptor key, byte[] publicCert,
byte[] publicCertChain) throws KeyStoreException {
handleRemoteExceptionWithRetry((service) -> {
service.updateSubcomponent(key, publicCert, publicCertChain);
return 0;
});
}
/**
* Delete the key designed by the key descriptor.
* @see IKeystoreService#deleteKey(KeyDescriptor) for more details.
* @param descriptor
* @throws KeyStoreException
* @hide
*/
public void deleteKey(@NonNull KeyDescriptor descriptor)
throws KeyStoreException {
handleRemoteExceptionWithRetry((service) -> {
service.deleteKey(descriptor);
return 0;
});
}
protected static void interruptedPreservingSleep(long millis) {
boolean wasInterrupted = false;
Calendar calendar = Calendar.getInstance();
long target = calendar.getTimeInMillis() + millis;
while (true) {
try {
Thread.sleep(target - calendar.getTimeInMillis());
break;
} catch (InterruptedException e) {
wasInterrupted = true;
} catch (IllegalArgumentException e) {
// This means that the argument to sleep was negative.
// So we are done sleeping.
break;
}
}
if (wasInterrupted) {
Thread.currentThread().interrupt();
}
}
}

View File

@@ -0,0 +1,141 @@
/*
* 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" 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;
import android.annotation.NonNull;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.security.keymaster.KeymasterDefs;
import android.system.keystore2.IKeystoreOperation;
import android.system.keystore2.KeyParameter;
import android.system.keystore2.ResponseCode;
import android.util.Log;
/**
* @hide
*/
public class KeyStoreOperation {
static final String TAG = "KeyStoreOperation";
private final IKeystoreOperation mOperation;
private final Long mChallenge;
private final KeyParameter[] mParameters;
public KeyStoreOperation(
@NonNull IKeystoreOperation operation,
Long challenge,
KeyParameter[] parameters
) {
this.mOperation = operation;
this.mChallenge = challenge;
this.mParameters = parameters;
}
/**
* Gets the challenge associated with this operation.
* @return null if the operation does not required authorization. A 64bit operation
* challenge otherwise.
*/
public Long getChallenge() {
return mChallenge;
}
/**
* Gets the parameters associated with this operation.
* @return
*/
public KeyParameter[] getParameters() {
return mParameters;
}
private <R> R handleExceptions(@NonNull CheckedRemoteRequest<R> request)
throws KeyStoreException {
try {
return request.execute();
} catch (ServiceSpecificException e) {
switch(e.errorCode) {
case ResponseCode.OPERATION_BUSY: {
throw new IllegalThreadStateException(
"Cannot update the same operation concurrently."
);
}
default:
// TODO Human readable string. Use something like KeyStore.getKeyStoreException
throw new KeyStoreException(e.errorCode, "");
}
} catch (RemoteException e) {
// Log exception and report invalid operation handle.
// This should prompt the caller drop the reference to this operation and retry.
Log.e(
TAG,
"Remote exception while advancing a KeyStoreOperation.",
e
);
throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE, "");
}
}
/**
* Updates the Keystore operation represented by this object with more associated data.
* @see IKeystoreOperation#updateAad(byte[]) for more details.
* @param input
* @throws KeyStoreException
*/
public void updateAad(@NonNull byte[] input) throws KeyStoreException {
handleExceptions(() -> {
mOperation.updateAad(input);
return 0;
});
}
/**
* Updates the Keystore operation represented by this object.
* @see IKeystoreOperation#update(byte[]) for more details.
* @param input
* @return
* @throws KeyStoreException
* @hide
*/
public byte[] update(@NonNull byte[] input) throws KeyStoreException {
return handleExceptions(() -> mOperation.update(input));
}
/**
* Finalizes the Keystore operation represented by this object.
* @see IKeystoreOperation#finish(byte[], byte[]) for more details.
* @param input
* @param signature
* @return
* @throws KeyStoreException
* @hide
*/
public byte[] finish(byte[] input, byte[] signature) throws KeyStoreException {
return handleExceptions(() -> mOperation.finish(input, signature));
}
/**
* Aborts the Keystore operation represented by this object.
* @see IKeystoreOperation#abort() for more details.
* @throws KeyStoreException
* @hide
*/
public void abort() throws KeyStoreException {
handleExceptions(() -> {
mOperation.abort();
return 0;
});
}
}

View File

@@ -0,0 +1,217 @@
/*
* 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" 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;
import android.annotation.NonNull;
import android.app.compat.CompatChanges;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.security.keystore.BackendBusyException;
import android.security.keystore.KeyStoreConnectException;
import android.system.keystore2.AuthenticatorSpec;
import android.system.keystore2.CreateOperationResponse;
import android.system.keystore2.IKeystoreSecurityLevel;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyMetadata;
import android.system.keystore2.KeyParameter;
import android.system.keystore2.ResponseCode;
import android.util.Log;
import java.util.Calendar;
import java.util.Collection;
/**
* This is a shim around the security level specific interface of Keystore 2.0. Services with
* this interface are instantiated per KeyMint backend, each having there own security level.
* Thus this object representation of a security level.
* @hide
*/
public class KeyStoreSecurityLevel {
private static final String TAG = "KeyStoreSecurityLevel";
private final IKeystoreSecurityLevel mSecurityLevel;
public KeyStoreSecurityLevel(IKeystoreSecurityLevel securityLevel) {
this.mSecurityLevel = securityLevel;
}
private <R> R handleExceptions(CheckedRemoteRequest<R> request) throws KeyStoreException {
try {
return request.execute();
} catch (ServiceSpecificException e) {
throw new KeyStoreException(e.errorCode, "");
} catch (RemoteException e) {
// Log exception and report invalid operation handle.
// This should prompt the caller drop the reference to this operation and retry.
Log.e(TAG, "Could not connect to Keystore.", e);
throw new KeyStoreException(ResponseCode.SYSTEM_ERROR, "");
}
}
/**
* Creates a new keystore operation.
* @see IKeystoreSecurityLevel#createOperation(KeyDescriptor, KeyParameter[], boolean) for more
* details.
* @param keyDescriptor
* @param args
* @return
* @throws KeyStoreException
* @hide
*/
public KeyStoreOperation createOperation(@NonNull KeyDescriptor keyDescriptor,
Collection<KeyParameter> args) throws KeyStoreException {
while (true) {
try {
CreateOperationResponse createOperationResponse =
mSecurityLevel.createOperation(
keyDescriptor,
args.toArray(new KeyParameter[args.size()]),
false /* forced */
);
Long challenge = null;
if (createOperationResponse.operationChallenge != null) {
challenge = createOperationResponse.operationChallenge.challenge;
}
KeyParameter[] parameters = null;
if (createOperationResponse.parameters != null) {
parameters = createOperationResponse.parameters.keyParameter;
}
return new KeyStoreOperation(
createOperationResponse.iOperation,
challenge,
parameters);
} catch (ServiceSpecificException e) {
switch (e.errorCode) {
case ResponseCode.BACKEND_BUSY: {
if (CompatChanges.isChangeEnabled(
KeyStore2.KEYSTORE_OPERATION_CREATION_MAY_FAIL)) {
// Starting with Android S we inform the caller about the
// backend being busy.
throw new BackendBusyException();
} else {
// Before Android S operation creation must always succeed. So we
// just have to retry. We do so with a randomized back-off between
// 50 and 250ms.
// It is a little awkward that we cannot break out of this loop
// by interrupting this thread. But that is the expected behavior.
// There is some comfort in the fact that interrupting a thread
// also does not unblock a thread waiting for a binder transaction.
interruptedPreservingSleep((long) (Math.random() * 200 + 50));
}
break;
}
default:
throw new KeyStoreException(e.errorCode, "");
}
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
throw new KeyStoreConnectException();
}
}
}
/**
* Generates a new key in Keystore.
* @see IKeystoreSecurityLevel#generateKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int,
* byte[]) for more details.
* @param descriptor
* @param attestationKey
* @param args
* @param flags
* @param entropy
* @return
* @throws KeyStoreException
* @hide
*/
public KeyMetadata generateKey(@NonNull KeyDescriptor descriptor, KeyDescriptor attestationKey,
Collection<KeyParameter> args, int flags, byte[] entropy)
throws KeyStoreException {
return handleExceptions(() -> mSecurityLevel.generateKey(
descriptor, attestationKey, args.toArray(new KeyParameter[args.size()]),
flags, entropy));
}
/**
* Imports a key into Keystore.
* @see IKeystoreSecurityLevel#importKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int,
* byte[]) for more details.
* @param descriptor
* @param attestationKey
* @param args
* @param flags
* @param keyData
* @return
* @throws KeyStoreException
* @hide
*/
public KeyMetadata importKey(KeyDescriptor descriptor, KeyDescriptor attestationKey,
Collection<KeyParameter> args, int flags, byte[] keyData)
throws KeyStoreException {
return handleExceptions(() -> mSecurityLevel.importKey(descriptor, attestationKey,
args.toArray(new KeyParameter[args.size()]), flags, keyData));
}
/**
* Imports a wrapped key into Keystore.
* @see IKeystoreSecurityLevel#importWrappedKey(KeyDescriptor, KeyDescriptor, byte[],
* KeyParameter[], AuthenticatorSpec[]) for more details.
* @param wrappedKeyDescriptor
* @param wrappingKeyDescriptor
* @param wrappedKey
* @param maskingKey
* @param args
* @param authenticatorSpecs
* @return
* @throws KeyStoreException
* @hide
*/
public KeyMetadata importWrappedKey(@NonNull KeyDescriptor wrappedKeyDescriptor,
@NonNull KeyDescriptor wrappingKeyDescriptor,
@NonNull byte[] wrappedKey, byte[] maskingKey,
Collection<KeyParameter> args, @NonNull AuthenticatorSpec[] authenticatorSpecs)
throws KeyStoreException {
KeyDescriptor keyDescriptor = new KeyDescriptor();
keyDescriptor.alias = wrappedKeyDescriptor.alias;
keyDescriptor.nspace = wrappedKeyDescriptor.nspace;
keyDescriptor.blob = wrappedKey;
keyDescriptor.domain = wrappedKeyDescriptor.domain;
return handleExceptions(() -> mSecurityLevel.importWrappedKey(wrappedKeyDescriptor,
wrappingKeyDescriptor, maskingKey,
args.toArray(new KeyParameter[args.size()]), authenticatorSpecs));
}
protected static void interruptedPreservingSleep(long millis) {
boolean wasInterrupted = false;
Calendar calendar = Calendar.getInstance();
long target = calendar.getTimeInMillis() + millis;
while (true) {
try {
Thread.sleep(target - calendar.getTimeInMillis());
break;
} catch (InterruptedException e) {
wasInterrupted = true;
} catch (IllegalArgumentException e) {
// This means that the argument to sleep was negative.
// So we are done sleeping.
break;
}
}
if (wasInterrupted) {
Thread.currentThread().interrupt();
}
}
}

View File

@@ -34,7 +34,7 @@ import java.security.Provider;
*
* @hide
*/
class AndroidKeyStoreBCWorkaroundProvider extends Provider {
public class AndroidKeyStoreBCWorkaroundProvider extends Provider {
// IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these
// classes when this provider is instantiated and installed early on during each app's
@@ -50,8 +50,14 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider {
private static final String DESEDE_SYSTEM_PROPERTY = "ro.hardware.keystore_desede";
AndroidKeyStoreBCWorkaroundProvider() {
super("AndroidKeyStoreBCWorkaround",
/** @hide */
public AndroidKeyStoreBCWorkaroundProvider() {
this("AndroidKeyStoreBCWorkaround");
}
/** @hide **/
public AndroidKeyStoreBCWorkaroundProvider(String providerName) {
super(providerName,
1.0,
"Android KeyStore security provider to work around Bouncy Castle");

View File

@@ -71,14 +71,20 @@ public class AndroidKeyStoreProvider extends Provider {
private static final String DESEDE_SYSTEM_PROPERTY =
"ro.hardware.keystore_desede";
/** @hide **/
/** @hide */
public AndroidKeyStoreProvider() {
super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
this(PROVIDER_NAME);
}
/** @hide **/
public AndroidKeyStoreProvider(String providerName) {
super(providerName, 1.0, "Android KeyStore security provider");
boolean supports3DES = "true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY));
// java.security.KeyStore
put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi");
put("alg.alias.KeyStore.AndroidKeyStoreLegacy", "AndroidKeyStore");
// java.security.KeyPairGenerator
put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
@@ -438,8 +444,12 @@ public class AndroidKeyStoreProvider extends Provider {
@NonNull
public static java.security.KeyStore getKeyStoreForUid(int uid)
throws KeyStoreException, NoSuchProviderException {
String providerName = PROVIDER_NAME;
if (android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) {
providerName = "AndroidKeyStoreLegacy";
}
java.security.KeyStore result =
java.security.KeyStore.getInstance("AndroidKeyStore", PROVIDER_NAME);
java.security.KeyStore.getInstance(providerName);
try {
result.load(new AndroidKeyStoreLoadStoreParameter(uid));
} catch (NoSuchAlgorithmException | CertificateException | IOException e) {

View File

@@ -0,0 +1,52 @@
/*
* 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" 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 java.security.ProviderException;
/**
* Indicates a transient error that prevented a key operation from being created.
* Callers should try again with a back-off period of 10-30 milliseconds.
*/
public class BackendBusyException extends ProviderException {
/**
* Constructs a new {@code BackendBusyException} without detail message and cause.
*/
public BackendBusyException() {
super("The keystore backend has no operation slots available. Retry later.");
}
/**
* Constructs a new {@code BackendBusyException} with the provided detail message and
* no cause.
*/
public BackendBusyException(@NonNull String message) {
super(message);
}
/**
* Constructs a new {@code BackendBusyException} with the provided detail message and
* cause.
*/
public BackendBusyException(@NonNull String message, @NonNull Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,317 @@
/*
* Copyright (C) 2018 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.keystore2;
import android.annotation.NonNull;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
import android.security.keystore.KeyProperties;
import android.system.keystore2.KeyParameter;
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 java.util.List;
import javax.crypto.CipherSpi;
import javax.crypto.spec.IvParameterSpec;
/**
* Base class for Android Keystore 3DES {@link CipherSpi} implementations.
*
* @hide
*/
public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase {
private static final int BLOCK_SIZE_BYTES = 8;
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;
AndroidKeyStore3DESCipherSpi(
int keymasterBlockMode,
int keymasterPadding,
boolean ivRequired) {
mKeymasterBlockMode = keymasterBlockMode;
mKeymasterPadding = keymasterPadding;
mIvRequired = ivRequired;
}
abstract static class ECB extends AndroidKeyStore3DESCipherSpi {
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 AndroidKeyStore3DESCipherSpi {
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);
}
}
}
@Override
protected void initKey(int i, Key key) throws InvalidKeyException {
if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeyException(
"Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
}
if (!KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException(
"Unsupported key algorithm: " + key.getAlgorithm() + ". Only " +
KeyProperties.KEY_ALGORITHM_3DES + " supported");
}
setKey((AndroidKeyStoreSecretKey) key);
}
@Override
protected int engineGetBlockSize() {
return BLOCK_SIZE_BYTES;
}
@Override
protected int engineGetOutputSize(int inputLen) {
return inputLen + 3 * BLOCK_SIZE_BYTES;
}
@Override
protected final byte[] engineGetIV() {
return ArrayUtils.cloneIfNotEmpty(mIv);
}
@Override
protected AlgorithmParameters engineGetParameters() {
if (!mIvRequired) {
return null;
}
if ((mIv != null) && (mIv.length > 0)) {
try {
AlgorithmParameters params = AlgorithmParameters.getInstance("DESede");
params.init(new IvParameterSpec(mIv));
return params;
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(
"Failed to obtain 3DES AlgorithmParameters", e);
} catch (InvalidParameterSpecException e) {
throw new ProviderException(
"Failed to initialize 3DES AlgorithmParameters with an IV",
e);
}
}
return null;
}
@Override
protected 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 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 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 (!"DESede".equalsIgnoreCase(params.getAlgorithm())) {
throw new InvalidAlgorithmParameterException(
"Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm()
+ ". Supported: DESede");
}
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 int getAdditionalEntropyAmountForFinish() {
return 0;
}
@Override
protected void addAlgorithmSpecificParametersToBegin(@NonNull List<KeyParameter> parameters) {
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.");
}
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_ALGORITHM,
KeymasterDefs.KM_ALGORITHM_3DES
));
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_BLOCK_MODE,
mKeymasterBlockMode
));
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PADDING,
mKeymasterPadding
));
if (mIvRequired && (mIv != null)) {
parameters.add(KeyStore2ParameterUtils.makeBytes(KeymasterDefs.KM_TAG_NONCE, mIv));
}
}
@Override
protected void loadAlgorithmSpecificParametersFromBeginResult(
KeyParameter[] parameters) {
mIvHasBeenUsed = true;
// NOTE: Keymaster doesn't always return an IV, even if it's used.
byte[] returnedIv = null;
if (parameters != null) {
for (KeyParameter p : parameters) {
if (p.tag == KeymasterDefs.KM_TAG_NONCE) {
returnedIv = p.blob;
break;
}
}
}
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 void resetAll() {
mIv = null;
mIvHasBeenUsed = false;
super.resetAll();
}
}

View File

@@ -0,0 +1,439 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
import android.security.keystore.KeyProperties;
import android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer.Stream;
import android.system.keystore2.KeyParameter;
import libcore.util.EmptyArray;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
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 java.util.List;
import javax.crypto.CipherSpi;
import javax.crypto.spec.GCMParameterSpec;
/**
* Base class for Android Keystore authenticated AES {@link CipherSpi} implementations.
*
* @hide
*/
abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
abstract static class GCM extends AndroidKeyStoreAuthenticatedAESCipherSpi {
static final int MIN_SUPPORTED_TAG_LENGTH_BITS = 96;
private static final int MAX_SUPPORTED_TAG_LENGTH_BITS = 128;
private static final int DEFAULT_TAG_LENGTH_BITS = 128;
private static final int IV_LENGTH_BYTES = 12;
private int mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
GCM(int keymasterPadding) {
super(KeymasterDefs.KM_MODE_GCM, keymasterPadding);
}
@Override
protected final void resetAll() {
mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
super.resetAll();
}
@Override
protected final void resetWhilePreservingInitState() {
super.resetWhilePreservingInitState();
}
@Override
protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {
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 {
// IV is used
if (params == null) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException(
"GCMParameterSpec must be provided when decrypting");
}
return;
}
if (!(params instanceof GCMParameterSpec)) {
throw new InvalidAlgorithmParameterException("Only GCMParameterSpec supported");
}
GCMParameterSpec spec = (GCMParameterSpec) params;
byte[] iv = spec.getIV();
if (iv == null) {
throw new InvalidAlgorithmParameterException("Null IV in GCMParameterSpec");
} else if (iv.length != IV_LENGTH_BYTES) {
throw new InvalidAlgorithmParameterException("Unsupported IV length: "
+ iv.length + " bytes. Only " + IV_LENGTH_BYTES
+ " bytes long IV supported");
}
int tagLengthBits = spec.getTLen();
if ((tagLengthBits < MIN_SUPPORTED_TAG_LENGTH_BITS)
|| (tagLengthBits > MAX_SUPPORTED_TAG_LENGTH_BITS)
|| ((tagLengthBits % 8) != 0)) {
throw new InvalidAlgorithmParameterException(
"Unsupported tag length: " + tagLengthBits + " bits"
+ ". Supported lengths: 96, 104, 112, 120, 128");
}
setIv(iv);
mTagLengthBits = tagLengthBits;
}
@Override
protected final void initAlgorithmSpecificParameters(AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
if (params == null) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException("IV required when decrypting"
+ ". Use GCMParameterSpec or GCM AlgorithmParameters to provide it.");
}
return;
}
if (!"GCM".equalsIgnoreCase(params.getAlgorithm())) {
throw new InvalidAlgorithmParameterException(
"Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm()
+ ". Supported: GCM");
}
GCMParameterSpec spec;
try {
spec = params.getParameterSpec(GCMParameterSpec.class);
} catch (InvalidParameterSpecException e) {
if (!isEncrypting()) {
// IV must be provided by the caller
throw new InvalidAlgorithmParameterException("IV and tag length required when"
+ " decrypting, but not found in parameters: " + params, e);
}
setIv(null);
return;
}
initAlgorithmSpecificParameters(spec);
}
@Nullable
@Override
protected final AlgorithmParameters engineGetParameters() {
byte[] iv = getIv();
if ((iv != null) && (iv.length > 0)) {
try {
AlgorithmParameters params = AlgorithmParameters.getInstance("GCM");
params.init(new GCMParameterSpec(mTagLengthBits, iv));
return params;
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(
"Failed to obtain GCM AlgorithmParameters", e);
} catch (InvalidParameterSpecException e) {
throw new ProviderException(
"Failed to initialize GCM AlgorithmParameters", e);
}
}
return null;
}
@NonNull
@Override
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
KeyStoreOperation operation) {
KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
operation), 0);
if (isEncrypting()) {
return streamer;
} else {
// When decrypting, to avoid leaking unauthenticated plaintext, do not return any
// plaintext before ciphertext is authenticated by KeyStore.finish.
return new BufferAllOutputUntilDoFinalStreamer(streamer);
}
}
@NonNull
@Override
protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
KeyStoreOperation operation) {
return new KeyStoreCryptoOperationChunkedStreamer(
new AdditionalAuthenticationDataStream(operation), 0);
}
@Override
protected final int getAdditionalEntropyAmountForBegin() {
if ((getIv() == null) && (isEncrypting())) {
// IV will need to be generated
return IV_LENGTH_BYTES;
}
return 0;
}
@Override
protected final int getAdditionalEntropyAmountForFinish() {
return 0;
}
@Override
protected final void addAlgorithmSpecificParametersToBegin(
@NonNull List<KeyParameter> parameters) {
super.addAlgorithmSpecificParametersToBegin(parameters);
parameters.add(KeyStore2ParameterUtils.makeInt(
KeymasterDefs.KM_TAG_MAC_LENGTH,
mTagLengthBits
));
}
protected final int getTagLengthBits() {
return mTagLengthBits;
}
public static final class NoPadding extends GCM {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
@Override
protected final int engineGetOutputSize(int inputLen) {
int tagLengthBytes = (getTagLengthBits() + 7) / 8;
long result;
if (isEncrypting()) {
result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
+ tagLengthBytes;
} else {
result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen
- tagLengthBytes;
}
if (result < 0) {
return 0;
} else if (result > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
return (int) result;
}
}
}
private static final int BLOCK_SIZE_BYTES = 16;
private final int mKeymasterBlockMode;
private final int mKeymasterPadding;
private byte[] mIv;
/** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
private boolean mIvHasBeenUsed;
AndroidKeyStoreAuthenticatedAESCipherSpi(
int keymasterBlockMode,
int keymasterPadding) {
mKeymasterBlockMode = keymasterBlockMode;
mKeymasterPadding = keymasterPadding;
}
@Override
protected void resetAll() {
mIv = null;
mIvHasBeenUsed = false;
super.resetAll();
}
@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 void addAlgorithmSpecificParametersToBegin(
@NonNull List<KeyParameter> parameters) {
if ((isEncrypting()) && (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.");
}
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_ALGORITHM,
KeymasterDefs.KM_ALGORITHM_AES
));
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_BLOCK_MODE,
mKeymasterBlockMode
));
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PADDING,
mKeymasterPadding
));
if (mIv != null) {
parameters.add(KeyStore2ParameterUtils.makeBytes(KeymasterDefs.KM_TAG_NONCE, mIv));
}
}
@Override
protected final void loadAlgorithmSpecificParametersFromBeginResult(
KeyParameter[] parameters) {
mIvHasBeenUsed = true;
// NOTE: Keymaster doesn't always return an IV, even if it's used.
byte[] returnedIv = null;
if (parameters != null) {
for (KeyParameter p : parameters) {
if (p.tag == KeymasterDefs.KM_TAG_NONCE) {
returnedIv = p.blob;
break;
}
}
}
if (mIv == null) {
mIv = returnedIv;
} else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
throw new ProviderException("IV in use differs from provided IV");
}
}
@Override
protected final int engineGetBlockSize() {
return BLOCK_SIZE_BYTES;
}
@Override
protected final byte[] engineGetIV() {
return ArrayUtils.cloneIfNotEmpty(mIv);
}
protected void setIv(byte[] iv) {
mIv = iv;
}
protected byte[] getIv() {
return mIv;
}
/**
* {@link KeyStoreCryptoOperationStreamer} which buffers all output until {@code doFinal} from
* which it returns all output in one go, provided {@code doFinal} succeeds.
*/
private static class BufferAllOutputUntilDoFinalStreamer
implements KeyStoreCryptoOperationStreamer {
private final KeyStoreCryptoOperationStreamer mDelegate;
private ByteArrayOutputStream mBufferedOutput = new ByteArrayOutputStream();
private long mProducedOutputSizeBytes;
private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) {
mDelegate = delegate;
}
@Override
public byte[] update(byte[] input, int inputOffset, int inputLength)
throws KeyStoreException {
byte[] output = mDelegate.update(input, inputOffset, inputLength);
if (output != null) {
try {
mBufferedOutput.write(output);
} catch (IOException e) {
throw new ProviderException("Failed to buffer output", e);
}
}
return EmptyArray.BYTE;
}
@Override
public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
byte[] signature) throws KeyStoreException {
byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, signature);
if (output != null) {
try {
mBufferedOutput.write(output);
} catch (IOException e) {
throw new ProviderException("Failed to buffer output", e);
}
}
byte[] result = mBufferedOutput.toByteArray();
mBufferedOutput.reset();
mProducedOutputSizeBytes += result.length;
return result;
}
@Override
public long getConsumedInputSizeBytes() {
return mDelegate.getConsumedInputSizeBytes();
}
@Override
public long getProducedOutputSizeBytes() {
return mProducedOutputSizeBytes;
}
}
/**
* Additional Authentication Data (AAD) stream via a KeyStore streaming operation. This stream
* sends AAD into the KeyStore.
*/
private static class AdditionalAuthenticationDataStream implements Stream {
private final KeyStoreOperation mOperation;
private AdditionalAuthenticationDataStream(KeyStoreOperation operation) {
mOperation = operation;
}
@Override
public byte[] update(byte[] input) throws KeyStoreException {
mOperation.updateAad(input);
return null;
}
@Override
public byte[] finish(byte[] input, byte[] signature) {
return null;
}
}
}

View File

@@ -0,0 +1,273 @@
/*
* 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.keystore2;
import java.security.Provider;
/**
* {@link Provider} of JCA crypto operations operating on Android KeyStore keys.
*
* <p>This provider was separated out of {@link AndroidKeyStoreProvider} to work around the issue
* that Bouncy Castle provider incorrectly declares that it accepts arbitrary keys (incl. Android
* KeyStore ones). This causes JCA to select the Bouncy Castle's implementation of JCA crypto
* operations for Android KeyStore keys unless Android KeyStore's own implementations are installed
* as higher-priority than Bouncy Castle ones. The purpose of this provider is to do just that: to
* offer crypto operations operating on Android KeyStore keys and to be installed at higher priority
* than the Bouncy Castle provider.
*
* <p>Once Bouncy Castle provider is fixed, this provider can be merged into the
* {@code AndroidKeyStoreProvider}.
*
* @hide
*/
class AndroidKeyStoreBCWorkaroundProvider extends Provider {
// IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these
// classes when this provider is instantiated and installed early on during each app's
// initialization process.
private static final String PACKAGE_NAME = "android.security.keystore2";
private static final String KEYSTORE_SECRET_KEY_CLASS_NAME =
PACKAGE_NAME + ".AndroidKeyStoreSecretKey";
private static final String KEYSTORE_PRIVATE_KEY_CLASS_NAME =
PACKAGE_NAME + ".AndroidKeyStorePrivateKey";
private static final String KEYSTORE_PUBLIC_KEY_CLASS_NAME =
PACKAGE_NAME + ".AndroidKeyStorePublicKey";
private static final String DESEDE_SYSTEM_PROPERTY = "ro.hardware.keystore_desede";
AndroidKeyStoreBCWorkaroundProvider() {
super("AndroidKeyStoreBCWorkaround",
1.0,
"Android KeyStore security provider to work around Bouncy Castle");
// --------------------- javax.crypto.Mac
putMacImpl("HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA1");
put("Alg.Alias.Mac.1.2.840.113549.2.7", "HmacSHA1");
put("Alg.Alias.Mac.HMAC-SHA1", "HmacSHA1");
put("Alg.Alias.Mac.HMAC/SHA1", "HmacSHA1");
putMacImpl("HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA224");
put("Alg.Alias.Mac.1.2.840.113549.2.9", "HmacSHA224");
put("Alg.Alias.Mac.HMAC-SHA224", "HmacSHA224");
put("Alg.Alias.Mac.HMAC/SHA224", "HmacSHA224");
putMacImpl("HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA256");
put("Alg.Alias.Mac.1.2.840.113549.2.9", "HmacSHA256");
put("Alg.Alias.Mac.HMAC-SHA256", "HmacSHA256");
put("Alg.Alias.Mac.HMAC/SHA256", "HmacSHA256");
putMacImpl("HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA384");
put("Alg.Alias.Mac.1.2.840.113549.2.10", "HmacSHA384");
put("Alg.Alias.Mac.HMAC-SHA384", "HmacSHA384");
put("Alg.Alias.Mac.HMAC/SHA384", "HmacSHA384");
putMacImpl("HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA512");
put("Alg.Alias.Mac.1.2.840.113549.2.11", "HmacSHA512");
put("Alg.Alias.Mac.HMAC-SHA512", "HmacSHA512");
put("Alg.Alias.Mac.HMAC/SHA512", "HmacSHA512");
// --------------------- javax.crypto.Cipher
putSymmetricCipherImpl("AES/ECB/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$NoPadding");
putSymmetricCipherImpl("AES/ECB/PKCS7Padding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$PKCS7Padding");
putSymmetricCipherImpl("AES/CBC/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$NoPadding");
putSymmetricCipherImpl("AES/CBC/PKCS7Padding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$PKCS7Padding");
putSymmetricCipherImpl("AES/CTR/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding");
if ("true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY))) {
putSymmetricCipherImpl("DESede/CBC/NoPadding",
PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$CBC$NoPadding");
putSymmetricCipherImpl("DESede/CBC/PKCS7Padding",
PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$CBC$PKCS7Padding");
putSymmetricCipherImpl("DESede/ECB/NoPadding",
PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$ECB$NoPadding");
putSymmetricCipherImpl("DESede/ECB/PKCS7Padding",
PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$ECB$PKCS7Padding");
}
putSymmetricCipherImpl("AES/GCM/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreAuthenticatedAESCipherSpi$GCM$NoPadding");
putAsymmetricCipherImpl("RSA/ECB/NoPadding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$NoPadding");
put("Alg.Alias.Cipher.RSA/None/NoPadding", "RSA/ECB/NoPadding");
putAsymmetricCipherImpl("RSA/ECB/PKCS1Padding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$PKCS1Padding");
put("Alg.Alias.Cipher.RSA/None/PKCS1Padding", "RSA/ECB/PKCS1Padding");
putAsymmetricCipherImpl("RSA/ECB/OAEPPadding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA1AndMGF1Padding");
put("Alg.Alias.Cipher.RSA/None/OAEPPadding", "RSA/ECB/OAEPPadding");
putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-1AndMGF1Padding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA1AndMGF1Padding");
put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-1AndMGF1Padding",
"RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-224AndMGF1Padding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA224AndMGF1Padding");
put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-224AndMGF1Padding",
"RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-256AndMGF1Padding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA256AndMGF1Padding");
put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-256AndMGF1Padding",
"RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-384AndMGF1Padding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA384AndMGF1Padding");
put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-384AndMGF1Padding",
"RSA/ECB/OAEPWithSHA-384AndMGF1Padding");
putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-512AndMGF1Padding",
PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA512AndMGF1Padding");
put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-512AndMGF1Padding",
"RSA/ECB/OAEPWithSHA-512AndMGF1Padding");
// --------------------- java.security.Signature
putSignatureImpl("NONEwithRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$NONEWithPKCS1Padding");
putSignatureImpl("MD5withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$MD5WithPKCS1Padding");
put("Alg.Alias.Signature.MD5WithRSAEncryption", "MD5withRSA");
put("Alg.Alias.Signature.MD5/RSA", "MD5withRSA");
put("Alg.Alias.Signature.1.2.840.113549.1.1.4", "MD5withRSA");
put("Alg.Alias.Signature.1.2.840.113549.2.5with1.2.840.113549.1.1.1", "MD5withRSA");
putSignatureImpl("SHA1withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA1WithPKCS1Padding");
put("Alg.Alias.Signature.SHA1WithRSAEncryption", "SHA1withRSA");
put("Alg.Alias.Signature.SHA1/RSA", "SHA1withRSA");
put("Alg.Alias.Signature.SHA-1/RSA", "SHA1withRSA");
put("Alg.Alias.Signature.1.2.840.113549.1.1.5", "SHA1withRSA");
put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.1", "SHA1withRSA");
put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.5", "SHA1withRSA");
put("Alg.Alias.Signature.1.3.14.3.2.29", "SHA1withRSA");
putSignatureImpl("SHA224withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA224WithPKCS1Padding");
put("Alg.Alias.Signature.SHA224WithRSAEncryption", "SHA224withRSA");
put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA224withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.113549.1.1.1",
"SHA224withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.113549.1.1.11",
"SHA224withRSA");
putSignatureImpl("SHA256withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA256WithPKCS1Padding");
put("Alg.Alias.Signature.SHA256WithRSAEncryption", "SHA256withRSA");
put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA256withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.1",
"SHA256withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.11",
"SHA256withRSA");
putSignatureImpl("SHA384withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA384WithPKCS1Padding");
put("Alg.Alias.Signature.SHA384WithRSAEncryption", "SHA384withRSA");
put("Alg.Alias.Signature.1.2.840.113549.1.1.12", "SHA384withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.2with1.2.840.113549.1.1.1",
"SHA384withRSA");
putSignatureImpl("SHA512withRSA",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA512WithPKCS1Padding");
put("Alg.Alias.Signature.SHA512WithRSAEncryption", "SHA512withRSA");
put("Alg.Alias.Signature.1.2.840.113549.1.1.13", "SHA512withRSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.3with1.2.840.113549.1.1.1",
"SHA512withRSA");
putSignatureImpl("SHA1withRSA/PSS",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA1WithPSSPadding");
putSignatureImpl("SHA224withRSA/PSS",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA224WithPSSPadding");
putSignatureImpl("SHA256withRSA/PSS",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA256WithPSSPadding");
putSignatureImpl("SHA384withRSA/PSS",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA384WithPSSPadding");
putSignatureImpl("SHA512withRSA/PSS",
PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA512WithPSSPadding");
putSignatureImpl("NONEwithECDSA",
PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$NONE");
putSignatureImpl("SHA1withECDSA", PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA1");
put("Alg.Alias.Signature.ECDSA", "SHA1withECDSA");
put("Alg.Alias.Signature.ECDSAwithSHA1", "SHA1withECDSA");
// iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA1(1)
put("Alg.Alias.Signature.1.2.840.10045.4.1", "SHA1withECDSA");
put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.10045.2.1", "SHA1withECDSA");
// iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3)
putSignatureImpl("SHA224withECDSA",
PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA224");
// ecdsa-with-SHA224(1)
put("Alg.Alias.Signature.1.2.840.10045.4.3.1", "SHA224withECDSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.10045.2.1", "SHA224withECDSA");
// iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3)
putSignatureImpl("SHA256withECDSA",
PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA256");
// ecdsa-with-SHA256(2)
put("Alg.Alias.Signature.1.2.840.10045.4.3.2", "SHA256withECDSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.10045.2.1", "SHA256withECDSA");
putSignatureImpl("SHA384withECDSA",
PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA384");
// ecdsa-with-SHA384(3)
put("Alg.Alias.Signature.1.2.840.10045.4.3.3", "SHA384withECDSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.2with1.2.840.10045.2.1", "SHA384withECDSA");
putSignatureImpl("SHA512withECDSA",
PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA512");
// ecdsa-with-SHA512(4)
put("Alg.Alias.Signature.1.2.840.10045.4.3.4", "SHA512withECDSA");
put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.3with1.2.840.10045.2.1", "SHA512withECDSA");
}
private void putMacImpl(String algorithm, String implClass) {
put("Mac." + algorithm, implClass);
put("Mac." + algorithm + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
}
private void putSymmetricCipherImpl(String transformation, String implClass) {
put("Cipher." + transformation, implClass);
put("Cipher." + transformation + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
}
private void putAsymmetricCipherImpl(String transformation, String implClass) {
put("Cipher." + transformation, implClass);
put("Cipher." + transformation + " SupportedKeyClasses",
KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME);
}
private void putSignatureImpl(String algorithm, String implClass) {
put("Signature." + algorithm, implClass);
put("Signature." + algorithm + " SupportedKeyClasses",
KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME);
}
public static String[] getSupportedEcdsaSignatureDigests() {
return new String[] {"NONE", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"};
}
public static String[] getSupportedRsaSignatureWithPkcs1PaddingDigests() {
return new String[] {"NONE", "MD5", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"};
}
}

View File

@@ -0,0 +1,905 @@
/*
* 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.keystore2;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyStoreCryptoOperation;
import android.system.keystore2.KeyParameter;
import libcore.util.EmptyArray;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
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.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
/**
* Base class for {@link CipherSpi} implementations of Android KeyStore backed ciphers.
*
* @hide
*/
abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation {
private static final String TAG = "AndroidKeyStoreCipherSpiBase";
// Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
// doFinal finishes.
private boolean mEncrypting;
private int mKeymasterPurposeOverride = -1;
private AndroidKeyStoreKey mKey;
private SecureRandom mRng;
/**
* Object representing 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 KeyStoreOperation mOperation;
/**
* The operation challenge is required when an operation needs user authorization.
* The challenge is subjected to an authenticator, e.g., Gatekeeper or a biometric
* authenticator, and included in the authentication token minted by this authenticator.
* It may be null, if the operation does not require authorization.
*/
private long mOperationChallenge;
private KeyStoreCryptoOperationStreamer mMainDataStreamer;
private KeyStoreCryptoOperationStreamer mAdditionalAuthenticationDataStreamer;
private boolean mAdditionalAuthenticationDataStreamerClosed;
/**
* 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() {
mOperation = null;
mEncrypting = false;
mKeymasterPurposeOverride = -1;
mKey = null;
mRng = null;
mOperationChallenge = 0;
mMainDataStreamer = null;
mAdditionalAuthenticationDataStreamer = null;
mAdditionalAuthenticationDataStreamerClosed = false;
mCachedException = null;
}
@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 {
switch (opmode) {
case Cipher.ENCRYPT_MODE:
case Cipher.WRAP_MODE:
mEncrypting = true;
break;
case Cipher.DECRYPT_MODE:
case Cipher.UNWRAP_MODE:
mEncrypting = false;
break;
default:
throw new InvalidParameterException("Unsupported opmode: " + opmode);
}
initKey(opmode, key);
if (mKey == null) {
throw new ProviderException("initKey did not initialize the key");
}
mRng = random;
}
private void abortOperation() {
KeyStoreCryptoOperationUtils.abortOperation(mOperation);
mOperation = null;
}
/**
* 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() {
abortOperation();
mEncrypting = false;
mKeymasterPurposeOverride = -1;
mKey = null;
mRng = null;
mOperationChallenge = 0;
mMainDataStreamer = null;
mAdditionalAuthenticationDataStreamer = null;
mAdditionalAuthenticationDataStreamerClosed = false;
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() {
abortOperation();
mOperationChallenge = 0;
mMainDataStreamer = null;
mAdditionalAuthenticationDataStreamer = null;
mAdditionalAuthenticationDataStreamerClosed = false;
mCachedException = null;
}
private void ensureKeystoreOperationInitialized() throws InvalidKeyException,
InvalidAlgorithmParameterException {
if (mMainDataStreamer != null) {
return;
}
if (mCachedException != null) {
return;
}
if (mKey == null) {
throw new IllegalStateException("Not initialized");
}
List<KeyParameter> parameters = new ArrayList<>();
addAlgorithmSpecificParametersToBegin(parameters);
int purpose;
if (mKeymasterPurposeOverride != -1) {
purpose = mKeymasterPurposeOverride;
} else {
purpose = mEncrypting
? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT;
}
parameters.add(KeyStore2ParameterUtils.makeEnum(KeymasterDefs.KM_TAG_PURPOSE, purpose));
try {
mOperation = mKey.getSecurityLevel().createOperation(
mKey.getKeyIdDescriptor(),
parameters
);
} catch (KeyStoreException keyStoreException) {
GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit(
mKey, keyStoreException);
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);
}
}
}
// Now we check if we got an operation challenge. This indicates that user authorization
// is required. And if we got a challenge we check if the authorization can possibly
// succeed.
mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(
mOperation, mKey);
loadAlgorithmSpecificParametersFromBeginResult(mOperation.getParameters());
mMainDataStreamer = createMainDataStreamer(mOperation);
mAdditionalAuthenticationDataStreamer =
createAdditionalAuthenticationDataStreamer(mOperation);
mAdditionalAuthenticationDataStreamerClosed = false;
}
/**
* Creates a streamer which sends plaintext/ciphertext into the provided KeyStore and receives
* the corresponding ciphertext/plaintext from the KeyStore.
*
* <p>This implementation returns a working streamer.
*/
@NonNull
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
KeyStoreOperation operation) {
return new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
operation), 0);
}
/**
* Creates a streamer which sends Additional Authentication Data (AAD) into the KeyStore.
*
* <p>This implementation returns {@code null}.
*
* @return stream or {@code null} if AAD is not supported by this cipher.
*/
@Nullable
protected KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer(
@SuppressWarnings("unused") KeyStoreOperation operation) {
return null;
}
@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 {
flushAAD();
output = mMainDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
return null;
}
if (output.length == 0) {
return null;
}
return output;
}
private void flushAAD() throws KeyStoreException {
if ((mAdditionalAuthenticationDataStreamer != null)
&& (!mAdditionalAuthenticationDataStreamerClosed)) {
byte[] output;
try {
output = mAdditionalAuthenticationDataStreamer.doFinal(
EmptyArray.BYTE, 0, 0,
null); // no signature
} finally {
mAdditionalAuthenticationDataStreamerClosed = true;
}
if ((output != null) && (output.length > 0)) {
throw new ProviderException(
"AAD update unexpectedly returned data: " + output.length + " bytes");
}
}
}
@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 {
if (input == null) {
throw new NullPointerException("input == null");
}
if (output == null) {
throw new NullPointerException("output == null");
}
int inputSize = input.remaining();
byte[] outputArray;
if (input.hasArray()) {
outputArray =
engineUpdate(
input.array(), input.arrayOffset() + input.position(), inputSize);
input.position(input.position() + inputSize);
} else {
byte[] inputArray = new byte[inputSize];
input.get(inputArray);
outputArray = engineUpdate(inputArray, 0, inputSize);
}
int outputSize = (outputArray != null) ? outputArray.length : 0;
if (outputSize > 0) {
int outputBufferAvailable = output.remaining();
try {
output.put(outputArray);
} catch (BufferOverflowException e) {
throw new ShortBufferException(
"Output buffer too small. Produced: " + outputSize + ", available: "
+ outputBufferAvailable);
}
}
return outputSize;
}
@Override
protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
if (mCachedException != null) {
return;
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
mCachedException = e;
return;
}
if (mAdditionalAuthenticationDataStreamerClosed) {
throw new IllegalStateException(
"AAD can only be provided before Cipher.update is invoked");
}
if (mAdditionalAuthenticationDataStreamer == null) {
throw new IllegalStateException("This cipher does not support AAD");
}
byte[] output;
try {
output = mAdditionalAuthenticationDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
return;
}
if ((output != null) && (output.length > 0)) {
throw new ProviderException("AAD update unexpectedly produced output: "
+ output.length + " bytes");
}
}
@Override
protected final void engineUpdateAAD(ByteBuffer src) {
if (src == null) {
throw new IllegalArgumentException("src == null");
}
if (!src.hasRemaining()) {
return;
}
byte[] input;
int inputOffset;
int inputLen;
if (src.hasArray()) {
input = src.array();
inputOffset = src.arrayOffset() + src.position();
inputLen = src.remaining();
src.position(src.limit());
} else {
input = new byte[src.remaining()];
inputOffset = 0;
inputLen = input.length;
src.get(input);
}
engineUpdateAAD(input, inputOffset, inputLen);
}
@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 {
flushAAD();
output = mMainDataStreamer.doFinal(
input, inputOffset, inputLen,
null); // no signature involved
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT:
throw (BadPaddingException) new BadPaddingException().initCause(e);
case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
throw (AEADBadTagException) new AEADBadTagException().initCause(e);
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 {
if (input == null) {
throw new NullPointerException("input == null");
}
if (output == null) {
throw new NullPointerException("output == null");
}
int inputSize = input.remaining();
byte[] outputArray;
if (input.hasArray()) {
outputArray =
engineDoFinal(
input.array(), input.arrayOffset() + input.position(), inputSize);
input.position(input.position() + inputSize);
} else {
byte[] inputArray = new byte[inputSize];
input.get(inputArray);
outputArray = engineDoFinal(inputArray, 0, inputSize);
}
int outputSize = (outputArray != null) ? outputArray.length : 0;
if (outputSize > 0) {
int outputBufferAvailable = output.remaining();
try {
output.put(outputArray);
} catch (BufferOverflowException e) {
throw new ShortBufferException(
"Output buffer too small. Produced: " + outputSize + ", available: "
+ outputBufferAvailable);
}
}
return outputSize;
}
@Override
protected final byte[] engineWrap(Key key)
throws IllegalBlockSizeException, InvalidKeyException {
if (mKey == null) {
throw new IllegalStateException("Not initilized");
}
if (!isEncrypting()) {
throw new IllegalStateException(
"Cipher must be initialized in Cipher.WRAP_MODE to wrap keys");
}
if (key == null) {
throw new NullPointerException("key == null");
}
byte[] encoded = null;
if (key instanceof SecretKey) {
if ("RAW".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
}
if (encoded == null) {
try {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(key.getAlgorithm());
SecretKeySpec spec =
(SecretKeySpec) keyFactory.getKeySpec(
(SecretKey) key, SecretKeySpec.class);
encoded = spec.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material",
e);
}
}
} else if (key instanceof PrivateKey) {
if ("PKCS8".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
}
if (encoded == null) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
PKCS8EncodedKeySpec spec =
keyFactory.getKeySpec(key, PKCS8EncodedKeySpec.class);
encoded = spec.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material",
e);
}
}
} else if (key instanceof PublicKey) {
if ("X.509".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
}
if (encoded == null) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
X509EncodedKeySpec spec =
keyFactory.getKeySpec(key, X509EncodedKeySpec.class);
encoded = spec.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material",
e);
}
}
} else {
throw new InvalidKeyException("Unsupported key type: " + key.getClass().getName());
}
if (encoded == null) {
throw new InvalidKeyException(
"Failed to wrap key because it does not export its key material");
}
try {
return engineDoFinal(encoded, 0, encoded.length);
} catch (BadPaddingException e) {
throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
}
}
@Override
protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException {
if (mKey == null) {
throw new IllegalStateException("Not initilized");
}
if (isEncrypting()) {
throw new IllegalStateException(
"Cipher must be initialized in Cipher.WRAP_MODE to wrap keys");
}
if (wrappedKey == null) {
throw new NullPointerException("wrappedKey == null");
}
byte[] encoded;
try {
encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new InvalidKeyException("Failed to unwrap key", e);
}
switch (wrappedKeyType) {
case Cipher.SECRET_KEY:
{
return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
// break;
}
case Cipher.PRIVATE_KEY:
{
KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
try {
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to create private key from its PKCS#8 encoded form", e);
}
// break;
}
case Cipher.PUBLIC_KEY:
{
KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
try {
return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to create public key from its X.509 encoded form", e);
}
// break;
}
default:
throw new InvalidParameterException(
"Unsupported wrappedKeyType: " + 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 {
abortOperation();
} finally {
super.finalize();
}
}
@Override
public final long getOperationHandle() {
return mOperationChallenge;
}
protected final void setKey(@NonNull AndroidKeyStoreKey key) {
mKey = key;
}
/**
* Overrides the default purpose/type of the crypto operation.
*/
protected final void setKeymasterPurposeOverride(int keymasterPurpose) {
mKeymasterPurposeOverride = keymasterPurpose;
}
protected final int getKeymasterPurposeOverride() {
return mKeymasterPurposeOverride;
}
/**
* 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;
}
protected final long getConsumedInputSizeBytes() {
if (mMainDataStreamer == null) {
throw new IllegalStateException("Not initialized");
}
return mMainDataStreamer.getConsumedInputSizeBytes();
}
protected final long getProducedOutputSizeBytes() {
if (mMainDataStreamer == null) {
throw new IllegalStateException("Not initialized");
}
return mMainDataStreamer.getProducedOutputSizeBytes();
}
static String opmodeToString(int opmode) {
switch (opmode) {
case Cipher.ENCRYPT_MODE:
return "ENCRYPT_MODE";
case Cipher.DECRYPT_MODE:
return "DECRYPT_MODE";
case Cipher.WRAP_MODE:
return "WRAP_MODE";
case Cipher.UNWRAP_MODE:
return "UNWRAP_MODE";
default:
return String.valueOf(opmode);
}
}
// 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. This amount of entropy is typically what's consumed to generate
* random parameters, such as IV.
*
* <p>For decryption, the return value should be {@code 0} because decryption should not be
* consuming any entropy. For encryption, the value combined with
* {@link #getAdditionalEntropyAmountForFinish()} 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 the return value should be {@code 0}, whereas for
* the case where IV is generated by the KeyStore's {@code begin} operation it should be
* {@code 16}.
*/
protected abstract int getAdditionalEntropyAmountForBegin();
/**
* Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
* {@code finish} operation. This amount of entropy is typically what's consumed by encryption
* padding scheme.
*
* <p>For decryption, the return value should be {@code 0} because decryption should not be
* consuming any entropy. For encryption, the value combined with
* {@link #getAdditionalEntropyAmountForBegin()} 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 RSA with
* OAEP the return value should be the size of the OAEP hash output. For RSA with PKCS#1 padding
* the return value should be the size of the padding string or could be raised (for simplicity)
* to the size of the modulus.
*/
protected abstract int getAdditionalEntropyAmountForFinish();
/**
* Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
*
* @param parameters keystore/keymaster arguments to be populated with algorithm-specific
* parameters.
*/
protected abstract void addAlgorithmSpecificParametersToBegin(
@NonNull List<KeyParameter> parameters);
/**
* 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 parameters keystore/keymaster arguments returned by KeyStore {@code createOperation}.
*/
protected abstract void loadAlgorithmSpecificParametersFromBeginResult(
KeyParameter[] parameters);
}

View File

@@ -0,0 +1,207 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyProperties;
import android.system.keystore2.Authorization;
import android.system.keystore2.KeyParameter;
import libcore.util.EmptyArray;
import java.io.ByteArrayOutputStream;
import java.security.InvalidKeyException;
import java.security.SignatureSpi;
import java.util.List;
/**
* Base class for {@link SignatureSpi} providing Android KeyStore backed ECDSA signatures.
*
* @hide
*/
abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignatureSpiBase {
public final static class NONE extends AndroidKeyStoreECDSASignatureSpi {
public NONE() {
super(KeymasterDefs.KM_DIGEST_NONE);
}
@Override
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
KeyStoreOperation operation) {
return new TruncateToFieldSizeMessageStreamer(
super.createMainDataStreamer(operation),
getGroupSizeBits());
}
/**
* Streamer which buffers all input, then truncates it to field size, and then sends it into
* KeyStore via the provided delegate streamer.
*/
private static class TruncateToFieldSizeMessageStreamer
implements KeyStoreCryptoOperationStreamer {
private final KeyStoreCryptoOperationStreamer mDelegate;
private final int mGroupSizeBits;
private final ByteArrayOutputStream mInputBuffer = new ByteArrayOutputStream();
private long mConsumedInputSizeBytes;
private TruncateToFieldSizeMessageStreamer(
KeyStoreCryptoOperationStreamer delegate,
int groupSizeBits) {
mDelegate = delegate;
mGroupSizeBits = groupSizeBits;
}
@Override
public byte[] update(byte[] input, int inputOffset, int inputLength)
throws KeyStoreException {
if (inputLength > 0) {
mInputBuffer.write(input, inputOffset, inputLength);
mConsumedInputSizeBytes += inputLength;
}
return EmptyArray.BYTE;
}
@Override
public byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature)
throws KeyStoreException {
if (inputLength > 0) {
mConsumedInputSizeBytes += inputLength;
mInputBuffer.write(input, inputOffset, inputLength);
}
byte[] bufferedInput = mInputBuffer.toByteArray();
mInputBuffer.reset();
// Truncate input at field size (bytes)
return mDelegate.doFinal(bufferedInput,
0,
Math.min(bufferedInput.length, ((mGroupSizeBits + 7) / 8)),
signature);
}
@Override
public long getConsumedInputSizeBytes() {
return mConsumedInputSizeBytes;
}
@Override
public long getProducedOutputSizeBytes() {
return mDelegate.getProducedOutputSizeBytes();
}
}
}
public final static class SHA1 extends AndroidKeyStoreECDSASignatureSpi {
public SHA1() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
public final static class SHA224 extends AndroidKeyStoreECDSASignatureSpi {
public SHA224() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
public final static class SHA256 extends AndroidKeyStoreECDSASignatureSpi {
public SHA256() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
public final static class SHA384 extends AndroidKeyStoreECDSASignatureSpi {
public SHA384() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
public final static class SHA512 extends AndroidKeyStoreECDSASignatureSpi {
public SHA512() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
}
private final int mKeymasterDigest;
private int mGroupSizeBits = -1;
AndroidKeyStoreECDSASignatureSpi(int keymasterDigest) {
mKeymasterDigest = keymasterDigest;
}
@Override
protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
if (!KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
+ ". Only" + KeyProperties.KEY_ALGORITHM_EC + " supported");
}
long keySizeBits = -1;
for (Authorization a : key.getAuthorizations()) {
if (a.keyParameter.tag == KeymasterDefs.KM_TAG_KEY_SIZE) {
keySizeBits = KeyStore2ParameterUtils.getUnsignedInt(a);
}
}
if (keySizeBits == -1) {
throw new InvalidKeyException("Size of key not known");
} else if (keySizeBits > Integer.MAX_VALUE) {
throw new InvalidKeyException("Key too large: " + keySizeBits + " bits");
}
mGroupSizeBits = (int) keySizeBits;
super.initKey(key);
}
@Override
protected final void resetAll() {
mGroupSizeBits = -1;
super.resetAll();
}
@Override
protected final void resetWhilePreservingInitState() {
super.resetWhilePreservingInitState();
}
@Override
protected final void addAlgorithmSpecificParametersToBegin(
@NonNull List<KeyParameter> parameters) {
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_EC
));
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
));
}
@Override
protected final int getAdditionalEntropyAmountForSign() {
return (mGroupSizeBits + 7) / 8;
}
protected final int getGroupSizeBits() {
if (mGroupSizeBits == -1) {
throw new IllegalStateException("Not initialized");
}
return mGroupSizeBits;
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.security.KeyStoreSecurityLevel;
import android.security.keystore.KeyProperties;
import android.system.keystore2.Authorization;
import android.system.keystore2.KeyDescriptor;
import java.security.PrivateKey;
import java.security.interfaces.ECKey;
import java.security.spec.ECParameterSpec;
/**
* EC private key (instance of {@link PrivateKey} and {@link ECKey}) backed by keystore.
*
* @hide
*/
public class AndroidKeyStoreECPrivateKey extends AndroidKeyStorePrivateKey implements ECKey {
private final ECParameterSpec mParams;
public AndroidKeyStoreECPrivateKey(@NonNull KeyDescriptor descriptor,
long keyId,
Authorization[] authorizations,
@NonNull KeyStoreSecurityLevel securityLevel,
@NonNull ECParameterSpec params) {
super(descriptor, keyId, authorizations, KeyProperties.KEY_ALGORITHM_EC, securityLevel);
mParams = params;
}
@Override
public ECParameterSpec getParams() {
return mParams;
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.security.KeyStoreSecurityLevel;
import android.security.keystore.KeyProperties;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyMetadata;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
/**
* {@link ECPublicKey} backed by keystore.
*
* @hide
*/
public class AndroidKeyStoreECPublicKey extends AndroidKeyStorePublicKey implements ECPublicKey {
private final ECParameterSpec mParams;
private final ECPoint mW;
public AndroidKeyStoreECPublicKey(@NonNull KeyDescriptor descriptor,
@NonNull KeyMetadata metadata,
@NonNull KeyStoreSecurityLevel securityLevel,
@NonNull ECParameterSpec params, @NonNull ECPoint w) {
super(descriptor, metadata, KeyProperties.KEY_ALGORITHM_EC, securityLevel);
mParams = params;
mW = w;
}
public AndroidKeyStoreECPublicKey(@NonNull KeyDescriptor descriptor,
@NonNull KeyMetadata metadata,
@NonNull KeyStoreSecurityLevel securityLevel, @NonNull ECPublicKey info) {
this(descriptor, metadata, securityLevel, info.getParams(), info.getW());
if (!"X.509".equalsIgnoreCase(info.getFormat())) {
throw new IllegalArgumentException(
"Unsupported key export format: " + info.getFormat());
}
}
@Override
public AndroidKeyStorePrivateKey getPrivateKey() {
return new AndroidKeyStoreECPrivateKey(
getUserKeyDescriptor(), getKeyIdDescriptor().nspace, getAuthorizations(),
getSecurityLevel(), mParams);
}
@Override
public ECParameterSpec getParams() {
return mParams;
}
@Override
public ECPoint getW() {
return mW;
}
}

View File

@@ -0,0 +1,259 @@
/*
* 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.keystore2;
import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyStoreCryptoOperation;
import android.security.keystore.KeymasterUtils;
import android.system.keystore2.KeyParameter;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.ProviderException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.MacSpi;
/**
* {@link MacSpi} which provides HMAC implementations backed by Android KeyStore.
*
* @hide
*/
public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation {
private static final String TAG = "AndroidKeyStoreHmacSpi";
public static class HmacSHA1 extends AndroidKeyStoreHmacSpi {
public HmacSHA1() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
public static class HmacSHA224 extends AndroidKeyStoreHmacSpi {
public HmacSHA224() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
public static class HmacSHA256 extends AndroidKeyStoreHmacSpi {
public HmacSHA256() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
public static class HmacSHA384 extends AndroidKeyStoreHmacSpi {
public HmacSHA384() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
public static class HmacSHA512 extends AndroidKeyStoreHmacSpi {
public HmacSHA512() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
}
private final int mKeymasterDigest;
private final int mMacSizeBits;
// Fields below are populated by engineInit and should be preserved after engineDoFinal.
private AndroidKeyStoreSecretKey mKey;
// Fields below are reset when engineDoFinal succeeds.
private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer;
private KeyStoreOperation mOperation;
private long mOperationChallenge;
protected AndroidKeyStoreHmacSpi(int keymasterDigest) {
mKeymasterDigest = keymasterDigest;
mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
mOperation = null;
mOperationChallenge = 0;
mKey = null;
mChunkedStreamer = null;
}
@Override
protected int engineGetMacLength() {
return (mMacSizeBits + 7) / 8;
}
@Override
protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
init(key, params);
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
private void init(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
InvalidAlgorithmParameterException {
if (key == null) {
throw new InvalidKeyException("key == null");
} else if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeyException(
"Only Android KeyStore secret keys supported. Key: " + key);
}
mKey = (AndroidKeyStoreSecretKey) key;
if (params != null) {
throw new InvalidAlgorithmParameterException(
"Unsupported algorithm parameters: " + params);
}
}
private void abortOperation() {
KeyStoreCryptoOperationUtils.abortOperation(mOperation);
mOperation = null;
}
private void resetAll() {
abortOperation();
mOperationChallenge = 0;
mKey = null;
mChunkedStreamer = null;
}
private void resetWhilePreservingInitState() {
abortOperation();
mOperationChallenge = 0;
mChunkedStreamer = null;
}
@Override
protected void engineReset() {
resetWhilePreservingInitState();
}
private void ensureKeystoreOperationInitialized() throws InvalidKeyException {
if (mChunkedStreamer != null) {
return;
}
if (mKey == null) {
throw new IllegalStateException("Not initialized");
}
List<KeyParameter> parameters = new ArrayList<>();
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC
));
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
));
parameters.add(KeyStore2ParameterUtils.makeInt(
KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits
));
try {
mOperation = mKey.getSecurityLevel().createOperation(
mKey.getKeyIdDescriptor(),
parameters
);
} catch (KeyStoreException keyStoreException) {
// If necessary, throw an exception due to KeyStore operation having failed.
InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyException(
mKey, keyStoreException);
if (e != null) {
throw e;
}
}
// Now we check if we got an operation challenge. This indicates that user authorization
// is required. And if we got a challenge we check if the authorization can possibly
// succeed.
mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(
mOperation, mKey);
mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
mOperation));
}
@Override
protected void engineUpdate(byte input) {
engineUpdate(new byte[] {input}, 0, 1);
}
@Override
protected void engineUpdate(byte[] input, int offset, int len) {
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException e) {
throw new ProviderException("Failed to reinitialize MAC", e);
}
byte[] output;
try {
output = mChunkedStreamer.update(input, offset, len);
} catch (KeyStoreException e) {
throw new ProviderException("Keystore operation failed", e);
}
if ((output != null) && (output.length != 0)) {
throw new ProviderException("Update operation unexpectedly produced output");
}
}
@Override
protected byte[] engineDoFinal() {
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException e) {
throw new ProviderException("Failed to reinitialize MAC", e);
}
byte[] result;
try {
result = mChunkedStreamer.doFinal(
null, 0, 0,
null); // no signature provided -- this invocation will generate one
} catch (KeyStoreException e) {
throw new ProviderException("Keystore operation failed", e);
}
resetWhilePreservingInitState();
return result;
}
@Override
public void finalize() throws Throwable {
try {
abortOperation();
} finally {
super.finalize();
}
}
@Override
public long getOperationHandle() {
return mOperationChallenge;
}
}

View File

@@ -0,0 +1,141 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.security.KeyStoreSecurityLevel;
import android.system.keystore2.Authorization;
import android.system.keystore2.Domain;
import android.system.keystore2.KeyDescriptor;
import android.util.Log;
import java.security.Key;
/**
* {@link Key} backed by Android Keystore.
*
* @hide
*/
public class AndroidKeyStoreKey implements Key {
// This is the original KeyDescriptor by which the key was loaded from
// with alias and domain.
private final KeyDescriptor mDescriptor;
// The key id can be used make certain manipulations to the keystore database
// assuring that the manipulation is made to the exact key that was loaded
// from the database. Alias based manipulations can not assure this, because
// aliases can be rebound to other keys at any time.
private final long mKeyId;
private final Authorization[] mAuthorizations;
// TODO extract algorithm string from metadata.
private final String mAlgorithm;
// This is the security level interface, that this key is associated with.
// We do not include this member in comparisons.
private final KeyStoreSecurityLevel mSecurityLevel;
AndroidKeyStoreKey(@NonNull KeyDescriptor descriptor,
long keyId,
@NonNull Authorization[] authorizations,
@NonNull String algorithm,
@NonNull KeyStoreSecurityLevel securityLevel) {
mDescriptor = descriptor;
mKeyId = keyId;
mAuthorizations = authorizations;
mAlgorithm = algorithm;
mSecurityLevel = securityLevel;
}
KeyDescriptor getUserKeyDescriptor() {
return mDescriptor;
}
KeyDescriptor getKeyIdDescriptor() {
KeyDescriptor descriptor = new KeyDescriptor();
descriptor.nspace = mKeyId;
descriptor.domain = Domain.KEY_ID;
descriptor.alias = null;
descriptor.blob = null;
return descriptor;
}
Authorization[] getAuthorizations() {
return mAuthorizations;
}
KeyStoreSecurityLevel getSecurityLevel() {
return mSecurityLevel;
}
@Override
public String getAlgorithm() {
return mAlgorithm;
}
@Override
public String getFormat() {
// This key does not export its key material
return null;
}
@Override
public byte[] getEncoded() {
// This key does not export its key material
return null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((mDescriptor == null) ? 0 : mDescriptor.hashCode());
result = prime * result + (int) (mKeyId >>> 32);
result = prime * result + (int) (mKeyId & 0xffffffff);
result = prime * result + ((mAuthorizations == null) ? 0 : mAuthorizations.hashCode());
result = prime * result + ((mAlgorithm == null) ? 0 : mAlgorithm.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AndroidKeyStoreKey other = (AndroidKeyStoreKey) obj;
if (mKeyId != other.mKeyId) {
return false;
}
// If the key ids are equal and the class matches all the other fields cannot differ
// unless we have a bug.
if (!mAlgorithm.equals(other.mAlgorithm)
|| !mAuthorizations.equals(other.mAuthorizations)
|| !mDescriptor.equals(other.mDescriptor)) {
Log.e("AndroidKeyStoreKey", "Bug: key ids are identical, but key metadata"
+ "differs.");
return false;
}
return true;
}
}

View File

@@ -0,0 +1,144 @@
/*
* 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.keystore2;
import android.security.KeyStore;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyInfo;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactorySpi;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* {@link KeyFactorySpi} backed by Android KeyStore.
*
* @hide
*/
public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi {
private final KeyStore mKeyStore = KeyStore.getInstance();
@Override
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpecClass)
throws InvalidKeySpecException {
if (key == null) {
throw new InvalidKeySpecException("key == null");
} else if ((!(key instanceof AndroidKeyStorePrivateKey))
&& (!(key instanceof AndroidKeyStorePublicKey))) {
throw new InvalidKeySpecException(
"Unsupported key type: " + key.getClass().getName()
+ ". This KeyFactory supports only Android Keystore asymmetric keys");
}
// key is an Android Keystore private or public key
if (keySpecClass == null) {
throw new InvalidKeySpecException("keySpecClass == null");
} else if (KeyInfo.class.equals(keySpecClass)) {
if (!(key instanceof AndroidKeyStorePrivateKey)) {
throw new InvalidKeySpecException(
"Unsupported key type: " + key.getClass().getName()
+ ". KeyInfo can be obtained only for Android Keystore private keys");
}
AndroidKeyStorePrivateKey keystorePrivateKey = (AndroidKeyStorePrivateKey) key;
@SuppressWarnings("unchecked")
T result = (T) AndroidKeyStoreSecretKeyFactorySpi.getKeyInfo(keystorePrivateKey);
return result;
} else if (X509EncodedKeySpec.class.equals(keySpecClass)) {
if (!(key instanceof AndroidKeyStorePublicKey)) {
throw new InvalidKeySpecException(
"Unsupported key type: " + key.getClass().getName()
+ ". X509EncodedKeySpec can be obtained only for Android Keystore public"
+ " keys");
}
@SuppressWarnings("unchecked")
T result = (T) new X509EncodedKeySpec(((AndroidKeyStorePublicKey) key).getEncoded());
return result;
} else if (PKCS8EncodedKeySpec.class.equals(keySpecClass)) {
if (key instanceof AndroidKeyStorePrivateKey) {
throw new InvalidKeySpecException(
"Key material export of Android Keystore private keys is not supported");
} else {
throw new InvalidKeySpecException(
"Cannot export key material of public key in PKCS#8 format."
+ " Only X.509 format (X509EncodedKeySpec) supported for public keys.");
}
} else if (RSAPublicKeySpec.class.equals(keySpecClass)) {
if (key instanceof AndroidKeyStoreRSAPublicKey) {
AndroidKeyStoreRSAPublicKey rsaKey = (AndroidKeyStoreRSAPublicKey) key;
@SuppressWarnings("unchecked")
T result =
(T) new RSAPublicKeySpec(rsaKey.getModulus(), rsaKey.getPublicExponent());
return result;
} else {
throw new InvalidKeySpecException(
"Obtaining RSAPublicKeySpec not supported for " + key.getAlgorithm() + " "
+ ((key instanceof AndroidKeyStorePrivateKey) ? "private" : "public")
+ " key");
}
} else if (ECPublicKeySpec.class.equals(keySpecClass)) {
if (key instanceof AndroidKeyStoreECPublicKey) {
AndroidKeyStoreECPublicKey ecKey = (AndroidKeyStoreECPublicKey) key;
@SuppressWarnings("unchecked")
T result = (T) new ECPublicKeySpec(ecKey.getW(), ecKey.getParams());
return result;
} else {
throw new InvalidKeySpecException(
"Obtaining ECPublicKeySpec not supported for " + key.getAlgorithm() + " "
+ ((key instanceof AndroidKeyStorePrivateKey) ? "private" : "public")
+ " key");
}
} else {
throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
}
}
@Override
protected PrivateKey engineGeneratePrivate(KeySpec spec) throws InvalidKeySpecException {
throw new InvalidKeySpecException(
"To generate a key pair in Android Keystore, use KeyPairGenerator initialized with"
+ " " + KeyGenParameterSpec.class.getName());
}
@Override
protected PublicKey engineGeneratePublic(KeySpec spec) throws InvalidKeySpecException {
throw new InvalidKeySpecException(
"To generate a key pair in Android Keystore, use KeyPairGenerator initialized with"
+ " " + KeyGenParameterSpec.class.getName());
}
@Override
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
if (key == null) {
throw new InvalidKeyException("key == null");
} else if ((!(key instanceof AndroidKeyStorePrivateKey))
&& (!(key instanceof AndroidKeyStorePublicKey))) {
throw new InvalidKeyException(
"To import a key into Android Keystore, use KeyStore.setEntry");
}
return key;
}
}

View File

@@ -0,0 +1,428 @@
/*
* 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.keystore2;
import android.security.KeyStore2;
import android.security.KeyStoreSecurityLevel;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeymasterUtils;
import android.security.keystore.StrongBoxUnavailableException;
import android.system.keystore2.Domain;
import android.system.keystore2.IKeystoreSecurityLevel;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyMetadata;
import android.system.keystore2.KeyParameter;
import android.system.keystore2.SecurityLevel;
import android.util.Log;
import libcore.util.EmptyArray;
import java.security.InvalidAlgorithmParameterException;
import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.crypto.KeyGeneratorSpi;
import javax.crypto.SecretKey;
/**
* {@link KeyGeneratorSpi} backed by Android KeyStore.
*
* @hide
*/
public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
private static final String TAG = "AndroidKeyStoreKeyGeneratorSpi";
public static class AES extends AndroidKeyStoreKeyGeneratorSpi {
public AES() {
super(KeymasterDefs.KM_ALGORITHM_AES, 128);
}
@Override
protected void engineInit(AlgorithmParameterSpec params, SecureRandom random)
throws InvalidAlgorithmParameterException {
super.engineInit(params, random);
if ((mKeySizeBits != 128) && (mKeySizeBits != 192) && (mKeySizeBits != 256)) {
throw new InvalidAlgorithmParameterException(
"Unsupported key size: " + mKeySizeBits
+ ". Supported: 128, 192, 256.");
}
}
}
public static class DESede extends AndroidKeyStoreKeyGeneratorSpi {
public DESede() {
super(KeymasterDefs.KM_ALGORITHM_3DES, 168);
}
}
protected static abstract class HmacBase extends AndroidKeyStoreKeyGeneratorSpi {
protected HmacBase(int keymasterDigest) {
super(KeymasterDefs.KM_ALGORITHM_HMAC,
keymasterDigest,
KeymasterUtils.getDigestOutputSizeBits(keymasterDigest));
}
}
public static class HmacSHA1 extends HmacBase {
public HmacSHA1() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
public static class HmacSHA224 extends HmacBase {
public HmacSHA224() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
public static class HmacSHA256 extends HmacBase {
public HmacSHA256() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
public static class HmacSHA384 extends HmacBase {
public HmacSHA384() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
public static class HmacSHA512 extends HmacBase {
public HmacSHA512() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
}
private final KeyStore2 mKeyStore = KeyStore2.getInstance();
private final int mKeymasterAlgorithm;
private final int mKeymasterDigest;
private final int mDefaultKeySizeBits;
private KeyGenParameterSpec mSpec;
private SecureRandom mRng;
protected int mKeySizeBits;
private int[] mKeymasterPurposes;
private int[] mKeymasterBlockModes;
private int[] mKeymasterPaddings;
private int[] mKeymasterDigests;
protected AndroidKeyStoreKeyGeneratorSpi(
int keymasterAlgorithm,
int defaultKeySizeBits) {
this(keymasterAlgorithm, -1, defaultKeySizeBits);
}
protected AndroidKeyStoreKeyGeneratorSpi(
int keymasterAlgorithm,
int keymasterDigest,
int defaultKeySizeBits) {
mKeymasterAlgorithm = keymasterAlgorithm;
mKeymasterDigest = keymasterDigest;
mDefaultKeySizeBits = defaultKeySizeBits;
if (mDefaultKeySizeBits <= 0) {
throw new IllegalArgumentException("Default key size must be positive");
}
if ((mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) && (mKeymasterDigest == -1)) {
throw new IllegalArgumentException(
"Digest algorithm must be specified for HMAC key");
}
}
@Override
protected void engineInit(SecureRandom random) {
throw new UnsupportedOperationException("Cannot initialize without a "
+ KeyGenParameterSpec.class.getName() + " parameter");
}
@Override
protected void engineInit(int keySize, SecureRandom random) {
throw new UnsupportedOperationException("Cannot initialize without a "
+ KeyGenParameterSpec.class.getName() + " parameter");
}
@Override
protected void engineInit(AlgorithmParameterSpec params, SecureRandom random)
throws InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
if ((params == null) || (!(params instanceof KeyGenParameterSpec))) {
throw new InvalidAlgorithmParameterException("Cannot initialize without a "
+ KeyGenParameterSpec.class.getName() + " parameter");
}
KeyGenParameterSpec spec = (KeyGenParameterSpec) params;
if (spec.getKeystoreAlias() == null) {
throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided");
}
mRng = random;
mSpec = spec;
mKeySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits;
if (mKeySizeBits <= 0) {
throw new InvalidAlgorithmParameterException(
"Key size must be positive: " + mKeySizeBits);
} else if ((mKeySizeBits % 8) != 0) {
throw new InvalidAlgorithmParameterException(
"Key size must be a multiple of 8: " + mKeySizeBits);
}
try {
mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes());
mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
spec.getEncryptionPaddings());
if (spec.getSignaturePaddings().length > 0) {
throw new InvalidAlgorithmParameterException(
"Signature paddings not supported for symmetric key algorithms");
}
mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes());
if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
&& (spec.isRandomizedEncryptionRequired())) {
for (int keymasterBlockMode : mKeymasterBlockModes) {
if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
keymasterBlockMode)) {
throw new InvalidAlgorithmParameterException(
"Randomized encryption (IND-CPA) required but may be violated"
+ " by block mode: "
+ KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode)
+ ". See " + KeyGenParameterSpec.class.getName()
+ " documentation.");
}
}
}
if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_3DES) {
if (mKeySizeBits != 168) {
throw new InvalidAlgorithmParameterException(
"3DES key size must be 168 bits.");
}
}
if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) {
if (mKeySizeBits < 64 || mKeySizeBits > 512) {
throw new InvalidAlgorithmParameterException(
"HMAC key sizes must be within 64-512 bits, inclusive.");
}
// JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm
// implies SHA-256 digest). Because keymaster HMAC key is authorized only for
// one digest, we don't let algorithm parameter spec override the digest implied
// by the key. If the spec specifies digests at all, it must specify only one
// digest, the only implied by key algorithm.
mKeymasterDigests = new int[] {mKeymasterDigest};
if (spec.isDigestsSpecified()) {
// Digest(s) explicitly specified in the spec. Check that the list
// consists of exactly one digest, the one implied by key algorithm.
int[] keymasterDigestsFromSpec =
KeyProperties.Digest.allToKeymaster(spec.getDigests());
if ((keymasterDigestsFromSpec.length != 1)
|| (keymasterDigestsFromSpec[0] != mKeymasterDigest)) {
throw new InvalidAlgorithmParameterException(
"Unsupported digests specification: "
+ Arrays.asList(spec.getDigests()) + ". Only "
+ KeyProperties.Digest.fromKeymaster(mKeymasterDigest)
+ " supported for this HMAC key algorithm");
}
}
} else {
// Key algorithm does not imply a digest.
if (spec.isDigestsSpecified()) {
mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests());
} else {
mKeymasterDigests = EmptyArray.INT;
}
}
// Check that user authentication related parameters are acceptable. This method
// will throw an IllegalStateException if there are issues (e.g., secure lock screen
// not set up).
KeymasterUtils.addUserAuthArgs(new KeymasterArguments(), spec);
} catch (IllegalStateException | IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(e);
}
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
private void resetAll() {
mSpec = null;
mRng = null;
mKeySizeBits = -1;
mKeymasterPurposes = null;
mKeymasterPaddings = null;
mKeymasterBlockModes = null;
}
@Override
protected SecretKey engineGenerateKey() {
KeyGenParameterSpec spec = mSpec;
if (spec == null) {
throw new IllegalStateException("Not initialized");
}
List<KeyParameter> params = new ArrayList<>();
params.add(KeyStore2ParameterUtils.makeInt(
KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits
));
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm
));
ArrayUtils.forEach(mKeymasterPurposes, (purpose) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PURPOSE, purpose
));
});
ArrayUtils.forEach(mKeymasterBlockModes, (blockMode) -> {
if (blockMode == KeymasterDefs.KM_MODE_GCM
&& mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES) {
params.add(KeyStore2ParameterUtils.makeInt(
KeymasterDefs.KM_TAG_MIN_MAC_LENGTH,
AndroidKeyStoreAuthenticatedAESCipherSpi.GCM
.MIN_SUPPORTED_TAG_LENGTH_BITS
));
}
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_BLOCK_MODE, blockMode
));
});
ArrayUtils.forEach(mKeymasterPaddings, (padding) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PADDING, padding
));
});
ArrayUtils.forEach(mKeymasterDigests, (digest) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_DIGEST, digest
));
});
if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC
&& mKeymasterDigests.length != 0) {
int digestOutputSizeBits = KeymasterUtils.getDigestOutputSizeBits(mKeymasterDigests[0]);
if (digestOutputSizeBits == -1) {
throw new ProviderException(
"HMAC key authorized for unsupported digest: "
+ KeyProperties.Digest.fromKeymaster(mKeymasterDigests[0]));
}
params.add(KeyStore2ParameterUtils.makeInt(
KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, digestOutputSizeBits
));
}
KeyStore2ParameterUtils.addUserAuthArgs(params, spec);
if (spec.getKeyValidityStart() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart()
));
}
if (spec.getKeyValidityForOriginationEnd() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
spec.getKeyValidityForOriginationEnd()
));
}
if (spec.getKeyValidityForConsumptionEnd() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
spec.getKeyValidityForConsumptionEnd()
));
}
if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
&& (!spec.isRandomizedEncryptionRequired())) {
// Permit caller-provided IV when encrypting with this key
params.add(KeyStore2ParameterUtils.makeBool(
KeymasterDefs.KM_TAG_CALLER_NONCE
));
}
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, (mKeySizeBits + 7) / 8);
@SecurityLevel int securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT;
if (spec.isStrongBoxBacked()) {
securityLevel = SecurityLevel.STRONGBOX;
}
int flags = 0;
if (spec.isCriticalToDeviceEncryption()) {
flags |= IKeystoreSecurityLevel.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING;
}
KeyDescriptor descriptor = new KeyDescriptor();
descriptor.alias = spec.getKeystoreAlias();
descriptor.nspace = spec.getNamespace();
descriptor.domain = descriptor.nspace == KeyProperties.NAMESPACE_APPLICATION
? Domain.APP
: Domain.SELINUX;
descriptor.blob = null;
KeyMetadata metadata = null;
KeyStoreSecurityLevel iSecurityLevel = null;
try {
iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel);
metadata = iSecurityLevel.generateKey(
descriptor,
null, /* Attestation key not applicable to symmetric keys. */
params,
flags,
additionalEntropy);
} catch (android.security.KeyStoreException e) {
switch (e.getErrorCode()) {
// TODO replace with ErrorCode.HARDWARE_TYPE_UNAVAILABLE when KeyMint spec
// becomes available.
case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
throw new StrongBoxUnavailableException("Failed to generate key");
default:
throw new ProviderException("Keystore key generation failed", e);
}
}
@KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA;
try {
keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
mKeymasterAlgorithm, mKeymasterDigest);
} catch (IllegalArgumentException e) {
try {
mKeyStore.deleteKey(descriptor);
} catch (android.security.KeyStoreException kse) {
Log.e(TAG, "Failed to delete key after generating successfully but"
+ " failed to get the algorithm string.", kse);
}
throw new ProviderException("Failed to obtain JCA secret key algorithm name", e);
}
SecretKey result = new AndroidKeyStoreSecretKey(descriptor, metadata, keyAlgorithmJCA,
iSecurityLevel);
return result;
}
}

View File

@@ -0,0 +1,804 @@
/*
* Copyright (C) 2012 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.keystore2;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore2;
import android.security.KeyStoreException;
import android.security.KeyStoreSecurityLevel;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeymasterUtils;
import android.security.keystore.SecureKeyImportUnavailableException;
import android.security.keystore.StrongBoxUnavailableException;
import android.system.keystore2.Domain;
import android.system.keystore2.IKeystoreSecurityLevel;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyMetadata;
import android.system.keystore2.KeyParameter;
import android.system.keystore2.ResponseCode;
import android.system.keystore2.SecurityLevel;
import android.util.Log;
import libcore.util.EmptyArray;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyPairGeneratorSpi;
import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
* Provides a way to create instances of a KeyPair which will be placed in the
* Android keystore service usable only by the application that called it. This
* can be used in conjunction with
* {@link java.security.KeyStore#getInstance(String)} using the
* {@code "AndroidKeyStore"} type.
* <p>
* This class can not be directly instantiated and must instead be used via the
* {@link KeyPairGenerator#getInstance(String)
* KeyPairGenerator.getInstance("AndroidKeyStore")} API.
*
* @hide
*/
public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGeneratorSpi {
private static final String TAG = "AndroidKeyStoreKeyPairGeneratorSpi";
public static class RSA extends AndroidKeyStoreKeyPairGeneratorSpi {
public RSA() {
super(KeymasterDefs.KM_ALGORITHM_RSA);
}
}
public static class EC extends AndroidKeyStoreKeyPairGeneratorSpi {
public EC() {
super(KeymasterDefs.KM_ALGORITHM_EC);
}
}
/*
* These must be kept in sync with system/security/keystore/defaults.h
*/
/* EC */
private static final int EC_DEFAULT_KEY_SIZE = 256;
/* RSA */
private static final int RSA_DEFAULT_KEY_SIZE = 2048;
private static final int RSA_MIN_KEY_SIZE = 512;
private static final int RSA_MAX_KEY_SIZE = 8192;
private static final Map<String, Integer> SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE =
new HashMap<String, Integer>();
private static final List<String> SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList<String>();
private static final List<Integer> SUPPORTED_EC_NIST_CURVE_SIZES = new ArrayList<Integer>();
static {
// Aliases for NIST P-224
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-224", 224);
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp224r1", 224);
// Aliases for NIST P-256
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-256", 256);
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp256r1", 256);
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime256v1", 256);
// Aliases for NIST P-384
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-384", 384);
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp384r1", 384);
// Aliases for NIST P-521
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-521", 521);
SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp521r1", 521);
SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet());
Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES);
SUPPORTED_EC_NIST_CURVE_SIZES.addAll(
new HashSet<Integer>(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.values()));
Collections.sort(SUPPORTED_EC_NIST_CURVE_SIZES);
}
private final int mOriginalKeymasterAlgorithm;
private KeyStore2 mKeyStore;
private KeyGenParameterSpec mSpec;
private String mEntryAlias;
private int mEntryUid;
private @KeyProperties.KeyAlgorithmEnum String mJcaKeyAlgorithm;
private int mKeymasterAlgorithm = -1;
private int mKeySizeBits;
private SecureRandom mRng;
private int[] mKeymasterPurposes;
private int[] mKeymasterBlockModes;
private int[] mKeymasterEncryptionPaddings;
private int[] mKeymasterSignaturePaddings;
private int[] mKeymasterDigests;
private Long mRSAPublicExponent;
protected AndroidKeyStoreKeyPairGeneratorSpi(int keymasterAlgorithm) {
mOriginalKeymasterAlgorithm = keymasterAlgorithm;
}
@SuppressWarnings("deprecation")
@Override
public void initialize(int keysize, SecureRandom random) {
throw new IllegalArgumentException(
KeyGenParameterSpec.class.getName() + " or " + KeyPairGeneratorSpec.class.getName()
+ " required to initialize this KeyPairGenerator");
}
@SuppressWarnings("deprecation")
@Override
public void initialize(AlgorithmParameterSpec params, SecureRandom random)
throws InvalidAlgorithmParameterException {
resetAll();
boolean success = false;
try {
if (params == null) {
throw new InvalidAlgorithmParameterException(
"Must supply params of type " + KeyGenParameterSpec.class.getName()
+ " or " + KeyPairGeneratorSpec.class.getName());
}
KeyGenParameterSpec spec;
boolean encryptionAtRestRequired = false;
int keymasterAlgorithm = mOriginalKeymasterAlgorithm;
if (params instanceof KeyGenParameterSpec) {
spec = (KeyGenParameterSpec) params;
} else if (params instanceof KeyPairGeneratorSpec) {
// Legacy/deprecated spec
KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params;
try {
KeyGenParameterSpec.Builder specBuilder;
String specKeyAlgorithm = legacySpec.getKeyType();
if (specKeyAlgorithm != null) {
// Spec overrides the generator's default key algorithm
try {
keymasterAlgorithm =
KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
specKeyAlgorithm);
} catch (IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(
"Invalid key type in parameters", e);
}
}
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_EC:
specBuilder = new KeyGenParameterSpec.Builder(
legacySpec.getKeystoreAlias(),
KeyProperties.PURPOSE_SIGN
| KeyProperties.PURPOSE_VERIFY);
// Authorized to be used with any digest (including no digest).
// MD5 was never offered for Android Keystore for ECDSA.
specBuilder.setDigests(
KeyProperties.DIGEST_NONE,
KeyProperties.DIGEST_SHA1,
KeyProperties.DIGEST_SHA224,
KeyProperties.DIGEST_SHA256,
KeyProperties.DIGEST_SHA384,
KeyProperties.DIGEST_SHA512);
break;
case KeymasterDefs.KM_ALGORITHM_RSA:
specBuilder = new KeyGenParameterSpec.Builder(
legacySpec.getKeystoreAlias(),
KeyProperties.PURPOSE_ENCRYPT
| KeyProperties.PURPOSE_DECRYPT
| KeyProperties.PURPOSE_SIGN
| KeyProperties.PURPOSE_VERIFY);
// Authorized to be used with any digest (including no digest).
specBuilder.setDigests(
KeyProperties.DIGEST_NONE,
KeyProperties.DIGEST_MD5,
KeyProperties.DIGEST_SHA1,
KeyProperties.DIGEST_SHA224,
KeyProperties.DIGEST_SHA256,
KeyProperties.DIGEST_SHA384,
KeyProperties.DIGEST_SHA512);
// Authorized to be used with any encryption and signature padding
// schemes (including no padding).
specBuilder.setEncryptionPaddings(
KeyProperties.ENCRYPTION_PADDING_NONE,
KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
KeyProperties.ENCRYPTION_PADDING_RSA_OAEP);
specBuilder.setSignaturePaddings(
KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
KeyProperties.SIGNATURE_PADDING_RSA_PSS);
// Disable randomized encryption requirement to support encryption
// padding NONE above.
specBuilder.setRandomizedEncryptionRequired(false);
break;
default:
throw new ProviderException(
"Unsupported algorithm: " + mKeymasterAlgorithm);
}
if (legacySpec.getKeySize() != -1) {
specBuilder.setKeySize(legacySpec.getKeySize());
}
if (legacySpec.getAlgorithmParameterSpec() != null) {
specBuilder.setAlgorithmParameterSpec(
legacySpec.getAlgorithmParameterSpec());
}
specBuilder.setCertificateSubject(legacySpec.getSubjectDN());
specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber());
specBuilder.setCertificateNotBefore(legacySpec.getStartDate());
specBuilder.setCertificateNotAfter(legacySpec.getEndDate());
specBuilder.setUserAuthenticationRequired(false);
spec = specBuilder.build();
} catch (NullPointerException | IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(e);
}
} else {
throw new InvalidAlgorithmParameterException(
"Unsupported params class: " + params.getClass().getName()
+ ". Supported: " + KeyGenParameterSpec.class.getName()
+ ", " + KeyPairGeneratorSpec.class.getName());
}
mEntryAlias = spec.getKeystoreAlias();
mEntryUid = spec.getUid();
mSpec = spec;
mKeymasterAlgorithm = keymasterAlgorithm;
mKeySizeBits = spec.getKeySize();
initAlgorithmSpecificParameters();
if (mKeySizeBits == -1) {
mKeySizeBits = getDefaultKeySize(keymasterAlgorithm);
}
checkValidKeySize(keymasterAlgorithm, mKeySizeBits, mSpec.isStrongBoxBacked());
if (spec.getKeystoreAlias() == null) {
throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided");
}
String jcaKeyAlgorithm;
try {
jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm(
keymasterAlgorithm);
mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes());
mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes());
mKeymasterEncryptionPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
spec.getEncryptionPaddings());
if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
&& (spec.isRandomizedEncryptionRequired())) {
for (int keymasterPadding : mKeymasterEncryptionPaddings) {
if (!KeymasterUtils
.isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto(
keymasterPadding)) {
throw new InvalidAlgorithmParameterException(
"Randomized encryption (IND-CPA) required but may be violated"
+ " by padding scheme: "
+ KeyProperties.EncryptionPadding.fromKeymaster(
keymasterPadding)
+ ". See " + KeyGenParameterSpec.class.getName()
+ " documentation.");
}
}
}
mKeymasterSignaturePaddings = KeyProperties.SignaturePadding.allToKeymaster(
spec.getSignaturePaddings());
if (spec.isDigestsSpecified()) {
mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests());
} else {
mKeymasterDigests = EmptyArray.INT;
}
// Check that user authentication related parameters are acceptable. This method
// will throw an IllegalStateException if there are issues (e.g., secure lock screen
// not set up).
KeymasterUtils.addUserAuthArgs(new KeymasterArguments(), mSpec);
} catch (IllegalArgumentException | IllegalStateException e) {
throw new InvalidAlgorithmParameterException(e);
}
mJcaKeyAlgorithm = jcaKeyAlgorithm;
mRng = random;
mKeyStore = KeyStore2.getInstance();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
private void resetAll() {
mEntryAlias = null;
mEntryUid = KeyProperties.NAMESPACE_APPLICATION;
mJcaKeyAlgorithm = null;
mKeymasterAlgorithm = -1;
mKeymasterPurposes = null;
mKeymasterBlockModes = null;
mKeymasterEncryptionPaddings = null;
mKeymasterSignaturePaddings = null;
mKeymasterDigests = null;
mKeySizeBits = 0;
mSpec = null;
mRSAPublicExponent = null;
mRng = null;
mKeyStore = null;
}
private void initAlgorithmSpecificParameters() throws InvalidAlgorithmParameterException {
AlgorithmParameterSpec algSpecificSpec = mSpec.getAlgorithmParameterSpec();
switch (mKeymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_RSA:
{
BigInteger publicExponent = null;
if (algSpecificSpec instanceof RSAKeyGenParameterSpec) {
RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) algSpecificSpec;
if (mKeySizeBits == -1) {
mKeySizeBits = rsaSpec.getKeysize();
} else if (mKeySizeBits != rsaSpec.getKeysize()) {
throw new InvalidAlgorithmParameterException("RSA key size must match "
+ " between " + mSpec + " and " + algSpecificSpec
+ ": " + mKeySizeBits + " vs " + rsaSpec.getKeysize());
}
publicExponent = rsaSpec.getPublicExponent();
} else if (algSpecificSpec != null) {
throw new InvalidAlgorithmParameterException(
"RSA may only use RSAKeyGenParameterSpec");
}
if (publicExponent == null) {
publicExponent = RSAKeyGenParameterSpec.F4;
}
if (publicExponent.compareTo(BigInteger.ZERO) < 1) {
throw new InvalidAlgorithmParameterException(
"RSA public exponent must be positive: " + publicExponent);
}
if ((publicExponent.signum() == -1)
|| (publicExponent.compareTo(KeymasterArguments.UINT64_MAX_VALUE) > 0)) {
throw new InvalidAlgorithmParameterException(
"Unsupported RSA public exponent: " + publicExponent
+ ". Maximum supported value: " + KeymasterArguments.UINT64_MAX_VALUE);
}
mRSAPublicExponent = publicExponent.longValue();
break;
}
case KeymasterDefs.KM_ALGORITHM_EC:
if (algSpecificSpec instanceof ECGenParameterSpec) {
ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec;
String curveName = ecSpec.getName();
Integer ecSpecKeySizeBits = SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.get(
curveName.toLowerCase(Locale.US));
if (ecSpecKeySizeBits == null) {
throw new InvalidAlgorithmParameterException(
"Unsupported EC curve name: " + curveName
+ ". Supported: " + SUPPORTED_EC_NIST_CURVE_NAMES);
}
if (mKeySizeBits == -1) {
mKeySizeBits = ecSpecKeySizeBits;
} else if (mKeySizeBits != ecSpecKeySizeBits) {
throw new InvalidAlgorithmParameterException("EC key size must match "
+ " between " + mSpec + " and " + algSpecificSpec
+ ": " + mKeySizeBits + " vs " + ecSpecKeySizeBits);
}
} else if (algSpecificSpec != null) {
throw new InvalidAlgorithmParameterException(
"EC may only use ECGenParameterSpec");
}
break;
default:
throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm);
}
}
@Override
public KeyPair generateKeyPair() {
if (mKeyStore == null || mSpec == null) {
throw new IllegalStateException("Not initialized");
}
final @SecurityLevel int securityLevel =
mSpec.isStrongBoxBacked()
? SecurityLevel.STRONGBOX
: SecurityLevel.TRUSTED_ENVIRONMENT;
final int flags =
mSpec.isCriticalToDeviceEncryption()
? IKeystoreSecurityLevel
.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING
: 0;
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, (mKeySizeBits + 7) / 8);
KeyDescriptor descriptor = new KeyDescriptor();
descriptor.alias = mEntryAlias;
descriptor.domain = mEntryUid == KeyProperties.NAMESPACE_APPLICATION
? Domain.APP
: Domain.SELINUX;
descriptor.nspace = mEntryUid;
descriptor.blob = null;
boolean success = false;
try {
KeyStoreSecurityLevel iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel);
KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, null,
constructKeyGenerationArguments(), flags, additionalEntropy);
AndroidKeyStorePublicKey publicKey =
AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse(
descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm);
success = true;
return new KeyPair(publicKey, publicKey.getPrivateKey());
} catch (android.security.KeyStoreException e) {
switch(e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
throw new StrongBoxUnavailableException("Failed to generated key pair.", e);
default:
ProviderException p = new ProviderException("Failed to generate key pair.", e);
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
throw new SecureKeyImportUnavailableException(p);
}
throw p;
}
} catch (UnrecoverableKeyException e) {
throw new ProviderException(
"Failed to construct key object from newly generated key pair.", e);
} finally {
if (!success) {
try {
mKeyStore.deleteKey(descriptor);
} catch (KeyStoreException e) {
if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) {
Log.e(TAG, "Failed to delete newly generated key after "
+ "generation failed unexpectedly.", e);
}
}
}
}
}
private void addAttestationParameters(@NonNull List<KeyParameter> params)
throws ProviderException {
byte[] challenge = mSpec.getAttestationChallenge();
if (challenge != null) {
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, challenge
));
if (mSpec.isDevicePropertiesAttestationIncluded()) {
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
Build.BRAND.getBytes(StandardCharsets.UTF_8)
));
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
Build.DEVICE.getBytes(StandardCharsets.UTF_8)
));
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
Build.PRODUCT.getBytes(StandardCharsets.UTF_8)
));
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER,
Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)
));
params.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL,
Build.MODEL.getBytes(StandardCharsets.UTF_8)
));
}
} else {
if (mSpec.isDevicePropertiesAttestationIncluded()) {
throw new ProviderException("An attestation challenge must be provided when "
+ "requesting device properties attestation.");
}
}
}
private Collection<KeyParameter> constructKeyGenerationArguments() {
List<KeyParameter> params = new ArrayList<>();
params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits));
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm
));
ArrayUtils.forEach(mKeymasterPurposes, (purpose) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PURPOSE, purpose
));
});
ArrayUtils.forEach(mKeymasterBlockModes, (blockMode) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_BLOCK_MODE, blockMode
));
});
ArrayUtils.forEach(mKeymasterEncryptionPaddings, (padding) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PADDING, padding
));
});
ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PADDING, padding
));
});
ArrayUtils.forEach(mKeymasterDigests, (digest) -> {
params.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_DIGEST, digest
));
});
KeyStore2ParameterUtils.addUserAuthArgs(params, mSpec);
if (mSpec.getKeyValidityStart() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart()
));
}
if (mSpec.getKeyValidityForOriginationEnd() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
mSpec.getKeyValidityForOriginationEnd()
));
}
if (mSpec.getKeyValidityForConsumptionEnd() != null) {
params.add(KeyStore2ParameterUtils.makeDate(
KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
mSpec.getKeyValidityForConsumptionEnd()
));
}
addAlgorithmSpecificParameters(params);
if (mSpec.isUniqueIdIncluded()) {
params.add(KeyStore2ParameterUtils.makeBool(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID));
}
addAttestationParameters(params);
return params;
}
private void addAlgorithmSpecificParameters(List<KeyParameter> params) {
switch (mKeymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_RSA:
params.add(KeyStore2ParameterUtils.makeLong(
KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, mRSAPublicExponent
));
break;
case KeymasterDefs.KM_ALGORITHM_EC:
break;
default:
throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm);
}
}
private static int getDefaultKeySize(int keymasterAlgorithm) {
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_EC:
return EC_DEFAULT_KEY_SIZE;
case KeymasterDefs.KM_ALGORITHM_RSA:
return RSA_DEFAULT_KEY_SIZE;
default:
throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm);
}
}
private static void checkValidKeySize(
int keymasterAlgorithm,
int keySize,
boolean isStrongBoxBacked)
throws InvalidAlgorithmParameterException {
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_EC:
if (isStrongBoxBacked && keySize != 256) {
throw new InvalidAlgorithmParameterException(
"Unsupported StrongBox EC key size: "
+ keySize + " bits. Supported: 256");
}
if (!SUPPORTED_EC_NIST_CURVE_SIZES.contains(keySize)) {
throw new InvalidAlgorithmParameterException("Unsupported EC key size: "
+ keySize + " bits. Supported: " + SUPPORTED_EC_NIST_CURVE_SIZES);
}
break;
case KeymasterDefs.KM_ALGORITHM_RSA:
if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) {
throw new InvalidAlgorithmParameterException("RSA key size must be >= "
+ RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE);
}
break;
default:
throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm);
}
}
/**
* Returns the {@code Signature} algorithm to be used for signing a certificate using the
* specified key or {@code null} if the key cannot be used for signing a certificate.
*/
@Nullable
private static String getCertificateSignatureAlgorithm(
int keymasterAlgorithm,
int keySizeBits,
KeyGenParameterSpec spec) {
// Constraints:
// 1. Key must be authorized for signing without user authentication.
// 2. Signature digest must be one of key's authorized digests.
// 3. For RSA keys, the digest output size must not exceed modulus size minus space overhead
// of RSA PKCS#1 signature padding scheme (about 30 bytes).
// 4. For EC keys, the there is no point in using a digest whose output size is longer than
// key/field size because the digest will be truncated to that size.
if ((spec.getPurposes() & KeyProperties.PURPOSE_SIGN) == 0) {
// Key not authorized for signing
return null;
}
if (spec.isUserAuthenticationRequired()) {
// Key not authorized for use without user authentication
return null;
}
if (!spec.isDigestsSpecified()) {
// Key not authorized for any digests -- can't sign
return null;
}
switch (keymasterAlgorithm) {
case KeymasterDefs.KM_ALGORITHM_EC:
{
Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests(
spec.getDigests(),
AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests());
int bestKeymasterDigest = -1;
int bestDigestOutputSizeBits = -1;
for (int keymasterDigest : availableKeymasterDigests) {
int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
if (outputSizeBits == keySizeBits) {
// Perfect match -- use this digest
bestKeymasterDigest = keymasterDigest;
bestDigestOutputSizeBits = outputSizeBits;
break;
}
// Not a perfect match -- check against the best digest so far
if (bestKeymasterDigest == -1) {
// First digest tested -- definitely the best so far
bestKeymasterDigest = keymasterDigest;
bestDigestOutputSizeBits = outputSizeBits;
} else {
// Prefer output size to be as close to key size as possible, with output
// sizes larger than key size preferred to those smaller than key size.
if (bestDigestOutputSizeBits < keySizeBits) {
// Output size of the best digest so far is smaller than key size.
// Anything larger is a win.
if (outputSizeBits > bestDigestOutputSizeBits) {
bestKeymasterDigest = keymasterDigest;
bestDigestOutputSizeBits = outputSizeBits;
}
} else {
// Output size of the best digest so far is larger than key size.
// Anything smaller is a win, as long as it's not smaller than key size.
if ((outputSizeBits < bestDigestOutputSizeBits)
&& (outputSizeBits >= keySizeBits)) {
bestKeymasterDigest = keymasterDigest;
bestDigestOutputSizeBits = outputSizeBits;
}
}
}
}
if (bestKeymasterDigest == -1) {
return null;
}
return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest(
bestKeymasterDigest) + "WithECDSA";
}
case KeymasterDefs.KM_ALGORITHM_RSA:
{
// Check whether this key is authorized for PKCS#1 signature padding.
// We use Bouncy Castle to generate self-signed RSA certificates. Bouncy Castle
// only supports RSA certificates signed using PKCS#1 padding scheme. The key needs
// to be authorized for PKCS#1 padding or padding NONE which means any padding.
boolean pkcs1SignaturePaddingSupported =
com.android.internal.util.ArrayUtils.contains(
KeyProperties.SignaturePadding.allToKeymaster(
spec.getSignaturePaddings()),
KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN);
if (!pkcs1SignaturePaddingSupported) {
// Key not authorized for PKCS#1 signature padding -- can't sign
return null;
}
Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests(
spec.getDigests(),
AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests());
// The amount of space available for the digest is less than modulus size by about
// 30 bytes because padding must be at least 11 bytes long (00 || 01 || PS || 00,
// where PS must be at least 8 bytes long), and then there's also the 15--19 bytes
// overhead (depending the on chosen digest) for encoding digest OID and digest
// value in DER.
int maxDigestOutputSizeBits = keySizeBits - 30 * 8;
int bestKeymasterDigest = -1;
int bestDigestOutputSizeBits = -1;
for (int keymasterDigest : availableKeymasterDigests) {
int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
if (outputSizeBits > maxDigestOutputSizeBits) {
// Digest too long (signature generation will fail) -- skip
continue;
}
if (bestKeymasterDigest == -1) {
// First digest tested -- definitely the best so far
bestKeymasterDigest = keymasterDigest;
bestDigestOutputSizeBits = outputSizeBits;
} else {
// The longer the better
if (outputSizeBits > bestDigestOutputSizeBits) {
bestKeymasterDigest = keymasterDigest;
bestDigestOutputSizeBits = outputSizeBits;
}
}
}
if (bestKeymasterDigest == -1) {
return null;
}
return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest(
bestKeymasterDigest) + "WithRSA";
}
default:
throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm);
}
}
private static Set<Integer> getAvailableKeymasterSignatureDigests(
@KeyProperties.DigestEnum String[] authorizedKeyDigests,
@KeyProperties.DigestEnum String[] supportedSignatureDigests) {
Set<Integer> authorizedKeymasterKeyDigests = new HashSet<Integer>();
for (int keymasterDigest : KeyProperties.Digest.allToKeymaster(authorizedKeyDigests)) {
authorizedKeymasterKeyDigests.add(keymasterDigest);
}
Set<Integer> supportedKeymasterSignatureDigests = new HashSet<Integer>();
for (int keymasterDigest
: KeyProperties.Digest.allToKeymaster(supportedSignatureDigests)) {
supportedKeymasterSignatureDigests.add(keymasterDigest);
}
Set<Integer> result = new HashSet<Integer>(supportedKeymasterSignatureDigests);
result.retainAll(authorizedKeymasterKeyDigests);
return result;
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.keystore2;
import java.security.KeyStore;
import java.security.KeyStore.ProtectionParameter;
/**
* @hide
*/
class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter {
private final int mNamespace;
AndroidKeyStoreLoadStoreParameter(int namespace) {
mNamespace = namespace;
}
@Override
public ProtectionParameter getProtectionParameter() {
return null;
}
int getNamespace() {
return mNamespace;
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.security.KeyStoreSecurityLevel;
import android.system.keystore2.Authorization;
import android.system.keystore2.KeyDescriptor;
import java.security.PrivateKey;
/**
* {@link PrivateKey} backed by Android Keystore.
*
* @hide
*/
public class AndroidKeyStorePrivateKey extends AndroidKeyStoreKey implements PrivateKey {
public AndroidKeyStorePrivateKey(@NonNull KeyDescriptor descriptor,
long keyId, @NonNull Authorization[] authorizations, @NonNull String algorithm,
@NonNull KeyStoreSecurityLevel securityLevel) {
super(descriptor, keyId, authorizations, algorithm, securityLevel);
}
}

View File

@@ -0,0 +1,415 @@
/*
* Copyright (C) 2012 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.keystore2;
import android.annotation.NonNull;
import android.security.KeyStore;
import android.security.KeyStore2;
import android.security.KeyStoreSecurityLevel;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyStoreCryptoOperation;
import android.system.keystore2.Authorization;
import android.system.keystore2.Domain;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyEntryResponse;
import android.system.keystore2.KeyMetadata;
import android.system.keystore2.ResponseCode;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.UnrecoverableKeyException;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import javax.crypto.Mac;
/**
* A provider focused on providing JCA interfaces for the Android KeyStore.
*
* @hide
*/
public class AndroidKeyStoreProvider extends Provider {
private static final String PROVIDER_NAME = "AndroidKeyStore";
// IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these
// classes when this provider is instantiated and installed early on during each app's
// initialization process.
//
// Crypto operations operating on the AndroidKeyStore keys must not be offered by this provider.
// Instead, they need to be offered by AndroidKeyStoreBCWorkaroundProvider. See its Javadoc
// for details.
private static final String PACKAGE_NAME = "android.security.keystore2";
private static final String DESEDE_SYSTEM_PROPERTY =
"ro.hardware.keystore_desede";
/** @hide **/
public AndroidKeyStoreProvider() {
super(PROVIDER_NAME, 1.0, "Android KeyStore security provider");
boolean supports3DES = "true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY));
// java.security.KeyStore
put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi");
// java.security.KeyPairGenerator
put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
// java.security.KeyFactory
putKeyFactoryImpl("EC");
putKeyFactoryImpl("RSA");
// javax.crypto.KeyGenerator
put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA1");
put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA224");
put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA256");
put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA384");
put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA512");
if (supports3DES) {
put("KeyGenerator.DESede", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$DESede");
}
// java.security.SecretKeyFactory
putSecretKeyFactoryImpl("AES");
if (supports3DES) {
putSecretKeyFactoryImpl("DESede");
}
putSecretKeyFactoryImpl("HmacSHA1");
putSecretKeyFactoryImpl("HmacSHA224");
putSecretKeyFactoryImpl("HmacSHA256");
putSecretKeyFactoryImpl("HmacSHA384");
putSecretKeyFactoryImpl("HmacSHA512");
}
private static boolean sInstalled = false;
/**
* This function indicates whether or not this provider was installed. This is manly used
* as indicator for
* {@link android.security.keystore.AndroidKeyStoreProvider#getKeyStoreForUid(int)}
* to whether or not to retrieve the Keystore provider by "AndroidKeyStoreLegacy".
* This function can be removed once the transition to Keystore 2.0 is complete.
* b/171305684
*
* @return true if this provider was installed.
* @hide
*/
public static boolean isInstalled() {
return sInstalled;
}
/**
* Installs a new instance of this provider (and the
* {@link AndroidKeyStoreBCWorkaroundProvider}).
* @hide
*/
public static void install() {
Provider[] providers = Security.getProviders();
int bcProviderIndex = -1;
for (int i = 0; i < providers.length; i++) {
Provider provider = providers[i];
if ("BC".equals(provider.getName())) {
bcProviderIndex = i;
break;
}
}
sInstalled = true;
Security.addProvider(new AndroidKeyStoreProvider());
Security.addProvider(
new android.security.keystore.AndroidKeyStoreProvider(
"AndroidKeyStoreLegacy"));
Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider();
Provider legacyWorkaroundProvider =
new android.security.keystore.AndroidKeyStoreBCWorkaroundProvider(
"AndroidKeyStoreBCWorkaroundLegacy");
if (bcProviderIndex != -1) {
// Bouncy Castle provider found -- install the workaround provider above it.
// insertProviderAt uses 1-based positions.
Security.insertProviderAt(legacyWorkaroundProvider, bcProviderIndex + 1);
Security.insertProviderAt(workaroundProvider, bcProviderIndex + 1);
} else {
// Bouncy Castle provider not found -- install the workaround provider at lowest
// priority.
Security.addProvider(workaroundProvider);
Security.addProvider(legacyWorkaroundProvider);
}
}
private void putSecretKeyFactoryImpl(String algorithm) {
put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreSecretKeyFactorySpi");
}
private void putKeyFactoryImpl(String algorithm) {
put("KeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreKeyFactorySpi");
}
/**
* Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto
* primitive.
*
* <p>The following primitives are supported: {@link Cipher} and {@link Mac}.
*
* @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation
* is not in progress.
*
* @throws IllegalArgumentException if the provided primitive is not supported or is not backed
* by AndroidKeyStore provider.
* @throws IllegalStateException if the provided primitive is not initialized.
* @hide
*/
public static long getKeyStoreOperationHandle(Object cryptoPrimitive) {
if (cryptoPrimitive == null) {
throw new NullPointerException();
}
Object spi;
if (cryptoPrimitive instanceof Signature) {
spi = ((Signature) cryptoPrimitive).getCurrentSpi();
} else if (cryptoPrimitive instanceof Mac) {
spi = ((Mac) cryptoPrimitive).getCurrentSpi();
} else if (cryptoPrimitive instanceof Cipher) {
spi = ((Cipher) cryptoPrimitive).getCurrentSpi();
} else {
throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive
+ ". Supported: Signature, Mac, Cipher");
}
if (spi == null) {
throw new IllegalStateException("Crypto primitive not initialized");
} else if (!(spi instanceof KeyStoreCryptoOperation)) {
throw new IllegalArgumentException(
"Crypto primitive not backed by AndroidKeyStore provider: " + cryptoPrimitive
+ ", spi: " + spi);
}
return ((KeyStoreCryptoOperation) spi).getOperationHandle();
}
/**
* This helper function gets called if the key loaded from the keystore daemon
* is for an asymmetric algorithm. It constructs an instance of {@link AndroidKeyStorePublicKey}
* which implements {@link PublicKey}.
*
* @param descriptor The original key descriptor that was used to load the key.
*
* @param metadata The key metadata which includes the public key material, a reference to the
* stored private key material, the key characteristics.
* @param iSecurityLevel A binder interface that allows using the private key.
* @param algorithm Must indicate EC or RSA.
* @return AndroidKeyStorePublicKey
* @throws UnrecoverableKeyException
* @hide
*/
@NonNull
static AndroidKeyStorePublicKey makeAndroidKeyStorePublicKeyFromKeyEntryResponse(
@NonNull KeyDescriptor descriptor,
@NonNull KeyMetadata metadata,
@NonNull KeyStoreSecurityLevel iSecurityLevel, int algorithm)
throws UnrecoverableKeyException {
if (metadata.certificate == null) {
throw new UnrecoverableKeyException("Failed to obtain X.509 form of public key."
+ " Keystore has no public certificate stored.");
}
final byte[] x509EncodedPublicKey = metadata.certificate;
String jcaKeyAlgorithm;
try {
jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm(
algorithm);
} catch (IllegalArgumentException e) {
throw (UnrecoverableKeyException)
new UnrecoverableKeyException("Failed to load private key")
.initCause(e);
}
PublicKey publicKey;
try {
KeyFactory keyFactory = KeyFactory.getInstance(jcaKeyAlgorithm);
publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedPublicKey));
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(
"Failed to obtain " + jcaKeyAlgorithm + " KeyFactory", e);
} catch (InvalidKeySpecException e) {
throw new ProviderException("Invalid X.509 encoding of public key", e);
}
KeyStoreSecurityLevel securityLevel = iSecurityLevel;
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(jcaKeyAlgorithm)) {
return new AndroidKeyStoreECPublicKey(descriptor, metadata,
iSecurityLevel, (ECPublicKey) publicKey);
} else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(jcaKeyAlgorithm)) {
return new AndroidKeyStoreRSAPublicKey(descriptor, metadata,
iSecurityLevel, (RSAPublicKey) publicKey);
} else {
throw new ProviderException("Unsupported Android Keystore public key algorithm: "
+ jcaKeyAlgorithm);
}
}
/** @hide **/
@NonNull
public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore(
@NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace)
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
AndroidKeyStoreKey key =
loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace);
if (key instanceof AndroidKeyStorePublicKey) {
return (AndroidKeyStorePublicKey) key;
} else {
throw new UnrecoverableKeyException("No asymmetric key found by the given alias.");
}
}
/** @hide **/
@NonNull
public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore(
@NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace)
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
AndroidKeyStoreKey key =
loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace);
if (key instanceof AndroidKeyStorePublicKey) {
AndroidKeyStorePublicKey publicKey = (AndroidKeyStorePublicKey) key;
return new KeyPair(publicKey, publicKey.getPrivateKey());
} else {
throw new UnrecoverableKeyException("No asymmetric key found by the given alias.");
}
}
/** @hide **/
@NonNull
public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore(
@NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace)
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
AndroidKeyStoreKey key =
loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace);
if (key instanceof AndroidKeyStorePublicKey) {
return ((AndroidKeyStorePublicKey) key).getPrivateKey();
} else {
throw new UnrecoverableKeyException("No asymmetric key found by the given alias.");
}
}
@NonNull
private static AndroidKeyStoreSecretKey makeAndroidKeyStoreSecretKeyFromKeyEntryResponse(
@NonNull KeyDescriptor descriptor,
@NonNull KeyEntryResponse response, int algorithm, int digest)
throws UnrecoverableKeyException {
@KeyProperties.KeyAlgorithmEnum String keyAlgorithmString;
try {
keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm(
algorithm, digest);
} catch (IllegalArgumentException e) {
throw (UnrecoverableKeyException)
new UnrecoverableKeyException("Unsupported secret key type").initCause(e);
}
return new AndroidKeyStoreSecretKey(descriptor,
response.metadata, keyAlgorithmString,
new KeyStoreSecurityLevel(response.iSecurityLevel));
}
/**
* Loads an an AndroidKeyStoreKey from the AndroidKeyStore backend.
*
* @param keyStore The keystore2 backend.
* @param alias The alias of the key in the Keystore database.
* @param namespace The a Keystore namespace. This is used by system api only to request
* Android system specific keystore namespace, which can be configured
* in the device's SEPolicy. Third party apps and most system components
* set this parameter to -1 to indicate their application specific namespace.
* TODO b/171806779 link to public Keystore 2.0 documentation.
* See bug for more details for now.
* @hide
**/
@NonNull
public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore(
@NonNull KeyStore2 keyStore, @NonNull String alias, int namespace)
throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {
KeyDescriptor descriptor = new KeyDescriptor();
if (namespace == KeyProperties.NAMESPACE_APPLICATION) {
descriptor.nspace = 0; // ignored;
descriptor.domain = Domain.APP;
} else {
descriptor.nspace = namespace;
descriptor.domain = Domain.SELINUX;
}
descriptor.alias = alias;
descriptor.blob = null;
KeyEntryResponse response = null;
try {
response = keyStore.getKeyEntry(descriptor);
} catch (android.security.KeyStoreException e) {
if (e.getErrorCode() == ResponseCode.KEY_PERMANENTLY_INVALIDATED) {
throw new KeyPermanentlyInvalidatedException(
"User changed or deleted their auth credentials",
e);
} else {
throw (UnrecoverableKeyException)
new UnrecoverableKeyException("Failed to obtain information about key")
.initCause(e);
}
}
Integer keymasterAlgorithm = null;
// We just need one digest for the algorithm name
int keymasterDigest = -1;
for (Authorization a : response.metadata.authorizations) {
switch (a.keyParameter.tag) {
case KeymasterDefs.KM_TAG_ALGORITHM:
keymasterAlgorithm = a.keyParameter.integer;
break;
case KeymasterDefs.KM_TAG_DIGEST:
if (keymasterDigest == -1) keymasterDigest = a.keyParameter.integer;
break;
}
}
if (keymasterAlgorithm == null) {
throw new UnrecoverableKeyException("Key algorithm unknown");
}
if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC ||
keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES ||
keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_3DES) {
return makeAndroidKeyStoreSecretKeyFromKeyEntryResponse(descriptor, response,
keymasterAlgorithm, keymasterDigest);
} else if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_RSA ||
keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
return makeAndroidKeyStorePublicKeyFromKeyEntryResponse(descriptor, response.metadata,
new KeyStoreSecurityLevel(response.iSecurityLevel),
keymasterAlgorithm);
} else {
throw new UnrecoverableKeyException("Key algorithm unknown");
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.security.KeyStoreSecurityLevel;
import android.security.keystore.ArrayUtils;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyMetadata;
import java.security.PublicKey;
/**
* {@link PublicKey} backed by Android Keystore.
*
* @hide
*/
public abstract class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements PublicKey {
private final byte[] mCertificate;
private final byte[] mCertificateChain;
public AndroidKeyStorePublicKey(@NonNull KeyDescriptor descriptor,
@NonNull KeyMetadata metadata, @NonNull String algorithm,
@NonNull KeyStoreSecurityLevel securityLevel) {
super(descriptor, metadata.key.nspace, metadata.authorizations, algorithm, securityLevel);
mCertificate = metadata.certificate;
mCertificateChain = metadata.certificateChain;
}
abstract AndroidKeyStorePrivateKey getPrivateKey();
@Override
public String getFormat() {
return "X.509";
}
@Override
public byte[] getEncoded() {
return ArrayUtils.cloneIfNotEmpty(mCertificate);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + super.hashCode();
result = prime * result + ((mCertificate == null) ? 0 : mCertificate.hashCode());
result = prime * result + ((mCertificateChain == null) ? 0 : mCertificateChain.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,525 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeymasterUtils;
import android.system.keystore2.Authorization;
import android.system.keystore2.KeyParameter;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.MGF1ParameterSpec;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
/**
* Base class for {@link CipherSpi} providing Android KeyStore backed RSA encryption/decryption.
*
* @hide
*/
abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase {
/**
* Raw RSA cipher without any padding.
*/
public static final class NoPadding extends AndroidKeyStoreRSACipherSpi {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
@Override
protected boolean adjustConfigForEncryptingWithPrivateKey() {
// RSA encryption with no padding using private key is a way to implement raw RSA
// signatures which JCA does not expose via Signature. We thus have to support this.
setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN);
return true;
}
@Override
protected void initAlgorithmSpecificParameters() throws InvalidKeyException {}
@Override
protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
if (params != null) {
throw new InvalidAlgorithmParameterException(
"Unexpected parameters: " + params + ". No parameters supported");
}
}
@Override
protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
if (params != null) {
throw new InvalidAlgorithmParameterException(
"Unexpected parameters: " + params + ". No parameters supported");
}
}
@Override
protected AlgorithmParameters engineGetParameters() {
return null;
}
@Override
protected final int getAdditionalEntropyAmountForBegin() {
return 0;
}
@Override
protected final int getAdditionalEntropyAmountForFinish() {
return 0;
}
}
/**
* RSA cipher with PKCS#1 v1.5 encryption padding.
*/
public static final class PKCS1Padding extends AndroidKeyStoreRSACipherSpi {
public PKCS1Padding() {
super(KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT);
}
@Override
protected boolean adjustConfigForEncryptingWithPrivateKey() {
// RSA encryption with PCKS#1 padding using private key is a way to implement RSA
// signatures with PKCS#1 padding. We have to support this for legacy reasons.
setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN);
setKeymasterPaddingOverride(KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN);
return true;
}
@Override
protected void initAlgorithmSpecificParameters() throws InvalidKeyException {}
@Override
protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
if (params != null) {
throw new InvalidAlgorithmParameterException(
"Unexpected parameters: " + params + ". No parameters supported");
}
}
@Override
protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
if (params != null) {
throw new InvalidAlgorithmParameterException(
"Unexpected parameters: " + params + ". No parameters supported");
}
}
@Override
protected AlgorithmParameters engineGetParameters() {
return null;
}
@Override
protected final int getAdditionalEntropyAmountForBegin() {
return 0;
}
@Override
protected final int getAdditionalEntropyAmountForFinish() {
return (isEncrypting()) ? getModulusSizeBytes() : 0;
}
}
/**
* RSA cipher with OAEP encryption padding. Only SHA-1 based MGF1 is supported as MGF.
*/
abstract static class OAEPWithMGF1Padding extends AndroidKeyStoreRSACipherSpi {
private static final String MGF_ALGORITGM_MGF1 = "MGF1";
private int mKeymasterDigest = -1;
private int mDigestOutputSizeBytes;
OAEPWithMGF1Padding(int keymasterDigest) {
super(KeymasterDefs.KM_PAD_RSA_OAEP);
mKeymasterDigest = keymasterDigest;
mDigestOutputSizeBytes =
(KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8;
}
@Override
protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {}
@Override
protected final void initAlgorithmSpecificParameters(
@Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException {
if (params == null) {
return;
}
if (!(params instanceof OAEPParameterSpec)) {
throw new InvalidAlgorithmParameterException(
"Unsupported parameter spec: " + params
+ ". Only OAEPParameterSpec supported");
}
OAEPParameterSpec spec = (OAEPParameterSpec) params;
if (!MGF_ALGORITGM_MGF1.equalsIgnoreCase(spec.getMGFAlgorithm())) {
throw new InvalidAlgorithmParameterException(
"Unsupported MGF: " + spec.getMGFAlgorithm()
+ ". Only " + MGF_ALGORITGM_MGF1 + " supported");
}
String jcaDigest = spec.getDigestAlgorithm();
int keymasterDigest;
try {
keymasterDigest = KeyProperties.Digest.toKeymaster(jcaDigest);
} catch (IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(
"Unsupported digest: " + jcaDigest, e);
}
switch (keymasterDigest) {
case KeymasterDefs.KM_DIGEST_SHA1:
case KeymasterDefs.KM_DIGEST_SHA_2_224:
case KeymasterDefs.KM_DIGEST_SHA_2_256:
case KeymasterDefs.KM_DIGEST_SHA_2_384:
case KeymasterDefs.KM_DIGEST_SHA_2_512:
// Permitted.
break;
default:
throw new InvalidAlgorithmParameterException(
"Unsupported digest: " + jcaDigest);
}
AlgorithmParameterSpec mgfParams = spec.getMGFParameters();
if (mgfParams == null) {
throw new InvalidAlgorithmParameterException("MGF parameters must be provided");
}
// Check whether MGF parameters match the OAEPParameterSpec
if (!(mgfParams instanceof MGF1ParameterSpec)) {
throw new InvalidAlgorithmParameterException("Unsupported MGF parameters"
+ ": " + mgfParams + ". Only MGF1ParameterSpec supported");
}
MGF1ParameterSpec mgfSpec = (MGF1ParameterSpec) mgfParams;
String mgf1JcaDigest = mgfSpec.getDigestAlgorithm();
if (!KeyProperties.DIGEST_SHA1.equalsIgnoreCase(mgf1JcaDigest)) {
throw new InvalidAlgorithmParameterException(
"Unsupported MGF1 digest: " + mgf1JcaDigest
+ ". Only " + KeyProperties.DIGEST_SHA1 + " supported");
}
PSource pSource = spec.getPSource();
if (!(pSource instanceof PSource.PSpecified)) {
throw new InvalidAlgorithmParameterException(
"Unsupported source of encoding input P: " + pSource
+ ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported");
}
PSource.PSpecified pSourceSpecified = (PSource.PSpecified) pSource;
byte[] pSourceValue = pSourceSpecified.getValue();
if ((pSourceValue != null) && (pSourceValue.length > 0)) {
throw new InvalidAlgorithmParameterException(
"Unsupported source of encoding input P: " + pSource
+ ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported");
}
mKeymasterDigest = keymasterDigest;
mDigestOutputSizeBytes =
(KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8;
}
@Override
protected final void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
if (params == null) {
return;
}
OAEPParameterSpec spec;
try {
spec = params.getParameterSpec(OAEPParameterSpec.class);
} catch (InvalidParameterSpecException e) {
throw new InvalidAlgorithmParameterException("OAEP parameters required"
+ ", but not found in parameters: " + params, e);
}
if (spec == null) {
throw new InvalidAlgorithmParameterException("OAEP parameters required"
+ ", but not provided in parameters: " + params);
}
initAlgorithmSpecificParameters(spec);
}
@Override
protected final AlgorithmParameters engineGetParameters() {
OAEPParameterSpec spec =
new OAEPParameterSpec(
KeyProperties.Digest.fromKeymaster(mKeymasterDigest),
MGF_ALGORITGM_MGF1,
MGF1ParameterSpec.SHA1,
PSource.PSpecified.DEFAULT);
try {
AlgorithmParameters params = AlgorithmParameters.getInstance("OAEP");
params.init(spec);
return params;
} catch (NoSuchAlgorithmException e) {
throw new ProviderException(
"Failed to obtain OAEP AlgorithmParameters", e);
} catch (InvalidParameterSpecException e) {
throw new ProviderException(
"Failed to initialize OAEP AlgorithmParameters with an IV",
e);
}
}
@Override
protected final void addAlgorithmSpecificParametersToBegin(
@NonNull List<KeyParameter> parameters) {
super.addAlgorithmSpecificParametersToBegin(parameters);
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
));
}
@Override
protected final void loadAlgorithmSpecificParametersFromBeginResult(
KeyParameter[] parameters) {
super.loadAlgorithmSpecificParametersFromBeginResult(parameters);
}
@Override
protected final int getAdditionalEntropyAmountForBegin() {
return 0;
}
@Override
protected final int getAdditionalEntropyAmountForFinish() {
return (isEncrypting()) ? mDigestOutputSizeBytes : 0;
}
}
public static class OAEPWithSHA1AndMGF1Padding extends OAEPWithMGF1Padding {
public OAEPWithSHA1AndMGF1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
public static class OAEPWithSHA224AndMGF1Padding extends OAEPWithMGF1Padding {
public OAEPWithSHA224AndMGF1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
public static class OAEPWithSHA256AndMGF1Padding extends OAEPWithMGF1Padding {
public OAEPWithSHA256AndMGF1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
public static class OAEPWithSHA384AndMGF1Padding extends OAEPWithMGF1Padding {
public OAEPWithSHA384AndMGF1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
public static class OAEPWithSHA512AndMGF1Padding extends OAEPWithMGF1Padding {
public OAEPWithSHA512AndMGF1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
}
private final int mKeymasterPadding;
private int mKeymasterPaddingOverride;
private int mModulusSizeBytes = -1;
AndroidKeyStoreRSACipherSpi(int keymasterPadding) {
mKeymasterPadding = keymasterPadding;
}
@Override
protected final void initKey(int opmode, Key key) throws InvalidKeyException {
if (key == null) {
throw new InvalidKeyException("Unsupported key: null");
}
if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
+ ". Only " + KeyProperties.KEY_ALGORITHM_RSA + " supported");
}
AndroidKeyStoreKey keystoreKey;
if (key instanceof AndroidKeyStorePrivateKey) {
keystoreKey = (AndroidKeyStoreKey) key;
} else if (key instanceof AndroidKeyStorePublicKey) {
keystoreKey = (AndroidKeyStoreKey) key;
} else {
throw new InvalidKeyException("Unsupported key type: " + key);
}
if (keystoreKey instanceof PrivateKey) {
// Private key
switch (opmode) {
case Cipher.DECRYPT_MODE:
case Cipher.UNWRAP_MODE:
// Permitted
break;
case Cipher.ENCRYPT_MODE:
case Cipher.WRAP_MODE:
if (!adjustConfigForEncryptingWithPrivateKey()) {
throw new InvalidKeyException(
"RSA private keys cannot be used with " + opmodeToString(opmode)
+ " and padding "
+ KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding)
+ ". Only RSA public keys supported for this mode");
}
break;
default:
throw new InvalidKeyException(
"RSA private keys cannot be used with opmode: " + opmode);
}
} else {
// Public key
switch (opmode) {
case Cipher.ENCRYPT_MODE:
case Cipher.WRAP_MODE:
// Permitted
break;
case Cipher.DECRYPT_MODE:
case Cipher.UNWRAP_MODE:
throw new InvalidKeyException(
"RSA public keys cannot be used with " + opmodeToString(opmode)
+ " and padding "
+ KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding)
+ ". Only RSA private keys supported for this opmode.");
// break;
default:
throw new InvalidKeyException(
"RSA public keys cannot be used with " + opmodeToString(opmode));
}
}
long keySizeBits = -1;
for (Authorization a : keystoreKey.getAuthorizations()) {
if (a.keyParameter.tag == KeymasterDefs.KM_TAG_KEY_SIZE) {
keySizeBits = KeyStore2ParameterUtils.getUnsignedInt(a);
}
}
if (keySizeBits == -1) {
throw new InvalidKeyException("Size of key not known");
} else if (keySizeBits > Integer.MAX_VALUE) {
throw new InvalidKeyException("Key too large: " + keySizeBits + " bits");
}
mModulusSizeBytes = (int) ((keySizeBits + 7) / 8);
setKey(keystoreKey);
}
/**
* Adjusts the configuration of this cipher for encrypting using the private key.
*
* <p>The default implementation does nothing and refuses to adjust the configuration.
*
* @return {@code true} if the configuration has been adjusted, {@code false} if encrypting
* using private key is not permitted for this cipher.
*/
protected boolean adjustConfigForEncryptingWithPrivateKey() {
return false;
}
@Override
protected final void resetAll() {
mModulusSizeBytes = -1;
mKeymasterPaddingOverride = -1;
super.resetAll();
}
@Override
protected final void resetWhilePreservingInitState() {
super.resetWhilePreservingInitState();
}
@Override
protected void addAlgorithmSpecificParametersToBegin(
@NonNull List<KeyParameter> parameters) {
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA
));
int keymasterPadding = getKeymasterPaddingOverride();
if (keymasterPadding == -1) {
keymasterPadding = mKeymasterPadding;
}
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PADDING, keymasterPadding
));
int purposeOverride = getKeymasterPurposeOverride();
if ((purposeOverride != -1)
&& ((purposeOverride == KeymasterDefs.KM_PURPOSE_SIGN)
|| (purposeOverride == KeymasterDefs.KM_PURPOSE_VERIFY))) {
// Keymaster sign/verify requires digest to be specified.
// For raw sign/verify it's NONE.
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE
));
}
}
@Override
protected void loadAlgorithmSpecificParametersFromBeginResult(
KeyParameter[] parameters) {
}
@Override
protected final int engineGetBlockSize() {
// Not a block cipher, according to the RI
return 0;
}
@Override
protected final byte[] engineGetIV() {
// IV never used
return null;
}
@Override
protected final int engineGetOutputSize(int inputLen) {
return getModulusSizeBytes();
}
protected final int getModulusSizeBytes() {
if (mModulusSizeBytes == -1) {
throw new IllegalStateException("Not initialized");
}
return mModulusSizeBytes;
}
/**
* Overrides the default padding of the crypto operation.
*/
protected final void setKeymasterPaddingOverride(int keymasterPadding) {
mKeymasterPaddingOverride = keymasterPadding;
}
protected final int getKeymasterPaddingOverride() {
return mKeymasterPaddingOverride;
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.security.KeyStoreSecurityLevel;
import android.security.keystore.KeyProperties;
import android.system.keystore2.Authorization;
import android.system.keystore2.KeyDescriptor;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.interfaces.RSAKey;
/**
* RSA private key (instance of {@link PrivateKey} and {@link RSAKey}) backed by keystore.
*
* @hide
*/
public class AndroidKeyStoreRSAPrivateKey extends AndroidKeyStorePrivateKey implements RSAKey {
private final BigInteger mModulus;
public AndroidKeyStoreRSAPrivateKey(@NonNull KeyDescriptor descriptor,
long keyId,
@NonNull Authorization[] authorizations,
@NonNull KeyStoreSecurityLevel securityLevel, @NonNull BigInteger modulus) {
super(descriptor, keyId, authorizations, KeyProperties.KEY_ALGORITHM_RSA, securityLevel);
mModulus = modulus;
}
@Override
public BigInteger getModulus() {
return mModulus;
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.security.KeyStoreSecurityLevel;
import android.security.keystore.KeyProperties;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyMetadata;
import java.math.BigInteger;
import java.security.interfaces.RSAPublicKey;
/**
* {@link RSAPublicKey} backed by Android Keystore.
*
* @hide
*/
public class AndroidKeyStoreRSAPublicKey extends AndroidKeyStorePublicKey implements RSAPublicKey {
private final BigInteger mModulus;
private final BigInteger mPublicExponent;
public AndroidKeyStoreRSAPublicKey(@NonNull KeyDescriptor descriptor,
@NonNull KeyMetadata metadata,
@NonNull KeyStoreSecurityLevel securityLevel, @NonNull BigInteger modulus,
@NonNull BigInteger publicExponent) {
super(descriptor, metadata, KeyProperties.KEY_ALGORITHM_RSA, securityLevel);
mModulus = modulus;
mPublicExponent = publicExponent;
}
public AndroidKeyStoreRSAPublicKey(@NonNull KeyDescriptor descriptor,
@NonNull KeyMetadata metadata,
@NonNull KeyStoreSecurityLevel securityLevel, @NonNull RSAPublicKey info) {
this(descriptor, metadata, securityLevel, info.getModulus(), info.getPublicExponent());
if (!"X.509".equalsIgnoreCase(info.getFormat())) {
throw new IllegalArgumentException(
"Unsupported key export format: " + info.getFormat());
}
}
@Override
public AndroidKeyStorePrivateKey getPrivateKey() {
return new AndroidKeyStoreRSAPrivateKey(getUserKeyDescriptor(), getKeyIdDescriptor().nspace,
getAuthorizations(), getSecurityLevel(), mModulus);
}
@Override
public BigInteger getModulus() {
return mModulus;
}
@Override
public BigInteger getPublicExponent() {
return mPublicExponent;
}
}

View File

@@ -0,0 +1,172 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyProperties;
import android.system.keystore2.KeyParameter;
import java.security.InvalidKeyException;
import java.security.SignatureSpi;
import java.util.List;
/**
* Base class for {@link SignatureSpi} providing Android KeyStore backed RSA signatures.
*
* @hide
*/
abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSpiBase {
abstract static class PKCS1Padding extends AndroidKeyStoreRSASignatureSpi {
PKCS1Padding(int keymasterDigest) {
super(keymasterDigest, KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN);
}
@Override
protected final int getAdditionalEntropyAmountForSign() {
// No entropy required for this deterministic signature scheme.
return 0;
}
}
public static final class NONEWithPKCS1Padding extends PKCS1Padding {
public NONEWithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_NONE);
}
}
public static final class MD5WithPKCS1Padding extends PKCS1Padding {
public MD5WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_MD5);
}
}
public static final class SHA1WithPKCS1Padding extends PKCS1Padding {
public SHA1WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
public static final class SHA224WithPKCS1Padding extends PKCS1Padding {
public SHA224WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
public static final class SHA256WithPKCS1Padding extends PKCS1Padding {
public SHA256WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
public static final class SHA384WithPKCS1Padding extends PKCS1Padding {
public SHA384WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
public static final class SHA512WithPKCS1Padding extends PKCS1Padding {
public SHA512WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
}
abstract static class PSSPadding extends AndroidKeyStoreRSASignatureSpi {
private static final int SALT_LENGTH_BYTES = 20;
PSSPadding(int keymasterDigest) {
super(keymasterDigest, KeymasterDefs.KM_PAD_RSA_PSS);
}
@Override
protected final int getAdditionalEntropyAmountForSign() {
return SALT_LENGTH_BYTES;
}
}
public static final class SHA1WithPSSPadding extends PSSPadding {
public SHA1WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
}
public static final class SHA224WithPSSPadding extends PSSPadding {
public SHA224WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
}
public static final class SHA256WithPSSPadding extends PSSPadding {
public SHA256WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
}
public static final class SHA384WithPSSPadding extends PSSPadding {
public SHA384WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
}
public static final class SHA512WithPSSPadding extends PSSPadding {
public SHA512WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
}
private final int mKeymasterDigest;
private final int mKeymasterPadding;
AndroidKeyStoreRSASignatureSpi(int keymasterDigest, int keymasterPadding) {
mKeymasterDigest = keymasterDigest;
mKeymasterPadding = keymasterPadding;
}
@Override
protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
+ ". Only" + KeyProperties.KEY_ALGORITHM_RSA + " supported");
}
super.initKey(key);
}
@Override
protected final void resetAll() {
super.resetAll();
}
@Override
protected final void resetWhilePreservingInitState() {
super.resetWhilePreservingInitState();
}
@Override
protected final void addAlgorithmSpecificParametersToBegin(
@NonNull List<KeyParameter> parameters) {
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA
));
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
));
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding
));
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.security.KeyStoreSecurityLevel;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyMetadata;
import javax.crypto.SecretKey;
/**
* {@link SecretKey} backed by Android Keystore.
*
* @hide
*/
public class AndroidKeyStoreSecretKey extends AndroidKeyStoreKey implements SecretKey {
public AndroidKeyStoreSecretKey(@NonNull KeyDescriptor descriptor,
@NonNull KeyMetadata metadata, @NonNull String algorithm,
@NonNull KeyStoreSecurityLevel securityLevel) {
super(descriptor, metadata.key.nspace, metadata.authorizations, algorithm, securityLevel);
}
}

View File

@@ -0,0 +1,272 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.security.GateKeeper;
import android.security.KeyStore;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyInfo;
import android.security.keystore.KeyProperties;
import android.system.keystore2.Authorization;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.ProviderException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactorySpi;
import javax.crypto.spec.SecretKeySpec;
/**
* {@link SecretKeyFactorySpi} backed by Android Keystore.
*
* @hide
*/
public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
private final KeyStore mKeyStore = KeyStore.getInstance();
@Override
protected KeySpec engineGetKeySpec(SecretKey key,
@SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException {
if (keySpecClass == null) {
throw new InvalidKeySpecException("keySpecClass == null");
}
if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeySpecException("Only Android KeyStore secret keys supported: " +
((key != null) ? key.getClass().getName() : "null"));
}
if (SecretKeySpec.class.isAssignableFrom(keySpecClass)) {
throw new InvalidKeySpecException(
"Key material export of Android KeyStore keys is not supported");
}
if (!KeyInfo.class.equals(keySpecClass)) {
throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName());
}
AndroidKeyStoreKey keystoreKey = (AndroidKeyStoreKey) key;
return getKeyInfo(keystoreKey);
}
static @NonNull KeyInfo getKeyInfo(@NonNull AndroidKeyStoreKey key) {
@KeyProperties.SecurityLevelEnum int securityLevel =
KeyProperties.SECURITY_LEVEL_SOFTWARE;
boolean insideSecureHardware = false;
@KeyProperties.OriginEnum int origin = -1;
int keySize = -1;
@KeyProperties.PurposeEnum int purposes = 0;
String[] encryptionPaddings;
String[] signaturePaddings;
List<String> digestsList = new ArrayList<>();
List<String> blockModesList = new ArrayList<>();
int keymasterSwEnforcedUserAuthenticators = 0;
int keymasterHwEnforcedUserAuthenticators = 0;
List<BigInteger> keymasterSecureUserIds = new ArrayList<BigInteger>();
List<String> encryptionPaddingsList = new ArrayList<String>();
List<String> signaturePaddingsList = new ArrayList<String>();
Date keyValidityStart = null;
Date keyValidityForOriginationEnd = null;
Date keyValidityForConsumptionEnd = null;
long userAuthenticationValidityDurationSeconds = 0;
boolean userAuthenticationRequired = true;
boolean userAuthenticationValidWhileOnBody = false;
boolean trustedUserPresenceRequired = false;
boolean trustedUserConfirmationRequired = false;
try {
for (Authorization a : key.getAuthorizations()) {
switch (a.keyParameter.tag) {
case KeymasterDefs.KM_TAG_ORIGIN:
insideSecureHardware =
KeyStore2ParameterUtils.isSecureHardware(a.securityLevel);
securityLevel = a.securityLevel;
origin = KeyProperties.Origin.fromKeymaster(a.keyParameter.integer);
break;
case KeymasterDefs.KM_TAG_KEY_SIZE:
long keySizeUnsigned = KeyStore2ParameterUtils.getUnsignedInt(a);
if (keySizeUnsigned > Integer.MAX_VALUE) {
throw new ProviderException(
"Key too large: " + keySizeUnsigned + " bits");
}
keySize = (int) keySizeUnsigned;
break;
case KeymasterDefs.KM_TAG_PURPOSE:
purposes |= KeyProperties.Purpose.fromKeymaster(a.keyParameter.integer);
break;
case KeymasterDefs.KM_TAG_PADDING:
try {
if (a.keyParameter.integer == KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN
|| a.keyParameter.integer == KeymasterDefs.KM_PAD_RSA_PSS) {
@KeyProperties.SignaturePaddingEnum String padding =
KeyProperties.SignaturePadding.fromKeymaster(
a.keyParameter.integer);
signaturePaddingsList.add(padding);
} else {
@KeyProperties.EncryptionPaddingEnum String jcaPadding =
KeyProperties.EncryptionPadding.fromKeymaster(
a.keyParameter.integer);
encryptionPaddingsList.add(jcaPadding);
}
} catch (IllegalArgumentException e) {
throw new ProviderException("Unsupported padding: "
+ a.keyParameter.integer);
}
break;
case KeymasterDefs.KM_TAG_DIGEST:
digestsList.add(KeyProperties.Digest.fromKeymaster(a.keyParameter.integer));
break;
case KeymasterDefs.KM_TAG_BLOCK_MODE:
blockModesList.add(
KeyProperties.BlockMode.fromKeymaster(a.keyParameter.integer)
);
break;
case KeymasterDefs.KM_TAG_USER_AUTH_TYPE:
if (KeyStore2ParameterUtils.isSecureHardware(a.securityLevel)) {
keymasterHwEnforcedUserAuthenticators = a.keyParameter.integer;
} else {
keymasterSwEnforcedUserAuthenticators = a.keyParameter.integer;
}
break;
case KeymasterDefs.KM_TAG_USER_SECURE_ID:
keymasterSecureUserIds.add(
KeymasterArguments.toUint64(a.keyParameter.longInteger));
break;
case KeymasterDefs.KM_TAG_ACTIVE_DATETIME:
keyValidityStart = KeyStore2ParameterUtils.getDate(a);
break;
case KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME:
keyValidityForOriginationEnd =
KeyStore2ParameterUtils.getDate(a);
break;
case KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME:
keyValidityForConsumptionEnd =
KeyStore2ParameterUtils.getDate(a);
break;
case KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED:
userAuthenticationRequired = false;
break;
case KeymasterDefs.KM_TAG_AUTH_TIMEOUT:
userAuthenticationValidityDurationSeconds =
KeyStore2ParameterUtils.getUnsignedInt(a);
if (userAuthenticationValidityDurationSeconds > Integer.MAX_VALUE) {
throw new ProviderException(
"User authentication timeout validity too long: "
+ userAuthenticationValidityDurationSeconds + " seconds");
}
break;
case KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY:
userAuthenticationValidWhileOnBody =
KeyStore2ParameterUtils.isSecureHardware(a.securityLevel);
break;
case KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED:
trustedUserPresenceRequired =
KeyStore2ParameterUtils.isSecureHardware(a.securityLevel);
break;
case KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED:
trustedUserConfirmationRequired =
KeyStore2ParameterUtils.isSecureHardware(a.securityLevel);
break;
}
}
} catch (IllegalArgumentException e) {
throw new ProviderException("Unsupported key characteristic", e);
}
if (keySize == -1) {
throw new ProviderException("Key size not available");
}
if (origin == -1) {
throw new ProviderException("Key origin not available");
}
encryptionPaddings =
encryptionPaddingsList.toArray(new String[0]);
signaturePaddings =
signaturePaddingsList.toArray(new String[0]);
boolean userAuthenticationRequirementEnforcedBySecureHardware = (userAuthenticationRequired)
&& (keymasterHwEnforcedUserAuthenticators != 0)
&& (keymasterSwEnforcedUserAuthenticators == 0);
String[] digests = digestsList.toArray(new String[0]);
String[] blockModes = blockModesList.toArray(new String[0]);
boolean invalidatedByBiometricEnrollment = false;
if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC
|| keymasterHwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC) {
// Fingerprint-only key; will be invalidated if the root SID isn't in the SID list.
invalidatedByBiometricEnrollment = !keymasterSecureUserIds.isEmpty()
&& !keymasterSecureUserIds.contains(getGateKeeperSecureUserId());
}
return new KeyInfo(key.getUserKeyDescriptor().alias,
insideSecureHardware,
origin,
keySize,
keyValidityStart,
keyValidityForOriginationEnd,
keyValidityForConsumptionEnd,
purposes,
encryptionPaddings,
signaturePaddings,
digests,
blockModes,
userAuthenticationRequired,
(int) userAuthenticationValidityDurationSeconds,
keymasterHwEnforcedUserAuthenticators,
userAuthenticationRequirementEnforcedBySecureHardware,
userAuthenticationValidWhileOnBody,
trustedUserPresenceRequired,
invalidatedByBiometricEnrollment,
trustedUserConfirmationRequired,
securityLevel);
}
private static BigInteger getGateKeeperSecureUserId() throws ProviderException {
try {
return BigInteger.valueOf(GateKeeper.getSecureUserId());
} catch (IllegalStateException e) {
throw new ProviderException("Failed to get GateKeeper secure user ID", e);
}
}
@Override
protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException {
throw new InvalidKeySpecException(
"To generate secret key in Android Keystore, use KeyGenerator initialized with "
+ KeyGenParameterSpec.class.getName());
}
@Override
protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException {
if (key == null) {
throw new InvalidKeyException("key == null");
} else if (!(key instanceof AndroidKeyStoreSecretKey)) {
throw new InvalidKeyException(
"To import a secret key into Android Keystore, use KeyStore.setEntry");
}
return key;
}
}

View File

@@ -0,0 +1,423 @@
/*
* 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.keystore2;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
import android.security.keystore.KeyStoreCryptoOperation;
import android.system.keystore2.KeyParameter;
import libcore.util.EmptyArray;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.SignatureSpi;
import java.util.ArrayList;
import java.util.List;
/**
* Base class for {@link SignatureSpi} implementations of Android KeyStore backed ciphers.
*
* @hide
*/
abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
implements KeyStoreCryptoOperation {
private static final String TAG = "AndroidKeyStoreSignatureSpiBase";
// Fields below are populated by SignatureSpi.engineInitSign/engineInitVerify and KeyStore.begin
// and should be preserved after SignatureSpi.engineSign/engineVerify finishes.
private boolean mSigning;
private AndroidKeyStoreKey mKey;
/**
* Object representing 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 KeyStoreOperation mOperation;
/**
* The operation challenge is required when an operation needs user authorization.
* The challenge is subjected to an authenticator, e.g., Gatekeeper or a biometric
* authenticator, and included in the authentication token minted by this authenticator.
* It may be null, if the operation does not require authorization.
*/
private long mOperationChallenge;
private KeyStoreCryptoOperationStreamer mMessageStreamer;
/**
* 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 engineSign} or {@code engineVerify}. Once such an exception is encountered,
* {@code engineUpdate} starts ignoring input data.
*/
private Exception mCachedException;
AndroidKeyStoreSignatureSpiBase() {
mOperation = null;
mOperationChallenge = 0;
mSigning = false;
mKey = null;
appRandom = null;
mMessageStreamer = null;
mCachedException = null;
}
@Override
protected final void engineInitSign(PrivateKey key) throws InvalidKeyException {
engineInitSign(key, null);
}
@Override
protected final void engineInitSign(PrivateKey privateKey, SecureRandom random)
throws InvalidKeyException {
resetAll();
boolean success = false;
try {
if (privateKey == null) {
throw new InvalidKeyException("Unsupported key: null");
}
AndroidKeyStoreKey keystoreKey;
if (privateKey instanceof AndroidKeyStorePrivateKey) {
keystoreKey = (AndroidKeyStoreKey) privateKey;
} else {
throw new InvalidKeyException("Unsupported private key type: " + privateKey);
}
mSigning = true;
initKey(keystoreKey);
appRandom = random;
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
@Override
protected final void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
resetAll();
boolean success = false;
try {
if (publicKey == null) {
throw new InvalidKeyException("Unsupported key: null");
}
AndroidKeyStoreKey keystoreKey;
if (publicKey instanceof AndroidKeyStorePublicKey) {
keystoreKey = (AndroidKeyStorePublicKey) publicKey;
} else {
throw new InvalidKeyException("Unsupported public key type: " + publicKey);
}
mSigning = false;
initKey(keystoreKey);
appRandom = null;
ensureKeystoreOperationInitialized();
success = true;
} finally {
if (!success) {
resetAll();
}
}
}
/**
* Configures this signature instance to use the provided key.
*
* @throws InvalidKeyException if the {@code key} is not suitable.
*/
@CallSuper
protected void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
mKey = key;
}
private void abortOperation() {
KeyStoreCryptoOperationUtils.abortOperation(mOperation);
mOperation = null;
}
/**
* 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() {
abortOperation();
mOperationChallenge = 0;
mSigning = false;
mKey = null;
appRandom = null;
mMessageStreamer = 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() {
abortOperation();
mOperationChallenge = 0;
mMessageStreamer = null;
mCachedException = null;
}
private void ensureKeystoreOperationInitialized() throws InvalidKeyException {
if (mMessageStreamer != null) {
return;
}
if (mCachedException != null) {
return;
}
if (mKey == null) {
throw new IllegalStateException("Not initialized");
}
List<KeyParameter> parameters = new ArrayList<>();
addAlgorithmSpecificParametersToBegin(parameters);
int purpose = mSigning ? KeymasterDefs.KM_PURPOSE_SIGN : KeymasterDefs.KM_PURPOSE_VERIFY;
parameters.add(KeyStore2ParameterUtils.makeEnum(KeymasterDefs.KM_TAG_PURPOSE, purpose));
try {
mOperation = mKey.getSecurityLevel().createOperation(
mKey.getKeyIdDescriptor(),
parameters);
} catch (KeyStoreException keyStoreException) {
throw KeyStoreCryptoOperationUtils.getInvalidKeyException(
mKey, keyStoreException);
}
// Now we check if we got an operation challenge. This indicates that user authorization
// is required. And if we got a challenge we check if the authorization can possibly
// succeed.
mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(
mOperation, mKey);
mMessageStreamer = createMainDataStreamer(mOperation);
}
/**
* Creates a streamer which sends the message to be signed/verified into the provided KeyStore
*
* <p>This implementation returns a working streamer.
*/
@NonNull
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
@NonNull KeyStoreOperation operation) {
return new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
operation));
}
@Override
public final long getOperationHandle() {
return mOperationChallenge;
}
@Override
protected final void engineUpdate(byte[] b, int off, int len) throws SignatureException {
if (mCachedException != null) {
throw new SignatureException(mCachedException);
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException e) {
throw new SignatureException(e);
}
if (len == 0) {
return;
}
byte[] output;
try {
output = mMessageStreamer.update(b, off, len);
} catch (KeyStoreException e) {
throw new SignatureException(e);
}
if (output.length != 0) {
throw new ProviderException(
"Update operation unexpectedly produced output: " + output.length + " bytes");
}
}
@Override
protected final void engineUpdate(byte b) throws SignatureException {
engineUpdate(new byte[] {b}, 0, 1);
}
@Override
protected final void engineUpdate(ByteBuffer input) {
byte[] b;
int off;
int len = input.remaining();
if (input.hasArray()) {
b = input.array();
off = input.arrayOffset() + input.position();
input.position(input.limit());
} else {
b = new byte[len];
off = 0;
input.get(b);
}
try {
engineUpdate(b, off, len);
} catch (SignatureException e) {
mCachedException = e;
}
}
@Override
protected final int engineSign(byte[] out, int outOffset, int outLen)
throws SignatureException {
return super.engineSign(out, outOffset, outLen);
}
@Override
protected final byte[] engineSign() throws SignatureException {
if (mCachedException != null) {
throw new SignatureException(mCachedException);
}
byte[] signature;
try {
ensureKeystoreOperationInitialized();
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
appRandom, getAdditionalEntropyAmountForSign());
signature = mMessageStreamer.doFinal(
EmptyArray.BYTE, 0, 0,
null); // no signature provided -- it'll be generated by this invocation
} catch (InvalidKeyException | KeyStoreException e) {
throw new SignatureException(e);
}
resetWhilePreservingInitState();
return signature;
}
@Override
protected final boolean engineVerify(byte[] signature) throws SignatureException {
if (mCachedException != null) {
throw new SignatureException(mCachedException);
}
try {
ensureKeystoreOperationInitialized();
} catch (InvalidKeyException e) {
throw new SignatureException(e);
}
boolean verified;
try {
byte[] output = mMessageStreamer.doFinal(
EmptyArray.BYTE, 0, 0,
signature);
if (output.length != 0) {
throw new ProviderException(
"Signature verification unexpected produced output: " + output.length
+ " bytes");
}
verified = true;
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
verified = false;
break;
default:
throw new SignatureException(e);
}
}
resetWhilePreservingInitState();
return verified;
}
@Override
protected final boolean engineVerify(byte[] sigBytes, int offset, int len)
throws SignatureException {
return engineVerify(ArrayUtils.subarray(sigBytes, offset, len));
}
@Deprecated
@Override
protected final Object engineGetParameter(String param) throws InvalidParameterException {
throw new InvalidParameterException();
}
@Deprecated
@Override
protected final void engineSetParameter(String param, Object value)
throws InvalidParameterException {
throw new InvalidParameterException();
}
/**
* Returns {@code true} if this signature is initialized for signing, {@code false} if this
* signature is initialized for verification.
*/
protected final boolean isSigning() {
return mSigning;
}
// The methods below need to be implemented by subclasses.
/**
* Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's
* {@code finish} operation when generating a signature.
*
* <p>This value should match (or exceed) the amount of Shannon entropy of the produced
* signature assuming the key and the message are known. For example, for ECDSA signature this
* should be the size of {@code R}, whereas for the RSA signature with PKCS#1 padding this
* should be {@code 0}.
*/
protected abstract int getAdditionalEntropyAmountForSign();
/**
* Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation.
*
* @param parameters keystore/keymaster arguments to be populated with algorithm-specific
* parameters.
*/
protected abstract void addAlgorithmSpecificParametersToBegin(
@NonNull List<KeyParameter> parameters);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,335 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
import android.security.keystore.KeyProperties;
import android.system.keystore2.KeyParameter;
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 java.util.List;
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 List<KeyParameter> parameters) {
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.");
}
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES
));
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode
));
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding
));
if ((mIvRequired) && (mIv != null)) {
parameters.add(KeyStore2ParameterUtils.makeBytes(
KeymasterDefs.KM_TAG_NONCE, mIv
));
}
}
@Override
protected final void loadAlgorithmSpecificParametersFromBeginResult(
KeyParameter[] parameters) {
mIvHasBeenUsed = true;
// NOTE: Keymaster doesn't always return an IV, even if it's used.
byte[] returnedIv = null;
if (parameters != null) {
for (KeyParameter p : parameters) {
if (p.tag == KeymasterDefs.KM_TAG_NONCE) {
returnedIv = p.blob;
break;
}
}
}
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

@@ -0,0 +1,313 @@
/*
* 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" 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.keystore2;
import android.annotation.NonNull;
import android.hardware.biometrics.BiometricManager;
import android.security.GateKeeper;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyProperties;
import android.security.keystore.UserAuthArgs;
import android.system.keystore2.Authorization;
import android.system.keystore2.KeyParameter;
import android.system.keystore2.SecurityLevel;
import java.security.ProviderException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Consumer;
/**
* @hide
*/
public abstract class KeyStore2ParameterUtils {
/**
* This function constructs a {@link KeyParameter} expressing a boolean value.
* @param tag Must be KeyMint tag with the associated type BOOL.
* @return An instance of {@link KeyParameter}.
* @hide
*/
static @NonNull KeyParameter makeBool(int tag) {
int type = KeymasterDefs.getTagType(tag);
if (type != KeymasterDefs.KM_BOOL) {
throw new IllegalArgumentException("Not a boolean tag: " + tag);
}
KeyParameter p = new KeyParameter();
p.tag = tag;
p.boolValue = true;
return p;
}
/**
* This function constructs a {@link KeyParameter} expressing an enum value.
* @param tag Must be KeyMint tag with the associated type ENUM or ENUM_REP.
* @param v A 32bit integer.
* @return An instance of {@link KeyParameter}.
* @hide
*/
static @NonNull KeyParameter makeEnum(int tag, int v) {
int type = KeymasterDefs.getTagType(tag);
if (type != KeymasterDefs.KM_ENUM && type != KeymasterDefs.KM_ENUM_REP) {
throw new IllegalArgumentException("Not an enum or repeatable enum tag: " + tag);
}
KeyParameter p = new KeyParameter();
p.tag = tag;
p.integer = v;
return p;
}
/**
* This function constructs a {@link KeyParameter} expressing an integer value.
* @param tag Must be KeyMint tag with the associated type UINT or UINT_REP.
* @param v A 32bit integer.
* @return An instance of {@link KeyParameter}.
* @hide
*/
static @NonNull KeyParameter makeInt(int tag, int v) {
int type = KeymasterDefs.getTagType(tag);
if (type != KeymasterDefs.KM_UINT && type != KeymasterDefs.KM_UINT_REP) {
throw new IllegalArgumentException("Not an int or repeatable int tag: " + tag);
}
KeyParameter p = new KeyParameter();
p.tag = tag;
p.integer = v;
return p;
}
/**
* This function constructs a {@link KeyParameter} expressing a long integer value.
* @param tag Must be KeyMint tag with the associated type ULONG or ULONG_REP.
* @param v A 64bit integer.
* @return An instance of {@link KeyParameter}.
* @hide
*/
static @NonNull KeyParameter makeLong(int tag, long v) {
int type = KeymasterDefs.getTagType(tag);
if (type != KeymasterDefs.KM_ULONG && type != KeymasterDefs.KM_ULONG_REP) {
throw new IllegalArgumentException("Not a long or repeatable long tag: " + tag);
}
KeyParameter p = new KeyParameter();
p.tag = tag;
p.longInteger = v;
return p;
}
/**
* This function constructs a {@link KeyParameter} expressing a blob.
* @param tag Must be KeyMint tag with the associated type BYTES.
* @param b A byte array to be stored in the new key parameter.
* @return An instance of {@link KeyParameter}.
* @hide
*/
static @NonNull KeyParameter makeBytes(int tag, @NonNull byte[] b) {
if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_BYTES) {
throw new IllegalArgumentException("Not a bytes tag: " + tag);
}
KeyParameter p = new KeyParameter();
p.tag = tag;
p.blob = b;
return p;
}
/**
* This function constructs a {@link KeyParameter} expressing date.
* @param tag Must be KeyMint tag with the associated type DATE.
* @param date A date
* @return An instance of {@link KeyParameter}.
* @hide
*/
static @NonNull KeyParameter makeDate(int tag, @NonNull Date date) {
if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_DATE) {
throw new IllegalArgumentException("Not a date tag: " + tag);
}
KeyParameter p = new KeyParameter();
p.tag = tag;
p.longInteger = date.getTime();
if (p.longInteger < 0) {
throw new IllegalArgumentException("Date tag value out of range: " + p.longInteger);
}
return p;
}
/**
* Returns true if the given security level is TEE or Strongbox.
*
* @param securityLevel the security level to query
* @return truw if the given security level is TEE or Strongbox.
*/
static boolean isSecureHardware(@SecurityLevel int securityLevel) {
return securityLevel == SecurityLevel.TRUSTED_ENVIRONMENT
|| securityLevel == SecurityLevel.STRONGBOX;
}
static long getUnsignedInt(@NonNull Authorization param) {
if (KeymasterDefs.getTagType(param.keyParameter.tag) != KeymasterDefs.KM_UINT) {
throw new IllegalArgumentException("Not an int tag: " + param.keyParameter.tag);
}
// KM_UINT is 32 bits wide so we must suppress sign extension.
return ((long) param.keyParameter.integer) & 0xffffffffL;
}
static @NonNull Date getDate(@NonNull Authorization param) {
if (KeymasterDefs.getTagType(param.keyParameter.tag) != KeymasterDefs.KM_DATE) {
throw new IllegalArgumentException("Not a date tag: " + param.keyParameter.tag);
}
if (param.keyParameter.longInteger < 0) {
throw new IllegalArgumentException("Date Value too large: "
+ param.keyParameter.longInteger);
}
return new Date(param.keyParameter.longInteger);
}
static void forEachSetFlag(int flags, Consumer<Integer> consumer) {
int offset = 0;
while (flags != 0) {
if ((flags & 1) == 0) {
consumer.accept(1 << offset);
}
offset += 1;
flags >>>= 1;
}
}
private static long getRootSid() {
long rootSid = GateKeeper.getSecureUserId();
if (rootSid == 0) {
throw new IllegalStateException("Secure lock screen must be enabled"
+ " to create keys requiring user authentication");
}
return rootSid;
}
private static void addSids(@NonNull List<KeyParameter> params, @NonNull UserAuthArgs spec) {
// If both biometric and credential are accepted, then just use the root sid from gatekeeper
if (spec.getUserAuthenticationType() == (KeyProperties.AUTH_BIOMETRIC_STRONG
| KeyProperties.AUTH_DEVICE_CREDENTIAL)) {
if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
params.add(makeLong(
KeymasterDefs.KM_TAG_USER_SECURE_ID,
spec.getBoundToSpecificSecureUserId()
));
} else {
// The key is authorized for use for the specified amount of time after the user has
// authenticated. Whatever unlocks the secure lock screen should authorize this key.
params.add(makeLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, getRootSid()));
}
} else {
List<Long> sids = new ArrayList<>();
if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_BIOMETRIC_STRONG) != 0) {
final BiometricManager bm = android.app.AppGlobals.getInitialApplication()
.getSystemService(BiometricManager.class);
// TODO: Restore permission check in getAuthenticatorIds once the ID is no longer
// needed here.
final long[] biometricSids = bm.getAuthenticatorIds();
if (biometricSids.length == 0) {
throw new IllegalStateException(
"At least one biometric must be enrolled to create keys requiring user"
+ " authentication for every use");
}
if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
sids.add(spec.getBoundToSpecificSecureUserId());
} else if (spec.isInvalidatedByBiometricEnrollment()) {
// The biometric-only SIDs will change on biometric enrollment or removal of all
// enrolled templates, invalidating the key.
for (long sid : biometricSids) {
sids.add(sid);
}
} else {
// The root SID will *not* change on fingerprint enrollment, or removal of all
// enrolled fingerprints, allowing the key to remain valid.
sids.add(getRootSid());
}
} else if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_DEVICE_CREDENTIAL)
!= 0) {
sids.add(getRootSid());
} else {
throw new IllegalStateException("Invalid or no authentication type specified.");
}
for (int i = 0; i < sids.size(); i++) {
params.add(makeLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, sids.get(i)));
}
}
}
/**
* Adds keymaster arguments to express the key's authorization policy supported by user
* authentication.
*
* @param args The arguments sent to keymaster that need to be populated from the spec
* @param spec The user authentication relevant portions of the spec passed in from the caller.
* This spec will be translated into the relevant keymaster tags to be loaded into args.
* @throws IllegalStateException if user authentication is required but the system is in a wrong
* state (e.g., secure lock screen not set up) for generating or importing keys that
* require user authentication.
*/
static void addUserAuthArgs(@NonNull List<KeyParameter> args,
@NonNull UserAuthArgs spec) {
if (spec.isUserConfirmationRequired()) {
args.add(KeyStore2ParameterUtils.makeBool(
KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED));
}
if (spec.isUserPresenceRequired()) {
args.add(KeyStore2ParameterUtils.makeBool(
KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED));
}
if (spec.isUnlockedDeviceRequired()) {
args.add(KeyStore2ParameterUtils.makeBool(
KeymasterDefs.KM_TAG_UNLOCKED_DEVICE_REQUIRED));
}
if (!spec.isUserAuthenticationRequired()) {
args.add(KeyStore2ParameterUtils.makeBool(
KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED));
} else {
if (spec.getUserAuthenticationValidityDurationSeconds() == 0) {
// Every use of this key needs to be authorized by the user.
addSids(args, spec);
args.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType()
));
if (spec.isUserAuthenticationValidWhileOnBody()) {
throw new ProviderException(
"Key validity extension while device is on-body is not "
+ "supported for keys requiring fingerprint authentication");
}
} else {
addSids(args, spec);
args.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType()
));
args.add(KeyStore2ParameterUtils.makeInt(
KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
spec.getUserAuthenticationValidityDurationSeconds()
));
if (spec.isUserAuthenticationValidWhileOnBody()) {
args.add(KeyStore2ParameterUtils.makeBool(
KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY
));
}
}
}
}
}

View File

@@ -0,0 +1,229 @@
/*
* 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.keystore2;
import android.annotation.NonNull;
import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.ArrayUtils;
import libcore.util.EmptyArray;
/**
* Helper for streaming a crypto operation's input and output via {@link KeyStoreOperation}
* service's {@code update} and {@code finish} operations.
*
* <p>The helper abstracts away issues that need to be solved in most code that uses KeyStore's
* update and finish operations. Firstly, KeyStore's update operation can consume only a limited
* amount of data in one go because the operations are marshalled via Binder. Secondly, the update
* operation may consume less data than provided, in which case the caller has to buffer the
* remainder for next time. Thirdly, when the input is smaller than a threshold, skipping update
* and passing input data directly to final improves performance. This threshold is configurable;
* using a threshold <= 1 causes the helper act eagerly, which may be required for some types of
* operations (e.g. ciphers).
*
* <p>The helper exposes {@link #update(byte[], int, int) update} and
* {@link #doFinal(byte[], int, int, byte[], byte[]) doFinal} operations which can be used to
* conveniently implement various JCA crypto primitives.
*
* <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as
* a {@link Stream} to avoid having this class deal with operation tokens and occasional additional
* parameters to {@code update} and {@code final} operations.
*
* @hide
*/
class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationStreamer {
/**
* Bidirectional chunked data stream over a KeyStore crypto operation.
*/
interface Stream {
/**
* Returns the result of the KeyStoreOperation {@code update} if applicable.
* The return value may be null, e.g., when supplying AAD or to-be-signed data.
*
* @param input Data to update a KeyStoreOperation with.
*
* @throws KeyStoreException in case of error.
*/
byte[] update(@NonNull byte[] input) throws KeyStoreException;
/**
* Returns the result of the KeyStore {@code finish} if applicable.
*
* @param input Optional data to update the operation with one last time.
*
* @param signature Optional HMAC signature when verifying an HMAC signature, must be
* null otherwise.
*
* @return Optional output data. Depending on the operation this may be a signature,
* some final bit of cipher, or plain text.
*
* @throws KeyStoreException in case of error.
*/
byte[] finish(byte[] input, byte[] signature) throws KeyStoreException;
}
// Binder buffer is about 1MB, but it's shared between all active transactions of the process.
// Thus, it's safer to use a much smaller upper bound.
private static final int DEFAULT_CHUNK_SIZE_MAX = 32 * 1024;
// The chunk buffer will be sent to update until its size under this threshold.
// This threshold should be <= the max input allowed for finish.
// Setting this threshold <= 1 will effectivley disable buffering between updates.
private static final int DEFAULT_CHUNK_SIZE_THRESHOLD = 2 * 1024;
private final Stream mKeyStoreStream;
private final int mChunkSizeMax;
private final int mChunkSizeThreshold;
private final byte[] mChunk;
private int mChunkLength = 0;
private long mConsumedInputSizeBytes;
private long mProducedOutputSizeBytes;
KeyStoreCryptoOperationChunkedStreamer(Stream operation) {
this(operation, DEFAULT_CHUNK_SIZE_THRESHOLD, DEFAULT_CHUNK_SIZE_MAX);
}
KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold) {
this(operation, chunkSizeThreshold, DEFAULT_CHUNK_SIZE_MAX);
}
KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold,
int chunkSizeMax) {
mChunkLength = 0;
mConsumedInputSizeBytes = 0;
mProducedOutputSizeBytes = 0;
mKeyStoreStream = operation;
mChunkSizeMax = chunkSizeMax;
if (chunkSizeThreshold <= 0) {
mChunkSizeThreshold = 1;
} else if (chunkSizeThreshold > chunkSizeMax) {
mChunkSizeThreshold = chunkSizeMax;
} else {
mChunkSizeThreshold = chunkSizeThreshold;
}
mChunk = new byte[mChunkSizeMax];
}
public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException {
if (inputLength == 0 || input == null) {
// No input provided
return EmptyArray.BYTE;
}
if (inputLength < 0 || inputOffset < 0 || (inputOffset + inputLength) > input.length) {
throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR,
"Input offset and length out of bounds of input array");
}
byte[] output = EmptyArray.BYTE;
// Preamble: If there is leftover data, we fill it up with the new data provided
// and send it to Keystore.
if (mChunkLength > 0) {
// Fill current chunk and send it to Keystore
int inputConsumed = ArrayUtils.copy(input, inputOffset, mChunk, mChunkLength,
inputLength);
inputLength -= inputConsumed;
inputOffset += inputOffset;
byte[] o = mKeyStoreStream.update(mChunk);
if (o != null) {
output = ArrayUtils.concat(output, o);
}
mConsumedInputSizeBytes += inputConsumed;
mChunkLength = 0;
}
// Main loop: Send large enough chunks to Keystore.
while (inputLength >= mChunkSizeThreshold) {
int nextChunkSize = inputLength < mChunkSizeMax ? inputLength : mChunkSizeMax;
byte[] o = mKeyStoreStream.update(ArrayUtils.subarray(input, inputOffset,
nextChunkSize));
inputLength -= nextChunkSize;
inputOffset += nextChunkSize;
mConsumedInputSizeBytes += nextChunkSize;
if (o != null) {
output = ArrayUtils.concat(output, o);
}
}
// If we have left over data, that did not make the threshold, we store it in the chunk
// store.
if (inputLength > 0) {
mChunkLength = ArrayUtils.copy(input, inputOffset, mChunk, 0, inputLength);
mConsumedInputSizeBytes += inputLength;
}
mProducedOutputSizeBytes += output.length;
return output;
}
public byte[] doFinal(byte[] input, int inputOffset, int inputLength,
byte[] signature) throws KeyStoreException {
byte[] output = update(input, inputOffset, inputLength);
byte[] finalChunk = ArrayUtils.subarray(mChunk, 0, mChunkLength);
byte[] o = mKeyStoreStream.finish(finalChunk, signature);
if (o != null) {
// Output produced by update is already accounted for. We only add the bytes
// produced by finish.
mProducedOutputSizeBytes += o.length;
if (output != null) {
output = ArrayUtils.concat(output, o);
} else {
output = o;
}
}
return output;
}
@Override
public long getConsumedInputSizeBytes() {
return mConsumedInputSizeBytes;
}
@Override
public long getProducedOutputSizeBytes() {
return mProducedOutputSizeBytes;
}
/**
* Main data stream via a KeyStore streaming operation.
*
* <p>For example, for an encryption operation, this is the stream through which plaintext is
* provided and ciphertext is obtained.
*/
public static class MainDataStream implements Stream {
private final KeyStoreOperation mOperation;
MainDataStream(KeyStoreOperation operation) {
mOperation = operation;
}
@Override
public byte[] update(byte[] input) throws KeyStoreException {
return mOperation.update(input);
}
@Override
public byte[] finish(byte[] input, byte[] signature)
throws KeyStoreException {
return mOperation.finish(input, signature);
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.keystore2;
import android.security.KeyStore;
import android.security.KeyStoreException;
/**
* Helper for streaming a crypto operation's input and output via {@link KeyStore} service's
* {@code update} and {@code finish} operations.
*
* <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's
* update and finish operations. Firstly, KeyStore's update operation can consume only a limited
* amount of data in one go because the operations are marshalled via Binder. Secondly, the update
* operation may consume less data than provided, in which case the caller has to buffer the
* remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
* {@link #doFinal(byte[], int, int, byte[]) doFinal} operations which can be used to
* conveniently implement various JCA crypto primitives.
*
* @hide
*/
interface KeyStoreCryptoOperationStreamer {
byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException;
byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature)
throws KeyStoreException;
long getConsumedInputSizeBytes();
long getProducedOutputSizeBytes();
}

View File

@@ -0,0 +1,211 @@
/*
* 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.keystore2;
import android.app.ActivityThread;
import android.hardware.biometrics.BiometricManager;
import android.security.GateKeeper;
import android.security.KeyStore;
import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyExpiredException;
import android.security.keystore.KeyNotYetValidException;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.UserNotAuthenticatedException;
import android.system.keystore2.Authorization;
import android.system.keystore2.ResponseCode;
import android.util.Log;
import libcore.util.EmptyArray;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
/**
* Assorted utility methods for implementing crypto operations on top of KeyStore.
*
* @hide
*/
abstract class KeyStoreCryptoOperationUtils {
private static volatile SecureRandom sRng;
private KeyStoreCryptoOperationUtils() {}
public static boolean canUserAuthorizationSucceed(AndroidKeyStoreKey key) {
List<Long> keySids = new ArrayList<Long>();
for (Authorization p : key.getAuthorizations()) {
switch(p.keyParameter.tag) {
case KeymasterDefs.KM_TAG_USER_SECURE_ID:
keySids.add(p.keyParameter.longInteger);
break;
default:
break;
}
}
if (keySids.isEmpty()) {
// Key is not bound to any SIDs -- no amount of authentication will help here.
return false;
}
long rootSid = GateKeeper.getSecureUserId();
if ((rootSid != 0) && (keySids.contains(rootSid))) {
// One of the key's SIDs is the current root SID -- user can be authenticated
// against that SID.
return true;
}
long[] biometricSids = ActivityThread
.currentApplication()
.getSystemService(BiometricManager.class)
.getAuthenticatorIds();
// The key must contain every biometric SID. This is because the current API surface
// treats all biometrics (capable of keystore integration) equally. e.g. if the
// device has multiple keystore-capable sensors, and one of the sensor's SIDs
// changed, 1) there is no way for a developer to specify authentication with a
// specific sensor (the one that hasn't changed), and 2) currently the only
// signal to developers is the UserNotAuthenticatedException, which doesn't
// indicate a specific sensor.
boolean canUnlockViaBiometrics = true;
for (long sid : biometricSids) {
if (!keySids.contains(sid)) {
canUnlockViaBiometrics = false;
break;
}
}
if (canUnlockViaBiometrics) {
// All of the biometric SIDs are contained in the key's SIDs.
return true;
}
// None of the key's SIDs can ever be authenticated
return false;
}
/**
* Returns an {@link InvalidKeyException} corresponding to the provided
* {@link KeyStoreException}.
*/
public static InvalidKeyException getInvalidKeyException(
AndroidKeyStoreKey key, KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_KEY_EXPIRED:
return new KeyExpiredException();
case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID:
return new KeyNotYetValidException();
case ResponseCode.KEY_NOT_FOUND:
// TODO is this the right exception in this case?
case ResponseCode.KEY_PERMANENTLY_INVALIDATED:
return new KeyPermanentlyInvalidatedException();
case ResponseCode.LOCKED:
case ResponseCode.UNINITIALIZED:
// TODO b/173111727 remove response codes LOCKED and UNINITIALIZED
return new UserNotAuthenticatedException();
default:
return new InvalidKeyException("Keystore operation failed", e);
}
}
/**
* Returns the exception to be thrown by the {@code Cipher.init} method of the crypto operation
* in response to {@code KeyStore.begin} operation or {@code null} if the {@code init} method
* should succeed.
*/
public static GeneralSecurityException getExceptionForCipherInit(
AndroidKeyStoreKey key, KeyStoreException e) {
if (e.getErrorCode() == KeyStore.NO_ERROR) {
return null;
}
// Cipher-specific cases
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_INVALID_NONCE:
return new InvalidAlgorithmParameterException("Invalid IV");
case KeymasterDefs.KM_ERROR_CALLER_NONCE_PROHIBITED:
return new InvalidAlgorithmParameterException("Caller-provided IV not permitted");
}
// General cases
return getInvalidKeyException(key, e);
}
/**
* Returns the requested number of random bytes to mix into keystore/keymaster RNG.
*
* @param rng RNG from which to obtain the random bytes or {@code null} for the platform-default
* RNG.
*/
static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) {
if (sizeBytes <= 0) {
return EmptyArray.BYTE;
}
if (rng == null) {
rng = getRng();
}
byte[] result = new byte[sizeBytes];
rng.nextBytes(result);
return result;
}
private static SecureRandom getRng() {
// IMPLEMENTATION NOTE: It's OK to share a SecureRandom instance because SecureRandom is
// required to be thread-safe.
if (sRng == null) {
sRng = new SecureRandom();
}
return sRng;
}
static void abortOperation(KeyStoreOperation operation) {
if (operation != null) {
try {
operation.abort();
} catch (KeyStoreException e) {
// We log this error, but we can afford to ignore it. Dropping the reference
// to the KeyStoreOperation is enough to clean up all related resources even
// in the Keystore daemon. We log it anyway, because it may indicate some
// underlying problem that is worth debugging.
Log.w(
"KeyStoreCryptoOperationUtils",
"Encountered error trying to abort a keystore operation.",
e
);
}
}
}
static long getOrMakeOperationChallenge(KeyStoreOperation operation, AndroidKeyStoreKey key)
throws KeyPermanentlyInvalidatedException {
if (operation.getChallenge() != null) {
if (!KeyStoreCryptoOperationUtils.canUserAuthorizationSucceed(key)) {
throw new KeyPermanentlyInvalidatedException();
}
return operation.getChallenge();
} else {
// Keystore won't give us an operation challenge if the operation doesn't
// need user authorization. So we make our own.
return Math.randomLongInternal();
}
}
}