• 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.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