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