1 {pkgs, lib, config, ...}:
2 let inherit (builtins) toString;
4 inherit (config.services) x509;
5 when = x: y: if x == null then "" else y;
9 options.services.x509 = {
10 enable = lib.mkEnableOption "Generation of X.509 material";
11 scheme = lib.mkOption {
12 type = types.enum [ "manual" "self-signed" "letsencrypt" ];
13 default = "self-signed";
15 Scheme for X.509 material.
18 Use certificate and key manually copied on the target at certFile and keyFile.
20 Generate a self-signed certificate.
22 Generate a certificate signed by Let's Encrypt.
27 default = "/var/x509";
28 description = ''In "self-signed" scheme: directory of the certificate and key.'';
30 certFile = lib.mkOption {
32 example = "/var/x509/cert.pem";
33 description = ''In "manual" scheme: location of the certificate'';
35 keyFile = lib.mkOption {
37 example = "/var/x509/key.pem";
38 description = ''In "manual" scheme: location of the key file.'';
43 description = ''Bit size of Diffie–Hellman parameters.'';
46 type = types.nullOr types.string;
47 default = config.networking.domain;
49 distributionHost = lib.mkOption {
51 default = config.networking.domain;
53 domains = lib.mkOption {
54 type = with types; listOf string;
55 example = [ "example.coop" ];
62 keySize = lib.mkOption {
69 if x509.scheme == "manual"
71 else if x509.scheme == "self-signed"
72 then "${x509.dir}/${x509.host}.cert.self-signed.pem"
73 else if x509.scheme == "letsencrypt"
74 then "/var/lib/acme/${x509.host}/fullchain.pem"
75 else throw ''Error: Certificate Scheme must be in [ "manual" "self-signed" "letsencrypt" ]'';
80 if x509.scheme == "manual"
82 else if x509.scheme == "self-signed"
83 then "${x509.dir}/${x509.host}.key.pem"
84 else if x509.scheme == "letsencrypt"
85 then "/var/lib/acme/${x509.host}/key.pem"
86 else throw ''Error: Certificate Scheme must be in [ "manual" "self-signed" "letsencrypt" ]'';
88 opensslConf = lib.mkOption {
89 type = (with types; attrsOf string);
90 default = x509.opensslConf_common //
91 x509.opensslConf_self-signed //
92 x509.opensslConf_auth-signed //
93 x509.opensslConf_user-cert;
95 pkgs.writeText "openssl.conf"
99 (if title == "" then "" else "[ ${title} ]\n") +
103 opensslConf_common = lib.mkOption {
104 type = (with types; attrsOf string);
107 RANDFILE = /var/x509/openssl.rand
108 oid_section = extra_oids
111 # NOTE: only useful for Extended Validation (EV)
112 jurisdictionOfIncorporationLocalityName = 1.3.6.1.4.1.311.60.2.1.1
113 jurisdictionOfIncorporationStateOrProvinceName = 1.3.6.1.4.1.311.60.2.1.2
114 jurisdictionOfIncorporationCountryName = 1.3.6.1.4.1.311.60.2.1.3
118 distinguished_name = distinguished_name
120 #x509_extensions = root_extensions
121 #req_extensions = extension
122 #attributes = req_attributes
124 distinguished_name = ''
125 commonName = ${x509.host}
127 #stateOrProvinceName =
129 #"0.organizationName" =
130 #organizationalUnitName =
132 #jurisdictionOfIncorporationLocalityName = $stateOrProvinceName
133 #jurisdictionOfIncorporationStateOrProvinceName = $stateOrProvinceName
134 #jurisdictionOfIncorporationCountryName = $countryName
138 opensslConf_self-signed = lib.mkOption {
139 type = (with types; attrsOf string);
141 self_signed_extensions = ''
142 basicConstraints = critical,CA:TRUE,pathlen:0
143 keyUsage = keyCertSign,cRLSign,digitalSignature,keyEncipherment
144 subjectAltName = ${lib.concatMapStringsSep "," (dom: "DNS:${dom}") x509.domains}
145 subjectKeyIdentifier = hash
146 issuerAltName = issuer:copy
147 authorityKeyIdentifier = keyid:always,issuer:always
148 authorityInfoAccess = caIssuers;URI:http://${x509.distributionHost}/x509/${x509.host}.cert.pem
149 crlDistributionPoints = URI:http://${x509.distributionHost}/x509/${x509.host}.crl.self-signed.pem
152 dir = ${x509.dir}/pub
153 private_key = $dir/sec/key.pem
155 crlnumber = $dir/crl.self-signed.num
156 crl = $dir/crl.self-signed.pem
157 database = $dir/idx.self-signed.txt
161 opensslConf_auth-signed = lib.mkOption {
162 type = (with types; attrsOf string);
164 auth_signed_extensions = ''
165 basicConstraints = critical,CA:FALSE
166 keyUsage = digitalSignature,keyEncipherment
167 subjectAltName = ${lib.concatMapStringsSep "," (dom: "DNS:${dom}") x509.domains}
168 subjectKeyIdentifier = hash
169 issuerAltName = issuer:copy
170 authorityKeyIdentifier = keyid:always,issuer:always
171 authorityInfoAccess = caIssuers;URI:http://${x509.distributionHost}/x509/${x509.host}.cert.pem
172 crlDistributionPoints = URI:http://${x509.distributionHost}/x509/${x509.host}.crl.pem
173 certificatePolicies = @certificate_policies
175 certificate_policies = ''
176 policyIdentifier = 1.2.250.1.42
177 CPS.1 = https://${x509.host}/x509/cps
180 dir = ${x509.dir}/pub
181 private_key = $dir/sec/key.pem
183 crlnumber = $dir/crl.num
185 database = $dir/idx.txt
189 opensslConf_user-cert = lib.mkOption {
190 type = (with types; attrsOf string);
192 user_cert_extensions = ''
193 basicConstraints = critical,CA:FALSE,pathlen:0
194 keyUsage = digitalSignature,keyEncipherment
195 subjectAltName = email:$ENV::user@${x509.host}
196 subjectKeyIdentifier = hash
197 issuerAltName = issuer:copy
198 authorityKeyIdentifier = keyid:always,issuer:always
199 authorityInfoAccess = caIssuers;URI:http://${x509.distributionHost}/x509/${x509.host}.cert.pem
206 systemd.services.x509 = {
208 wantedBy = [ "multi-user.target" ];
209 before = [ "keys.target" ];
211 ExecStart = pkgs.writeShellScriptBin "x509.service" (
212 lib.optionalString (x509.scheme == "self-signed") ''
213 # Generate DH parameters
214 { ${pkgs.openssl}/bin/openssl dhparam -in ${x509.dir}/dh.pem -text |
216 ${pkgs.gnugrep}/bin/grep >/dev/null "(${toString x509.keySize} bit)"
219 ${pkgs.openssl}/bin/openssl dhparam \
220 ${toString x509.dh} \
224 [ -s "${x509.key}" ] || {
228 "${pkgs.openssl}/bin/openssl" genrsa \
231 ${toString x509.keySize}
234 # Make self-signed certificate
235 [ -s "${x509.cert}" ] &&
236 [ "${x509.cert}" -nt "${x509.key}" ] || {
238 ${pkgs.openssl}/bin/openssl req \
244 -config ${x509.opensslConf} \
245 -extensions self_signed_extensions \
246 -reqexts self_signed_extensions \
250 ${when x509.days "-days ${toString x509.days}"} \
251 -set_serial 0x$(sleep 1; date '+%Y%m%d%H%M%S') \
252 -reqopt no_pubkey,no_sigdump \
256 '') + "/bin/x509.service";