1 /* 2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package com.ohos.hapsigntool.signer; 17 18 import com.ohos.hapsigntool.adapter.LocalizationAdapter; 19 import com.ohos.hapsigntool.error.CustomException; 20 import com.ohos.hapsigntool.error.ERROR; 21 import com.ohos.hapsigntool.utils.StringUtils; 22 23 import org.apache.logging.log4j.LogManager; 24 import org.apache.logging.log4j.Logger; 25 26 import java.io.File; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.UnsupportedEncodingException; 30 import java.lang.reflect.Constructor; 31 import java.lang.reflect.InvocationTargetException; 32 import java.net.MalformedURLException; 33 import java.net.URL; 34 import java.net.URLClassLoader; 35 import java.net.URLDecoder; 36 import java.net.URLEncoder; 37 import java.security.KeyPair; 38 import java.util.HashMap; 39 import java.util.Map; 40 import java.util.Optional; 41 import java.util.Properties; 42 43 /** 44 * Factory pattern to create signer. 45 * 46 * @since 2021/12/28 47 */ 48 public class SignerFactory { 49 private static final Logger LOGGER = LogManager.getLogger(SignerFactory.class); 50 51 private static final Map<URL, ClassLoader> SIGNER_LOADERS = new HashMap<>(); 52 53 /** 54 * Create a signer. 55 * 56 * @param adapter Params adapter 57 * @return Local signer or remote signer 58 */ getSigner(LocalizationAdapter adapter)59 public ISigner getSigner(LocalizationAdapter adapter) { 60 if (adapter.isRemoteSigner()) { 61 Optional<ISigner> remoteSigner = loadRemoteSigner(adapter); 62 if (remoteSigner.isPresent()) { 63 return remoteSigner.get(); 64 } 65 LOGGER.warn("load remote signer failed, use default implementation"); 66 return new RemoteSigner(adapter.getOptions()); 67 } 68 KeyPair keyPair = adapter.getAliasKey(false); 69 adapter.releasePwd(); 70 return new LocalSigner(keyPair.getPrivate(), adapter.getSignCertChain()); 71 } 72 loadRemoteSigner(LocalizationAdapter adapter)73 private Optional<ISigner> loadRemoteSigner(LocalizationAdapter adapter) { 74 String signerPlugin = adapter.getOptions().getString("signerPlugin"); 75 if (StringUtils.isEmpty(signerPlugin)) { 76 LOGGER.warn("lost parameter signerPlugin"); 77 return Optional.empty(); 78 } 79 80 File plugin = new File(signerPlugin); 81 if (!plugin.exists()) { 82 File classLocation = getClassLocation(); 83 plugin = new File(classLocation, signerPlugin); 84 } 85 86 try { 87 LOGGER.info("try to find plugin by path {}", plugin.getCanonicalPath()); 88 } catch (IOException e) { 89 LOGGER.warn("can not find signerPlugin by param signerPlugin = {}", signerPlugin); 90 return Optional.empty(); 91 } 92 Optional<URL> url = fileToUrl(plugin); 93 if (!url.isPresent()) { 94 return Optional.empty(); 95 } 96 try { 97 ClassLoader classLoader = generateSignerClassLoader(url.get()); 98 try (InputStream inputStream = classLoader.getResourceAsStream("signer.properties")) { 99 if (inputStream == null) { 100 LOGGER.warn("can not find entry signer.properties in {}", plugin); 101 return Optional.empty(); 102 } 103 Properties properties = new Properties(); 104 properties.load(inputStream); 105 String implClassName = properties.getProperty(ISigner.class.getName()); 106 if (StringUtils.isEmpty(implClassName)) { 107 LOGGER.warn("can not find {} in signer.properties", ISigner.class.getName()); 108 return Optional.empty(); 109 } 110 Class<?> implClass = classLoader.loadClass(implClassName); 111 Constructor<?> constructor = implClass.getConstructor(Map.class); 112 Object signer = constructor.newInstance(adapter.getOptions()); 113 if (signer instanceof ISigner) { 114 return Optional.of((ISigner) signer); 115 } 116 } 117 } catch (IOException | ClassNotFoundException | NoSuchMethodException 118 | InvocationTargetException | InstantiationException | IllegalAccessException e) { 119 LOGGER.warn("load remote signer from {} failed, msg: {}", signerPlugin, e.getMessage()); 120 } 121 return Optional.empty(); 122 } 123 fileToUrl(File file)124 private Optional<URL> fileToUrl(File file) { 125 if (!file.exists()) { 126 LOGGER.warn("{} is not exists", file); 127 return Optional.empty(); 128 } 129 try { 130 return Optional.of(file.toURI().toURL()); 131 } catch (MalformedURLException e) { 132 LOGGER.warn("{} can not convert to valid url, msg: {}", file, e.getMessage()); 133 } 134 return Optional.empty(); 135 } 136 getClassLocation()137 private File getClassLocation() { 138 String jarPath = SignerFactory.class.getProtectionDomain().getCodeSource().getLocation().getFile(); 139 if (StringUtils.isEmpty(jarPath)) { 140 CustomException.throwException(ERROR.COMMAND_ERROR, "class path is empty"); 141 } 142 try { 143 jarPath = URLDecoder.decode(URLEncoder.encode(jarPath, "utf-8"), "utf-8"); 144 } catch (UnsupportedEncodingException | IllegalArgumentException e) { 145 LOGGER.warn("decode class location failed, will ignored. msg :{}", e.getMessage()); 146 } 147 File jarFile = new File(jarPath); 148 if (!jarFile.exists()) { 149 CustomException.throwException(ERROR.COMMAND_ERROR, "class path" + jarFile + "is not exists"); 150 } 151 if (jarFile.isFile()) { 152 return jarFile.getParentFile(); 153 } 154 return jarFile; 155 } 156 generateSignerClassLoader(URL signerClassUrl)157 private static synchronized ClassLoader generateSignerClassLoader(URL signerClassUrl) { 158 ClassLoader classLoader = SIGNER_LOADERS.get(signerClassUrl); 159 if (classLoader == null) { 160 ClassLoader parent = SignerFactory.class.getClassLoader(); 161 classLoader = URLClassLoader.newInstance(new URL[]{signerClassUrl}, parent); 162 SIGNER_LOADERS.put(signerClassUrl, classLoader); 163 } 164 return classLoader; 165 } 166 } 167