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.SignToolErrMsg; 22 import com.ohos.hapsigntool.error.VerifyCertificateChainException; 23 import com.ohos.hapsigntool.utils.KeyPairTools; 24 import com.ohos.hapsigntool.utils.KeyStoreHelper; 25 import com.ohos.hapsigntool.utils.CertUtils; 26 import com.ohos.hapsigntool.utils.FileUtils; 27 import com.ohos.hapsigntool.utils.LogUtils; 28 import com.ohos.hapsigntool.utils.StringUtils; 29 import com.ohos.hapsigntool.utils.ValidateUtils; 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 LogUtils LOGGER = new LogUtils(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, 190 SignToolErrMsg.PARAM_VALUE_EMPTY.toString("KeyAlias")); 191 KeyPair keyPair = null; 192 if (keyStoreHelper.hasAlias(alias)) { 193 keyPair = keyStoreHelper.loadKeyPair(alias, keyPwd); 194 } else { 195 if (autoCreate) { 196 options.required(Options.KEY_ALG, Options.KEY_SIZE); 197 keyPair = KeyPairTools.generateKeyPair(options.getString(Options.KEY_ALG), 198 options.getInt(Options.KEY_SIZE)); 199 keyStoreHelper.store(alias, keyPwd, keyPair, null); 200 } 201 } 202 ValidateUtils.throwIfNotMatches(keyPair != null, ERROR.PARAM_NOT_EXIST_ERROR, 203 SignToolErrMsg.KEY_ALIAS_NOT_FOUND.toString(alias, 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, SignToolErrMsg.CERT_CHAIN_FORMAT_FAILED.toString(certPath)); 222 return certificates; 223 } 224 225 /** 226 * Get the cert file of sub ca. 227 * 228 * @return the cert file of sub ca. 229 */ getSubCaCertFile()230 public X509Certificate getSubCaCertFile() { 231 String certPath = options.getString(Options.SUB_CA_CERT_FILE); 232 return getCertsFromFile(certPath, Options.SUB_CA_CERT_FILE).get(0); 233 } 234 235 /** 236 * Get the cert file of root ca. 237 * 238 * @return the cert file of root ca. 239 */ getCaCertFile()240 public X509Certificate getCaCertFile() { 241 String certPath = options.getString(Options.CA_CERT_FILE); 242 return getCertsFromFile(certPath, Options.CA_CERT_FILE).get(0); 243 } 244 245 /** 246 * Check whether the form is cert chain. 247 * 248 * @return result indicating whether the form is cert chain. 249 */ isOutFormChain()250 public boolean isOutFormChain() { 251 String outForm = options.getString(Options.OUT_FORM, "certChain"); 252 return "certChain".equals(outForm); 253 } 254 255 /** 256 * Get certificates from file. 257 * 258 * @param certPath the path of cert 259 * @param logTitle log title 260 * @return certificates 261 */ getCertsFromFile(String certPath, String logTitle)262 public List<X509Certificate> getCertsFromFile(String certPath, String logTitle) { 263 ValidateUtils.throwIfNotMatches(!StringUtils.isEmpty(certPath), ERROR.PARAM_NOT_EXIST_ERROR, 264 SignToolErrMsg.FILE_NOT_EXIST.toString(logTitle)); 265 266 File certFile = new File(certPath); 267 ValidateUtils.throwIfNotMatches(certFile.exists(), ERROR.FILE_NOT_FOUND, 268 SignToolErrMsg.FILE_NOT_EXIST.toString(certPath)); 269 List<X509Certificate> certificates = null; 270 try { 271 certificates = CertUtils.generateCertificates(FileUtils.readFile(certFile)); 272 } catch (IOException | CertificateException | VerifyCertificateChainException exception) { 273 LOGGER.debug(exception.getMessage(), exception); 274 CustomException.throwException(ERROR.ACCESS_ERROR, SignToolErrMsg.CERT_FORMAT_FAILED 275 .toString(exception.getMessage())); 276 } 277 ValidateUtils.throwIfNotMatches(certificates != null && !certificates.isEmpty(), ERROR.READ_FILE_ERROR, 278 SignToolErrMsg.CERT_FORMAT_FAILED.toString("can not found certificates in file " + certPath)); 279 return certificates; 280 } 281 282 /** 283 * Get signature algorithm. 284 * 285 * @return signature algorithm. 286 */ getSignAlg()287 public String getSignAlg() { 288 return options.getString(Options.SIGN_ALG); 289 } 290 291 /** 292 * Check whether the key usage is critical. 293 * 294 * @return result indicating whether the key usage is critical. 295 */ isKeyUsageCritical()296 public boolean isKeyUsageCritical() { 297 return options.getBoolean(Options.KEY_USAGE_CRITICAL, true); 298 } 299 300 /** 301 * Check whether the external key usage is critical. 302 * 303 * @return result indicating whether the external key usage is critical. 304 */ isExtKeyUsageCritical()305 public boolean isExtKeyUsageCritical() { 306 return options.getBoolean(Options.EXT_KEY_USAGE_CRITICAL, true); 307 } 308 309 /** 310 * Check whether the basic constraints is ca. 311 * 312 * @return result indicating whether the basic constraints is ca. 313 */ isBasicConstraintsCa()314 public boolean isBasicConstraintsCa() { 315 return options.getBoolean(Options.BASIC_CONSTRAINTS_CA, false); 316 } 317 318 /** 319 * Check whether the basic constraints is critical. 320 * 321 * @return result indicating whether the basic constraints is critical. 322 */ isBasicConstraintsCritical()323 public boolean isBasicConstraintsCritical() { 324 return options.getBoolean(Options.BASIC_CONSTRAINTS_CRITICAL, false); 325 } 326 327 /** 328 * Get the path length of basic constraints. 329 * 330 * @return the path length of basic constraints. 331 */ getBasicConstraintsPathLen()332 public int getBasicConstraintsPathLen() { 333 return options.getInt(Options.BASIC_CONSTRAINTS_PATH_LEN); 334 } 335 336 /** 337 * Get the external key usage. 338 * 339 * @return KeyPurposeId[] of ExtKeyUsage. 340 */ getExtKeyUsage()341 public KeyPurposeId[] getExtKeyUsage() { 342 return CertUtils.parseExtKeyUsage(options.getString(Options.EXT_KEY_USAGE)); 343 } 344 345 /** 346 * Get the key usage. 347 * 348 * @return the key usage. 349 */ getKeyUsage()350 public KeyUsage getKeyUsage() { 351 return new KeyUsage(CertUtils.parseKeyUsage(options.getString(Options.KEY_USAGE))); 352 } 353 354 /** 355 * Get the subject of cert. 356 * 357 * @return the subject of cert. 358 */ getSubject()359 public X500Name getSubject() { 360 String subject = options.getString(Options.SUBJECT); 361 return CertUtils.buildDN(subject); 362 } 363 364 /** 365 * Get the subject of issuer. 366 * 367 * @return the subject of issuer. 368 */ getIssuer()369 public X500Name getIssuer() { 370 String issuer = options.getString(Options.ISSUER, options.getString(Options.SUBJECT)); 371 return CertUtils.buildDN(issuer); 372 } 373 374 /** 375 * Get the output file. 376 * 377 * @return the string of output file. 378 */ getOutFile()379 public String getOutFile() { 380 return options.getString(Options.OUT_FILE); 381 } 382 383 /** 384 * Get the input file. 385 * 386 * @return the string of input file. 387 */ getInFile()388 public String getInFile() { 389 String file = options.getString(Options.IN_FILE); 390 ValidateUtils.throwIfNotMatches(new File(file).exists(), 391 ERROR.FILE_NOT_FOUND, SignToolErrMsg.FILE_NOT_EXIST.toString(file)); 392 return file; 393 } 394 395 /** 396 * Check if it is a remote signature. 397 * 398 * @return result indicating whether the signer is a remote signer. 399 */ isRemoteSigner()400 public boolean isRemoteSigner() { 401 String mode = options.getString(Options.MODE, "localSign"); 402 return "remoteSign".equalsIgnoreCase(mode); 403 } 404 405 /** 406 * Reset the password to ensure security. 407 */ releasePwd()408 public void releasePwd() { 409 resetChars(options.getChars(Options.KEY_STORE_RIGHTS)); 410 resetChars(options.getChars(Options.KEY_RIGHTS)); 411 resetChars(options.getChars(Options.ISSUER_KEY_RIGHTS)); 412 resetChars(options.getChars(Options.ISSUER_KEY_STORE_RIGHTS)); 413 } 414 resetChars(char[] chars)415 private void resetChars(char[] chars) { 416 if (chars == null) { 417 return; 418 } 419 Arrays.fill(chars, (char) 0); 420 } 421 } 422