1 /* 2 * Copyright (c) 2014, 2019, 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 jdk.incubator.jpackage.internal; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.text.MessageFormat; 31 import java.util.*; 32 33 import static jdk.incubator.jpackage.internal.StandardBundlerParam.*; 34 import static jdk.incubator.jpackage.internal.MacAppBundler.*; 35 import static jdk.incubator.jpackage.internal.OverridableResource.createResource; 36 37 public class MacAppStoreBundler extends MacBaseInstallerBundler { 38 39 private static final ResourceBundle I18N = ResourceBundle.getBundle( 40 "jdk.incubator.jpackage.internal.resources.MacResources"); 41 42 private static final String TEMPLATE_BUNDLE_ICON_HIDPI = "java.icns"; 43 private final static String DEFAULT_ENTITLEMENTS = 44 "MacAppStore.entitlements"; 45 private final static String DEFAULT_INHERIT_ENTITLEMENTS = 46 "MacAppStore_Inherit.entitlements"; 47 48 public static final BundlerParamInfo<String> MAC_APP_STORE_APP_SIGNING_KEY = 49 new StandardBundlerParam<>( 50 "mac.signing-key-app", 51 String.class, 52 params -> { 53 String result = MacBaseInstallerBundler.findKey( 54 "3rd Party Mac Developer Application: " + 55 SIGNING_KEY_USER.fetchFrom(params), 56 SIGNING_KEYCHAIN.fetchFrom(params), 57 VERBOSE.fetchFrom(params)); 58 if (result != null) { 59 MacCertificate certificate = new MacCertificate(result); 60 61 if (!certificate.isValid()) { 62 Log.error(MessageFormat.format( 63 I18N.getString("error.certificate.expired"), 64 result)); 65 } 66 } 67 68 return result; 69 }, 70 (s, p) -> s); 71 72 public static final BundlerParamInfo<String> MAC_APP_STORE_PKG_SIGNING_KEY = 73 new StandardBundlerParam<>( 74 "mac.signing-key-pkg", 75 String.class, 76 params -> { 77 String result = MacBaseInstallerBundler.findKey( 78 "3rd Party Mac Developer Installer: " + 79 SIGNING_KEY_USER.fetchFrom(params), 80 SIGNING_KEYCHAIN.fetchFrom(params), 81 VERBOSE.fetchFrom(params)); 82 83 if (result != null) { 84 MacCertificate certificate = new MacCertificate(result); 85 86 if (!certificate.isValid()) { 87 Log.error(MessageFormat.format( 88 I18N.getString("error.certificate.expired"), 89 result)); 90 } 91 } 92 93 return result; 94 }, 95 (s, p) -> s); 96 97 public static final StandardBundlerParam<File> MAC_APP_STORE_ENTITLEMENTS = 98 new StandardBundlerParam<>( 99 Arguments.CLIOptions.MAC_APP_STORE_ENTITLEMENTS.getId(), 100 File.class, 101 params -> null, 102 (s, p) -> new File(s)); 103 104 public static final BundlerParamInfo<String> INSTALLER_SUFFIX = 105 new StandardBundlerParam<> ( 106 "mac.app-store.installerName.suffix", 107 String.class, 108 params -> "-MacAppStore", 109 (s, p) -> s); 110 111 public File bundle(Map<String, ? super Object> params, 112 File outdir) throws PackagerException { 113 Log.verbose(MessageFormat.format(I18N.getString( 114 "message.building-bundle"), APP_NAME.fetchFrom(params))); 115 116 IOUtils.writableOutputDir(outdir.toPath()); 117 118 // first, load in some overrides 119 // icns needs @2 versions, so load in the @2 default 120 params.put(DEFAULT_ICNS_ICON.getID(), TEMPLATE_BUNDLE_ICON_HIDPI); 121 122 // now we create the app 123 File appImageDir = APP_IMAGE_TEMP_ROOT.fetchFrom(params); 124 try { 125 appImageDir.mkdirs(); 126 127 try { 128 MacAppImageBuilder.addNewKeychain(params); 129 } catch (InterruptedException e) { 130 Log.error(e.getMessage()); 131 } 132 // first, make sure we don't use the local signing key 133 params.put(DEVELOPER_ID_APP_SIGNING_KEY.getID(), null); 134 File appLocation = prepareAppBundle(params); 135 136 prepareEntitlements(params); 137 138 String signingIdentity = 139 MAC_APP_STORE_APP_SIGNING_KEY.fetchFrom(params); 140 String identifierPrefix = 141 BUNDLE_ID_SIGNING_PREFIX.fetchFrom(params); 142 String entitlementsFile = 143 getConfig_Entitlements(params).toString(); 144 String inheritEntitlements = 145 getConfig_Inherit_Entitlements(params).toString(); 146 147 MacAppImageBuilder.signAppBundle(params, appLocation.toPath(), 148 signingIdentity, identifierPrefix, 149 entitlementsFile, inheritEntitlements); 150 MacAppImageBuilder.restoreKeychainList(params); 151 152 ProcessBuilder pb; 153 154 // create the final pkg file 155 File finalPKG = new File(outdir, INSTALLER_NAME.fetchFrom(params) 156 + INSTALLER_SUFFIX.fetchFrom(params) 157 + ".pkg"); 158 outdir.mkdirs(); 159 160 String installIdentify = 161 MAC_APP_STORE_PKG_SIGNING_KEY.fetchFrom(params); 162 163 List<String> buildOptions = new ArrayList<>(); 164 buildOptions.add("productbuild"); 165 buildOptions.add("--component"); 166 buildOptions.add(appLocation.toString()); 167 buildOptions.add("/Applications"); 168 buildOptions.add("--sign"); 169 buildOptions.add(installIdentify); 170 buildOptions.add("--product"); 171 buildOptions.add(appLocation + "/Contents/Info.plist"); 172 String keychainName = SIGNING_KEYCHAIN.fetchFrom(params); 173 if (keychainName != null && !keychainName.isEmpty()) { 174 buildOptions.add("--keychain"); 175 buildOptions.add(keychainName); 176 } 177 buildOptions.add(finalPKG.getAbsolutePath()); 178 179 pb = new ProcessBuilder(buildOptions); 180 181 IOUtils.exec(pb); 182 return finalPKG; 183 } catch (PackagerException pe) { 184 throw pe; 185 } catch (Exception ex) { 186 Log.verbose(ex); 187 throw new PackagerException(ex); 188 } 189 } 190 191 private File getConfig_Entitlements(Map<String, ? super Object> params) { 192 return new File(CONFIG_ROOT.fetchFrom(params), 193 APP_NAME.fetchFrom(params) + ".entitlements"); 194 } 195 196 private File getConfig_Inherit_Entitlements( 197 Map<String, ? super Object> params) { 198 return new File(CONFIG_ROOT.fetchFrom(params), 199 APP_NAME.fetchFrom(params) + "_Inherit.entitlements"); 200 } 201 202 private void prepareEntitlements(Map<String, ? super Object> params) 203 throws IOException { 204 createResource(DEFAULT_ENTITLEMENTS, params) 205 .setCategory( 206 I18N.getString("resource.mac-app-store-entitlements")) 207 .setExternal(MAC_APP_STORE_ENTITLEMENTS.fetchFrom(params)) 208 .saveToFile(getConfig_Entitlements(params)); 209 210 createResource(DEFAULT_INHERIT_ENTITLEMENTS, params) 211 .setCategory(I18N.getString( 212 "resource.mac-app-store-inherit-entitlements")) 213 .saveToFile(getConfig_Entitlements(params)); 214 } 215 216 /////////////////////////////////////////////////////////////////////// 217 // Implement Bundler 218 /////////////////////////////////////////////////////////////////////// 219 220 @Override 221 public String getName() { 222 return I18N.getString("store.bundler.name"); 223 } 224 225 @Override 226 public String getID() { 227 return "mac.appStore"; 228 } 229 230 @Override 231 public boolean validate(Map<String, ? super Object> params) 232 throws ConfigException { 233 try { 234 Objects.requireNonNull(params); 235 236 // hdiutil is always available so there's no need to test for 237 // availability. 238 // run basic validation to ensure requirements are met 239 240 // we are not interested in return code, only possible exception 241 validateAppImageAndBundeler(params); 242 243 // reject explicitly set to not sign 244 if (!Optional.ofNullable(MacAppImageBuilder. 245 SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) { 246 throw new ConfigException( 247 I18N.getString("error.must-sign-app-store"), 248 I18N.getString("error.must-sign-app-store.advice")); 249 } 250 251 // make sure we have settings for signatures 252 if (MAC_APP_STORE_APP_SIGNING_KEY.fetchFrom(params) == null) { 253 throw new ConfigException( 254 I18N.getString("error.no-app-signing-key"), 255 I18N.getString("error.no-app-signing-key.advice")); 256 } 257 if (MAC_APP_STORE_PKG_SIGNING_KEY.fetchFrom(params) == null) { 258 throw new ConfigException( 259 I18N.getString("error.no-pkg-signing-key"), 260 I18N.getString("error.no-pkg-signing-key.advice")); 261 } 262 263 // things we could check... 264 // check the icons, make sure it has hidpi icons 265 // check the category, 266 // make sure it fits in the list apple has provided 267 // validate bundle identifier is reverse dns 268 // check for \a+\.\a+\.. 269 270 return true; 271 } catch (RuntimeException re) { 272 if (re.getCause() instanceof ConfigException) { 273 throw (ConfigException) re.getCause(); 274 } else { 275 throw new ConfigException(re); 276 } 277 } 278 } 279 280 @Override 281 public File execute(Map<String, ? super Object> params, 282 File outputParentDir) throws PackagerException { 283 return bundle(params, outputParentDir); 284 } 285 286 @Override 287 public boolean supported(boolean runtimeInstaller) { 288 // return (!runtimeInstaller && 289 // Platform.getPlatform() == Platform.MAC); 290 return false; // mac-app-store not yet supported 291 } 292 293 @Override 294 public boolean isDefault() { 295 return false; 296 } 297 298 }