{ 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 .. <cert.pem

    begin="-----BEGIN CERTIFICATE-----"
    end="-----END CERTIFICATE-----"

    buf=
    ${pkgs.gnused}/bin/sed -n -e "/^$begin/,/^$end/p" |
    while IFS= read -r line
     do
      case $line in
       ("$begin") buf=;;
       ("$end") printf '%s\n%s\n%s\n' "$begin" "$(printf %s "$buf" | ${pkgs.gnused}/bin/sed -e 's/\(.\{64\}\)/\1\n/g')" "$end" | "$@";;
       (*) buf="$buf$line";;
       esac
     done
  '';
  openssl-cert-print = pkgs.writeScriptBin "openssl-cert-print" ''
    set -eu
    cat -- "$@" |
    ${openssl-cert-iter}/bin/openssl-cert-iter \
     ${pkgs.openssl}/bin/openssl x509 \
      -nameopt multiline,-esc_msb,utf8 \
      -certopt no_pubkey,no_sigdump \
      -fingerprint \
      ''${x509_md:+-"$x509_md"} \
      -in "/dev/stdin" \
      -noout \
      -text \
      ''${OPENSSL_FLAGS-} ''${OPENSSL_X509_FLAGS-}
  '';
  openssl-cert-fetch = pkgs.writeScriptBin "openssl-cert-fetch" ''
    set -eu
    # SYNTAX: $url
    x509="''${x509:-x509}"
    url="$1"
    host=$(
      printf %s "$url" |
      ${pkgs.gnused}/bin/sed \
       -e 's,^[a-z][a-z]*://,,' \
       -e 's,/.*$,,')
    servername=$(
      printf %s "$host" |
      ${pkgs.gnused}/bin/sed \
       -e 's,:[0-9][0-9]*,,')
    mkdir -p "x509/$host"
    begin="-----BEGIN CERTIFICATE-----"
    end="-----END CERTIFICATE-----"
    echo |
    "$tool"/openssl s_client \
     -showcerts \
     -servername "$servername" \
     -connect    "$host" \
     "$@" |
    ${pkgs.gnused}/bin/sed -n \
     -e "/^$begin/,/^$end/p" \
     >"$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
  ];
};
}