]> Git — Sourcephile - sourcephile-nix.git/blob - nixos/modules/services/security/x509.nix
nix: revamp the hierarchy
[sourcephile-nix.git] / nixos / modules / services / security / x509.nix
1 {pkgs, lib, config, ...}:
2 let inherit (builtins) toString;
3 inherit (lib) types;
4 inherit (config.services) x509;
5 when = x: y: if x == null then "" else y;
6 in
7 {
8
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";
14 description = ''
15 Scheme for X.509 material.
16
17 manual:
18 Use certificate and key manually copied on the target at certFile and keyFile.
19 self-signed:
20 Generate a self-signed certificate.
21 letsencrypt:
22 Generate a certificate signed by Let's Encrypt.
23 '';
24 };
25 dir = lib.mkOption {
26 type = types.string;
27 default = "/var/x509";
28 description = ''In "self-signed" scheme: directory of the certificate and key.'';
29 };
30 certFile = lib.mkOption {
31 type = types.path;
32 example = "/var/x509/cert.pem";
33 description = ''In "manual" scheme: location of the certificate'';
34 };
35 keyFile = lib.mkOption {
36 type = types.path;
37 example = "/var/x509/key.pem";
38 description = ''In "manual" scheme: location of the key file.'';
39 };
40 dh = lib.mkOption {
41 type = types.int;
42 default = 4096;
43 description = ''Bit size of Diffie–Hellman parameters.'';
44 };
45 host = lib.mkOption {
46 type = types.nullOr types.string;
47 default = config.networking.domain;
48 };
49 distributionHost = lib.mkOption {
50 type = types.string;
51 default = config.networking.domain;
52 };
53 domains = lib.mkOption {
54 type = with types; listOf string;
55 example = [ "example.coop" ];
56 default = [];
57 };
58 days = lib.mkOption {
59 type = types.int;
60 default = 3650;
61 };
62 keySize = lib.mkOption {
63 type = types.int;
64 default = 4096;
65 };
66 cert = lib.mkOption {
67 type = types.string;
68 default =
69 if x509.scheme == "manual"
70 then x509.certFile
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" ]'';
76 };
77 key = lib.mkOption {
78 type = types.string;
79 default =
80 if x509.scheme == "manual"
81 then x509.keyFile
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" ]'';
87 };
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;
94 apply = cnf:
95 pkgs.writeText "openssl.conf"
96 (lib.concatStrings
97 (lib.mapAttrsToList
98 (title: body:
99 (if title == "" then "" else "[ ${title} ]\n") +
100 body)
101 cnf));
102 };
103 opensslConf_common = lib.mkOption {
104 type = (with types; attrsOf string);
105 default = {
106 "" = ''
107 RANDFILE = /var/x509/openssl.rand
108 oid_section = extra_oids
109 '';
110 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
115 '';
116 req = ''
117 prompt = no
118 distinguished_name = distinguished_name
119 string_mask = pkix
120 #x509_extensions = root_extensions
121 #req_extensions = extension
122 #attributes = req_attributes
123 '';
124 distinguished_name = ''
125 commonName = ${x509.host}
126 #countryName =
127 #stateOrProvinceName =
128 #localityName =
129 #"0.organizationName" =
130 #organizationalUnitName =
131 #businessCategory =
132 #jurisdictionOfIncorporationLocalityName = $stateOrProvinceName
133 #jurisdictionOfIncorporationStateOrProvinceName = $stateOrProvinceName
134 #jurisdictionOfIncorporationCountryName = $countryName
135 '';
136 };
137 };
138 opensslConf_self-signed = lib.mkOption {
139 type = (with types; attrsOf string);
140 default = {
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
150 '';
151 self_signed_ca = ''
152 dir = ${x509.dir}/pub
153 private_key = $dir/sec/key.pem
154 crl_dir = $dir
155 crlnumber = $dir/crl.self-signed.num
156 crl = $dir/crl.self-signed.pem
157 database = $dir/idx.self-signed.txt
158 '';
159 };
160 };
161 opensslConf_auth-signed = lib.mkOption {
162 type = (with types; attrsOf string);
163 default = {
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
174 '';
175 certificate_policies = ''
176 policyIdentifier = 1.2.250.1.42
177 CPS.1 = https://${x509.host}/x509/cps
178 '';
179 ca = ''
180 dir = ${x509.dir}/pub
181 private_key = $dir/sec/key.pem
182 crl_dir = $dir
183 crlnumber = $dir/crl.num
184 crl = $dir/crl.pem
185 database = $dir/idx.txt
186 '';
187 };
188 };
189 opensslConf_user-cert = lib.mkOption {
190 type = (with types; attrsOf string);
191 default = {
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
200 '';
201 };
202 };
203 };
204
205 config = {
206 systemd.services.x509 = {
207 enable = true;
208 wantedBy = [ "multi-user.target" ];
209 before = [ "keys.target" ];
210 serviceConfig = {
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 |
215 head -n 1 |
216 ${pkgs.gnugrep}/bin/grep >/dev/null "(${toString x509.keySize} bit)"
217 } || {
218 mkdir -p ${x509.dir}
219 ${pkgs.openssl}/bin/openssl dhparam \
220 ${toString x509.dh} \
221 >${x509.dir}/dh.pem
222 }
223 # Make private key
224 [ -s "${x509.key}" ] || {
225 mkdir -p ${x509.dir}
226 (
227 umask 077
228 "${pkgs.openssl}/bin/openssl" genrsa \
229 -out "${x509.key}" \
230 -rand /dev/urandom \
231 ${toString x509.keySize}
232 )
233 }
234 # Make self-signed certificate
235 [ -s "${x509.cert}" ] &&
236 [ "${x509.cert}" -nt "${x509.key}" ] || {
237 user= \
238 ${pkgs.openssl}/bin/openssl req \
239 -batch \
240 -new \
241 -x509 \
242 -utf8 \
243 -rand /dev/urandom \
244 -config ${x509.opensslConf} \
245 -extensions self_signed_extensions \
246 -reqexts self_signed_extensions \
247 -inform PEM \
248 -outform PEM \
249 -keyform PEM \
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 \
253 -key ${x509.key} \
254 -out ${x509.cert}
255 }
256 '') + "/bin/x509.service";
257 };
258 };
259 };
260
261 }