blob: 359686c500d8e285b1ef33bac37ff14e3f1763b2 [file] [log] [blame]
/*
* 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.tools.build.apkzlib.sign;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import com.android.tools.build.apkzlib.utils.ApkZFileTestUtils;
import com.android.tools.build.apkzlib.utils.ApkZLibPair;
import com.android.tools.build.apkzlib.zip.StoredEntry;
import com.android.tools.build.apkzlib.zip.ZFile;
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)) {
ApkZFileTestUtils.addAndroidManifest(zf);
ManifestGenerationExtension manifestExtension =
new ManifestGenerationExtension("Me", "Me");
manifestExtension.register(zf);
ApkZLibPair<PrivateKey, X509Certificate> p =
SignatureTestUtils.generateSignaturePre18();
new SigningExtension(12, p.v2, p.v1, true, false).register(zf);
}
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)) {
ApkZFileTestUtils.addAndroidManifest(zf1);
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 SigningExtension(10, p.v2, p.v1, true, false).register(zf2);
}
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("LGSOwy4uGcUWoc+ZhS8ukzmf0fY=", 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)) {
ApkZFileTestUtils.addAndroidManifest(zf1);
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 SigningExtension(21, p.v2, p.v1, true, false).register(zf2);
}
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("dBnaLpqNjmUnLlZF4tNqOcDWL8wy8Tsw1ZYFqTZhjIs=",
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)) {
ApkZFileTestUtils.addAndroidManifest(zf);
ManifestGenerationExtension manifestExtension =
new ManifestGenerationExtension("Me", "Me");
manifestExtension.register(zf);
ApkZLibPair<PrivateKey, X509Certificate> p = SignatureTestUtils.generateSignaturePre18();
new SigningExtension(12, p.v2, p.v1, false, true).register(zf);
}
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)) {
ApkZFileTestUtils.addAndroidManifest(zf1);
zf1.add(file1Name, new ByteArrayInputStream(file1Contents));
ManifestGenerationExtension me = new ManifestGenerationExtension(builtBy, createdBy);
me.register(zf1);
new SigningExtension(21, p.v2, p.v1, true, false).register(zf1);
zf1.update();
StoredEntry manifestEntry = zf1.get("META-INF/MANIFEST.MF");
assertNotNull(manifestEntry);
try (InputStream manifestIs = manifestEntry.open()) {
Manifest manifest = new Manifest(manifestIs);
assertEquals(2, 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(2, 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)) {
ApkZFileTestUtils.addAndroidManifest(zf2);
ManifestGenerationExtension me = new ManifestGenerationExtension(builtBy, createdBy);
me.register(zf2);
new SigningExtension(21, p.v2, p.v1, true, false).register(zf2);
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(2, 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)) {
ApkZFileTestUtils.addAndroidManifest(zf);
ManifestGenerationExtension me = new ManifestGenerationExtension("I", "Android");
me.register(zf);
new SigningExtension(21, p.v2, p.v1, true, false).register(zf);
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 SigningExtension(21, p.v2, p.v1, true, false).register(zf);
}
/*
* 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 SigningExtension(21, p.v2, p.v1, true, false).register(zf);
}
/*
* Check the file was touched.
*/
assertNotEquals(fileTimestamp, zipFile.lastModified());
}
}