• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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