#!python3 # dlitz 2025 from contextlib import ExitStack, contextmanager import multiprocessing import subprocess import os import re class PKCS12Exporter: openssl_prog = "openssl" def export_pkcs12(self, privkey_data, cert_data, chain_data, passphrase): assert re.search(r"^-----BEGIN (.*) PRIVATE KEY-----\n", privkey_data, re.M) assert re.search(r"^-----BEGIN CERTIFICATE-----\n", cert_data, re.M) assert "PRIVATE KEY" not in cert_data assert chain_data is None or "PRIVATE KEY" not in chain_data fullchain_data = cert_data + "\n" + (chain_data or "") + "\n" all_data = privkey_data + "\n" + fullchain_data with ExitStack() as stack: passphrase_r, passphrase_w = stack.enter_context(self.open_pipes(text=True)) cmd = [ self.openssl_prog, "pkcs12", "-export", "-passout", f"fd:{passphrase_r.fileno():d}", "-macalg", "SHA256", "-keypbe", "AES-256-CBC", "-certpbe", "NONE", ] passphrase_proc = multiprocessing.Process(target=self.data_writer, args=(passphrase_w, passphrase)) passphrase_proc.start() passphrase_w.close() print(cmd) #subprocess.run(["bash", "-x", "-c", "ls -l /dev/fd/; cat /dev/fd/5; cat /dev/fd/3"], pass_fds=(passphrase_r.fileno(), privkey_r.fileno())) #subprocess.run(cmd, pass_fds=(passphrase_r.fileno(), privkey_r.fileno()), input=fullchain_data.encode()) subprocess.run(cmd, pass_fds=(passphrase_r.fileno(), privkey_r.fileno()), input=all_data.encode()) passphrase_proc.terminate() passphrase_proc. @contextmanager def open_pipes(self, *, text=False): rfd, wfd = os.pipe() rfile = wfile = None try: rfile = open(rfd, "r" if text else "rb") wfile = open(wfd, "w" if text else "wb") except: if rfile is not None: rfile.close() else: os.close(rfd) if wfile is not None: wfile.close() else: os.close(wfd) with rfile as r, wfile as w: yield r, w @classmethod def data_writer(cls, file, data): file.write(data) file.close()