cryptography

Migrating to Post-Quantum Cryptography

Inventory cryptography, deploy hybrid X25519 and ML-KEM, and prioritize harvest-now-decrypt-later data.

cbomcrypto-agilitycryptographyml-dsaml-kempost-quantumquantum-readinesstls
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Scope and Authorization: This skill describes defensive cryptographic-migration engineering on systems you own or operate. Cryptographic discovery scanning can touch sensitive key material and production traffic — run inventory tooling only with authorization and in line with your organization's change-management and data-handling policies.

Overview

A cryptographically relevant quantum computer (CRQC) running Shor's algorithm will break the public-key cryptography that secures almost all of today's communications and signatures: RSA, finite-field and elliptic-curve Diffie-Hellman (DH/ECDH), and ECDSA. Symmetric primitives (AES) and hashes (SHA-2/3) are only weakened (Grover gives a quadratic speedup, mitigated by larger key/output sizes), but asymmetric algorithms are catastrophically broken. The most urgent threat is harvest-now, decrypt-later (HNDL): adversaries capturing encrypted traffic today to decrypt once a CRQC exists, which puts long-lived secrets (health records, state secrets, intellectual property, root-of-trust keys) at risk now.

On 13 August 2024 NIST finalized the first post-quantum standards: FIPS 203 (ML-KEM, Module-Lattice KEM, formerly CRYSTALS-Kyber) for key establishment, FIPS 204 (ML-DSA, Module-Lattice digital signatures, formerly CRYSTALS-Dilithium), and FIPS 205 (SLH-DSA, the stateless hash-based signature scheme SPHINCS+). The migration playbook (NIST SP 1800-38, Migration to Post-Quantum Cryptography) is: (1) build a cryptographic inventory / CBOM, (2) prioritize by HNDL exposure and crypto-agility, (3) deploy hybrid schemes (a classical algorithm AND a PQC algorithm combined, e.g. X25519MLKEM768) so a break in either leg does not compromise the session, and (4) re-key and rotate.

This skill maps to ATT&CK T1573 – Encrypted Channel: the same cryptographic channels adversaries abuse for stealthy C2 are the channels defenders must make quantum-resistant; understanding the algorithms in use is foundational to both attack detection and defensive migration. The NIST CSF outcome is PR.DS-02 (data-in-transit protection) — and by extension data-at-rest for HNDL-sensitive stores.

When to Use

  • When building an enterprise cryptographic inventory / Cryptography Bill of Materials (CBOM) for quantum-readiness.
  • When prioritizing which systems must migrate first based on data lifetime and HNDL exposure.
  • When enabling hybrid post-quantum key exchange (X25519MLKEM768) on TLS endpoints, VPNs, or SSH.
  • When issuing PQC or hybrid certificates and testing PQC signature verification.
  • When evaluating crypto-agility — the ability to swap algorithms without re-architecting applications.

Prerequisites

  • OpenSSL 3.5.0 or later, which ships native ML-KEM, ML-DSA, and SLH-DSA support:
    openssl version            # expect 3.5.0+
    openssl list -kem-algorithms | grep -i mlkem
    openssl list -signature-algorithms | grep -i mldsa
  • For OpenSSL 3.0–3.4, the Open Quantum Safe oqs-provider plus liboqs:
    git clone https://github.com/open-quantum-safe/liboqs && \
      cmake -S liboqs -B liboqs/build && cmake --build liboqs/build && \
      sudo cmake --install liboqs/build
    git clone https://github.com/open-quantum-safe/oqs-provider && \
      cmake -S oqs-provider -B oqs-provider/_build && \
      cmake --build oqs-provider/_build && \
      sudo cmake --install oqs-provider/_build
  • Python 3.8+ for the inventory helper:
    python3 -m pip install cryptography
  • (Optional) A CBOM generator: CycloneDX cdxgen, or cbomkit-theia for container/directory crypto discovery.

Objectives

  • Produce a cryptographic inventory (CBOM) of algorithms, key sizes, certificates, and protocols in use.
  • Classify assets by quantum vulnerability and HNDL exposure and prioritize migration.
  • Stand up and verify hybrid X25519MLKEM768 key exchange on a TLS endpoint.
  • Generate ML-KEM and ML-DSA keys and a PQC/hybrid certificate, and verify signatures.
  • Establish a crypto-agility baseline and a re-keying / rotation plan.

