-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstapler.sh
294 lines (256 loc) · 11.5 KB
/
stapler.sh
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
#!/bin/sh
# ocsp response stapling and refresh
# intended for opnsense webgui (lighttpd) ocsp stapling
# recommended to run this scripty every 5 min to keep stapled response fresh
#
# defaults
freq=5 # when checking the OCSP validity, the default is a 5-minute update interval
verb_l=1 # only errors in stdout
openssl="openssl" # <- opnsense uses /usr/local/bin/openssl
tag="lighttpd" # send to webgui log. configd log can catch stderr on error
good_only="1" # staple only if response good
validate="1" # verify OCSP signature by default. disable for debugging only
always_update="" # dont fetch too often by default
cert_pem="" # cert pem path
ocsp_bin="" # ocsp staple der path
issuer_pem="" # issuer chain pem path
min_freq=24 # update ocsp once a day even if its sill valid with nextUpdate present
min_freq2=60 # update ocsp once a hour if its "always fresh" (no nextUpdate present)
usage() {
echo "Request and staple OCSP response for server certificate"
echo
echo "Options:"
echo " -c, --cert file : server certificate file path"
echo " -i, --issuer file : issuer chain file path"
echo " -b, --staple_bin file : OCSP staple binary file path"
echo " -s, --ssl_path file : open_ssl path"
echo " -m, --min_freq hours : minimal update frequency for OCSP with Next Update time present"
echo " : update response even if Next Update is still valid (default is 24H)"
echo " -m2, --min_freq2 minutes : minimal update frequency for OCSP with no Next Update time present"
echo " : update response based on thisUpdate value and current time with selected frequency"
echo " -f, --freq minutes : this script execution frequency. to check if refresh is needed"
echo " : at this cycle"
echo " -d, --debug number : verbosity level: 0 - quiet, 1 - stdout, 2 - as 1 with logger"
echo " : 3 - as 2 with stderr, 4 - as 3 with debug messages"
echo " -t, --tag string : syslog tag. using lighttpd by default"
echo " -ng, --no_good : staple response even if status is not 'good'. debugging only"
echo " -nv, --no_validate : staple response even if OCSP signature validation failed. debugging only"
echo " -a, --always_update : refresh staple on each run (refresh can be skipped if possible by default)"
echo " -h, --help : read this"
}
# args processing
while [ $# -gt 0 ]
do
case "$1" in
-c|--cert)
if [ $# -le 1 ]; then
echo "path value is missing for $1 argument"
exit 1
fi
cert_pem=$2
shift
;;
-i|--issuer)
if [ $# -le 1 ]; then
echo "path value is missing for $1 argument"
exit 1
fi
issuer_pem=$2
shift
;;
-b|--staple_bin)
if [ $# -le 1 ]; then
echo "path value is missing for $1 argument"
exit 1
fi
ocsp_bin=$2
shift
;;
-s|--ssl_path)
if [ $# -le 1 ]; then
echo "path value is missing for $1 argument"
exit 1
fi
openssl=$2
shift
;;
-m|--min_freq)
if [ $# -le 1 ]; then
echo "time value is missing for $1 argument"
exit 1
fi
min_freq=$2
shift
;;
-m2|--min_freq2)
if [ $# -le 1 ]; then
echo "time value is missing for $1 argument"
exit 1
fi
min_freq2=$2
shift
;;
-d|--debug)
if [ $# -le 1 ]; then
echo "debug level value is missing for $1 argument"
exit 1
fi
verb_l=$2
shift
;;
-t|--tag)
if [ $# -le 1 ]; then
echo "syslog tag value is missing for $1 argument"
exit 1
fi
tag=$2
shift
;;
-f|--freq)
if [ $# -le 1 ]; then
echo "time value is missing for $1 argument"
exit 1
fi
freq=$2
shift
;;
-ng|--no_good)
good_only=""
;;
-nv|--no_validate)
validate=""
;;
-a|--always-update)
always_update="1"
;;
-h|--help)
usage
exit 0
;;
*)
if [ -n "$1" ]; then
echo "unknown option: $1" >&2
exit 1
fi
esac
shift
done
say() {
verb="$1"
msg="$2"
prio="user.$3"
echostderr=0
if [ "$verb" -le "$verb_l" ]; then
if [ "$verb_l" -ge 2 ] && [ "$3" != "debug" ] || [ "$verb_l" -ge 4 ]; then
logger -t $tag -p "$prio" "$msg"
fi
if [ "$verb_l" -ge 3 ] && [ "$3" = "error" ]; then
echostderr=1
fi
if [ "$verb_l" -ge 1 ]; then
[ $echostderr -eq 1 ] && echo "$msg" >&2 || echo "$msg" >&2
fi
fi
}
bye() {
returncode="$1"
verb="$2"
msg="$3"
prio="$4"
say "$verb" "$msg" "$prio"
[ -n "$ocsp_tmp" ] && rm -f "$ocsp_tmp"
exit "$returncode"
}
handle_bad_status() {
reason="$1"
if [ "$reason" = "nogood" ]; then
if [ -z "$good_only" ]; then
say 3 "ocsp staple: WARNING. Response status is no good but --no_good option is set. Try to staple response. Please dont use in production environment" debug
else
bye 1 1 "ocsp staple error: OCSP response status is no good" error
fi
elif [ "$reason" = "verify" ]; then
if [ -z "$validate" ]; then
say 3 "ocsp staple: WARNING. OCSP signature validation failed but --no_validate option is set. Try to staple response. Please dont use in production environment" debug
else
bye 1 1 "ocsp staple error: OCSP signature validation failed" error
fi
fi
}
now=$(date +%s)
ocsp_tmp="$ocsp_bin.temp"
# try to keep staple fresh (based on thisUpdate, nextUpdate and current times). time skew tolerance is 5min in openssl - let it be here too.
# FF wants the staple to be no older than a day - let it be here too
# no need to fetch if staple exists, it will be valid on next freq (so and nextUpdate exists) and less then 24H old. or its less then 1H from last update if nextUpdate absents
if [ -z "$always_update" ] && [ -L "$ocsp_bin" ] && [ -e "$ocsp_bin" ] && [ "$freq" -gt 0 ]; then
target_nextupdate=0
t=$(readlink "$ocsp_bin")
# epoch timestamps
run_freq_sec=$((freq * 60)) # script run frequency
min_freq_sec=$((min_freq * 3600)) # min refresh frequency for OCSPs with nextUpdate time
min_freq2_sec=$((min_freq2 * 60)) # min refresh frequency for OCSPs without nextUpdate time
target_updated="$(echo "$t" | cut -d '.' -f 3)" # last refresh time
target_date="$(echo "$t" | cut -d '.' -f 4)" # thisUpdate
target_nextupdate=${t##*.} # nextUpdate
say 4 "ocsp staple: checking if refresh required. now: $now; nextUpdate: $target_nextupdate; thisUpdate: $target_date; last refresh: $target_updated" debug
if [ -n "$target_updated" ] && [ $((target_updated + min_freq_sec)) -lt $((now + run_freq_sec)) ]; then
say 4 "ocsp staple: stapled respone is more then ""$min_freq""H old or will be on next cycle. need to renew" debug
else
if [ "$target_nextupdate" -gt 0 ] && [ $((now + run_freq_sec)) -lt "$target_nextupdate" ]; then
bye 0 3 "ocsp staple: ocsp response will be valid over next $freq minutes. skippig request this time" debug
elif [ "$target_nextupdate" = 0 ] && [ $((now + run_freq_sec)) -lt $((target_date + min_freq2_sec)) ]; then
bye 0 3 "ocsp staple: ocsp response have no nextUpdate field and updated less then $min_freq2 min. skippig request this time" debug
else
if [ "$target_nextupdate" = 0 ] && [ $((target_updated + min_freq2_sec)) -gt "$now" ] && [ $((target_date + min_freq_sec)) -lt "$now" ]; then
say 4 "ocsp staple: stapled respone have no nextUpdate value and is more then ""$min_freq""H old. some clients may treat this as an outdated response" debug
fi
say 4 "ocsp staple: there is less then ""$freq""m before OCSP response expires or it will be more then ""$min_freq2""m since last update for ocsp without nextUpdate. need to renew" debug
fi
fi
fi
# read ocsp responder uri from pem
if resp_uri=$(openssl x509 -in "$cert_pem" -ocsp_uri -noout); then
[ -n "$resp_uri" ] && say 4 "ocsp staple: OCSP Responder URI: $resp_uri" debug
else
bye 1 1 "ocsp staple error: OCSP Responder URI not found" error
fi
uri_type=$(echo "$resp_uri" | cut -c 1-4)
if [ "$uri_type" != "http" ]; then
bye 1 1 "ocsp staple error: OCSP Respinder URI format not supported. URI: $resp_uri" error
fi
say 4 "ocsp staple: fetch OCSP. Responder URI: $resp_uri" debug
# set host header
o_ver=$($openssl version | cut -c 9-13)
# o_ver="1.0.1" # <- debug
[ "${o_ver}" != "${o_ver#1.0.}" ] && key_val_sign=" " || key_val_sign="="
ocsp_hh="-header host${key_val_sign}$(echo "$resp_uri" | cut -d/ -f3)"
# fetch OCSP
if resp=$(openssl ocsp -issuer "$issuer_pem" -cert "$cert_pem" -respout "$ocsp_tmp" -noverify -no_nonce -url "$resp_uri" ${ocsp_hh}); then
say 4 "ocsp staple: OCSP response received. $resp" debug
else
bye 1 1 "ocsp staple error: OCSP request failed with return code $?. $resp" error
fi
# check response status
resp_status="$(printf %s "$resp" | head -1)"
[ "$resp_status" = "$cert_pem: good" ] || handle_bad_status "nogood"
# parse OCSP dates
next_date="$(echo "$resp" | grep 'Next Update:' | cut -d ' ' -f 3-)"
[ -n "$next_date" ] || next_date=0
this_date="$(echo "$resp" | grep 'This Update:' | cut -d ' ' -f 3-)"
resp_expire=$(date -d "$next_date" +%s 2>/dev/null || date -jf "%b %e %T %Y %Z" "$next_date" +%s) # nextUpdate in epoch
resp_date=$(date -d "$this_date" +%s 2>/dev/null || date -jf "%b %e %T %Y %Z" "$this_date" +%s) # thisUpdate in epoch
# validate OCSP signatures
resp_verify=$(openssl ocsp -issuer "$issuer_pem" -verify_other "$issuer_pem" -cert "$cert_pem" -respin "$ocsp_tmp" -no_nonce -out /dev/null 2>&1)
[ "$resp_verify" = "Response verify OK" ] || handle_bad_status "verify"
# save respone and update link.
ocsp_out="$ocsp_bin.$now.$resp_date.$resp_expire"
mv "$ocsp_tmp" "$ocsp_out" || bye 1 1 "ocsp staple error: error saving ocsp response to file $ocsp_out" error
say 4 "ocsp staple: ocsp response saved in $ocsp_out" debug
ln -sf "$ocsp_out" "$ocsp_bin" || bye 1 1 "ocsp staple error: error linking $ocsp_out to $ocsp_bin" error
for i in "$ocsp_bin".*.*.*; do
created="$(echo "$i" | cut -d '.' -f 3)"
if [ -n "$created" ] && [ "$created" -ne "$now" ]; then
rm -f "$i"
fi
done
say 4 "ocsp staple: ocsp response updated. current time: $now; ocsp update time: $resp_date; ocsp expire time: $resp_expire" debug