Spaces:
Running
Running
"""SSL Certificate class for handling certificate operations.""" | |
import ssl | |
import socket | |
import base64 | |
import json | |
from typing import Dict, Any, Optional | |
from urllib.parse import urlparse | |
import OpenSSL.crypto | |
from pathlib import Path | |
class SSLCertificate: | |
""" | |
A class representing an SSL certificate with methods to export in various formats. | |
Attributes: | |
cert_info (Dict[str, Any]): The certificate information. | |
Methods: | |
from_url(url: str, timeout: int = 10) -> Optional['SSLCertificate']: Create SSLCertificate instance from a URL. | |
from_file(file_path: str) -> Optional['SSLCertificate']: Create SSLCertificate instance from a file. | |
from_binary(binary_data: bytes) -> Optional['SSLCertificate']: Create SSLCertificate instance from binary data. | |
export_as_pem() -> str: Export the certificate as PEM format. | |
export_as_der() -> bytes: Export the certificate as DER format. | |
export_as_json() -> Dict[str, Any]: Export the certificate as JSON format. | |
export_as_text() -> str: Export the certificate as text format. | |
""" | |
def __init__(self, cert_info: Dict[str, Any]): | |
self._cert_info = self._decode_cert_data(cert_info) | |
def from_url(url: str, timeout: int = 10) -> Optional['SSLCertificate']: | |
""" | |
Create SSLCertificate instance from a URL. | |
Args: | |
url (str): URL of the website. | |
timeout (int): Timeout for the connection (default: 10). | |
Returns: | |
Optional[SSLCertificate]: SSLCertificate instance if successful, None otherwise. | |
""" | |
try: | |
hostname = urlparse(url).netloc | |
if ':' in hostname: | |
hostname = hostname.split(':')[0] | |
context = ssl.create_default_context() | |
with socket.create_connection((hostname, 443), timeout=timeout) as sock: | |
with context.wrap_socket(sock, server_hostname=hostname) as ssock: | |
cert_binary = ssock.getpeercert(binary_form=True) | |
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, cert_binary) | |
cert_info = { | |
"subject": dict(x509.get_subject().get_components()), | |
"issuer": dict(x509.get_issuer().get_components()), | |
"version": x509.get_version(), | |
"serial_number": hex(x509.get_serial_number()), | |
"not_before": x509.get_notBefore(), | |
"not_after": x509.get_notAfter(), | |
"fingerprint": x509.digest("sha256").hex(), | |
"signature_algorithm": x509.get_signature_algorithm(), | |
"raw_cert": base64.b64encode(cert_binary) | |
} | |
# Add extensions | |
extensions = [] | |
for i in range(x509.get_extension_count()): | |
ext = x509.get_extension(i) | |
extensions.append({ | |
"name": ext.get_short_name(), | |
"value": str(ext) | |
}) | |
cert_info["extensions"] = extensions | |
return SSLCertificate(cert_info) | |
except Exception as e: | |
return None | |
def _decode_cert_data(data: Any) -> Any: | |
"""Helper method to decode bytes in certificate data.""" | |
if isinstance(data, bytes): | |
return data.decode('utf-8') | |
elif isinstance(data, dict): | |
return { | |
(k.decode('utf-8') if isinstance(k, bytes) else k): SSLCertificate._decode_cert_data(v) | |
for k, v in data.items() | |
} | |
elif isinstance(data, list): | |
return [SSLCertificate._decode_cert_data(item) for item in data] | |
return data | |
def to_json(self, filepath: Optional[str] = None) -> Optional[str]: | |
""" | |
Export certificate as JSON. | |
Args: | |
filepath (Optional[str]): Path to save the JSON file (default: None). | |
Returns: | |
Optional[str]: JSON string if successful, None otherwise. | |
""" | |
json_str = json.dumps(self._cert_info, indent=2, ensure_ascii=False) | |
if filepath: | |
Path(filepath).write_text(json_str, encoding='utf-8') | |
return None | |
return json_str | |
def to_pem(self, filepath: Optional[str] = None) -> Optional[str]: | |
""" | |
Export certificate as PEM. | |
Args: | |
filepath (Optional[str]): Path to save the PEM file (default: None). | |
Returns: | |
Optional[str]: PEM string if successful, None otherwise. | |
""" | |
try: | |
x509 = OpenSSL.crypto.load_certificate( | |
OpenSSL.crypto.FILETYPE_ASN1, | |
base64.b64decode(self._cert_info['raw_cert']) | |
) | |
pem_data = OpenSSL.crypto.dump_certificate( | |
OpenSSL.crypto.FILETYPE_PEM, | |
x509 | |
).decode('utf-8') | |
if filepath: | |
Path(filepath).write_text(pem_data, encoding='utf-8') | |
return None | |
return pem_data | |
except Exception as e: | |
return None | |
def to_der(self, filepath: Optional[str] = None) -> Optional[bytes]: | |
""" | |
Export certificate as DER. | |
Args: | |
filepath (Optional[str]): Path to save the DER file (default: None). | |
Returns: | |
Optional[bytes]: DER bytes if successful, None otherwise. | |
""" | |
try: | |
der_data = base64.b64decode(self._cert_info['raw_cert']) | |
if filepath: | |
Path(filepath).write_bytes(der_data) | |
return None | |
return der_data | |
except Exception: | |
return None | |
def issuer(self) -> Dict[str, str]: | |
"""Get certificate issuer information.""" | |
return self._cert_info.get('issuer', {}) | |
def subject(self) -> Dict[str, str]: | |
"""Get certificate subject information.""" | |
return self._cert_info.get('subject', {}) | |
def valid_from(self) -> str: | |
"""Get certificate validity start date.""" | |
return self._cert_info.get('not_before', '') | |
def valid_until(self) -> str: | |
"""Get certificate validity end date.""" | |
return self._cert_info.get('not_after', '') | |
def fingerprint(self) -> str: | |
"""Get certificate fingerprint.""" | |
return self._cert_info.get('fingerprint', '') | |