]> Git — Sourcephile - sourcephile-nix.git/blob - shell/modules/tools/security/openssl.nix
mermet: add dovecot
[sourcephile-nix.git] / shell / modules / tools / security / openssl.nix
1 { pkgs, lib, config, ... }:
2 let
3 inherit (builtins) toString;
4 inherit (pkgs.lib) types unlines;
5 inherit (config) openssl;
6
7 generateCerts = certs: unlines (lib.mapAttrsToList generateCert certs);
8 generateKey = id: cert: ''
9 mkdir -p "${openssl.opensslHome}/${id}"
10 test -s "$PASSWORD_STORE_DIR/${cert.passPrefix}/${id}/key.pem.gpg" || {
11 info " generateKey: $PASSWORD_STORE_DIR/${cert.passPrefix}/${id}/key.pem.gpg"
12 ${pkgs.openssl}/bin/openssl genrsa \
13 -out stdout \
14 -rand /dev/urandom \
15 ${toString cert.keySize} |
16 ${pkgs.pass}/bin/pass insert --multiline "${cert.passPrefix}/${id}/key.pem"
17 }
18 '';
19 generateCert = id: cert:
20 ''
21 info '"${id}"'
22 ''
23 + generateKey id cert
24 + ''
25 genCert () {
26 info " generateCert: ${openssl.opensslHome}/${id}/cert.self-signed.pem"
27 ${pkgs.pass}/bin/pass "${cert.passPrefix}/${id}/key.pem" |
28 user= \
29 ${pkgs.openssl}/bin/openssl req \
30 -batch \
31 -new \
32 -x509 \
33 -utf8 \
34 -rand /dev/urandom \
35 -config ${cert.opensslConf} \
36 -extensions self_signed_extensions \
37 -reqexts self_signed_extensions \
38 -inform PEM \
39 -outform PEM \
40 -keyform PEM \
41 ${lib.optionalString (cert.days != null) "-days ${toString cert.days}"} \
42 -set_serial 0x$(sleep 1; date '+%Y%m%d%H%M%S') \
43 -reqopt no_pubkey,no_sigdump \
44 -key /dev/stdin \
45 -out "${openssl.opensslHome}/${id}/cert.self-signed.pem"
46 }
47 if test ! -s "${openssl.opensslHome}/${id}/cert.self-signed.pem"
48 then genCert
49 else
50 if test ! "${openssl.opensslHome}/${id}/cert.self-signed.pem" -nt \
51 "$PASSWORD_STORE_DIR/${cert.passPrefix}/${id}/key.pem.gpg"
52 then genCert
53 else
54 test ! "''${DEEPCHECK:+set}" || {
55 info " generateCert: checking wether key and certificate match"
56 key_mod_pub="$(
57 ${pkgs.pass}/bin/pass "${cert.passPrefix}/${id}/key.pem" |
58 ${pkgs.openssl}/bin/openssl rsa -modulus -noout -in /dev/stdin)"
59 crt_mod_pub="$(
60 ${pkgs.openssl}/bin/openssl x509 -noout -modulus \
61 -in "${openssl.opensslHome}/${id}/cert.self-signed.pem")"
62 test "$key_mod_pub" = "$crt_mod_pub" ||
63 error "key and certificate do not match"
64 }
65 fi
66 fi
67 '';
68 error = ''
69 error(){
70 echo >&2 "openssl-init: ERROR: $*"
71 exit 1
72 }
73 '';
74 info = ''
75 info(){
76 echo >&2 "openssl-init: $*"
77 }
78 '';
79 # Initialize the keyring according to openssl.certificates.
80 openssl-init = pkgs.writeShellScriptBin "openssl-init" (''
81 set -eu
82 set -o pipefail
83 ${info}
84 '' +
85 generateCerts openssl.certificates
86 );
87 openssl-cert-iter = pkgs.writeScriptBin "openssl-cert-iter" ''
88 set -eu
89 # SYNTAX: $command .. <cert.pem
90
91 begin="-----BEGIN CERTIFICATE-----"
92 end="-----END CERTIFICATE-----"
93
94 buf=
95 ${pkgs.gnused}/bin/sed -n -e "/^$begin/,/^$end/p" |
96 while IFS= read -r line
97 do
98 case $line in
99 ("$begin") buf=;;
100 ("$end") printf '%s\n%s\n%s\n' "$begin" "$(printf %s "$buf" | ${pkgs.gnused}/bin/sed -e 's/\(.\{64\}\)/\1\n/g')" "$end" | "$@";;
101 (*) buf="$buf$line";;
102 esac
103 done
104 '';
105 openssl-cert-print = pkgs.writeScriptBin "openssl-cert-print" ''
106 set -eu
107 cat -- "$@" |
108 ${openssl-cert-iter}/bin/openssl-cert-iter \
109 ${pkgs.openssl}/bin/openssl x509 \
110 -nameopt multiline,-esc_msb,utf8 \
111 -certopt no_pubkey,no_sigdump \
112 -fingerprint \
113 ''${x509_md:+-"$x509_md"} \
114 -in "/dev/stdin" \
115 -noout \
116 -text \
117 ''${OPENSSL_FLAGS-} ''${OPENSSL_X509_FLAGS-}
118 '';
119 openssl-cert-fetch = pkgs.writeScriptBin "openssl-cert-fetch" ''
120 set -eu
121 # SYNTAX: $url
122 x509="''${x509:-x509}"
123 url="$1"
124 host=$(
125 printf %s "$url" |
126 ${pkgs.gnused}/bin/sed \
127 -e 's,^[a-z][a-z]*://,,' \
128 -e 's,/.*$,,')
129 servername=$(
130 printf %s "$host" |
131 ${pkgs.gnused}/bin/sed \
132 -e 's,:[0-9][0-9]*,,')
133 mkdir -p "x509/$host"
134 begin="-----BEGIN CERTIFICATE-----"
135 end="-----END CERTIFICATE-----"
136 echo |
137 "$tool"/openssl s_client \
138 -showcerts \
139 -servername "$servername" \
140 -connect "$host" \
141 "$@" |
142 ${pkgs.gnused}/bin/sed -n \
143 -e "/^$begin/,/^$end/p" \
144 >"$x509/$host/crt.pem"
145 '';
146 in
147 {
148 options.openssl = {
149 enable = lib.mkEnableOption "Configuration of X.509 certificates";
150 opensslHome = lib.mkOption {
151 type = types.path;
152 default = "sec/openssl";
153 description = ''
154 OpenSSL's directory.
155 '';
156 };
157 certificates = lib.mkOption {
158 default = {};
159 example =
160 { "example" = rec {
161 passPrefix = "x509";
162 host = "example.coop";
163 distributionPoint = "http://example.coop/x509";
164 domains = [ "example.org" ];
165 days = 365;
166 keySize = 4096;
167 digest = "sha512";
168 };
169 };
170 type = types.attrsOf (types.submodule ({name, ...}:
171 let cert = openssl.certificates."${name}"; in
172 {
173 #config.uid = lib.mkDefault uid;
174 options = {
175 host = lib.mkOption {
176 type = types.nullOr types.str;
177 example = "example.coop";
178 };
179 passPrefix = lib.mkOption {
180 type = types.str;
181 example = "x509";
182 default = "x509";
183 };
184 distributionPoint = lib.mkOption {
185 type = types.nullOr types.str;
186 default = null;
187 example = "http://example.coop/x509";
188 };
189 domains = lib.mkOption {
190 type = types.listOf types.str;
191 example = [ "example.coop" ];
192 default = [];
193 };
194 days = lib.mkOption {
195 type = types.int;
196 default = 3650;
197 example = 3650;
198 };
199 keySize = lib.mkOption {
200 type = types.int;
201 default = 4096;
202 example = 4096;
203 };
204 digest = lib.mkOption {
205 type = types.str;
206 default = "sha512";
207 example = "sha512";
208 description = "A digest from openssl list --digest-commands";
209 };
210 opensslConf = lib.mkOption {
211 type = types.attrsOf types.lines;
212 default = {};
213 apply = certConf:
214 let confBySection =
215 cert.opensslConf_common //
216 cert.opensslConf_self-signed //
217 cert.opensslConf_auth-signed //
218 cert.opensslConf_user-cert //
219 certConf
220 ; in
221 pkgs.writeText "openssl.conf"
222 (lib.concatStrings
223 (lib.mapAttrsToList
224 (sectionHead: sectionBody:
225 (if sectionHead == "" then "" else "[ ${sectionHead} ]\n") +
226 sectionBody)
227 confBySection));
228 };
229 opensslConf_common = lib.mkOption {
230 type = types.attrsOf types.lines;
231 default = {
232 "" = ''
233 RANDFILE = ${openssl.opensslHome}/openssl.rand
234 oid_section = extra_oids
235 default_md = ${cert.digest}
236 '';
237 extra_oids = ''
238 '';
239 req = ''
240 prompt = no
241 distinguished_name = distinguished_name
242 string_mask = pkix
243 #x509_extensions = root_extensions
244 #req_extensions = extension
245 #attributes = req_attributes
246 '';
247 distinguished_name = ''
248 commonName = ${cert.host}
249 #countryName =
250 #stateOrProvinceName =
251 #localityName =
252 #"0.organizationName" =
253 #organizationalUnitName =
254 #businessCategory =
255 '';
256 };
257 };
258 opensslConf_self-signed = lib.mkOption {
259 type = types.attrsOf types.lines;
260 default = {
261 self_signed_extensions = ''
262 basicConstraints = critical,CA:TRUE,pathlen:0
263 keyUsage = keyCertSign,cRLSign,digitalSignature,keyEncipherment
264 subjectAltName = ${lib.concatMapStringsSep "," (dom: "DNS:${dom}") cert.domains}
265 subjectKeyIdentifier = hash
266 issuerAltName = issuer:copy
267 authorityKeyIdentifier = keyid:always,issuer:always
268 '' + lib.optionalString (cert.distributionPoint != null) ''
269 authorityInfoAccess = caIssuers;URI:${cert.distributionPoint}/${cert.host}.cert.pem
270 crlDistributionPoints = URI:${cert.distributionPoint}/${cert.host}.crl.self-signed.pem
271 '';
272 /*
273 self_signed_ca = ''
274 dir = ${openssl.opensslHome}/${name}
275 private_key = $dir/key.pem
276 crl_dir = $dir
277 crlnumber = $dir/crl.self-signed.num
278 crl = $dir/crl.self-signed.pem
279 database = $dir/idx.self-signed.txt
280 '';
281 */
282 };
283 };
284 opensslConf_auth-signed = lib.mkOption {
285 type = types.attrsOf types.lines;
286 default = {
287 auth_signed_extensions = ''
288 basicConstraints = critical,CA:FALSE
289 keyUsage = digitalSignature,keyEncipherment
290 subjectAltName = ${lib.concatMapStringsSep "," (dom: "DNS:${dom}") cert.domains}
291 subjectKeyIdentifier = hash
292 issuerAltName = issuer:copy
293 authorityKeyIdentifier = keyid:always,issuer:always
294 certificatePolicies = @certificate_policies
295 '' + lib.optionalString (cert.distributionPoint != null) ''
296 authorityInfoAccess = caIssuers;URI:${cert.distributionPoint}/${cert.host}.cert.pem
297 crlDistributionPoints = URI:${cert.distributionPoint}/${cert.host}.crl.pem
298 '';
299 certificate_policies = lib.optionalString (cert.distributionPoint != null) ''
300 policyIdentifier = 1.2.250.1.42
301 CPS.1 = https://${cert.distributionPoint}/cps
302 '';
303 /*
304 ca = ''
305 dir = ${openssl.opensslHome}/${name}
306 private_key = $dir/key.pem
307 crl_dir = $dir
308 crlnumber = $dir/crl.num
309 crl = $dir/crl.pem
310 database = $dir/idx.txt
311 '';
312 */
313 };
314 };
315 opensslConf_user-cert = lib.mkOption {
316 type = types.attrsOf types.lines;
317 default = {
318 user_cert_extensions = ''
319 basicConstraints = critical,CA:FALSE,pathlen:0
320 keyUsage = digitalSignature,keyEncipherment
321 subjectAltName = email:$ENV::user@${cert.host}
322 subjectKeyIdentifier = hash
323 issuerAltName = issuer:copy
324 authorityKeyIdentifier = keyid:always,issuer:always
325 '' + lib.optionalString (cert.distributionPoint != null) ''
326 authorityInfoAccess = caIssuers;URI:${cert.distributionPoint}/${cert.host}.cert.pem
327 '';
328 };
329 };
330
331 };
332 }));
333 };
334 };
335
336 config = lib.mkIf openssl.enable {
337 nix-shell.buildInputs = [
338 openssl-cert-iter
339 openssl-cert-print
340 openssl-cert-fetch
341 openssl-init
342 ];
343 };
344 }