]> Git — Sourcephile - sourcephile-nix.git/blob - shell/modules/tools/security/openssl.nix
nix: reuse julm-nix nixos/default.nix
[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 -rand /dev/urandom \
14 ${toString cert.keySize} |
15 ${pkgs.pass}/bin/pass insert --multiline "${cert.passPrefix}/${id}/key.pem"
16 }
17 '';
18 generateCert = id: cert:
19 ''
20 info '"${id}"'
21 ''
22 + generateKey id cert
23 + ''
24 genCert () {
25 info " generateCert: ${openssl.opensslHome}/${id}/cert.self-signed.pem"
26 ${pkgs.pass}/bin/pass "${cert.passPrefix}/${id}/key.pem" |
27 user= \
28 ${pkgs.openssl}/bin/openssl req \
29 -batch \
30 -new \
31 -x509 \
32 -utf8 \
33 -rand /dev/urandom \
34 -config ${cert.opensslConf} \
35 -extensions self_signed_extensions \
36 -reqexts self_signed_extensions \
37 -inform PEM \
38 -outform PEM \
39 -keyform PEM \
40 ${lib.optionalString (cert.days != null) "-days ${toString cert.days}"} \
41 -set_serial 0x$(sleep 1; date '+%Y%m%d%H%M%S') \
42 -reqopt no_pubkey,no_sigdump \
43 -key /dev/stdin \
44 -out "${openssl.opensslHome}/${id}/cert.self-signed.pem"
45 }
46 if test ! -s "${openssl.opensslHome}/${id}/cert.self-signed.pem"
47 then genCert
48 else
49 if test ! "${openssl.opensslHome}/${id}/cert.self-signed.pem" -nt \
50 "$PASSWORD_STORE_DIR/${cert.passPrefix}/${id}/key.pem.gpg"
51 then genCert
52 else
53 test ! "''${DEEPCHECK:+set}" || {
54 info " generateCert: checking wether key and certificate match"
55 key_mod_pub="$(
56 ${pkgs.pass}/bin/pass "${cert.passPrefix}/${id}/key.pem" |
57 ${pkgs.openssl}/bin/openssl rsa -modulus -noout -in /dev/stdin)"
58 crt_mod_pub="$(
59 ${pkgs.openssl}/bin/openssl x509 -noout -modulus \
60 -in "${openssl.opensslHome}/${id}/cert.self-signed.pem")"
61 test "$key_mod_pub" = "$crt_mod_pub" ||
62 error "key and certificate do not match"
63 }
64 fi
65 fi
66 '';
67 error = ''
68 error(){
69 echo >&2 "openssl-init: ERROR: $*"
70 exit 1
71 }
72 '';
73 info = ''
74 info(){
75 echo >&2 "openssl-init: $*"
76 }
77 '';
78 # Initialize the keyring according to openssl.certificates.
79 openssl-init = pkgs.writeShellScriptBin "openssl-init" (''
80 set -eu
81 set -o pipefail
82 ${info}
83 '' +
84 generateCerts openssl.certificates
85 );
86 openssl-cert-iter = pkgs.writeScriptBin "openssl-cert-iter" ''
87 set -eu
88 # SYNTAX: $command .. <cert.pem
89
90 begin="-----BEGIN CERTIFICATE-----"
91 end="-----END CERTIFICATE-----"
92
93 buf=
94 ${pkgs.gnused}/bin/sed -n -e "/^$begin/,/^$end/p" |
95 while IFS= read -r line
96 do
97 case $line in
98 ("$begin") buf=;;
99 ("$end") printf '%s\n%s\n%s\n' "$begin" "$(printf %s "$buf" | ${pkgs.gnused}/bin/sed -e 's/\(.\{64\}\)/\1\n/g')" "$end" | "$@";;
100 (*) buf="$buf$line";;
101 esac
102 done
103 '';
104 openssl-cert-print = pkgs.writeScriptBin "openssl-cert-print" ''
105 set -eu
106 cat -- "$@" |
107 ${openssl-cert-iter}/bin/openssl-cert-iter \
108 ${pkgs.openssl}/bin/openssl x509 \
109 -nameopt multiline,-esc_msb,utf8 \
110 -certopt no_pubkey,no_sigdump \
111 -fingerprint \
112 ''${x509_md:+-"$x509_md"} \
113 -in "/dev/stdin" \
114 -noout \
115 -text \
116 ''${OPENSSL_FLAGS-} ''${OPENSSL_X509_FLAGS-}
117 '';
118 openssl-cert-fetch = pkgs.writeScriptBin "openssl-cert-fetch" ''
119 set -eu
120 # SYNTAX: $url
121 x509="''${x509:-x509}"
122 url="$1"; shift
123 host=$(
124 printf %s "$url" |
125 ${pkgs.gnused}/bin/sed \
126 -e 's,^[a-z][a-z]*://,,' \
127 -e 's,/.*$,,')
128 servername=$(
129 printf %s "$host" |
130 ${pkgs.gnused}/bin/sed \
131 -e 's,:[0-9][0-9]*,,')
132 mkdir -p "x509/$host"
133 begin="-----BEGIN CERTIFICATE-----"
134 end="-----END CERTIFICATE-----"
135 set -x
136 echo |
137 ${pkgs.openssl}/bin/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.str;
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 }