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 if (!plugin.exists() || !plugin.isFile()) { 86 LOGGER.warn("can not find signerPlugin or not a file by param signerPlugin = {}", signerPlugin); 87 return Optional.empty(); 88 } 89 Optional<URL> url = fileToUrl(plugin); 90 if (!url.isPresent()) { 91 return Optional.empty(); 92 } 93 try { 94 ClassLoader classLoader = generateSignerClassLoader(url.get()); 95 try (InputStream inputStream = classLoader.getResourceAsStream("signer.properties")) { 96 if (inputStream == null) { 97 LOGGER.warn("can not find entry signer.properties in {}", plugin); 98 return Optional.empty(); 99 } 100 Properties properties = new Properties(); 101 properties.load(inputStream); 102 String implClassName = properties.getProperty(ISigner.class.getName()); 103 if (StringUtils.isEmpty(implClassName)) { 104 LOGGER.warn("can not find {} in signer.properties", ISigner.class.getName()); 105 return Optional.empty(); 106 } 107 Class<?> implClass = classLoader.loadClass(implClassName); 108 Constructor<?> constructor = implClass.getConstructor(Map.class); 109 Object signer = constructor.newInstance(adapter.getOptions()); 110 if (signer instanceof ISigner) { 111 return Optional.of((ISigner) signer); 112 } 113 } 114 } catch (IOException | ClassNotFoundException | NoSuchMethodException 115 | InvocationTargetException | InstantiationException | IllegalAccessException e) { 116 LOGGER.warn("load remote signer from {} failed, msg: {}", signerPlugin, e.getMessage()); 117 } 118 return Optional.empty(); 119 } 120 fileToUrl(File file)121 private Optional<URL> fileToUrl(File file) { 122 if (!file.exists()) { 123 LOGGER.warn("{} is not exists", file); 124 return Optional.empty(); 125 } 126 try { 127 return Optional.of(file.toURI().toURL()); 128 } catch (MalformedURLException e) { 129 LOGGER.warn("{} can not convert to valid url, msg: {}", file, e.getMessage()); 130 } 131 return Optional.empty(); 132 } 133 getClassLocation()134 private File getClassLocation() { 135 String jarPath = SignerFactory.class.getProtectionDomain().getCodeSource().getLocation().getFile(); 136 if (StringUtils.isEmpty(jarPath)) { 137 CustomException.throwException(ERROR.COMMAND_ERROR, "class path is empty"); 138 } 139 try { 140 jarPath = URLDecoder.decode(URLEncoder.encode(jarPath, "utf-8"), "utf-8"); 141 } catch (UnsupportedEncodingException | IllegalArgumentException e) { 142 LOGGER.warn("decode class location failed, will ignored. msg :{}", e.getMessage()); 143 } 144 File jarFile = new File(jarPath); 145 if (!jarFile.exists()) { 146 CustomException.throwException(ERROR.COMMAND_ERROR, "class path" + jarFile + "is not exists"); 147 } 148 if (jarFile.isFile()) { 149 return jarFile.getParentFile(); 150 } 151 return jarFile; 152 } 153 generateSignerClassLoader(URL signerClassUrl)154 private static synchronized ClassLoader generateSignerClassLoader(URL signerClassUrl) { 155 ClassLoader classLoader = SIGNER_LOADERS.get(signerClassUrl); 156 if (classLoader == null) { 157 ClassLoader parent = SignerFactory.class.getClassLoader(); 158 classLoader = URLClassLoader.newInstance(new URL[]{signerClassUrl}, parent); 159 SIGNER_LOADERS.put(signerClassUrl, classLoader); 160 } 161 return classLoader; 162 } 163 } 164