summaryrefslogtreecommitdiff
path: root/cert-info
blob: e6e38fe259a05b136531931c6ca161fe5716689a (plain)
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
#!/bin/bash
# see also Docs/ssl-infos/tools/
# i= &&tail -n+$((i*5000)) /tmp/top-1m.csv | head -5000 |~/scripts/cert-info csv $i
# killall -v cert-info -s 0
usage() {
	[ -z "$1" ] || echo "$1" >&2
	echo "Usage: $0 [options] hostname[:port] [hostname[:port]].." >&2
	echo "       $0 csv out-dir <file [options]" >&2
	echo "options (may be interleaved between hosts):"
	awk '/^parse_arg\(/{p=1}
	/}/{if(p)exit}
	/#/{if(p){sub(/\).*# */,"\t");print}}' "$0"
	echo "csv file from http://s3.amazonaws.com/alexa-static/top-1m.csv.zip"
	exit 1
}

auto_less() {
	[ ! -t 1 ] || { "$0" "$@" | less; exit $?; }
}

# Compatibility for macOS
type timeout 2>/dev/null >&2 ||
timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; }

main() {
if [ "$1" = "csv" ]; then
	[ $# -ge 2 ] || usage "Missing outdir"
	outdir="${2:-.}/"
	mkdir -p "$outdir"
	tmp=$(mktemp); trap 'rm -f "$tmp"' EXIT
	shift 2
	for arg; do parse_arg "$arg" || usage "Unrecognized option $arg"; done
	i=0
	while IFS=,; read -r no host; do
		# strip path if any
		host="${host%%/*}"
		[ -n "$host" ] || continue
		outfile="$outdir$host.txt"
		if [ -e "$outfile" ]; then
			echo "Skipping existing $outfile"
		else
			printf "%5d %s %6d %s\n" $((++i)) "$(date -R)" "$no" "$host"
			get_cert "$host" 443 | parse_cert > "$tmp" 2>&1
			mv "$tmp" "$outfile"
			#[ -s "$outfile" ] || echo "# no cert for $host"
		fi
	done
elif [ ! -t 0 ]; then
	auto_less "$@"
	for arg; do
		parse_arg "$arg" || :
	done
	parse_cert
elif [ $# -gt 0 ]; then
	auto_less "$@"
	for arg; do
		! parse_arg "$arg" || continue
		host="${arg%%:*}"
		port="${arg##*:}"
		[ "$port" != "$arg" ] && [ -n "$port" ] || port=443
		echo "# === $host:$port ==="
		get_cert "$host" "$port" | parse_cert
	done
else
	usage
fi
}

get_cert() {
	local host="$1" port="$2"
	if ! nc -z -w 2 "$host" "$port" 2>/dev/null; then
		echo "# conn timeout for $host:$port!" >&2
		return 1
	fi
	</dev/null 2>/dev/null \
	timeout 5 openssl s_client \
		-connect "$host:$port" -servername "$host" -showcerts
}

parse_arg() {
	case "$1" in
	-) depth_1=1 ;; # less certificates [def]
	+) depth_1=0 ;; # more certificates
	-=) cert_only=1 ;; # less output (just raw cert)
	+=) cert_only=0 ;; # more output (verbose details) [def]
	-cert) include_cert=0 ;; # hide raw cert (-noout)
	+cert) include_cert=1 ;; # include raw cert [def]
	*) return 1 ;;
	esac
}
parse_arg -
parse_arg +=
parse_arg +cert

parse_cert() {
	infocmd="openssl x509 -text -nameopt sep_comma_plus_space"
	[ $include_cert -eq 1 ] || infocmd="$infocmd -noout"
	awk -v OneOnly="$depth_1" -v CertOnly="$cert_only" \
		-v infocmd="$infocmd" '
BEGIN {
	sep="# ";
	for (i=0; i<77; i++) {
		sep=sep"-";
	}
}
/^-----BEGIN (TRUSTED )?CERTIFICATE-----$/ {
	in_cert=1;
}
{
	if (in_cert) {
		cert = cert $0 "\n";
	}
}
/^-----END (TRUSTED )?CERTIFICATE-----$/ {
	in_cert = 0;
}
{
	if (!in_cert && cert) {
		if (CertOnly) {
			print cert;
		} else {
			print cert | infocmd;
			close(infocmd);
			print sep;
		}
		cert="";
		if (OneOnly) exit;
	}
}
'
}

main "$@"