Merge changes I9731d978,I9e325782,I441a4d4d,I86a85e48,I9268fd66, ... am: aeb15e8592
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1471565 Change-Id: I3b5af42cdab9357fa4f4a0924af206f1df5488e4
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
33
keystore/java/android/security/CheckedRemoteRequest.java
Normal file
33
keystore/java/android/security/CheckedRemoteRequest.java
Normal 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;
|
||||
}
|
||||
277
keystore/java/android/security/KeyStore2.java
Normal file
277
keystore/java/android/security/KeyStore2.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
141
keystore/java/android/security/KeyStoreOperation.java
Normal file
141
keystore/java/android/security/KeyStoreOperation.java
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
217
keystore/java/android/security/KeyStoreSecurityLevel.java
Normal file
217
keystore/java/android/security/KeyStoreSecurityLevel.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
141
keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
Normal file
141
keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
1167
keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
Normal file
1167
keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user