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