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.adapter; 17 18 import com.ohos.hapsigntool.entity.Options; 19 import com.ohos.hapsigntool.error.CustomException; 20 import com.ohos.hapsigntool.error.ERROR; 21 import com.ohos.hapsigntool.error.VerifyCertificateChainException; 22 import com.ohos.hapsigntool.utils.KeyPairTools; 23 import com.ohos.hapsigntool.utils.KeyStoreHelper; 24 import com.ohos.hapsigntool.utils.CertUtils; 25 import com.ohos.hapsigntool.utils.FileUtils; 26 import com.ohos.hapsigntool.utils.StringUtils; 27 import com.ohos.hapsigntool.utils.ValidateUtils; 28 import org.apache.logging.log4j.LogManager; 29 import org.apache.logging.log4j.Logger; 30 import org.bouncycastle.asn1.x500.X500Name; 31 import org.bouncycastle.asn1.x509.KeyPurposeId; 32 import org.bouncycastle.asn1.x509.KeyUsage; 33 34 import java.io.File; 35 import java.io.IOException; 36 import java.security.KeyPair; 37 import java.security.cert.CertificateException; 38 import java.security.cert.X509Certificate; 39 import java.util.Arrays; 40 import java.util.List; 41 42 /** 43 * Localization adapter. 44 * 45 * @since 2021/12/28 46 */ 47 public class LocalizationAdapter { 48 /** 49 * Check cert chain size 50 */ 51 private static final int MIN_CERT_CHAIN_SIZE = 2; 52 private static final int MAX_CERT_CHAIN_SIZE = 3; 53 54 /** 55 * Logger 56 */ 57 private static final Logger logger = LogManager.getLogger(LocalizationAdapter.class); 58 59 /** 60 * Params 61 */ 62 private final Options options; 63 64 /** 65 * Operation of keystore 66 */ 67 private KeyStoreHelper keyStoreHelper; 68 69 /** 70 * Judge whether IssuerKeyStoreFile exists 71 */ 72 private boolean isIssuerKeyStoreFile = false; 73 74 /** 75 * Constructor of LocalizationAdapter. 76 * 77 * @param options options 78 */ LocalizationAdapter(Options options)79 public LocalizationAdapter(Options options) { 80 this.options = options; 81 } 82 83 /** 84 * Set keyStoreHelper 85 * 86 * @param keyStoreHelper keyStoreHelper 87 */ setKeyStoreHelper(KeyStoreHelper keyStoreHelper)88 public void setKeyStoreHelper(KeyStoreHelper keyStoreHelper) { 89 this.keyStoreHelper = keyStoreHelper; 90 } 91 92 /** 93 * Set issuerKeyStoreFile 94 * 95 * @param issuerKeyStoreFile issuerKeyStoreFile 96 */ setIssuerKeyStoreFile(boolean issuerKeyStoreFile)97 public void setIssuerKeyStoreFile(boolean issuerKeyStoreFile) { 98 this.isIssuerKeyStoreFile = issuerKeyStoreFile; 99 } 100 101 /** 102 * Get options. 103 * 104 * @return options 105 */ getOptions()106 public Options getOptions() { 107 return options; 108 } 109 initKeyStore()110 private void initKeyStore() { 111 // Avoid duplicated initialization 112 if (keyStoreHelper != null) { 113 return; 114 } 115 116 String keyStore ; 117 if (this.isIssuerKeyStoreFile) { 118 keyStore = options.getString(Options.ISSUER_KEY_STORE_FILE, ""); 119 keyStoreHelper = new KeyStoreHelper(keyStore, options.getChars(Options.ISSUER_KEY_STORE_RIGHTS)); 120 } else { 121 keyStore = options.getString(Options.KEY_STORE_FILE, ""); 122 keyStoreHelper = new KeyStoreHelper(keyStore, options.getChars(Options.KEY_STORE_RIGHTS)); 123 } 124 this.setIssuerKeyStoreFile(false); 125 } 126 127 /** 128 * Get KeyPair through key alias and password. 129 * 130 * @param autoCreate autoCreate 131 * @return keyPair 132 */ getAliasKey(boolean autoCreate)133 public KeyPair getAliasKey(boolean autoCreate) { 134 return getKeyPair(options.getString(Options.KEY_ALIAS), 135 options.getChars(Options.KEY_RIGHTS), autoCreate); 136 } 137 138 /** 139 * Get the alias of issuer key. 140 * 141 * @return param of issuer alias key . 142 */ getIssuerAliasKey()143 public KeyPair getIssuerAliasKey() { 144 return getKeyPair(options.getString(Options.ISSUER_KEY_ALIAS), 145 options.getChars(Options.ISSUER_KEY_RIGHTS), false); 146 } 147 148 /** 149 * Check whether the keystore has alias or not. 150 * 151 * @param alias alias of key 152 * @return true or false 153 */ hasAlias(String alias)154 public boolean hasAlias(String alias) { 155 if (keyStoreHelper == null) { 156 initKeyStore(); 157 } 158 return keyStoreHelper.hasAlias(alias); 159 } 160 161 /** 162 * Error if not exist. 163 * 164 * @param alias alias 165 */ errorIfNotExist(String alias)166 public void errorIfNotExist(String alias) { 167 if (keyStoreHelper == null) { 168 initKeyStore(); 169 } 170 keyStoreHelper.errorIfNotExist(alias); 171 } 172 173 /** 174 * Error on exist. 175 * 176 * @param alias alias 177 */ errorOnExist(String alias)178 public void errorOnExist(String alias) { 179 if (keyStoreHelper == null) { 180 initKeyStore(); 181 } 182 keyStoreHelper.errorOnExist(alias); 183 } 184 getKeyPair(String alias, char[] keyPwd, boolean autoCreate)185 private KeyPair getKeyPair(String alias, char[] keyPwd, boolean autoCreate) { 186 if (keyStoreHelper == null) { 187 initKeyStore(); 188 } 189 ValidateUtils.throwIfNotMatches(!StringUtils.isEmpty(alias), ERROR.ACCESS_ERROR, "Alias could not be empty"); 190 KeyPair keyPair = null; 191 if (keyStoreHelper.hasAlias(alias)) { 192 keyPair = keyStoreHelper.loadKeyPair(alias, keyPwd); 193 } else { 194 if (autoCreate) { 195 options.required(Options.KEY_ALG, Options.KEY_SIZE); 196 keyPair = KeyPairTools.generateKeyPair(options.getString(Options.KEY_ALG), 197 options.getInt(Options.KEY_SIZE)); 198 keyStoreHelper.store(alias, keyPwd, keyPair, null); 199 } 200 } 201 ValidateUtils.throwIfNotMatches(keyPair != null, ERROR.PARAM_NOT_EXIST_ERROR, 202 String.format("%s: '%s' is not exist in %s", Options.KEY_ALIAS, alias, 203 keyStoreHelper.getKeyStorePath())); 204 return keyPair; 205 } 206 207 /** 208 * Get profile cert. 209 * 210 * @return profile cert. 211 */ getSignCertChain()212 public List<X509Certificate> getSignCertChain() { 213 String certPath = options.getString(Options.PROFILE_CERT_FILE); 214 if (StringUtils.isEmpty(certPath)) { 215 certPath = options.getString(Options.APP_CERT_FILE); 216 } 217 List<X509Certificate> certificates = getCertsFromFile(certPath, Options.PROFILE_CERT_FILE); 218 219 ValidateUtils.throwIfNotMatches( 220 certificates.size() >= MIN_CERT_CHAIN_SIZE && certificates.size() <= MAX_CERT_CHAIN_SIZE, 221 ERROR.NOT_SUPPORT_ERROR, String.format("Profile cert '%s' must a cert chain", certPath) 222 ); 223 return certificates; 224 } 225 226 /** 227 * Get the cert file of sub ca. 228 * 229 * @return the cert file of sub ca. 230 */ getSubCaCertFile()231 public X509Certificate getSubCaCertFile() { 232 String certPath = options.getString(Options.SUB_CA_CERT_FILE); 233 return getCertsFromFile(certPath, Options.SUB_CA_CERT_FILE).get(0); 234 } 235 236 /** 237 * Get the cert file of root ca. 238 * 239 * @return the cert file of root ca. 240 */ getCaCertFile()241 public X509Certificate getCaCertFile() { 242 String certPath = options.getString(Options.CA_CERT_FILE); 243 return getCertsFromFile(certPath, Options.CA_CERT_FILE).get(0); 244 } 245 246 /** 247 * Check whether the form is cert chain. 248 * 249 * @return result indicating whether the form is cert chain. 250 */ isOutFormChain()251 public boolean isOutFormChain() { 252 String outForm = options.getString(Options.OUT_FORM, "certChain"); 253 return "certChain".equals(outForm); 254 } 255 256 /** 257 * Get certificates from file. 258 * 259 * @param certPath the path of cert 260 * @param logTitle log title 261 * @return certificates 262 */ getCertsFromFile(String certPath, String logTitle)263 public List<X509Certificate> getCertsFromFile(String certPath, String logTitle) { 264 ValidateUtils.throwIfNotMatches(!StringUtils.isEmpty(certPath), ERROR.PARAM_NOT_EXIST_ERROR, 265 String.format("Params '%s' not exist", logTitle)); 266 267 File certFile = new File(certPath); 268 ValidateUtils.throwIfNotMatches(certFile.exists(), ERROR.FILE_NOT_FOUND, 269 String.format("%s: '%s' not exist", logTitle, certPath)); 270 List<X509Certificate> certificates = null; 271 try { 272 certificates = CertUtils.generateCertificates(FileUtils.readFile(certFile)); 273 } catch (IOException | CertificateException | VerifyCertificateChainException exception) { 274 logger.debug(exception.getMessage(), exception); 275 CustomException.throwException(ERROR.ACCESS_ERROR, exception.getMessage() 276 + "\nSolutions:" 277 + "\n> The certificate format is incorrect, please check your appCertFile parameter."); 278 } 279 ValidateUtils.throwIfNotMatches(certificates != null && certificates.size() > 0, ERROR.READ_FILE_ERROR, 280 String.format("Read fail from %s, bot found certificates", certPath)); 281 return certificates; 282 } 283 284 /** 285 * Get signature algorithm. 286 * 287 * @return signature algorithm. 288 */ getSignAlg()289 public String getSignAlg() { 290 return options.getString(Options.SIGN_ALG); 291 } 292 293 /** 294 * Check whether the key usage is critical. 295 * 296 * @return result indicating whether the key usage is critical. 297 */ isKeyUsageCritical()298 public boolean isKeyUsageCritical() { 299 return options.getBoolean(Options.KEY_USAGE_CRITICAL, true); 300 } 301 302 /** 303 * Check whether the external key usage is critical. 304 * 305 * @return result indicating whether the external key usage is critical. 306 */ isExtKeyUsageCritical()307 public boolean isExtKeyUsageCritical() { 308 return options.getBoolean(Options.EXT_KEY_USAGE_CRITICAL, true); 309 } 310 311 /** 312 * Check whether the basic constraints is ca. 313 * 314 * @return result indicating whether the basic constraints is ca. 315 */ isBasicConstraintsCa()316 public boolean isBasicConstraintsCa() { 317 return options.getBoolean(Options.BASIC_CONSTRAINTS_CA, false); 318 } 319 320 /** 321 * Check whether the basic constraints is critical. 322 * 323 * @return result indicating whether the basic constraints is critical. 324 */ isBasicConstraintsCritical()325 public boolean isBasicConstraintsCritical() { 326 return options.getBoolean(Options.BASIC_CONSTRAINTS_CRITICAL, false); 327 } 328 329 /** 330 * Get the path length of basic constraints. 331 * 332 * @return the path length of basic constraints. 333 */ getBasicConstraintsPathLen()334 public int getBasicConstraintsPathLen() { 335 return options.getInt(Options.BASIC_CONSTRAINTS_PATH_LEN); 336 } 337 338 /** 339 * Get the external key usage. 340 * 341 * @return KeyPurposeId[] of ExtKeyUsage. 342 */ getExtKeyUsage()343 public KeyPurposeId[] getExtKeyUsage() { 344 return CertUtils.parseExtKeyUsage(options.getString(Options.EXT_KEY_USAGE)); 345 } 346 347 /** 348 * Get the key usage. 349 * 350 * @return the key usage. 351 */ getKeyUsage()352 public KeyUsage getKeyUsage() { 353 return new KeyUsage(CertUtils.parseKeyUsage(options.getString(Options.KEY_USAGE))); 354 } 355 356 /** 357 * Get the subject of cert. 358 * 359 * @return the subject of cert. 360 */ getSubject()361 public X500Name getSubject() { 362 String subject = options.getString(Options.SUBJECT); 363 return CertUtils.buildDN(subject); 364 } 365 366 /** 367 * Get the subject of issuer. 368 * 369 * @return the subject of issuer. 370 */ getIssuer()371 public X500Name getIssuer() { 372 String issuer = options.getString(Options.ISSUER, options.getString(Options.SUBJECT)); 373 return CertUtils.buildDN(issuer); 374 } 375 376 /** 377 * Get the output file. 378 * 379 * @return the string of output file. 380 */ getOutFile()381 public String getOutFile() { 382 return options.getString(Options.OUT_FILE); 383 } 384 385 /** 386 * Get the input file. 387 * 388 * @return the string of input file. 389 */ getInFile()390 public String getInFile() { 391 String file = options.getString(Options.IN_FILE); 392 ValidateUtils.throwIfNotMatches(new File(file).exists(), ERROR.FILE_NOT_FOUND, 393 String.format("Required %s: '%s' not exist", Options.IN_FILE, file)); 394 return file; 395 } 396 397 /** 398 * Check if it is a remote signature. 399 * 400 * @return result indicating whether the signer is a remote signer. 401 */ isRemoteSigner()402 public boolean isRemoteSigner() { 403 String mode = options.getString(Options.MODE, "localSign"); 404 return "remoteSign".equalsIgnoreCase(mode); 405 } 406 407 /** 408 * Reset the password to ensure security. 409 */ releasePwd()410 public void releasePwd() { 411 resetChars(options.getChars(Options.KEY_STORE_RIGHTS)); 412 resetChars(options.getChars(Options.KEY_RIGHTS)); 413 resetChars(options.getChars(Options.ISSUER_KEY_RIGHTS)); 414 resetChars(options.getChars(Options.ISSUER_KEY_STORE_RIGHTS)); 415 } 416 resetChars(char[] chars)417 private void resetChars(char[] chars) { 418 if (chars == null) { 419 return; 420 } 421 Arrays.fill(chars, (char) 0); 422 } 423 } 424