Home | History | Annotate | Download | only in sign
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tools.build.apkzlib.sign;
     18 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertNotEquals;
     21 import static org.junit.Assert.assertNotNull;
     22 
     23 import com.android.tools.build.apkzlib.utils.ApkZFileTestUtils;
     24 import com.android.tools.build.apkzlib.utils.ApkZLibPair;
     25 import com.android.tools.build.apkzlib.zip.StoredEntry;
     26 import com.android.tools.build.apkzlib.zip.ZFile;
     27 import com.google.common.base.Charsets;
     28 import com.google.common.hash.Hashing;
     29 import java.io.ByteArrayInputStream;
     30 import java.io.File;
     31 import java.io.InputStream;
     32 import java.security.PrivateKey;
     33 import java.security.cert.X509Certificate;
     34 import java.util.Base64;
     35 import java.util.jar.Attributes;
     36 import java.util.jar.Manifest;
     37 import org.junit.Rule;
     38 import org.junit.Test;
     39 import org.junit.rules.TemporaryFolder;
     40 
     41 public class JarSigningTest {
     42 
     43     @Rule
     44     public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
     45 
     46     @Test
     47     public void signEmptyJar() throws Exception {
     48         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
     49 
     50         try (ZFile zf = new ZFile(zipFile)) {
     51             ApkZFileTestUtils.addAndroidManifest(zf);
     52             ManifestGenerationExtension manifestExtension =
     53                     new ManifestGenerationExtension("Me", "Me");
     54             manifestExtension.register(zf);
     55 
     56             ApkZLibPair<PrivateKey, X509Certificate> p =
     57                     SignatureTestUtils.generateSignaturePre18();
     58 
     59             new SigningExtension(12, p.v2, p.v1, true, false).register(zf);
     60         }
     61 
     62         try (ZFile verifyZFile = new ZFile(zipFile)) {
     63             StoredEntry manifestEntry = verifyZFile.get("META-INF/MANIFEST.MF");
     64             assertNotNull(manifestEntry);
     65 
     66             Manifest manifest = new Manifest(new ByteArrayInputStream(manifestEntry.read()));
     67             assertEquals(3, manifest.getMainAttributes().size());
     68             assertEquals("1.0", manifest.getMainAttributes().getValue("Manifest-Version"));
     69             assertEquals("Me", manifest.getMainAttributes().getValue("Created-By"));
     70             assertEquals("Me", manifest.getMainAttributes().getValue("Built-By"));
     71         }
     72     }
     73 
     74     @Test
     75     public void signJarWithPrexistingSimpleTextFilePre18() throws Exception {
     76         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
     77         ApkZLibPair<PrivateKey, X509Certificate> p = SignatureTestUtils.generateSignaturePre18();
     78 
     79         try (ZFile zf1 = new ZFile(zipFile)) {
     80             ApkZFileTestUtils.addAndroidManifest(zf1);
     81             zf1.add("directory/file",
     82                     new ByteArrayInputStream("useless text".getBytes(Charsets.US_ASCII)));
     83         }
     84 
     85         try (ZFile zf2 = new ZFile(zipFile)) {
     86             ManifestGenerationExtension me = new ManifestGenerationExtension("Merry", "Christmas");
     87             me.register(zf2);
     88             new SigningExtension(10, p.v2, p.v1, true, false).register(zf2);
     89         }
     90 
     91         try (ZFile zf3 = new ZFile(zipFile)) {
     92             StoredEntry manifestEntry = zf3.get("META-INF/MANIFEST.MF");
     93             assertNotNull(manifestEntry);
     94 
     95             Manifest manifest = new Manifest(new ByteArrayInputStream(manifestEntry.read()));
     96             assertEquals(3, manifest.getMainAttributes().size());
     97             assertEquals("1.0", manifest.getMainAttributes().getValue("Manifest-Version"));
     98             assertEquals("Merry", manifest.getMainAttributes().getValue("Built-By"));
     99             assertEquals("Christmas", manifest.getMainAttributes().getValue("Created-By"));
    100 
    101             Attributes attrs = manifest.getAttributes("directory/file");
    102             assertNotNull(attrs);
    103             assertEquals(1, attrs.size());
    104             assertEquals("OOQgIEXBissIvva3ydRoaXk29Rk=", attrs.getValue("SHA1-Digest"));
    105 
    106             StoredEntry signatureEntry = zf3.get("META-INF/CERT.SF");
    107             assertNotNull(signatureEntry);
    108 
    109             Manifest signature = new Manifest(new ByteArrayInputStream(signatureEntry.read()));
    110             assertEquals(3, signature.getMainAttributes().size());
    111             assertEquals("1.0", signature.getMainAttributes().getValue("Signature-Version"));
    112             assertEquals("1.0 (Android)", signature.getMainAttributes().getValue("Created-By"));
    113 
    114             byte[] manifestTextBytes = manifestEntry.read();
    115             byte[] manifestSha1Bytes = Hashing.sha1().hashBytes(manifestTextBytes).asBytes();
    116             String manifestSha1 = Base64.getEncoder().encodeToString(manifestSha1Bytes);
    117 
    118             assertEquals(manifestSha1,
    119                     signature.getMainAttributes().getValue("SHA1-Digest-Manifest"));
    120 
    121             Attributes signAttrs = signature.getAttributes("directory/file");
    122             assertNotNull(signAttrs);
    123             assertEquals(1, signAttrs.size());
    124             assertEquals("LGSOwy4uGcUWoc+ZhS8ukzmf0fY=", signAttrs.getValue("SHA1-Digest"));
    125 
    126             StoredEntry rsaEntry = zf3.get("META-INF/CERT.RSA");
    127             assertNotNull(rsaEntry);
    128         }
    129     }
    130 
    131     @Test
    132     public void signJarWithPrexistingSimpleTextFilePos18() throws Exception {
    133         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
    134         try (ZFile zf1 = new ZFile(zipFile)) {
    135             ApkZFileTestUtils.addAndroidManifest(zf1);
    136             zf1.add("directory/file", new ByteArrayInputStream("useless text".getBytes(
    137                     Charsets.US_ASCII)));
    138         }
    139 
    140         ApkZLibPair<PrivateKey, X509Certificate> p = SignatureTestUtils.generateSignaturePos18();
    141 
    142         try (ZFile zf2 = new ZFile(zipFile)) {
    143             ManifestGenerationExtension me = new ManifestGenerationExtension("Merry", "Christmas");
    144             me.register(zf2);
    145             new SigningExtension(21, p.v2, p.v1, true, false).register(zf2);
    146         }
    147 
    148         try (ZFile zf3 = new ZFile(zipFile)) {
    149             StoredEntry manifestEntry = zf3.get("META-INF/MANIFEST.MF");
    150             assertNotNull(manifestEntry);
    151 
    152             Manifest manifest = new Manifest(new ByteArrayInputStream(manifestEntry.read()));
    153             assertEquals(3, manifest.getMainAttributes().size());
    154             assertEquals("1.0", manifest.getMainAttributes().getValue("Manifest-Version"));
    155             assertEquals("Merry", manifest.getMainAttributes().getValue("Built-By"));
    156             assertEquals("Christmas", manifest.getMainAttributes().getValue("Created-By"));
    157 
    158             Attributes attrs = manifest.getAttributes("directory/file");
    159             assertNotNull(attrs);
    160             assertEquals(1, attrs.size());
    161             assertEquals("QjupZsopQM/01O6+sWHqH64ilMmoBEtljg9VEqN6aI4=",
    162                     attrs.getValue("SHA-256-Digest"));
    163 
    164             StoredEntry signatureEntry = zf3.get("META-INF/CERT.SF");
    165             assertNotNull(signatureEntry);
    166 
    167             Manifest signature = new Manifest(new ByteArrayInputStream(signatureEntry.read()));
    168             assertEquals(3, signature.getMainAttributes().size());
    169             assertEquals("1.0", signature.getMainAttributes().getValue("Signature-Version"));
    170             assertEquals("1.0 (Android)", signature.getMainAttributes().getValue("Created-By"));
    171 
    172             byte[] manifestTextBytes = manifestEntry.read();
    173             byte[] manifestSha256Bytes = Hashing.sha256().hashBytes(manifestTextBytes).asBytes();
    174             String manifestSha256 = Base64.getEncoder().encodeToString(manifestSha256Bytes);
    175 
    176             assertEquals(manifestSha256, signature.getMainAttributes().getValue(
    177                     "SHA-256-Digest-Manifest"));
    178 
    179             Attributes signAttrs = signature.getAttributes("directory/file");
    180             assertNotNull(signAttrs);
    181             assertEquals(1, signAttrs.size());
    182             assertEquals("dBnaLpqNjmUnLlZF4tNqOcDWL8wy8Tsw1ZYFqTZhjIs=",
    183                     signAttrs.getValue("SHA-256-Digest"));
    184 
    185             StoredEntry ecdsaEntry = zf3.get("META-INF/CERT.EC");
    186             assertNotNull(ecdsaEntry);
    187         }
    188     }
    189 
    190     @Test
    191     public void v2SignAddsApkSigningBlock() throws Exception {
    192         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
    193         try (ZFile zf = new ZFile(zipFile)) {
    194             ApkZFileTestUtils.addAndroidManifest(zf);
    195             ManifestGenerationExtension manifestExtension =
    196                     new ManifestGenerationExtension("Me", "Me");
    197             manifestExtension.register(zf);
    198 
    199             ApkZLibPair<PrivateKey, X509Certificate> p = SignatureTestUtils.generateSignaturePre18();
    200 
    201             new SigningExtension(12, p.v2, p.v1, false, true).register(zf);
    202         }
    203 
    204         try (ZFile verifyZFile = new ZFile(zipFile)) {
    205             long centralDirOffset = verifyZFile.getCentralDirectoryOffset();
    206             byte[] apkSigningBlockMagic = new byte[16];
    207             verifyZFile.directFullyRead(
    208                     centralDirOffset - apkSigningBlockMagic.length, apkSigningBlockMagic);
    209             assertEquals("APK Sig Block 42", new String(apkSigningBlockMagic, "US-ASCII"));
    210         }
    211     }
    212 
    213     @Test
    214     public void v1ReSignOnFileChange() throws Exception {
    215         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
    216         ApkZLibPair<PrivateKey, X509Certificate> p = SignatureTestUtils.generateSignaturePos18();
    217 
    218         byte[] file1Contents = "I am a test file".getBytes(Charsets.US_ASCII);
    219         String file1Name = "path/to/file1";
    220         byte[] file1Sha = Hashing.sha256().hashBytes(file1Contents).asBytes();
    221         String file1ShaTxt = Base64.getEncoder().encodeToString(file1Sha);
    222 
    223         String builtBy = "Santa Claus";
    224         String createdBy = "Uses Android";
    225 
    226         try (ZFile zf1 = new ZFile(zipFile)) {
    227             ApkZFileTestUtils.addAndroidManifest(zf1);
    228             zf1.add(file1Name, new ByteArrayInputStream(file1Contents));
    229             ManifestGenerationExtension me = new ManifestGenerationExtension(builtBy, createdBy);
    230             me.register(zf1);
    231             new SigningExtension(21, p.v2, p.v1, true, false).register(zf1);
    232 
    233             zf1.update();
    234 
    235             StoredEntry manifestEntry = zf1.get("META-INF/MANIFEST.MF");
    236             assertNotNull(manifestEntry);
    237 
    238             try (InputStream manifestIs = manifestEntry.open()) {
    239                 Manifest manifest = new Manifest(manifestIs);
    240 
    241                 assertEquals(2, manifest.getEntries().size());
    242 
    243                 Attributes file1Attrs = manifest.getEntries().get(file1Name);
    244                 assertNotNull(file1Attrs);
    245                 assertEquals(file1ShaTxt, file1Attrs.getValue("SHA-256-Digest"));
    246             }
    247 
    248             /*
    249              * Change the file without closing the zip.
    250              */
    251             file1Contents = "I am a modified test file".getBytes(Charsets.US_ASCII);
    252             file1Sha = Hashing.sha256().hashBytes(file1Contents).asBytes();
    253             file1ShaTxt = Base64.getEncoder().encodeToString(file1Sha);
    254 
    255             zf1.add(file1Name, new ByteArrayInputStream(file1Contents));
    256 
    257             zf1.update();
    258 
    259             manifestEntry = zf1.get("META-INF/MANIFEST.MF");
    260             assertNotNull(manifestEntry);
    261 
    262             try (InputStream manifestIs = manifestEntry.open()) {
    263                 Manifest manifest = new Manifest(manifestIs);
    264 
    265                 assertEquals(2, manifest.getEntries().size());
    266 
    267                 Attributes file1Attrs = manifest.getEntries().get(file1Name);
    268                 assertNotNull(file1Attrs);
    269                 assertEquals(file1ShaTxt, file1Attrs.getValue("SHA-256-Digest"));
    270             }
    271         }
    272 
    273         /*
    274          * Change the file closing the zip.
    275          */
    276         file1Contents = "I have changed again!".getBytes(Charsets.US_ASCII);
    277         file1Sha = Hashing.sha256().hashBytes(file1Contents).asBytes();
    278         file1ShaTxt = Base64.getEncoder().encodeToString(file1Sha);
    279 
    280         try (ZFile zf2 = new ZFile(zipFile)) {
    281             ApkZFileTestUtils.addAndroidManifest(zf2);
    282             ManifestGenerationExtension me = new ManifestGenerationExtension(builtBy, createdBy);
    283             me.register(zf2);
    284             new SigningExtension(21, p.v2, p.v1, true, false).register(zf2);
    285 
    286             zf2.add(file1Name, new ByteArrayInputStream(file1Contents));
    287 
    288             zf2.update();
    289 
    290             StoredEntry manifestEntry = zf2.get("META-INF/MANIFEST.MF");
    291             assertNotNull(manifestEntry);
    292 
    293             try (InputStream manifestIs = manifestEntry.open()) {
    294                 Manifest manifest = new Manifest(manifestIs);
    295 
    296                 assertEquals(2, manifest.getEntries().size());
    297 
    298                 Attributes file1Attrs = manifest.getEntries().get(file1Name);
    299                 assertNotNull(file1Attrs);
    300                 assertEquals(file1ShaTxt, file1Attrs.getValue("SHA-256-Digest"));
    301             }
    302         }
    303     }
    304 
    305     @Test
    306     public void openSignedJarDoesNotForcesWriteIfSignatureIsNotCorrect() throws Exception {
    307         File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip");
    308 
    309         ApkZLibPair<PrivateKey, X509Certificate> p = SignatureTestUtils.generateSignaturePos18();
    310 
    311         String fileName = "file";
    312         byte[] fileContents = "Very interesting contents".getBytes(Charsets.US_ASCII);
    313 
    314         try (ZFile zf = new ZFile(zipFile)) {
    315             ApkZFileTestUtils.addAndroidManifest(zf);
    316             ManifestGenerationExtension me = new ManifestGenerationExtension("I", "Android");
    317             me.register(zf);
    318             new SigningExtension(21, p.v2, p.v1, true, false).register(zf);
    319 
    320             zf.add(fileName, new ByteArrayInputStream(fileContents));
    321         }
    322 
    323         long fileTimestamp = zipFile.lastModified();
    324 
    325         ApkZFileTestUtils.waitForFileSystemTick(fileTimestamp);
    326 
    327         /*
    328          * Open the zip file, but don't touch it.
    329          */
    330         try (ZFile zf = new ZFile(zipFile)) {
    331             ManifestGenerationExtension me = new ManifestGenerationExtension("I", "Android");
    332             me.register(zf);
    333             new SigningExtension(21, p.v2, p.v1, true, false).register(zf);
    334         }
    335 
    336         /*
    337          * Check the file wasn't touched.
    338          */
    339         assertEquals(fileTimestamp, zipFile.lastModified());
    340 
    341         /*
    342          * Change the file contents ignoring any signing.
    343          */
    344         fileContents = "Not so interesting contents".getBytes(Charsets.US_ASCII);
    345         try (ZFile zf = new ZFile(zipFile)) {
    346             zf.add(fileName, new ByteArrayInputStream(fileContents));
    347         }
    348 
    349         fileTimestamp = zipFile.lastModified();
    350 
    351         /*
    352          * Wait to make sure the timestamp can increase.
    353          */
    354         while (true) {
    355             File notUsed = mTemporaryFolder.newFile();
    356             long notTimestamp = notUsed.lastModified();
    357             notUsed.delete();
    358             if (notTimestamp > fileTimestamp) {
    359                 break;
    360             }
    361         }
    362 
    363         /*
    364          * Open the zip file, but do any changes. The need to updating the signature should force
    365          * a file update.
    366          */
    367         try (ZFile zf = new ZFile(zipFile)) {
    368             ManifestGenerationExtension me = new ManifestGenerationExtension("I", "Android");
    369             me.register(zf);
    370             new SigningExtension(21, p.v2, p.v1, true, false).register(zf);
    371         }
    372 
    373         /*
    374          * Check the file was touched.
    375          */
    376         assertNotEquals(fileTimestamp, zipFile.lastModified());
    377     }
    378 }
    379