Interlock protocol chat program source code

Anonymous discord-nobody at erisiandiscord.de
Sun Oct 5 22:40:42 EDT 2003


/* interchat.c
 *
 * Two player chat program using the Interlock Protocol
 *
 * Based on Rivest and Shamir, "How to expose an eavesdropper",
 * Communications of the ACM, v 27 no 4 (Apr 1984), pp 393-395.
 *
 * Requires the free OpenSSL crypto library, from www.openssl.org.
 *
 * Warning: this is a quick hack written in about three hours, as a
 * demonstration of the principle and not as bulletproof security code.
 *
 * Usage: First person starts the program listening for a connection
 * with ./interchat portnum.
 * Second person connects to him with ./interchat hostname portnum.
 * Each party types a line of chat, and once each person has entered
 * his line, the data is exchanged.  No data will be received until each
 * person has entered their next line.
 *
 * Under certain assumptions, this approach will detect man-in-the-middle
 * attacks.  Actually, this program can be used to try acting as a MITM:
 * the MITM runs two chat sessions in different windows, connecting
 * to the two "victims" who think they are talking with each other.
 * By manipulating the two conversations he can try to get the two
 * parties to talk and share information with each other such that
 * he learns any secrets being exchanged.
 *
 * This software is 
 * Copyright (C) 2003 by PGP key ID AEE49232,
 * fingerprint CC6BAB551D0D2B65BB0B6D981C801DF4AEE49232.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following condition
 * is met:
 *
 *    Redistributions of source code must retain the above copyright
 *    notice, this condition and the following disclaimer.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <assert.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>

#include "openssl/bn.h"
#include "openssl/sha.h"
#include "openssl/evp.h"
#include "openssl/hmac.h"

typedef unsigned char uchar;

#define PADLEN		16
#define MYBUFLEN	256
#define MIN(x,y) (((x)<(y))?(x):(y))

BN_CTX *bnctx;

/* DH parameters */
BIGNUM *p, *q, *g;
#define XBITS	200

/* Oakley group 5, RFC 2412 */
char *p_hex =	"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
				"29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
				"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
				"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
				"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
				"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
				"83655D23DCA3AD961C62F356208552BB9ED529077096966D"
				"670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF";
char *q_hex =	"7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68"
				"948127044533E63A0105DF531D89CD9128A5043CC71A026E"
				"F7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122"
				"F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6"
				"F71C35FDAD44CFD2D74F9208BE258FF324943328F6722D9E"
				"E1003E5C50B1DF82CC6D241B0E2AE9CD348B1FD47E9267AF"
				"C1B2AE91EE51D6CB0E3179AB1042A95DCF6A9483B84B4B36"
				"B3861AA7255E4C0278BA36046511B993FFFFFFFFFFFFFFFF";
char *g_hex =	"2";

/* DH public and private values */
BIGNUM *my_x, *my_y, *his_y, *shared;

/* Encrypt/decrypt keys */
uchar enckey[16], deckey[16];
uchar mymackey[16], hismackey[16];



/* I/O functions */

static void
bnwrite( BIGNUM *bn, FILE *f )
{
	unsigned char *p;
	int len;

	len = BN_bn2mpi( bn, NULL );
	p = malloc( len );
	BN_bn2mpi( bn, p );
	fwrite( p, 1, len, f );
	free( p );
	fflush( f );
}

static void
bnread( BIGNUM **bn, FILE *f )
{
	unsigned char *p;
	int len;
	unsigned char mpibuf[4];

	if( fread( mpibuf, 1, sizeof(mpibuf), f ) != sizeof(mpibuf) )
	{
		perror("fread");
		exit(1);
	}
	len = (mpibuf[0]<<24)|(mpibuf[1]<<16)|(mpibuf[2]<<8)|(mpibuf[3]);
	p = malloc( len+sizeof(mpibuf) );
	if( p==NULL )
	{
		perror("malloc");
		exit(1);
	}
	memcpy(p, mpibuf, sizeof(mpibuf));
	if( fread( p+sizeof(mpibuf), 1, len, f ) != len )
	{
		perror("fread");
		exit(1);
	}
	*bn = BN_mpi2bn( p, len+sizeof(mpibuf), NULL );
	free( p );
}


/* Utility functions */


/* Hash a bignum */
static void
bnhash (EVP_MD_CTX *ctx, BIGNUM *bn)
{
	unsigned buflen = BN_bn2mpi(bn, NULL);
	uchar *buf = malloc (buflen);
	BN_bn2mpi (bn, buf);
	EVP_DigestUpdate (ctx, buf, buflen);
	free (buf);
}



