diff --git a/ChangeLog b/ChangeLog index 14eb11514..70d5baa58 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,27 @@ - jmc@cvs.openbsd.org 2008/06/11 07:30:37 [sshd.8] kill trailing whitespace; + - grunk@cvs.openbsd.org 2008/06/11 21:01:35 + [ssh_config.5 key.h readconf.c readconf.h ssh-keygen.1 ssh-keygen.c key.c + sshconnect.c] + Introduce SSH Fingerprint ASCII Visualization, a technique inspired by the + graphical hash visualization schemes known as "random art", and by + Dan Kaminsky's musings on the subject during a BlackOp talk at the + 23C3 in Berlin. + Scientific publication (original paper): + "Hash Visualization: a New Technique to improve Real-World Security", + Perrig A. and Song D., 1999, International Workshop on Cryptographic + Techniques and E-Commerce (CrypTEC '99) + http://sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf + The algorithm used here is a worm crawling over a discrete plane, + leaving a trace (augmenting the field) everywhere it goes. + Movement is taken from dgst_raw 2bit-wise. Bumping into walls + makes the respective movement vector be ignored for this turn, + thus switching to the other color of the chessboard. + Graphs are not unambiguous for now, because circles in graphs can be + walked in either direction. + discussions with several people, + help, corrections and ok markus@ djm@ 20080611 - (djm) [channels.c configure.ac] @@ -4165,4 +4186,4 @@ OpenServer 6 and add osr5bigcrypt support so when someone migrates passwords between UnixWare and OpenServer they will still work. OK dtucker@ -$Id: ChangeLog,v 1.4968 2008/06/12 18:32:00 dtucker Exp $ +$Id: ChangeLog,v 1.4969 2008/06/12 18:40:35 dtucker Exp $ diff --git a/key.c b/key.c index 62bf8361d..5d357a8d6 100644 --- a/key.c +++ b/key.c @@ -1,4 +1,4 @@ -/* $OpenBSD: key.c,v 1.69 2007/07/12 05:48:05 ray Exp $ */ +/* $OpenBSD: key.c,v 1.70 2008/06/11 21:01:35 grunk Exp $ */ /* * read_bignum(): * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -35,6 +35,7 @@ #include "includes.h" +#include #include #include @@ -295,6 +296,105 @@ key_fingerprint_bubblebabble(u_char *dgst_raw, u_int dgst_raw_len) return retval; } +/* + * Draw an ASCII-Art representing the fingerprint so human brain can + * profit from its built-in pattern recognition ability. + * This technique is called "random art" and can be found in some + * scientific publications like this original paper: + * + * "Hash Visualization: a New Technique to improve Real-World Security", + * Perrig A. and Song D., 1999, International Workshop on Cryptographic + * Techniques and E-Commerce (CrypTEC '99) + * sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf + * + * The subject came up in a talk by Dan Kaminsky, too. + * + * If you see the picture is different, the key is different. + * If the picture looks the same, you still know nothing. + * + * The algorithm used here is a worm crawling over a discrete plane, + * leaving a trace (augmenting the field) everywhere it goes. + * Movement is taken from dgst_raw 2bit-wise. Bumping into walls + * makes the respective movement vector be ignored for this turn. + * Graphs are not unambiguous, because circles in graphs can be + * walked in either direction. + */ +#define FLDSIZE_Y 8 +#define FLDSIZE_X FLDSIZE_Y * 2 +static char * +key_fingerprint_randomart(u_char *dgst_raw, u_int dgst_raw_len) +{ + /* + * Chars to be used after each other every time the worm + * intersects with itself. Matter of taste. + */ + char *augmentation_string = " .o+=*BOX@%&#/^"; + char *retval, *p; + char field[FLDSIZE_X][FLDSIZE_Y]; + u_int i, b; + int x, y; + + retval = xcalloc(1, (FLDSIZE_X + 3) * (FLDSIZE_Y + 2)); + + /* initialize field */ + memset(field, ' ', FLDSIZE_X * FLDSIZE_Y * sizeof(char)); + x = FLDSIZE_X / 2; + y = FLDSIZE_Y / 2; + field[x][y] = '.'; + + /* process raw key */ + for (i = 0; i < dgst_raw_len; i++) { + int input; + /* each byte conveys four 2-bit move commands */ + input = dgst_raw[i]; + for (b = 0; b < 4; b++) { + /* evaluate 2 bit, rest is shifted later */ + x += (input & 0x1) ? 1 : -1; + y += (input & 0x2) ? 1 : -1; + + /* assure we are still in bounds */ + x = MAX(x, 0); + y = MAX(y, 0); + x = MIN(x, FLDSIZE_X - 1); + y = MIN(y, FLDSIZE_Y - 1); + + /* augment the field */ + p = strchr(augmentation_string, field[x][y]); + if (*++p != '\0') + field[x][y] = *p; + + input = input >> 2; + } + } + + /* fill in retval */ + p = retval; + + /* output upper border */ + *p++ = '+'; + for (i = 0; i < FLDSIZE_X; i++) + *p++ = '-'; + *p++ = '+'; + *p++ = '\n'; + + /* output content */ + for (y = 0; y < FLDSIZE_Y; y++) { + *p++ = '|'; + for (x = 0; x < FLDSIZE_X; x++) + *p++ = field[x][y]; + *p++ = '|'; + *p++ = '\n'; + } + + /* output lower border */ + *p++ = '+'; + for (i = 0; i < FLDSIZE_X; i++) + *p++ = '-'; + *p++ = '+'; + + return retval; +} + char * key_fingerprint(const Key *k, enum fp_type dgst_type, enum fp_rep dgst_rep) { @@ -312,6 +412,9 @@ key_fingerprint(const Key *k, enum fp_type dgst_type, enum fp_rep dgst_rep) case SSH_FP_BUBBLEBABBLE: retval = key_fingerprint_bubblebabble(dgst_raw, dgst_raw_len); break; + case SSH_FP_RANDOMART: + retval = key_fingerprint_randomart(dgst_raw, dgst_raw_len); + break; default: fatal("key_fingerprint_ex: bad digest representation %d", dgst_rep); diff --git a/key.h b/key.h index 6873dd793..14aac79c2 100644 --- a/key.h +++ b/key.h @@ -1,4 +1,4 @@ -/* $OpenBSD: key.h,v 1.26 2006/08/03 03:34:42 deraadt Exp $ */ +/* $OpenBSD: key.h,v 1.27 2008/06/11 21:01:35 grunk Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -42,7 +42,8 @@ enum fp_type { }; enum fp_rep { SSH_FP_HEX, - SSH_FP_BUBBLEBABBLE + SSH_FP_BUBBLEBABBLE, + SSH_FP_RANDOMART }; /* key is stored in external hardware */ diff --git a/readconf.c b/readconf.c index 3ddb4d392..1d61145c4 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.165 2008/01/19 23:09:49 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.166 2008/06/11 21:01:35 grunk Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -452,7 +452,23 @@ parse_flag: case oCheckHostIP: intptr = &options->check_host_ip; - goto parse_flag; + arg = strdelim(&s); + if (!arg || *arg == '\0') + fatal("%.200s line %d: Missing CheckHostIP argument.", + filename, linenum); + value = 0; /* To avoid compiler warning... */ + if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0) + value = SSHCTL_CHECKHOSTIP_YES; + else if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0) + value = SSHCTL_CHECKHOSTIP_NO; + else if (strcmp(arg, "fingerprint") == 0) + value = SSHCTL_CHECKHOSTIP_FPR; + else + fatal("%.200s line %d: Bad CheckHostIP argument.", + filename, linenum); + if (*activep && *intptr == -1) + *intptr = value; + break; case oVerifyHostKeyDNS: intptr = &options->verify_host_key_dns; diff --git a/readconf.h b/readconf.h index 6257f4b2f..5c16a0ba6 100644 --- a/readconf.h +++ b/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.72 2008/01/19 23:09:49 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.73 2008/06/11 21:01:35 grunk Exp $ */ /* * Author: Tatu Ylonen @@ -123,6 +123,10 @@ typedef struct { } Options; +#define SSHCTL_CHECKHOSTIP_NO 0 +#define SSHCTL_CHECKHOSTIP_YES 1 +#define SSHCTL_CHECKHOSTIP_FPR 2 + #define SSHCTL_MASTER_NO 0 #define SSHCTL_MASTER_YES 1 #define SSHCTL_MASTER_AUTO 2 diff --git a/ssh-keygen.1 b/ssh-keygen.1 index 877935053..36249b288 100644 --- a/ssh-keygen.1 +++ b/ssh-keygen.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-keygen.1,v 1.75 2007/05/31 19:20:16 jmc Exp $ +.\" $OpenBSD: ssh-keygen.1,v 1.76 2008/06/11 21:01:35 grunk Exp $ .\" .\" -*- nroff -*- .\" @@ -37,7 +37,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: May 31 2007 $ +.Dd $Mdocdate: June 11 2008 $ .Dt SSH-KEYGEN 1 .Os .Sh NAME @@ -257,11 +257,12 @@ RFC 4716 SSH Public Key File Format. This option allows importing keys from several commercial SSH implementations. .It Fl l -Show fingerprint of specified public key file. +Show fingerprint and ASCII art representation of specified public key file. Private RSA1 keys are also supported. For RSA and DSA keys .Nm -tries to find the matching public key file and prints its fingerprint. +tries to find the matching public key file and prints its fingerprint +and representation. .It Fl M Ar memory Specify the amount of memory to use (in megabytes) when generating candidate moduli for DH-GEX. diff --git a/ssh-keygen.c b/ssh-keygen.c index a03c6575d..c22e814da 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keygen.c,v 1.166 2008/05/19 15:46:31 djm Exp $ */ +/* $OpenBSD: ssh-keygen.c,v 1.167 2008/06/11 21:01:35 grunk Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1994 Tatu Ylonen , Espoo, Finland @@ -504,7 +504,7 @@ do_fingerprint(struct passwd *pw) { FILE *f; Key *public; - char *comment = NULL, *cp, *ep, line[16*1024], *fp; + char *comment = NULL, *cp, *ep, line[16*1024], *fp, *ra; int i, skip = 0, num = 0, invalid = 1; enum fp_rep rep; enum fp_type fptype; @@ -522,9 +522,12 @@ do_fingerprint(struct passwd *pw) public = key_load_public(identity_file, &comment); if (public != NULL) { fp = key_fingerprint(public, fptype, rep); + ra = key_fingerprint(public, fptype, rep); printf("%u %s %s\n", key_size(public), fp, comment); + verbose("%s\n", ra); key_free(public); xfree(comment); + xfree(ra); xfree(fp); exit(0); } @@ -582,8 +585,11 @@ do_fingerprint(struct passwd *pw) } comment = *cp ? cp : comment; fp = key_fingerprint(public, fptype, rep); + ra = key_fingerprint(public, fptype, SSH_FP_RANDOMART); printf("%u %s %s\n", key_size(public), fp, comment ? comment : "no comment"); + verbose("%s\n", ra); + xfree(ra); xfree(fp); key_free(public); invalid = 0; @@ -603,12 +609,14 @@ print_host(FILE *f, const char *name, Key *public, int hash) if (print_fingerprint) { enum fp_rep rep; enum fp_type fptype; - char *fp; + char *fp, *ra; fptype = print_bubblebabble ? SSH_FP_SHA1 : SSH_FP_MD5; rep = print_bubblebabble ? SSH_FP_BUBBLEBABBLE : SSH_FP_HEX; fp = key_fingerprint(public, fptype, rep); - printf("%u %s %s\n", key_size(public), fp, name); + ra = key_fingerprint(public, fptype, SSH_FP_RANDOMART); + printf("%u %s %s\n%s\n", key_size(public), fp, name, ra); + xfree(ra); xfree(fp); } else { if (hash && (name = host_hash(name, NULL, 0)) == NULL) @@ -1451,10 +1459,15 @@ passphrase_again: if (!quiet) { char *fp = key_fingerprint(public, SSH_FP_MD5, SSH_FP_HEX); + char *ra = key_fingerprint(public, SSH_FP_MD5, + SSH_FP_RANDOMART); printf("Your public key has been saved in %s.\n", identity_file); printf("The key fingerprint is:\n"); printf("%s %s\n", fp, comment); + printf("The key's randomart image is:\n"); + printf("%s\n", ra); + xfree(ra); xfree(fp); } diff --git a/ssh_config.5 b/ssh_config.5 index d6f3fbf80..28ac724c8 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -34,8 +34,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh_config.5,v 1.106 2008/06/10 18:21:24 dtucker Exp $ -.Dd $Mdocdate: June 10 2008 $ +.\" $OpenBSD: ssh_config.5,v 1.107 2008/06/11 21:01:35 grunk Exp $ +.Dd $Mdocdate: June 11 2008 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -161,6 +161,10 @@ will additionally check the host IP address in the file. This allows ssh to detect if a host key changed due to DNS spoofing. If the option is set to +.Dq fingerprint , +not only the host IP address will be checked, but also an ASCII art +representation of the key will be printed. +If the option is set to .Dq no , the check will not be executed. The default is diff --git a/sshconnect.c b/sshconnect.c index a604c9724..151299614 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.c,v 1.203 2007/12/27 14:22:08 dtucker Exp $ */ +/* $OpenBSD: sshconnect.c,v 1.204 2008/06/11 21:01:35 grunk Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -602,7 +602,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, Key *file_key; const char *type = key_type(host_key); char *ip = NULL, *host = NULL; - char hostline[1000], *hostp, *fp; + char hostline[1000], *hostp, *fp, *ra; HostStatus host_status; HostStatus ip_status; int r, local = 0, host_ip_differ = 0; @@ -740,6 +740,13 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, logit("Warning: Permanently added the %s host " "key for IP address '%.128s' to the list " "of known hosts.", type, ip); + } else if (options.check_host_ip == SSHCTL_CHECKHOSTIP_FPR) { + fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); + ra = key_fingerprint(host_key, SSH_FP_MD5, + SSH_FP_RANDOMART); + logit("Host key fingerprint is %s\n%s\n", fp, ra); + xfree(ra); + xfree(fp); } break; case HOST_NEW: @@ -775,6 +782,8 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, snprintf(msg1, sizeof(msg1), "."); /* The default */ fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); + ra = key_fingerprint(host_key, SSH_FP_MD5, + SSH_FP_RANDOMART); msg2[0] = '\0'; if (options.verify_host_key_dns) { if (matching_host_key_dns) @@ -789,10 +798,11 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, snprintf(msg, sizeof(msg), "The authenticity of host '%.200s (%s)' can't be " "established%s\n" - "%s key fingerprint is %s.\n%s" + "%s key fingerprint is %s.\n%s\n%s" "Are you sure you want to continue connecting " "(yes/no)? ", - host, ip, msg1, type, fp, msg2); + host, ip, msg1, type, fp, ra, msg2); + xfree(ra); xfree(fp); if (!confirm(msg)) goto fail; @@ -1063,18 +1073,20 @@ static int show_key_from_file(const char *file, const char *host, int keytype) { Key *found; - char *fp; + char *fp, *ra; int line, ret; found = key_new(keytype); if ((ret = lookup_key_in_hostfile_by_type(file, host, keytype, found, &line))) { fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX); + ra = key_fingerprint(found, SSH_FP_MD5, SSH_FP_RANDOMART); logit("WARNING: %s key found for host %s\n" "in %s:%d\n" - "%s key fingerprint %s.", + "%s key fingerprint %s.\n%s\n", key_type(found), host, file, line, - key_type(found), fp); + key_type(found), fp, ra); + xfree(ra); xfree(fp); } key_free(found);