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