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.utils; 17 18 import com.ohos.hapsigntool.error.CustomException; 19 import com.ohos.hapsigntool.error.ERROR; 20 import org.apache.logging.log4j.LogManager; 21 import org.apache.logging.log4j.Logger; 22 import org.bouncycastle.asn1.x500.X500NameBuilder; 23 import org.bouncycastle.asn1.x500.style.BCStyle; 24 import org.bouncycastle.cert.X509v3CertificateBuilder; 25 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; 26 import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; 27 import org.bouncycastle.operator.ContentSigner; 28 29 import java.io.FileInputStream; 30 import java.io.FileOutputStream; 31 import java.io.IOException; 32 import java.security.Key; 33 import java.security.KeyPair; 34 import java.security.KeyStore; 35 import java.security.KeyStoreException; 36 import java.security.NoSuchAlgorithmException; 37 import java.security.PrivateKey; 38 import java.security.UnrecoverableKeyException; 39 import java.security.cert.Certificate; 40 import java.security.cert.CertificateException; 41 import java.security.cert.CertificateExpiredException; 42 import java.security.cert.CertificateNotYetValidException; 43 import java.security.cert.X509Certificate; 44 import java.time.LocalDateTime; 45 import java.time.ZoneId; 46 import java.util.ArrayList; 47 import java.util.Date; 48 import java.util.List; 49 50 /** 51 * Read and save Keypair and certificate. 52 * 53 * @since 2021/12/28 54 */ 55 public class KeyStoreHelper { 56 /** 57 * Field KEYSTORE_TYPE_PKCS12. 58 */ 59 private static final String KEYSTORE_TYPE_PKCS12 = "pkcs12"; 60 61 /** 62 * Field KEYSTORE_TYPE_JKS. 63 */ 64 private static final String KEYSTORE_TYPE_JKS = "jks"; 65 66 /** 67 * Field FILE_TYPE_JKS. 68 */ 69 private static final String FILE_TYPE_JKS = "jks"; 70 71 /** 72 * Field FILE_TYPE_PKCS12. 73 */ 74 private static final String FILE_TYPE_PKCS12 = "p12"; 75 76 /** 77 * Field number 100. 78 */ 79 private static final int NUM_ONE_HUNDRED = 100; 80 81 /** 82 * Use LogManager to show log instead of use "System.out.print" to show log. 83 */ 84 private static final Logger logger = LogManager.getLogger(KeyStoreHelper.class); 85 86 /** 87 * Field keyStorePath. 88 */ 89 private final String keyStorePath; 90 91 /** 92 * Field keyStorePwd. 93 */ 94 private final char[] keyStorePwd; 95 96 /** 97 * Field keyStore. 98 */ 99 private final KeyStore keyStore; 100 101 /** 102 * Helper to load and save pair. 103 * 104 * @param keyStorePath File path 105 * @param storePwd passwd of key store 106 */ KeyStoreHelper(String keyStorePath, char[] storePwd)107 public KeyStoreHelper(String keyStorePath, char[] storePwd) { 108 char[] pwd = storePwd; 109 ValidateUtils.throwIfMatches(StringUtils.isEmpty(keyStorePath), ERROR.COMMAND_ERROR, 110 "Missed params: 'keyStorePath'"); 111 if (pwd == null) { 112 pwd = new char[0]; 113 } 114 this.keyStorePwd = pwd; 115 this.keyStorePath = keyStorePath; 116 this.keyStore = createKeyStoreAccordingFileType(keyStorePath); 117 FileInputStream fis = null; 118 try { 119 if (FileUtils.isFileExist(keyStorePath)) { 120 logger.info("{} is exist. Try to load it with given passwd", keyStorePath); 121 fis = new FileInputStream(keyStorePath); 122 keyStore.load(fis, pwd); 123 } else { 124 keyStore.load(null, null); 125 } 126 } catch (IOException | NoSuchAlgorithmException | CertificateException exception) { 127 logger.debug(exception.getMessage(), exception); 128 CustomException.throwException(ERROR.ACCESS_ERROR, "Init keystore failed: " + exception.getMessage() 129 + "\nSolutions:" 130 + "\n> The key store file does not exist, please check the key store file path." 131 + "\n> Incorrect keystore password, please input the correct plaintext password." 132 + "\n> The keystore was created by a newer JDK version, please use the same JDK version"); 133 } finally { 134 FileUtils.close(fis); 135 } 136 } 137 getKeyStorePath()138 public String getKeyStorePath() { 139 return keyStorePath; 140 } 141 142 /** 143 * Throw exception if alias exist in keystore. 144 * 145 * @param alias alias of key 146 */ errorOnExist(String alias)147 public void errorOnExist(String alias) { 148 ValidateUtils.throwIfMatches(this.hasAlias(alias), ERROR.ACCESS_ERROR, 149 String.format("Could not overwrite! Already exist '%s' in %s", alias, this.keyStorePath)); 150 } 151 152 /** 153 * Throw exception if alias no exist in keystore. 154 * 155 * @param alias alias of key 156 */ errorIfNotExist(String alias)157 public void errorIfNotExist(String alias) { 158 ValidateUtils.throwIfNotMatches(this.hasAlias(alias), ERROR.FILE_NOT_FOUND, 159 String.format("Not exist '%s' in %s", alias, this.keyStorePath)); 160 } 161 162 /** 163 * Check if keystore contain the alias. 164 * 165 * @param alias key alias 166 * @return result 167 */ hasAlias(String alias)168 public boolean hasAlias(String alias) { 169 try { 170 return keyStore.containsAlias(alias); 171 } catch (KeyStoreException exception) { 172 logger.debug(exception.getMessage(), exception); 173 CustomException.throwException(ERROR.ACCESS_ERROR, exception.getMessage()); 174 return false; 175 } 176 } 177 178 /** 179 * Load keypair form keystore. 180 * 181 * @param alias alias 182 * @param certPwd key pwd 183 * @return Keypair 184 */ loadKeyPair(String alias, char[] certPwd)185 public KeyPair loadKeyPair(String alias, char[] certPwd) { 186 List<X509Certificate> certificates = loadCertificates(alias); 187 PrivateKey privateKey = loadPrivateKey(alias, certPwd); 188 return new KeyPair(certificates.get(0).getPublicKey(), privateKey); 189 } 190 191 /** 192 * Get private key from give key store 193 * 194 * @param alias Cert alias 195 * @param certPwd Cert pwd 196 * @return private key 197 */ loadPrivateKey(String alias, char[] certPwd)198 public PrivateKey loadPrivateKey(String alias, char[] certPwd) { 199 char[] pwd = certPwd; 200 if (pwd == null) { 201 pwd = new char[0]; 202 } 203 try { 204 Key key = keyStore.getKey(alias, pwd); 205 if (key instanceof PrivateKey) { 206 return (PrivateKey) key; 207 } 208 } catch (KeyStoreException | NoSuchAlgorithmException exception) { 209 logger.debug(exception.getMessage(), exception); 210 CustomException.throwException(ERROR.ACCESS_ERROR, exception.getMessage()); 211 } catch (UnrecoverableKeyException exception) { 212 logger.debug(exception.getMessage(), exception); 213 CustomException.throwException(ERROR.ACCESS_ERROR, "Password error of '" + alias + "'"); 214 } 215 return null; 216 } 217 218 /** 219 * Validate the cert and save into cert list. 220 * 221 * @param certificates Result list to save 222 * @param certificate Cert to validate 223 */ putValidCert(List<X509Certificate> certificates, Certificate certificate)224 private void putValidCert(List<X509Certificate> certificates, Certificate certificate) { 225 if (!(certificate instanceof X509Certificate)) { 226 return; 227 } 228 X509Certificate cert = (X509Certificate) certificate; 229 try { 230 cert.checkValidity(); 231 } catch (CertificateExpiredException | CertificateNotYetValidException exception) { 232 logger.info("p12's certificates is not valid"); 233 } 234 certificates.add(cert); 235 } 236 237 /** 238 * Get certificates from keystore. 239 * 240 * @param alias cert alias 241 * @return certificates of alias 242 */ loadCertificates(String alias)243 public List<X509Certificate> loadCertificates(String alias) { 244 ValidateUtils.throwIfNotMatches(this.hasAlias(alias), ERROR.FILE_NOT_FOUND, 245 String.format("Not found '%s' in %s", alias, this.keyStorePath)); 246 247 List<X509Certificate> certificates = new ArrayList<>(); 248 try { 249 if (keyStore.isKeyEntry(alias)) { 250 Certificate[] certs = keyStore.getCertificateChain(alias); 251 for (Certificate certificate : certs) { 252 putValidCert(certificates, certificate); 253 } 254 } else { 255 if (keyStore.isCertificateEntry(alias)) { 256 Certificate cert = keyStore.getCertificate(alias); 257 putValidCert(certificates, cert); 258 } 259 } 260 } catch (KeyStoreException exception) { 261 logger.debug(exception.getMessage(), exception); 262 CustomException.throwException(ERROR.KEYSTORE_OPERATION_ERROR, exception.getMessage()); 263 } 264 265 ValidateUtils.throwIfNotMatches(certificates.size() > 0, ERROR.ACCESS_ERROR, 266 "No usable cert found in " + this.keyStorePath); 267 return certificates; 268 } 269 270 /** 271 * Create simple keystore file. 272 * Exist if file exist. 273 * 274 * @param alias cert alias 275 * @param keyPwd password of String format 276 * @param keyPair keypair to save 277 * @param certs will create one if null 278 * @return Boolean value. 279 */ store(String alias, char[] keyPwd, KeyPair keyPair, X509Certificate[] certs)280 public boolean store(String alias, char[] keyPwd, KeyPair keyPair, X509Certificate[] certs) { 281 errorOnExist(alias); 282 char[] pwd = keyPwd; 283 if (pwd == null) { 284 pwd = new char[0]; 285 } 286 X509Certificate[] certificates = certs; 287 if (certificates == null) { 288 X509Certificate certificate = createKeyOnly(keyPair, alias); 289 certificates = new X509Certificate[]{certificate}; 290 } 291 try (FileOutputStream fos = new FileOutputStream(keyStorePath)) { 292 keyStore.setKeyEntry(alias, keyPair.getPrivate(), pwd, certificates); 293 keyStore.store(fos, keyStorePwd); 294 fos.flush(); 295 } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException exception) { 296 logger.debug(exception.getMessage(), exception); 297 CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); 298 return false; 299 } 300 return true; 301 } 302 createKeyOnly(KeyPair keyPair, String alias)303 private X509Certificate createKeyOnly(KeyPair keyPair, String alias) { 304 X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); 305 nameBuilder.addRDN(BCStyle.CN, alias); 306 307 LocalDateTime notBefore = LocalDateTime.now(); 308 LocalDateTime notAfter = notBefore.plusYears(NUM_ONE_HUNDRED); 309 310 X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder( 311 nameBuilder.build(), 312 CertUtils.randomSerial(), 313 Date.from(notBefore.atZone(ZoneId.systemDefault()).toInstant()), 314 Date.from(notAfter.atZone(ZoneId.systemDefault()).toInstant()), 315 nameBuilder.build(), 316 keyPair.getPublic() 317 ); 318 ContentSigner contentSigner = CertUtils.createFixedContentSigner(keyPair.getPrivate(), "SHA256withRSA"); 319 try { 320 return new JcaX509CertificateConverter().setProvider("BC") 321 .getCertificate(certificateBuilder.build(contentSigner)); 322 } catch (CertificateException exception) { 323 logger.debug(exception.getMessage(), exception); 324 CustomException.throwException(ERROR.IO_CERT_ERROR, exception.getMessage()); 325 return null; 326 } 327 } 328 329 /** 330 * Auto create jks/pkcs12 keystore according file type 331 * 332 * @param keyFile keystore file path 333 * @return keystore instance 334 */ createKeyStoreAccordingFileType(String keyFile)335 private KeyStore createKeyStoreAccordingFileType(String keyFile) { 336 KeyStore typeKeyStore = null; 337 String type = FileUtils.getSuffix(keyFile); 338 try { 339 if (type.equalsIgnoreCase(FILE_TYPE_PKCS12)) { 340 typeKeyStore = KeyStore.getInstance(KEYSTORE_TYPE_PKCS12); 341 } else if (type.equalsIgnoreCase(FILE_TYPE_JKS)) { 342 typeKeyStore = KeyStore.getInstance(KEYSTORE_TYPE_JKS); 343 } else { 344 typeKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 345 } 346 } catch (KeyStoreException exception) { 347 logger.debug(exception.getMessage(), exception); 348 CustomException.throwException(ERROR.KEYSTORE_OPERATION_ERROR, exception.getMessage()); 349 } 350 return typeKeyStore; 351 } 352 } 353