1#! /bin/bash
2#
3# Copyright 2016-2021 The OpenSSL Project Authors. All Rights Reserved.
4# Copyright (c) 2016 Viktor Dukhovni <openssl-users@dukhovni.org>.
5# All rights reserved.
6#
7# Licensed under the Apache License 2.0 (the "License").  You may not use
8# this file except in compliance with the License.  You can obtain a copy
9# in the file LICENSE in the source distribution or at
10# https://www.openssl.org/source/license.html
11
12# This file is dual-licensed and is also available under other terms.
13# Please contact the author.
14
15# 100 years should be enough for now
16if [ -z "$DAYS" ]; then
17    DAYS=36525
18fi
19
20if [ -z "$OPENSSL_SIGALG" ]; then
21    OPENSSL_SIGALG=sha256
22fi
23
24if [ -z "$REQMASK" ]; then
25    REQMASK=utf8only
26fi
27
28stderr_onerror() {
29    (
30        err=$("$@" >&3 2>&1) || {
31            printf "%s\n" "$err" >&2
32            exit 1
33        }
34    ) 3>&1
35}
36
37key() {
38    local key=$1; shift
39
40    local alg=rsa
41    if [ -n "$OPENSSL_KEYALG" ]; then
42        alg=$OPENSSL_KEYALG
43    fi
44
45    local bits=2048
46    if [ -n "$OPENSSL_KEYBITS" ]; then
47        bits=$OPENSSL_KEYBITS
48    fi
49
50    if [ ! -f "${key}.pem" ]; then
51        args=(-algorithm "$alg")
52        case $alg in
53        rsa) args=("${args[@]}" -pkeyopt rsa_keygen_bits:$bits );;
54        ec)  args=("${args[@]}" -pkeyopt "ec_paramgen_curve:$bits")
55               args=("${args[@]}" -pkeyopt ec_param_enc:named_curve);;
56        dsa)  args=(-paramfile "$bits");;
57        ed25519)  ;;
58        ed448)  ;;
59        *) printf "Unsupported key algorithm: %s\n" "$alg" >&2; return 1;;
60        esac
61        stderr_onerror \
62            openssl genpkey "${args[@]}" -out "${key}.pem"
63    fi
64}
65
66# Usage: $0 req keyname dn1 dn2 ...
67req() {
68    local key=$1; shift
69
70    key "$key"
71    local errs
72
73    stderr_onerror \
74        openssl req -new -"${OPENSSL_SIGALG}" -key "${key}.pem" \
75            -config <(printf "string_mask=%s\n[req]\n%s\n%s\n[dn]\n" \
76              "$REQMASK" "prompt = no" "distinguished_name = dn"
77                      for dn in "$@"; do echo "$dn"; done)
78}
79
80req_nocn() {
81    local key=$1; shift
82
83    key "$key"
84    stderr_onerror \
85        openssl req -new -"${OPENSSL_SIGALG}" -subj / -key "${key}.pem" \
86            -config <(printf "[req]\n%s\n[dn]\nCN_default =\n" \
87		      "distinguished_name = dn")
88}
89
90cert() {
91    local cert=$1; shift
92    local exts=$1; shift
93
94    stderr_onerror \
95        openssl x509 -req -"${OPENSSL_SIGALG}" -out "${cert}.pem" \
96            -extfile <(printf "%s\n" "$exts") "$@"
97}
98
99genroot() {
100    local cn=$1; shift
101    local key=$1; shift
102    local cert=$1; shift
103    local bcon="basicConstraints = critical,CA:true"
104    local ku="keyUsage = keyCertSign,cRLSign"
105    local skid="subjectKeyIdentifier = hash"
106    local akid="authorityKeyIdentifier = keyid"
107
108    exts=$(printf "%s\n%s\n%s\n" "$bcon" "$ku" "$skid" "$akid")
109    for eku in "$@"
110    do
111        exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku")
112    done
113    csr=$(req "$key" "CN = $cn") || return 1
114    echo "$csr" |
115       cert "$cert" "$exts" -signkey "${key}.pem" -set_serial 1 -days "${DAYS}"
116}
117
118genca() {
119    local OPTIND=1
120    local purpose=
121
122    while getopts p: o
123    do
124        case $o in
125        p) purpose="$OPTARG";;
126        *) echo "Usage: $0 genca [-p EKU] cn keyname certname cakeyname cacertname" >&2
127           return 1;;
128        esac
129    done
130
131    shift $((OPTIND - 1))
132    local cn=$1; shift
133    local key=$1; shift
134    local cert=$1; shift
135    local cakey=$1; shift
136    local cacert=$1; shift
137    local bcon="basicConstraints = critical,CA:true"
138    local ku="keyUsage = keyCertSign,cRLSign"
139    local skid="subjectKeyIdentifier = hash"
140    local akid="authorityKeyIdentifier = keyid"
141
142    exts=$(printf "%s\n%s\n%s\n" "$bcon" "$ku" "$skid" "$akid")
143    if [ -n "$purpose" ]; then
144        exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$purpose")
145    fi
146    if [ -n "$NC" ]; then
147        exts=$(printf "%s\nnameConstraints = %s\n" "$exts" "$NC")
148    fi
149    csr=$(req "$key" "CN = $cn") || return 1
150    echo "$csr" |
151        cert "$cert" "$exts" -CA "${cacert}.pem" -CAkey "${cakey}.pem" \
152	    -set_serial 2 -days "${DAYS}" "$@"
153}
154
155gen_nonbc_ca() {
156    local cn=$1; shift
157    local key=$1; shift
158    local cert=$1; shift
159    local cakey=$1; shift
160    local cacert=$1; shift
161    local skid="subjectKeyIdentifier = hash"
162    local akid="authorityKeyIdentifier = keyid"
163
164    exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid")
165    exts=$(printf "%s\nkeyUsage = %s\n" "$exts" "keyCertSign, cRLSign")
166    for eku in "$@"
167    do
168        exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku")
169    done
170    csr=$(req "$key" "CN = $cn") || return 1
171    echo "$csr" |
172        cert "$cert" "$exts" -CA "${cacert}.pem" -CAkey "${cakey}.pem" \
173	    -set_serial 2 -days "${DAYS}"
174}
175
176# Usage: $0 genpc keyname certname eekeyname eecertname pcext1 pcext2 ...
177#
178# Note: takes csr on stdin, so must be used with $0 req like this:
179#
180# $0 req keyname dn | $0 genpc keyname certname eekeyname eecertname pcext ...
181genpc() {
182    local key=$1; shift
183    local cert=$1; shift
184    local cakey=$1; shift
185    local ca=$1; shift
186
187    exts=$(printf "%s\n%s\n%s\n%s\n" \
188	    "subjectKeyIdentifier = hash" \
189	    "authorityKeyIdentifier = keyid, issuer:always" \
190	    "basicConstraints = CA:false" \
191	    "proxyCertInfo = critical, @pcexts";
192           echo "[pcexts]";
193           for x in "$@"; do echo $x; done)
194    cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
195	 -set_serial 2 -days "${DAYS}"
196}
197
198# Usage: $0 geneealt keyname certname cakeyname cacertname alt1 alt2 ...
199#
200# Note: takes csr on stdin, so must be used with $0 req like this:
201#
202# $0 req keyname dn | $0 geneealt keyname certname cakeyname cacertname alt ...
203geneealt() {
204    local key=$1; shift
205    local cert=$1; shift
206    local cakey=$1; shift
207    local ca=$1; shift
208
209    exts=$(printf "%s\n%s\n%s\n%s\n" \
210	    "subjectKeyIdentifier = hash" \
211	    "authorityKeyIdentifier = keyid" \
212	    "basicConstraints = CA:false" \
213	    "subjectAltName = @alts";
214           echo "[alts]";
215           for x in "$@"; do echo $x; done)
216    cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
217	 -set_serial 2 -days "${DAYS}"
218}
219
220genee() {
221    local OPTIND=1
222    local purpose=serverAuth
223
224    while getopts p: o
225    do
226        case $o in
227        p) purpose="$OPTARG";;
228        *) echo "Usage: $0 genee [-p EKU] cn keyname certname cakeyname cacertname" >&2
229           return 1;;
230        esac
231    done
232
233    shift $((OPTIND - 1))
234    local cn=$1; shift
235    local key=$1; shift
236    local cert=$1; shift
237    local cakey=$1; shift
238    local ca=$1; shift
239
240    exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
241	    "subjectKeyIdentifier = hash" \
242	    "authorityKeyIdentifier = keyid, issuer" \
243	    "basicConstraints = CA:false" \
244	    "extendedKeyUsage = $purpose" \
245	    "subjectAltName = @alts" "DNS=${cn}")
246    csr=$(req "$key" "CN = $cn") || return 1
247    echo "$csr" |
248	cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
249	    -set_serial 2 -days "${DAYS}" "$@"
250}
251
252geneeextra() {
253    local OPTIND=1
254    local purpose=serverAuth
255
256    while getopts p: o
257    do
258        case $o in
259        p) purpose="$OPTARG";;
260        *) echo "Usage: $0 geneeextra [-p EKU] cn keyname certname cakeyname cacertname extraext" >&2
261           return 1;;
262        esac
263    done
264
265    shift $((OPTIND - 1))
266    local cn=$1; shift
267    local key=$1; shift
268    local cert=$1; shift
269    local cakey=$1; shift
270    local ca=$1; shift
271    local extraext=$1; shift
272
273    exts=$(printf "%s\n%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
274	    "subjectKeyIdentifier = hash" \
275	    "authorityKeyIdentifier = keyid, issuer" \
276	    "basicConstraints = CA:false" \
277	    "extendedKeyUsage = $purpose" \
278	    "subjectAltName = @alts"\
279	    "$extraext" "DNS=${cn}")
280    csr=$(req "$key" "CN = $cn") || return 1
281    echo "$csr" |
282	cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
283	    -set_serial 2 -days "${DAYS}" "$@"
284}
285
286geneenocsr() {
287    local OPTIND=1
288    local purpose=serverAuth
289
290    while getopts p: o
291    do
292        case $o in
293        p) purpose="$OPTARG";;
294        *) echo "Usage: $0 geneenocsr [-p EKU] cn certname cakeyname cacertname" >&2
295           return 1;;
296        esac
297    done
298
299    shift $((OPTIND - 1))
300    local cn=$1; shift
301    local cert=$1; shift
302    local cakey=$1; shift
303    local ca=$1; shift
304
305    exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
306	    "subjectKeyIdentifier = hash" \
307	    "authorityKeyIdentifier = keyid, issuer" \
308	    "basicConstraints = CA:false" \
309	    "extendedKeyUsage = $purpose" \
310	    "subjectAltName = @alts" "DNS=${cn}")
311	cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
312	    -set_serial 2 -days "${DAYS}" "$@"
313}
314
315genss() {
316    local cn=$1; shift
317    local key=$1; shift
318    local cert=$1; shift
319
320    exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
321	    "subjectKeyIdentifier   = hash" \
322	    "authorityKeyIdentifier = keyid, issuer" \
323	    "basicConstraints = CA:false" \
324	    "extendedKeyUsage = serverAuth" \
325	    "subjectAltName = @alts" "DNS=${cn}")
326    csr=$(req "$key" "CN = $cn") || return 1
327    echo "$csr" |
328        cert "$cert" "$exts" -signkey "${key}.pem" \
329            -set_serial 1 -days "${DAYS}" "$@"
330}
331
332gennocn() {
333    local key=$1; shift
334    local cert=$1; shift
335
336    csr=$(req_nocn "$key") || return 1
337    echo "$csr" |
338	cert "$cert" "" -signkey "${key}.pem" -set_serial 1 -days -1 "$@"
339}
340
341genct() {
342    local OPTIND=1
343    local purpose=serverAuth
344
345    while getopts p: o
346    do
347        case $o in
348        p) purpose="$OPTARG";;
349        *) echo "Usage: $0 genct [-p EKU] cn keyname certname cakeyname cacertname ctlogkey" >&2
350           return 1;;
351        esac
352    done
353
354    shift $((OPTIND - 1))
355    local cn=$1; shift
356    local key=$1; shift
357    local cert=$1; shift
358    local cakey=$1; shift
359    local ca=$1; shift
360    local logkey=$1; shift
361
362    exts=$(printf "%s\n%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \
363	    "subjectKeyIdentifier = hash" \
364	    "authorityKeyIdentifier = keyid, issuer" \
365	    "basicConstraints = CA:false" \
366	    "extendedKeyUsage = $purpose" \
367            "1.3.6.1.4.1.11129.2.4.3 = critical,ASN1:NULL"\
368	    "subjectAltName = @alts" "DNS=${cn}")
369    csr=$(req "$key" "CN = $cn") || return 1
370    echo "$csr" |
371	cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
372	    -set_serial 2 -days "${DAYS}" "$@"
373    cat ${cert}.pem ${ca}.pem > ${cert}-chain.pem
374    go run github.com/google/certificate-transparency-go/ctutil/sctgen \
375       --log_private_key ${logkey}.pem \
376       --timestamp="2020-01-01T00:00:00Z" \
377       --cert_chain ${cert}-chain.pem \
378       --tls_out ${cert}.tlssct
379    rm ${cert}-chain.pem
380    filesize=$(wc -c <${cert}.tlssct)
381    exts=$(printf "%s\n%s\n%s\n%s\n%s%04X%04X%s\n%s\n[alts]\n%s\n" \
382	    "subjectKeyIdentifier = hash" \
383	    "authorityKeyIdentifier = keyid, issuer" \
384	    "basicConstraints = CA:false" \
385	    "extendedKeyUsage = $purpose" \
386	    "1.3.6.1.4.1.11129.2.4.2 = ASN1:FORMAT:HEX,OCT:" $((filesize+2)) $filesize `xxd -p ${cert}.tlssct | tr -d '\n'` \
387	    "subjectAltName = @alts" "DNS=${cn}")
388    echo "$csr" |
389	cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \
390	    -set_serial 2 -days "${DAYS}" "$@"
391}
392
393"$@"
394