• 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 com.ohos.hapsigntool.hap.exception.VerifyCertificateChainException;
21 import org.apache.logging.log4j.LogManager;
22 import org.apache.logging.log4j.Logger;
23 import org.bouncycastle.asn1.x500.X500Name;
24 import org.bouncycastle.asn1.x509.KeyPurposeId;
25 import org.bouncycastle.asn1.x509.KeyUsage;
26 import org.bouncycastle.util.io.pem.PemObject;
27 import org.bouncycastle.util.io.pem.PemWriter;
28 
29 import javax.security.auth.x500.X500Principal;
30 import java.io.ByteArrayInputStream;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.io.OutputStreamWriter;
34 import java.math.BigInteger;
35 import java.nio.charset.StandardCharsets;
36 import java.security.SecureRandom;
37 import java.security.cert.CRLException;
38 import java.security.cert.CertificateEncodingException;
39 import java.security.cert.CertificateException;
40 import java.security.cert.CertificateFactory;
41 import java.security.cert.X509CRL;
42 import java.security.cert.X509Certificate;
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.List;
46 
47 /**
48  * Cert Usage Util.
49  *
50  * @since 2021/12/28
51  */
52 public final class CertUtils {
53     /**
54      * Logger.
55      */
56     private static final Logger LOGGER = LogManager.getLogger(CertUtils.class);
57 
58     /**
59      * Max length to print certificate string.
60      */
61     private static final int MAX_LINE_LENGTH = 65;
62     /**
63      * Length of serial security random number.
64      */
65     private static final int RANDOM_SERIAL_LENGTH = 32;
66     /**
67      * number constant.
68      */
69     private static final int SECOND_INDEX = 2;
70 
CertUtils()71     private CertUtils() {
72         // Empty constructor
73     }
74 
75     /**
76      * Parse string to key usage.
77      *
78      * @param keyUsageStr Key usage string
79      * @return Key usage
80      */
parseKeyUsage(String keyUsageStr)81     public static int parseKeyUsage(String keyUsageStr) {
82         int keyUsage = 0;
83         if (keyUsageStr.contains("digitalSignature")) {
84             keyUsage |= KeyUsage.digitalSignature;
85         }
86         if (keyUsageStr.contains("nonRepudiation")) {
87             keyUsage |= KeyUsage.nonRepudiation;
88         }
89         if (keyUsageStr.contains("keyEncipherment")) {
90             keyUsage |= KeyUsage.keyEncipherment;
91         }
92         if (keyUsageStr.contains("dataEncipherment")) {
93             keyUsage |= KeyUsage.dataEncipherment;
94         }
95         if (keyUsageStr.contains("keyAgreement")) {
96             keyUsage |= KeyUsage.keyAgreement;
97         }
98         if (keyUsageStr.contains("certificateSignature")) {
99             keyUsage |= KeyUsage.keyCertSign;
100         }
101         if (keyUsageStr.contains("crlSignature")) {
102             keyUsage |= KeyUsage.cRLSign;
103         }
104         if (keyUsageStr.contains("encipherOnly")) {
105             keyUsage |= KeyUsage.encipherOnly;
106         }
107         if (keyUsageStr.contains("decipherOnly")) {
108             keyUsage |= KeyUsage.decipherOnly;
109         }
110         return keyUsage;
111     }
112 
113     /**
114      * Parse string to KeyPurposeId[]
115      *
116      * @param extKeyUsageStr ext key usage string
117      * @return KeyPurposeId[]
118      */
parseExtKeyUsage(String extKeyUsageStr)119     public static KeyPurposeId[] parseExtKeyUsage(String extKeyUsageStr) {
120         ArrayList<KeyPurposeId> ids = new ArrayList<>();
121         if (extKeyUsageStr.contains("clientAuthentication")) {
122             ids.add(KeyPurposeId.id_kp_clientAuth);
123         }
124         if (extKeyUsageStr.contains("serverAuthentication")) {
125             ids.add(KeyPurposeId.id_kp_serverAuth);
126         }
127         if (extKeyUsageStr.contains("codeSignature")) {
128             ids.add(KeyPurposeId.id_kp_codeSigning);
129         }
130         if (extKeyUsageStr.contains("emailProtection")) {
131             ids.add(KeyPurposeId.id_kp_emailProtection);
132         }
133         if (extKeyUsageStr.contains("smartCardLogin")) {
134             ids.add(KeyPurposeId.id_kp_smartcardlogon);
135         }
136         if (extKeyUsageStr.contains("timestamp")) {
137             ids.add(KeyPurposeId.id_kp_timeStamping);
138         }
139         if (extKeyUsageStr.contains("ocspSignature")) {
140             ids.add(KeyPurposeId.id_kp_OCSPSigning);
141         }
142         return ids.toArray(new KeyPurposeId[]{});
143     }
144 
145     /**
146      * buildDN
147      * @param nameString nameString
148      * @return X500Name
149      */
buildDN(String nameString)150     public static X500Name buildDN(String nameString) {
151         checkDN(nameString);
152         X500Name dn = null;
153         try {
154             dn = new X500Name(nameString);
155         } catch (IllegalArgumentException | IndexOutOfBoundsException exception) {
156             LOGGER.debug(exception.getMessage(), exception);
157             CustomException.throwException(ERROR.COMMAND_ERROR,
158                     String.format("Error params near: %s. Reason: %s", nameString, exception.getMessage()));
159         }
160         return dn;
161     }
162 
163     /**
164      * To verify the format of subject or issuer.
165      * Refer to X500NameStyle.fromString().
166      *
167      * @param nameString subject or issuer
168      */
checkDN(String nameString)169     private static void checkDN(String nameString) {
170         String errorMsg = String.format("Format error, must be \"X=xx,XX=xxx,...\", please check: \"%s\"", nameString);
171         ValidateUtils.throwIfNotMatches(!StringUtils.isEmpty(nameString), ERROR.COMMAND_ERROR, errorMsg);
172         String[] pairs = nameString.split(",");
173         for (String pair : pairs) {
174             ValidateUtils.throwIfNotMatches(!StringUtils.isEmpty(nameString.trim()), ERROR.COMMAND_ERROR, errorMsg);
175             String[] kvPair = pair.split("=");
176             ValidateUtils.throwIfNotMatches(kvPair.length == SECOND_INDEX, ERROR.COMMAND_ERROR, errorMsg);
177             // Key will be checked in X500NameStyle.attrNameToOID
178             ValidateUtils.throwIfNotMatches(!StringUtils.isEmpty(kvPair[1].trim()), ERROR.COMMAND_ERROR, errorMsg);
179         }
180     }
181 
182     /**
183      * Generate crl.
184      *
185      * @param crl crl
186      * @return X509CRL
187      */
generateCrl(byte[] crl)188     public static X509CRL generateCrl(byte[] crl) {
189         try {
190             CertificateFactory factory = CertificateFactory.getInstance("X.509");
191             return (X509CRL) factory.generateCRL(new ByteArrayInputStream(crl));
192         } catch (CertificateException | CRLException exception) {
193             LOGGER.debug(exception.getMessage(), exception);
194             CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage());
195         }
196         return null;
197     }
198 
199     /**
200      * Convert byte to CSR String.
201      *
202      * @param csr bytes of CSR
203      * @return String
204      */
toCsrTemplate(byte[] csr)205     public static String toCsrTemplate(byte[] csr) {
206         return "-----BEGIN NEW CERTIFICATE REQUEST-----\n"
207                 + java.util.Base64.getMimeEncoder(MAX_LINE_LENGTH, "\n".getBytes(StandardCharsets.UTF_8))
208                 .encodeToString(csr)
209                 + "\n-----END NEW CERTIFICATE REQUEST-----\n";
210     }
211 
212     /**
213      * Encoding cert to String.
214      *
215      * @param certificate Cert to convert to string
216      * @return Cert templated string
217      * @throws CertificateEncodingException Failed encoding
218      */
generateCertificateInCer(X509Certificate certificate)219     public static String generateCertificateInCer(X509Certificate certificate)
220             throws CertificateEncodingException {
221         return "-----BEGIN CERTIFICATE-----\n"
222                 + java.util.Base64.getMimeEncoder(MAX_LINE_LENGTH, "\n".getBytes(StandardCharsets.UTF_8))
223                 .encodeToString(certificate.getEncoded())
224                 + "\n" + "-----END CERTIFICATE-----" + "\n";
225     }
226 
227     /**
228      * Random serial.
229      *
230      * @return Random big integer
231      */
randomSerial()232     public static BigInteger randomSerial() {
233         return new BigInteger(RANDOM_SERIAL_LENGTH, new SecureRandom());
234     }
235 
236     /**
237      * save2Pem.
238      *
239      * @param certificates certificates to save
240      * @param filePath     filePath to save
241      */
save2Pem(List<X509Certificate> certificates, String filePath)242     public static void save2Pem(List<X509Certificate> certificates, String filePath) {
243         try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(new FileOutputStream(filePath)))) {
244             for (X509Certificate certificate : certificates) {
245                 PemObject object = new PemObject("certificate", certificate.getEncoded());
246                 pemWriter.writeObject(object);
247             }
248         } catch (CertificateEncodingException exception) {
249             LOGGER.debug(exception.getMessage(), exception);
250             CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, exception.getMessage());
251         } catch (IOException exception) {
252             LOGGER.debug(exception.getMessage(), exception);
253             CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage());
254         }
255     }
256 
257     /**
258      * Convert byte to cert.
259      *
260      * @param cert Byte from cert file
261      * @return Certs
262      * @throws CertificateException Convert failed
263      * @throws VerifyCertificateChainException certificates in file are not certificate chain
264      */
265     @SuppressWarnings("unchecked")
generateCertificates(byte[] cert)266     public static List<X509Certificate> generateCertificates(byte[] cert) throws CertificateException,
267             VerifyCertificateChainException {
268         CertificateFactory factory = CertificateFactory.getInstance("X.509");
269         List<X509Certificate> certificates =
270                 (List<X509Certificate>) factory.generateCertificates(new ByteArrayInputStream(cert));
271         sortCertificateChain(certificates);
272         CertificateUtils.verifyCertChain(certificates);
273         return certificates;
274     }
275 
276     /**
277      * Sort cert chain to sign cert, sub cert, root cert
278      *
279      * @param certificates cert chain
280      */
sortCertificateChain(List<X509Certificate> certificates)281     public static void sortCertificateChain(List<X509Certificate> certificates) {
282         if (certificates != null && certificates.size() > 1) {
283             int size = certificates.size();
284             X500Principal lastSubjectX500Principal = (certificates.get(size - 1)).getSubjectX500Principal();
285             X500Principal beforeIssuerX500Principal = (certificates.get(size - SECOND_INDEX)).getIssuerX500Principal();
286             if (!lastSubjectX500Principal.equals(beforeIssuerX500Principal)) {
287                 Collections.reverse(certificates);
288             }
289         }
290     }
291 }
292