/* Communication functions */


/* Encrypt and send a message */
static void
fwrite_enc (uchar *buf, int len, FILE *f)
{
	EVP_CIPHER_CTX encctx;
	uchar hbuf[SHA_DIGEST_LENGTH];
	uchar iv[16];
	unsigned outlen = (len + 16);
	uchar *outbuf = malloc(outlen);
	unsigned out1, out2;
	unsigned out12;
	int i;

	RAND_bytes (iv, sizeof(iv));
	EVP_EncryptInit (&encctx, EVP_aes_128_cbc(), enckey, iv);
	EVP_EncryptUpdate (&encctx, outbuf, &out1, buf, len);
	EVP_EncryptFinal (&encctx, outbuf+out1, &out2);
	HMAC (EVP_sha1(), mymackey, sizeof(mymackey), outbuf, out1+out2,
			hbuf, NULL);
	/* Increment MAC key every time to get the effect of a seq number */
	for (i=sizeof(mymackey)-1; ++mymackey[i]==0; i--)
		;
	out12 = htonl(out1+out2+sizeof(iv)+SHA_DIGEST_LENGTH);
	fwrite (&out12, 1, sizeof(out12), f);
	fwrite (iv, 1, sizeof(iv), f);
	fwrite (outbuf, 1, out1+out2, f);
	fwrite (hbuf, 1, SHA_DIGEST_LENGTH, f);
	fflush (f);
	free (outbuf);
}

/* Read and decrypt a message */
static void
fread_dec (uchar *buf, int len, FILE *f)
{
	EVP_CIPHER_CTX decctx;
	uchar hbuf[SHA_DIGEST_LENGTH];
	uchar hbuf2[SHA_DIGEST_LENGTH];
	uchar iv[16];
	unsigned inlen = (len + 16);
	uchar *inbuf = malloc(inlen);
	uchar *outbuf = malloc (len + 2*16);
	unsigned in1, in2;
	unsigned in12;
	int nread;
	int i;

	fread (&in12, 1, sizeof(in12), f);
	in12 = ntohl(in12);
	if (in12-sizeof(iv)-SHA_DIGEST_LENGTH > inlen)
	{
		fprintf (stderr, "Excessively large input packet %d, protocol failure\n",
				in12);
		exit (2);
	}
	fread (iv, 1, sizeof(iv), f);
	nread = fread (inbuf, 1, in12-sizeof(iv)-SHA_DIGEST_LENGTH, f);
	fread (hbuf, 1, SHA_DIGEST_LENGTH, f);
	HMAC (EVP_sha1(), hismackey, sizeof(hismackey), inbuf, nread,
			hbuf2, NULL);
	if (memcmp (hbuf, hbuf2, sizeof(hbuf)) != 0)
	{
		fprintf (stderr, "Incorrect message hash, protocol failure or attack\n");
		exit (2);
	}
	/* Increment MAC key every time to get the effect of a seq number */
	for (i=sizeof(hismackey)-1; ++hismackey[i]==0; i--)
		;
	EVP_DecryptInit (&decctx, EVP_aes_128_cbc(), deckey, iv);
	EVP_DecryptUpdate (&decctx, outbuf, &in1, inbuf, nread);
	EVP_DecryptFinal (&decctx, outbuf+in1, &in2);
	if (in1 + in2 != len)
	{
		fprintf (stderr, "Incorrect incoming message length, protocol failure\n");
		exit (2);
	}
	memcpy (buf, outbuf, in1+in2);
	free (inbuf);
	free (outbuf);
}

/* Hash a message, include DH parameters */
static void
hashmsg (uchar *hashbuf, uchar *msg, int msglen, int direction, int mine)
{
	EVP_MD_CTX hashctx;
	uchar dir = (uchar)direction;

	EVP_DigestInit (&hashctx, EVP_sha1());
	bnhash (&hashctx, shared);
	if (mine)
	{
		bnhash (&hashctx, my_y);
		bnhash (&hashctx, his_y);
	} else {
		bnhash (&hashctx, his_y);
		bnhash (&hashctx, my_y);
	}
	EVP_DigestUpdate (&hashctx, &dir, 1);
	EVP_DigestUpdate (&hashctx, msg, msglen);
	EVP_DigestFinal (&hashctx, hashbuf, 0);
	EVP_MD_CTX_cleanup (&hashctx);
}


/* This is the main loop of the program.  Each side reads a message,
 * then they exchange hashes of those messages, hashed with the public
 * and secret DH handshake values.  No, I need to add some randomness there,
 * too, don't I?
 */
