]> Git — Sourcephile - sourcephile-nix.git/blob - shell/modules/tools/security/openssl.nix
nix: make .envrc more robust to failure
[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"; shift
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 set -x
137 echo |
138 ${pkgs.openssl}/bin/openssl s_client \
139 -showcerts \
140 -servername "$servername" \
141 -connect "$host" \
142 "$@" |
143 ${pkgs.gnused}/bin/sed -n \
144 -e "/^$begin/,/^$end/p" \
145 >"$x509/$host/crt.pem"
146 '';
147 in
148 {
149 options.openssl = {
150 enable = lib.mkEnableOption "Configuration of X.509 certificates";
151 opensslHome = lib.mkOption {
152 type = types.path;
153 default = "sec/openssl";
154 description = ''
155 OpenSSL's directory.
156 '';
157 };
158 certificates = lib.mkOption {
159 default = {};
160 example =
161 { "example" = rec {
162 passPrefix = "x509";
163 host = "example.coop";
164 distributionPoint = "http://example.coop/x509";
165 domains = [ "example.org" ];
166 days = 365;
167 keySize = 4096;
168 digest = "sha512";
169 };
170 };
171 type = types.attrsOf (types.submodule ({name, ...}:
172 let cert = openssl.certificates."${name}"; in
173 {
174 #config.uid = lib.mkDefault uid;
175 options = {
176 host = lib.mkOption {
177 type = types.nullOr types.str;
178 example = "example.coop";
179 };
180 passPrefix = lib.mkOption {
181 type = types.str;
182 example = "x509";
183 default = "x509";
184 };
185 distributionPoint = lib.mkOption {
186 type = types.nullOr types.str;
187 default = null;
188 example = "http://example.coop/x509";
189 };
190 domains = lib.mkOption {
191 type = types.listOf types.str;
192 example = [ "example.coop" ];
193 default = [];
194 };
195 days = lib.mkOption {
196 type = types.int;
197 default = 3650;
198 example = 3650;
199 };
200 keySize = lib.mkOption {
201 type = types.int;
202 default = 4096;
203 example = 4096;
204 };
205 digest = lib.mkOption {
206 type = types.str;
207 default = "sha512";
208 example = "sha512";
209 description = "A digest from openssl list --digest-commands";
210 };
211 opensslConf = lib.mkOption {
212 type = types.attrsOf types.lines;
213 default = {};
214 apply = certConf:
215 let confBySection =
216 cert.opensslConf_common //
217 cert.opensslConf_self-signed //
218 cert.opensslConf_auth-signed //
219 cert.opensslConf_user-cert //
220 certConf
221 ; in
222 pkgs.writeText "openssl.conf"
223 (lib.concatStrings
224 (lib.mapAttrsToList
225 (sectionHead: sectionBody:
226 (if sectionHead == "" then "" else "[ ${sectionHead} ]\n") +
227 sectionBody)
228 confBySection));
229 };
230 opensslConf_common = lib.mkOption {
231 type = types.attrsOf types.lines;
232 default = {
233 "" = ''
234 RANDFILE = ${openssl.opensslHome}/openssl.rand
235 oid_section = extra_oids
236 default_md = ${cert.digest}
237 '';
238 extra_oids = ''
239 '';
240 req = ''
241 prompt = no
242 distinguished_name = distinguished_name
243 string_mask = pkix
244 #x509_extensions = root_extensions
245 #req_extensions = extension
246 #attributes = req_attributes
247 '';
248 distinguished_name = ''
249 commonName = ${cert.host}
250 #countryName =
251 #stateOrProvinceName =
252 #localityName =
253 #"0.organizationName" =
254 #organizationalUnitName =
255 #businessCategory =
256 '';
257 };
258 };
259 opensslConf_self-signed = lib.mkOption {
260 type = types.attrsOf types.lines;
261 default = {
262 self_signed_extensions = ''
263 basicConstraints = critical,CA:TRUE,pathlen:0
264 keyUsage = keyCertSign,cRLSign,digitalSignature,keyEncipherment
265 subjectAltName = ${lib.concatMapStringsSep "," (dom: "DNS:${dom}") cert.domains}
266 subjectKeyIdentifier = hash
267 issuerAltName = issuer:copy
268 authorityKeyIdentifier = keyid:always,issuer:always
269 '' + lib.optionalString (cert.distributionPoint != null) ''
270 authorityInfoAccess = caIssuers;URI:${cert.distributionPoint}/${cert.host}.cert.pem
271 crlDistributionPoints = URI:${cert.distributionPoint}/${cert.host}.crl.self-signed.pem
272 '';
273 /*
274 self_signed_ca = ''
275 dir = ${openssl.opensslHome}/${name}
276 private_key = $dir/key.pem
277 crl_dir = $dir
278 crlnumber = $dir/crl.self-signed.num
279 crl = $dir/crl.self-signed.pem
280 database = $dir/idx.self-signed.txt
281 '';
282 */
283 };
284 };
285 opensslConf_auth-signed = lib.mkOption {
286 type = types.attrsOf types.lines;
287 default = {
288 auth_signed_extensions = ''
289 basicConstraints = critical,CA:FALSE
290 keyUsage = digitalSignature,keyEncipherment
291 subjectAltName = ${lib.concatMapStringsSep "," (dom: "DNS:${dom}") cert.domains}
292 subjectKeyIdentifier = hash
293 issuerAltName = issuer:copy
294 authorityKeyIdentifier = keyid:always,issuer:always
295 certificatePolicies = @certificate_policies
296 '' + lib.optionalString (cert.distributionPoint != null) ''
297 authorityInfoAccess = caIssuers;URI:${cert.distributionPoint}/${cert.host}.cert.pem
298 crlDistributionPoints = URI:${cert.distributionPoint}/${cert.host}.crl.pem
299 '';
300 certificate_policies = lib.optionalString (cert.distributionPoint != null) ''
301 policyIdentifier = 1.2.250.1.42
302 CPS.1 = https://${cert.distributionPoint}/cps
303 '';
304 /*
305 ca = ''
306 dir = ${openssl.opensslHome}/${name}
307 private_key = $dir/key.pem
308 crl_dir = $dir
309 crlnumber = $dir/crl.num
310 crl = $dir/crl.pem
311 database = $dir/idx.txt
312 '';
313 */
314 };
315 };
316 opensslConf_user-cert = lib.mkOption {
317 type = types.attrsOf types.lines;
318 default = {
319 user_cert_extensions = ''
320 basicConstraints = critical,CA:FALSE,pathlen:0
321 keyUsage = digitalSignature,keyEncipherment
322 subjectAltName = email:$ENV::user@${cert.host}
323 subjectKeyIdentifier = hash
324 issuerAltName = issuer:copy
325 authorityKeyIdentifier = keyid:always,issuer:always
326 '' + lib.optionalString (cert.distributionPoint != null) ''
327 authorityInfoAccess = caIssuers;URI:${cert.distributionPoint}/${cert.host}.cert.pem
328 '';
329 };
330 };
331
332 };
333 }));
334 };
335 };
336
337 config = lib.mkIf openssl.enable {
338 nix-shell.buildInputs = [
339 openssl-cert-iter
340 openssl-cert-print
341 openssl-cert-fetch
342 openssl-init
343 ];
344 };
345 }