1 /* 2 * Copyright (c) 2014, 2015, 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/"); 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.cnf", 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 @Test 446 public void quarantinedAppTest() throws IOException, ConfigException, UnsupportedPlatformException { 447 AbstractBundler bundler = new MacAppBundler(); 448 449 assertNotNull(bundler.getName()); 450 assertNotNull(bundler.getID()); 451 assertNotNull(bundler.getDescription()); 452 //assertNotNull(bundler.getBundleParameters()); 453 454 Map<String, Object> bundleParams = new HashMap<>(); 455 456 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 457 458 bundleParams.put(APP_NAME.getID(), "Quarantined Test App"); 459 bundleParams.put(MAC_CF_BUNDLE_NAME.getID(), "Quarantine"); 460 bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle"); 461 bundleParams.put(PREFERENCES_ID.getID(), "the/really/long/preferences/id"); 462 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 463 bundleParams.put(VERBOSE.getID(), true); 464 465 if (runtimeJdk != null) { 466 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk); 467 } 468 469 if (!signingKeysPresent) { 470 String keychain = createFakeCerts(bundleParams); 471 Assume.assumeNotNull(keychain); 472 bundleParams.put(SIGNING_KEYCHAIN.getID(), keychain); 473 } 474 475 boolean valid = bundler.validate(bundleParams); 476 assertTrue(valid); 477 478 File result = bundler.execute(bundleParams, new File(workDir, "quarantine")); 479 System.err.println("Bundle at - " + result); 480 assertNotNull(result); 481 assertTrue(result.exists()); 482 validateSignatures(result); 483 484 // mark it as though it's been downloaded 485 ProcessBuilder pb = new ProcessBuilder( 486 "xattr", "-w", "com.apple.quarantine", 487 "0000;" + Long.toHexString(System.currentTimeMillis() / 1000L) + ";Java Unit Tests;|com.oracle.jvm.8u", 488 result.toString()); 489 IOUtils.exec(pb, true); 490 } 491 492 /** 493 * The bare minimum configuration needed to make it work 494 * <ul> 495 * <li>Where to build it</li> 496 * <li>The jar containing the application (with a main-class attribute)</li> 497 * </ul> 498 * 499 * All other values will be driven off of those two values. 500 */ 501 @Test 502 public void minimumConfig() throws IOException, ConfigException, UnsupportedPlatformException { 503 Bundler bundler = new MacAppBundler(); 504 505 Map<String, Object> bundleParams = new HashMap<>(); 506 507 // not part of the typical setup, for testing 508 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 509 510 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 511 512 if (runtimeJdk != null) { 513 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk); 514 } 515 516 String keychain = null; 517 if (!signingKeysPresent) { 518 keychain = createFakeCerts(bundleParams); 519 if (keychain != null) { 520 bundleParams.put(SIGNING_KEYCHAIN.getID(), keychain); 521 } 522 } 523 524 File output = bundler.execute(bundleParams, new File(workDir, "BareMinimum")); 525 System.err.println("Bundle at - " + output); 526 assertNotNull(output); 527 assertTrue(output.exists()); 528 if (signingKeysPresent || keychain != null) { 529 validateSignatures(output); 530 } 531 } 532 533 /** 534 * Test with unicode in places we expect it to be 535 */ 536 @Test 537 public void unicodeConfig() throws IOException, ConfigException, UnsupportedPlatformException { 538 Bundler bundler = new MacAppBundler(); 539 540 Map<String, Object> bundleParams = new HashMap<>(); 541 542 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 543 544 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 545 546 bundleParams.put(APP_NAME.getID(), "хелловорлд"); 547 bundleParams.put(TITLE.getID(), "ХеллоВорлд аппликейшн"); 548 bundleParams.put(VENDOR.getID(), "Оракл девелопмент"); 549 bundleParams.put(DESCRIPTION.getID(), "крайне большое описание со странными символами"); 550 551 if (runtimeJdk != null) { 552 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk); 553 } 554 555 String keychain = null; 556 if (!signingKeysPresent) { 557 keychain = createFakeCerts(bundleParams); 558 if (keychain != null) { 559 bundleParams.put(SIGNING_KEYCHAIN.getID(), keychain); 560 } 561 } 562 563 bundler.validate(bundleParams); 564 565 File output = bundler.execute(bundleParams, new File(workDir, "Unicode")); 566 System.err.println("Bundle at - " + output); 567 assertNotNull(output); 568 assertTrue(output.exists()); 569 if (signingKeysPresent || keychain != null) { 570 validateSignatures(output); 571 } 572 } 573 574 /** 575 * Test a misconfiguration where the runtime is misconfigured. 576 */ 577 @Test(expected = ConfigException.class) 578 public void runtimeBad() throws IOException, ConfigException, UnsupportedPlatformException { 579 Bundler bundler = new MacAppBundler(); 580 581 Map<String, Object> bundleParams = new HashMap<>(); 582 583 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 584 bundleParams.put(VERBOSE.getID(), true); 585 586 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 587 bundleParams.put(MAC_RUNTIME.getID(), APP_RESOURCES.fetchFrom(bundleParams)); 588 589 bundler.validate(bundleParams); 590 } 591 592 /** 593 * Test a misconfiguration where signature is requested but no key is kept. 594 */ 595 @Test(expected = ConfigException.class) 596 public void signButNoCert() throws IOException, ConfigException, UnsupportedPlatformException { 597 Bundler bundler = new MacAppBundler(); 598 599 Map<String, Object> bundleParams = new HashMap<>(); 600 601 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 602 bundleParams.put(VERBOSE.getID(), true); 603 604 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 605 606 bundleParams.put(SIGN_BUNDLE.getID(), true); 607 bundleParams.put(DEVELOPER_ID_APP_SIGNING_KEY.getID(), null); 608 609 bundler.validate(bundleParams); 610 } 611 612 @Test 613 public void configureEverything() throws Exception { 614 AbstractBundler bundler = new MacAppBundler(); 615 Collection<BundlerParamInfo<?>> parameters = bundler.getBundleParameters(); 616 617 Map<String, Object> bundleParams = new HashMap<>(); 618 619 bundleParams.put(APP_NAME.getID(), "Everything App Name"); 620 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 621 bundleParams.put(ARGUMENTS.getID(), Arrays.asList("He Said", "She Said")); 622 bundleParams.put(BUNDLE_ID_SIGNING_PREFIX.getID(), "everything.signing.prefix."); 623 bundleParams.put(CLASSPATH.getID(), "mainApp.jar"); 624 bundleParams.put(DEVELOPER_ID_APP_SIGNING_KEY.getID(), "Developer ID Application"); 625 bundleParams.put(ICON_ICNS.getID(), hdpiIcon); 626 bundleParams.put(JVM_OPTIONS.getID(), "-Xms128M"); 627 bundleParams.put(JVM_PROPERTIES.getID(), "everything.jvm.property=everything.jvm.property.value"); 628 bundleParams.put(MAC_CATEGORY.getID(), "public.app-category.developer-tools"); 629 bundleParams.put(MAC_CF_BUNDLE_IDENTIFIER.getID(), "com.example.everything.cf-bundle-identifier"); 630 bundleParams.put(MAC_CF_BUNDLE_NAME.getID(), "Everything CF Bundle Name"); 631 bundleParams.put(MAC_CF_BUNDLE_VERSION.getID(), "8.2.0"); 632 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk == null ? System.getProperty("java.home") : runtimeJdk); 633 bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle"); 634 bundleParams.put(MAIN_JAR.getID(), "mainApp.jar"); 635 bundleParams.put(PREFERENCES_ID.getID(), "everything/preferences/id"); 636 bundleParams.put(PRELOADER_CLASS.getID(), "hello.HelloPreloader"); 637 bundleParams.put(SIGNING_KEYCHAIN.getID(), ""); 638 bundleParams.put(USER_JVM_OPTIONS.getID(), "-Xmx=256M\n"); 639 bundleParams.put(VERSION.getID(), "1.2.3.4"); 640 641 // assert they are set 642 for (BundlerParamInfo bi :parameters) { 643 assertTrue("Bundle args should contain " + bi.getID(), bundleParams.containsKey(bi.getID())); 644 } 645 646 // and only those are set 647 bundleParamLoop: 648 for (String s :bundleParams.keySet()) { 649 for (BundlerParamInfo<?> bpi : parameters) { 650 if (s.equals(bpi.getID())) { 651 continue bundleParamLoop; 652 } 653 } 654 fail("Enumerated parameters does not contain " + s); 655 } 656 657 // assert they resolve 658 for (BundlerParamInfo bi :parameters) { 659 bi.fetchFrom(bundleParams); 660 } 661 662 // add verbose now that we are done scoping out parameters 663 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 664 bundleParams.put(VERBOSE.getID(), true); 665 bundleParams.put(SIGN_BUNDLE.getID(), false); 666 667 // assert it validates 668 boolean valid = bundler.validate(bundleParams); 669 assertTrue(valid); 670 671 // only run the bundle with full tests 672 Assume.assumeTrue(Boolean.parseBoolean(System.getProperty("FULL_TEST"))); 673 674 // but first remove signing keys, test servers don't have these... 675 bundleParams.remove(DEVELOPER_ID_APP_SIGNING_KEY.getID()); 676 677 File result = bundler.execute(bundleParams, new File(workDir, "everything")); 678 System.err.println("Bundle at - " + result); 679 assertNotNull(result); 680 assertTrue(result.exists()); 681 } 682 683 @Ignore // this test is noisy and only valid for by-hand validation 684 @Test 685 public void jvmUserOptionsTest() throws IOException, ConfigException, UnsupportedPlatformException { 686 687 for (String name : Arrays.asList("", "example", "com.example", "com.example.helloworld", "com.example.hello.world", "com.example.hello.world.app")) { 688 689 AbstractBundler bundler = new MacAppBundler(); 690 691 Map<String, Object> bundleParams = new HashMap<>(); 692 693 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 694 695 bundleParams.put(APP_NAME.getID(), "User JVM Options App - " + name); 696 bundleParams.put(MAC_CF_BUNDLE_NAME.getID(), name + ".application"); 697 bundleParams.put(MAIN_CLASS.getID(), "hello.HelloRectangle"); 698 bundleParams.put(IDENTIFIER.getID(), name); 699 bundleParams.put(PREFERENCES_ID.getID(), name.replace(".", "/")); 700 bundleParams.put(MAIN_JAR.getID(), 701 new RelativeFileSet(fakeMainJar.getParentFile(), 702 new HashSet<>(Arrays.asList(fakeMainJar))) 703 ); 704 bundleParams.put(CLASSPATH.getID(), "mainApp.jar"); 705 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 706 bundleParams.put(VERBOSE.getID(), true); 707 bundleParams.put(SIGN_BUNDLE.getID(), false); // force no signing 708 709 if (runtimeJdk != null) { 710 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk); 711 } 712 713 boolean valid = bundler.validate(bundleParams); 714 assertTrue(valid); 715 716 File result = bundler.execute(bundleParams, new File(workDir, "UserOpts-" + name.replace(".", "-"))); 717 System.err.println("Bundle at - " + result); 718 assertNotNull(result); 719 assertTrue(result.exists()); 720 } 721 } 722 723 724 /** 725 * User a JRE instead of a JDK 726 */ 727 @Test 728 public void testJRE() throws IOException, ConfigException, UnsupportedPlatformException { 729 String jre = runtimeJre == null ? "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/" : runtimeJre; 730 Assume.assumeTrue(new File(jre).isDirectory()); 731 732 Bundler bundler = new MacAppBundler(); 733 734 Map<String, Object> bundleParams = new HashMap<>(); 735 736 // not part of the typical setup, for testing 737 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 738 739 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 740 bundleParams.put(MAC_RUNTIME.getID(), jre); 741 742 String keychain = null; 743 if (!signingKeysPresent) { 744 keychain = createFakeCerts(bundleParams); 745 if (keychain != null) { 746 bundleParams.put(SIGNING_KEYCHAIN.getID(), keychain); 747 } 748 } 749 750 boolean valid = bundler.validate(bundleParams); 751 assertTrue(valid); 752 753 File output = bundler.execute(bundleParams, new File(workDir, "JRETest")); 754 System.err.println("Bundle at - " + output); 755 assertNotNull(output); 756 assertTrue(output.exists()); 757 if (signingKeysPresent || keychain != null) { 758 validateSignatures(output); 759 } 760 } 761 762 /** 763 * Turn on AppCDS 764 */ 765 @Test 766 public void testAppCDS() throws IOException, ConfigException, UnsupportedPlatformException { 767 Bundler bundler = new MacAppBundler(); 768 769 Map<String, Object> bundleParams = new HashMap<>(); 770 771 // not part of the typical setup, for testing 772 bundleParams.put(BUILD_ROOT.getID(), tmpBase); 773 bundleParams.put(VERBOSE.getID(), true); 774 775 bundleParams.put(APP_NAME.getID(), "AppCDSTest"); 776 bundleParams.put(IDENTIFIER.getID(), "com.example.appcds.Test"); 777 bundleParams.put(APP_RESOURCES.getID(), new RelativeFileSet(appResourcesDir, appResources)); 778 bundleParams.put(UNLOCK_COMMERCIAL_FEATURES.getID(), true); 779 bundleParams.put(ENABLE_APP_CDS.getID(), true); 780 781 if (runtimeJdk != null) { 782 bundleParams.put(MAC_RUNTIME.getID(), runtimeJdk); 783 } 784 785 boolean valid = bundler.validate(bundleParams); 786 assertTrue(valid); 787 788 File output = bundler.execute(bundleParams, new File(workDir, "CDSTest")); 789 System.err.println("Bundle at - " + output); 790 assertNotNull(output); 791 assertTrue(output.exists()); 792 } 793 794 /** 795 * Verify a match on too many keys doesn't blow things up 796 */ 797 @Test 798 public void testTooManyKeyMatches() { 799 Assume.assumeTrue(MacBaseInstallerBundler.findKey("Developer ID Application:", null, true) != null); 800 Assume.assumeTrue(MacBaseInstallerBundler.findKey("Developer ID Installer:", null, true) != null); 801 assertTrue(MacBaseInstallerBundler.findKey("Developer", null, true) == null); 802 assertTrue(MacBaseInstallerBundler.findKey("A completely bogus key that should never realistically exist unless we are attempting to falsely break the tests", null, true) == null); 803 } 804 805 public void validateSignatures(File appLocation) throws IOException { 806 // shallow validation 807 ProcessBuilder pb = new ProcessBuilder( 808 "codesign", "--verify", 809 "-v", // single verbose 810 appLocation.getCanonicalPath()); 811 IOUtils.exec(pb, true); 812 813 // deep validation 814 pb = new ProcessBuilder( 815 "codesign", "--verify", 816 "--deep", 817 "-v", // single verbose 818 appLocation.getCanonicalPath()); 819 IOUtils.exec(pb, true); 820 821 // only run spctl for pre-existing keys 822 if (signingKeysPresent) { 823 //spctl, this verifies gatekeeper 824 pb = new ProcessBuilder( 825 "spctl", "--assess", 826 "-v", // single verbose 827 appLocation.getCanonicalPath()); 828 IOUtils.exec(pb, true); 829 } 830 } 831 832 833 }