static void
intercomm (int prio, FILE *f_in, FILE *f_out)
{
	char mybuf[PADLEN+MYBUFLEN];
	char hisbuf[PADLEN+MYBUFLEN];
	uchar myhash[SHA_DIGEST_LENGTH];
	uchar hishash[SHA_DIGEST_LENGTH];
	uchar hishash2[SHA_DIGEST_LENGTH];
	int mylen, mylen2, hislen;

	mybuf[sizeof(mybuf)-1] = '\0';
	for ( ; ; )
	{
		/* Prefix message by random bytes to make it unguessable */
		RAND_bytes (mybuf, PADLEN);
		fgets (mybuf+PADLEN, sizeof(mybuf)-PADLEN-1, stdin);
		mylen = strlen(mybuf+PADLEN) + PADLEN;
		/* Strip end of line characters */
		while ((mybuf[mylen-1] == '\n' || mybuf[mylen-1] == '\r')
				&& mylen > PADLEN)
			--mylen;
		/* Hash message and exchange hashes */
		hashmsg (myhash, mybuf, mylen, prio, 1);
		if (prio)
		{
			fwrite_enc (myhash, sizeof(myhash), f_out);
			fread_dec (hishash, sizeof(hishash), f_in);
		} else {
			fread_dec (hishash, sizeof(hishash), f_in);
			fwrite_enc (myhash, sizeof(myhash), f_out);
		}
		/* Exchange messages and verify remote hash */
		if (prio)
		{
			mylen2 = htonl(mylen);
			fwrite_enc ((uchar *)&mylen2, sizeof(mylen2), f_out);
			fwrite_enc (mybuf, mylen, f_out);
		}
		fread_dec ((uchar *)&hislen, sizeof(hislen), f_in);
		hislen = ntohl(hislen);
		if (hislen >= sizeof(hisbuf)-1 || hislen < PADLEN)
		{
			fprintf (stderr, "Incoming buffer bad size, %d bytes\n", hislen);
			exit (2);
		}
		fread_dec (hisbuf, hislen, f_in);
		hisbuf[hislen] = '\0';
		hashmsg (hishash2, hisbuf, hislen, !prio, 0);
		if (memcmp (hishash, hishash2, sizeof(hishash)) != 0)
		{
			fprintf (stderr, "Incorrect hash, protocol failure or attack detected\n");
			exit (2);
		}
		printf ("OTHER: %s\n", hisbuf+PADLEN);
		if (!prio)
		{
			mylen2 = htonl(mylen);
			fwrite_enc ((uchar *)&mylen2, sizeof(mylen2), f_out);
			fwrite_enc (mybuf, mylen, f_out);
		}
	}
}


/* Setup and handshake functions */

/* Do an anonymous DH exchange */
static int
dodh (int prio, FILE *f_in, FILE *f_out)
{
	my_x = BN_new();
	my_y = BN_new();
	shared = BN_new();
	p = BN_new();
	q = BN_new();
	g = BN_new();

	BN_hex2bn( &p, p_hex );
	BN_hex2bn( &q, q_hex );
	BN_hex2bn( &g, g_hex );

	BN_rand (my_x, XBITS, 0, 0);
	BN_mod_exp (my_y, g, my_x, p, bnctx);
	if (prio)
	{
		bnwrite (my_y, f_out);
		fflush (f_out);
		bnread (&his_y, f_in);
	} else {
		bnread (&his_y, f_in);
		bnwrite (my_y, f_out);
		fflush (f_out);
	}
	BN_mod_exp (shared, his_y, my_x, p, bnctx);
}



/* Generate a key of the given length and name, using HMAC-SHA1 */
static void
keygen (uchar *key, unsigned keylen, char *name)
{
	int namelen = strlen(name) + 1 + 1;
	char *namebuf = malloc (namelen);
	uchar hbuf[SHA_DIGEST_LENGTH];
	int hbuflen;
	int iter = '0';
	unsigned sharedlen = BN_bn2mpi(shared, NULL);
	uchar *sharedbuf = malloc(sharedlen);

	BN_bn2mpi (shared, sharedbuf);

	while (keylen)
	{
		/* put a prefix char on the name for wide keygen */
		namebuf[0] = iter++;
		strcpy (namebuf+1, name);
		HMAC (EVP_sha1(), sharedbuf, sharedlen, namebuf, namelen,
				hbuf, NULL);
		hbuflen = MIN(keylen, SHA_DIGEST_LENGTH);
		memcpy (key, hbuf, hbuflen);
		keylen -= hbuflen;
		key += hbuflen;
	}
	memset (sharedbuf, 0, sharedlen);
	memset (hbuf, 0, SHA_DIGEST_LENGTH);
	free (namebuf);
	free (sharedbuf);
}


