#!/usr/bin/env ucode 'use strict'; import { basename, readfile, writefile, stdin } from "fs"; let pk = require("pkgen"); let valid_from = "20240101000000"; let valid_to = "21001231235959"; let subject, password, password_stdin; let keytype = "ec"; let keylen = 2048; let keyexp = 65537; let keycurve = "secp256r1"; let no_ca; let legacy; const usage_message = `Usage: ${basename(sourcepath())} [] [] Commands: ca : Create a new CA. (creates ca.pem, ca.key, ca.serial) cert : Create a new certificate/key using the CA from ca.pem. (creates cert.pem and ca.key) cert_p12 : Create a new PKCS#12 certificate/key using the CA from ca.pem. (creates ca.p12) selfsigned : Create a self-signed certificate (creates cert.pem) Options: -C Set EC curve type (default: ${keycurve}) Possible values: secp521r1, secp384r1, secp256r1, secp256k1, secp224r1, secp224k1, secp192r1, secp192k1 -E Set RSA key exponent (default: ${keyexp}) -L Set RSA key length (default: ${keylen}) -N Omit CA certificate for PKCS#12 files -p Set PKCS#12 password to -P Read PKCS#12 password from stdin (default: random password, printed to stdout) -s Set subject for generated certificate to . -t rsa|ec Set key type to rsa or ec (default: ec) -V Set validity for generated certificates. (default: ${valid_from} ${valid_to}) -W Use weaker PKCS#12 encryption for compatibility with Windows and Apple systems `; function perror(msg) { let err = pk.errno() == -1 ? "Invalid arguments" : pk.error(); warn(`${msg}: ${err}\n`); exit(1); } function usage() { warn(usage_message); exit(1); } function check_pem_path(pem_file) { if (substr(pem_file, -4) != ".pem") { warn(`Path with .pem extension expected\n`); exit(1); } return pem_file; } function gen_key() { let key = pk.generate_key({ type: keytype, curve: keycurve, size: keylen, exponent: keyexp, }); if (!key) perror("Failed to generate CA key"); return key; } function gen_cert(key, args) { let cert = pk.generate_cert({ subject_name: subject, subject_key: key, validity: [ valid_from, valid_to ], ...args }); if (!cert) perror("Failed to generate certificate"); cert = cert.pem(); if (!cert) perror("Failed to complete certificate"); return cert; } function gen_client_cert(ca_file, ca_data, key) { let ca_base = substr(ca_file, 0, -4); let ca_info = pk.cert_info(ca_data); if (!length(ca_info)) perror("Failed to load CA certificate"); let ca_key = pk.load_key(readfile(ca_base + ".key")); if (!ca_key) perror("Failed to load CA key"); let ca_serial = +readfile(ca_base + ".serial"); if (!ca_serial) perror("Failed to load CA serial"); let cert = gen_cert(key, { serial: ++ca_serial, issuer_name: ca_info[0].subject, issuer_key: ca_key, }); writefile(ca_base + ".serial", "" + ca_serial); return cert; } let cmds = { ca: function(args) { let ca_file = check_pem_path(shift(args)); let ca_base = substr(ca_file, 0, -4); let key = gen_key(); let ca_cert = gen_cert(key, { ca: true, serial: 1, issuer_name: subject, issuer_key: key, key_usage: [ "key_cert_sign" ], }); writefile(ca_file, ca_cert); writefile(ca_base + ".key", key.pem()); writefile(ca_base + ".serial", "1"); }, cert: function (args) { let ca_file = check_pem_path(shift(args)); let crt_file = check_pem_path(shift(args)); let crt_base = substr(crt_file, 0, -4); let key = gen_key(); let ca_data = readfile(ca_file); let cert = gen_client_cert(ca_file, ca_data, key); writefile(crt_base + ".key", key.pem()); writefile(crt_file, cert); }, cert_p12: function (args) { let ca_file = check_pem_path(shift(args)); let p12_file = shift(args); if (!p12_file) usage(); let key = gen_key(); let ca_data = readfile(ca_file); let cert = gen_client_cert(ca_file, ca_data, key); if (password_stdin) password = rtrim(stdin.read("line")); else if (!password) print((password = hexenc(readfile("/dev/urandom", 8))) + "\n"); let p12 = pk.generate_pkcs12({ password, cert, key, legacy, extra: no_ca ? null : [ ca_data ], }); writefile(p12_file, p12); }, selfsigned: function(args) { let crt_file = check_pem_path(shift(args)); let crt_base = substr(crt_file, 0, -4); let key = gen_key(); let cert = gen_cert(key, { serial: 1, issuer_name: subject, issuer_key: key, }); writefile(crt_base + ".key", key.pem()); writefile(crt_file, cert); }, }; while (substr(ARGV[0], 0, 1) == "-") { let opt = substr(shift(ARGV), 1); switch (opt) { case 'C': keycurve = shift(ARGV); break; case 'L': keylen = +shift(ARGV); break; case 'N': no_ca = true; break; case 'p': password = shift(ARGV); if (password_stdin) usage(); break; case 'P': password_stdin = true; if (password) usage(); break; case 's': subject = shift(ARGV); break; case 't': keytype = shift(ARGV); if (keytype != "rsa" && keytype != "ec") { warn(`Unsupported key type ${keytype}\n`); exit(1); } break; case 'V': valid_from = shift(ARGV); valid_to = shift(ARGV); break; case 'W': legacy = true; break; default: usage(); break; } } let cmd = shift(ARGV); if (!cmd || !cmds[cmd]) usage(); if (subject == null) { warn(`Missing -s option\n`); exit(1); } cmds[cmd](ARGV);