diff options
22 files changed, 1303 insertions, 46 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/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/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java b/core/java/android/os/IProcessInfoService.aidl index 2fe68f8dfd58..c98daa282ec4 100644 --- a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java +++ b/core/java/android/os/IProcessInfoService.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * 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. @@ -14,20 +14,16 @@ * limitations under the License. */ -package com.android.server.updates; +package android.os; -import android.util.Base64; - -import java.io.IOException; - -public class TZInfoInstallReceiver extends ConfigUpdateInstallReceiver { - - public TZInfoInstallReceiver() { - super("/data/misc/zoneinfo/", "tzdata", "metadata/", "version"); - } - - @Override - protected void install(byte[] encodedContent, int version) throws IOException { - super.install(Base64.decode(encodedContent, Base64.DEFAULT), version); - } +/** {@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/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/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/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index ce50d965118f..05825134880b 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -558,6 +558,8 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) char dex2oatXmxFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX]; char dex2oatCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX]; char dex2oatImageCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX]; + char dex2oatThreadsBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX]; + char dex2oatThreadsImageBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX]; char dex2oatFlagsBuf[PROPERTY_VALUE_MAX]; char dex2oatImageFlagsBuf[PROPERTY_VALUE_MAX]; char extraOptsBuf[PROPERTY_VALUE_MAX]; @@ -732,6 +734,9 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) parseCompilerOption("dalvik.vm.dex2oat-filter", dex2oatCompilerFilterBuf, "--compiler-filter=", "-Xcompiler-option"); } + parseCompilerOption("dalvik.vm.dex2oat-threads", dex2oatThreadsBuf, "-j", "-Xcompiler-option"); + parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j", + "-Ximage-compiler-option"); property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, ""); parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option"); 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/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java index f3eb317eb0fd..846d1f1fbe9f 100644 --- a/keystore/java/android/security/AndroidKeyStore.java +++ b/keystore/java/android/security/AndroidKeyStore.java @@ -457,7 +457,7 @@ public class AndroidKeyStore extends KeyStoreSpi { String keyAlgorithmString = key.getAlgorithm(); @KeyStoreKeyConstraints.AlgorithmEnum int keyAlgorithm; - @KeyStoreKeyConstraints.AlgorithmEnum Integer digest; + @KeyStoreKeyConstraints.DigestEnum Integer digest; try { keyAlgorithm = KeyStoreKeyConstraints.Algorithm.fromJCASecretKeyAlgorithm(keyAlgorithmString); @@ -493,6 +493,19 @@ public class AndroidKeyStore extends KeyStoreSpi { if (digest != null) { args.addInt(KeymasterDefs.KM_TAG_DIGEST, KeyStoreKeyConstraints.Digest.toKeymaster(digest)); + Integer digestOutputSizeBytes = + KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest); + if (digestOutputSizeBytes != null) { + // TODO: Remove MAC length constraint once Keymaster API no longer requires it. + // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster + args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes); + } + } + if (keyAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) { + if (digest == null) { + throw new IllegalStateException("Digest algorithm must be specified for key" + + " algorithm " + keyAlgorithmString); + } } @KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null) @@ -547,6 +560,12 @@ public class AndroidKeyStore extends KeyStoreSpi { // 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 598bcd8dcd3d..6cf9b7a71be6 100644 --- a/keystore/java/android/security/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/AndroidKeyStoreProvider.java @@ -39,5 +39,32 @@ public class AndroidKeyStoreProvider extends Provider { // javax.crypto.KeyGenerator put("KeyGenerator.AES", KeyStoreKeyGeneratorSpi.AES.class.getName()); put("KeyGenerator.HmacSHA256", KeyStoreKeyGeneratorSpi.HmacSHA256.class.getName()); + + // javax.crypto.Mac + putMacImpl("HmacSHA256", KeyStoreHmacSpi.HmacSHA256.class.getName()); + + // javax.crypto.Cipher + putSymmetricCipherImpl("AES/ECB/NoPadding", + KeyStoreCipherSpi.AES.ECB.NoPadding.class.getName()); + putSymmetricCipherImpl("AES/ECB/PKCS7Padding", + KeyStoreCipherSpi.AES.ECB.PKCS7Padding.class.getName()); + + putSymmetricCipherImpl("AES/CBC/NoPadding", + KeyStoreCipherSpi.AES.CBC.NoPadding.class.getName()); + putSymmetricCipherImpl("AES/CBC/PKCS7Padding", + KeyStoreCipherSpi.AES.CBC.PKCS7Padding.class.getName()); + + putSymmetricCipherImpl("AES/CTR/NoPadding", + KeyStoreCipherSpi.AES.CTR.NoPadding.class.getName()); + } + + private void putMacImpl(String algorithm, String implClass) { + put("Mac." + algorithm, implClass); + put("Mac." + algorithm + " SupportedKeyClasses", KeyStoreSecretKey.class.getName()); + } + + private void putSymmetricCipherImpl(String transformation, String implClass) { + put("Cipher." + transformation, implClass); + put("Cipher." + transformation + " SupportedKeyClasses", KeyStoreSecretKey.class.getName()); } } diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index f68b3f6baace..94a479b4c1b4 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -476,4 +476,34 @@ public class KeyStore { return SYSTEM_ERROR; } } + + /** + * Check if the operation referenced by {@code token} is currently authorized. + * + * @param token An operation token returned by a call to {@link KeyStore.begin}. + */ + public boolean isOperationAuthorized(IBinder token) { + try { + return mBinder.isOperationAuthorized(token); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return false; + } + } + + /** + * Add an authentication record to the keystore authorization table. + * + * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster. + * @return {@code KeyStore.NO_ERROR} on success, otherwise an error value corresponding to + * a {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode. + */ + public int addAuthToken(byte[] authToken) { + try { + return mBinder.addAuthToken(authToken); + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + return SYSTEM_ERROR; + } + } } diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java new file mode 100644 index 000000000000..6863236f00bd --- /dev/null +++ b/keystore/java/android/security/KeyStoreCipherSpi.java @@ -0,0 +1,540 @@ +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 { + + public abstract static class AES extends KeyStoreCipherSpi { + protected AES(@KeyStoreKeyConstraints.BlockModeEnum int blockMode, + @KeyStoreKeyConstraints.PaddingEnum int padding, boolean ivUsed) { + super(KeyStoreKeyConstraints.Algorithm.AES, + blockMode, + padding, + 16, + ivUsed); + } + + public abstract static class ECB extends AES { + protected ECB(@KeyStoreKeyConstraints.PaddingEnum int padding) { + super(KeyStoreKeyConstraints.BlockMode.ECB, padding, false); + } + + public static class NoPadding extends ECB { + public NoPadding() { + super(KeyStoreKeyConstraints.Padding.NONE); + } + } + + public static class PKCS7Padding extends ECB { + public PKCS7Padding() { + super(KeyStoreKeyConstraints.Padding.PKCS7); + } + } + } + + public abstract static class CBC extends AES { + protected CBC(@KeyStoreKeyConstraints.BlockModeEnum int padding) { + super(KeyStoreKeyConstraints.BlockMode.CBC, padding, true); + } + + public static class NoPadding extends CBC { + public NoPadding() { + super(KeyStoreKeyConstraints.Padding.NONE); + } + } + + public static class PKCS7Padding extends CBC { + public PKCS7Padding() { + super(KeyStoreKeyConstraints.Padding.PKCS7); + } + } + } + + public abstract static class CTR extends AES { + protected CTR(@KeyStoreKeyConstraints.BlockModeEnum int padding) { + super(KeyStoreKeyConstraints.BlockMode.CTR, padding, true); + } + + public static class NoPadding extends CTR { + public NoPadding() { + super(KeyStoreKeyConstraints.Padding.NONE); + } + } + } + } + + private final KeyStore mKeyStore; + private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm; + private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockMode; + private final @KeyStoreKeyConstraints.PaddingEnum int mPadding; + private final int mBlockSizeBytes; + private final boolean mIvUsed; + + // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after + // doFinal finishes. + protected boolean mEncrypting; + private KeyStoreSecretKey mKey; + private SecureRandom mRng; + private boolean mFirstOperationInitiated; + byte[] mIv; + + // Fields below must be reset + private byte[] mAdditionalEntropyForBegin; + /** + * Token referencing this operation inside keystore service. It is initialized by + * {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and one some + * error conditions in between. + */ + private IBinder mOperationToken; + private KeyStoreCryptoOperationChunkedStreamer mMainDataStreamer; + + protected KeyStoreCipherSpi( + @KeyStoreKeyConstraints.AlgorithmEnum int algorithm, + @KeyStoreKeyConstraints.BlockModeEnum int blockMode, + @KeyStoreKeyConstraints.PaddingEnum int padding, + int blockSizeBytes, + boolean ivUsed) { + mKeyStore = KeyStore.getInstance(); + mAlgorithm = algorithm; + mBlockMode = blockMode; + mPadding = padding; + mBlockSizeBytes = blockSizeBytes; + mIvUsed = ivUsed; + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + init(opmode, key, random); + initAlgorithmSpecificParameters(); + ensureKeystoreOperationInitialized(); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + } + + private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + reset(); + if (!(key instanceof KeyStoreSecretKey)) { + throw new InvalidKeyException( + "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); + } + mKey = (KeyStoreSecretKey) key; + mRng = random; + mIv = null; + mFirstOperationInitiated = false; + + if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) { + throw new UnsupportedOperationException( + "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode); + } + mEncrypting = opmode == Cipher.ENCRYPT_MODE; + } + + private void reset() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mMainDataStreamer = null; + mAdditionalEntropyForBegin = null; + } + + private void ensureKeystoreOperationInitialized() { + if (mMainDataStreamer != null) { + return; + } + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + + KeymasterArguments keymasterInputArgs = new KeymasterArguments(); + keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mAlgorithm); + keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, mBlockMode); + keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_PADDING, mPadding); + addAlgorithmSpecificParametersToBegin(keymasterInputArgs); + + KeymasterArguments keymasterOutputArgs = new KeymasterArguments(); + OperationResult opResult = mKeyStore.begin( + mKey.getAlias(), + mEncrypting ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT, + true, // permit aborting this operation if keystore runs out of resources + keymasterInputArgs, + mAdditionalEntropyForBegin, + keymasterOutputArgs); + mAdditionalEntropyForBegin = null; + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw new CryptoOperationException("Failed to start keystore operation", + KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode)); + } + + if (opResult.token == null) { + throw new CryptoOperationException("Keystore returned null operation token"); + } + mOperationToken = opResult.token; + loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs); + mFirstOperationInitiated = true; + mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + mKeyStore, opResult.token)); + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + ensureKeystoreOperationInitialized(); + + if (inputLen == 0) { + return null; + } + + byte[] output; + try { + output = mMainDataStreamer.update(input, inputOffset, inputLen); + } catch (KeymasterException e) { + throw new CryptoOperationException("Keystore operation failed", 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 { + ensureKeystoreOperationInitialized(); + + byte[] outputCopy = engineUpdate(input, inputOffset, inputLen); + if (outputCopy == null) { + return 0; + } + int outputAvailable = output.length - outputOffset; + if (outputCopy.length > outputAvailable) { + throw new ShortBufferException("Output buffer too short. Produced: " + + outputCopy.length + ", available: " + outputAvailable); + } + System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); + return outputCopy.length; + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + ensureKeystoreOperationInitialized(); + + byte[] output; + try { + output = mMainDataStreamer.doFinal(input, inputOffset, inputLen); + } catch (KeymasterException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH: + throw new IllegalBlockSizeException(); + case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: + throw new BadPaddingException(); + case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: + throw new AEADBadTagException(); + default: + throw new CryptoOperationException("Keystore operation failed", e); + } + } + + reset(); + return output; + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, + int outputOffset) throws ShortBufferException, IllegalBlockSizeException, + BadPaddingException { + byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen); + if (outputCopy == null) { + return 0; + } + int outputAvailable = output.length - outputOffset; + if (outputCopy.length > outputAvailable) { + throw new ShortBufferException("Output buffer too short. Produced: " + + outputCopy.length + ", available: " + outputAvailable); + } + System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); + return outputCopy.length; + } + + @Override + protected int engineGetBlockSize() { + return mBlockSizeBytes; + } + + @Override + protected byte[] engineGetIV() { + return (mIv != null) ? mIv.clone() : null; + } + + @Override + protected int engineGetOutputSize(int inputLen) { + return inputLen + 3 * engineGetBlockSize(); + } + + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + // This should never be invoked because all algorithms registered with the AndroidKeyStore + // provide explicitly specify block mode. + throw new UnsupportedOperationException(); + } + + @Override + protected void engineSetPadding(String arg0) throws NoSuchPaddingException { + // This should never be invoked because all algorithms registered with the AndroidKeyStore + // provide explicitly specify padding mode. + throw new UnsupportedOperationException(); + } + + // The methods below may need to be overridden by subclasses that use algorithm-specific + // parameters. + + /** + * Returns algorithm-specific parameters used by this {@code CipherSpi} instance or {@code null} + * if no algorithm-specific parameters are used. + * + * <p>This implementation only handles the IV parameter. + */ + @Override + protected AlgorithmParameters engineGetParameters() { + if (!mIvUsed) { + return null; + } + if ((mIv != null) && (mIv.length > 0)) { + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); + params.init(new IvParameterSpec(mIv)); + return params; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Failed to obtain AES AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new RuntimeException( + "Failed to initialize AES AlgorithmParameters with an IV", e); + } + } + return null; + } + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters + * may need to be stored to be reused after {@code doFinal}. + * + * <p>The default implementation only handles the IV parameters. + * + * @param params algorithm parameters. + * + * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be + * automatically configured and thus {@code Cipher.init} needs to be invoked with + * explicitly provided parameters. + */ + protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (!mIvUsed) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!mEncrypting) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException( + "IvParameterSpec must be provided when decrypting"); + } + return; + } + if (!(params instanceof IvParameterSpec)) { + throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported"); + } + mIv = ((IvParameterSpec) params).getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec"); + } + } + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters + * may need to be stored to be reused after {@code doFinal}. + * + * <p>The default implementation only handles the IV parameters. + * + * @param params algorithm parameters. + * + * @throws InvalidAlgorithmParameterException if some/all of the parameters cannot be + * automatically configured and thus {@code Cipher.init} needs to be invoked with + * explicitly provided parameters. + */ + protected void initAlgorithmSpecificParameters(AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (!mIvUsed) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!mEncrypting) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + return; + } + + IvParameterSpec ivSpec; + try { + ivSpec = params.getParameterSpec(IvParameterSpec.class); + } catch (InvalidParameterSpecException e) { + if (!mEncrypting) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ", but not found in parameters: " + params, e); + } + mIv = null; + return; + } + mIv = ivSpec.getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters"); + } + } + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters. These parameters + * may need to be stored to be reused after {@code doFinal}. + * + * <p>The default implementation only handles the IV parameter. + * + * @throws InvalidKeyException if some/all of the parameters cannot be automatically configured + * and thus {@code Cipher.init} needs to be invoked with explicitly provided parameters. + */ + protected void initAlgorithmSpecificParameters() throws InvalidKeyException { + if (!mIvUsed) { + return; + } + + // IV is used + if (!mEncrypting) { + throw new InvalidKeyException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + } + + /** + * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. + * + * <p>The default implementation takes care of the IV. + * + * @param keymasterArgs keystore/keymaster arguments to be populated with algorithm-specific + * parameters. + */ + protected void addAlgorithmSpecificParametersToBegin(KeymasterArguments keymasterArgs) { + if (!mFirstOperationInitiated) { + // First begin operation -- see if we need to provide additional entropy for IV + // generation. + if (mIvUsed) { + // IV is needed + if ((mIv == null) && (mEncrypting)) { + // TODO: Switch to keymaster-generated IV code below once keymaster supports + // that. + // IV is needed but was not provided by the caller -- generate an IV. + mIv = new byte[mBlockSizeBytes]; + SecureRandom rng = (mRng != null) ? mRng : new SecureRandom(); + rng.nextBytes(mIv); +// // IV was not provided by the caller and thus will be generated by keymaster. +// // Mix in some additional entropy from the provided SecureRandom. +// if (mRng != null) { +// mAdditionalEntropyForBegin = new byte[mBlockSizeBytes]; +// mRng.nextBytes(mAdditionalEntropyForBegin); +// } + } + } + } + + if ((mIvUsed) && (mIv != null)) { + keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv); + } + } + + /** + * Invoked by {@code engineInit} to obtain algorithm-specific parameters from the result of the + * Keymaster's {@code begin} operation. Some of these parameters may need to be reused after + * {@code doFinal} by {@link #addAlgorithmSpecificParametersToBegin(KeymasterArguments)}. + * + * <p>The default implementation only takes care of the IV. + * + * @param keymasterArgs keystore/keymaster arguments returned by KeyStore {@code begin} + * operation. + */ + protected void loadAlgorithmSpecificParametersFromBeginResult( + KeymasterArguments keymasterArgs) { + // NOTE: Keymaster doesn't always return an IV, even if it's used. + byte[] returnedIv = keymasterArgs.getBlob(KeymasterDefs.KM_TAG_NONCE, null); + if ((returnedIv != null) && (returnedIv.length == 0)) { + returnedIv = null; + } + + if (mIvUsed) { + if (mIv == null) { + mIv = returnedIv; + } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { + throw new CryptoOperationException("IV in use differs from provided IV"); + } + } else { + if (returnedIv != null) { + throw new CryptoOperationException( + "IV in use despite IV not being used by this transformation"); + } + } + } +} diff --git a/keystore/java/android/security/KeyStoreConnectException.java b/keystore/java/android/security/KeyStoreConnectException.java new file mode 100644 index 000000000000..4c465a4fff7a --- /dev/null +++ b/keystore/java/android/security/KeyStoreConnectException.java @@ -0,0 +1,12 @@ +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/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java new file mode 100644 index 000000000000..6385947356d9 --- /dev/null +++ b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java @@ -0,0 +1,293 @@ +package android.security; + +import android.os.IBinder; +import android.security.keymaster.OperationResult; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's + * {@code update} and {@code finish} operations. + * + * <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's + * update and finish operations. Firstly, KeyStore's update operation can consume only a limited + * amount of data in one go because the operations are marshalled via Binder. Secondly, the update + * operation may consume less data than provided, in which case the caller has to buffer the + * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and + * {@link #doFinal(byte[], int, int) doFinal} operations which can be used to conveniently implement + * various JCA crypto primitives. + * + * <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as + * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional + * parameters to {@code update} and {@code final} operations. + * + * @hide + */ +public class KeyStoreCryptoOperationChunkedStreamer { + + /** + * Bidirectional chunked data stream over a KeyStore crypto operation. + */ + public interface Stream { + /** + * Returns the result of the KeyStore {@code update} operation or null if keystore couldn't + * be reached. + */ + OperationResult update(byte[] input); + + /** + * Returns the result of the KeyStore {@code finish} operation or null if keystore couldn't + * be reached. + */ + OperationResult finish(); + } + + // Binder buffer is about 1MB, but it's shared between all active transactions of the process. + // Thus, it's safer to use a much smaller upper bound. + private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024; + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + private final Stream mKeyStoreStream; + private final int mMaxChunkSize; + + private byte[] mBuffered = EMPTY_BYTE_ARRAY; + private int mBufferedOffset; + private int mBufferedLength; + + public KeyStoreCryptoOperationChunkedStreamer(Stream operation) { + this(operation, DEFAULT_MAX_CHUNK_SIZE); + } + + public KeyStoreCryptoOperationChunkedStreamer(Stream operation, int maxChunkSize) { + mKeyStoreStream = operation; + mMaxChunkSize = maxChunkSize; + } + + public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeymasterException { + if (inputLength == 0) { + // No input provided + return EMPTY_BYTE_ARRAY; + } + + ByteArrayOutputStream bufferedOutput = null; + + while (inputLength > 0) { + byte[] chunk; + int inputBytesInChunk; + if ((mBufferedLength + inputLength) > mMaxChunkSize) { + // Too much input for one chunk -- extract one max-sized chunk and feed it into the + // update operation. + inputBytesInChunk = mMaxChunkSize - mBufferedLength; + chunk = concat(mBuffered, mBufferedOffset, mBufferedLength, + input, inputOffset, inputBytesInChunk); + } else { + // All of available input fits into one chunk. + if ((mBufferedLength == 0) && (inputOffset == 0) + && (inputLength == input.length)) { + // Nothing buffered and all of input array needs to be fed into the update + // operation. + chunk = input; + inputBytesInChunk = input.length; + } else { + // Need to combine buffered data with input data into one array. + inputBytesInChunk = inputLength; + chunk = concat(mBuffered, mBufferedOffset, mBufferedLength, + input, inputOffset, inputBytesInChunk); + } + } + // Update input array references to reflect that some of its bytes are now in mBuffered. + inputOffset += inputBytesInChunk; + inputLength -= inputBytesInChunk; + + OperationResult opResult = mKeyStoreStream.update(chunk); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode); + } + + if (opResult.inputConsumed == chunk.length) { + // The whole chunk was consumed + mBuffered = EMPTY_BYTE_ARRAY; + mBufferedOffset = 0; + mBufferedLength = 0; + } else if (opResult.inputConsumed == 0) { + // Nothing was consumed. More input needed. + if (inputLength > 0) { + // More input is available, but it wasn't included into the previous chunk + // because the chunk reached its maximum permitted size. + // Shouldn't have happened. + throw new CryptoOperationException("Nothing consumed from max-sized chunk: " + + chunk.length + " bytes"); + } + mBuffered = chunk; + mBufferedOffset = 0; + mBufferedLength = chunk.length; + } else if (opResult.inputConsumed < chunk.length) { + // The chunk was consumed only partially -- buffer the rest of the chunk + mBuffered = chunk; + mBufferedOffset = opResult.inputConsumed; + mBufferedLength = chunk.length - opResult.inputConsumed; + } else { + throw new CryptoOperationException("Consumed more than provided: " + + opResult.inputConsumed + ", provided: " + chunk.length); + } + + if ((opResult.output != null) && (opResult.output.length > 0)) { + if (inputLength > 0) { + // More output might be produced in this loop -- buffer the current output + if (bufferedOutput == null) { + bufferedOutput = new ByteArrayOutputStream(); + try { + bufferedOutput.write(opResult.output); + } catch (IOException e) { + throw new CryptoOperationException("Failed to buffer output", e); + } + } + } else { + // No more output will be produced in this loop + if (bufferedOutput == null) { + // No previously buffered output + return opResult.output; + } else { + // There was some previously buffered output + try { + bufferedOutput.write(opResult.output); + } catch (IOException e) { + throw new CryptoOperationException("Failed to buffer output", e); + } + return bufferedOutput.toByteArray(); + } + } + } + } + + if (bufferedOutput == null) { + // No output produced + return EMPTY_BYTE_ARRAY; + } else { + return bufferedOutput.toByteArray(); + } + } + + public byte[] doFinal(byte[] input, int inputOffset, int inputLength) + throws KeymasterException { + if (inputLength == 0) { + // No input provided -- simplify the rest of the code + input = EMPTY_BYTE_ARRAY; + inputOffset = 0; + } + + // Flush all buffered input and provided input into keystore/keymaster. + byte[] output = update(input, inputOffset, inputLength); + output = concat(output, flush()); + + OperationResult opResult = mKeyStoreStream.finish(); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode); + } + + return concat(output, opResult.output); + } + + /** + * Passes all of buffered input into the the KeyStore operation (via the {@code update} + * operation) and returns output. + */ + public byte[] flush() throws KeymasterException { + if (mBufferedLength <= 0) { + return EMPTY_BYTE_ARRAY; + } + + byte[] chunk = subarray(mBuffered, mBufferedOffset, mBufferedLength); + mBuffered = EMPTY_BYTE_ARRAY; + mBufferedLength = 0; + mBufferedOffset = 0; + + OperationResult opResult = mKeyStoreStream.update(chunk); + if (opResult == null) { + throw new KeyStoreConnectException(); + } else if (opResult.resultCode != KeyStore.NO_ERROR) { + throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode); + } + + if (opResult.inputConsumed < chunk.length) { + throw new CryptoOperationException("Keystore failed to consume all input. Provided: " + + chunk.length + ", consumed: " + opResult.inputConsumed); + } else if (opResult.inputConsumed > chunk.length) { + throw new CryptoOperationException("Keystore consumed more input than provided" + + " . Provided: " + chunk.length + ", consumed: " + opResult.inputConsumed); + } + + return (opResult.output != null) ? opResult.output : EMPTY_BYTE_ARRAY; + } + + private static byte[] concat(byte[] arr1, byte[] arr2) { + if ((arr1 == null) || (arr1.length == 0)) { + return arr2; + } else if ((arr2 == null) || (arr2.length == 0)) { + return arr1; + } else { + byte[] result = new byte[arr1.length + arr2.length]; + System.arraycopy(arr1, 0, result, 0, arr1.length); + System.arraycopy(arr2, 0, result, arr1.length, arr2.length); + return result; + } + } + + private static byte[] concat(byte[] arr1, int offset1, int len1, byte[] arr2, int offset2, + int len2) { + if (len1 == 0) { + return subarray(arr2, offset2, len2); + } else if (len2 == 0) { + return subarray(arr1, offset1, len1); + } else { + byte[] result = new byte[len1 + len2]; + System.arraycopy(arr1, offset1, result, 0, len1); + System.arraycopy(arr2, offset2, result, len1, len2); + return result; + } + } + + private static byte[] subarray(byte[] arr, int offset, int len) { + if (len == 0) { + return EMPTY_BYTE_ARRAY; + } + if ((offset == 0) && (len == arr.length)) { + return arr; + } + byte[] result = new byte[len]; + System.arraycopy(arr, offset, result, 0, len); + return result; + } + + /** + * Main data stream via a KeyStore streaming operation. + * + * <p>For example, for an encryption operation, this is the stream through which plaintext is + * provided and ciphertext is obtained. + */ + public static class MainDataStream implements Stream { + + private final KeyStore mKeyStore; + private final IBinder mOperationToken; + + public MainDataStream(KeyStore keyStore, IBinder operationToken) { + mKeyStore = keyStore; + mOperationToken = operationToken; + } + + @Override + public OperationResult update(byte[] input) { + return mKeyStore.update(mOperationToken, null, input); + } + + @Override + public OperationResult finish() { + return mKeyStore.finish(mOperationToken, null, null); + } + } +} diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java new file mode 100644 index 000000000000..e3c98b8f587b --- /dev/null +++ b/keystore/java/android/security/KeyStoreHmacSpi.java @@ -0,0 +1,151 @@ +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 { + + public static class HmacSHA256 extends KeyStoreHmacSpi { + public HmacSHA256() { + super(KeyStoreKeyConstraints.Digest.SHA256, 256 / 8); + } + } + + private final KeyStore mKeyStore = KeyStore.getInstance(); + private final @KeyStoreKeyConstraints.DigestEnum int mDigest; + private final int mMacSizeBytes; + + private String mKeyAliasInKeyStore; + + // The fields below are reset by the engineReset operation. + private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer; + private IBinder mOperationToken; + + protected KeyStoreHmacSpi(@KeyStoreKeyConstraints.DigestEnum int digest, int macSizeBytes) { + mDigest = digest; + mMacSizeBytes = macSizeBytes; + } + + @Override + protected int engineGetMacLength() { + return mMacSizeBytes; + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, + InvalidAlgorithmParameterException { + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if (!(key instanceof KeyStoreSecretKey)) { + throw new InvalidKeyException( + "Only Android KeyStore secret keys supported. Key: " + key); + } + + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unsupported algorithm parameters: " + params); + } + + mKeyAliasInKeyStore = ((KeyStoreSecretKey) key).getAlias(); + engineReset(); + } + + @Override + protected void engineReset() { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + mChunkedStreamer = null; + + KeymasterArguments keymasterArgs = new KeymasterArguments(); + 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 new CryptoOperationException("Failed to start keystore operation", + KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode)); + } + mOperationToken = opResult.token; + if (mOperationToken == null) { + throw new CryptoOperationException("Keystore returned null operation token"); + } + 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) { + if (mChunkedStreamer == null) { + throw new IllegalStateException("Not initialized"); + } + + byte[] output; + try { + output = mChunkedStreamer.update(input, offset, len); + } catch (KeymasterException e) { + throw new CryptoOperationException("Keystore operation failed", e); + } + if ((output != null) && (output.length != 0)) { + throw new CryptoOperationException("Update operation unexpectedly produced output"); + } + } + + @Override + protected byte[] engineDoFinal() { + if (mChunkedStreamer == null) { + throw new IllegalStateException("Not initialized"); + } + + byte[] result; + try { + result = mChunkedStreamer.doFinal(null, 0, 0); + } catch (KeymasterException e) { + throw new CryptoOperationException("Keystore operation failed", e); + } + + engineReset(); + return result; + } + + @Override + public void finalize() throws Throwable { + try { + IBinder operationToken = mOperationToken; + if (operationToken != null) { + mOperationToken = null; + mKeyStore.abort(operationToken); + } + } finally { + super.finalize(); + } + } +} diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java index 47bb1ccbbeb7..58ea388337bc 100644 --- a/keystore/java/android/security/KeyStoreKeyConstraints.java +++ b/keystore/java/android/security/KeyStoreKeyConstraints.java @@ -222,16 +222,6 @@ public abstract class KeyStoreKeyConstraints { throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm); } } - - /** - * @hide - */ - public static String toJCAKeyPairAlgorithm(@AlgorithmEnum int algorithm) { - switch (algorithm) { - default: - throw new IllegalArgumentException("Unsupported key alorithm: " + algorithm); - } - } } @Retention(RetentionPolicy.SOURCE) @@ -306,6 +296,20 @@ public abstract class KeyStoreKeyConstraints { throw new IllegalArgumentException("Unknown padding: " + padding); } } + + /** + * @hide + */ + public static @PaddingEnum int fromJCAPadding(String padding) { + String paddingLower = padding.toLowerCase(Locale.US); + if ("nopadding".equals(paddingLower)) { + return NONE; + } else if ("pkcs7padding".equals(paddingLower)) { + return PKCS7; + } else { + throw new IllegalArgumentException("Unknown padding: " + padding); + } + } } @Retention(RetentionPolicy.SOURCE) @@ -401,10 +405,24 @@ public abstract class KeyStoreKeyConstraints { throw new IllegalArgumentException("Unknown digest: " + digest); } } + + /** + * @hide + */ + public static Integer getOutputSizeBytes(@DigestEnum int digest) { + switch (digest) { + case NONE: + return null; + case SHA256: + return 256 / 8; + default: + throw new IllegalArgumentException("Unknown digest: " + digest); + } + } } @Retention(RetentionPolicy.SOURCE) - @IntDef({BlockMode.ECB}) + @IntDef({BlockMode.ECB, BlockMode.CBC, BlockMode.CTR}) public @interface BlockModeEnum {} /** @@ -413,11 +431,15 @@ public abstract class KeyStoreKeyConstraints { public static abstract class BlockMode { private BlockMode() {} - /** - * Electronic Codebook (ECB) block mode. - */ + /** Electronic Codebook (ECB) block mode. */ public static final int ECB = 0; + /** Cipher Block Chaining (CBC) block mode. */ + public static final int CBC = 1; + + /** Counter (CTR) block mode. */ + public static final int CTR = 2; + /** * @hide */ @@ -425,6 +447,10 @@ public abstract class KeyStoreKeyConstraints { switch (mode) { case ECB: return KeymasterDefs.KM_MODE_ECB; + case CBC: + return KeymasterDefs.KM_MODE_CBC; + case CTR: + return KeymasterDefs.KM_MODE_CTR; default: throw new IllegalArgumentException("Unknown block mode: " + mode); } @@ -437,6 +463,10 @@ public abstract class KeyStoreKeyConstraints { switch (mode) { case KeymasterDefs.KM_MODE_ECB: return ECB; + case KeymasterDefs.KM_MODE_CBC: + return CBC; + case KeymasterDefs.KM_MODE_CTR: + return CTR; default: throw new IllegalArgumentException("Unknown block mode: " + mode); } @@ -449,9 +479,29 @@ public abstract class KeyStoreKeyConstraints { switch (mode) { case ECB: return "ECB"; + case CBC: + return "CBC"; + case CTR: + return "CTR"; default: throw new IllegalArgumentException("Unknown block mode: " + mode); } } + + /** + * @hide + */ + public static @BlockModeEnum int fromJCAMode(String mode) { + String modeLower = mode.toLowerCase(Locale.US); + if ("ecb".equals(modeLower)) { + return ECB; + } else if ("cbc".equals(modeLower)) { + return CBC; + } else if ("ctr".equals(modeLower)) { + return CTR; + } else { + throw new IllegalArgumentException("Unknown block mode: " + mode); + } + } } } diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java index 86950dd0d117..3e5b5c002857 100644 --- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java @@ -28,13 +28,14 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { public HmacSHA256() { super(KeyStoreKeyConstraints.Algorithm.HMAC, KeyStoreKeyConstraints.Digest.SHA256, - 256); + KeyStoreKeyConstraints.Digest.getOutputSizeBytes( + KeyStoreKeyConstraints.Digest.SHA256) * 8); } } private final KeyStore mKeyStore = KeyStore.getInstance(); private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm; - private final @KeyStoreKeyConstraints.AlgorithmEnum Integer mDigest; + private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest; private final int mDefaultKeySizeBits; private KeyGeneratorSpec mSpec; @@ -75,6 +76,19 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { 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); diff --git a/keystore/java/android/security/KeymasterException.java b/keystore/java/android/security/KeymasterException.java index 4ff71152f834..bc8198f58d72 100644 --- a/keystore/java/android/security/KeymasterException.java +++ b/keystore/java/android/security/KeymasterException.java @@ -7,7 +7,14 @@ package android.security; */ public class KeymasterException extends Exception { - public KeymasterException(String message) { + private final int mErrorCode; + + public KeymasterException(int errorCode, String message) { super(message); + mErrorCode = errorCode; + } + + public int getErrorCode() { + return mErrorCode; } } diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java index e6e88c741dfd..4f175864a9df 100644 --- a/keystore/java/android/security/KeymasterUtils.java +++ b/keystore/java/android/security/KeymasterUtils.java @@ -13,9 +13,11 @@ public abstract class KeymasterUtils { case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT: // The name of this parameter significantly differs between Keymaster and framework // APIs. Use the framework wording to make life easier for developers. - return new KeymasterException("Invalid user authentication validity duration"); + return new KeymasterException(keymasterErrorCode, + "Invalid user authentication validity duration"); default: - return new KeymasterException(KeymasterDefs.getErrorMessage(keymasterErrorCode)); + return new KeymasterException(keymasterErrorCode, + KeymasterDefs.getErrorMessage(keymasterErrorCode)); } } } diff --git a/services/core/Android.mk b/services/core/Android.mk index 5c4520170fd8..43249e723bbb 100644 --- a/services/core/Android.mk +++ b/services/core/Android.mk @@ -10,5 +10,6 @@ LOCAL_SRC_FILES += \ java/com/android/server/am/EventLogTags.logtags LOCAL_JAVA_LIBRARIES := android.policy telephony-common +LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ab1a1e84bd24..f5a9847255d6 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); @@ -6805,7 +6807,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 +17667,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 +17988,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/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index a6c616ae8de2..b18b05723656 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; + import android.util.ArraySet; import android.util.EventLog; import android.util.Slog; @@ -83,10 +85,10 @@ final class ProcessRecord { int curSchedGroup; // Currently desired scheduling class int setSchedGroup; // Last set to background scheduling class int trimMemoryLevel; // Last selected memory trimming level - int curProcState = -1; // Currently computed process state: ActivityManager.PROCESS_STATE_* - int repProcState = -1; // Last reported process state - int setProcState = -1; // Last set process state in process tracker - int pssProcState = -1; // The proc state we are currently requesting pss for + int curProcState = PROCESS_STATE_NONEXISTENT; // Currently computed process state + int repProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state + int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker + int pssProcState = PROCESS_STATE_NONEXISTENT; // Currently requesting pss for boolean serviceb; // Process currently is on the service B list boolean serviceHighRam; // We are forcing to service B list due to its RAM use boolean setIsForeground; // Running foreground UI when last set? diff --git a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java new file mode 100644 index 000000000000..b260e4e2fc34 --- /dev/null +++ b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.updates; + +import android.util.Slog; + +import java.io.File; +import java.io.IOException; +import libcore.tzdata.update.TzDataBundleInstaller; + +/** + * An install receiver responsible for installing timezone data updates. + */ +public class TzDataInstallReceiver extends ConfigUpdateInstallReceiver { + + private static final String TAG = "TZDataInstallReceiver"; + + private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo"); + private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/"; + private static final String UPDATE_METADATA_DIR_NAME = "metadata/"; + private static final String UPDATE_VERSION_FILE_NAME = "version"; + private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_bundle.zip"; + + private final TzDataBundleInstaller installer; + + public TzDataInstallReceiver() { + super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME, + UPDATE_VERSION_FILE_NAME); + installer = new TzDataBundleInstaller(TAG, TZ_DATA_DIR); + } + + @Override + protected void install(byte[] content, int version) throws IOException { + boolean valid = installer.install(content); + Slog.i(TAG, "Timezone data install valid for this device: " + valid); + // Even if !valid, we call super.install(). Only in the event of an exception should we + // not. If we didn't do this we could attempt to install repeatedly. + super.install(content, version); + } +} |