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.api.LocalizationAdapter; 19 import com.ohos.hapsigntool.error.CustomException; 20 import com.ohos.hapsigntool.error.ERROR; 21 import com.ohos.hapsigntool.utils.StringUtils; 22 import org.apache.logging.log4j.LogManager; 23 import org.apache.logging.log4j.Logger; 24 25 import java.io.File; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.io.UnsupportedEncodingException; 29 import java.lang.reflect.Constructor; 30 import java.lang.reflect.InvocationTargetException; 31 import java.net.MalformedURLException; 32 import java.net.URL; 33 import java.net.URLClassLoader; 34 import java.net.URLDecoder; 35 import java.security.KeyPair; 36 import java.util.HashMap; 37 import java.util.Map; 38 import java.util.Optional; 39 import java.util.Properties; 40 41 /** 42 * Factory pattern to create signer. 43 * 44 * @since 2021/12/28 45 */ 46 public class SignerFactory { 47 private static final Logger LOGGER = LogManager.getLogger(SignerFactory.class); 48 49 private static final Map<URL, ClassLoader> SIGNER_LOADERS = new HashMap<>(); 50 51 /** 52 * Create a signer. 53 * 54 * @param adapter Params adapter 55 * @return Local signer or remote signer 56 */ getSigner(LocalizationAdapter adapter)57 public ISigner getSigner(LocalizationAdapter adapter) { 58 if (adapter.isRemoteSigner()) { 59 Optional<ISigner> remoteSigner = loadRemoteSigner(adapter); 60 if (remoteSigner.isPresent()) { 61 return remoteSigner.get(); 62 } 63 LOGGER.warn("load remote signer failed, use default implementation"); 64 return new RemoteSigner(adapter.getOptions()); 65 } 66 KeyPair keyPair = adapter.getAliasKey(false); 67 adapter.releasePwd(); 68 return new LocalSigner(keyPair.getPrivate(), adapter.getSignCertChain()); 69 } 70 loadRemoteSigner(LocalizationAdapter adapter)71 private Optional<ISigner> loadRemoteSigner(LocalizationAdapter adapter) { 72 String signerPlugin = adapter.getOptions().getString("signerPlugin"); 73 if (StringUtils.isEmpty(signerPlugin)) { 74 LOGGER.warn("lost parameter signerPlugin"); 75 return Optional.empty(); 76 } 77 File classLocation = getClassLocation(); 78 File plugin = new File(classLocation, signerPlugin); 79 Optional<URL> url = fileToUrl(plugin); 80 if (!url.isPresent()) { 81 return Optional.empty(); 82 } 83 try { 84 ClassLoader classLoader = generateSignerClassLoader(url.get()); 85 try (InputStream inputStream = classLoader.getResourceAsStream("signer.properties")) { 86 if (inputStream == null) { 87 LOGGER.warn("can not find entry signer.properties in {}", plugin); 88 return Optional.empty(); 89 } 90 Properties properties = new Properties(); 91 properties.load(inputStream); 92 String implClassName = properties.getProperty(ISigner.class.getName()); 93 if (StringUtils.isEmpty(implClassName)) { 94 LOGGER.warn("can not find {} in signer.properties", ISigner.class.getName()); 95 return Optional.empty(); 96 } 97 Class<?> implClass = classLoader.loadClass(implClassName); 98 Constructor<?> constructor = implClass.getConstructor(Map.class); 99 Object signer = constructor.newInstance(adapter.getOptions()); 100 if (signer instanceof ISigner) { 101 return Optional.of((ISigner) signer); 102 } 103 } 104 } catch (IOException | ClassNotFoundException | NoSuchMethodException 105 | InvocationTargetException | InstantiationException | IllegalAccessException e) { 106 LOGGER.warn("load remote signer from {} failed, msg: {}", signerPlugin, e.getMessage()); 107 } 108 return Optional.empty(); 109 } 110 fileToUrl(File file)111 private Optional<URL> fileToUrl(File file) { 112 if (!file.exists()) { 113 LOGGER.warn("{} is not exists", file); 114 return Optional.empty(); 115 } 116 try { 117 return Optional.of(file.toURI().toURL()); 118 } catch (MalformedURLException e) { 119 LOGGER.warn("{} can not convert to valid url, msg: {}", file, e.getMessage()); 120 } 121 return Optional.empty(); 122 } 123 getClassLocation()124 private File getClassLocation() { 125 String jarPath = SignerFactory.class.getProtectionDomain().getCodeSource().getLocation().getFile(); 126 if (StringUtils.isEmpty(jarPath)) { 127 CustomException.throwException(ERROR.COMMAND_ERROR, "class path is empty"); 128 } 129 try { 130 jarPath = URLDecoder.decode(jarPath, "utf-8"); 131 } catch (UnsupportedEncodingException | IllegalArgumentException e) { 132 LOGGER.warn("decode class location failed, will ignored. msg :{}", e.getMessage()); 133 } 134 File jarFile = new File(jarPath); 135 if (!jarFile.exists()) { 136 CustomException.throwException(ERROR.COMMAND_ERROR, "class path" + jarFile + "is not exists"); 137 } 138 if (jarFile.isFile()) { 139 return jarFile.getParentFile(); 140 } 141 return jarFile; 142 } 143 generateSignerClassLoader(URL signerClassUrl)144 private static synchronized ClassLoader generateSignerClassLoader(URL signerClassUrl) { 145 ClassLoader classLoader = SIGNER_LOADERS.get(signerClassUrl); 146 if (classLoader == null) { 147 ClassLoader parent = SignerFactory.class.getClassLoader(); 148 classLoader = URLClassLoader.newInstance(new URL[]{signerClassUrl}, parent); 149 SIGNER_LOADERS.put(signerClassUrl, classLoader); 150 } 151 return classLoader; 152 } 153 } 154