Skip to content

Instantly share code, notes, and snippets.

@yakirn
Created March 19, 2026 10:04
Show Gist options
  • Select an option

  • Save yakirn/1336c9ea85b0bb8761bb1862ae25795a to your computer and use it in GitHub Desktop.

Select an option

Save yakirn/1336c9ea85b0bb8761bb1862ae25795a to your computer and use it in GitHub Desktop.
Self signed certificate utility
#!/usr/bin/env python3
"""
gencert.py — Self-signed certificate generator
Examples:
python gencert.py # 7-day cert (default)
python gencert.py --days 2 # 2-day cert
python gencert.py --hours 5 # 5-hour cert
python gencert.py --cn myapp.local # custom Common Name
Requires: pip install cryptography
"""
import argparse
import os
import sys
from datetime import datetime, timedelta, timezone
try:
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
except ImportError:
print(
"Missing dependency. Install it with:\n pip install cryptography",
file=sys.stderr,
)
sys.exit(1)
def _duration_label(days: int, hours: int) -> str:
if hours is not None:
return f"{hours}hour" if hours == 1 else f"{hours}hours"
return f"{days}day" if days == 1 else f"{days}days"
def generate(cn: str, not_after: datetime):
"""Generate an RSA-2048 self-signed cert. Returns (key_pem, cert_pem, cert_obj)."""
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
now = datetime.now(timezone.utc)
name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)])
cert = (
x509.CertificateBuilder()
.subject_name(name)
.issuer_name(name)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(now)
.not_valid_after(not_after)
.add_extension(
x509.SubjectAlternativeName([x509.DNSName(cn)]),
critical=False,
)
.sign(key, hashes.SHA256())
)
key_pem = key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
cert_pem = cert.public_bytes(serialization.Encoding.PEM)
return key_pem, cert_pem, cert
def main():
parser = argparse.ArgumentParser(
description="Generate a self-signed TLS certificate.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
group = parser.add_mutually_exclusive_group()
group.add_argument("--days", type=int, help="Validity period in days (default: 7)")
group.add_argument("--hours", type=int, help="Validity period in hours")
parser.add_argument(
"--cn", default="localhost", help="Common Name / hostname (default: localhost)"
)
args = parser.parse_args()
if args.days is not None and args.days <= 0:
parser.error("--days must be a positive integer")
if args.hours is not None and args.hours <= 0:
parser.error("--hours must be a positive integer")
now = datetime.now(timezone.utc)
if args.hours is not None:
not_after = now + timedelta(hours=args.hours)
label = _duration_label(None, args.hours)
else:
days = args.days if args.days is not None else 7
not_after = now + timedelta(days=days)
label = _duration_label(days, None)
# Build filename with human-readable expiry (local time)
expiry_local = not_after.astimezone()
expiry_ts = expiry_local.strftime("%Y-%m-%d_%H%M%S")
stem = f"selfsigned_{label}_expires_{expiry_ts}"
out_dir = os.getcwd()
key_path = os.path.join(out_dir, f"{stem}.key")
cert_path = os.path.join(out_dir, f"{stem}.crt")
key_pem, cert_pem, cert_obj = generate(args.cn, not_after)
with open(key_path, "wb") as f:
f.write(key_pem)
with open(cert_path, "wb") as f:
f.write(cert_pem)
expiry_fmt = expiry_local.strftime("%Y-%m-%d %H:%M:%S %Z")
print(f"Common Name : {args.cn}")
print(f"Valid for : {label}")
print(f"Expires : {expiry_fmt}")
print(f"Certificate : {cert_path}")
print(f"Private key : {key_path}")
print()
print("─" * 64)
print(cert_pem.decode(), end="")
print("─" * 64)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment