1 /* 2 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.oracle.tools.packager.mac; 27 28 import com.oracle.tools.packager.AbstractBundler; 29 import com.oracle.tools.packager.Bundler; 30 import com.oracle.tools.packager.BundlerParamInfo; 31 import com.oracle.tools.packager.ConfigException; 32 import com.oracle.tools.packager.IOUtils; 33 import com.oracle.tools.packager.Log; 34 import com.oracle.tools.packager.RelativeFileSet; 35 import com.oracle.tools.packager.UnsupportedPlatformException; 36 import org.junit.After; 37 import org.junit.Assume; 38 import org.junit.Before; 39 import org.junit.BeforeClass; 40 import org.junit.Ignore; 41 import org.junit.Test; 42 43 import java.io.File; 44 import java.io.IOException; 45 import java.nio.file.Files; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.Collection; 49 import java.util.HashMap; 50 import java.util.HashSet; 51 import java.util.List; 52 import java.util.Map; 53 import java.util.Set; 54 import java.util.TreeMap; 55 56 import static com.oracle.tools.packager.StandardBundlerParam.*; 57 import static com.oracle.tools.packager.mac.MacAppBundler.*; 58 import static com.oracle.tools.packager.mac.MacBaseInstallerBundler.SIGNING_KEYCHAIN; 59 import static com.oracle.tools.packager.mac.MacPkgBundler.DEVELOPER_ID_INSTALLER_SIGNING_KEY; 60 import static org.junit.Assert.*; 61 62 public class MacAppBundlerTest { 63 64 static File tmpBase; 65 static File workDir; 66 static File appResourcesDir; 67 static File fakeMainJar; 68 static File hdpiIcon; 69 static String runtimeJdk; 70 static String runtimeJre; 71 static Set<File> appResources; 72 static boolean retain = false; 73 static boolean signingKeysPresent = false; 74 75 static final File FAKE_CERT_ROOT = new File("build/tmp/tests/cert/").getAbsoluteFile(); 76 77 @BeforeClass 78 public static void prepareApp() { 79 // only run on mac 80 Assume.assumeTrue(System.getProperty("os.name").toLowerCase().contains("os x")); 81 82 runtimeJdk = System.getenv("PACKAGER_JDK_ROOT"); 83 runtimeJre = System.getenv("PACKAGER_JRE_ROOT"); 84 85 // and only if we have the correct JRE settings 86 String jre = System.getProperty("java.home").toLowerCase(); 87 Assume.assumeTrue(runtimeJdk != null || jre.endsWith("/contents/home/jre") || jre.endsWith("/contents/home/jre")); 88 89 Log.setLogger(new Log.Logger(true)); 90 Log.setDebug(true); 91 92 retain = Boolean.parseBoolean(System.getProperty("RETAIN_PACKAGER_TESTS")); 93 94 workDir = new File("build/tmp/tests", "macapp"); 95 hdpiIcon = new File("build/tmp/tests", "GenericAppHiDPI.icns"); 96 appResourcesDir = new File("build/tmp/tests", "appResources"); 97 fakeMainJar = new File(appResourcesDir, "mainApp.jar"); 98 99 appResources = new HashSet<>(Arrays.asList(fakeMainJar)); 100 101 signingKeysPresent = DEVELOPER_ID_INSTALLER_SIGNING_KEY.fetchFrom(new TreeMap<>()) != null; 102 } 103 104 @Before 105 public void createTmpDir() throws IOException { 106 if (retain) { 107 tmpBase = new File("build/tmp/tests/macapp"); 108 } else { 109 tmpBase = BUILD_ROOT.fetchFrom(new TreeMap<>()); 110 } 111 tmpBase.mkdir(); 112 } 113 114 115 public String createFakeCerts(Map<String, ? super Object> p) { 116 File config = new File(FAKE_CERT_ROOT, "app-cert.cfg"); 117 config.getParentFile().mkdirs(); 118 try { 119 // create the config file holding the key config 120 Files.write(config.toPath(), Arrays.<String>asList("[ codesign ]", 121 "keyUsage=critical,digitalSignature", 122 "basicConstraints=critical,CA:false", 123 "extendedKeyUsage=critical,codeSigning")); 124 125 // create the SSL keys 126 ProcessBuilder pb = new ProcessBuilder("openssl", "req", 127 "-newkey", "rsa:2048", 128 "-nodes", 129 "-out", FAKE_CERT_ROOT + "/app.csr", 130 "-keyout", FAKE_CERT_ROOT + "/app.key", 131 "-subj", "/CN=Developer ID Application: Insecure Test Cert/OU=JavaFX Dev/O=Oracle/C=US"); 132 IOUtils.exec(pb, VERBOSE.fetchFrom(p)); 133 134 // create the cert 135 pb = new ProcessBuilder("openssl", "x509", 136 "-req", 137 "-days", "1", 138 "-in", FAKE_CERT_ROOT + "/app.csr", 139 "-signkey", FAKE_CERT_ROOT + "/app.key", 140 "-out", FAKE_CERT_ROOT + "/app.crt", 141 "-extfile", FAKE_CERT_ROOT + "/app-cert.cfg", 142 "-extensions", "codesign"); 143 IOUtils.exec(pb, VERBOSE.fetchFrom(p)); 144 145 // create and add it to the keychain 146 pb = new ProcessBuilder("certtool", 147 "i", FAKE_CERT_ROOT + "/app.crt", 148 "k=" + FAKE_CERT_ROOT + "/app.keychain", 149 "r=" + FAKE_CERT_ROOT + "/app.key", 150 "c", 151 "p="); 152 IOUtils.exec(pb, VERBOSE.fetchFrom(p)); 153 154 return FAKE_CERT_ROOT + "/app.keychain"; 155 } catch (IOException e) { 156 e.printStackTrace(); 157 } 158 159 return null; 160 } 161 162 @After 163 public void maybeCleanupTmpDir() { 164 if (!retain) { 165 attemptDelete(tmpBase); 166 } 167 attemptDelete(FAKE_CERT_ROOT); 168 } 169 170 private void attemptDelete(File tmpBase) { 171 if (tmpBase.isDirectory()) { 172 File[] children = tmpBase.listFiles(); 173 if (children != null) { 174 for (File f : children) { 175 attemptDelete(f); 176 } 177 } 178 } 179 boolean success; 180 try { 181 success = !tmpBase.exists() || tmpBase.delete(); 182 } catch (SecurityException se) { 183 success = false; 184 } 185 if (!success) { 186 System.err.println("Could not clean up " + tmpBase.toString()); 187 } 188 } 189 190 191 @Test 192 public void testValidateVersion() { 193 MacAppBundler b = new MacAppBundler(); 194 String validVersions[] = {"1", "255", "1.0", "1.0.0", "255.255.0", "255.255.6000"}; 195 String invalidVersions[] = {null, "alpha", "1.0-alpha", "0.300", "-300", "1.-1", "1.1.-1"}; 196 197 for(String v: validVersions) { 198 assertTrue("Expect to be valid ["+v+"]", 199 MacAppBundler.validCFBundleVersion(v)); 200 try { 201 Map<String, Object> params = new HashMap<>(); 202 params.put(BUILD_ROOT.getID(), tmpBase); 203 params.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 204 205 if (runtimeJdk != null) { 206 params.put(MAC_RUNTIME.getID(), runtimeJdk); 207 } 208 209 params.put(VERSION.getID(), v); 210 b.validate(params); 211 } catch (ConfigException ce) { 212 ce.printStackTrace(); 213 assertTrue("Expect to be valid via '" + VERSION.getID() + "' ["+v+"]", 214 false); 215 } catch (UnsupportedPlatformException ignore) { 216 } 217 try { 218 Map<String, Object> params = new HashMap<>(); 219 params.put(BUILD_ROOT.getID(), tmpBase); 220 params.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 221 222 if (runtimeJdk != null) { 223 params.put(MAC_RUNTIME.getID(), runtimeJdk); 224 } 225 226 params.put(MAC_CF_BUNDLE_VERSION.getID(), v); 227 b.validate(params); 228 } catch (ConfigException ce) { 229 assertTrue("Expect to be valid via '" + VERSION.getID() + "' ["+v+"]", 230 false); 231 } catch (UnsupportedPlatformException ignore) { 232 } 233 } 234 235 for(String v: invalidVersions) { 236 assertFalse("Expect to be invalid ["+v+"]", 237 MacAppBundler.validCFBundleVersion(v)); 238 try { 239 Map<String, Object> params = new HashMap<>(); 240 params.put(BUILD_ROOT.getID(), tmpBase); 241 params.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 242 243 if (runtimeJdk != null) { 244 params.put(MAC_RUNTIME.getID(), runtimeJdk); 245 } 246 247 params.put(VERSION.getID(), v); 248 b.validate(params); 249 assertFalse("Invalid appVersion is not the mac.CFBundleVersion", MAC_CF_BUNDLE_VERSION.fetchFrom(params).equals(VERSION.fetchFrom(params))); 250 } catch (ConfigException ce) { 251 ce.printStackTrace(); 252 assertTrue("Expect to be ignored when invalid via '" + VERSION.getID() + "' ["+v+"]", 253 false); 254 } catch (UnsupportedPlatformException ignore) { 255 } 256 try { 257 Map<String, Object> params = new HashMap<>(); 258 params.put(BUILD_ROOT.getID(), tmpBase); 259 params.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 260 261 if (runtimeJdk != null) { 262 params.put(MAC_RUNTIME.getID(), runtimeJdk); 263 } 264 265 params.put(MAC_CF_BUNDLE_VERSION.getID(), v); 266 b.validate(params); 267 assertTrue("Expect to be invalid via '" + VERSION.getID() + "' ["+v+"]", 268 false); 269 } catch (ConfigException | UnsupportedPlatformException ignore) { 270 } 271 } 272 } 273 274 275 /** 276 * See if smoke comes out 277 */ 278 @Test 279 public void smokeTest() throws IOException, ConfigException, UnsupportedPlatformException { 280 AbstractBundler bundler = new MacAppBundler(); 281 282 assertNotNull(bundler.getName()); 283 assertNotNull(bundler.getID()); 284 assertNotNull(bundler.getDescription()); 285 //assertNotNull(bundler.getBundleParameters()); 286 287 Map<String, Object> bundleParams = new HashMap<>(); 288 289 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 290 291 bundleParams.put(APP_NAME.getID(), "Smoke Test App"); 292 bundleParams.put(MAC_CF_BUNDLE_NAME.getID(), "Smoke"); 293 bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle"); 294 bundleParams.put(PREFERENCES_ID.getID(), "the/really/long/preferences/id"); 295 bundleParams.put(MAIN_JAR.getID(), 296 new RelativeFileSet(fakeMainJar.getParentFile(), 297 new HashSet<>(Arrays.asList(fakeMainJar))) 298 ); 299 bundleParams.put(CLASSPATH.getID(), "mainApp.jar"); 300 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 301 bundleParams.put(VERBOSE.getID(), true); 302 bundleParams.put(SIGN_BUNDLE.getID(), false); 303 304 if (runtimeJdk != null) { 305 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk); 306 } 307 308 boolean valid = bundler.validate(bundleParams); 309 assertTrue(valid); 310 311 File result = bundler.execute(bundleParams, new File(workDir, "smoke")); 312 System.err.println("Bundle at - " + result); 313 assertNotNull(result); 314 assertTrue(result.exists()); 315 } 316 317 /** 318 * Set File Association 319 */ 320 @Test 321 public void testFileAssociation() 322 throws IOException, ConfigException, UnsupportedPlatformException 323 { 324 // only run the bundle with full tests 325 Assume.assumeTrue(Boolean.parseBoolean(System.getProperty("FULL_TEST"))); 326 327 testFileAssociation("FASmoke 1", "Bogus File", "bogus", "application/x-vnd.test-bogus", 328 new File(appResourcesDir, "test.icns")); 329 } 330 331 @Test 332 public void testFileAssociationWithNullExtension() 333 throws IOException, ConfigException, UnsupportedPlatformException 334 { 335 // association with no extension is still valid case (see RT-38625) 336 testFileAssociation("FASmoke null", "Bogus File", null, "application/x-vnd.test-bogus", 337 new File(appResourcesDir, "test.icns")); 338 } 339 340 @Test 341 public void testFileAssociationWithMultipleExtension() 342 throws IOException, ConfigException, UnsupportedPlatformException 343 { 344 // only run the bundle with full tests 345 Assume.assumeTrue(Boolean.parseBoolean(System.getProperty("FULL_TEST"))); 346 347 testFileAssociation("FASmoke ME", "Bogus File", "bogus fake", "application/x-vnd.test-bogus", 348 new File(appResourcesDir, "test.icns")); 349 } 350 351 @Test 352 public void testMultipleFileAssociation() 353 throws IOException, ConfigException, UnsupportedPlatformException 354 { 355 // only run the bundle with full tests 356 Assume.assumeTrue(Boolean.parseBoolean(System.getProperty("FULL_TEST"))); 357 358 testFileAssociationMultiples("FASmoke MA", 359 new String[]{"Bogus File", "Fake file"}, 360 new String[]{"bogus", "fake"}, 361 new String[]{"application/x-vnd.test-bogus", "application/x-vnd.test-fake"}, 362 new File[]{new File(appResourcesDir, "test.icns"), new File(appResourcesDir, "test.icns")}); 363 } 364 365 @Test 366 public void testMultipleFileAssociationWithMultipleExtension() 367 throws IOException, ConfigException, UnsupportedPlatformException 368 { 369 // association with no extension is still valid case (see RT-38625) 370 testFileAssociationMultiples("FASmoke MAME", 371 new String[]{"Bogus File", "Fake file"}, 372 new String[]{"bogus boguser", "fake faker"}, 373 new String[]{"application/x-vnd.test-bogus", "application/x-vnd.test-fake"}, 374 new File[]{new File(appResourcesDir, "test.icns"), new File(appResourcesDir, "test.icns")}); 375 } 376 377 private void testFileAssociation(String appName, String description, String extensions, 378 String contentType, File icon) 379 throws IOException, ConfigException, UnsupportedPlatformException 380 { 381 testFileAssociationMultiples(appName, new String[] {description}, new String[] {extensions}, 382 new String[] {contentType}, new File[] {icon}); 383 } 384 385 private void testFileAssociationMultiples(String appName, String[] description, String[] extensions, 386 String[] contentType, File[] icon) 387 throws IOException, ConfigException, UnsupportedPlatformException 388 { 389 assertEquals("Sanity: description same length as extensions", description.length, extensions.length); 390 assertEquals("Sanity: extensions same length as contentType", extensions.length, contentType.length); 391 assertEquals("Sanity: contentType same length as icon", contentType.length, icon.length); 392 393 AbstractBundler bundler = new MacAppBundler(); 394 395 assertNotNull(bundler.getName()); 396 assertNotNull(bundler.getID()); 397 assertNotNull(bundler.getDescription()); 398 //assertNotNull(bundler.getBundleParameters()); 399 400 Map<String, Object> bundleParams = new HashMap<>(); 401 402 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 403 404 if (runtimeJdk != null) { 405 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk); 406 } 407 408 bundleParams.put(APP_NAME.getID(), appName); 409 bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle"); 410 bundleParams.put(MAIN_JAR.getID(), 411 new RelativeFileSet(fakeMainJar.getParentFile(), 412 new HashSet<>(Arrays.asList(fakeMainJar))) 413 ); 414 bundleParams.put(CLASSPATH.getID(), "mainApp.jar"); 415 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 416 bundleParams.put(VERBOSE.getID(), true); 417 bundleParams.put(SIGN_BUNDLE.getID(), false); 418 419 List<Map<String, Object>> associations = new ArrayList<>(); 420 421 for (int i = 0; i < description.length; i++) { 422 Map<String, Object> fileAssociation = new HashMap<>(); 423 fileAssociation.put(FA_DESCRIPTION.getID(), description[i]); 424 fileAssociation.put(FA_EXTENSIONS.getID(), extensions[i]); 425 fileAssociation.put(FA_CONTENT_TYPE.getID(), contentType[i]); 426 fileAssociation.put(FA_ICON.getID(), icon[i]); 427 428 associations.add(fileAssociation); 429 } 430 431 bundleParams.put(FILE_ASSOCIATIONS.getID(), associations); 432 433 boolean valid = bundler.validate(bundleParams); 434 assertTrue(valid); 435 436 File result = bundler.execute(bundleParams, new File(workDir, APP_FS_NAME.fetchFrom(bundleParams))); 437 System.err.println("Bundle at - " + result); 438 assertNotNull(result); 439 assertTrue(result.exists()); 440 } 441 442 /** 443 * Build signed smoke test and mark it as quarantined, skip if no keys present 444 */ 445 @Ignore("JDK-8193232") 446 @Test 447 public void quarantinedAppTest() throws IOException, ConfigException, UnsupportedPlatformException { 448 AbstractBundler bundler = new MacAppBundler(); 449 450 assertNotNull(bundler.getName()); 451 assertNotNull(bundler.getID()); 452 assertNotNull(bundler.getDescription()); 453 //assertNotNull(bundler.getBundleParameters()); 454 455 Map<String, Object> bundleParams = new HashMap<>(); 456 457 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 458 459 bundleParams.put(APP_NAME.getID(), "Quarantined Test App"); 460 bundleParams.put(MAC_CF_BUNDLE_NAME.getID(), "Quarantine"); 461 bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle"); 462 bundleParams.put(PREFERENCES_ID.getID(), "the/really/long/preferences/id"); 463 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 464 bundleParams.put(VERBOSE.getID(), true); 465 466 if (runtimeJdk != null) { 467 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk); 468 } 469 470 if (!signingKeysPresent) { 471 String keychain = createFakeCerts(bundleParams); 472 Assume.assumeNotNull(keychain); 473 bundleParams.put(SIGNING_KEYCHAIN.getID(), keychain); 474 } 475 476 boolean valid = bundler.validate(bundleParams); 477 assertTrue(valid); 478 479 File result = bundler.execute(bundleParams, new File(workDir, "quarantine")); 480 System.err.println("Bundle at - " + result); 481 assertNotNull(result); 482 assertTrue(result.exists()); 483 validateSignatures(result); 484 485 // mark it as though it's been downloaded 486 ProcessBuilder pb = new ProcessBuilder( 487 "xattr", "-w", "com.apple.quarantine", 488 "0000;" + Long.toHexString(System.currentTimeMillis() / 1000L) + ";Java Unit Tests;|com.oracle.jvm.8u", 489 result.toString()); 490 IOUtils.exec(pb, true); 491 } 492 493 /** 494 * The bare minimum configuration needed to make it work 495 * <ul> 496 * <li>Where to build it</li> 497 * <li>The jar containing the application (with a main-class attribute)</li> 498 * </ul> 499 * 500 * All other values will be driven off of those two values. 501 */ 502 @Ignore("JDK-8193232") 503 @Test 504 public void minimumConfig() throws IOException, ConfigException, UnsupportedPlatformException { 505 Bundler bundler = new MacAppBundler(); 506 507 Map<String, Object> bundleParams = new HashMap<>(); 508 509 // not part of the typical setup, for testing 510 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 511 512 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 513 514 if (runtimeJdk != null) { 515 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk); 516 } 517 518 String keychain = null; 519 if (!signingKeysPresent) { 520 keychain = createFakeCerts(bundleParams); 521 if (keychain != null) { 522 bundleParams.put(SIGNING_KEYCHAIN.getID(), keychain); 523 } 524 } 525 526 File output = bundler.execute(bundleParams, new File(workDir, "BareMinimum")); 527 System.err.println("Bundle at - " + output); 528 assertNotNull(output); 529 assertTrue(output.exists()); 530 if (signingKeysPresent || keychain != null) { 531 validateSignatures(output); 532 } 533 } 534 535 /** 536 * Test with unicode in places we expect it to be 537 */ 538 @Ignore("JDK-8163859") 539 @Test 540 public void unicodeConfig() throws IOException, ConfigException, UnsupportedPlatformException { 541 Bundler bundler = new MacAppBundler(); 542 543 Map<String, Object> bundleParams = new HashMap<>(); 544 545 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 546 547 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 548 549 bundleParams.put(APP_NAME.getID(), "хелловорлд"); 550 bundleParams.put(TITLE.getID(), "ХеллоВорлд аппликейшн"); 551 bundleParams.put(VENDOR.getID(), "Оракл девелопмент"); 552 bundleParams.put(DESCRIPTION.getID(), "крайне большое описание со странными символами"); 553 554 if (runtimeJdk != null) { 555 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk); 556 } 557 558 String keychain = null; 559 if (!signingKeysPresent) { 560 keychain = createFakeCerts(bundleParams); 561 if (keychain != null) { 562 bundleParams.put(SIGNING_KEYCHAIN.getID(), keychain); 563 } 564 } 565 566 bundler.validate(bundleParams); 567 568 File output = bundler.execute(bundleParams, new File(workDir, "Unicode")); 569 System.err.println("Bundle at - " + output); 570 assertNotNull(output); 571 assertTrue(output.exists()); 572 if (signingKeysPresent || keychain != null) { 573 validateSignatures(output); 574 } 575 } 576 577 /** 578 * Test a misconfiguration where the runtime is misconfigured. 579 */ 580 @Test(expected = ConfigException.class) 581 public void runtimeBad() throws IOException, ConfigException, UnsupportedPlatformException { 582 Bundler bundler = new MacAppBundler(); 583 584 Map<String, Object> bundleParams = new HashMap<>(); 585 586 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 587 bundleParams.put(VERBOSE.getID(), true); 588 589 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 590 bundleParams.put(MAC_RUNTIME.getID(), APP_RESOURCES.fetchFrom(bundleParams)); 591 592 bundler.validate(bundleParams); 593 } 594 595 /** 596 * Test a misconfiguration where signature is requested but no key is kept. 597 */ 598 @Test(expected = ConfigException.class) 599 public void signButNoCert() throws IOException, ConfigException, UnsupportedPlatformException { 600 Bundler bundler = new MacAppBundler(); 601 602 Map<String, Object> bundleParams = new HashMap<>(); 603 604 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 605 bundleParams.put(VERBOSE.getID(), true); 606 607 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 608 609 bundleParams.put(SIGN_BUNDLE.getID(), true); 610 bundleParams.put(DEVELOPER_ID_APP_SIGNING_KEY.getID(), null); 611 612 bundler.validate(bundleParams); 613 } 614 615 @Test 616 public void configureEverything() throws Exception { 617 AbstractBundler bundler = new MacAppBundler(); 618 Collection<BundlerParamInfo<?>> parameters = bundler.getBundleParameters(); 619 620 Map<String, Object> bundleParams = new HashMap<>(); 621 622 bundleParams.put(APP_NAME.getID(), "Everything App Name"); 623 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 624 bundleParams.put(ARGUMENTS.getID(), Arrays.asList("He Said", "She Said")); 625 bundleParams.put(BUNDLE_ID_SIGNING_PREFIX.getID(), "everything.signing.prefix."); 626 bundleParams.put(CLASSPATH.getID(), "mainApp.jar"); 627 bundleParams.put(DEVELOPER_ID_APP_SIGNING_KEY.getID(), "Developer ID Application"); 628 bundleParams.put(ICON_ICNS.getID(), hdpiIcon); 629 bundleParams.put(JVM_OPTIONS.getID(), "-Xms128M"); 630 bundleParams.put(JVM_PROPERTIES.getID(), "everything.jvm.property=everything.jvm.property.value"); 631 bundleParams.put(MAC_CATEGORY.getID(), "public.app-category.developer-tools"); 632 bundleParams.put(MAC_CF_BUNDLE_IDENTIFIER.getID(), "com.example.everything.cf-bundle-identifier"); 633 bundleParams.put(MAC_CF_BUNDLE_NAME.getID(), "Everything CF Bundle Name"); 634 bundleParams.put(MAC_CF_BUNDLE_VERSION.getID(), "8.2.0"); 635 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk == null ? System.getProperty("java.home") : runtimeJdk); 636 bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle"); 637 bundleParams.put(MAIN_JAR.getID(), "mainApp.jar"); 638 bundleParams.put(PREFERENCES_ID.getID(), "everything/preferences/id"); 639 bundleParams.put(PRELOADER_CLASS.getID(), "hello.HelloPreloader"); 640 bundleParams.put(SIGNING_KEYCHAIN.getID(), ""); 641 bundleParams.put(USER_JVM_OPTIONS.getID(), "-Xmx=256M\n"); 642 bundleParams.put(VERSION.getID(), "1.2.3.4"); 643 644 // assert they are set 645 for (BundlerParamInfo bi :parameters) { 646 assertTrue("Bundle args should contain " + bi.getID(), bundleParams.containsKey(bi.getID())); 647 } 648 649 // and only those are set 650 bundleParamLoop: 651 for (String s :bundleParams.keySet()) { 652 for (BundlerParamInfo<?> bpi : parameters) { 653 if (s.equals(bpi.getID())) { 654 continue bundleParamLoop; 655 } 656 } 657 fail("Enumerated parameters does not contain " + s); 658 } 659 660 // assert they resolve 661 for (BundlerParamInfo bi :parameters) { 662 bi.fetchFrom(bundleParams); 663 } 664 665 // add verbose now that we are done scoping out parameters 666 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 667 bundleParams.put(VERBOSE.getID(), true); 668 bundleParams.put(SIGN_BUNDLE.getID(), false); 669 670 // assert it validates 671 boolean valid = bundler.validate(bundleParams); 672 assertTrue(valid); 673 674 // only run the bundle with full tests 675 Assume.assumeTrue(Boolean.parseBoolean(System.getProperty("FULL_TEST"))); 676 677 // but first remove signing keys, test servers don't have these... 678 bundleParams.remove(DEVELOPER_ID_APP_SIGNING_KEY.getID()); 679 680 File result = bundler.execute(bundleParams, new File(workDir, "everything")); 681 System.err.println("Bundle at - " + result); 682 assertNotNull(result); 683 assertTrue(result.exists()); 684 } 685 686 @Ignore // this test is noisy and only valid for by-hand validation 687 @Test 688 public void jvmUserOptionsTest() throws IOException, ConfigException, UnsupportedPlatformException { 689 690 for (String name : Arrays.asList("", "example", "com.example", "com.example.helloworld", "com.example.hello.world", "com.example.hello.world.app")) { 691 692 AbstractBundler bundler = new MacAppBundler(); 693 694 Map<String, Object> bundleParams = new HashMap<>(); 695 696 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 697 698 bundleParams.put(APP_NAME.getID(), "User JVM Options App - " + name); 699 bundleParams.put(MAC_CF_BUNDLE_NAME.getID(), name + ".application"); 700 bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle"); 701 bundleParams.put(IDENTIFIER.getID(), name); 702 bundleParams.put(PREFERENCES_ID.getID(), name.replace(".", "/")); 703 bundleParams.put(MAIN_JAR.getID(), 704 new RelativeFileSet(fakeMainJar.getParentFile(), 705 new HashSet<>(Arrays.asList(fakeMainJar))) 706 ); 707 bundleParams.put(CLASSPATH.getID(), "mainApp.jar"); 708 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 709 bundleParams.put(VERBOSE.getID(), true); 710 bundleParams.put(SIGN_BUNDLE.getID(), false); // force no signing 711 712 if (runtimeJdk != null) { 713 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk); 714 } 715 716 boolean valid = bundler.validate(bundleParams); 717 assertTrue(valid); 718 719 File result = bundler.execute(bundleParams, new File(workDir, "UserOpts-" + name.replace(".", "-"))); 720 System.err.println("Bundle at - " + result); 721 assertNotNull(result); 722 assertTrue(result.exists()); 723 } 724 } 725 726 727 /** 728 * User a JRE instead of a JDK 729 */ 730 @Ignore("JDK-8193232") 731 @Test 732 public void testJRE() throws IOException, ConfigException, UnsupportedPlatformException { 733 String jre = runtimeJre == null ? "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/" : runtimeJre; 734 Assume.assumeTrue(new File(jre).isDirectory()); 735 736 Bundler bundler = new MacAppBundler(); 737 738 Map<String, Object> bundleParams = new HashMap<>(); 739 740 // not part of the typical setup, for testing 741 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 742 743 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 744 bundleParams.put(MAC_RUNTIME.getID(), jre); 745 746 String keychain = null; 747 if (!signingKeysPresent) { 748 keychain = createFakeCerts(bundleParams); 749 if (keychain != null) { 750 bundleParams.put(SIGNING_KEYCHAIN.getID(), keychain); 751 } 752 } 753 754 boolean valid = bundler.validate(bundleParams); 755 assertTrue(valid); 756 757 File output = bundler.execute(bundleParams, new File(workDir, "JRETest")); 758 System.err.println("Bundle at - " + output); 759 assertNotNull(output); 760 assertTrue(output.exists()); 761 if (signingKeysPresent || keychain != null) { 762 validateSignatures(output); 763 } 764 } 765 766 /** 767 * Turn on AppCDS 768 */ 769 @Ignore@Test 770 public void testAppCDS() throws IOException, ConfigException, UnsupportedPlatformException { 771 Bundler bundler = new MacAppBundler(); 772 773 Map<String, Object> bundleParams = new HashMap<>(); 774 775 // not part of the typical setup, for testing 776 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 777 bundleParams.put(VERBOSE.getID(), true); 778 779 bundleParams.put(APP_NAME.getID(), "AppCDSTest"); 780 bundleParams.put(IDENTIFIER.getID(), "com.example.appcds.Test"); 781 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 782 bundleParams.put(UNLOCK_COMMERCIAL_FEATURES.getID(), true); 783 bundleParams.put(ENABLE_APP_CDS.getID(), true); 784 785 if (runtimeJdk != null) { 786 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk); 787 } 788 789 boolean valid = bundler.validate(bundleParams); 790 assertTrue(valid); 791 792 File output = bundler.execute(bundleParams, new File(workDir, "CDSTest")); 793 System.err.println("Bundle at - " + output); 794 assertNotNull(output); 795 assertTrue(output.exists()); 796 } 797 798 /** 799 * Verify a match on too many keys doesn't blow things up 800 */ 801 @Test 802 public void testTooManyKeyMatches() { 803 Assume.assumeTrue(MacBaseInstallerBundler.findKey("Developer ID Application:", null, true) != null); 804 Assume.assumeTrue(MacBaseInstallerBundler.findKey("Developer ID Installer:", null, true) != null); 805 assertTrue(MacBaseInstallerBundler.findKey("Developer", null, true) == null); 806 assertTrue(MacBaseInstallerBundler.findKey("A completely bogus key that should never realistically exist unless we are attempting to falsely break the tests", null, true) == null); 807 } 808 809 public void validateSignatures(File appLocation) throws IOException { 810 // shallow validation 811 ProcessBuilder pb = new ProcessBuilder( 812 "codesign", "--verify", 813 "-v", // single verbose 814 appLocation.getCanonicalPath()); 815 IOUtils.exec(pb, true); 816 817 // deep validation 818 pb = new ProcessBuilder( 819 "codesign", "--verify", 820 "--deep", 821 "-v", // single verbose 822 appLocation.getCanonicalPath()); 823 IOUtils.exec(pb, true); 824 825 // only run spctl for pre-existing keys 826 if (signingKeysPresent) { 827 //spctl, this verifies gatekeeper 828 pb = new ProcessBuilder( 829 "spctl", "--assess", 830 "-v", // single verbose 831 appLocation.getCanonicalPath()); 832 IOUtils.exec(pb, true); 833 } 834 } 835 836 837 }