diff options
91 files changed, 4776 insertions, 804 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/api/current.txt b/api/current.txt index 144f2923b757..ed80e38c184c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -547,6 +547,7 @@ package android { field public static final int expandableListViewWhiteStyle = 16843446; // 0x10102b6 field public static final int exported = 16842768; // 0x1010010 field public static final int extraTension = 16843371; // 0x101026b + field public static final int extractNativeLibs = 16843990; // 0x10104d6 field public static final int factor = 16843219; // 0x10101d3 field public static final int fadeDuration = 16843384; // 0x1010278 field public static final int fadeEnabled = 16843390; // 0x101027e @@ -8368,6 +8369,7 @@ package android.content.pm { field public static final int FLAG_ALLOW_TASK_REPARENTING = 32; // 0x20 field public static final int FLAG_DEBUGGABLE = 2; // 0x2 field public static final int FLAG_EXTERNAL_STORAGE = 262144; // 0x40000 + field public static final int FLAG_EXTRACT_NATIVE_LIBS = 268435456; // 0x10000000 field public static final int FLAG_FACTORY_TEST = 16; // 0x10 field public static final int FLAG_FULL_BACKUP_ONLY = 67108864; // 0x4000000 field public static final int FLAG_HAS_CODE = 4; // 0x4 @@ -17515,7 +17517,7 @@ package android.net.http { method public static android.net.http.HttpResponseCache getInstalled(); method public int getNetworkCount(); method public int getRequestCount(); - method public static android.net.http.HttpResponseCache install(java.io.File, long) throws java.io.IOException; + method public static synchronized android.net.http.HttpResponseCache install(java.io.File, long) throws java.io.IOException; method public long maxSize(); method public java.net.CacheRequest put(java.net.URI, java.net.URLConnection) throws java.io.IOException; method public long size(); @@ -41519,7 +41521,7 @@ package java.lang { method public static double nextUp(double); method public static float nextUp(float); method public static double pow(double, double); - method public static synchronized double random(); + method public static double random(); method public static double rint(double); method public static long round(double); method public static int round(float); diff --git a/api/system-current.txt b/api/system-current.txt index 7ab0e486d0e3..8f1b8b275f92 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -617,6 +617,7 @@ package android { field public static final int expandableListViewWhiteStyle = 16843446; // 0x10102b6 field public static final int exported = 16842768; // 0x1010010 field public static final int extraTension = 16843371; // 0x101026b + field public static final int extractNativeLibs = 16843990; // 0x10104d6 field public static final int factor = 16843219; // 0x10101d3 field public static final int fadeDuration = 16843384; // 0x1010278 field public static final int fadeEnabled = 16843390; // 0x101027e @@ -8616,6 +8617,7 @@ package android.content.pm { field public static final int FLAG_ALLOW_TASK_REPARENTING = 32; // 0x20 field public static final int FLAG_DEBUGGABLE = 2; // 0x2 field public static final int FLAG_EXTERNAL_STORAGE = 262144; // 0x40000 + field public static final int FLAG_EXTRACT_NATIVE_LIBS = 268435456; // 0x10000000 field public static final int FLAG_FACTORY_TEST = 16; // 0x10 field public static final int FLAG_FULL_BACKUP_ONLY = 67108864; // 0x4000000 field public static final int FLAG_HAS_CODE = 4; // 0x4 @@ -18843,7 +18845,7 @@ package android.net.http { method public static android.net.http.HttpResponseCache getInstalled(); method public int getNetworkCount(); method public int getRequestCount(); - method public static android.net.http.HttpResponseCache install(java.io.File, long) throws java.io.IOException; + method public static synchronized android.net.http.HttpResponseCache install(java.io.File, long) throws java.io.IOException; method public long maxSize(); method public java.net.CacheRequest put(java.net.URI, java.net.URLConnection) throws java.io.IOException; method public long size(); @@ -44055,7 +44057,7 @@ package java.lang { method public static double nextUp(double); method public static float nextUp(float); method public static double pow(double, double); - method public static synchronized double random(); + method public static double random(); method public static double rint(double); method public static long round(double); method public static int round(float); 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..9269f602a165 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -97,7 +97,7 @@ import android.view.ViewRootImpl; import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.renderscript.RenderScript; +import android.renderscript.RenderScriptCacheDir; import android.security.AndroidKeyStoreProvider; import com.android.internal.app.IVoiceInteractor; @@ -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; @@ -3184,7 +3187,7 @@ public final class ActivityThread { if (cv == null) { mThumbnailCanvas = cv = new Canvas(); } - + cv.setBitmap(thumbnail); if (!r.activity.onCreateThumbnail(thumbnail, cv)) { mAvailThumbnailBitmap = thumbnail; @@ -3482,12 +3485,12 @@ public final class ActivityThread { private void handleWindowVisibility(IBinder token, boolean show) { ActivityClientRecord r = mActivities.get(token); - + if (r == null) { Log.w(TAG, "handleWindowVisibility: no activity for token " + token); return; } - + if (!show && !r.stopped) { performStopActivityInner(r, null, show, false); } else if (show && r.stopped) { @@ -3915,10 +3918,10 @@ public final class ActivityThread { } } } - + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity " + tmp.token + ": changedConfig=" + changedConfig); - + // If there was a pending configuration change, execute it first. if (changedConfig != null) { mCurDefaultDisplayDpi = changedConfig.densityDpi; @@ -4115,7 +4118,7 @@ public final class ActivityThread { if (config == null) { return; } - + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: " + config); @@ -4163,7 +4166,7 @@ public final class ActivityThread { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: " + r.activityInfo.name); - + performConfigurationChanged(r.activity, mCompatConfiguration); freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(mCompatConfiguration)); @@ -4244,7 +4247,7 @@ public final class ActivityThread { ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo); } - + final void handleLowMemory() { ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null); @@ -4291,10 +4294,10 @@ public final class ActivityThread { String[] packages = getPackageManager().getPackagesForUid(uid); // If there are several packages in this application we won't - // initialize the graphics disk caches + // initialize the graphics disk caches if (packages != null && packages.length == 1) { HardwareRenderer.setupDiskCache(cacheDir); - RenderScript.setupDiskCache(cacheDir); + RenderScriptCacheDir.setupDiskCache(cacheDir); } } catch (RemoteException e) { // Ignore @@ -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( @@ -5186,7 +5222,7 @@ public final class ActivityThread { if (mPendingConfiguration == null || mPendingConfiguration.isOtherSeqNewer(newConfig)) { mPendingConfiguration = newConfig; - + sendMessage(H.CONFIGURATION_CHANGED, newConfig); } } diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index 6c2511eef357..8692336439f9 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -235,10 +235,13 @@ public class ApplicationErrorReport implements Parcelable { dest.writeString(processName); dest.writeLong(time); dest.writeInt(systemApp ? 1 : 0); + dest.writeInt(crashInfo != null ? 1 : 0); switch (type) { case TYPE_CRASH: - crashInfo.writeToParcel(dest, flags); + if (crashInfo != null) { + crashInfo.writeToParcel(dest, flags); + } break; case TYPE_ANR: anrInfo.writeToParcel(dest, flags); @@ -259,10 +262,11 @@ public class ApplicationErrorReport implements Parcelable { processName = in.readString(); time = in.readLong(); systemApp = in.readInt() == 1; + boolean hasCrashInfo = in.readInt() == 1; switch (type) { case TYPE_CRASH: - crashInfo = new CrashInfo(in); + crashInfo = hasCrashInfo ? new CrashInfo(in) : null; anrInfo = null; batteryInfo = null; runningServiceInfo = null; diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 05c19db1fd48..4a087da69597 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -346,6 +346,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 1<<27; /** + * When set installer extracts native libs from .apk files. + */ + public static final int FLAG_EXTRACT_NATIVE_LIBS = 1<<28; + + /** * Value for {@link #flags}: true if code from this application will need to be * loaded into other applications' processes. On devices that support multiple * instruction sets, this implies the code might be loaded into a process that's @@ -900,6 +905,20 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** * @hide */ + public boolean isSystemApp() { + return (flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + /** + * @hide + */ + public boolean isUpdatedSystemApp() { + return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + } + + /** + * @hide + */ @Override protected ApplicationInfo getApplicationInfo() { return this; } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 4952ba1989c8..53aa6ffed89a 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -268,6 +268,7 @@ public class PackageParser { public final boolean coreApp; public final boolean multiArch; + public final boolean extractNativeLibs; public PackageLite(String codePath, ApkLite baseApk, String[] splitNames, String[] splitCodePaths, int[] splitRevisionCodes) { @@ -283,6 +284,7 @@ public class PackageParser { this.splitRevisionCodes = splitRevisionCodes; this.coreApp = baseApk.coreApp; this.multiArch = baseApk.multiArch; + this.extractNativeLibs = baseApk.extractNativeLibs; } public List<String> getAllCodePaths() { @@ -309,10 +311,12 @@ public class PackageParser { public final Signature[] signatures; public final boolean coreApp; public final boolean multiArch; + public final boolean extractNativeLibs; public ApkLite(String codePath, String packageName, String splitName, int versionCode, int revisionCode, int installLocation, List<VerifierInfo> verifiers, - Signature[] signatures, boolean coreApp, boolean multiArch) { + Signature[] signatures, boolean coreApp, boolean multiArch, + boolean extractNativeLibs) { this.codePath = codePath; this.packageName = packageName; this.splitName = splitName; @@ -323,6 +327,7 @@ public class PackageParser { this.signatures = signatures; this.coreApp = coreApp; this.multiArch = multiArch; + this.extractNativeLibs = extractNativeLibs; } } @@ -1269,6 +1274,7 @@ public class PackageParser { int revisionCode = 0; boolean coreApp = false; boolean multiArch = false; + boolean extractNativeLibs = true; for (int i = 0; i < attrs.getAttributeCount(); i++) { final String attr = attrs.getAttributeName(i); @@ -1307,14 +1313,17 @@ public class PackageParser { final String attr = attrs.getAttributeName(i); if ("multiArch".equals(attr)) { multiArch = attrs.getAttributeBooleanValue(i, false); - break; + } + if ("extractNativeLibs".equals(attr)) { + extractNativeLibs = attrs.getAttributeBooleanValue(i, true); } } } } return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode, - revisionCode, installLocation, verifiers, signatures, coreApp, multiArch); + revisionCode, installLocation, verifiers, signatures, coreApp, multiArch, + extractNativeLibs); } /** @@ -2567,6 +2576,12 @@ public class PackageParser { ai.flags |= ApplicationInfo.FLAG_MULTIARCH; } + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_extractNativeLibs, + true)) { + ai.flags |= ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS; + } + String str; str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestApplication_permission, 0); @@ -4444,6 +4459,20 @@ public class PackageParser { return applicationInfo.isForwardLocked(); } + /** + * @hide + */ + public boolean isSystemApp() { + return applicationInfo.isSystemApp(); + } + + /** + * @hide + */ + public boolean isUpdatedSystemApp() { + return applicationInfo.isUpdatedSystemApp(); + } + public String toString() { return "Package{" + Integer.toHexString(System.identityHashCode(this)) diff --git a/core/java/android/inputmethodservice/ExtractEditLayout.java b/core/java/android/inputmethodservice/ExtractEditLayout.java index 56968398871f..e902443b16f4 100644 --- a/core/java/android/inputmethodservice/ExtractEditLayout.java +++ b/core/java/android/inputmethodservice/ExtractEditLayout.java @@ -163,6 +163,8 @@ public class ExtractEditLayout extends LinearLayout { mCallback.onDestroyActionMode(this); mCallback = null; + mMenu.close(); + mExtractActionButton.setVisibility(VISIBLE); mEditButton.setVisibility(INVISIBLE); 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 14b57489ac95..579cdbeb40f4 100644 --- a/core/java/android/security/IKeystoreService.aidl +++ b/core/java/android/security/IKeystoreService.aidl @@ -73,4 +73,6 @@ interface IKeystoreService { 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..6176399bb9a6 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 @@ -168,6 +171,9 @@ public final class KeymasterDefs { public static final int KM_KEY_FORMAT_PKCS12 = 2; public static final int KM_KEY_FORMAT_RAW = 3; + // User authenticators. + public static final int HW_AUTH_PASSWORD = 1 << 0; + // Error codes. public static final int KM_ERROR_OK = 0; public static final int KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1; @@ -178,7 +184,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 +230,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/text/SpanSet.java b/core/java/android/text/SpanSet.java index 3ca60338e29e..00f14939a7ad 100644 --- a/core/java/android/text/SpanSet.java +++ b/core/java/android/text/SpanSet.java @@ -17,6 +17,7 @@ package android.text; import java.lang.reflect.Array; +import java.util.Arrays; /** * A cached set of spans. Caches the result of {@link Spanned#getSpans(int, int, Class)} and then @@ -54,6 +55,7 @@ public class SpanSet<E> { spanFlags = new int[length]; } + int prevNumberOfSpans = numberOfSpans; numberOfSpans = 0; for (int i = 0; i < length; i++) { final E span = allSpans[i]; @@ -71,6 +73,12 @@ public class SpanSet<E> { numberOfSpans++; } + + // cleanup extra spans left over from previous init() call + if (numberOfSpans < prevNumberOfSpans) { + // prevNumberofSpans was > 0, therefore spans != null + Arrays.fill(spans, numberOfSpans, prevNumberOfSpans, null); + } } /** @@ -103,9 +111,8 @@ public class SpanSet<E> { * Removes all internal references to the spans to avoid memory leaks. */ public void recycle() { - // The spans array is guaranteed to be not null when numberOfSpans is > 0 - for (int i = 0; i < numberOfSpans; i++) { - spans[i] = null; // prevent a leak: no reference kept when TextLine is recycled + if (spans != null) { + Arrays.fill(spans, 0, numberOfSpans, null); } } } 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/ProgressBar.java b/core/java/android/widget/ProgressBar.java index de1bbc7affad..358dbdeb547d 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -1812,9 +1812,7 @@ public class ProgressBar extends View { } if (mRefreshProgressRunnable != null) { removeCallbacks(mRefreshProgressRunnable); - } - if (mRefreshProgressRunnable != null && mRefreshIsPosted) { - removeCallbacks(mRefreshProgressRunnable); + mRefreshIsPosted = false; } if (mAccessibilityEventSender != null) { removeCallbacks(mAccessibilityEventSender); 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/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index 02f675c3aaac..f479f4feca35 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -33,6 +33,7 @@ import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.os.Build; import android.os.SELinux; +import android.os.SystemProperties; import android.system.ErrnoException; import android.system.Os; import android.util.Slog; @@ -74,6 +75,7 @@ public class NativeLibraryHelper { final long[] apkHandles; final boolean multiArch; + final boolean extractNativeLibs; public static Handle create(File packageFile) throws IOException { try { @@ -86,14 +88,16 @@ public class NativeLibraryHelper { public static Handle create(Package pkg) throws IOException { return create(pkg.getAllCodePaths(), - (pkg.applicationInfo.flags & ApplicationInfo.FLAG_MULTIARCH) != 0); + (pkg.applicationInfo.flags & ApplicationInfo.FLAG_MULTIARCH) != 0, + (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0); } public static Handle create(PackageLite lite) throws IOException { - return create(lite.getAllCodePaths(), lite.multiArch); + return create(lite.getAllCodePaths(), lite.multiArch, lite.extractNativeLibs); } - private static Handle create(List<String> codePaths, boolean multiArch) throws IOException { + private static Handle create(List<String> codePaths, boolean multiArch, + boolean extractNativeLibs) throws IOException { final int size = codePaths.size(); final long[] apkHandles = new long[size]; for (int i = 0; i < size; i++) { @@ -108,12 +112,13 @@ public class NativeLibraryHelper { } } - return new Handle(apkHandles, multiArch); + return new Handle(apkHandles, multiArch, extractNativeLibs); } - Handle(long[] apkHandles, boolean multiArch) { + Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs) { this.apkHandles = apkHandles; this.multiArch = multiArch; + this.extractNativeLibs = extractNativeLibs; mGuard.open("close"); } @@ -146,8 +151,8 @@ public class NativeLibraryHelper { private static native long nativeSumNativeBinaries(long handle, String cpuAbi); - private native static int nativeCopyNativeBinaries(long handle, - String sharedLibraryPath, String abiToCopy); + private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath, + String abiToCopy, boolean extractNativeLibs, boolean hasNativeBridge); private static long sumNativeBinaries(Handle handle, String abi) { long sum = 0; @@ -167,7 +172,8 @@ public class NativeLibraryHelper { */ public static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) { for (long apkHandle : handle.apkHandles) { - int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi); + int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi, + handle.extractNativeLibs, HAS_NATIVE_BRIDGE); if (res != INSTALL_SUCCEEDED) { return res; } @@ -218,7 +224,8 @@ public class NativeLibraryHelper { /** * Remove the native binaries of a given package. This deletes the files */ - public static void removeNativeBinariesFromDirLI(File nativeLibraryRoot, boolean deleteRootDir) { + public static void removeNativeBinariesFromDirLI(File nativeLibraryRoot, + boolean deleteRootDir) { if (DEBUG_NATIVE) { Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryRoot.getPath()); } @@ -247,7 +254,8 @@ public class NativeLibraryHelper { // asked to or this will prevent installation of future updates. if (deleteRootDir) { if (!nativeLibraryRoot.delete()) { - Slog.w(TAG, "Could not delete native binary directory: " + nativeLibraryRoot.getPath()); + Slog.w(TAG, "Could not delete native binary directory: " + + nativeLibraryRoot.getPath()); } } } @@ -416,6 +424,9 @@ public class NativeLibraryHelper { // We don't care about the other return values for now. private static final int BITCODE_PRESENT = 1; + private static final boolean HAS_NATIVE_BRIDGE = + !"0".equals(SystemProperties.get("ro.dalvik.vm.native.bridge", "0")); + private static native int hasRenderscriptBitcode(long apkHandle); public static boolean hasRenderscriptBitcode(Handle handle) throws IOException { diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java index 433a54b4341a..a4cdf194189f 100644 --- a/core/java/com/android/internal/os/InstallerConnection.java +++ b/core/java/com/android/internal/os/InstallerConnection.java @@ -91,11 +91,11 @@ public class InstallerConnection { } public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) { - return dexopt(apkPath, uid, isPublic, "*", instructionSet, false, false); + return dexopt(apkPath, uid, isPublic, "*", instructionSet, false, false, null); } public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName, - String instructionSet, boolean vmSafeMode, boolean debuggable) { + String instructionSet, boolean vmSafeMode, boolean debuggable, String outputPath) { StringBuilder builder = new StringBuilder("dexopt"); builder.append(' '); builder.append(apkPath); @@ -108,6 +108,8 @@ public class InstallerConnection { builder.append(instructionSet); builder.append(vmSafeMode ? " 1" : " 0"); builder.append(debuggable ? " 1" : " 0"); + builder.append(' '); + builder.append(outputPath != null ? outputPath : "!"); return execute(builder.toString()); } diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index ce50d965118f..0bfc0c96492c 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -536,7 +536,6 @@ bool AndroidRuntime::parseCompilerRuntimeOption(const char* property, */ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) { - int result = -1; JavaVMInitArgs initArgs; char propBuf[PROPERTY_VALUE_MAX]; char stackTraceFileBuf[sizeof("-Xstacktracefile:")-1 + PROPERTY_VALUE_MAX]; @@ -552,12 +551,19 @@ 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 dex2oat_isa_variant_key[PROPERTY_KEY_MAX]; + char dex2oat_isa_variant[sizeof("--instruction-set-variant=") -1 + PROPERTY_VALUE_MAX]; + char dex2oat_isa_features_key[PROPERTY_KEY_MAX]; + char dex2oat_isa_features[sizeof("--instruction-set-features=") -1 + PROPERTY_VALUE_MAX]; char dex2oatFlagsBuf[PROPERTY_VALUE_MAX]; char dex2oatImageFlagsBuf[PROPERTY_VALUE_MAX]; char extraOptsBuf[PROPERTY_VALUE_MAX]; @@ -580,6 +586,7 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) char localeOption[sizeof("-Duser.locale=") + PROPERTY_VALUE_MAX]; char lockProfThresholdBuf[sizeof("-Xlockprofthreshold:")-1 + PROPERTY_VALUE_MAX]; char nativeBridgeLibrary[sizeof("-XX:NativeBridge=") + PROPERTY_VALUE_MAX]; + char cpuAbiListBuf[sizeof("--cpu-abilist=") + PROPERTY_VALUE_MAX]; bool checkJni = false; property_get("dalvik.vm.checkjni", propBuf, ""); @@ -698,7 +705,7 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) if (!hasFile("/system/etc/preloaded-classes")) { ALOGE("Missing preloaded-classes file, /system/etc/preloaded-classes not found: %s\n", strerror(errno)); - goto bail; + return -1; } addOption("-Ximage-compiler-option"); addOption("--image-classes=/system/etc/preloaded-classes"); @@ -732,6 +739,46 @@ 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"); + + // The runtime will compile a boot image, when necessary, not using installd. Thus, we need to + // pass the instruction-set-features/variant as an image-compiler-option. + // TODO: Find a better way for the instruction-set. +#if defined(__arm__) + constexpr const char* instruction_set = "arm"; +#elif defined(__aarch64__) + constexpr const char* instruction_set = "arm64"; +#elif defined(__mips__) && !defined(__LP64__) + constexpr const char* instruction_set = "mips"; +#elif defined(__mips__) && defined(__LP64__) + constexpr const char* instruction_set = "mips64"; +#elif defined(__i386__) + constexpr const char* instruction_set = "x86"; +#elif defined(__x86_64__) + constexpr const char* instruction_set = "x86_64"; +#else + constexpr const char* instruction_set = "unknown"; +#endif + // Note: it is OK to reuse the buffer, as the values are exactly the same between + // * compiler-option, used for runtime compilation (DexClassLoader) + // * image-compiler-option, used for boot-image compilation on device + + // Copy the variant. + sprintf(dex2oat_isa_variant_key, "dalvik.vm.isa.%s.variant", instruction_set); + parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant, + "--instruction-set-variant=", "-Ximage-compiler-option"); + parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant, + "--instruction-set-variant=", "-Xcompiler-option"); + // Copy the features. + sprintf(dex2oat_isa_features_key, "dalvik.vm.isa.%s.features", instruction_set); + parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features, + "--instruction-set-features=", "-Ximage-compiler-option"); + parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features, + "--instruction-set-features=", "-Xcompiler-option"); + + property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, ""); parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option"); @@ -810,6 +857,22 @@ 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="); +#if defined(__LP64__) + const char* cpu_abilist_property_name = "ro.product.cpu.abilist64"; +#else + const char* cpu_abilist_property_name = "ro.product.cpu.abilist32"; +#endif // defined(__LP64__) + property_get(cpu_abilist_property_name, propBuf, ""); + if (propBuf[0] == '\0') { + ALOGE("%s is not expected to be empty", cpu_abilist_property_name); + return -1; + } + snprintf(cpuAbiListBuf, sizeof(cpuAbiListBuf), "--cpu-abilist=%s", propBuf); + addOption(cpuAbiListBuf); + initArgs.version = JNI_VERSION_1_4; initArgs.options = mOptions.editArray(); initArgs.nOptions = mOptions.size(); @@ -824,13 +887,10 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) */ if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) { ALOGE("JNI_CreateJavaVM failed\n"); - goto bail; + return -1; } - result = 0; - -bail: - return result; + return 0; } char* AndroidRuntime::toSlashClassName(const char* className) diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index 3c1993e8650b..9307ff9081ab 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -33,6 +33,7 @@ #include <string.h> #include <time.h> #include <unistd.h> +#include <inttypes.h> #include <sys/stat.h> #include <sys/types.h> @@ -173,7 +174,11 @@ sumFiles(JNIEnv*, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char static install_status_t copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName) { - jstring* javaNativeLibPath = (jstring*) arg; + void** args = reinterpret_cast<void**>(arg); + jstring* javaNativeLibPath = (jstring*) args[0]; + jboolean extractNativeLibs = *(jboolean*) args[1]; + jboolean hasNativeBridge = *(jboolean*) args[2]; + ScopedUtfChars nativeLibPath(env, *javaNativeLibPath); size_t uncompLen; @@ -181,13 +186,31 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr long crc; time_t modTime; - if (!zipFile->getEntryInfo(zipEntry, NULL, &uncompLen, NULL, NULL, &when, &crc)) { + int method; + off64_t offset; + + if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, NULL, &offset, &when, &crc)) { ALOGD("Couldn't read zip entry info\n"); return INSTALL_FAILED_INVALID_APK; - } else { - struct tm t; - ZipUtils::zipTimeToTimespec(when, &t); - modTime = mktime(&t); + } + + if (!extractNativeLibs) { + // check if library is uncompressed and page-aligned + if (method != ZipFileRO::kCompressStored) { + ALOGD("Library '%s' is compressed - will not be able to open it directly from apk.\n", + fileName); + return INSTALL_FAILED_INVALID_APK; + } + + if (offset % PAGE_SIZE != 0) { + ALOGD("Library '%s' is not page-aligned - will not be able to open it directly from" + " apk.\n", fileName); + return INSTALL_FAILED_INVALID_APK; + } + + if (!hasNativeBridge) { + return INSTALL_SUCCEEDED; + } } // Build local file path @@ -208,6 +231,9 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr } // Only copy out the native file if it's different. + struct tm t; + ZipUtils::zipTimeToTimespec(when, &t); + modTime = mktime(&t); struct stat64 st; if (!isFileDifferent(localFileName, uncompLen, modTime, crc, &st)) { return INSTALL_SUCCEEDED; @@ -465,10 +491,12 @@ static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supported static jint com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz, - jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi) + jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi, + jboolean extractNativeLibs, jboolean hasNativeBridge) { + void* args[] = { &javaNativeLibPath, &extractNativeLibs, &hasNativeBridge }; return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, - copyFileIfChanged, &javaNativeLibPath); + copyFileIfChanged, reinterpret_cast<void*>(args)); } static jlong @@ -548,7 +576,7 @@ static JNINativeMethod gMethods[] = { "(J)V", (void *)com_android_internal_content_NativeLibraryHelper_close}, {"nativeCopyNativeBinaries", - "(JLjava/lang/String;Ljava/lang/String;)I", + "(JLjava/lang/String;Ljava/lang/String;ZZ)I", (void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries}, {"nativeSumNativeBinaries", "(JLjava/lang/String;)J", 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/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index ea592cf5d065..f69772ac6b33 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1021,6 +1021,10 @@ <p>The default value of this attribute is <code>false</code>. --> <attr name="resumeWhilePausing" format="boolean" /> + <!-- When set installer will extract native libraries. If set to false + libraries in the apk must be stored and page-aligned. --> + <attr name="extractNativeLibs" format="boolean"/> + <!-- The <code>manifest</code> tag is the root of an <code>AndroidManifest.xml</code> file, describing the contents of an Android package (.apk) file. One @@ -1151,8 +1155,8 @@ @hide --> <attr name="usesCleartextTraffic" /> <attr name="multiArch" /> + <attr name="extractNativeLibs" /> </declare-styleable> - <!-- The <code>permission</code> tag declares a security permission that can be used to control access from other packages to specific components or features in your package (or other packages). See the diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 2bb9aa8dc7e8..bfd0d2655e1e 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2598,4 +2598,5 @@ <public type="style" name="Theme.DeviceDefault.Dialog.Alert" /> <public type="style" name="Theme.DeviceDefault.Light.Dialog.Alert" /> + <public type="attr" name="extractNativeLibs" /> </resources> diff --git a/core/tests/coretests/apks/install_jni_lib/Android.mk b/core/tests/coretests/apks/install_jni_lib/Android.mk index b61ea8edda71..7322e8d6949b 100644 --- a/core/tests/coretests/apks/install_jni_lib/Android.mk +++ b/core/tests/coretests/apks/install_jni_lib/Android.mk @@ -23,6 +23,14 @@ LOCAL_SHARED_LIBRARIES := \ libnativehelper LOCAL_MODULE := libframeworks_coretests_jni + +# this does not prevent build system +# from installing library to /system/lib LOCAL_MODULE_TAGS := tests +# .. we want to avoid that... so we put it somewhere +# bionic linker cant find it without outside help (nativetests): +LOCAL_MODULE_PATH_32 := $($(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_DATA_NATIVE_TESTS)/$(LOCAL_MODULE) +LOCAL_MODULE_PATH_64 := $(TARGET_OUT_DATA_NATIVE_TESTS)/$(LOCAL_MODULE) + include $(BUILD_SHARED_LIBRARY) diff --git a/core/tests/coretests/apks/install_jni_lib/com_android_frameworks_coretests_JNITest.cpp b/core/tests/coretests/apks/install_jni_lib/com_android_frameworks_coretests_JNITest.cpp index 957fc4a1f53e..e0b616c16184 100644 --- a/core/tests/coretests/apks/install_jni_lib/com_android_frameworks_coretests_JNITest.cpp +++ b/core/tests/coretests/apks/install_jni_lib/com_android_frameworks_coretests_JNITest.cpp @@ -27,8 +27,8 @@ static JNINativeMethod sMethods[] = { { "checkFunction", "()I", (void*) checkFunction }, }; -int register_com_android_framework_coretests_JNITests(JNIEnv* env) { - return jniRegisterNativeMethods(env, "com/android/framework/coretests/JNITests", sMethods, +int register_com_android_frameworks_coretests_JNITests(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/frameworks/coretests/JNITests", sMethods, NELEM(sMethods)); } @@ -46,7 +46,7 @@ jint JNI_OnLoad(JavaVM *jvm, void *reserved) { return JNI_ERR; } - if ((status = android::register_com_android_framework_coretests_JNITests(e)) < 0) { + if ((status = android::register_com_android_frameworks_coretests_JNITests(e)) < 0) { return JNI_ERR; } diff --git a/core/tests/coretests/apks/install_jni_lib_open_from_apk/Android.mk b/core/tests/coretests/apks/install_jni_lib_open_from_apk/Android.mk new file mode 100644 index 000000000000..5fa24055d9a8 --- /dev/null +++ b/core/tests/coretests/apks/install_jni_lib_open_from_apk/Android.mk @@ -0,0 +1,11 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := install_jni_lib_open_from_apk + +LOCAL_JNI_SHARED_LIBRARIES_ZIP_OPTIONS := -0 +LOCAL_PAGE_ALIGN_JNI_SHARED_LIBRARIES := true + +include $(FrameworkCoreTests_BUILD_PACKAGE) diff --git a/core/tests/coretests/apks/install_jni_lib_open_from_apk/AndroidManifest.xml b/core/tests/coretests/apks/install_jni_lib_open_from_apk/AndroidManifest.xml new file mode 100644 index 000000000000..190f89474405 --- /dev/null +++ b/core/tests/coretests/apks/install_jni_lib_open_from_apk/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.coretests.install_jni_lib_open_from_apk"> + + <application android:hasCode="true" android:label="@string/app_name" android:extractNativeLibs="false"> + <activity android:name="com.android.frameworks.coretests.OpenFromApkActivity" + android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/core/tests/coretests/apks/install_jni_lib_open_from_apk/res/values/strings.xml b/core/tests/coretests/apks/install_jni_lib_open_from_apk/res/values/strings.xml new file mode 100644 index 000000000000..8c2a0bfbee73 --- /dev/null +++ b/core/tests/coretests/apks/install_jni_lib_open_from_apk/res/values/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_name">Load From Apk Test</string> +</resources> diff --git a/core/tests/coretests/apks/install_jni_lib_open_from_apk/src/com/android/frameworks/coretests/JNITests.java b/core/tests/coretests/apks/install_jni_lib_open_from_apk/src/com/android/frameworks/coretests/JNITests.java new file mode 100644 index 000000000000..4f9176c8f7b8 --- /dev/null +++ b/core/tests/coretests/apks/install_jni_lib_open_from_apk/src/com/android/frameworks/coretests/JNITests.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 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.frameworks.coretests; + +public class JNITests { + static { + System.loadLibrary("frameworks_coretests_jni"); + } + + public static native int checkFunction(); +} diff --git a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java b/core/tests/coretests/apks/install_jni_lib_open_from_apk/src/com/android/frameworks/coretests/OpenFromApkActivity.java index 2fe68f8dfd58..524cad7cfb69 100644 --- a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java +++ b/core/tests/coretests/apks/install_jni_lib_open_from_apk/src/com/android/frameworks/coretests/OpenFromApkActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2014 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. @@ -12,22 +12,27 @@ * 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; +package com.android.frameworks.coretests; -import android.util.Base64; +import android.app.Activity; +import android.widget.TextView; +import android.os.Bundle; -import java.io.IOException; +public class OpenFromApkActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); -public class TZInfoInstallReceiver extends ConfigUpdateInstallReceiver { + TextView tv = new TextView(this); - public TZInfoInstallReceiver() { - super("/data/misc/zoneinfo/", "tzdata", "metadata/", "version"); - } + int i = JNITests.checkFunction(); - @Override - protected void install(byte[] encodedContent, int version) throws IOException { - super.install(Base64.decode(encodedContent, Base64.DEFAULT), version); + tv.setText("All is well: i=" + i); + + setContentView(tv); } + } 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..f72c7acd182b 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); @@ -466,87 +466,94 @@ public class AndroidKeyStore extends KeyStoreSpi { throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString); } - if ((params.getAlgorithm() != null) && (params.getAlgorithm() != keyAlgorithm)) { - throw new KeyStoreException("Key algorithm mismatch. Key: " + keyAlgorithmString - + ", parameter spec: " - + KeyStoreKeyConstraints.Algorithm.toString(params.getAlgorithm())); - } - KeymasterArguments args = new KeymasterArguments(); args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeyStoreKeyConstraints.Algorithm.toKeymaster(keyAlgorithm)); - if (digest != null) { - // Digest available from JCA key algorithm - if (params.getDigest() != null) { - // Digest also specified in parameters -- check that these two match - if (digest != params.getDigest()) { - throw new KeyStoreException("Key digest mismatch. Key: " + keyAlgorithmString + @KeyStoreKeyConstraints.DigestEnum int digests; + if (params.isDigestsSpecified()) { + // Digest(s) specified in parameters + if (digest != null) { + // Digest also specified in the JCA key algorithm name. + if ((params.getDigests() & digest) != digest) { + throw new KeyStoreException("Key digest mismatch" + + ". Key: " + keyAlgorithmString + ", parameter spec: " - + KeyStoreKeyConstraints.Digest.toString(params.getDigest())); + + KeyStoreKeyConstraints.Digest.allToString(params.getDigests())); } } + digests = params.getDigests(); } else { - // Digest not available from JCA key algorithm - digest = params.getDigest(); - } - if (digest != null) { - args.addInt(KeymasterDefs.KM_TAG_DIGEST, - KeyStoreKeyConstraints.Digest.toKeymaster(digest)); - } - - @KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null) - ? params.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); + // No digest specified in parameters + if (digest != null) { + // Digest specified in the JCA key algorithm name. + digests = digest; + } else { + digests = 0; + } } - if (params.getBlockMode() != null) { - args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, - KeyStoreKeyConstraints.BlockMode.toKeymaster(params.getBlockMode())); + for (int keymasterDigest : KeyStoreKeyConstraints.Digest.allToKeymaster(digests)) { + args.addInt(KeymasterDefs.KM_TAG_DIGEST, keymasterDigest); + } + if (digests != 0) { + // TODO: Remove MAC length constraint once Keymaster API no longer requires it. + // This code will blow up if mode than one digest is specified. + Integer digestOutputSizeBytes = + KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest); + if (digestOutputSizeBytes != null) { + // 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 (digests == 0) { + throw new KeyStoreException("At least one digest algorithm must be specified" + + " for key algorithm " + keyAlgorithmString); + } } - if (params.getPadding() != null) { - args.addInt(KeymasterDefs.KM_TAG_PADDING, - KeyStoreKeyConstraints.Padding.toKeymaster(params.getPadding())); + + int purposes = params.getPurposes(); + for (int keymasterPurpose : KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) { + args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); } - if (params.getMaxUsesPerBoot() != null) { - args.addInt(KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT, params.getMaxUsesPerBoot()); + for (int keymasterBlockMode : + KeyStoreKeyConstraints.BlockMode.allToKeymaster(params.getBlockModes())) { + args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode); } - if (params.getMinSecondsBetweenOperations() != null) { - args.addInt(KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS, - params.getMinSecondsBetweenOperations()); + for (int keymasterPadding : + KeyStoreKeyConstraints.Padding.allToKeymaster(params.getPaddings())) { + args.addInt(KeymasterDefs.KM_TAG_PADDING, keymasterPadding); } - if (params.getUserAuthenticators().isEmpty()) { + if (params.getUserAuthenticators() == 0) { 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) { + if (params.getUserAuthenticationValidityDurationSeconds() != -1) { 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..635b2fa94b09 100644 --- a/keystore/java/android/security/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/AndroidKeyStoreProvider.java @@ -18,6 +18,9 @@ package android.security; import java.security.Provider; +import javax.crypto.Cipher; +import javax.crypto.Mac; + /** * A provider focused on providing JCA interfaces for the Android KeyStore. * @@ -26,14 +29,104 @@ import java.security.Provider; public class AndroidKeyStoreProvider extends Provider { public static final String PROVIDER_NAME = "AndroidKeyStore"; + // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these + // classes when this provider is instantiated and installed early on during each app's + // initialization process. + + private static final String PACKAGE_NAME = "android.security"; + private static final String KEYSTORE_SECRET_KEY_CLASS_NAME = + PACKAGE_NAME + ".KeyStoreSecretKey"; + public AndroidKeyStoreProvider() { super(PROVIDER_NAME, 1.0, "Android KeyStore security provider"); // java.security.KeyStore - put("KeyStore." + AndroidKeyStore.NAME, AndroidKeyStore.class.getName()); + put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStore"); // java.security.KeyPairGenerator - put("KeyPairGenerator.EC", AndroidKeyPairGenerator.EC.class.getName()); - put("KeyPairGenerator.RSA", AndroidKeyPairGenerator.RSA.class.getName()); + put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyPairGenerator$EC"); + put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyPairGenerator$RSA"); + + // javax.crypto.KeyGenerator + put("KeyGenerator.AES", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$AES"); + put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA1"); + put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA224"); + put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA256"); + put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA384"); + put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA512"); + + // java.security.SecretKeyFactory + putSecretKeyFactoryImpl("AES"); + putSecretKeyFactoryImpl("HmacSHA1"); + putSecretKeyFactoryImpl("HmacSHA224"); + putSecretKeyFactoryImpl("HmacSHA256"); + putSecretKeyFactoryImpl("HmacSHA384"); + putSecretKeyFactoryImpl("HmacSHA512"); + + // javax.crypto.Mac + putMacImpl("HmacSHA224", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA224"); + putMacImpl("HmacSHA256", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA256"); + putMacImpl("HmacSHA384", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA384"); + putMacImpl("HmacSHA512", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA512"); + + // javax.crypto.Cipher + putSymmetricCipherImpl("AES/ECB/NoPadding", + PACKAGE_NAME + ".KeyStoreCipherSpi$AES$ECB$NoPadding"); + putSymmetricCipherImpl("AES/ECB/PKCS7Padding", + PACKAGE_NAME + ".KeyStoreCipherSpi$AES$ECB$PKCS7Padding"); + + putSymmetricCipherImpl("AES/CBC/NoPadding", + PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CBC$NoPadding"); + putSymmetricCipherImpl("AES/CBC/PKCS7Padding", + PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CBC$PKCS7Padding"); + + putSymmetricCipherImpl("AES/CTR/NoPadding", + PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CTR$NoPadding"); + } + + private void putSecretKeyFactoryImpl(String algorithm) { + put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".KeyStoreSecretKeyFactorySpi"); + } + + private void putMacImpl(String algorithm, String implClass) { + put("Mac." + algorithm, implClass); + put("Mac." + algorithm + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME); + } + + private void putSymmetricCipherImpl(String transformation, String implClass) { + put("Cipher." + transformation, implClass); + put("Cipher." + transformation + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME); + } + + /** + * 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(); + } + Object spi; + if (cryptoPrimitive instanceof Mac) { + spi = ((Mac) cryptoPrimitive).getSpi(); + } else if (cryptoPrimitive instanceof Cipher) { + spi = ((Cipher) cryptoPrimitive).getSpi(); + } else { + throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive); + } + if (!(spi instanceof KeyStoreCryptoOperation)) { + throw new IllegalArgumentException( + "Crypto primitive not backed by AndroidKeyStore: " + 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..3ada7f6e5781 --- /dev/null +++ b/keystore/java/android/security/KeyGeneratorSpec.java @@ -0,0 +1,401 @@ +/* + * 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.spec.AlgorithmParameterSpec; +import java.util.Date; + +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}. + * + * @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 int mPurposes; + private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings; + private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes; + private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators; + private final int mUserAuthenticationValidityDurationSeconds; + + private KeyGeneratorSpec( + Context context, + String keyStoreAlias, + int flags, + Integer keySize, + Date keyValidityStart, + Date keyValidityForOriginationEnd, + Date keyValidityForConsumptionEnd, + @KeyStoreKeyConstraints.PurposeEnum int purposes, + @KeyStoreKeyConstraints.PaddingEnum int paddings, + @KeyStoreKeyConstraints.BlockModeEnum int blockModes, + @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators, + int 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 < 0) + && (userAuthenticationValidityDurationSeconds != -1)) { + throw new IllegalArgumentException( + "userAuthenticationValidityDurationSeconds must not be negative"); + } + + mContext = context; + mKeystoreAlias = keyStoreAlias; + mFlags = flags; + mKeySize = keySize; + mKeyValidityStart = keyValidityStart; + mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; + mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mPurposes = purposes; + mPaddings = paddings; + mBlockModes = blockModes; + mUserAuthenticators = userAuthenticators; + 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. + */ + 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. + */ + public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() { + return mPurposes; + } + + /** + * Gets the set of padding schemes to which the key is restricted. + */ + public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() { + return mPaddings; + } + + /** + * Gets the set of block modes to which the key is restricted. + */ + public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() { + return mBlockModes; + } + + /** + * Gets the set of 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 {@code 0} if the key can be used without user authentication. + */ + public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() { + return 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 -1} if not restricted. {@code 0} means authentication + * is required for every use of the key. + */ + public int 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 int mPurposes; + private @KeyStoreKeyConstraints.PaddingEnum int mPaddings; + private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes; + private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators; + private int mUserAuthenticationValidityDurationSeconds = -1; + + /** + * 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) + */ + 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) + */ + 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) + */ + 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) + */ + public Builder setKeyValidityForConsumptionEnd(Date endDate) { + mKeyValidityForConsumptionEnd = endDate; + return this; + } + + /** + * Restricts the key to being used only for the provided set of purposes. + * + * <p>This restriction must be specified. There is no default. + */ + public Builder setPurposes(@KeyStoreKeyConstraints.PurposeEnum int purposes) { + mPurposes = purposes; + return this; + } + + /** + * Restricts the key to being used only with the provided padding schemes. 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. + */ + public Builder setPaddings(@KeyStoreKeyConstraints.PaddingEnum int paddings) { + mPaddings = paddings; + return this; + } + + /** + * Restricts the key to being used only with the provided block modes. 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. + */ + public Builder setBlockModes(@KeyStoreKeyConstraints.BlockModeEnum int blockModes) { + mBlockModes = blockModes; + 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) + */ + public Builder setUserAuthenticators( + @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators) { + mUserAuthenticators = userAuthenticators; + 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(int) + */ + 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, + mPaddings, + mBlockModes, + 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..edaa9a5444c6 100644 --- a/keystore/java/android/security/KeyPairGeneratorSpec.java +++ b/keystore/java/android/security/KeyPairGeneratorSpec.java @@ -72,6 +72,24 @@ 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 int mPurposes; + + private final @KeyStoreKeyConstraints.DigestEnum int mDigests; + + private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings; + + private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes; + + private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators; + + private final int mUserAuthenticationValidityDurationSeconds; + /** * Parameter specification for the "{@code AndroidKeyPairGenerator}" * instance of the {@link java.security.KeyPairGenerator} API. The @@ -106,7 +124,16 @@ 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 int purposes, + @KeyStoreKeyConstraints.DigestEnum int digests, + @KeyStoreKeyConstraints.PaddingEnum int paddings, + @KeyStoreKeyConstraints.BlockModeEnum int blockModes, + @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators, + int userAuthenticationValidityDurationSeconds) { if (context == null) { throw new IllegalArgumentException("context == null"); } else if (TextUtils.isEmpty(keyStoreAlias)) { @@ -121,6 +148,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 < 0) + && (userAuthenticationValidityDurationSeconds != -1)) { + throw new IllegalArgumentException( + "userAuthenticationValidityDurationSeconds must not be negative"); } mContext = context; @@ -133,6 +164,26 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { mStartDate = startDate; mEndDate = endDate; mFlags = flags; + mKeyValidityStart = keyValidityStart; + mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; + mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mPurposes = purposes; + mDigests = digests; + mPaddings = paddings; + mBlockModes = blockModes; + mUserAuthenticators = userAuthenticators; + 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, 0, 0, 0, 0, 0, -1); } /** @@ -222,6 +273,107 @@ 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. + * + * @hide + */ + public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() { + return mPurposes; + } + + /** + * Gets the set of digests to which the key is restricted. + * + * @hide + */ + public @KeyStoreKeyConstraints.DigestEnum int getDigests() { + return mDigests; + } + + /** + * Gets the set of padding schemes to which the key is restricted. + * + * @hide + */ + public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() { + return mPaddings; + } + + /** + * Gets the set of block modes to which the key is restricted. + * + * @hide + */ + public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() { + return mBlockModes; + } + + /** + * Gets the set of 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 {@code 0} if the key can be used without user authentication. + * + * @hide + */ + public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() { + return 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 -1} if not restricted. {@code 0} means authentication + * is required for every use of the key. + * + * @hide + */ + public int 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 +415,24 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private int mFlags; + private Date mKeyValidityStart; + + private Date mKeyValidityForOriginationEnd; + + private Date mKeyValidityForConsumptionEnd; + + private @KeyStoreKeyConstraints.PurposeEnum int mPurposes; + + private @KeyStoreKeyConstraints.DigestEnum int mDigests; + + private @KeyStoreKeyConstraints.PaddingEnum int mPaddings; + + private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes; + + private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators; + + private int mUserAuthenticationValidityDurationSeconds = -1; + /** * 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 +559,185 @@ 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 key to being used only for the provided set of purposes. + * + * <p>This restriction must be specified. There is no default. + * + * @hide + */ + public Builder setPurposes(@KeyStoreKeyConstraints.PurposeEnum int purposes) { + mPurposes = purposes; + return this; + } + + /** + * Restricts the key to being used only with the provided digests. 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 setDigests(@KeyStoreKeyConstraints.DigestEnum int digests) { + mDigests = digests; + return this; + } + + /** + * Restricts the key to being used only with the provided padding schemes. 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 setPaddings(@KeyStoreKeyConstraints.PaddingEnum int paddings) { + mPaddings = paddings; + 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 setBlockModes(@KeyStoreKeyConstraints.BlockModeEnum int blockModes) { + mBlockModes = blockModes; + 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 {@code 0} if this key can be accessed + * without user authentication. + * + * @see #setUserAuthenticationValidityDurationSeconds(int) + * + * @hide + */ + public Builder setUserAuthenticators( + @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators) { + mUserAuthenticators = userAuthenticators; + 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(int) + * + * @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, + mDigests, + mPaddings, + mBlockModes, + mUserAuthenticators, + mUserAuthenticationValidityDurationSeconds); } } } diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index f68b3f6baace..84a664e30163 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -26,6 +26,7 @@ import android.security.keymaster.ExportResult; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterBlob; +import android.security.keymaster.KeymasterDefs; import android.security.keymaster.OperationResult; import android.util.Log; @@ -476,4 +477,87 @@ 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; + } + } + + public static KeyStoreException getKeyStoreException(int errorCode) { + if (errorCode > 0) { + // KeyStore layer error + switch (errorCode) { + case NO_ERROR: + return new KeyStoreException(errorCode, "OK"); + case LOCKED: + return new KeyStoreException(errorCode, "Keystore locked"); + case UNINITIALIZED: + return new KeyStoreException(errorCode, "Keystore not initialized"); + case SYSTEM_ERROR: + return new KeyStoreException(errorCode, "System error"); + case PERMISSION_DENIED: + return new KeyStoreException(errorCode, "Permission denied"); + case KEY_NOT_FOUND: + return new KeyStoreException(errorCode, "Key not found"); + case VALUE_CORRUPTED: + return new KeyStoreException(errorCode, "Key blob corrupted"); + default: + return new KeyStoreException(errorCode, String.valueOf(errorCode)); + } + } else { + // Keymaster layer error + switch (errorCode) { + case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT: + // The name of this parameter significantly differs between Keymaster and + // framework APIs. Use the framework wording to make life easier for developers. + return new KeyStoreException(errorCode, + "Invalid user authentication validity duration"); + default: + return new KeyStoreException(errorCode, + KeymasterDefs.getErrorMessage(errorCode)); + } + } + } + + public static CryptoOperationException getCryptoOperationException(KeyStoreException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_KEY_EXPIRED: + return new KeyExpiredException(); + case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID: + return new KeyNotYetValidException(); + case KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED: + return new UserNotAuthenticatedException(); + default: + return new CryptoOperationException("Crypto operation failed", e); + } + } + + public static CryptoOperationException getCryptoOperationException(int errorCode) { + return getCryptoOperationException(getKeyStoreException(errorCode)); + } } diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java new file mode 100644 index 000000000000..487eac09963e --- /dev/null +++ b/keystore/java/android/security/KeyStoreCipherSpi.java @@ -0,0 +1,603 @@ +/* + * 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; + + /** Whether this transformation requires an IV. */ + private final boolean mIvRequired; + + // 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; + private byte[] mIv; + /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */ + private boolean mIvHasBeenUsed; + + // Fields below must be reset after doFinal + 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; + mIvRequired = 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 { + resetAll(); + 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 resetAll() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mEncrypting = false; + mKey = null; + mRng = null; + mFirstOperationInitiated = false; + mIv = null; + mIvHasBeenUsed = false; + mAdditionalEntropyForBegin = null; + mOperationToken = null; + mOperationHandle = null; + mMainDataStreamer = null; + } + + private void resetWhilePreservingInitState() { + 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"); + } + if ((mEncrypting) && (mIvRequired) && (mIvHasBeenUsed)) { + // IV is being reused for encryption: this violates security best practices. + throw new IllegalStateException( + "IV has already been used. Reusing IV in encryption mode violates security best" + + " practices."); + } + + 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 KeyStore.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; + mIvHasBeenUsed = 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 (KeyStoreException e) { + throw KeyStore.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 (KeyStoreException 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 KeyStore.getCryptoOperationException(e); + } + } + + resetWhilePreservingInitState(); + 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 (!mIvRequired) { + return null; + } + if ((mIv != null) && (mIv.length > 0)) { + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); + params.init(new IvParameterSpec(mIv)); + return params; + } catch (NoSuchAlgorithmException e) { + throw new 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 (!mIvRequired) { + 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 (!mIvRequired) { + 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 (!mIvRequired) { + 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 (mIvRequired) { + // 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 ((mIvRequired) && (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 (mIvRequired) { + 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..1f8b7e40b491 --- /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 KeyStoreException { + 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 KeyStore.getKeyStoreException(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 KeyStoreException { + 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 KeyStore.getKeyStoreException(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 KeyStoreException { + 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 KeyStore.getKeyStoreException(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/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java new file mode 100644 index 000000000000..88e768ce64ce --- /dev/null +++ b/keystore/java/android/security/KeyStoreException.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * KeyStore/keymaster exception with positive error codes coming from the KeyStore and negative + * ones from keymaster. + * + * @hide + */ +public class KeyStoreException extends Exception { + + private final int mErrorCode; + + public KeyStoreException(int errorCode, String message) { + super(message); + mErrorCode = errorCode; + } + + public int getErrorCode() { + return mErrorCode; + } +} diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java new file mode 100644 index 000000000000..f69e7d160a8b --- /dev/null +++ b/keystore/java/android/security/KeyStoreHmacSpi.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +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 HmacSHA1 extends KeyStoreHmacSpi { + public HmacSHA1() { + super(KeyStoreKeyConstraints.Digest.SHA1); + } + } + + public static class HmacSHA224 extends KeyStoreHmacSpi { + public HmacSHA224() { + super(KeyStoreKeyConstraints.Digest.SHA224); + } + } + + public static class HmacSHA256 extends KeyStoreHmacSpi { + public HmacSHA256() { + super(KeyStoreKeyConstraints.Digest.SHA256); + } + } + + public static class HmacSHA384 extends KeyStoreHmacSpi { + public HmacSHA384() { + super(KeyStoreKeyConstraints.Digest.SHA384); + } + } + + public static class HmacSHA512 extends KeyStoreHmacSpi { + public HmacSHA512() { + super(KeyStoreKeyConstraints.Digest.SHA512); + } + } + + 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) { + mDigest = digest; + mMacSizeBytes = KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest); + } + + @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 KeyStore.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 (KeyStoreException e) { + throw KeyStore.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 (KeyStoreException e) { + throw KeyStore.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..9321302c124b 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; @@ -5,7 +21,6 @@ import android.security.keymaster.KeymasterDefs; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; import java.util.Collection; import java.util.Locale; @@ -18,7 +33,8 @@ public abstract class KeyStoreKeyConstraints { private KeyStoreKeyConstraints() {} @Retention(RetentionPolicy.SOURCE) - @IntDef(flag=true, value={Purpose.ENCRYPT, Purpose.DECRYPT, Purpose.SIGN, Purpose.VERIFY}) + @IntDef(flag = true, + value = {Purpose.ENCRYPT, Purpose.DECRYPT, Purpose.SIGN, Purpose.VERIFY}) public @interface PurposeEnum {} /** @@ -48,11 +64,6 @@ public abstract class KeyStoreKeyConstraints { public static final int VERIFY = 1 << 3; /** - * Number of flags defined above. Needs to be kept in sync with the flags above. - */ - private static final int VALUE_COUNT = 4; - - /** * @hide */ public static int toKeymaster(@PurposeEnum int purpose) { @@ -91,22 +102,12 @@ public abstract class KeyStoreKeyConstraints { /** * @hide */ - public static int[] allToKeymaster(int purposes) { - int[] result = new int[VALUE_COUNT]; - int resultCount = 0; - int purpose = 1; - for (int i = 0; i < 32; i++) { - if ((purposes & 1) != 0) { - result[resultCount] = toKeymaster(purpose); - resultCount++; - } - purposes >>>= 1; - purpose <<= 1; - if (purposes == 0) { - break; - } + public static int[] allToKeymaster(@PurposeEnum int purposes) { + int[] result = getSetFlags(purposes); + for (int i = 0; i < result.length; i++) { + result[i] = toKeymaster(result[i]); } - return Arrays.copyOf(result, resultCount); + return result; } /** @@ -222,20 +223,11 @@ 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) - @IntDef({Padding.NONE, Padding.ZERO, Padding.PKCS7}) + @IntDef(flag = true, + value = {Padding.NONE, Padding.PKCS7}) public @interface PaddingEnum {} /** @@ -247,17 +239,12 @@ public abstract class KeyStoreKeyConstraints { /** * No padding. */ - public static final int NONE = 0; - - /** - * Pad with zeros. - */ - public static final int ZERO = 1; + public static final int NONE = 1 << 0; /** * PKCS#7 padding. */ - public static final int PKCS7 = 2; + public static final int PKCS7 = 1 << 1; /** * @hide @@ -266,8 +253,6 @@ public abstract class KeyStoreKeyConstraints { switch (padding) { case NONE: return KeymasterDefs.KM_PAD_NONE; - case ZERO: - return KeymasterDefs.KM_PAD_ZERO; case PKCS7: return KeymasterDefs.KM_PAD_PKCS7; default: @@ -282,18 +267,75 @@ public abstract class KeyStoreKeyConstraints { switch (padding) { case KeymasterDefs.KM_PAD_NONE: return NONE; - case KeymasterDefs.KM_PAD_ZERO: - return ZERO; case KeymasterDefs.KM_PAD_PKCS7: return PKCS7; default: throw new IllegalArgumentException("Unknown padding: " + padding); } } + + /** + * @hide + */ + public static String toString(@PaddingEnum int padding) { + switch (padding) { + case NONE: + return "NONE"; + 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); + } + } + + /** + * @hide + */ + public static int[] allToKeymaster(@PaddingEnum int paddings) { + int[] result = getSetFlags(paddings); + for (int i = 0; i < result.length; i++) { + result[i] = toKeymaster(result[i]); + } + return result; + } + + /** + * @hide + */ + public static @PaddingEnum int allFromKeymaster(Collection<Integer> paddings) { + @PaddingEnum int result = 0; + for (int keymasterPadding : paddings) { + result |= fromKeymaster(keymasterPadding); + } + return result; + } } @Retention(RetentionPolicy.SOURCE) - @IntDef({Digest.NONE, Digest.SHA256}) + @IntDef(flag = true, + value = { + Digest.NONE, + Digest.MD5, + Digest.SHA1, + Digest.SHA224, + Digest.SHA256, + Digest.SHA384, + Digest.SHA512, + }) public @interface DigestEnum {} /** @@ -306,12 +348,37 @@ public abstract class KeyStoreKeyConstraints { /** * No digest: sign/authenticate the raw message. */ - public static final int NONE = 0; + public static final int NONE = 1 << 0; /** - * SHA-256 digest. + * MD5 digest. */ - public static final int SHA256 = 1; + public static final int MD5 = 1 << 1; + + /** + * SHA-1 digest. + */ + public static final int SHA1 = 1 << 2; + + /** + * SHA-2 224 (aka SHA-224) digest. + */ + public static final int SHA224 = 1 << 3; + + /** + * SHA-2 256 (aka SHA-256) digest. + */ + public static final int SHA256 = 1 << 4; + + /** + * SHA-2 384 (aka SHA-384) digest. + */ + public static final int SHA384 = 1 << 5; + + /** + * SHA-2 512 (aka SHA-512) digest. + */ + public static final int SHA512 = 1 << 6; /** * @hide @@ -320,8 +387,18 @@ public abstract class KeyStoreKeyConstraints { switch (digest) { case NONE: return "NONE"; + case MD5: + return "MD5"; + case SHA1: + return "SHA-1"; + case SHA224: + return "SHA-224"; case SHA256: - return "SHA256"; + return "SHA-256"; + case SHA384: + return "SHA-384"; + case SHA512: + return "SHA-512"; default: throw new IllegalArgumentException("Unknown digest: " + digest); } @@ -330,12 +407,40 @@ public abstract class KeyStoreKeyConstraints { /** * @hide */ + public static String allToString(@DigestEnum int digests) { + StringBuilder result = new StringBuilder("["); + boolean firstValue = true; + for (@DigestEnum int digest : getSetFlags(digests)) { + if (firstValue) { + firstValue = false; + } else { + result.append(", "); + } + result.append(toString(digest)); + } + result.append(']'); + return result.toString(); + } + + /** + * @hide + */ public static int toKeymaster(@DigestEnum int digest) { switch (digest) { case NONE: return KeymasterDefs.KM_DIGEST_NONE; + case MD5: + return KeymasterDefs.KM_DIGEST_MD5; + case SHA1: + return KeymasterDefs.KM_DIGEST_SHA1; + case SHA224: + return KeymasterDefs.KM_DIGEST_SHA_2_224; case SHA256: return KeymasterDefs.KM_DIGEST_SHA_2_256; + case SHA384: + return KeymasterDefs.KM_DIGEST_SHA_2_384; + case SHA512: + return KeymasterDefs.KM_DIGEST_SHA_2_512; default: throw new IllegalArgumentException("Unknown digest: " + digest); } @@ -348,8 +453,18 @@ public abstract class KeyStoreKeyConstraints { switch (digest) { case KeymasterDefs.KM_DIGEST_NONE: return NONE; + case KeymasterDefs.KM_DIGEST_MD5: + return MD5; + case KeymasterDefs.KM_DIGEST_SHA1: + return SHA1; + case KeymasterDefs.KM_DIGEST_SHA_2_224: + return SHA224; case KeymasterDefs.KM_DIGEST_SHA_2_256: return SHA256; + case KeymasterDefs.KM_DIGEST_SHA_2_384: + return SHA384; + case KeymasterDefs.KM_DIGEST_SHA_2_512: + return SHA512; default: throw new IllegalArgumentException("Unknown digest: " + digest); } @@ -358,14 +473,46 @@ public abstract class KeyStoreKeyConstraints { /** * @hide */ + public static int[] allToKeymaster(@DigestEnum int digests) { + int[] result = getSetFlags(digests); + for (int i = 0; i < result.length; i++) { + result[i] = toKeymaster(result[i]); + } + return result; + } + + /** + * @hide + */ + public static @DigestEnum int allFromKeymaster(Collection<Integer> digests) { + @DigestEnum int result = 0; + for (int keymasterDigest : digests) { + result |= fromKeymaster(keymasterDigest); + } + return result; + } + + /** + * @hide + */ public static @DigestEnum Integer fromJCASecretKeyAlgorithm(String algorithm) { String algorithmLower = algorithm.toLowerCase(Locale.US); if (algorithmLower.startsWith("hmac")) { - if ("hmacsha256".equals(algorithmLower)) { + String digestLower = algorithmLower.substring("hmac".length()); + if ("md5".equals(digestLower)) { + return MD5; + } else if ("sha1".equals(digestLower)) { + return SHA1; + } else if ("sha224".equals(digestLower)) { + return SHA224; + } else if ("sha256".equals(digestLower)) { return SHA256; + } else if ("sha384".equals(digestLower)) { + return SHA384; + } else if ("sha512".equals(digestLower)) { + return SHA512; } else { - throw new IllegalArgumentException("Unsupported digest: " - + algorithmLower.substring("hmac".length())); + throw new IllegalArgumentException("Unsupported digest: " + digestLower); } } else { return null; @@ -379,8 +526,42 @@ public abstract class KeyStoreKeyConstraints { switch (digest) { case NONE: return "NONE"; + case MD5: + return "MD5"; + case SHA1: + return "SHA1"; + case SHA224: + return "SHA224"; case SHA256: return "SHA256"; + case SHA384: + return "SHA384"; + case SHA512: + return "SHA512"; + default: + throw new IllegalArgumentException("Unknown digest: " + digest); + } + } + + /** + * @hide + */ + public static Integer getOutputSizeBytes(@DigestEnum int digest) { + switch (digest) { + case NONE: + return null; + case MD5: + return 128 / 8; + case SHA1: + return 160 / 8; + case SHA224: + return 224 / 8; + case SHA256: + return 256 / 8; + case SHA384: + return 384 / 8; + case SHA512: + return 512 / 8; default: throw new IllegalArgumentException("Unknown digest: " + digest); } @@ -388,7 +569,8 @@ public abstract class KeyStoreKeyConstraints { } @Retention(RetentionPolicy.SOURCE) - @IntDef({BlockMode.ECB}) + @IntDef(flag = true, + value = {BlockMode.ECB, BlockMode.CBC, BlockMode.CTR, BlockMode.GCM}) public @interface BlockModeEnum {} /** @@ -397,10 +579,17 @@ public abstract class KeyStoreKeyConstraints { public static abstract class BlockMode { private BlockMode() {} - /** - * Electronic Codebook (ECB) block mode. - */ - public static final int ECB = 0; + /** Electronic Codebook (ECB) block mode. */ + public static final int ECB = 1 << 0; + + /** Cipher Block Chaining (CBC) block mode. */ + public static final int CBC = 1 << 1; + + /** Counter (CTR) block mode. */ + public static final int CTR = 1 << 2; + + /** Galois/Counter Mode (GCM) block mode. */ + public static final int GCM = 1 << 3; /** * @hide @@ -409,6 +598,12 @@ 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; + case GCM: + return KeymasterDefs.KM_MODE_GCM; default: throw new IllegalArgumentException("Unknown block mode: " + mode); } @@ -421,9 +616,193 @@ 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; + case KeymasterDefs.KM_MODE_GCM: + return GCM; default: throw new IllegalArgumentException("Unknown block mode: " + mode); } } + + /** + * @hide + */ + public static int[] allToKeymaster(@BlockModeEnum int modes) { + int[] result = getSetFlags(modes); + for (int i = 0; i < result.length; i++) { + result[i] = toKeymaster(result[i]); + } + return result; + } + + /** + * @hide + */ + public static @BlockModeEnum int allFromKeymaster(Collection<Integer> modes) { + @BlockModeEnum int result = 0; + for (int keymasterMode : modes) { + result |= fromKeymaster(keymasterMode); + } + return result; + } + + /** + * @hide + */ + public static String toString(@BlockModeEnum int mode) { + switch (mode) { + case ECB: + return "ECB"; + case CBC: + return "CBC"; + case CTR: + return "CTR"; + case GCM: + return "GCM"; + 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 if ("gcm".equals(modeLower)) { + return GCM; + } else { + throw new IllegalArgumentException("Unknown block mode: " + mode); + } + } + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, + value = {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 << 0; + + /** + * @hide + */ + public static int toKeymaster(@UserAuthenticatorEnum int userAuthenticator) { + switch (userAuthenticator) { + case LOCK_SCREEN: + return KeymasterDefs.HW_AUTH_PASSWORD; + default: + throw new IllegalArgumentException( + "Unknown user authenticator: " + userAuthenticator); + } + } + + /** + * @hide + */ + public static @UserAuthenticatorEnum int fromKeymaster(int userAuthenticator) { + switch (userAuthenticator) { + case KeymasterDefs.HW_AUTH_PASSWORD: + return LOCK_SCREEN; + default: + throw new IllegalArgumentException( + "Unknown user authenticator: " + userAuthenticator); + } + } + + /** + * @hide + */ + public static int allToKeymaster(@UserAuthenticatorEnum int userAuthenticators) { + int result = 0; + int userAuthenticator = 1; + while (userAuthenticators != 0) { + if ((userAuthenticators & 1) != 0) { + result |= toKeymaster(userAuthenticator); + } + userAuthenticators >>>= 1; + userAuthenticator <<= 1; + } + return result; + } + + /** + * @hide + */ + public static @UserAuthenticatorEnum int allFromKeymaster(int userAuthenticators) { + @UserAuthenticatorEnum int result = 0; + int userAuthenticator = 1; + while (userAuthenticators != 0) { + if ((userAuthenticators & 1) != 0) { + result |= fromKeymaster(userAuthenticator); + } + userAuthenticators >>>= 1; + userAuthenticator <<= 1; + } + return result; + } + + /** + * @hide + */ + public static String toString(@UserAuthenticatorEnum int userAuthenticator) { + switch (userAuthenticator) { + case LOCK_SCREEN: + return "LOCK_SCREEN"; + default: + throw new IllegalArgumentException( + "Unknown user authenticator: " + userAuthenticator); + } + } + } + + private static final int[] EMPTY_INT_ARRAY = new int[0]; + + private static int[] getSetFlags(int flags) { + if (flags == 0) { + return EMPTY_INT_ARRAY; + } + int result[] = new int[getSetBitCount(flags)]; + int resultOffset = 0; + int flag = 1; + while (flags != 0) { + if ((flags & 1) != 0) { + result[resultOffset] = flag; + resultOffset++; + } + flags >>>= 1; + flag <<= 1; + } + return result; + } + + private static int getSetBitCount(int value) { + if (value == 0) { + return 0; + } + int result = 0; + while (value != 0) { + if ((value & 1) != 0) { + result++; + } + value >>>= 1; + } + return result; } } diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java new file mode 100644 index 000000000000..5be8c394f2be --- /dev/null +++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java @@ -0,0 +1,227 @@ +/* + * 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); + } + } + + protected static abstract class HmacBase extends KeyStoreKeyGeneratorSpi { + protected HmacBase(@KeyStoreKeyConstraints.DigestEnum int digest) { + super(KeyStoreKeyConstraints.Algorithm.HMAC, + digest, + KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest) * 8); + } + } + + public static class HmacSHA1 extends HmacBase { + public HmacSHA1() { + super(KeyStoreKeyConstraints.Digest.SHA1); + } + } + + public static class HmacSHA224 extends HmacBase { + public HmacSHA224() { + super(KeyStoreKeyConstraints.Digest.SHA224); + } + } + + public static class HmacSHA256 extends HmacBase { + public HmacSHA256() { + super(KeyStoreKeyConstraints.Digest.SHA256); + } + } + + public static class HmacSHA384 extends HmacBase { + public HmacSHA384() { + super(KeyStoreKeyConstraints.Digest.SHA384); + } + } + + public static class HmacSHA512 extends HmacBase { + public HmacSHA512() { + super(KeyStoreKeyConstraints.Digest.SHA512); + } + } + + 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); + int purposes = spec.getPurposes(); + for (int keymasterPurpose : + KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) { + args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); + } + for (int keymasterBlockMode : + KeyStoreKeyConstraints.BlockMode.allToKeymaster(spec.getBlockModes())) { + args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode); + } + for (int keymasterPadding : + KeyStoreKeyConstraints.Padding.allToKeymaster(spec.getPaddings())) { + args.addInt(KeymasterDefs.KM_TAG_PADDING, keymasterPadding); + } + if (spec.getUserAuthenticators() == 0) { + 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() != -1) { + 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 KeyStore.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..df4c958f1de3 --- /dev/null +++ b/keystore/java/android/security/KeyStoreKeySpec.java @@ -0,0 +1,190 @@ +/* + * 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.Date; + +/** + * 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 int mPaddings; + private final @KeyStoreKeyConstraints.DigestEnum int mDigests; + private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes; + private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators; + private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mTeeEnforcedUserAuthenticators; + private final int 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 int paddings, + @KeyStoreKeyConstraints.DigestEnum int digests, + @KeyStoreKeyConstraints.BlockModeEnum int blockModes, + @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators, + @KeyStoreKeyConstraints.UserAuthenticatorEnum int teeEnforcedUserAuthenticators, + int userAuthenticationValidityDurationSeconds) { + mKeystoreAlias = keystoreKeyAlias; + mOrigin = origin; + mKeySize = keySize; + mKeyValidityStart = keyValidityStart; + mKeyValidityForOriginationEnd = keyValidityForOriginationEnd; + mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd; + mPurposes = purposes; + mAlgorithm = algorithm; + mPaddings = paddings; + mDigests = digests; + mBlockModes = blockModes; + mUserAuthenticators = userAuthenticators; + mTeeEnforcedUserAuthenticators = teeEnforcedUserAuthenticators; + 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 size of the key 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 set of block modes with which the key can be used. + */ + public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() { + return mBlockModes; + } + + /** + * Gets the set of padding modes with which the key can be used. + */ + public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() { + return mPaddings; + } + + /** + * Gets the set of digest algorithms with which the key can be used. + */ + public @KeyStoreKeyConstraints.DigestEnum int getDigests() { + return mDigests; + } + + /** + * Gets the set of 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 {@code 0} if the key can be used without user authentication. + */ + public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() { + return mUserAuthenticators; + } + + /** + * Gets the set of user authenticators for which the TEE enforces access restrictions for this + * key. This is a subset of the user authentications returned by + * {@link #getUserAuthenticators()}. + */ + public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getTeeEnforcedUserAuthenticators() { + return mTeeEnforcedUserAuthenticators; + } + + /** + * 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 -1} if not restricted. {@code 0} means authentication + * is required for every use of the key. + */ + public int getUserAuthenticationValidityDurationSeconds() { + return mUserAuthenticationValidityDurationSeconds; + } +} diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java index 2428c2a57a7a..49094677bf51 100644 --- a/keystore/java/android/security/KeyStoreParameter.java +++ b/keystore/java/android/security/KeyStoreParameter.java @@ -18,12 +18,10 @@ package android.security; import android.content.Context; +import java.security.Key; import java.security.KeyPairGenerator; import java.security.KeyStore.ProtectionParameter; -import java.util.Collections; import java.util.Date; -import java.util.HashSet; -import java.util.Set; /** * This provides the optional parameters that can be specified for @@ -50,29 +48,25 @@ public final class KeyStoreParameter implements ProtectionParameter { private final Date mKeyValidityStart; private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; - private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes; - private final @KeyStoreKeyConstraints.AlgorithmEnum Integer 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 Integer mUserAuthenticationValidityDurationSeconds; - - private KeyStoreParameter(int flags, Date keyValidityStart, - Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, - @KeyStoreKeyConstraints.PurposeEnum Integer purposes, - @KeyStoreKeyConstraints.AlgorithmEnum Integer algorithm, - @KeyStoreKeyConstraints.PaddingEnum Integer padding, - @KeyStoreKeyConstraints.DigestEnum Integer digest, - @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode, - Integer minSecondsBetweenOperations, - Integer maxUsesPerBoot, - Set<Integer> userAuthenticators, - Integer userAuthenticationValidityDurationSeconds) { - if ((userAuthenticationValidityDurationSeconds != null) - && (userAuthenticationValidityDurationSeconds < 0)) { + private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes; + private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings; + private final @KeyStoreKeyConstraints.DigestEnum Integer mDigests; + private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes; + private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators; + private final int mUserAuthenticationValidityDurationSeconds; + + private KeyStoreParameter(int flags, + Date keyValidityStart, + Date keyValidityForOriginationEnd, + Date keyValidityForConsumptionEnd, + @KeyStoreKeyConstraints.PurposeEnum int purposes, + @KeyStoreKeyConstraints.PaddingEnum int paddings, + @KeyStoreKeyConstraints.DigestEnum Integer digests, + @KeyStoreKeyConstraints.BlockModeEnum int blockModes, + @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators, + int userAuthenticationValidityDurationSeconds) { + if ((userAuthenticationValidityDurationSeconds < 0) + && (userAuthenticationValidityDurationSeconds != -1)) { throw new IllegalArgumentException( "userAuthenticationValidityDurationSeconds must not be negative"); } @@ -82,15 +76,10 @@ public final class KeyStoreParameter implements ProtectionParameter { 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(); + mPaddings = paddings; + mDigests = digests; + mBlockModes = blockModes; + mUserAuthenticators = userAuthenticators; mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; } @@ -142,105 +131,81 @@ public final class KeyStoreParameter implements ProtectionParameter { } /** - * Gets the set of purposes for which the key can be used to the provided set of purposes. - * - * @return set of purposes or {@code null} if the key can be used for any purpose. + * Gets the set of purposes for which the key can be used. * * @hide */ - public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() { + public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() { return mPurposes; } /** - * Gets the algorithm to which the key is restricted. + * Gets the set of padding schemes to which the key is restricted. * - * @return algorithm or {@code null} if it's not restricted. * @hide */ - public @KeyStoreKeyConstraints.AlgorithmEnum Integer getAlgorithm() { - return mAlgorithm; + public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() { + return mPaddings; } /** - * Gets the padding scheme to which the key is restricted. + * Gets the set of digests to which the key is restricted. * - * @return padding scheme or {@code null} if the padding scheme is not restricted. + * @throws IllegalStateException if this restriction has not been specified. * - * @hide - */ - public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() { - return mPadding; - } - - /** - * Gets the digest to which the key is restricted when generating Message Authentication Codes - * (MACs). - * - * @return digest or {@code null} if the digest is not restricted. + * @see #isDigestsSpecified() * * @hide */ - public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() { - return mDigest; - } - - /** - * 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; + public @KeyStoreKeyConstraints.DigestEnum int getDigests() { + if (mDigests == null) { + throw new IllegalStateException("Digests not specified"); + } + return mDigests; } /** - * Gets the minimum number of seconds that must expire since the most recent use of the key - * before it can be used again. + * Returns {@code true} if digest restrictions have been specified. * - * @return number of seconds or {@code null} if there is no restriction on how frequently a key - * can be used. + * @see #getDigests() * * @hide */ - public Integer getMinSecondsBetweenOperations() { - return mMinSecondsBetweenOperations; + public boolean isDigestsSpecified() { + return mDigests != null; } /** - * Gets the number of times the key can be used without rebooting the device. + * Gets the set of block modes to which the key is restricted. * - * @return maximum number of times or {@code null} if there is no restriction. * @hide */ - public Integer getMaxUsesPerBoot() { - return mMaxUsesPerBoot; + public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() { + return mBlockModes; } /** - * 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. + * Gets the set of 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. + * @return user authenticators or {@code 0} if the key can be used without user authentication. * * @hide */ - public Set<Integer> getUserAuthenticators() { - return new HashSet<Integer>(mUserAuthenticators); + public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() { + return 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 + * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication * is required for every use of the key. * * @hide */ - public Integer getUserAuthenticationValidityDurationSeconds() { + public int getUserAuthenticationValidityDurationSeconds() { return mUserAuthenticationValidityDurationSeconds; } @@ -266,15 +231,12 @@ public final class KeyStoreParameter implements ProtectionParameter { private Date mKeyValidityStart; private Date mKeyValidityForOriginationEnd; private Date mKeyValidityForConsumptionEnd; - private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes; - private @KeyStoreKeyConstraints.AlgorithmEnum Integer mAlgorithm; - private @KeyStoreKeyConstraints.PaddingEnum Integer mPadding; - private @KeyStoreKeyConstraints.DigestEnum Integer mDigest; - private @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode; - private Integer mMinSecondsBetweenOperations; - private Integer mMaxUsesPerBoot; - private Set<Integer> mUserAuthenticators; - private Integer mUserAuthenticationValidityDurationSeconds; + private @KeyStoreKeyConstraints.PurposeEnum int mPurposes; + private @KeyStoreKeyConstraints.PaddingEnum int mPaddings; + private @KeyStoreKeyConstraints.DigestEnum Integer mDigests; + private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes; + private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators; + private int mUserAuthenticationValidityDurationSeconds = -1; /** * Creates a new instance of the {@code Builder} with the given @@ -366,9 +328,9 @@ public final class KeyStoreParameter implements ProtectionParameter { } /** - * Restricts the purposes for which the key can be used to the provided set of purposes. + * Restricts the key to being used only for the provided set of purposes. * - * <p>By default, the key can be used for encryption, decryption, signing, and verification. + * <p>This restriction must be specified. There is no default. * * @hide */ @@ -378,83 +340,43 @@ public final class KeyStoreParameter implements ProtectionParameter { } /** - * Sets the algorithm of the key. - * - * <p>The algorithm of symmetric keys can be deduced from the key itself. Thus, explicitly - * specifying the algorithm of symmetric keys using this method is not necessary. - * - * @hide - */ - public Builder setAlgorithm(@KeyStoreKeyConstraints.AlgorithmEnum int algorithm) { - mAlgorithm = algorithm; - return this; - } - - /** - * Restricts the key to being used only with the provided padding scheme. Attempts to use + * Restricts the key to being used only with the provided padding schemes. 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 digest when generating 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. - * - * @see java.security.Key#getAlgorithm() - * - * @hide - */ - public Builder setDigest(@KeyStoreKeyConstraints.DigestEnum int digest) { - mDigest = digest; - 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; + public Builder setPaddings(@KeyStoreKeyConstraints.PaddingEnum int paddings) { + mPaddings = paddings; 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. + * Restricts the key to being used only with the provided digests when generating signatures + * or HMACs. Attempts to use the key with any other digest will be rejected. * - * <p>By default, there is no restriction on how frequently a key can be used. + * <p>For HMAC keys, the default is to restrict to the digest specified in + * {@link Key#getAlgorithm()}. For asymmetric signing keys this constraint must be specified + * because there is no default. * * @hide */ - public Builder setMinSecondsBetweenOperations(int seconds) { - mMinSecondsBetweenOperations = seconds; + public Builder setDigests(@KeyStoreKeyConstraints.DigestEnum int digests) { + mDigests = digests; return this; } /** - * Sets the maximum number of times a key can be used without rebooting the device. + * Restricts the key to being used only with the provided block modes. Attempts to use the + * key with any other block modes will be rejected. * - * <p>By default, the key can be used for an unlimited number of times. + * <p>This restriction must be specified for symmetric encryption/decryption keys. * * @hide */ - public Builder setMaxUsesPerBoot(int count) { - mMaxUsesPerBoot = count; + public Builder setBlockModes(@KeyStoreKeyConstraints.BlockModeEnum int blockModes) { + mBlockModes = blockModes; return this; } @@ -464,16 +386,16 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key can be used without user authentication. * - * @param userAuthenticators user authenticators or empty list if this key can be accessed + * @param userAuthenticators user authenticators or {@code 0} 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; + public Builder setUserAuthenticators( + @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators) { + mUserAuthenticators = userAuthenticators; return this; } @@ -486,7 +408,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for * every use of the key. * - * @see #setUserAuthenticators(Set) + * @see #setUserAuthenticators(int) * * @hide */ @@ -502,10 +424,15 @@ 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, + mPaddings, + mDigests, + mBlockModes, + 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..09f0b0034d2e --- /dev/null +++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java @@ -0,0 +1,174 @@ +/* + * 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 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 int paddings; + @KeyStoreKeyConstraints.DigestEnum int digests; + @KeyStoreKeyConstraints.BlockModeEnum int blockModes; + @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators; + @KeyStoreKeyConstraints.UserAuthenticatorEnum int teeEnforcedUserAuthenticators; + 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); + paddings = KeyStoreKeyConstraints.Padding.allFromKeymaster( + KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_PADDING)); + digests = KeyStoreKeyConstraints.Digest.allFromKeymaster( + KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_DIGEST)); + blockModes = KeyStoreKeyConstraints.BlockMode.allFromKeymaster( + KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_BLOCK_MODE)); + + @KeyStoreKeyConstraints.UserAuthenticatorEnum + int swEnforcedKeymasterUserAuthenticators = + keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); + @KeyStoreKeyConstraints.UserAuthenticatorEnum + int hwEnforcedKeymasterUserAuthenticators = + keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); + @KeyStoreKeyConstraints.UserAuthenticatorEnum + int keymasterUserAuthenticators = + swEnforcedKeymasterUserAuthenticators | hwEnforcedKeymasterUserAuthenticators; + userAuthenticators = KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster( + keymasterUserAuthenticators); + teeEnforcedUserAuthenticators = + KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster( + hwEnforcedKeymasterUserAuthenticators); + } 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; + } + Integer userAuthenticationValidityDurationSeconds = + KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_AUTH_TIMEOUT); + + return new KeyStoreKeySpec(entryAlias, + origin, + keySize, + keyValidityStart, + keyValidityForOriginationEnd, + keyValidityForConsumptionEnd, + purposes, + algorithm, + paddings, + digests, + blockModes, + userAuthenticators, + teeEnforcedUserAuthenticators, + ((userAuthenticationValidityDurationSeconds != null) + ? userAuthenticationValidityDurationSeconds : -1)); + } + + @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/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java new file mode 100644 index 000000000000..3143d4d7e5d0 --- /dev/null +++ b/keystore/java/android/security/KeymasterUtils.java @@ -0,0 +1,63 @@ +/* + * 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 java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @hide + */ +public abstract class KeymasterUtils { + private KeymasterUtils() {} + + 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 7468fb5e1000..c9a140c63141 100644 --- a/keystore/tests/src/android/security/KeyStoreTest.java +++ b/keystore/tests/src/android/security/KeyStoreTest.java @@ -712,6 +712,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { 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.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048); args.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4.longValue()); @@ -735,6 +736,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { 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.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048); args.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4.longValue()); @@ -769,6 +771,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE); args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048); args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB); + args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); args.addBlob(KeymasterDefs.KM_TAG_APPLICATION_ID, id); args.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4.longValue()); @@ -806,6 +809,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB); args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096); args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16); + args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); KeyCharacteristics outCharacteristics = new KeyCharacteristics(); int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics); @@ -838,6 +842,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE); args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mode); args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, size); + args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); return mKeyStore.importKey(name, args, KeymasterDefs.KM_KEY_FORMAT_RAW, key, 0, new KeyCharacteristics()); } @@ -901,6 +906,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB); args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096); args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16); + args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); KeyCharacteristics outCharacteristics = new KeyCharacteristics(); int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics); @@ -922,4 +928,30 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { assertEquals("Operation should be pruned", KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE, mKeyStore.update(first, null, new byte[] {0x01}).resultCode); } + + public void testAuthNeeded() throws Exception { + 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_AES); + args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE); + args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256); + args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB); + args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096); + args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16); + args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 1); + + KeyCharacteristics outCharacteristics = new KeyCharacteristics(); + int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics); + KeymasterArguments out = new KeymasterArguments(); + assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc); + OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, + true, args, null, out); + assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode); + IBinder token = result.token; + result = mKeyStore.update(token, null, new byte[] {0x01, 0x02, 0x03, 0x04}); + assertEquals("Update should require authorization", + KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED, result.resultCode); + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index e66934e5564f..fae0643c2aef 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -773,6 +773,11 @@ public class KeyguardViewMediator extends SystemUI { synchronized (this) { if (DEBUG) Log.d(TAG, "setKeyguardEnabled(" + enabled + ")"); + if (isSecure()) { + Log.d(TAG, "current mode is SecurityMode, ignore hide keyguard"); + return; + } + mExternallyEnabled = enabled; if (!enabled && mShowing) { diff --git a/preloaded-classes b/preloaded-classes index c8d8c5d096b8..151766f6e249 100644 --- a/preloaded-classes +++ b/preloaded-classes @@ -1146,8 +1146,6 @@ android.provider.Settings$SettingNotFoundException android.provider.Settings$System android.provider.Telephony$Mms android.renderscript.RenderScript -android.security.AndroidKeyPairGenerator -android.security.AndroidKeyStore android.security.AndroidKeyStoreProvider android.speech.tts.TextToSpeechService android.speech.tts.TextToSpeechService$SpeechItemV1 diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java index 523c8fb7b189..28547ccdd13f 100644 --- a/rs/java/android/renderscript/Allocation.java +++ b/rs/java/android/renderscript/Allocation.java @@ -1340,15 +1340,22 @@ public class Allocation extends BaseObj { private void copyTo(Object array, Element.DataType dt, int arrayLen) { Trace.traceBegin(RenderScript.TRACE_TAG, "copyTo"); - if (dt.mSize * arrayLen < mSize) { - throw new RSIllegalArgumentException( - "Size of output array cannot be smaller than size of allocation."); - } mRS.validate(); boolean usePadding = false; if (mAutoPadding && (mType.getElement().getVectorSize() == 3)) { usePadding = true; } + if (usePadding) { + if (dt.mSize * arrayLen < mSize / 4 * 3) { + throw new RSIllegalArgumentException( + "Size of output array cannot be smaller than size of allocation."); + } + } else { + if (dt.mSize * arrayLen < mSize) { + throw new RSIllegalArgumentException( + "Size of output array cannot be smaller than size of allocation."); + } + } mRS.nAllocationRead(getID(mRS), array, dt, mType.mElement.mType.mSize, usePadding); Trace.traceEnd(RenderScript.TRACE_TAG); } diff --git a/rs/java/android/renderscript/FieldPacker.java b/rs/java/android/renderscript/FieldPacker.java index 0f967fcae26a..de1c49730aaa 100644 --- a/rs/java/android/renderscript/FieldPacker.java +++ b/rs/java/android/renderscript/FieldPacker.java @@ -47,6 +47,15 @@ public class FieldPacker { // subAlign() can never work correctly for copied FieldPacker objects. } + static FieldPacker createFromArray(Object[] args) { + FieldPacker fp = new FieldPacker(RenderScript.sPointerSize * 8); + for (Object arg : args) { + fp.addSafely(arg); + } + fp.resize(fp.mPos); + return fp; + } + public void align(int v) { if ((v <= 0) || ((v & (v - 1)) != 0)) { throw new RSIllegalArgumentException("argument must be a non-negative non-zero power of 2: " + v); @@ -618,294 +627,182 @@ public class FieldPacker { return mPos; } - private static void addToPack(FieldPacker fp, Object obj) { + private void add(Object obj) { if (obj instanceof Boolean) { - fp.addBoolean(((Boolean)obj).booleanValue()); + addBoolean((Boolean)obj); return; } if (obj instanceof Byte) { - fp.addI8(((Byte)obj).byteValue()); + addI8((Byte)obj); return; } if (obj instanceof Short) { - fp.addI16(((Short)obj).shortValue()); + addI16((Short)obj); return; } if (obj instanceof Integer) { - fp.addI32(((Integer)obj).intValue()); + addI32((Integer)obj); return; } if (obj instanceof Long) { - fp.addI64(((Long)obj).longValue()); + addI64((Long)obj); return; } if (obj instanceof Float) { - fp.addF32(((Float)obj).floatValue()); + addF32((Float)obj); return; } if (obj instanceof Double) { - fp.addF64(((Double)obj).doubleValue()); + addF64((Double)obj); return; } if (obj instanceof Byte2) { - fp.addI8((Byte2)obj); + addI8((Byte2)obj); return; } if (obj instanceof Byte3) { - fp.addI8((Byte3)obj); + addI8((Byte3)obj); return; } if (obj instanceof Byte4) { - fp.addI8((Byte4)obj); + addI8((Byte4)obj); return; } if (obj instanceof Short2) { - fp.addI16((Short2)obj); + addI16((Short2)obj); return; } if (obj instanceof Short3) { - fp.addI16((Short3)obj); + addI16((Short3)obj); return; } if (obj instanceof Short4) { - fp.addI16((Short4)obj); + addI16((Short4)obj); return; } if (obj instanceof Int2) { - fp.addI32((Int2)obj); + addI32((Int2)obj); return; } if (obj instanceof Int3) { - fp.addI32((Int3)obj); + addI32((Int3)obj); return; } if (obj instanceof Int4) { - fp.addI32((Int4)obj); + addI32((Int4)obj); return; } if (obj instanceof Long2) { - fp.addI64((Long2)obj); + addI64((Long2)obj); return; } if (obj instanceof Long3) { - fp.addI64((Long3)obj); + addI64((Long3)obj); return; } if (obj instanceof Long4) { - fp.addI64((Long4)obj); + addI64((Long4)obj); return; } if (obj instanceof Float2) { - fp.addF32((Float2)obj); + addF32((Float2)obj); return; } if (obj instanceof Float3) { - fp.addF32((Float3)obj); + addF32((Float3)obj); return; } if (obj instanceof Float4) { - fp.addF32((Float4)obj); + addF32((Float4)obj); return; } if (obj instanceof Double2) { - fp.addF64((Double2)obj); + addF64((Double2)obj); return; } if (obj instanceof Double3) { - fp.addF64((Double3)obj); + addF64((Double3)obj); return; } if (obj instanceof Double4) { - fp.addF64((Double4)obj); + addF64((Double4)obj); return; } if (obj instanceof Matrix2f) { - fp.addMatrix((Matrix2f)obj); + addMatrix((Matrix2f)obj); return; } if (obj instanceof Matrix3f) { - fp.addMatrix((Matrix3f)obj); + addMatrix((Matrix3f)obj); return; } if (obj instanceof Matrix4f) { - fp.addMatrix((Matrix4f)obj); + addMatrix((Matrix4f)obj); return; } if (obj instanceof BaseObj) { - fp.addObj((BaseObj)obj); + addObj((BaseObj)obj); return; } } - private static int getPackedSize(Object obj) { - if (obj instanceof Boolean) { - return 1; - } - - if (obj instanceof Byte) { - return 1; - } - - if (obj instanceof Short) { - return 2; - } - - if (obj instanceof Integer) { - return 4; - } - - if (obj instanceof Long) { - return 8; - } - - if (obj instanceof Float) { - return 4; - } - - if (obj instanceof Double) { - return 8; - } - - if (obj instanceof Byte2) { - return 2; - } - - if (obj instanceof Byte3) { - return 3; - } - - if (obj instanceof Byte4) { - return 4; - } - - if (obj instanceof Short2) { - return 4; - } - - if (obj instanceof Short3) { - return 6; - } - - if (obj instanceof Short4) { - return 8; - } - - if (obj instanceof Int2) { - return 8; - } - - if (obj instanceof Int3) { - return 12; - } - - if (obj instanceof Int4) { - return 16; - } - - if (obj instanceof Long2) { - return 16; - } - - if (obj instanceof Long3) { - return 24; - } - - if (obj instanceof Long4) { - return 32; - } - - if (obj instanceof Float2) { - return 8; - } - - if (obj instanceof Float3) { - return 12; + private boolean resize(int newSize) { + if (newSize == mLen) { + return false; } - if (obj instanceof Float4) { - return 16; - } - - if (obj instanceof Double2) { - return 16; - } - - if (obj instanceof Double3) { - return 24; - } - - if (obj instanceof Double4) { - return 32; - } - - if (obj instanceof Matrix2f) { - return 16; - } - - if (obj instanceof Matrix3f) { - return 36; - } - - if (obj instanceof Matrix4f) { - return 64; - } - - if (obj instanceof BaseObj) { - if (RenderScript.sPointerSize == 8) { - return 32; - } else { - return 4; - } - } - - return 0; + byte[] newData = new byte[newSize]; + System.arraycopy(mData, 0, newData, 0, mPos); + mData = newData; + mLen = newSize; + return true; } - static FieldPacker createFieldPack(Object[] args) { - int len = 0; - for (Object arg : args) { - len += getPackedSize(arg); - } - FieldPacker fp = new FieldPacker(len); - for (Object arg : args) { - addToPack(fp, arg); - } - return fp; + private void addSafely(Object obj) { + boolean retry; + final int oldPos = mPos; + do { + retry = false; + try { + add(obj); + } catch (ArrayIndexOutOfBoundsException e) { + mPos = oldPos; + resize(mLen * 2); + retry = true; + } + } while (retry); } - private final byte mData[]; + private byte mData[]; private int mPos; private int mLen; private BitSet mAlignment; - } - - diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java index 45f0ca61ff80..51387196dea9 100644 --- a/rs/java/android/renderscript/RenderScript.java +++ b/rs/java/android/renderscript/RenderScript.java @@ -16,7 +16,6 @@ package android.renderscript; -import java.io.File; import java.lang.reflect.Method; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -130,8 +129,6 @@ public class RenderScript { native void nContextInitToClient(long con); native void nContextDeinitToClient(long con); - static File mCacheDir; - // this should be a monotonically increasing ID // used in conjunction with the API version of a device static final long sMinorID = 1; @@ -146,23 +143,6 @@ public class RenderScript { return sMinorID; } - /** - * Sets the directory to use as a persistent storage for the - * renderscript object file cache. - * - * @hide - * @param cacheDir A directory the current process can write to - */ - public static void setupDiskCache(File cacheDir) { - if (!sInitialized) { - Log.e(LOG_TAG, "RenderScript.setupDiskCache() called when disabled"); - return; - } - - // Defer creation of cache path to nScriptCCreate(). - mCacheDir = cacheDir; - } - /** * ContextType specifies the specific type of context to be created. * diff --git a/rs/java/android/renderscript/RenderScriptCacheDir.java b/rs/java/android/renderscript/RenderScriptCacheDir.java new file mode 100644 index 000000000000..95a9d7575945 --- /dev/null +++ b/rs/java/android/renderscript/RenderScriptCacheDir.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008-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.renderscript; + +import java.io.File; + +/** + * Used only for tracking the RenderScript cache directory. + * @hide + */ +public class RenderScriptCacheDir { + /** + * Sets the directory to use as a persistent storage for the + * renderscript object file cache. + * + * @hide + * @param cacheDir A directory the current process can write to + */ + public static void setupDiskCache(File cacheDir) { + // Defer creation of cache path to nScriptCCreate(). + mCacheDir = cacheDir; + } + + static File mCacheDir; + +} diff --git a/rs/java/android/renderscript/ScriptC.java b/rs/java/android/renderscript/ScriptC.java index 64d21e49dee5..bf706c131e85 100644 --- a/rs/java/android/renderscript/ScriptC.java +++ b/rs/java/android/renderscript/ScriptC.java @@ -124,7 +124,7 @@ public class ScriptC extends Script { // Create the RS cache path if we haven't done so already. if (mCachePath == null) { - File f = new File(rs.mCacheDir, CACHE_PATH); + File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH); mCachePath = f.getAbsolutePath(); f.mkdirs(); } @@ -135,7 +135,7 @@ public class ScriptC extends Script { private static synchronized long internalStringCreate(RenderScript rs, String resName, byte[] bitcode) { // Create the RS cache path if we haven't done so already. if (mCachePath == null) { - File f = new File(rs.mCacheDir, CACHE_PATH); + File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH); mCachePath = f.getAbsolutePath(); f.mkdirs(); } diff --git a/rs/java/android/renderscript/ScriptGroup2.java b/rs/java/android/renderscript/ScriptGroup2.java index 4a56572fe739..857e9fb32f0d 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(); @@ -110,9 +112,10 @@ public class ScriptGroup2 extends BaseObj { public Closure(RenderScript rs, Script.InvokeID invokeID, Object[] args, Map<Script.FieldID, Object> globals) { super(0, rs); - mFP = FieldPacker.createFieldPack(args); + mFP = FieldPacker.createFromArray(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/java/android/renderscript/ScriptIntrinsicBlur.java b/rs/java/android/renderscript/ScriptIntrinsicBlur.java index 5c4edd3ae9d8..60e2b6d99b1e 100644 --- a/rs/java/android/renderscript/ScriptIntrinsicBlur.java +++ b/rs/java/android/renderscript/ScriptIntrinsicBlur.java @@ -34,7 +34,7 @@ public final class ScriptIntrinsicBlur extends ScriptIntrinsic { * Create an intrinsic for applying a blur to an allocation. The * default radius is 5.0. * - * Supported elements types are {@link Element#U8_4} + * Supported elements types are {@link Element#U8_4 Element#U8} * * @param rs The RenderScript context * @param e Element type for inputs and outputs 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/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index d9ef766f2b71..758b0fc4bbc9 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2022,6 +2022,8 @@ public class ConnectivityService extends IConnectivityManager.Stub ReapUnvalidatedNetworks.DONT_REAP); } } + NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo); + if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name); } // If this method proves to be too slow then we can maintain a separate diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 059dde156260..4e7aa77557ee 100755 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2127,8 +2127,16 @@ public final class ActiveServices { } } - // First clear app state from services. - for (int i=app.services.size()-1; i>=0; i--) { + // Clean up any connections this application has to other services. + for (int i = app.connections.size() - 1; i >= 0; i--) { + ConnectionRecord r = app.connections.valueAt(i); + removeConnectionLocked(r, app, null); + } + updateServiceConnectionActivitiesLocked(app); + app.connections.clear(); + + // Clear app state from services. + for (int i = app.services.size() - 1; i >= 0; i--) { ServiceRecord sr = app.services.valueAt(i); synchronized (sr.stats.getBatteryStats()) { sr.stats.stopLaunchedLocked(); @@ -2188,14 +2196,6 @@ public final class ActiveServices { } } - // Clean up any connections this application has to other services. - for (int i=app.connections.size()-1; i>=0; i--) { - ConnectionRecord r = app.connections.valueAt(i); - removeConnectionLocked(r, app, null); - } - updateServiceConnectionActivitiesLocked(app); - app.connections.clear(); - ServiceMap smap = getServiceMap(app.userId); // Now do remaining service cleanup. @@ -2228,7 +2228,7 @@ public final class ActiveServices { EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH, sr.userId, sr.crashCount, sr.shortName, app.pid); bringDownServiceLocked(sr); - } else if (!allowRestart) { + } else if (!allowRestart || !mAm.isUserRunningLocked(sr.userId, false)) { bringDownServiceLocked(sr); } else { boolean canceled = scheduleServiceRestartLocked(sr, true); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ab1a1e84bd24..09ebe60c88aa 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); @@ -2787,10 +2789,38 @@ public final class ActivityManagerService extends ActivityManagerNative if (!isolated) { app = getProcessRecordLocked(processName, info.uid, keepIfLarge); checkTime(startTime, "startProcess: after getProcessRecord"); + + if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) { + // If we are in the background, then check to see if this process + // is bad. If so, we will just silently fail. + if (mBadProcesses.get(info.processName, info.uid) != null) { + if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid + + "/" + info.processName); + return null; + } + } else { + // When the user is explicitly starting a process, then clear its + // crash count so that we won't make it bad until they see at + // least one crash dialog again, and make the process good again + // if it had been bad. + if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid + + "/" + info.processName); + mProcessCrashTimes.remove(info.processName, info.uid); + if (mBadProcesses.get(info.processName, info.uid) != null) { + EventLog.writeEvent(EventLogTags.AM_PROC_GOOD, + UserHandle.getUserId(info.uid), info.uid, + info.processName); + mBadProcesses.remove(info.processName, info.uid); + if (app != null) { + app.bad = false; + } + } + } } else { // If this is an isolated process, it can't re-use an existing process. app = null; } + // We don't have to do anything more if: // (1) There is an existing application record; and // (2) The caller doesn't think it is dead, OR there is no thread @@ -2824,35 +2854,6 @@ public final class ActivityManagerService extends ActivityManagerNative String hostingNameStr = hostingName != null ? hostingName.flattenToShortString() : null; - if (!isolated) { - if ((intentFlags&Intent.FLAG_FROM_BACKGROUND) != 0) { - // If we are in the background, then check to see if this process - // is bad. If so, we will just silently fail. - if (mBadProcesses.get(info.processName, info.uid) != null) { - if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid - + "/" + info.processName); - return null; - } - } else { - // When the user is explicitly starting a process, then clear its - // crash count so that we won't make it bad until they see at - // least one crash dialog again, and make the process good again - // if it had been bad. - if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid - + "/" + info.processName); - mProcessCrashTimes.remove(info.processName, info.uid); - if (mBadProcesses.get(info.processName, info.uid) != null) { - EventLog.writeEvent(EventLogTags.AM_PROC_GOOD, - UserHandle.getUserId(info.uid), info.uid, - info.processName); - mBadProcesses.remove(info.processName, info.uid); - if (app != null) { - app.bad = false; - } - } - } - } - if (app == null) { checkTime(startTime, "startProcess: creating new process record"); app = newProcessRecordLocked(info, processName, isolated, isolatedUid); @@ -4590,17 +4591,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 +6802,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 +17662,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 +17983,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..ada16e7d22ee 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -899,6 +899,11 @@ final class ActivityStack { r.userId, System.identityHashCode(r), r.shortComponentName, mPausingActivity != null ? mPausingActivity.shortComponentName : "(none)"); + if (r.finishing && r.state == ActivityState.PAUSING) { + if (DEBUG_PAUSE) Slog.v(TAG, + "Executing finish of failed to pause activity: " + r); + finishCurrentActivityLocked(r, FINISH_AFTER_VISIBLE, false); + } } } } @@ -3390,6 +3395,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 +3439,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; @@ -3900,16 +3905,18 @@ final class ActivityStack { } void getTasksLocked(List<RunningTaskInfo> list, int callingUid, boolean allowed) { + boolean focusedStack = mStackSupervisor.getFocusedStack() == this; + boolean topTask = true; for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); + if (task.getTopActivity() == null) { + continue; + } ActivityRecord r = null; ActivityRecord top = null; int numActivities = 0; int numRunning = 0; final ArrayList<ActivityRecord> activities = task.mActivities; - if (activities.isEmpty()) { - continue; - } if (!allowed && !task.isHomeTask() && task.effectiveUid != callingUid) { continue; } @@ -3938,14 +3945,18 @@ final class ActivityStack { ci.baseActivity = r.intent.getComponent(); ci.topActivity = top.intent.getComponent(); ci.lastActiveTime = task.lastActiveTime; + if (focusedStack && topTask) { + // Give the latest time to ensure foreground task can be sorted + // at the first, because lastActiveTime of creating task is 0. + ci.lastActiveTime = System.currentTimeMillis(); + topTask = false; + } if (top.task != null) { ci.description = top.task.lastDescription; } ci.numActivities = numActivities; ci.numRunning = numRunning; - //System.out.println( - // "#" + maxNum + ": " + " descr=" + ci.description); list.add(ci); } } 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..466831a4c51a 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? @@ -418,7 +420,7 @@ final class ProcessRecord { tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList); origBase.makeInactive(); } - baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid, + baseProcessTracker = tracker.getProcessStateLocked(info.packageName, uid, info.versionCode, processName); baseProcessTracker.makeActive(); for (int i=0; i<pkgList.size(); i++) { @@ -426,7 +428,7 @@ final class ProcessRecord { if (holder.state != null && holder.state != origBase) { holder.state.makeInactive(); } - holder.state = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid, + holder.state = tracker.getProcessStateLocked(pkgList.keyAt(i), uid, info.versionCode, processName); if (holder.state != baseProcessTracker) { holder.state.makeActive(); @@ -617,7 +619,7 @@ final class ProcessRecord { versionCode); if (baseProcessTracker != null) { holder.state = tracker.getProcessStateLocked( - pkg, info.uid, versionCode, processName); + pkg, uid, versionCode, processName); pkgList.put(pkg, holder); if (holder.state != baseProcessTracker) { holder.state.makeActive(); @@ -664,7 +666,7 @@ final class ProcessRecord { } pkgList.clear(); ProcessStats.ProcessState ps = tracker.getProcessStateLocked( - info.packageName, info.uid, info.versionCode, processName); + info.packageName, uid, info.versionCode, processName); ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder( info.versionCode); holder.state = ps; diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 5cde8ea1db68..b4a44a646551 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageStats; import android.os.Build; @@ -83,14 +84,15 @@ public final class Installer extends SystemService { } public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName, - String instructionSet, boolean vmSafeMode, boolean debuggable) { + String instructionSet, boolean vmSafeMode, boolean debuggable, + @Nullable String outputPath) { if (!isValidInstructionSet(instructionSet)) { Slog.e(TAG, "Invalid instruction set: " + instructionSet); return -1; } return mInstaller.dexopt(apkPath, uid, isPublic, pkgName, instructionSet, vmSafeMode, - debuggable); + debuggable, outputPath); } public int idmap(String targetApkPath, String overlayApkPath, int uid) { @@ -134,6 +136,16 @@ public final class Installer extends SystemService { return mInstaller.execute(builder.toString()); } + /** + * Removes packageDir or its subdirectory + */ + public int rmPackageDir(String packageDir) { + StringBuilder builder = new StringBuilder("rmpackagedir"); + builder.append(' '); + builder.append(packageDir); + return mInstaller.execute(builder.toString()); + } + public int remove(String name, int userId) { StringBuilder builder = new StringBuilder("remove"); builder.append(' '); @@ -331,6 +343,15 @@ public final class Installer extends SystemService { return (mInstaller.execute(builder.toString()) == 0); } + public int createOatDir(String oatDir, String dexInstructionSet) { + StringBuilder builder = new StringBuilder("createoatdir"); + builder.append(' '); + builder.append(oatDir); + builder.append(' '); + builder.append(dexInstructionSet); + return mInstaller.execute(builder.toString()); + } + /** * Returns true iff. {@code instructionSet} is a valid instruction set. */ diff --git a/services/core/java/com/android/server/pm/InstructionSets.java b/services/core/java/com/android/server/pm/InstructionSets.java index 79e7a20fff0f..5092ebf9d4b9 100644 --- a/services/core/java/com/android/server/pm/InstructionSets.java +++ b/services/core/java/com/android/server/pm/InstructionSets.java @@ -74,6 +74,7 @@ public class InstructionSets { * a native bridge this might be different than the one shared libraries use. */ public static String getDexCodeInstructionSet(String sharedLibraryIsa) { + // TODO b/19550105 Build mapping once instead of querying each time String dexCodeIsa = SystemProperties.get("ro.dalvik.vm.isa." + sharedLibraryIsa); return TextUtils.isEmpty(dexCodeIsa) ? sharedLibraryIsa : dexCodeIsa; } @@ -111,4 +112,13 @@ public class InstructionSets { return allInstructionSets; } + + public static String getPrimaryInstructionSet(ApplicationInfo info) { + if (info.primaryCpuAbi == null) { + return getPreferredInstructionSet(); + } + + return VMRuntime.getInstructionSet(info.primaryCpuAbi); + } + } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 2dbce0a4ff52..680ec4b4bb87 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.content.pm.PackageParser; import android.os.UserHandle; @@ -23,6 +24,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.Slog; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; @@ -38,7 +40,9 @@ import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; * Helper class for running dexopt command on packages. */ final class PackageDexOptimizer { - static final String TAG = "PackageManager.DexOptimizer"; + private static final String TAG = "PackageManager.DexOptimizer"; + static final String OAT_DIR_NAME = "oat"; + // TODO b/19550105 Remove error codes and use exceptions static final int DEX_OPT_SKIPPED = 0; static final int DEX_OPT_PERFORMED = 1; static final int DEX_OPT_DEFERRED = 2; @@ -117,19 +121,30 @@ final class PackageDexOptimizer { final byte isDexOptNeeded = DexFile.isDexOptNeededInternal(path, pkg.packageName, dexCodeInstructionSet, defer); if (forceDex || (!defer && isDexOptNeeded == DexFile.DEXOPT_NEEDED)) { + File oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet); Log.i(TAG, "Running dexopt on: " + path + " pkg=" + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet - + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable); + + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable + + " oatDir = " + oatDir); final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); - final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid, - !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet, - vmSafeMode, debuggable); - if (ret < 0) { - // Don't bother running dexopt again if we failed, it will probably - // just result in an error again. Also, don't bother dexopting for other - // paths & ISAs. - return DEX_OPT_FAILED; + if (oatDir != null) { + int ret = mPackageManagerService.mInstaller.dexopt( + path, sharedGid, !pkg.isForwardLocked(), pkg.packageName, + dexCodeInstructionSet, vmSafeMode, debuggable, + oatDir.getAbsolutePath()); + if (ret < 0) { + return DEX_OPT_FAILED; + } + } else { + final int ret = mPackageManagerService.mInstaller + .dexopt(path, sharedGid, + !pkg.isForwardLocked(), pkg.packageName, + dexCodeInstructionSet, + vmSafeMode, debuggable, null); + if (ret < 0) { + return DEX_OPT_FAILED; + } } performedDexOpt = true; @@ -186,6 +201,36 @@ final class PackageDexOptimizer { return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; } + /** + * Creates oat dir for the specified package. In certain cases oat directory + * <strong>cannot</strong> be created: + * <ul> + * <li>{@code pkg} is a system app, which is not updated.</li> + * <li>Package location is not a directory, i.e. monolithic install.</li> + * </ul> + * + * @return oat directory or null, if oat directory cannot be created. + */ + @Nullable + private File createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet) + throws IOException { + if (pkg.isSystemApp() && !pkg.isUpdatedSystemApp()) { + return null; + } + File codePath = new File(pkg.codePath); + if (codePath.isDirectory()) { + File oatDir = getOatDir(codePath); + mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(), + dexInstructionSet); + return oatDir; + } + return null; + } + + static File getOatDir(File codePath) { + return new File(codePath, OAT_DIR_NAME); + } + private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets, boolean forceDex, boolean defer, ArraySet<String> done) { for (String libName : libs) { @@ -209,7 +254,7 @@ final class PackageDexOptimizer { public void addPackageForDeferredDexopt(PackageParser.Package pkg) { if (mDeferredDexOpt == null) { - mDeferredDexOpt = new ArraySet<PackageParser.Package>(); + mDeferredDexOpt = new ArraySet<>(); } mDeferredDexOpt.add(pkg); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 2150e9ab002a..95d7a52893d4 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -53,7 +53,6 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Environment; -import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -225,9 +224,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { for (File stage : unclaimedStages) { Slog.w(TAG, "Deleting orphan stage " + stage); if (stage.isDirectory()) { - FileUtils.deleteContents(stage); + mPm.mInstaller.rmPackageDir(stage.getAbsolutePath()); + } else { + stage.delete(); } - stage.delete(); } // Clean up orphaned icons diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index aba930fb7abd..bb8a78575014 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -59,6 +59,7 @@ import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; import static com.android.server.pm.InstructionSets.getPreferredInstructionSet; +import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet; import android.util.ArrayMap; @@ -1872,9 +1873,10 @@ public class PackageManagerService extends IPackageManager.Stub { removeDataDirsLI(ps.name); if (ps.codePath != null) { if (ps.codePath.isDirectory()) { - FileUtils.deleteContents(ps.codePath); + mInstaller.rmPackageDir(ps.codePath.getAbsolutePath()); + } else { + ps.codePath.delete(); } - ps.codePath.delete(); } if (ps.resourcePath != null && !ps.resourcePath.equals(ps.codePath)) { if (ps.resourcePath.isDirectory()) { @@ -4190,9 +4192,10 @@ public class PackageManagerService extends IPackageManager.Stub { e.error == PackageManager.INSTALL_FAILED_INVALID_APK) { logCriticalInfo(Log.WARN, "Deleting invalid package at " + file); if (file.isDirectory()) { - FileUtils.deleteContents(file); + mInstaller.rmPackageDir(file.getAbsolutePath()); + } else { + file.delete(); } - file.delete(); } } } @@ -4640,7 +4643,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Give priority to system apps. for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) { PackageParser.Package pkg = it.next(); - if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { + if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) { if (DEBUG_DEXOPT) { Log.i(TAG, "Adding system app " + sortedPkgs.size() + ": " + pkg.packageName); } @@ -4651,7 +4654,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Give priority to updated system apps. for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) { PackageParser.Package pkg = it.next(); - if (isUpdatedSystemApp(pkg)) { + if (pkg.isUpdatedSystemApp()) { if (DEBUG_DEXOPT) { Log.i(TAG, "Adding updated system app " + sortedPkgs.size() + ": " + pkg.packageName); } @@ -4772,14 +4775,6 @@ public class PackageManagerService extends IPackageManager.Stub { return performDexOpt(packageName, instructionSet, false); } - private static String getPrimaryInstructionSet(ApplicationInfo info) { - if (info.primaryCpuAbi == null) { - return getPreferredInstructionSet(); - } - - return VMRuntime.getInstructionSet(info.primaryCpuAbi); - } - public boolean performDexOpt(String packageName, String instructionSet, boolean backgroundDexopt) { boolean dexopt = mLazyDexOpt || backgroundDexopt; boolean updateUsage = !backgroundDexopt; // Don't update usage if this is just a backgroundDexopt @@ -5513,7 +5508,7 @@ public class PackageManagerService extends IPackageManager.Stub { final String path = scanFile.getPath(); final String codePath = pkg.applicationInfo.getCodePath(); final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting); - if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { + if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) { setBundledAppAbisAndRoots(pkg, pkgSetting); // If we haven't found any native libraries for the app, check if it has @@ -5728,7 +5723,6 @@ public class PackageManagerService extends IPackageManager.Stub { throw new PackageManagerException(INSTALL_FAILED_DEXOPT, "scanPackageLI"); } } - if (mFactoryTest && pkg.requestedPermissions.contains( android.Manifest.permission.FACTORY_TEST)) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST; @@ -5744,7 +5738,7 @@ public class PackageManagerService extends IPackageManager.Stub { for (int i=0; i<pkg.libraryNames.size(); i++) { String name = pkg.libraryNames.get(i); boolean allowed = false; - if (isUpdatedSystemApp(pkg)) { + if (pkg.isUpdatedSystemApp()) { // New library entries can only be added through the // system image. This is important to get rid of a lot // of nasty edge cases: for example if we allowed a non- @@ -6350,7 +6344,7 @@ public class PackageManagerService extends IPackageManager.Stub { final ApplicationInfo info = pkg.applicationInfo; final String codePath = pkg.codePath; final File codeFile = new File(codePath); - final boolean bundledApp = isSystemApp(info) && !isUpdatedSystemApp(info); + final boolean bundledApp = info.isSystemApp() && !info.isUpdatedSystemApp(); final boolean asecApp = info.isForwardLocked() || isExternal(info); info.nativeLibraryRootDir = null; @@ -7002,7 +6996,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (isSystemApp(pkg)) { // For updated system applications, a system permission // is granted only if it had been defined by the original application. - if (isUpdatedSystemApp(pkg)) { + if (pkg.isUpdatedSystemApp()) { final PackageSetting sysPs = mSettings .getDisabledSystemPkgLPr(pkg.packageName); final GrantedPermissions origGp = sysPs.sharedUser != null @@ -7090,7 +7084,7 @@ public class PackageManagerService extends IPackageManager.Stub { } public final void addActivity(PackageParser.Activity a, String type) { - final boolean systemApp = isSystemApp(a.info.applicationInfo); + final boolean systemApp = a.info.applicationInfo.isSystemApp(); mActivities.put(a.getComponentName(), a); if (DEBUG_SHOW_INFO) Log.v( @@ -7217,7 +7211,7 @@ public class PackageManagerService extends IPackageManager.Stub { } else { res.icon = info.icon; } - res.system = isSystemApp(res.activityInfo.applicationInfo); + res.system = res.activityInfo.applicationInfo.isSystemApp(); return res; } @@ -7433,7 +7427,7 @@ public class PackageManagerService extends IPackageManager.Stub { res.labelRes = info.labelRes; res.nonLocalizedLabel = info.nonLocalizedLabel; res.icon = info.icon; - res.system = isSystemApp(res.serviceInfo.applicationInfo); + res.system = res.serviceInfo.applicationInfo.isSystemApp(); return res; } @@ -7656,7 +7650,7 @@ public class PackageManagerService extends IPackageManager.Stub { res.labelRes = info.labelRes; res.nonLocalizedLabel = info.nonLocalizedLabel; res.icon = info.icon; - res.system = isSystemApp(res.providerInfo.applicationInfo); + res.system = res.providerInfo.applicationInfo.isSystemApp(); return res; } @@ -9386,9 +9380,10 @@ public class PackageManagerService extends IPackageManager.Stub { } if (codeFile.isDirectory()) { - FileUtils.deleteContents(codeFile); + mInstaller.rmPackageDir(codeFile.getAbsolutePath()); + } else { + codeFile.delete(); } - codeFile.delete(); if (resourceFile != null && !FileUtils.contains(codeFile, resourceFile)) { resourceFile.delete(); @@ -9830,22 +9825,6 @@ public class PackageManagerService extends IPackageManager.Stub { return result; } - // Utility method used to ignore ADD/REMOVE events - // by directory observer. - private static boolean ignoreCodePath(String fullPathStr) { - String apkName = deriveCodePathName(fullPathStr); - int idx = apkName.lastIndexOf(INSTALL_PACKAGE_SUFFIX); - if (idx != -1 && ((idx+1) < apkName.length())) { - // Make sure the package ends with a numeral - String version = apkName.substring(idx+1); - try { - Integer.parseInt(version); - return true; - } catch (NumberFormatException e) {} - } - return false; - } - // Utility method that returns the relative package path with respect // to the installation directory. Like say for /data/data/com.test-1.apk // string com.test-1 is returned. @@ -10454,17 +10433,27 @@ public class PackageManagerService extends IPackageManager.Stub { return; } + // Run dexopt before old package gets removed, to minimize time when app is not available + int result = mPackageDexOptimizer + .performDexOpt(pkg, null /* instruction sets */, true /* forceDex */, + false /* defer */, false /* inclDependencies */); + if (result == PackageDexOptimizer.DEX_OPT_FAILED) { + res.setError(INSTALL_FAILED_DEXOPT, "Dexopt failed for " + pkg.codePath); + return; + } + if (!args.doRename(res.returnCode, pkg, oldCodePath)) { res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename"); return; } + // Call with SCAN_NO_DEX, since dexopt has already been made if (replace) { - replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user, + replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING | SCAN_NO_DEX, args.user, installerPackageName, res); } else { - installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES, - args.user, installerPackageName, res); + installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES + | SCAN_NO_DEX, args.user, installerPackageName, res); } synchronized (mPackages) { final PackageSetting ps = mSettings.mPackages.get(pkgName); @@ -10502,10 +10491,6 @@ public class PackageManagerService extends IPackageManager.Stub { return (pkg.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; } - private static boolean isSystemApp(ApplicationInfo info) { - return (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; - } - private static boolean isSystemApp(PackageSetting ps) { return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0; } @@ -10514,14 +10499,6 @@ public class PackageManagerService extends IPackageManager.Stub { return (ps.pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; } - private static boolean isUpdatedSystemApp(PackageParser.Package pkg) { - return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; - } - - private static boolean isUpdatedSystemApp(ApplicationInfo info) { - return (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; - } - private int packageFlagsToInstallFlags(PackageSetting ps) { int installFlags = 0; if (isExternal(ps)) { 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..8998d39134bc 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -504,7 +504,12 @@ public class WindowManagerService extends IWindowManager.Stub int mLastDisplayFreezeDuration = 0; Object mLastFinishedFreezeSource = null; boolean mWaitingForConfig = false; - boolean mWindowsFreezingScreen = false; + + final static int WINDOWS_FREEZING_SCREENS_NONE = 0; + final static int WINDOWS_FREEZING_SCREENS_ACTIVE = 1; + final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2; + private int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE; + boolean mClientFreezingScreen = false; int mAppsFreezingScreen = 0; int mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -4626,19 +4631,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); @@ -4712,7 +4717,8 @@ public class WindowManagerService extends IWindowManager.Stub WindowState w = wtoken.allAppWindows.get(i); if (w.mAppFreezing) { w.mAppFreezing = false; - if (w.mHasSurface && !w.mOrientationChanging) { + if (w.mHasSurface && !w.mOrientationChanging + && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) { if (DEBUG_ORIENTATION) Slog.v(TAG, "set mOrientationChanging of " + w); w.mOrientationChanging = true; mInnerFields.mOrientationChangeComplete = false; @@ -4761,7 +4767,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mAppsFreezingScreen == 1) { startFreezingDisplayLocked(false, 0, 0); mH.removeMessages(H.APP_FREEZE_TIMEOUT); - mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 5000); + mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000); } } final int N = wtoken.allAppWindows.size(); @@ -6550,7 +6556,7 @@ public class WindowManagerService extends IWindowManager.Stub mAltOrientation = altOrientation; mPolicy.setRotationLw(mRotation); - mWindowsFreezingScreen = true; + mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE; mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION); mWaitingForConfig = true; @@ -7920,6 +7926,7 @@ public class WindowManagerService extends IWindowManager.Stub // TODO(multidisplay): Can non-default displays rotate? synchronized (mWindowMap) { Slog.w(TAG, "Window freeze timeout expired."); + mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT; final WindowList windows = getDefaultWindowListLocked(); int i = windows.size(); while (i > 0) { @@ -7991,6 +7998,7 @@ public class WindowManagerService extends IWindowManager.Stub case APP_FREEZE_TIMEOUT: { synchronized (mWindowMap) { Slog.w(TAG, "App freeze timeout expired."); + mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT; final int numStacks = mStackIdToStack.size(); for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { final TaskStack stack = mStackIdToStack.valueAt(stackNdx); @@ -9076,13 +9084,13 @@ public class WindowManagerService extends IWindowManager.Stub // If the screen is currently frozen or off, then keep // it frozen/off until this window draws at its new // orientation. - if (!okToDisplay()) { + if (!okToDisplay() && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) { if (DEBUG_ORIENTATION) Slog.v(TAG, "Changing surface while display frozen: " + w); w.mOrientationChanging = true; w.mLastFreezeDuration = 0; mInnerFields.mOrientationChangeComplete = false; - if (!mWindowsFreezingScreen) { - mWindowsFreezingScreen = true; + if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) { + mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE; // XXX should probably keep timeout from // when we first froze the display. mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); @@ -10107,8 +10115,8 @@ public class WindowManagerService extends IWindowManager.Stub "With display frozen, orientationChangeComplete=" + mInnerFields.mOrientationChangeComplete); if (mInnerFields.mOrientationChangeComplete) { - if (mWindowsFreezingScreen) { - mWindowsFreezingScreen = false; + if (mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) { + mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE; mLastFinishedFreezeSource = mInnerFields.mLastWindowFreezeSource; mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); } @@ -10394,7 +10402,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { mInnerFields.mOrientationChangeComplete = true; mInnerFields.mLastWindowFreezeSource = mAnimator.mLastWindowFreezeSource; - if (mWindowsFreezingScreen) { + if (mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) { doRequest = true; } } @@ -10739,7 +10747,8 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (mWaitingForConfig || mAppsFreezingScreen > 0 || mWindowsFreezingScreen + if (mWaitingForConfig || mAppsFreezingScreen > 0 + || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE || mClientFreezingScreen || !mOpeningApps.isEmpty()) { if (DEBUG_ORIENTATION) Slog.d(TAG, "stopFreezingDisplayLocked: Returning mWaitingForConfig=" + mWaitingForConfig diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 978f5c30cc24..938c1ce46213 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1434,6 +1434,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { mOrientationChanging = false; mLastFreezeDuration = (int)(SystemClock.elapsedRealtime() - mService.mDisplayFreezeTime); + // We are assuming the hosting process is dead or in a zombie state. + Slog.w(TAG, "Failed to report 'resized' to the client of " + this + + ", removing this window."); + mService.mPendingRemove.add(this); + mService.requestTraversalLocked(); } } diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index a6b862eaa3a0..beb94fdbcfc5 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -22,7 +22,7 @@ // STATUST: mingw does seem to redefine UNKNOWN_ERROR from our enum value, so a cast is necessary. -#if HAVE_PRINTF_ZD +#if !defined(_WIN32) # define ZD "%zd" # define ZD_TYPE ssize_t # define STATUST(x) x diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 585a6ded591f..c5fccbf02914 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -20,7 +20,7 @@ // SSIZE: mingw does not have signed size_t == ssize_t. // STATUST: mingw does seem to redefine UNKNOWN_ERROR from our enum value, so a cast is necessary. -#if HAVE_PRINTF_ZD +#if !defined(_WIN32) # define SSIZE(x) x # define STATUST(x) x #else diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp index a18e9f191941..9908c44c6d1f 100644 --- a/tools/aapt/StringPool.cpp +++ b/tools/aapt/StringPool.cpp @@ -13,7 +13,7 @@ #include "ResourceTable.h" // SSIZE: mingw does not have signed size_t == ssize_t. -#if HAVE_PRINTF_ZD +#if !defined(_WIN32) # define ZD "%zd" # define ZD_TYPE ssize_t # define SSIZE(x) x diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp index b38b2eda4f73..9033cf72fb57 100644 --- a/tools/aapt/XMLNode.cpp +++ b/tools/aapt/XMLNode.cpp @@ -18,7 +18,7 @@ // SSIZE: mingw does not have signed size_t == ssize_t. // STATUST: mingw does seem to redefine UNKNOWN_ERROR from our enum value, so a cast is necessary. -#if HAVE_PRINTF_ZD +#if !defined(_WIN32) # define SSIZE(x) x # define STATUST(x) x #else |