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