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.KeyPair; 33 import java.security.KeyStore; 34 import java.security.KeyStoreException; 35 import java.security.NoSuchAlgorithmException; 36 import java.security.PrivateKey; 37 import java.security.UnrecoverableKeyException; 38 import java.security.cert.Certificate; 39 import java.security.cert.CertificateException; 40 import java.security.cert.CertificateExpiredException; 41 import java.security.cert.CertificateNotYetValidException; 42 import java.security.cert.X509Certificate; 43 import java.time.LocalDateTime; 44 import java.time.ZoneId; 45 import java.util.ArrayList; 46 import java.util.Date; 47 import java.util.List; 48 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 getKeyStorePath()101 public String getKeyStorePath() { 102 return keyStorePath; 103 } 104 105 /** 106 * Helper to load and save pair. 107 * 108 * @param keyStorePath File path 109 * @param storePwd passwd of key store 110 */ KeyStoreHelper(String keyStorePath, char[] storePwd)111 public KeyStoreHelper(String keyStorePath, char[] storePwd) { 112 ValidateUtils.throwIfMatches(StringUtils.isEmpty(keyStorePath), ERROR.COMMAND_ERROR, 113 "Missed params: 'keyStorePath'"); 114 if (storePwd == null) { 115 storePwd = new char[0]; 116 } 117 this.keyStorePwd = storePwd; 118 this.keyStorePath = keyStorePath; 119 this.keyStore = createKeyStoreAccordingFileType(keyStorePath); 120 FileInputStream fis = null; 121 try { 122 if (FileUtils.isFileExist(keyStorePath)) { 123 logger.info("{} is exist. Try to load it with given passwd",keyStorePath); 124 fis = new FileInputStream(keyStorePath); 125 keyStore.load(fis, storePwd); 126 } else { 127 keyStore.load(null, null); 128 } 129 } catch (IOException | NoSuchAlgorithmException | CertificateException exception) { 130 logger.debug(exception.getMessage(), exception); 131 CustomException.throwException(ERROR.ACCESS_ERROR, "Init keystore failed: " + exception.getMessage()); 132 }finally { 133 FileUtils.close(fis); 134 } 135 } 136 137 /** 138 * Throw exception if alias exist in keystore. 139 * 140 * @param alias alias of key 141 */ errorOnExist(String alias)142 public void errorOnExist(String alias) { 143 ValidateUtils.throwIfMatches(this.hasAlias(alias), ERROR.ACCESS_ERROR, 144 String.format("Could not overwrite! Already exist '%s' in %s", alias, this.keyStorePath)); 145 } 146 147 /** 148 * Throw exception if alias no exist in keystore. 149 * 150 * @param alias alias of key 151 */ errorIfNotExist(String alias)152 public void errorIfNotExist(String alias) { 153 ValidateUtils.throwIfNotMatches(this.hasAlias(alias), ERROR.FILE_NOT_FOUND, 154 String.format("Not exist '%s' in %s", alias, this.keyStorePath)); 155 } 156 157 /** 158 * Check if keystore contain the alias. 159 * 160 * @param alias key alias 161 * @return result 162 */ hasAlias(String alias)163 public boolean hasAlias(String alias) { 164 try { 165 return keyStore.containsAlias(alias); 166 } catch (KeyStoreException exception) { 167 logger.debug(exception.getMessage(), exception); 168 CustomException.throwException(ERROR.ACCESS_ERROR, exception.getMessage()); 169 return false; 170 } 171 } 172 173 /** 174 * Load keypair form keystore. 175 * 176 * @param alias alias 177 * @param certPwd key pwd 178 * @return Keypair 179 */ loadKeyPair(String alias, char[] certPwd)180 public KeyPair loadKeyPair(String alias, char[] certPwd) { 181 List<X509Certificate> certificates = loadCertificates(alias); 182 PrivateKey privateKey = loadPrivateKey(alias, certPwd); 183 return new KeyPair(certificates.get(0).getPublicKey(), privateKey); 184 } 185 186 /** 187 * Get private key from give key store 188 * 189 * @param alias Cert alias 190 * @param certPwd Cert pwd 191 * @return private key 192 */ loadPrivateKey(String alias, char[] certPwd)193 public PrivateKey loadPrivateKey(String alias, char[] certPwd) { 194 if (certPwd == null) { 195 certPwd = new char[0]; 196 } 197 try { 198 return (PrivateKey) keyStore.getKey(alias, certPwd); 199 } catch (KeyStoreException | NoSuchAlgorithmException exception) { 200 logger.debug(exception.getMessage(), exception); 201 CustomException.throwException(ERROR.ACCESS_ERROR, exception.getMessage()); 202 } catch (UnrecoverableKeyException exception) { 203 logger.debug(exception.getMessage(), exception); 204 CustomException.throwException(ERROR.ACCESS_ERROR, "Password error of '" + alias + "'"); 205 } 206 return null; 207 } 208 209 /** 210 * Validate the cert and save into cert list. 211 * 212 * @param certificates Result list to save 213 * @param certificate Cert to validate 214 */ putValidCert(List<X509Certificate> certificates, Certificate certificate)215 private void putValidCert(List<X509Certificate> certificates, Certificate certificate) { 216 if (!(certificate instanceof X509Certificate)) { 217 return; 218 } 219 X509Certificate cert = (X509Certificate) certificate; 220 try { 221 cert.checkValidity(); 222 } catch (CertificateExpiredException | CertificateNotYetValidException exception) { 223 logger.info("p12's certificates is not valid"); 224 } 225 certificates.add(cert); 226 } 227 228 /** 229 * Get certificates from keystore. 230 * 231 * @param alias cert alias 232 * @return certificates of alias 233 */ loadCertificates(String alias)234 public List<X509Certificate> loadCertificates(String alias) { 235 ValidateUtils.throwIfNotMatches(this.hasAlias(alias), ERROR.FILE_NOT_FOUND, 236 String.format("Not found '%s' in %s", alias, this.keyStorePath)); 237 238 List<X509Certificate> certificates = new ArrayList<>(); 239 try { 240 if (keyStore.isKeyEntry(alias)) { 241 Certificate[] certs = keyStore.getCertificateChain(alias); 242 for (Certificate certificate : certs) { 243 putValidCert(certificates, certificate); 244 } 245 } else { 246 if (keyStore.isCertificateEntry(alias)) { 247 Certificate cert = keyStore.getCertificate(alias); 248 putValidCert(certificates, cert); 249 } 250 } 251 } catch (KeyStoreException exception) { 252 logger.debug(exception.getMessage(), exception); 253 CustomException.throwException(ERROR.KEYSTORE_OPERATION_ERROR, exception.getMessage()); 254 } 255 256 ValidateUtils.throwIfNotMatches(certificates.size() > 0, ERROR.ACCESS_ERROR, 257 "No usable cert found in " + this.keyStorePath); 258 return certificates; 259 } 260 261 /** 262 * Create simple keystore file. 263 * Exist if file exist. 264 * 265 * @param alias cert alias 266 * @param keyPwd password of String format 267 * @param keyPair keypair to save 268 * @param certs will create one if null 269 * @return Boolean value. 270 */ store(String alias, char[] keyPwd, KeyPair keyPair, X509Certificate[] certs)271 public boolean store(String alias, char[] keyPwd, KeyPair keyPair, X509Certificate[] certs) { 272 errorOnExist(alias); 273 if (keyPwd == null) { 274 keyPwd = new char[0]; 275 } 276 if (certs == null) { 277 X509Certificate certificate = createKeyOnly(keyPair, alias); 278 certs = new X509Certificate[]{certificate}; 279 } 280 try (FileOutputStream fos = new FileOutputStream(keyStorePath)){ 281 keyStore.setKeyEntry(alias, keyPair.getPrivate(), keyPwd, certs); 282 keyStore.store(fos, keyStorePwd); 283 fos.flush(); 284 } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException exception) { 285 logger.debug(exception.getMessage(), exception); 286 CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); 287 return false; 288 } 289 return true; 290 } 291 createKeyOnly(KeyPair keyPair, String alias)292 private X509Certificate createKeyOnly(KeyPair keyPair, String alias) { 293 X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); 294 nameBuilder.addRDN(BCStyle.CN, alias); 295 296 LocalDateTime notBefore = LocalDateTime.now(); 297 LocalDateTime notAfter = notBefore.plusYears(NUM_ONE_HUNDRED); 298 299 X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder( 300 nameBuilder.build(), 301 CertUtils.randomSerial(), 302 Date.from(notBefore.atZone(ZoneId.systemDefault()).toInstant()), 303 Date.from(notAfter.atZone(ZoneId.systemDefault()).toInstant()), 304 nameBuilder.build(), 305 keyPair.getPublic() 306 ); 307 ContentSigner contentSigner = CertUtils.createFixedContentSigner(keyPair.getPrivate(), "SHA256withRSA"); 308 try { 309 return new JcaX509CertificateConverter().setProvider("BC") 310 .getCertificate(certificateBuilder.build(contentSigner)); 311 } catch (CertificateException exception) { 312 logger.debug(exception.getMessage(), exception); 313 CustomException.throwException(ERROR.IO_CERT_ERROR, exception.getMessage()); 314 return null; 315 } 316 } 317 318 /** 319 * Auto create jks/pkcs12 keystore according file type 320 * 321 * @param keyFile keystore file path 322 * @return keystore instance 323 */ createKeyStoreAccordingFileType(String keyFile)324 private KeyStore createKeyStoreAccordingFileType(String keyFile) { 325 KeyStore typeKeyStore = null; 326 String type = FileUtils.getSuffix(keyFile); 327 try { 328 if (type.equalsIgnoreCase(FILE_TYPE_PKCS12)) { 329 typeKeyStore = KeyStore.getInstance(KEYSTORE_TYPE_PKCS12); 330 } else if (type.equalsIgnoreCase(FILE_TYPE_JKS)) { 331 typeKeyStore = KeyStore.getInstance(KEYSTORE_TYPE_JKS); 332 } else { 333 typeKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 334 } 335 } catch (KeyStoreException exception) { 336 logger.debug(exception.getMessage(), exception); 337 CustomException.throwException(ERROR.KEYSTORE_OPERATION_ERROR, exception.getMessage()); 338 } 339 return typeKeyStore; 340 } 341 } 342