MITRE ATT&CK Mapping

ID Official Technique Name Relevance
T1573 Encrypted Channel Migration secures the encrypted channels (TLS/VPN/SSH) that protect data in transit; cryptographic inventory of these channels also underpins detection of adversary-controlled encrypted C2.
T1573.002 Encrypted Channel: Asymmetric Cryptography RSA/ECDH key exchange — the exact asymmetric primitives broken by a CRQC and replaced by ML-KEM hybrids.
T1573.001 Encrypted Channel: Symmetric Cryptography AES and other symmetric ciphers; quantum-weakened by Grover, mitigated by 256-bit keys rather than replacement.

Workflow

1. Confirm PQC algorithm availability

openssl version
# List quantum-safe KEMs and signatures available in this OpenSSL build
openssl list -kem-algorithms | grep -Ei 'mlkem|kyber'
openssl list -signature-algorithms | grep -Ei 'mldsa|dilithium|slhdsa|sphincs'
openssl list -tls-groups 2>/dev/null | grep -Ei 'mlkem'

If using oqs-provider on OpenSSL 3.0–3.4, activate it in openssl.cnf:

[provider_sect]
default = default_sect
oqsprovider = oqsprovider_sect
[default_sect]
activate = 1
[oqsprovider_sect]
activate = 1

2. Build a cryptographic inventory (CBOM)

Generate a CycloneDX CBOM from a code repo or container with cbomkit-theia / cdxgen:

# Directory / container image crypto discovery
cbomkit-theia dir ./myapp --output cbom.json
# or with cdxgen (Java keystores, certs, source-level algorithms)
cdxgen -t java --include-crypto -o cbom.json ./myapp

Enumerate TLS algorithms and certificate signature schemes across live endpoints with the helper agent.py scan (below), and the public-key strength of any certificate:

openssl x509 -in server.crt -noout -text | grep -E 'Signature Algorithm|Public Key'

3. Classify and prioritize by HNDL exposure

