1 |
#! /bin/sh |
2 |
# |
3 |
# @(#)$Id$ |
4 |
# rev 2 |
5 |
# |
6 |
# TCS G4 client certificate format management tool for POSIX systems |
7 |
# including installation to Grid Community Toolkit (formerly Globus) |
8 |
# user credential directory formats |
9 |
# |
10 |
# Requirements: sh, awk, sed, openssl, date, mktemp, ls, |
11 |
# mkdir, rmdir, mv, basename, grep, chmod |
12 |
# in addition requires curl if you use URLs for the PKCS#7 input |
13 |
# |
14 |
# Copyright 2020 David Groep, Nikhef, Amsterdam |
15 |
# |
16 |
# Licensed under the Apache License, Version 2.0 (the "License"); |
17 |
# you may not use this file except in compliance with the License. |
18 |
# You may obtain a copy of the License at |
19 |
# |
20 |
# http://www.apache.org/licenses/LICENSE-2.0 |
21 |
# |
22 |
# Unless required by applicable law or agreed to in writing, software |
23 |
# distributed under the License is distributed on an "AS IS" BASIS, |
24 |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
25 |
# See the License for the specific language governing permissions and |
26 |
# limitations under the License. |
27 |
# |
28 |
# |
29 |
destdir=. |
30 |
DATE=`date +%Y%m%d-%H%M%S` |
31 |
progname=`basename "$0"` |
32 |
bckprefix=backup |
33 |
makecsr=0 |
34 |
nameformat=friendly |
35 |
certfn= |
36 |
profile="" |
37 |
|
38 |
# ############################################################################ |
39 |
# usage help and instructions |
40 |
# |
41 |
help() { cat <<EOF |
42 |
Usage: tcsg4-install-servercert.sh [-d destdir] [-r|-R] [-f] |
43 |
[-b backupprefix] <PKCS7.p7b> |
44 |
|
45 |
-d destdir write result files to <destdir> |
46 |
-r use EEC commonName as basis for new filenames |
47 |
--no-rename use the base filename of the P7B file for new filenames |
48 |
-R use EEC commonName and date as basis for filenames |
49 |
-f do not make backups of existing files |
50 |
-b bckprefix prefix of the filename to use when making backups |
51 |
|
52 |
<PKCS7.p7b> filename of the blob produced by Sectigo |
53 |
or URL to the PKCS#7 blob from the success email |
54 |
(https://cer.../ssl?action=download&sslId=1234567&format=bin) |
55 |
remember to "quote" the URL to preserve the ampersands |
56 |
or Self-Enrollment ID number (numeric) |
57 |
|
58 |
EOF |
59 |
return; |
60 |
} |
61 |
|
62 |
# ############################################################################ |
63 |
# |
64 |
while [ $# -gt 0 ]; do |
65 |
case "$1" in |
66 |
-r | --rename ) nameformat="friendly"; shift 1 ;; |
67 |
-x | --no-rename ) nameformat=""; shift 1 ;; |
68 |
-R | --rename-with-date ) nameformat="dated"; shift 1 ;; |
69 |
-f | --force ) bckprefix=""; shift 1 ;; |
70 |
-h | --help ) help ; exit 0 ;; |
71 |
-b | --backupprefix ) bckprefix="$2"; shift 2 ;; |
72 |
-d | --destination ) destdir="$2"; shift 2 ;; |
73 |
-* ) echo "Unknown option $1, exiting" >&2 ; exit 1 ;; |
74 |
* ) break ;; |
75 |
esac |
76 |
done |
77 |
|
78 |
case $# in |
79 |
0 ) help ; exit 0 ;; |
80 |
1 ) pkfile="$1"; break ;; |
81 |
* ) echo "Too many arguments." >&2 ; exit 1 ;; |
82 |
esac |
83 |
|
84 |
# ############################################################################ |
85 |
# retrieve PKCS#7 from URL, if URL given (beware of quoting the ampersand) |
86 |
# or from order number |
87 |
# |
88 |
[ "$pkfile" -gt 0 ] > /dev/null 2>&1 |
89 |
if [ $? -eq 0 ]; then |
90 |
# this was a pure number, so an order ID |
91 |
echo "Recognised order ID $pkfile, downloading" |
92 |
pkfile="https://cert-manager.com/customer/surfnet/ssl?action=download&sslId=${pkfile}&format=bin" |
93 |
fi |
94 |
|
95 |
case "$pkfile" in |
96 |
https://*format=bin | https://*format=base64 ) |
97 |
sslid=`echo "$pkfile"|sed -e's/.*sslId=\([0-9]*\).*/\1/'` |
98 |
[ "$sslid" -gt 0 ] >/dev/null 2>&1 |
99 |
if [ $? -ne 0 ]; then |
100 |
echo "URL provided is not a Sectigo SSL PKCS#7 enrollment result" >&2 |
101 |
exit 1 |
102 |
fi |
103 |
curl -s -o "sectigo-order-$sslid.p7b" "$pkfile" |
104 |
if [ $? -ne 0 ]; then |
105 |
echo "URL cannot be downloaded ($pkfile)" >&2 |
106 |
exit 1 |
107 |
fi |
108 |
case "$pkfile" in |
109 |
*format=base64 ) |
110 |
mv "sectigo-order-$sslid.p7b" "sectigo-order-$sslid.p7b.pem" |
111 |
openssl pkcs7 \ |
112 |
-inform pem -in "sectigo-order-$sslid.p7b.pem" \ |
113 |
-outform der -out "sectigo-order-$sslid.p7b" |
114 |
;; |
115 |
esac |
116 |
if [ ! -s "sectigo-order-$sslid.p7b" ]; then |
117 |
echo "URL download result empty in sectigo-order-$sslid.p7b " >&2 |
118 |
echo " (source $pkfile)" >&2 |
119 |
exit 1 |
120 |
fi |
121 |
pkfile="sectigo-order-$sslid.p7b" |
122 |
;; |
123 |
esac |
124 |
|
125 |
|
126 |
# ############################################################################ |
127 |
# input validation |
128 |
# |
129 |
if [ ! -r "$pkfile" ]; then echo "Cannot read $pkfile" >&2; exit 1; fi |
130 |
|
131 |
case "$pkfile" in |
132 |
*.p7b ) credbase=`basename "$pkfile" .p7b` ;; |
133 |
* ) echo "Unlikely PKCS#12 file: $pkfile" >&2 ; exit 2 ;; |
134 |
esac |
135 |
|
136 |
# ############################################################################ |
137 |
# extraction of Sectigo blob of p7b |
138 |
# |
139 |
tempdir=`mktemp -d tcsg4unpack.XXXXXX` |
140 |
|
141 |
if [ ! -d "$tempdir" ]; then |
142 |
echo "Error creating temporary working directory here" >&2 |
143 |
exit 1 |
144 |
fi |
145 |
|
146 |
openssl pkcs7 -inform der -in "$pkfile" -print_certs \ |
147 |
-out "$tempdir/p7b-$credbase.pem" |
148 |
|
149 |
if [ $? -ne 0 ]; then |
150 |
echo "Error: cannot extract data from PKCS7 file $pkfile" >&2 |
151 |
echo " PASSPHRASE INCORRECT?" >&2 |
152 |
exit 3 |
153 |
fi |
154 |
|
155 |
if [ ! -s "$tempdir/p7b-$credbase.pem" ]; then |
156 |
echo "Error: cannot extract data from PKCS7 file $pkfile" >&2 |
157 |
echo " resulting direct-rendered p7b file not found" >&2 |
158 |
exit 4 |
159 |
fi |
160 |
|
161 |
if [ `grep -c CERTIFICATE "$tempdir/p7b-$credbase.pem"` -eq 0 ]; then |
162 |
echo "Error: cannot extract data from PKCS7 file $pkfile" >&2 |
163 |
echo " resulting p7b file has no certificate material" >&2 |
164 |
exit 4 |
165 |
fi |
166 |
|
167 |
# extract |
168 |
awk ' |
169 |
BEGIN { icert = 0; } |
170 |
/^-----BEGIN CERTIFICATE-----$/ { |
171 |
icert++; |
172 |
print $0 > "'$tempdir/cert-'"icert"'-$credbase.pem'"; |
173 |
do { |
174 |
getline ln; |
175 |
print ln > "'$tempdir/cert-'"icert"'-$credbase.pem'"; |
176 |
} while ( ln != "-----END CERTIFICATE-----" ); |
177 |
} |
178 |
' "$tempdir/p7b-$credbase.pem" |
179 |
|
180 |
# ############################################################################ |
181 |
# generate per-certificate and key output files |
182 |
# |
183 |
[ -d "$destdir" ] || mkdir -p "$destdir" |
184 |
|
185 |
havewrittenchain=0 |
186 |
for i in "$tempdir"/cert-*-"$credbase".pem |
187 |
do |
188 |
certcn=`openssl x509 -noout -subject -nameopt oneline,sep_comma_plus \ |
189 |
-in "$i" | \ |
190 |
sed -e 's/.*CN = \([a-zA-Z0-9\._][- a-zA-Z0-9:\._@]*\).*/\1/'` |
191 |
issuercn=`openssl x509 -noout -issuer -nameopt oneline,sep_comma_plus \ |
192 |
-in "$i" | \ |
193 |
sed -e 's/.*CN = \([a-zA-Z0-9\._][- a-zA-Z0-9:\._@]*\).*/\1/'` |
194 |
|
195 |
certdate=`openssl x509 -noout -text -in "$i" | \ |
196 |
awk '/ Not Before:/ { print $4,$3,$6; }'` |
197 |
certisca=`openssl x509 -noout -text -in "$i" | \ |
198 |
awk 'BEGIN { ca=0; } |
199 |
/CA:FALSE/ { ca=0; } /CA:TRUE/ { ca=1; } |
200 |
END {print ca;}'` |
201 |
|
202 |
if [ "$certcn" = "$issuercn" -o "$issuercn" = "AddTrust External CA Root" ] |
203 |
then |
204 |
continue |
205 |
fi |
206 |
|
207 |
# these CAs as intermediate subjects are known useless |
208 |
case "$certcn" in |
209 |
"AAA Certificate Services" ) continue ;; |
210 |
"USERTrust RSA Certification Authority" ) continue ;; |
211 |
* ) ;; |
212 |
esac |
213 |
|
214 |
if [ $certisca -eq 0 ]; then |
215 |
certfn=`echo "$certcn" | sed -e 's/[^-a-zA-Z0-9_\.]/_/g'` |
216 |
certfndated=`echo "$certcn issued $certdate" | \ |
217 |
sed -e 's/[^-a-zA-Z0-9_]/_/g'` |
218 |
echo "Processing EEC certificate: $certcn" |
219 |
friendlyname="${friendlyname:-$certcn issued $certdate}" |
220 |
echo " (friendly name: $friendlyname)" |
221 |
fi |
222 |
|
223 |
if [ $certisca -eq 1 ]; then |
224 |
echo "Processing CA certificate: $certcn" |
225 |
if [ $havewrittenchain -eq 0 ]; then |
226 |
if [ -f "$destdir/chain-$credbase.pem" -a -n "$bckprefix" ]; then |
227 |
mv "$destdir/chain-$credbase.pem" \ |
228 |
"$destdir/$bckprefix.$DATE.chain-$credbase.pem" |
229 |
fi |
230 |
havewrittenchain=1 |
231 |
echo -ne "" > "$destdir/chain-$credbase.pem" |
232 |
fi |
233 |
cat $i >> "$destdir/chain-$credbase.pem" |
234 |
fi |
235 |
|
236 |
if [ $certisca -eq 0 ]; then |
237 |
if [ -f "$destdir/cert-$credbase.pem" ]; then |
238 |
mv "$destdir/cert-$credbase.pem" "$destdir/$bckprefix.$DATE.cert-$credbase.pem" |
239 |
fi |
240 |
cat $i > "$destdir/cert-$credbase.pem" |
241 |
|
242 |
case "$issuercn" in |
243 |
GEANT\ OV\ *) profile="ov" ;; |
244 |
GEANT\ EV\ *) profile="ev" ;; |
245 |
GEANT\ eScience\ *) profile="igtfov" ;; |
246 |
* ) profile="" ;; |
247 |
esac |
248 |
fi |
249 |
|
250 |
done |
251 |
|
252 |
# ############################################################################ |
253 |
# cleanup intermate files and name output properly |
254 |
# |
255 |
rm "$tempdir"/cert-*-$credbase.pem |
256 |
rm "$tempdir"/p7b-$credbase.pem |
257 |
rmdir "$tempdir" |
258 |
if [ $? -ne 0 ]; then |
259 |
echo "Error: cannot remove working directory $tempdir" >&2 |
260 |
echo " internal inconsistency or prior error encountered" >&2 |
261 |
fi |
262 |
|
263 |
# rename, if so required |
264 |
if [ -n "$nameformat" ]; then |
265 |
if [ "$nameformat" = "friendly" ]; then |
266 |
certfn="${certfn:-$credbase}" |
267 |
elif [ "$nameformat" = "dated" ]; then |
268 |
certfn="${certfndated:-$credbase}" |
269 |
else |
270 |
echo "Unknown filename format, error" >&2 |
271 |
exit 2 |
272 |
fi |
273 |
mv "$destdir/cert-$credbase.pem" "$destdir/cert-$certfn.pem" |
274 |
mv "$destdir/chain-$credbase.pem" "$destdir/chain-$certfn.pem" |
275 |
else |
276 |
certfn="$credbase" |
277 |
fi |
278 |
|
279 |
# ############################################################################ |
280 |
# create the nginx compatible single file: cert+chain concatenated |
281 |
# |
282 |
cat "$destdir/cert-$certfn.pem" "$destdir/chain-$certfn.pem" \ |
283 |
> "$destdir/nginx-$certfn.pem" |
284 |
|
285 |
# ############################################################################ |
286 |
# make per-profile copies in case of key re-use for same host new profile |
287 |
# |
288 |
if [ "$profile" != "" ]; then |
289 |
[ -f "$destdir/cert-$certfn.pem" ] && cp -p "$destdir/cert-$certfn.pem" "$destdir/$profile-cert-$certfn.pem" |
290 |
[ -f "$destdir/chain-$certfn.pem" ] && cp -p "$destdir/chain-$certfn.pem" "$destdir/$profile-chain-$certfn.pem" |
291 |
[ -f "$destdir/nginx-$certfn.pem" ] && cp -p "$destdir/nginx-$certfn.pem" "$destdir/$profile-nginx-$certfn.pem" |
292 |
fi |
293 |
|
294 |
# ############################################################################ |
295 |
# inform user of result and of globus compatibility |
296 |
# |
297 |
echo "The following files have been created for you:" |
298 |
echo -ne " " ; ls -l1a "$destdir/cert-$certfn.pem" |
299 |
echo -ne " " ; ls -l1a "$destdir/chain-$certfn.pem" |
300 |
|
301 |
# |
302 |
# |
303 |
# ############################################################################ |