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