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