/* Do the handshake and fall into the communications loop */
static int
docomm (int prio, FILE *f_in, FILE *f_out)
{
	dodh (prio, f_in, f_out);
	if (prio)
	{
		keygen (enckey, sizeof(enckey), "Client interlock encrypt");
		keygen (deckey, sizeof(deckey), "Server interlock encrypt");
		keygen (mymackey, sizeof(mymackey), "Client interlock HMAC");
		keygen (hismackey, sizeof(hismackey), "Server interlock HMAC");
	} else {
		keygen (enckey, sizeof(enckey), "Server interlock encrypt");
		keygen (deckey, sizeof(deckey), "Client interlock encrypt");
		keygen (mymackey, sizeof(mymackey), "Server interlock HMAC");
		keygen (hismackey, sizeof(hismackey), "Client interlock HMAC");
	}
	intercomm (prio, f_in, f_out);
	/* Never returns */
	return 0;
}


static int
client (char *target, int port, int nsides)
{
	struct hostent *targetinfo;
    int s, s1;
    int n;
    struct sockaddr_in sockaddr;
	FILE *f_in, *f_out;
	int err;

    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) {
		perror ("socket");
		exit (2);
    }
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(port);

	if (!(targetinfo = gethostbyname(target))) {
	    fprintf (stderr, "Unknown target machine name\n");
	    exit (2);
	}
	if (!targetinfo->h_addr_list) {
	    fprintf (stderr, "No address information available for %s\n",
		     target);
	    exit (2);
	}
	sockaddr.sin_addr.s_addr = **(u_long **)targetinfo->h_addr_list;
	if (connect (s, &sockaddr, sizeof(sockaddr)) < 0) {
	    perror ("connect");
	    exit (2);
	}
	f_in = fdopen (s, "r");
	f_out = fdopen (dup(s), "w");

	err = docomm (0, f_in, f_out);

	fclose (f_in);
	fclose (f_out);

	return err;
}

static char *
revlookup (struct sockaddr_in *otheraddr)
{
	unsigned addr = otheraddr->sin_addr.s_addr;
	static char buf[1024];
	struct hostent *he = gethostbyaddr ((char *)&addr, sizeof (addr), AF_INET);
	if (he == NULL)
	{
		sprintf (buf, "[%d.%d.%d.%d]", addr&0xff, (addr>>8)&0xff,
			(addr>>16)&0xff, (addr>>24)&0xff);
	} else {
		strncpy (buf, he->h_name, sizeof(buf)-1);
	}
	return buf;
}

static int
server (int port, int nsides)
{
    int s, s1;
    int n;
    struct sockaddr_in sockaddr, otheraddr;
	int otheraddrsize = sizeof(otheraddr);
	int reuseflag = -1;
	FILE *f_in, *f_out;
	int err;

    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) {
		perror ("socket");
		exit (2);
    }
	setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &reuseflag, sizeof(reuseflag));
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(port);

	sockaddr.sin_addr.s_addr = INADDR_ANY;
	if (bind (s, &sockaddr, sizeof(sockaddr)) < 0) {
	    perror ("bind");
	    exit (2);
	}
	if (listen(s, 5) < 0) {
	    perror ("listen");
	    exit (2);
	}
	s1 = accept (s, &otheraddr, &otheraddrsize);
	if (s1 < 0) {
	    perror ("accept");
	    exit (2);
	}
	fprintf (stderr, "Incoming connection from %s\n", revlookup(&otheraddr));
	close(s);

	f_in = fdopen (s1, "r");
	f_out = fdopen (dup(s1), "w");

	err = docomm (1, f_in, f_out);

	fclose (f_in);
	fclose (f_out);

	return err;
}

int
main (int ac, char **av)
{
	int port;
	char *host = NULL;
	int nsides;

	bnctx = BN_CTX_new();

	if (ac < 2 || ac > 3)
	{
		fprintf (stderr, "Usage: %s [otherhost] port\n", av[0]);
		exit (1);
	}

	if (ac == 3)
	{
		host = av[1];
		av[1] = av[0];
		av++; ac--;
	}

	port = atoi(av[1]);
	if (port < 0 || port > 65536)
	{
		fprintf (stderr, "Illegal port number %d\n", port);
		exit (1);
	}

	if (host == NULL)
		return server (port, nsides);
	else
		return client (host, port, nsides);
}

---------------------------------------------------------------------
The Cryptography Mailing List
Unsubscribe by sending "unsubscribe cryptography" to majordomo at metzdowd.com



More information about the cryptography mailing list