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