423 lines
17 KiB
Java
423 lines
17 KiB
Java
/*
|
|
* 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.Disabled;
|
|
import android.os.Binder;
|
|
import android.os.RemoteException;
|
|
import android.os.ServiceManager;
|
|
import android.os.ServiceSpecificException;
|
|
import android.os.StrictMode;
|
|
import android.security.keymaster.KeymasterDefs;
|
|
import android.system.keystore2.Domain;
|
|
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
|
|
@Disabled // See b/180133780
|
|
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) {
|
|
throw getKeyStoreException(e.errorCode, e.getMessage());
|
|
} 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, "", e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final String KEYSTORE2_SERVICE_NAME =
|
|
"android.system.keystore2.IKeystoreService/default";
|
|
|
|
private KeyStore2() {
|
|
mBinder = null;
|
|
}
|
|
|
|
public static KeyStore2 getInstance() {
|
|
return new KeyStore2();
|
|
}
|
|
|
|
/**
|
|
* Gets the {@link IKeystoreService} that should be started in early_hal in Android.
|
|
*
|
|
* @throws IllegalStateException if the KeystoreService is not available or has not
|
|
* been initialized when called. This is a state that should not happen and indicates
|
|
* and error somewhere in the stack or with the calling processes access permissions.
|
|
*/
|
|
@NonNull private synchronized IKeystoreService getService(boolean retryLookup) {
|
|
if (mBinder == null || retryLookup) {
|
|
mBinder = IKeystoreService.Stub.asInterface(ServiceManager
|
|
.getService(KEYSTORE2_SERVICE_NAME));
|
|
}
|
|
if (mBinder == null) {
|
|
throw new IllegalStateException(
|
|
"Could not connect to Keystore service. Keystore may have crashed or not been"
|
|
+ " initialized");
|
|
}
|
|
Binder.allowBlocking(mBinder.asBinder());
|
|
return mBinder;
|
|
}
|
|
|
|
void delete(KeyDescriptor descriptor) throws KeyStoreException {
|
|
StrictMode.noteDiskWrite();
|
|
|
|
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 {
|
|
StrictMode.noteDiskRead();
|
|
|
|
return handleRemoteExceptionWithRetry((service) -> service.listEntries(domain, namespace));
|
|
}
|
|
|
|
/**
|
|
* List all entries in the keystore for in the given namespace.
|
|
*/
|
|
public KeyDescriptor[] listBatch(int domain, long namespace, String startPastAlias)
|
|
throws KeyStoreException {
|
|
StrictMode.noteDiskRead();
|
|
|
|
return handleRemoteExceptionWithRetry(
|
|
(service) -> service.listEntriesBatched(domain, namespace, startPastAlias));
|
|
}
|
|
|
|
/**
|
|
* Grant string prefix as used by the keystore boringssl engine. Must be kept in sync
|
|
* with system/security/keystore-engine. Note: The prefix here includes the 0x which
|
|
* std::stringstream used in keystore-engine needs to identify the number as hex represented.
|
|
* Here we include it in the prefix, because Long#parseUnsignedLong does not understand it
|
|
* and gets the radix as explicit argument.
|
|
* @hide
|
|
*/
|
|
private static final String KEYSTORE_ENGINE_GRANT_ALIAS_PREFIX =
|
|
"ks2_keystore-engine_grant_id:0x";
|
|
|
|
/**
|
|
* This function turns a grant identifier into a specific string that is understood by the
|
|
* keystore-engine in system/security/keystore-engine. Is only used by VPN and WI-FI components
|
|
* to allow certain system components like racoon or vendor components like WPA supplicant
|
|
* to use keystore keys with boring ssl.
|
|
*
|
|
* @param grantId the grant id as returned by {@link #grant} in the {@code nspace} filed of
|
|
* the resulting {@code KeyDescriptor}.
|
|
* @return The grant descriptor string.
|
|
* @hide
|
|
*/
|
|
public static String makeKeystoreEngineGrantString(long grantId) {
|
|
return String.format("%s%016X", KEYSTORE_ENGINE_GRANT_ALIAS_PREFIX, grantId);
|
|
}
|
|
|
|
/**
|
|
* Convenience function to turn a keystore engine grant string as returned by
|
|
* {@link #makeKeystoreEngineGrantString(long)} back into a grant KeyDescriptor.
|
|
*
|
|
* @param grantString As string returned by {@link #makeKeystoreEngineGrantString(long)}
|
|
* @return The grant key descriptor.
|
|
* @hide
|
|
*/
|
|
public static KeyDescriptor keystoreEngineGrantString2KeyDescriptor(String grantString) {
|
|
KeyDescriptor key = new KeyDescriptor();
|
|
key.domain = Domain.GRANT;
|
|
key.nspace = Long.parseUnsignedLong(
|
|
grantString.substring(KEYSTORE_ENGINE_GRANT_ALIAS_PREFIX.length()), 16);
|
|
key.alias = null;
|
|
key.blob = null;
|
|
return key;
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
StrictMode.noteDiskWrite();
|
|
|
|
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 {
|
|
StrictMode.noteDiskWrite();
|
|
|
|
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 {
|
|
StrictMode.noteDiskRead();
|
|
|
|
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 {
|
|
StrictMode.noteDiskWrite();
|
|
|
|
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 {
|
|
StrictMode.noteDiskWrite();
|
|
|
|
handleRemoteExceptionWithRetry((service) -> {
|
|
service.deleteKey(descriptor);
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns the number of Keystore entries for a given domain and namespace.
|
|
*/
|
|
public int getNumberOfEntries(int domain, long namespace) throws KeyStoreException {
|
|
StrictMode.noteDiskRead();
|
|
|
|
return handleRemoteExceptionWithRetry((service)
|
|
-> service.getNumberOfEntries(domain, namespace));
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
|
|
static KeyStoreException getKeyStoreException(int errorCode, String serviceErrorMessage) {
|
|
if (errorCode > 0) {
|
|
// KeyStore layer error
|
|
switch (errorCode) {
|
|
case ResponseCode.LOCKED:
|
|
return new KeyStoreException(errorCode, "User authentication required",
|
|
serviceErrorMessage);
|
|
case ResponseCode.UNINITIALIZED:
|
|
return new KeyStoreException(errorCode, "Keystore not initialized",
|
|
serviceErrorMessage);
|
|
case ResponseCode.SYSTEM_ERROR:
|
|
return new KeyStoreException(errorCode, "System error", serviceErrorMessage);
|
|
case ResponseCode.PERMISSION_DENIED:
|
|
return new KeyStoreException(errorCode, "Permission denied",
|
|
serviceErrorMessage);
|
|
case ResponseCode.KEY_NOT_FOUND:
|
|
return new KeyStoreException(errorCode, "Key not found", serviceErrorMessage);
|
|
case ResponseCode.VALUE_CORRUPTED:
|
|
return new KeyStoreException(errorCode, "Key blob corrupted",
|
|
serviceErrorMessage);
|
|
case ResponseCode.KEY_PERMANENTLY_INVALIDATED:
|
|
return new KeyStoreException(errorCode, "Key permanently invalidated",
|
|
serviceErrorMessage);
|
|
case ResponseCode.OUT_OF_KEYS:
|
|
// Getting a more specific RKP status requires the security level, which we
|
|
// don't have here. Higher layers of the stack can interpret this exception
|
|
// and add more flavor.
|
|
return new KeyStoreException(errorCode, serviceErrorMessage,
|
|
KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE);
|
|
default:
|
|
return new KeyStoreException(errorCode, String.valueOf(errorCode),
|
|
serviceErrorMessage);
|
|
}
|
|
} else {
|
|
// Keymaster layer error
|
|
switch (errorCode) {
|
|
case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT:
|
|
// The name of this parameter significantly differs between Keymaster and
|
|
// framework APIs. Use the framework wording to make life easier for developers.
|
|
return new KeyStoreException(errorCode,
|
|
"Invalid user authentication validity duration",
|
|
serviceErrorMessage);
|
|
default:
|
|
return new KeyStoreException(errorCode,
|
|
KeymasterDefs.getErrorMessage(errorCode),
|
|
serviceErrorMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|