/* 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 [EMAIL PROTECTED]

Reply via email to