summaryrefslogtreecommitdiffstats
path: root/scripts/certs/cert-check
blob: a19e79ce45323e5c151321a1ec845202d0b9e0e2 (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
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
#!/bin/bash

# trivial check if server cert is OK incl. best effort to download
# referenced certificates and CRLs in chain
#
# jpokorny@redhat.com
#
# TODO:
# - currently, only cl[tl] files supported, not immediate PEM etc.;
#   also any reference to external resource has to start with URI
#   (is it a convention or a single case?)
# - couldn't get rid of dependency on temporary file as it is read
#   twice in two substituted commands and neither env. variable nor
#   file descriptor sharing is suitable (stdin can be read only once,
#   generally, there is a race between the two?)
# - wget vs. certificates? switch to curl?
# - remove unneeded subshells? ( '()' -> '{}' )
# - p7s: https://lists.fedoraproject.org/pipermail/devel/2013-February/178272.html

CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt
HOMEBUNDLE=~/.pki/tls/certs/ca-bundle.crt
#WGET="wget -nv -U '' --ca-certificate <(cat "${CA_BUNDLE}" "${HOMEBUNDLE}")"
WGET="wget -nv -U ''"

guess_inform() {
	case "{1##*.}" in
		der)       echo DER;;
		crt|pem|*) echo PEM;;
	esac
}

guess_vercmd() {
	echo "$1" | grep -q 'X509 CRL' && echo 'crl' || echo 'verify'
}

vercmd2cmd() {
	echo "$1" | grep -q 'crl' && echo 'crl' || echo 'x509'
}

guess_cmd() {
	case "$(basename "$1")" in
		crl*|revoke*|*crl) echo "crl";;
		*crt|*)            echo "x509";;
	esac
}
# export because it is used in a separate bash invocation
export -f guess_cmd

