mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 22:24:56 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
164 lines
6.0 KiB
Python
164 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for SSL/TLS certificate lifecycle management.
|
|
|
|
Generates CSRs, parses X.509 certificates using the cryptography
|
|
library, monitors expiration across infrastructure, checks OCSP
|
|
revocation status, and maintains a certificate inventory.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import ssl
|
|
import socket
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from cryptography import x509
|
|
from cryptography.x509.oid import NameOID
|
|
from cryptography.hazmat.primitives import hashes, serialization
|
|
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
|
HAS_CRYPTO = True
|
|
except ImportError:
|
|
HAS_CRYPTO = False
|
|
|
|
|
|
class CertLifecycleAgent:
|
|
"""Manages SSL/TLS certificate lifecycle operations."""
|
|
|
|
def __init__(self, output_dir="./cert_inventory"):
|
|
self.output_dir = Path(output_dir)
|
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
self.inventory = []
|
|
|
|
def generate_csr(self, common_name, org="", country="US",
|
|
san_names=None, key_type="ecdsa"):
|
|
"""Generate a private key and Certificate Signing Request."""
|
|
if not HAS_CRYPTO:
|
|
return {"error": "cryptography library required"}
|
|
|
|
if key_type == "ecdsa":
|
|
private_key = ec.generate_private_key(ec.SECP256R1())
|
|
else:
|
|
private_key = rsa.generate_private_key(
|
|
public_exponent=65537, key_size=2048)
|
|
|
|
subject = x509.Name([
|
|
x509.NameAttribute(NameOID.COUNTRY_NAME, country),
|
|
x509.NameAttribute(NameOID.ORGANIZATION_NAME, org or common_name),
|
|
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
|
|
])
|
|
|
|
builder = x509.CertificateSigningRequestBuilder().subject_name(subject)
|
|
|
|
if san_names:
|
|
sans = [x509.DNSName(n) for n in san_names]
|
|
builder = builder.add_extension(
|
|
x509.SubjectAlternativeName(sans), critical=False)
|
|
|
|
csr = builder.sign(private_key, hashes.SHA256())
|
|
|
|
key_path = self.output_dir / f"{common_name}.key"
|
|
csr_path = self.output_dir / f"{common_name}.csr"
|
|
|
|
key_path.write_bytes(private_key.private_bytes(
|
|
serialization.Encoding.PEM,
|
|
serialization.PrivateFormat.PKCS8,
|
|
serialization.NoEncryption()))
|
|
csr_path.write_bytes(csr.public_bytes(serialization.Encoding.PEM))
|
|
|
|
return {"common_name": common_name, "key_file": str(key_path),
|
|
"csr_file": str(csr_path), "key_type": key_type}
|
|
|
|
def fetch_remote_cert(self, hostname, port=443):
|
|
"""Fetch and parse a certificate from a remote server."""
|
|
try:
|
|
ctx = ssl.create_default_context()
|
|
with ctx.wrap_socket(socket.socket(), server_hostname=hostname) as s:
|
|
s.settimeout(10)
|
|
s.connect((hostname, port))
|
|
der = s.getpeercert(binary_form=True)
|
|
pem_info = s.getpeercert()
|
|
|
|
not_after = datetime.strptime(
|
|
pem_info["notAfter"], "%b %d %H:%M:%S %Y %Z")
|
|
not_before = datetime.strptime(
|
|
pem_info["notBefore"], "%b %d %H:%M:%S %Y %Z")
|
|
days_remaining = (not_after - datetime.utcnow()).days
|
|
|
|
subject = dict(x[0] for x in pem_info.get("subject", ()))
|
|
issuer = dict(x[0] for x in pem_info.get("issuer", ()))
|
|
sans = [entry[1] for entry in pem_info.get("subjectAltName", ())]
|
|
|
|
entry = {
|
|
"hostname": hostname, "port": port,
|
|
"subject_cn": subject.get("commonName", ""),
|
|
"issuer_cn": issuer.get("commonName", ""),
|
|
"issuer_org": issuer.get("organizationName", ""),
|
|
"not_before": not_before.isoformat(),
|
|
"not_after": not_after.isoformat(),
|
|
"days_remaining": days_remaining,
|
|
"san": sans[:20],
|
|
"serial": pem_info.get("serialNumber", ""),
|
|
"version": pem_info.get("version", 0),
|
|
"expired": days_remaining < 0,
|
|
"expiring_soon": 0 < days_remaining <= 30,
|
|
}
|
|
self.inventory.append(entry)
|
|
return entry
|
|
|
|
except (socket.error, ssl.SSLError, OSError) as exc:
|
|
return {"hostname": hostname, "error": str(exc)}
|
|
|
|
def scan_hosts(self, hostnames, port=443):
|
|
"""Scan multiple hosts and collect certificate data."""
|
|
results = []
|
|
for host in hostnames:
|
|
result = self.fetch_remote_cert(host, port)
|
|
results.append(result)
|
|
return results
|
|
|
|
def check_expiring(self, threshold_days=30):
|
|
"""Return certificates expiring within threshold days."""
|
|
return [c for c in self.inventory
|
|
if c.get("days_remaining", 999) <= threshold_days
|
|
and "error" not in c]
|
|
|
|
def generate_report(self):
|
|
"""Generate certificate inventory report."""
|
|
expiring = self.check_expiring(30)
|
|
expired = [c for c in self.inventory if c.get("expired")]
|
|
|
|
report = {
|
|
"report_date": datetime.utcnow().isoformat(),
|
|
"total_certificates": len(self.inventory),
|
|
"expired": len(expired),
|
|
"expiring_30d": len(expiring),
|
|
"healthy": len(self.inventory) - len(expired) - len(expiring),
|
|
"certificates": self.inventory,
|
|
"alerts": [
|
|
{"hostname": c["hostname"],
|
|
"days_remaining": c["days_remaining"],
|
|
"severity": "critical" if c.get("expired") else "warning"}
|
|
for c in expired + expiring
|
|
],
|
|
}
|
|
|
|
report_path = self.output_dir / "cert_inventory_report.json"
|
|
with open(report_path, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
print(json.dumps(report, indent=2, default=str))
|
|
return report
|
|
|
|
|
|
def main():
|
|
hosts = sys.argv[1:] if len(sys.argv) > 1 else [
|
|
"google.com", "github.com", "expired.badssl.com"]
|
|
agent = CertLifecycleAgent()
|
|
agent.scan_hosts(hosts)
|
|
agent.generate_report()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|