diff options
author | Colin Watson <cjwatson@debian.org> | 2008-05-12 23:33:01 +0000 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2008-05-12 23:33:01 +0000 |
commit | 47608c17e64138f8d16aa2bdc49a0eb00e1c3549 (patch) | |
tree | 92572d90b9aa8f45c0d9e6dbb185065667fdcea0 /ssh-vulnkey.c | |
parent | 19ccea525446d5a3c2a176d813c505be81b91cbf (diff) |
* Mitigate OpenSSL security vulnerability:
- Add key blacklisting support. Keys listed in
/etc/ssh/blacklist.TYPE-LENGTH will be rejected for authentication by
sshd, unless "PermitBlacklistedKeys yes" is set in
/etc/ssh/sshd_config.
- Add a new program, ssh-vulnkey, which can be used to check keys
against these blacklists.
- Depend on openssh-blacklist.
- Force dependencies on libssl0.9.8 / libcrypto0.9.8-udeb to at least
0.9.8g-9.
- Automatically regenerate known-compromised host keys, with a
critical-priority debconf note. (I regret that there was no time to
gather translations.)
Diffstat (limited to 'ssh-vulnkey.c')
-rw-r--r-- | ssh-vulnkey.c | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/ssh-vulnkey.c b/ssh-vulnkey.c new file mode 100644 index 000000000..ba87cbd28 --- /dev/null +++ b/ssh-vulnkey.c | |||
@@ -0,0 +1,311 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2008 Canonical Ltd. All rights reserved. | ||
3 | * | ||
4 | * Redistribution and use in source and binary forms, with or without | ||
5 | * modification, are permitted provided that the following conditions | ||
6 | * are met: | ||
7 | * 1. Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * 2. Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * | ||
13 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | ||
14 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | ||
15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | ||
16 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | ||
17 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | ||
18 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
19 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
20 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | ||
22 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
23 | */ | ||
24 | |||
25 | #include "includes.h" | ||
26 | |||
27 | #include <sys/types.h> | ||
28 | #include <sys/stat.h> | ||
29 | |||
30 | #include <string.h> | ||
31 | #include <stdio.h> | ||
32 | #include <fcntl.h> | ||
33 | #include <unistd.h> | ||
34 | |||
35 | #include <openssl/evp.h> | ||
36 | |||
37 | #include "xmalloc.h" | ||
38 | #include "ssh.h" | ||
39 | #include "log.h" | ||
40 | #include "key.h" | ||
41 | #include "authfile.h" | ||
42 | #include "pathnames.h" | ||
43 | #include "misc.h" | ||
44 | |||
45 | extern char *__progname; | ||
46 | |||
47 | /* Default files to check */ | ||
48 | static char *default_host_files[] = { | ||
49 | _PATH_HOST_RSA_KEY_FILE, | ||
50 | _PATH_HOST_DSA_KEY_FILE, | ||
51 | _PATH_HOST_KEY_FILE, | ||
52 | NULL | ||
53 | }; | ||
54 | static char *default_files[] = { | ||
55 | _PATH_SSH_CLIENT_ID_RSA, | ||
56 | _PATH_SSH_CLIENT_ID_DSA, | ||
57 | _PATH_SSH_CLIENT_IDENTITY, | ||
58 | _PATH_SSH_USER_PERMITTED_KEYS, | ||
59 | _PATH_SSH_USER_PERMITTED_KEYS2, | ||
60 | NULL | ||
61 | }; | ||
62 | |||
63 | static int quiet = 0; | ||
64 | |||
65 | static void | ||
66 | usage(void) | ||
67 | { | ||
68 | fprintf(stderr, "usage: %s [-aq] [file ...]\n", __progname); | ||
69 | fprintf(stderr, "Options:\n"); | ||
70 | fprintf(stderr, " -a Check keys of all users.\n"); | ||
71 | fprintf(stderr, " -q Quiet mode.\n"); | ||
72 | exit(1); | ||
73 | } | ||
74 | |||
75 | void | ||
76 | describe_key(const char *msg, const Key *key, const char *comment) | ||
77 | { | ||
78 | char *fp; | ||
79 | |||
80 | fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); | ||
81 | if (!quiet) | ||
82 | printf("%s: %u %s %s\n", msg, key_size(key), fp, comment); | ||
83 | xfree(fp); | ||
84 | } | ||
85 | |||
86 | int | ||
87 | do_key(const Key *key, const char *comment) | ||
88 | { | ||
89 | char *blacklist_file; | ||
90 | struct stat st; | ||
91 | int ret = 1; | ||
92 | |||
93 | blacklist_file = blacklist_filename(key); | ||
94 | if (stat(blacklist_file, &st) < 0) | ||
95 | describe_key("Unknown (no blacklist information)", | ||
96 | key, comment); | ||
97 | else if (blacklisted_key(key)) { | ||
98 | describe_key("COMPROMISED", key, comment); | ||
99 | ret = 0; | ||
100 | } else | ||
101 | describe_key("Not blacklisted", key, comment); | ||
102 | xfree(blacklist_file); | ||
103 | |||
104 | return ret; | ||
105 | } | ||
106 | |||
107 | int | ||
108 | do_filename(const char *filename, int quiet_open) | ||
109 | { | ||
110 | FILE *f; | ||
111 | char line[SSH_MAX_PUBKEY_BYTES]; | ||
112 | char *cp; | ||
113 | u_long linenum = 0; | ||
114 | Key *key; | ||
115 | char *comment = NULL; | ||
116 | int found = 0, ret = 1; | ||
117 | |||
118 | /* Copy much of key_load_public's logic here so that we can read | ||
119 | * several keys from a single file (e.g. authorized_keys). | ||
120 | */ | ||
121 | |||
122 | if (strcmp(filename, "-") != 0) { | ||
123 | f = fopen(filename, "r"); | ||
124 | if (!f) { | ||
125 | char pubfile[MAXPATHLEN]; | ||
126 | if (strlcpy(pubfile, filename, sizeof pubfile) < | ||
127 | sizeof(pubfile) && | ||
128 | strlcat(pubfile, ".pub", sizeof pubfile) < | ||
129 | sizeof(pubfile)) | ||
130 | f = fopen(pubfile, "r"); | ||
131 | } | ||
132 | if (!f) { | ||
133 | if (!quiet_open) | ||
134 | perror(filename); | ||
135 | return -1; | ||
136 | } | ||
137 | } else | ||
138 | f = stdin; | ||
139 | while (read_keyfile_line(f, filename, line, sizeof(line), | ||
140 | &linenum) != -1) { | ||
141 | cp = line; | ||
142 | switch (*cp) { | ||
143 | case '#': | ||
144 | case '\n': | ||
145 | case '\0': | ||
146 | continue; | ||
147 | } | ||
148 | /* Skip leading whitespace. */ | ||
149 | for (; *cp && (*cp == ' ' || *cp == '\t'); cp++) | ||
150 | ; | ||
151 | /* Cope with ssh-keyscan output. */ | ||
152 | comment = NULL; | ||
153 | if (*cp) { | ||
154 | char *space; | ||
155 | int type; | ||
156 | |||
157 | space = strchr(cp, ' '); | ||
158 | if (!space) | ||
159 | continue; | ||
160 | *space = '\0'; | ||
161 | type = key_type_from_name(cp); | ||
162 | if (type == KEY_UNSPEC) { | ||
163 | comment = xstrdup(cp); | ||
164 | cp = space + 1; | ||
165 | } | ||
166 | *space = ' '; | ||
167 | } | ||
168 | if (!comment) | ||
169 | comment = xstrdup(filename); | ||
170 | if (*cp) { | ||
171 | key = key_new(KEY_RSA1); | ||
172 | if (key_read(key, &cp) == 1) { | ||
173 | if (!do_key(key, comment)) | ||
174 | ret = 0; | ||
175 | key_free(key); | ||
176 | found = 1; | ||
177 | } else { | ||
178 | key_free(key); | ||
179 | key = key_new(KEY_UNSPEC); | ||
180 | if (key_read(key, &cp) == 1) { | ||
181 | if (!do_key(key, comment)) | ||
182 | ret = 0; | ||
183 | key_free(key); | ||
184 | found = 1; | ||
185 | } | ||
186 | } | ||
187 | } | ||
188 | xfree(comment); | ||
189 | comment = NULL; | ||
190 | } | ||
191 | if (f != stdin) | ||
192 | fclose(f); | ||
193 | |||
194 | if (!found && filename) { | ||
195 | key = key_load_public(filename, &comment); | ||
196 | if (key) { | ||
197 | if (!do_key(key, comment)) | ||
198 | ret = 0; | ||
199 | found = 1; | ||
200 | } | ||
201 | if (comment) | ||
202 | xfree(comment); | ||
203 | } | ||
204 | |||
205 | return ret; | ||
206 | } | ||
207 | |||
208 | int | ||
209 | do_host(void) | ||
210 | { | ||
211 | int i; | ||
212 | struct stat st; | ||
213 | int ret = 1; | ||
214 | |||
215 | for (i = 0; default_host_files[i]; i++) { | ||
216 | if (stat(default_host_files[i], &st) < 0) | ||
217 | continue; | ||
218 | if (!do_filename(default_host_files[i], 1)) | ||
219 | ret = 0; | ||
220 | } | ||
221 | |||
222 | return ret; | ||
223 | } | ||
224 | |||
225 | int | ||
226 | do_user(const char *dir) | ||
227 | { | ||
228 | int i; | ||
229 | char buf[MAXPATHLEN]; | ||
230 | struct stat st; | ||
231 | int ret = 1; | ||
232 | |||
233 | for (i = 0; default_files[i]; i++) { | ||
234 | snprintf(buf, sizeof(buf), "%s/%s", dir, default_files[i]); | ||
235 | if (stat(buf, &st) < 0) | ||
236 | continue; | ||
237 | if (!do_filename(buf, 0)) | ||
238 | ret = 0; | ||
239 | } | ||
240 | |||
241 | return ret; | ||
242 | } | ||
243 | |||
244 | int | ||
245 | main(int argc, char **argv) | ||
246 | { | ||
247 | int opt, all_users = 0; | ||
248 | int ret = 1; | ||
249 | extern int optind; | ||
250 | |||
251 | /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ | ||
252 | sanitise_stdfd(); | ||
253 | |||
254 | __progname = ssh_get_progname(argv[0]); | ||
255 | |||
256 | SSLeay_add_all_algorithms(); | ||
257 | log_init(argv[0], SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_USER, 1); | ||
258 | |||
259 | /* We don't need the RNG ourselves, but symbol references here allow | ||
260 | * ld to link us properly. | ||
261 | */ | ||
262 | init_rng(); | ||
263 | seed_rng(); | ||
264 | |||
265 | while ((opt = getopt(argc, argv, "ahq")) != -1) { | ||
266 | switch (opt) { | ||
267 | case 'a': | ||
268 | all_users = 1; | ||
269 | break; | ||
270 | case 'q': | ||
271 | quiet = 1; | ||
272 | break; | ||
273 | case 'h': | ||
274 | default: | ||
275 | usage(); | ||
276 | } | ||
277 | } | ||
278 | |||
279 | if (all_users) { | ||
280 | struct passwd *pw; | ||
281 | |||
282 | if (!do_host()) | ||
283 | ret = 0; | ||
284 | |||
285 | while ((pw = getpwent()) != NULL) { | ||
286 | if (pw->pw_dir) { | ||
287 | if (!do_user(pw->pw_dir)) | ||
288 | ret = 0; | ||
289 | } | ||
290 | } | ||
291 | } else if (optind == argc) { | ||
292 | struct passwd *pw; | ||
293 | |||
294 | if (!do_host()) | ||
295 | ret = 0; | ||
296 | |||
297 | if ((pw = getpwuid(getuid())) == NULL) | ||
298 | fprintf(stderr, "No user found with uid %u\n", | ||
299 | (u_int)getuid()); | ||
300 | else { | ||
301 | if (!do_user(pw->pw_dir)) | ||
302 | ret = 0; | ||
303 | } | ||
304 | } else { | ||
305 | while (optind < argc) | ||
306 | if (!do_filename(argv[optind++], 0)) | ||
307 | ret = 0; | ||
308 | } | ||
309 | |||
310 | return ret; | ||
311 | } | ||