cert_pick_file() {
	[[ "$1" =~ .*://.* ]] && return 1
	echo "Trying file" >&2
	local inform=$(guess_inform "$1")
	local cmd=$(guess_cmd "$1")
	[ -f "$1" ] && openssl ${cmd} -inform "${inform}" -in "$1"
}

# when CA cert is hosted on https server signed by this very CA
cert_pick_url_selfsigned() {
	[[ "$1" =~ https://.* ]] || return 1
	echo "Trying self-signed" >&2
	local ret=
	local start=${1##https://}
	local host=${start%%/*}
	local machine=${host%%:*}
	local port=${host#*:}
	[ "${port}" = "${machine}" ] && port=443
	local cont=${start#*/}
	local inform=$(guess_inform "${cont}")
	[ "$(guess_cmd "${cont}")" = "x509" ] || return $?
	( echo -e "GET /${cont} /HTTP 1.0\n"; sleep 2 ) \
	  | openssl s_client -connect "${machine}:${port}" -crlf 2>/dev/null \
	  | sed -ne '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' \
	  | ( local tmpfile=$(mktemp /tmp/.XXXXXX)
	      cat >${tmpfile}
	      openssl verify -CAfile \
	        <(awk '/-BEGIN CERTIFICATE-/{if(++i > 2){print; exit;}}{if(i == 2){print;}}' ${tmpfile} \
                  | cat "${CA_BUNDLE}" "${HOMEBUNDLE}" -) \
	        <(awk '/-BEGIN CERTIFICATE-/{if(++i > 2){exit;}}{if(i == 1){print;}}' ${tmpfile}) >&2;
	      ret=$?
	      [ $ret -eq 0 ] \
	        && openssl x509 -inform "${inform}" -in ${tmpfile}
	      rm -- ${tmpfile}
	      return $ret )
}

cert_pick_url() {
	echo "Trying URL.." >&2
	local inform=$(guess_inform "$1")
	local cmd=$(guess_cmd "$1")
	(if ! ${WGET} "$1" -O- && [[ "$1" =~ https://.* ]]; then
		local start=${1##https://}
		local host=${start%%/*}
		local machine=${host%%:*}
		local port=${host#*:}
		[ "${port}" = "${machine}" ] && port=443
		( echo ">>> recursion" >&2
		  cert_pick_check "${machine}" "${port}" \
		  || cert_pick_check -nocrl "${machine}" "${port}"
		  echo "<<< recursion" >&2 ) >&2 \
		  && ${WGET} --no-check-certificate "$1" -O-
	fi) | openssl ${cmd} -inform "${inform}"
}

cert_pick_from_server() {
	echo "Trying from server.." >&2
	local server=$1
	local port=443  # https
	local opts=
	[ $# -ge 2 ] && [[ "${2}" =~ ^[^-].* ]] && shift && port=$1
	[ $# -ge 2 ] && opts=$2
	( echo; sleep 2 ) \
	  | openssl s_client -connect "${server}:${port}" -crlf $opts 2>/dev/null \
	  | sed -ne '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' \
	  | awk '/-BEGIN CERTIFICATE-/{if(++i > 1){exit;}}{print;}'
}

cert_pick() {
	cert_pick_file "$@" \
	  || cert_pick_url_selfsigned "$@" \
	  || cert_pick_url "$@" \
	  || cert_pick_from_server "$@"
}

cert_check() {
	local ret= tmpfile=$(mktemp /tmp/.XXXXXX)
	cat >${tmpfile}
	export vercmd=$(local fst; read fst < <(head -n1 "${tmpfile}"); guess_vercmd "${fst}")
	  openssl ${vercmd} $([ "$1" != "0" ] && echo '-crl_check') -CAfile \
	    <( openssl $(vercmd2cmd "${vercmd}") -noout -text -in ${tmpfile} \
	      | sed -n 's|.*URI:\(.\+\)|\1|p' \
	      | xargs -I '{}' bash -c "case \$(guess_cmd '{}') in \
	          verify) ${WGET} -O- '{}' | openssl x509 -outform PEM;; \
	          crl)    ${WGET} -O- '{}' | openssl crl -outform PEM;; \
	          *)      echo 'Sorry, URI {} not supported' >&2;; \
	          esac" \
	      | cat "${CA_BUNDLE}" "${HOMEBUNDLE}" - 2>/dev/null ) \
	   $(echo "${vercmd}" | grep -q crl && echo '-in') ${tmpfile} >&2
	  ret=$?
	  [ $ret -eq 0 ] && cat ${tmpfile}
	unset vercmd
	rm -- ${tmpfile}
	echo "$ret" >&2
	return $ret
}

colorize() {
	# last line = exitcode
	( ( test -t 1 || [ $# -ge 1 ] ) \
	  && sed -u \
	    -e 's|\(^Trying.*$\)|\x1b[33m\1\x1b[0m|' \
	    -e 's|\(^Adding.*$\)|\x1b[33m\1\x1b[0m|' \
	    -e 's|\(^error\s\+.*\)|\x1b[31m\1\x1b[0m|' \
	    -e 's|\(^\S\+:.*\)|\x1b[32m\1\x1b[0m|' \
	    -e 's|\(^\S\+\s\+\S\+\s\+URL:.*\)|\x1b[36m\1\x1b[0m|' \
	    || cat ) | awk 'FNR == 1 { last=$1; while (getline) { print last; last=$0; } exit last}'
}

cert_pick_check() {
	local crl=1
	[ "$1" = "-nocrl" ] && shift && crl=0
	cert_pick "$@" | cert_check $crl
}

setup() {
	set -u
	RESTOREUMASK=$(umask -p)
	umask 077
}

teardown() {
	${RESTOREUMASK}
	unset RESTOREUMASK
	unset vercmd
	return $1
}

[[ "${BASH_SOURCE[0]}" != "${0}" ]] || \
  { [ $# -lt 1 ] \
    && echo "usage: $0" \
            "[-nocrl] file-or-url-or-server [server-port=443]" \
    || { setup; { cert_pick_check "$@"; echo $?; } |& colorize 1; teardown $?; }
  }