#ifndef _X11VNC_ENC_H #define _X11VNC_ENC_H /* -- enc.h -- */ #if 0 :r /home/runge/ultraSC/rc4/ultravnc_dsm_helper.c #endif /* * ultravnc_dsm_helper.c unix/openssl UltraVNC encryption encoder/decoder. * * compile via: cc -O -o ultravnc_dsm_helper ultravnc_dsm_helper.c -lssl -lcrypto cc -DDBG -O -o ultravnc_dsm_helper ultravnc_dsm_helper.c -lssl -lcrypto * * See usage below for how to run it. * * Note: since the UltraVNC DSM plugin implementation changes the RFB * (aka VNC) protocol (extra data is sent), you will *ALSO* need to modify * your VNC viewer or server to discard (or insert) this extra data. * * This tool knows nothing about the RFB protocol: it simply * encrypts/decrypts a stream using a symmetric cipher, arc4 and aesv2, * (others have been added, see usage). It could be used as a general * encrypted tunnel: * * any-client <=> ultravnc_dsm_helper <--network--> ultravnc_dsm_helper(reverse mode) <=> any-server * * e.g. to connect a non-ultra-dsm-vnc viewer to a non-ultra-dsm-vnc server * * ----------------------------------------------------------------------- * Copyright (c) 2008 Karl J. Runge * All rights reserved. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. * ----------------------------------------------------------------------- */ static char *usage = "\n" "usage: ultravnc_dsm_helper cipher keyfile listenport remotehost:port\n" "\n" "e.g.: ultravnc_dsm_helper arc4 ./arc4.key 5901 snoopy.com:5900\n" "\n" " cipher: specify 'msrc4', 'msrc4_sc', 'arc4', 'aesv2',\n" " 'aes-cfb', 'blowfish', or '3des'.\n" "\n" " 'msrc4_sc' enables a workaround for UVNC SC -plugin use.\n" "\n" " use '.' to have it try to guess the cipher from the keyfile name.\n" "\n" " use 'rev:arc4', etc. to reverse the roles of encrypter and decrypter.\n" " (i.e. if you want to use it for a vnc server, not vnc viewer)\n" "\n" " use 'noultra:...' to skip steps involving salt and IV to be compatible\n" " to be compatible with UltraVNC DSM, i.e. assume a normal symmetric\n" " cipher at the other end.\n" "\n" " use 'noultra:rev:...' if both are to be supplied.\n" "\n" " keyfile: file holding the key (16 bytes for arc4 and aesv2, 87 for msrc4)\n" " E.g. dd if=/dev/random of=./my.key bs=16 count=1\n" " keyfile can also be pw= to use \"string\" for the key.\n" "\n" " listenport: port to listen for incoming connection on. (use 0 to connect\n" " to stdio, use a negative value to force localhost)\n" "\n" " remotehost:port: host and port to connect to. (e.g. ultravnc server)\n" "\n" "\n" " Also: cipher may be cipher@n,m where n is the salt size and m is the\n" " initialization vector size. E.g. aesv2@8,16\n" ; /* * We can also run as a module included into x11vnc (-enc option) * The includer must set ENC_MODULE and ENC_HAVE_OPENSSL. * * Note that when running as a module we still assume we have been * forked off of the parent process and are communicating back to it * via a socket. So we still exit(3) at the end or on error. And * the globals would not work. */ #ifdef ENC_MODULE # define main __enc_main static char *prog = "enc_helper"; #else # define ENC_HAVE_OPENSSL 1 static char *prog = "ultravnc_dsm_helper"; #endif /* unix includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef INADDR_NONE #define INADDR_NONE ((in_addr_t) 0xffffffff) #endif #if ENC_HAVE_OPENSSL /* openssl includes */ #include #include static const EVP_CIPHER *Cipher; #endif static char *cipher = NULL; /* name of cipher, e.g. "aesv2" */ static int reverse = 0; /* listening connection */ static int msrc4_sc = 0; /* enables workaround for SC I/II */ static int noultra = 0; /* manage salt and iv differently than ultradsm */ /* The data that was read in from key file (or pw=password) */ static char keydata[1024]; static int keydata_len; /* Size of salt and IV; based on UltraVNC DSM */ #define SALT 16 #define MSRC4_SALT 11 #define IVEC 16 /* Set default values of salt and IV */ static int salt_size = SALT; static int ivec_size = IVEC; /* To track parent and child pids */ static pid_t parent, child; #define BSIZE 8192 /* Some very verbose debugging stuff I enable for testing */ #ifdef DBG # include "dbg.h" #else # define DEC_CT_DBG(p, n) # define DEC_PT_DBG(p, n) # define ENC_CT_DBG(p, n) # define ENC_PT_DBG(p, n) # define PRINT_IVEC # define PRINT_KEYDATA # define PRINT_KEYSTR_AND_FRIENDS #endif static void enc_connections(int, char*, int); #if !ENC_HAVE_OPENSSL /* In case we are a module and there is no OpenSSL buildtime support */ extern void enc_do(char *ciph, char *keyfile, char *lport, char *rhp) { fprintf(stderr, "%s: not compiled with OpenSSL\n", prog); exit(1); } #else /* If we are a module, enc_do() is the only interface we export. */ /* This works out key type & etc., reads key, calls enc_connections */ extern void enc_do(char *ciph, char *keyfile, char *lport, char *rhp) { struct stat sb; char *q, *p, *connect_host; char tmp[16]; int fd, len, listen_port, connect_port, mbits; /* check for noultra mode: */ q = ciph; if (strstr(q, "noultra:") == q) { noultra = 1; q += strlen("noultra:"); } /* check for reverse mode: */ if (strstr(q, "rev:") == q) { reverse = 1; q += strlen("rev:"); } /* work out which cipher and set Cipher to the selected one. */ if (!strcasecmp(q, "msrc4")) { Cipher = EVP_rc4(); cipher = "msrc4"; } else if (!strcasecmp(q, "msrc4_sc")) { Cipher = EVP_rc4(); cipher = "msrc4"; msrc4_sc = 1; /* no salt/iv workaround */ } else if (strstr(q, "arc4") == q) { Cipher = EVP_rc4(); cipher = "arc4"; } else if (strstr(q, "aesv2") == q || strstr(q, "aes-ofb") == q) { Cipher = EVP_aes_128_ofb(); cipher = "aesv2"; } else if (strstr(q, "aes-cfb") == q) { Cipher = EVP_aes_128_cfb(); cipher = "aes-cfb"; } else if (strstr(q, "blowfish") == q) { Cipher = EVP_bf_cfb(); cipher = "blowfish"; } else if (strstr(q, "3des") == q) { Cipher = EVP_des_ede3_ofb(); cipher = "3des"; } else { /* otherwise, try to guess cipher from key filename: */ if (strstr(keyfile, "arc4.key")) { Cipher = EVP_rc4(); cipher = "arc4"; } else if (strstr(keyfile, "rc4.key")) { Cipher = EVP_rc4(); cipher = "msrc4"; } else if (strstr(keyfile, "aesv2.key")) { Cipher = EVP_aes_128_ofb(); cipher = "aesv2"; } else if (strstr(keyfile, "aes-cfb.key")) { Cipher = EVP_aes_128_cfb(); cipher = "aes-cfb"; } else if (strstr(keyfile, "blowfish.key")) { Cipher = EVP_bf_cfb(); cipher = "blowfish"; } else if (strstr(keyfile, "3des.key")) { Cipher = EVP_des_ede3_ofb(); cipher = "3des"; } else { fprintf(stderr, "cannot figure out cipher, supply 'msrc4', 'arc4', or 'aesv2' ...\n"); exit(1); } } /* look for user specified salt and IV sizes at the end: */ p = strchr(q, '@'); if (p) { int s, v; if (sscanf(p+1, "%d,%d", &s, &v) == 2) { if (0 <= s && s <= SALT) { salt_size = s; } if (0 <= v && v <= EVP_MAX_IV_LENGTH) { ivec_size = v; } } else if (sscanf(p+1, "%d", &s) == 1) { if (0 <= s && s <= SALT) { salt_size = s; } } } /* port to listen on (0 => stdio, negative => localhost) */ listen_port = atoi(lport); /* extract remote hostname and port */ q = strrchr(rhp, ':'); if (q) { connect_port = atoi(q+1); *q = '\0'; } else { /* otherwise guess VNC display 0 ... */ connect_port = 5900; } connect_host = strdup(rhp); /* check for and read in the key file */ if (stat(keyfile, &sb) != 0) { if (strstr(keyfile, "pw=") == keyfile) { /* user specified key/password on cmdline */ int i; len = 0; for (i=0; i < strlen(keyfile); i++) { /* load the string to keydata: */ int n = i + strlen("pw="); keydata[i] = keyfile[n]; if (keyfile[n] == '\0') break; len++; } goto readed_in; } perror("stat"); exit(1); } if (sb.st_size > 1024) { fprintf(stderr, "%s: key file too big.\n", prog); exit(1); } fd = open(keyfile, O_RDONLY); if (fd < 0) { perror("open"); exit(1); } /* read it all in */ len = (int) read(fd, keydata, (size_t) sb.st_size); if (len != sb.st_size) { perror("read"); fprintf(stderr, "%s, could not read key file.\n", prog); exit(1); } close(fd); readed_in: /* check for ultravnc msrc4 format 'rc4.key' */ mbits = 0; if (strstr(keydata, "128 bit") == keydata) { mbits = 128; } else if (strstr(keydata, " 56 bit") == keydata) { mbits = 56; } else if (strstr(keydata, " 40 bit") == keydata) { mbits = 40; } if (mbits > 0) { /* 4 is for int key length, 12 is for BLOBHEADER. */ int i, offset = strlen("xxx bit") + 4 + 12; /* the key is stored in reverse order! */ len = mbits/8; for (i=0; i < len; i++) { tmp[i] = keydata[offset + len - i - 1]; } /* clear keydata and then copy the reversed bytes there: */ memset(keydata, 0, sizeof(keydata)); memcpy(keydata, tmp, len); } keydata_len = len; /* initialize random */ RAND_poll(); /* * Setup connections, then transfer data when they are all * hooked up. */ enc_connections(listen_port, connect_host, connect_port); } #endif #if ENC_HAVE_OPENSSL /* * Initialize cipher context and then loop till EOF doing transfer & * encrypt or decrypt. */ static void enc_xfer(int sock_fr, int sock_to, int encrypt) { /* * We keep both E and D aspects in case we revert back to a * single process calling select(2) on all fds... */ unsigned char E_keystr[EVP_MAX_KEY_LENGTH]; unsigned char D_keystr[EVP_MAX_KEY_LENGTH]; EVP_CIPHER_CTX E_ctx, D_ctx; EVP_CIPHER_CTX *ctx; unsigned char buf[BSIZE], out[BSIZE]; unsigned char *psrc = NULL, *keystr; unsigned char salt[SALT+1]; unsigned char ivec[EVP_MAX_IV_LENGTH]; int i, cnt, len, n = 0, m, vb = 0, pa = 1, first = 1; int whoops = 1; /* for the msrc4 problem */ char *encstr; /* zero the buffers */ memset(buf, 0, BSIZE); memset(out, 0, BSIZE); memset(salt, 0, sizeof(salt)); memset(ivec, 0, sizeof(ivec)); memset(E_keystr, 0, sizeof(E_keystr)); memset(D_keystr, 0, sizeof(D_keystr)); if (!strcmp(cipher, "msrc4")) { salt_size = MSRC4_SALT; /* 11 vs. 16 */ } if (getenv("ENCRYPT_VERBOSE")) { vb = 1; /* let user turn on some debugging via env. var. */ } /* * reverse mode, e.g. we help a vnc server instead of a viewer. */ if (reverse) { encrypt = (!encrypt); } encstr = encrypt ? "encrypt" : "decrypt"; /* string for messages */ if (msrc4_sc) { whoops = 1; } if (encrypt) { /* encrypter initializes the salt and initialization vector */ /* * Our salt is 16 bytes but I believe only the first 8 * bytes are used by EVP_BytesToKey(3). Since we send it * to the other "plugin" we need to keep it 16. */ RAND_bytes(salt, salt_size); RAND_bytes(ivec, ivec_size); /* place them in the send buffer: */ memcpy(buf, salt, salt_size); memcpy(buf+salt_size, ivec, ivec_size); n = salt_size + ivec_size; ENC_PT_DBG(buf, n); /* use the encryption context variables below */ ctx = &E_ctx; keystr = E_keystr; } else { /* decrypter needs to read salt + iv from the wire: */ /* sleep 100 ms (TODO: select on fd) */ struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 100 * 1000; select(1, NULL, NULL, NULL, &tv); n = read(sock_fr, buf, salt_size+ivec_size+96); if (n == 0 && salt_size+ivec_size > 0) { fprintf(stderr, "%s: decrypt finished.\n", prog); goto finished; } if (n < salt_size+ivec_size) { if (msrc4_sc && n == 12) { fprintf(stderr, "%s: only %d bytes read. Assuming UVNC Single Click server.\n", prog, n); } else { if (n < 0) perror("read"); fprintf(stderr, "%s: could not read enough for salt and ivec: n=%d\n", prog, n); goto finished; } } DEC_CT_DBG(buf, n); if (msrc4_sc && n == 12) { ; /* send it as is */ } else { /* extract them to their buffers: */ memcpy(salt, buf, salt_size); memcpy(ivec, buf+salt_size, ivec_size); /* the rest is some encrypted data: */ n = n - salt_size - ivec_size; psrc = buf + salt_size + ivec_size; if (n > 0) { /* copy it down to the start of buf for sending below */ for (i=0; i < n; i++) { buf[i] = psrc[i]; } } } /* use the decryption context variables below */ ctx = &D_ctx; keystr = D_keystr; } /* debug output */ PRINT_KEYDATA; PRINT_IVEC; if (!strcmp(cipher, "msrc4")) { /* special cases for MSRC4: */ if (whoops) { fprintf(stderr, "%s: %s - WARNING: MSRC4 mode and IGNORING random salt\n", prog, encstr); fprintf(stderr, "%s: %s - WARNING: and initialization vector!!\n", prog, encstr); EVP_CIPHER_CTX_init(ctx); EVP_CipherInit_ex(ctx, Cipher, NULL, (unsigned char *) keydata, NULL, encrypt); } else { /* XXX might not be correct */ exit(1); EVP_BytesToKey(Cipher, EVP_md5(), NULL, keydata, keydata_len, 1, keystr, ivec); EVP_CIPHER_CTX_init(ctx); EVP_CipherInit_ex(ctx, Cipher, NULL, keystr, ivec, encrypt); } } else { unsigned char *in_salt; if (salt_size <= 0) { /* let salt_size = 0 mean keep it out of the MD5 */ fprintf(stderr, "%s: %s - WARNING: no salt\n", prog, encstr); in_salt = NULL; } else { in_salt = salt; } if (ivec_size < Cipher->iv_len) { fprintf(stderr, "%s: %s - WARNING: short IV %d < %d\n", prog, encstr, ivec_size, Cipher->iv_len); } /* make the hashed value and place in keystr */ /* XXX N.B.: DSM plugin had count=0, and overwrote ivec by not passing NULL iv */ if (noultra && ivec_size > 0) { EVP_BytesToKey(Cipher, EVP_md5(), in_salt, keydata, keydata_len, 1, keystr, NULL); } else { /* even under noultra we overwrite ivec if ivec_size = 0 */ EVP_BytesToKey(Cipher, EVP_md5(), in_salt, keydata, keydata_len, 1, keystr, ivec); } /* initialize the context */ EVP_CIPHER_CTX_init(ctx); /* set the cipher & initialize */ /* XXX N.B.: DSM plugin had encrypt=1 for both (i.e. perfectly symmetric) */ EVP_CipherInit_ex(ctx, Cipher, NULL, keystr, ivec, encrypt); } /* debug output */ PRINT_KEYSTR_AND_FRIENDS; /* now loop forever processing the data stream */ while (1) { errno = 0; if (first && n > 0) { if (encrypt && msrc4_sc) { /* skip sending salt+iv */ first = 0; continue; } /* use that first block of data placed in buf above */ } else { /* general case of loop, read some in: */ n = read(sock_fr, buf, BSIZE); } /* debug output: */ if (vb) fprintf(stderr, "%s%d/%d ", encrypt ? "+" : "-", n, errno); if (n <= 0) {} else if (encrypt) {ENC_PT_DBG(buf, n);} else {DEC_CT_DBG(buf, n);} if (n == 0 || (n < 0 && errno != EINTR)) { /* failure to read any data... it is EOF or fatal error. */ /* debug output: */ char tmp[32]; int err = errno; if (encrypt) {ENC_PT_DBG("--EOF--", 7);} else {DEC_CT_DBG("--EOF--", 7);} sprintf(tmp, "err=%d,n=%d", err, n); fprintf(stderr, "%s: %s - input stream finished: n=%d, err=%d", prog, encstr, n, err); if (encrypt) {ENC_PT_DBG(tmp, strlen(tmp));} else {DEC_CT_DBG(tmp, strlen(tmp));} /* EOF or fatal error */ break; } else if (n > 0) { /* we read in some data, now transform it: */ if (first && encrypt) { /* first time, copy the salt and ivec to out[] for sending */ memcpy(out, buf, n); cnt = n; } else if (!EVP_CipherUpdate(ctx, out, &cnt, buf, n)) { /* otherwise, we transform the data */ fprintf(stderr, "%s: enc_xfer EVP_CipherUpdate failed.\n", prog); break; } /* debug output: */ if (vb) fprintf(stderr, "c%d/%d ", cnt, n); if (encrypt) {ENC_CT_DBG(out, cnt);} else {DEC_PT_DBG(out, cnt);} /* write transformed data to the other end: */ len = cnt; psrc = out; while (len > 0) { errno = 0; m = write(sock_to, psrc, len); /* debug output: */ if (vb) fprintf(stderr, "m%s%d/%d ", encrypt ? "+" : "-", m, errno); if (m > 0) { /* scoot them by how much was written: */ psrc += m; len -= m; } if (m < 0 && (errno == EINTR || errno == EAGAIN)) { /* interrupted or blocked */ continue; } /* EOF or fatal error */ break; } } else { /* this is EINTR */ } first = 0; } /* transfer done (viewer exited or some error) */ finished: fprintf(stderr, "\n%s: %s - close sock_to\n", prog, encstr); close(sock_to); fprintf(stderr, "%s: %s - close sock_fr\n", prog, encstr); close(sock_fr); /* kill our partner after 2 secs. */ sleep(2); if (child) { if (kill(child, SIGTERM) == 0) { fprintf(stderr, "%s[%d]: %s - killed my partner: %d\n", prog, (int) getpid(), encstr, (int) child); } } else { if (kill(parent, SIGTERM) == 0) { fprintf(stderr, "%s[%d]: %s - killed my partner: %d\n", prog, (int) getpid(), encstr, (int) parent); } } } /* * Listens on incoming port for a client, then connects to remote server. * Then forks into two processes one is the encrypter the other the * decrypter. */ static void enc_connections(int listen_port, char *connect_host, int connect_port) { int listen_fd, conn1, conn2, ret, n, one = 1; socklen_t clen; struct hostent *hp; struct sockaddr_in client, server; /* zero means use stdio (preferably from socketpair()) */ if (listen_port == 0) { conn1 = fileno(stdin); goto use_stdio; } /* fd=n,m means use the supplied already established sockets */ if (sscanf(connect_host, "fd=%d,%d", &conn1, &conn2) == 2) { goto use_input_fds; } /* create the listening socket: */ memset(&client, 0, sizeof(client)); client.sin_family = AF_INET; if (listen_port < 0) { /* negative port means use loopback */ client.sin_addr.s_addr = htonl(INADDR_LOOPBACK); client.sin_port = htons(-listen_port); } else { client.sin_addr.s_addr = htonl(INADDR_ANY); client.sin_port = htons(listen_port); } listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd < 0) { perror("socket"); exit(1); } ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)); if (ret < 0) { perror("setsockopt"); exit(1); } ret = bind(listen_fd, (struct sockaddr *) &client, sizeof(client)); if (ret < 0) { perror("bind"); exit(1); } ret = listen(listen_fd, 2); if (ret < 0) { perror("listen"); exit(1); } fprintf(stderr, "%s: waiting for connection on port: %d\n", prog, listen_port); /* wait for a connection: */ clen = sizeof(client); conn1 = accept(listen_fd, (struct sockaddr *) &client, &clen); if (conn1 < 0) { perror("accept"); exit(1); } /* done with the listening socket: */ close(listen_fd); use_stdio: fprintf(stderr, "%s: got connection: %d\n", prog, conn1); /* now connect to remote server: */ memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(connect_port); if ((server.sin_addr.s_addr = inet_addr(connect_host)) == htonl(INADDR_NONE)) { if (!(hp = gethostbyname(connect_host))) { perror("gethostbyname"); close(conn1); exit(1); } server.sin_addr.s_addr = *(unsigned long *)hp->h_addr; } conn2 = socket(AF_INET, SOCK_STREAM, 0); if (conn2 < 0) { perror("socket"); close(conn1); exit(1); } if (connect(conn2, (struct sockaddr *)&server, (sizeof(server))) < 0) { perror("connect"); close(conn1); exit(1); } use_input_fds: /* fork into two processes; one for each direction: */ parent = getpid(); child = fork(); if (child == (pid_t) -1) { /* couldn't fork... */ perror("fork"); exit(1); } /* Do transfer/encode/decode loop: */ if (child == 0) { /* encrypter: local-viewer -> remote-server */ enc_xfer(conn1, conn2, 1); } else { /* decrypter: remote-server -> local-viewer */ enc_xfer(conn2, conn1, 0); } } #endif /* ENC_HAVE_OPENSSL */ extern int main (int argc, char *argv[]) { char *kf, *q; if (argc < 4) { fprintf(stderr, "%s\n", usage); exit(1); } /* guard against pw= on cmdline (e.g. linux) */ kf = strdup(argv[2]); q = strstr(argv[2], "pw="); if (q) { while (*q != '\0') { *q = '\0'; q++; } } enc_do(argv[1], kf, argv[3], argv[4]); return 0; } /* * a crude utility to have this work "keyless" i.e. the vnc password * is used instead of a pre-shared key file. */ /* #!/usr/bin/perl # # md5_to_rc4key.pl # # This program requires md5sum(1) installed on your machine. # # It translates a VNC password to a ultravnc dsm plugin # compatible key file. # # Supply VNC password on cmdline, capture in key file: # # md5_to_rc4key.pl swordfish > rc4.key # md5_to_rc4key.pl -a swordfish > arc4.key # # Use rc4.key with ultravnc_dsm_helper in msrc4 mode, # or arc4.key in either arc4 or aesv4 mode. # # $rfmt = 1; if ($ARGV[0] eq '-a') { $rfmt = 0; shift; } # n.b. this is not super secure against bad locals... $pw = shift; $tmp = "/tmp/md5out.$$"; open(MD5, "| md5sum > $tmp"); print MD5 $pw; close MD5; $md5 = `cat $tmp`; unlink $tmp; ($md5, $junk) = split(/\s/, $md5); print "128 bit" if $rfmt; print 'a' x 4 if $rfmt; print 'b' x 12 if $rfmt; $str = ''; foreach $d (split(//, $md5)) { $str .= $d; if (length($str) == 2) { push @key, $str; $str = ''; } } @key = (reverse @key) if $rfmt; foreach $h (@key) { $c = pack('c', hex("0x$h")); print $c; } print 'c' x 48 if $rfmt; */ #endif /* _X11VNC_ENC_H */