-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcheck-ssl
More file actions
executable file
Β·353 lines (308 loc) Β· 10.7 KB
/
Copy pathcheck-ssl
File metadata and controls
executable file
Β·353 lines (308 loc) Β· 10.7 KB
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
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
#!/usr/bin/env bash
# SSL/TLS certificate checker
# Usage: ./check-ssl [OPTIONS] <domain>
set -euo pipefail
# Set terminal type for proper color support
export TERM=xterm-256color
# Main function wrapper
main() {
# Check if common-functions exists
if [[ ! -f "$(dirname "$0")/common-functions" ]]; then
echo "Downloading common-functions from GitHub..."
if ! curl -fsSL https://raw.githubusercontent.com/Flower7C3/bash-tools/master/common-functions -o "$(dirname "$0")/common-functions"; then
echo "Failed to download common-functions"
exit 1
fi
fi
# Source common functions
source "$(dirname "$0")/common-functions"
# Call the actual main function
ssl_main "$@"
}
ssl_main() {
# Function to display usage
function show_usage() {
log_usage_title '[OPTIONS] <domain|batch file>'
echo
echo 'Check SSL/TLS certificates for domains'
echo
log_header 'Options'
log_usage_options_line '-p;--port <PORT>' \
'Port to check (default: %s)' "$DEFAULT_PORT"
log_usage_options_line '-t;--timeout <SECONDS>' \
'Connection timeout (default: %s)' "$DEFAULT_TIMEOUT"
log_usage_options_line '-b;--batch' \
'Batch mode for multiple domains'
log_usage_options_line '-f;--format <FORMAT>' \
'Output format: text, json, csv (default: text)'
log_usage_options_line '-r;--revocation' \
'Check certificate revocation'
log_usage_options_line '-u;--update' \
'Update app'
log_usage_options_line '-h;--help' \
'Show this help message'
echo
log_header 'Examples'
log_usage_example_line 'example.com'
log_usage_example_line '-p 8443 example.com'
log_usage_example_line '-b -f json domains.txt'
log_usage_example_line '-c -r example.com'
exit 0
}
# Default values
DEFAULT_PORT=443
DEFAULT_TIMEOUT=10
DEFAULT_BATCH=0
# Global variables
DOMAIN_NAME=""
PORT=$DEFAULT_PORT
TIMEOUT=$DEFAULT_TIMEOUT
BATCH_MODE=$DEFAULT_BATCH
OUTPUT_FORMAT="text"
CHECK_REVOCATION=0
# Function to get certificate information
get_certificate_info() {
local _domain="$1"
local _port="$2"
loading_animation_loop 'Reading certificate information for %s:%s' "$_domain" "$_port"
# Get certificate details
local _cert_info
if ! _cert_info=$(perl -e "alarm($TIMEOUT); system('openssl s_client -servername \"$_domain\" -connect \"$_domain:$_port\" -showcerts 2>/dev/null <<< \"Q\" | openssl x509 -noout -text 2>/dev/null')" 2>/dev/null); then
loading_animation_loop ---exit-code 1 'Failed to retrieve certificate for %s:%d' "$_domain" "$_port"
return 1
fi
# Get basic certificate info
local _subject
local _issuer
local _not_before
local _not_after
local _serial
local _fingerprint
_subject=$(echo "$_cert_info" | grep -e "Subject:" | sed 's/Subject://' | awk '{$1=$1};1')
_issuer=$(echo "$_cert_info" | grep -e "Issuer:" | sed 's/Issuer://' | awk '{$1=$1};1')
_not_before=$(echo "$_cert_info" | grep -e "Not Before:" | sed 's/Not Before://' | awk '{$1=$1};1')
_not_after=$(echo "$_cert_info" | grep -e "Not After :" | sed 's/Not After ://' | awk '{$1=$1};1')
_serial=$(echo "$_cert_info" | grep -e "Serial Number:" | sed 's/Serial Number://' | awk '{$1=$1};1')
_fingerprint=$(echo "$_cert_info" | grep -e "SHA1 Fingerprint=" | sed 's/SHA1 Fingerprint=//' | awk '{$1=$1};1')
# Get certificate dates in a parseable format
local _not_before_epoch
local _not_after_epoch
local _current_epoch
_not_before_epoch=$(date -d "$_not_before" +%s 2>/dev/null || echo "0")
_not_after_epoch=$(date -d "$_not_after" +%s 2>/dev/null || echo "0")
_current_epoch=$(date +%s)
# Calculate days until expiry
local _days_until_expiry
if [[ "$_not_after_epoch" -gt 0 ]]; then
_days_until_expiry=$(((_not_after_epoch - _current_epoch) / 86400))
else
_days_until_expiry="unknown"
fi
# Check if certificate is expired
local _is_expired=0
if [[ "$_not_after_epoch" -gt 0 && "$_current_epoch" -gt "$_not_after_epoch" ]]; then
_is_expired=1
fi
# Check if certificate is not yet valid
local _not_yet_valid=0
if [[ "$_not_before_epoch" -gt 0 && "$_current_epoch" -lt "$_not_before_epoch" ]]; then
_not_yet_valid=1
fi
# Check if certificate expires soon (within 30 days)
local _expires_soon=0
if [[ "$_days_until_expiry" != "unknown" && "$_days_until_expiry" -lt 30 && "$_days_until_expiry" -gt 0 ]]; then
_expires_soon=1
fi
loading_animation_loop 'Retrieve certificate information for %s:%d' "$_domain" "$_port"
# Output results based on format
case "$OUTPUT_FORMAT" in
"json")
cat <<EOF
{
"domain": "$_domain",
"port": $_port,
"subject": "$_subject",
"issuer": "$_issuer",
"not_before": "$_not_before",
"not_after": "$_not_after",
"serial": "$_serial",
"fingerprint": "$_fingerprint",
"days_until_expiry": $_days_until_expiry,
"is_expired": $_is_expired,
"not_yet_valid": $_not_yet_valid,
"expires_soon": $_expires_soon,
"status": "$(if [[ $_is_expired -eq 1 ]]; then echo "expired"; elif [[ $_not_yet_valid -eq 1 ]]; then echo "_not_yet_valid"; elif [[ $_expires_soon -eq 1 ]]; then echo "_expires_soon"; else echo "valid"; fi)"
}
EOF
;;
"csv")
echo "$_domain,$_port,$_subject,$_issuer,$_not_before,$_not_after,$_days_until_expiry,$(if [[ $_is_expired -eq 1 ]]; then echo "expired"; elif [[ $_not_yet_valid -eq 1 ]]; then echo "_not_yet_valid"; elif [[ $_expires_soon -eq 1 ]]; then echo "_expires_soon"; else echo "valid"; fi)"
;;
*)
# Text format
log_header 'Certificate Information for %s:%s' "$_domain" "$_port"
log_info ---icon 'π€' "Subject" ---status "$_subject"
log_info ---icon 'π’' "Issuer" ---status "$_issuer"
log_info ---icon 'π
' "Valid From" ---status "$_not_before"
log_info ---icon 'π
' "Valid Until" ---status "$_not_after"
log_info ---icon 'π’' "Serial Number" ---status "$_serial"
log_info ---icon 'π' "SHA1 Fingerprint" ---status "$_fingerprint"
if [[ $_is_expired -eq 1 ]]; then
log_error ---icon 'β' 'Status' ---status 'EXPIRED'
elif [[ $_not_yet_valid -eq 1 ]]; then
log_warning ---icon 'β³' 'Status' ---status 'NOT YET VALID'
elif [[ $_expires_soon -eq 1 ]]; then
log_warning ---icon 'β ' 'Status' ---status "EXPIRES SOON ($_days_until_expiry days)"
else
log_success ---icon 'π' 'Status' ---status "VALID ($_days_until_expiry days remaining)"
fi
;;
esac
}
# Function to check certificate revocation
check_certificate_revocation() {
local _domain="$1"
local _port="$2"
log_header 'OCSP Information for %s:%s' "$_domain" "$_port"
# Get certificate
local _cert
if ! _cert=$(perl -e "alarm($TIMEOUT); system('openssl s_client -servername \"$_domain\" -connect \"$_domain:$_port\" 2>/dev/null <<<\"Q\" | openssl x509 -outform PEM 2>/dev/null')"); then
log_error 'Failed to retrieve certificate for revocation check'
return 1
fi
# Check OCSP responder
local _ocsp_url
_ocsp_url=$(echo "$_cert" | openssl x509 -noout -ocsp_uri 2>/dev/null || echo "")
if [[ -n "$_ocsp_url" ]]; then
log_success "OCSP URL: %s" "$_ocsp_url"
# Note: Full OCSP checking would require additional tools
log_info "Note: Full OCSP verification requires additional tools"
else
log_error "No OCSP URL found in certificate"
fi
}
# Function to process batch file
process_batch() {
local _file="$1"
if [[ ! -f "$_file" ]]; then
die 1 'Batch file not found: <u>%s</u>' "$_file"
fi
log_info ---icon 'π' ---status "$_file" 'Processing batch file'
# Add CSV header if output format is CSV
if [[ "$OUTPUT_FORMAT" == "csv" ]]; then
echo "Domain,Port,Subject,Issuer,Not Before,Not After,Days Until Expiry,Status"
fi
while IFS= read -r line; do
# Skip empty lines and comments
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
# Extract domain and optional port
local _domain _port
if [[ "$line" =~ ^([^:]+):([0-9]+)$ ]]; then
_domain="${BASH_REMATCH[1]}"
_port="${BASH_REMATCH[2]}"
else
_domain="$line"
_port="$DEFAULT_PORT"
fi
# Validate domain
if ! validate_domain "$_domain"; then
continue
fi
# Check certificate
if ! get_certificate_info "$_domain" "$_port"; then
log_warning 'Failed to check certificate for %s:%d' "$_domain" "$_port"
fi
# Add separator for text format
if [[ "$OUTPUT_FORMAT" == "text" ]]; then
echo "----------------------------------------"
fi
done <"$_file"
}
# Parse command line arguments
parse_arguments() {
while [[ $# -gt 0 ]]; do
case "$1" in
-p | --port)
PORT="$2"
if ! [[ "$PORT" =~ ^[0-9]+$ ]] || [[ "$PORT" -lt 1 ]] || [[ "$PORT" -gt 65535 ]]; then
die 1 'Invalid port number: %d' "$PORT"
fi
shift 2
;;
-t | --timeout)
TIMEOUT="$2"
if ! [[ "$TIMEOUT" =~ ^[0-9]+$ ]] || [[ "$TIMEOUT" -lt 1 ]]; then
die 1 'Invalid timeout value: %s' "$TIMEOUT"
fi
shift 2
;;
-b | --batch)
BATCH_MODE=1
shift
;;
-f | --format)
OUTPUT_FORMAT="$2"
if [[ "$OUTPUT_FORMAT" != "text" && "$OUTPUT_FORMAT" != "json" && "$OUTPUT_FORMAT" != "csv" ]]; then
die 1 'Invalid output format: %s' "$OUTPUT_FORMAT"
fi
shift 2
;;
-r | --revocation)
CHECK_REVOCATION=1
shift
;;
-h | --help)
show_usage
;;
-u | --update)
update_application
;;
-*)
log_error 'Unknown option: <b>%s</b>' "$1"
echo
show_usage
;;
*)
if [[ -z "$DOMAIN_NAME" ]]; then
DOMAIN_NAME="$1"
else
die 1 'Multiple domains specified. Use batch mode for multiple domains.'
fi
shift
;;
esac
done
}
check_dependencies openssl
parse_arguments "$@"
if [[ -z "$DOMAIN_NAME" && "$BATCH_MODE" -eq 0 ]]; then
log_error 'No domain specified'
echo
show_usage
fi
log_title 'SSL Certificate Checker'
# Process batch mode
if [[ "$BATCH_MODE" -eq 1 ]]; then
if [[ -z "$DOMAIN_NAME" ]]; then
log_error 'Batch mode requires a file path'
echo
show_usage
fi
process_batch "$DOMAIN_NAME"
return 0
fi
# Validate domain
if ! validate_domain "$DOMAIN_NAME"; then
exit 1
fi
# Check certificate
if ! get_certificate_info "$DOMAIN_NAME" "$PORT"; then
exit 1
fi
if [[ "$CHECK_REVOCATION" -eq 1 ]]; then
echo
check_certificate_revocation "$DOMAIN_NAME" "$PORT"
fi
}
# Run main function
main "$@"