diff options
51 files changed, 3851 insertions, 195 deletions
diff --git a/Android.mk b/Android.mk index 2a94f3a5ae32..d9e44554176f 100644 --- a/Android.mk +++ b/Android.mk @@ -199,6 +199,7 @@ LOCAL_SRC_FILES += \ core/java/android/os/INetworkActivityListener.aidl \ core/java/android/os/INetworkManagementService.aidl \ core/java/android/os/IPermissionController.aidl \ + core/java/android/os/IProcessInfoService.aidl \ core/java/android/os/IPowerManager.aidl \ core/java/android/os/IRemoteCallback.aidl \ core/java/android/os/ISchedulingPolicyService.aidl \ diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index c86fd5398dc3..c5af9921ba14 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -7,6 +7,12 @@ #define LOG_TAG "appproc" +#include <stdio.h> +#include <stdlib.h> +#include <sys/prctl.h> +#include <sys/stat.h> +#include <unistd.h> + #include <binder/IPCThreadState.h> #include <binder/ProcessState.h> #include <utils/Log.h> @@ -17,11 +23,6 @@ #include <android_runtime/AndroidRuntime.h> #include <private/android_filesystem_config.h> // for AID_SYSTEM -#include <stdlib.h> -#include <stdio.h> -#include <unistd.h> -#include <sys/prctl.h> - namespace android { static void app_usage() diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp index 197e36bd97af..84158d32ddbe 100644 --- a/cmds/idmap/scan.cpp +++ b/cmds/idmap/scan.cpp @@ -1,3 +1,6 @@ +#include <dirent.h> +#include <sys/stat.h> + #include "idmap.h" #include <UniquePtr.h> @@ -9,8 +12,6 @@ #include <utils/String16.h> #include <utils/String8.h> -#include <dirent.h> - #define NO_OVERLAY_TAG (-1000) using namespace android; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 7a636dbdb59d..c6ffef64f7e8 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -256,6 +256,9 @@ public class ActivityManager { /** @hide User operation call: given user id is the current user, can't be stopped. */ public static final int USER_OP_IS_CURRENT = -2; + /** @hide Process does not exist. */ + public static final int PROCESS_STATE_NONEXISTENT = -1; + /** @hide Process is a persistent system process. */ public static final int PROCESS_STATE_PERSISTENT = 0; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index bdd9e41f23f7..beb244bc2b8b 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -257,18 +257,21 @@ public final class ActivityThread { } } + static final class AcquiringProviderRecord { + IActivityManager.ContentProviderHolder holder; + boolean acquiring = true; + int requests = 1; + } + // The lock of mProviderMap protects the following variables. - final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap - = new ArrayMap<ProviderKey, ProviderClientRecord>(); - final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap - = new ArrayMap<IBinder, ProviderRefCount>(); - final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders - = new ArrayMap<IBinder, ProviderClientRecord>(); - final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName - = new ArrayMap<ComponentName, ProviderClientRecord>(); + final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap = new ArrayMap<>(); + final ArrayMap<ProviderKey, AcquiringProviderRecord> mAcquiringProviderMap = new ArrayMap<>(); + final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap = new ArrayMap<>(); + final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders = new ArrayMap<>(); + final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName = new ArrayMap<>(); final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners - = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>(); + = new ArrayMap<>(); final GcIdler mGcIdler = new GcIdler(); boolean mGcIdlerScheduled = false; @@ -345,7 +348,7 @@ public final class ActivityThread { } } - final class ProviderClientRecord { + static final class ProviderClientRecord { final String[] mNames; final IContentProvider mProvider; final ContentProvider mLocalProvider; @@ -4648,22 +4651,57 @@ public final class ActivityThread { public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) { - final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable); + final ProviderKey key = new ProviderKey(auth, userId); + final IContentProvider provider = acquireExistingProvider(c, key, stable); if (provider != null) { return provider; } + AcquiringProviderRecord r; + boolean first = false; + synchronized (mAcquiringProviderMap) { + r = mAcquiringProviderMap.get(key); + if (r == null) { + r = new AcquiringProviderRecord(); + mAcquiringProviderMap.put(key, r); + first = true; + } else { + r.requests++; + } + } - // There is a possible race here. Another thread may try to acquire - // the same provider at the same time. When this happens, we want to ensure - // that the first one wins. - // Note that we cannot hold the lock while acquiring and installing the - // provider since it might take a long time to run and it could also potentially - // be re-entrant in the case where the provider is in the same process. IActivityManager.ContentProviderHolder holder = null; - try { - holder = ActivityManagerNative.getDefault().getContentProvider( - getApplicationThread(), auth, userId, stable); - } catch (RemoteException ex) { + if (first) { + // Multiple threads may try to acquire the same provider at the same time. + // When this happens, we only let the first one really gets provider. + // Other threads just wait for its result. + // Note that we cannot hold the lock while acquiring and installing the + // provider since it might take a long time to run and it could also potentially + // be re-entrant in the case where the provider is in the same process. + try { + holder = ActivityManagerNative.getDefault().getContentProvider( + getApplicationThread(), auth, userId, stable); + } catch (RemoteException ex) { + } + synchronized (r) { + r.holder = holder; + r.acquiring = false; + r.notifyAll(); + } + } else { + synchronized (r) { + while (r.acquiring) { + try { + r.wait(); + } catch (InterruptedException e) { + } + } + holder = r.holder; + } + } + synchronized (mAcquiringProviderMap) { + if (--r.requests == 0) { + mAcquiringProviderMap.remove(key); + } } if (holder == null) { Slog.e(TAG, "Failed to find provider info for " + auth); @@ -4747,8 +4785,12 @@ public final class ActivityThread { public final IContentProvider acquireExistingProvider( Context c, String auth, int userId, boolean stable) { + return acquireExistingProvider(c, new ProviderKey(auth, userId), stable); + } + + final IContentProvider acquireExistingProvider( + Context c, ProviderKey key, boolean stable) { synchronized (mProviderMap) { - final ProviderKey key = new ProviderKey(auth, userId); final ProviderClientRecord pr = mProviderMap.get(key); if (pr == null) { return null; @@ -4759,7 +4801,7 @@ public final class ActivityThread { if (!jBinder.isBinderAlive()) { // The hosting process of the provider has died; we can't // use this one. - Log.i(TAG, "Acquiring provider " + auth + " for user " + userId + Log.i(TAG, "Acquiring provider " + key.authority + " for user " + key.userId + ": existing object's process dead"); handleUnstableProviderDiedLocked(jBinder, true); return null; @@ -5081,18 +5123,12 @@ public final class ActivityThread { if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, updating ref count"); } - // We need to transfer our new reference to the existing - // ref count, releasing the old one... but only if - // release is needed (that is, it is not running in the - // system process). + // The provider has already been installed, so we need + // to increase reference count to the existing one, but + // only if release is needed (that is, it is not running + // in the system process or local to the process). if (!noReleaseNeeded) { incProviderRefLocked(prc, stable); - try { - ActivityManagerNative.getDefault().removeContentProvider( - holder.connection, stable); - } catch (RemoteException e) { - //do nothing content provider object is dead any way - } } } else { ProviderClientRecord client = installProviderAuthoritiesLocked( diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 2099c3f9b02f..fb2f445002ee 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -384,6 +384,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } } return builder.toString(); + } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https") + || scheme.equalsIgnoreCase("ftp")) { + ssp = "//" + ((getHost() != null) ? getHost() : "") + + ((getPort() != -1) ? (":" + getPort()) : "") + + "/..."; } } // Not a sensitive scheme, but let's still be conservative about diff --git a/core/java/android/os/IProcessInfoService.aidl b/core/java/android/os/IProcessInfoService.aidl new file mode 100644 index 000000000000..c98daa282ec4 --- /dev/null +++ b/core/java/android/os/IProcessInfoService.aidl @@ -0,0 +1,29 @@ +/* + * Copyright 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.os; + +/** {@hide} */ +interface IProcessInfoService +{ + /** + * For each PID in the given input array, write the current process state + * for that process into the output array, or ActivityManager.PROCESS_STATE_NONEXISTENT + * to indicate that no process with the given PID exists. + */ + void getProcessStatesFromPids(in int[] pids, out int[] states); +} + diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index 0224c7310a09..d989cd1295b4 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -1426,7 +1426,7 @@ public class Preference implements Comparable<Preference> { protected boolean persistString(String value) { if (shouldPersist()) { // Shouldn't store null - if (value == getPersistedString(null)) { + if (TextUtils.equals(value, getPersistedString(null))) { // It's already there, so the same as persisting return true; } diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl index d24bc13a2a66..579cdbeb40f4 100644 --- a/core/java/android/security/IKeystoreService.aidl +++ b/core/java/android/security/IKeystoreService.aidl @@ -60,8 +60,8 @@ interface IKeystoreService { // Keymaster 0.4 methods int addRngEntropy(in byte[] data); - int generateKey(String alias, in KeymasterArguments arguments, int uid, int flags, - out KeyCharacteristics characteristics); + int generateKey(String alias, in KeymasterArguments arguments, in byte[] entropy, int uid, + int flags, out KeyCharacteristics characteristics); int getKeyCharacteristics(String alias, in KeymasterBlob clientId, in KeymasterBlob appId, out KeyCharacteristics characteristics); int importKey(String alias, in KeymasterArguments arguments, int format, @@ -69,8 +69,10 @@ interface IKeystoreService { ExportResult exportKey(String alias, int format, in KeymasterBlob clientId, in KeymasterBlob appId); OperationResult begin(IBinder appToken, String alias, int purpose, boolean pruneable, - in KeymasterArguments params, out KeymasterArguments operationParams); + in KeymasterArguments params, in byte[] entropy, out KeymasterArguments operationParams); OperationResult update(IBinder token, in KeymasterArguments params, in byte[] input); OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature); int abort(IBinder handle); + boolean isOperationAuthorized(IBinder token); + int addAuthToken(in byte[] authToken); } diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index e653b74cd9fd..e94a31222ed2 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -16,6 +16,9 @@ package android.security.keymaster; +import java.util.HashMap; +import java.util.Map; + /** * Class tracking all the keymaster enum values needed for the binder API to keystore. * This must be kept in sync with hardware/libhardware/include/hardware/keymaster_defs.h @@ -178,7 +181,7 @@ public final class KeymasterDefs { public static final int KM_ERROR_UNSUPPORTED_KEY_SIZE = -6; public static final int KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7; public static final int KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8; - public static final int KM_ERROR_UNSUPPORTED_TAG_LENGTH = -9; + public static final int KM_ERROR_UNSUPPORTED_MAC_LENGTH = -9; public static final int KM_ERROR_UNSUPPORTED_PADDING_MODE = -10; public static final int KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11; public static final int KM_ERROR_UNSUPPORTED_DIGEST = -12; @@ -224,7 +227,54 @@ public final class KeymasterDefs { public static final int KM_ERROR_VERSION_MISMATCH = -101; public static final int KM_ERROR_UNKNOWN_ERROR = -1000; + public static final Map<Integer, String> sErrorCodeToString = new HashMap<Integer, String>(); + static { + sErrorCodeToString.put(KM_ERROR_OK, "OK"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PURPOSE, "Unsupported purpose"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PURPOSE, "Incompatible purpose"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_ALGORITHM, "Unsupported algorithm"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_ALGORITHM, "Incompatible algorithm"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_SIZE, "Unsupported key size"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_BLOCK_MODE, "Unsupported block mode"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_BLOCK_MODE, "Incompatible block mode"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_MAC_LENGTH, + "Unsupported MAC or authentication tag length"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PADDING_MODE, "Unsupported padding mode"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PADDING_MODE, "Incompatible padding mode"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_DIGEST, "Unsupported digest"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_DIGEST, "Incompatible digest"); + sErrorCodeToString.put(KM_ERROR_INVALID_EXPIRATION_TIME, "Invalid expiration time"); + sErrorCodeToString.put(KM_ERROR_INVALID_USER_ID, "Invalid user ID"); + sErrorCodeToString.put(KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT, + "Invalid user authorization timeout"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_FORMAT, "Unsupported key format"); + sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_KEY_FORMAT, "Incompatible key format"); + sErrorCodeToString.put(KM_ERROR_INVALID_INPUT_LENGTH, "Invalid input length"); + sErrorCodeToString.put(KM_ERROR_KEY_NOT_YET_VALID, "Key not yet valid"); + sErrorCodeToString.put(KM_ERROR_KEY_EXPIRED, "Key expired"); + sErrorCodeToString.put(KM_ERROR_KEY_USER_NOT_AUTHENTICATED, "Key user not authenticated"); + sErrorCodeToString.put(KM_ERROR_INVALID_OPERATION_HANDLE, "Invalid operation handle"); + sErrorCodeToString.put(KM_ERROR_VERIFICATION_FAILED, "Signature/MAC verification failed"); + sErrorCodeToString.put(KM_ERROR_TOO_MANY_OPERATIONS, "Too many operations"); + sErrorCodeToString.put(KM_ERROR_INVALID_KEY_BLOB, "Invalid key blob"); + sErrorCodeToString.put(KM_ERROR_INVALID_ARGUMENT, "Invalid argument"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_TAG, "Unsupported tag"); + sErrorCodeToString.put(KM_ERROR_INVALID_TAG, "Invalid tag"); + sErrorCodeToString.put(KM_ERROR_MEMORY_ALLOCATION_FAILED, "Memory allocation failed"); + sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_EC_FIELD, "Unsupported EC field"); + sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented"); + sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error"); + } + public static int getTagType(int tag) { return tag & (0xF << 28); } + + public static String getErrorMessage(int errorCode) { + String result = sErrorCodeToString.get(errorCode); + if (result != null) { + return result; + } + return String.valueOf(errorCode); + } } diff --git a/core/java/android/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java index ad54c96cc0f8..7cc43d39c687 100644 --- a/core/java/android/security/keymaster/OperationResult.java +++ b/core/java/android/security/keymaster/OperationResult.java @@ -30,6 +30,7 @@ import java.util.List; public class OperationResult implements Parcelable { public final int resultCode; public final IBinder token; + public final long operationHandle; public final int inputConsumed; public final byte[] output; @@ -47,6 +48,7 @@ public class OperationResult implements Parcelable { protected OperationResult(Parcel in) { resultCode = in.readInt(); token = in.readStrongBinder(); + operationHandle = in.readLong(); inputConsumed = in.readInt(); output = in.createByteArray(); } @@ -60,6 +62,7 @@ public class OperationResult implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeInt(resultCode); out.writeStrongBinder(token); + out.writeLong(operationHandle); out.writeInt(inputConsumed); out.writeByteArray(output); } diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 749f8138e9ad..d7512664da45 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -436,11 +436,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { Request removeRequest(IBinder reqInterface) { synchronized (this) { - Request req = mActiveRequests.get(reqInterface); - if (req != null) { - mActiveRequests.remove(req); - } - return req; + return mActiveRequests.remove(reqInterface); } } diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 50e64c6ea6fb..a237afd3d500 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -1005,31 +1005,23 @@ public class ViewDebug { return fields; } - final ArrayList<Field> declaredFields = new ArrayList(); - klass.getDeclaredFieldsUnchecked(false, declaredFields); - - final ArrayList<Field> foundFields = new ArrayList<Field>(); - final int count = declaredFields.size(); - for (int i = 0; i < count; i++) { - final Field field = declaredFields.get(i); - - // Ensure the field type can be resolved. - try { - field.getType(); - } catch (NoClassDefFoundError e) { - continue; - } - - if (field.isAnnotationPresent(ExportedProperty.class)) { - field.setAccessible(true); - foundFields.add(field); - sAnnotations.put(field, field.getAnnotation(ExportedProperty.class)); + try { + final Field[] declaredFields = klass.getDeclaredFieldsUnchecked(false); + final ArrayList<Field> foundFields = new ArrayList<Field>(); + for (final Field field : declaredFields) { + // Fields which can't be resolved have a null type. + if (field.getType() != null && field.isAnnotationPresent(ExportedProperty.class)) { + field.setAccessible(true); + foundFields.add(field); + sAnnotations.put(field, field.getAnnotation(ExportedProperty.class)); + } } + fields = foundFields.toArray(new Field[foundFields.size()]); + map.put(klass, fields); + } catch (NoClassDefFoundError e) { + throw new AssertionError(e); } - fields = foundFields.toArray(new Field[foundFields.size()]); - map.put(klass, fields); - return fields; } @@ -1651,4 +1643,4 @@ public class ViewDebug { } }); } -}
\ No newline at end of file +} diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 56bdb9ba4b0e..5eaf20c54f11 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -817,12 +817,12 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback mContext = context; mIntent = intent; - mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1); - - mLayoutInflater = LayoutInflater.from(context); if (mIntent == null) { throw new IllegalArgumentException("Non-null Intent must be specified."); } + + mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1); + mLayoutInflater = LayoutInflater.from(context); mRequestedViews = new RemoteViewsFrameLayoutRefSet(); // Strip the previously injected app widget id from service intent diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index ce50d965118f..b44adcb2a8ef 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -552,12 +552,15 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) char gctypeOptsBuf[sizeof("-Xgc:")-1 + PROPERTY_VALUE_MAX]; char backgroundgcOptsBuf[sizeof("-XX:BackgroundGC=")-1 + PROPERTY_VALUE_MAX]; char heaptargetutilizationOptsBuf[sizeof("-XX:HeapTargetUtilization=")-1 + PROPERTY_VALUE_MAX]; + char cachePruneBuf[sizeof("-Xzygote-max-boot-retry=")-1 + PROPERTY_VALUE_MAX]; char dex2oatXmsImageFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX]; char dex2oatXmxImageFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX]; char dex2oatXmsFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX]; char dex2oatXmxFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX]; char dex2oatCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX]; char dex2oatImageCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX]; + char dex2oatThreadsBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX]; + char dex2oatThreadsImageBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX]; char dex2oatFlagsBuf[PROPERTY_VALUE_MAX]; char dex2oatImageFlagsBuf[PROPERTY_VALUE_MAX]; char extraOptsBuf[PROPERTY_VALUE_MAX]; @@ -732,6 +735,9 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) parseCompilerOption("dalvik.vm.dex2oat-filter", dex2oatCompilerFilterBuf, "--compiler-filter=", "-Xcompiler-option"); } + parseCompilerOption("dalvik.vm.dex2oat-threads", dex2oatThreadsBuf, "-j", "-Xcompiler-option"); + parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j", + "-Ximage-compiler-option"); property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, ""); parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option"); @@ -810,6 +816,10 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) addOption(nativeBridgeLibrary); } + // Dalvik-cache pruning counter. + parseRuntimeOption("dalvik.vm.zygote.max-boot-retry", cachePruneBuf, + "-Xzygote-max-boot-retry="); + initArgs.version = JNI_VERSION_1_4; initArgs.options = mOptions.editArray(); initArgs.nOptions = mOptions.size(); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ccdb5db3d647..0ded6d3299ad 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3094,9 +3094,9 @@ </intent-filter> </receiver> - <receiver android:name="com.android.server.updates.TZInfoInstallReceiver" > + <receiver android:name="com.android.server.updates.TzDataInstallReceiver" > <intent-filter> - <action android:name="android.intent.action.UPDATE_TZINFO" /> + <action android:name="android.intent.action.UPDATE_TZDATA" /> <data android:scheme="content" android:host="*" android:mimeType="*/*" /> </intent-filter> </receiver> diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index cd4501788be3..6fa28b1ccdaa 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -804,4 +804,56 @@ public class UriTest extends TestCase { assertFalse(Uri.parse("content://com.example/path/path").isPathPrefixMatch( Uri.parse("content://com.example/path%2Fpath"))); } + + public void testToSafeString() { + checkToSafeString("tel:xxxxxx", "tel:Google"); + checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890"); + checkToSafeString("tEl:xxx.xxx-xxxx", "tEl:123.456-7890"); + + checkToSafeString("sms:xxxxxx", "sms:123abc"); + checkToSafeString("smS:xxx.xxx-xxxx", "smS:123.456-7890"); + + checkToSafeString("smsto:xxxxxx", "smsto:123abc"); + checkToSafeString("SMSTo:xxx.xxx-xxxx", "SMSTo:123.456-7890"); + + checkToSafeString("mailto:xxxxxxx@xxxxxxx.xxx", "mailto:android@android.com"); + checkToSafeString("Mailto:xxxxxxx@xxxxxxx.xxxxxxxxxx", + "Mailto:android@android.com/secret"); + + checkToSafeString("sip:xxxxxxx@xxxxxxx.xxxxxxxx", "sip:android@android.com:1234"); + checkToSafeString("sIp:xxxxxxx@xxxxxxx.xxx", "sIp:android@android.com"); + + checkToSafeString("http://www.android.com/...", "http://www.android.com"); + checkToSafeString("HTTP://www.android.com/...", "HTTP://www.android.com"); + checkToSafeString("http://www.android.com/...", "http://www.android.com/"); + checkToSafeString("http://www.android.com/...", "http://www.android.com/secretUrl?param"); + checkToSafeString("http://www.android.com/...", + "http://user:pwd@www.android.com/secretUrl?param"); + checkToSafeString("http://www.android.com/...", + "http://user@www.android.com/secretUrl?param"); + checkToSafeString("http://www.android.com/...", "http://www.android.com/secretUrl?param"); + checkToSafeString("http:///...", "http:///path?param"); + checkToSafeString("http:///...", "http://"); + checkToSafeString("http://:12345/...", "http://:12345/"); + + checkToSafeString("https://www.android.com/...", "https://www.android.com/secretUrl?param"); + checkToSafeString("https://www.android.com:8443/...", + "https://user:pwd@www.android.com:8443/secretUrl?param"); + checkToSafeString("https://www.android.com/...", "https://user:pwd@www.android.com"); + checkToSafeString("Https://www.android.com/...", "Https://user:pwd@www.android.com"); + + checkToSafeString("ftp://ftp.android.com/...", "ftp://ftp.android.com/"); + checkToSafeString("ftP://ftp.android.com/...", "ftP://anonymous@ftp.android.com/"); + checkToSafeString("ftp://ftp.android.com:2121/...", + "ftp://root:love@ftp.android.com:2121/"); + + checkToSafeString("unsupported://ajkakjah/askdha/secret?secret", + "unsupported://ajkakjah/askdha/secret?secret"); + checkToSafeString("unsupported:ajkakjah/askdha/secret?secret", + "unsupported:ajkakjah/askdha/secret?secret"); + } + + private void checkToSafeString(String expectedSafeString, String original) { + assertEquals(expectedSafeString, Uri.parse(original).toSafeString()); + } } diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java index f3eb317eb0fd..dcc79be5eaa9 100644 --- a/keystore/java/android/security/AndroidKeyStore.java +++ b/keystore/java/android/security/AndroidKeyStore.java @@ -457,7 +457,7 @@ public class AndroidKeyStore extends KeyStoreSpi { String keyAlgorithmString = key.getAlgorithm(); @KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm; - @KeyStoreKeyConstraints.AlgorithmEnum Integer digest; + @KeyStoreKeyConstraints.DigestEnum Integer digest; try { keyAlgorithm = KeyStoreKeyConstraints.Algorithm.fromJCASecretKeyAlgorithm(keyAlgorithmString); @@ -493,6 +493,19 @@ public class AndroidKeyStore extends KeyStoreSpi { if (digest != null) { args.addInt(KeymasterDefs.KM_TAG_DIGEST, KeyStoreKeyConstraints.Digest.toKeymaster(digest)); + Integer digestOutputSizeBytes = + KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest); + if (digestOutputSizeBytes != null) { + // TODO: Remove MAC length constraint once Keymaster API no longer requires it. + // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster + args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes); + } + } + if (keyAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) { + if (digest == null) { + throw new IllegalStateException("Digest algorithm must be specified for key" + + " algorithm " + keyAlgorithmString); + } } @KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null) @@ -523,30 +536,33 @@ public class AndroidKeyStore extends KeyStoreSpi { if (params.getUserAuthenticators().isEmpty()) { args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); } else { - // TODO: Pass-in user authenticator IDs once the Keymaster API has stabilized -// for (int userAuthenticatorId : params.getUserAuthenticators()) { -// args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_ID, userAuthenticatorId); -// } + args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, + KeyStoreKeyConstraints.UserAuthenticator.allToKeymaster( + params.getUserAuthenticators())); } if (params.getUserAuthenticationValidityDurationSeconds() != null) { args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, params.getUserAuthenticationValidityDurationSeconds()); } - if (params.getKeyValidityStart() != null) { - args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, params.getKeyValidityStart()); - } - if (params.getKeyValidityForOriginationEnd() != null) { - args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, - params.getKeyValidityForOriginationEnd()); - } - if (params.getKeyValidityForConsumptionEnd() != null) { - args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, - params.getKeyValidityForConsumptionEnd()); - } + args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, + (params.getKeyValidityStart() != null) + ? params.getKeyValidityStart() : new Date(0)); + args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + (params.getKeyValidityForOriginationEnd() != null) + ? params.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE)); + args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + (params.getKeyValidityForConsumptionEnd() != null) + ? params.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); // TODO: Remove this once keymaster does not require us to specify the size of imported key. args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8); + if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0) + || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) { + // Permit caller-specified IV. This is needed for the Cipher abstraction. + args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); + } + Credentials.deleteAllTypesForAlias(mKeyStore, entryAlias); String keyAliasInKeystore = Credentials.USER_SECRET_KEY + entryAlias; int errorCode = mKeyStore.importKey( diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java index 9081e92006b5..a7c2ddbbe037 100644 --- a/keystore/java/android/security/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/AndroidKeyStoreProvider.java @@ -16,8 +16,12 @@ package android.security; +import java.lang.reflect.Method; import java.security.Provider; +import javax.crypto.Cipher; +import javax.crypto.Mac; + /** * A provider focused on providing JCA interfaces for the Android KeyStore. * @@ -35,5 +39,78 @@ public class AndroidKeyStoreProvider extends Provider { // java.security.KeyPairGenerator put("KeyPairGenerator.EC", AndroidKeyPairGenerator.EC.class.getName()); put("KeyPairGenerator.RSA", AndroidKeyPairGenerator.RSA.class.getName()); + + // javax.crypto.KeyGenerator + put("KeyGenerator.AES", KeyStoreKeyGeneratorSpi.AES.class.getName()); + put("KeyGenerator.HmacSHA256", KeyStoreKeyGeneratorSpi.HmacSHA256.class.getName()); + + // java.security.SecretKeyFactory + put("SecretKeyFactory.AES", KeyStoreSecretKeyFactorySpi.class.getName()); + put("SecretKeyFactory.HmacSHA256", KeyStoreSecretKeyFactorySpi.class.getName()); + + // javax.crypto.Mac + putMacImpl("HmacSHA256", KeyStoreHmacSpi.HmacSHA256.class.getName()); + + // javax.crypto.Cipher + putSymmetricCipherImpl("AES/ECB/NoPadding", + KeyStoreCipherSpi.AES.ECB.NoPadding.class.getName()); + putSymmetricCipherImpl("AES/ECB/PKCS7Padding", + KeyStoreCipherSpi.AES.ECB.PKCS7Padding.class.getName()); + + putSymmetricCipherImpl("AES/CBC/NoPadding", + KeyStoreCipherSpi.AES.CBC.NoPadding.class.getName()); + putSymmetricCipherImpl("AES/CBC/PKCS7Padding", + KeyStoreCipherSpi.AES.CBC.PKCS7Padding.class.getName()); + + putSymmetricCipherImpl("AES/CTR/NoPadding", + KeyStoreCipherSpi.AES.CTR.NoPadding.class.getName()); + } + + private void putMacImpl(String algorithm, String implClass) { + put("Mac." + algorithm, implClass); + put("Mac." + algorithm + " SupportedKeyClasses", KeyStoreSecretKey.class.getName()); + } + + private void putSymmetricCipherImpl(String transformation, String implClass) { + put("Cipher." + transformation, implClass); + put("Cipher." + transformation + " SupportedKeyClasses", KeyStoreSecretKey.class.getName()); + } + + /** + * 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 null} 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. + */ + public static Long getKeyStoreOperationHandle(Object cryptoPrimitive) { + if (cryptoPrimitive == null) { + throw new NullPointerException(); + } + if ((!(cryptoPrimitive instanceof Mac)) && (!(cryptoPrimitive instanceof Cipher))) { + throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive); + } + Object spi; + // TODO: Replace this Reflection based codewith direct invocations once the libcore changes + // are in. + try { + Method getSpiMethod = cryptoPrimitive.getClass().getDeclaredMethod("getSpi"); + getSpiMethod.setAccessible(true); + spi = getSpiMethod.invoke(cryptoPrimitive); + } catch (ReflectiveOperationException e) { + throw new IllegalArgumentException( + "Unsupported crypto primitive: " + cryptoPrimitive, e); + } + if (!(spi instanceof KeyStoreCryptoOperation)) { + throw new IllegalArgumentException( + "Crypto primitive not backed by Android KeyStore: " + cryptoPrimitive + + ", spi: " + spi); + } + return ((KeyStoreCryptoOperation) spi).getOperationHandle(); } } diff --git a/keystore/java/android/security/CryptoOperationException.java b/keystore/java/android/security/CryptoOperationException.java new file mode 100644 index 000000000000..00c142fbec9d --- /dev/null +++ b/keystore/java/android/security/CryptoOperationException.java @@ -0,0 +1,61 @@ +/* + * 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; + +/** + * Base class for exceptions during cryptographic operations which cannot throw a suitable checked + * exception. + * + * <p>The contract of the majority of crypto primitives/operations (e.g. {@code Cipher} or + * {@code Signature}) is that they can throw a checked exception during initialization, but are not + * permitted to throw a checked exception during operation. Because crypto operations can fail + * for a variety of reasons after initialization, this base class provides type-safety for unchecked + * exceptions that may be thrown in those cases. + * + * @hide + */ +public class CryptoOperationException extends RuntimeException { + + /** + * Constructs a new {@code CryptoOperationException} without detail message and cause. + */ + public CryptoOperationException() { + super(); + } + + /** + * Constructs a new {@code CryptoOperationException} with the provided detail message and no + * cause. + */ + public CryptoOperationException(String message) { + super(message); + } + + /** + * Constructs a new {@code CryptoOperationException} with the provided detail message and cause. + */ + public CryptoOperationException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new {@code CryptoOperationException} with the provided cause. + */ + public CryptoOperationException(Throwable cause) { + super(cause); + } +} diff --git a/keystore/java/android/security/KeyExpiredException.java b/keystore/java/android/security/KeyExpiredException.java new file mode 100644 index 000000000000..35a5accbc94c --- /dev/null +++ b/keystore/java/android/security/KeyExpiredException.java @@ -0,0 +1,47 @@ +/* + * 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; + +/** + * Indicates that a cryptographic operation failed because the employed key's validity end date + * is in the past. + * + * @hide + */ +public class KeyExpiredException extends CryptoOperationException { + + /** + * Constructs a new {@code KeyExpiredException} without detail message and cause. + */ + public KeyExpiredException() { + super("Key expired"); + } + + /** + * Constructs a new {@code KeyExpiredException} with the provided detail message and no cause. + */ + public KeyExpiredException(String message) { + super(message); + } + + /** + * Constructs a new {@code KeyExpiredException} with the provided detail message and cause. + */ + public KeyExpiredException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java new file mode 100644 index 000000000000..13113685f808 --- /dev/null +++ b/keystore/java/android/security/KeyGeneratorSpec.java @@ -0,0 +1,487 @@ +/* + * 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; + +import android.content.Context; +import android.text.TextUtils; + +import java.security.cert.Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +/** + * {@link AlgorithmParameterSpec} for initializing a {@code KeyGenerator} that works with + * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore facility</a>. + * + * <p>The Android KeyStore facility is accessed through a {@link KeyGenerator} API + * using the {@code AndroidKeyStore} provider. The {@code context} passed in may be used to pop up + * some UI to ask the user to unlock or initialize the Android KeyStore facility. + * + * <p>After generation, the {@code keyStoreAlias} is used with the + * {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter)} + * interface to retrieve the {@link SecretKey} and its associated {@link Certificate} chain. + * + * @hide + */ +public class KeyGeneratorSpec implements AlgorithmParameterSpec { + + private final Context mContext; + private final String mKeystoreAlias; + private final int mFlags; + private final Integer mKeySize; + private final Date mKeyValidityStart; + private final Date mKeyValidityForOriginationEnd; + private final Date mKeyValidityForConsumptionEnd; + private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes; + private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding; + private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode; + private final Integer mMinSecondsBetweenOperations; + private final Integer mMaxUsesPerBoot; + private final Set<Integer> mUserAuthenticators; + private final Integer mUserAuthenticationValidityDurationSeconds; + + private KeyGeneratorSpec( + Context context, + String keyStoreAlias, + int flags, + Integer keySize, + Date keyValidityStart, + Date keyValidityForOriginationEnd, + Date keyValidityForConsumptionEnd, + @KeyStoreKeyConstraints.PurposeEnum Integer purposes, + @KeyStoreKeyConstraints.PaddingEnum Integer padding, + @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode, + Integer minSecondsBetweenOperations, + Integer maxUsesPerBoot, + Set<Integer> userAuthenticators, + Integer userAuthenticationValidityDurationSeconds) { + if (context == null) { + throw new IllegalArgumentException("context == null"); + } else if (TextUtils.isEmpty(keyStoreAlias)) { + throw new IllegalArgumentException("keyStoreAlias must not be empty"); + } else if ((userAuthenticationValidityDurationSeconds != null) + && (userAuthenticationValidityDurationSeconds < 0)) { + throw new IllegalArgumentException( + "userAuthenticationValidityDurationSeconds must not be negative"); + } + + mContext = context; + mKeystoreAlias = keyStoreAlias; + mFlags = flags; + mKeySize = keySize; + mKeyValidityStart = keyValidityStart; + mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; + mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mPurposes = purposes; + mPadding = padding; + mBlockMode = blockMode; + mMinSecondsBetweenOperations = minSecondsBetweenOperations; + mMaxUsesPerBoot = maxUsesPerBoot; + mUserAuthenticators = (userAuthenticators != null) + ? new HashSet<Integer>(userAuthenticators) + : Collections.<Integer>emptySet(); + mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; + } + + /** + * Gets the Android context used for operations with this instance. + */ + public Context getContext() { + return mContext; + } + + /** + * Returns the alias that will be used in the {@code java.security.KeyStore} in conjunction with + * the {@code AndroidKeyStore}. + */ + public String getKeystoreAlias() { + return mKeystoreAlias; + } + + /** + * @hide + */ + public int getFlags() { + return mFlags; + } + + /** + * Gets the requested key size or {@code null} if the default size should be used. + */ + public Integer getKeySize() { + return mKeySize; + } + + /** + * Gets the time instant before which the key is not yet valid. + * + * @return instant or {@code null} if not restricted. + */ + public Date getKeyValidityStart() { + return mKeyValidityStart; + } + + /** + * Gets the time instant after which the key is no longer valid for decryption and verification. + * + * @return instant or {@code null} if not restricted. + * + * @hide + */ + public Date getKeyValidityForConsumptionEnd() { + return mKeyValidityForConsumptionEnd; + } + + /** + * Gets the time instant after which the key is no longer valid for encryption and signing. + * + * @return instant or {@code null} if not restricted. + */ + public Date getKeyValidityForOriginationEnd() { + return mKeyValidityForOriginationEnd; + } + + /** + * Gets the set of purposes for which the key can be used. + * + * @return set of purposes or {@code null} if the key can be used for any purpose. + */ + public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() { + return mPurposes; + } + + /** + * Gets the padding scheme to which the key is restricted. + * + * @return padding scheme or {@code null} if the padding scheme is not restricted. + */ + public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() { + return mPadding; + } + + /** + * Gets the block mode to which the key is restricted when used for encryption or decryption. + * + * @return block more or {@code null} if block mode is not restricted. + * + * @hide + */ + public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() { + return mBlockMode; + } + + /** + * Gets the minimum number of seconds that must expire since the most recent use of the key + * before it can be used again. + * + * @return number of seconds or {@code null} if there is no restriction on how frequently a key + * can be used. + * + * @hide + */ + public Integer getMinSecondsBetweenOperations() { + return mMinSecondsBetweenOperations; + } + + /** + * Gets the number of times the key can be used without rebooting the device. + * + * @return maximum number of times or {@code null} if there is no restriction. + * @hide + */ + public Integer getMaxUsesPerBoot() { + return mMaxUsesPerBoot; + } + + /** + * Gets the user authenticators which protect access to this key. The key can only be used iff + * the user has authenticated to at least one of these user authenticators. + * + * @return user authenticators or empty set if the key can be used without user authentication. + * + * @hide + */ + public Set<Integer> getUserAuthenticators() { + return new HashSet<Integer>(mUserAuthenticators); + } + + /** + * Gets the duration of time (seconds) for which this key can be used after the user + * successfully authenticates to one of the associated user authenticators. + * + * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication + * is required for every use of the key. + * + * @hide + */ + public Integer getUserAuthenticationValidityDurationSeconds() { + return mUserAuthenticationValidityDurationSeconds; + } + + /** + * Returns {@code true} if the key must be encrypted in the {@link java.security.KeyStore}. + */ + public boolean isEncryptionRequired() { + return (mFlags & KeyStore.FLAG_ENCRYPTED) != 0; + } + + public static class Builder { + private final Context mContext; + private String mKeystoreAlias; + private int mFlags; + private Integer mKeySize; + private Date mKeyValidityStart; + private Date mKeyValidityForOriginationEnd; + private Date mKeyValidityForConsumptionEnd; + private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes; + private @KeyStoreKeyConstraints.PaddingEnum Integer mPadding; + private @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode; + private Integer mMinSecondsBetweenOperations; + private Integer mMaxUsesPerBoot; + private Set<Integer> mUserAuthenticators; + private Integer mUserAuthenticationValidityDurationSeconds; + + /** + * Creates a new instance of the {@code Builder} with the given {@code context}. The + * {@code context} passed in may be used to pop up some UI to ask the user to unlock or + * initialize the Android KeyStore facility. + */ + public Builder(Context context) { + if (context == null) { + throw new NullPointerException("context == null"); + } + mContext = context; + } + + /** + * Sets the alias to be used to retrieve the key later from a {@link java.security.KeyStore} + * instance using the {@code AndroidKeyStore} provider. + * + * <p>The alias must be provided. There is no default. + */ + public Builder setAlias(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + mKeystoreAlias = alias; + return this; + } + + /** + * Sets the size (in bits) of the key to be generated. + * + * <p>By default, the key size will be determines based on the key algorithm. For example, + * for {@code HmacSHA256}, the key size will default to {@code 256}. + */ + public Builder setKeySize(int keySize) { + mKeySize = keySize; + return this; + } + + /** + * Indicates that this key must be encrypted at rest on storage. Note that enabling this + * will require that the user enable a strong lock screen (e.g., PIN, password) before + * creating or using the generated key is successful. + */ + public Builder setEncryptionRequired(boolean required) { + if (required) { + mFlags |= KeyStore.FLAG_ENCRYPTED; + } else { + mFlags &= ~KeyStore.FLAG_ENCRYPTED; + } + return this; + } + + /** + * Sets the time instant before which the key is not yet valid. + * + * <b>By default, the key is valid at any instant. + * + * @see #setKeyValidityEnd(Date) + * + * @hide + */ + public Builder setKeyValidityStart(Date startDate) { + mKeyValidityStart = startDate; + return this; + } + + /** + * Sets the time instant after which the key is no longer valid. + * + * <b>By default, the key is valid at any instant. + * + * @see #setKeyValidityStart(Date) + * @see #setKeyValidityForConsumptionEnd(Date) + * @see #setKeyValidityForOriginationEnd(Date) + * + * @hide + */ + public Builder setKeyValidityEnd(Date endDate) { + setKeyValidityForOriginationEnd(endDate); + setKeyValidityForConsumptionEnd(endDate); + return this; + } + + /** + * Sets the time instant after which the key is no longer valid for encryption and signing. + * + * <b>By default, the key is valid at any instant. + * + * @see #setKeyValidityForConsumptionEnd(Date) + * + * @hide + */ + public Builder setKeyValidityForOriginationEnd(Date endDate) { + mKeyValidityForOriginationEnd = endDate; + return this; + } + + /** + * Sets the time instant after which the key is no longer valid for decryption and + * verification. + * + * <b>By default, the key is valid at any instant. + * + * @see #setKeyValidityForOriginationEnd(Date) + * + * @hide + */ + public Builder setKeyValidityForConsumptionEnd(Date endDate) { + mKeyValidityForConsumptionEnd = endDate; + return this; + } + + /** + * Restricts the purposes for which the key can be used to the provided set of purposes. + * + * <p>By default, the key can be used for encryption, decryption, signing, and verification. + * + * @hide + */ + public Builder setPurposes(@KeyStoreKeyConstraints.PurposeEnum int purposes) { + mPurposes = purposes; + return this; + } + + /** + * Restricts the key to being used only with the provided padding scheme. Attempts to use + * the key with any other padding will be rejected. + * + * <p>This restriction must be specified for keys which are used for encryption/decryption. + * + * @hide + */ + public Builder setPadding(@KeyStoreKeyConstraints.PaddingEnum int padding) { + mPadding = padding; + return this; + } + + /** + * Restricts the key to being used only with the provided block mode when encrypting or + * decrypting. Attempts to use the key with any other block modes will be rejected. + * + * <p>This restriction must be specified for keys which are used for encryption/decryption. + * + * @hide + */ + public Builder setBlockMode(@KeyStoreKeyConstraints.BlockModeEnum int blockMode) { + mBlockMode = blockMode; + return this; + } + + /** + * Sets the minimum number of seconds that must expire since the most recent use of the key + * before it can be used again. + * + * <p>By default, there is no restriction on how frequently a key can be used. + * + * @hide + */ + public Builder setMinSecondsBetweenOperations(int seconds) { + mMinSecondsBetweenOperations = seconds; + return this; + } + + /** + * Sets the maximum number of times a key can be used without rebooting the device. + * + * <p>By default, the key can be used for an unlimited number of times. + * + * @hide + */ + public Builder setMaxUsesPerBoot(int count) { + mMaxUsesPerBoot = count; + return this; + } + + /** + * Sets the user authenticators which protect access to this key. The key can only be used + * iff the user has authenticated to at least one of these user authenticators. + * + * <p>By default, the key can be used without user authentication. + * + * @param userAuthenticators user authenticators or empty list if this key can be accessed + * without user authentication. + * + * @see #setUserAuthenticationValidityDurationSeconds(int) + * + * @hide + */ + public Builder setUserAuthenticators(Set<Integer> userAuthenticators) { + mUserAuthenticators = + (userAuthenticators != null) ? new HashSet<Integer>(userAuthenticators) : null; + return this; + } + + /** + * Sets the duration of time (seconds) for which this key can be used after the user + * successfully authenticates to one of the associated user authenticators. + * + * <p>By default, the user needs to authenticate for every use of the key. + * + * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for + * every use of the key. + * + * @see #setUserAuthenticators(Set) + * + * @hide + */ + public Builder setUserAuthenticationValidityDurationSeconds(int seconds) { + mUserAuthenticationValidityDurationSeconds = seconds; + return this; + } + + /** + * Builds a new instance instance of {@code KeyGeneratorSpec}. + * + * @throws IllegalArgumentException if a required field is missing or violates a constraint. + */ + public KeyGeneratorSpec build() { + return new KeyGeneratorSpec(mContext, mKeystoreAlias, mFlags, mKeySize, + mKeyValidityStart, mKeyValidityForOriginationEnd, mKeyValidityForConsumptionEnd, + mPurposes, mPadding, mBlockMode, mMinSecondsBetweenOperations, mMaxUsesPerBoot, + mUserAuthenticators, mUserAuthenticationValidityDurationSeconds); + } + } +} diff --git a/keystore/java/android/security/KeyNotYetValidException.java b/keystore/java/android/security/KeyNotYetValidException.java new file mode 100644 index 000000000000..f1c2cac672b9 --- /dev/null +++ b/keystore/java/android/security/KeyNotYetValidException.java @@ -0,0 +1,48 @@ +/* + * 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; + +/** + * Indicates that a cryptographic operation failed because the employed key's validity start date + * is in the future. + * + * @hide + */ +public class KeyNotYetValidException extends CryptoOperationException { + + /** + * Constructs a new {@code KeyNotYetValidException} without detail message and cause. + */ + public KeyNotYetValidException() { + super("Key not yet valid"); + } + + /** + * Constructs a new {@code KeyNotYetValidException} with the provided detail message and no + * cause. + */ + public KeyNotYetValidException(String message) { + super(message); + } + + /** + * Constructs a new {@code KeyNotYetValidException} with the provided detail message and cause. + */ + public KeyNotYetValidException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java index cc097aad69f8..00016040a11f 100644 --- a/keystore/java/android/security/KeyPairGeneratorSpec.java +++ b/keystore/java/android/security/KeyPairGeneratorSpec.java @@ -24,7 +24,10 @@ import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.spec.AlgorithmParameterSpec; +import java.util.Collections; import java.util.Date; +import java.util.HashSet; +import java.util.Set; import javax.security.auth.x500.X500Principal; @@ -72,6 +75,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private final int mFlags; + private final Date mKeyValidityStart; + + private final Date mKeyValidityForOriginationEnd; + + private final Date mKeyValidityForConsumptionEnd; + + private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes; + + private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest; + + private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding; + + private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode; + + private final Integer mMinSecondsBetweenOperations; + + private final Integer mMaxUsesPerBoot; + + private final Set<Integer> mUserAuthenticators; + + private final Integer mUserAuthenticationValidityDurationSeconds; + /** * Parameter specification for the "{@code AndroidKeyPairGenerator}" * instance of the {@link java.security.KeyPairGenerator} API. The @@ -106,7 +131,18 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { */ public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize, AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber, - Date startDate, Date endDate, int flags) { + Date startDate, Date endDate, int flags, + Date keyValidityStart, + Date keyValidityForOriginationEnd, + Date keyValidityForConsumptionEnd, + @KeyStoreKeyConstraints.PurposeEnum Integer purposes, + @KeyStoreKeyConstraints.DigestEnum Integer digest, + @KeyStoreKeyConstraints.PaddingEnum Integer padding, + @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode, + Integer minSecondsBetweenOperations, + Integer maxUsesPerBoot, + Set<Integer> userAuthenticators, + Integer userAuthenticationValidityDurationSeconds) { if (context == null) { throw new IllegalArgumentException("context == null"); } else if (TextUtils.isEmpty(keyStoreAlias)) { @@ -121,6 +157,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { throw new IllegalArgumentException("endDate == null"); } else if (endDate.before(startDate)) { throw new IllegalArgumentException("endDate < startDate"); + } else if ((userAuthenticationValidityDurationSeconds != null) + && (userAuthenticationValidityDurationSeconds < 0)) { + throw new IllegalArgumentException( + "userAuthenticationValidityDurationSeconds must not be negative"); } mContext = context; @@ -133,6 +173,31 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { mStartDate = startDate; mEndDate = endDate; mFlags = flags; + mKeyValidityStart = keyValidityStart; + mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; + mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mPurposes = purposes; + mDigest = digest; + mPadding = padding; + mBlockMode = blockMode; + mMinSecondsBetweenOperations = minSecondsBetweenOperations; + mMaxUsesPerBoot = maxUsesPerBoot; + mUserAuthenticators = (userAuthenticators != null) + ? new HashSet<Integer>(userAuthenticators) + : Collections.<Integer>emptySet(); + mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; + } + + /** + * TODO: Remove this constructor once tests are switched over to the new one above. + * @hide + */ + public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize, + AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber, + Date startDate, Date endDate, int flags) { + this(context, keyStoreAlias, keyType, keySize, spec, subjectDN, serialNumber, startDate, + endDate, flags, startDate, endDate, endDate, null, null, null, null, null, null, + null, null); } /** @@ -222,6 +287,145 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** + * Gets the time instant before which the key pair is not yet valid. + * + * @return instant or {@code null} if not restricted. + * + * @hide + */ + public Date getKeyValidityStart() { + return mKeyValidityStart; + } + + /** + * Gets the time instant after which the key pair is no longer valid for decryption and + * verification. + * + * @return instant or {@code null} if not restricted. + * + * @hide + */ + public Date getKeyValidityForConsumptionEnd() { + return mKeyValidityForConsumptionEnd; + } + + /** + * Gets the time instant after which the key pair is no longer valid for encryption and signing. + * + * @return instant or {@code null} if not restricted. + * + * @hide + */ + public Date getKeyValidityForOriginationEnd() { + return mKeyValidityForOriginationEnd; + } + + /** + * Gets the set of purposes for which the key can be used. + * + * @return set of purposes or {@code null} if the key can be used for any purpose. + * + * @hide + */ + public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() { + return mPurposes; + } + + /** + * Gets the digest to which the key is restricted. + * + * @return digest or {@code null} if the digest is not restricted. + * + * @hide + */ + public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() { + return mDigest; + } + + /** + * Gets the padding scheme to which the key is restricted. + * + * @return padding scheme or {@code null} if the padding scheme is not restricted. + * + * @hide + */ + public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() { + return mPadding; + } + + /** + * Gets the block mode to which the key is restricted when used for encryption or decryption. + * + * @return block more or {@code null} if block mode is not restricted. + * + * @hide + */ + public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() { + return mBlockMode; + } + + /** + * Gets the minimum number of seconds that must expire since the most recent use of the private + * key before it can be used again. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @return number of seconds or {@code null} if there is no restriction on how frequently a key + * can be used. + * + * @hide + */ + public Integer getMinSecondsBetweenOperations() { + return mMinSecondsBetweenOperations; + } + + /** + * Gets the number of times the private key can be used without rebooting the device. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @return maximum number of times or {@code null} if there is no restriction. + * + * @hide + */ + public Integer getMaxUsesPerBoot() { + return mMaxUsesPerBoot; + } + + /** + * Gets the user authenticators which protect access to the private key. The key can only be + * used iff the user has authenticated to at least one of these user authenticators. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @return user authenticators or empty set if the key can be used without user authentication. + * + * @hide + */ + public Set<Integer> getUserAuthenticators() { + return new HashSet<Integer>(mUserAuthenticators); + } + + /** + * Gets the duration of time (seconds) for which the private key can be used after the user + * successfully authenticates to one of the associated user authenticators. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication + * is required for every use of the key. + * + * @hide + */ + public Integer getUserAuthenticationValidityDurationSeconds() { + return mUserAuthenticationValidityDurationSeconds; + } + + /** * Builder class for {@link KeyPairGeneratorSpec} objects. * <p> * This will build a parameter spec for use with the <a href="{@docRoot} @@ -263,6 +467,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private int mFlags; + private Date mKeyValidityStart; + + private Date mKeyValidityForOriginationEnd; + + private Date mKeyValidityForConsumptionEnd; + + private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes; + + private @KeyStoreKeyConstraints.DigestEnum Integer mDigest; + + private @KeyStoreKeyConstraints.PaddingEnum Integer mPadding; + + private @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode; + + private Integer mMinSecondsBetweenOperations; + + private Integer mMaxUsesPerBoot; + + private Set<Integer> mUserAuthenticators; + + private Integer mUserAuthenticationValidityDurationSeconds; + /** * Creates a new instance of the {@code Builder} with the given * {@code context}. The {@code context} passed in may be used to pop up @@ -389,14 +615,218 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** + * Sets the time instant before which the key is not yet valid. + * + * <b>By default, the key is valid at any instant. + * + * @see #setKeyValidityEnd(Date) + * + * @hide + */ + public Builder setKeyValidityStart(Date startDate) { + mKeyValidityStart = startDate; + return this; + } + + /** + * Sets the time instant after which the key is no longer valid. + * + * <b>By default, the key is valid at any instant. + * + * @see #setKeyValidityStart(Date) + * @see #setKeyValidityForConsumptionEnd(Date) + * @see #setKeyValidityForOriginationEnd(Date) + * + * @hide + */ + public Builder setKeyValidityEnd(Date endDate) { + setKeyValidityForOriginationEnd(endDate); + setKeyValidityForConsumptionEnd(endDate); + return this; + } + + /** + * Sets the time instant after which the key is no longer valid for encryption and signing. + * + * <b>By default, the key is valid at any instant. + * + * @see #setKeyValidityForConsumptionEnd(Date) + * + * @hide + */ + public Builder setKeyValidityForOriginationEnd(Date endDate) { + mKeyValidityForOriginationEnd = endDate; + return this; + } + + /** + * Sets the time instant after which the key is no longer valid for decryption and + * verification. + * + * <b>By default, the key is valid at any instant. + * + * @see #setKeyValidityForOriginationEnd(Date) + * + * @hide + */ + public Builder setKeyValidityForConsumptionEnd(Date endDate) { + mKeyValidityForConsumptionEnd = endDate; + return this; + } + + /** + * Restricts the purposes for which the key can be used to the provided set of purposes. + * + * <p>By default, the key can be used for encryption, decryption, signing, and verification. + * + * @hide + */ + public Builder setPurposes(@KeyStoreKeyConstraints.PurposeEnum int purposes) { + mPurposes = purposes; + return this; + } + + /** + * Restricts the key to being used only with the provided digest. Attempts to use the key + * with any other digests be rejected. + * + * <p>This restriction must be specified for keys which are used for signing/verification. + * + * @hide + */ + public Builder setDigest(@KeyStoreKeyConstraints.DigestEnum int digest) { + mDigest = digest; + return this; + } + + /** + * Restricts the key to being used only with the provided padding scheme. Attempts to use + * the key with any other padding will be rejected. + * + * <p>This restriction must be specified for keys which are used for encryption/decryption. + * + * @hide + */ + public Builder setPadding(@KeyStoreKeyConstraints.PaddingEnum int padding) { + mPadding = padding; + return this; + } + + /** + * Restricts the key to being used only with the provided block mode when encrypting or + * decrypting. Attempts to use the key with any other block modes will be rejected. + * + * <p>This restriction must be specified for keys which are used for encryption/decryption. + * + * @hide + */ + public Builder setBlockMode(@KeyStoreKeyConstraints.BlockModeEnum int blockMode) { + mBlockMode = blockMode; + return this; + } + + /** + * Sets the minimum number of seconds that must expire since the most recent use of the key + * before it can be used again. + * + * <p>By default, there is no restriction on how frequently a key can be used. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @hide + */ + public Builder setMinSecondsBetweenOperations(int seconds) { + mMinSecondsBetweenOperations = seconds; + return this; + } + + /** + * Sets the maximum number of times a key can be used without rebooting the device. + * + * <p>By default, the key can be used for an unlimited number of times. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @hide + */ + public Builder setMaxUsesPerBoot(int count) { + mMaxUsesPerBoot = count; + return this; + } + + /** + * Sets the user authenticators which protect access to this key. The key can only be used + * iff the user has authenticated to at least one of these user authenticators. + * + * <p>By default, the key can be used without user authentication. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @param userAuthenticators user authenticators or empty list if this key can be accessed + * without user authentication. + * + * @see #setUserAuthenticationValidityDurationSeconds(int) + * + * @hide + */ + public Builder setUserAuthenticators(Set<Integer> userAuthenticators) { + mUserAuthenticators = + (userAuthenticators != null) ? new HashSet<Integer>(userAuthenticators) : null; + return this; + } + + /** + * Sets the duration of time (seconds) for which this key can be used after the user + * successfully authenticates to one of the associated user authenticators. + * + * <p>By default, the user needs to authenticate for every use of the key. + * + * <p>This restriction applies only to private key operations. Public key operations are not + * restricted. + * + * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for + * every use of the key. + * + * @see #setUserAuthenticators(Set) + * + * @hide + */ + public Builder setUserAuthenticationValidityDurationSeconds(int seconds) { + mUserAuthenticationValidityDurationSeconds = seconds; + return this; + } + + /** * Builds the instance of the {@code KeyPairGeneratorSpec}. * * @throws IllegalArgumentException if a required field is missing * @return built instance of {@code KeyPairGeneratorSpec} */ public KeyPairGeneratorSpec build() { - return new KeyPairGeneratorSpec(mContext, mKeystoreAlias, mKeyType, mKeySize, mSpec, - mSubjectDN, mSerialNumber, mStartDate, mEndDate, mFlags); + return new KeyPairGeneratorSpec(mContext, + mKeystoreAlias, + mKeyType, + mKeySize, + mSpec, + mSubjectDN, + mSerialNumber, + mStartDate, + mEndDate, + mFlags, + mKeyValidityStart, + mKeyValidityForOriginationEnd, + mKeyValidityForConsumptionEnd, + mPurposes, + mDigest, + mPadding, + mBlockMode, + mMinSecondsBetweenOperations, + mMaxUsesPerBoot, + mUserAuthenticators, + mUserAuthenticationValidityDurationSeconds); } } } diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 957e3c15ff47..94a479b4c1b4 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -389,19 +389,19 @@ public class KeyStore { } } - public int generateKey(String alias, KeymasterArguments args, int uid, int flags, - KeyCharacteristics outCharacteristics) { + public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int uid, + int flags, KeyCharacteristics outCharacteristics) { try { - return mBinder.generateKey(alias, args, uid, flags, outCharacteristics); + return mBinder.generateKey(alias, args, entropy, uid, flags, outCharacteristics); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return SYSTEM_ERROR; } } - public int generateKey(String alias, KeymasterArguments args, int flags, + public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int flags, KeyCharacteristics outCharacteristics) { - return generateKey(alias, args, UID_SELF, flags, outCharacteristics); + return generateKey(alias, args, entropy, UID_SELF, flags, outCharacteristics); } public int getKeyCharacteristics(String alias, KeymasterBlob clientId, KeymasterBlob appId, @@ -441,9 +441,9 @@ public class KeyStore { } public OperationResult begin(String alias, int purpose, boolean pruneable, - KeymasterArguments args, KeymasterArguments outArgs) { + KeymasterArguments args, byte[] entropy, KeymasterArguments outArgs) { try { - return mBinder.begin(getToken(), alias, purpose, pruneable, args, outArgs); + return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy, outArgs); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return null; @@ -476,4 +476,34 @@ public class KeyStore { return SYSTEM_ERROR; } } + + /** + * Check if the operation referenced by {@code token} is currently authorized. + * + * @param token An operation token returned by a call to {@link KeyStore.begin}. + */ + public boolean isOperationAuthorized(IBinder token) { + try { + return mBinder.isOperationAuthorized(token); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return false; + } + } + + /** + * 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; + } + } } diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java new file mode 100644 index 000000000000..afb5e3641197 --- /dev/null +++ b/keystore/java/android/security/KeyStoreCipherSpi.java @@ -0,0 +1,573 @@ +/* + * 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; + +import android.os.IBinder; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keymaster.OperationResult; + +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; + +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.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; + +/** + * Base class for {@link CipherSpi} providing Android KeyStore backed ciphers. + * + * @hide + */ +public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCryptoOperation { + + public abstract static class AES extends KeyStoreCipherSpi { + protected AES(@KeyStoreKeyConstraints.BlockModeEnum int blockMode, + @KeyStoreKeyConstraints.PaddingEnum int padding, boolean ivUsed) { + super(KeyStoreKeyConstraints.Algorithm.AES, + blockMode, + padding, + 16, + ivUsed); + } + + public abstract static class ECB extends AES { + protected ECB(@KeyStoreKeyConstraints.PaddingEnum int padding) { + super(KeyStoreKeyConstraints.BlockMode.ECB, padding, false); + } + + public static class NoPadding extends ECB { + public NoPadding() { + super(KeyStoreKeyConstraints.Padding.NONE); + } + } + + public static class PKCS7Padding extends ECB { + public PKCS7Padding() { + super(KeyStoreKeyConstraints.Padding.PKCS7); + } + } + } + + public abstract static class CBC extends AES { + protected CBC(@KeyStoreKeyConstraints.BlockModeEnum int padding) { + super(KeyStoreKeyConstraints.BlockMode.CBC, padding, true); + } + + public static class NoPadding extends CBC { + public NoPadding() { + super(KeyStoreKeyConstraints.Padding.NONE); + } + } + + public static class PKCS7Padding extends CBC { + public PKCS7Padding() { + super(KeyStoreKeyConstraints.Padding.PKCS7); + } + } + } + + public abstract static class CTR extends AES { + protected CTR(@KeyStoreKeyConstraints.BlockModeEnum int padding) { + super(KeyStoreKeyConstraints.BlockMode.CTR, padding, true); + } + + public static class NoPadding extends CTR { + public NoPadding() { + super(KeyStoreKeyConstraints.Padding.NONE); + } + } + } + } + + private final KeyStore mKeyStore; + private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm; + private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockMode; + private final @KeyStoreKeyConstraints.PaddingEnum int mPadding; + private final int mBlockSizeBytes; + private final boolean mIvUsed; + + // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after + // doFinal finishes. + protected boolean mEncrypting; + private KeyStoreSecretKey mKey; + private SecureRandom mRng; + private boolean mFirstOperationInitiated; + byte[] mIv; + + // Fields below must be reset + private byte[] mAdditionalEntropyForBegin; + /** + * Token referencing this operation inside keystore service. It is initialized by + * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and one some + * error conditions in between. + */ + private IBinder mOperationToken; + private Long mOperationHandle; + private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer; + + protected KeyStoreCipherSpi( + @KeyStoreKeyConstraints.AlgorithmEnum int algorithm, + @KeyStoreKeyConstraints.BlockModeEnum int blockMode, + @KeyStoreKeyConstraints.PaddingEnum int padding, + int blockSizeBytes, + boolean ivUsed) { + mKeyStore = KeyStore.getInstance(); + mAlgorithm = algorithm; + mBlockMode = blockMode; + mPadding = padding; + mBlockSizeBytes = blockSizeBytes; + mIvUsed = ivUsed; + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + init(opmode, key, random); + initAlgorithmSpecificParameters(); + ensureKeystoreOperationInitialized(); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + } + + private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + reset(); + if (!(key instanceof KeyStoreSecretKey)) { + throw new InvalidKeyException( + "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); + } + mKey = (KeyStoreSecretKey) key; + mRng = random; + mIv = null; + mFirstOperationInitiated = false; + + if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) { + throw new UnsupportedOperationException( + "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode); + } + mEncrypting = opmode == Cipher.ENCRYPT_MODE; + } + + private void reset() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mOperationHandle = null; + mMainDataStreamer = null; + mAdditionalEntropyForBegin = null; + } + + private void ensureKeystoreOperationInitialized() { + if (mMainDataStreamer != null) { + return; + } + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + + KeymasterArguments keymasterInputArgs = new KeymasterArguments(); + keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mAlgorithm); + keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mBlockMode); + keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mPadding); + addAlgorithmSpecificParametersToBegin(keymasterInputArgs); + + KeymasterArguments keymasterOutputArgs = new KeymasterArguments(); + OperationResult opResult = mKeyStore.begin( + mKey.getAlias(), + mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT, + true, // permit aborting this operation if keystore runs out of resources + keymasterInputArgs, + mAdditionalEntropyForBegin, + keymasterOutputArgs); + mAdditionalEntropyForBegin = null; + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getCryptoOperationException(opResult.resultCode); + } + + if (opResult.token == null) { + throw new CryptoOperationException("Keystore returned null operation token"); + } + mOperationToken = opResult.token; + mOperationHandle = opResult.operationHandle; + loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs); + mFirstOperationInitiated = true; + mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + mKeyStore, opResult.token)); + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + ensureKeystoreOperationInitialized(); + + if (inputLen == 0) { + return null; + } + + byte[] output; + try { + output = mMainDataStreamer.update(input, inputOffset, inputLen); + } catch (KeymasterException e) { + throw KeymasterUtils.getCryptoOperationException(e); + } + + if (output.length == 0) { + return null; + } + + return output; + } + + @Override + protected 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 byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + ensureKeystoreOperationInitialized(); + + byte[] output; + try { + output = mMainDataStreamer.doFinal(input, inputOffset, inputLen); + } catch (KeymasterException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: + throw new IllegalBlockSizeException(); + case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: + throw new BadPaddingException(); + case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: + throw new AEADBadTagException(); + default: + throw KeymasterUtils.getCryptoOperationException(e); + } + } + + reset(); + return output; + } + + @Override + protected 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 int engineGetBlockSize() { + return mBlockSizeBytes; + } + + @Override + protected byte[] engineGetIV() { + return (mIv != null) ? mIv.clone() : null; + } + + @Override + protected int engineGetOutputSize(int inputLen) { + return inputLen + 3 * engineGetBlockSize(); + } + + @Override + protected 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 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 + public void finalize() throws Throwable { + try { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + } finally { + super.finalize(); + } + } + + @Override + public Long getOperationHandle() { + return mOperationHandle; + } + + // The methods below may need to be overridden by subclasses that use algorithm-specific + // parameters. + + /** + * Returns algorithm-specific parameters used by this {@code CipherSpi} instance or {@code null} + * if no algorithm-specific parameters are used. + * + * <p>This implementation only handles the IV parameter. + */ + @Override + protected AlgorithmParameters engineGetParameters() { + if (!mIvUsed) { + 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 RuntimeException("Failed to obtain AES AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new RuntimeException( + "Failed to initialize AES AlgorithmParameters with an IV", e); + } + } + return null; + } + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters + * may need to be stored to be reused after {@code doFinal}. + * + * <p>The default implementation only handles the IV parameters. + * + * @param params algorithm parameters. + * + * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be + * automatically configured and thus {@code Cipher.init} needs to be invoked with + * explicitly provided parameters. + */ + protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (!mIvUsed) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!mEncrypting) { + // 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"); + } + } + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters + * may need to be stored to be reused after {@code doFinal}. + * + * <p>The default implementation only handles the IV parameters. + * + * @param params algorithm parameters. + * + * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be + * automatically configured and thus {@code Cipher.init} needs to be invoked with + * explicitly provided parameters. + */ + protected void initAlgorithmSpecificParameters(AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (!mIvUsed) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!mEncrypting) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + return; + } + + IvParameterSpec ivSpec; + try { + ivSpec = params.getParameterSpec(IvParameterSpec.class); + } catch (InvalidParameterSpecException e) { + if (!mEncrypting) { + // 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"); + } + } + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters + * may need to be stored to be reused after {@code doFinal}. + * + * <p>The default implementation only handles the IV parameter. + * + * @throws InvalidKeyException if some/all of the parameters cannot be automatically configured + * and thus {@code Cipher.init} needs to be invoked with explicitly provided parameters. + */ + protected void initAlgorithmSpecificParameters() throws InvalidKeyException { + if (!mIvUsed) { + return; + } + + // IV is used + if (!mEncrypting) { + throw new InvalidKeyException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + } + + /** + * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. + * + * <p>The default implementation takes care of the IV. + * + * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific + * parameters. + */ + protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) { + if (!mFirstOperationInitiated) { + // First begin operation -- see if we need to provide additional entropy for IV + // generation. + if (mIvUsed) { + // IV is needed + if ((mIv == null) && (mEncrypting)) { + // TODO: Switch to keymaster-generated IV code below once keymaster supports + // that. + // IV is needed but was not provided by the caller -- generate an IV. + mIv = new byte[mBlockSizeBytes]; + SecureRandom rng = (mRng != null) ? mRng : new SecureRandom(); + rng.nextBytes(mIv); +// // IV was not provided by the caller and thus will be generated by keymaster. +// // Mix in some additional entropy from the provided SecureRandom. +// if (mRng != null) { +// mAdditionalEntropyForBegin = new byte[mBlockSizeBytes]; +// mRng.nextBytes(mAdditionalEntropyForBegin); +// } + } + } + } + + if ((mIvUsed) && (mIv != null)) { + keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv); + } + } + + /** + * Invoked by {@code engineInit} to obtain algorithm-specific parameters from the result of the + * Keymaster's {@code begin} operation. Some of these parameters may need to be reused after + * {@code doFinal} by {@link #addAlgorithmSpecificParametersToBegin(KeymasterArguments)}. + * + * <p>The default implementation only takes care of the IV. + * + * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin} + * operation. + */ + protected void loadAlgorithmSpecificParametersFromBeginResult( + KeymasterArguments keymasterArgs) { + // NOTE: Keymaster doesn't always return an IV, even if it's used. + byte[] returnedIv = keymasterArgs.getBlob(KeymasterDefs.KM_TAG_NONCE, null); + if ((returnedIv != null) && (returnedIv.length == 0)) { + returnedIv = null; + } + + if (mIvUsed) { + if (mIv == null) { + mIv = returnedIv; + } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { + throw new CryptoOperationException("IV in use differs from provided IV"); + } + } else { + if (returnedIv != null) { + throw new CryptoOperationException( + "IV in use despite IV not being used by this transformation"); + } + } + } +} diff --git a/keystore/java/android/security/KeyStoreConnectException.java b/keystore/java/android/security/KeyStoreConnectException.java new file mode 100644 index 000000000000..8ed6e04ddfe8 --- /dev/null +++ b/keystore/java/android/security/KeyStoreConnectException.java @@ -0,0 +1,28 @@ +/* + * 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; + +/** + * Indicates a communications error with keystore service. + * + * @hide + */ +public class KeyStoreConnectException extends CryptoOperationException { + public KeyStoreConnectException() { + super("Failed to communicate with keystore service"); + } +} diff --git a/keystore/java/android/security/KeyStoreCryptoOperation.java b/keystore/java/android/security/KeyStoreCryptoOperation.java new file mode 100644 index 000000000000..19abd05553bf --- /dev/null +++ b/keystore/java/android/security/KeyStoreCryptoOperation.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * Cryptographic operation backed by {@link KeyStore}. + * + * @hide + */ +public interface KeyStoreCryptoOperation { + /** + * Gets the KeyStore operation handle of this crypto operation. + * + * @return handle or {@code null} if the KeyStore operation is not in progress. + */ + Long getOperationHandle(); +} diff --git a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java new file mode 100644 index 000000000000..993614b9861c --- /dev/null +++ b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java @@ -0,0 +1,309 @@ +/* + * 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; + +import android.os.IBinder; +import android.security.keymaster.OperationResult; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * 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) 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 + */ +public class KeyStoreCryptoOperationChunkedStreamer { + + /** + * Bidirectional chunked data stream over a KeyStore crypto operation. + */ + public interface Stream { + /** + * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't + * be reached. + */ + OperationResult update(byte[] input); + + /** + * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't + * be reached. + */ + OperationResult finish(); + } + + // 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_MAX_CHUNK_SIZE = 64 * 1024; + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + private final Stream mKeyStoreStream; + private final int mMaxChunkSize; + + private byte[] mBuffered = EMPTY_BYTE_ARRAY; + private int mBufferedOffset; + private int mBufferedLength; + + public KeyStoreCryptoOperationChunkedStreamer(Stream operation) { + this(operation, DEFAULT_MAX_CHUNK_SIZE); + } + + public KeyStoreCryptoOperationChunkedStreamer(Stream operation, int maxChunkSize) { + mKeyStoreStream = operation; + mMaxChunkSize = maxChunkSize; + } + + public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeymasterException { + if (inputLength == 0) { + // No input provided + return EMPTY_BYTE_ARRAY; + } + + ByteArrayOutputStream bufferedOutput = null; + + while (inputLength > 0) { + byte[] chunk; + int inputBytesInChunk; + if ((mBufferedLength + inputLength) > mMaxChunkSize) { + // Too much input for one chunk -- extract one max-sized chunk and feed it into the + // update operation. + inputBytesInChunk = mMaxChunkSize - mBufferedLength; + chunk = concat(mBuffered, mBufferedOffset, mBufferedLength, + input, inputOffset, inputBytesInChunk); + } else { + // All of available input fits into one chunk. + if ((mBufferedLength == 0) && (inputOffset == 0) + && (inputLength == input.length)) { + // Nothing buffered and all of input array needs to be fed into the update + // operation. + chunk = input; + inputBytesInChunk = input.length; + } else { + // Need to combine buffered data with input data into one array. + inputBytesInChunk = inputLength; + chunk = concat(mBuffered, mBufferedOffset, mBufferedLength, + input, inputOffset, inputBytesInChunk); + } + } + // Update input array references to reflect that some of its bytes are now in mBuffered. + inputOffset += inputBytesInChunk; + inputLength -= inputBytesInChunk; + + OperationResult opResult = mKeyStoreStream.update(chunk); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getKeymasterException(opResult.resultCode); + } + + if (opResult.inputConsumed == chunk.length) { + // The whole chunk was consumed + mBuffered = EMPTY_BYTE_ARRAY; + mBufferedOffset = 0; + mBufferedLength = 0; + } else if (opResult.inputConsumed == 0) { + // Nothing was consumed. More input needed. + if (inputLength > 0) { + // More input is available, but it wasn't included into the previous chunk + // because the chunk reached its maximum permitted size. + // Shouldn't have happened. + throw new CryptoOperationException("Nothing consumed from max-sized chunk: " + + chunk.length + " bytes"); + } + mBuffered = chunk; + mBufferedOffset = 0; + mBufferedLength = chunk.length; + } else if (opResult.inputConsumed < chunk.length) { + // The chunk was consumed only partially -- buffer the rest of the chunk + mBuffered = chunk; + mBufferedOffset = opResult.inputConsumed; + mBufferedLength = chunk.length - opResult.inputConsumed; + } else { + throw new CryptoOperationException("Consumed more than provided: " + + opResult.inputConsumed + ", provided: " + chunk.length); + } + + if ((opResult.output != null) && (opResult.output.length > 0)) { + if (inputLength > 0) { + // More output might be produced in this loop -- buffer the current output + if (bufferedOutput == null) { + bufferedOutput = new ByteArrayOutputStream(); + try { + bufferedOutput.write(opResult.output); + } catch (IOException e) { + throw new CryptoOperationException("Failed to buffer output", e); + } + } + } else { + // No more output will be produced in this loop + if (bufferedOutput == null) { + // No previously buffered output + return opResult.output; + } else { + // There was some previously buffered output + try { + bufferedOutput.write(opResult.output); + } catch (IOException e) { + throw new CryptoOperationException("Failed to buffer output", e); + } + return bufferedOutput.toByteArray(); + } + } + } + } + + if (bufferedOutput == null) { + // No output produced + return EMPTY_BYTE_ARRAY; + } else { + return bufferedOutput.toByteArray(); + } + } + + public byte[] doFinal(byte[] input, int inputOffset, int inputLength) + throws KeymasterException { + if (inputLength == 0) { + // No input provided -- simplify the rest of the code + input = EMPTY_BYTE_ARRAY; + inputOffset = 0; + } + + // Flush all buffered input and provided input into keystore/keymaster. + byte[] output = update(input, inputOffset, inputLength); + output = concat(output, flush()); + + OperationResult opResult = mKeyStoreStream.finish(); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getKeymasterException(opResult.resultCode); + } + + return concat(output, opResult.output); + } + + /** + * Passes all of buffered input into the the KeyStore operation (via the {@code update} + * operation) and returns output. + */ + public byte[] flush() throws KeymasterException { + if (mBufferedLength <= 0) { + return EMPTY_BYTE_ARRAY; + } + + byte[] chunk = subarray(mBuffered, mBufferedOffset, mBufferedLength); + mBuffered = EMPTY_BYTE_ARRAY; + mBufferedLength = 0; + mBufferedOffset = 0; + + OperationResult opResult = mKeyStoreStream.update(chunk); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getKeymasterException(opResult.resultCode); + } + + if (opResult.inputConsumed < chunk.length) { + throw new CryptoOperationException("Keystore failed to consume all input. Provided: " + + chunk.length + ", consumed: " + opResult.inputConsumed); + } else if (opResult.inputConsumed > chunk.length) { + throw new CryptoOperationException("Keystore consumed more input than provided" + + " . Provided: " + chunk.length + ", consumed: " + opResult.inputConsumed); + } + + return (opResult.output != null) ? opResult.output : EMPTY_BYTE_ARRAY; + } + + private static byte[] concat(byte[] arr1, byte[] arr2) { + if ((arr1 == null) || (arr1.length == 0)) { + return arr2; + } else if ((arr2 == null) || (arr2.length == 0)) { + return arr1; + } else { + byte[] result = new byte[arr1.length + arr2.length]; + System.arraycopy(arr1, 0, result, 0, arr1.length); + System.arraycopy(arr2, 0, result, arr1.length, arr2.length); + return result; + } + } + + private static byte[] concat(byte[] arr1, int offset1, int len1, byte[] arr2, int offset2, + int len2) { + if (len1 == 0) { + return subarray(arr2, offset2, len2); + } else if (len2 == 0) { + return subarray(arr1, offset1, len1); + } else { + byte[] result = new byte[len1 + len2]; + System.arraycopy(arr1, offset1, result, 0, len1); + System.arraycopy(arr2, offset2, result, len1, len2); + return result; + } + } + + private static byte[] subarray(byte[] arr, int offset, int len) { + if (len == 0) { + return EMPTY_BYTE_ARRAY; + } + if ((offset == 0) && (len == arr.length)) { + return arr; + } + byte[] result = new byte[len]; + System.arraycopy(arr, offset, result, 0, len); + return result; + } + + /** + * 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 KeyStore mKeyStore; + private final IBinder mOperationToken; + + public MainDataStream(KeyStore keyStore, IBinder operationToken) { + mKeyStore = keyStore; + mOperationToken = operationToken; + } + + @Override + public OperationResult update(byte[] input) { + return mKeyStore.update(mOperationToken, null, input); + } + + @Override + public OperationResult finish() { + return mKeyStore.finish(mOperationToken, null, null); + } + } +} diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java new file mode 100644 index 000000000000..6d0e1ae34633 --- /dev/null +++ b/keystore/java/android/security/KeyStoreHmacSpi.java @@ -0,0 +1,183 @@ +/* + * 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; + +import android.os.IBinder; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keymaster.OperationResult; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.MacSpi; + +/** + * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore. + * + * @hide + */ +public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation { + + public static class HmacSHA256 extends KeyStoreHmacSpi { + public HmacSHA256() { + super(KeyStoreKeyConstraints.Digest.SHA256, 256 / 8); + } + } + + private final KeyStore mKeyStore = KeyStore.getInstance(); + private final @KeyStoreKeyConstraints.DigestEnum int mDigest; + private final int mMacSizeBytes; + + private String mKeyAliasInKeyStore; + + // The fields below are reset by the engineReset operation. + private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer; + private IBinder mOperationToken; + private Long mOperationHandle; + + protected KeyStoreHmacSpi(@KeyStoreKeyConstraints.DigestEnum int digest, int macSizeBytes) { + mDigest = digest; + mMacSizeBytes = macSizeBytes; + } + + @Override + protected int engineGetMacLength() { + return mMacSizeBytes; + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, + InvalidAlgorithmParameterException { + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if (!(key instanceof KeyStoreSecretKey)) { + throw new InvalidKeyException( + "Only Android KeyStore secret keys supported. Key: " + key); + } + + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unsupported algorithm parameters: " + params); + } + + mKeyAliasInKeyStore = ((KeyStoreSecretKey) key).getAlias(); + if (mKeyAliasInKeyStore == null) { + throw new InvalidKeyException("Key's KeyStore alias not known"); + } + engineReset(); + ensureKeystoreOperationInitialized(); + } + + @Override + protected void engineReset() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mOperationHandle = null; + mChunkedStreamer = null; + } + + private void ensureKeystoreOperationInitialized() { + if (mChunkedStreamer != null) { + return; + } + if (mKeyAliasInKeyStore == null) { + throw new IllegalStateException("Not initialized"); + } + + KeymasterArguments keymasterArgs = new KeymasterArguments(); + keymasterArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeyStoreKeyConstraints.Algorithm.HMAC); + keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, mDigest); + + OperationResult opResult = mKeyStore.begin(mKeyAliasInKeyStore, + KeymasterDefs.KM_PURPOSE_SIGN, + true, + keymasterArgs, + null, + new KeymasterArguments()); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getCryptoOperationException(opResult.resultCode); + } + if (opResult.token == null) { + throw new CryptoOperationException("Keystore returned null operation token"); + } + mOperationToken = opResult.token; + mOperationHandle = opResult.operationHandle; + mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + mKeyStore, mOperationToken)); + } + + @Override + protected void engineUpdate(byte input) { + engineUpdate(new byte[] {input}, 0, 1); + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + ensureKeystoreOperationInitialized(); + + byte[] output; + try { + output = mChunkedStreamer.update(input, offset, len); + } catch (KeymasterException e) { + throw KeymasterUtils.getCryptoOperationException(e); + } + if ((output != null) && (output.length != 0)) { + throw new CryptoOperationException("Update operation unexpectedly produced output"); + } + } + + @Override + protected byte[] engineDoFinal() { + ensureKeystoreOperationInitialized(); + + byte[] result; + try { + result = mChunkedStreamer.doFinal(null, 0, 0); + } catch (KeymasterException e) { + throw KeymasterUtils.getCryptoOperationException(e); + } + + engineReset(); + return result; + } + + @Override + public void finalize() throws Throwable { + try { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mKeyStore.abort(operationToken); + } + } finally { + super.finalize(); + } + } + + @Override + public Long getOperationHandle() { + return mOperationHandle; + } +} diff --git a/keystore/java/android/security/KeyStoreKeyCharacteristics.java b/keystore/java/android/security/KeyStoreKeyCharacteristics.java new file mode 100644 index 000000000000..543b5d8ca4a0 --- /dev/null +++ b/keystore/java/android/security/KeyStoreKeyCharacteristics.java @@ -0,0 +1,68 @@ +/* + * 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; + +import android.annotation.IntDef; +import android.security.keymaster.KeymasterDefs; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Characteristics of {@code AndroidKeyStore} keys. + * + * @hide + */ +public abstract class KeyStoreKeyCharacteristics { + private KeyStoreKeyCharacteristics() {} + + @Retention(RetentionPolicy.SOURCE) + @IntDef({Origin.GENERATED_INSIDE_TEE, Origin.GENERATED_OUTSIDE_OF_TEE, Origin.IMPORTED}) + public @interface OriginEnum {} + + /** + * Origin of the key. + */ + public static abstract class Origin { + private Origin() {} + + /** Key was generated inside a TEE. */ + public static final int GENERATED_INSIDE_TEE = 1; + + /** Key was generated outside of a TEE. */ + public static final int GENERATED_OUTSIDE_OF_TEE = 2; + + /** Key was imported. */ + public static final int IMPORTED = 0; + + /** + * @hide + */ + public static @OriginEnum int fromKeymaster(int origin) { + switch (origin) { + case KeymasterDefs.KM_ORIGIN_HARDWARE: + return GENERATED_INSIDE_TEE; + case KeymasterDefs.KM_ORIGIN_SOFTWARE: + return GENERATED_OUTSIDE_OF_TEE; + case KeymasterDefs.KM_ORIGIN_IMPORTED: + return IMPORTED; + default: + throw new IllegalArgumentException("Unknown origin: " + origin); + } + } + } +} diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java index 01e6dcd2f705..c27ccb167054 100644 --- a/keystore/java/android/security/KeyStoreKeyConstraints.java +++ b/keystore/java/android/security/KeyStoreKeyConstraints.java @@ -1,3 +1,19 @@ +/* + * 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; import android.annotation.IntDef; @@ -7,7 +23,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.Locale; +import java.util.Set; /** * Constraints for {@code AndroidKeyStore} keys. @@ -222,16 +241,6 @@ public abstract class KeyStoreKeyConstraints { throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm); } } - - /** - * @hide - */ - public static String toJCAKeyPairAlgorithm(@AlgorithmEnum int algorithm) { - switch (algorithm) { - default: - throw new IllegalArgumentException("Unsupported key alorithm: " + algorithm); - } - } } @Retention(RetentionPolicy.SOURCE) @@ -290,6 +299,36 @@ public abstract class KeyStoreKeyConstraints { throw new IllegalArgumentException("Unknown padding: " + padding); } } + + /** + * @hide + */ + public static String toString(@PaddingEnum int padding) { + switch (padding) { + case NONE: + return "NONE"; + case ZERO: + return "ZERO"; + case PKCS7: + return "PKCS#7"; + default: + throw new IllegalArgumentException("Unknown padding: " + padding); + } + } + + /** + * @hide + */ + public static @PaddingEnum int fromJCAPadding(String padding) { + String paddingLower = padding.toLowerCase(Locale.US); + if ("nopadding".equals(paddingLower)) { + return NONE; + } else if ("pkcs7padding".equals(paddingLower)) { + return PKCS7; + } else { + throw new IllegalArgumentException("Unknown padding: " + padding); + } + } } @Retention(RetentionPolicy.SOURCE) @@ -385,10 +424,24 @@ public abstract class KeyStoreKeyConstraints { throw new IllegalArgumentException("Unknown digest: " + digest); } } + + /** + * @hide + */ + public static Integer getOutputSizeBytes(@DigestEnum int digest) { + switch (digest) { + case NONE: + return null; + case SHA256: + return 256 / 8; + default: + throw new IllegalArgumentException("Unknown digest: " + digest); + } + } } @Retention(RetentionPolicy.SOURCE) - @IntDef({BlockMode.ECB}) + @IntDef({BlockMode.ECB, BlockMode.CBC, BlockMode.CTR}) public @interface BlockModeEnum {} /** @@ -397,11 +450,15 @@ public abstract class KeyStoreKeyConstraints { public static abstract class BlockMode { private BlockMode() {} - /** - * Electronic Codebook (ECB) block mode. - */ + /** Electronic Codebook (ECB) block mode. */ public static final int ECB = 0; + /** Cipher Block Chaining (CBC) block mode. */ + public static final int CBC = 1; + + /** Counter (CTR) block mode. */ + public static final int CTR = 2; + /** * @hide */ @@ -409,6 +466,10 @@ public abstract class KeyStoreKeyConstraints { switch (mode) { case ECB: return KeymasterDefs.KM_MODE_ECB; + case CBC: + return KeymasterDefs.KM_MODE_CBC; + case CTR: + return KeymasterDefs.KM_MODE_CTR; default: throw new IllegalArgumentException("Unknown block mode: " + mode); } @@ -421,9 +482,128 @@ public abstract class KeyStoreKeyConstraints { switch (mode) { case KeymasterDefs.KM_MODE_ECB: return ECB; + case KeymasterDefs.KM_MODE_CBC: + return CBC; + case KeymasterDefs.KM_MODE_CTR: + return CTR; + default: + throw new IllegalArgumentException("Unknown block mode: " + mode); + } + } + + /** + * @hide + */ + public static String toString(@BlockModeEnum int mode) { + switch (mode) { + case ECB: + return "ECB"; + case CBC: + return "CBC"; + case CTR: + return "CTR"; default: throw new IllegalArgumentException("Unknown block mode: " + mode); } } + + /** + * @hide + */ + public static @BlockModeEnum int fromJCAMode(String mode) { + String modeLower = mode.toLowerCase(Locale.US); + if ("ecb".equals(modeLower)) { + return ECB; + } else if ("cbc".equals(modeLower)) { + return CBC; + } else if ("ctr".equals(modeLower)) { + return CTR; + } else { + throw new IllegalArgumentException("Unknown block mode: " + mode); + } + } + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({UserAuthenticator.LOCK_SCREEN}) + public @interface UserAuthenticatorEnum {} + + /** + * User authenticators which can be used to restrict/protect access to keys. + */ + public static abstract class UserAuthenticator { + private UserAuthenticator() {} + + /** Lock screen. */ + public static final int LOCK_SCREEN = 1; + + /** + * @hide + */ + public static int toKeymaster(@UserAuthenticatorEnum int userAuthenticator) { + switch (userAuthenticator) { + case LOCK_SCREEN: + return LOCK_SCREEN; + default: + throw new IllegalArgumentException( + "Unknown user authenticator: " + userAuthenticator); + } + } + + /** + * @hide + */ + public static @UserAuthenticatorEnum int fromKeymaster(int userAuthenticator) { + switch (userAuthenticator) { + case LOCK_SCREEN: + return LOCK_SCREEN; + default: + throw new IllegalArgumentException( + "Unknown user authenticator: " + userAuthenticator); + } + } + + /** + * @hide + */ + public static int allToKeymaster(Set<Integer> userAuthenticators) { + int result = 0; + for (@UserAuthenticatorEnum int userAuthenticator : userAuthenticators) { + result |= toKeymaster(userAuthenticator); + } + return result; + } + + /** + * @hide + */ + public static Set<Integer> allFromKeymaster(int userAuthenticators) { + int userAuthenticator = 1; + Set<Integer> result = null; + while (userAuthenticators != 0) { + if ((userAuthenticators & 1) != 0) { + if (result == null) { + result = new HashSet<Integer>(); + } + result.add(fromKeymaster(userAuthenticator)); + } + userAuthenticators >>>= 1; + userAuthenticator <<= 1; + } + return (result != null) ? result : Collections.<Integer>emptySet(); + } + + /** + * @hide + */ + public static String toString(@UserAuthenticatorEnum int userAuthenticator) { + switch (userAuthenticator) { + case LOCK_SCREEN: + return "LOCK_SCREEN"; + default: + throw new IllegalArgumentException( + "Unknown user authenticator: " + userAuthenticator); + } + } } } diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java new file mode 100644 index 000000000000..69533b407b3c --- /dev/null +++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java @@ -0,0 +1,210 @@ +/* + * 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; + +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; + +import java.security.InvalidAlgorithmParameterException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Date; + +import javax.crypto.KeyGeneratorSpi; +import javax.crypto.SecretKey; + +/** + * {@link KeyGeneratorSpi} backed by Android KeyStore. + * + * @hide + */ +public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { + + public static class AES extends KeyStoreKeyGeneratorSpi { + public AES() { + super(KeyStoreKeyConstraints.Algorithm.AES, 128); + } + } + + public static class HmacSHA256 extends KeyStoreKeyGeneratorSpi { + public HmacSHA256() { + super(KeyStoreKeyConstraints.Algorithm.HMAC, + KeyStoreKeyConstraints.Digest.SHA256, + KeyStoreKeyConstraints.Digest.getOutputSizeBytes( + KeyStoreKeyConstraints.Digest.SHA256) * 8); + } + } + + private final KeyStore mKeyStore = KeyStore.getInstance(); + private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm; + private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest; + private final int mDefaultKeySizeBits; + + private KeyGeneratorSpec mSpec; + private SecureRandom mRng; + + protected KeyStoreKeyGeneratorSpi( + @KeyStoreKeyConstraints.AlgorithmEnum int algorithm, + int defaultKeySizeBits) { + this(algorithm, null, defaultKeySizeBits); + } + + protected KeyStoreKeyGeneratorSpi( + @KeyStoreKeyConstraints.AlgorithmEnum int algorithm, + @KeyStoreKeyConstraints.DigestEnum Integer digest, + int defaultKeySizeBits) { + mAlgorithm = algorithm; + mDigest = digest; + mDefaultKeySizeBits = defaultKeySizeBits; + } + + @Override + protected SecretKey engineGenerateKey() { + KeyGeneratorSpec spec = mSpec; + if (spec == null) { + throw new IllegalStateException("Not initialized"); + } + + if ((spec.isEncryptionRequired()) + && (mKeyStore.state() != KeyStore.State.UNLOCKED)) { + throw new IllegalStateException( + "Android KeyStore must be in initialized and unlocked state if encryption is" + + " required"); + } + + KeymasterArguments args = new KeymasterArguments(); + args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, + KeyStoreKeyConstraints.Algorithm.toKeymaster(mAlgorithm)); + if (mDigest != null) { + args.addInt(KeymasterDefs.KM_TAG_DIGEST, + KeyStoreKeyConstraints.Digest.toKeymaster(mDigest)); + Integer digestOutputSizeBytes = + KeyStoreKeyConstraints.Digest.getOutputSizeBytes(mDigest); + if (digestOutputSizeBytes != null) { + // TODO: Remove MAC length constraint once Keymaster API no longer requires it. + // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster + args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes); + } + } + if (mAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) { + if (mDigest == null) { + throw new IllegalStateException("Digest algorithm must be specified for key" + + " algorithm " + KeyStoreKeyConstraints.Algorithm.toString(mAlgorithm)); + } + } + int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits; + args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits); + @KeyStoreKeyConstraints.PurposeEnum int purposes = (spec.getPurposes() != null) + ? spec.getPurposes() + : (KeyStoreKeyConstraints.Purpose.ENCRYPT + | KeyStoreKeyConstraints.Purpose.DECRYPT + | KeyStoreKeyConstraints.Purpose.SIGN + | KeyStoreKeyConstraints.Purpose.VERIFY); + for (int keymasterPurpose : + KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) { + args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); + } + if (spec.getBlockMode() != null) { + args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, + KeyStoreKeyConstraints.BlockMode.toKeymaster(spec.getBlockMode())); + } + if (spec.getPadding() != null) { + args.addInt(KeymasterDefs.KM_TAG_PADDING, + KeyStoreKeyConstraints.Padding.toKeymaster(spec.getPadding())); + } + if (spec.getMaxUsesPerBoot() != null) { + args.addInt(KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT, spec.getMaxUsesPerBoot()); + } + if (spec.getMinSecondsBetweenOperations() != null) { + args.addInt(KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS, + spec.getMinSecondsBetweenOperations()); + } + if (spec.getUserAuthenticators().isEmpty()) { + args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); + } else { + args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, + KeyStoreKeyConstraints.UserAuthenticator.allToKeymaster( + spec.getUserAuthenticators())); + } + if (spec.getUserAuthenticationValidityDurationSeconds() != null) { + args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, + spec.getUserAuthenticationValidityDurationSeconds()); + } + args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, + (spec.getKeyValidityStart() != null) + ? spec.getKeyValidityStart() : new Date(0)); + args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + (spec.getKeyValidityForOriginationEnd() != null) + ? spec.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE)); + args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + (spec.getKeyValidityForConsumptionEnd() != null) + ? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); + + if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0) + || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) { + // Permit caller-specified IV. This is needed due to the Cipher abstraction. + args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); + } + + byte[] additionalEntropy = null; + SecureRandom rng = mRng; + if (rng != null) { + additionalEntropy = new byte[(keySizeBits + 7) / 8]; + rng.nextBytes(additionalEntropy); + } + + int flags = spec.getFlags(); + String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias(); + int errorCode = mKeyStore.generateKey( + keyAliasInKeystore, args, additionalEntropy, flags, new KeyCharacteristics()); + if (errorCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getCryptoOperationException(errorCode); + } + String keyAlgorithmJCA = + KeyStoreKeyConstraints.Algorithm.toJCASecretKeyAlgorithm(mAlgorithm, mDigest); + return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); + } + + @Override + protected void engineInit(SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without an " + + KeyGeneratorSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + if ((params == null) || (!(params instanceof KeyGeneratorSpec))) { + throw new InvalidAlgorithmParameterException("Cannot initialize without an " + + KeyGeneratorSpec.class.getName() + " parameter"); + } + KeyGeneratorSpec spec = (KeyGeneratorSpec) params; + if (spec.getKeystoreAlias() == null) { + throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); + } + + mSpec = spec; + mRng = random; + } + + @Override + protected void engineInit(int keySize, SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without a " + + KeyGeneratorSpec.class.getName() + " parameter"); + } +} diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java new file mode 100644 index 000000000000..ddeefbd246c2 --- /dev/null +++ b/keystore/java/android/security/KeyStoreKeySpec.java @@ -0,0 +1,226 @@ +/* + * 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; + +import java.security.spec.KeySpec; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * Information about a key from the <a href="{@docRoot}training/articles/keystore.html">Android + * KeyStore</a>. + * + * @hide + */ +public class KeyStoreKeySpec implements KeySpec { + private final String mKeystoreAlias; + private final int mKeySize; + private final @KeyStoreKeyCharacteristics.OriginEnum int mOrigin; + private final Date mKeyValidityStart; + private final Date mKeyValidityForOriginationEnd; + private final Date mKeyValidityForConsumptionEnd; + private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes; + private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm; + private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding; + private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest; + private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode; + private final Integer mMinSecondsBetweenOperations; + private final Integer mMaxUsesPerBoot; + private final Set<Integer> mUserAuthenticators; + private final Set<Integer> mTeeBackedUserAuthenticators; + private final Integer mUserAuthenticationValidityDurationSeconds; + + + /** + * @hide + */ + KeyStoreKeySpec(String keystoreKeyAlias, + @KeyStoreKeyCharacteristics.OriginEnum int origin, + int keySize, Date keyValidityStart, Date keyValidityForOriginationEnd, + Date keyValidityForConsumptionEnd, + @KeyStoreKeyConstraints.PurposeEnum int purposes, + @KeyStoreKeyConstraints.AlgorithmEnum int algorithm, + @KeyStoreKeyConstraints.PaddingEnum Integer padding, + @KeyStoreKeyConstraints.DigestEnum Integer digest, + @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode, + Integer minSecondsBetweenOperations, + Integer maxUsesPerBoot, + Set<Integer> userAuthenticators, + Set<Integer> teeBackedUserAuthenticators, + Integer userAuthenticationValidityDurationSeconds) { + mKeystoreAlias = keystoreKeyAlias; + mOrigin = origin; + mKeySize = keySize; + mKeyValidityStart = keyValidityStart; + mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; + mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mPurposes = purposes; + mAlgorithm = algorithm; + mPadding = padding; + mDigest = digest; + mBlockMode = blockMode; + mMinSecondsBetweenOperations = minSecondsBetweenOperations; + mMaxUsesPerBoot = maxUsesPerBoot; + mUserAuthenticators = (userAuthenticators != null) + ? new HashSet<Integer>(userAuthenticators) + : Collections.<Integer>emptySet(); + mTeeBackedUserAuthenticators = (teeBackedUserAuthenticators != null) + ? new HashSet<Integer>(teeBackedUserAuthenticators) + : Collections.<Integer>emptySet(); + mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; + } + + /** + * Gets the entry alias under which the key is stored in the {@code AndroidKeyStore}. + */ + public String getKeystoreAlias() { + return mKeystoreAlias; + } + + /** + * Gets the origin of the key. + */ + public @KeyStoreKeyCharacteristics.OriginEnum int getOrigin() { + return mOrigin; + } + + /** + * Gets the key's size in bits. + */ + public int getKeySize() { + return mKeySize; + } + + /** + * Gets the time instant before which the key is not yet valid. + * + * @return instant or {@code null} if not restricted. + */ + public Date getKeyValidityStart() { + return mKeyValidityStart; + } + + /** + * Gets the time instant after which the key is no long valid for decryption and verification. + * + * @return instant or {@code null} if not restricted. + */ + public Date getKeyValidityForConsumptionEnd() { + return mKeyValidityForConsumptionEnd; + } + + /** + * Gets the time instant after which the key is no long valid for encryption and signing. + * + * @return instant or {@code null} if not restricted. + */ + public Date getKeyValidityForOriginationEnd() { + return mKeyValidityForOriginationEnd; + } + + /** + * Gets the set of purposes for which the key can be used. + */ + public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() { + return mPurposes; + } + + /** + * Gets the algorithm of the key. + */ + public @KeyStoreKeyConstraints.AlgorithmEnum int getAlgorithm() { + return mAlgorithm; + } + + /** + * Gets the only block mode with which the key can be used. + * + * @return block mode or {@code null} if the block mode is not restricted. + */ + public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() { + return mBlockMode; + } + + /** + * Gets the only padding mode with which the key can be used. + * + * @return padding mode or {@code null} if the padding mode is not restricted. + */ + public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() { + return mPadding; + } + + /** + * Gets the only digest algorithm with which the key can be used. + * + * @return digest algorithm or {@code null} if the digest algorithm is not restricted. + */ + public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() { + return mDigest; + } + + /** + * Gets the minimum number of seconds that must expire since the most recent use of the key + * before it can be used again. + * + * @return number of seconds or {@code null} if there is no restriction on how frequently a key + * can be used. + */ + public Integer getMinSecondsBetweenOperations() { + return mMinSecondsBetweenOperations; + } + + /** + * Gets the number of times the key can be used without rebooting the device. + * + * @return maximum number of times or {@code null} if there is no restriction. + */ + public Integer getMaxUsesPerBoot() { + return mMaxUsesPerBoot; + } + + /** + * Gets the user authenticators which protect access to the key. The key can only be used iff + * the user has authenticated to at least one of these user authenticators. + * + * @return user authenticators or empty set if the key can be used without user authentication. + */ + public Set<Integer> getUserAuthenticators() { + return new HashSet<Integer>(mUserAuthenticators); + } + + /** + * Gets the TEE-backed user authenticators which protect access to the key. This is a subset of + * the user authentications returned by {@link #getUserAuthenticators()}. + */ + public Set<Integer> getTeeBackedUserAuthenticators() { + return new HashSet<Integer>(mTeeBackedUserAuthenticators); + } + + /** + * Gets the duration of time (seconds) for which the key can be used after the user + * successfully authenticates to one of the associated user authenticators. + * + * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication + * is required for every use of the key. + */ + public Integer getUserAuthenticationValidityDurationSeconds() { + return mUserAuthenticationValidityDurationSeconds; + } +} diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java index 2428c2a57a7a..998e1d98f737 100644 --- a/keystore/java/android/security/KeyStoreParameter.java +++ b/keystore/java/android/security/KeyStoreParameter.java @@ -60,8 +60,10 @@ public final class KeyStoreParameter implements ProtectionParameter { private final Set<Integer> mUserAuthenticators; private final Integer mUserAuthenticationValidityDurationSeconds; - private KeyStoreParameter(int flags, Date keyValidityStart, - Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, + private KeyStoreParameter(int flags, + Date keyValidityStart, + Date keyValidityForOriginationEnd, + Date keyValidityForConsumptionEnd, @KeyStoreKeyConstraints.PurposeEnum Integer purposes, @KeyStoreKeyConstraints.AlgorithmEnum Integer algorithm, @KeyStoreKeyConstraints.PaddingEnum Integer padding, @@ -174,8 +176,8 @@ public final class KeyStoreParameter implements ProtectionParameter { } /** - * Gets the digest to which the key is restricted when generating Message Authentication Codes - * (MACs). + * Gets the digest to which the key is restricted when generating signatures or Message + * Authentication Codes (MACs). * * @return digest or {@code null} if the digest is not restricted. * @@ -404,12 +406,13 @@ public final class KeyStoreParameter implements ProtectionParameter { } /** - * Restricts the key to being used only with the provided digest when generating Message - * Authentication Codes (MACs). Attempts to use the key with any other digest will be - * rejected. + * Restricts the key to being used only with the provided digest when generating signatures + * or Message Authentication Codes (MACs). Attempts to use the key with any other digest + * will be rejected. * * <p>For MAC keys, the default is to restrict to the digest specified in the key algorithm - * name. + * name. For asymmetric signing keys this constraint must be specified because there is no + * default. * * @see java.security.Key#getAlgorithm() * @@ -502,10 +505,18 @@ public final class KeyStoreParameter implements ProtectionParameter { * @return built instance of {@code KeyStoreParameter} */ public KeyStoreParameter build() { - return new KeyStoreParameter(mFlags, mKeyValidityStart, - mKeyValidityForOriginationEnd, mKeyValidityForConsumptionEnd, mPurposes, - mAlgorithm, mPadding, mDigest, mBlockMode, mMinSecondsBetweenOperations, - mMaxUsesPerBoot, mUserAuthenticators, + return new KeyStoreParameter(mFlags, + mKeyValidityStart, + mKeyValidityForOriginationEnd, + mKeyValidityForConsumptionEnd, + mPurposes, + mAlgorithm, + mPadding, + mDigest, + mBlockMode, + mMinSecondsBetweenOperations, + mMaxUsesPerBoot, + mUserAuthenticators, mUserAuthenticationValidityDurationSeconds); } } diff --git a/keystore/java/android/security/KeyStoreSecretKey.java b/keystore/java/android/security/KeyStoreSecretKey.java index 94101271b0e6..7f0e3d39aa03 100644 --- a/keystore/java/android/security/KeyStoreSecretKey.java +++ b/keystore/java/android/security/KeyStoreSecretKey.java @@ -1,3 +1,19 @@ +/* + * 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; import javax.crypto.SecretKey; diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java new file mode 100644 index 000000000000..f552759cfb82 --- /dev/null +++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java @@ -0,0 +1,175 @@ +/* + * 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; + +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterDefs; + +import java.security.InvalidKeyException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Date; +import java.util.Set; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactorySpi; +import javax.crypto.spec.SecretKeySpec; + +/** + * {@link SecretKeyFactorySpi} backed by Android KeyStore. + * + * @hide + */ +public class KeyStoreSecretKeyFactorySpi 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 KeyStoreSecretKey)) { + 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 (!KeyStoreKeySpec.class.equals(keySpecClass)) { + throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); + } + String keyAliasInKeystore = ((KeyStoreSecretKey) key).getAlias(); + String entryAlias; + if (keyAliasInKeystore.startsWith(Credentials.USER_SECRET_KEY)) { + entryAlias = keyAliasInKeystore.substring(Credentials.USER_SECRET_KEY.length()); + } else { + throw new InvalidKeySpecException("Invalid key alias: " + keyAliasInKeystore); + } + + KeyCharacteristics keyCharacteristics = new KeyCharacteristics(); + int errorCode = + mKeyStore.getKeyCharacteristics(keyAliasInKeystore, null, null, keyCharacteristics); + if (errorCode != KeyStore.NO_ERROR) { + throw new InvalidKeySpecException("Failed to obtain information about key." + + " Keystore error: " + errorCode); + } + + @KeyStoreKeyCharacteristics.OriginEnum Integer origin; + int keySize; + @KeyStoreKeyConstraints.PurposeEnum int purposes; + @KeyStoreKeyConstraints.AlgorithmEnum int algorithm; + @KeyStoreKeyConstraints.PaddingEnum Integer padding; + @KeyStoreKeyConstraints.DigestEnum Integer digest; + @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode; + try { + origin = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ORIGIN); + if (origin == null) { + throw new InvalidKeySpecException("Key origin not available"); + } + origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(origin); + Integer keySizeInteger = + KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_KEY_SIZE); + if (keySizeInteger == null) { + throw new InvalidKeySpecException("Key size not available"); + } + keySize = keySizeInteger; + purposes = KeyStoreKeyConstraints.Purpose.allFromKeymaster( + KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_PURPOSE)); + Integer alg = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ALGORITHM); + if (alg == null) { + throw new InvalidKeySpecException("Key algorithm not available"); + } + algorithm = KeyStoreKeyConstraints.Algorithm.fromKeymaster(alg); + padding = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_PADDING); + if (padding != null) { + padding = KeyStoreKeyConstraints.Padding.fromKeymaster(padding); + } + digest = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_DIGEST); + if (digest != null) { + digest = KeyStoreKeyConstraints.Digest.fromKeymaster(digest); + } + blockMode = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_BLOCK_MODE); + if (blockMode != null) { + blockMode = KeyStoreKeyConstraints.BlockMode.fromKeymaster(blockMode); + } + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException("Unsupported key characteristic", e); + } + + Date keyValidityStart = + KeymasterUtils.getDate(keyCharacteristics, KeymasterDefs.KM_TAG_ACTIVE_DATETIME); + if ((keyValidityStart != null) && (keyValidityStart.getTime() <= 0)) { + keyValidityStart = null; + } + Date keyValidityForOriginationEnd = KeymasterUtils.getDate(keyCharacteristics, + KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME); + if ((keyValidityForOriginationEnd != null) + && (keyValidityForOriginationEnd.getTime() == Long.MAX_VALUE)) { + keyValidityForOriginationEnd = null; + } + Date keyValidityForConsumptionEnd = KeymasterUtils.getDate(keyCharacteristics, + KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME); + if ((keyValidityForConsumptionEnd != null) + && (keyValidityForConsumptionEnd.getTime() == Long.MAX_VALUE)) { + keyValidityForConsumptionEnd = null; + } + + int swEnforcedUserAuthenticatorIds = + keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); + int hwEnforcedUserAuthenticatorIds = + keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); + int userAuthenticatorIds = swEnforcedUserAuthenticatorIds | hwEnforcedUserAuthenticatorIds; + Set<Integer> userAuthenticators = + KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster(userAuthenticatorIds); + Set<Integer> teeBackedUserAuthenticators = + KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster( + hwEnforcedUserAuthenticatorIds); + + return new KeyStoreKeySpec(entryAlias, + origin, + keySize, + keyValidityStart, + keyValidityForOriginationEnd, + keyValidityForConsumptionEnd, + purposes, + algorithm, + padding, + digest, + blockMode, + KeymasterUtils.getInt(keyCharacteristics, + KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS), + KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT), + userAuthenticators, + teeBackedUserAuthenticators, + KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_AUTH_TIMEOUT)); + } + + @Override + protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException { + throw new UnsupportedOperationException( + "Key import into Android KeyStore is not supported"); + } + + @Override + protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException { + throw new UnsupportedOperationException( + "Key import into Android KeyStore is not supported"); + } +} diff --git a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java b/keystore/java/android/security/KeymasterException.java index 2fe68f8dfd58..484be12f5ade 100644 --- a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java +++ b/keystore/java/android/security/KeymasterException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * 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. @@ -14,20 +14,23 @@ * limitations under the License. */ -package com.android.server.updates; +package android.security; -import android.util.Base64; - -import java.io.IOException; +/** + * Keymaster exception. + * + * @hide + */ +public class KeymasterException extends Exception { -public class TZInfoInstallReceiver extends ConfigUpdateInstallReceiver { + private final int mErrorCode; - public TZInfoInstallReceiver() { - super("/data/misc/zoneinfo/", "tzdata", "metadata/", "version"); + public KeymasterException(int errorCode, String message) { + super(message); + mErrorCode = errorCode; } - @Override - protected void install(byte[] encodedContent, int version) throws IOException { - super.install(Base64.decode(encodedContent, Base64.DEFAULT), version); + public int getErrorCode() { + return mErrorCode; } } diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java new file mode 100644 index 000000000000..c426a34bb5e7 --- /dev/null +++ b/keystore/java/android/security/KeymasterUtils.java @@ -0,0 +1,94 @@ +/* + * 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; + +import android.security.keymaster.KeyCharacteristics; +import android.security.keymaster.KeymasterDefs; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @hide + */ +public abstract class KeymasterUtils { + private KeymasterUtils() {} + + public static KeymasterException getKeymasterException(int keymasterErrorCode) { + switch (keymasterErrorCode) { + 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 KeymasterException(keymasterErrorCode, + "Invalid user authentication validity duration"); + default: + return new KeymasterException(keymasterErrorCode, + KeymasterDefs.getErrorMessage(keymasterErrorCode)); + } + } + + public static CryptoOperationException getCryptoOperationException(KeymasterException 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 KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED: + return new UserNotAuthenticatedException(); + default: + return new CryptoOperationException("Crypto operation failed", e); + } + } + + public static CryptoOperationException getCryptoOperationException(int keymasterErrorCode) { + return getCryptoOperationException(getKeymasterException(keymasterErrorCode)); + } + + public static Integer getInt(KeyCharacteristics keyCharacteristics, int tag) { + if (keyCharacteristics.hwEnforced.containsTag(tag)) { + return keyCharacteristics.hwEnforced.getInt(tag, -1); + } else if (keyCharacteristics.swEnforced.containsTag(tag)) { + return keyCharacteristics.swEnforced.getInt(tag, -1); + } else { + return null; + } + } + + public static List<Integer> getInts(KeyCharacteristics keyCharacteristics, int tag) { + List<Integer> result = new ArrayList<Integer>(); + result.addAll(keyCharacteristics.hwEnforced.getInts(tag)); + result.addAll(keyCharacteristics.swEnforced.getInts(tag)); + return result; + } + + public static Date getDate(KeyCharacteristics keyCharacteristics, int tag) { + Date result = keyCharacteristics.hwEnforced.getDate(tag, null); + if (result == null) { + result = keyCharacteristics.swEnforced.getDate(tag, null); + } + return result; + } + + public static boolean getBoolean(KeyCharacteristics keyCharacteristics, int tag) { + if (keyCharacteristics.hwEnforced.containsTag(tag)) { + return keyCharacteristics.hwEnforced.getBoolean(tag, false); + } else { + return keyCharacteristics.swEnforced.getBoolean(tag, false); + } + } +} diff --git a/keystore/java/android/security/UserNotAuthenticatedException.java b/keystore/java/android/security/UserNotAuthenticatedException.java new file mode 100644 index 000000000000..e6342ef79673 --- /dev/null +++ b/keystore/java/android/security/UserNotAuthenticatedException.java @@ -0,0 +1,49 @@ +/* + * 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; + +/** + * Indicates that a cryptographic operation could not be performed because the user has not been + * authenticated recently enough. + * + * @hide + */ +public class UserNotAuthenticatedException extends CryptoOperationException { + + /** + * Constructs a new {@code UserNotAuthenticatedException} without detail message and cause. + */ + public UserNotAuthenticatedException() { + super("User not authenticated"); + } + + /** + * Constructs a new {@code UserNotAuthenticatedException} with the provided detail message and + * no cause. + */ + public UserNotAuthenticatedException(String message) { + super(message); + } + + /** + * Constructs a new {@code UserNotAuthenticatedException} with the provided detail message and + * cause. + */ + public UserNotAuthenticatedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java index f755bb08a6e4..7468fb5e1000 100644 --- a/keystore/tests/src/android/security/KeyStoreTest.java +++ b/keystore/tests/src/android/security/KeyStoreTest.java @@ -717,7 +717,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { RSAKeyGenParameterSpec.F4.longValue()); KeyCharacteristics outCharacteristics = new KeyCharacteristics(); - int result = mKeyStore.generateKey(name, args, 0, outCharacteristics); + int result = mKeyStore.generateKey(name, args, null, 0, outCharacteristics); assertEquals("generateRsaKey should succeed", KeyStore.NO_ERROR, result); return outCharacteristics; } @@ -726,6 +726,24 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { generateRsaKey("test"); mKeyStore.delete("test"); } + + public void testGenerateRsaWithEntropy() throws Exception { + byte[] entropy = new byte[] {1,2,3,4,5}; + String name = "test"; + KeymasterArguments args = new KeymasterArguments(); + args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT); + args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT); + args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA); + args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE); + args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048); + args.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, + RSAKeyGenParameterSpec.F4.longValue()); + + KeyCharacteristics outCharacteristics = new KeyCharacteristics(); + int result = mKeyStore.generateKey(name, args, entropy, 0, outCharacteristics); + assertEquals("generateKey should succeed", KeyStore.NO_ERROR, result); + } + public void testGenerateAndDelete() throws Exception { generateRsaKey("test"); assertTrue("delete should succeed", mKeyStore.delete("test")); @@ -756,7 +774,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { RSAKeyGenParameterSpec.F4.longValue()); KeyCharacteristics outCharacteristics = new KeyCharacteristics(); - int result = mKeyStore.generateKey(name, args, 0, outCharacteristics); + int result = mKeyStore.generateKey(name, args, null, 0, outCharacteristics); assertEquals("generateRsaKey should succeed", KeyStore.NO_ERROR, result); assertEquals("getKeyCharacteristics should fail without application ID", KeymasterDefs.KM_ERROR_INVALID_KEY_BLOB, @@ -790,13 +808,13 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16); KeyCharacteristics outCharacteristics = new KeyCharacteristics(); - int rc = mKeyStore.generateKey(name, args, 0, outCharacteristics); + int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics); assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc); KeymasterArguments out = new KeymasterArguments(); args = new KeymasterArguments(); OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, - true, args, out); + true, args, null, out); IBinder token = result.token; assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode); result = mKeyStore.update(token, null, new byte[] {0x01, 0x02, 0x03, 0x04}); @@ -826,7 +844,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { private byte[] doOperation(String name, int purpose, byte[] in, KeymasterArguments beginArgs) { KeymasterArguments out = new KeymasterArguments(); OperationResult result = mKeyStore.begin(name, purpose, - true, beginArgs, out); + true, beginArgs, null, out); assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode); IBinder token = result.token; result = mKeyStore.update(token, null, in); @@ -885,18 +903,19 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16); KeyCharacteristics outCharacteristics = new KeyCharacteristics(); - int rc = mKeyStore.generateKey(name, args, 0, outCharacteristics); + int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics); assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc); KeymasterArguments out = new KeymasterArguments(); args = new KeymasterArguments(); OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, - true, args, out); + true, args, null, out); assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode); IBinder first = result.token; // Implementation detail: softkeymaster supports 16 concurrent operations for (int i = 0; i < 16; i++) { - result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, true, args, out); + result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, true, args, null, + out); assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode); } // At this point the first operation should be pruned. diff --git a/rs/java/android/renderscript/ScriptGroup2.java b/rs/java/android/renderscript/ScriptGroup2.java index 4a56572fe739..13e22aa2327a 100644 --- a/rs/java/android/renderscript/ScriptGroup2.java +++ b/rs/java/android/renderscript/ScriptGroup2.java @@ -44,6 +44,7 @@ you will need approval. public class ScriptGroup2 extends BaseObj { public static class Closure extends BaseObj { + private Object[] mArgs; private Allocation mReturnValue; private Map<Script.FieldID, Object> mBindings; @@ -62,8 +63,9 @@ public class ScriptGroup2 extends BaseObj { Object[] args, Map<Script.FieldID, Object> globals) { super(0, rs); + mArgs = args; mReturnValue = Allocation.createTyped(rs, returnType); - mBindings = new HashMap<Script.FieldID, Object>(); + mBindings = globals; mGlobalFuture = new HashMap<Script.FieldID, Future>(); int numValues = args.length + globals.size(); @@ -112,7 +114,8 @@ public class ScriptGroup2 extends BaseObj { super(0, rs); mFP = FieldPacker.createFieldPack(args); - mBindings = new HashMap<Script.FieldID, Object>(); + mArgs = args; + mBindings = globals; mGlobalFuture = new HashMap<Script.FieldID, Future>(); int numValues = globals.size(); @@ -198,11 +201,13 @@ public class ScriptGroup2 extends BaseObj { } void setArg(int index, Object obj) { + mArgs[index] = obj; ValueAndSize vs = new ValueAndSize(mRS, obj); mRS.nClosureSetArg(getID(mRS), index, vs.value, vs.size); } void setGlobal(Script.FieldID fieldID, Object obj) { + mBindings.put(fieldID, obj); ValueAndSize vs = new ValueAndSize(mRS, obj); mRS.nClosureSetGlobal(getID(mRS), fieldID.getID(mRS), vs.value, vs.size); } diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp index ba20881580ba..3591199acdaf 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -372,7 +372,7 @@ nClosureCreate(JNIEnv *_env, jobject _this, jlong con, jlong kernelID, return (jlong)(uintptr_t)rsClosureCreate( (RsContext)con, (RsScriptKernelID)kernelID, (RsAllocation)returnValue, fieldIDs, (size_t)fieldIDs_length, values, (size_t)values_length, - (size_t*)sizes, (size_t)sizes_length, + (int*)sizes, (size_t)sizes_length, depClosures, (size_t)depClosures_length, depFieldIDs, (size_t)depFieldIDs_length); } @@ -405,7 +405,7 @@ nInvokeClosureCreate(JNIEnv *_env, jobject _this, jlong con, jlong invokeID, return (jlong)(uintptr_t)rsInvokeClosureCreate( (RsContext)con, (RsScriptInvokeID)invokeID, jParams, jParamLength, fieldIDs, (size_t)fieldIDs_length, values, (size_t)values_length, - (size_t*)sizes, (size_t)sizes_length); + (int*)sizes, (size_t)sizes_length); } static void diff --git a/services/core/Android.mk b/services/core/Android.mk index 5c4520170fd8..43249e723bbb 100644 --- a/services/core/Android.mk +++ b/services/core/Android.mk @@ -10,5 +10,6 @@ LOCAL_SRC_FILES += \ java/com/android/server/am/EventLogTags.logtags LOCAL_JAVA_LIBRARIES := android.policy telephony-common +LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ab1a1e84bd24..bcbc54b29446 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -169,6 +169,7 @@ import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; import android.os.IPermissionController; +import android.os.IProcessInfoService; import android.os.IRemoteCallback; import android.os.IUserManager; import android.os.Looper; @@ -1918,6 +1919,7 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceManager.addService("cpuinfo", new CpuBinder(this)); } ServiceManager.addService("permission", new PermissionController(this)); + ServiceManager.addService("processinfo", new ProcessInfoService(this)); ApplicationInfo info = mContext.getPackageManager().getApplicationInfo( "android", STOCK_PM_FLAGS); @@ -4590,17 +4592,13 @@ public final class ActivityManagerService extends ActivityManagerNative finishInstrumentationLocked(app, Activity.RESULT_CANCELED, info); } - if (!restarting) { - if (!mStackSupervisor.resumeTopActivitiesLocked()) { - // If there was nothing to resume, and we are not already - // restarting this process, but there is a visible activity that - // is hosted by the process... then make sure all visible - // activities are running, taking care of restarting this - // process. - if (hasVisibleActivities) { - mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); - } - } + if (!restarting && hasVisibleActivities && !mStackSupervisor.resumeTopActivitiesLocked()) { + // If there was nothing to resume, and we are not already + // restarting this process, but there is a visible activity that + // is hosted by the process... then make sure all visible + // activities are running, taking care of restarting this + // process. + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); } } @@ -6805,7 +6803,46 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + + // ========================================================= + // PROCESS INFO + // ========================================================= + + static class ProcessInfoService extends IProcessInfoService.Stub { + final ActivityManagerService mActivityManagerService; + ProcessInfoService(ActivityManagerService activityManagerService) { + mActivityManagerService = activityManagerService; + } + + @Override + public void getProcessStatesFromPids(/*in*/ int[] pids, /*out*/ int[] states) { + mActivityManagerService.getProcessStatesForPIDs(/*in*/ pids, /*out*/ states); + } + } + + /** + * For each PID in the given input array, write the current process state + * for that process into the output array, or -1 to indicate that no + * process with the given PID exists. + */ + public void getProcessStatesForPIDs(/*in*/ int[] pids, /*out*/ int[] states) { + if (pids == null) { + throw new NullPointerException("pids"); + } else if (states == null) { + throw new NullPointerException("states"); + } else if (pids.length != states.length) { + throw new IllegalArgumentException("input and output arrays have different lengths!"); + } + + synchronized (mPidsSelfLocked) { + for (int i = 0; i < pids.length; i++) { + ProcessRecord pr = mPidsSelfLocked.get(pids[i]); + states[i] = (pr == null) ? ActivityManager.PROCESS_STATE_NONEXISTENT : + pr.curProcState; + } + } + } + // ========================================================= // PERMISSIONS // ========================================================= @@ -17626,8 +17663,12 @@ public final class ActivityManagerService extends ActivityManagerNative mFullPssPending = true; mPendingPssProcesses.ensureCapacity(mLruProcesses.size()); mPendingPssProcesses.clear(); - for (int i=mLruProcesses.size()-1; i>=0; i--) { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { ProcessRecord app = mLruProcesses.get(i); + if (app.thread == null + || app.curProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) { + continue; + } if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) { app.pssProcState = app.setProcState; app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true, @@ -17943,8 +17984,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - if (app.setProcState < 0 || ProcessList.procStatesDifferForMem(app.curProcState, - app.setProcState)) { + if (app.setProcState == ActivityManager.PROCESS_STATE_NONEXISTENT + || ProcessList.procStatesDifferForMem(app.curProcState, app.setProcState)) { if (false && mTestPssMode && app.setProcState >= 0 && app.lastStateTime <= (now-200)) { // Experimental code to more aggressively collect pss while // running test... the problem is that this tends to collect diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index b1b2a5cbda3c..1497c1d40800 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -982,24 +982,21 @@ final class ActivityRecord { } private ActivityRecord getWaitingHistoryRecordLocked() { - // First find the real culprit... if we are waiting - // for another app to start, then we have paused dispatching - // for this activity. - ActivityRecord r = this; - if (r.waitingVisible) { + // First find the real culprit... if this activity is waiting for + // another activity to start or has stopped, then the key dispatching + // timeout should not be caused by this. + if (waitingVisible || stopped) { final ActivityStack stack = mStackSupervisor.getFocusedStack(); - // Hmmm, who might we be waiting for? - r = stack.mResumedActivity; + // Try to use the one which is closest to top. + ActivityRecord r = stack.mResumedActivity; if (r == null) { r = stack.mPausingActivity; } - // Both of those null? Fall back to 'this' again - if (r == null) { - r = this; + if (r != null) { + return r; } } - - return r; + return this; } public boolean keyDispatchingTimedOut(String reason) { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 9f22aa95fde1..82c71e3aec37 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -3390,6 +3390,9 @@ final class ActivityStack { if (DEBUG_CLEANUP) Slog.v( TAG, "Record #" + i + " " + r + ": app=" + r.app); if (r.app == app) { + if (r.visible) { + hasVisibleActivities = true; + } boolean remove; if ((!r.haveState && !r.stateNotNeeded) || r.finishing) { // Don't currently have state for the activity, or @@ -3431,9 +3434,6 @@ final class ActivityStack { // it can be restarted later when needed. if (localLOGV) Slog.v( TAG, "Keeping entry, setting app to null"); - if (r.visible) { - hasVisibleActivities = true; - } if (DEBUG_APP) Slog.v(TAG, "Clearing app during removeHistory for activity " + r); r.app = null; diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 27c5404e9fa4..8ab2368a3949 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -607,17 +607,21 @@ public final class ActivityStackSupervisor implements DisplayListener { } boolean allResumedActivitiesVisible() { + boolean foundResumed = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); final ActivityRecord r = stack.mResumedActivity; - if (r != null && (!r.nowVisible || r.waitingVisible)) { - return false; + if (r != null) { + if (!r.nowVisible || r.waitingVisible) { + return false; + } + foundResumed = true; } } } - return true; + return foundResumed; } /** diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index a6c616ae8de2..b18b05723656 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; + import android.util.ArraySet; import android.util.EventLog; import android.util.Slog; @@ -83,10 +85,10 @@ final class ProcessRecord { int curSchedGroup; // Currently desired scheduling class int setSchedGroup; // Last set to background scheduling class int trimMemoryLevel; // Last selected memory trimming level - int curProcState = -1; // Currently computed process state: ActivityManager.PROCESS_STATE_* - int repProcState = -1; // Last reported process state - int setProcState = -1; // Last set process state in process tracker - int pssProcState = -1; // The proc state we are currently requesting pss for + int curProcState = PROCESS_STATE_NONEXISTENT; // Currently computed process state + int repProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state + int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker + int pssProcState = PROCESS_STATE_NONEXISTENT; // Currently requesting pss for boolean serviceb; // Process currently is on the service B list boolean serviceHighRam; // We are forcing to service B list due to its RAM use boolean setIsForeground; // Running foreground UI when last set? diff --git a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java new file mode 100644 index 000000000000..b260e4e2fc34 --- /dev/null +++ b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java @@ -0,0 +1,54 @@ +/* + * 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 com.android.server.updates; + +import android.util.Slog; + +import java.io.File; +import java.io.IOException; +import libcore.tzdata.update.TzDataBundleInstaller; + +/** + * An install receiver responsible for installing timezone data updates. + */ +public class TzDataInstallReceiver extends ConfigUpdateInstallReceiver { + + private static final String TAG = "TZDataInstallReceiver"; + + private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo"); + private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/"; + private static final String UPDATE_METADATA_DIR_NAME = "metadata/"; + private static final String UPDATE_VERSION_FILE_NAME = "version"; + private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_bundle.zip"; + + private final TzDataBundleInstaller installer; + + public TzDataInstallReceiver() { + super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME, + UPDATE_VERSION_FILE_NAME); + installer = new TzDataBundleInstaller(TAG, TZ_DATA_DIR); + } + + @Override + protected void install(byte[] content, int version) throws IOException { + boolean valid = installer.install(content); + Slog.i(TAG, "Timezone data install valid for this device: " + valid); + // Even if !valid, we call super.install(). Only in the event of an exception should we + // not. If we didn't do this we could attempt to install repeatedly. + super.install(content, version); + } +} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 109785c45a59..de7cb33c218a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -4626,19 +4626,19 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.hiddenRequested, HIDE_STACK_CRAWLS ? null : new RuntimeException("here").fillInStackTrace()); + mOpeningApps.remove(wtoken); + mClosingApps.remove(wtoken); + wtoken.waitingToShow = wtoken.waitingToHide = false; + wtoken.hiddenRequested = !visible; + // If we are preparing an app transition, then delay changing // the visibility of this token until we execute that transition. if (okToDisplay() && mAppTransition.isTransitionSet()) { - wtoken.hiddenRequested = !visible; - if (!wtoken.startingDisplayed) { if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Setting dummy animation on: " + wtoken); wtoken.mAppAnimator.setDummyAnimation(); } - mOpeningApps.remove(wtoken); - mClosingApps.remove(wtoken); - wtoken.waitingToShow = wtoken.waitingToHide = false; wtoken.inPendingTransaction = true; if (visible) { mOpeningApps.add(wtoken); |