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