diff options
Diffstat (limited to 'debian/patches/ssh-vulnkey.patch')
-rw-r--r-- | debian/patches/ssh-vulnkey.patch | 1376 |
1 files changed, 1376 insertions, 0 deletions
diff --git a/debian/patches/ssh-vulnkey.patch b/debian/patches/ssh-vulnkey.patch new file mode 100644 index 000000000..3e4e96493 --- /dev/null +++ b/debian/patches/ssh-vulnkey.patch | |||
@@ -0,0 +1,1376 @@ | |||
1 | Index: b/Makefile.in | ||
2 | =================================================================== | ||
3 | --- a/Makefile.in | ||
4 | +++ b/Makefile.in | ||
5 | @@ -26,6 +26,7 @@ | ||
6 | SFTP_SERVER=$(libexecdir)/sftp-server | ||
7 | SSH_KEYSIGN=$(libexecdir)/ssh-keysign | ||
8 | RAND_HELPER=$(libexecdir)/ssh-rand-helper | ||
9 | +SSH_DATADIR=$(datadir)/ssh | ||
10 | PRIVSEP_PATH=@PRIVSEP_PATH@ | ||
11 | SSH_PRIVSEP_USER=@SSH_PRIVSEP_USER@ | ||
12 | STRIP_OPT=@STRIP_OPT@ | ||
13 | @@ -37,7 +38,8 @@ | ||
14 | -D_PATH_SSH_KEY_SIGN=\"$(SSH_KEYSIGN)\" \ | ||
15 | -D_PATH_SSH_PIDDIR=\"$(piddir)\" \ | ||
16 | -D_PATH_PRIVSEP_CHROOT_DIR=\"$(PRIVSEP_PATH)\" \ | ||
17 | - -DSSH_RAND_HELPER=\"$(RAND_HELPER)\" | ||
18 | + -DSSH_RAND_HELPER=\"$(RAND_HELPER)\" \ | ||
19 | + -D_PATH_SSH_DATADIR=\"$(SSH_DATADIR)\" | ||
20 | |||
21 | CC=@CC@ | ||
22 | LD=@LD@ | ||
23 | @@ -60,7 +62,7 @@ | ||
24 | INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@ | ||
25 | INSTALL_SSH_RAND_HELPER=@INSTALL_SSH_RAND_HELPER@ | ||
26 | |||
27 | -TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT) | ||
28 | +TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-vulnkey$(EXEEXT) | ||
29 | |||
30 | LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o buffer.o \ | ||
31 | canohost.o channels.o cipher.o cipher-acss.o cipher-aes.o \ | ||
32 | @@ -91,8 +93,8 @@ | ||
33 | audit.o audit-bsm.o platform.o sftp-server.o sftp-common.o \ | ||
34 | roaming_common.o | ||
35 | |||
36 | -MANPAGES = moduli.5.out scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out ssh-rand-helper.8.out ssh-keysign.8.out sshd_config.5.out ssh_config.5.out | ||
37 | -MANPAGES_IN = moduli.5 scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 ssh-rand-helper.8 ssh-keysign.8 sshd_config.5 ssh_config.5 | ||
38 | +MANPAGES = moduli.5.out scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out ssh-rand-helper.8.out ssh-keysign.8.out ssh-vulnkey.1.out sshd_config.5.out ssh_config.5.out | ||
39 | +MANPAGES_IN = moduli.5 scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 ssh-rand-helper.8 ssh-keysign.8 ssh-vulnkey.1 sshd_config.5 ssh_config.5 | ||
40 | MANTYPE = @MANTYPE@ | ||
41 | |||
42 | CONFIGFILES=sshd_config.out ssh_config.out moduli.out | ||
43 | @@ -169,6 +171,9 @@ | ||
44 | ssh-rand-helper${EXEEXT}: $(LIBCOMPAT) libssh.a ssh-rand-helper.o | ||
45 | $(LD) -o $@ ssh-rand-helper.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) | ||
46 | |||
47 | +ssh-vulnkey$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-vulnkey.o | ||
48 | + $(LD) -o $@ ssh-vulnkey.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) | ||
49 | + | ||
50 | # test driver for the loginrec code - not built by default | ||
51 | logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o | ||
52 | $(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS) | ||
53 | @@ -268,6 +273,7 @@ | ||
54 | $(INSTALL) -m 4711 $(STRIP_OPT) ssh-keysign $(DESTDIR)$(SSH_KEYSIGN) | ||
55 | $(INSTALL) -m 0755 $(STRIP_OPT) sftp $(DESTDIR)$(bindir)/sftp | ||
56 | $(INSTALL) -m 0755 $(STRIP_OPT) sftp-server $(DESTDIR)$(SFTP_SERVER) | ||
57 | + $(INSTALL) -m 0755 $(STRIP_OPT) ssh-vulnkey $(DESTDIR)$(bindir)/ssh-vulnkey | ||
58 | $(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1 | ||
59 | $(INSTALL) -m 644 scp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1 | ||
60 | $(INSTALL) -m 644 ssh-add.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1 | ||
61 | @@ -284,6 +290,7 @@ | ||
62 | $(INSTALL) -m 644 sftp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1 | ||
63 | $(INSTALL) -m 644 sftp-server.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8 | ||
64 | $(INSTALL) -m 644 ssh-keysign.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8 | ||
65 | + $(INSTALL) -m 644 ssh-vulnkey.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1 | ||
66 | -rm -f $(DESTDIR)$(bindir)/slogin | ||
67 | ln -s ./ssh$(EXEEXT) $(DESTDIR)$(bindir)/slogin | ||
68 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1 | ||
69 | @@ -365,6 +372,7 @@ | ||
70 | -rm -f $(DESTDIR)$(bindir)/ssh-agent$(EXEEXT) | ||
71 | -rm -f $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT) | ||
72 | -rm -f $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT) | ||
73 | + -rm -f $(DESTDIR)$(bindir)/ssh-vulnkey$(EXEEXT) | ||
74 | -rm -f $(DESTDIR)$(bindir)/sftp$(EXEEXT) | ||
75 | -rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT) | ||
76 | -rm -r $(DESTDIR)$(SFTP_SERVER)$(EXEEXT) | ||
77 | @@ -377,6 +385,7 @@ | ||
78 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1 | ||
79 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1 | ||
80 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keyscan.1 | ||
81 | + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1 | ||
82 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8 | ||
83 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-rand-helper.8 | ||
84 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8 | ||
85 | Index: b/auth-rh-rsa.c | ||
86 | =================================================================== | ||
87 | --- a/auth-rh-rsa.c | ||
88 | +++ b/auth-rh-rsa.c | ||
89 | @@ -44,6 +44,9 @@ | ||
90 | { | ||
91 | HostStatus host_status; | ||
92 | |||
93 | + if (reject_blacklisted_key(client_host_key, 0) == 1) | ||
94 | + return 0; | ||
95 | + | ||
96 | /* Check if we would accept it using rhosts authentication. */ | ||
97 | if (!auth_rhosts(pw, cuser)) | ||
98 | return 0; | ||
99 | Index: b/auth-rsa.c | ||
100 | =================================================================== | ||
101 | --- a/auth-rsa.c | ||
102 | +++ b/auth-rsa.c | ||
103 | @@ -246,6 +246,9 @@ | ||
104 | "actual %d vs. announced %d.", | ||
105 | file, linenum, BN_num_bits(key->rsa->n), bits); | ||
106 | |||
107 | + if (reject_blacklisted_key(key, 0) == 1) | ||
108 | + continue; | ||
109 | + | ||
110 | /* We have found the desired key. */ | ||
111 | /* | ||
112 | * If our options do not allow this key to be used, | ||
113 | Index: b/auth.c | ||
114 | =================================================================== | ||
115 | --- a/auth.c | ||
116 | +++ b/auth.c | ||
117 | @@ -59,6 +59,7 @@ | ||
118 | #include "servconf.h" | ||
119 | #include "key.h" | ||
120 | #include "hostfile.h" | ||
121 | +#include "authfile.h" | ||
122 | #include "auth.h" | ||
123 | #include "auth-options.h" | ||
124 | #include "canohost.h" | ||
125 | @@ -398,6 +399,38 @@ | ||
126 | return host_status; | ||
127 | } | ||
128 | |||
129 | +int | ||
130 | +reject_blacklisted_key(Key *key, int hostkey) | ||
131 | +{ | ||
132 | + char *fp; | ||
133 | + | ||
134 | + if (blacklisted_key(key, &fp) != 1) | ||
135 | + return 0; | ||
136 | + | ||
137 | + if (options.permit_blacklisted_keys) { | ||
138 | + if (hostkey) | ||
139 | + error("Host key %s blacklisted (see " | ||
140 | + "ssh-vulnkey(1)); continuing anyway", fp); | ||
141 | + else | ||
142 | + logit("Public key %s from %s blacklisted (see " | ||
143 | + "ssh-vulnkey(1)); continuing anyway", | ||
144 | + fp, get_remote_ipaddr()); | ||
145 | + xfree(fp); | ||
146 | + } else { | ||
147 | + if (hostkey) | ||
148 | + error("Host key %s blacklisted (see " | ||
149 | + "ssh-vulnkey(1))", fp); | ||
150 | + else | ||
151 | + logit("Public key %s from %s blacklisted (see " | ||
152 | + "ssh-vulnkey(1))", | ||
153 | + fp, get_remote_ipaddr()); | ||
154 | + xfree(fp); | ||
155 | + return 1; | ||
156 | + } | ||
157 | + | ||
158 | + return 0; | ||
159 | +} | ||
160 | + | ||
161 | |||
162 | /* | ||
163 | * Check a given file for security. This is defined as all components | ||
164 | Index: b/auth.h | ||
165 | =================================================================== | ||
166 | --- a/auth.h | ||
167 | +++ b/auth.h | ||
168 | @@ -178,6 +178,8 @@ | ||
169 | check_key_in_hostfiles(struct passwd *, Key *, const char *, | ||
170 | const char *, const char *); | ||
171 | |||
172 | +int reject_blacklisted_key(Key *, int); | ||
173 | + | ||
174 | /* hostkey handling */ | ||
175 | Key *get_hostkey_by_index(int); | ||
176 | Key *get_hostkey_by_type(int); | ||
177 | Index: b/auth2-hostbased.c | ||
178 | =================================================================== | ||
179 | --- a/auth2-hostbased.c | ||
180 | +++ b/auth2-hostbased.c | ||
181 | @@ -145,6 +145,9 @@ | ||
182 | HostStatus host_status; | ||
183 | int len; | ||
184 | |||
185 | + if (reject_blacklisted_key(key, 0) == 1) | ||
186 | + return 0; | ||
187 | + | ||
188 | resolvedname = get_canonical_hostname(options.use_dns); | ||
189 | ipaddr = get_remote_ipaddr(); | ||
190 | |||
191 | Index: b/auth2-pubkey.c | ||
192 | =================================================================== | ||
193 | --- a/auth2-pubkey.c | ||
194 | +++ b/auth2-pubkey.c | ||
195 | @@ -254,6 +254,9 @@ | ||
196 | int success; | ||
197 | char *file; | ||
198 | |||
199 | + if (reject_blacklisted_key(key, 0) == 1) | ||
200 | + return 0; | ||
201 | + | ||
202 | file = authorized_keys_file(pw); | ||
203 | success = user_key_allowed2(pw, key, file); | ||
204 | xfree(file); | ||
205 | Index: b/authfile.c | ||
206 | =================================================================== | ||
207 | --- a/authfile.c | ||
208 | +++ b/authfile.c | ||
209 | @@ -65,6 +65,7 @@ | ||
210 | #include "rsa.h" | ||
211 | #include "misc.h" | ||
212 | #include "atomicio.h" | ||
213 | +#include "pathnames.h" | ||
214 | |||
215 | /* Version identification string for SSH v1 identity files. */ | ||
216 | static const char authfile_id_string[] = | ||
217 | @@ -677,3 +678,140 @@ | ||
218 | key_free(pub); | ||
219 | return NULL; | ||
220 | } | ||
221 | + | ||
222 | +/* Scan a blacklist of known-vulnerable keys in blacklist_file. */ | ||
223 | +static int | ||
224 | +blacklisted_key_in_file(const Key *key, const char *blacklist_file, char **fp) | ||
225 | +{ | ||
226 | + int fd = -1; | ||
227 | + char *dgst_hex = NULL; | ||
228 | + char *dgst_packed = NULL, *p; | ||
229 | + int i; | ||
230 | + size_t line_len; | ||
231 | + struct stat st; | ||
232 | + char buf[256]; | ||
233 | + off_t start, lower, upper; | ||
234 | + int ret = 0; | ||
235 | + | ||
236 | + debug("Checking blacklist file %s", blacklist_file); | ||
237 | + fd = open(blacklist_file, O_RDONLY); | ||
238 | + if (fd < 0) { | ||
239 | + ret = -1; | ||
240 | + goto out; | ||
241 | + } | ||
242 | + | ||
243 | + dgst_hex = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); | ||
244 | + /* Remove all colons */ | ||
245 | + dgst_packed = xcalloc(1, strlen(dgst_hex) + 1); | ||
246 | + for (i = 0, p = dgst_packed; dgst_hex[i]; i++) | ||
247 | + if (dgst_hex[i] != ':') | ||
248 | + *p++ = dgst_hex[i]; | ||
249 | + /* Only compare least-significant 80 bits (to keep the blacklist | ||
250 | + * size down) | ||
251 | + */ | ||
252 | + line_len = strlen(dgst_packed + 12); | ||
253 | + if (line_len > 32) | ||
254 | + goto out; | ||
255 | + | ||
256 | + /* Skip leading comments */ | ||
257 | + start = 0; | ||
258 | + for (;;) { | ||
259 | + ssize_t r; | ||
260 | + char *newline; | ||
261 | + | ||
262 | + r = atomicio(read, fd, buf, sizeof(buf)); | ||
263 | + if (r <= 0) | ||
264 | + goto out; | ||
265 | + if (buf[0] != '#') | ||
266 | + break; | ||
267 | + | ||
268 | + newline = memchr(buf, '\n', sizeof(buf)); | ||
269 | + if (!newline) | ||
270 | + goto out; | ||
271 | + start += newline + 1 - buf; | ||
272 | + if (lseek(fd, start, SEEK_SET) < 0) | ||
273 | + goto out; | ||
274 | + } | ||
275 | + | ||
276 | + /* Initialise binary search record numbers */ | ||
277 | + if (fstat(fd, &st) < 0) | ||
278 | + goto out; | ||
279 | + lower = 0; | ||
280 | + upper = (st.st_size - start) / (line_len + 1); | ||
281 | + | ||
282 | + while (lower != upper) { | ||
283 | + off_t cur; | ||
284 | + int cmp; | ||
285 | + | ||
286 | + cur = lower + (upper - lower) / 2; | ||
287 | + | ||
288 | + /* Read this line and compare to digest; this is | ||
289 | + * overflow-safe since cur < max(off_t) / (line_len + 1) */ | ||
290 | + if (lseek(fd, start + cur * (line_len + 1), SEEK_SET) < 0) | ||
291 | + break; | ||
292 | + if (atomicio(read, fd, buf, line_len) != line_len) | ||
293 | + break; | ||
294 | + cmp = memcmp(buf, dgst_packed + 12, line_len); | ||
295 | + if (cmp < 0) { | ||
296 | + if (cur == lower) | ||
297 | + break; | ||
298 | + lower = cur; | ||
299 | + } else if (cmp > 0) { | ||
300 | + if (cur == upper) | ||
301 | + break; | ||
302 | + upper = cur; | ||
303 | + } else { | ||
304 | + debug("Found %s in blacklist", dgst_hex); | ||
305 | + ret = 1; | ||
306 | + break; | ||
307 | + } | ||
308 | + } | ||
309 | + | ||
310 | +out: | ||
311 | + if (dgst_packed) | ||
312 | + xfree(dgst_packed); | ||
313 | + if (ret != 1 && dgst_hex) { | ||
314 | + xfree(dgst_hex); | ||
315 | + dgst_hex = NULL; | ||
316 | + } | ||
317 | + if (fp) | ||
318 | + *fp = dgst_hex; | ||
319 | + if (fd >= 0) | ||
320 | + close(fd); | ||
321 | + return ret; | ||
322 | +} | ||
323 | + | ||
324 | +/* | ||
325 | + * Scan blacklists of known-vulnerable keys. If a vulnerable key is found, | ||
326 | + * its fingerprint is returned in *fp, unless fp is NULL. | ||
327 | + */ | ||
328 | +int | ||
329 | +blacklisted_key(const Key *key, char **fp) | ||
330 | +{ | ||
331 | + Key *public; | ||
332 | + char *blacklist_file; | ||
333 | + int ret, ret2; | ||
334 | + | ||
335 | + public = key_demote(key); | ||
336 | + if (public->type == KEY_RSA1) | ||
337 | + public->type = KEY_RSA; | ||
338 | + | ||
339 | + xasprintf(&blacklist_file, "%s.%s-%u", | ||
340 | + _PATH_BLACKLIST, key_type(public), key_size(public)); | ||
341 | + ret = blacklisted_key_in_file(public, blacklist_file, fp); | ||
342 | + xfree(blacklist_file); | ||
343 | + if (ret > 0) { | ||
344 | + key_free(public); | ||
345 | + return ret; | ||
346 | + } | ||
347 | + | ||
348 | + xasprintf(&blacklist_file, "%s.%s-%u", | ||
349 | + _PATH_BLACKLIST_CONFIG, key_type(public), key_size(public)); | ||
350 | + ret2 = blacklisted_key_in_file(public, blacklist_file, fp); | ||
351 | + xfree(blacklist_file); | ||
352 | + if (ret2 > ret) | ||
353 | + ret = ret2; | ||
354 | + | ||
355 | + key_free(public); | ||
356 | + return ret; | ||
357 | +} | ||
358 | Index: b/authfile.h | ||
359 | =================================================================== | ||
360 | --- a/authfile.h | ||
361 | +++ b/authfile.h | ||
362 | @@ -23,4 +23,6 @@ | ||
363 | Key *key_load_private_pem(int, int, const char *, char **); | ||
364 | int key_perm_ok(int, const char *); | ||
365 | |||
366 | +int blacklisted_key(const Key *key, char **fp); | ||
367 | + | ||
368 | #endif | ||
369 | Index: b/pathnames.h | ||
370 | =================================================================== | ||
371 | --- a/pathnames.h | ||
372 | +++ b/pathnames.h | ||
373 | @@ -18,6 +18,10 @@ | ||
374 | #define SSHDIR ETCDIR "/ssh" | ||
375 | #endif | ||
376 | |||
377 | +#ifndef _PATH_SSH_DATADIR | ||
378 | +#define _PATH_SSH_DATADIR "/usr/share/ssh" | ||
379 | +#endif | ||
380 | + | ||
381 | #ifndef _PATH_SSH_PIDDIR | ||
382 | #define _PATH_SSH_PIDDIR "/var/run" | ||
383 | #endif | ||
384 | @@ -43,6 +47,9 @@ | ||
385 | /* Backwards compatibility */ | ||
386 | #define _PATH_DH_PRIMES SSHDIR "/primes" | ||
387 | |||
388 | +#define _PATH_BLACKLIST _PATH_SSH_DATADIR "/blacklist" | ||
389 | +#define _PATH_BLACKLIST_CONFIG SSHDIR "/blacklist" | ||
390 | + | ||
391 | #ifndef _PATH_SSH_PROGRAM | ||
392 | #define _PATH_SSH_PROGRAM "/usr/bin/ssh" | ||
393 | #endif | ||
394 | Index: b/readconf.c | ||
395 | =================================================================== | ||
396 | --- a/readconf.c | ||
397 | +++ b/readconf.c | ||
398 | @@ -123,6 +123,7 @@ | ||
399 | oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication, | ||
400 | oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias, | ||
401 | oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication, | ||
402 | + oUseBlacklistedKeys, | ||
403 | oHostKeyAlgorithms, oBindAddress, oSmartcardDevice, | ||
404 | oClearAllForwardings, oNoHostAuthenticationForLocalhost, | ||
405 | oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, | ||
406 | @@ -152,6 +153,7 @@ | ||
407 | { "passwordauthentication", oPasswordAuthentication }, | ||
408 | { "kbdinteractiveauthentication", oKbdInteractiveAuthentication }, | ||
409 | { "kbdinteractivedevices", oKbdInteractiveDevices }, | ||
410 | + { "useblacklistedkeys", oUseBlacklistedKeys }, | ||
411 | { "rsaauthentication", oRSAAuthentication }, | ||
412 | { "pubkeyauthentication", oPubkeyAuthentication }, | ||
413 | { "dsaauthentication", oPubkeyAuthentication }, /* alias */ | ||
414 | @@ -459,6 +461,10 @@ | ||
415 | intptr = &options->challenge_response_authentication; | ||
416 | goto parse_flag; | ||
417 | |||
418 | + case oUseBlacklistedKeys: | ||
419 | + intptr = &options->use_blacklisted_keys; | ||
420 | + goto parse_flag; | ||
421 | + | ||
422 | case oGssAuthentication: | ||
423 | intptr = &options->gss_authentication; | ||
424 | goto parse_flag; | ||
425 | @@ -1048,6 +1054,7 @@ | ||
426 | options->kbd_interactive_devices = NULL; | ||
427 | options->rhosts_rsa_authentication = -1; | ||
428 | options->hostbased_authentication = -1; | ||
429 | + options->use_blacklisted_keys = -1; | ||
430 | options->batch_mode = -1; | ||
431 | options->check_host_ip = -1; | ||
432 | options->strict_host_key_checking = -1; | ||
433 | @@ -1150,6 +1157,8 @@ | ||
434 | options->rhosts_rsa_authentication = 0; | ||
435 | if (options->hostbased_authentication == -1) | ||
436 | options->hostbased_authentication = 0; | ||
437 | + if (options->use_blacklisted_keys == -1) | ||
438 | + options->use_blacklisted_keys = 0; | ||
439 | if (options->batch_mode == -1) | ||
440 | options->batch_mode = 0; | ||
441 | if (options->check_host_ip == -1) | ||
442 | Index: b/readconf.h | ||
443 | =================================================================== | ||
444 | --- a/readconf.h | ||
445 | +++ b/readconf.h | ||
446 | @@ -54,6 +54,7 @@ | ||
447 | int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ | ||
448 | char *kbd_interactive_devices; /* Keyboard-interactive auth devices. */ | ||
449 | int zero_knowledge_password_authentication; /* Try jpake */ | ||
450 | + int use_blacklisted_keys; /* If true, send */ | ||
451 | int batch_mode; /* Batch mode: do not ask for passwords. */ | ||
452 | int check_host_ip; /* Also keep track of keys for IP address */ | ||
453 | int strict_host_key_checking; /* Strict host key checking. */ | ||
454 | Index: b/servconf.c | ||
455 | =================================================================== | ||
456 | --- a/servconf.c | ||
457 | +++ b/servconf.c | ||
458 | @@ -99,6 +99,7 @@ | ||
459 | options->password_authentication = -1; | ||
460 | options->kbd_interactive_authentication = -1; | ||
461 | options->challenge_response_authentication = -1; | ||
462 | + options->permit_blacklisted_keys = -1; | ||
463 | options->permit_empty_passwd = -1; | ||
464 | options->permit_user_env = -1; | ||
465 | options->use_login = -1; | ||
466 | @@ -227,6 +228,8 @@ | ||
467 | options->kbd_interactive_authentication = 0; | ||
468 | if (options->challenge_response_authentication == -1) | ||
469 | options->challenge_response_authentication = 1; | ||
470 | + if (options->permit_blacklisted_keys == -1) | ||
471 | + options->permit_blacklisted_keys = 0; | ||
472 | if (options->permit_empty_passwd == -1) | ||
473 | options->permit_empty_passwd = 0; | ||
474 | if (options->permit_user_env == -1) | ||
475 | @@ -302,7 +305,7 @@ | ||
476 | sListenAddress, sAddressFamily, | ||
477 | sPrintMotd, sPrintLastLog, sIgnoreRhosts, | ||
478 | sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost, | ||
479 | - sStrictModes, sEmptyPasswd, sTCPKeepAlive, | ||
480 | + sStrictModes, sPermitBlacklistedKeys, sEmptyPasswd, sTCPKeepAlive, | ||
481 | sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression, | ||
482 | sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups, | ||
483 | sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile, | ||
484 | @@ -410,6 +413,7 @@ | ||
485 | { "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL }, | ||
486 | { "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL }, | ||
487 | { "strictmodes", sStrictModes, SSHCFG_GLOBAL }, | ||
488 | + { "permitblacklistedkeys", sPermitBlacklistedKeys, SSHCFG_GLOBAL }, | ||
489 | { "permitemptypasswords", sEmptyPasswd, SSHCFG_ALL }, | ||
490 | { "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL }, | ||
491 | { "uselogin", sUseLogin, SSHCFG_GLOBAL }, | ||
492 | @@ -976,6 +980,10 @@ | ||
493 | intptr = &options->tcp_keep_alive; | ||
494 | goto parse_flag; | ||
495 | |||
496 | + case sPermitBlacklistedKeys: | ||
497 | + intptr = &options->permit_blacklisted_keys; | ||
498 | + goto parse_flag; | ||
499 | + | ||
500 | case sEmptyPasswd: | ||
501 | intptr = &options->permit_empty_passwd; | ||
502 | goto parse_flag; | ||
503 | @@ -1643,6 +1651,7 @@ | ||
504 | dump_cfg_fmtint(sX11UseLocalhost, o->x11_use_localhost); | ||
505 | dump_cfg_fmtint(sStrictModes, o->strict_modes); | ||
506 | dump_cfg_fmtint(sTCPKeepAlive, o->tcp_keep_alive); | ||
507 | + dump_cfg_fmtint(sPermitBlacklistedKeys, o->permit_blacklisted_keys); | ||
508 | dump_cfg_fmtint(sEmptyPasswd, o->permit_empty_passwd); | ||
509 | dump_cfg_fmtint(sPermitUserEnvironment, o->permit_user_env); | ||
510 | dump_cfg_fmtint(sUseLogin, o->use_login); | ||
511 | Index: b/servconf.h | ||
512 | =================================================================== | ||
513 | --- a/servconf.h | ||
514 | +++ b/servconf.h | ||
515 | @@ -101,6 +101,7 @@ | ||
516 | int challenge_response_authentication; | ||
517 | int zero_knowledge_password_authentication; | ||
518 | /* If true, permit jpake auth */ | ||
519 | + int permit_blacklisted_keys; /* If true, permit */ | ||
520 | int permit_empty_passwd; /* If false, do not permit empty | ||
521 | * passwords. */ | ||
522 | int permit_user_env; /* If true, read ~/.ssh/environment */ | ||
523 | Index: b/ssh-add.1 | ||
524 | =================================================================== | ||
525 | --- a/ssh-add.1 | ||
526 | +++ b/ssh-add.1 | ||
527 | @@ -75,6 +75,10 @@ | ||
528 | .Nm | ||
529 | to work. | ||
530 | .Pp | ||
531 | +Any keys recorded in the blacklist of known-compromised keys (see | ||
532 | +.Xr ssh-vulnkey 1 ) | ||
533 | +will be refused. | ||
534 | +.Pp | ||
535 | The options are as follows: | ||
536 | .Bl -tag -width Ds | ||
537 | .It Fl c | ||
538 | @@ -174,6 +178,7 @@ | ||
539 | .Xr ssh 1 , | ||
540 | .Xr ssh-agent 1 , | ||
541 | .Xr ssh-keygen 1 , | ||
542 | +.Xr ssh-vulnkey 1 , | ||
543 | .Xr sshd 8 | ||
544 | .Sh AUTHORS | ||
545 | OpenSSH is a derivative of the original and free | ||
546 | Index: b/ssh-add.c | ||
547 | =================================================================== | ||
548 | --- a/ssh-add.c | ||
549 | +++ b/ssh-add.c | ||
550 | @@ -139,7 +139,7 @@ | ||
551 | add_file(AuthenticationConnection *ac, const char *filename) | ||
552 | { | ||
553 | Key *private; | ||
554 | - char *comment = NULL; | ||
555 | + char *comment = NULL, *fp; | ||
556 | char msg[1024]; | ||
557 | int fd, perms_ok, ret = -1; | ||
558 | |||
559 | @@ -184,6 +184,14 @@ | ||
560 | "Bad passphrase, try again for %.200s: ", comment); | ||
561 | } | ||
562 | } | ||
563 | + if (blacklisted_key(private, &fp) == 1) { | ||
564 | + fprintf(stderr, "Public key %s blacklisted (see " | ||
565 | + "ssh-vulnkey(1)); refusing to add it\n", fp); | ||
566 | + xfree(fp); | ||
567 | + key_free(private); | ||
568 | + xfree(comment); | ||
569 | + return -1; | ||
570 | + } | ||
571 | |||
572 | if (ssh_add_identity_constrained(ac, private, comment, lifetime, | ||
573 | confirm)) { | ||
574 | Index: b/ssh-keygen.1 | ||
575 | =================================================================== | ||
576 | --- a/ssh-keygen.1 | ||
577 | +++ b/ssh-keygen.1 | ||
578 | @@ -451,6 +451,7 @@ | ||
579 | .Xr ssh 1 , | ||
580 | .Xr ssh-add 1 , | ||
581 | .Xr ssh-agent 1 , | ||
582 | +.Xr ssh-vulnkey 1 , | ||
583 | .Xr moduli 5 , | ||
584 | .Xr sshd 8 | ||
585 | .Rs | ||
586 | Index: b/ssh-vulnkey.1 | ||
587 | =================================================================== | ||
588 | --- /dev/null | ||
589 | +++ b/ssh-vulnkey.1 | ||
590 | @@ -0,0 +1,242 @@ | ||
591 | +.\" Copyright (c) 2008 Canonical Ltd. All rights reserved. | ||
592 | +.\" | ||
593 | +.\" Redistribution and use in source and binary forms, with or without | ||
594 | +.\" modification, are permitted provided that the following conditions | ||
595 | +.\" are met: | ||
596 | +.\" 1. Redistributions of source code must retain the above copyright | ||
597 | +.\" notice, this list of conditions and the following disclaimer. | ||
598 | +.\" 2. Redistributions in binary form must reproduce the above copyright | ||
599 | +.\" notice, this list of conditions and the following disclaimer in the | ||
600 | +.\" documentation and/or other materials provided with the distribution. | ||
601 | +.\" | ||
602 | +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | ||
603 | +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | ||
604 | +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | ||
605 | +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | ||
606 | +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | ||
607 | +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
608 | +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
609 | +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
610 | +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | ||
611 | +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
612 | +.\" | ||
613 | +.Dd $Mdocdate: May 12 2008 $ | ||
614 | +.Dt SSH-VULNKEY 1 | ||
615 | +.Os | ||
616 | +.Sh NAME | ||
617 | +.Nm ssh-vulnkey | ||
618 | +.Nd check blacklist of compromised keys | ||
619 | +.Sh SYNOPSIS | ||
620 | +.Nm | ||
621 | +.Op Fl q | Fl v | ||
622 | +.Ar file ... | ||
623 | +.Nm | ||
624 | +.Fl a | ||
625 | +.Sh DESCRIPTION | ||
626 | +.Nm | ||
627 | +checks a key against a blacklist of compromised keys. | ||
628 | +.Pp | ||
629 | +A substantial number of keys are known to have been generated using a broken | ||
630 | +version of OpenSSL distributed by Debian which failed to seed its random | ||
631 | +number generator correctly. | ||
632 | +Keys generated using these OpenSSL versions should be assumed to be | ||
633 | +compromised. | ||
634 | +This tool may be useful in checking for such keys. | ||
635 | +.Pp | ||
636 | +Keys that are compromised cannot be repaired; replacements must be generated | ||
637 | +using | ||
638 | +.Xr ssh-keygen 1 . | ||
639 | +Make sure to update | ||
640 | +.Pa authorized_keys | ||
641 | +files on all systems where compromised keys were permitted to authenticate. | ||
642 | +.Pp | ||
643 | +The argument list will be interpreted as a list of paths to public key files | ||
644 | +or | ||
645 | +.Pa authorized_keys | ||
646 | +files. | ||
647 | +If no suitable file is found at a given path, | ||
648 | +.Nm | ||
649 | +will append | ||
650 | +.Pa .pub | ||
651 | +and retry, in case it was given a private key file. | ||
652 | +If no files are given as arguments, | ||
653 | +.Nm | ||
654 | +will check | ||
655 | +.Pa ~/.ssh/id_rsa , | ||
656 | +.Pa ~/.ssh/id_dsa , | ||
657 | +.Pa ~/.ssh/identity , | ||
658 | +.Pa ~/.ssh/authorized_keys | ||
659 | +and | ||
660 | +.Pa ~/.ssh/authorized_keys2 , | ||
661 | +as well as the system's host keys if readable. | ||
662 | +.Pp | ||
663 | +If | ||
664 | +.Dq - | ||
665 | +is given as an argument, | ||
666 | +.Nm | ||
667 | +will read from standard input. | ||
668 | +This can be used to process output from | ||
669 | +.Xr ssh-keyscan 1 , | ||
670 | +for example: | ||
671 | +.Pp | ||
672 | +.Dl $ ssh-keyscan -t rsa remote.example.org | ssh-vulnkey - | ||
673 | +.Pp | ||
674 | +Unless the | ||
675 | +.Cm PermitBlacklistedKeys | ||
676 | +option is used, | ||
677 | +.Xr sshd 8 | ||
678 | +will reject attempts to authenticate with keys in the compromised list. | ||
679 | +.Pp | ||
680 | +The output from | ||
681 | +.Nm | ||
682 | +looks like this: | ||
683 | +.Pp | ||
684 | +.Bd -literal -offset indent | ||
685 | +/etc/ssh/ssh_host_key:1: COMPROMISED: RSA1 2048 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx root@host | ||
686 | +/home/user/.ssh/id_dsa:1: Not blacklisted: DSA 1024 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx /home/user/.ssh/id_dsa.pub | ||
687 | +/home/user/.ssh/authorized_keys:3: Unknown (blacklist file not installed): RSA 1024 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx user@host | ||
688 | +.Ed | ||
689 | +.Pp | ||
690 | +Each line is of the following format (any lines beginning with | ||
691 | +.Dq # | ||
692 | +should be ignored by scripts): | ||
693 | +.Pp | ||
694 | +.Dl Ar filename : Ns Ar line : Ar status : Ar type Ar size Ar fingerprint Ar comment | ||
695 | +.Pp | ||
696 | +It is important to distinguish between the possible values of | ||
697 | +.Ar status : | ||
698 | +.Pp | ||
699 | +.Bl -tag -width Ds | ||
700 | +.It COMPROMISED | ||
701 | +These keys are listed in a blacklist file, normally because their | ||
702 | +corresponding private keys are well-known. | ||
703 | +Replacements must be generated using | ||
704 | +.Xr ssh-keygen 1 . | ||
705 | +.It Not blacklisted | ||
706 | +A blacklist file exists for this key type and size, but this key is not | ||
707 | +listed in it. | ||
708 | +Unless there is some particular reason to believe otherwise, this key | ||
709 | +may be used safely. | ||
710 | +(Note that DSA keys used with the broken version of OpenSSL distributed | ||
711 | +by Debian may be compromised in the event that anyone captured a network | ||
712 | +trace, even if they were generated with a secure version of OpenSSL.) | ||
713 | +.It Unknown (blacklist file not installed) | ||
714 | +No blacklist file exists for this key type and size. | ||
715 | +You should find a suitable published blacklist and install it before | ||
716 | +deciding whether this key is safe to use. | ||
717 | +.El | ||
718 | +.Pp | ||
719 | +The options are as follows: | ||
720 | +.Bl -tag -width Ds | ||
721 | +.It Fl a | ||
722 | +Check keys of all users on the system. | ||
723 | +You will typically need to run | ||
724 | +.Nm | ||
725 | +as root to use this option. | ||
726 | +For each user, | ||
727 | +.Nm | ||
728 | +will check | ||
729 | +.Pa ~/.ssh/id_rsa , | ||
730 | +.Pa ~/.ssh/id_dsa , | ||
731 | +.Pa ~/.ssh/identity , | ||
732 | +.Pa ~/.ssh/authorized_keys | ||
733 | +and | ||
734 | +.Pa ~/.ssh/authorized_keys2 . | ||
735 | +It will also check the system's host keys. | ||
736 | +.It Fl q | ||
737 | +Quiet mode. | ||
738 | +Normally, | ||
739 | +.Nm | ||
740 | +outputs the fingerprint of each key scanned, with a description of its | ||
741 | +status. | ||
742 | +This option suppresses that output. | ||
743 | +.It Fl v | ||
744 | +Verbose mode. | ||
745 | +Normally, | ||
746 | +.Nm | ||
747 | +does not output anything for keys that are not listed in their corresponding | ||
748 | +blacklist file (although it still produces output for keys for which there | ||
749 | +is no blacklist file, since their status is unknown). | ||
750 | +This option causes | ||
751 | +.Nm | ||
752 | +to produce output for all keys. | ||
753 | +.El | ||
754 | +.Sh EXIT STATUS | ||
755 | +.Nm | ||
756 | +will exit zero if any of the given keys were in the compromised list, | ||
757 | +otherwise non-zero. | ||
758 | +.Sh BLACKLIST FILE FORMAT | ||
759 | +The blacklist file may start with comments, on lines starting with | ||
760 | +.Dq # . | ||
761 | +After these initial comments, it must follow a strict format: | ||
762 | +.Pp | ||
763 | +.Bl -bullet -offset indent -compact | ||
764 | +.It | ||
765 | +All the lines must be exactly the same length (20 characters followed by a | ||
766 | +newline) and must be in sorted order. | ||
767 | +.It | ||
768 | +Each line must consist of the lower-case hexadecimal MD5 key fingerprint, | ||
769 | +without colons, and with the first 12 characters removed (that is, the least | ||
770 | +significant 80 bits of the fingerprint). | ||
771 | +.El | ||
772 | +.Pp | ||
773 | +The key fingerprint may be generated using | ||
774 | +.Xr ssh-keygen 1 : | ||
775 | +.Pp | ||
776 | +.Dl $ ssh-keygen -l -f /path/to/key | ||
777 | +.Pp | ||
778 | +This strict format is necessary to allow the blacklist file to be checked | ||
779 | +quickly, using a binary-search algorithm. | ||
780 | +.Sh FILES | ||
781 | +.Bl -tag -width Ds | ||
782 | +.It Pa ~/.ssh/id_rsa | ||
783 | +If present, contains the protocol version 2 RSA authentication identity of | ||
784 | +the user. | ||
785 | +.It Pa ~/.ssh/id_dsa | ||
786 | +If present, contains the protocol version 2 DSA authentication identity of | ||
787 | +the user. | ||
788 | +.It Pa ~/.ssh/identity | ||
789 | +If present, contains the protocol version 1 RSA authentication identity of | ||
790 | +the user. | ||
791 | +.It Pa ~/.ssh/authorized_keys | ||
792 | +If present, lists the public keys (RSA/DSA) that can be used for logging in | ||
793 | +as this user. | ||
794 | +.It Pa ~/.ssh/authorized_keys2 | ||
795 | +Obsolete name for | ||
796 | +.Pa ~/.ssh/authorized_keys . | ||
797 | +This file may still be present on some old systems, but should not be | ||
798 | +created if it is missing. | ||
799 | +.It Pa /etc/ssh/ssh_host_rsa_key | ||
800 | +If present, contains the protocol version 2 RSA identity of the system. | ||
801 | +.It Pa /etc/ssh/ssh_host_dsa_key | ||
802 | +If present, contains the protocol version 2 DSA identity of the system. | ||
803 | +.It Pa /etc/ssh/ssh_host_key | ||
804 | +If present, contains the protocol version 1 RSA identity of the system. | ||
805 | +.It Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH | ||
806 | +If present, lists the blacklisted keys of type | ||
807 | +.Ar TYPE | ||
808 | +.Pf ( Dq RSA | ||
809 | +or | ||
810 | +.Dq DSA ) | ||
811 | +and bit length | ||
812 | +.Ar LENGTH . | ||
813 | +The format of this file is described above. | ||
814 | +RSA1 keys are converted to RSA before being checked in the blacklist. | ||
815 | +Note that the fingerprints of RSA1 keys are computed differently, so you | ||
816 | +will not be able to find them in the blacklist by hand. | ||
817 | +.It Pa /etc/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH | ||
818 | +Same as | ||
819 | +.Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH , | ||
820 | +but may be edited by the system administrator to add new blacklist entries. | ||
821 | +.El | ||
822 | +.Sh SEE ALSO | ||
823 | +.Xr ssh-keygen 1 , | ||
824 | +.Xr sshd 8 | ||
825 | +.Sh AUTHORS | ||
826 | +.An -nosplit | ||
827 | +.An Colin Watson Aq cjwatson@ubuntu.com | ||
828 | +.Pp | ||
829 | +Florian Weimer suggested the option to check keys of all users, and the idea | ||
830 | +of processing | ||
831 | +.Xr ssh-keyscan 1 | ||
832 | +output. | ||
833 | Index: b/ssh-vulnkey.c | ||
834 | =================================================================== | ||
835 | --- /dev/null | ||
836 | +++ b/ssh-vulnkey.c | ||
837 | @@ -0,0 +1,388 @@ | ||
838 | +/* | ||
839 | + * Copyright (c) 2008 Canonical Ltd. All rights reserved. | ||
840 | + * | ||
841 | + * Redistribution and use in source and binary forms, with or without | ||
842 | + * modification, are permitted provided that the following conditions | ||
843 | + * are met: | ||
844 | + * 1. Redistributions of source code must retain the above copyright | ||
845 | + * notice, this list of conditions and the following disclaimer. | ||
846 | + * 2. Redistributions in binary form must reproduce the above copyright | ||
847 | + * notice, this list of conditions and the following disclaimer in the | ||
848 | + * documentation and/or other materials provided with the distribution. | ||
849 | + * | ||
850 | + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | ||
851 | + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | ||
852 | + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | ||
853 | + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | ||
854 | + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | ||
855 | + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
856 | + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
857 | + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
858 | + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | ||
859 | + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
860 | + */ | ||
861 | + | ||
862 | +#include "includes.h" | ||
863 | + | ||
864 | +#include <sys/types.h> | ||
865 | +#include <sys/stat.h> | ||
866 | + | ||
867 | +#include <errno.h> | ||
868 | +#include <string.h> | ||
869 | +#include <stdio.h> | ||
870 | +#include <fcntl.h> | ||
871 | +#include <unistd.h> | ||
872 | + | ||
873 | +#include <openssl/evp.h> | ||
874 | + | ||
875 | +#include "xmalloc.h" | ||
876 | +#include "ssh.h" | ||
877 | +#include "log.h" | ||
878 | +#include "key.h" | ||
879 | +#include "authfile.h" | ||
880 | +#include "pathnames.h" | ||
881 | +#include "uidswap.h" | ||
882 | +#include "misc.h" | ||
883 | + | ||
884 | +extern char *__progname; | ||
885 | + | ||
886 | +/* Default files to check */ | ||
887 | +static char *default_host_files[] = { | ||
888 | + _PATH_HOST_RSA_KEY_FILE, | ||
889 | + _PATH_HOST_DSA_KEY_FILE, | ||
890 | + _PATH_HOST_KEY_FILE, | ||
891 | + NULL | ||
892 | +}; | ||
893 | +static char *default_files[] = { | ||
894 | + _PATH_SSH_CLIENT_ID_RSA, | ||
895 | + _PATH_SSH_CLIENT_ID_DSA, | ||
896 | + _PATH_SSH_CLIENT_IDENTITY, | ||
897 | + _PATH_SSH_USER_PERMITTED_KEYS, | ||
898 | + _PATH_SSH_USER_PERMITTED_KEYS2, | ||
899 | + NULL | ||
900 | +}; | ||
901 | + | ||
902 | +static int verbosity = 0; | ||
903 | + | ||
904 | +static int some_keys = 0; | ||
905 | +static int some_unknown = 0; | ||
906 | +static int some_compromised = 0; | ||
907 | + | ||
908 | +static void | ||
909 | +usage(void) | ||
910 | +{ | ||
911 | + fprintf(stderr, "usage: %s [-aqv] [file ...]\n", __progname); | ||
912 | + fprintf(stderr, "Options:\n"); | ||
913 | + fprintf(stderr, " -a Check keys of all users.\n"); | ||
914 | + fprintf(stderr, " -q Quiet mode.\n"); | ||
915 | + fprintf(stderr, " -v Verbose mode.\n"); | ||
916 | + exit(1); | ||
917 | +} | ||
918 | + | ||
919 | +void | ||
920 | +describe_key(const char *filename, u_long linenum, const char *msg, | ||
921 | + const Key *key, const char *comment, int min_verbosity) | ||
922 | +{ | ||
923 | + char *fp; | ||
924 | + | ||
925 | + fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); | ||
926 | + if (verbosity >= min_verbosity) { | ||
927 | + if (strchr(filename, ':')) | ||
928 | + printf("\"%s\"", filename); | ||
929 | + else | ||
930 | + printf("%s", filename); | ||
931 | + printf(":%lu: %s: %s %u %s %s\n", linenum, msg, | ||
932 | + key_type(key), key_size(key), fp, comment); | ||
933 | + } | ||
934 | + xfree(fp); | ||
935 | +} | ||
936 | + | ||
937 | +int | ||
938 | +do_key(const char *filename, u_long linenum, | ||
939 | + const Key *key, const char *comment) | ||
940 | +{ | ||
941 | + Key *public; | ||
942 | + int blacklist_status; | ||
943 | + int ret = 1; | ||
944 | + | ||
945 | + some_keys = 1; | ||
946 | + | ||
947 | + public = key_demote(key); | ||
948 | + if (public->type == KEY_RSA1) | ||
949 | + public->type = KEY_RSA; | ||
950 | + | ||
951 | + blacklist_status = blacklisted_key(public, NULL); | ||
952 | + if (blacklist_status == -1) { | ||
953 | + describe_key(filename, linenum, | ||
954 | + "Unknown (blacklist file not installed)", key, comment, 0); | ||
955 | + some_unknown = 1; | ||
956 | + } else if (blacklist_status == 1) { | ||
957 | + describe_key(filename, linenum, | ||
958 | + "COMPROMISED", key, comment, 0); | ||
959 | + some_compromised = 1; | ||
960 | + ret = 0; | ||
961 | + } else | ||
962 | + describe_key(filename, linenum, | ||
963 | + "Not blacklisted", key, comment, 1); | ||
964 | + | ||
965 | + key_free(public); | ||
966 | + | ||
967 | + return ret; | ||
968 | +} | ||
969 | + | ||
970 | +int | ||
971 | +do_filename(const char *filename, int quiet_open) | ||
972 | +{ | ||
973 | + FILE *f; | ||
974 | + char line[SSH_MAX_PUBKEY_BYTES]; | ||
975 | + char *cp; | ||
976 | + u_long linenum = 0; | ||
977 | + Key *key; | ||
978 | + char *comment = NULL; | ||
979 | + int found = 0, ret = 1; | ||
980 | + | ||
981 | + /* Copy much of key_load_public's logic here so that we can read | ||
982 | + * several keys from a single file (e.g. authorized_keys). | ||
983 | + */ | ||
984 | + | ||
985 | + if (strcmp(filename, "-") != 0) { | ||
986 | + int save_errno; | ||
987 | + f = fopen(filename, "r"); | ||
988 | + save_errno = errno; | ||
989 | + if (!f) { | ||
990 | + char pubfile[MAXPATHLEN]; | ||
991 | + if (strlcpy(pubfile, filename, sizeof pubfile) < | ||
992 | + sizeof(pubfile) && | ||
993 | + strlcat(pubfile, ".pub", sizeof pubfile) < | ||
994 | + sizeof(pubfile)) | ||
995 | + f = fopen(pubfile, "r"); | ||
996 | + } | ||
997 | + errno = save_errno; /* earlier errno is more useful */ | ||
998 | + if (!f) { | ||
999 | + if (!quiet_open) | ||
1000 | + perror(filename); | ||
1001 | + return -1; | ||
1002 | + } | ||
1003 | + if (verbosity > 0) | ||
1004 | + printf("# %s\n", filename); | ||
1005 | + } else | ||
1006 | + f = stdin; | ||
1007 | + while (read_keyfile_line(f, filename, line, sizeof(line), | ||
1008 | + &linenum) != -1) { | ||
1009 | + int i; | ||
1010 | + char *space; | ||
1011 | + int type; | ||
1012 | + char *end; | ||
1013 | + | ||
1014 | + /* Chop trailing newline. */ | ||
1015 | + i = strlen(line) - 1; | ||
1016 | + if (line[i] == '\n') | ||
1017 | + line[i] = '\0'; | ||
1018 | + | ||
1019 | + /* Skip leading whitespace, empty and comment lines. */ | ||
1020 | + for (cp = line; *cp == ' ' || *cp == '\t'; cp++) | ||
1021 | + ; | ||
1022 | + if (!*cp || *cp == '\n' || *cp == '#') | ||
1023 | + continue; | ||
1024 | + | ||
1025 | + /* Cope with ssh-keyscan output and options in | ||
1026 | + * authorized_keys files. | ||
1027 | + */ | ||
1028 | + space = strchr(cp, ' '); | ||
1029 | + if (!space) | ||
1030 | + continue; | ||
1031 | + *space = '\0'; | ||
1032 | + type = key_type_from_name(cp); | ||
1033 | + *space = ' '; | ||
1034 | + /* Leading number (RSA1) or valid type (RSA/DSA) indicates | ||
1035 | + * that we have no host name or options to skip. | ||
1036 | + */ | ||
1037 | + if ((strtol(cp, &end, 10) == 0 || *end != ' ') && | ||
1038 | + type == KEY_UNSPEC) { | ||
1039 | + int quoted = 0; | ||
1040 | + | ||
1041 | + for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) { | ||
1042 | + if (*cp == '\\' && cp[1] == '"') | ||
1043 | + cp++; /* Skip both */ | ||
1044 | + else if (*cp == '"') | ||
1045 | + quoted = !quoted; | ||
1046 | + } | ||
1047 | + /* Skip remaining whitespace. */ | ||
1048 | + for (; *cp == ' ' || *cp == '\t'; cp++) | ||
1049 | + ; | ||
1050 | + if (!*cp) | ||
1051 | + continue; | ||
1052 | + } | ||
1053 | + | ||
1054 | + /* Read and process the key itself. */ | ||
1055 | + key = key_new(KEY_RSA1); | ||
1056 | + if (key_read(key, &cp) == 1) { | ||
1057 | + while (*cp == ' ' || *cp == '\t') | ||
1058 | + cp++; | ||
1059 | + if (!do_key(filename, linenum, | ||
1060 | + key, *cp ? cp : filename)) | ||
1061 | + ret = 0; | ||
1062 | + found = 1; | ||
1063 | + } else { | ||
1064 | + key_free(key); | ||
1065 | + key = key_new(KEY_UNSPEC); | ||
1066 | + if (key_read(key, &cp) == 1) { | ||
1067 | + while (*cp == ' ' || *cp == '\t') | ||
1068 | + cp++; | ||
1069 | + if (!do_key(filename, linenum, | ||
1070 | + key, *cp ? cp : filename)) | ||
1071 | + ret = 0; | ||
1072 | + found = 1; | ||
1073 | + } | ||
1074 | + } | ||
1075 | + key_free(key); | ||
1076 | + } | ||
1077 | + if (f != stdin) | ||
1078 | + fclose(f); | ||
1079 | + | ||
1080 | + if (!found && filename) { | ||
1081 | + key = key_load_public(filename, &comment); | ||
1082 | + if (key) { | ||
1083 | + if (!do_key(filename, 1, key, comment)) | ||
1084 | + ret = 0; | ||
1085 | + found = 1; | ||
1086 | + } | ||
1087 | + if (comment) | ||
1088 | + xfree(comment); | ||
1089 | + } | ||
1090 | + | ||
1091 | + return ret; | ||
1092 | +} | ||
1093 | + | ||
1094 | +int | ||
1095 | +do_host(int quiet_open) | ||
1096 | +{ | ||
1097 | + int i; | ||
1098 | + struct stat st; | ||
1099 | + int ret = 1; | ||
1100 | + | ||
1101 | + for (i = 0; default_host_files[i]; i++) { | ||
1102 | + if (stat(default_host_files[i], &st) < 0 && errno == ENOENT) | ||
1103 | + continue; | ||
1104 | + if (!do_filename(default_host_files[i], quiet_open)) | ||
1105 | + ret = 0; | ||
1106 | + } | ||
1107 | + | ||
1108 | + return ret; | ||
1109 | +} | ||
1110 | + | ||
1111 | +int | ||
1112 | +do_user(const char *dir) | ||
1113 | +{ | ||
1114 | + int i; | ||
1115 | + char *file; | ||
1116 | + struct stat st; | ||
1117 | + int ret = 1; | ||
1118 | + | ||
1119 | + for (i = 0; default_files[i]; i++) { | ||
1120 | + xasprintf(&file, "%s/%s", dir, default_files[i]); | ||
1121 | + if (stat(file, &st) < 0 && errno == ENOENT) { | ||
1122 | + xfree(file); | ||
1123 | + continue; | ||
1124 | + } | ||
1125 | + if (!do_filename(file, 0)) | ||
1126 | + ret = 0; | ||
1127 | + xfree(file); | ||
1128 | + } | ||
1129 | + | ||
1130 | + return ret; | ||
1131 | +} | ||
1132 | + | ||
1133 | +int | ||
1134 | +main(int argc, char **argv) | ||
1135 | +{ | ||
1136 | + int opt, all_users = 0; | ||
1137 | + int ret = 1; | ||
1138 | + extern int optind; | ||
1139 | + | ||
1140 | + /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ | ||
1141 | + sanitise_stdfd(); | ||
1142 | + | ||
1143 | + __progname = ssh_get_progname(argv[0]); | ||
1144 | + | ||
1145 | + SSLeay_add_all_algorithms(); | ||
1146 | + log_init(argv[0], SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_USER, 1); | ||
1147 | + | ||
1148 | + /* We don't need the RNG ourselves, but symbol references here allow | ||
1149 | + * ld to link us properly. | ||
1150 | + */ | ||
1151 | + init_rng(); | ||
1152 | + seed_rng(); | ||
1153 | + | ||
1154 | + while ((opt = getopt(argc, argv, "ahqv")) != -1) { | ||
1155 | + switch (opt) { | ||
1156 | + case 'a': | ||
1157 | + all_users = 1; | ||
1158 | + break; | ||
1159 | + case 'q': | ||
1160 | + verbosity--; | ||
1161 | + break; | ||
1162 | + case 'v': | ||
1163 | + verbosity++; | ||
1164 | + break; | ||
1165 | + case 'h': | ||
1166 | + default: | ||
1167 | + usage(); | ||
1168 | + } | ||
1169 | + } | ||
1170 | + | ||
1171 | + if (all_users) { | ||
1172 | + struct passwd *pw; | ||
1173 | + | ||
1174 | + if (!do_host(0)) | ||
1175 | + ret = 0; | ||
1176 | + | ||
1177 | + while ((pw = getpwent()) != NULL) { | ||
1178 | + if (pw->pw_dir) { | ||
1179 | + temporarily_use_uid(pw); | ||
1180 | + if (!do_user(pw->pw_dir)) | ||
1181 | + ret = 0; | ||
1182 | + restore_uid(); | ||
1183 | + } | ||
1184 | + } | ||
1185 | + } else if (optind == argc) { | ||
1186 | + struct passwd *pw; | ||
1187 | + | ||
1188 | + if (!do_host(1)) | ||
1189 | + ret = 0; | ||
1190 | + | ||
1191 | + if ((pw = getpwuid(geteuid())) == NULL) | ||
1192 | + fprintf(stderr, "No user found with uid %u\n", | ||
1193 | + (u_int)geteuid()); | ||
1194 | + else { | ||
1195 | + if (!do_user(pw->pw_dir)) | ||
1196 | + ret = 0; | ||
1197 | + } | ||
1198 | + } else { | ||
1199 | + while (optind < argc) | ||
1200 | + if (!do_filename(argv[optind++], 0)) | ||
1201 | + ret = 0; | ||
1202 | + } | ||
1203 | + | ||
1204 | + if (verbosity >= 0) { | ||
1205 | + if (some_unknown) { | ||
1206 | + printf("#\n"); | ||
1207 | + printf("# The status of some keys on your system is unknown.\n"); | ||
1208 | + printf("# You may need to install additional blacklist files.\n"); | ||
1209 | + } | ||
1210 | + if (some_compromised) { | ||
1211 | + printf("#\n"); | ||
1212 | + printf("# Some keys on your system have been compromised!\n"); | ||
1213 | + printf("# You must replace them using ssh-keygen(1).\n"); | ||
1214 | + } | ||
1215 | + if (some_unknown || some_compromised) { | ||
1216 | + printf("#\n"); | ||
1217 | + printf("# See the ssh-vulnkey(1) manual page for further advice.\n"); | ||
1218 | + } else if (some_keys && verbosity > 0) { | ||
1219 | + printf("#\n"); | ||
1220 | + printf("# No blacklisted keys!\n"); | ||
1221 | + } | ||
1222 | + } | ||
1223 | + | ||
1224 | + return ret; | ||
1225 | +} | ||
1226 | Index: b/ssh.1 | ||
1227 | =================================================================== | ||
1228 | --- a/ssh.1 | ||
1229 | +++ b/ssh.1 | ||
1230 | @@ -1396,6 +1396,7 @@ | ||
1231 | .Xr ssh-agent 1 , | ||
1232 | .Xr ssh-keygen 1 , | ||
1233 | .Xr ssh-keyscan 1 , | ||
1234 | +.Xr ssh-vulnkey 1 , | ||
1235 | .Xr tun 4 , | ||
1236 | .Xr hosts.equiv 5 , | ||
1237 | .Xr ssh_config 5 , | ||
1238 | Index: b/ssh.c | ||
1239 | =================================================================== | ||
1240 | --- a/ssh.c | ||
1241 | +++ b/ssh.c | ||
1242 | @@ -1229,7 +1229,7 @@ | ||
1243 | static void | ||
1244 | load_public_identity_files(void) | ||
1245 | { | ||
1246 | - char *filename, *cp, thishost[NI_MAXHOST]; | ||
1247 | + char *filename, *cp, thishost[NI_MAXHOST], *fp; | ||
1248 | char *pwdir = NULL, *pwname = NULL; | ||
1249 | int i = 0; | ||
1250 | Key *public; | ||
1251 | @@ -1276,6 +1276,22 @@ | ||
1252 | public = key_load_public(filename, NULL); | ||
1253 | debug("identity file %s type %d", filename, | ||
1254 | public ? public->type : -1); | ||
1255 | + if (public && blacklisted_key(public, &fp) == 1) { | ||
1256 | + if (options.use_blacklisted_keys) | ||
1257 | + logit("Public key %s blacklisted (see " | ||
1258 | + "ssh-vulnkey(1)); continuing anyway", fp); | ||
1259 | + else | ||
1260 | + logit("Public key %s blacklisted (see " | ||
1261 | + "ssh-vulnkey(1)); refusing to send it", | ||
1262 | + fp); | ||
1263 | + xfree(fp); | ||
1264 | + if (!options.use_blacklisted_keys) { | ||
1265 | + key_free(public); | ||
1266 | + xfree(filename); | ||
1267 | + filename = NULL; | ||
1268 | + public = NULL; | ||
1269 | + } | ||
1270 | + } | ||
1271 | xfree(options.identity_files[i]); | ||
1272 | options.identity_files[i] = filename; | ||
1273 | options.identity_keys[i] = public; | ||
1274 | Index: b/ssh_config.5 | ||
1275 | =================================================================== | ||
1276 | --- a/ssh_config.5 | ||
1277 | +++ b/ssh_config.5 | ||
1278 | @@ -1041,6 +1041,23 @@ | ||
1279 | .Dq any . | ||
1280 | The default is | ||
1281 | .Dq any:any . | ||
1282 | +.It Cm UseBlacklistedKeys | ||
1283 | +Specifies whether | ||
1284 | +.Xr ssh 1 | ||
1285 | +should use keys recorded in its blacklist of known-compromised keys (see | ||
1286 | +.Xr ssh-vulnkey 1 ) | ||
1287 | +for authentication. | ||
1288 | +If | ||
1289 | +.Dq yes , | ||
1290 | +then attempts to use compromised keys for authentication will be logged but | ||
1291 | +accepted. | ||
1292 | +It is strongly recommended that this be used only to install new authorized | ||
1293 | +keys on the remote system, and even then only with the utmost care. | ||
1294 | +If | ||
1295 | +.Dq no , | ||
1296 | +then attempts to use compromised keys for authentication will be prevented. | ||
1297 | +The default is | ||
1298 | +.Dq no . | ||
1299 | .It Cm UsePrivilegedPort | ||
1300 | Specifies whether to use a privileged port for outgoing connections. | ||
1301 | The argument must be | ||
1302 | Index: b/sshconnect2.c | ||
1303 | =================================================================== | ||
1304 | --- a/sshconnect2.c | ||
1305 | +++ b/sshconnect2.c | ||
1306 | @@ -1418,6 +1418,8 @@ | ||
1307 | |||
1308 | /* list of keys stored in the filesystem */ | ||
1309 | for (i = 0; i < options.num_identity_files; i++) { | ||
1310 | + if (options.identity_files[i] == NULL) | ||
1311 | + continue; | ||
1312 | key = options.identity_keys[i]; | ||
1313 | if (key && key->type == KEY_RSA1) | ||
1314 | continue; | ||
1315 | @@ -1508,7 +1510,7 @@ | ||
1316 | if (id->key && id->key->type != KEY_RSA1) { | ||
1317 | debug("Offering public key: %s", id->filename); | ||
1318 | sent = send_pubkey_test(authctxt, id); | ||
1319 | - } else if (id->key == NULL) { | ||
1320 | + } else if (id->key == NULL && id->filename) { | ||
1321 | debug("Trying private key: %s", id->filename); | ||
1322 | id->key = load_identity_file(id->filename); | ||
1323 | if (id->key != NULL) { | ||
1324 | Index: b/sshd.8 | ||
1325 | =================================================================== | ||
1326 | --- a/sshd.8 | ||
1327 | +++ b/sshd.8 | ||
1328 | @@ -871,6 +871,7 @@ | ||
1329 | .Xr ssh-agent 1 , | ||
1330 | .Xr ssh-keygen 1 , | ||
1331 | .Xr ssh-keyscan 1 , | ||
1332 | +.Xr ssh-vulnkey 1 , | ||
1333 | .Xr chroot 2 , | ||
1334 | .Xr hosts_access 5 , | ||
1335 | .Xr login.conf 5 , | ||
1336 | Index: b/sshd.c | ||
1337 | =================================================================== | ||
1338 | --- a/sshd.c | ||
1339 | +++ b/sshd.c | ||
1340 | @@ -1518,6 +1518,11 @@ | ||
1341 | sensitive_data.host_keys[i] = NULL; | ||
1342 | continue; | ||
1343 | } | ||
1344 | + if (reject_blacklisted_key(key, 1) == 1) { | ||
1345 | + key_free(key); | ||
1346 | + sensitive_data.host_keys[i] = NULL; | ||
1347 | + continue; | ||
1348 | + } | ||
1349 | switch (key->type) { | ||
1350 | case KEY_RSA1: | ||
1351 | sensitive_data.ssh1_host_key = key; | ||
1352 | Index: b/sshd_config.5 | ||
1353 | =================================================================== | ||
1354 | --- a/sshd_config.5 | ||
1355 | +++ b/sshd_config.5 | ||
1356 | @@ -685,6 +685,20 @@ | ||
1357 | Specifies whether password authentication is allowed. | ||
1358 | The default is | ||
1359 | .Dq yes . | ||
1360 | +.It Cm PermitBlacklistedKeys | ||
1361 | +Specifies whether | ||
1362 | +.Xr sshd 8 | ||
1363 | +should allow keys recorded in its blacklist of known-compromised keys (see | ||
1364 | +.Xr ssh-vulnkey 1 ) . | ||
1365 | +If | ||
1366 | +.Dq yes , | ||
1367 | +then attempts to authenticate with compromised keys will be logged but | ||
1368 | +accepted. | ||
1369 | +If | ||
1370 | +.Dq no , | ||
1371 | +then attempts to authenticate with compromised keys will be rejected. | ||
1372 | +The default is | ||
1373 | +.Dq no . | ||
1374 | .It Cm PermitEmptyPasswords | ||
1375 | When password authentication is allowed, it specifies whether the | ||
1376 | server allows login to accounts with empty password strings. | ||