* [Qemu-devel] PATCH 0/8: Authentication support for the VNC server @ 2007-08-13 19:25 Daniel P. Berrange 2007-08-13 19:41 ` [Qemu-devel] PATCH 1/8: Refactor VNC server setup API Daniel P. Berrange ` (8 more replies) 0 siblings, 9 replies; 10+ messages in thread From: Daniel P. Berrange @ 2007-08-13 19:25 UTC (permalink / raw) To: qemu-devel The current VNC server implementation does not have support for the authentication of incoming client connections. The following series of patches provide support for a number of alternatives, all compliant with the VNC protocol spec. The simplest mechanism (and the weakest) is the traditional VNC password scheme based on weak d3des hashing of an 8 byte key. The more serious mechanism uses TLS for data encryption of the entire session, and x509 certificates for both client and server authentication. The patches are an iteration on the previous work I posted a couple of weeks ago[1]. This addresses all the issues raised in the previous review along with a couple of edge cases I discovered. Since TLS can be quite perplexing, I also included some documentation on how to setup a CA, and issue client & server certs in a manner suitable for use with the VNC server. For the basic VNC password auth, this patch should be compatible with any standard VNC client such as RealVNC. The TLS based auth schemes require a client that implements the VeNCrypt extension[2]. The client from the VeNCrypt[3] project of course is one example. The GTK-VNC[4] widget which is used by Virt Manager[5] and Vinagre [6] also support it, and are my primary testing platform. The 8 individual patches will follow shortly in replies to this mail. Regards, Dan. [1] http://www.mail-archive.com/qemu-devel@nongnu.org/msg11554.html [2] http://www.mail-archive.com/qemu-devel@nongnu.org/msg08681.html [3] http://sourceforge.net/projects/vencrypt/ [4] http://gtk-vnc.sourceforge.net/ [5] http://virt-manager.org/ [6] http://www.gnome.org/~jwendell/vinagre/ -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=| ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [Qemu-devel] PATCH 1/8: Refactor VNC server setup API 2007-08-13 19:25 [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Daniel P. Berrange @ 2007-08-13 19:41 ` Daniel P. Berrange 2007-08-13 19:42 ` [Qemu-devel] PATCH 2/8: Extend monitor 'change' command for VNC Daniel P. Berrange ` (7 subsequent siblings) 8 siblings, 0 replies; 10+ messages in thread From: Daniel P. Berrange @ 2007-08-13 19:41 UTC (permalink / raw) To: qemu-devel This patch splits the vnc_display_init function into two parts, the resulting vnc_display_init function merely initializes a little state. The new vnc_display_open function is responsible for starting the server. This refactoring is in preparation for the next patch. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> diff -r 6e989d01127e vl.c --- a/vl.c Wed Aug 08 15:03:02 2007 -0400 +++ b/vl.c Wed Aug 08 15:03:05 2007 -0400 @@ -7944,7 +7944,9 @@ int main(int argc, char **argv) /* nearly nothing to do */ dumb_display_init(ds); } else if (vnc_display != NULL) { - vnc_display_init(ds, vnc_display); + vnc_display_init(ds); + if (vnc_display_open(ds, vnc_display) < 0) + exit(1); } else { #if defined(CONFIG_SDL) sdl_display_init(ds, full_screen, no_frame); diff -r 6e989d01127e vl.h --- a/vl.h Wed Aug 08 15:03:02 2007 -0400 +++ b/vl.h Mon Aug 13 11:51:50 2007 -0400 @@ -968,7 +968,9 @@ void cocoa_display_init(DisplayState *ds void cocoa_display_init(DisplayState *ds, int full_screen); /* vnc.c */ -void vnc_display_init(DisplayState *ds, const char *display); +void vnc_display_init(DisplayState *ds); +void vnc_display_close(DisplayState *ds); +int vnc_display_open(DisplayState *ds, const char *display); void do_info_vnc(void); /* x_keymap.c */ diff -r 6e989d01127e vnc.c --- a/vnc.c Wed Aug 08 15:03:02 2007 -0400 +++ b/vnc.c Mon Aug 13 11:52:27 2007 -0400 @@ -73,7 +73,7 @@ struct VncState int last_x; int last_y; - const char *display; + char *display; Buffer output; Buffer input; @@ -1169,7 +1169,67 @@ static void vnc_listen_read(void *opaque extern int parse_host_port(struct sockaddr_in *saddr, const char *str); -void vnc_display_init(DisplayState *ds, const char *arg) +void vnc_display_init(DisplayState *ds) +{ + VncState *vs; + + vs = qemu_mallocz(sizeof(VncState)); + if (!vs) + exit(1); + + ds->opaque = vs; + vnc_state = vs; + vs->display = NULL; + + vs->lsock = -1; + vs->csock = -1; + vs->depth = 4; + vs->last_x = -1; + vs->last_y = -1; + + vs->ds = ds; + + if (!keyboard_layout) + keyboard_layout = "en-us"; + + vs->kbd_layout = init_keyboard_layout(keyboard_layout); + if (!vs->kbd_layout) + exit(1); + + vs->ds->data = NULL; + vs->ds->dpy_update = vnc_dpy_update; + vs->ds->dpy_resize = vnc_dpy_resize; + vs->ds->dpy_refresh = vnc_dpy_refresh; + + memset(vs->dirty_row, 0xFF, sizeof(vs->dirty_row)); + + vnc_dpy_resize(vs->ds, 640, 400); +} + +void vnc_display_close(DisplayState *ds) +{ + VncState *vs = (VncState *)ds->opaque; + + if (vs->display) { + qemu_free(vs->display); + vs->display = NULL; + } + if (vs->lsock != -1) { + qemu_set_fd_handler2(vs->lsock, NULL, NULL, NULL, NULL); + close(vs->lsock); + vs->lsock = -1; + } + if (vs->csock != -1) { + qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL); + closesocket(vs->csock); + vs->csock = -1; + buffer_reset(&vs->input); + buffer_reset(&vs->output); + vs->need_update = 0; + } +} + +int vnc_display_open(DisplayState *ds, const char *arg) { struct sockaddr *addr; struct sockaddr_in iaddr; @@ -1179,40 +1239,14 @@ void vnc_display_init(DisplayState *ds, int reuse_addr, ret; socklen_t addrlen; const char *p; - VncState *vs; - - vs = qemu_mallocz(sizeof(VncState)); - if (!vs) - exit(1); - - ds->opaque = vs; - vnc_state = vs; - vs->display = arg; - - vs->lsock = -1; - vs->csock = -1; - vs->depth = 4; - vs->last_x = -1; - vs->last_y = -1; - - vs->ds = ds; - - if (!keyboard_layout) - keyboard_layout = "en-us"; - - vs->kbd_layout = init_keyboard_layout(keyboard_layout); - if (!vs->kbd_layout) - exit(1); - - vs->ds->data = NULL; - vs->ds->dpy_update = vnc_dpy_update; - vs->ds->dpy_resize = vnc_dpy_resize; - vs->ds->dpy_refresh = vnc_dpy_refresh; - - memset(vs->dirty_row, 0xFF, sizeof(vs->dirty_row)); - - vnc_dpy_resize(vs->ds, 640, 400); - + VncState *vs = (VncState *)ds->opaque; + + vnc_display_close(ds); + if (strcmp(arg, "none") == 0) + return 0; + + if (!(vs->display = strdup(arg))) + return -1; #ifndef _WIN32 if (strstart(arg, "unix:", &p)) { addr = (struct sockaddr *)&uaddr; @@ -1221,7 +1255,9 @@ void vnc_display_init(DisplayState *ds, vs->lsock = socket(PF_UNIX, SOCK_STREAM, 0); if (vs->lsock == -1) { fprintf(stderr, "Could not create socket\n"); - exit(1); + free(vs->display); + vs->display = NULL; + return -1; } uaddr.sun_family = AF_UNIX; @@ -1235,40 +1271,53 @@ void vnc_display_init(DisplayState *ds, addr = (struct sockaddr *)&iaddr; addrlen = sizeof(iaddr); + if (parse_host_port(&iaddr, arg) < 0) { + fprintf(stderr, "Could not parse VNC address\n"); + free(vs->display); + vs->display = NULL; + return -1; + } + + iaddr.sin_port = htons(ntohs(iaddr.sin_port) + 5900); + vs->lsock = socket(PF_INET, SOCK_STREAM, 0); if (vs->lsock == -1) { fprintf(stderr, "Could not create socket\n"); - exit(1); + free(vs->display); + vs->display = NULL; + return -1; } - - if (parse_host_port(&iaddr, arg) < 0) { - fprintf(stderr, "Could not parse VNC address\n"); - exit(1); - } - - iaddr.sin_port = htons(ntohs(iaddr.sin_port) + 5900); reuse_addr = 1; ret = setsockopt(vs->lsock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse_addr, sizeof(reuse_addr)); if (ret == -1) { fprintf(stderr, "setsockopt() failed\n"); - exit(1); + close(vs->lsock); + vs->lsock = -1; + free(vs->display); + vs->display = NULL; + return -1; } } if (bind(vs->lsock, addr, addrlen) == -1) { fprintf(stderr, "bind() failed\n"); - exit(1); + close(vs->lsock); + vs->lsock = -1; + free(vs->display); + vs->display = NULL; + return -1; } if (listen(vs->lsock, 1) == -1) { fprintf(stderr, "listen() failed\n"); - exit(1); - } - - ret = qemu_set_fd_handler2(vs->lsock, vnc_listen_poll, vnc_listen_read, NULL, vs); - if (ret == -1) { - exit(1); - } -} + close(vs->lsock); + vs->lsock = -1; + free(vs->display); + vs->display = NULL; + return -1; + } + + return qemu_set_fd_handler2(vs->lsock, vnc_listen_poll, vnc_listen_read, NULL, vs); +} -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=| ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [Qemu-devel] PATCH 2/8: Extend monitor 'change' command for VNC 2007-08-13 19:25 [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Daniel P. Berrange 2007-08-13 19:41 ` [Qemu-devel] PATCH 1/8: Refactor VNC server setup API Daniel P. Berrange @ 2007-08-13 19:42 ` Daniel P. Berrange 2007-08-13 19:44 ` [Qemu-devel] PATCH 3/8: VNC password authentication Daniel P. Berrange ` (6 subsequent siblings) 8 siblings, 0 replies; 10+ messages in thread From: Daniel P. Berrange @ 2007-08-13 19:42 UTC (permalink / raw) To: qemu-devel This patch extends the QEMU monitor 'change' command so that it can be used to change the configuration of the VNC server. On the command line the user can use -vnc none, and then issue the 'change vnc :1' command later from the monitor. This is utilized in the next patch to let the monitor fetch a password for the server. The concept could also be extended to serial & parallel devices allowing changing of their configuration on the fly. Example usage: qemu [...OPTIONS...] -vnc none -monitor stdio (qemu) change vnc 127.0.0.1:1,password Signed-off-by: Daniel P. Berrange <berrange@redhat.com> diff -r 4bae9b5b1e5b monitor.c --- a/monitor.c Wed Aug 08 15:04:25 2007 -0400 +++ b/monitor.c Wed Aug 08 15:04:27 2007 -0400 @@ -386,7 +386,7 @@ static void do_eject(int force, const ch eject_device(bs, force); } -static void do_change(const char *device, const char *filename) +static void do_change_block(const char *device, const char *filename) { BlockDriverState *bs; @@ -399,6 +399,21 @@ static void do_change(const char *device return; bdrv_open(bs, filename, 0); qemu_key_check(bs, filename); +} + +static void do_change_vnc(const char *target) +{ + if (vnc_display_open(NULL, target) < 0) + term_printf("could not start VNC server on %s\n", target); +} + +static void do_change(const char *device, const char *target) +{ + if (strcmp(device, "vnc") == 0) { + do_change_vnc(target); + } else { + do_change_block(device, target); + } } static void do_screen_dump(const char *filename) diff -r 4bae9b5b1e5b vnc.c --- a/vnc.c Wed Aug 08 15:04:25 2007 -0400 +++ b/vnc.c Wed Aug 08 15:04:27 2007 -0400 @@ -1208,7 +1208,7 @@ void vnc_display_init(DisplayState *ds) void vnc_display_close(DisplayState *ds) { - VncState *vs = (VncState *)ds->opaque; + VncState *vs = ds ? (VncState *)ds->opaque : vnc_state; if (vs->display) { qemu_free(vs->display); @@ -1238,7 +1238,7 @@ int vnc_display_open(DisplayState *ds, c int reuse_addr, ret; socklen_t addrlen; const char *p; - VncState *vs = (VncState *)ds->opaque; + VncState *vs = ds ? (VncState *)ds->opaque : vnc_state; vnc_display_close(ds); if (strcmp(arg, "none") == 0) -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=| ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [Qemu-devel] PATCH 3/8: VNC password authentication 2007-08-13 19:25 [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Daniel P. Berrange 2007-08-13 19:41 ` [Qemu-devel] PATCH 1/8: Refactor VNC server setup API Daniel P. Berrange 2007-08-13 19:42 ` [Qemu-devel] PATCH 2/8: Extend monitor 'change' command for VNC Daniel P. Berrange @ 2007-08-13 19:44 ` Daniel P. Berrange 2007-08-13 19:46 ` [Qemu-devel] PATCH 4/8: VeNCrypt basic TLS support Daniel P. Berrange ` (5 subsequent siblings) 8 siblings, 0 replies; 10+ messages in thread From: Daniel P. Berrange @ 2007-08-13 19:44 UTC (permalink / raw) To: qemu-devel This patch introduces support for VNC protocols upto 3.8 and with it, support for password based authentication. VNC's password based authentication is not entirely secure, but it is a standard and the RFB spec requires that all clients support it. The password can be provided by using the monitor 'change vnc password' and it will prompt for a password to be entered. Passwords have upto 8 letters of context. Until the 'change vnc password' monitor command is run, all client connection attempts will be rejected. This avoids a startup race where no password would be present. NB, we need a custom copy of d3des here because VNC uses a 'special' modification of the algorithm. This d3des code is public domain & in all other VNC servers & clients. For client compatability, protocol 3.5 is treated as identical to protocol 3.3. Example usage: qemu [...OPTIONS...] -vnc :1,password -monitor stdio (qemu) change vnc password Password: ******** (qemu) Signed-off-by: Daniel P. Berrange <berrange@redhat.com> diff -r 08374728639d Makefile.target --- a/Makefile.target Wed Aug 08 15:04:44 2007 -0400 +++ b/Makefile.target Mon Aug 13 11:25:43 2007 -0400 @@ -482,7 +482,7 @@ ifdef CONFIG_SDL ifdef CONFIG_SDL VL_OBJS+=sdl.o x_keymap.o endif -VL_OBJS+=vnc.o +VL_OBJS+=vnc.o d3des.o ifdef CONFIG_COCOA VL_OBJS+=cocoa.o COCOA_LIBS=-F/System/Library/Frameworks -framework Cocoa -framework IOKit @@ -543,7 +543,7 @@ sdl.o: sdl.c keymaps.c sdl_keysym.h sdl.o: sdl.c keymaps.c sdl_keysym.h $(CC) $(CFLAGS) $(CPPFLAGS) $(SDL_CFLAGS) $(BASE_CFLAGS) -c -o $@ $< -vnc.o: vnc.c keymaps.c sdl_keysym.h vnchextile.h +vnc.o: vnc.c keymaps.c sdl_keysym.h vnchextile.h d3des.c d3des.h $(CC) $(CFLAGS) $(CPPFLAGS) $(BASE_CFLAGS) -c -o $@ $< sdlaudio.o: sdlaudio.c diff -r 08374728639d d3des.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/d3des.c Wed Aug 08 15:04:47 2007 -0400 @@ -0,0 +1,434 @@ +/* + * This is D3DES (V5.09) by Richard Outerbridge with the double and + * triple-length support removed for use in VNC. Also the bytebit[] array + * has been reversed so that the most significant bit in each byte of the + * key is ignored, not the least significant. + * + * These changes are: + * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* D3DES (V5.09) - + * + * A portable, public domain, version of the Data Encryption Standard. + * + * Written with Symantec's THINK (Lightspeed) C by Richard Outerbridge. + * Thanks to: Dan Hoey for his excellent Initial and Inverse permutation + * code; Jim Gillogly & Phil Karn for the DES key schedule code; Dennis + * Ferguson, Eric Young and Dana How for comparing notes; and Ray Lau, + * for humouring me on. + * + * Copyright (c) 1988,1989,1990,1991,1992 by Richard Outerbridge. + * (GEnie : OUTER; CIS : [71755,204]) Graven Imagery, 1992. + */ + +#include "d3des.h" + +static void scrunch(unsigned char *, unsigned long *); +static void unscrun(unsigned long *, unsigned char *); +static void desfunc(unsigned long *, unsigned long *); +static void cookey(unsigned long *); + +static unsigned long KnL[32] = { 0L }; + +static unsigned short bytebit[8] = { + 01, 02, 04, 010, 020, 040, 0100, 0200 }; + +static unsigned long bigbyte[24] = { + 0x800000L, 0x400000L, 0x200000L, 0x100000L, + 0x80000L, 0x40000L, 0x20000L, 0x10000L, + 0x8000L, 0x4000L, 0x2000L, 0x1000L, + 0x800L, 0x400L, 0x200L, 0x100L, + 0x80L, 0x40L, 0x20L, 0x10L, + 0x8L, 0x4L, 0x2L, 0x1L }; + +/* Use the key schedule specified in the Standard (ANSI X3.92-1981). */ + +static unsigned char pc1[56] = { + 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, + 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, + 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, + 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 }; + +static unsigned char totrot[16] = { + 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28 }; + +static unsigned char pc2[48] = { + 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, + 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, + 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, + 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 }; + +void deskey(key, edf) /* Thanks to James Gillogly & Phil Karn! */ +unsigned char *key; +int edf; +{ + register int i, j, l, m, n; + unsigned char pc1m[56], pcr[56]; + unsigned long kn[32]; + + for ( j = 0; j < 56; j++ ) { + l = pc1[j]; + m = l & 07; + pc1m[j] = (key[l >> 3] & bytebit[m]) ? 1 : 0; + } + for( i = 0; i < 16; i++ ) { + if( edf == DE1 ) m = (15 - i) << 1; + else m = i << 1; + n = m + 1; + kn[m] = kn[n] = 0L; + for( j = 0; j < 28; j++ ) { + l = j + totrot[i]; + if( l < 28 ) pcr[j] = pc1m[l]; + else pcr[j] = pc1m[l - 28]; + } + for( j = 28; j < 56; j++ ) { + l = j + totrot[i]; + if( l < 56 ) pcr[j] = pc1m[l]; + else pcr[j] = pc1m[l - 28]; + } + for( j = 0; j < 24; j++ ) { + if( pcr[pc2[j]] ) kn[m] |= bigbyte[j]; + if( pcr[pc2[j+24]] ) kn[n] |= bigbyte[j]; + } + } + cookey(kn); + return; + } + +static void cookey(raw1) +register unsigned long *raw1; +{ + register unsigned long *cook, *raw0; + unsigned long dough[32]; + register int i; + + cook = dough; + for( i = 0; i < 16; i++, raw1++ ) { + raw0 = raw1++; + *cook = (*raw0 & 0x00fc0000L) << 6; + *cook |= (*raw0 & 0x00000fc0L) << 10; + *cook |= (*raw1 & 0x00fc0000L) >> 10; + *cook++ |= (*raw1 & 0x00000fc0L) >> 6; + *cook = (*raw0 & 0x0003f000L) << 12; + *cook |= (*raw0 & 0x0000003fL) << 16; + *cook |= (*raw1 & 0x0003f000L) >> 4; + *cook++ |= (*raw1 & 0x0000003fL); + } + usekey(dough); + return; + } + +void cpkey(into) +register unsigned long *into; +{ + register unsigned long *from, *endp; + + from = KnL, endp = &KnL[32]; + while( from < endp ) *into++ = *from++; + return; + } + +void usekey(from) +register unsigned long *from; +{ + register unsigned long *to, *endp; + + to = KnL, endp = &KnL[32]; + while( to < endp ) *to++ = *from++; + return; + } + +void des(inblock, outblock) +unsigned char *inblock, *outblock; +{ + unsigned long work[2]; + + scrunch(inblock, work); + desfunc(work, KnL); + unscrun(work, outblock); + return; + } + +static void scrunch(outof, into) +register unsigned char *outof; +register unsigned long *into; +{ + *into = (*outof++ & 0xffL) << 24; + *into |= (*outof++ & 0xffL) << 16; + *into |= (*outof++ & 0xffL) << 8; + *into++ |= (*outof++ & 0xffL); + *into = (*outof++ & 0xffL) << 24; + *into |= (*outof++ & 0xffL) << 16; + *into |= (*outof++ & 0xffL) << 8; + *into |= (*outof & 0xffL); + return; + } + +static void unscrun(outof, into) +register unsigned long *outof; +register unsigned char *into; +{ + *into++ = (unsigned char)((*outof >> 24) & 0xffL); + *into++ = (unsigned char)((*outof >> 16) & 0xffL); + *into++ = (unsigned char)((*outof >> 8) & 0xffL); + *into++ = (unsigned char)(*outof++ & 0xffL); + *into++ = (unsigned char)((*outof >> 24) & 0xffL); + *into++ = (unsigned char)((*outof >> 16) & 0xffL); + *into++ = (unsigned char)((*outof >> 8) & 0xffL); + *into = (unsigned char)(*outof & 0xffL); + return; + } + +static unsigned long SP1[64] = { + 0x01010400L, 0x00000000L, 0x00010000L, 0x01010404L, + 0x01010004L, 0x00010404L, 0x00000004L, 0x00010000L, + 0x00000400L, 0x01010400L, 0x01010404L, 0x00000400L, + 0x01000404L, 0x01010004L, 0x01000000L, 0x00000004L, + 0x00000404L, 0x01000400L, 0x01000400L, 0x00010400L, + 0x00010400L, 0x01010000L, 0x01010000L, 0x01000404L, + 0x00010004L, 0x01000004L, 0x01000004L, 0x00010004L, + 0x00000000L, 0x00000404L, 0x00010404L, 0x01000000L, + 0x00010000L, 0x01010404L, 0x00000004L, 0x01010000L, + 0x01010400L, 0x01000000L, 0x01000000L, 0x00000400L, + 0x01010004L, 0x00010000L, 0x00010400L, 0x01000004L, + 0x00000400L, 0x00000004L, 0x01000404L, 0x00010404L, + 0x01010404L, 0x00010004L, 0x01010000L, 0x01000404L, + 0x01000004L, 0x00000404L, 0x00010404L, 0x01010400L, + 0x00000404L, 0x01000400L, 0x01000400L, 0x00000000L, + 0x00010004L, 0x00010400L, 0x00000000L, 0x01010004L }; + +static unsigned long SP2[64] = { + 0x80108020L, 0x80008000L, 0x00008000L, 0x00108020L, + 0x00100000L, 0x00000020L, 0x80100020L, 0x80008020L, + 0x80000020L, 0x80108020L, 0x80108000L, 0x80000000L, + 0x80008000L, 0x00100000L, 0x00000020L, 0x80100020L, + 0x00108000L, 0x00100020L, 0x80008020L, 0x00000000L, + 0x80000000L, 0x00008000L, 0x00108020L, 0x80100000L, + 0x00100020L, 0x80000020L, 0x00000000L, 0x00108000L, + 0x00008020L, 0x80108000L, 0x80100000L, 0x00008020L, + 0x00000000L, 0x00108020L, 0x80100020L, 0x00100000L, + 0x80008020L, 0x80100000L, 0x80108000L, 0x00008000L, + 0x80100000L, 0x80008000L, 0x00000020L, 0x80108020L, + 0x00108020L, 0x00000020L, 0x00008000L, 0x80000000L, + 0x00008020L, 0x80108000L, 0x00100000L, 0x80000020L, + 0x00100020L, 0x80008020L, 0x80000020L, 0x00100020L, + 0x00108000L, 0x00000000L, 0x80008000L, 0x00008020L, + 0x80000000L, 0x80100020L, 0x80108020L, 0x00108000L }; + +static unsigned long SP3[64] = { + 0x00000208L, 0x08020200L, 0x00000000L, 0x08020008L, + 0x08000200L, 0x00000000L, 0x00020208L, 0x08000200L, + 0x00020008L, 0x08000008L, 0x08000008L, 0x00020000L, + 0x08020208L, 0x00020008L, 0x08020000L, 0x00000208L, + 0x08000000L, 0x00000008L, 0x08020200L, 0x00000200L, + 0x00020200L, 0x08020000L, 0x08020008L, 0x00020208L, + 0x08000208L, 0x00020200L, 0x00020000L, 0x08000208L, + 0x00000008L, 0x08020208L, 0x00000200L, 0x08000000L, + 0x08020200L, 0x08000000L, 0x00020008L, 0x00000208L, + 0x00020000L, 0x08020200L, 0x08000200L, 0x00000000L, + 0x00000200L, 0x00020008L, 0x08020208L, 0x08000200L, + 0x08000008L, 0x00000200L, 0x00000000L, 0x08020008L, + 0x08000208L, 0x00020000L, 0x08000000L, 0x08020208L, + 0x00000008L, 0x00020208L, 0x00020200L, 0x08000008L, + 0x08020000L, 0x08000208L, 0x00000208L, 0x08020000L, + 0x00020208L, 0x00000008L, 0x08020008L, 0x00020200L }; + +static unsigned long SP4[64] = { + 0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L, + 0x00802080L, 0x00800081L, 0x00800001L, 0x00002001L, + 0x00000000L, 0x00802000L, 0x00802000L, 0x00802081L, + 0x00000081L, 0x00000000L, 0x00800080L, 0x00800001L, + 0x00000001L, 0x00002000L, 0x00800000L, 0x00802001L, + 0x00000080L, 0x00800000L, 0x00002001L, 0x00002080L, + 0x00800081L, 0x00000001L, 0x00002080L, 0x00800080L, + 0x00002000L, 0x00802080L, 0x00802081L, 0x00000081L, + 0x00800080L, 0x00800001L, 0x00802000L, 0x00802081L, + 0x00000081L, 0x00000000L, 0x00000000L, 0x00802000L, + 0x00002080L, 0x00800080L, 0x00800081L, 0x00000001L, + 0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L, + 0x00802081L, 0x00000081L, 0x00000001L, 0x00002000L, + 0x00800001L, 0x00002001L, 0x00802080L, 0x00800081L, + 0x00002001L, 0x00002080L, 0x00800000L, 0x00802001L, + 0x00000080L, 0x00800000L, 0x00002000L, 0x00802080L }; + +static unsigned long SP5[64] = { + 0x00000100L, 0x02080100L, 0x02080000L, 0x42000100L, + 0x00080000L, 0x00000100L, 0x40000000L, 0x02080000L, + 0x40080100L, 0x00080000L, 0x02000100L, 0x40080100L, + 0x42000100L, 0x42080000L, 0x00080100L, 0x40000000L, + 0x02000000L, 0x40080000L, 0x40080000L, 0x00000000L, + 0x40000100L, 0x42080100L, 0x42080100L, 0x02000100L, + 0x42080000L, 0x40000100L, 0x00000000L, 0x42000000L, + 0x02080100L, 0x02000000L, 0x42000000L, 0x00080100L, + 0x00080000L, 0x42000100L, 0x00000100L, 0x02000000L, + 0x40000000L, 0x02080000L, 0x42000100L, 0x40080100L, + 0x02000100L, 0x40000000L, 0x42080000L, 0x02080100L, + 0x40080100L, 0x00000100L, 0x02000000L, 0x42080000L, + 0x42080100L, 0x00080100L, 0x42000000L, 0x42080100L, + 0x02080000L, 0x00000000L, 0x40080000L, 0x42000000L, + 0x00080100L, 0x02000100L, 0x40000100L, 0x00080000L, + 0x00000000L, 0x40080000L, 0x02080100L, 0x40000100L }; + +static unsigned long SP6[64] = { + 0x20000010L, 0x20400000L, 0x00004000L, 0x20404010L, + 0x20400000L, 0x00000010L, 0x20404010L, 0x00400000L, + 0x20004000L, 0x00404010L, 0x00400000L, 0x20000010L, + 0x00400010L, 0x20004000L, 0x20000000L, 0x00004010L, + 0x00000000L, 0x00400010L, 0x20004010L, 0x00004000L, + 0x00404000L, 0x20004010L, 0x00000010L, 0x20400010L, + 0x20400010L, 0x00000000L, 0x00404010L, 0x20404000L, + 0x00004010L, 0x00404000L, 0x20404000L, 0x20000000L, + 0x20004000L, 0x00000010L, 0x20400010L, 0x00404000L, + 0x20404010L, 0x00400000L, 0x00004010L, 0x20000010L, + 0x00400000L, 0x20004000L, 0x20000000L, 0x00004010L, + 0x20000010L, 0x20404010L, 0x00404000L, 0x20400000L, + 0x00404010L, 0x20404000L, 0x00000000L, 0x20400010L, + 0x00000010L, 0x00004000L, 0x20400000L, 0x00404010L, + 0x00004000L, 0x00400010L, 0x20004010L, 0x00000000L, + 0x20404000L, 0x20000000L, 0x00400010L, 0x20004010L }; + +static unsigned long SP7[64] = { + 0x00200000L, 0x04200002L, 0x04000802L, 0x00000000L, + 0x00000800L, 0x04000802L, 0x00200802L, 0x04200800L, + 0x04200802L, 0x00200000L, 0x00000000L, 0x04000002L, + 0x00000002L, 0x04000000L, 0x04200002L, 0x00000802L, + 0x04000800L, 0x00200802L, 0x00200002L, 0x04000800L, + 0x04000002L, 0x04200000L, 0x04200800L, 0x00200002L, + 0x04200000L, 0x00000800L, 0x00000802L, 0x04200802L, + 0x00200800L, 0x00000002L, 0x04000000L, 0x00200800L, + 0x04000000L, 0x00200800L, 0x00200000L, 0x04000802L, + 0x04000802L, 0x04200002L, 0x04200002L, 0x00000002L, + 0x00200002L, 0x04000000L, 0x04000800L, 0x00200000L, + 0x04200800L, 0x00000802L, 0x00200802L, 0x04200800L, + 0x00000802L, 0x04000002L, 0x04200802L, 0x04200000L, + 0x00200800L, 0x00000000L, 0x00000002L, 0x04200802L, + 0x00000000L, 0x00200802L, 0x04200000L, 0x00000800L, + 0x04000002L, 0x04000800L, 0x00000800L, 0x00200002L }; + +static unsigned long SP8[64] = { + 0x10001040L, 0x00001000L, 0x00040000L, 0x10041040L, + 0x10000000L, 0x10001040L, 0x00000040L, 0x10000000L, + 0x00040040L, 0x10040000L, 0x10041040L, 0x00041000L, + 0x10041000L, 0x00041040L, 0x00001000L, 0x00000040L, + 0x10040000L, 0x10000040L, 0x10001000L, 0x00001040L, + 0x00041000L, 0x00040040L, 0x10040040L, 0x10041000L, + 0x00001040L, 0x00000000L, 0x00000000L, 0x10040040L, + 0x10000040L, 0x10001000L, 0x00041040L, 0x00040000L, + 0x00041040L, 0x00040000L, 0x10041000L, 0x00001000L, + 0x00000040L, 0x10040040L, 0x00001000L, 0x00041040L, + 0x10001000L, 0x00000040L, 0x10000040L, 0x10040000L, + 0x10040040L, 0x10000000L, 0x00040000L, 0x10001040L, + 0x00000000L, 0x10041040L, 0x00040040L, 0x10000040L, + 0x10040000L, 0x10001000L, 0x10001040L, 0x00000000L, + 0x10041040L, 0x00041000L, 0x00041000L, 0x00001040L, + 0x00001040L, 0x00040040L, 0x10000000L, 0x10041000L }; + +static void desfunc(block, keys) +register unsigned long *block, *keys; +{ + register unsigned long fval, work, right, leftt; + register int round; + + leftt = block[0]; + right = block[1]; + work = ((leftt >> 4) ^ right) & 0x0f0f0f0fL; + right ^= work; + leftt ^= (work << 4); + work = ((leftt >> 16) ^ right) & 0x0000ffffL; + right ^= work; + leftt ^= (work << 16); + work = ((right >> 2) ^ leftt) & 0x33333333L; + leftt ^= work; + right ^= (work << 2); + work = ((right >> 8) ^ leftt) & 0x00ff00ffL; + leftt ^= work; + right ^= (work << 8); + right = ((right << 1) | ((right >> 31) & 1L)) & 0xffffffffL; + work = (leftt ^ right) & 0xaaaaaaaaL; + leftt ^= work; + right ^= work; + leftt = ((leftt << 1) | ((leftt >> 31) & 1L)) & 0xffffffffL; + + for( round = 0; round < 8; round++ ) { + work = (right << 28) | (right >> 4); + work ^= *keys++; + fval = SP7[ work & 0x3fL]; + fval |= SP5[(work >> 8) & 0x3fL]; + fval |= SP3[(work >> 16) & 0x3fL]; + fval |= SP1[(work >> 24) & 0x3fL]; + work = right ^ *keys++; + fval |= SP8[ work & 0x3fL]; + fval |= SP6[(work >> 8) & 0x3fL]; + fval |= SP4[(work >> 16) & 0x3fL]; + fval |= SP2[(work >> 24) & 0x3fL]; + leftt ^= fval; + work = (leftt << 28) | (leftt >> 4); + work ^= *keys++; + fval = SP7[ work & 0x3fL]; + fval |= SP5[(work >> 8) & 0x3fL]; + fval |= SP3[(work >> 16) & 0x3fL]; + fval |= SP1[(work >> 24) & 0x3fL]; + work = leftt ^ *keys++; + fval |= SP8[ work & 0x3fL]; + fval |= SP6[(work >> 8) & 0x3fL]; + fval |= SP4[(work >> 16) & 0x3fL]; + fval |= SP2[(work >> 24) & 0x3fL]; + right ^= fval; + } + + right = (right << 31) | (right >> 1); + work = (leftt ^ right) & 0xaaaaaaaaL; + leftt ^= work; + right ^= work; + leftt = (leftt << 31) | (leftt >> 1); + work = ((leftt >> 8) ^ right) & 0x00ff00ffL; + right ^= work; + leftt ^= (work << 8); + work = ((leftt >> 2) ^ right) & 0x33333333L; + right ^= work; + leftt ^= (work << 2); + work = ((right >> 16) ^ leftt) & 0x0000ffffL; + leftt ^= work; + right ^= (work << 16); + work = ((right >> 4) ^ leftt) & 0x0f0f0f0fL; + leftt ^= work; + right ^= (work << 4); + *block++ = right; + *block = leftt; + return; + } + +/* Validation sets: + * + * Single-length key, single-length plaintext - + * Key : 0123 4567 89ab cdef + * Plain : 0123 4567 89ab cde7 + * Cipher : c957 4425 6a5e d31d + * + * Double-length key, single-length plaintext - + * Key : 0123 4567 89ab cdef fedc ba98 7654 3210 + * Plain : 0123 4567 89ab cde7 + * Cipher : 7f1d 0a77 826b 8aff + * + * Double-length key, double-length plaintext - + * Key : 0123 4567 89ab cdef fedc ba98 7654 3210 + * Plain : 0123 4567 89ab cdef 0123 4567 89ab cdff + * Cipher : 27a0 8440 406a df60 278f 47cf 42d6 15d7 + * + * Triple-length key, single-length plaintext - + * Key : 0123 4567 89ab cdef fedc ba98 7654 3210 89ab cdef 0123 4567 + * Plain : 0123 4567 89ab cde7 + * Cipher : de0b 7c06 ae5e 0ed5 + * + * Triple-length key, double-length plaintext - + * Key : 0123 4567 89ab cdef fedc ba98 7654 3210 89ab cdef 0123 4567 + * Plain : 0123 4567 89ab cdef 0123 4567 89ab cdff + * Cipher : ad0d 1b30 ac17 cf07 0ed1 1c63 81e4 4de5 + * + * d3des V5.0a rwo 9208.07 18:44 Graven Imagery + **********************************************************************/ diff -r 08374728639d d3des.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/d3des.h Wed Aug 08 15:04:47 2007 -0400 @@ -0,0 +1,51 @@ +/* + * This is D3DES (V5.09) by Richard Outerbridge with the double and + * triple-length support removed for use in VNC. + * + * These changes are: + * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* d3des.h - + * + * Headers and defines for d3des.c + * Graven Imagery, 1992. + * + * Copyright (c) 1988,1989,1990,1991,1992 by Richard Outerbridge + * (GEnie : OUTER; CIS : [71755,204]) + */ + +#define EN0 0 /* MODE == encrypt */ +#define DE1 1 /* MODE == decrypt */ + +extern void deskey(unsigned char *, int); +/* hexkey[8] MODE + * Sets the internal key register according to the hexadecimal + * key contained in the 8 bytes of hexkey, according to the DES, + * for encryption or decryption according to MODE. + */ + +extern void usekey(unsigned long *); +/* cookedkey[32] + * Loads the internal key register with the data in cookedkey. + */ + +extern void cpkey(unsigned long *); +/* cookedkey[32] + * Copies the contents of the internal key register into the storage + * located at &cookedkey[0]. + */ + +extern void des(unsigned char *, unsigned char *); +/* from[8] to[8] + * Encrypts/Decrypts (according to the key currently loaded in the + * internal key register) one block of eight bytes at address 'from' + * into the block at address 'to'. They can be the same. + */ + +/* d3des.h V5.09 rwo 9208.04 15:06 Graven Imagery + ********************************************************************/ diff -r 08374728639d monitor.c --- a/monitor.c Wed Aug 08 15:04:44 2007 -0400 +++ b/monitor.c Mon Aug 13 11:36:07 2007 -0400 @@ -403,8 +403,17 @@ static void do_change_block(const char * static void do_change_vnc(const char *target) { - if (vnc_display_open(NULL, target) < 0) - term_printf("could not start VNC server on %s\n", target); + if (strcmp(target, "passwd") == 0 || + strcmp(target, "password") == 0) { + char password[9]; + monitor_readline("Password: ", 1, password, sizeof(password)-1); + password[sizeof(password)-1] = '\0'; + if (vnc_display_password(NULL, password) < 0) + term_printf("could not set VNC server password\n"); + } else { + if (vnc_display_open(NULL, target) < 0) + term_printf("could not start VNC server on %s\n", target); + } } static void do_change(const char *device, const char *target) diff -r 08374728639d vl.h --- a/vl.h Wed Aug 08 15:04:44 2007 -0400 +++ b/vl.h Wed Aug 08 15:04:47 2007 -0400 @@ -971,6 +971,7 @@ void vnc_display_init(DisplayState *ds); void vnc_display_init(DisplayState *ds); void vnc_display_close(DisplayState *ds); int vnc_display_open(DisplayState *ds, const char *display); +int vnc_display_password(DisplayState *ds, const char *password); void do_info_vnc(void); /* x_keymap.c */ diff -r 08374728639d vnc.c --- a/vnc.c Wed Aug 08 15:04:44 2007 -0400 +++ b/vnc.c Mon Aug 13 11:51:38 2007 -0400 @@ -30,6 +30,15 @@ #include "vnc_keysym.h" #include "keymaps.c" +#include "d3des.h" + +// #define _VNC_DEBUG + +#ifdef _VNC_DEBUG +#define VNC_DEBUG(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define VNC_DEBUG(fmt, ...) do { } while (0) +#endif typedef struct Buffer { @@ -53,6 +62,20 @@ typedef void VncSendHextileTile(VncState #define VNC_MAX_WIDTH 2048 #define VNC_MAX_HEIGHT 2048 #define VNC_DIRTY_WORDS (VNC_MAX_WIDTH / (16 * 32)) + +#define VNC_AUTH_CHALLENGE_SIZE 16 + +enum { + VNC_AUTH_INVALID = 0, + VNC_AUTH_NONE = 1, + VNC_AUTH_VNC = 2, + VNC_AUTH_RA2 = 5, + VNC_AUTH_RA2NE = 6, + VNC_AUTH_TIGHT = 16, + VNC_AUTH_ULTRA = 17, + VNC_AUTH_TLS = 18, + VNC_AUTH_VENCRYPT = 19 +}; struct VncState { @@ -73,7 +96,13 @@ struct VncState int last_x; int last_y; + int major; + int minor; + char *display; + char *password; + int auth; + char challenge[VNC_AUTH_CHALLENGE_SIZE]; Buffer output; Buffer input; @@ -1125,23 +1154,171 @@ static int protocol_client_init(VncState return 0; } +static void make_challenge(VncState *vs) +{ + int i; + + srand(time(NULL)+getpid()+getpid()*987654+rand()); + + for (i = 0 ; i < sizeof(vs->challenge) ; i++) + vs->challenge[i] = (int) (256.0*rand()/(RAND_MAX+1.0)); +} + +static int protocol_client_auth_vnc(VncState *vs, char *data, size_t len) +{ + char response[VNC_AUTH_CHALLENGE_SIZE]; + int i, j, pwlen; + char key[8]; + + if (!vs->password || !vs->password[0]) { + VNC_DEBUG("No password configured on server"); + vnc_write_u32(vs, 1); /* Reject auth */ + if (vs->minor >= 8) { + static const char err[] = "Authentication failed"; + vnc_write_u32(vs, sizeof(err)); + vnc_write(vs, err, sizeof(err)); + } + vnc_flush(vs); + vnc_client_error(vs); + return 0; + } + + memcpy(response, vs->challenge, VNC_AUTH_CHALLENGE_SIZE); + + /* Calculate the expected challenge response */ + pwlen = strlen(vs->password); + for (i=0; i<sizeof(key); i++) + key[i] = i<pwlen ? vs->password[i] : 0; + deskey(key, EN0); + for (j = 0; j < VNC_AUTH_CHALLENGE_SIZE; j += 8) + des(response+j, response+j); + + /* Compare expected vs actual challenge response */ + if (memcmp(response, data, VNC_AUTH_CHALLENGE_SIZE) != 0) { + VNC_DEBUG("Client challenge reponse did not match\n"); + vnc_write_u32(vs, 1); /* Reject auth */ + if (vs->minor >= 8) { + static const char err[] = "Authentication failed"; + vnc_write_u32(vs, sizeof(err)); + vnc_write(vs, err, sizeof(err)); + } + vnc_flush(vs); + vnc_client_error(vs); + } else { + VNC_DEBUG("Accepting VNC challenge response\n"); + vnc_write_u32(vs, 0); /* Accept auth */ + vnc_flush(vs); + + vnc_read_when(vs, protocol_client_init, 1); + } + return 0; +} + +static int start_auth_vnc(VncState *vs) +{ + make_challenge(vs); + /* Send client a 'random' challenge */ + vnc_write(vs, vs->challenge, sizeof(vs->challenge)); + vnc_flush(vs); + + vnc_read_when(vs, protocol_client_auth_vnc, sizeof(vs->challenge)); + return 0; +} + +static int protocol_client_auth(VncState *vs, char *data, size_t len) +{ + /* We only advertise 1 auth scheme at a time, so client + * must pick the one we sent. Verify this */ + if (data[0] != vs->auth) { /* Reject auth */ + VNC_DEBUG("Reject auth %d\n", (int)data[0]); + vnc_write_u32(vs, 1); + if (vs->minor >= 8) { + static const char err[] = "Authentication failed"; + vnc_write_u32(vs, sizeof(err)); + vnc_write(vs, err, sizeof(err)); + } + vnc_client_error(vs); + } else { /* Accept requested auth */ + VNC_DEBUG("Client requested auth %d\n", (int)data[0]); + switch (vs->auth) { + case VNC_AUTH_NONE: + VNC_DEBUG("Accept auth none\n"); + vnc_write_u32(vs, 0); /* Accept auth completion */ + vnc_read_when(vs, protocol_client_init, 1); + break; + + case VNC_AUTH_VNC: + VNC_DEBUG("Start VNC auth\n"); + return start_auth_vnc(vs); + + default: /* Should not be possible, but just in case */ + VNC_DEBUG("Reject auth %d\n", vs->auth); + vnc_write_u8(vs, 1); + if (vs->minor >= 8) { + static const char err[] = "Authentication failed"; + vnc_write_u32(vs, sizeof(err)); + vnc_write(vs, err, sizeof(err)); + } + vnc_client_error(vs); + } + } + return 0; +} + static int protocol_version(VncState *vs, char *version, size_t len) { char local[13]; - int maj, min; memcpy(local, version, 12); local[12] = 0; - if (sscanf(local, "RFB %03d.%03d\n", &maj, &min) != 2) { + if (sscanf(local, "RFB %03d.%03d\n", &vs->major, &vs->minor) != 2) { + VNC_DEBUG("Malformed protocol version %s\n", local); vnc_client_error(vs); return 0; } - - vnc_write_u32(vs, 1); /* None */ - vnc_flush(vs); - - vnc_read_when(vs, protocol_client_init, 1); + VNC_DEBUG("Client request protocol version %d.%d\n", vs->major, vs->minor); + if (vs->major != 3 || + (vs->minor != 3 && + vs->minor != 5 && + vs->minor != 7 && + vs->minor != 8)) { + VNC_DEBUG("Unsupported client version\n"); + vnc_write_u32(vs, VNC_AUTH_INVALID); + vnc_flush(vs); + vnc_client_error(vs); + return 0; + } + /* Some broken client report v3.5 which spec requires to be treated + * as equivalent to v3.3 by servers + */ + if (vs->minor == 5) + vs->minor = 3; + + if (vs->minor == 3) { + if (vs->auth == VNC_AUTH_NONE) { + VNC_DEBUG("Tell client auth none\n"); + vnc_write_u32(vs, vs->auth); + vnc_flush(vs); + vnc_read_when(vs, protocol_client_init, 1); + } else if (vs->auth == VNC_AUTH_VNC) { + VNC_DEBUG("Tell client VNC auth\n"); + vnc_write_u32(vs, vs->auth); + vnc_flush(vs); + start_auth_vnc(vs); + } else { + VNC_DEBUG("Unsupported auth %d for protocol 3.3\n", vs->auth); + vnc_write_u32(vs, VNC_AUTH_INVALID); + vnc_flush(vs); + vnc_client_error(vs); + } + } else { + VNC_DEBUG("Telling client we support auth %d\n", vs->auth); + vnc_write_u8(vs, 1); /* num auth */ + vnc_write_u8(vs, vs->auth); + vnc_read_when(vs, protocol_client_auth, 1); + vnc_flush(vs); + } return 0; } @@ -1156,7 +1333,7 @@ static void vnc_listen_read(void *opaque if (vs->csock != -1) { socket_set_nonblock(vs->csock); qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, opaque); - vnc_write(vs, "RFB 003.003\n", 12); + vnc_write(vs, "RFB 003.008\n", 12); vnc_flush(vs); vnc_read_when(vs, protocol_version, 12); memset(vs->old_data, 0, vs->ds->linesize * vs->ds->height); @@ -1180,6 +1357,7 @@ void vnc_display_init(DisplayState *ds) ds->opaque = vs; vnc_state = vs; vs->display = NULL; + vs->password = NULL; vs->lsock = -1; vs->csock = -1; @@ -1226,9 +1404,26 @@ void vnc_display_close(DisplayState *ds) buffer_reset(&vs->output); vs->need_update = 0; } -} - -int vnc_display_open(DisplayState *ds, const char *arg) + vs->auth = VNC_AUTH_INVALID; +} + +int vnc_display_password(DisplayState *ds, const char *password) +{ + VncState *vs = ds ? (VncState *)ds->opaque : vnc_state; + + if (vs->password) { + qemu_free(vs->password); + vs->password = NULL; + } + if (password && password[0]) { + if (!(vs->password = qemu_strdup(password))) + return -1; + } + + return 0; +} + +int vnc_display_open(DisplayState *ds, const char *display) { struct sockaddr *addr; struct sockaddr_in iaddr; @@ -1239,15 +1434,32 @@ int vnc_display_open(DisplayState *ds, c socklen_t addrlen; const char *p; VncState *vs = ds ? (VncState *)ds->opaque : vnc_state; + const char *options; + int password = 0; vnc_display_close(ds); - if (strcmp(arg, "none") == 0) + if (strcmp(display, "none") == 0) return 0; - if (!(vs->display = strdup(arg))) + if (!(vs->display = strdup(display))) return -1; + + options = display; + while ((options = strchr(options, ','))) { + options++; + if (strncmp(options, "password", 8) == 0) + password = 1; /* Require password auth */ + } + + if (password) { + VNC_DEBUG("Initializing VNC server with password auth\n"); + vs->auth = VNC_AUTH_VNC; + } else { + VNC_DEBUG("Initializing VNC server with no auth\n"); + vs->auth = VNC_AUTH_NONE; + } #ifndef _WIN32 - if (strstart(arg, "unix:", &p)) { + if (strstart(display, "unix:", &p)) { addr = (struct sockaddr *)&uaddr; addrlen = sizeof(uaddr); @@ -1270,7 +1482,7 @@ int vnc_display_open(DisplayState *ds, c addr = (struct sockaddr *)&iaddr; addrlen = sizeof(iaddr); - if (parse_host_port(&iaddr, arg) < 0) { + if (parse_host_port(&iaddr, display) < 0) { fprintf(stderr, "Could not parse VNC address\n"); free(vs->display); vs->display = NULL; -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=| ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [Qemu-devel] PATCH 4/8: VeNCrypt basic TLS support 2007-08-13 19:25 [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Daniel P. Berrange ` (2 preceding siblings ...) 2007-08-13 19:44 ` [Qemu-devel] PATCH 3/8: VNC password authentication Daniel P. Berrange @ 2007-08-13 19:46 ` Daniel P. Berrange 2007-08-13 19:47 ` [Qemu-devel] PATCH 5/8: x509 certificate for server Daniel P. Berrange ` (4 subsequent siblings) 8 siblings, 0 replies; 10+ messages in thread From: Daniel P. Berrange @ 2007-08-13 19:46 UTC (permalink / raw) To: qemu-devel This patch introduces minimal support for the VeNCrypt protocol extension. This layers use of TLS (aka SSL) into the VNC data stream, providing session encryption. This patch is the bare minimum protocol support. It is enabled by using the 'tls' option flag eg "-vnc :1,tls' This is not secure on its own since it uses anonymous credentials. The next patches will introduce x509 certificate credentials. The configure script is setup to automatically look for the GNU TLS libraries using pkgconfig. If they are not found then TLS support is disabled. The --disable-vnc-tls argument to configure can be used to disable support even if available. This should avoid any breakage on platforms without the GNU TLS libraries. Daniel P. Berrange <berrange@redhat.com> diff -r b859a28df03a Makefile.target --- a/Makefile.target Wed Aug 08 15:04:52 2007 -0400 +++ b/Makefile.target Wed Aug 08 15:04:53 2007 -0400 @@ -405,6 +405,11 @@ endif endif AUDIODRV+= wavcapture.o +ifdef CONFIG_VNC_TLS +CPPFLAGS += $(CONFIG_VNC_TLS_CFLAGS) +LIBS += $(CONFIG_VNC_TLS_LIBS) +endif + VL_OBJS += i2c.o smbus.o # SCSI layer diff -r b859a28df03a configure --- a/configure Wed Aug 08 15:04:52 2007 -0400 +++ b/configure Wed Aug 08 15:04:53 2007 -0400 @@ -89,6 +89,7 @@ fmod="no" fmod="no" fmod_lib="" fmod_inc="" +vnc_tls="yes" bsd="no" linux="no" kqemu="no" @@ -252,6 +253,8 @@ for opt do ;; --fmod-inc=*) fmod_inc="$optarg" ;; + --disable-vnc-tls) vnc_tls="no" + ;; --enable-mingw32) mingw32="yes" ; cross_prefix="i386-mingw32-" ; linux_user="no" ;; --disable-slirp) slirp="no" @@ -362,6 +365,7 @@ echo " --enable-alsa enable echo " --enable-alsa enable ALSA audio driver" echo " --enable-fmod enable FMOD audio driver" echo " --enable-dsound enable DirectSound audio driver" +echo " --disable-vnc-tls disable TLS encryption for VNC server" echo " --enable-system enable all system emulation targets" echo " --disable-system disable all system emulation targets" echo " --enable-linux-user enable all linux usermode emulation targets" @@ -589,6 +593,16 @@ fi # -z $sdl fi # -z $sdl ########################################## +# VNC TLS detection +if test "$vnc_tls" = "yes" ; then + `pkg-config gnutls` || vnc_tls="no" +fi +if test "$vnc_tls" = "yes" ; then + vnc_tls_cflags=`pkg-config --cflags gnutls` + vnc_tls_libs=`pkg-config --libs gnutls` +fi + +########################################## # alsa sound support libraries if test "$alsa" = "yes" ; then @@ -675,6 +689,11 @@ fi fi echo "FMOD support $fmod $fmod_support" echo "OSS support $oss" +echo "VNC TLS support $vnc_tls" +if test "$vnc_tls" = "yes" ; then + echo " TLS CFLAGS $vnc_tls_cflags" + echo " TLS LIBS $vnc_tls_libs" +fi if test -n "$sparc_cpu"; then echo "Target Sparc Arch $sparc_cpu" fi @@ -847,6 +866,12 @@ if test "$fmod" = "yes" ; then echo "CONFIG_FMOD_INC=$fmod_inc" >> $config_mak echo "#define CONFIG_FMOD 1" >> $config_h fi +if test "$vnc_tls" = "yes" ; then + echo "CONFIG_VNC_TLS=yes" >> $config_mak + echo "CONFIG_VNC_TLS_CFLAGS=$vnc_tls_cflags" >> $config_mak + echo "CONFIG_VNC_TLS_LIBS=$vnc_tls_libs" >> $config_mak + echo "#define CONFIG_VNC_TLS 1" >> $config_h +fi qemu_version=`head $source_path/VERSION` echo "VERSION=$qemu_version" >>$config_mak echo "#define QEMU_VERSION \"$qemu_version\"" >> $config_h diff -r b859a28df03a vnc.c --- a/vnc.c Wed Aug 08 15:04:52 2007 -0400 +++ b/vnc.c Wed Aug 08 15:04:53 2007 -0400 @@ -32,13 +32,26 @@ #include "keymaps.c" #include "d3des.h" -// #define _VNC_DEBUG - -#ifdef _VNC_DEBUG +#if CONFIG_VNC_TLS +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#endif /* CONFIG_VNC_TLS */ + +// #define _VNC_DEBUG 1 + +#if _VNC_DEBUG #define VNC_DEBUG(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) + +#if CONFIG_VNC_TLS && _VNC_DEBUG >= 2 +/* Very verbose, so only enabled for _VNC_DEBUG >= 2 */ +static void vnc_debug_gnutls_log(int level, const char* str) { + VNC_DEBUG("%d %s", level, str); +} +#endif /* CONFIG_VNC_TLS && _VNC_DEBUG */ #else #define VNC_DEBUG(fmt, ...) do { } while (0) #endif + typedef struct Buffer { @@ -76,6 +89,23 @@ enum { VNC_AUTH_TLS = 18, VNC_AUTH_VENCRYPT = 19 }; + +#if CONFIG_VNC_TLS +enum { + VNC_WIREMODE_CLEAR, + VNC_WIREMODE_TLS, +}; + +enum { + VNC_AUTH_VENCRYPT_PLAIN = 256, + VNC_AUTH_VENCRYPT_TLSNONE = 257, + VNC_AUTH_VENCRYPT_TLSVNC = 258, + VNC_AUTH_VENCRYPT_TLSPLAIN = 259, + VNC_AUTH_VENCRYPT_X509NONE = 260, + VNC_AUTH_VENCRYPT_X509VNC = 261, + VNC_AUTH_VENCRYPT_X509PLAIN = 262, +}; +#endif /* CONFIG_VNC_TLS */ struct VncState { @@ -102,7 +132,15 @@ struct VncState char *display; char *password; int auth; +#if CONFIG_VNC_TLS + int subauth; +#endif char challenge[VNC_AUTH_CHALLENGE_SIZE]; + +#if CONFIG_VNC_TLS + int wiremode; + gnutls_session_t tls_session; +#endif Buffer output; Buffer input; @@ -579,12 +617,20 @@ static int vnc_client_io_error(VncState if (ret == -1 && (last_errno == EINTR || last_errno == EAGAIN)) return 0; + VNC_DEBUG("Closing down client sock %d %d\n", ret, ret < 0 ? last_errno : 0); qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL); closesocket(vs->csock); vs->csock = -1; buffer_reset(&vs->input); buffer_reset(&vs->output); vs->need_update = 0; +#if CONFIG_VNC_TLS + if (vs->tls_session) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + } + vs->wiremode = VNC_WIREMODE_CLEAR; +#endif /* CONFIG_VNC_TLS */ return 0; } return ret; @@ -600,7 +646,19 @@ static void vnc_client_write(void *opaqu long ret; VncState *vs = opaque; - ret = send(vs->csock, vs->output.buffer, vs->output.offset, 0); +#if CONFIG_VNC_TLS + if (vs->tls_session) { + ret = gnutls_write(vs->tls_session, vs->output.buffer, vs->output.offset); + if (ret < 0) { + if (ret == GNUTLS_E_AGAIN) + errno = EAGAIN; + else + errno = EIO; + ret = -1; + } + } else +#endif /* CONFIG_VNC_TLS */ + ret = send(vs->csock, vs->output.buffer, vs->output.offset, 0); ret = vnc_client_io_error(vs, ret, socket_error()); if (!ret) return; @@ -626,7 +684,19 @@ static void vnc_client_read(void *opaque buffer_reserve(&vs->input, 4096); - ret = recv(vs->csock, buffer_end(&vs->input), 4096, 0); +#if CONFIG_VNC_TLS + if (vs->tls_session) { + ret = gnutls_read(vs->tls_session, buffer_end(&vs->input), 4096); + if (ret < 0) { + if (ret == GNUTLS_E_AGAIN) + errno = EAGAIN; + else + errno = EIO; + ret = -1; + } + } else +#endif /* CONFIG_VNC_TLS */ + ret = recv(vs->csock, buffer_end(&vs->input), 4096, 0); ret = vnc_client_io_error(vs, ret, socket_error()); if (!ret) return; @@ -720,6 +790,41 @@ static uint32_t read_u32(uint8_t *data, return ((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]); } + +#if CONFIG_VNC_TLS +ssize_t vnc_tls_push(gnutls_transport_ptr_t transport, + const void *data, + size_t len) { + struct VncState *vs = (struct VncState *)transport; + int ret; + + retry: + ret = send(vs->csock, data, len, 0); + if (ret < 0) { + if (errno == EINTR) + goto retry; + return -1; + } + return ret; +} + + +ssize_t vnc_tls_pull(gnutls_transport_ptr_t transport, + void *data, + size_t len) { + struct VncState *vs = (struct VncState *)transport; + int ret; + + retry: + ret = recv(vs->csock, data, len, 0); + if (ret < 0) { + if (errno == EINTR) + goto retry; + return -1; + } + return ret; +} +#endif /* CONFIG_VNC_TLS */ static void client_cut_text(VncState *vs, size_t len, char *text) { @@ -1225,6 +1330,243 @@ static int start_auth_vnc(VncState *vs) return 0; } + +#if CONFIG_VNC_TLS +#define DH_BITS 1024 +static gnutls_dh_params_t dh_params; + +static int vnc_tls_initialize(void) +{ + static int tlsinitialized = 0; + + if (tlsinitialized) + return 1; + + if (gnutls_global_init () < 0) + return 0; + + /* XXX ought to re-generate diffie-hellmen params periodically */ + if (gnutls_dh_params_init (&dh_params) < 0) + return 0; + if (gnutls_dh_params_generate2 (dh_params, DH_BITS) < 0) + return 0; + +#if _VNC_DEBUG == 2 + gnutls_global_set_log_level(10); + gnutls_global_set_log_function(vnc_debug_gnutls_log); +#endif + + tlsinitialized = 1; + + return 1; +} + +static gnutls_anon_server_credentials vnc_tls_initialize_anon_cred(void) +{ + gnutls_anon_server_credentials anon_cred; + int ret; + + if ((ret = gnutls_anon_allocate_server_credentials(&anon_cred)) < 0) { + VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret)); + return NULL; + } + + gnutls_anon_set_server_dh_params(anon_cred, dh_params); + + return anon_cred; +} + + +static int start_auth_vencrypt_subauth(VncState *vs) +{ + switch (vs->subauth) { + case VNC_AUTH_VENCRYPT_TLSNONE: + VNC_DEBUG("Accept TLS auth none\n"); + vnc_write_u32(vs, 0); /* Accept auth completion */ + vnc_read_when(vs, protocol_client_init, 1); + break; + + case VNC_AUTH_VENCRYPT_TLSVNC: + VNC_DEBUG("Start TLS auth VNC\n"); + return start_auth_vnc(vs); + + default: /* Should not be possible, but just in case */ + VNC_DEBUG("Reject auth %d\n", vs->auth); + vnc_write_u8(vs, 1); + if (vs->minor >= 8) { + static const char err[] = "Unsupported authentication type"; + vnc_write_u32(vs, sizeof(err)); + vnc_write(vs, err, sizeof(err)); + } + vnc_client_error(vs); + } + + return 0; +} + +static void vnc_handshake_io(void *opaque); + +static int vnc_continue_handshake(struct VncState *vs) { + int ret; + + if ((ret = gnutls_handshake(vs->tls_session)) < 0) { + if (!gnutls_error_is_fatal(ret)) { + VNC_DEBUG("Handshake interrupted (blocking)\n"); + if (!gnutls_record_get_direction(vs->tls_session)) + qemu_set_fd_handler(vs->csock, vnc_handshake_io, NULL, vs); + else + qemu_set_fd_handler(vs->csock, NULL, vnc_handshake_io, vs); + return 0; + } + VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret)); + vnc_client_error(vs); + return -1; + } + + VNC_DEBUG("Handshake done, switching to TLS data mode\n"); + vs->wiremode = VNC_WIREMODE_TLS; + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs); + + return start_auth_vencrypt_subauth(vs); +} + +static void vnc_handshake_io(void *opaque) { + struct VncState *vs = (struct VncState *)opaque; + + VNC_DEBUG("Handshake IO continue\n"); + vnc_continue_handshake(vs); +} + +static int vnc_start_tls(struct VncState *vs) { + static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; + static const int protocol_priority[]= { GNUTLS_TLS1_1, GNUTLS_TLS1_0, GNUTLS_SSL3, 0 }; + static const int kx_anon[] = {GNUTLS_KX_ANON_DH, 0}; + gnutls_anon_server_credentials anon_cred = NULL; + + VNC_DEBUG("Do TLS setup\n"); + if (vnc_tls_initialize() < 0) { + VNC_DEBUG("Failed to init TLS\n"); + vnc_client_error(vs); + return -1; + } + if (vs->tls_session == NULL) { + if (gnutls_init(&vs->tls_session, GNUTLS_SERVER) < 0) { + vnc_client_error(vs); + return -1; + } + + if (gnutls_set_default_priority(vs->tls_session) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + + if (gnutls_kx_set_priority(vs->tls_session, kx_anon) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + + if (gnutls_certificate_type_set_priority(vs->tls_session, cert_type_priority) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + + if (gnutls_protocol_set_priority(vs->tls_session, protocol_priority) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + + anon_cred = vnc_tls_initialize_anon_cred(); + if (!anon_cred) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + if (gnutls_credentials_set(vs->tls_session, GNUTLS_CRD_ANON, anon_cred) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + gnutls_anon_free_server_credentials(anon_cred); + vnc_client_error(vs); + return -1; + } + + gnutls_transport_set_ptr(vs->tls_session, (gnutls_transport_ptr_t)vs); + gnutls_transport_set_push_function(vs->tls_session, vnc_tls_push); + gnutls_transport_set_pull_function(vs->tls_session, vnc_tls_pull); + } + + VNC_DEBUG("Start TLS handshake process\n"); + return vnc_continue_handshake(vs); +} + +static int protocol_client_vencrypt_auth(VncState *vs, char *data, size_t len) +{ + int auth = read_u32(data, 0); + + if (auth != vs->subauth) { + VNC_DEBUG("Rejecting auth %d\n", auth); + vnc_write_u8(vs, 0); /* Reject auth */ + vnc_flush(vs); + vnc_client_error(vs); + } else { + VNC_DEBUG("Accepting auth %d, starting handshake\n", auth); + vnc_write_u8(vs, 1); /* Accept auth */ + vnc_flush(vs); + + if (vnc_start_tls(vs) < 0) { + VNC_DEBUG("Failed to complete TLS\n"); + return 0; + } + + if (vs->wiremode == VNC_WIREMODE_TLS) { + VNC_DEBUG("Starting VeNCrypt subauth\n"); + return start_auth_vencrypt_subauth(vs); + } else { + VNC_DEBUG("TLS handshake blocked\n"); + return 0; + } + } + return 0; +} + +static int protocol_client_vencrypt_init(VncState *vs, char *data, size_t len) +{ + if (data[0] != 0 || + data[1] != 2) { + VNC_DEBUG("Unsupported VeNCrypt protocol %d.%d\n", (int)data[0], (int)data[1]); + vnc_write_u8(vs, 1); /* Reject version */ + vnc_flush(vs); + vnc_client_error(vs); + } else { + VNC_DEBUG("Sending allowed auth %d\n", vs->subauth); + vnc_write_u8(vs, 0); /* Accept version */ + vnc_write_u8(vs, 1); /* Number of sub-auths */ + vnc_write_u32(vs, vs->subauth); /* The supported auth */ + vnc_flush(vs); + vnc_read_when(vs, protocol_client_vencrypt_auth, 4); + } + return 0; +} + +static int start_auth_vencrypt(VncState *vs) +{ + /* Send VeNCrypt version 0.2 */ + vnc_write_u8(vs, 0); + vnc_write_u8(vs, 2); + + vnc_read_when(vs, protocol_client_vencrypt_init, 2); + return 0; +} +#endif /* CONFIG_VNC_TLS */ + static int protocol_client_auth(VncState *vs, char *data, size_t len) { /* We only advertise 1 auth scheme at a time, so client @@ -1250,6 +1592,12 @@ static int protocol_client_auth(VncState case VNC_AUTH_VNC: VNC_DEBUG("Start VNC auth\n"); return start_auth_vnc(vs); + +#if CONFIG_VNC_TLS + case VNC_AUTH_VENCRYPT: + VNC_DEBUG("Accept VeNCrypt auth\n");; + return start_auth_vencrypt(vs); +#endif /* CONFIG_VNC_TLS */ default: /* Should not be possible, but just in case */ VNC_DEBUG("Reject auth %d\n", vs->auth); @@ -1331,6 +1679,7 @@ static void vnc_listen_read(void *opaque vs->csock = accept(vs->lsock, (struct sockaddr *)&addr, &addrlen); if (vs->csock != -1) { + VNC_DEBUG("New client on socket %d\n", vs->csock); socket_set_nonblock(vs->csock); qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, opaque); vnc_write(vs, "RFB 003.008\n", 12); @@ -1403,8 +1752,18 @@ void vnc_display_close(DisplayState *ds) buffer_reset(&vs->input); buffer_reset(&vs->output); vs->need_update = 0; +#if CONFIG_VNC_TLS + if (vs->tls_session) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + } + vs->wiremode = VNC_WIREMODE_CLEAR; +#endif /* CONFIG_VNC_TLS */ } vs->auth = VNC_AUTH_INVALID; +#if CONFIG_VNC_TLS + vs->subauth = VNC_AUTH_INVALID; +#endif } int vnc_display_password(DisplayState *ds, const char *password) @@ -1436,6 +1795,9 @@ int vnc_display_open(DisplayState *ds, c VncState *vs = ds ? (VncState *)ds->opaque : vnc_state; const char *options; int password = 0; +#if CONFIG_VNC_TLS + int tls = 0; +#endif vnc_display_close(ds); if (strcmp(display, "none") == 0) @@ -1449,14 +1811,40 @@ int vnc_display_open(DisplayState *ds, c options++; if (strncmp(options, "password", 8) == 0) password = 1; /* Require password auth */ +#if CONFIG_VNC_TLS + else if (strncmp(options, "tls", 3) == 0) + tls = 1; /* Require TLS */ +#endif } if (password) { - VNC_DEBUG("Initializing VNC server with password auth\n"); - vs->auth = VNC_AUTH_VNC; +#if CONFIG_VNC_TLS + if (tls) { + VNC_DEBUG("Initializing VNC server with TLS password auth\n"); + vs->auth = VNC_AUTH_VENCRYPT; + vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC; + } else { +#endif + VNC_DEBUG("Initializing VNC server with password auth\n"); + vs->auth = VNC_AUTH_VNC; +#if CONFIG_VNC_TLS + vs->subauth = VNC_AUTH_INVALID; + } +#endif } else { - VNC_DEBUG("Initializing VNC server with no auth\n"); - vs->auth = VNC_AUTH_NONE; +#if CONFIG_VNC_TLS + if (tls) { + VNC_DEBUG("Initializing VNC server with TLS no auth\n"); + vs->auth = VNC_AUTH_VENCRYPT; + vs->subauth = VNC_AUTH_VENCRYPT_TLSNONE; + } else { +#endif + VNC_DEBUG("Initializing VNC server with no auth\n"); + vs->auth = VNC_AUTH_NONE; +#if CONFIG_VNC_TLS + vs->subauth = VNC_AUTH_INVALID; + } +#endif } #ifndef _WIN32 if (strstart(display, "unix:", &p)) { -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=| ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [Qemu-devel] PATCH 5/8: x509 certificate for server 2007-08-13 19:25 [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Daniel P. Berrange ` (3 preceding siblings ...) 2007-08-13 19:46 ` [Qemu-devel] PATCH 4/8: VeNCrypt basic TLS support Daniel P. Berrange @ 2007-08-13 19:47 ` Daniel P. Berrange 2007-08-13 19:48 ` [Qemu-devel] PATCH 6/8: x509 client certificate verification Daniel P. Berrange ` (3 subsequent siblings) 8 siblings, 0 replies; 10+ messages in thread From: Daniel P. Berrange @ 2007-08-13 19:47 UTC (permalink / raw) To: qemu-devel This patch adds support for using x509 certificates on the server end. The server needs a CA certificate, and its own certificate and private key. A CA revocation list is optional. This this patch the file names are hardcoded. The next-but-one patch will make them configurable. The use of x509 certificates is controlled by the 'x509' flag to the VNC arg, eg '-vnc :1,tls,x509'. This only provides encryption of the session, no authentication. The subsequent patch will allow certificates to be used for authentication too. Example using TLS, x509 server certificates and password auth qemu [...OPTIONS...] -vnc :1,password,tls,x509 -monitor stdio (qemu) change vnc password Password: ******** (qemu) Signed-off-by: Daniel P. Berrange <berrange@redhat.com> diff -r 4199204d1b36 vnc.c --- a/vnc.c Wed Aug 08 12:53:40 2007 -0400 +++ b/vnc.c Wed Aug 08 12:53:43 2007 -0400 @@ -105,6 +105,14 @@ enum { VNC_AUTH_VENCRYPT_X509VNC = 261, VNC_AUTH_VENCRYPT_X509PLAIN = 262, }; + +#if CONFIG_VNC_TLS +#define X509_CA_CERT_FILE "ca-cert.pem" +#define X509_CA_CRL_FILE "ca-crl.pem" +#define X509_SERVER_KEY_FILE "server-key.pem" +#define X509_SERVER_CERT_FILE "server-cert.pem" +#endif + #endif /* CONFIG_VNC_TLS */ struct VncState @@ -1377,16 +1385,60 @@ static gnutls_anon_server_credentials vn } +static gnutls_certificate_credentials_t vnc_tls_initialize_x509_cred(void) +{ + gnutls_certificate_credentials_t x509_cred; + int ret; + struct stat st; + + if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) { + VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret)); + return NULL; + } + if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred, X509_CA_CERT_FILE, GNUTLS_X509_FMT_PEM)) < 0) { + VNC_DEBUG("Cannot load CA certificate %s\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + + if ((ret = gnutls_certificate_set_x509_key_file (x509_cred, X509_SERVER_CERT_FILE, + X509_SERVER_KEY_FILE, + GNUTLS_X509_FMT_PEM)) < 0) { + VNC_DEBUG("Cannot load certificate & key %s\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + + if (stat(X509_CA_CRL_FILE, &st) < 0) { + if (errno != ENOENT) { + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + } else { + if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred, X509_CA_CRL_FILE, GNUTLS_X509_FMT_PEM)) < 0) { + VNC_DEBUG("Cannot load CRL %s\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + } + + gnutls_certificate_set_dh_params (x509_cred, dh_params); + + return x509_cred; +} + static int start_auth_vencrypt_subauth(VncState *vs) { switch (vs->subauth) { case VNC_AUTH_VENCRYPT_TLSNONE: + case VNC_AUTH_VENCRYPT_X509NONE: VNC_DEBUG("Accept TLS auth none\n"); vnc_write_u32(vs, 0); /* Accept auth completion */ vnc_read_when(vs, protocol_client_init, 1); break; case VNC_AUTH_VENCRYPT_TLSVNC: + case VNC_AUTH_VENCRYPT_X509VNC: VNC_DEBUG("Start TLS auth VNC\n"); return start_auth_vnc(vs); @@ -1437,11 +1489,17 @@ static void vnc_handshake_io(void *opaqu vnc_continue_handshake(vs); } +#define NEED_X509_AUTH(vs) \ + ((vs)->subauth == VNC_AUTH_VENCRYPT_X509NONE || \ + (vs)->subauth == VNC_AUTH_VENCRYPT_X509VNC || \ + (vs)->subauth == VNC_AUTH_VENCRYPT_X509PLAIN) + + static int vnc_start_tls(struct VncState *vs) { static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; static const int protocol_priority[]= { GNUTLS_TLS1_1, GNUTLS_TLS1_0, GNUTLS_SSL3, 0 }; static const int kx_anon[] = {GNUTLS_KX_ANON_DH, 0}; - gnutls_anon_server_credentials anon_cred = NULL; + static const int kx_x509[] = {GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0}; VNC_DEBUG("Do TLS setup\n"); if (vnc_tls_initialize() < 0) { @@ -1462,7 +1520,7 @@ static int vnc_start_tls(struct VncState return -1; } - if (gnutls_kx_set_priority(vs->tls_session, kx_anon) < 0) { + if (gnutls_kx_set_priority(vs->tls_session, NEED_X509_AUTH(vs) ? kx_x509 : kx_anon) < 0) { gnutls_deinit(vs->tls_session); vs->tls_session = NULL; vnc_client_error(vs); @@ -1483,19 +1541,36 @@ static int vnc_start_tls(struct VncState return -1; } - anon_cred = vnc_tls_initialize_anon_cred(); - if (!anon_cred) { - gnutls_deinit(vs->tls_session); - vs->tls_session = NULL; - vnc_client_error(vs); - return -1; - } - if (gnutls_credentials_set(vs->tls_session, GNUTLS_CRD_ANON, anon_cred) < 0) { - gnutls_deinit(vs->tls_session); - vs->tls_session = NULL; - gnutls_anon_free_server_credentials(anon_cred); - vnc_client_error(vs); - return -1; + if (NEED_X509_AUTH(vs)) { + gnutls_certificate_server_credentials x509_cred = vnc_tls_initialize_x509_cred(); + if (!x509_cred) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + if (gnutls_credentials_set(vs->tls_session, GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + gnutls_certificate_free_credentials(x509_cred); + vnc_client_error(vs); + return -1; + } + } else { + gnutls_anon_server_credentials anon_cred = vnc_tls_initialize_anon_cred(); + if (!anon_cred) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + vnc_client_error(vs); + return -1; + } + if (gnutls_credentials_set(vs->tls_session, GNUTLS_CRD_ANON, anon_cred) < 0) { + gnutls_deinit(vs->tls_session); + vs->tls_session = NULL; + gnutls_anon_free_server_credentials(anon_cred); + vnc_client_error(vs); + return -1; + } } gnutls_transport_set_ptr(vs->tls_session, (gnutls_transport_ptr_t)vs); @@ -1796,7 +1871,7 @@ int vnc_display_open(DisplayState *ds, c const char *options; int password = 0; #if CONFIG_VNC_TLS - int tls = 0; + int tls = 0, x509 = 0; #endif vnc_display_close(ds); @@ -1814,15 +1889,22 @@ int vnc_display_open(DisplayState *ds, c #if CONFIG_VNC_TLS else if (strncmp(options, "tls", 3) == 0) tls = 1; /* Require TLS */ + else if (strncmp(options, "x509", 4) == 0) + x509 = 1; /* Require x509 certificates */ #endif } if (password) { #if CONFIG_VNC_TLS if (tls) { - VNC_DEBUG("Initializing VNC server with TLS password auth\n"); vs->auth = VNC_AUTH_VENCRYPT; - vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC; + if (x509) { + VNC_DEBUG("Initializing VNC server with x509 password auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_X509VNC; + } else { + VNC_DEBUG("Initializing VNC server with TLS password auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC; + } } else { #endif VNC_DEBUG("Initializing VNC server with password auth\n"); @@ -1834,9 +1916,14 @@ int vnc_display_open(DisplayState *ds, c } else { #if CONFIG_VNC_TLS if (tls) { - VNC_DEBUG("Initializing VNC server with TLS no auth\n"); vs->auth = VNC_AUTH_VENCRYPT; - vs->subauth = VNC_AUTH_VENCRYPT_TLSNONE; + if (x509) { + VNC_DEBUG("Initializing VNC server with x509 no auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_X509NONE; + } else { + VNC_DEBUG("Initializing VNC server with TLS no auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_TLSNONE; + } } else { #endif VNC_DEBUG("Initializing VNC server with no auth\n"); -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=| ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [Qemu-devel] PATCH 6/8: x509 client certificate verification 2007-08-13 19:25 [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Daniel P. Berrange ` (4 preceding siblings ...) 2007-08-13 19:47 ` [Qemu-devel] PATCH 5/8: x509 certificate for server Daniel P. Berrange @ 2007-08-13 19:48 ` Daniel P. Berrange 2007-08-13 19:50 ` [Qemu-devel] PATCH 7/8: custom location for x509 cert paths Daniel P. Berrange ` (2 subsequent siblings) 8 siblings, 0 replies; 10+ messages in thread From: Daniel P. Berrange @ 2007-08-13 19:48 UTC (permalink / raw) To: qemu-devel This patch adds support for requesting and validating client certificates. In effect the client certificates are being used as the authentication mechansim, making VNC passwords unccessary, though using a combination of both is also possible. Example usage of just certificate verification qemu [...OPTIONS...] -vnc :1,tls,x509verify -monitor stdio Or, combined with passwords: qemu [...OPTIONS...] -vnc :1,password,tls,x509verify -monitor stdio (qemu) change vnc password Password: ******** (qemu) Signed-off-by: Daniel P. Berrange <berrange@redhat.com> diff -r bfd4dbe12342 vnc.c --- a/vnc.c Wed Aug 08 12:53:50 2007 -0400 +++ b/vnc.c Wed Aug 08 12:54:10 2007 -0400 @@ -142,6 +142,7 @@ struct VncState int auth; #if CONFIG_VNC_TLS int subauth; + int x509verify; #endif char challenge[VNC_AUTH_CHALLENGE_SIZE]; @@ -1427,6 +1428,85 @@ static gnutls_certificate_credentials_t return x509_cred; } +static int vnc_validate_certificate(struct VncState *vs) +{ + int ret; + unsigned int status; + const gnutls_datum_t *certs; + unsigned int nCerts, i; + time_t now; + + VNC_DEBUG("Validating client certificate\n"); + if ((ret = gnutls_certificate_verify_peers2 (vs->tls_session, &status)) < 0) { + VNC_DEBUG("Verify failed %s\n", gnutls_strerror(ret)); + return -1; + } + + if ((now = time(NULL)) == ((time_t)-1)) { + return -1; + } + + if (status != 0) { + if (status & GNUTLS_CERT_INVALID) + VNC_DEBUG("The certificate is not trusted.\n"); + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + VNC_DEBUG("The certificate hasn't got a known issuer.\n"); + + if (status & GNUTLS_CERT_REVOKED) + VNC_DEBUG("The certificate has been revoked.\n"); + + if (status & GNUTLS_CERT_INSECURE_ALGORITHM) + VNC_DEBUG("The certificate uses an insecure algorithm\n"); + + return -1; + } else { + VNC_DEBUG("Certificate is valid!\n"); + } + + /* Only support x509 for now */ + if (gnutls_certificate_type_get(vs->tls_session) != GNUTLS_CRT_X509) + return -1; + + if (!(certs = gnutls_certificate_get_peers(vs->tls_session, &nCerts))) + return -1; + + for (i = 0 ; i < nCerts ; i++) { + gnutls_x509_crt_t cert; + VNC_DEBUG ("Checking certificate chain %d\n", i); + if (gnutls_x509_crt_init (&cert) < 0) + return -1; + + if (gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER) < 0) { + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (gnutls_x509_crt_get_expiration_time (cert) < now) { + VNC_DEBUG("The certificate has expired\n"); + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (gnutls_x509_crt_get_activation_time (cert) > now) { + VNC_DEBUG("The certificate is not yet activated\n"); + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (gnutls_x509_crt_get_activation_time (cert) > now) { + VNC_DEBUG("The certificate is not yet activated\n"); + gnutls_x509_crt_deinit (cert); + return -1; + } + + gnutls_x509_crt_deinit (cert); + } + + return 0; +} + + static int start_auth_vencrypt_subauth(VncState *vs) { switch (vs->subauth) { @@ -1473,6 +1553,16 @@ static int vnc_continue_handshake(struct VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret)); vnc_client_error(vs); return -1; + } + + if (vs->x509verify) { + if (vnc_validate_certificate(vs) < 0) { + VNC_DEBUG("Client verification failed\n"); + vnc_client_error(vs); + return -1; + } else { + VNC_DEBUG("Client verification passed\n"); + } } VNC_DEBUG("Handshake done, switching to TLS data mode\n"); @@ -1556,6 +1646,11 @@ static int vnc_start_tls(struct VncState vnc_client_error(vs); return -1; } + if (vs->x509verify) { + VNC_DEBUG("Requesting a client certificate\n"); + gnutls_certificate_server_set_request (vs->tls_session, GNUTLS_CERT_REQUEST); + } + } else { gnutls_anon_server_credentials anon_cred = vnc_tls_initialize_anon_cred(); if (!anon_cred) { @@ -1838,6 +1933,7 @@ void vnc_display_close(DisplayState *ds) vs->auth = VNC_AUTH_INVALID; #if CONFIG_VNC_TLS vs->subauth = VNC_AUTH_INVALID; + vs->x509verify = 0; #endif } @@ -1884,14 +1980,18 @@ int vnc_display_open(DisplayState *ds, c options = display; while ((options = strchr(options, ','))) { options++; - if (strncmp(options, "password", 8) == 0) + if (strncmp(options, "password", 8) == 0) { password = 1; /* Require password auth */ #if CONFIG_VNC_TLS - else if (strncmp(options, "tls", 3) == 0) + } else if (strncmp(options, "tls", 3) == 0) { tls = 1; /* Require TLS */ - else if (strncmp(options, "x509", 4) == 0) + } else if (strncmp(options, "x509verify", 10) == 0) { + x509 = 1; /* Require x509 certificates... */ + vs->x509verify = 1;/* ...and verify client certs */ + } else if (strncmp(options, "x509", 4) == 0) { x509 = 1; /* Require x509 certificates */ #endif + } } if (password) { -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=| ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [Qemu-devel] PATCH 7/8: custom location for x509 cert paths 2007-08-13 19:25 [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Daniel P. Berrange ` (5 preceding siblings ...) 2007-08-13 19:48 ` [Qemu-devel] PATCH 6/8: x509 client certificate verification Daniel P. Berrange @ 2007-08-13 19:50 ` Daniel P. Berrange 2007-08-13 19:51 ` [Qemu-devel] PATCH 8/8: document all VNC authentication options Daniel P. Berrange 2007-08-15 4:32 ` [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Anthony Liguori 8 siblings, 0 replies; 10+ messages in thread From: Daniel P. Berrange @ 2007-08-13 19:50 UTC (permalink / raw) To: qemu-devel This final code patch extends the VNC server config syntax so that the x509 and x509verify flags both use a path following them. This path is then used to determine the filenames for the CA certificate & revocation list, and the server certificate & private key. If the path containing the certificates is not provided, all client authentication attempts will be rejected. Example usage on command line: qemu [...OPTIONS...] -vnc :1,tls,x509verify=/etc/pki/qemu Signed-off-by: Daniel P. Berrange <berrange@redhat.com> diff -r 109ed8417169 vnc.c --- a/vnc.c Wed Aug 08 12:54:13 2007 -0400 +++ b/vnc.c Wed Aug 08 12:54:18 2007 -0400 @@ -143,6 +143,11 @@ struct VncState #if CONFIG_VNC_TLS int subauth; int x509verify; + + char *x509cacert; + char *x509cacrl; + char *x509cert; + char *x509key; #endif char challenge[VNC_AUTH_CHALLENGE_SIZE]; @@ -1386,37 +1391,49 @@ static gnutls_anon_server_credentials vn } -static gnutls_certificate_credentials_t vnc_tls_initialize_x509_cred(void) +static gnutls_certificate_credentials_t vnc_tls_initialize_x509_cred(VncState *vs) { gnutls_certificate_credentials_t x509_cred; int ret; - struct stat st; + + if (!vs->x509cacert) { + VNC_DEBUG("No CA x509 certificate specified\n"); + return NULL; + } + if (!vs->x509cert) { + VNC_DEBUG("No server x509 certificate specified\n"); + return NULL; + } + if (!vs->x509key) { + VNC_DEBUG("No server private key specified\n"); + return NULL; + } if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) { VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret)); return NULL; } - if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred, X509_CA_CERT_FILE, GNUTLS_X509_FMT_PEM)) < 0) { + if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred, + vs->x509cacert, + GNUTLS_X509_FMT_PEM)) < 0) { VNC_DEBUG("Cannot load CA certificate %s\n", gnutls_strerror(ret)); gnutls_certificate_free_credentials(x509_cred); return NULL; } - if ((ret = gnutls_certificate_set_x509_key_file (x509_cred, X509_SERVER_CERT_FILE, - X509_SERVER_KEY_FILE, + if ((ret = gnutls_certificate_set_x509_key_file (x509_cred, + vs->x509cert, + vs->x509key, GNUTLS_X509_FMT_PEM)) < 0) { VNC_DEBUG("Cannot load certificate & key %s\n", gnutls_strerror(ret)); gnutls_certificate_free_credentials(x509_cred); return NULL; } - if (stat(X509_CA_CRL_FILE, &st) < 0) { - if (errno != ENOENT) { - gnutls_certificate_free_credentials(x509_cred); - return NULL; - } - } else { - if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred, X509_CA_CRL_FILE, GNUTLS_X509_FMT_PEM)) < 0) { + if (vs->x509cacrl) { + if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred, + vs->x509cacrl, + GNUTLS_X509_FMT_PEM)) < 0) { VNC_DEBUG("Cannot load CRL %s\n", gnutls_strerror(ret)); gnutls_certificate_free_credentials(x509_cred); return NULL; @@ -1632,7 +1649,7 @@ static int vnc_start_tls(struct VncState } if (NEED_X509_AUTH(vs)) { - gnutls_certificate_server_credentials x509_cred = vnc_tls_initialize_x509_cred(); + gnutls_certificate_server_credentials x509_cred = vnc_tls_initialize_x509_cred(vs); if (!x509_cred) { gnutls_deinit(vs->tls_session); vs->tls_session = NULL; @@ -1903,6 +1920,63 @@ void vnc_display_init(DisplayState *ds) vnc_dpy_resize(vs->ds, 640, 400); } +#if CONFIG_VNC_TLS +static int vnc_set_x509_credential(VncState *vs, + const char *certdir, + const char *filename, + char **cred, + int ignoreMissing) +{ + struct stat sb; + + if (*cred) { + qemu_free(*cred); + *cred = NULL; + } + + if (!(*cred = qemu_malloc(strlen(certdir) + strlen(filename) + 2))) + return -1; + + strcpy(*cred, certdir); + strcat(*cred, "/"); + strcat(*cred, filename); + + VNC_DEBUG("Check %s\n", *cred); + if (stat(*cred, &sb) < 0) { + qemu_free(*cred); + *cred = NULL; + if (ignoreMissing && errno == ENOENT) + return 0; + return -1; + } + + return 0; +} + +static int vnc_set_x509_credential_dir(VncState *vs, + const char *certdir) +{ + if (vnc_set_x509_credential(vs, certdir, X509_CA_CERT_FILE, &vs->x509cacert, 0) < 0) + goto cleanup; + if (vnc_set_x509_credential(vs, certdir, X509_CA_CRL_FILE, &vs->x509cacrl, 1) < 0) + goto cleanup; + if (vnc_set_x509_credential(vs, certdir, X509_SERVER_CERT_FILE, &vs->x509cert, 0) < 0) + goto cleanup; + if (vnc_set_x509_credential(vs, certdir, X509_SERVER_KEY_FILE, &vs->x509key, 0) < 0) + goto cleanup; + + return 0; + + cleanup: + qemu_free(vs->x509cacert); + qemu_free(vs->x509cacrl); + qemu_free(vs->x509cert); + qemu_free(vs->x509key); + vs->x509cacert = vs->x509cacrl = vs->x509cert = vs->x509key = NULL; + return -1; +} +#endif /* CONFIG_VNC_TLS */ + void vnc_display_close(DisplayState *ds) { VncState *vs = ds ? (VncState *)ds->opaque : vnc_state; @@ -1985,11 +2059,36 @@ int vnc_display_open(DisplayState *ds, c #if CONFIG_VNC_TLS } else if (strncmp(options, "tls", 3) == 0) { tls = 1; /* Require TLS */ - } else if (strncmp(options, "x509verify", 10) == 0) { - x509 = 1; /* Require x509 certificates... */ - vs->x509verify = 1;/* ...and verify client certs */ } else if (strncmp(options, "x509", 4) == 0) { + char *start, *end; x509 = 1; /* Require x509 certificates */ + if (strncmp(options, "x509verify", 10) == 0) + vs->x509verify = 1; /* ...and verify client certs */ + + /* Now check for 'x509=/some/path' postfix + * and use that to setup x509 certificate/key paths */ + start = strchr(options, '='); + end = strchr(options, ','); + if (start && (!end || (start < end))) { + int len = end ? end-(start+1) : strlen(start+1); + char *path = qemu_malloc(len+1); + strncpy(path, start+1, len); + path[len] = '\0'; + VNC_DEBUG("Trying certificate path '%s'\n", path); + if (vnc_set_x509_credential_dir(vs, path) < 0) { + fprintf(stderr, "Failed to find x509 certificates/keys in %s\n", path); + qemu_free(path); + qemu_free(vs->display); + vs->display = NULL; + return -1; + } + qemu_free(path); + } else { + fprintf(stderr, "No certificate path provided\n"); + qemu_free(vs->display); + vs->display = NULL; + return -1; + } #endif } } -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=| ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [Qemu-devel] PATCH 8/8: document all VNC authentication options 2007-08-13 19:25 [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Daniel P. Berrange ` (6 preceding siblings ...) 2007-08-13 19:50 ` [Qemu-devel] PATCH 7/8: custom location for x509 cert paths Daniel P. Berrange @ 2007-08-13 19:51 ` Daniel P. Berrange 2007-08-15 4:32 ` [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Anthony Liguori 8 siblings, 0 replies; 10+ messages in thread From: Daniel P. Berrange @ 2007-08-13 19:51 UTC (permalink / raw) To: qemu-devel This patch updates the user documentation to detail the new syntax options for VNC server configuration. It moves all the display related options into a combined logical section for clarity. It documents the different deployment secenarios possible with the new VNC server capabilities and their security. It also provides a guide for setting up a certificate authority and issuing clients and server with their certificates Signed-off-by: Daniel P. Berrange <berrange@redhat.com> diff -r e5ce3da5ebb1 qemu-doc.texi --- a/qemu-doc.texi Mon Aug 13 11:57:58 2007 -0400 +++ b/qemu-doc.texi Mon Aug 13 12:05:16 2007 -0400 @@ -129,6 +129,7 @@ Download the experimental binary install * pcsys_network:: Network emulation * direct_linux_boot:: Direct Linux Boot * pcsys_usb:: USB emulation +* vnc_security:: VNC security * gdb_usage:: GDB usage * pcsys_os_specific:: Target OS specific information @end menu @@ -243,6 +244,56 @@ Simulate an SMP system with @var{n} CPUs Simulate an SMP system with @var{n} CPUs. On the PC target, up to 255 CPUs are supported. +@item -audio-help + +Will show the audio subsystem help: list of drivers, tunable +parameters. + +@item -soundhw card1,card2,... or -soundhw all + +Enable audio and selected sound hardware. Use ? to print all +available sound hardware. + +@example +qemu -soundhw sb16,adlib hda +qemu -soundhw es1370 hda +qemu -soundhw all hda +qemu -soundhw ? +@end example + +@item -localtime +Set the real time clock to local time (the default is to UTC +time). This option is needed to have correct date in MS-DOS or +Windows. + +@item -pidfile file +Store the QEMU process PID in @var{file}. It is useful if you launch QEMU +from a script. + +@item -daemonize +Daemonize the QEMU process after initialization. QEMU will not detach from +standard IO until it is ready to receive connections on any of its devices. +This option is a useful way for external programs to launch QEMU without having +to cope with initialization race conditions. + +@item -win2k-hack +Use it when installing Windows 2000 to avoid a disk full bug. After +Windows 2000 is installed, you no longer need this option (this option +slows down the IDE transfers). + +@item -option-rom file +Load the contents of file as an option ROM. This option is useful to load +things like EtherBoot. + +@item -name string +Sets the name of the guest. This name will be display in the SDL window +caption. The name will also be used for the VNC server. + +@end table + +Display options: +@table @option + @item -nographic Normally, QEMU uses SDL to display the VGA output. With this option, @@ -257,21 +308,80 @@ available screen space. This makes the u available screen space. This makes the using QEMU in a dedicated desktop workspace more convenient. -@item -vnc display +@item -full-screen +Start in full screen. + +@item -vnc display[,option[,option[,...]]] Normally, QEMU uses SDL to display the VGA output. With this option, you can have QEMU listen on VNC display @var{display} and redirect the VGA display over the VNC session. It is very useful to enable the usb tablet device when using this option (option @option{-usbdevice tablet}). When using the VNC display, you must use the @option{-k} -option to set the keyboard layout if you are not using en-us. - -@var{display} may be in the form @var{interface:d}, in which case connections -will only be allowed from @var{interface} on display @var{d}. Optionally, -@var{interface} can be omitted. @var{display} can also be in the form -@var{unix:path} where @var{path} is the location of a unix socket to listen for -connections on. - +parameter to set the keyboard layout if you are not using en-us. Valid +syntax for the @var{display} is + +@table @code + +@item @var{interface:d} + +TCP connections will only be allowed from @var{interface} on display @var{d}. +By convention the TCP port is 5900+@var{d}. Optionally, @var{interface} can +be omitted in which case the server will bind to all interfaces. + +@item @var{unix:path} + +Connections will be allowed over UNIX domain sockets where @var{path} is the +location of a unix socket to listen for connections on. + +@item @var{none} + +VNC is initialized by not started. The monitor @code{change} command can be used +to later start the VNC server. + +@end table + +Following the @var{display} value there may be one or more @var{option} flags +separated by commas. Valid options are + +@table @code + +@item @var{password} + +Require that password based authentication is used for client connections. +The password must be set separately using the @code{change} command in the +@ref{pcsys_monitor} + +@item @var{tls} + +Require that client use TLS when communicating with the VNC server. This +uses anonymous TLS credentials so is susceptible to a man-in-the-middle +attack. It is recommended that this option be combined with either the +@var{x509} or @var{x509verify} options. + +@item @var{x509=/path/to/certificate/dir} + +Valid if @var{tls} is specified. Require that x509 credentials are used +for negotiating the TLS session. The server will send its x509 certificate +to the client. It is recommended that a password be set on the VNC server +to provide authentication of the client when this is used. The path following +this option specifies where the x509 certificates are to be loaded from. +See the @ref{vnc_security} section for details on generating certificates. + +@item @var{x509verify=/path/to/certificate/dir} + +Valid if @var{tls} is specified. Require that x509 credentials are used +for negotiating the TLS session. The server will send its x509 certificate +to the client, and request that the client send its own x509 certificate. +The server will validate the client's certificate against the CA certificate, +and reject clients when validation fails. If the certificate authority is +trusted, this is a sufficient authentication mechanism. You may still wish +to set a password on the VNC server as a second authentication layer. The +path following this option specifies where the x509 certificates are to +be loaded from. See the @ref{vnc_security} section for details on generating +certificates. + +@end table @item -k language @@ -289,54 +399,6 @@ de en-us fi fr-be hr it lv nl- @end example The default is @code{en-us}. - -@item -audio-help - -Will show the audio subsystem help: list of drivers, tunable -parameters. - -@item -soundhw card1,card2,... or -soundhw all - -Enable audio and selected sound hardware. Use ? to print all -available sound hardware. - -@example -qemu -soundhw sb16,adlib hda -qemu -soundhw es1370 hda -qemu -soundhw all hda -qemu -soundhw ? -@end example - -@item -localtime -Set the real time clock to local time (the default is to UTC -time). This option is needed to have correct date in MS-DOS or -Windows. - -@item -full-screen -Start in full screen. - -@item -pidfile file -Store the QEMU process PID in @var{file}. It is useful if you launch QEMU -from a script. - -@item -daemonize -Daemonize the QEMU process after initialization. QEMU will not detach from -standard IO until it is ready to receive connections on any of its devices. -This option is a useful way for external programs to launch QEMU without having -to cope with initialization race conditions. - -@item -win2k-hack -Use it when installing Windows 2000 to avoid a disk full bug. After -Windows 2000 is installed, you no longer need this option (this option -slows down the IDE transfers). - -@item -option-rom file -Load the contents of file as an option ROM. This option is useful to load -things like EtherBoot. - -@item -name string -Sets the name of the guest. This name will be display in the SDL window -caption. The name will also be used for the VNC server. @end table @@ -862,8 +924,38 @@ Quit the emulator. @item eject [-f] device Eject a removable medium (use -f to force it). -@item change device filename -Change a removable medium. +@item change device setting + +Change the configuration of a device + +@table @option +@item change @var{diskdevice} @var{filename} +Change the medium for a removable disk device to point to @var{filename}. eg + +@example +(qemu) change cdrom /path/to/some.iso +@end example + +@item change vnc @var{display,options} +Change the configuration of the VNC server. The valid syntax for @var{display} +and @var{options} are described at @ref{sec_invocation}. eg + +@example +(qemu) change vnc localhost:1 +@end example + +@item change vnc password + +Change the password associated with the VNC server. The monitor will prompt for +the new password to be entered. VNC passwords are only significant upto 8 letters. +eg. + +@example +(qemu) change vnc password +Password: ******** +@end example + +@end table @item screendump filename Save screen into PPM image @var{filename}. @@ -1421,6 +1513,213 @@ When relaunching QEMU, you may have to u When relaunching QEMU, you may have to unplug and plug again the USB device to make it work again (this is a bug). +@node vnc_security +@section VNC security + +The VNC server capability provides access to the graphical console +of the guest VM across the network. This has a number of security +considerations depending on the deployment scenarios. + +@menu +* vnc_sec_none:: +* vnc_sec_password:: +* vnc_sec_certificate:: +* vnc_sec_certificate_verify:: +* vnc_sec_certificate_pw:: +* vnc_generate_cert:: +@end menu +@node vnc_sec_none +@subsection Without passwords + +The simplest VNC server setup does not include any form of authentication. +For this setup it is recommended to restrict it to listen on a UNIX domain +socket only. For example + +@example +qemu [...OPTIONS...] -vnc unix:/home/joebloggs/.qemu-myvm-vnc +@end example + +This ensures that only users on local box with read/write access to that +path can access the VNC server. To securely access the VNC server from a +remote machine, a combination of netcat+ssh can be used to provide a secure +tunnel. + +@node vnc_sec_password +@subsection With passwords + +The VNC protocol has limited support for password based authentication. Since +the protocol limits passwords to 8 characters it should not be considered +to provide high security. The password can be fairly easily brute-forced by +a client making repeat connections. For this reason, a VNC server using password +authentication should be restricted to only listen on the loopback interface +or UNIX domain sockets. Password ayuthentication is requested with the @code{password} +option, and then once QEMU is running the password is set with the monitor. Until +the monitor is used to set the password all clients will be rejected. + +@example +qemu [...OPTIONS...] -vnc :1,password -monitor stdio +(qemu) change vnc password +Password: ******** +(qemu) +@end example + +@node vnc_sec_certificate +@subsection With x509 certificates + +The QEMU VNC server also implements the VeNCrypt extension allowing use of +TLS for encryption of the session, and x509 certificates for authentication. +The use of x509 certificates is strongly recommended, because TLS on its +own is susceptible to man-in-the-middle attacks. Basic x509 certificate +support provides a secure session, but no authentication. This allows any +client to connect, and provides an encrypted session. + +@example +qemu [...OPTIONS...] -vnc :1,tls,x509=/etc/pki/qemu -monitor stdio +@end example + +In the above example @code{/etc/pki/qemu} should contain at least three files, +@code{ca-cert.pem}, @code{server-cert.pem} and @code{server-key.pem}. Unprivileged +users will want to use a private directory, for example @code{$HOME/.pki/qemu}. +NB the @code{server-key.pem} file should be protected with file mode 0600 to +only be readable by the user owning it. + +@node vnc_sec_certificate_verify +@subsection With x509 certificates and client verification + +Certificates can also provide a means to authenticate the client connecting. +The server will request that the client provide a certificate, which it will +then validate against the CA certificate. This is a good choice if deploying +in an environment with a private internal certificate authority. + +@example +qemu [...OPTIONS...] -vnc :1,tls,x509verify=/etc/pki/qemu -monitor stdio +@end example + + +@node vnc_sec_certificate_pw +@subsection With x509 certificates, client verification and passwords + +Finally, the previous method can be combined with VNC password authentication +to provide two layers of authentication for clients. + +@example +qemu [...OPTIONS...] -vnc :1,password,tls,x509verify=/etc/pki/qemu -monitor stdio +(qemu) change vnc password +Password: ******** +(qemu) +@end example + +@node vnc_generate_cert +@subsection Generating certificates for VNC + +The GNU TLS packages provides a command called @code{certtool} which can +be used to generate certificates and keys in PEM format. At a minimum it +is neccessary to setup a certificate authority, and issue certificates to +each server. If using certificates for authentication, then each client +will also need to be issued a certificate. The recommendation is for the +server to keep its certificates in either @code{/etc/pki/qemu} or for +unprivileged users in @code{$HOME/.pki/qemu}. + +@menu +* vnc_generate_ca:: +* vnc_generate_server:: +* vnc_generate_client:: +@end menu +@node vnc_generate_ca +@subsubsection Setup the Certificate Authority + +This step only needs to be performed once per organization / organizational +unit. First the CA needs a private key. This key must be kept VERY secret +and secure. If this key is compromised the entire trust chain of the certificates +issued with it is lost. + +@example +# certtool --generate-privkey > ca-key.pem +@end example + +A CA needs to have a public certificate. For simplicity it can be a self-signed +certificate, or one issue by a commercial certificate issuing authority. To +generate a self-signed certificate requires one core piece of information, the +name of the organization. + +@example +# cat > ca.info <<EOF +cn = Name of your organization +ca +cert_signing_key +EOF +# certtool --generate-self-signed \ + --load-privkey ca-key.pem + --template ca.info \ + --outfile ca-cert.pem +@end example + +The @code{ca-cert.pem} file should be copied to all servers and clients wishing to utilize +TLS support in the VNC server. The @code{ca-key.pem} must not be disclosed/copied at all. + +@node vnc_generate_server +@subsubsection Issuing server certificates + +Each server (or host) needs to be issued with a key and certificate. When connecting +the certificate is sent to the client which validates it against the CA certificate. +The core piece of information for a server certificate is the hostname. This should +be the fully qualified hostname that the client will connect with, since the client +will typically also verify the hostname in the certificate. On the host holding the +secure CA private key: + +@example +# cat > server.info <<EOF +organization = Name of your organization +cn = server.foo.example.com +tls_www_server +encryption_key +signing_key +EOF +# certtool --generate-privkey > server-key.pem +# certtool --generate-certificate \ + --load-ca-certificate ca-cert.pem \ + --load-ca-privkey ca-key.pem \ + --load-privkey server server-key.pem \ + --template server.info \ + --outfile server-cert.pem +@end example + +The @code{server-key.pem} and @code{server-cert.pem} files should now be securely copied +to the server for which they were generated. The @code{server-key.pem} is security +sensitive and should be kept protected with file mode 0600 to prevent disclosure. + +@node vnc_generate_client +@subsubsection Issuing client certificates + +If the QEMU VNC server is to use the @code{x509verify} option to validate client +certificates as its authentication mechanism, each client also needs to be issued +a certificate. The client certificate contains enough metadata to uniquely identify +the client, typically organization, state, city, building, etc. On the host holding +the secure CA private key: + +@example +# cat > client.info <<EOF +country = GB +state = London +locality = London +organiazation = Name of your organization +cn = client.foo.example.com +tls_www_client +encryption_key +signing_key +EOF +# certtool --generate-privkey > client-key.pem +# certtool --generate-certificate \ + --load-ca-certificate ca-cert.pem \ + --load-ca-privkey ca-key.pem \ + --load-privkey client-key.pem \ + --template client.info \ + --outfile client-cert.pem +@end example + +The @code{client-key.pem} and @code{client-cert.pem} files should now be securely +copied to the client for which they were generated. + @node gdb_usage @section GDB usage -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=| ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [Qemu-devel] PATCH 0/8: Authentication support for the VNC server 2007-08-13 19:25 [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Daniel P. Berrange ` (7 preceding siblings ...) 2007-08-13 19:51 ` [Qemu-devel] PATCH 8/8: document all VNC authentication options Daniel P. Berrange @ 2007-08-15 4:32 ` Anthony Liguori 8 siblings, 0 replies; 10+ messages in thread From: Anthony Liguori @ 2007-08-15 4:32 UTC (permalink / raw) To: Daniel P. Berrange, qemu-devel These all look good to me! Regards, Anthony Liguori On Mon, 2007-08-13 at 20:25 +0100, Daniel P. Berrange wrote: > The current VNC server implementation does not have support for the > authentication of incoming client connections. The following series > of patches provide support for a number of alternatives, all compliant > with the VNC protocol spec. The simplest mechanism (and the weakest) > is the traditional VNC password scheme based on weak d3des hashing of > an 8 byte key. The more serious mechanism uses TLS for data encryption > of the entire session, and x509 certificates for both client and server > authentication. > > The patches are an iteration on the previous work I posted a couple > of weeks ago[1]. This addresses all the issues raised in the previous > review along with a couple of edge cases I discovered. Since TLS can be > quite perplexing, I also included some documentation on how to setup a > CA, and issue client & server certs in a manner suitable for use with > the VNC server. > > For the basic VNC password auth, this patch should be compatible with > any standard VNC client such as RealVNC. The TLS based auth schemes > require a client that implements the VeNCrypt extension[2]. The client > from the VeNCrypt[3] project of course is one example. The GTK-VNC[4] > widget which is used by Virt Manager[5] and Vinagre [6] also support > it, and are my primary testing platform. > > The 8 individual patches will follow shortly in replies to this mail. > > Regards, > Dan. > > [1] http://www.mail-archive.com/qemu-devel@nongnu.org/msg11554.html > [2] http://www.mail-archive.com/qemu-devel@nongnu.org/msg08681.html > [3] http://sourceforge.net/projects/vencrypt/ > [4] http://gtk-vnc.sourceforge.net/ > [5] http://virt-manager.org/ > [6] http://www.gnome.org/~jwendell/vinagre/ ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2007-08-15 4:32 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2007-08-13 19:25 [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Daniel P. Berrange 2007-08-13 19:41 ` [Qemu-devel] PATCH 1/8: Refactor VNC server setup API Daniel P. Berrange 2007-08-13 19:42 ` [Qemu-devel] PATCH 2/8: Extend monitor 'change' command for VNC Daniel P. Berrange 2007-08-13 19:44 ` [Qemu-devel] PATCH 3/8: VNC password authentication Daniel P. Berrange 2007-08-13 19:46 ` [Qemu-devel] PATCH 4/8: VeNCrypt basic TLS support Daniel P. Berrange 2007-08-13 19:47 ` [Qemu-devel] PATCH 5/8: x509 certificate for server Daniel P. Berrange 2007-08-13 19:48 ` [Qemu-devel] PATCH 6/8: x509 client certificate verification Daniel P. Berrange 2007-08-13 19:50 ` [Qemu-devel] PATCH 7/8: custom location for x509 cert paths Daniel P. Berrange 2007-08-13 19:51 ` [Qemu-devel] PATCH 8/8: document all VNC authentication options Daniel P. Berrange 2007-08-15 4:32 ` [Qemu-devel] PATCH 0/8: Authentication support for the VNC server Anthony Liguori
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).