351 // JDK 9, 10, and 11 have extra '/jli/' subdir 352 Path jli = runtimeRoot.resolve("lib/libjli.dylib"); 353 if (!Files.exists(jli)) { 354 jli = runtimeRoot.resolve("lib/jli/libjli.dylib"); 355 } 356 357 Files.copy(jli, runtimeMacOSDir.resolve("libjli.dylib")); 358 } 359 360 private void sign(Map<String, ? super Object> params) throws IOException { 361 if (Optional.ofNullable( 362 SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) { 363 try { 364 addNewKeychain(params); 365 } catch (InterruptedException e) { 366 Log.error(e.getMessage()); 367 } 368 String signingIdentity = 369 DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(params); 370 if (signingIdentity != null) { 371 signAppBundle(params, root, signingIdentity, 372 BUNDLE_ID_SIGNING_PREFIX.fetchFrom(params), null, null); 373 } 374 restoreKeychainList(params); 375 } 376 } 377 378 private String getLauncherName(Map<String, ? super Object> params) { 379 if (APP_NAME.fetchFrom(params) != null) { 380 return APP_NAME.fetchFrom(params); 381 } else { 382 return MAIN_CLASS.fetchFrom(params); 383 } 384 } 385 386 public static String getLauncherCfgName( 387 Map<String, ? super Object> params) { 388 return "Contents/app/" + APP_NAME.fetchFrom(params) + ".cfg"; 389 } 390 391 private void copyClassPathEntries(Path javaDirectory, 392 Map<String, ? super Object> params) throws IOException { 393 List<RelativeFileSet> resourcesList = 394 APP_RESOURCES_LIST.fetchFrom(params); 395 if (resourcesList == null) { 396 throw new RuntimeException( 397 I18N.getString("message.null-classpath")); 398 } 718 Platform.getMinorVersion() < 12)) { 719 // we need this for OS X 10.12+ 720 return; 721 } 722 723 if (keyChains == null || keyChains.isEmpty()) { 724 return; 725 } 726 727 List<String> args = new ArrayList<>(); 728 args.add("security"); 729 args.add("list-keychains"); 730 args.add("-s"); 731 732 args.addAll(keyChains); 733 734 ProcessBuilder pb = new ProcessBuilder(args); 735 IOUtils.exec(pb); 736 } 737 738 public static void signAppBundle( 739 Map<String, ? super Object> params, Path appLocation, 740 String signingIdentity, String identifierPrefix, 741 String entitlementsFile, String inheritedEntitlements) 742 throws IOException { 743 AtomicReference<IOException> toThrow = new AtomicReference<>(); 744 String appExecutable = "/Contents/MacOS/" + APP_NAME.fetchFrom(params); 745 String keyChain = SIGNING_KEYCHAIN.fetchFrom(params); 746 747 // sign all dylibs and jars 748 try (Stream<Path> stream = Files.walk(appLocation)) { 749 stream.peek(path -> { // fix permissions 750 try { 751 Set<PosixFilePermission> pfp = 752 Files.getPosixFilePermissions(path); 753 if (!pfp.contains(PosixFilePermission.OWNER_WRITE)) { 754 pfp = EnumSet.copyOf(pfp); 755 pfp.add(PosixFilePermission.OWNER_WRITE); 756 Files.setPosixFilePermissions(path, pfp); 757 } 758 } catch (IOException e) { 759 Log.verbose(e); 760 } 761 }).filter(p -> Files.isRegularFile(p) 762 && !(p.toString().contains("/Contents/MacOS/libjli.dylib") 763 || p.toString().endsWith(appExecutable) 764 || p.toString().contains("/Contents/runtime") 765 || p.toString().contains("/Contents/Frameworks"))).forEach(p -> { 766 //noinspection ThrowableResultOfMethodCallIgnored 767 if (toThrow.get() != null) return; 768 769 // If p is a symlink then skip the signing process. 770 if (Files.isSymbolicLink(p)) { 771 if (VERBOSE.fetchFrom(params)) { 772 Log.verbose(MessageFormat.format(I18N.getString( 773 "message.ignoring.symlink"), p.toString())); 774 } 775 } else { 776 if (p.toString().endsWith(LIBRARY_NAME)) { 777 if (isFileSigned(p)) { 778 return; 779 } 780 } 781 782 List<String> args = new ArrayList<>(); 783 args.addAll(Arrays.asList("codesign", 784 "-s", signingIdentity, // sign with this key 785 "--prefix", identifierPrefix, 786 // use the identifier as a prefix 787 "-vvvv")); 788 if (entitlementsFile != null && 789 (p.toString().endsWith(".jar") 790 || p.toString().endsWith(".dylib"))) { 791 args.add("--entitlements"); 792 args.add(entitlementsFile); // entitlements 793 } else if (inheritedEntitlements != null && 794 Files.isExecutable(p)) { 795 args.add("--entitlements"); 796 args.add(inheritedEntitlements); 797 // inherited entitlements for executable processes 798 } 799 if (keyChain != null && !keyChain.isEmpty()) { 800 args.add("--keychain"); 801 args.add(keyChain); 802 } 803 args.add(p.toString()); 804 805 try { 806 Set<PosixFilePermission> oldPermissions = 807 Files.getPosixFilePermissions(p); 808 File f = p.toFile(); 809 f.setWritable(true, true); 810 811 ProcessBuilder pb = new ProcessBuilder(args); 812 IOUtils.exec(pb); 813 814 Files.setPosixFilePermissions(p, oldPermissions); 815 } catch (IOException ioe) { 816 toThrow.set(ioe); 817 } 818 } 819 }); 820 } 821 IOException ioe = toThrow.get(); 822 if (ioe != null) { 823 throw ioe; 824 } 825 826 // sign all runtime and frameworks 827 Consumer<? super Path> signIdentifiedByPList = path -> { 828 //noinspection ThrowableResultOfMethodCallIgnored 829 if (toThrow.get() != null) return; 830 831 try { 832 List<String> args = new ArrayList<>(); 833 args.addAll(Arrays.asList("codesign", 834 "-f", 835 "-s", signingIdentity, // sign with this key 836 "--prefix", identifierPrefix, 837 // use the identifier as a prefix 838 "-vvvv")); 839 if (keyChain != null && !keyChain.isEmpty()) { 840 args.add("--keychain"); 841 args.add(keyChain); 842 } 843 args.add(path.toString()); 844 ProcessBuilder pb = new ProcessBuilder(args); 845 IOUtils.exec(pb); 846 847 args = new ArrayList<>(); 848 args.addAll(Arrays.asList("codesign", 849 "-s", signingIdentity, // sign with this key 850 "--prefix", identifierPrefix, 851 // use the identifier as a prefix 852 "-vvvv")); 853 if (keyChain != null && !keyChain.isEmpty()) { 854 args.add("--keychain"); 855 args.add(keyChain); 856 } 857 args.add(path.toString() 858 + "/Contents/_CodeSignature/CodeResources"); 859 pb = new ProcessBuilder(args); 860 IOUtils.exec(pb); 861 } catch (IOException e) { 862 toThrow.set(e); 863 } 864 }; 865 866 Path javaPath = appLocation.resolve("Contents/runtime"); 867 if (Files.isDirectory(javaPath)) { 868 signIdentifiedByPList.accept(javaPath); 869 870 ioe = toThrow.get(); 871 if (ioe != null) { 872 throw ioe; 873 } 874 } 875 Path frameworkPath = appLocation.resolve("Contents/Frameworks"); 876 if (Files.isDirectory(frameworkPath)) { 877 Files.list(frameworkPath) 878 .forEach(signIdentifiedByPList); 879 880 ioe = toThrow.get(); 881 if (ioe != null) { 882 throw ioe; 883 } 884 } 885 886 // sign the app itself 887 List<String> args = new ArrayList<>(); 888 args.addAll(Arrays.asList("codesign", 889 "-s", signingIdentity, // sign with this key 890 "-vvvv")); // super verbose output 891 if (entitlementsFile != null) { 892 args.add("--entitlements"); 893 args.add(entitlementsFile); // entitlements 894 } 895 if (keyChain != null && !keyChain.isEmpty()) { 896 args.add("--keychain"); 897 args.add(keyChain); 898 } 899 args.add(appLocation.toString()); 900 901 ProcessBuilder pb = 902 new ProcessBuilder(args.toArray(new String[args.size()])); 903 IOUtils.exec(pb); 904 } 905 906 private static boolean isFileSigned(Path file) { 907 ProcessBuilder pb = 908 new ProcessBuilder("codesign", "--verify", file.toString()); 909 910 try { 911 IOUtils.exec(pb); 912 } catch (IOException ex) { 913 return false; 914 } 915 916 return true; 917 } 918 919 private static String extractBundleIdentifier(Map<String, Object> params) { 920 if (PREDEFINED_APP_IMAGE.fetchFrom(params) == null) { 921 return null; 922 } | 351 // JDK 9, 10, and 11 have extra '/jli/' subdir 352 Path jli = runtimeRoot.resolve("lib/libjli.dylib"); 353 if (!Files.exists(jli)) { 354 jli = runtimeRoot.resolve("lib/jli/libjli.dylib"); 355 } 356 357 Files.copy(jli, runtimeMacOSDir.resolve("libjli.dylib")); 358 } 359 360 private void sign(Map<String, ? super Object> params) throws IOException { 361 if (Optional.ofNullable( 362 SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) { 363 try { 364 addNewKeychain(params); 365 } catch (InterruptedException e) { 366 Log.error(e.getMessage()); 367 } 368 String signingIdentity = 369 DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(params); 370 if (signingIdentity != null) { 371 prepareEntitlements(params); 372 signAppBundle(params, root, signingIdentity, 373 BUNDLE_ID_SIGNING_PREFIX.fetchFrom(params), 374 getConfig_Entitlements(params)); 375 } 376 restoreKeychainList(params); 377 } 378 } 379 380 static File getConfig_Entitlements(Map<String, ? super Object> params) { 381 return new File(CONFIG_ROOT.fetchFrom(params), 382 getLauncherName(params) + ".entitlements"); 383 } 384 385 static void prepareEntitlements(Map<String, ? super Object> params) 386 throws IOException { 387 createResource("entitlements.plist", params) 388 .setCategory(I18N.getString("resource.entitlements")) 389 .saveToFile(getConfig_Entitlements(params)); 390 } 391 392 private static String getLauncherName(Map<String, ? super Object> params) { 393 if (APP_NAME.fetchFrom(params) != null) { 394 return APP_NAME.fetchFrom(params); 395 } else { 396 return MAIN_CLASS.fetchFrom(params); 397 } 398 } 399 400 public static String getLauncherCfgName( 401 Map<String, ? super Object> params) { 402 return "Contents/app/" + APP_NAME.fetchFrom(params) + ".cfg"; 403 } 404 405 private void copyClassPathEntries(Path javaDirectory, 406 Map<String, ? super Object> params) throws IOException { 407 List<RelativeFileSet> resourcesList = 408 APP_RESOURCES_LIST.fetchFrom(params); 409 if (resourcesList == null) { 410 throw new RuntimeException( 411 I18N.getString("message.null-classpath")); 412 } 732 Platform.getMinorVersion() < 12)) { 733 // we need this for OS X 10.12+ 734 return; 735 } 736 737 if (keyChains == null || keyChains.isEmpty()) { 738 return; 739 } 740 741 List<String> args = new ArrayList<>(); 742 args.add("security"); 743 args.add("list-keychains"); 744 args.add("-s"); 745 746 args.addAll(keyChains); 747 748 ProcessBuilder pb = new ProcessBuilder(args); 749 IOUtils.exec(pb); 750 } 751 752 static void signAppBundle( 753 Map<String, ? super Object> params, Path appLocation, 754 String signingIdentity, String identifierPrefix, File entitlements) 755 throws IOException { 756 AtomicReference<IOException> toThrow = new AtomicReference<>(); 757 String appExecutable = "/Contents/MacOS/" + APP_NAME.fetchFrom(params); 758 String keyChain = SIGNING_KEYCHAIN.fetchFrom(params); 759 760 // sign all dylibs and executables 761 try (Stream<Path> stream = Files.walk(appLocation)) { 762 stream.peek(path -> { // fix permissions 763 try { 764 Set<PosixFilePermission> pfp = 765 Files.getPosixFilePermissions(path); 766 if (!pfp.contains(PosixFilePermission.OWNER_WRITE)) { 767 pfp = EnumSet.copyOf(pfp); 768 pfp.add(PosixFilePermission.OWNER_WRITE); 769 Files.setPosixFilePermissions(path, pfp); 770 } 771 } catch (IOException e) { 772 Log.verbose(e); 773 } 774 }).filter(p -> Files.isRegularFile(p) && 775 (Files.isExecutable(p) || p.toString().endsWith(".dylib")) 776 && !(p.toString().endsWith(appExecutable) 777 || p.toString().contains("/Contents/runtime") 778 || p.toString().contains("/Contents/Frameworks")) 779 ).forEach(p -> { 780 // noinspection ThrowableResultOfMethodCallIgnored 781 if (toThrow.get() != null) return; 782 783 // If p is a symlink then skip the signing process. 784 if (Files.isSymbolicLink(p)) { 785 Log.verbose(MessageFormat.format(I18N.getString( 786 "message.ignoring.symlink"), p.toString())); 787 } else if (isFileSigned(p)) { 788 // executable or lib already signed 789 Log.verbose(MessageFormat.format(I18N.getString( 790 "message.already.signed"), p.toString())); 791 } else { 792 List<String> args = new ArrayList<>(); 793 args.addAll(Arrays.asList("codesign", 794 "--timestamp", 795 "--options", "runtime", 796 "-s", signingIdentity, 797 "--prefix", identifierPrefix, 798 "-vvvv")); 799 if (keyChain != null && !keyChain.isEmpty()) { 800 args.add("--keychain"); 801 args.add(keyChain); 802 } 803 804 if (Files.isExecutable(p)) { 805 if (entitlements != null) { 806 args.add("--entitlements"); 807 args.add(entitlements.toString()); 808 } 809 } 810 811 args.add(p.toString()); 812 813 try { 814 Set<PosixFilePermission> oldPermissions = 815 Files.getPosixFilePermissions(p); 816 File f = p.toFile(); 817 f.setWritable(true, true); 818 819 ProcessBuilder pb = new ProcessBuilder(args); 820 821 IOUtils.exec(pb); 822 823 Files.setPosixFilePermissions(p, oldPermissions); 824 } catch (IOException ioe) { 825 toThrow.set(ioe); 826 } 827 } 828 }); 829 } 830 IOException ioe = toThrow.get(); 831 if (ioe != null) { 832 throw ioe; 833 } 834 835 // sign all runtime and frameworks 836 Consumer<? super Path> signIdentifiedByPList = path -> { 837 //noinspection ThrowableResultOfMethodCallIgnored 838 if (toThrow.get() != null) return; 839 840 try { 841 List<String> args = new ArrayList<>(); 842 args.addAll(Arrays.asList("codesign", 843 "--timestamp", 844 "--options", "runtime", 845 "--deep", 846 "--force", 847 "-s", signingIdentity, // sign with this key 848 "--prefix", identifierPrefix, 849 // use the identifier as a prefix 850 "-vvvv")); 851 852 if (keyChain != null && !keyChain.isEmpty()) { 853 args.add("--keychain"); 854 args.add(keyChain); 855 } 856 args.add(path.toString()); 857 ProcessBuilder pb = new ProcessBuilder(args); 858 859 IOUtils.exec(pb); 860 } catch (IOException e) { 861 toThrow.set(e); 862 } 863 }; 864 865 Path javaPath = appLocation.resolve("Contents/runtime"); 866 if (Files.isDirectory(javaPath)) { 867 signIdentifiedByPList.accept(javaPath); 868 869 ioe = toThrow.get(); 870 if (ioe != null) { 871 throw ioe; 872 } 873 } 874 Path frameworkPath = appLocation.resolve("Contents/Frameworks"); 875 if (Files.isDirectory(frameworkPath)) { 876 Files.list(frameworkPath) 877 .forEach(signIdentifiedByPList); 878 879 ioe = toThrow.get(); 880 if (ioe != null) { 881 throw ioe; 882 } 883 } 884 885 // sign the app itself 886 List<String> args = new ArrayList<>(); 887 args.addAll(Arrays.asList("codesign", 888 "--timestamp", 889 "--options", "runtime", 890 "--deep", 891 "--force", 892 "-s", signingIdentity, 893 "-vvvv")); 894 895 if (keyChain != null && !keyChain.isEmpty()) { 896 args.add("--keychain"); 897 args.add(keyChain); 898 } 899 900 if (entitlements != null) { 901 args.add("--entitlements"); 902 args.add(entitlements.toString()); 903 } 904 905 args.add(appLocation.toString()); 906 907 ProcessBuilder pb = 908 new ProcessBuilder(args.toArray(new String[args.size()])); 909 910 IOUtils.exec(pb); 911 } 912 913 private static boolean isFileSigned(Path file) { 914 ProcessBuilder pb = 915 new ProcessBuilder("codesign", "--verify", file.toString()); 916 917 try { 918 IOUtils.exec(pb); 919 } catch (IOException ex) { 920 return false; 921 } 922 923 return true; 924 } 925 926 private static String extractBundleIdentifier(Map<String, Object> params) { 927 if (PREDEFINED_APP_IMAGE.fetchFrom(params) == null) { 928 return null; 929 } |