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