First working version

This commit is contained in:
2026-03-19 19:05:42 -06:00
parent b2239d5414
commit 222dbee9aa
11 changed files with 508 additions and 647 deletions

View File

@@ -1,107 +1,132 @@
#!/usr/bin/env python3
# dlitz 2025
import sys
import os
import shlex
import subprocess
import sys
import tempfile
from argparse import ArgumentParser
from pathlib import Path
from .cert_util import split_certs
from .ssl_util import SSLUtil
#from .cert_pusher import MTCertPusher
from .routeros_ssh import RouterOS, SSHConnector
from cryptography import x509
from cryptography.hazmat.primitives.serialization import (
load_pem_private_key,
load_pem_public_key,
)
#def generate_random_passphrase():
# return os.urandom(64).hex()
from .connector import SSHConnector
from .routeros import RouterOS
def make_arg_parser():
if Path(sys.argv[0]).stem == '__main__':
if Path(sys.argv[0]).stem == "__main__":
prog = __package__
else:
prog = None
parser = ArgumentParser(
prog=prog,
description="push TLS privkey & certificate to MikroTik RouterOS router"
description="push TLS privkey & certificate to MikroTik RouterOS router",
)
parser.add_argument(
subparsers = parser.add_subparsers(dest="subcommand")
install_parser = subparsers.add_parser(
"install", help="push TLS privkey & certificate to MikroTik RouterOS router"
)
install_parser.add_argument(
"-k", "--privkey", type=Path, required=True, help="private key file"
)
parser.add_argument("--cert", type=Path, required=True, help="certificate file")
parser.add_argument(
install_parser.add_argument(
"--cert", type=Path, required=True, help="certificate file"
)
install_parser.add_argument(
"--chain", type=Path, help="separate certificate chain file (optional)"
)
parser.add_argument("--ssh-config", type=Path, help="ssh config file")
parser.add_argument("--ssh-host", required=True, help="target ssh host")
parser.add_argument("--ssh-user", help="target ssh user")
parser.add_argument("--ssh-port", type=int, help="target ssh port")
install_parser.add_argument("--ssh-config", type=Path, help="ssh config file")
install_parser.add_argument("--ssh-host", required=True, help="target ssh host")
install_parser.add_argument("--ssh-user", help="target ssh user")
install_parser.add_argument("--ssh-port", type=int, help="target ssh port")
fingerprint_parser = subparsers.add_parser(
"fingerprint", aliases=["fpr"], help="calculate fingerprint of certificate(s)"
)
fingerprint_parser.add_argument(
dest="files",
metavar="cert.pem",
nargs='+',
type=Path,
help="PEM certificate file to read",
)
skid_parser = subparsers.add_parser(
#"skid", help="calculate SubjectKeyIdentifier of certificate(s) or key(s)"
"skid", help="show the SubjectKeyIdentifier of certificate(s)"
)
skid_parser.add_argument(
dest="files",
#metavar="file.pem",
metavar="cert.pem",
nargs='+',
type=Path,
#help="PEM file to read",
help="PEM certificate file to read",
)
return parser
def parse_args():
parser = make_arg_parser()
args = parser.parse_args()
assert ":" not in args.ssh_host
return args, parser
def main():
args, parser = parse_args()
# TODO: Check certificate serial number before attempting to copy cert, and at end.
if args.subcommand == "install":
assert ":" not in args.ssh_host
privkey_data = args.privkey.read_text()
cert_data = args.cert.read_text()
chain_data = args.chain.read_text() if args.chain is not None else None
privkey_data = args.privkey.read_text()
cert_data = args.cert.read_text()
chain_data = args.chain.read_text() if args.chain is not None else None
#key_passphrase = generate_random_passphrase()
#chain_certs = split_certs(chain_data)
connector = SSHConnector(
host=args.ssh_host,
port=args.ssh_port,
user=args.ssh_user,
ssh_config_path=args.ssh_config,
)
ros_remote = RouterOS(connector=connector)
ssl_util = SSLUtil()
ssh_connector = SSHConnector(host=args.ssh_host, port=args.ssh_port, user=args.ssh_user, ssh_config_path=args.ssh_config)
ros_remote = RouterOS(connector=ssh_connector, ssl_util=ssl_util)
fingerprint, host_cert_obj = ros_remote.install_key_and_certificates(
key=privkey_data, cert=cert_data, chain=chain_data
)
#ros_ssh = RouterOS_SSH(host=args.ssh_host)
ros_remote.use_certificate(fingerprint)
ros_remote.install_key_and_certificates(key=privkey_data, cert=cert_data, chain=chain_data)
# pkcs12_data = sslutil.export_pkcs12(
# privkey_data=privkey_data,
# cert_data=cert_data,
# chain_data=chain_data,
# passphrase=key_passphrase,
# )
elif args.subcommand in ("fingerprint", "fpr"):
for path in args.files:
try:
for cert_obj in x509.load_pem_x509_certificates(path.read_bytes()):
print(RouterOS.cert_fingerprint(cert_obj))
except Exception as exc:
exc.add_note(f"path={path}")
raise
#
#
# with tempfile.NamedTemporaryFile(dir="/dev/shm") as tf:
# tf.write(pkcs12_data)
# tf.flush()
#
# ssh_options = [
# "-oBatchMode=yes",
# "-oControlMaster=no",
# ]
#
# cmd = ["scp", *ssh_options, "-q", tf.name, f"{args.ssh_host}:/cert-pusher-data.p12"]
# # print("executing:", shlex.join(cmd))
# subprocess.run(cmd, check=True)
#
# # ros_command = f'/certificate import name=www_ssl_cert file-name=cert-pusher-data.p12 no-key-export=yes passphrase="{key_passphrase}"'
# ros_command = f'/certificate import name=www_ssl_cert file-name=cert-pusher-data.p12 no-key-export=yes passphrase="{key_passphrase}"; /file remove [/file find name=cert-pusher-data.p12]'
# cmd = [
# "ssh",
# *ssh_options,
# args.ssh_host,
# ros_command,
# ]
# result = subprocess.check_output(cmd, text=True)
# assert " files-imported: 1" in result
# # print(result)
elif args.subcommand in ("skid"):
for path in args.files:
try:
for cert_obj in x509.load_pem_x509_certificates(path.read_bytes()):
print(RouterOS.cert_skid(cert_obj))
except Exception as exc:
exc.add_note(f"path={path}")
raise
else:
raise NotImplementedError(args.subcommand)
if __name__ == "__main__":