{ pkgs, lib, config, ... }: let inherit (builtins) toString; inherit (pkgs.lib) types unlines; inherit (config) openssl; generateCerts = certs: unlines (lib.mapAttrsToList generateCert certs); generateKey = id: cert: '' mkdir -p "${openssl.opensslHome}/${id}" test -s "$PASSWORD_STORE_DIR/${cert.passPrefix}/${id}/key.pem.gpg" || { info " generateKey: $PASSWORD_STORE_DIR/${cert.passPrefix}/${id}/key.pem.gpg" ${pkgs.openssl}/bin/openssl genrsa \ -out stdout \ -rand /dev/urandom \ ${toString cert.keySize} | ${pkgs.pass}/bin/pass insert --multiline "${cert.passPrefix}/${id}/key.pem" } ''; generateCert = id: cert: '' info '"${id}"' '' + generateKey id cert + '' genCert () { info " generateCert: ${openssl.opensslHome}/${id}/cert.self-signed.pem" ${pkgs.pass}/bin/pass "${cert.passPrefix}/${id}/key.pem" | user= \ ${pkgs.openssl}/bin/openssl req \ -batch \ -new \ -x509 \ -utf8 \ -rand /dev/urandom \ -config ${cert.opensslConf} \ -extensions self_signed_extensions \ -reqexts self_signed_extensions \ -inform PEM \ -outform PEM \ -keyform PEM \ ${lib.optionalString (cert.days != null) "-days ${toString cert.days}"} \ -set_serial 0x$(sleep 1; date '+%Y%m%d%H%M%S') \ -reqopt no_pubkey,no_sigdump \ -key /dev/stdin \ -out "${openssl.opensslHome}/${id}/cert.self-signed.pem" } if test ! -s "${openssl.opensslHome}/${id}/cert.self-signed.pem" then genCert else if test ! "${openssl.opensslHome}/${id}/cert.self-signed.pem" -nt \ "$PASSWORD_STORE_DIR/${cert.passPrefix}/${id}/key.pem.gpg" then genCert else test ! "''${DEEPCHECK:+set}" || { info " generateCert: checking wether key and certificate match" key_mod_pub="$( ${pkgs.pass}/bin/pass "${cert.passPrefix}/${id}/key.pem" | ${pkgs.openssl}/bin/openssl rsa -modulus -noout -in /dev/stdin)" crt_mod_pub="$( ${pkgs.openssl}/bin/openssl x509 -noout -modulus \ -in "${openssl.opensslHome}/${id}/cert.self-signed.pem")" test "$key_mod_pub" = "$crt_mod_pub" || error "key and certificate do not match" } fi fi ''; error = '' error(){ echo >&2 "openssl-init: ERROR: $*" exit 1 } ''; info = '' info(){ echo >&2 "openssl-init: $*" } ''; # Initialize the keyring according to openssl.certificates. openssl-init = pkgs.writeShellScriptBin "openssl-init" ('' set -eu set -o pipefail ${info} '' + generateCerts openssl.certificates ); openssl-cert-iter = pkgs.writeScriptBin "openssl-cert-iter" '' set -eu # SYNTAX: $command .. "$x509/$host/crt.pem" ''; in { options.openssl = { enable = lib.mkEnableOption "Configuration of X.509 certificates"; opensslHome = lib.mkOption { type = types.path; default = "sec/openssl"; description = '' OpenSSL's directory. ''; }; certificates = lib.mkOption { default = {}; example = { "example" = rec { passPrefix = "x509"; host = "example.coop"; distributionPoint = "http://example.coop/x509"; domains = [ "example.org" ]; days = 365; keySize = 4096; digest = "sha512"; }; }; type = types.attrsOf (types.submodule ({name, ...}: let cert = openssl.certificates."${name}"; in { #config.uid = lib.mkDefault uid; options = { host = lib.mkOption { type = types.nullOr types.str; example = "example.coop"; }; passPrefix = lib.mkOption { type = types.str; example = "x509"; default = "x509"; }; distributionPoint = lib.mkOption { type = types.nullOr types.str; default = null; example = "http://example.coop/x509"; }; domains = lib.mkOption { type = types.listOf types.str; example = [ "example.coop" ]; default = []; }; days = lib.mkOption { type = types.int; default = 3650; example = 3650; }; keySize = lib.mkOption { type = types.int; default = 4096; example = 4096; }; digest = lib.mkOption { type = types.str; default = "sha512"; example = "sha512"; description = "A digest from openssl list --digest-commands"; }; opensslConf = lib.mkOption { type = types.attrsOf types.lines; default = {}; apply = certConf: let confBySection = cert.opensslConf_common // cert.opensslConf_self-signed // cert.opensslConf_auth-signed // cert.opensslConf_user-cert // certConf ; in pkgs.writeText "openssl.conf" (lib.concatStrings (lib.mapAttrsToList (sectionHead: sectionBody: (if sectionHead == "" then "" else "[ ${sectionHead} ]\n") + sectionBody) confBySection)); }; opensslConf_common = lib.mkOption { type = types.attrsOf types.lines; default = { "" = '' RANDFILE = ${openssl.opensslHome}/openssl.rand oid_section = extra_oids default_md = ${cert.digest} ''; extra_oids = '' ''; req = '' prompt = no distinguished_name = distinguished_name string_mask = pkix #x509_extensions = root_extensions #req_extensions = extension #attributes = req_attributes ''; distinguished_name = '' commonName = ${cert.host} #countryName = #stateOrProvinceName = #localityName = #"0.organizationName" = #organizationalUnitName = #businessCategory = ''; }; }; opensslConf_self-signed = lib.mkOption { type = types.attrsOf types.lines; default = { self_signed_extensions = '' basicConstraints = critical,CA:TRUE,pathlen:0 keyUsage = keyCertSign,cRLSign,digitalSignature,keyEncipherment subjectAltName = ${lib.concatMapStringsSep "," (dom: "DNS:${dom}") cert.domains} subjectKeyIdentifier = hash issuerAltName = issuer:copy authorityKeyIdentifier = keyid:always,issuer:always '' + lib.optionalString (cert.distributionPoint != null) '' authorityInfoAccess = caIssuers;URI:${cert.distributionPoint}/${cert.host}.cert.pem crlDistributionPoints = URI:${cert.distributionPoint}/${cert.host}.crl.self-signed.pem ''; /* self_signed_ca = '' dir = ${openssl.opensslHome}/${name} private_key = $dir/key.pem crl_dir = $dir crlnumber = $dir/crl.self-signed.num crl = $dir/crl.self-signed.pem database = $dir/idx.self-signed.txt ''; */ }; }; opensslConf_auth-signed = lib.mkOption { type = types.attrsOf types.lines; default = { auth_signed_extensions = '' basicConstraints = critical,CA:FALSE keyUsage = digitalSignature,keyEncipherment subjectAltName = ${lib.concatMapStringsSep "," (dom: "DNS:${dom}") cert.domains} subjectKeyIdentifier = hash issuerAltName = issuer:copy authorityKeyIdentifier = keyid:always,issuer:always certificatePolicies = @certificate_policies '' + lib.optionalString (cert.distributionPoint != null) '' authorityInfoAccess = caIssuers;URI:${cert.distributionPoint}/${cert.host}.cert.pem crlDistributionPoints = URI:${cert.distributionPoint}/${cert.host}.crl.pem ''; certificate_policies = lib.optionalString (cert.distributionPoint != null) '' policyIdentifier = 1.2.250.1.42 CPS.1 = https://${cert.distributionPoint}/cps ''; /* ca = '' dir = ${openssl.opensslHome}/${name} private_key = $dir/key.pem crl_dir = $dir crlnumber = $dir/crl.num crl = $dir/crl.pem database = $dir/idx.txt ''; */ }; }; opensslConf_user-cert = lib.mkOption { type = types.attrsOf types.lines; default = { user_cert_extensions = '' basicConstraints = critical,CA:FALSE,pathlen:0 keyUsage = digitalSignature,keyEncipherment subjectAltName = email:$ENV::user@${cert.host} subjectKeyIdentifier = hash issuerAltName = issuer:copy authorityKeyIdentifier = keyid:always,issuer:always '' + lib.optionalString (cert.distributionPoint != null) '' authorityInfoAccess = caIssuers;URI:${cert.distributionPoint}/${cert.host}.cert.pem ''; }; }; }; })); }; }; config = lib.mkIf openssl.enable { nix-shell.buildInputs = [ openssl-cert-iter openssl-cert-print openssl-cert-fetch openssl-init ]; }; }