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