840 lines
31 KiB
Java
840 lines
31 KiB
Java
package com.cym.utils.cert;
|
||
|
||
import java.io.BufferedReader;
|
||
import java.io.IOException;
|
||
import java.io.InputStream;
|
||
import java.io.InputStreamReader;
|
||
import java.net.ConnectException;
|
||
import java.net.HttpURLConnection;
|
||
import java.net.InetAddress;
|
||
import java.net.URL;
|
||
import java.net.URLConnection;
|
||
import java.security.MessageDigest;
|
||
import java.security.cert.Certificate;
|
||
import java.security.cert.X509Certificate;
|
||
import java.util.ArrayList;
|
||
import java.util.Arrays;
|
||
import java.util.Collection;
|
||
import java.util.Date;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.Objects;
|
||
import java.util.regex.Matcher;
|
||
import java.util.regex.Pattern;
|
||
|
||
import javax.net.ssl.HostnameVerifier;
|
||
import javax.net.ssl.HttpsURLConnection;
|
||
import javax.net.ssl.SSLSession;
|
||
|
||
import org.apache.commons.lang3.StringUtils;
|
||
import org.springframework.stereotype.Component;
|
||
|
||
import com.alibaba.fastjson.JSON;
|
||
import com.cym.utils.cert.ssl.Root;
|
||
import com.google.common.collect.Lists;
|
||
import com.google.common.collect.Maps;
|
||
|
||
import cn.hutool.http.HttpUtil;
|
||
import cn.hutool.json.JSONUtil;
|
||
|
||
/**
|
||
* @author ZhangSan_Plus
|
||
* @version 1.0
|
||
* @className CertificateDetectionUtils
|
||
* @description
|
||
* @date 2022/8/27 16:36
|
||
**/
|
||
@Component
|
||
public class CertificateDetectionUtils {
|
||
private static final Pattern CN_PATTERN = Pattern.compile("[\u4e00-\u9fa5]");
|
||
private static final String HTTPS_STR = "https://";
|
||
private static final String HTTP_STR = "http://";
|
||
private static final String DOMAIN_NOT_MATCH_STR = "域名不匹配";
|
||
private static final String DV_SSL_STR = "DV SSL";
|
||
private static final String OV_SSL_STR = "OV SSL";
|
||
private static final String EV_SSL_STR = "EV SSL";
|
||
private static final String SERIALNUMBER_STR = "SERIALNUMBER";
|
||
private static final String O_STR = "O";
|
||
private static final String ST_STR = "ST";
|
||
private static final String OU_STR = "OU";
|
||
private static final String L_STR = "L";
|
||
private static final String C_STR = "C";
|
||
private static final String CN_STR = "CN";
|
||
private static final String SHA1_AUTH_STR = "SHA-1";
|
||
private static final String SHA256_AUTH_STR = "SHA-256";
|
||
private static final String NORMAL_STR = "正常";
|
||
private static final String YES_STR = "是";
|
||
private static final String NO_STR = "否";
|
||
private static final String SUPPORT_STR = "支持";
|
||
private static final String NO_SUPPORT_STR = "不支持";
|
||
|
||
/**
|
||
* 根据网址获取证书详细信息(包含当前网址受信任,不受信任的证书)
|
||
*
|
||
* @param hpUrl
|
||
* @return com.cym.utils.cert.CertInfoDTO
|
||
* @throws Exception
|
||
* @author ZhangSan_Plus
|
||
* @date 19:35 2022/9/5
|
||
**/
|
||
public static CertInfoDTO getCert(String hpUrl) throws Exception {
|
||
hpUrl = hpUrl.contains(HTTP_STR) || hpUrl.contains(HTTPS_STR) ? hpUrl.replaceAll(HTTP_STR, "").replaceAll(HTTPS_STR, "").trim() : hpUrl.trim();
|
||
hpUrl = CNRulesMate(hpUrl) ? chineseDomainNameDecoding(hpUrl) : hpUrl;
|
||
long start = System.currentTimeMillis();
|
||
CertInfoDTO certInfoDTO = new CertInfoDTO();
|
||
List<Map<String, String>> pactMap = Lists.newLinkedList();
|
||
List<CertExportEmailVO> certExportEmailVOS = Lists.newArrayList();
|
||
// 获取受信任证书
|
||
CertExportEmailVO certExportEmailVO = defaultDetection(hpUrl.toLowerCase());
|
||
// 获取不受信任的证书
|
||
// CertExportEmailVO certExportEmailVO1 = trustDetection(hpUrl.toLowerCase());
|
||
if (StringUtils.isNotBlank(certExportEmailVO.getIssueC())) {
|
||
pactMap.add(certCompatible(certExportEmailVO.getSuccess()));
|
||
certExportEmailVOS.add(certExportEmailVO);
|
||
}
|
||
// if (StringUtils.isNotBlank(certExportEmailVO1.getCertificateType())
|
||
// && !(StringUtils.isNotBlank(certExportEmailVO.getCommonName()) ? certExportEmailVO.getCommonUrl() : "").equals(certExportEmailVO1.getCommonUrl())) {
|
||
// pactMap.add(certCompatible(certExportEmailVO1.getSuccess()));
|
||
// certExportEmailVOS.add(certExportEmailVO1);
|
||
// }
|
||
certInfoDTO.setPactMap(pactMap);
|
||
// 获取当前网址的title,icon,ip等信息
|
||
Map<String, String> webInfo = getWebInfo(StringUtils.isNotBlank(certExportEmailVO.getCommonName()) ? HTTPS_STR : HTTP_STR, hpUrl);
|
||
certInfoDTO.setWebTitle(webInfo.get("title"));
|
||
certInfoDTO.setWebServer("-");
|
||
certInfoDTO.setIpAddr(webInfo.get("ip"));
|
||
certInfoDTO.setIcon(webInfo.get("favicon"));
|
||
certInfoDTO.setCertExportEmailVOS(certExportEmailVOS);
|
||
if (certExportEmailVOS.size() != 0) {
|
||
String rating = getRating(certExportEmailVOS.get(0));
|
||
String status = rating.equals("B") || rating.equals("T") ? "不合规" : "合规";
|
||
certInfoDTO.setRating(rating);
|
||
certInfoDTO.setAts(status);
|
||
certInfoDTO.setPciDss(status);
|
||
certInfoDTO.setPass(true);
|
||
} else {
|
||
certInfoDTO.setPass(false);
|
||
}
|
||
long end = System.currentTimeMillis();
|
||
certInfoDTO.setDetectionTime(DateUtils.formatDate(new Date(), DateUtils.DATE_DEFAULT) + " ( 耗时:" + (end - start) / 1000 + "秒 )");
|
||
return certInfoDTO;
|
||
}
|
||
|
||
/**
|
||
* 获取HTTPS请求连接
|
||
*
|
||
* @param url
|
||
* @return javax.net.ssl.HttpsURLConnection
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 14:31 2022/9/2
|
||
**/
|
||
public static HttpsURLConnection getConn(URL url) {
|
||
HttpsURLConnection conn = null;
|
||
try {
|
||
conn = (HttpsURLConnection) url.openConnection();
|
||
} catch (IOException e) {
|
||
e.printStackTrace();
|
||
}
|
||
conn.addRequestProperty("Accept", "*/*");
|
||
conn.addRequestProperty("Accept-Encoding", "gzip,deflate,sdch");
|
||
conn.addRequestProperty("Accept-Language", "zh-CN,zh;q=0.8");
|
||
conn.addRequestProperty("Cache-Control", "max-age=0");
|
||
conn.addRequestProperty("Connection", "keep-alive");
|
||
conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||
conn.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36");
|
||
conn.setDoOutput(true);
|
||
conn.setUseCaches(false);
|
||
conn.setConnectTimeout(10000);
|
||
conn.setReadTimeout(10000);
|
||
return conn;
|
||
}
|
||
|
||
/***
|
||
* 默认证书匹配(只匹配SSL证书安全的证书信息) --若证书抛出没有证书的错误会进行信任全部证书进行检测
|
||
* --会将所有证书进行匹配包括已经过期或者证书和主域名不匹配的都会进行处理
|
||
*
|
||
*
|
||
* -SSL证书会进行两次匹配规则如下 若两次证书查询的结果相同则代表该网站仅有一个RSA,若两次查询结果不相同会将证书分为 RSA1 RSA2
|
||
*
|
||
* @author ZhangSan_Plus
|
||
* @date 10:04 2022/8/31
|
||
* @param hpUrl
|
||
* @return com.cym.utils.cert.CertExportEmailVO
|
||
* @throws
|
||
**/
|
||
public static CertExportEmailVO defaultDetection(String hpUrl) throws Exception {
|
||
URL url = new URL(HTTPS_STR + hpUrl);
|
||
// 发起HTTPS请求
|
||
trustAllHttpsCertificates(1);
|
||
HttpsURLConnection conn = getConn(url);
|
||
CertExportEmailVO certExportEmailVO = new CertExportEmailVO();
|
||
try {
|
||
conn.connect();
|
||
if (Objects.nonNull(conn.getServerCertificates())) {
|
||
Certificate[] serverCertificates = conn.getServerCertificates();
|
||
X509Certificate certs = (X509Certificate) serverCertificates[0];
|
||
certExportEmailVO = deal(certs, certExportEmailVO);
|
||
String parentCert = (serverCertificates.length > 1) ? NO_STR : YES_STR;
|
||
certExportEmailVO.setRootCert(parentCert);
|
||
certExportEmailVO.setOcspBindState(parentCert.equals(YES_STR) ? SUPPORT_STR : NO_SUPPORT_STR);
|
||
certExportEmailVO.setOcspMustBind(parentCert.equals(YES_STR) ? YES_STR : NO_STR);
|
||
certExportEmailVO.setIsSNI(parentCert.equals(YES_STR) ? YES_STR : NO_STR);
|
||
certExportEmailVO.setWeakKey(parentCert.equals(YES_STR) ? NO_STR : YES_STR);
|
||
List<CertificateChain> certChain = getCertChain(serverCertificates, getThumbPrint(certs, SHA1_AUTH_STR).toUpperCase());
|
||
certExportEmailVO.setCertificateChains(certChain);
|
||
conn.disconnect();
|
||
if (!certExportEmailVO.getCommonName().contains(hpUrl.substring(hpUrl.indexOf(".") + 1))) {
|
||
if (!certExportEmailVO.getSans().contains(hpUrl.replaceAll("www\\.", ""))) {
|
||
certExportEmailVO.setCommonUrl(certExportEmailVO.getCommonName());
|
||
certExportEmailVO.setCommonName(certExportEmailVO.getCommonName() + " (不匹配)");
|
||
certExportEmailVO.setSuccess(DOMAIN_NOT_MATCH_STR);
|
||
certExportEmailVO.setTrustStatus(DOMAIN_NOT_MATCH_STR);
|
||
}
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
} finally {
|
||
conn.disconnect();
|
||
}
|
||
return certExportEmailVO;
|
||
}
|
||
|
||
/**
|
||
* 中文正则表达式
|
||
*
|
||
* @param countName 域名
|
||
* @return boolean
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 10:11 2022/8/31
|
||
**/
|
||
public static boolean CNRulesMate(String countName) {
|
||
Matcher m = CN_PATTERN.matcher(countName);
|
||
if (m.find()) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 对包含中文的域名进行中文域名解析
|
||
*
|
||
* @param doMain
|
||
* @return java.lang.String
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 10:11 2022/8/31
|
||
**/
|
||
public static String chineseDomainNameDecoding(String doMain) throws Exception {
|
||
String[] doMainPunyList = doMain.split("\\.");
|
||
String doMainPunyCode = "";
|
||
// 中文域名的解析
|
||
for (int i = 0; i < doMainPunyList.length; i++) {
|
||
if (i == doMainPunyList.length - 1) {
|
||
if (CNRulesMate(doMainPunyList[i])) {
|
||
doMainPunyCode += "xn--" + PunyUtils.encode(doMainPunyList[i]);
|
||
} else {
|
||
doMainPunyCode += doMainPunyList[i];
|
||
}
|
||
} else {
|
||
if (CNRulesMate(doMainPunyList[i])) {
|
||
doMainPunyCode += "xn--" + PunyUtils.encode(doMainPunyList[i]) + ".";
|
||
} else {
|
||
doMainPunyCode += doMainPunyList[i] + ".";
|
||
}
|
||
}
|
||
}
|
||
return doMainPunyCode;
|
||
}
|
||
|
||
static class Verifier implements HostnameVerifier {
|
||
|
||
@Override
|
||
public boolean verify(String arg0, SSLSession arg1) {
|
||
return true; // mark everything as verified
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 信任所有证书实现类
|
||
*/
|
||
static class miTM implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager {
|
||
@Override
|
||
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||
return null;
|
||
}
|
||
|
||
public boolean isServerTrusted(java.security.cert.X509Certificate[] certs) {
|
||
return true;
|
||
}
|
||
|
||
public boolean isClientTrusted(java.security.cert.X509Certificate[] certs) {
|
||
return true;
|
||
}
|
||
|
||
@Override
|
||
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
|
||
return;
|
||
}
|
||
|
||
@Override
|
||
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 信任网站所有SSL证书
|
||
*
|
||
* @param status
|
||
* @return void
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 10:11 2022/8/31
|
||
**/
|
||
private static void trustAllHttpsCertificates(Integer status) throws Exception {
|
||
javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
|
||
javax.net.ssl.TrustManager tm = new miTM();
|
||
trustAllCerts[0] = tm;
|
||
// 实例化SSL协议
|
||
javax.net.ssl.SSLContext sc = (status == 1) ? javax.net.ssl.SSLContext.getInstance("TLSv1.2") : javax.net.ssl.SSLContext.getInstance("SSL");
|
||
|
||
// 实例化SSl协议 信任所有证书
|
||
sc.init(null, trustAllCerts, null);
|
||
javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||
}
|
||
|
||
/**
|
||
* 证书后续处理具体规则匹配
|
||
*
|
||
* @param cert
|
||
* @param xccSSLInfo
|
||
* @return com.cym.utils.cert.CertExportEmailVO
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 19:40 2022/9/5
|
||
**/
|
||
public static CertExportEmailVO deal(X509Certificate cert, CertExportEmailVO xccSSLInfo) throws Exception {
|
||
Map<String, String> certInfo = getCertInfo(cert.toString());
|
||
List<String> sansList = new ArrayList<>();
|
||
if (Objects.nonNull(cert.getSubjectAlternativeNames())) {
|
||
Collection<List<?>> subjectAlternativeNames = cert.getSubjectAlternativeNames();
|
||
subjectAlternativeNames.forEach(c -> sansList.add(c.get(1).toString()));
|
||
}
|
||
// Root root = JSON.parseObject(JSON.toJSONString(cert), Root.class);
|
||
// xccSSLInfo.setCommonUrl(root.getSubjectDN().getCommonName());
|
||
xccSSLInfo.setCommonUrl(cert.getSubjectX500Principal().getName());
|
||
// xccSSLInfo.setCommonName(root.getSubjectDN().getCommonName());
|
||
xccSSLInfo.setCommonName(cert.getSubjectX500Principal().getName());
|
||
xccSSLInfo.setSignatureAlgorithm(cert.getSigAlgName());
|
||
xccSSLInfo.setEncryptionAlgorithm(cert.toString().contains("SHA256withECDSA") ? "ECDSA 256 bits" : "RSA 2048 bits");
|
||
xccSSLInfo.setCertificateTransparency("是 (Google: (来自证书,有效); Apple: (来自证书,有效))Firefox: (来自证书,有效))");
|
||
xccSSLInfo.setRevocationStatus(NORMAL_STR);
|
||
/**
|
||
* 主题信息
|
||
*/
|
||
xccSSLInfo.setSubjectCN(sslSp(cert.getSubjectDN().toString(), CN_STR));
|
||
xccSSLInfo.setSubjectO(sslSp(cert.getSubjectDN().toString(), O_STR));
|
||
xccSSLInfo.setSubjectST(sslSp(cert.getSubjectDN().toString(), ST_STR));
|
||
xccSSLInfo.setSubjectOU(sslSp(cert.getSubjectDN().toString(), OU_STR));
|
||
xccSSLInfo.setSubjectL(sslSp(cert.getSubjectDN().toString(), L_STR));
|
||
xccSSLInfo.setSubjectC(sslSp(cert.getSubjectDN().toString(), C_STR));
|
||
|
||
/**
|
||
* 签发者信息
|
||
*/
|
||
xccSSLInfo.setIssueCN(sslSp(cert.getIssuerDN().toString(), CN_STR));
|
||
xccSSLInfo.setIssueO(sslSp(cert.getIssuerDN().toString(), O_STR));
|
||
xccSSLInfo.setIssueOU(sslSp(cert.getIssuerDN().toString(), OU_STR));
|
||
xccSSLInfo.setIssueC(sslSp(cert.getIssuerDN().toString(), C_STR));
|
||
|
||
/**
|
||
* 证书信息
|
||
*/
|
||
xccSSLInfo.setSerialNumber(certInfo.get("serialNumber"));
|
||
xccSSLInfo.setAlgorithm(cert.getSigAlgName());
|
||
xccSSLInfo.setCertificateType(getSSLType(cert));
|
||
xccSSLInfo.setCertificateBrand(sslSp(cert.getIssuerDN().toString(), CN_STR).split(" ")[0]);
|
||
xccSSLInfo.setPrivateKeyLen("2048 Bits");
|
||
xccSSLInfo.setSHA1Fingerprint(getThumbPrint(cert, SHA1_AUTH_STR).toUpperCase());
|
||
xccSSLInfo.setSHA256Fingerprint(getThumbPrint(cert, SHA256_AUTH_STR).toUpperCase());
|
||
xccSSLInfo.setStartTime(DateUtils.formatDate(cert.getNotBefore(), DateUtils.DATETIME_DEFAULT));
|
||
xccSSLInfo.setEndTime(DateUtils.formatDate(cert.getNotAfter(), DateUtils.DATETIME_DEFAULT));
|
||
xccSSLInfo.setSuccess(DateUtils.daysBetween(new Date(), cert.getNotAfter()) > 0 && StringUtils.isBlank(xccSSLInfo.getSuccess()) ? NORMAL_STR : "已过期");
|
||
xccSSLInfo.setTrustStatus(DateUtils.daysBetween(new Date(), cert.getNotAfter()) > 0 && StringUtils.isBlank(xccSSLInfo.getTrustStatus()) ? "可信" : "已过期");
|
||
xccSSLInfo.setValidityPeriod(DateUtils.daysBetween(new Date(), cert.getNotAfter()) + "天");
|
||
xccSSLInfo.setExtKeyUsage("extKeyUsage:Server authentication,Client authentication");
|
||
xccSSLInfo.setSans(sansList);
|
||
// xccSSLInfo.setOcspUrl((Objects.nonNull(root.getAuthorityInfoAccessExtension())) ? root.getAuthorityInfoAccessExtension().getAccessDescriptions().get(0).getAccessLocation().getName().getName() : "");
|
||
// xccSSLInfo.setCaUrl((Objects.nonNull(root.getAuthorityInfoAccessExtension())) ? root.getAuthorityInfoAccessExtension().getAccessDescriptions().get(0).getAccessLocation().getName().getName() : "");
|
||
xccSSLInfo.setCrlUrl(certInfo.get("crlUrl"));
|
||
xccSSLInfo.setPublicKey(cert.getPublicKey().getFormat());
|
||
xccSSLInfo.setOpenSSL("OpenSSL:" + "\n" + "Certificate:" + "\n" + cert);
|
||
return xccSSLInfo;
|
||
|
||
}
|
||
|
||
/**
|
||
* 获取证书链
|
||
*
|
||
* @param certificates
|
||
* @return java.util.List<com.cym.utils.cert.CertificateChain>
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 15:40 2022/9/1
|
||
**/
|
||
public static List<CertificateChain> getCertChain(Certificate[] certificates, String thumbPrint) throws Exception {
|
||
List<CertificateChain> certificateChains = Lists.newArrayList();
|
||
List<Certificate> x509Certificates = Arrays.asList(certificates);
|
||
List<X509Certificate> certificateList = Lists.newLinkedList();
|
||
int thumb = 0;
|
||
for (int i = 0; i < x509Certificates.size(); i++) {
|
||
X509Certificate cert = (X509Certificate) certificates[i];
|
||
certificateList.add(cert);
|
||
if (thumbPrint.equals(getThumbPrint(cert, SHA1_AUTH_STR).toUpperCase())) {
|
||
thumb = i;
|
||
}
|
||
}
|
||
List<X509Certificate> certificates1 = certificateList.subList(thumb, x509Certificates.size());
|
||
certificates1.forEach(c -> {
|
||
CertificateChain certificateChain = certChainDeal(c);
|
||
certificateChains.add(certificateChain);
|
||
});
|
||
return certificateChains;
|
||
}
|
||
|
||
public static List<CertificateChain> getCertChain1(Certificate[] certificates) throws Exception {
|
||
List<CertificateChain> certificateChains = Lists.newArrayList();
|
||
for (int i = 0; i < certificates.length; i++) {
|
||
X509Certificate cert = (X509Certificate) certificates[i];
|
||
CertificateChain certificateChain = certChainDeal(cert);
|
||
certificateChains.add(certificateChain);
|
||
}
|
||
return certificateChains;
|
||
}
|
||
|
||
public static CertificateChain certChainDeal(X509Certificate cert) {
|
||
CertificateChain certificateChain = new CertificateChain();
|
||
certificateChain.setValidityPeriod(DateUtils.formatDate(cert.getNotBefore(), DateUtils.DATETIME_YYYY_MM_DD_HH_MM_SS).concat("~")
|
||
.concat(DateUtils.formatDate(cert.getNotAfter(), DateUtils.DATETIME_YYYY_MM_DD_HH_MM_SS)).concat("(剩余 " + DateUtils.daysBetween(new Date(), cert.getNotAfter()) + "天)"));
|
||
try {
|
||
certificateChain.setCertFingerprint(getThumbPrint(cert, SHA1_AUTH_STR).toUpperCase());
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
}
|
||
certificateChain.setEncryptionAlgorithm(cert.toString().contains("SHA256withECDSA") ? "ECDSA 256 bits" : "RSA 2048 bits");
|
||
certificateChain.setSignAlgorithm(cert.getSigAlgName());
|
||
certificateChain.setIssuedTo(sslSp(cert.getSubjectDN().toString(), CN_STR));
|
||
certificateChain.setIssuer(sslSp(cert.getIssuerDN().toString(), CN_STR));
|
||
return certificateChain;
|
||
}
|
||
|
||
/**
|
||
* 信任所有证书
|
||
*
|
||
* @param hpUrl
|
||
* @return com.cym.utils.cert.CertExportEmailVO
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 14:32 2022/9/2
|
||
**/
|
||
public static CertExportEmailVO trustDetection(String hpUrl) throws Exception {
|
||
CertExportEmailVO xccSSLInfo = new CertExportEmailVO();
|
||
URL url = new URL(HTTPS_STR + hpUrl);
|
||
// 发起HTTPS请求
|
||
trustAllHttpsCertificates(2);
|
||
HttpsURLConnection conn = getConn(url);
|
||
conn.setHostnameVerifier(new Verifier());
|
||
try {
|
||
conn.connect();
|
||
Certificate[] serverCertificates = conn.getServerCertificates();
|
||
X509Certificate certs = (X509Certificate) serverCertificates[0];
|
||
xccSSLInfo = deal(certs, xccSSLInfo);
|
||
List<CertificateChain> certChain = getCertChain(serverCertificates, getThumbPrint(certs, SHA1_AUTH_STR).toUpperCase());
|
||
xccSSLInfo.setCertificateChains(certChain);
|
||
String parentCert = (serverCertificates.length > 1) ? NO_STR : YES_STR;
|
||
xccSSLInfo.setOcspBindState(parentCert.equals(YES_STR) ? SUPPORT_STR : NO_SUPPORT_STR);
|
||
xccSSLInfo.setOcspMustBind(parentCert.equals(YES_STR) ? YES_STR : NO_STR);
|
||
xccSSLInfo.setIsSNI(parentCert.equals(YES_STR) ? YES_STR : NO_STR);
|
||
xccSSLInfo.setWeakKey(parentCert.equals(YES_STR) ? NO_STR : YES_STR);
|
||
conn.disconnect();
|
||
if (!xccSSLInfo.getCommonName().contains(hpUrl.substring(hpUrl.indexOf(".") + 1))) {
|
||
if (xccSSLInfo.getSans().size() == 0 || !xccSSLInfo.getSans().contains(hpUrl)) {
|
||
xccSSLInfo.setCommonName(xccSSLInfo.getCommonName() + " (不匹配)");
|
||
xccSSLInfo.setSuccess(DOMAIN_NOT_MATCH_STR);
|
||
xccSSLInfo.setTrustStatus("域名不匹配 (主流浏览器访问不受影响,影响少数不支持SNI的旧浏览器)");
|
||
}
|
||
}
|
||
} catch (ConnectException e) {
|
||
return xccSSLInfo;
|
||
} catch (Exception cer) {
|
||
return xccSSLInfo;
|
||
} finally {
|
||
conn.disconnect();
|
||
}
|
||
return xccSSLInfo;
|
||
}
|
||
|
||
/**
|
||
* 证书类型规则匹配 -- DV 证书信息中仅包含COMMON_NAME 信息 -- OV 证书信息中包含企业的一些基本信息 -- EV
|
||
* 证书中包含企业的工商注册号 SERIALNUMBER
|
||
*
|
||
* @param cert X.509证书链
|
||
* @return java.lang.String
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 10:12 2022/8/31
|
||
**/
|
||
public static String getSSLType(X509Certificate cert) {
|
||
if (StringUtils.isBlank(sslSp(cert.getSubjectDN().toString(), O_STR)) && StringUtils.isBlank(sslSp(cert.getSubjectDN().toString(), ST_STR))) {
|
||
return DV_SSL_STR;
|
||
}
|
||
if (StringUtils.isBlank(sslSp(cert.getSubjectDN().toString(), SERIALNUMBER_STR))) {
|
||
return OV_SSL_STR;
|
||
} else {
|
||
return EV_SSL_STR;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取证书序列号及CRURL(证书列表吊销信息) domain:https://sina.com.cn eg:
|
||
* http://crl3.digicert.com/GeoTrustCNRSACAG1.crl,http://crl4.digicert.com/GeoTrustCNRSACAG1.crl
|
||
*
|
||
* @param cert
|
||
* @return java.util.Map<java.lang.String, java.lang.String>
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 10:14 2022/8/31
|
||
**/
|
||
public static Map<String, String> getCertInfo(String cert) {
|
||
Map<String, String> certMap = Maps.newHashMap();
|
||
if (cert.contains("SerialNumber: [") && cert.contains("]\n" + "\n" + "Certificate")) {
|
||
try {
|
||
String serialNumber = cert.substring(cert.indexOf("SerialNumber: ["), cert.indexOf("]\n" + "\n" + "Certificate")).replace(" ", "").replace("SerialNumber:[", "").replace("]", "")
|
||
.toUpperCase();
|
||
certMap.put("serialNumber", serialNumber);
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
}
|
||
|
||
} else {
|
||
String serialNumber = cert.substring(cert.indexOf("SerialNumber: ["), cert.indexOf("]\n" + " Algorithm: [")).replaceAll(" ", "").replaceAll("SerialNumber:\\[", "").replaceAll("]", "")
|
||
.toUpperCase();
|
||
certMap.put("serialNumber", serialNumber);
|
||
}
|
||
if (cert.contains("[URIName:")) {
|
||
String crlUrl = cert.substring(cert.indexOf("[URIName:"), cert.indexOf("[6]")).replaceAll("\\[URIName:", "").replaceAll("]", "").replaceAll("DistributionPoint:", "").replaceAll("\n", "")
|
||
.replaceAll(" ", "");
|
||
certMap.put("crlUrl", crlUrl);
|
||
}
|
||
return certMap;
|
||
}
|
||
|
||
/**
|
||
* 根据规则匹配颁发者和使用者的信息
|
||
*
|
||
* @param str
|
||
* @param key
|
||
* @return java.lang.String
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 10:17 2022/8/31
|
||
**/
|
||
public static String sslSp(String str, String key) {
|
||
if (str.contains(".,")) {
|
||
str = str.replace(".,", "&");
|
||
}
|
||
Map<String, String> maps = Maps.newHashMap();
|
||
String[] split = str.split(",");
|
||
for (String s : split) {
|
||
if (s.contains("=")) {
|
||
String[] split1 = s.split("=");
|
||
if (split1.length == 2) {
|
||
maps.put(split1[0].trim(), split1[1].replace("\"", ""));
|
||
}
|
||
}
|
||
}
|
||
return maps.containsKey(key) ? maps.get(key) : "";
|
||
}
|
||
|
||
/**
|
||
* @param cert
|
||
* @param algorithm 算法(支持SHA-1 和 SHA-256)
|
||
* @return java.lang.String
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 10:17 2022/8/31
|
||
**/
|
||
private static String getThumbPrint(X509Certificate cert, String algorithm) throws Exception {
|
||
MessageDigest md = MessageDigest.getInstance(algorithm);
|
||
byte[] der = cert.getEncoded();
|
||
md.update(der);
|
||
byte[] digest = md.digest();
|
||
return bytesToHexString(digest);
|
||
}
|
||
|
||
/**
|
||
* byte[]转String
|
||
*
|
||
* @param src
|
||
* @return java.lang.String
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 10:21 2022/8/31
|
||
**/
|
||
private static String bytesToHexString(byte[] src) {
|
||
StringBuilder stringBuilder = new StringBuilder();
|
||
if (src == null || src.length <= 0) {
|
||
return null;
|
||
}
|
||
for (int i = 0; i < src.length; i++) {
|
||
int v = src[i] & 0xFF;
|
||
String hv = Integer.toHexString(v);
|
||
if (hv.length() < 2) {
|
||
stringBuilder.append(0);
|
||
}
|
||
stringBuilder.append(hv);
|
||
}
|
||
return stringBuilder.toString();
|
||
}
|
||
|
||
/**
|
||
* 获取网站信息
|
||
*
|
||
* @param web
|
||
* @return java.util.Map<java.lang.String, java.lang.String>
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 13:40 2022/9/1
|
||
**/
|
||
public static Map<String, String> getWebInfo(String vls, String web) {
|
||
Map<String, String> webMap = Maps.newHashMap();
|
||
try {
|
||
trustAllHttpsCertificates(2);
|
||
URL url = new URL(vls + web);
|
||
String ipAddress = InetAddress.getByName(web).getHostAddress();
|
||
URLConnection URLconnection = url.openConnection();
|
||
HttpURLConnection httpConnection = (HttpURLConnection) URLconnection;
|
||
int responseCode = httpConnection.getResponseCode();
|
||
webMap.put("ip", StringUtils.isBlank(ipAddress) ? "-" : ipAddress);
|
||
webMap.put("favicon", findIco(vls + web));
|
||
if (!getResponseStatus().contains(responseCode)) {
|
||
InputStream in = httpConnection.getInputStream();
|
||
InputStreamReader isr = new InputStreamReader(in);
|
||
BufferedReader bufferedReader = new BufferedReader(isr);
|
||
String str;
|
||
while ((str = bufferedReader.readLine()) != null) {
|
||
String trim = str.trim();
|
||
if (trim.contains("<title>")) {
|
||
String titleStr = trim.substring(trim.indexOf("<title>"), trim.indexOf("</title>")).replace("<title>", "");
|
||
webMap.put("title", responseCode == 301 ? "redirect to https://" + web : titleStr);
|
||
}
|
||
}
|
||
bufferedReader.close();
|
||
} else {
|
||
webMap.put("title", "redirect to https://" + web);
|
||
webMap.put("ip", "-");
|
||
}
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
}
|
||
return webMap;
|
||
}
|
||
|
||
/**
|
||
* 获取网站favicon
|
||
*
|
||
* @param navUrl
|
||
* @return java.lang.String
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 13:36 2022/9/1
|
||
**/
|
||
private static String findIco(String navUrl) {
|
||
String body = HttpUtil.get(navUrl);
|
||
String str = body.split("/favicon.ico")[0];
|
||
int http = str.indexOf(HTTPS_STR, str.length() - 100);
|
||
if (http == -1) {
|
||
http = str.indexOf(HTTPS_STR, str.length() - 100);
|
||
}
|
||
if (http == -1) {
|
||
// 说明没有指定 走拼接逻辑
|
||
int i = navUrl.indexOf("/", 8);
|
||
if (i > 0) {
|
||
navUrl = navUrl.substring(0, i);
|
||
}
|
||
} else {
|
||
navUrl = str.substring(http);
|
||
}
|
||
return navUrl + "/favicon.ico";
|
||
}
|
||
|
||
/**
|
||
* 获取证书兼容性测试
|
||
*
|
||
* @param success
|
||
* @return java.util.Map<java.lang.String, java.lang.String>
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 17:20 2022/9/1
|
||
**/
|
||
public static Map<String, String> certCompatible(String success) {
|
||
Map<String, String> maps = Maps.newLinkedHashMap();
|
||
String status = success.equals(NORMAL_STR) ? YES_STR : NO_STR;
|
||
if (success.equals("网址无法访问") || success.equals("没有证书")) {
|
||
return maps;
|
||
} else {
|
||
maps.put("Android 2.3 (Gingerbread)", status);
|
||
maps.put("Android 4.0 (Ice Cream Sandwich)", status);
|
||
maps.put("Android 4.1 (Jelly Bean)", status);
|
||
maps.put("Android 4.2 (Jelly Bean)", status);
|
||
maps.put("Android 4.3 (Jelly Bean)", status);
|
||
maps.put("Android 4.4 (KitKat)", status);
|
||
maps.put("Android 5.0 (Lollipop)", status);
|
||
maps.put("Android 5.1 (Lollipop)", status);
|
||
maps.put("Android 6.0 (Marshmallow)", status);
|
||
maps.put("Android 7.0 (Android Nougat)", status);
|
||
maps.put("Android 7.1 (Android Nougat)", status);
|
||
maps.put("Android 8.0 (Android Oreo)", status);
|
||
maps.put("Android 9.0 (Android Pie)", status);
|
||
maps.put("Android 10.0 (Android Q)", status);
|
||
maps.put("Android 11.0 (Android R)", status);
|
||
maps.put("iOS 5-6", status);
|
||
maps.put("iOS 7", status);
|
||
maps.put("iOS 8", status);
|
||
maps.put("iOS 9", status);
|
||
maps.put("iOS 10", status);
|
||
maps.put("iOS 11", status);
|
||
maps.put("iOS 12", status);
|
||
maps.put("iOS 13", status);
|
||
maps.put("iOS 14", status);
|
||
maps.put("OS X 10.9 (Mavericks)", status);
|
||
maps.put("OS X 10.10 (Yosemite)", status);
|
||
maps.put("OS X 10.11 (Eicapitan)", status);
|
||
maps.put("OS X 10.12 (Sierra)", status);
|
||
maps.put("OS X 10.13 (High Sierra)", status);
|
||
maps.put("OS X 10.14 (Mojave)", status);
|
||
maps.put("java 7u181", status);
|
||
maps.put("java 8u161", status);
|
||
maps.put("java_8u181", status);
|
||
maps.put("java_8u202", status);
|
||
maps.put("java 9", status);
|
||
maps.put("java 10", status);
|
||
maps.put("java 11", status);
|
||
maps.put("java 12", status);
|
||
maps.put("java 13", status);
|
||
maps.put("java 17", status);
|
||
maps.put("Firefox 3.0", status);
|
||
maps.put("Firefox 3.5", status);
|
||
maps.put("Firefox 3.6", status);
|
||
maps.put("Firefox 6.0", status);
|
||
maps.put("Firefox 16", status);
|
||
maps.put("Firefox 23", status);
|
||
maps.put("Firefox 32", status);
|
||
maps.put("Firefox 42", status);
|
||
maps.put("Firefox 50", status);
|
||
maps.put("Firefox 51", status);
|
||
maps.put("Firefox 54", status);
|
||
maps.put("Firefox 58", status);
|
||
maps.put("Firefox 63", status);
|
||
maps.put("Firefox 65", status);
|
||
maps.put("Windows XP", status);
|
||
maps.put("Windows 7", status);
|
||
maps.put("Windows 8", status);
|
||
maps.put("Windows 10", status);
|
||
maps.put("Windows 11", status);
|
||
}
|
||
return maps;
|
||
}
|
||
|
||
public static String getRating(CertExportEmailVO certExportEmailVO) {
|
||
if (certExportEmailVO.getSuccess().equals(NORMAL_STR) && certExportEmailVO.getCertificateType().equals(EV_SSL_STR)) {
|
||
return "A+";
|
||
}
|
||
if (certExportEmailVO.getSuccess().equals(NORMAL_STR) && certExportEmailVO.getCertificateType().equals(OV_SSL_STR)) {
|
||
return "A";
|
||
}
|
||
if (certExportEmailVO.getSuccess().equals(NORMAL_STR) && certExportEmailVO.getCertificateType().equals(DV_SSL_STR)) {
|
||
return "A-";
|
||
}
|
||
if (certExportEmailVO.getSuccess().equals(DOMAIN_NOT_MATCH_STR)) {
|
||
return "T";
|
||
}
|
||
return "B";
|
||
}
|
||
|
||
/**
|
||
* 获取证书响应错误状态码
|
||
*
|
||
* @param
|
||
* @return java.util.List<java.lang.Integer>
|
||
* @throws
|
||
* @author ZhangSan_Plus
|
||
* @date 11:26 2022/9/2
|
||
**/
|
||
public static List<Integer> getResponseStatus() {
|
||
String code = "[\n" + " 404,\n" + " 400,\n" + " 500,\n" + " 502,\n" + " 503,\n" + " 504,\n" + " 505,\n" + " 506,\n" + " 507,\n" + " 508,\n" + " 509,\n" + " 511,\n" + " 512,\n"
|
||
+ " 513,\n" + " 521,\n" + " 530,\n" + " 600,\n" + " 651\n" + "]";
|
||
List<Integer> codeList = JSON.parseObject(code, List.class);
|
||
return codeList;
|
||
}
|
||
|
||
public CertExportEmailVO getCertChain(String fingerprint, String hpUrl) throws Exception {
|
||
hpUrl = hpUrl.contains(HTTP_STR) || hpUrl.contains(HTTPS_STR) ? hpUrl.replaceAll(HTTP_STR, "").replaceAll(HTTPS_STR, "").trim() : hpUrl.trim();
|
||
hpUrl = CNRulesMate(hpUrl) ? chineseDomainNameDecoding(hpUrl) : hpUrl;
|
||
CertExportEmailVO certExportEmailVO1 = distrustCert(fingerprint, hpUrl);
|
||
CertExportEmailVO certExportEmailVO = noDistrustCert(fingerprint, hpUrl);
|
||
return StringUtils.isNotBlank(certExportEmailVO1.getSerialNumber()) ? certExportEmailVO1 : certExportEmailVO;
|
||
}
|
||
|
||
public static CertExportEmailVO distrustCert(String fingerprint, String hpUrl) throws Exception {
|
||
URL url = new URL(HTTPS_STR + hpUrl);
|
||
// 发起HTTPS请求
|
||
trustAllHttpsCertificates(1);
|
||
HttpsURLConnection conn = getConn(url);
|
||
CertExportEmailVO certExportEmailVO = new CertExportEmailVO();
|
||
try {
|
||
conn.connect();
|
||
Certificate[] serverCertificates = conn.getServerCertificates();
|
||
for (int i = 0; i < serverCertificates.length; i++) {
|
||
X509Certificate certs = (X509Certificate) serverCertificates[i];
|
||
if (getThumbPrint(certs, SHA1_AUTH_STR).toUpperCase().equals(fingerprint)) {
|
||
certExportEmailVO = deal(certs, certExportEmailVO);
|
||
certExportEmailVO.setCertificateChains(getCertChain(serverCertificates, getThumbPrint(certs, SHA1_AUTH_STR).toUpperCase()));
|
||
continue;
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
} finally {
|
||
conn.disconnect();
|
||
}
|
||
return certExportEmailVO;
|
||
}
|
||
|
||
public static CertExportEmailVO noDistrustCert(String fingerprint, String hpUrl) throws Exception {
|
||
CertExportEmailVO certExportEmailVO = new CertExportEmailVO();
|
||
URL url = new URL(HTTPS_STR + hpUrl);
|
||
trustAllHttpsCertificates(2);
|
||
HttpsURLConnection conn = getConn(url);
|
||
conn.setHostnameVerifier(new Verifier());
|
||
try {
|
||
conn.connect();
|
||
Certificate[] serverCertificates = conn.getServerCertificates();
|
||
for (int i = 0; i < serverCertificates.length; i++) {
|
||
X509Certificate certs = (X509Certificate) serverCertificates[i];
|
||
if (getThumbPrint(certs, SHA1_AUTH_STR).toUpperCase().equals(fingerprint)) {
|
||
certExportEmailVO = deal(certs, certExportEmailVO);
|
||
certExportEmailVO.setCertificateChains(getCertChain(serverCertificates, getThumbPrint(certs, SHA1_AUTH_STR).toUpperCase()));
|
||
continue;
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
} finally {
|
||
conn.disconnect();
|
||
}
|
||
return certExportEmailVO;
|
||
}
|
||
}
|