Files
dromara--domain-admin/domain_admin/utils/cert_util/cert_openssl_v2.py
T
2026-07-13 12:31:34 +08:00

157 lines
4.5 KiB
Python

# -*- coding: utf-8 -*-
"""
@File : cert_openssl_v2.py
@Date : 2023-06-19
"""
from __future__ import print_function, unicode_literals, absolute_import, division
import socket
import ssl
import OpenSSL
from OpenSSL.crypto import X509
from domain_admin.enums.ssl_type_enum import SSLTypeEnum
from domain_admin.utils import domain_util, time_util, json_util
from domain_admin.utils.cert_util import cert_common
# 默认的ssl端口
DEFAULT_SSL_PORT = 443
# www.
WWW_WITH_DOT = 'www.'
def verify_cert(cert, domain):
"""
验证证书和域名是否匹配
:param cert:
:param domain:
:return:
"""
# 检查 颁发对象 域名(CN) 备用域名(SAN)
common_name = cert.get_subject().commonName
dns_names = cert_common.get_certificate_san(cert)
if common_name not in dns_names:
dns_names.insert(0, common_name)
# 申请域名为www.domain.com的证书同时支持保护domain.com
if common_name.startswith(WWW_WITH_DOT):
not_www_common_name = common_name[len(WWW_WITH_DOT):]
if not_www_common_name not in dns_names:
dns_names.append(not_www_common_name)
else:
www_common_name = WWW_WITH_DOT + common_name
if www_common_name not in dns_names:
dns_names.append(www_common_name)
for dns_name in dns_names:
domain_checked = domain_util.verify_cert_common_name(dns_name, domain)
if domain_checked:
return True
return False
def get_ssl_cert(
domain,
host=None,
port=443,
timeout=3,
ssl_type=SSLTypeEnum.SSL_TLS
):
"""
不验证证书,仅验证域名
支持通配符
:param ssl_type:
:param domain: str
:param host: str
:param port: int
:param timeout: int
:return:
"""
if domain.startswith("https://"):
domain = domain[len("https://"):]
if domain.startswith("http://"):
domain = domain[len("http://"):]
# split port in domain
if ":" in domain:
temp_list = domain.split(":")
domain = temp_list[0]
try:
port = int(temp_list[-1])
except Exception:
print("Illegal port ", temp_list[-1])
# 默认参数
host = host or domain
# socket
# try ipv4
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect((host, port))
# try ipv6
except Exception:
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect((host, port))
# 用户可以设置使用协议:STARTTLS、SSL/TLS
# issues: https://github.com/mouday/domain-admin/issues/57
# ref: https://stackoverflow.com/questions/5108681/use-python-to-get-an-smtp-server-certificate/62695088#62695088
# ref: https://serverfault.com/questions/131627/how-to-inspect-remote-smtp-servers-tls-certificate#:~:text=If%20you%20don%27t%20have%20OpenSSL%2C%20you%20can%20also,ssl.DER_cert_to_PEM_cert%20%28connection.sock.getpeercert%20%28binary_form%3DTrue%29%29%20where%20%5Bhostname%5D%20is%20the%20server.
if ssl_type == SSLTypeEnum.START_TLS:
try:
sock.recv(1000)
sock.send('EHLO\nSTARTTLS\n'.encode('utf-8'))
sock.recv(1000)
except:
pass
# ssl
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
ssl_context.verify_mode = ssl.CERT_NONE
ssl_context.check_hostname = False
# fix: Python2 AttributeError: __exit__
wrap_socket = ssl_context.wrap_socket(sock, server_hostname=domain)
dercert = wrap_socket.getpeercert(True)
wrap_socket.close()
server_cert = ssl.DER_cert_to_PEM_cert(dercert)
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, server_cert.encode())
return cert
def get_ssl_cert_by_openssl(
domain,
host=None,
port=443,
timeout=3,
ssl_type=SSLTypeEnum.SSL_TLS
):
"""
不验证证书,仅验证域名
支持通配符
:param ssl_type:
:param domain: str
:param host: str
:param port: int
:param timeout: int
:return:
"""
cert = get_ssl_cert(domain, host, port, timeout, ssl_type=ssl_type)
# verify
domain_checked = verify_cert(cert, domain)
if not domain_checked:
raise Exception("domain not verified")
return {
'start_date': time_util.parse_time(cert.get_notBefore().decode()),
'expire_date': time_util.parse_time(cert.get_notAfter().decode()),
}