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