bin/*: Use plain `/usr/bin/tclsh' in shebang lines.
[ca] / bin / check
CommitLineData
1f46e042 1#! /usr/bin/tclsh
69ab55f7
MW
2### -*-tcl-*-
3###
4### Check that the certificate authority database and files are consistent.
5###
6### (c) 2011 Mark Wooding
7###
8
9###----- Licensing notice ---------------------------------------------------
10###
11### This program is free software; you can redistribute it and/or modify
12### it under the terms of the GNU General Public License as published by
13### the Free Software Foundation; either version 2 of the License, or
14### (at your option) any later version.
15###
16### This program is distributed in the hope that it will be useful,
17### but WITHOUT ANY WARRANTY; without even the implied warranty of
18### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19### GNU General Public License for more details.
20###
21### You should have received a copy of the GNU General Public License
22### along with this program; if not, write to the Free Software Foundation,
23### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24
25## Find the common utilities.
26source [file join [file dirname $argv0] "../lib/func.tcl"]
27
28## Open the database
29sqlite3 db "$CERTROOT/state/ca.db"
30db nullvalue nil
31
32## Build a map of the active requests. Verify that active requests have
33## distinct tags.
34array unset actreq
35array unset complain
36foreach {tag id} [db eval {
37 SELECT tag, id FROM request WHERE st = 'active';
38}] {
39 if {[info exists actreq($tag)] && ![info exists complain(dup-req-$tag)]} {
40 bad 4 "multiple active requests with tag `$tag' ($id and $actreq($tag))"
41 set complain(dup-req-$tag) 1
42 } else {
43 set actreq($tag) $id
44 }
45}
46
47## Go through the active certificates. Each one should tie up to an active
48## request. We don't check here that the request exists at all: that gets
49## done later.
50array unset actcert
51foreach {seq tag st} [db eval {
52 SELECT c.seq, r.tag, r.st
53 FROM certificate AS c JOIN request AS r ON c.req = r.id
54 WHERE c.st = 'active';
55}] {
56 if {[info exists actcert($tag)] &&
57 ![info exists complain(dup-cert-$tag)]} {
58 bad 4 [join {
59 "multiple active certificates with "
60 "tag `$tag' ($seq and $actcert($tag))"} ""]
61 continue
62 }
63 set actcert($tag) $seq
64 if {![string equal $st active]} {
65 bad 2 [join {
66 "active cert $seq associated with "
67 "request $id (`$tag') which is $st not active"} ""]
68 }
69}
70
71## Check that the certificates for a revoked request are revoked or
72## expired.
73foreach {seq id tag st} [db eval {
74 SELECT c.seq, r.id, r.tag, c.st
75 FROM certificate AS c JOIN request AS r ON c.req = r.id
76 WHERE r.st = 'revoked' AND c.st != 'revoked' AND c.st != 'expired';
77}] {
78 bad 4 "cert $seq for revoked request $id (`$tag') is $st not revoked"
79}
80
81## Similarly, check that revoked certificates match up with revoked
82## requests.
83foreach {seq id tag st} [db eval {
84 SELECT c.seq, r.id, r.tag, c.st
85 FROM certificate AS c JOIN request AS r ON c.req = r.id
86 WHERE c.st = 'revoked' AND r.st != 'revoked';
87}] {
88 bad 2 [join {
89 "revoked cert $seq associated with "
90 "request $id (`$tag') which is $st not revoked"} ""]
91}
92
93## Check that the active symlinks are correct.
94foreach {what key dir actvar} {
95 "request" "id" "req" actreq
96 "certificate" "seq" "cert" actcert
97} {
98 upvar 0 $actvar act
99
100 ## Check that there's a symlink DIR/active/TAG for each active item, and
101 ## that it points to the correct item.
102 foreach tag [array names act] {
103 set link "$CERTROOT/$dir/active/$tag"
104 set id $act($tag)
105 if {![file exists $link]} {
106 bad 1 "missing symlink for active $what `$tag' ($key = $id)"
107 } elseif {![string equal [file type $link] link]} {
108 bad 1 "entry for active $what `$tag' ($key = $id) isn't a link"
109 } elseif {![string equal [file readlink $link] "../by-$key/$id"]} {
110 bad 1 "link for active $what `$tag' ($key = $id) is wrong"
111 moan "\t(actually `[file readlink $link]'; should be `../by-$key/$id')"
112 }
113 }
114
115 ## Check that there aren't any other stray things.
116 foreach tag \
117 [glob -tails -directory "$CERTROOT/$dir/active" -nocomplain *] {
118 if {![info exists act($tag)]} {
119 bad 1 "bogus file `$dir/active/$tag'"
120 }
121 }
122}
123
124## Now run through all of the requests and check that they match the
125## corresponding request files.
126array unset reqmap
127foreach {id tag st dn hash} [db eval {
128 SELECT id, tag, st, dn, hash FROM request;
129}] {
130 if {[info exists reqmap($id)]} {
131 bad 4 "duplicate request id $id"
132 continue
133 }
134 set reqmap($id) 1
135
136 switch -exact -- $st {
137 active - withdrawn - revoked { }
138 default {
139 bad 2 "request $id (`$tag') has unknown state `$st'"
140 }
141 }
142
143 set reqfile "$CERTROOT/req/by-id/$id"
144 if {![file exists $reqfile]} {
145 bad 4 "missing file for request $id (`$tag')"
146 continue
147 }
148
149 set req_dn [req-dn $reqfile]
150 if {![string equal $req_dn $dn]} {
151 bad 2 "request $id (`$tag') has DN mismatch"
152 moan "\t(db has dn = $dn)"
153 moan "\t(file has dn = $req_dn)"
154 }
155
156 set req_hash [req-key-hash $reqfile]
157 if {![string equal $req_hash $hash]} {
158 bad 2 "request $id (`$tag') has key hash mismatch"
159 moan "\t(db has hash = $hash)"
160 moan "\t(file has hash = $req_hash)"
161 }
162}
163
164## Run through all of the certificates and check that they match the
165## correspoding certificate files. This is a good opportunity to verify that
166## the certificates match up with requests.
167array unset certmap
168foreach {seq req tag st dn hash} [db eval {
169 SELECT c.seq, r.id, r.tag, c.st, r.cert_dn, r.hash
170 FROM certificate AS c LEFT OUTER JOIN request AS r ON c.req = r.id;
171}] {
172 if {[info exists certmap($seq)]} {
173 bad 4 "duplicate certificate serial number $seq"
174 continue
175 }
176 set certmap($seq) 1
177
178 if {[string equal $req nil]} {
179 bad 2 "certificate $seq has no certificate request"
180 }
181
182 switch -exact -- $st {
183 active - withdrawn - superceded - revoked - expired { }
184 default {
185 bad 2 "certificate $id (`$tag') has unknown state `$st'"
186 }
187 }
188
189 set certfile "$CERTROOT/cert/by-seq/$seq"
190 if {![file exists $certfile]} {
191 bad 4 "missing file for certficate $seq (`$tag')"
192 continue
193 }
194 if {[string equal $req nil]} { continue }
195
196 set cert_dn [cert-dn $certfile]
197 if {![string equal $dn $cert_dn]} {
198 bad 2 "certificate $seq (`$tag') has DN mismatch"
199 moan "\t(db has dn = $dn)"
200 moan "\t(file has dn = $cert_dn)"
201 }
202
203 set cert_hash [cert-key-hash $certfile]
204 if {![string equal $cert_hash $hash]} {
205 bad 2 "certificate $seq (`$tag') has key hash mismatch"
206 moan "\t(db has hash = $hash)"
207 moan "\t(file has hash = $cert_hash)"
208 }
209}
210
211## Finally, make sure that there aren't any stray files in those directories.
212foreach {dir mapvar} {
213 "req/by-id" reqmap
214 "cert/by-seq" certmap
215} {
216 upvar 0 $mapvar map
217 foreach file [glob -tails -directory "$CERTROOT/$dir" -nocomplain *] {
218 if {![info exists map($file)]} {
219 bad 1 "bogus file `$dir/$file'"
220 }
221 }
222}
223
224## Done!
225quit
226
227###----- That's all, folks --------------------------------------------------