获取https证书信息

说明:

      获取https证书信息包括以下的方法:

  1. 通过whois命令,使用当前工具类执行,但是需要有whois的命令权限
  2. 调用域名获取证书相关信息

1、测试入口

   @Test
    public void test() {
        TslDTO tslDTO = TlsUtils.getFirstTlsInfo("https://www.baidu.com");
        System.out.println(JSON.toJSONString(tslDTO));

        TslByShellDTO tslByShellDTO = TlsUtils.getFirstTlsByShell("https://www.sina.com");
        System.out.println(JSON.toJSONString(tslByShellDTO));

        Assert.assertTrue(ObjectUtils.anyNotNull(tslByShellDTO));
    }

 

2、注解


import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;

/**
 * 属性转换的值
 *
 * @author wangmingcong
 * @date 2021/4/23
 */
@Documented
@Target(value = {FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldValue {

    /**
     * 属性值
     *
     * @return 属性值
     */
    String value();
}

3、实体类


import xxxxxx.annotation.FieldValue;
import lombok.Data;

import java.util.Date;

/**
 * @author wangmingcong
 * @date 2021/4/25
 */
@Data
public class TslByShellDTO {

    /**
     * 服务器 Tengine
     */
    @FieldValue(value = "server")
    private String server;

    /**
     * Sun, 25 Apr 2021 06:35:47 GMT
     */
    @FieldValue(value = "date")
    private Date date;

    /**
     * text/html; charset=utf-8
     */
    @FieldValue(value = "content-type")
    private String contentType;

    /**
     * 472997
     */
    @FieldValue(value = "content-length")
    private String contentLength;

    /**
     * 1; mode=block
     */
    @FieldValue(value = "x-xss-protection")
    private String xXssProtection;

    /**
     * nosniff
     */
    @FieldValue(value = "x-content-type-options")
    private String xContentTypeOptions;

    /**
     * noopen
     */
    @FieldValue(value = "x-download-options")
    private String xDownloadOptions;

    /**
     * 275
     */
    @FieldValue(value = "x-readtime")
    private String xReadTime;

    /**
     * Sun,25Apr 2021 06:38:47GMT
     */
    @FieldValue(value = "expires")
    private Date expires;

    /**
     * Nov 30 00:00:00 2020 GMT
     */
    @FieldValue(value = "*  start date")
    private Date startDate;

    /**
     * Dec 31 23:59:59 2021 GMT
     */
    @FieldValue(value = "*  expire date")
    private Date expireDate;

    /**
     * max-age=180
     */
    @FieldValue(value = "cache-control")
    private String cacheControl;

    /**
     * HIT
     */
    @FieldValue(value = "cache-status")
    private String cacheStatus;

    /**
     * www.9ji.com/'homessr'
     */
    @FieldValue(value = "cache-status1")
    private String cacheStatus1;
}

import lombok.Data;

import java.math.BigInteger;
import java.security.PublicKey;
import java.util.Date;

/**
 * @author wangmingcong
 * @date 2021/4/25
 */
@Data
public class TslDTO {

    /**
     * 版本号
     */
    private Integer version;

    /**
     * 证书编号
     */
    private BigInteger serialNumber;

    /**
     * 颁发机构
     */
    private String subjectDNName;

    /**
     * 颁发者
     */
    private String issuerDNName;

    /**
     * 证书开始时间
     */
    private Date notBefore;

    /**
     * 有效期止时间
     */
    private Date notAfter;

    /**
     * 签名算法
     */
    private String sigAlgName;

    /**
     * 签名算法
     */
    private PublicKey publicKey;

    /**
     * 证书签名
     */
    private byte[] signature;
}

4、Http请求工具类

import javax.net.ssl.*;
import java.io.IOException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.util.HashMap;
import java.util.Map;

/**
 * 功能不满足,扩展之前的接口
 *
 * @author wangmingcong
 * @date 2021/4/25
 */
public class HttpTlsUtils {

    private static final HashMap<String, String> HEADER_GET = new HashMap<>();

    static {
        HEADER_GET.put("Content-Type", "application/x-www-form-urlencoded");
    }

    /**
     * 获取 TSL 的信息
     *
     * @param httpsUrl 请求地址
     * @return 返回 TSL 的信息
     */
    public static Certificate[] getCertificate(String httpsUrl) {
        HttpsURLConnection conn = null;
        try {
            HttpsURLConnection.setDefaultHostnameVerifier(new HttpTlsUtils().new NullHostNameVerifier());
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, getTrustManager(), new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
            URL url = new URL(httpsUrl);
            conn = (HttpsURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            for (Map.Entry<String, String> entry : HEADER_GET.entrySet()) {
                conn.setRequestProperty(entry.getKey(), entry.getValue());
            }
            conn.setConnectTimeout(3000);
            conn.setReadTimeout(5000);
            conn.connect();
            return conn.getServerCertificates();
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
        } catch (IOException ioException) {
            ioException.printStackTrace();
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
        return new Certificate[0];
    }

    /**
     * getTrustManager
     *
     * @return getTrustManager
     */
    private static TrustManager[] getTrustManager() {
        return new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {

            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {

            }

            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[0];
            }
        }};
    }

    /**
     * NullHostNameVerifier
     */
    private class NullHostNameVerifier implements HostnameVerifier {

        @Override
        public boolean verify(String arg0, SSLSession arg1) {
            return true;
        }
    }
}

Shell执行工具类

https://blog.csdn.net/qq_38428623/article/details/116945589

 

5、主要工具类


import xxxx.common.exception.CustomizeException;
import xxxx.utils.ShellUtils;
import xxxx.utils.tls.dto.TslByShellDTO;
import xxxx.utils.tls.dto.TslDTO;
import xxxx.utils.whois.WhoisCommonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.util.CollectionUtils;

import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 获取 证书显相关的工具类
 *
 * @author wangmingcong
 * @date 2021/4/25
 */
@Slf4j
public class TlsUtils {

    /**
     * 私有构造函数
     */
    private TlsUtils() {
    }

    /**
     * 获取 第一个证书的信息
     *
     * @param httpsUrl 必须是 https 的域名
     * @return 返回 证书的相关信息
     */
    public static TslDTO getFirstTlsInfo(String httpsUrl) {
        Certificate[] certificates = HttpTlsUtils.getCertificate(httpsUrl);
        if (ArrayUtils.isEmpty(certificates)) {
            return null;
        }
        return convertTslDTO((X509Certificate) certificates[0]);
    }

    /**
     * 获取 所有的Tls信息
     *
     * @param httpsUrl 必须是 https 的域名
     * @return 返回 所有的Tls信息
     */
    public static List<TslDTO> getAllTlsInfo(String httpsUrl) {
        Certificate[] certificates = HttpTlsUtils.getCertificate(httpsUrl);
        if (ArrayUtils.isEmpty(certificates)) {
            return Collections.emptyList();
        }
        List<TslDTO> tslDTOList = new ArrayList<>();
        for (Certificate certificate : certificates) {
            if (certificate == null) {
                continue;
            }
            tslDTOList.add(convertTslDTO((X509Certificate) certificate));
        }
        return tslDTOList;
    }

    /**
     * 通过shell 方法获取 TSL 信息
     * 有点问题,后面优化
     *
     * @param httpsUrl https 地址
     * @return 返回 方法获取 TSL 信息
     */
    public static TslByShellDTO getFirstTlsByShell(String httpsUrl) {
        List<String> processList = new ArrayList<>();
        try {
            String shellStr = String.format("%s %s", "curl -v -I -k ", httpsUrl);
            List<String> resultList = ShellUtils.getInputResult(shellStr);
            processList.addAll(resultList);
            List<String> errorResultList = ShellUtils.getErrorResult(shellStr);
            processList.addAll(errorResultList);
        } catch (Exception e) {
            if (e instanceof InterruptedException) {
                throw new CustomizeException(e.getMessage());
            }
            log.error("{}", e);
        }
        return convertTslByShellDTO(processList);
    }

    /**
     * 转为 TslByShellDTO对象
     *
     * @param processList list 数据
     * @return 返回 转换之后的数据
     */
    private static TslByShellDTO convertTslByShellDTO(List<String> processList) {
        if (CollectionUtils.isEmpty(processList)) {
            return null;
        }
        TslByShellDTO tslByShellDTO = new TslByShellDTO();
        for (String process : processList) {
            WhoisCommonUtil.validateFiled(tslByShellDTO, process);
        }
        return tslByShellDTO;
    }

    // ----- 私有方法 -----

    /**
     * 转换为 TslDTO
     *
     * @param certificate x509Certificate
     * @return 返回 TslDTO
     */
    private static TslDTO convertTslDTO(X509Certificate certificate) {
        TslDTO tslDTO = new TslDTO();
        tslDTO.setVersion(certificate.getVersion());
        tslDTO.setSerialNumber(certificate.getSerialNumber());
        tslDTO.setSubjectDNName(certificate.getSubjectDN().getName());
        tslDTO.setIssuerDNName(certificate.getIssuerDN().getName());
        tslDTO.setNotBefore(certificate.getNotBefore());
        tslDTO.setNotAfter(certificate.getNotAfter());
        tslDTO.setSigAlgName(certificate.getSigAlgName());
        tslDTO.setPublicKey(certificate.getPublicKey());
        tslDTO.setSignature(certificate.getSignature());

        return tslDTO;
    }
}

 


import cn.hutool.http.HttpUtil;
import xxxx.annotation.FieldValue;
import io.vertx.core.impl.ConcurrentHashSet;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.reflections.ReflectionUtils;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Whois 解析结果
 *
 * @author wangmingcong
 * @date 2021/4/23
 */
@Slf4j
@Data
public class WhoisCommonUtil {

    /**
     * 静态的变量
     */
    public static final String EMPTY_STRING, COLON_STRING, URL_DOMAIN_FORMAT;

    /**
     * 时间格式,为了兼容
     */
    private static final ConcurrentHashSet<String> EN_DATE_FORMAT = new ConcurrentHashSet<>();

    static {
        EMPTY_STRING = "";
        COLON_STRING = ":";
        URL_DOMAIN_FORMAT = "%s%s";
        EN_DATE_FORMAT.add("yyyy-MM-dd");
        EN_DATE_FORMAT.add("yyyy-MM-dd HH:mm:ss");
        EN_DATE_FORMAT.add("EEE, dd MMM yyyy hh:mm:ss");
        EN_DATE_FORMAT.add("EEE, dd MMM yyyy hh:mm:ss z");
        EN_DATE_FORMAT.add("EEE MMM dd yyyy hh:mm:ss");
        EN_DATE_FORMAT.add("EEE MMM dd yyyy hh:mm:ss z");
        //Nov 30 00:00:00 2020 GMT
        EN_DATE_FORMAT.add("MMM dd hh:mm:ss yyyy z");
    }

    private WhoisCommonUtil() {

    }

    /**
     * 获取 whois的内容
     *
     * @param url 地址
     * @return 返回 whois的内容
     */
    public static String getWhoisContent(String url) {
        return getWhoisContent(url, "");
    }

    /**
     * 获取 whois的内容
     *
     * @param url        地址
     * @param domainName 域名
     * @return 返回 whois的内容
     */
    public static String getWhoisContent(String url, String domainName) {
        try {
            return HttpUtil.get(String.format(URL_DOMAIN_FORMAT, url, domainName), 3000);
        } catch (Exception e) {
            log.error("", e);
            return null;
        }
    }

    /**
     * 获取 匹配的结果
     *
     * @param content 待匹配的内容
     * @param pattern 正则表达式
     * @return 返回 匹配的结果
     */
    public static List<String> getMatchResult(String content, String pattern) {
        if (StringUtils.isBlank(content) || StringUtils.isBlank(pattern)) {
            return Collections.emptyList();
        }
        Pattern r = Pattern.compile(pattern);
        //创建 matcher 对象
        Matcher m = r.matcher(content);
        List<String> patternList = new ArrayList<>();
        while (m.find()) {
            patternList.add(m.group());
        }
        return patternList;
    }

    /**
     * 返回 替换的内容
     *
     * @param content 待替换的内容
     * @param pattern 正则表达式
     * @return 返回 替换之后的内容
     */
    public static String getReplaceContent(String content, String pattern) {
        return getReplaceContent(content, pattern, EMPTY_STRING);
    }

    /**
     * 返回 替换的内容
     *
     * @param content    待替换的内容
     * @param pattern    正则表达式
     * @param newContent 替换的字符串
     * @return 返回 替换之后的内容
     */
    public static String getReplaceContent(String content, String pattern, String newContent) {
        if (StringUtils.isBlank(content) || StringUtils.isBlank(pattern) || StringUtils.isBlank(newContent)) {
            return "";
        }
        return content.replaceAll(pattern, newContent);
    }

    /**
     * 属性验证
     *
     * @param obj     对象
     * @param content 内容
     * @param <T>     泛型
     */
    public static <T> void validateFiled(T obj, String content) {
        if (StringUtils.isBlank(content)) {
            return;
        }
        // 特殊处理时间的问题,因为时间的分隔符也是冒号 :,所以使用substring,不能使用 split
        int indexOf = content.indexOf(WhoisCommonUtil.COLON_STRING);
        if (indexOf == -1) {
            return;
        }
        // 名称
        Object fileName = content.substring(0, indexOf).trim();
        // 值
        Object fileValue = content.substring(indexOf + 1).trim();
        // 验证并且赋值
        WhoisCommonUtil.validateFiled(obj, fileName, fileValue);
    }

    /**
     * 属性验证
     *
     * @param obj       域名返回的地址
     * @param fileName  属性名
     * @param fileValue 属性对应的值
     */
    public static <T> void validateFiled(T obj, Object fileName, Object fileValue) {
        // 获取 所有的 属性信息
        Set<Field> fieldSet = ReflectionUtils.getAllFields(obj.getClass());
        for (Field field : fieldSet) {
            FieldValue fieldAnnotation = field.getAnnotation(FieldValue.class);
            if (fieldAnnotation.value().equals(fileName)) {
                validateField(obj, fileValue, field);
                break;
            }
        }
    }

    /**
     * 验证并且赋值
     *
     * @param obj       obj 对象
     * @param fileValue 属性的值
     * @param field     属性
     */
    private static <T> void validateField(T obj, Object fileValue, Field field) {
        Class<?> fieldType = field.getType();
        if (!isPrimitive(fieldType)) {
            // 获取值
            Object objValue = null;
            try {
                field.setAccessible(true);
                objValue = field.get(obj);
            } catch (IllegalAccessException e) {
                log.error("{}", e);
            }
            // 假如是 List
            if (List.class.equals(fieldType)) {
                List<Object> list = (List) objValue;
                if (CollectionUtils.isEmpty(list)) {
                    list = new ArrayList<>();
                }
                list.add(fileValue);
                fileValue = list;
            }
        }
        // 赋值
        setFieldValue(obj, fileValue, field);
    }

    /**
     * 属性赋值
     *
     * @param obj       域名返回的结果
     * @param fileValue 属性值
     * @param field     属性
     */
    private static <T> void setFieldValue(T obj, Object fileValue, Field field) {
        // 转换值的格式
        fileValue = convertValueFormat(field, fileValue);
        try {
            field.setAccessible(true);
            field.set(obj, fileValue);
        } catch (IllegalAccessException e) {
            log.error("{}", e);
        }
    }

    /**
     * 根据 属性的类型,转换对应的值
     *
     * @param field     属性
     * @param fileValue 属性值
     * @return 返回 属性的类型,转换对应的值
     */
    private static Object convertValueFormat(Field field, Object fileValue) {
        if (field.getType().equals(Date.class)) {
            String value = fileValue.toString();
            for (String dateFormat : EN_DATE_FORMAT) {
                try {
                    fileValue = DateUtils.parseDate(value, dateFormat);
                    break;
                } catch (ParseException e) {
                    SimpleDateFormat sf = new SimpleDateFormat(dateFormat, Locale.ENGLISH);
                    try {
                        fileValue = sf.parse(value);
                        break;
                    } catch (ParseException parseException) {
                        continue;
                    }
                }
            }
        }
        return fileValue;
    }

    /**
     * 验证 是否是基本类型或者基本类型的包装类
     *
     * @param fieldType 类型
     * @return 返回 是否是基本类型或者基本类型的包装类
     */
    private static boolean isPrimitive(Class<?> fieldType) {
        if (fieldType.isPrimitive()) {
            return true;
        }
        return fieldType.equals(String.class)
                || fieldType.equals(Double.class)
                || fieldType.equals(Float.class)
                || fieldType.equals(Long.class)
                || fieldType.equals(Integer.class)
                || fieldType.equals(Short.class)
                || fieldType.equals(Character.class);
    }
}

 

6、执行结果

{"issuerDNName":"CN=GlobalSign Organization Validation CA - SHA256 - G2, O=GlobalSign nv-sa, C=BE","notAfter":1627277462000,"notBefore":1585811098000,"publicKey":{"algorithm":"RSA","algorithmId":{"name":"RSA","oID":{}},"encoded":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwamwrkca0lfrHRUfblyy5PgLINvqAN8p/6RriSZLnyMv7FewirhGQCp+vNxaRZdPrUEOvCCGSwxdVSFH4jE8V6fsmUfrRw1y18gWVHXv00URD0vOYHpGXCh0ro4bvthwZnuok0ko0qN2lFXefCfyD/eYDK2G2sau/Z/w2YEympfjIe4EkpbkeBHlxBAOEDF6Speg68ebxNqJN6nDN9dWsX9Sx9kmCtavOBaxbftzebFoeQOQ64h7jEiRmFGlB5SGpXhGeY9Ym+k1Wafxe1cxCpDPJM4NJOeSsmrp5pY3Crh8hy900lzoSwpfZhinQYbPJqYIjqVJF5JTs5Glz1OwMQIDAQAB","encodedInternal":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwamwrkca0lfrHRUfblyy5PgLINvqAN8p/6RriSZLnyMv7FewirhGQCp+vNxaRZdPrUEOvCCGSwxdVSFH4jE8V6fsmUfrRw1y18gWVHXv00URD0vOYHpGXCh0ro4bvthwZnuok0ko0qN2lFXefCfyD/eYDK2G2sau/Z/w2YEympfjIe4EkpbkeBHlxBAOEDF6Speg68ebxNqJN6nDN9dWsX9Sx9kmCtavOBaxbftzebFoeQOQ64h7jEiRmFGlB5SGpXhGeY9Ym+k1Wafxe1cxCpDPJM4NJOeSsmrp5pY3Crh8hy900lzoSwpfZhinQYbPJqYIjqVJF5JTs5Glz1OwMQIDAQAB","format":"X.509","modulus":24447670194681134930622535078849307650955709445907141319429480038436215335358073627900931159797668335850497404378455565832224636917345203491168006207927092349898005019173027938424401309836372580357513567321708170696086150650622138642758058891488241515864554714488540371116467529413156897546991986743817547978784330145647870682680887376850278609713886918023141693015453400702875454035455875710531226071413246691218088873710308709233736395323607087215365814260642368927749970191077661316040264473990918611638306735542549194843604547458738232510083862807640123860703823935330939847871556783613794587557047453884618092593,"publicExponent":65537},"serialNumber":35388244279832734960132917320,"sigAlgName":"SHA256withRSA","signature":"vNwC0NnejMXi2f5N77rRIos0QlmEkjGC1Qq8QDXbBrITbsjPAfFfwOe3NDc6qAjynzLV+SCAn7/T/21HnHbRy/HH8duDMzflPxinAOK92v5PKUVXh3hfU4UNs6NcY5P+4CZe+ZKM7XajXznmIgU2xTJz0M1RqsjDH6isWya32ZRgCIGB0/W3ek/fOSFYM7UVYwKMuCLq2Xp07FpBuz2nyeJAIeo0Gkrtc2BGx5Y7meT15ZITzvQ8FtViD7oOma5cpS002JpVt1hEzgE4u9B2LGTejQArmeLdYRDtwLBe5ao3QNh8EzddBV9h7mlL3+Tsz/jyrqVfVSsPMfJkClOr6w==","subjectDNName":"CN=baidu.com, O=\"Beijing Baidu Netcom Science Technology Co., Ltd\", OU=service operation department, L=beijing, ST=beijing, C=CN","version":3}


{"cacheControl":"max-age=120","contentLength":"24000","contentType":"text/html","date":1621180947000,"expires":1621180962000,"server":"Tengine"}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

比嗨皮兔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值