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