/*
* Copyright (C) 2009 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.app.ActivityThread;
import android.app.Application;
import android.app.KeyguardManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.hardware.biometrics.BiometricManager;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterBlob;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
import android.security.keystore.IKeystoreService;
import android.security.keystore.KeyExpiredException;
import android.security.keystore.KeyNotYetValidException;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeystoreResponse;
import android.security.keystore.UserNotAuthenticatedException;
import android.util.Log;
import com.android.org.bouncycastle.asn1.ASN1InputStream;
import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import sun.security.util.ObjectIdentifier;
import sun.security.x509.AlgorithmId;
/**
* @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 KeyStore {
private static final String TAG = "KeyStore";
// ResponseCodes - see system/security/keystore/include/keystore/keystore.h
@UnsupportedAppUsage
public static final int NO_ERROR = 1;
public static final int LOCKED = 2;
public static final int UNINITIALIZED = 3;
public static final int SYSTEM_ERROR = 4;
public static final int PROTOCOL_ERROR = 5;
public static final int PERMISSION_DENIED = 6;
public static final int KEY_NOT_FOUND = 7;
public static final int VALUE_CORRUPTED = 8;
public static final int UNDEFINED_ACTION = 9;
public static final int WRONG_PASSWORD = 10;
public static final int KEY_ALREADY_EXISTS = 16;
public static final int CANNOT_ATTEST_IDS = -66;
public static final int HARDWARE_TYPE_UNAVAILABLE = -68;
/**
* Per operation authentication is needed before this operation is valid.
* This is returned from {@link #begin} when begin succeeds but the operation uses
* per-operation authentication and must authenticate before calling {@link #update} or
* {@link #finish}.
*/
public static final int OP_AUTH_NEEDED = 15;
// Used when a user changes their pin, invalidating old auth bound keys.
public static final int KEY_PERMANENTLY_INVALIDATED = 17;
// Used for UID field to indicate the calling UID.
public static final int UID_SELF = -1;
// Flags for "put" "import" and "generate"
public static final int FLAG_NONE = 0;
/**
* Indicates that this key (or key pair) must be encrypted at rest. This will protect the key
* (or key pair) with the secure lock screen credential (e.g., password, PIN, or pattern).
*
*
Note that this requires that the secure lock screen (e.g., password, PIN, pattern) is set
* up, otherwise key (or key pair) generation or import will fail. Moreover, this key (or key
* pair) will be deleted when the secure lock screen is disabled or reset (e.g., by the user or
* a Device Administrator). Finally, this key (or key pair) cannot be used until the user
* unlocks the secure lock screen after boot.
*
* @see KeyguardManager#isDeviceSecure()
*/
public static final int FLAG_ENCRYPTED = 1;
/**
* Select Software keymaster device, which as of this writing is the lowest security
* level available on an android device. If neither FLAG_STRONGBOX nor FLAG_SOFTWARE is provided
* A TEE based keymaster implementation is implied.
*
* Need to be in sync with KeyStoreFlag in system/security/keystore/include/keystore/keystore.h
* For historical reasons this corresponds to the KEYSTORE_FLAG_FALLBACK flag.
*/
public static final int FLAG_SOFTWARE = 1 << 1;
/**
* A private flag that's only available to system server to indicate that this key is part of
* device encryption flow so it receives special treatment from keystore. For example this key
* will not be super encrypted, and it will be stored separately under an unique UID instead
* of the caller UID i.e. SYSTEM.
*
* Need to be in sync with KeyStoreFlag in system/security/keystore/include/keystore/keystore.h
*/
public static final int FLAG_CRITICAL_TO_DEVICE_ENCRYPTION = 1 << 3;
/**
* Select Strongbox keymaster device, which as of this writing the the highest security level
* available an android devices. If neither FLAG_STRONGBOX nor FLAG_SOFTWARE is provided
* A TEE based keymaster implementation is implied.
*
* Need to be in sync with KeyStoreFlag in system/security/keystore/include/keystore/keystore.h
*/
public static final int FLAG_STRONGBOX = 1 << 4;
// States
public enum State {
@UnsupportedAppUsage
UNLOCKED,
@UnsupportedAppUsage
LOCKED,
UNINITIALIZED
};
private int mError = NO_ERROR;
private final IKeystoreService mBinder;
private final Context mContext;
private IBinder mToken;
private KeyStore(IKeystoreService binder) {
mBinder = binder;
mContext = getApplicationContext();
}
@UnsupportedAppUsage
public static Context getApplicationContext() {
Application application = ActivityThread.currentApplication();
if (application == null) {
throw new IllegalStateException(
"Failed to obtain application Context from ActivityThread");
}
return application;
}
@UnsupportedAppUsage
public static KeyStore getInstance() {
IKeystoreService keystore = IKeystoreService.Stub.asInterface(ServiceManager
.getService("android.security.keystore"));
return new KeyStore(keystore);
}
private synchronized IBinder getToken() {
if (mToken == null) {
mToken = new Binder();
}
return mToken;
}
@UnsupportedAppUsage
public State state(int userId) {
final int ret;
try {
ret = mBinder.getState(userId);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
throw new AssertionError(e);
}
switch (ret) {
case NO_ERROR: return State.UNLOCKED;
case LOCKED: return State.LOCKED;
case UNINITIALIZED: return State.UNINITIALIZED;
default: throw new AssertionError(mError);
}
}
@UnsupportedAppUsage
public State state() {
return state(UserHandle.myUserId());
}
public boolean isUnlocked() {
return state() == State.UNLOCKED;
}
public byte[] get(String key, int uid) {
return get(key, uid, false);
}
@UnsupportedAppUsage
public byte[] get(String key) {
return get(key, UID_SELF);
}
public byte[] get(String key, int uid, boolean suppressKeyNotFoundWarning) {
try {
key = key != null ? key : "";
return mBinder.get(key, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
} catch (android.os.ServiceSpecificException e) {
if (!suppressKeyNotFoundWarning || e.errorCode != KEY_NOT_FOUND) {
Log.w(TAG, "KeyStore exception", e);
}
return null;
}
}
public byte[] get(String key, boolean suppressKeyNotFoundWarning) {
return get(key, UID_SELF, suppressKeyNotFoundWarning);
}
public boolean put(String key, byte[] value, int uid, int flags) {
return insert(key, value, uid, flags) == NO_ERROR;
}
public int insert(String key, byte[] value, int uid, int flags) {
try {
if (value == null) {
value = new byte[0];
}
int error = mBinder.insert(key, value, uid, flags);
if (error == KEY_ALREADY_EXISTS) {
mBinder.del(key, uid);
error = mBinder.insert(key, value, uid, flags);
}
return error;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
}
}
int delete2(String key, int uid) {
try {
return mBinder.del(key, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
}
}
public boolean delete(String key, int uid) {
int ret = delete2(key, uid);
return ret == NO_ERROR || ret == KEY_NOT_FOUND;
}
@UnsupportedAppUsage
public boolean delete(String key) {
return delete(key, UID_SELF);
}
public boolean contains(String key, int uid) {
try {
return mBinder.exist(key, uid) == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
}
}
public boolean contains(String key) {
return contains(key, UID_SELF);
}
/**
* List all entries in the keystore for {@code uid} starting with {@code prefix}.
*/
public String[] list(String prefix, int uid) {
try {
return mBinder.list(prefix, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
} catch (android.os.ServiceSpecificException e) {
Log.w(TAG, "KeyStore exception", e);
return null;
}
}
/**
* List uids of all keys that are auth bound to the current user.
* Only system is allowed to call this method.
*/
@UnsupportedAppUsage
public int[] listUidsOfAuthBoundKeys() {
// uids are returned as a list of strings because list of integers
// as an output parameter is not supported by aidl-cpp.
List uidsOut = new ArrayList<>();
try {
int rc = mBinder.listUidsOfAuthBoundKeys(uidsOut);
if (rc != NO_ERROR) {
Log.w(TAG, String.format("listUidsOfAuthBoundKeys failed with error code %d", rc));
return null;
}
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
} catch (android.os.ServiceSpecificException e) {
Log.w(TAG, "KeyStore exception", e);
return null;
}
// Turn list of strings into an array of uid integers.
return uidsOut.stream().mapToInt(Integer::parseInt).toArray();
}
public String[] list(String prefix) {
return list(prefix, UID_SELF);
}
/**
* Attempt to lock the keystore for {@code user}.
*
* @param userId Android user to lock.
* @return whether {@code user}'s keystore was locked.
*/
public boolean lock(int userId) {
try {
return mBinder.lock(userId) == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
}
}
public boolean lock() {
return lock(UserHandle.myUserId());
}
/**
* Attempt to unlock the keystore for {@code user} with the password {@code password}.
* This is required before keystore entries created with FLAG_ENCRYPTED can be accessed or
* created.
*
* @param userId Android user ID to operate on
* @param password user's keystore password. Should be the most recent value passed to
* {@link #onUserPasswordChanged} for the user.
*
* @return whether the keystore was unlocked.
*/
public boolean unlock(int userId, String password) {
try {
password = password != null ? password : "";
mError = mBinder.unlock(userId, password);
return mError == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
}
}
@UnsupportedAppUsage
public boolean unlock(String password) {
return unlock(UserHandle.getUserId(Process.myUid()), password);
}
/**
* Check if the keystore for {@code userId} is empty.
*/
public boolean isEmpty(int userId) {
try {
return mBinder.isEmpty(userId) != 0;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public boolean isEmpty() {
return isEmpty(UserHandle.myUserId());
}
public String grant(String key, int uid) {
try {
String grantAlias = mBinder.grant(key, uid);
if (grantAlias == "") return null;
return grantAlias;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
}
}
public boolean ungrant(String key, int uid) {
try {
return mBinder.ungrant(key, uid) == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
}
}
/**
* Returns the last modification time of the key in milliseconds since the
* epoch. Will return -1L if the key could not be found or other error.
*/
public long getmtime(String key, int uid) {
try {
final long millis = mBinder.getmtime(key, uid);
if (millis == -1L) {
return -1L;
}
return millis * 1000L;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return -1L;
}
}
public long getmtime(String key) {
return getmtime(key, UID_SELF);
}
// TODO: remove this when it's removed from Settings
public boolean isHardwareBacked() {
return isHardwareBacked("RSA");
}
public boolean isHardwareBacked(String keyType) {
try {
return mBinder.is_hardware_backed(keyType.toUpperCase(Locale.US)) == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
}
}
public boolean clearUid(int uid) {
try {
return mBinder.clear_uid(uid) == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
}
}
public int getLastError() {
return mError;
}
public boolean addRngEntropy(byte[] data, int flags) {
KeystoreResultPromise promise = new KeystoreResultPromise();
try {
mBinder.asBinder().linkToDeath(promise, 0);
int errorCode = mBinder.addRngEntropy(promise, data, flags);
if (errorCode == NO_ERROR) {
return interruptedPreservingGet(promise.getFuture()).getErrorCode() == NO_ERROR;
} else {
return false;
}
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
} catch (ExecutionException e) {
Log.e(TAG, "AddRngEntropy completed with exception", e);
return false;
} finally {
mBinder.asBinder().unlinkToDeath(promise, 0);
}
}
private class KeyCharacteristicsCallbackResult {
private KeystoreResponse keystoreResponse;
private KeyCharacteristics keyCharacteristics;
public KeyCharacteristicsCallbackResult(KeystoreResponse keystoreResponse,
KeyCharacteristics keyCharacteristics) {
this.keystoreResponse = keystoreResponse;
this.keyCharacteristics = keyCharacteristics;
}
public KeystoreResponse getKeystoreResponse() {
return keystoreResponse;
}
public void setKeystoreResponse(KeystoreResponse keystoreResponse) {
this.keystoreResponse = keystoreResponse;
}
public KeyCharacteristics getKeyCharacteristics() {
return keyCharacteristics;
}
public void setKeyCharacteristics(KeyCharacteristics keyCharacteristics) {
this.keyCharacteristics = keyCharacteristics;
}
}
private class KeyCharacteristicsPromise
extends android.security.keystore.IKeystoreKeyCharacteristicsCallback.Stub
implements IBinder.DeathRecipient {
final private CompletableFuture future =
new CompletableFuture();
@Override
public void onFinished(KeystoreResponse keystoreResponse,
KeyCharacteristics keyCharacteristics)
throws android.os.RemoteException {
future.complete(
new KeyCharacteristicsCallbackResult(keystoreResponse, keyCharacteristics));
}
public final CompletableFuture getFuture() {
return future;
}
@Override
public void binderDied() {
future.completeExceptionally(new RemoteException("Keystore died"));
}
};
private int generateKeyInternal(String alias, KeymasterArguments args, byte[] entropy, int uid,
int flags, KeyCharacteristics outCharacteristics)
throws RemoteException, ExecutionException {
KeyCharacteristicsPromise promise = new KeyCharacteristicsPromise();
int error = NO_ERROR;
KeyCharacteristicsCallbackResult result = null;
try {
mBinder.asBinder().linkToDeath(promise, 0);
error = mBinder.generateKey(promise, alias, args, entropy, uid, flags);
if (error != NO_ERROR) {
Log.e(TAG, "generateKeyInternal failed on request " + error);
return error;
}
result = interruptedPreservingGet(promise.getFuture());
} finally {
mBinder.asBinder().unlinkToDeath(promise, 0);
}
error = result.getKeystoreResponse().getErrorCode();
if (error != NO_ERROR) {
Log.e(TAG, "generateKeyInternal failed on response " + error);
return error;
}
KeyCharacteristics characteristics = result.getKeyCharacteristics();
if (characteristics == null) {
Log.e(TAG, "generateKeyInternal got empty key characteristics " + error);
return SYSTEM_ERROR;
}
outCharacteristics.shallowCopyFrom(characteristics);
return NO_ERROR;
}
public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int uid,
int flags, KeyCharacteristics outCharacteristics) {
try {
entropy = entropy != null ? entropy : new byte[0];
args = args != null ? args : new KeymasterArguments();
int error = generateKeyInternal(alias, args, entropy, uid, flags, outCharacteristics);
if (error == KEY_ALREADY_EXISTS) {
mBinder.del(alias, uid);
error = generateKeyInternal(alias, args, entropy, uid, flags, outCharacteristics);
}
return error;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
} catch (ExecutionException e) {
Log.e(TAG, "generateKey completed with exception", e);
return SYSTEM_ERROR;
}
}
public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int flags,
KeyCharacteristics outCharacteristics) {
return generateKey(alias, args, entropy, UID_SELF, flags, outCharacteristics);
}
public int getKeyCharacteristics(String alias, KeymasterBlob clientId, KeymasterBlob appId,
int uid, KeyCharacteristics outCharacteristics) {
KeyCharacteristicsPromise promise = new KeyCharacteristicsPromise();
try {
mBinder.asBinder().linkToDeath(promise, 0);
clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
int error = mBinder.getKeyCharacteristics(promise, alias, clientId, appId, uid);
if (error != NO_ERROR) return error;
KeyCharacteristicsCallbackResult result = interruptedPreservingGet(promise.getFuture());
error = result.getKeystoreResponse().getErrorCode();
if (error != NO_ERROR) return error;
KeyCharacteristics characteristics = result.getKeyCharacteristics();
if (characteristics == null) return SYSTEM_ERROR;
outCharacteristics.shallowCopyFrom(characteristics);
return NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
} catch (ExecutionException e) {
Log.e(TAG, "GetKeyCharacteristics completed with exception", e);
return SYSTEM_ERROR;
} finally {
mBinder.asBinder().unlinkToDeath(promise, 0);
}
}
public int getKeyCharacteristics(String alias, KeymasterBlob clientId, KeymasterBlob appId,
KeyCharacteristics outCharacteristics) {
return getKeyCharacteristics(alias, clientId, appId, UID_SELF, outCharacteristics);
}
private int importKeyInternal(String alias, KeymasterArguments args, int format, byte[] keyData,
int uid, int flags, KeyCharacteristics outCharacteristics)
throws RemoteException, ExecutionException {
KeyCharacteristicsPromise promise = new KeyCharacteristicsPromise();
mBinder.asBinder().linkToDeath(promise, 0);
try {
int error = mBinder.importKey(promise, alias, args, format, keyData, uid, flags);
if (error != NO_ERROR) return error;
KeyCharacteristicsCallbackResult result = interruptedPreservingGet(promise.getFuture());
error = result.getKeystoreResponse().getErrorCode();
if (error != NO_ERROR) return error;
KeyCharacteristics characteristics = result.getKeyCharacteristics();
if (characteristics == null) return SYSTEM_ERROR;
outCharacteristics.shallowCopyFrom(characteristics);
return NO_ERROR;
} finally {
mBinder.asBinder().unlinkToDeath(promise, 0);
}
}
public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData,
int uid, int flags, KeyCharacteristics outCharacteristics) {
try {
int error = importKeyInternal(alias, args, format, keyData, uid, flags,
outCharacteristics);
if (error == KEY_ALREADY_EXISTS) {
mBinder.del(alias, uid);
error = importKeyInternal(alias, args, format, keyData, uid, flags,
outCharacteristics);
}
return error;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
} catch (ExecutionException e) {
Log.e(TAG, "ImportKey completed with exception", e);
return SYSTEM_ERROR;
}
}
public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData,
int flags, KeyCharacteristics outCharacteristics) {
return importKey(alias, args, format, keyData, UID_SELF, flags, outCharacteristics);
}
private String getAlgorithmFromPKCS8(byte[] keyData) {
try {
final ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(keyData));
final PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
final String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
return new AlgorithmId(new ObjectIdentifier(algOid)).getName();
} catch (IOException e) {
Log.e(TAG, "getAlgorithmFromPKCS8 Failed to parse key data");
Log.e(TAG, Log.getStackTraceString(e));
return null;
}
}
private KeymasterArguments makeLegacyArguments(String algorithm) {
KeymasterArguments args = new KeymasterArguments();
args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM,
KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(algorithm));
args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_SIGN);
args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_VERIFY);
args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
if (algorithm.equalsIgnoreCase(KeyProperties.KEY_ALGORITHM_RSA)) {
args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_RSA_OAEP);
args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT);
args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN);
args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_RSA_PSS);
}
args.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE);
args.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_MD5);
args.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA1);
args.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_224);
args.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_256);
args.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_384);
args.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_SHA_2_512);
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, new Date(Long.MAX_VALUE));
args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, new Date(Long.MAX_VALUE));
args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, new Date(0));
return args;
}
public boolean importKey(String alias, byte[] keyData, int uid, int flags) {
String algorithm = getAlgorithmFromPKCS8(keyData);
if (algorithm == null) return false;
KeymasterArguments args = makeLegacyArguments(algorithm);
KeyCharacteristics out = new KeyCharacteristics();
int result = importKey(alias, args, KeymasterDefs.KM_KEY_FORMAT_PKCS8, keyData, uid,
flags, out);
if (result != NO_ERROR) {
Log.e(TAG, Log.getStackTraceString(
new KeyStoreException(result, "legacy key import failed")));
return false;
}
return true;
}
private int importWrappedKeyInternal(String wrappedKeyAlias, byte[] wrappedKey,
String wrappingKeyAlias,
byte[] maskingKey, KeymasterArguments args, long rootSid, long fingerprintSid,
KeyCharacteristics outCharacteristics)
throws RemoteException, ExecutionException {
KeyCharacteristicsPromise promise = new KeyCharacteristicsPromise();
mBinder.asBinder().linkToDeath(promise, 0);
try {
int error = mBinder.importWrappedKey(promise, wrappedKeyAlias, wrappedKey,
wrappingKeyAlias, maskingKey, args, rootSid, fingerprintSid);
if (error != NO_ERROR) return error;
KeyCharacteristicsCallbackResult result = interruptedPreservingGet(promise.getFuture());
error = result.getKeystoreResponse().getErrorCode();
if (error != NO_ERROR) return error;
KeyCharacteristics characteristics = result.getKeyCharacteristics();
if (characteristics == null) return SYSTEM_ERROR;
outCharacteristics.shallowCopyFrom(characteristics);
return NO_ERROR;
} finally {
mBinder.asBinder().unlinkToDeath(promise, 0);
}
}
public int importWrappedKey(String wrappedKeyAlias, byte[] wrappedKey,
String wrappingKeyAlias,
byte[] maskingKey, KeymasterArguments args, long rootSid, long fingerprintSid, int uid,
KeyCharacteristics outCharacteristics) {
// TODO b/119217337 uid parameter gets silently ignored.
try {
int error = importWrappedKeyInternal(wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
maskingKey, args, rootSid, fingerprintSid, outCharacteristics);
if (error == KEY_ALREADY_EXISTS) {
mBinder.del(wrappedKeyAlias, UID_SELF);
error = importWrappedKeyInternal(wrappedKeyAlias, wrappedKey, wrappingKeyAlias,
maskingKey, args, rootSid, fingerprintSid, outCharacteristics);
}
return error;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
} catch (ExecutionException e) {
Log.e(TAG, "ImportWrappedKey completed with exception", e);
return SYSTEM_ERROR;
}
}
private class ExportKeyPromise
extends android.security.keystore.IKeystoreExportKeyCallback.Stub
implements IBinder.DeathRecipient {
final private CompletableFuture future = new CompletableFuture();
@Override
public void onFinished(ExportResult exportKeyResult) throws android.os.RemoteException {
future.complete(exportKeyResult);
}
public final CompletableFuture getFuture() {
return future;
}
@Override
public void binderDied() {
future.completeExceptionally(new RemoteException("Keystore died"));
}
};
public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
KeymasterBlob appId, int uid) {
ExportKeyPromise promise = new ExportKeyPromise();
try {
mBinder.asBinder().linkToDeath(promise, 0);
clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
int error = mBinder.exportKey(promise, alias, format, clientId, appId, uid);
if (error == NO_ERROR) {
return interruptedPreservingGet(promise.getFuture());
} else {
return new ExportResult(error);
}
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
} catch (ExecutionException e) {
Log.e(TAG, "ExportKey completed with exception", e);
return null;
} finally {
mBinder.asBinder().unlinkToDeath(promise, 0);
}
}
public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
KeymasterBlob appId) {
return exportKey(alias, format, clientId, appId, UID_SELF);
}
private class OperationPromise
extends android.security.keystore.IKeystoreOperationResultCallback.Stub
implements IBinder.DeathRecipient {
final private CompletableFuture future = new CompletableFuture();
@Override
public void onFinished(OperationResult operationResult) throws android.os.RemoteException {
future.complete(operationResult);
}
public final CompletableFuture getFuture() {
return future;
}
@Override
public void binderDied() {
future.completeExceptionally(new RemoteException("Keystore died"));
}
};
public OperationResult begin(String alias, int purpose, boolean pruneable,
KeymasterArguments args, byte[] entropy, int uid) {
OperationPromise promise = new OperationPromise();
try {
mBinder.asBinder().linkToDeath(promise, 0);
args = args != null ? args : new KeymasterArguments();
entropy = entropy != null ? entropy : new byte[0];
int errorCode = mBinder.begin(promise, getToken(), alias, purpose, pruneable, args,
entropy, uid);
if (errorCode == NO_ERROR) {
return interruptedPreservingGet(promise.getFuture());
} else {
return new OperationResult(errorCode);
}
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
} catch (ExecutionException e) {
Log.e(TAG, "Begin completed with exception", e);
return null;
} finally {
mBinder.asBinder().unlinkToDeath(promise, 0);
}
}
public OperationResult begin(String alias, int purpose, boolean pruneable,
KeymasterArguments args, byte[] entropy) {
entropy = entropy != null ? entropy : new byte[0];
args = args != null ? args : new KeymasterArguments();
return begin(alias, purpose, pruneable, args, entropy, UID_SELF);
}
public OperationResult update(IBinder token, KeymasterArguments arguments, byte[] input) {
OperationPromise promise = new OperationPromise();
try {
mBinder.asBinder().linkToDeath(promise, 0);
arguments = arguments != null ? arguments : new KeymasterArguments();
input = input != null ? input : new byte[0];
int errorCode = mBinder.update(promise, token, arguments, input);
if (errorCode == NO_ERROR) {
return interruptedPreservingGet(promise.getFuture());
} else {
return new OperationResult(errorCode);
}
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
} catch (ExecutionException e) {
Log.e(TAG, "Update completed with exception", e);
return null;
} finally {
mBinder.asBinder().unlinkToDeath(promise, 0);
}
}
/**
* Android KeyStore finish operation.
*
* @param token Authentication token.
* @param arguments Keymaster arguments
* @param input Optional additional input data.
* @param signature Optional signature to be verified.
* @param entropy Optional additional entropy
* @return OperationResult that will indicate success or error of the operation.
*/
public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] input,
byte[] signature, byte[] entropy) {
OperationPromise promise = new OperationPromise();
try {
mBinder.asBinder().linkToDeath(promise, 0);
arguments = arguments != null ? arguments : new KeymasterArguments();
entropy = entropy != null ? entropy : new byte[0];
input = input != null ? input : new byte[0];
signature = signature != null ? signature : new byte[0];
int errorCode = mBinder.finish(promise, token, arguments, input, signature, entropy);
if (errorCode == NO_ERROR) {
return interruptedPreservingGet(promise.getFuture());
} else {
return new OperationResult(errorCode);
}
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
} catch (ExecutionException e) {
Log.e(TAG, "Finish completed with exception", e);
return null;
} finally {
mBinder.asBinder().unlinkToDeath(promise, 0);
}
}
public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature) {
return finish(token, arguments, null, signature, null);
}
private class KeystoreResultPromise
extends android.security.keystore.IKeystoreResponseCallback.Stub
implements IBinder.DeathRecipient {
final private CompletableFuture future = new CompletableFuture();
@Override
public void onFinished(KeystoreResponse keystoreResponse) throws android.os.RemoteException {
future.complete(keystoreResponse);
}
public final CompletableFuture getFuture() {
return future;
}
@Override
public void binderDied() {
future.completeExceptionally(new RemoteException("Keystore died"));
}
};
public int abort(IBinder token) {
KeystoreResultPromise promise = new KeystoreResultPromise();
try {
mBinder.asBinder().linkToDeath(promise, 0);
int errorCode = mBinder.abort(promise, token);
if (errorCode == NO_ERROR) {
return interruptedPreservingGet(promise.getFuture()).getErrorCode();
} else {
return errorCode;
}
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
} catch (ExecutionException e) {
Log.e(TAG, "Abort completed with exception", e);
return SYSTEM_ERROR;
} finally {
mBinder.asBinder().unlinkToDeath(promise, 0);
}
}
/**
* Add an authentication record to the keystore authorization table.
*
* @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster.
* @return {@code KeyStore.NO_ERROR} on success, otherwise an error value corresponding to
* a {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode.
*/
public int addAuthToken(byte[] authToken) {
try {
return mBinder.addAuthToken(authToken);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
}
}
/**
* Notify keystore that a user's password has changed.
*
* @param userId the user whose password changed.
* @param newPassword the new password or "" if the password was removed.
*/
public boolean onUserPasswordChanged(int userId, String newPassword) {
// Parcel.cpp doesn't support deserializing null strings and treats them as "". Make that
// explicit here.
if (newPassword == null) {
newPassword = "";
}
try {
return mBinder.onUserPasswordChanged(userId, newPassword) == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
}
}
/**
* Notify keystore that a user was added.
*
* @param userId the new user.
* @param parentId the parent of the new user, or -1 if the user has no parent. If parentId is
* specified then the new user's keystore will be intialized with the same secure lockscreen
* password as the parent.
*/
public void onUserAdded(int userId, int parentId) {
try {
mBinder.onUserAdded(userId, parentId);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
}
}
/**
* Notify keystore that a user was added.
*
* @param userId the new user.
*/
public void onUserAdded(int userId) {
onUserAdded(userId, -1);
}
/**
* Notify keystore that a user was removed.
*
* @param userId the removed user.
*/
public void onUserRemoved(int userId) {
try {
mBinder.onUserRemoved(userId);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
}
}
public boolean onUserPasswordChanged(String newPassword) {
return onUserPasswordChanged(UserHandle.getUserId(Process.myUid()), newPassword);
}
/**
* Notify keystore about the latest user locked state. This is to support keyguard-bound key.
*/
public void onUserLockedStateChanged(int userHandle, boolean locked) {
try {
mBinder.onKeyguardVisibilityChanged(locked, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed to update user locked state " + userHandle, e);
}
}
private class KeyAttestationCallbackResult {
private KeystoreResponse keystoreResponse;
private KeymasterCertificateChain certificateChain;
public KeyAttestationCallbackResult(KeystoreResponse keystoreResponse,
KeymasterCertificateChain certificateChain) {
this.keystoreResponse = keystoreResponse;
this.certificateChain = certificateChain;
}
public KeystoreResponse getKeystoreResponse() {
return keystoreResponse;
}
public void setKeystoreResponse(KeystoreResponse keystoreResponse) {
this.keystoreResponse = keystoreResponse;
}
public KeymasterCertificateChain getCertificateChain() {
return certificateChain;
}
public void setCertificateChain(KeymasterCertificateChain certificateChain) {
this.certificateChain = certificateChain;
}
}
private class CertificateChainPromise
extends android.security.keystore.IKeystoreCertificateChainCallback.Stub
implements IBinder.DeathRecipient {
final private CompletableFuture future = new CompletableFuture();
@Override
public void onFinished(KeystoreResponse keystoreResponse,
KeymasterCertificateChain certificateChain) throws android.os.RemoteException {
future.complete(new KeyAttestationCallbackResult(keystoreResponse, certificateChain));
}
public final CompletableFuture getFuture() {
return future;
}
@Override
public void binderDied() {
future.completeExceptionally(new RemoteException("Keystore died"));
}
};
public int attestKey(
String alias, KeymasterArguments params, KeymasterCertificateChain outChain) {
CertificateChainPromise promise = new CertificateChainPromise();
try {
mBinder.asBinder().linkToDeath(promise, 0);
if (params == null) {
params = new KeymasterArguments();
}
if (outChain == null) {
outChain = new KeymasterCertificateChain();
}
int error = mBinder.attestKey(promise, alias, params);
if (error != NO_ERROR) return error;
KeyAttestationCallbackResult result = interruptedPreservingGet(promise.getFuture());
error = result.getKeystoreResponse().getErrorCode();
if (error == NO_ERROR) {
outChain.shallowCopyFrom(result.getCertificateChain());
}
return error;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
} catch (ExecutionException e) {
Log.e(TAG, "AttestKey completed with exception", e);
return SYSTEM_ERROR;
} finally {
mBinder.asBinder().unlinkToDeath(promise, 0);
}
}
public int attestDeviceIds(KeymasterArguments params, KeymasterCertificateChain outChain) {
CertificateChainPromise promise = new CertificateChainPromise();
try {
mBinder.asBinder().linkToDeath(promise, 0);
if (params == null) {
params = new KeymasterArguments();
}
if (outChain == null) {
outChain = new KeymasterCertificateChain();
}
int error = mBinder.attestDeviceIds(promise, params);
if (error != NO_ERROR) return error;
KeyAttestationCallbackResult result = interruptedPreservingGet(promise.getFuture());
error = result.getKeystoreResponse().getErrorCode();
if (error == NO_ERROR) {
outChain.shallowCopyFrom(result.getCertificateChain());
}
return error;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return SYSTEM_ERROR;
} catch (ExecutionException e) {
Log.e(TAG, "AttestDevicdeIds completed with exception", e);
return SYSTEM_ERROR;
} finally {
mBinder.asBinder().unlinkToDeath(promise, 0);
}
}
/**
* Notify keystore that the device went off-body.
*/
public void onDeviceOffBody() {
try {
mBinder.onDeviceOffBody();
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
}
}
// Keep in sync with confirmationui/1.0/types.hal.
public static final int CONFIRMATIONUI_OK = 0;
public static final int CONFIRMATIONUI_CANCELED = 1;
public static final int CONFIRMATIONUI_ABORTED = 2;
public static final int CONFIRMATIONUI_OPERATION_PENDING = 3;
public static final int CONFIRMATIONUI_IGNORED = 4;
public static final int CONFIRMATIONUI_SYSTEM_ERROR = 5;
public static final int CONFIRMATIONUI_UNIMPLEMENTED = 6;
public static final int CONFIRMATIONUI_UNEXPECTED = 7;
public static final int CONFIRMATIONUI_UIERROR = 0x10000;
public static final int CONFIRMATIONUI_UIERROR_MISSING_GLYPH = 0x10001;
public static final int CONFIRMATIONUI_UIERROR_MESSAGE_TOO_LONG = 0x10002;
public static final int CONFIRMATIONUI_UIERROR_MALFORMED_UTF8_ENCODING = 0x10003;
/**
* Requests keystore call into the confirmationui HAL to display a prompt.
*
* @param listener the binder to use for callbacks.
* @param promptText the prompt to display.
* @param extraData extra data / nonce from application.
* @param locale the locale as a BCP 47 langauge tag.
* @param uiOptionsAsFlags the UI options to use, as flags.
* @return one of the {@code CONFIRMATIONUI_*} constants, for
* example {@code KeyStore.CONFIRMATIONUI_OK}.
*/
public int presentConfirmationPrompt(IBinder listener, String promptText, byte[] extraData,
String locale, int uiOptionsAsFlags) {
try {
return mBinder.presentConfirmationPrompt(listener, promptText, extraData, locale,
uiOptionsAsFlags);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return CONFIRMATIONUI_SYSTEM_ERROR;
}
}
/**
* Requests keystore call into the confirmationui HAL to cancel displaying a prompt.
*
* @param listener the binder passed to the {@link #presentConfirmationPrompt} method.
* @return one of the {@code CONFIRMATIONUI_*} constants, for
* example {@code KeyStore.CONFIRMATIONUI_OK}.
*/
public int cancelConfirmationPrompt(IBinder listener) {
try {
return mBinder.cancelConfirmationPrompt(listener);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return CONFIRMATIONUI_SYSTEM_ERROR;
}
}
/**
* Requests keystore to check if the confirmationui HAL is available.
*
* @return whether the confirmationUI HAL is available.
*/
public boolean isConfirmationPromptSupported() {
try {
return mBinder.isConfirmationPromptSupported();
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
}
}
/**
* Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error
* code.
*/
@UnsupportedAppUsage
public static KeyStoreException getKeyStoreException(int errorCode) {
if (errorCode > 0) {
// KeyStore layer error
switch (errorCode) {
case NO_ERROR:
return new KeyStoreException(errorCode, "OK");
case LOCKED:
return new KeyStoreException(errorCode, "User authentication required");
case UNINITIALIZED:
return new KeyStoreException(errorCode, "Keystore not initialized");
case SYSTEM_ERROR:
return new KeyStoreException(errorCode, "System error");
case PERMISSION_DENIED:
return new KeyStoreException(errorCode, "Permission denied");
case KEY_NOT_FOUND:
return new KeyStoreException(errorCode, "Key not found");
case VALUE_CORRUPTED:
return new KeyStoreException(errorCode, "Key blob corrupted");
case OP_AUTH_NEEDED:
return new KeyStoreException(errorCode, "Operation requires authorization");
case KEY_PERMANENTLY_INVALIDATED:
return new KeyStoreException(errorCode, "Key permanently invalidated");
default:
return new KeyStoreException(errorCode, String.valueOf(errorCode));
}
} 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");
default:
return new KeyStoreException(errorCode,
KeymasterDefs.getErrorMessage(errorCode));
}
}
}
/**
* Returns an {@link InvalidKeyException} corresponding to the provided
* {@link KeyStoreException}.
*/
public InvalidKeyException getInvalidKeyException(
String keystoreKeyAlias, int uid, KeyStoreException e) {
switch (e.getErrorCode()) {
case LOCKED:
return new UserNotAuthenticatedException();
case KeymasterDefs.KM_ERROR_KEY_EXPIRED:
return new KeyExpiredException();
case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID:
return new KeyNotYetValidException();
case KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED:
case OP_AUTH_NEEDED:
{
// We now need to determine whether the key/operation can become usable if user
// authentication is performed, or whether it can never become usable again.
// User authentication requirements are contained in the key's characteristics. We
// need to check whether these requirements can be be satisfied by asking the user
// to authenticate.
KeyCharacteristics keyCharacteristics = new KeyCharacteristics();
int getKeyCharacteristicsErrorCode =
getKeyCharacteristics(keystoreKeyAlias, null, null, uid,
keyCharacteristics);
if (getKeyCharacteristicsErrorCode != NO_ERROR) {
return new InvalidKeyException(
"Failed to obtained key characteristics",
getKeyStoreException(getKeyCharacteristicsErrorCode));
}
List keySids =
keyCharacteristics.getUnsignedLongs(KeymasterDefs.KM_TAG_USER_SECURE_ID);
if (keySids.isEmpty()) {
// Key is not bound to any SIDs -- no amount of authentication will help here.
return new KeyPermanentlyInvalidatedException();
}
long rootSid = GateKeeper.getSecureUserId();
if ((rootSid != 0) && (keySids.contains(KeymasterArguments.toUint64(rootSid)))) {
// One of the key's SIDs is the current root SID -- user can be authenticated
// against that SID.
return new UserNotAuthenticatedException();
}
final BiometricManager bm = mContext.getSystemService(BiometricManager.class);
long[] biometricSids = bm.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(KeymasterArguments.toUint64(sid))) {
canUnlockViaBiometrics = false;
break;
}
}
if (canUnlockViaBiometrics) {
// All of the biometric SIDs are contained in the key's SIDs.
return new UserNotAuthenticatedException();
}
// None of the key's SIDs can ever be authenticated
return new KeyPermanentlyInvalidatedException();
}
case UNINITIALIZED:
return new KeyPermanentlyInvalidatedException();
default:
return new InvalidKeyException("Keystore operation failed", e);
}
}
/**
* Returns an {@link InvalidKeyException} corresponding to the provided keystore/keymaster error
* code.
*/
public InvalidKeyException getInvalidKeyException(String keystoreKeyAlias, int uid,
int errorCode) {
return getInvalidKeyException(keystoreKeyAlias, uid, getKeyStoreException(errorCode));
}
private static R interruptedPreservingGet(CompletableFuture future)
throws ExecutionException {
boolean wasInterrupted = false;
while (true) {
try {
R result = future.get();
if (wasInterrupted) {
Thread.currentThread().interrupt();
}
return result;
} catch (InterruptedException e) {
wasInterrupted = true;
}
}
}
}