From ceb1b0bfaea56251796b08c07b963de7403d84eb Mon Sep 17 00:00:00 2001 From: Anonymous Coward Date: Tue, 24 Apr 2012 10:35:16 -0700 Subject: [PATCH] Add encryption parameters to package installation Change-Id: Ic9f8ab9f8110f08bb3c00725cfce5b8ee7b766f3 --- cmds/pm/src/com/android/commands/pm/Pm.java | 135 +++++++- .../android/app/ApplicationPackageManager.java | 5 +- .../content/pm/ContainerEncryptionParams.aidl | 19 ++ .../content/pm/ContainerEncryptionParams.java | 378 +++++++++++++++++++++ core/java/android/content/pm/IPackageManager.aidl | 3 +- .../content/pm/LimitedLengthInputStream.java | 82 +++++ .../content/pm/MacAuthenticatedInputStream.java | 78 +++++ core/java/android/content/pm/PackageManager.java | 12 +- .../internal/app/IMediaContainerService.aidl | 7 +- .../content/pm/ContainerEncryptionParamsTest.java | 370 ++++++++++++++++++++ .../content/pm/LimitedLengthInputStreamTest.java | 186 ++++++++++ .../pm/MacAuthenticatedInputStreamTest.java | 120 +++++++ .../defcontainer/DefaultContainerService.java | 276 +++++++++++---- .../android/server/pm/PackageManagerService.java | 98 ++++-- .../src/android/test/mock/MockPackageManager.java | 8 +- 15 files changed, 1678 insertions(+), 99 deletions(-) create mode 100644 core/java/android/content/pm/ContainerEncryptionParams.aidl create mode 100644 core/java/android/content/pm/ContainerEncryptionParams.java create mode 100644 core/java/android/content/pm/LimitedLengthInputStream.java create mode 100644 core/java/android/content/pm/MacAuthenticatedInputStream.java create mode 100644 core/tests/coretests/src/android/content/pm/ContainerEncryptionParamsTest.java create mode 100644 core/tests/coretests/src/android/content/pm/LimitedLengthInputStreamTest.java create mode 100644 core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index e19ad6606b2b..f9ff861e88f5 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -16,9 +16,12 @@ package com.android.commands.pm; +import com.android.internal.content.PackageHelper; + import android.app.ActivityManagerNative; import android.content.ComponentName; import android.content.pm.ApplicationInfo; +import android.content.pm.ContainerEncryptionParams; import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; @@ -40,17 +43,20 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import com.android.internal.content.PackageHelper; - import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.security.InvalidAlgorithmParameterException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.WeakHashMap; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + public final class Pm { IPackageManager mPm; @@ -763,6 +769,15 @@ public final class Pm { String installerPackageName = null; String opt; + + String algo = null; + byte[] iv = null; + byte[] key = null; + + String macAlgo = null; + byte[] macKey = null; + byte[] tag = null; + while ((opt=nextOption()) != null) { if (opt.equals("-l")) { installFlags |= PackageManager.INSTALL_FORWARD_LOCK; @@ -783,6 +798,48 @@ public final class Pm { } else if (opt.equals("-f")) { // Override if -s option is specified. installFlags |= PackageManager.INSTALL_INTERNAL; + } else if (opt.equals("--algo")) { + algo = nextOptionData(); + if (algo == null) { + System.err.println("Error: must supply argument for --algo"); + showUsage(); + return; + } + } else if (opt.equals("--iv")) { + iv = hexToBytes(nextOptionData()); + if (iv == null) { + System.err.println("Error: must supply argument for --iv"); + showUsage(); + return; + } + } else if (opt.equals("--key")) { + key = hexToBytes(nextOptionData()); + if (key == null) { + System.err.println("Error: must supply argument for --key"); + showUsage(); + return; + } + } else if (opt.equals("--macalgo")) { + macAlgo = nextOptionData(); + if (macAlgo == null) { + System.err.println("Error: must supply argument for --macalgo"); + showUsage(); + return; + } + } else if (opt.equals("--mackey")) { + macKey = hexToBytes(nextOptionData()); + if (macKey == null) { + System.err.println("Error: must supply argument for --mackey"); + showUsage(); + return; + } + } else if (opt.equals("--tag")) { + tag = hexToBytes(nextOptionData()); + if (tag == null) { + System.err.println("Error: must supply argument for --tag"); + showUsage(); + return; + } } else { System.err.println("Error: Unknown option: " + opt); showUsage(); @@ -790,6 +847,44 @@ public final class Pm { } } + final ContainerEncryptionParams encryptionParams; + if (algo != null || iv != null || key != null || macAlgo != null || macKey != null + || tag != null) { + if (algo == null || iv == null || key == null) { + System.err.println("Error: all of --algo, --iv, and --key must be specified"); + showUsage(); + return; + } + + if (macAlgo != null || macKey != null || tag != null) { + if (macAlgo == null || macKey == null || tag == null) { + System.err.println("Error: all of --macalgo, --mackey, and --tag must " + + "be specified"); + showUsage(); + return; + } + } + + try { + final SecretKey encKey = new SecretKeySpec(key, "RAW"); + + final SecretKey macSecretKey; + if (macKey == null || macKey.length == 0) { + macSecretKey = null; + } else { + macSecretKey = new SecretKeySpec(macKey, "RAW"); + } + + encryptionParams = new ContainerEncryptionParams(algo, new IvParameterSpec(iv), + encKey, macAlgo, null, macSecretKey, tag, -1, -1, -1); + } catch (InvalidAlgorithmParameterException e) { + e.printStackTrace(); + return; + } + } else { + encryptionParams = null; + } + final Uri apkURI; final Uri verificationURI; @@ -816,7 +911,7 @@ public final class Pm { PackageInstallObserver obs = new PackageInstallObserver(); try { mPm.installPackageWithVerification(apkURI, obs, installFlags, installerPackageName, - verificationURI, null); + verificationURI, null, encryptionParams); synchronized (obs) { while (!obs.finished) { @@ -839,6 +934,37 @@ public final class Pm { } } + /** + * Convert a string containing hex-encoded bytes to a byte array. + * + * @param input String containing hex-encoded bytes + * @return input as an array of bytes + */ + private byte[] hexToBytes(String input) { + if (input == null) { + return null; + } + + final int inputLength = input.length(); + if ((inputLength % 2) != 0) { + System.err.print("Invalid length; must be multiple of 2"); + return null; + } + + final int byteLength = inputLength / 2; + final byte[] output = new byte[byteLength]; + + int inputIndex = 0; + int byteIndex = 0; + while (inputIndex < inputLength) { + output[byteIndex++] = (byte) Integer.parseInt( + input.substring(inputIndex, inputIndex + 2), 16); + inputIndex += 2; + } + + return output; + } + public void runCreateUser() { // Need to be run as root if (Process.myUid() != ROOT_UID) { @@ -1236,7 +1362,8 @@ public final class Pm { System.err.println(" pm list libraries"); System.err.println(" pm list users"); System.err.println(" pm path PACKAGE"); - System.err.println(" pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] PATH"); + System.err.println(" pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f]"); + System.err.println(" [--algo --key --iv ] PATH"); System.err.println(" pm uninstall [-k] PACKAGE"); System.err.println(" pm clear PACKAGE"); System.err.println(" pm enable [--user USER_ID] PACKAGE_OR_COMPONENT"); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 0510de16842d..191a69612a16 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -24,6 +24,7 @@ import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; +import android.content.pm.ContainerEncryptionParams; import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; @@ -973,10 +974,10 @@ final class ApplicationPackageManager extends PackageManager { @Override public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, Uri verificationURI, - ManifestDigest manifestDigest) { + ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) { try { mPM.installPackageWithVerification(packageURI, observer, flags, installerPackageName, - verificationURI, manifestDigest); + verificationURI, manifestDigest, encryptionParams); } catch (RemoteException e) { // Should never happen! } diff --git a/core/java/android/content/pm/ContainerEncryptionParams.aidl b/core/java/android/content/pm/ContainerEncryptionParams.aidl new file mode 100644 index 000000000000..c13d94662ea4 --- /dev/null +++ b/core/java/android/content/pm/ContainerEncryptionParams.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2012, 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.content.pm; + +parcelable ContainerEncryptionParams; diff --git a/core/java/android/content/pm/ContainerEncryptionParams.java b/core/java/android/content/pm/ContainerEncryptionParams.java new file mode 100644 index 000000000000..5b1440d907d5 --- /dev/null +++ b/core/java/android/content/pm/ContainerEncryptionParams.java @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2012 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.content.pm; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Slog; + +import java.security.InvalidAlgorithmParameterException; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + +/** + * Represents encryption parameters used to read a container. + * + * @hide + */ +public class ContainerEncryptionParams implements Parcelable { + protected static final String TAG = "ContainerEncryptionParams"; + + /** What we print out first when toString() is called. */ + private static final String TO_STRING_PREFIX = "ContainerEncryptionParams{"; + + /** + * Parameter type for parceling that indicates the next parameters are + * IvParameters. + */ + private static final int ENC_PARAMS_IV_PARAMETERS = 1; + + /** Parameter type for paceling that indicates there are no MAC parameters. */ + private static final int MAC_PARAMS_NONE = 1; + + /** The encryption algorithm used. */ + private final String mEncryptionAlgorithm; + + /** The parameter spec to be used for encryption. */ + private final IvParameterSpec mEncryptionSpec; + + /** Secret key to be used for decryption. */ + private final SecretKey mEncryptionKey; + + /** Algorithm name for the MAC to be used. */ + private final String mMacAlgorithm; + + /** The parameter spec to be used for the MAC tag authentication. */ + private final AlgorithmParameterSpec mMacSpec; + + /** Secret key to be used for MAC tag authentication. */ + private final SecretKey mMacKey; + + /** MAC tag authenticating the data in the container. */ + private final byte[] mMacTag; + + /** Offset into file where authenticated (e.g., MAC protected) data begins. */ + private final int mAuthenticatedDataStart; + + /** Offset into file where encrypted data begins. */ + private final int mEncryptedDataStart; + + /** + * Offset into file for the end of encrypted data (and, by extension, + * authenticated data) in file. + */ + private final int mDataEnd; + + public ContainerEncryptionParams(String encryptionAlgorithm, + AlgorithmParameterSpec encryptionSpec, SecretKey encryptionKey) + throws InvalidAlgorithmParameterException { + this(encryptionAlgorithm, encryptionSpec, encryptionKey, null, null, null, null, -1, -1, + -1); + } + + /** + * Creates container encryption specifications for installing from encrypted + * containers. + * + * @param encryptionAlgorithm encryption algorithm to use; format matches + * JCE + * @param encryptionSpec algorithm parameter specification + * @param encryptionKey key used for decryption + * @param macAlgorithm MAC algorithm to use; format matches JCE + * @param macSpec algorithm parameters specification, may be {@code null} + * @param macKey key used for authentication (i.e., for the MAC tag) + * @param authenticatedDataStart offset of start of authenticated data in + * stream + * @param encryptedDataStart offset of start of encrypted data in stream + * @param dataEnd offset of the end of both the authenticated and encrypted + * data + * @throws InvalidAlgorithmParameterException + */ + public ContainerEncryptionParams(String encryptionAlgorithm, + AlgorithmParameterSpec encryptionSpec, SecretKey encryptionKey, String macAlgorithm, + AlgorithmParameterSpec macSpec, SecretKey macKey, byte[] macTag, + int authenticatedDataStart, int encryptedDataStart, int dataEnd) + throws InvalidAlgorithmParameterException { + if (TextUtils.isEmpty(encryptionAlgorithm)) { + throw new NullPointerException("algorithm == null"); + } else if (encryptionSpec == null) { + throw new NullPointerException("encryptionSpec == null"); + } else if (encryptionKey == null) { + throw new NullPointerException("encryptionKey == null"); + } + + if (!TextUtils.isEmpty(macAlgorithm)) { + if (macKey == null) { + throw new NullPointerException("macKey == null"); + } + } + + if (!(encryptionSpec instanceof IvParameterSpec)) { + throw new InvalidAlgorithmParameterException( + "Unknown parameter spec class; must be IvParameters"); + } + + mEncryptionAlgorithm = encryptionAlgorithm; + mEncryptionSpec = (IvParameterSpec) encryptionSpec; + mEncryptionKey = encryptionKey; + + mMacAlgorithm = macAlgorithm; + mMacSpec = macSpec; + mMacKey = macKey; + mMacTag = macTag; + + mAuthenticatedDataStart = authenticatedDataStart; + mEncryptedDataStart = encryptedDataStart; + mDataEnd = dataEnd; + } + + public String getEncryptionAlgorithm() { + return mEncryptionAlgorithm; + } + + public AlgorithmParameterSpec getEncryptionSpec() { + return mEncryptionSpec; + } + + public SecretKey getEncryptionKey() { + return mEncryptionKey; + } + + public String getMacAlgorithm() { + return mMacAlgorithm; + } + + public AlgorithmParameterSpec getMacSpec() { + return mMacSpec; + } + + public SecretKey getMacKey() { + return mMacKey; + } + + public byte[] getMacTag() { + return mMacTag; + } + + public int getAuthenticatedDataStart() { + return mAuthenticatedDataStart; + } + + public int getEncryptedDataStart() { + return mEncryptedDataStart; + } + + public int getDataEnd() { + return mDataEnd; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof ContainerEncryptionParams)) { + return false; + } + + final ContainerEncryptionParams other = (ContainerEncryptionParams) o; + + // Primitive comparison + if ((mAuthenticatedDataStart != other.mAuthenticatedDataStart) + || (mEncryptedDataStart != other.mEncryptedDataStart) + || (mDataEnd != other.mDataEnd)) { + return false; + } + + // String comparison + if (!mEncryptionAlgorithm.equals(other.mEncryptionAlgorithm) + || !mMacAlgorithm.equals(other.mMacAlgorithm)) { + return false; + } + + // Object comparison + if (!isSecretKeyEqual(mEncryptionKey, other.mEncryptionKey) + || !isSecretKeyEqual(mMacKey, other.mMacKey)) { + return false; + } + + if (!Arrays.equals(mEncryptionSpec.getIV(), other.mEncryptionSpec.getIV()) + || !Arrays.equals(mMacTag, other.mMacTag) || (mMacSpec != other.mMacSpec)) { + return false; + } + + return true; + } + + private static final boolean isSecretKeyEqual(SecretKey key1, SecretKey key2) { + final String keyFormat = key1.getFormat(); + final String otherKeyFormat = key2.getFormat(); + + if (keyFormat == null) { + if (keyFormat != otherKeyFormat) { + return false; + } + + if (key1.getEncoded() != key2.getEncoded()) { + return false; + } + } else { + if (!keyFormat.equals(key2.getFormat())) { + return false; + } + + if (!Arrays.equals(key1.getEncoded(), key2.getEncoded())) { + return false; + } + } + + return true; + } + + @Override + public int hashCode() { + int hash = 3; + + hash += 5 * mEncryptionAlgorithm.hashCode(); + hash += 7 * Arrays.hashCode(mEncryptionSpec.getIV()); + hash += 11 * mEncryptionKey.hashCode(); + hash += 13 * mMacAlgorithm.hashCode(); + hash += 17 * mMacKey.hashCode(); + hash += 19 * Arrays.hashCode(mMacTag); + hash += 23 * mAuthenticatedDataStart; + hash += 29 * mEncryptedDataStart; + hash += 31 * mDataEnd; + + return hash; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(TO_STRING_PREFIX); + + sb.append("mEncryptionAlgorithm=\""); + sb.append(mEncryptionAlgorithm); + sb.append("\","); + sb.append("mEncryptionSpec="); + sb.append(mEncryptionSpec.toString()); + sb.append("mEncryptionKey="); + sb.append(mEncryptionKey.toString()); + + sb.append("mMacAlgorithm=\""); + sb.append(mMacAlgorithm); + sb.append("\","); + sb.append("mMacSpec="); + sb.append(mMacSpec.toString()); + sb.append("mMacKey="); + sb.append(mMacKey.toString()); + + sb.append(",mAuthenticatedDataStart="); + sb.append(mAuthenticatedDataStart); + sb.append(",mEncryptedDataStart="); + sb.append(mEncryptedDataStart); + sb.append(",mDataEnd="); + sb.append(mDataEnd); + sb.append('}'); + + return sb.toString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mEncryptionAlgorithm); + dest.writeInt(ENC_PARAMS_IV_PARAMETERS); + dest.writeByteArray(mEncryptionSpec.getIV()); + dest.writeSerializable(mEncryptionKey); + + dest.writeString(mMacAlgorithm); + dest.writeInt(MAC_PARAMS_NONE); + dest.writeByteArray(new byte[0]); + dest.writeSerializable(mMacKey); + + dest.writeByteArray(mMacTag); + + dest.writeInt(mAuthenticatedDataStart); + dest.writeInt(mEncryptedDataStart); + dest.writeInt(mDataEnd); + } + + private ContainerEncryptionParams(Parcel source) throws InvalidAlgorithmParameterException { + mEncryptionAlgorithm = source.readString(); + final int encParamType = source.readInt(); + final byte[] encParamsEncoded = source.createByteArray(); + mEncryptionKey = (SecretKey) source.readSerializable(); + + mMacAlgorithm = source.readString(); + final int macParamType = source.readInt(); + source.createByteArray(); // byte[] macParamsEncoded + mMacKey = (SecretKey) source.readSerializable(); + + mMacTag = source.createByteArray(); + + mAuthenticatedDataStart = source.readInt(); + mEncryptedDataStart = source.readInt(); + mDataEnd = source.readInt(); + + switch (encParamType) { + case ENC_PARAMS_IV_PARAMETERS: + mEncryptionSpec = new IvParameterSpec(encParamsEncoded); + break; + default: + throw new InvalidAlgorithmParameterException("Unknown parameter type " + + encParamType); + } + + switch (macParamType) { + case MAC_PARAMS_NONE: + mMacSpec = null; + break; + default: + throw new InvalidAlgorithmParameterException("Unknown parameter type " + + macParamType); + } + + if (mEncryptionKey == null) { + throw new NullPointerException("encryptionKey == null"); + } + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public ContainerEncryptionParams createFromParcel(Parcel source) { + try { + return new ContainerEncryptionParams(source); + } catch (InvalidAlgorithmParameterException e) { + Slog.e(TAG, "Invalid algorithm parameters specified", e); + return null; + } + } + + public ContainerEncryptionParams[] newArray(int size) { + return new ContainerEncryptionParams[size]; + } + }; +} \ No newline at end of file diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 9b8454aa2ffc..70c0c48afa1d 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -22,6 +22,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ContainerEncryptionParams; import android.content.pm.FeatureInfo; import android.content.pm.IPackageInstallObserver; import android.content.pm.IPackageDeleteObserver; @@ -362,7 +363,7 @@ interface IPackageManager { void installPackageWithVerification(in Uri packageURI, in IPackageInstallObserver observer, int flags, in String installerPackageName, in Uri verificationURI, - in ManifestDigest manifestDigest); + in ManifestDigest manifestDigest, in ContainerEncryptionParams encryptionParams); void verifyPendingInstall(int id, int verificationCode); diff --git a/core/java/android/content/pm/LimitedLengthInputStream.java b/core/java/android/content/pm/LimitedLengthInputStream.java new file mode 100644 index 000000000000..25a490f66f68 --- /dev/null +++ b/core/java/android/content/pm/LimitedLengthInputStream.java @@ -0,0 +1,82 @@ +package android.content.pm; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A class that limits the amount of data that is read from an InputStream. When + * the specified length is reached, the stream returns an EOF even if the + * underlying stream still has more data. + * + * @hide + */ +public class LimitedLengthInputStream extends FilterInputStream { + /** + * The end of the stream where we don't want to allow more data to be read. + */ + private final int mEnd; + + /** + * Current offset in the stream. + */ + private int mOffset; + + /** + * @param in underlying stream to wrap + * @param offset offset into stream where data starts + * @param length length of data at offset + * @throws IOException if an error occured with the underlying stream + */ + public LimitedLengthInputStream(InputStream in, int offset, int length) throws IOException { + super(in); + + if (in == null) { + throw new IOException("in == null"); + } + + if (offset < 0) { + throw new IOException("offset == " + offset); + } + + if (length < 0) { + throw new IOException("length must be non-negative; is " + length); + } + + mEnd = offset + length; + + skip(offset); + mOffset = offset; + } + + @Override + public synchronized int read() throws IOException { + if (mOffset >= mEnd) { + return -1; + } + + mOffset++; + return super.read(); + } + + @Override + public int read(byte[] buffer, int offset, int byteCount) throws IOException { + if (mOffset >= mEnd) { + return -1; + } + + if (mOffset + byteCount > mEnd) { + byteCount = mEnd - mOffset; + } + + final int numRead = super.read(buffer, offset, byteCount); + mOffset += numRead; + + return numRead; + } + + @Override + public int read(byte[] buffer) throws IOException { + return read(buffer, 0, buffer.length); + } +} diff --git a/core/java/android/content/pm/MacAuthenticatedInputStream.java b/core/java/android/content/pm/MacAuthenticatedInputStream.java new file mode 100644 index 000000000000..11f4b9471366 --- /dev/null +++ b/core/java/android/content/pm/MacAuthenticatedInputStream.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012 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.content.pm; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.crypto.Mac; + +/** + * An input stream filter that applies a MAC to the data passing through it. At + * the end of the data that should be authenticated, the tag can be calculated. + * After that, the stream should not be used. + * + * @hide + */ +public class MacAuthenticatedInputStream extends FilterInputStream { + private final Mac mMac; + + public MacAuthenticatedInputStream(InputStream in, Mac mac) { + super(in); + + mMac = mac; + } + + public boolean isTagEqual(byte[] tag) { + final byte[] actualTag = mMac.doFinal(); + + if (tag == null || actualTag == null || tag.length != actualTag.length) { + return false; + } + + /* + * Attempt to prevent timing attacks by doing the same amount of work + * whether the first byte matches or not. Do not change this to a loop + * that exits early when a byte does not match. + */ + int value = 0; + for (int i = 0; i < tag.length; i++) { + value |= tag[i] ^ actualTag[i]; + } + + return value == 0; + } + + @Override + public int read() throws IOException { + final int b = super.read(); + if (b >= 0) { + mMac.update((byte) b); + } + return b; + } + + @Override + public int read(byte[] buffer, int offset, int count) throws IOException { + int numRead = super.read(buffer, offset, count); + if (numRead > 0) { + mMac.update(buffer, offset, numRead); + } + return numRead; + } +} diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index c3ce1cfc8e3a..a48924e9fa8a 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -28,7 +28,6 @@ import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.Build; import android.os.Environment; import android.util.AndroidException; import android.util.DisplayMetrics; @@ -2199,12 +2198,19 @@ public abstract class PackageManager { * is performing the installation. This identifies which market * the package came from. * @param verificationURI The location of the supplementary verification - * file. This can be a 'file:' or a 'content:' URI. + * file. This can be a 'file:' or a 'content:' URI. May be + * {@code null}. + * @param manifestDigest an object that holds the digest of the package + * which can be used to verify ownership. May be {@code null}. + * @param encryptionParams if the package to be installed is encrypted, + * these parameters describing the encryption and authentication + * used. May be {@code null}. * @hide */ public abstract void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, - Uri verificationURI, ManifestDigest manifestDigest); + Uri verificationURI, ManifestDigest manifestDigest, + ContainerEncryptionParams encryptionParams); /** * Allows a package listening to the diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl index 727c09442dca..c9f7a58efbf4 100755 --- a/core/java/com/android/internal/app/IMediaContainerService.aidl +++ b/core/java/com/android/internal/app/IMediaContainerService.aidl @@ -18,6 +18,7 @@ package com.android.internal.app; import android.net.Uri; import android.os.ParcelFileDescriptor; +import android.content.pm.ContainerEncryptionParams; import android.content.pm.PackageInfoLite; import android.content.res.ObbInfo; @@ -25,9 +26,9 @@ interface IMediaContainerService { String copyResourceToContainer(in Uri packageURI, String containerId, String key, String resFileName, String publicResFileName, boolean isExternal, boolean isForwardLocked); - int copyResource(in Uri packageURI, - in ParcelFileDescriptor outStream); - PackageInfoLite getMinimalPackageInfo(in Uri fileUri, in int flags, in long threshold); + int copyResource(in Uri packageURI, in ContainerEncryptionParams encryptionParams, + in ParcelFileDescriptor outStream); + PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold); boolean checkInternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in long threshold); boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked); ObbInfo getObbInfo(in String filename); diff --git a/core/tests/coretests/src/android/content/pm/ContainerEncryptionParamsTest.java b/core/tests/coretests/src/android/content/pm/ContainerEncryptionParamsTest.java new file mode 100644 index 000000000000..7deaa9a70a81 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/ContainerEncryptionParamsTest.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2012 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.content.pm; + +import android.os.Parcel; +import android.test.AndroidTestCase; + +import java.util.Arrays; + +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class ContainerEncryptionParamsTest extends AndroidTestCase { + private static final String ENC_ALGORITHM = "AES/CBC/PKCS7Padding"; + + private static final byte[] IV_BYTES = "FOOBAR".getBytes(); + + private static final IvParameterSpec ENC_PARAMS = new IvParameterSpec(IV_BYTES); + + private static final byte[] ENC_KEY_BYTES = "abcd1234wxyz7890".getBytes(); + + private static final SecretKey ENC_KEY = new SecretKeySpec(ENC_KEY_BYTES, "RAW"); + + private static final String MAC_ALGORITHM = "HMAC-SHA1"; + + private static final byte[] MAC_KEY_BYTES = "4wxyzabcd1237890".getBytes(); + + private static final SecretKey MAC_KEY = new SecretKeySpec(MAC_KEY_BYTES, "RAW"); + + private static final byte[] MAC_TAG = "faketag".getBytes(); + + private static final int AUTHENTICATED_START = 5; + + private static final int ENCRYPTED_START = 11; + + private static final int DATA_END = 19; + + public void testParcel() throws Exception { + ContainerEncryptionParams expected = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + Parcel parcel = Parcel.obtain(); + expected.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + ContainerEncryptionParams actual = ContainerEncryptionParams.CREATOR + .createFromParcel(parcel); + + assertEquals(ENC_ALGORITHM, actual.getEncryptionAlgorithm()); + + if (!(actual.getEncryptionSpec() instanceof IvParameterSpec)) { + fail("encryption parameters should be IvParameterSpec"); + } else { + IvParameterSpec actualParams = (IvParameterSpec) actual.getEncryptionSpec(); + assertTrue(Arrays.equals(IV_BYTES, actualParams.getIV())); + } + + assertEquals(ENC_KEY, actual.getEncryptionKey()); + + assertEquals(MAC_ALGORITHM, actual.getMacAlgorithm()); + + assertNull(actual.getMacSpec()); + + assertEquals(MAC_KEY, actual.getMacKey()); + + assertTrue(Arrays.equals(MAC_TAG, actual.getMacTag())); + + assertEquals(AUTHENTICATED_START, actual.getAuthenticatedDataStart()); + + assertEquals(ENCRYPTED_START, actual.getEncryptedDataStart()); + + assertEquals(DATA_END, actual.getDataEnd()); + } + + public void testEquals_Success() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + assertEquals(params1, params2); + } + + public void testEquals_EncAlgo_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams(new String( + "AES-256/CBC/PKCS7Padding"), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + assertFalse(params1.equals(params2)); + } + + public void testEquals_EncParams_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec("BLAHBLAH".getBytes()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + assertFalse(params1.equals(params2)); + } + + public void testEquals_EncKey_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec("BLAHBLAH".getBytes(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + assertFalse(params1.equals(params2)); + } + + public void testEquals_MacAlgo_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), "BLAHBLAH", null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + assertFalse(params1.equals(params2)); + } + + public void testEquals_MacKey_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec("FAKE_MAC_KEY".getBytes(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + assertFalse(params1.equals(params2)); + } + + public void testEquals_MacTag_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), "broken".getBytes(), + AUTHENTICATED_START, ENCRYPTED_START, DATA_END); + + assertFalse(params1.equals(params2)); + } + + public void testEquals_AuthenticatedStart_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START - 1, + ENCRYPTED_START, DATA_END); + + assertFalse(params1.equals(params2)); + } + + public void testEquals_EncryptedStart_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START - 1, DATA_END); + + assertFalse(params1.equals(params2)); + } + + public void testEquals_DataEnd_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END + 1); + + assertFalse(params1.equals(params2)); + } + + public void testHashCode_Success() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + assertEquals(params1.hashCode(), params2.hashCode()); + } + + public void testHashCode_EncAlgo_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams(new String( + "AES-256/CBC/PKCS7Padding"), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + assertFalse(params1.hashCode() == params2.hashCode()); + } + + public void testHashCode_EncParams_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec("BLAHBLAH".getBytes()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + assertFalse(params1.hashCode() == params2.hashCode()); + } + + public void testHashCode_EncKey_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec("BLAHBLAH".getBytes(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + assertFalse(params1.hashCode() == params2.hashCode()); + } + + public void testHashCode_MacAlgo_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), "BLAHBLAH", null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + assertFalse(params1.hashCode() == params2.hashCode()); + } + + public void testHashCode_MacKey_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec("FAKE_MAC_KEY".getBytes(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + assertFalse(params1.hashCode() == params2.hashCode()); + } + + public void testHashCode_MacTag_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), "broken".getBytes(), + AUTHENTICATED_START, ENCRYPTED_START, DATA_END); + + assertFalse(params1.hashCode() == params2.hashCode()); + } + + public void testHashCode_AuthenticatedStart_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START - 1, + ENCRYPTED_START, DATA_END); + + assertFalse(params1.hashCode() == params2.hashCode()); + } + + public void testHashCode_EncryptedStart_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START - 1, DATA_END); + + assertFalse(params1.hashCode() == params2.hashCode()); + } + + public void testHashCode_DataEnd_Failure() throws Exception { + ContainerEncryptionParams params1 = new ContainerEncryptionParams(ENC_ALGORITHM, + ENC_PARAMS, ENC_KEY, MAC_ALGORITHM, null, MAC_KEY, MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END); + + ContainerEncryptionParams params2 = new ContainerEncryptionParams( + new String(ENC_ALGORITHM), new IvParameterSpec(IV_BYTES.clone()), + new SecretKeySpec(ENC_KEY_BYTES.clone(), "RAW"), new String(MAC_ALGORITHM), null, + new SecretKeySpec(MAC_KEY_BYTES.clone(), "RAW"), MAC_TAG, AUTHENTICATED_START, + ENCRYPTED_START, DATA_END + 1); + + assertFalse(params1.hashCode() == params2.hashCode()); + } +} diff --git a/core/tests/coretests/src/android/content/pm/LimitedLengthInputStreamTest.java b/core/tests/coretests/src/android/content/pm/LimitedLengthInputStreamTest.java new file mode 100644 index 000000000000..0a0152b83b62 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/LimitedLengthInputStreamTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2012 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.content.pm; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +public class LimitedLengthInputStreamTest extends AndroidTestCase { + private final byte[] TEST_STRING1 = "This is a test".getBytes(); + + private InputStream mTestStream1; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mTestStream1 = new ByteArrayInputStream(TEST_STRING1); + } + + @MediumTest + public void testConstructor_NegativeOffset_Failure() throws Exception { + try { + InputStream is = new LimitedLengthInputStream(mTestStream1, -1, TEST_STRING1.length); + fail("Should throw IOException on negative index"); + } catch (IOException e) { + // success + } + } + + @MediumTest + public void testConstructor_NegativeLength_Failure() throws Exception { + try { + InputStream is = new LimitedLengthInputStream(mTestStream1, 0, -1); + fail("Should throw IOException on negative length"); + } catch (IOException e) { + // success + } + } + + @MediumTest + public void testConstructor_NullInputStream_Failure() throws Exception { + try { + InputStream is = new LimitedLengthInputStream(null, 0, 1); + fail("Should throw IOException on null input stream"); + } catch (IOException e) { + // success + } + } + + private void checkReadBytesWithOffsetAndLength_WithString1(int offset, int length) + throws Exception { + byte[] temp = new byte[TEST_STRING1.length]; + byte[] expected = new byte[length]; + byte[] actual = new byte[length]; + + System.arraycopy(TEST_STRING1, offset, expected, 0, length); + + InputStream is = new LimitedLengthInputStream(mTestStream1, offset, length); + assertEquals(length, is.read(temp, 0, temp.length)); + + System.arraycopy(temp, 0, actual, 0, length); + assertTrue(Arrays.equals(expected, actual)); + + assertEquals(-1, is.read(temp, 0, temp.length)); + } + + @MediumTest + public void testReadBytesWithOffsetAndLength_ZeroOffset_PartialLength_Success() + throws Exception { + checkReadBytesWithOffsetAndLength_WithString1(0, 2); + } + + @MediumTest + public void testReadBytesWithOffsetAndLength_NonZeroOffset_PartialLength_Success() + throws Exception { + checkReadBytesWithOffsetAndLength_WithString1(3, 2); + } + + @MediumTest + public void testReadBytesWithOffsetAndLength_ZeroOffset_FullLength_Success() throws Exception { + checkReadBytesWithOffsetAndLength_WithString1(0, TEST_STRING1.length); + } + + @MediumTest + public void testReadBytesWithOffsetAndLength_NonZeroOffset_FullLength_Success() + throws Exception { + checkReadBytesWithOffsetAndLength_WithString1(3, TEST_STRING1.length - 3); + } + + @MediumTest + public void testReadBytesWithOffsetAndLength_ZeroOffset_PastEnd_Success() throws Exception { + byte[] temp = new byte[TEST_STRING1.length + 10]; + InputStream is = new LimitedLengthInputStream(mTestStream1, 0, TEST_STRING1.length + 10); + assertEquals(TEST_STRING1.length, is.read(temp, 0, TEST_STRING1.length + 10)); + + byte[] actual = new byte[TEST_STRING1.length]; + System.arraycopy(temp, 0, actual, 0, actual.length); + assertTrue(Arrays.equals(TEST_STRING1, actual)); + } + + private void checkReadBytes_WithString1(int offset, int length) throws Exception { + byte[] temp = new byte[TEST_STRING1.length]; + byte[] expected = new byte[length]; + byte[] actual = new byte[length]; + + System.arraycopy(TEST_STRING1, offset, expected, 0, length); + + InputStream is = new LimitedLengthInputStream(mTestStream1, offset, length); + assertEquals(length, is.read(temp)); + + System.arraycopy(temp, 0, actual, 0, length); + assertTrue(Arrays.equals(expected, actual)); + + assertEquals(-1, is.read(temp)); + } + + @MediumTest + public void testReadBytes_ZeroOffset_PartialLength_Success() throws Exception { + checkReadBytesWithOffsetAndLength_WithString1(0, 2); + } + + @MediumTest + public void testReadBytes_NonZeroOffset_PartialLength_Success() throws Exception { + checkReadBytesWithOffsetAndLength_WithString1(3, 2); + } + + @MediumTest + public void testReadBytes_ZeroOffset_FullLength_Success() throws Exception { + checkReadBytesWithOffsetAndLength_WithString1(0, TEST_STRING1.length); + } + + @MediumTest + public void testReadBytes_NonZeroOffset_FullLength_Success() throws Exception { + checkReadBytesWithOffsetAndLength_WithString1(3, TEST_STRING1.length - 3); + } + + private void checkSingleByteRead_WithString1(int offset, int length) throws Exception { + InputStream is = new LimitedLengthInputStream(mTestStream1, offset, length); + + for (int i = 0; i < length; i++) { + assertEquals(TEST_STRING1[offset + i], is.read()); + } + + assertEquals(-1, is.read()); + } + + @MediumTest + public void testSingleByteRead_ZeroOffset_PartialLength_Success() throws Exception { + checkSingleByteRead_WithString1(0, 2); + } + + @MediumTest + public void testSingleByteRead_NonZeroOffset_PartialLength_Success() throws Exception { + checkSingleByteRead_WithString1(3, 2); + } + + @MediumTest + public void testSingleByteRead_ZeroOffset_FullLength_Success() throws Exception { + checkSingleByteRead_WithString1(0, TEST_STRING1.length); + } + + @MediumTest + public void testSingleByteRead_NonZeroOffset_FullLength_Success() throws Exception { + checkSingleByteRead_WithString1(3, TEST_STRING1.length - 3); + } + +} diff --git a/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java b/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java new file mode 100644 index 000000000000..948e7220978b --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012 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.content.pm; + +import android.test.AndroidTestCase; + +import java.io.ByteArrayInputStream; +import java.util.Arrays; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import libcore.io.Streams; + +public class MacAuthenticatedInputStreamTest extends AndroidTestCase { + + private static final SecretKey HMAC_KEY_1 = new SecretKeySpec("test_key_1".getBytes(), "HMAC"); + + private static final byte[] TEST_STRING_1 = "Hello, World!".getBytes(); + + /** + * Generated with: + * + * echo -n 'Hello, World!' | openssl dgst -hmac 'test_key_1' -binary -sha1 | recode ..//x1 | + * sed 's/0x/(byte) 0x/g' + */ + private static final byte[] TEST_STRING_1_MAC = { + (byte) 0x29, (byte) 0xB1, (byte) 0x87, (byte) 0x6B, (byte) 0xFE, (byte) 0x83, + (byte) 0x96, (byte) 0x51, (byte) 0x61, (byte) 0x02, (byte) 0xAF, (byte) 0x7B, + (byte) 0xBA, (byte) 0x05, (byte) 0xE6, (byte) 0xA4, (byte) 0xAB, (byte) 0x36, + (byte) 0x18, (byte) 0x02 + }; + + /** + * Same as TEST_STRING_1_MAC but with the first byte as 0x28 instead of + * 0x29. + */ + private static final byte[] TEST_STRING_1_MAC_BROKEN = { + (byte) 0x28, (byte) 0xB1, (byte) 0x87, (byte) 0x6B, (byte) 0xFE, (byte) 0x83, + (byte) 0x96, (byte) 0x51, (byte) 0x61, (byte) 0x02, (byte) 0xAF, (byte) 0x7B, + (byte) 0xBA, (byte) 0x05, (byte) 0xE6, (byte) 0xA4, (byte) 0xAB, (byte) 0x36, + (byte) 0x18, (byte) 0x02 + }; + + private ByteArrayInputStream mTestStream1; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mTestStream1 = new ByteArrayInputStream(TEST_STRING_1); + } + + public void testString1Authenticate_Success() throws Exception { + Mac mac = Mac.getInstance("HMAC-SHA1"); + mac.init(HMAC_KEY_1); + + MacAuthenticatedInputStream is = new MacAuthenticatedInputStream(mTestStream1, mac); + + assertTrue(Arrays.equals(TEST_STRING_1, Streams.readFully(is))); + + assertTrue(is.isTagEqual(TEST_STRING_1_MAC)); + } + + public void testString1Authenticate_WrongTag_Failure() throws Exception { + Mac mac = Mac.getInstance("HMAC-SHA1"); + mac.init(HMAC_KEY_1); + + MacAuthenticatedInputStream is = new MacAuthenticatedInputStream(mTestStream1, mac); + + assertTrue(Arrays.equals(TEST_STRING_1, Streams.readFully(is))); + + assertFalse(is.isTagEqual(TEST_STRING_1_MAC_BROKEN)); + } + + public void testString1Authenticate_NullTag_Failure() throws Exception { + Mac mac = Mac.getInstance("HMAC-SHA1"); + mac.init(HMAC_KEY_1); + + MacAuthenticatedInputStream is = new MacAuthenticatedInputStream(mTestStream1, mac); + + assertTrue(Arrays.equals(TEST_STRING_1, Streams.readFully(is))); + + assertFalse(is.isTagEqual(null)); + } + + public void testString1Authenticate_ReadSingleByte_Success() throws Exception { + Mac mac = Mac.getInstance("HMAC-SHA1"); + mac.init(HMAC_KEY_1); + + MacAuthenticatedInputStream is = new MacAuthenticatedInputStream(mTestStream1, mac); + + int numRead = 0; + while (is.read() != -1) { + numRead++; + + if (numRead > TEST_STRING_1.length) { + fail("read too many bytes"); + } + } + assertEquals(TEST_STRING_1.length, numRead); + + assertTrue(is.isTagEqual(TEST_STRING_1_MAC)); + } +} diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index c709e4012deb..17e5f4ee9708 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -22,7 +22,10 @@ import com.android.internal.content.PackageHelper; import android.app.IntentService; import android.content.Intent; +import android.content.pm.MacAuthenticatedInputStream; +import android.content.pm.ContainerEncryptionParams; import android.content.pm.IPackageManager; +import android.content.pm.LimitedLengthInputStream; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; @@ -49,9 +52,21 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.security.DigestException; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; import libcore.io.ErrnoException; +import libcore.io.IoUtils; import libcore.io.Libcore; +import libcore.io.Streams; import libcore.io.StructStatFs; /* @@ -68,7 +83,7 @@ public class DefaultContainerService extends IntentService { private static final String LIB_DIR_NAME = "lib"; private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { - /* + /** * Creates a new container and copies resource there. * @param paackageURI the uri of resource to be copied. Can be either * a content uri or a file uri @@ -92,15 +107,19 @@ public class DefaultContainerService extends IntentService { isExternal, isForwardLocked); } - /* + /** * Copy specified resource to output stream + * * @param packageURI the uri of resource to be copied. Should be a file - * uri + * uri + * @param encryptionParams parameters describing the encryption used for + * this file * @param outStream Remote file descriptor to be used for copying - * @return returns status code according to those in {@link - * PackageManager} + * @return returns status code according to those in + * {@link PackageManager} */ - public int copyResource(final Uri packageURI, ParcelFileDescriptor outStream) { + public int copyResource(final Uri packageURI, ContainerEncryptionParams encryptionParams, + ParcelFileDescriptor outStream) { if (packageURI == null || outStream == null) { return PackageManager.INSTALL_FAILED_INVALID_URI; } @@ -109,7 +128,7 @@ public class DefaultContainerService extends IntentService { = new ParcelFileDescriptor.AutoCloseOutputStream(outStream); try { - copyFile(packageURI, autoOut); + copyFile(packageURI, autoOut, encryptionParams); return PackageManager.INSTALL_SUCCEEDED; } catch (FileNotFoundException e) { Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " FNF: " @@ -119,10 +138,14 @@ public class DefaultContainerService extends IntentService { Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " IO: " + e.getMessage()); return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + } catch (DigestException e) { + Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " Security: " + + e.getMessage()); + return PackageManager.INSTALL_FAILED_INVALID_APK; } } - /* + /** * Determine the recommended install location for package * specified by file uri location. * @param fileUri the uri of resource to be copied. Should be a @@ -130,28 +153,24 @@ public class DefaultContainerService extends IntentService { * @return Returns PackageInfoLite object containing * the package info and recommended app location. */ - public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags, long threshold) { + public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags, + long threshold) { PackageInfoLite ret = new PackageInfoLite(); - if (fileUri == null) { - Slog.i(TAG, "Invalid package uri " + fileUri); + + if (packagePath == null) { + Slog.i(TAG, "Invalid package file " + packagePath); ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; return ret; } - String scheme = fileUri.getScheme(); - if (scheme != null && !scheme.equals("file")) { - Slog.w(TAG, "Falling back to installing on internal storage only"); - ret.recommendedInstallLocation = PackageHelper.RECOMMEND_INSTALL_INTERNAL; - return ret; - } - String archiveFilePath = fileUri.getPath(); + DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); - PackageParser.PackageLite pkg = PackageParser.parsePackageLite(archiveFilePath, 0); + PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packagePath, 0); if (pkg == null) { Slog.w(TAG, "Failed to parse package"); - final File apkFile = new File(archiveFilePath); + final File apkFile = new File(packagePath); if (!apkFile.exists()) { ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; } else { @@ -160,12 +179,13 @@ public class DefaultContainerService extends IntentService { return ret; } + ret.packageName = pkg.packageName; ret.installLocation = pkg.installLocation; ret.verifiers = pkg.verifiers; ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, - archiveFilePath, flags, threshold); + packagePath, flags, threshold); return ret; } @@ -392,55 +412,195 @@ public class DefaultContainerService extends IntentService { } } - private static void copyToFile(File srcFile, OutputStream out) - throws FileNotFoundException, IOException { - InputStream inputStream = new BufferedInputStream(new FileInputStream(srcFile)); + private void copyFile(Uri pPackageURI, OutputStream outStream, + ContainerEncryptionParams encryptionParams) throws FileNotFoundException, IOException, + DigestException { + String scheme = pPackageURI.getScheme(); + InputStream inStream = null; try { - copyToFile(inputStream, out); + if (scheme == null || scheme.equals("file")) { + final InputStream is = new FileInputStream(new File(pPackageURI.getPath())); + inStream = new BufferedInputStream(is); + } else if (scheme.equals("content")) { + final ParcelFileDescriptor fd; + try { + fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); + } catch (FileNotFoundException e) { + Slog.e(TAG, "Couldn't open file descriptor from download service. " + + "Failed with exception " + e); + throw e; + } + + if (fd == null) { + Slog.e(TAG, "Provider returned no file descriptor for " + + pPackageURI.toString()); + throw new FileNotFoundException("provider returned no file descriptor"); + } else { + if (localLOGV) { + Slog.i(TAG, "Opened file descriptor from download service."); + } + inStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); + } + } else { + Slog.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); + throw new FileNotFoundException("Package URI is not 'file:' or 'content:'"); + } + + /* + * If this resource is encrypted, get the decrypted stream version + * of it. + */ + ApkContainer container = new ApkContainer(inStream, encryptionParams); + + try { + /* + * We copy the source package file to a temp file and then + * rename it to the destination file in order to eliminate a + * window where the package directory scanner notices the new + * package file but it's not completely copied yet. + */ + copyToFile(container.getInputStream(), outStream); + + if (!container.isAuthenticated()) { + throw new DigestException(); + } + } catch (GeneralSecurityException e) { + throw new DigestException("A problem occured copying the file."); + } } finally { - try { inputStream.close(); } catch (IOException e) {} + IoUtils.closeQuietly(inStream); } } - private void copyFile(Uri pPackageURI, OutputStream outStream) throws FileNotFoundException, - IOException { - String scheme = pPackageURI.getScheme(); - if (scheme == null || scheme.equals("file")) { - final File srcPackageFile = new File(pPackageURI.getPath()); - // We copy the source package file to a temp file and then rename it to the - // destination file in order to eliminate a window where the package directory - // scanner notices the new package file but it's not completely copied yet. - copyToFile(srcPackageFile, outStream); - } else if (scheme.equals("content")) { - ParcelFileDescriptor fd = null; + private static class ApkContainer { + private final InputStream mInStream; + + private MacAuthenticatedInputStream mAuthenticatedStream; + + private byte[] mTag; + + public ApkContainer(InputStream inStream, ContainerEncryptionParams encryptionParams) + throws IOException { + if (encryptionParams == null) { + mInStream = inStream; + } else { + mInStream = getDecryptedStream(inStream, encryptionParams); + mTag = encryptionParams.getMacTag(); + } + } + + public boolean isAuthenticated() { + if (mAuthenticatedStream == null) { + return true; + } + + return mAuthenticatedStream.isTagEqual(mTag); + } + + private Mac getMacInstance(ContainerEncryptionParams encryptionParams) throws IOException { + final Mac m; try { - fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); - } catch (FileNotFoundException e) { - Slog.e(TAG, "Couldn't open file descriptor from download service. " - + "Failed with exception " + e); - throw e; + final String macAlgo = encryptionParams.getMacAlgorithm(); + + if (macAlgo != null) { + m = Mac.getInstance(macAlgo); + m.init(encryptionParams.getMacKey(), encryptionParams.getMacSpec()); + } else { + m = null; + } + + return m; + } catch (NoSuchAlgorithmException e) { + throw new IOException(e); + } catch (InvalidKeyException e) { + throw new IOException(e); + } catch (InvalidAlgorithmParameterException e) { + throw new IOException(e); } + } - if (fd == null) { - Slog.e(TAG, "Provider returned no file descriptor for " + pPackageURI.toString()); - throw new FileNotFoundException("provider returned no file descriptor"); - } else { - if (localLOGV) { - Slog.i(TAG, "Opened file descriptor from download service."); + public InputStream getInputStream() { + return mInStream; + } + + private InputStream getDecryptedStream(InputStream inStream, + ContainerEncryptionParams encryptionParams) throws IOException { + final Cipher c; + try { + c = Cipher.getInstance(encryptionParams.getEncryptionAlgorithm()); + c.init(Cipher.DECRYPT_MODE, encryptionParams.getEncryptionKey(), + encryptionParams.getEncryptionSpec()); + } catch (NoSuchAlgorithmException e) { + throw new IOException(e); + } catch (NoSuchPaddingException e) { + throw new IOException(e); + } catch (InvalidKeyException e) { + throw new IOException(e); + } catch (InvalidAlgorithmParameterException e) { + throw new IOException(e); + } + + final int encStart = encryptionParams.getEncryptedDataStart(); + final int end = encryptionParams.getDataEnd(); + if (end < encStart) { + throw new IOException("end <= encStart"); + } + + final Mac mac = getMacInstance(encryptionParams); + if (mac != null) { + final int macStart = encryptionParams.getAuthenticatedDataStart(); + + final int furtherOffset; + if (macStart >= 0 && encStart >= 0 && macStart < encStart) { + /* + * If there is authenticated data at the beginning, read + * that into our MAC first. + */ + final int authenticatedLength = encStart - macStart; + final byte[] authenticatedData = new byte[authenticatedLength]; + + Streams.readFully(inStream, authenticatedData, macStart, authenticatedLength); + mac.update(authenticatedData, 0, authenticatedLength); + + furtherOffset = 0; + } else { + /* + * No authenticated data at the beginning. Just skip the + * required number of bytes to the beginning of the stream. + */ + if (encStart > 0) { + furtherOffset = encStart; + } else { + furtherOffset = 0; + } } - ParcelFileDescriptor.AutoCloseInputStream dlStream - = new ParcelFileDescriptor.AutoCloseInputStream(fd); - // We copy the source package file to a temp file and then rename it to the - // destination file in order to eliminate a window where the package directory - // scanner notices the new package file but it's not completely - // copied - copyToFile(dlStream, outStream); + /* + * If there is data at the end of the stream we want to ignore, + * wrap this in a LimitedLengthInputStream. + */ + if (furtherOffset >= 0 && end > furtherOffset) { + inStream = new LimitedLengthInputStream(inStream, furtherOffset, end - encStart); + } else if (furtherOffset > 0) { + inStream.skip(furtherOffset); + } + + mAuthenticatedStream = new MacAuthenticatedInputStream(inStream, mac); + + inStream = mAuthenticatedStream; + } else { + if (encStart >= 0) { + if (end > encStart) { + inStream = new LimitedLengthInputStream(inStream, encStart, end - encStart); + } else { + inStream.skip(encStart); + } + } } - } else { - Slog.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); - throw new FileNotFoundException("Package URI is not 'file:' or 'content:'"); + + return new CipherInputStream(inStream, c); } + } private static final int PREFER_INTERNAL = 1; diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index b5d0b604c22d..b5567d3187bc 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -53,6 +53,7 @@ import android.content.ServiceConnection; import android.content.IntentSender.SendIntentException; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ContainerEncryptionParams; import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; @@ -116,7 +117,6 @@ import java.io.FileOutputStream; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; -import java.io.InputStream; import java.io.PrintWriter; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; @@ -128,7 +128,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; -import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -136,9 +135,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; import libcore.io.ErrnoException; import libcore.io.IoUtils; @@ -5133,13 +5129,13 @@ public class PackageManagerService extends IPackageManager.Stub { final Uri packageURI, final IPackageInstallObserver observer, final int flags, final String installerPackageName) { installPackageWithVerification(packageURI, observer, flags, installerPackageName, null, - null); + null, null); } @Override public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, Uri verificationURI, - ManifestDigest manifestDigest) { + ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null); final int uid = Binder.getCallingUid(); @@ -5157,7 +5153,7 @@ public class PackageManagerService extends IPackageManager.Stub { final Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName, - verificationURI, manifestDigest); + verificationURI, manifestDigest, encryptionParams); mHandler.sendMessage(msg); } @@ -5560,22 +5556,27 @@ public class PackageManagerService extends IPackageManager.Stub { class InstallParams extends HandlerParams { final IPackageInstallObserver observer; int flags; - final Uri packageURI; + + private final Uri mPackageURI; final String installerPackageName; final Uri verificationURI; final ManifestDigest manifestDigest; private InstallArgs mArgs; private int mRet; + private File mTempPackage; + final ContainerEncryptionParams encryptionParams; InstallParams(Uri packageURI, IPackageInstallObserver observer, int flags, - String installerPackageName, Uri verificationURI, ManifestDigest manifestDigest) { - this.packageURI = packageURI; + String installerPackageName, Uri verificationURI, ManifestDigest manifestDigest, + ContainerEncryptionParams encryptionParams) { + this.mPackageURI = packageURI; this.flags = flags; this.observer = observer; this.installerPackageName = installerPackageName; this.verificationURI = verificationURI; this.manifestDigest = manifestDigest; + this.encryptionParams = encryptionParams; } private int installLocationPolicy(PackageInfoLite pkgLite, int flags) { @@ -5655,16 +5656,51 @@ public class PackageManagerService extends IPackageManager.Stub { lowThreshold = dsm.getMemoryLowThreshold(); } - // Remote call to find out default install location try { - mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, + mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, mPackageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags, - lowThreshold); + + final File packageFile; + if (encryptionParams != null || !"file".equals(mPackageURI.getScheme())) { + ParcelFileDescriptor out = null; + + mTempPackage = createTempPackageFile(mDrmAppPrivateInstallDir); + if (mTempPackage != null) { + try { + out = ParcelFileDescriptor.open(mTempPackage, + ParcelFileDescriptor.MODE_READ_WRITE); + } catch (FileNotFoundException e) { + Slog.e(TAG, "Failed to create temporary file for : " + mPackageURI); + } + + // Make a temporary file for decryption. + ret = mContainerService + .copyResource(mPackageURI, encryptionParams, out); + + packageFile = mTempPackage; + + FileUtils.setPermissions(packageFile.getAbsolutePath(), + FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IROTH, + -1, -1); + } else { + packageFile = null; + } + } else { + packageFile = new File(mPackageURI.getPath()); + } + + if (packageFile != null) { + // Remote call to find out default install location + pkgLite = mContainerService.getMinimalPackageInfo( + packageFile.getAbsolutePath(), flags, lowThreshold); + } } finally { - mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); + mContext.revokeUriPermission(mPackageURI, + Intent.FLAG_GRANT_READ_URI_PERMISSION); } + } + if (ret == PackageManager.INSTALL_SUCCEEDED) { int loc = pkgLite.recommendedInstallLocation; if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) { ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; @@ -5708,8 +5744,9 @@ public class PackageManagerService extends IPackageManager.Stub { final int requiredUid = mRequiredVerifierPackage == null ? -1 : getPackageUid(mRequiredVerifierPackage, 0); if (requiredUid != -1 && isVerificationEnabled()) { - final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); - verification.setDataAndType(packageURI, PACKAGE_MIME_TYPE); + final Intent verification = new Intent( + Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); + verification.setDataAndType(getPackageUri(), PACKAGE_MIME_TYPE); verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); final List receivers = queryIntentReceivers(verification, null, @@ -5812,6 +5849,13 @@ public class PackageManagerService extends IPackageManager.Stub { if (mArgs != null) { processPendingInstall(mArgs, mRet); } + + if (mTempPackage != null) { + if (!mTempPackage.delete()) { + Slog.w(TAG, "Couldn't delete temporary file: " + + mTempPackage.getAbsolutePath()); + } + } } @Override @@ -5823,6 +5867,14 @@ public class PackageManagerService extends IPackageManager.Stub { public boolean isForwardLocked() { return (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; } + + public Uri getPackageUri() { + if (mTempPackage != null) { + return Uri.fromFile(mTempPackage); + } else { + return mPackageURI; + } + } } /* @@ -6037,8 +6089,8 @@ public class PackageManagerService extends IPackageManager.Stub { boolean created = false; FileInstallArgs(InstallParams params) { - super(params.packageURI, params.observer, params.flags, params.installerPackageName, - params.manifestDigest); + super(params.getPackageUri(), params.observer, params.flags, + params.installerPackageName, params.manifestDigest); } FileInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath) { @@ -6128,7 +6180,7 @@ public class PackageManagerService extends IPackageManager.Stub { try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - ret = imcs.copyResource(packageURI, out); + ret = imcs.copyResource(packageURI, null, out); } finally { IoUtils.closeQuietly(out); mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -6315,8 +6367,8 @@ public class PackageManagerService extends IPackageManager.Stub { String libraryPath; AsecInstallArgs(InstallParams params) { - super(params.packageURI, params.observer, params.flags, params.installerPackageName, - params.manifestDigest); + super(params.getPackageUri(), params.observer, params.flags, + params.installerPackageName, params.manifestDigest); } AsecInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath, diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 5610134f0c44..86689f3560f3 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -22,6 +22,7 @@ import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ContainerEncryptionParams; import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; @@ -29,6 +30,7 @@ import android.content.pm.IPackageInstallObserver; import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; import android.content.pm.InstrumentationInfo; +import android.content.pm.ManifestDigest; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PermissionGroupInfo; @@ -36,16 +38,12 @@ import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.pm.Signature; import android.content.pm.UserInfo; -import android.content.pm.ManifestDigest; import android.content.pm.VerifierDeviceIdentity; -import android.content.pm.VerifierInfo; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.RemoteException; import java.util.List; @@ -565,7 +563,7 @@ public class MockPackageManager extends PackageManager { @Override public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, Uri verificationURI, - ManifestDigest manifestDigest) { + ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) { throw new UnsupportedOperationException(); } -- 2.11.0