diff options
Diffstat (limited to 'core')
11 files changed, 1251 insertions, 9 deletions
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 0510de1..191a696 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 0000000..c13d946 --- /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 0000000..5b1440d --- /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<ContainerEncryptionParams> CREATOR = + new Parcelable.Creator<ContainerEncryptionParams>() { + 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 9b8454a..70c0c48 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 0000000..25a490f --- /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 0000000..11f4b94 --- /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 c3ce1cf..a48924e 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 727c094..c9f7a58 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 0000000..7deaa9a --- /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 0000000..0a0152b --- /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 0000000..948e722 --- /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)); + } +} |