1 {pkgs, lib, config, ...}:
2 let inherit (builtins) toString;
4 inherit (config.services) x509;
7 options.services.x509 = {
8 enable = lib.mkEnableOption "Configuration of one X.509 certificate";
9 scheme = lib.mkOption {
10 type = types.enum [ "manual" "self-signed" "letsencrypt" ];
11 default = "self-signed";
13 Scheme for X.509 material.
16 Use certificate and key manually copied on the target at certFile and keyFile.
18 Generate a self-signed certificate.
20 Generate a certificate signed by Let's Encrypt.
25 default = "/var/x509";
26 description = ''In "self-signed" scheme: directory of the certificate and key.'';
28 certFile = lib.mkOption {
30 example = "/var/x509/cert.pem";
31 description = ''In "manual" scheme: location of the certificate'';
33 keyFile = lib.mkOption {
35 example = "/var/x509/key.pem";
36 description = ''In "manual" scheme: location of the key file.'';
39 type = types.nullOr types.string;
40 default = config.networking.domain;
42 distributionHost = lib.mkOption {
44 default = config.networking.domain;
46 domains = lib.mkOption {
47 type = with types; listOf string;
48 example = [ "example.coop" ];
55 keySize = lib.mkOption {
62 if x509.scheme == "manual"
64 else if x509.scheme == "self-signed"
65 then "${x509.dir}/${x509.host}.cert.self-signed.pem"
66 else if x509.scheme == "letsencrypt"
67 then "/var/lib/acme/${x509.host}/fullchain.pem"
68 else throw ''Error: Certificate Scheme must be in [ "manual" "self-signed" "letsencrypt" ]'';
73 if x509.scheme == "manual"
75 else if x509.scheme == "self-signed"
76 then "${x509.dir}/${x509.host}.key.pem"
77 else if x509.scheme == "letsencrypt"
78 then "/var/lib/acme/${x509.host}/key.pem"
79 else throw ''Error: Certificate Scheme must be in [ "manual" "self-signed" "letsencrypt" ]'';
81 opensslConf = lib.mkOption {
82 type = (with types; attrsOf string);
83 default = x509.opensslConf_common //
84 x509.opensslConf_self-signed //
85 x509.opensslConf_auth-signed //
86 x509.opensslConf_user-cert;
88 pkgs.writeText "openssl.conf"
92 (if title == "" then "" else "[ ${title} ]\n") +
96 opensslConf_common = lib.mkOption {
97 type = (with types; attrsOf string);
100 RANDFILE = /var/x509/openssl.rand
101 oid_section = extra_oids
104 # NOTE: only useful for Extended Validation (EV)
105 jurisdictionOfIncorporationLocalityName = 1.3.6.1.4.1.311.60.2.1.1
106 jurisdictionOfIncorporationStateOrProvinceName = 1.3.6.1.4.1.311.60.2.1.2
107 jurisdictionOfIncorporationCountryName = 1.3.6.1.4.1.311.60.2.1.3
111 distinguished_name = distinguished_name
113 #x509_extensions = root_extensions
114 #req_extensions = extension
115 #attributes = req_attributes
117 distinguished_name = ''
118 commonName = ${x509.host}
120 #stateOrProvinceName =
122 #"0.organizationName" =
123 #organizationalUnitName =
125 #jurisdictionOfIncorporationLocalityName = $stateOrProvinceName
126 #jurisdictionOfIncorporationStateOrProvinceName = $stateOrProvinceName
127 #jurisdictionOfIncorporationCountryName = $countryName
131 opensslConf_self-signed = lib.mkOption {
132 type = (with types; attrsOf string);
134 self_signed_extensions = ''
135 basicConstraints = critical,CA:TRUE,pathlen:0
136 keyUsage = keyCertSign,cRLSign,digitalSignature,keyEncipherment
137 subjectAltName = ${lib.concatMapStringsSep "," (dom: "DNS:${dom}") x509.domains}
138 subjectKeyIdentifier = hash
139 issuerAltName = issuer:copy
140 authorityKeyIdentifier = keyid:always,issuer:always
141 authorityInfoAccess = caIssuers;URI:http://${x509.distributionHost}/x509/${x509.host}.cert.pem
142 crlDistributionPoints = URI:http://${x509.distributionHost}/x509/${x509.host}.crl.self-signed.pem
145 dir = ${x509.dir}/pub
146 private_key = $dir/sec/key.pem
148 crlnumber = $dir/crl.self-signed.num
149 crl = $dir/crl.self-signed.pem
150 database = $dir/idx.self-signed.txt
154 opensslConf_auth-signed = lib.mkOption {
155 type = (with types; attrsOf string);
157 auth_signed_extensions = ''
158 basicConstraints = critical,CA:FALSE
159 keyUsage = digitalSignature,keyEncipherment
160 subjectAltName = ${lib.concatMapStringsSep "," (dom: "DNS:${dom}") x509.domains}
161 subjectKeyIdentifier = hash
162 issuerAltName = issuer:copy
163 authorityKeyIdentifier = keyid:always,issuer:always
164 authorityInfoAccess = caIssuers;URI:http://${x509.distributionHost}/x509/${x509.host}.cert.pem
165 crlDistributionPoints = URI:http://${x509.distributionHost}/x509/${x509.host}.crl.pem
166 certificatePolicies = @certificate_policies
168 certificate_policies = ''
169 policyIdentifier = 1.2.250.1.42
170 CPS.1 = https://${x509.host}/x509/cps
173 dir = ${x509.dir}/pub
174 private_key = $dir/sec/key.pem
176 crlnumber = $dir/crl.num
178 database = $dir/idx.txt
182 opensslConf_user-cert = lib.mkOption {
183 type = (with types; attrsOf string);
185 user_cert_extensions = ''
186 basicConstraints = critical,CA:FALSE,pathlen:0
187 keyUsage = digitalSignature,keyEncipherment
188 subjectAltName = email:$ENV::user@${x509.host}
189 subjectKeyIdentifier = hash
190 issuerAltName = issuer:copy
191 authorityKeyIdentifier = keyid:always,issuer:always
192 authorityInfoAccess = caIssuers;URI:http://${x509.distributionHost}/x509/${x509.host}.cert.pem
198 config = lib.mkIf (x509.scheme == "self-signed") {
199 systemd.services.x509 = {
201 wantedBy = [ "multi-user.target" ];
202 before = [ "keys.target" ];
204 ExecStart = pkgs.writeShellScript "x509.service" ''
206 [ -s "${x509.key}" ] || {
210 ${pkgs.openssl}/bin/openssl genrsa \
213 ${toString x509.keySize}
216 # Make self-signed certificate
217 [ -s "${x509.cert}" ] &&
218 [ "${x509.cert}" -nt "${x509.key}" ] || {
220 ${pkgs.openssl}/bin/openssl req \
226 -config ${x509.opensslConf} \
227 -extensions self_signed_extensions \
228 -reqexts self_signed_extensions \
232 ${lib.optionalString (x509.days != null) "-days ${toString x509.days}"} \
233 -set_serial 0x$(sleep 1; date '+%Y%m%d%H%M%S') \
234 -reqopt no_pubkey,no_sigdump \