{ 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 \
       -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
    '';
  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"; shift
    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-----"
    set -x
    echo |
    ${pkgs.openssl}/bin/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.str;
      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
    ];
  };
}