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 21 import com.ohos.hapsigntool.error.SignToolErrMsg; 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 LogUtils LOGGER = new LogUtils(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), 110 ERROR.COMMAND_ERROR, SignToolErrMsg.MISSING_PARAM.toString("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, SignToolErrMsg.INIT_KEYSTORE_FAILED 129 .toString(exception.getMessage())); 130 } finally { 131 FileUtils.close(fis); 132 } 133 } 134 getKeyStorePath()135 public String getKeyStorePath() { 136 return keyStorePath; 137 } 138 139 /** 140 * Throw exception if alias exist in keystore. 141 * 142 * @param alias alias of key 143 */ errorOnExist(String alias)144 public void errorOnExist(String alias) { 145 ValidateUtils.throwIfMatches(this.hasAlias(alias), 146 ERROR.ACCESS_ERROR, SignToolErrMsg.KEY_ALIAS_EXIST.toString(alias, this.keyStorePath)); 147 } 148 149 /** 150 * Throw exception if alias no exist in keystore. 151 * 152 * @param alias alias of key 153 */ errorIfNotExist(String alias)154 public void errorIfNotExist(String alias) { 155 ValidateUtils.throwIfNotMatches(this.hasAlias(alias), 156 ERROR.FILE_NOT_FOUND, SignToolErrMsg.KEY_ALIAS_NOT_FOUND.toString(alias, this.keyStorePath)); 157 } 158 159 /** 160 * Check if keystore contain the alias. 161 * 162 * @param alias key alias 163 * @return result 164 */ hasAlias(String alias)165 public boolean hasAlias(String alias) { 166 try { 167 return keyStore.containsAlias(alias); 168 } catch (KeyStoreException exception) { 169 LOGGER.debug(exception.getMessage(), exception); 170 CustomException.throwException(ERROR.ACCESS_ERROR, SignToolErrMsg.KEYSTORE_ERROR 171 .toString(exception.getMessage())); 172 return false; 173 } 174 } 175 176 /** 177 * Load keypair form keystore. 178 * 179 * @param alias alias 180 * @param certPwd key pwd 181 * @return Keypair 182 */ loadKeyPair(String alias, char[] certPwd)183 public KeyPair loadKeyPair(String alias, char[] certPwd) { 184 List<X509Certificate> certificates = loadCertificates(alias); 185 PrivateKey privateKey = loadPrivateKey(alias, certPwd); 186 return new KeyPair(certificates.get(0).getPublicKey(), privateKey); 187 } 188 189 /** 190 * Get private key from give key store 191 * 192 * @param alias Cert alias 193 * @param certPwd Cert pwd 194 * @return private key 195 */ loadPrivateKey(String alias, char[] certPwd)196 public PrivateKey loadPrivateKey(String alias, char[] certPwd) { 197 char[] pwd = certPwd; 198 if (pwd == null) { 199 pwd = new char[0]; 200 } 201 try { 202 Key key = keyStore.getKey(alias, pwd); 203 if (key instanceof PrivateKey) { 204 return (PrivateKey) key; 205 } 206 } catch (KeyStoreException | NoSuchAlgorithmException exception) { 207 LOGGER.debug(exception.getMessage(), exception); 208 CustomException.throwException(ERROR.ACCESS_ERROR, SignToolErrMsg.NO_SUCH_SIGNATURE 209 .toString(exception.getMessage())); 210 } catch (UnrecoverableKeyException exception) { 211 LOGGER.debug(exception.getMessage(), exception); 212 CustomException.throwException(ERROR.ACCESS_ERROR, SignToolErrMsg.KEY_PASSWORD_ERROR 213 .toString(alias, exception)); 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), 245 ERROR.FILE_NOT_FOUND, SignToolErrMsg.KEY_ALIAS_NOT_FOUND.toString(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, SignToolErrMsg.KEYSTORE_ERROR 263 .toString(exception.getMessage())); 264 } 265 266 ValidateUtils.throwIfNotMatches(!certificates.isEmpty(), ERROR.ACCESS_ERROR, SignToolErrMsg.NO_USABLE_CERT 267 .toString(this.keyStorePath)); 268 return certificates; 269 } 270 271 /** 272 * Create simple keystore file. 273 * Exist if file exist. 274 * 275 * @param alias cert alias 276 * @param keyPwd password of String format 277 * @param keyPair keypair to save 278 * @param certs will create one if null 279 * @return Boolean value. 280 */ store(String alias, char[] keyPwd, KeyPair keyPair, X509Certificate[] certs)281 public boolean store(String alias, char[] keyPwd, KeyPair keyPair, X509Certificate[] certs) { 282 errorOnExist(alias); 283 char[] pwd = keyPwd; 284 if (pwd == null) { 285 pwd = new char[0]; 286 } 287 X509Certificate[] certificates = certs; 288 if (certificates == null) { 289 X509Certificate certificate = createKeyOnly(keyPair, alias); 290 certificates = new X509Certificate[]{certificate}; 291 } 292 try (FileOutputStream fos = new FileOutputStream(keyStorePath)) { 293 keyStore.setKeyEntry(alias, keyPair.getPrivate(), pwd, certificates); 294 keyStore.store(fos, keyStorePwd); 295 fos.flush(); 296 } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException exception) { 297 LOGGER.debug(exception.getMessage(), exception); 298 CustomException.throwException(ERROR.WRITE_FILE_ERROR, SignToolErrMsg.FILE_WRITE_FAILED 299 .toString(exception.getMessage())); 300 return false; 301 } 302 return true; 303 } 304 createKeyOnly(KeyPair keyPair, String alias)305 private X509Certificate createKeyOnly(KeyPair keyPair, String alias) { 306 X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); 307 nameBuilder.addRDN(BCStyle.CN, alias); 308 309 LocalDateTime notBefore = LocalDateTime.now(); 310 LocalDateTime notAfter = notBefore.plusYears(NUM_ONE_HUNDRED); 311 312 X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder( 313 nameBuilder.build(), 314 CertUtils.randomSerial(), 315 Date.from(notBefore.atZone(ZoneId.systemDefault()).toInstant()), 316 Date.from(notAfter.atZone(ZoneId.systemDefault()).toInstant()), 317 nameBuilder.build(), 318 keyPair.getPublic() 319 ); 320 ContentSigner contentSigner = CertUtils.createFixedContentSigner(keyPair.getPrivate(), "SHA256withRSA"); 321 try { 322 return new JcaX509CertificateConverter().setProvider("BC") 323 .getCertificate(certificateBuilder.build(contentSigner)); 324 } catch (CertificateException exception) { 325 LOGGER.debug(exception.getMessage(), exception); 326 CustomException.throwException(ERROR.IO_CERT_ERROR, SignToolErrMsg.CERT_IO_FAILED 327 .toString(exception.getMessage())); 328 return null; 329 } 330 } 331 332 /** 333 * Auto create jks/pkcs12 keystore according file type 334 * 335 * @param keyFile keystore file path 336 * @return keystore instance 337 */ createKeyStoreAccordingFileType(String keyFile)338 private KeyStore createKeyStoreAccordingFileType(String keyFile) { 339 KeyStore typeKeyStore = null; 340 String type = FileUtils.getSuffix(keyFile); 341 try { 342 if (type.equalsIgnoreCase(FILE_TYPE_PKCS12)) { 343 typeKeyStore = KeyStore.getInstance(KEYSTORE_TYPE_PKCS12); 344 } else if (type.equalsIgnoreCase(FILE_TYPE_JKS)) { 345 typeKeyStore = KeyStore.getInstance(KEYSTORE_TYPE_JKS); 346 } else { 347 typeKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 348 } 349 } catch (KeyStoreException exception) { 350 LOGGER.debug(exception.getMessage(), exception); 351 CustomException.throwException(ERROR.KEYSTORE_OPERATION_ERROR, SignToolErrMsg.KEYSTORE_ERROR 352 .toString(exception.getMessage())); 353 } 354 return typeKeyStore; 355 } 356 } 357