/* * 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 execute(IKeystoreService service) throws RemoteException; } private R handleRemoteExceptionWithRetry(@NonNull CheckedRemoteRequest 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); } } } }