summary refs log tree commit diff stats
path: root/libotr/libotr-4.1.1/src/auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'libotr/libotr-4.1.1/src/auth.c')
-rw-r--r--libotr/libotr-4.1.1/src/auth.c1573
1 files changed, 1573 insertions, 0 deletions
diff --git a/libotr/libotr-4.1.1/src/auth.c b/libotr/libotr-4.1.1/src/auth.c
new file mode 100644
index 0000000..d0c5505
--- /dev/null
+++ b/libotr/libotr-4.1.1/src/auth.c
@@ -0,0 +1,1573 @@
+/*
+ *  Off-the-Record Messaging library
+ *  Copyright (C) 2004-2014  Ian Goldberg, David Goulet, Rob Smits,
+ *                           Chris Alexander, Willy Lew, Lisa Du,
+ *                           Nikita Borisov
+ *                           <otr@cypherpunks.ca>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of version 2.1 of the GNU Lesser General
+ *  Public License as published by the Free Software Foundation.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* system headers */
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/* libotr headers */
+#include "b64.h"
+#include "privkey.h"
+#include "auth.h"
+#include "serial.h"
+#include "proto.h"
+#include "context.h"
+#include "mem.h"
+
+#if OTRL_DEBUGGING
+#include <stdio.h>
+
+/* Dump the contents of an OtrlAuthInfo to the FILE *f. */
+void otrl_auth_dump(FILE *f, const OtrlAuthInfo *auth)
+{
+    int i;
+
+    fprintf(f, "  Auth info %p:\n", auth);
+    fprintf(f, "    State: %d (%s)\n", auth->authstate,
+	auth->authstate == OTRL_AUTHSTATE_NONE ? "NONE" :
+	auth->authstate == OTRL_AUTHSTATE_AWAITING_DHKEY ? "AWAITING_DHKEY" :
+	auth->authstate == OTRL_AUTHSTATE_AWAITING_REVEALSIG ?
+	    "AWAITING_REVEALSIG" :
+	auth->authstate == OTRL_AUTHSTATE_AWAITING_SIG ? "AWAITING_SIG" :
+	auth->authstate == OTRL_AUTHSTATE_V1_SETUP ? "V1_SETUP" :
+	"INVALID");
+    fprintf(f, "    Context: %p\n", auth->context);
+    fprintf(f, "    Our keyid:   %u\n", auth->our_keyid);
+    fprintf(f, "    Their keyid: %u\n", auth->their_keyid);
+    fprintf(f, "    Their fingerprint: ");
+    for (i=0;i<20;++i) {
+	fprintf(f, "%02x", auth->their_fingerprint[i]);
+    }
+    fprintf(f, "\n    Initiated = %d\n", auth->initiated);
+    fprintf(f, "\n    Proto version = %d\n", auth->protocol_version);
+    fprintf(f, "\n    Lastauthmsg = %s\n",
+	auth->lastauthmsg ? auth->lastauthmsg : "(nil)");
+    fprintf(f, "\n    Commit sent time = %ld\n",
+	(long) auth->commit_sent_time);
+}
+
+#endif
+
+/*
+ * Initialize the fields of an OtrlAuthInfo (already allocated).
+ */
+void otrl_auth_new(struct context *context)
+{
+    OtrlAuthInfo *auth = &(context->auth);
+    auth->authstate = OTRL_AUTHSTATE_NONE;
+    otrl_dh_keypair_init(&(auth->our_dh));
+    auth->our_keyid = 0;
+    auth->encgx = NULL;
+    auth->encgx_len = 0;
+    memset(auth->r, 0, 16);
+    memset(auth->hashgx, 0, 32);
+    auth->their_pub = NULL;
+    auth->their_keyid = 0;
+    auth->enc_c = NULL;
+    auth->enc_cp = NULL;
+    auth->mac_m1 = NULL;
+    auth->mac_m1p = NULL;
+    auth->mac_m2 = NULL;
+    auth->mac_m2p = NULL;
+    memset(auth->their_fingerprint, 0, 20);
+    auth->initiated = 0;
+    auth->protocol_version = 0;
+    memset(auth->secure_session_id, 0, 20);
+    auth->secure_session_id_len = 0;
+    auth->lastauthmsg = NULL;
+    auth->commit_sent_time = 0;
+    auth->context = context;
+}
+
+/*
+ * Clear the fields of an OtrlAuthInfo (but leave it allocated).
+ */
+void otrl_auth_clear(OtrlAuthInfo *auth)
+{
+    auth->authstate = OTRL_AUTHSTATE_NONE;
+    otrl_dh_keypair_free(&(auth->our_dh));
+    auth->our_keyid = 0;
+    free(auth->encgx);
+    auth->encgx = NULL;
+    auth->encgx_len = 0;
+    memset(auth->r, 0, 16);
+    memset(auth->hashgx, 0, 32);
+    gcry_mpi_release(auth->their_pub);
+    auth->their_pub = NULL;
+    auth->their_keyid = 0;
+    gcry_cipher_close(auth->enc_c);
+    gcry_cipher_close(auth->enc_cp);
+    gcry_md_close(auth->mac_m1);
+    gcry_md_close(auth->mac_m1p);
+    gcry_md_close(auth->mac_m2);
+    gcry_md_close(auth->mac_m2p);
+    auth->enc_c = NULL;
+    auth->enc_cp = NULL;
+    auth->mac_m1 = NULL;
+    auth->mac_m1p = NULL;
+    auth->mac_m2 = NULL;
+    auth->mac_m2p = NULL;
+    memset(auth->their_fingerprint, 0, 20);
+    auth->initiated = 0;
+    auth->protocol_version = 0;
+    memset(auth->secure_session_id, 0, 20);
+    auth->secure_session_id_len = 0;
+    free(auth->lastauthmsg);
+    auth->lastauthmsg = NULL;
+    auth->commit_sent_time = 0;
+}
+
+/*
+ * Start a fresh AKE (version 2 or 3) using the given OtrlAuthInfo.  Generate
+ * a fresh DH keypair to use.  If no error is returned, the message to
+ * transmit will be contained in auth->lastauthmsg.
+ */
+gcry_error_t otrl_auth_start_v23(OtrlAuthInfo *auth, int version)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+    size_t npub;
+    gcry_cipher_hd_t enc = NULL;
+    unsigned char ctr[16];
+    unsigned char *buf, *bufp;
+    size_t buflen, lenp;
+
+    /* Clear out this OtrlAuthInfo and start over */
+    otrl_auth_clear(auth);
+    auth->initiated = 1;
+    auth->protocol_version = version;
+    auth->context->protocol_version = version;
+
+    otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
+    auth->our_keyid = 1;
+
+    /* Pick an encryption key */
+    gcry_randomize(auth->r, 16, GCRY_STRONG_RANDOM);
+
+    /* Allocate space for the encrypted g^x */
+    gcry_mpi_print(format, NULL, 0, &npub, auth->our_dh.pub);
+    auth->encgx = malloc(4+npub);
+    if (auth->encgx == NULL) goto memerr;
+    auth->encgx_len = 4+npub;
+    bufp = auth->encgx;
+    lenp = auth->encgx_len;
+    write_mpi(auth->our_dh.pub, npub, "g^x");
+    assert(lenp == 0);
+
+    /* Hash g^x */
+    gcry_md_hash_buffer(GCRY_MD_SHA256, auth->hashgx, auth->encgx,
+	    auth->encgx_len);
+
+    /* Encrypt g^x using the key r */
+    err = gcry_cipher_open(&enc, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CTR,
+	    GCRY_CIPHER_SECURE);
+    if (err) goto err;
+
+    err = gcry_cipher_setkey(enc, auth->r, 16);
+    if (err) goto err;
+
+    memset(ctr, 0, 16);
+    err = gcry_cipher_setctr(enc, ctr, 16);
+    if (err) goto err;
+
+    err = gcry_cipher_encrypt(enc, auth->encgx, auth->encgx_len, NULL, 0);
+    if (err) goto err;
+
+    gcry_cipher_close(enc);
+    enc = NULL;
+
+    /* Now serialize the message */
+    lenp = OTRL_HEADER_LEN + (auth->protocol_version == 3 ? 8 : 0) + 4
+	    + auth->encgx_len + 4 + 32;
+    bufp = malloc(lenp);
+    if (bufp == NULL) goto memerr;
+    buf = bufp;
+    buflen = lenp;
+
+    /* Header */
+    write_header(auth->protocol_version, '\x02');
+    if (auth->protocol_version == 3) {
+	/* instance tags */
+	write_int(auth->context->our_instance);
+	debug_int("Sender instag", bufp-4);
+	write_int(auth->context->their_instance);
+	debug_int("Recipient instag", bufp-4);
+    }
+
+    /* Encrypted g^x */
+    write_int(auth->encgx_len);
+    debug_int("Enc gx len", bufp-4);
+    memmove(bufp, auth->encgx, auth->encgx_len);
+    debug_data("Enc gx", bufp, auth->encgx_len);
+    bufp += auth->encgx_len; lenp -= auth->encgx_len;
+
+    /* Hashed g^x */
+    write_int(32);
+    debug_int("hashgx len", bufp-4);
+    memmove(bufp, auth->hashgx, 32);
+    debug_data("hashgx", bufp, 32);
+    bufp += 32; lenp -= 32;
+
+    assert(lenp == 0);
+
+    auth->lastauthmsg = otrl_base64_otr_encode(buf, buflen);
+    free(buf);
+    if (auth->lastauthmsg == NULL) goto memerr;
+    auth->authstate = OTRL_AUTHSTATE_AWAITING_DHKEY;
+
+    return err;
+
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    otrl_auth_clear(auth);
+    gcry_cipher_close(enc);
+    return err;
+}
+
+/*
+ * Create a D-H Key Message using the our_dh value in the given auth,
+ * and store it in auth->lastauthmsg.
+ */
+static gcry_error_t create_key_message(OtrlAuthInfo *auth)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+    unsigned char *buf, *bufp;
+    size_t buflen, lenp;
+    size_t npub;
+
+    gcry_mpi_print(format, NULL, 0, &npub, auth->our_dh.pub);
+    buflen = OTRL_HEADER_LEN + (auth->protocol_version == 3 ? 8 : 0) + 4 + npub;
+    buf = malloc(buflen);
+    if (buf == NULL) goto memerr;
+    bufp = buf;
+    lenp = buflen;
+
+    /* header */
+    write_header(auth->protocol_version, '\x0a');
+    if (auth->protocol_version == 3) {
+	/* instance tags */
+	write_int(auth->context->our_instance);
+	debug_int("Sender instag", bufp-4);
+	write_int(auth->context->their_instance);
+	debug_int("Recipient instag", bufp-4);
+    }
+
+    /* g^y */
+    write_mpi(auth->our_dh.pub, npub, "g^y");
+
+    assert(lenp == 0);
+
+    free(auth->lastauthmsg);
+    auth->lastauthmsg = otrl_base64_otr_encode(buf, buflen);
+    free(buf);
+    if (auth->lastauthmsg == NULL) goto memerr;
+
+    return err;
+
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+    return err;
+}
+
+/*
+ * Handle an incoming D-H Commit Message.  If no error is returned, the
+ * message to send will be left in auth->lastauthmsg.  Generate a fresh
+ * keypair to use.
+ */
+gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth,
+	const char *commitmsg, int version)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp = NULL, *encbuf = NULL;
+    unsigned char hashbuf[32];
+    size_t buflen, lenp, enclen, hashlen;
+    int res;
+
+    /* Are we the auth for the master context? */
+    int is_master = (auth->context->m_context == auth->context);
+
+    res = otrl_base64_otr_decode(commitmsg, &buf, &buflen);
+    if (res == -1) goto memerr;
+    if (res == -2) goto invval;
+
+    bufp = buf;
+    lenp = buflen;
+
+    /* Header */
+    auth->protocol_version = version;
+    auth->context->protocol_version = version;
+    skip_header('\x02');
+
+    if (version == 3) {
+	require_len(8);
+	bufp += 8; lenp -= 8;
+    }
+
+    /* Encrypted g^x */
+    read_int(enclen);
+    require_len(enclen);
+    encbuf = malloc(enclen);
+    if (encbuf == NULL && enclen > 0) goto memerr;
+    memmove(encbuf, bufp, enclen);
+    bufp += enclen; lenp -= enclen;
+
+    /* Hashed g^x */
+    read_int(hashlen);
+    if (hashlen != 32) goto invval;
+    require_len(32);
+    memmove(hashbuf, bufp, 32);
+    bufp += 32; lenp -= 32;
+
+    if (lenp != 0) goto invval;
+    free(buf);
+    buf = NULL;
+
+    switch(auth->authstate) {
+	case OTRL_AUTHSTATE_NONE:
+	case OTRL_AUTHSTATE_AWAITING_SIG:
+	case OTRL_AUTHSTATE_V1_SETUP:
+	    /* Store the incoming information */
+	    otrl_auth_clear(auth);
+	    auth->protocol_version = version;
+
+	    otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
+
+	    auth->our_keyid = 1;
+	    auth->encgx = encbuf;
+	    encbuf = NULL;
+	    auth->encgx_len = enclen;
+	    memmove(auth->hashgx, hashbuf, 32);
+
+	    /* Create a D-H Key Message */
+	    err = create_key_message(auth);
+	    if (err) goto err;
+	    auth->authstate = OTRL_AUTHSTATE_AWAITING_REVEALSIG;
+	    break;
+
+	case OTRL_AUTHSTATE_AWAITING_DHKEY:
+	    /* We sent a D-H Commit Message, and we also received one
+	     * back.  If we're the master context, then the keypair in here
+	     * is probably stale; we just kept it around for a little
+	     * while in case some other logged in instance of our buddy
+	     * replied with a DHKEY message.  In that case, use the
+	     * incoming parameters.  Otherwise, compare the hashgx
+	     * values to see which one wins.
+	     *
+	     * This does NOT use constant time comparison because these
+	     * are two public values thus don't need it. Also, this checks
+	     * which pubkey is larger and not if they are the same. */
+	    if (!is_master && memcmp(auth->hashgx, hashbuf, 32) > 0) {
+		/* Ours wins.  Ignore the message we received, and just
+		 * resend the same D-H Commit message again. */
+		free(encbuf);
+		encbuf = NULL;
+	    } else {
+		/* Ours loses.  Use the incoming parameters instead. */
+		otrl_auth_clear(auth);
+		auth->protocol_version = version;
+		otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
+		auth->our_keyid = 1;
+		auth->encgx = encbuf;
+		encbuf = NULL;
+		auth->encgx_len = enclen;
+		memmove(auth->hashgx, hashbuf, 32);
+
+		/* Create a D-H Key Message */
+		err = create_key_message(auth);
+		if (err) goto err;
+		auth->authstate = OTRL_AUTHSTATE_AWAITING_REVEALSIG;
+	    }
+	    break;
+	case OTRL_AUTHSTATE_AWAITING_REVEALSIG:
+	    /* Use the incoming parameters, but just retransmit the old
+	     * D-H Key Message. */
+	    free(auth->encgx);
+	    auth->encgx = encbuf;
+	    encbuf = NULL;
+	    auth->encgx_len = enclen;
+	    memmove(auth->hashgx, hashbuf, 32);
+	    break;
+    }
+
+    return err;
+
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    free(encbuf);
+    return err;
+}
+
+/*
+ * Calculate the encrypted part of the Reveal Signature and Signature
+ * Messages, given a MAC key, an encryption key, two DH public keys, an
+ * authentication public key (contained in an OtrlPrivKey structure),
+ * and a keyid.  If no error is returned, *authbufp will point to the
+ * result, and *authlenp will point to its length.
+ */
+static gcry_error_t calculate_pubkey_auth(unsigned char **authbufp,
+	size_t *authlenp, gcry_md_hd_t mackey, gcry_cipher_hd_t enckey,
+	gcry_mpi_t our_dh_pub, gcry_mpi_t their_dh_pub,
+	OtrlPrivKey *privkey, unsigned int keyid)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+    size_t ourpublen, theirpublen, totallen, lenp;
+    unsigned char *buf = NULL, *bufp = NULL;
+    unsigned char macbuf[32];
+    unsigned char *sigbuf = NULL;
+    size_t siglen;
+
+    /* How big are the DH public keys? */
+    gcry_mpi_print(format, NULL, 0, &ourpublen, our_dh_pub);
+    gcry_mpi_print(format, NULL, 0, &theirpublen, their_dh_pub);
+
+    /* How big is the total structure to be MAC'd? */
+    totallen = 4 + ourpublen + 4 + theirpublen + 2 + privkey->pubkey_datalen
+	    + 4;
+    buf = malloc(totallen);
+    if (buf == NULL) goto memerr;
+
+    bufp = buf;
+    lenp = totallen;
+
+    /* Write the data to be MAC'd */
+    write_mpi(our_dh_pub, ourpublen, "Our DH pubkey");
+    write_mpi(their_dh_pub, theirpublen, "Their DH pubkey");
+    bufp[0] = ((privkey->pubkey_type) >> 8) & 0xff;
+    bufp[1] = (privkey->pubkey_type) & 0xff;
+    bufp += 2; lenp -= 2;
+    memmove(bufp, privkey->pubkey_data, privkey->pubkey_datalen);
+    debug_data("Pubkey", bufp, privkey->pubkey_datalen);
+    bufp += privkey->pubkey_datalen; lenp -= privkey->pubkey_datalen;
+    write_int(keyid);
+    debug_int("Keyid", bufp-4);
+
+    assert(lenp == 0);
+
+    /* Do the MAC */
+    gcry_md_reset(mackey);
+    gcry_md_write(mackey, buf, totallen);
+    memmove(macbuf, gcry_md_read(mackey, GCRY_MD_SHA256), 32);
+
+    free(buf);
+    buf = NULL;
+
+    /* Sign the MAC */
+    err = otrl_privkey_sign(&sigbuf, &siglen, privkey, macbuf, 32);
+    if (err) goto err;
+
+    /* Calculate the total size of the structure to be encrypted */
+    totallen = 2 + privkey->pubkey_datalen + 4 + siglen;
+    buf = malloc(totallen);
+    if (buf == NULL) goto memerr;
+    bufp = buf;
+    lenp = totallen;
+
+    /* Write the data to be encrypted */
+    bufp[0] = ((privkey->pubkey_type) >> 8) & 0xff;
+    bufp[1] = (privkey->pubkey_type) & 0xff;
+    bufp += 2; lenp -= 2;
+    memmove(bufp, privkey->pubkey_data, privkey->pubkey_datalen);
+    debug_data("Pubkey", bufp, privkey->pubkey_datalen);
+    bufp += privkey->pubkey_datalen; lenp -= privkey->pubkey_datalen;
+    write_int(keyid);
+    debug_int("Keyid", bufp-4);
+    memmove(bufp, sigbuf, siglen);
+    debug_data("Signature", bufp, siglen);
+    bufp += siglen; lenp -= siglen;
+    free(sigbuf);
+    sigbuf = NULL;
+
+    assert(lenp == 0);
+
+    /* Now do the encryption */
+    err = gcry_cipher_encrypt(enckey, buf, totallen, NULL, 0);
+    if (err) goto err;
+
+    *authbufp = buf;
+    buf = NULL;
+    *authlenp = totallen;
+
+    return err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    free(sigbuf);
+    return err;
+}
+
+/*
+ * Decrypt the authenticator in the Reveal Signature and Signature
+ * Messages, given a MAC key, and encryption key, and two DH public
+ * keys.  The fingerprint of the received public key will get put into
+ * fingerprintbufp, and the received keyid will get put in *keyidp.
+ * The encrypted data pointed to by authbuf will be decrypted in place.
+ */
+static gcry_error_t check_pubkey_auth(unsigned char fingerprintbufp[20],
+	unsigned int *keyidp, unsigned char *authbuf, size_t authlen,
+	gcry_md_hd_t mackey, gcry_cipher_hd_t enckey,
+	gcry_mpi_t our_dh_pub, gcry_mpi_t their_dh_pub)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+    size_t ourpublen, theirpublen, totallen, lenp;
+    unsigned char *buf = NULL, *bufp = NULL;
+    unsigned char macbuf[32];
+    unsigned short pubkey_type;
+    gcry_mpi_t p,q,g,y;
+    gcry_sexp_t pubs = NULL;
+    unsigned int received_keyid;
+    unsigned char *fingerprintstart, *fingerprintend, *sigbuf;
+    size_t siglen;
+
+    /* Start by decrypting it */
+    err = gcry_cipher_decrypt(enckey, authbuf, authlen, NULL, 0);
+    if (err) goto err;
+
+    bufp = authbuf;
+    lenp = authlen;
+
+    /* Get the public key and calculate its fingerprint */
+    require_len(2);
+    pubkey_type = (bufp[0] << 8) + bufp[1];
+    bufp += 2; lenp -= 2;
+    if (pubkey_type != OTRL_PUBKEY_TYPE_DSA) goto invval;
+    fingerprintstart = bufp;
+    read_mpi(p);
+    read_mpi(q);
+    read_mpi(g);
+    read_mpi(y);
+    fingerprintend = bufp;
+    gcry_md_hash_buffer(GCRY_MD_SHA1, fingerprintbufp,
+	    fingerprintstart, fingerprintend-fingerprintstart);
+    gcry_sexp_build(&pubs, NULL,
+	"(public-key (dsa (p %m)(q %m)(g %m)(y %m)))", p, q, g, y);
+    gcry_mpi_release(p);
+    gcry_mpi_release(q);
+    gcry_mpi_release(g);
+    gcry_mpi_release(y);
+
+    /* Get the keyid */
+    read_int(received_keyid);
+    if (received_keyid == 0) goto invval;
+
+    /* Get the signature */
+    sigbuf = bufp;
+    siglen = lenp;
+
+    /* How big are the DH public keys? */
+    gcry_mpi_print(format, NULL, 0, &ourpublen, our_dh_pub);
+    gcry_mpi_print(format, NULL, 0, &theirpublen, their_dh_pub);
+
+    /* Now calculate the message to be MAC'd. */
+    totallen = 4 + ourpublen + 4 + theirpublen + 2 +
+	(fingerprintend - fingerprintstart) + 4;
+    buf = malloc(totallen);
+    if (buf == NULL) goto memerr;
+
+    bufp = buf;
+    lenp = totallen;
+
+    write_mpi(their_dh_pub, theirpublen, "Their DH pubkey");
+    write_mpi(our_dh_pub, ourpublen, "Our DH pubkey");
+    bufp[0] = (pubkey_type >> 8) & 0xff;
+    bufp[1] = pubkey_type & 0xff;
+    bufp += 2; lenp -= 2;
+    memmove(bufp, fingerprintstart, fingerprintend - fingerprintstart);
+    debug_data("Pubkey", bufp, fingerprintend - fingerprintstart);
+    bufp += fingerprintend - fingerprintstart;
+    lenp -= fingerprintend - fingerprintstart;
+    write_int(received_keyid);
+    debug_int("Keyid", bufp-4);
+
+    assert(lenp == 0);
+
+    /* Do the MAC */
+    gcry_md_reset(mackey);
+    gcry_md_write(mackey, buf, totallen);
+    memmove(macbuf, gcry_md_read(mackey, GCRY_MD_SHA256), 32);
+
+    free(buf);
+    buf = NULL;
+
+    /* Verify the signature on the MAC */
+    err = otrl_privkey_verify(sigbuf, siglen, pubkey_type, pubs, macbuf, 32);
+    if (err) goto err;
+    gcry_sexp_release(pubs);
+    pubs = NULL;
+
+    /* Everything checked out */
+    *keyidp = received_keyid;
+
+    return err;
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    gcry_sexp_release(pubs);
+    return err;
+}
+
+/*
+ * Create a Reveal Signature Message using the values in the given auth,
+ * and store it in auth->lastauthmsg.  Use the given privkey to sign the
+ * message.
+ */
+static gcry_error_t create_revealsig_message(OtrlAuthInfo *auth,
+	OtrlPrivKey *privkey)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp, *startmac;
+    size_t buflen, lenp;
+
+    unsigned char *authbuf = NULL;
+    size_t authlen;
+
+    /* Get the encrypted authenticator */
+    err = calculate_pubkey_auth(&authbuf, &authlen, auth->mac_m1, auth->enc_c,
+	    auth->our_dh.pub, auth->their_pub, privkey, auth->our_keyid);
+    if (err) goto err;
+
+    buflen = OTRL_HEADER_LEN + (auth->protocol_version == 3 ? 8 : 0) + 4 + 16
+	    + 4 + authlen + 20;
+    buf = malloc(buflen);
+    if (buf == NULL) goto memerr;
+
+    bufp = buf;
+    lenp = buflen;
+
+    /* header */
+    write_header(auth->protocol_version, '\x11');
+    if (auth->protocol_version == 3) {
+	/* instance tags */
+	write_int(auth->context->our_instance);
+	debug_int("Sender instag", bufp-4);
+	write_int(auth->context->their_instance);
+	debug_int("Recipient instag", bufp-4);
+    }
+
+    /* r */
+    write_int(16);
+    memmove(bufp, auth->r, 16);
+    debug_data("r", bufp, 16);
+    bufp += 16; lenp -= 16;
+
+    /* Encrypted authenticator */
+    startmac = bufp;
+    write_int(authlen);
+    memmove(bufp, authbuf, authlen);
+    debug_data("auth", bufp, authlen);
+    bufp += authlen; lenp -= authlen;
+    free(authbuf);
+    authbuf = NULL;
+
+    /* MAC it, but only take the first 20 bytes */
+    gcry_md_reset(auth->mac_m2);
+    gcry_md_write(auth->mac_m2, startmac, bufp - startmac);
+    memmove(bufp, gcry_md_read(auth->mac_m2, GCRY_MD_SHA256), 20);
+    debug_data("MAC", bufp, 20);
+    bufp += 20; lenp -= 20;
+
+    assert(lenp == 0);
+
+    free(auth->lastauthmsg);
+    auth->lastauthmsg = otrl_base64_otr_encode(buf, buflen);
+    if (auth->lastauthmsg == NULL) goto memerr;
+    free(buf);
+    buf = NULL;
+
+    return err;
+
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    free(authbuf);
+    return err;
+}
+
+/*
+ * Create a Signature Message using the values in the given auth, and
+ * store it in auth->lastauthmsg.  Use the given privkey to sign the
+ * message.
+ */
+static gcry_error_t create_signature_message(OtrlAuthInfo *auth,
+	OtrlPrivKey *privkey)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp, *startmac;
+    size_t buflen, lenp;
+
+    unsigned char *authbuf = NULL;
+    size_t authlen;
+
+    /* Get the encrypted authenticator */
+    err = calculate_pubkey_auth(&authbuf, &authlen, auth->mac_m1p,
+	    auth->enc_cp, auth->our_dh.pub, auth->their_pub, privkey,
+	    auth->our_keyid);
+    if (err) goto err;
+
+    buflen = OTRL_HEADER_LEN + (auth->protocol_version == 3 ? 8 : 0) + 4
+	    + authlen + 20;
+    buf = malloc(buflen);
+    if (buf == NULL) goto memerr;
+
+    bufp = buf;
+    lenp = buflen;
+
+    /* header */
+    write_header(auth->protocol_version, '\x12');
+    if (auth->protocol_version == 3) {
+	/* instance tags */
+	write_int(auth->context->our_instance);
+	debug_int("Sender instag", bufp-4);
+	write_int(auth->context->their_instance);
+	debug_int("Recipient instag", bufp-4);
+    }
+
+    /* Encrypted authenticator */
+    startmac = bufp;
+    write_int(authlen);
+    memmove(bufp, authbuf, authlen);
+    debug_data("auth", bufp, authlen);
+    bufp += authlen; lenp -= authlen;
+    free(authbuf);
+    authbuf = NULL;
+
+    /* MAC it, but only take the first 20 bytes */
+    gcry_md_reset(auth->mac_m2p);
+    gcry_md_write(auth->mac_m2p, startmac, bufp - startmac);
+    memmove(bufp, gcry_md_read(auth->mac_m2p, GCRY_MD_SHA256), 20);
+    debug_data("MAC", bufp, 20);
+    bufp += 20; lenp -= 20;
+
+    assert(lenp == 0);
+
+    free(auth->lastauthmsg);
+    auth->lastauthmsg = otrl_base64_otr_encode(buf, buflen);
+    if (auth->lastauthmsg == NULL) goto memerr;
+    free(buf);
+    buf = NULL;
+
+    return err;
+
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    free(authbuf);
+    return err;
+}
+
+/*
+ * Handle an incoming D-H Key Message.  If no error is returned, and
+ * *havemsgp is 1, the message to sent will be left in auth->lastauthmsg.
+ * Use the given private authentication key to sign messages.
+ */
+gcry_error_t otrl_auth_handle_key(OtrlAuthInfo *auth, const char *keymsg,
+	int *havemsgp, OtrlPrivKey *privkey)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp = NULL;
+    size_t buflen, lenp;
+    gcry_mpi_t incoming_pub = NULL;
+    int res;
+    unsigned int msg_version;
+
+    *havemsgp = 0;
+
+    msg_version = otrl_proto_message_version(keymsg);
+
+    res = otrl_base64_otr_decode(keymsg, &buf, &buflen);
+    if (res == -1) goto memerr;
+    if (res == -2) goto invval;
+
+    bufp = buf;
+    lenp = buflen;
+
+    /* Header */
+    skip_header('\x0a');
+
+    if (msg_version == 3) {
+	require_len(8);
+	bufp += 8; lenp -= 8;
+    }
+
+    /* g^y */
+    read_mpi(incoming_pub);
+
+    if (lenp != 0) goto invval;
+    free(buf);
+    buf = NULL;
+
+    switch(auth->authstate) {
+	case OTRL_AUTHSTATE_AWAITING_DHKEY:
+	    /* The other party may also be establishing a session with
+	    another instance running a different version. Ignore any
+	    DHKEY messages we aren't expecting. */
+	    if (msg_version != auth->protocol_version) {
+	      goto err;
+	    }
+
+	    /* Store the incoming public key */
+	    gcry_mpi_release(auth->their_pub);
+	    auth->their_pub = incoming_pub;
+	    incoming_pub = NULL;
+
+	    /* Compute the encryption and MAC keys */
+	    err = otrl_dh_compute_v2_auth_keys(&(auth->our_dh),
+		    auth->their_pub, auth->secure_session_id,
+		    &(auth->secure_session_id_len),
+		    &(auth->enc_c), &(auth->enc_cp),
+		    &(auth->mac_m1), &(auth->mac_m1p),
+		    &(auth->mac_m2), &(auth->mac_m2p));
+	    if (err) goto err;
+
+	    /* Create the Reveal Signature Message */
+	    err = create_revealsig_message(auth, privkey);
+	    if (err) goto err;
+	    *havemsgp = 1;
+	    auth->authstate = OTRL_AUTHSTATE_AWAITING_SIG;
+
+	    break;
+
+	case OTRL_AUTHSTATE_AWAITING_SIG:
+	    if (gcry_mpi_cmp(incoming_pub, auth->their_pub) == 0) {
+		/* Retransmit the Reveal Signature Message */
+		*havemsgp = 1;
+	    } else {
+		/* Ignore this message */
+		*havemsgp = 0;
+	    }
+	    break;
+	case OTRL_AUTHSTATE_NONE:
+	case OTRL_AUTHSTATE_AWAITING_REVEALSIG:
+	case OTRL_AUTHSTATE_V1_SETUP:
+	    /* Ignore this message */
+	    *havemsgp = 0;
+	    break;
+    }
+
+    gcry_mpi_release(incoming_pub);
+    return err;
+
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    gcry_mpi_release(incoming_pub);
+    return err;
+}
+
+/*
+ * Handle an incoming Reveal Signature Message.  If no error is
+ * returned, and *havemsgp is 1, the message to be sent will be left in
+ * auth->lastauthmsg.  Use the given private authentication key to sign
+ * messages.  Call the auth_succeeded callback if authentication is
+ * successful.
+ */
+gcry_error_t otrl_auth_handle_revealsig(OtrlAuthInfo *auth,
+	const char *revealmsg, int *havemsgp, OtrlPrivKey *privkey,
+	gcry_error_t (*auth_succeeded)(const OtrlAuthInfo *auth, void *asdata),
+	void *asdata)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp = NULL, *gxbuf = NULL;
+    unsigned char *authstart, *authend, *macstart;
+    size_t buflen, lenp, rlen, authlen;
+    gcry_cipher_hd_t enc = NULL;
+    gcry_mpi_t incoming_pub = NULL;
+    unsigned char ctr[16], hashbuf[32];
+    int res;
+    unsigned char version;
+
+    *havemsgp = 0;
+
+    res = otrl_base64_otr_decode(revealmsg, &buf, &buflen);
+    if (res == -1) goto memerr;
+    if (res == -2) goto invval;
+
+    bufp = buf;
+    lenp = buflen;
+
+    require_len(3);
+    version = bufp[1];
+
+    /* Header */
+    skip_header('\x11');
+
+    if (version == 3) {
+	require_len(8);
+	bufp += 8; lenp -= 8;
+    }
+
+    /* r */
+    read_int(rlen);
+    if (rlen != 16) goto invval;
+    require_len(rlen);
+    memmove(auth->r, bufp, rlen);
+    bufp += rlen; lenp -= rlen;
+
+    /* auth */
+    authstart = bufp;
+    read_int(authlen);
+    require_len(authlen);
+    bufp += authlen; lenp -= authlen;
+    authend = bufp;
+
+    /* MAC */
+    require_len(20);
+    macstart = bufp;
+    bufp += 20; lenp -= 20;
+
+    if (lenp != 0) goto invval;
+
+    switch(auth->authstate) {
+	case OTRL_AUTHSTATE_AWAITING_REVEALSIG:
+	    gxbuf = malloc(auth->encgx_len);
+	    if (auth->encgx_len && gxbuf == NULL) goto memerr;
+
+	    /* Use r to decrypt the value of g^x we received earlier */
+	    err = gcry_cipher_open(&enc, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CTR,
+		    GCRY_CIPHER_SECURE);
+	    if (err) goto err;
+
+	    err = gcry_cipher_setkey(enc, auth->r, 16);
+	    if (err) goto err;
+
+	    memset(ctr, 0, 16);
+	    err = gcry_cipher_setctr(enc, ctr, 16);
+	    if (err) goto err;
+
+	    err = gcry_cipher_decrypt(enc, gxbuf, auth->encgx_len,
+		    auth->encgx, auth->encgx_len);
+	    if (err) goto err;
+
+	    gcry_cipher_close(enc);
+	    enc = NULL;
+
+	    /* Check the hash */
+	    gcry_md_hash_buffer(GCRY_MD_SHA256, hashbuf, gxbuf,
+		    auth->encgx_len);
+	    /* This isn't comparing secret data, but may as well use the
+	     * constant-time version. */
+	    if (otrl_mem_differ(hashbuf, auth->hashgx, 32)) goto decfail;
+
+	    /* Extract g^x */
+	    bufp = gxbuf;
+	    lenp = auth->encgx_len;
+
+	    read_mpi(incoming_pub);
+	    free(gxbuf);
+	    gxbuf = NULL;
+
+	    if (lenp != 0) goto invval;
+
+	    gcry_mpi_release(auth->their_pub);
+	    auth->their_pub = incoming_pub;
+	    incoming_pub = NULL;
+
+	    /* Compute the encryption and MAC keys */
+	    err = otrl_dh_compute_v2_auth_keys(&(auth->our_dh),
+		    auth->their_pub, auth->secure_session_id,
+		    &(auth->secure_session_id_len),
+		    &(auth->enc_c), &(auth->enc_cp),
+		    &(auth->mac_m1), &(auth->mac_m1p),
+		    &(auth->mac_m2), &(auth->mac_m2p));
+	    if (err) goto err;
+
+	    /* Check the MAC */
+	    gcry_md_reset(auth->mac_m2);
+	    gcry_md_write(auth->mac_m2, authstart, authend - authstart);
+
+	    if (otrl_mem_differ(macstart,
+			gcry_md_read(auth->mac_m2, GCRY_MD_SHA256),
+			20)) goto invval;
+
+	    /* Check the auth */
+	    err = check_pubkey_auth(auth->their_fingerprint,
+		    &(auth->their_keyid), authstart + 4,
+		    authend - authstart - 4, auth->mac_m1, auth->enc_c,
+		    auth->our_dh.pub, auth->their_pub);
+	    if (err) goto err;
+
+	    authstart = NULL;
+	    authend = NULL;
+	    macstart = NULL;
+	    free(buf);
+	    buf = NULL;
+
+	    /* Create the Signature Message */
+	    err = create_signature_message(auth, privkey);
+	    if (err) goto err;
+
+	    /* No error?  Then we've completed our end of the
+	     * authentication. */
+	    auth->session_id_half = OTRL_SESSIONID_SECOND_HALF_BOLD;
+	    if (auth_succeeded) err = auth_succeeded(auth, asdata);
+	    *havemsgp = 1;
+	    auth->our_keyid = 0;
+	    auth->authstate = OTRL_AUTHSTATE_NONE;
+
+	    break;
+	case OTRL_AUTHSTATE_NONE:
+	case OTRL_AUTHSTATE_AWAITING_DHKEY:
+	case OTRL_AUTHSTATE_AWAITING_SIG:
+	case OTRL_AUTHSTATE_V1_SETUP:
+	    /* Ignore this message */
+	    *havemsgp = 0;
+	    free(buf);
+	    buf = NULL;
+	    break;
+    }
+
+    return err;
+
+decfail:
+    err = gcry_error(GPG_ERR_NO_ERROR);
+    goto err;
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    free(gxbuf);
+    gcry_cipher_close(enc);
+    gcry_mpi_release(incoming_pub);
+    return err;
+}
+
+/*
+ * Handle an incoming Signature Message.  If no error is returned, and
+ * *havemsgp is 1, the message to be sent will be left in
+ * auth->lastauthmsg.  Call the auth_succeeded callback if
+ * authentication is successful.
+ */
+gcry_error_t otrl_auth_handle_signature(OtrlAuthInfo *auth,
+	const char *sigmsg, int *havemsgp,
+	gcry_error_t (*auth_succeeded)(const OtrlAuthInfo *auth, void *asdata),
+	void *asdata)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp = NULL;
+    unsigned char *authstart, *authend, *macstart;
+    size_t buflen, lenp, authlen;
+    int res;
+    unsigned char version;
+
+    *havemsgp = 0;
+
+    res = otrl_base64_otr_decode(sigmsg, &buf, &buflen);
+    if (res == -1) goto memerr;
+    if (res == -2) goto invval;
+
+    bufp = buf;
+    lenp = buflen;
+
+    require_len(3);
+    version = bufp[1];
+
+    /* Header */
+    skip_header('\x12');
+
+    if (version == 3) {
+	require_len(8);
+	bufp += 8; lenp -= 8;
+    }
+
+    /* auth */
+    authstart = bufp;
+    read_int(authlen);
+    require_len(authlen);
+    bufp += authlen; lenp -= authlen;
+    authend = bufp;
+
+    /* MAC */
+    require_len(20);
+    macstart = bufp;
+    bufp += 20; lenp -= 20;
+
+    if (lenp != 0) goto invval;
+
+    switch(auth->authstate) {
+	case OTRL_AUTHSTATE_AWAITING_SIG:
+	    /* Check the MAC */
+	    gcry_md_reset(auth->mac_m2p);
+	    gcry_md_write(auth->mac_m2p, authstart, authend - authstart);
+	    if (otrl_mem_differ(macstart,
+			gcry_md_read(auth->mac_m2p, GCRY_MD_SHA256),
+			20)) goto invval;
+
+	    /* Check the auth */
+	    err = check_pubkey_auth(auth->their_fingerprint,
+		    &(auth->their_keyid), authstart + 4,
+		    authend - authstart - 4, auth->mac_m1p, auth->enc_cp,
+		    auth->our_dh.pub, auth->their_pub);
+	    if (err) goto err;
+
+	    authstart = NULL;
+	    authend = NULL;
+	    macstart = NULL;
+	    free(buf);
+	    buf = NULL;
+
+	    /* No error?  Then we've completed our end of the
+	     * authentication. */
+	    auth->session_id_half = OTRL_SESSIONID_FIRST_HALF_BOLD;
+	    if (auth_succeeded) err = auth_succeeded(auth, asdata);
+	    free(auth->lastauthmsg);
+	    auth->lastauthmsg = NULL;
+	    *havemsgp = 0;
+	    auth->our_keyid = 0;
+	    auth->authstate = OTRL_AUTHSTATE_NONE;
+
+	    break;
+	case OTRL_AUTHSTATE_NONE:
+	case OTRL_AUTHSTATE_AWAITING_DHKEY:
+	case OTRL_AUTHSTATE_AWAITING_REVEALSIG:
+	case OTRL_AUTHSTATE_V1_SETUP:
+	    /* Ignore this message */
+	    *havemsgp = 0;
+	    free(buf);
+	    buf = NULL;
+	    break;
+    }
+
+    return err;
+
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    return err;
+}
+
+/* Version 1 routines, for compatibility */
+
+/*
+ * Create a verion 1 Key Exchange Message using the values in the given
+ * auth, and store it in auth->lastauthmsg.  Set the Reply field to the
+ * given value, and use the given privkey to sign the message.
+ */
+static gcry_error_t create_v1_key_exchange_message(OtrlAuthInfo *auth,
+	unsigned char reply, OtrlPrivKey *privkey)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+    unsigned char *buf = NULL, *bufp = NULL, *sigbuf = NULL;
+    size_t lenp, ourpublen, totallen, siglen;
+    unsigned char hashbuf[20];
+
+    if (privkey->pubkey_type != OTRL_PUBKEY_TYPE_DSA) {
+	return gpg_error(GPG_ERR_INV_VALUE);
+    }
+
+    /* How big is the DH public key? */
+    gcry_mpi_print(format, NULL, 0, &ourpublen, auth->our_dh.pub);
+
+    totallen = 3 + 1 + privkey->pubkey_datalen + 4 + 4 + ourpublen + 40;
+    buf = malloc(totallen);
+    if (buf == NULL) goto memerr;
+
+    bufp = buf;
+    lenp = totallen;
+
+    memmove(bufp, "\x00\x01\x0a", 3); /* header */
+    debug_data("Header", bufp, 3);
+    bufp += 3; lenp -= 3;
+
+    bufp[0] = reply;
+    debug_data("Reply", bufp, 1);
+    bufp += 1; lenp -= 1;
+
+    memmove(bufp, privkey->pubkey_data, privkey->pubkey_datalen);
+    debug_data("Pubkey", bufp, privkey->pubkey_datalen);
+    bufp += privkey->pubkey_datalen; lenp -= privkey->pubkey_datalen;
+
+    write_int(auth->our_keyid);
+    debug_int("Keyid", bufp-4);
+
+    write_mpi(auth->our_dh.pub, ourpublen, "D-H y");
+
+    /* Hash all the data written so far, and sign the hash */
+    gcry_md_hash_buffer(GCRY_MD_SHA1, hashbuf, buf, bufp - buf);
+
+    err = otrl_privkey_sign(&sigbuf, &siglen, privkey, hashbuf, 20);
+    if (err) goto err;
+
+    if (siglen != 40) goto invval;
+    memmove(bufp, sigbuf, 40);
+    debug_data("Signature", bufp, 40);
+    bufp += 40; lenp -= 40;
+    free(sigbuf);
+    sigbuf = NULL;
+
+    assert(lenp == 0);
+
+    free(auth->lastauthmsg);
+    auth->lastauthmsg = otrl_base64_otr_encode(buf, totallen);
+    if (auth->lastauthmsg == NULL) goto memerr;
+    free(buf);
+    buf = NULL;
+
+    return err;
+
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    free(sigbuf);
+    return err;
+}
+
+/*
+ * Start a fresh AKE (version 1) using the given OtrlAuthInfo.  If
+ * our_dh is NULL, generate a fresh DH keypair to use.  Otherwise, use a
+ * copy of the one passed (with the given keyid).  Use the given private
+ * key to sign the message.  If no error is returned, the message to
+ * transmit will be contained in auth->lastauthmsg.
+ */
+gcry_error_t otrl_auth_start_v1(OtrlAuthInfo *auth, DH_keypair *our_dh,
+	unsigned int our_keyid, OtrlPrivKey *privkey)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+
+    /* Clear out this OtrlAuthInfo and start over */
+    otrl_auth_clear(auth);
+    auth->initiated = 1;
+    auth->protocol_version = 1;
+
+    /* Import the given DH keypair, or else create a fresh one */
+    if (our_dh) {
+	otrl_dh_keypair_copy(&(auth->our_dh), our_dh);
+	auth->our_keyid = our_keyid;
+    } else {
+	otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
+	auth->our_keyid = 1;
+    }
+
+    err = create_v1_key_exchange_message(auth, 0, privkey);
+    if (!err) {
+	auth->authstate = OTRL_AUTHSTATE_V1_SETUP;
+    }
+
+    return err;
+}
+
+/*
+ * Handle an incoming v1 Key Exchange Message.  If no error is returned,
+ * and *havemsgp is 1, the message to be sent will be left in
+ * auth->lastauthmsg.  Use the given private authentication key to sign
+ * messages.  Call the auth_secceeded callback if authentication is
+ * successful.  If non-NULL, use a copy of the given D-H keypair, with
+ * the given keyid.
+ */
+gcry_error_t otrl_auth_handle_v1_key_exchange(OtrlAuthInfo *auth,
+	const char *keyexchmsg, int *havemsgp, OtrlPrivKey *privkey,
+	DH_keypair *our_dh, unsigned int our_keyid,
+	gcry_error_t (*auth_succeeded)(const OtrlAuthInfo *auth, void *asdata),
+	void *asdata)
+{
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+    unsigned char *buf = NULL, *bufp = NULL;
+    unsigned char *fingerprintstart, *fingerprintend;
+    unsigned char fingerprintbuf[20], hashbuf[20];
+    gcry_mpi_t p, q, g, y, received_pub = NULL;
+    gcry_sexp_t pubs = NULL;
+    size_t buflen, lenp;
+    unsigned char received_reply;
+    unsigned int received_keyid;
+    int res;
+
+    *havemsgp = 0;
+
+    res = otrl_base64_otr_decode(keyexchmsg, &buf, &buflen);
+    if (res == -1) goto memerr;
+    if (res == -2) goto invval;
+
+    bufp = buf;
+    lenp = buflen;
+
+    /* Header */
+    require_len(3);
+    if (memcmp(bufp, "\x00\x01\x0a", 3)) goto invval;
+    bufp += 3; lenp -= 3;
+
+    /* Reply */
+    require_len(1);
+    received_reply = bufp[0];
+    bufp += 1; lenp -= 1;
+
+    /* Public Key */
+    fingerprintstart = bufp;
+    read_mpi(p);
+    read_mpi(q);
+    read_mpi(g);
+    read_mpi(y);
+    fingerprintend = bufp;
+    gcry_md_hash_buffer(GCRY_MD_SHA1, fingerprintbuf,
+	    fingerprintstart, fingerprintend-fingerprintstart);
+    gcry_sexp_build(&pubs, NULL,
+	"(public-key (dsa (p %m)(q %m)(g %m)(y %m)))", p, q, g, y);
+    gcry_mpi_release(p);
+    gcry_mpi_release(q);
+    gcry_mpi_release(g);
+    gcry_mpi_release(y);
+
+    /* keyid */
+    read_int(received_keyid);
+    if (received_keyid == 0) goto invval;
+
+    /* D-H pubkey */
+    read_mpi(received_pub);
+
+    /* Verify the signature */
+    if (lenp != 40) goto invval;
+    gcry_md_hash_buffer(GCRY_MD_SHA1, hashbuf, buf, bufp - buf);
+    err = otrl_privkey_verify(bufp, lenp, OTRL_PUBKEY_TYPE_DSA,
+	    pubs, hashbuf, 20);
+    if (err) goto err;
+    gcry_sexp_release(pubs);
+    pubs = NULL;
+    free(buf);
+    buf = NULL;
+
+    if (auth->authstate != OTRL_AUTHSTATE_V1_SETUP && received_reply == 0x01) {
+	/* They're replying to something we never sent.  We must be
+	 * logged in more than once; ignore the message. */
+	err = gpg_error(GPG_ERR_NO_ERROR);
+	goto err;
+    }
+
+    if (auth->authstate != OTRL_AUTHSTATE_V1_SETUP) {
+	/* Clear the auth and start over */
+	otrl_auth_clear(auth);
+    }
+
+    /* Everything checked out */
+    auth->their_keyid = received_keyid;
+    gcry_mpi_release(auth->their_pub);
+    auth->their_pub = received_pub;
+    received_pub = NULL;
+    memmove(auth->their_fingerprint, fingerprintbuf, 20);
+
+    if (received_reply == 0x01) {
+	/* Don't send a reply to this. */
+	*havemsgp = 0;
+    } else {
+	/* Import the given DH keypair, or else create a fresh one */
+	if (our_dh) {
+	    otrl_dh_keypair_copy(&(auth->our_dh), our_dh);
+	    auth->our_keyid = our_keyid;
+	} else if (auth->our_keyid == 0) {
+	    otrl_dh_gen_keypair(DH1536_GROUP_ID, &(auth->our_dh));
+	    auth->our_keyid = 1;
+	}
+
+	/* Reply with our own Key Exchange Message */
+	err = create_v1_key_exchange_message(auth, 1, privkey);
+	if (err) goto err;
+	*havemsgp = 1;
+    }
+
+    /* Compute the session id */
+    err = otrl_dh_compute_v1_session_id(&(auth->our_dh),
+	    auth->their_pub, auth->secure_session_id,
+	    &(auth->secure_session_id_len),
+	    &(auth->session_id_half));
+    if (err) goto err;
+
+    /* We've completed our end of the authentication */
+    auth->protocol_version = 1;
+    if (auth_succeeded) err = auth_succeeded(auth, asdata);
+    auth->our_keyid = 0;
+    auth->authstate = OTRL_AUTHSTATE_NONE;
+
+    return err;
+
+invval:
+    err = gcry_error(GPG_ERR_INV_VALUE);
+    goto err;
+memerr:
+    err = gcry_error(GPG_ERR_ENOMEM);
+err:
+    free(buf);
+    gcry_sexp_release(pubs);
+    gcry_mpi_release(received_pub);
+    return err;
+}
+
+/*
+ * Copy relevant information from the master OtrlAuthInfo to an
+ * instance OtrlAuthInfo in response to a D-H Key with a new
+ * instance. The fields copied will depend on the state of the
+ * master auth.
+ */
+void otrl_auth_copy_on_key(OtrlAuthInfo *m_auth, OtrlAuthInfo *auth)
+{
+    switch(m_auth->authstate) {
+	case OTRL_AUTHSTATE_AWAITING_DHKEY:
+	case OTRL_AUTHSTATE_AWAITING_SIG:
+	    /* Copy our D-H Commit information to the new instance */
+	    otrl_dh_keypair_free(&(auth->our_dh));
+	    auth->initiated = m_auth->initiated;
+	    otrl_dh_keypair_copy(&(auth->our_dh), &(m_auth->our_dh));
+	    auth->our_keyid = m_auth->our_keyid;
+	    memmove(auth->r, m_auth->r, 16);
+	    if (auth->encgx) free(auth->encgx);
+	    auth->encgx = malloc(m_auth->encgx_len);
+	    memmove(auth->encgx, m_auth->encgx, m_auth->encgx_len);
+	    memmove(auth->hashgx, m_auth->hashgx, 32);
+
+	    auth->authstate = OTRL_AUTHSTATE_AWAITING_DHKEY;
+	    break;
+
+	default:
+	    /* This bad state will be detected and handled later */
+	    break;
+    }
+}
+
+#ifdef OTRL_TESTING_AUTH
+#include "mem.h"
+#include "privkey.h"
+
+#define CHECK_ERR if (err) { printf("Error: %s\n", gcry_strerror(err)); \
+			return 1; }
+
+static gcry_error_t starting(const OtrlAuthInfo *auth, void *asdata)
+{
+    char *name = asdata;
+
+    fprintf(stderr, "\nStarting ENCRYPTED mode for %s (v%d).\n",
+	    name, auth->protocol_version);
+
+    fprintf(stderr, "\nour_dh (%d):", auth->our_keyid);
+    gcry_mpi_dump(auth->our_dh.pub);
+    fprintf(stderr, "\ntheir_pub (%d):", auth->their_keyid);
+    gcry_mpi_dump(auth->their_pub);
+
+    debug_data("\nTheir fingerprint", auth->their_fingerprint, 20);
+    debug_data("\nSecure session id", auth->secure_session_id,
+	    auth->secure_session_id_len);
+    fprintf(stderr, "Sessionid half: %d\n\n", auth->session_id_half);
+
+    return gpg_error(GPG_ERR_NO_ERROR);
+}
+
+int main(int argc, char **argv)
+{
+    OtrlAuthInfo alice, bob;
+    gcry_error_t err;
+    int havemsg;
+    OtrlUserState us;
+    OtrlPrivKey *alicepriv, *bobpriv;
+
+    otrl_mem_init();
+    otrl_dh_init();
+    otrl_auth_new(&alice);
+    otrl_auth_new(&bob);
+
+    us = otrl_userstate_create();
+    otrl_privkey_read(us, "/home/iang/.gaim/otr.private_key");
+    alicepriv = otrl_privkey_find(us, "oneeyedian", "prpl-oscar");
+    bobpriv = otrl_privkey_find(us, "otr4ian", "prpl-oscar");
+
+    printf("\n\n  ***** V2 *****\n\n");
+
+    err = otrl_auth_start_v23(&bob, NULL, 0);
+    CHECK_ERR
+    printf("\nBob: %d\n%s\n\n", strlen(bob.lastauthmsg), bob.lastauthmsg);
+    err = otrl_auth_handle_commit(&alice, bob.lastauthmsg, NULL, 0);
+    CHECK_ERR
+    printf("\nAlice: %d\n%s\n\n", strlen(alice.lastauthmsg), alice.lastauthmsg);
+    err = otrl_auth_handle_key(&bob, alice.lastauthmsg, &havemsg, bobpriv);
+    CHECK_ERR
+    if (havemsg) {
+	printf("\nBob: %d\n%s\n\n", strlen(bob.lastauthmsg), bob.lastauthmsg);
+    } else {
+	printf("\nIGNORE\n\n");
+    }
+    err = otrl_auth_handle_revealsig(&alice, bob.lastauthmsg, &havemsg,
+	    alicepriv, starting, "Alice");
+    CHECK_ERR
+    if (havemsg) {
+	printf("\nAlice: %d\n%s\n\n", strlen(alice.lastauthmsg),
+		alice.lastauthmsg);
+    } else {
+	printf("\nIGNORE\n\n");
+    }
+    err = otrl_auth_handle_signature(&bob, alice.lastauthmsg, &havemsg,
+	    starting, "Bob");
+    CHECK_ERR
+
+    printf("\n\n  ***** V1 *****\n\n");
+
+    err = otrl_auth_start_v1(&bob, NULL, 0, bobpriv);
+    CHECK_ERR
+    printf("\nBob: %d\n%s\n\n", strlen(bob.lastauthmsg), bob.lastauthmsg);
+    err = otrl_auth_handle_v1_key_exchange(&alice, bob.lastauthmsg,
+	    &havemsg, alicepriv, NULL, 0, starting, "Alice");
+    CHECK_ERR
+    if (havemsg) {
+	printf("\nAlice: %d\n%s\n\n", strlen(alice.lastauthmsg),
+		alice.lastauthmsg);
+    } else {
+	printf("\nIGNORE\n\n");
+    }
+    err = otrl_auth_handle_v1_key_exchange(&bob, alice.lastauthmsg,
+	    &havemsg, bobpriv, NULL, 0, starting, "Bob");
+    CHECK_ERR
+    if (havemsg) {
+	printf("\nBob: %d\n%s\n\n", strlen(bob.lastauthmsg), bob.lastauthmsg);
+    } else {
+	printf("\nIGNORE\n\n");
+    }
+
+    otrl_userstate_free(us);
+    otrl_auth_clear(&alice);
+    otrl_auth_clear(&bob);
+    return 0;
+}
+#endif