文档
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