summaryrefslogtreecommitdiff
path: root/src/test/java/com/android/apkzlib/sign
diff options
context:
space:
mode:
authorPaulo Casanova <pasc@google.com>2016-11-14 16:02:08 +0000
committerPaulo Casanova <pasc@google.com>2016-11-16 05:38:00 +0000
commit5a1ccbecca5fc359bf488e717db310d307c0c9fc (patch)
treeb414a0e3428c16f5cb24b9bff8c89b9a792d8211 /src/test/java/com/android/apkzlib/sign
parenta5c71db7e08ae4e72804a64e470c6d4e816e2ee7 (diff)
downloadapkzlib-5a1ccbecca5fc359bf488e717db310d307c0c9fc.tar.gz
Renamed apkzlib packages.
Test: Included Change-Id: I9cce74b77719003875deaa5a0056e35f2930429e
Diffstat (limited to 'src/test/java/com/android/apkzlib/sign')
-rw-r--r--src/test/java/com/android/apkzlib/sign/FullApkSignTest.java105
-rw-r--r--src/test/java/com/android/apkzlib/sign/JarSigningTest.java375
-rw-r--r--src/test/java/com/android/apkzlib/sign/ManifestGenerationTest.java180
-rw-r--r--src/test/java/com/android/apkzlib/sign/SignatureTestUtils.java133
4 files changed, 793 insertions, 0 deletions
diff --git a/src/test/java/com/android/apkzlib/sign/FullApkSignTest.java b/src/test/java/com/android/apkzlib/sign/FullApkSignTest.java
new file mode 100644
index 0000000..d41978a
--- /dev/null
+++ b/src/test/java/com/android/apkzlib/sign/FullApkSignTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apkzlib.sign;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.apkzlib.utils.ApkZLibPair;
+import com.android.apkzlib.zip.AlignmentRule;
+import com.android.apkzlib.zip.AlignmentRules;
+import com.android.apkzlib.zip.StoredEntry;
+import com.android.apkzlib.zip.ZFile;
+import com.android.apkzlib.zip.ZFileOptions;
+import com.android.apkzlib.zip.ZFileTestConstants;
+import com.android.apkzlib.utils.ApkZFileTestUtils;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Tests that verify {@link FullApkSignExtension}.
+ */
+public class FullApkSignTest {
+
+ /**
+ * Folder used for tests.
+ */
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void testSignature() throws Exception {
+ File out = new File(mTemporaryFolder.getRoot(), "apk");
+
+ ApkZLibPair<PrivateKey, X509Certificate> signData =
+ SignatureTestUtils.generateSignaturePre18();
+
+ // The byte arrays below are larger when compressed, so we end up storing them uncompressed,
+ // which would normally cause them to be 4-aligned. Disable that, to make calculations
+ // easier.
+ ZFileOptions options = new ZFileOptions();
+ options.setAlignmentRule(AlignmentRules.constant(AlignmentRule.NO_ALIGNMENT));
+
+ /*
+ * Generate a signed zip.
+ */
+ ZFile zf = new ZFile(out, options);
+ FullApkSignExtension signExtension =
+ new FullApkSignExtension(zf, 13, signData.v2, signData.v1);
+ signExtension.register();
+ String f1Name = "abc";
+ byte[] f1Data = new byte[] { 1, 1, 1, 1 };
+ zf.add(f1Name, new ByteArrayInputStream(f1Data));
+ String f2Name = "defg";
+ byte[] f2Data = new byte[] { 2, 2, 2, 2, 3, 3, 3, 3};
+ zf.add(f2Name, new ByteArrayInputStream(f2Data));
+ zf.close();
+
+ /*
+ * We should see the data in place.
+ */
+ int f1DataStart = ZFileTestConstants.LOCAL_HEADER_SIZE + f1Name.length();
+ int f1DataEnd = f1DataStart + f1Data.length;
+ int f2DataStart = f1DataEnd + ZFileTestConstants.LOCAL_HEADER_SIZE + f2Name.length();
+ int f2DataEnd = f2DataStart + f2Data.length;
+
+ byte[] read1 = ApkZFileTestUtils.readSegment(out, f1DataStart, f1Data.length);
+ assertArrayEquals(f1Data, read1);
+ byte[] read2 = ApkZFileTestUtils.readSegment(out, f2DataStart, f2Data.length);
+ assertArrayEquals(f2Data, read2);
+
+ /*
+ * Read the signed zip.
+ */
+ ZFile zf2 = new ZFile(out);
+
+ StoredEntry se1 = zf2.get(f1Name);
+ assertNotNull(se1);
+ assertArrayEquals(f1Data, se1.read());
+
+ StoredEntry se2 = zf2.get(f2Name);
+ assertNotNull(se2);
+ assertArrayEquals(f2Data, se2.read());
+
+ zf2.close();
+ }
+}
diff --git a/src/test/java/com/android/apkzlib/sign/JarSigningTest.java b/src/test/java/com/android/apkzlib/sign/JarSigningTest.java
new file mode 100644
index 0000000..191bf53
--- /dev/null
+++ b/src/test/java/com/android/apkzlib/sign/JarSigningTest.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apkzlib.sign;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.apkzlib.zip.StoredEntry;
+import com.android.apkzlib.zip.ZFile;
+import com.android.apkzlib.utils.ApkZFileTestUtils;
+import com.android.apkzlib.utils.ApkZLibPair;
+import com.google.common.base.Charsets;
+import com.google.common.hash.Hashing;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class JarSigningTest {
+
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void signEmptyJar() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+
+ try (ZFile zf = new ZFile(zipFile)) {
+ ManifestGenerationExtension manifestExtension =
+ new ManifestGenerationExtension("Me", "Me");
+ manifestExtension.register(zf);
+
+ ApkZLibPair<PrivateKey, X509Certificate> p =
+ SignatureTestUtils.generateSignaturePre18();
+
+ SignatureExtension signatureExtension =
+ new SignatureExtension(manifestExtension, 12, p.v2, p.v1, null);
+ signatureExtension.register();
+ }
+
+ try (ZFile verifyZFile = new ZFile(zipFile)) {
+ StoredEntry manifestEntry = verifyZFile.get("META-INF/MANIFEST.MF");
+ assertNotNull(manifestEntry);
+
+ Manifest manifest = new Manifest(new ByteArrayInputStream(manifestEntry.read()));
+ assertEquals(3, manifest.getMainAttributes().size());
+ assertEquals("1.0", manifest.getMainAttributes().getValue("Manifest-Version"));
+ assertEquals("Me", manifest.getMainAttributes().getValue("Created-By"));
+ assertEquals("Me", manifest.getMainAttributes().getValue("Built-By"));
+ }
+ }
+
+ @Test
+ public void signJarWithPrexistingSimpleTextFilePre18() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+ ApkZLibPair<PrivateKey, X509Certificate> p = SignatureTestUtils.generateSignaturePre18();
+
+ try (ZFile zf1 = new ZFile(zipFile)) {
+ zf1.add("directory/file",
+ new ByteArrayInputStream("useless text".getBytes(Charsets.US_ASCII)));
+ }
+
+ try (ZFile zf2 = new ZFile(zipFile)) {
+ ManifestGenerationExtension me = new ManifestGenerationExtension("Merry", "Christmas");
+ me.register(zf2);
+ new SignatureExtension(me, 10, p.v2, p.v1, null).register();
+ }
+
+ try (ZFile zf3 = new ZFile(zipFile)) {
+ StoredEntry manifestEntry = zf3.get("META-INF/MANIFEST.MF");
+ assertNotNull(manifestEntry);
+
+ Manifest manifest = new Manifest(new ByteArrayInputStream(manifestEntry.read()));
+ assertEquals(3, manifest.getMainAttributes().size());
+ assertEquals("1.0", manifest.getMainAttributes().getValue("Manifest-Version"));
+ assertEquals("Merry", manifest.getMainAttributes().getValue("Built-By"));
+ assertEquals("Christmas", manifest.getMainAttributes().getValue("Created-By"));
+
+ Attributes attrs = manifest.getAttributes("directory/file");
+ assertNotNull(attrs);
+ assertEquals(1, attrs.size());
+ assertEquals("OOQgIEXBissIvva3ydRoaXk29Rk=", attrs.getValue("SHA1-Digest"));
+
+ StoredEntry signatureEntry = zf3.get("META-INF/CERT.SF");
+ assertNotNull(signatureEntry);
+
+ Manifest signature = new Manifest(new ByteArrayInputStream(signatureEntry.read()));
+ assertEquals(3, signature.getMainAttributes().size());
+ assertEquals("1.0", signature.getMainAttributes().getValue("Signature-Version"));
+ assertEquals("1.0 (Android)", signature.getMainAttributes().getValue("Created-By"));
+
+ byte[] manifestTextBytes = manifestEntry.read();
+ byte[] manifestSha1Bytes = Hashing.sha1().hashBytes(manifestTextBytes).asBytes();
+ String manifestSha1 = Base64.getEncoder().encodeToString(manifestSha1Bytes);
+
+ assertEquals(manifestSha1,
+ signature.getMainAttributes().getValue("SHA1-Digest-Manifest"));
+
+ Attributes signAttrs = signature.getAttributes("directory/file");
+ assertNotNull(signAttrs);
+ assertEquals(1, signAttrs.size());
+ assertEquals("OOQgIEXBissIvva3ydRoaXk29Rk=", signAttrs.getValue("SHA1-Digest"));
+
+ StoredEntry rsaEntry = zf3.get("META-INF/CERT.RSA");
+ assertNotNull(rsaEntry);
+ }
+ }
+
+ @Test
+ public void signJarWithPrexistingSimpleTextFilePos18() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+ try (ZFile zf1 = new ZFile(zipFile)) {
+ zf1.add("directory/file", new ByteArrayInputStream("useless text".getBytes(
+ Charsets.US_ASCII)));
+ }
+
+ ApkZLibPair<PrivateKey, X509Certificate> p = SignatureTestUtils.generateSignaturePos18();
+
+ try (ZFile zf2 = new ZFile(zipFile)) {
+ ManifestGenerationExtension me = new ManifestGenerationExtension("Merry", "Christmas");
+ me.register(zf2);
+ new SignatureExtension(me, 21, p.v2, p.v1, null).register();
+ }
+
+ try (ZFile zf3 = new ZFile(zipFile)) {
+ StoredEntry manifestEntry = zf3.get("META-INF/MANIFEST.MF");
+ assertNotNull(manifestEntry);
+
+ Manifest manifest = new Manifest(new ByteArrayInputStream(manifestEntry.read()));
+ assertEquals(3, manifest.getMainAttributes().size());
+ assertEquals("1.0", manifest.getMainAttributes().getValue("Manifest-Version"));
+ assertEquals("Merry", manifest.getMainAttributes().getValue("Built-By"));
+ assertEquals("Christmas", manifest.getMainAttributes().getValue("Created-By"));
+
+ Attributes attrs = manifest.getAttributes("directory/file");
+ assertNotNull(attrs);
+ assertEquals(1, attrs.size());
+ assertEquals("QjupZsopQM/01O6+sWHqH64ilMmoBEtljg9VEqN6aI4=",
+ attrs.getValue("SHA-256-Digest"));
+
+ StoredEntry signatureEntry = zf3.get("META-INF/CERT.SF");
+ assertNotNull(signatureEntry);
+
+ Manifest signature = new Manifest(new ByteArrayInputStream(signatureEntry.read()));
+ assertEquals(3, signature.getMainAttributes().size());
+ assertEquals("1.0", signature.getMainAttributes().getValue("Signature-Version"));
+ assertEquals("1.0 (Android)", signature.getMainAttributes().getValue("Created-By"));
+
+ byte[] manifestTextBytes = manifestEntry.read();
+ byte[] manifestSha256Bytes = Hashing.sha256().hashBytes(manifestTextBytes).asBytes();
+ String manifestSha256 = Base64.getEncoder().encodeToString(manifestSha256Bytes);
+
+ assertEquals(manifestSha256, signature.getMainAttributes().getValue(
+ "SHA-256-Digest-Manifest"));
+
+ Attributes signAttrs = signature.getAttributes("directory/file");
+ assertNotNull(signAttrs);
+ assertEquals(1, signAttrs.size());
+ assertEquals("QjupZsopQM/01O6+sWHqH64ilMmoBEtljg9VEqN6aI4=",
+ signAttrs.getValue("SHA-256-Digest"));
+
+ StoredEntry ecdsaEntry = zf3.get("META-INF/CERT.EC");
+ assertNotNull(ecdsaEntry);
+ }
+ }
+
+ @Test
+ public void v2SignAddsApkSigningBlock() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+ try (ZFile zf = new ZFile(zipFile)) {
+ ManifestGenerationExtension manifestExtension =
+ new ManifestGenerationExtension("Me", "Me");
+ manifestExtension.register(zf);
+
+ ApkZLibPair<PrivateKey, X509Certificate> p = SignatureTestUtils.generateSignaturePre18();
+
+ FullApkSignExtension signatureExtension =
+ new FullApkSignExtension(zf, 12, p.v2, p.v1);
+ signatureExtension.register();
+ }
+
+ try (ZFile verifyZFile = new ZFile(zipFile)) {
+ long centralDirOffset = verifyZFile.getCentralDirectoryOffset();
+ byte[] apkSigningBlockMagic = new byte[16];
+ verifyZFile.directFullyRead(
+ centralDirOffset - apkSigningBlockMagic.length, apkSigningBlockMagic);
+ assertEquals("APK Sig Block 42", new String(apkSigningBlockMagic, "US-ASCII"));
+ }
+ }
+
+ @Test
+ public void v1ReSignOnFileChange() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+ ApkZLibPair<PrivateKey, X509Certificate> p = SignatureTestUtils.generateSignaturePos18();
+
+ byte[] file1Contents = "I am a test file".getBytes(Charsets.US_ASCII);
+ String file1Name = "path/to/file1";
+ byte[] file1Sha = Hashing.sha256().hashBytes(file1Contents).asBytes();
+ String file1ShaTxt = Base64.getEncoder().encodeToString(file1Sha);
+
+ String builtBy = "Santa Claus";
+ String createdBy = "Uses Android";
+
+ try (ZFile zf1 = new ZFile(zipFile)) {
+ zf1.add(file1Name, new ByteArrayInputStream(file1Contents));
+ ManifestGenerationExtension me = new ManifestGenerationExtension(builtBy, createdBy);
+ me.register(zf1);
+ new SignatureExtension(me, 21, p.v2, p.v1, null).register();
+
+ zf1.update();
+
+ StoredEntry manifestEntry = zf1.get("META-INF/MANIFEST.MF");
+ assertNotNull(manifestEntry);
+
+ try (InputStream manifestIs = manifestEntry.open()) {
+ Manifest manifest = new Manifest(manifestIs);
+
+ assertEquals(1, manifest.getEntries().size());
+
+ Attributes file1Attrs = manifest.getEntries().get(file1Name);
+ assertNotNull(file1Attrs);
+ assertEquals(file1ShaTxt, file1Attrs.getValue("SHA-256-Digest"));
+ }
+
+ /*
+ * Change the file without closing the zip.
+ */
+ file1Contents = "I am a modified test file".getBytes(Charsets.US_ASCII);
+ file1Sha = Hashing.sha256().hashBytes(file1Contents).asBytes();
+ file1ShaTxt = Base64.getEncoder().encodeToString(file1Sha);
+
+ zf1.add(file1Name, new ByteArrayInputStream(file1Contents));
+
+ zf1.update();
+
+ manifestEntry = zf1.get("META-INF/MANIFEST.MF");
+ assertNotNull(manifestEntry);
+
+ try (InputStream manifestIs = manifestEntry.open()) {
+ Manifest manifest = new Manifest(manifestIs);
+
+ assertEquals(1, manifest.getEntries().size());
+
+ Attributes file1Attrs = manifest.getEntries().get(file1Name);
+ assertNotNull(file1Attrs);
+ assertEquals(file1ShaTxt, file1Attrs.getValue("SHA-256-Digest"));
+ }
+ }
+
+ /*
+ * Change the file closing the zip.
+ */
+ file1Contents = "I have changed again!".getBytes(Charsets.US_ASCII);
+ file1Sha = Hashing.sha256().hashBytes(file1Contents).asBytes();
+ file1ShaTxt = Base64.getEncoder().encodeToString(file1Sha);
+
+ try (ZFile zf2 = new ZFile(zipFile)) {
+ ManifestGenerationExtension me = new ManifestGenerationExtension(builtBy, createdBy);
+ me.register(zf2);
+ new SignatureExtension(me, 21, p.v2, p.v1, null).register();
+
+ zf2.add(file1Name, new ByteArrayInputStream(file1Contents));
+
+ zf2.update();
+
+ StoredEntry manifestEntry = zf2.get("META-INF/MANIFEST.MF");
+ assertNotNull(manifestEntry);
+
+ try (InputStream manifestIs = manifestEntry.open()) {
+ Manifest manifest = new Manifest(manifestIs);
+
+ assertEquals(1, manifest.getEntries().size());
+
+ Attributes file1Attrs = manifest.getEntries().get(file1Name);
+ assertNotNull(file1Attrs);
+ assertEquals(file1ShaTxt, file1Attrs.getValue("SHA-256-Digest"));
+ }
+ }
+ }
+
+ @Test
+ public void openSignedJarDoesNotForcesWriteifSignatureIsNotCorrect() throws Exception {
+ File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
+
+ ApkZLibPair<PrivateKey, X509Certificate> p = SignatureTestUtils.generateSignaturePos18();
+
+ String fileName = "file";
+ byte[] fileContents = "Very interesting contents".getBytes(Charsets.US_ASCII);
+
+ try (ZFile zf = new ZFile(zipFile)) {
+ ManifestGenerationExtension me = new ManifestGenerationExtension("I", "Android");
+ me.register(zf);
+ new SignatureExtension(me, 21, p.v2, p.v1, null).register();
+
+ zf.add(fileName, new ByteArrayInputStream(fileContents));
+ }
+
+ long fileTimestamp = zipFile.lastModified();
+
+ ApkZFileTestUtils.waitForFileSystemTick(fileTimestamp);
+
+ /*
+ * Open the zip file, but don't touch it.
+ */
+ try (ZFile zf = new ZFile(zipFile)) {
+ ManifestGenerationExtension me = new ManifestGenerationExtension("I", "Android");
+ me.register(zf);
+ new SignatureExtension(me, 21, p.v2, p.v1, null).register();
+ }
+
+ /*
+ * Check the file wasn't touched.
+ */
+ assertEquals(fileTimestamp, zipFile.lastModified());
+
+ /*
+ * Change the file contents ignoring any signing.
+ */
+ fileContents = "Not so interesting contents".getBytes(Charsets.US_ASCII);
+ try (ZFile zf = new ZFile(zipFile)) {
+ zf.add(fileName, new ByteArrayInputStream(fileContents));
+ }
+
+ fileTimestamp = zipFile.lastModified();
+
+ /*
+ * Wait to make sure the timestamp can increase.
+ */
+ while (true) {
+ File notUsed = mTemporaryFolder.newFile();
+ long notTimestamp = notUsed.lastModified();
+ notUsed.delete();
+ if (notTimestamp > fileTimestamp) {
+ break;
+ }
+ }
+
+ /*
+ * Open the zip file, but do any changes. The need to updating the signature should force
+ * a file update.
+ */
+ try (ZFile zf = new ZFile(zipFile)) {
+ ManifestGenerationExtension me = new ManifestGenerationExtension("I", "Android");
+ me.register(zf);
+ new SignatureExtension(me, 21, p.v2, p.v1, null).register();
+ }
+
+ /*
+ * Check the file was touched.
+ */
+ assertNotEquals(fileTimestamp, zipFile.lastModified());
+ }
+}
diff --git a/src/test/java/com/android/apkzlib/sign/ManifestGenerationTest.java b/src/test/java/com/android/apkzlib/sign/ManifestGenerationTest.java
new file mode 100644
index 0000000..746617b
--- /dev/null
+++ b/src/test/java/com/android/apkzlib/sign/ManifestGenerationTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apkzlib.sign;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.apkzlib.zip.StoredEntry;
+import com.android.apkzlib.zip.ZFile;
+import com.android.apkzlib.utils.ApkZFileTestUtils;
+import com.google.common.base.Charsets;
+import com.google.common.io.Closer;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.util.Set;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.internal.util.collections.Sets;
+
+public class ManifestGenerationTest {
+
+ private static final String WIKI_PATH = "/testData/packaging/text-files/wikipedia.html";
+
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void elementaryManifestGeneration() throws Exception {
+ File zip = new File(mTemporaryFolder.getRoot(), "f.zip");
+
+ try (ZFile zf = new ZFile(zip)) {
+ zf.add("abc", new ByteArrayInputStream(new byte[]{1}));
+ zf.add("x/", new ByteArrayInputStream(new byte[0]));
+ zf.add("x/abc", new ByteArrayInputStream(new byte[]{2}));
+
+ ManifestGenerationExtension extension =
+ new ManifestGenerationExtension("Me, of course", "Myself");
+ extension.register(zf);
+
+ zf.update();
+
+ StoredEntry se = zf.get("META-INF/MANIFEST.MF");
+ assertNotNull(se);
+
+ String text = new String(se.read(), Charsets.US_ASCII);
+ text = text.trim();
+ String lines[] = text.split(System.getProperty("line.separator"));
+ assertEquals(3, lines.length);
+
+ assertEquals("Manifest-Version: 1.0", lines[0].trim());
+
+ Set<String> linesSet = Sets.newSet();
+ for (String l : lines) {
+ linesSet.add(l.trim());
+ }
+
+ assertTrue(linesSet.contains("Built-By: Me, of course"));
+ assertTrue(linesSet.contains("Created-By: Myself"));
+ }
+ }
+
+ @Test
+ public void manifestGenerationOnHalfWrittenFile() throws Exception {
+ File zip = new File(mTemporaryFolder.getRoot(), "f.zip");
+ try (Closer closer = Closer.create()) {
+ ZFile zf = closer.register(new ZFile(zip));
+
+ try (InputStream wiki = getClass().getResourceAsStream(WIKI_PATH)) {
+ zf.add("wiki", wiki);
+ }
+
+ ManifestGenerationExtension extension =
+ new ManifestGenerationExtension("Me, of course", "Myself");
+ extension.register(zf);
+
+ zf.close();
+
+ StoredEntry se = zf.get("META-INF/MANIFEST.MF");
+ assertNotNull(se);
+
+ String text = new String(se.read(), Charsets.US_ASCII);
+ text = text.trim();
+ String lines[] = text.split(System.getProperty("line.separator"));
+ assertEquals(3, lines.length);
+
+ assertEquals("Manifest-Version: 1.0", lines[0].trim());
+
+ Set<String> linesSet = Sets.newSet();
+ for (String l : lines) {
+ linesSet.add(l.trim());
+ }
+
+ assertTrue(linesSet.contains("Built-By: Me, of course"));
+ assertTrue(linesSet.contains("Created-By: Myself"));
+ }
+ }
+
+ @Test
+ public void manifestGenerationOnExistingFile() throws Exception {
+ File zip = new File(mTemporaryFolder.getRoot(), "f.zip");
+ try (Closer closer = Closer.create()) {
+ ZFile zf = closer.register(new ZFile(zip));
+
+ try (InputStream wiki = getClass().getResourceAsStream(WIKI_PATH)) {
+ zf.add("wiki", wiki);
+ }
+
+ zf.close();
+
+ ManifestGenerationExtension extension =
+ new ManifestGenerationExtension("Me, of course", "Myself");
+ extension.register(zf);
+
+ zf.close();
+
+ StoredEntry se = zf.get("META-INF/MANIFEST.MF");
+ assertNotNull(se);
+
+ String text = new String(se.read(), Charsets.US_ASCII);
+ text = text.trim();
+ String lines[] = text.split(System.getProperty("line.separator"));
+ assertEquals(3, lines.length);
+
+ assertEquals("Manifest-Version: 1.0", lines[0].trim());
+
+ Set<String> linesSet = Sets.newSet();
+ for (String l : lines) {
+ linesSet.add(l.trim());
+ }
+
+ assertTrue(linesSet.contains("Built-By: Me, of course"));
+ assertTrue(linesSet.contains("Created-By: Myself"));
+ }
+ }
+
+ @Test
+ public void manifestGenerationOnIncrementalNoChanges() throws Exception {
+ File zip = new File(mTemporaryFolder.getRoot(), "f.zip");
+ try (Closer closer = Closer.create()) {
+ ZFile zf = closer.register(new ZFile(zip));
+
+ ManifestGenerationExtension extension =
+ new ManifestGenerationExtension("Me, of course", "Myself");
+ extension.register(zf);
+
+ try (InputStream wiki = getClass().getResourceAsStream(WIKI_PATH)) {
+ zf.add("wiki", wiki);
+ }
+
+ zf.close();
+
+ long timeOfWriting = zip.lastModified();
+
+ ApkZFileTestUtils.waitForFileSystemTick(timeOfWriting);
+
+ zf = closer.register(new ZFile(zip));
+ zf.close();
+
+ long secondTimeOfWriting = zip.lastModified();
+ assertEquals(timeOfWriting, secondTimeOfWriting);
+ }
+ }
+}
diff --git a/src/test/java/com/android/apkzlib/sign/SignatureTestUtils.java b/src/test/java/com/android/apkzlib/sign/SignatureTestUtils.java
new file mode 100644
index 0000000..71610ef
--- /dev/null
+++ b/src/test/java/com/android/apkzlib/sign/SignatureTestUtils.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.apkzlib.sign;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import com.android.annotations.NonNull;
+import com.android.apkzlib.utils.ApkZLibPair;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Date;
+import javax.security.auth.x500.X500Principal;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.junit.Assume;
+
+/**
+ * Utilities to use signatures in tests.
+ */
+public class SignatureTestUtils {
+
+ /**
+ * Generates a private key / certificate for pre-18 systems.
+ *
+ * @return the pair with the private key and certificate
+ * @throws Exception failed to generate the signature data
+ */
+ @NonNull
+ public static ApkZLibPair<PrivateKey, X509Certificate> generateSignaturePre18()
+ throws Exception {
+ return generateSignature("RSA", "SHA1withRSA");
+ }
+
+ /**
+ * Generates a private key / certificate for post-18 systems.
+ *
+ * @return the pair with the private key and certificate
+ * @throws Exception failed to generate the signature data
+ */
+ @NonNull
+ public static ApkZLibPair<PrivateKey, X509Certificate> generateSignaturePos18()
+ throws Exception {
+ return generateSignature("EC", "SHA256withECDSA");
+ }
+
+ /**
+ * Generates a private key / certificate.
+ *
+ * @param sign the asymmetric cypher, <em>e.g.</em>, {@code RSA}
+ * @param full the full signature algorithm name, <em>e.g.</em>, {@code SHA1withRSA}
+ * @return the pair with the private key and certificate
+ * @throws Exception failed to generate the signature data
+ */
+ @NonNull
+ public static ApkZLibPair<PrivateKey, X509Certificate> generateSignature(
+ @NonNull String sign,
+ @NonNull String full)
+ throws Exception {
+ // http://stackoverflow.com/questions/28538785/
+ // easy-way-to-generate-a-self-signed-certificate-for-java-security-keystore-using
+
+ KeyPairGenerator generator = null;
+ try {
+ generator = KeyPairGenerator.getInstance(sign);
+ } catch (NoSuchAlgorithmException e) {
+ Assume.assumeNoException("Algorithm " + sign + " not supported.", e);
+ }
+
+ assertNotNull(generator);
+ KeyPair keyPair = generator.generateKeyPair();
+
+ Date notBefore = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);
+ Date notAfter = new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000);
+
+ X500Name issuer = new X500Name(new X500Principal("cn=Myself").getName());
+
+ SubjectPublicKeyInfo publicKeyInfo;
+
+ if (keyPair.getPublic() instanceof RSAPublicKey) {
+ RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+ publicKeyInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(
+ new RSAKeyParameters(false, rsaPublicKey.getModulus(),
+ rsaPublicKey.getPublicExponent()));
+ } else if (keyPair.getPublic() instanceof ECPublicKey) {
+ publicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
+ } else {
+ fail();
+ publicKeyInfo = null;
+ }
+
+ X509v1CertificateBuilder builder = new X509v1CertificateBuilder(issuer, BigInteger.ONE,
+ notBefore, notAfter, issuer, publicKeyInfo);
+
+ ContentSigner signer = new JcaContentSignerBuilder(full).setProvider(
+ new BouncyCastleProvider()).build(keyPair.getPrivate());
+ X509CertificateHolder holder = builder.build(signer);
+
+ JcaX509CertificateConverter converter = new JcaX509CertificateConverter()
+ .setProvider(new BouncyCastleProvider());
+
+ return new ApkZLibPair(keyPair.getPrivate(), converter.getCertificate(holder));
+ }
+
+}