chengkun
2025-06-05 4080b5997b38ca84b3b203c7101dcadb97b76925
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
package com.alipay.easysdk.kernel.util;
 
import com.google.common.base.Strings;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PublicKey;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
/**
 * 证书文件可信校验
 *
 * @author junying.wjy
 * @version $Id: AntCertificationUtil.java, v 0.1 2019-07-29 下午04:46 junying.wjy Exp $
 */
public class AntCertificationUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(AntCertificationUtil.class);
 
    private static BouncyCastleProvider provider;
 
    static {
        provider = new BouncyCastleProvider();
        Security.addProvider(provider);
    }
 
    /**
     * 验证证书是否可信
     *
     * @param certContent     需要验证的目标证书或者证书链
     * @param rootCertContent 可信根证书列表
     */
    public static boolean isTrusted(String certContent, String rootCertContent) {
        X509Certificate[] certificates;
        try {
            certificates = readPemCertChain(certContent);
        } catch (Exception e) {
            LOGGER.error("读取证书失败", e);
            throw new RuntimeException(e);
        }
 
        List<X509Certificate> rootCerts = new ArrayList<X509Certificate>();
        try {
            X509Certificate[] certs = readPemCertChain(rootCertContent);
            rootCerts.addAll(Arrays.asList(certs));
        } catch (Exception e) {
            LOGGER.error("读取根证书失败", e);
            throw new RuntimeException(e);
        }
 
        return verifyCertChain(certificates, rootCerts.toArray(new X509Certificate[rootCerts.size()]));
    }
 
    /**
     * 验证证书是否是信任证书库中证书签发的
     *
     * @param cert      目标验证证书
     * @param rootCerts 可信根证书列表
     * @return 验证结果
     */
    private static boolean verifyCert(X509Certificate cert, X509Certificate[] rootCerts) {
        try {
            cert.checkValidity();
        } catch (CertificateExpiredException e) {
            LOGGER.error("证书已经过期", e);
            return false;
        } catch (CertificateNotYetValidException e) {
            LOGGER.error("证书未激活", e);
            return false;
        }
 
        Map<Principal, X509Certificate> subjectMap = new HashMap<Principal, X509Certificate>();
 
        for (X509Certificate root : rootCerts) {
            subjectMap.put(root.getSubjectDN(), root);
        }
 
        Principal issuerDN = cert.getIssuerDN();
        X509Certificate issuer = subjectMap.get(issuerDN);
        if (issuer == null) {
            LOGGER.error("证书链验证失败");
            return false;
        }
        try {
            PublicKey publicKey = issuer.getPublicKey();
            verifySignature(publicKey, cert);
        } catch (Exception e) {
            LOGGER.error("证书链验证失败", e);
            return false;
        }
        return true;
    }
 
    /**
     * 验证证书链是否是信任证书库中证书签发的
     *
     * @param certs     目标验证证书列表
     * @param rootCerts 可信根证书列表
     * @return 验证结果
     */
    private static boolean verifyCertChain(X509Certificate[] certs, X509Certificate[] rootCerts) {
        boolean sorted = sortByDn(certs);
        if (!sorted) {
            LOGGER.error("证书链验证失败:不是完整的证书链");
            return false;
        }
 
        //先验证第一个证书是不是信任库中证书签发的
        X509Certificate prev = certs[0];
        boolean firstOK = verifyCert(prev, rootCerts);
        if (!firstOK || certs.length == 1) {
            return firstOK;
        }
 
        //验证证书链
        for (int i = 1; i < certs.length; i++) {
            try {
                X509Certificate cert = certs[i];
                try {
                    cert.checkValidity();
                } catch (CertificateExpiredException e) {
                    LOGGER.error("证书已经过期");
                    return false;
                } catch (CertificateNotYetValidException e) {
                    LOGGER.error("证书未激活");
                    return false;
                }
                verifySignature(prev.getPublicKey(), cert);
                prev = cert;
            } catch (Exception e) {
                LOGGER.error("证书链验证失败");
                return false;
            }
        }
 
        return true;
    }
 
    private static void verifySignature(PublicKey publicKey, X509Certificate cert)
            throws NoSuchProviderException, CertificateException, NoSuchAlgorithmException, InvalidKeyException,
            SignatureException {
        cert.verify(publicKey, provider.getName());
    }
 
    /**
     * 将证书链按照完整的签发顺序进行排序,排序后证书链为:[issuerA, subjectA]-[issuerA, subjectB]-[issuerB, subjectC]-[issuerC, subjectD]...
     *
     * @param certs 证书链
     * @return true:排序成功,false:证书链不完整
     */
    private static boolean sortByDn(X509Certificate[] certs) {
        //主题和证书的映射
        Map<Principal, X509Certificate> subjectMap = new HashMap<Principal, X509Certificate>();
        //签发者和证书的映射
        Map<Principal, X509Certificate> issuerMap = new HashMap<Principal, X509Certificate>();
        //是否包含自签名证书
        boolean hasSelfSignedCert = false;
 
        for (X509Certificate cert : certs) {
            if (isSelfSigned(cert)) {
                if (hasSelfSignedCert) {
                    return false;
                }
                hasSelfSignedCert = true;
            }
 
            Principal subjectDN = cert.getSubjectDN();
            Principal issuerDN = cert.getIssuerDN();
 
            subjectMap.put(subjectDN, cert);
            issuerMap.put(issuerDN, cert);
        }
 
        List<X509Certificate> certChain = new ArrayList<X509Certificate>();
 
        X509Certificate current = certs[0];
        addressingUp(subjectMap, certChain, current);
        addressingDown(issuerMap, certChain, current);
 
        //说明证书链不完整
        if (certs.length != certChain.size()) {
            return false;
        }
 
        //将证书链复制到原先的数据
        for (int i = 0; i < certChain.size(); i++) {
            certs[i] = certChain.get(i);
        }
        return true;
    }
 
    /**
     * 验证证书是否是自签发的
     *
     * @param cert 目标证书
     * @return true;自签发,false;不是自签发
     */
    private static boolean isSelfSigned(X509Certificate cert) {
        return cert.getSubjectDN().equals(cert.getIssuerDN());
    }
 
    /**
     * 向上构造证书链
     *
     * @param subjectMap 主题和证书的映射
     * @param certChain  证书链
     * @param current    当前需要插入证书链的证书,include
     */
    private static void addressingUp(final Map<Principal, X509Certificate> subjectMap, List<X509Certificate> certChain,
                                     final X509Certificate current) {
        certChain.add(0, current);
        if (isSelfSigned(current)) {
            return;
        }
        Principal issuerDN = current.getIssuerDN();
        X509Certificate issuer = subjectMap.get(issuerDN);
        if (issuer == null) {
            return;
        }
        addressingUp(subjectMap, certChain, issuer);
    }
 
    /**
     * 向下构造证书链
     *
     * @param issuerMap 签发者和证书的映射
     * @param certChain 证书链
     * @param current   当前需要插入证书链的证书,exclude
     */
    private static void addressingDown(final Map<Principal, X509Certificate> issuerMap, List<X509Certificate> certChain,
                                       final X509Certificate current) {
        Principal subjectDN = current.getSubjectDN();
        X509Certificate subject = issuerMap.get(subjectDN);
        if (subject == null) {
            return;
        }
        if (isSelfSigned(subject)) {
            return;
        }
        certChain.add(subject);
        addressingDown(issuerMap, certChain, subject);
    }
 
    private static X509Certificate[] readPemCertChain(String cert) throws CertificateException {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(cert.getBytes());
        CertificateFactory factory = CertificateFactory.getInstance("X.509", provider);
        Collection<? extends Certificate> certificates = factory.generateCertificates(inputStream);
        return certificates.toArray(new X509Certificate[certificates.size()]);
    }
 
    /**
     * 获取支付宝根证书序列号
     *
     * @param rootCertContent 支付宝根证书内容
     * @return 支付宝根证书序列号
     */
    public static String getRootCertSN(String rootCertContent) {
        String rootCertSN = null;
        try {
            X509Certificate[] x509Certificates = readPemCertChain(rootCertContent);
            MessageDigest md = MessageDigest.getInstance("MD5");
            for (X509Certificate c : x509Certificates) {
                if (c.getSigAlgOID().startsWith("1.2.840.113549.1.1")) {
                    md.update((c.getIssuerX500Principal().getName() + c.getSerialNumber()).getBytes());
                    String certSN = new BigInteger(1, md.digest()).toString(16);
                    //BigInteger会把0省略掉,需补全至32位
                    certSN = fillMD5(certSN);
                    if (Strings.isNullOrEmpty(rootCertSN)) {
                        rootCertSN = certSN;
                    } else {
                        rootCertSN = rootCertSN + "_" + certSN;
                    }
                }
 
            }
        } catch (Exception e) {
            LOGGER.error("提取根证书失败");
        }
        return rootCertSN;
    }
 
    /**
     * 获取公钥证书序列号
     *
     * @param certContent 公钥证书内容
     * @return 公钥证书序列号
     */
    public static String getCertSN(String certContent) {
        try {
            InputStream inputStream = new ByteArrayInputStream(certContent.getBytes());
            CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC");
            X509Certificate cert = (X509Certificate) factory.generateCertificate(inputStream);
            return md5((cert.getIssuerX500Principal().getName() + cert.getSerialNumber()).getBytes());
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
 
    private static String md5(byte[] bytes) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        md.update(bytes);
        String certSN = new BigInteger(1, md.digest()).toString(16);
        //BigInteger会把0省略掉,需补全至32位
        certSN = fillMD5(certSN);
        return certSN;
    }
 
    private static String fillMD5(String md5) {
        return md5.length() == 32 ? md5 : fillMD5("0" + md5);
    }
 
    /**
     * 提取公钥证书中的公钥
     *
     * @param certContent 公钥证书内容
     * @return 公钥证书中的公钥
     */
    public static String getCertPublicKey(String certContent) {
        try {
            InputStream inputStream = new ByteArrayInputStream(certContent.getBytes());
            CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC");
            X509Certificate cert = (X509Certificate) factory.generateCertificate(inputStream);
            return Base64.toBase64String(cert.getPublicKey().getEncoded());
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
 
    /**
     * 从文件中读取证书内容
     *
     * @param certPath 证书路径
     * @return 证书内容
     */
    public static String readCertContent(String certPath) {
        if (existsInFileSystem(certPath)) {
            return readFromFileSystem(certPath);
        }
        return readFromClassPath(certPath);
    }
 
    private static boolean existsInFileSystem(String certPath) {
        try {
            return new File(certPath).exists();
        } catch (Throwable e) {
            return false;
        }
    }
 
    private static String readFromFileSystem(String certPath) {
        try {
            return new String(Files.toByteArray(new File(certPath)), StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new RuntimeException("从文件系统中读取[" + certPath + "]失败," + e.getMessage(), e);
        }
    }
 
    private static String readFromClassPath(String certPath) {
        try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(certPath)) {
            return new String(ByteStreams.toByteArray(inputStream), StandardCharsets.UTF_8);
        } catch (Exception e) {
            String errorMessage = e.getMessage() == null ? "" : e.getMessage() + "。";
            if (certPath.startsWith("/")) {
                errorMessage += "ClassPath路径不可以/开头,请去除后重试。";
            }
            throw new RuntimeException("读取[" + certPath + "]失败。" + errorMessage, e);
        }
    }
}