For each inventoried asset, record: algorithm, key size, where the key lives, data sensitivity, and data lifetime. Prioritize migration where data_lifetime_years + migration_time > years_until_CRQC (Mosca's inequality). Long-lived confidential data over public networks ranks highest; ephemeral internal traffic ranks lower. Hash-based signature roots-of-trust (firmware signing) are also high priority because they protect long-lived trust anchors.

4. Generate ML-KEM and ML-DSA key material

# ML-KEM-768 (key establishment) keypair
openssl genpkey -algorithm ML-KEM-768 -out mlkem768.key
# OpenSSL 3.0-3.4 + oqs-provider uses lowercase 'mlkem768'
# openssl genpkey -algorithm mlkem768 -out mlkem768.key
 
# ML-DSA-65 (signature) keypair
openssl genpkey -algorithm ML-DSA-65 -out mldsa65.key
openssl pkey -in mldsa65.key -pubout -out mldsa65.pub

5. Issue a PQC (ML-DSA) certificate

# Self-signed ML-DSA-65 certificate for testing
openssl req -new -x509 -key mldsa65.key -out mldsa65.crt -days 365 \
  -subj "/CN=pqc-test.example.com"
openssl x509 -in mldsa65.crt -noout -text | grep -A1 'Signature Algorithm'

6. Sign and verify with ML-DSA

echo "firmware-image-v2.bin" > artifact.txt
openssl dgst -sign mldsa65.key -out artifact.sig artifact.txt
openssl dgst -verify mldsa65.pub -signature artifact.sig artifact.txt
# -> "Verified OK"

7. Deploy and test hybrid TLS key exchange

Run a TLS 1.3 server and force the hybrid group X25519MLKEM768 (classical X25519 + ML-KEM-768):

# Server (use a classical or ML-DSA cert/key)
openssl s_server -accept 4433 -www -tls1_3 \
  -cert mldsa65.crt -key mldsa65.key -groups X25519MLKEM768
 
# Client — negotiate the hybrid group and confirm it was used
openssl s_client -connect localhost:4433 -tls1_3 -groups X25519MLKEM768 \
  </dev/null 2>/dev/null | grep -E 'Negotiated|Server Temp Key|Cipher'

For external endpoints, confirm support against a public PQC test server:

openssl s_client -groups X25519MLKEM768 -tls1_3 -connect pq.cloudflareresearch.com:443 </dev/null

8. Enable hybrid PQC on production TLS terminators

Configure the web server / load balancer to offer the hybrid group while keeping classical fallback for old clients. NGINX with OpenSSL 3.5+:

server {
    listen 443 ssl;
    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519MLKEM768:X25519:secp256r1;   # hybrid first, classical fallback
    ssl_certificate     /etc/nginx/certs/server.crt;
    ssl_certificate_key /etc/nginx/certs/server.key;
}

Reload and verify with the s_client command from step 7 against the live host.

9. Establish crypto-agility and a rotation plan

Centralize algorithm selection (config, not code), record key/cert expiry, and schedule re-keying. Re-run the inventory (step 2) on a cadence to confirm no quantum-vulnerable-only algorithms remain on prioritized assets, and track residual RSA/ECDH usage to zero on high-HNDL paths.

Tools and Resources

Tool / Resource Purpose Link
FIPS 203 (ML-KEM) KEM standard https://csrc.nist.gov/pubs/fips/203/final
FIPS 204 (ML-DSA) Signature standard https://csrc.nist.gov/pubs/fips/204/final
FIPS 205 (SLH-DSA) Hash-based signature standard https://csrc.nist.gov/pubs/fips/205/final
NIST SP 1800-38 Migration practice guide / crypto discovery https://www.nccoe.nist.gov/crypto-agility-considerations-migrating-post-quantum-cryptographic-algorithms
OpenSSL 3.5 Native ML-KEM/ML-DSA/SLH-DSA + hybrid groups https://www.openssl.org
oqs-provider / liboqs PQC for OpenSSL 3.0–3.4 https://github.com/open-quantum-safe/oqs-provider
CycloneDX CBOM Cryptography Bill of Materials spec https://cyclonedx.org/capabilities/cbom/
CBOMkit / cbomkit-theia Crypto discovery & CBOM generation https://github.com/cbomkit/cbomkit-theia

Algorithm Reference

Classical (broken/weakened) Quantum-safe replacement Standard Use
RSA / ECDH / DH key exchange ML-KEM-512/768/1024 (hybrid: X25519MLKEM768) FIPS 203 Key establishment
RSA / ECDSA / EdDSA signatures ML-DSA-44/65/87 FIPS 204 General signatures
(backup signature) SLH-DSA (SPHINCS+) FIPS 205 Conservative/firmware signing
AES-128 AES-256 FIPS 197 Symmetric (Grover-hardened)
SHA-256 SHA-384/512, SHA-3 FIPS 180-4/202 Hashing

Validation Criteria

  • OpenSSL 3.5+ (or 3.x + oqs-provider) confirmed exposing ML-KEM and ML-DSA.
  • Cryptographic inventory / CBOM produced covering algorithms, keys, certs, and protocols.
  • Assets classified and prioritized by HNDL exposure (Mosca's inequality applied).
  • ML-KEM-768 and ML-DSA-65 keypairs generated successfully.
  • PQC (ML-DSA) certificate issued and its signature algorithm verified.
  • Sign/verify round trip with ML-DSA returns "Verified OK".
  • Hybrid X25519MLKEM768 key exchange negotiated and confirmed on a test endpoint.
  • Production TLS terminator offers the hybrid group with classical fallback.
  • Crypto-agility/rotation plan documented and inventory re-run scheduled.
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 2

api-reference.md2.6 KB

OpenSSL PQC Command Reference

Discovery

Task Command
OpenSSL version (need 3.5+) openssl version
List quantum-safe KEMs openssl list -kem-algorithms | grep -i mlkem
List quantum-safe signatures openssl list -signature-algorithms | grep -Ei 'mldsa|slhdsa'
List TLS groups openssl list -tls-groups | grep -i mlkem
Inspect cert algorithm openssl x509 -in server.crt -noout -text | grep -E 'Signature Algorithm|Public Key'

Key generation

Task Command (OpenSSL 3.5+) oqs-provider (3.0–3.4)
ML-KEM-768 keypair openssl genpkey -algorithm ML-KEM-768 -out mlkem768.key -algorithm mlkem768
ML-DSA-65 keypair openssl genpkey -algorithm ML-DSA-65 -out mldsa65.key -algorithm mldsa65
Extract public key openssl pkey -in mldsa65.key -pubout -out mldsa65.pub same

Certificates

Task Command
Self-signed ML-DSA cert openssl req -new -x509 -key mldsa65.key -out mldsa65.crt -days 365 -subj "/CN=pqc.example.com"
Inspect signature alg openssl x509 -in mldsa65.crt -noout -text | grep -A1 'Signature Algorithm'

Sign / verify

Task Command
Sign openssl dgst -sign mldsa65.key -out artifact.sig artifact.txt
Verify openssl dgst -verify mldsa65.pub -signature artifact.sig artifact.txt

Hybrid TLS key exchange

Task Command
TLS server (hybrid group) openssl s_server -accept 4433 -www -tls1_3 -cert mldsa65.crt -key mldsa65.key -groups X25519MLKEM768
TLS client (hybrid group) openssl s_client -connect localhost:4433 -tls1_3 -groups X25519MLKEM768
Test public PQC endpoint openssl s_client -groups X25519MLKEM768 -tls1_3 -connect pq.cloudflareresearch.com:443

Standardized hybrid TLS groups

Group Classical leg PQC leg
X25519MLKEM768 X25519 ML-KEM-768
SecP256r1MLKEM768 NIST P-256 ML-KEM-768
SecP384r1MLKEM1024 NIST P-384 ML-KEM-1024

NGINX hybrid config (OpenSSL 3.5+)

ssl_protocols TLSv1.3;
ssl_ecdh_curve X25519MLKEM768:X25519:secp256r1;   # hybrid first, classical fallback

oqs-provider activation (openssl.cnf)

[provider_sect]
default = default_sect
oqsprovider = oqsprovider_sect
[default_sect]
activate = 1
[oqsprovider_sect]
activate = 1

CBOM generation

Tool Command
cbomkit-theia (dir) cbomkit-theia dir ./myapp --output cbom.json
cdxgen (Java + crypto) cdxgen -t java --include-crypto -o cbom.json ./myapp
standards.md1.8 KB

Standards and Framework Mapping

NIST Cybersecurity Framework 2.0

ID Name Rationale
PR.DS-02 The confidentiality, integrity, and availability of data-in-transit are protected Hybrid PQC key exchange (X25519MLKEM768) protects data in transit against harvest-now-decrypt-later attacks by a future CRQC.

MITRE ATT&CK

ID Name Rationale
T1573 Encrypted Channel Migration hardens the encrypted channels protecting data in transit; cryptographic inventory of these channels also underpins detection of adversary encrypted C2.
T1573.001 Encrypted Channel: Symmetric Cryptography AES/symmetric ciphers — quantum-weakened by Grover and hardened via 256-bit keys.
T1573.002 Encrypted Channel: Asymmetric Cryptography RSA/ECDH — the asymmetric primitives broken by Shor's algorithm and replaced by ML-KEM.

NIST Post-Quantum Standards (finalized 13 Aug 2024)

Standard Algorithm Former name Purpose
FIPS 203 ML-KEM (Module-Lattice KEM) CRYSTALS-Kyber Key encapsulation / establishment
FIPS 204 ML-DSA (Module-Lattice DSA) CRYSTALS-Dilithium Primary digital signatures
FIPS 205 SLH-DSA (Stateless Hash-based DSA) SPHINCS+ Conservative backup signatures

Migration Guidance

Reference Rationale
NIST SP 1800-38 (NCCoE, Migration to Post-Quantum Cryptography) Crypto-discovery test plan, CBOM-driven inventory, and migration architecture across CI/CD, operational systems, and network services.
Mosca's inequality Prioritization rule: migrate when data_lifetime + migration_time > time_to_CRQC.
CycloneDX 1.6 CBOM Cryptography Bill of Materials object model for inventory and dependency tracking.

Scripts 1

agent.py7.8 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
pqc_agent.py — Post-quantum cryptography migration helper.

Three defensive functions for quantum-readiness work:

  scan      Inventory the public-key crypto of a remote TLS endpoint and a set
            of local X.509 certificates, flagging quantum-vulnerable algorithms
            (RSA/EC/DSA/DH) vs. quantum-safe (ML-KEM / ML-DSA / SLH-DSA).
  prioritize  Apply Mosca's inequality to a CSV of assets to rank migration order.
  hybrid-test  Use the local OpenSSL CLI to negotiate the hybrid X25519MLKEM768
            TLS group against a host and report whether it succeeded.

Run only against systems you are authorized to assess. The scan opens TLS
sockets and reads certificates; it does not transmit or store key material.

Examples:
  python3 pqc_agent.py scan --host example.com --port 443 --certs ./certs/*.pem
  python3 pqc_agent.py prioritize --csv assets.csv --crqc-years 8
  python3 pqc_agent.py hybrid-test --host pq.cloudflareresearch.com --port 443
"""
import argparse
import csv
import glob
import socket
import ssl
import subprocess
import sys

try:
    from cryptography import x509
    from cryptography.hazmat.primitives.asymmetric import rsa, ec, dsa, ed25519, ed448
    HAVE_CRYPTO = True
except ImportError:
    HAVE_CRYPTO = False

QUANTUM_VULNERABLE = ("rsa", "ec", "ecdsa", "dsa", "dh", "ecdh", "ed25519", "ed448")
QUANTUM_SAFE = ("ml-kem", "mlkem", "ml-dsa", "mldsa", "slh-dsa", "slhdsa")


def _classify_public_key(pubkey):
    """Return (algorithm_label, vulnerable_bool, key_size_or_curve)."""
    if isinstance(pubkey, rsa.RSAPublicKey):
        return "RSA", True, pubkey.key_size
    if isinstance(pubkey, ec.EllipticCurvePublicKey):
        return "EC", True, pubkey.curve.name
    if isinstance(pubkey, dsa.DSAPublicKey):
        return "DSA", True, pubkey.key_size
    if isinstance(pubkey, (ed25519.Ed25519PublicKey, ed448.Ed448PublicKey)):
        return type(pubkey).__name__, True, "edwards"
    return pubkey.__class__.__name__, False, "?"


def _fetch_peer_cert_pem(host, port, timeout):
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    with socket.create_connection((host, port), timeout=timeout) as sock:
        with ctx.wrap_socket(sock, server_hostname=host) as ssock:
            der = ssock.getpeercert(binary_form=True)
            version = ssock.version()
            cipher = ssock.cipher()
    return ssl.DER_cert_to_PEM_cert(der), version, cipher


def _inspect_cert_pem(pem_text, label):
    if not HAVE_CRYPTO:
        sys.stderr.write("ERROR: install dependency: python3 -m pip install cryptography\n")
        sys.exit(2)
    cert = x509.load_pem_x509_certificate(pem_text.encode())
    alg, vuln, size = _classify_public_key(cert.public_key())
    sig = cert.signature_algorithm_oid._name
    sig_vuln = any(v in sig.lower() for v in QUANTUM_VULNERABLE) and \
        not any(s in sig.lower() for s in QUANTUM_SAFE)
    flag = "VULNERABLE" if (vuln or sig_vuln) else "quantum-safe"
    print(f"[{flag:>12}] {label}")
    print(f"               subject : {cert.subject.rfc4514_string()}")
    print(f"               pubkey  : {alg} ({size})")
    print(f"               sig alg : {sig}")
    return vuln or sig_vuln


def cmd_scan(args):
    findings = 0
    if args.host:
        try:
            pem, version, cipher = _fetch_peer_cert_pem(args.host, args.port, args.timeout)
            print(f"== Remote endpoint {args.host}:{args.port} ({version}, {cipher[0]}) ==")
            if _inspect_cert_pem(pem, f"{args.host}:{args.port} leaf cert"):
                findings += 1
        except (socket.error, ssl.SSLError, OSError) as exc:
            sys.stderr.write(f"ERROR: could not connect to {args.host}:{args.port}: {exc}\n")
    cert_paths = []
    for pattern in args.certs:
        cert_paths.extend(glob.glob(pattern))
    if cert_paths:
        print("\n== Local certificates ==")
    for path in cert_paths:
        try:
            with open(path, "r", encoding="utf-8") as fh:
                if _inspect_cert_pem(fh.read(), path):
                    findings += 1
        except (OSError, ValueError) as exc:
            sys.stderr.write(f"WARN: skipping {path}: {exc}\n")
    print(f"\nQuantum-vulnerable assets found: {findings}")
    return 1 if findings else 0


def cmd_prioritize(args):
    """Mosca's inequality: migrate if data_lifetime + migration_time > crqc_years."""
    try:
        with open(args.csv, newline="", encoding="utf-8") as fh:
            rows = list(csv.DictReader(fh))
    except OSError as exc:
        sys.stderr.write(f"ERROR: cannot read {args.csv}: {exc}\n")
        sys.exit(1)
    scored = []
    for r in rows:
        try:
            life = float(r.get("data_lifetime_years", 0))
            mig = float(r.get("migration_time_years", 1))
        except ValueError:
            life, mig = 0.0, 1.0
        urgent = (life + mig) > args.crqc_years
        margin = (life + mig) - args.crqc_years
        scored.append((margin, urgent, r.get("asset", "?"), life, mig))
    scored.sort(reverse=True)
    print(f"{'PRIORITY':<10}{'MARGIN':>8}  ASSET (data_life + migration vs CRQC={args.crqc_years}y)")
    print("-" * 70)
    for margin, urgent, asset, life, mig in scored:
        tag = "MIGRATE" if urgent else "monitor"
        print(f"{tag:<10}{margin:>+8.1f}  {asset}  (life={life}, mig={mig})")
    return 0


def cmd_hybrid_test(args):
    cmd = ["openssl", "s_client", "-tls1_3", "-groups", "X25519MLKEM768",
           "-connect", f"{args.host}:{args.port}"]
    try:
        proc = subprocess.run(cmd, input=b"", capture_output=True, timeout=args.timeout)
    except FileNotFoundError:
        sys.stderr.write("ERROR: openssl not found on PATH (need 3.5+ or oqs-provider).\n")
        sys.exit(2)
    except subprocess.TimeoutExpired:
        sys.stderr.write("ERROR: openssl s_client timed out.\n")
        sys.exit(1)
    out = (proc.stdout + proc.stderr).decode(errors="replace")
    ok = "Server Temp Key" in out or "Negotiated TLS1.3 group: X25519MLKEM768" in out
    if proc.returncode == 0 and ok:
        for line in out.splitlines():
            if "Temp Key" in line or "Negotiated" in line or "Cipher" in line:
                print(line.strip())
        print(f"\n[OK] {args.host}:{args.port} negotiated hybrid X25519MLKEM768")
        return 0
    sys.stderr.write(f"[FAIL] {args.host}:{args.port} did not negotiate X25519MLKEM768\n")
    sys.stderr.write(out[-800:] + "\n")
    return 1


def build_parser():
    p = argparse.ArgumentParser(description="Post-quantum cryptography migration helper.")
    sub = p.add_subparsers(dest="cmd", required=True)

    s = sub.add_parser("scan", help="Inventory TLS endpoint + local certs for quantum-vulnerable crypto")
    s.add_argument("--host", help="Remote host to inspect")
    s.add_argument("--port", type=int, default=443)
    s.add_argument("--certs", nargs="*", default=[], help="Glob(s) of local PEM certs")
    s.add_argument("--timeout", type=float, default=10)
    s.set_defaults(func=cmd_scan)

    pr = sub.add_parser("prioritize", help="Rank assets via Mosca's inequality from a CSV")
    pr.add_argument("--csv", required=True,
                    help="CSV with columns: asset,data_lifetime_years,migration_time_years")
    pr.add_argument("--crqc-years", type=float, default=10,
                    help="Estimated years until a cryptographically relevant quantum computer")
    pr.set_defaults(func=cmd_prioritize)

    h = sub.add_parser("hybrid-test", help="Test hybrid X25519MLKEM768 negotiation via openssl")
    h.add_argument("--host", required=True)
    h.add_argument("--port", type=int, default=443)
    h.add_argument("--timeout", type=float, default=15)
    h.set_defaults(func=cmd_hybrid_test)
    return p


def main():
    args = build_parser().parse_args()
    return args.func(args)


if __name__ == "__main__":
    sys.exit(main())
Keep exploring