文档
- openssl x509 -in <cert>.pem -noout -dates
-
- echo | openssl s_client -servername <doman> -connect <doman>:443 2>/dev/null | openssl x509 -noout -dates
-
- # coding: utf-8
- # 查询域名证书到期情况
-
- import re
- import subprocess
- from datetime import datetime
-
-
- def get_re_match_result(pattern, string):
- match = re.search(pattern, string)
- return match.group(1)
-
-
- def parse_time(date_str):
- return datetime.strptime(date_str, "%b %d %H:%M:%S %Y GMT")
-
-
- def format_time(date_time):
- return datetime.strftime(date_time, "%Y-%m-%d %H:%M:%S")
-
-
- def get_cert_info(domain):
- """获取证书信息"""
- cmd = f"curl -Ivs https://{domain} --connect-timeout 10"
-
- exitcode, output = subprocess.getstatusoutput(cmd)
-
- # 正则匹配
- start_date = get_re_match_result('start date: (.*)', output)
- expire_date = get_re_match_result('expire date: (.*)', output)
-
- # 解析匹配结果
- start_date = parse_time(start_date)
- expire_date = parse_time(expire_date)
-
- return {
- 'start_date': start_date,
- 'expire_date': expire_date
- }
-
-
- def get_cert_expire_date(domain):
- """获取证书剩余时间"""
- info = get_cert_info(domain)
- print(info)
-
- expire_date = info['expire_date']
-
- # 剩余天数
- return (expire_date - datetime.now()).days
-
-
- if __name__ == "__main__":
- domain = 'www.baidu.com'
- expire_date = get_cert_expire_date(domain)
- print(expire_date)
-
-
核心代码
- # -*- coding: utf-8 -*-
-
- import socket
- import ssl
-
-
- def get_domain_cert(domain):
- """
- 获取证书信息
- :param domain: str
- :return: dict
- """
- socket.setdefaulttimeout(5)
-
- cxt = ssl.create_default_context()
- skt = cxt.wrap_socket(socket.socket(), server_hostname=domain)
-
- skt.connect((domain, 443))
- cert = skt.getpeercert()
-
- skt.close()
-
- return cert
-
-
- if __name__ == "__main__":
- print(get_domain_cert("www.baidu.com"))
-
-
还有一种方式也记录一下
-
- import socket
- import ssl
-
- def get_domain_cert(host, port=443, timeout=3):
- """
- 获取证书信息
- 存在问题:没有指定主机ip,不一定能获取到正确的证书信息
- :param host: str
- :param port: int
- :param timeout: int
- :return: dict
- """
- context = ssl.create_default_context()
-
- with socket.create_connection(address=(host, port), timeout=timeout) as sock:
- with context.wrap_socket(sock, server_hostname=host) as wrap_socket:
- return wrap_socket.getpeercert()
-
输出
- {
- 'subject': ((('countryName', 'CN'),), (('stateOrProvinceName', 'beijing'),), (('localityName', 'beijing'),), (('organizationalUnitName', 'service operation department'),), (('organizationName', 'Beijing Baidu Netcom Science Technology Co., Ltd'),), (('commonName', 'baidu.com'),)),
- 'issuer': ((('countryName', 'BE'),), (('organizationName', 'GlobalSign nv-sa'),), (('commonName', 'GlobalSign RSA OV SSL CA 2018'),)),
- 'version': 3,
- 'serialNumber': '4417CE86EF82EC6921CC6F68',
- 'notBefore': 'Jul 5 05:16:02 2022 GMT',
- 'notAfter': 'Aug 6 05:16:01 2023 GMT',
- 'subjectAltName': (('DNS', 'baidu.com'), ),
- 'OCSP': ('http://ocsp.globalsign.com/gsrsaovsslca2018',),
- 'caIssuers': ('http://secure.globalsign.com/cacert/gsrsaovsslca2018.crt',),
- 'crlDistributionPoints': ('http://crl.globalsign.com/gsrsaovsslca2018.crl',)
- }
-
结构化输出内容后的完整代码
- # -*- coding: utf-8 -*-
-
- import socket
- import ssl
-
- from dateutil import parser
-
- # requests.packages.urllib3.disable_warnings()
-
- try:
- _create_unverified_https_context = ssl._create_unverified_context
- except AttributeError:
- # Legacy Python that doesn't verify HTTPS certificates by default
- pass
- else:
- # Handle target environment that doesn't support HTTPS verification
- ssl._create_default_https_context = _create_unverified_https_context
-
- DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
-
- socket.setdefaulttimeout(5)
-
-
- def get_domain_ip(domain):
- """
- 获取ip地址
- :param domain: str
- :return: str
- """
- try:
- addrinfo = socket.getaddrinfo(domain, None)
- return addrinfo[0][-1][0]
- except Exception as e:
- pass
-
- return None
-
-
- def get_domain_cert(domain):
- """
- 获取证书信息
- :param domain: str
- :return: dict
- """
- cxt = ssl.create_default_context()
- skt = cxt.wrap_socket(socket.socket(), server_hostname=domain)
-
- skt.connect((domain, 443))
- cert = skt.getpeercert()
- skt.close()
-
- return cert
-
-
- def get_cert_info(domain):
- """
- 获取证书信息
- :param domain: str
- :return: dict
- """
- cert = get_domain_cert(domain)
-
- issuer = _tuple_to_dict(cert['issuer'])
- subject = _tuple_to_dict(cert['subject'])
-
- return {
- 'domain': domain,
- 'ip': get_domain_ip(domain),
- 'subject': _name_convert(subject),
- 'issuer': _name_convert(issuer),
- # 'version': cert['version'],
- # 'serial_number': cert['serialNumber'],
- 'start_date': _parse_time(cert['notBefore']),
- 'expire_date': _parse_time(cert['notAfter']),
- }
-
-
- def _tuple_to_dict(cert_tuple):
- """
- cert证书 tuple转dict
- :param cert_tuple: tuple
- :return:
- """
- data = {}
- for item in cert_tuple:
- data[item[0][0]] = item[0][1]
-
- return data
-
-
- def _name_convert(data):
- """
- 名字转换
- :param data: dict
- :return: dict
- """
- name_map = {
- 'C': 'countryName',
- 'CN': 'commonName',
- 'O': 'organizationName',
- 'OU': 'organizationalUnitName',
- 'L': 'localityName',
- 'ST': 'stateOrProvinceName'
- }
-
- dct = {}
- for key, value in name_map.items():
- dct[key] = data.get(value, '')
-
- return dct
-
-
- def _parse_time(time_str):
- """
- 解析并格式化时间
- :param time_str: str
- :return: str
- """
- return parser.parse(time_str).astimezone().strftime(DATETIME_FORMAT)
-
-
- if __name__ == "__main__":
- print(get_cert_info("www.baidu.com"))
-
-
输出
- {
- "domain": "www.baidu.com",
- "ip": "39.156.66.14",
- "subject": {
- "C": "CN",
- "CN": "baidu.com",
- "O": "Beijing Baidu Netcom Science Technology Co., Ltd",
- "OU": "service operation department",
- "L": "beijing",
- "ST": "beijing"
- },
- "issuer": {
- "C": "BE",
- "CN": "GlobalSign RSA OV SSL CA 2018",
- "O": "GlobalSign nv-sa",
- "OU": "",
- "L": "",
- "ST": ""
- },
- "start_date": "2022-07-05 13:16:02",
- "expire_date": "2023-08-06 13:16:01"
- }
-
该方式,不校验证书合法性,只获取证书信息
文档:
依赖
- pip install pyOpenSSL
-
示例
- # -*- coding: utf-8 -*-
-
- import ssl
- import OpenSSL
-
-
- def get_ssl_expire_date(host, port=443):
- cert = ssl.get_server_certificate((host, port))
- x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
- return x509.get_notAfter().decode()
-
-
- if __name__ == '__main__':
- print(get_ssl_expire_date('www.baidu.com'))
- # 20230806051601Z
-
-
项目地址:https://github.com/mouday/domain-admin
运行环境:
- $ pip install domain_admin
-
- # 升级到最新版本,可选
- $ pip3 install -U domain-admin -i https://pypi.org/simple
-
- # 启动运行
- $ gunicorn 'domain_admin.main:app'
-
访问地址:http://127.0.0.1:8000
默认的管理员账号:admin 密码:123456