1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
|
#!/bin/bash
# OpenSLX SSL Certificate management
mkdir -p "/run/openslx"
if ! mkdir "/run/openslx/cert-manager" 2> /dev/null; then
echo "Already in progress."
exit 1
fi
trap 'rm -rf -- /run/openslx/cert-manager' EXIT
declare -rg BASE="/etc/ssl/openslx"
declare -rg PRIVDIR="$BASE/private"
declare -rg CERTDIR="$BASE/cert"
declare -rg LIGHTDIR="$BASE/lighttpd"
mkdir -p "$BASE" "$PRIVDIR" "$CERTDIR"
chown -R root:root "$BASE" || exit 1
chmod u+rwx,go+rx-w "$BASE" "$CERTDIR" || exit 1
chmod u+rwx,go-rwx "$PRIVDIR" || exit 1
# Before doing anything, make sure we have a CA with enough validity left
# File name format for ca is:
# ${PRIVDIR}/ca-TTTTTTTTTT.key
# ${CERTDIR}/ca-TTTTTTTTTT.crt
# Where TT is the unix timestamp of "validTo" of that cert.
#
# A new CA will be generated when the previous has less than two years
# of validity left.
# We deliver a new CA to clients immediately when it was generated, but
# keep signing server certs with the old one, until the old one only
# has a year of validity left, then we switch to the next-oldest CA-cert.
# CA certs are valid for 10 years, server certs for 1 year.
# This should allow for clients to have two years of uptime without any
# problems due to cert rollover.
declare -rg NOW="$( date +%s )"
# PROD
declare -rg srv_days=365 # 1y
declare -rg srv_s="$(( srv_days * 86400 ))"
declare -rg srv_min_remain_s="$(( srv_days / 2 * 86400 ))" # half a year
declare -rg srv_new_ts="$(( srv_days * 86400 + NOW ))"
declare -rg ca_days="$(( 10 * 365 ))" # 10y
declare -rg ca_min_remain_s="$(( 2 * srv_days * 86400 ))" # 2y, twice that of a server cert
declare -rg ca_new_expire_ts="$(( ca_days * 86400 + NOW ))"
# TEST
#declare -rg ca_days=1825 # 5y
#declare -rg ca_min_remain_s="$(( 1260 ))" # bit more than 1y
#declare -rg ca_new_expire_ts="$(( 1320 + NOW ))"
#declare -rg srv_days=365 # 1y
#declare -rg srv_min_remain_s="$(( 1200 ))" # half a year
#declare -rg srv_new_ts="$(( 1230 + NOW ))"
# Extract timestamp from given filename.
# Filename format has to be ca-UUUUUUUUU[.-]* or srv-UUUUUUUU[.-]*
# Where UUUUU is a unix timestamp
get_ts () {
ts="${1%.*}" # Remove everything from last dot on
ts="${ts##*/ca-}" # remove ca- prefix
ts="${ts##*/srv-}" # remove srv- prefix
from="${ts%-*}" # Remove everything from last dash on
if [ "$from" = "$ts" ]; then
# There was no dash, we have a normal timestamp
from=
else
# There was a dash - $from is now the start of the range,
# $ts is the end of the range
ts="${ts#*-}"
fi
# Set the return value
(( ts > 1234567890 ))
}
# Create an openssl config for signing CSRs
create_conf () {
ca_dir="$( mktemp -d /tmp/bwlp-XXXXXXXX )"
[ -z "$ca_dir" ] && exit 1
mkdir "$ca_dir"/{certs,crl,newcerts,private}
touch "$ca_dir"/index.txt
ca_config="$ca_dir/openssl.cnf"
cp -f "/etc/ssl/openssl.cnf" "$ca_config"
cat >> "$ca_config" <<-MYCA
[ CA_openslx ]
dir = $ca_dir
certs = \$dir/certs
crl_dir = \$dir/crl
database = \$dir/index.txt
new_certs_dir = \$dir/newcerts
serial = \$dir/serial
crl = \$dir/crl.pem
x509_extensions = usr_cert
name_opt = ca_default
cert_opt = ca_default
default_md = default
preserve = no
policy = policy_match
MYCA
}
declare -ag cmdargs=( "$@" )
maybe_restart () {
for i in "${cmdargs[@]}"; do
[ "$i" = "--restart" ] && exit 13
done
echo "Restating script, wiping everything except CAs..."
rm -f -- /etc/ssl/openslx/*/{srv,int}*
exec "$0" "${cmdargs[@]}" --restart
exit 14
}
# Start
# Check if existing CA is still valid for long enough
# Globbing is sorted so should make sure we check the newest one last
declare -a ca_list=()
ca_last=
latest_ca_cert=
oldest_ca_ts=
for i in "${PRIVDIR}"/ca-??????????.key; do
[ -s "$i" ] || continue
if ! get_ts "$i"; then
echo "Invalid ca-key: $i"
rm -f -- "$i"
continue
fi
cert="${CERTDIR}/ca-${ts}.crt"
if ! [ -s "$cert" ] \
|| (( ts < NOW )); then
# Missing cert, or expired -> delete
rm -f -- "$cert" "$i"
continue
fi
# Check if key and cert match
if [ "$( openssl x509 -in "$cert" -noout -pubkey )" != "$( openssl pkey -in "$i" -pubout )" ]; then
echo "Publickey in cert doesn't match publickey in keypair: $cert + $i"
rm -f -- "$cert" "$i"
continue
fi
# All CAs still valid
ca_list+=( "$cert" )
# Latest CA
ca_last="$ts"
latest_ca_cert="$cert"
# Oldest one that is still valid for as long as we want to sign a new cert
if [ -z "$oldest_ca_ts" ] && (( ts > NOW + srv_s )); then
oldest_ca_ts="$ts"
fi
done
mknew=
if [ -z "$ca_last" ] || (( ca_last < NOW + ca_min_remain_s )); then
# Make new CA since the newest one we have is about to expire
echo "Creating new CA..."
cert="${CERTDIR}/ca-${ca_new_expire_ts}.crt"
openssl req -new -newkey rsa:3072 -x509 -days "$ca_days" -extensions v3_ca \
-nodes -subj "/C=DE/ST=PewPew/L=HeyHey/O=bwLehrpool/CN=ca-${NOW}.bwlehrpool" \
-keyout "${PRIVDIR}/ca-${ca_new_expire_ts}.key" -out "$cert" || exit 2
ca_list+=( "$cert" )
ca_last="${ca_new_expire_ts}"
latest_ca_cert="$cert"
if [ -z "$oldest_ca_ts" ]; then
oldest_ca_ts="$ca_new_expire_ts"
fi
mknew=1
fi
# Repackage config.tgz module?
if [ -n "$mknew" ] || ! [ -s "/opt/openslx/configs/modules/self-signed-ca.tar" ] \
|| ! [ -e "/run/openslx/cert-conf-done" ] \
|| [ "/opt/openslx/configs/modules/self-signed-ca.tar" -ot "$latest_ca_cert" ]; then
# Rebuild config module for clients
echo "Updating client config module..."
(
tmpdir="$( mktemp -d '/tmp/bwlp-XXXXXXX' )"
# Copy all current ca-certs to tmpdir
cp -a "${CERTDIR}/"ca-*.crt "$tmpdir/"
cd "$tmpdir/" || exit 6
# Build hashed symlinks for openssl
openssl rehash .
# Put everything in config module and rebuild
tar -c -k -f "/opt/openslx/configs/modules/self-signed-ca.tar" \
--transform 's#^[./][./]*#/opt/openslx/ssl/#' .
cd /tmp || exit 7
rm -rf -- "$tmpdir"
sudo -u www-data -n php /srv/openslx/www/slx-admin/api.php sysconfig --action rebuild
echo "."
touch "/run/openslx/cert-conf-done"
)
fi
# Now check the server certificate
# First, check if we could still use old-style certs with intermediate
declare -a unt_list=()
for i in "${CERTDIR}"/intermediate-??????????.crt; do
[ -s "$i" ] || continue
if ! get_ts "$i"; then
echo "Invalid intermediate cert: $i"
rm -f -- "$i"
continue
fi
if (( ts < NOW )); then
echo "Expired intermediate: $i"
rm -f -- "$i"
continue
fi
unt_list+=( "-untrusted" "$i" )
done
# Now check existing server certs
have_srv=
for i in "${PRIVDIR}"/srv-??????????.key; do
[ -s "$i" ] || continue
if ! get_ts "$i"; then
echo "Invalid srv-key: $i"
rm -f -- "$i"
continue
fi
cert="${CERTDIR}/srv-${ts}.crt"
if (( ts < NOW + srv_min_remain_s )) || ! [ -s "$cert" ]; then
echo "Expired srv cert or key with no cert: $i"
rm -f -- "$i" "$cert"
continue
fi
# Keys match?
if [ "$( openssl x509 -in "$cert" -noout -pubkey )" != "$( openssl pkey -in "$i" -pubout )" ]; then
echo "Publickey in cert doesn't match publickey in keypair: $cert + $i"
rm -f -- "$cert" "$i"
continue
fi
# Validate chain
valid=
for ca in "${ca_list[@]}"; do
if openssl verify -CAfile "$ca" "${unt_list[@]}" \
"$cert" &> /dev/null; then
valid=1
break
fi
done
if [ -n "$valid" ]; then
have_srv=1
break
fi
echo "No valid CA/chain for $i, removing"
rm -f -- "$i" "$cert"
done
# Now still check the current lighttpd config, in case it is out of sync
# with our generated stuff for whatever reason.
if [ -n "$have_srv" ] || [ -z "$makenew" ]; then
if [ -s "${LIGHTDIR}/ca-chain.pem" ]; then
unt_list=( "-untrusted" "${LIGHTDIR}/ca-chain.pem" )
else
unt_list=()
fi
valid=
for ca in "${ca_list[@]}"; do
openssl verify -CAfile "$ca" "${unt_list[@]}" \
"${LIGHTDIR}/server.pem" &> /dev/null || continue
valid=1
break
done
if [ -z "$valid" ]; then
echo "Current lighttpd SSL setup seems invalid, making new one"
makenew=1
fi
fi
# Make new one?
if [ -z "$have_srv" ] || [ -n "$makenew" ]; then
# Request ServerCert
csr="$( mktemp /tmp/bwlp-XXXXXXX.csr )"
echo "Generating new Server Certificate. Key+CSR..."
rm -f -- "${CERTDIR}"/srv-*.crt "${PRIVDIR}/srv.key.tmp" "${PRIVDIR}"/srv-*.key
openssl req -newkey rsa:3072 -nodes -keyout "${PRIVDIR}/srv.key.tmp" -out "$csr" \
-subj "/C=DE/ST=PewPew/L=HeyHey/O=bwLehrpool/CN=satellite.bwlehrpool" || exit 4
echo "Signing Server Certificate with CA..."
sign_cert="${CERTDIR}/ca-${oldest_ca_ts}.crt"
sign_key="${PRIVDIR}/ca-${oldest_ca_ts}.key"
if ! [ -s "$sign_cert" ] || ! [ -s "$sign_key" ]; then
echo "CA sign/key to sign does not exist!?"
maybe_restart
fi
echo "Signing with $sign_cert"
create_conf
# Need extfile for SAN, chromium doesn't honor CN anymore
cat > "${csr}.cnf" <<-END
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "bwLehrpool Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = satellite.bwlehrpool
END
if ! openssl ca -config "$ca_config" -create_serial -policy policy_anything -days "$srv_days" \
-cert "$sign_cert" -keyfile "$sign_key" -extfile "${csr}.cnf" \
-notext -name CA_openslx -batch -out "${CERTDIR}/srv-${srv_new_ts}.crt" -in "$csr"; then
echo "Failed to sign CSR"
rm -f -- "$sign_key" "$sign_cert"
maybe_restart
fi
rm -rf -- "$ca_dir"
rm -f -- "$csr" "${csr}.cnf"
mv "${PRIVDIR}/srv.key.tmp" "${PRIVDIR}/srv-${srv_new_ts}.key" || exit 5
# Combine and prepare for lighttpd
mkdir -p "$LIGHTDIR" || exit 10
# Combine cert and key, as required by (older) lighttpd
echo "Writing out lighttpd PEMs..."
cat "${CERTDIR}/srv-${srv_new_ts}.crt" "${PRIVDIR}/srv-${srv_new_ts}.key" > "${LIGHTDIR}/server.pem" || exit 10
chmod 0600 "${LIGHTDIR}/server.pem"
# Don't need this anymore
rm -f -- "${LIGHTDIR}/ca-chain.pem"
if [ "$1" = "--restart" ] || [ -t 0 ]; then
echo "Restarting lighttpd..."
systemctl restart lighttpd.service
fi
fi
echo "Done."
exit 0
|