summary refs log tree commit diff stats
path: root/libotr/libotr-4.1.1/src/dh.c
diff options
context:
space:
mode:
Diffstat (limited to 'libotr/libotr-4.1.1/src/dh.c')
-rw-r--r--libotr/libotr-4.1.1/src/dh.c476
1 files changed, 476 insertions, 0 deletions
diff --git a/libotr/libotr-4.1.1/src/dh.c b/libotr/libotr-4.1.1/src/dh.c
new file mode 100644
index 0000000..d8bc45d
--- /dev/null
+++ b/libotr/libotr-4.1.1/src/dh.c
@@ -0,0 +1,476 @@
+/*
+ *  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>
+
+/* libgcrypt headers */
+#include <gcrypt.h>
+
+/* libotr headers */
+#include "dh.h"
+
+
+static const char* DH1536_MODULUS_S = "0x"
+    "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+    "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+    "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+    "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+    "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+    "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+    "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+    "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF";
+static const char *DH1536_GENERATOR_S = "0x02";
+static const int DH1536_MOD_LEN_BITS = 1536;
+static const int DH1536_MOD_LEN_BYTES = 192;
+
+static gcry_mpi_t DH1536_MODULUS = NULL;
+static gcry_mpi_t DH1536_MODULUS_MINUS_2 = NULL;
+static gcry_mpi_t DH1536_GENERATOR = NULL;
+
+/*
+ * Call this once, at plugin load time.  It sets up the modulus and
+ * generator MPIs.
+ */
+void otrl_dh_init(void)
+{
+    gcry_mpi_scan(&DH1536_MODULUS, GCRYMPI_FMT_HEX,
+	(const unsigned char *)DH1536_MODULUS_S, 0, NULL);
+    gcry_mpi_scan(&DH1536_GENERATOR, GCRYMPI_FMT_HEX,
+	(const unsigned char *)DH1536_GENERATOR_S, 0, NULL);
+    DH1536_MODULUS_MINUS_2 = gcry_mpi_new(DH1536_MOD_LEN_BITS);
+    gcry_mpi_sub_ui(DH1536_MODULUS_MINUS_2, DH1536_MODULUS, 2);
+}
+
+/*
+ * Initialize the fields of a DH keypair.
+ */
+void otrl_dh_keypair_init(DH_keypair *kp)
+{
+    kp->groupid = 0;
+    kp->priv = NULL;
+    kp->pub = NULL;
+}
+
+/*
+ * Copy a DH_keypair.
+ */
+void otrl_dh_keypair_copy(DH_keypair *dst, const DH_keypair *src)
+{
+    dst->groupid = src->groupid;
+    dst->priv = gcry_mpi_copy(src->priv);
+    dst->pub = gcry_mpi_copy(src->pub);
+}
+
+/*
+ * Deallocate the contents of a DH_keypair (but not the DH_keypair
+ * itself)
+ */
+void otrl_dh_keypair_free(DH_keypair *kp)
+{
+    gcry_mpi_release(kp->priv);
+    gcry_mpi_release(kp->pub);
+    kp->priv = NULL;
+    kp->pub = NULL;
+}
+
+/*
+ * Generate a DH keypair for a specified group.
+ */
+gcry_error_t otrl_dh_gen_keypair(unsigned int groupid, DH_keypair *kp)
+{
+    unsigned char *secbuf = NULL;
+    gcry_mpi_t privkey = NULL;
+
+    if (groupid != DH1536_GROUP_ID) {
+	/* Invalid group id */
+	return gcry_error(GPG_ERR_INV_VALUE);
+    }
+
+    /* Generate the secret key: a random 320-bit value */
+    secbuf = gcry_random_bytes_secure(40, GCRY_STRONG_RANDOM);
+    gcry_mpi_scan(&privkey, GCRYMPI_FMT_USG, secbuf, 40, NULL);
+    gcry_free(secbuf);
+
+    kp->groupid = groupid;
+    kp->priv = privkey;
+    kp->pub = gcry_mpi_new(DH1536_MOD_LEN_BITS);
+    gcry_mpi_powm(kp->pub, DH1536_GENERATOR, privkey, DH1536_MODULUS);
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/*
+ * Construct session keys from a DH keypair and someone else's public
+ * key.
+ */
+gcry_error_t otrl_dh_session(DH_sesskeys *sess, const DH_keypair *kp,
+	gcry_mpi_t y)
+{
+    gcry_mpi_t gab;
+    size_t gablen;
+    unsigned char *gabdata;
+    unsigned char *hashdata;
+    unsigned char sendbyte, rcvbyte;
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+
+    otrl_dh_session_blank(sess);
+
+    if (kp->groupid != DH1536_GROUP_ID) {
+	/* Invalid group id */
+	return gcry_error(GPG_ERR_INV_VALUE);
+    }
+
+    /* Calculate the shared secret MPI */
+    gab = gcry_mpi_snew(DH1536_MOD_LEN_BITS);
+    gcry_mpi_powm(gab, y, kp->priv, DH1536_MODULUS);
+
+    /* Output it in the right format */
+    gcry_mpi_print(GCRYMPI_FMT_USG, NULL, 0, &gablen, gab);
+    gabdata = gcry_malloc_secure(gablen + 5);
+    if (!gabdata) {
+	gcry_mpi_release(gab);
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    gabdata[1] = (gablen >> 24) & 0xff;
+    gabdata[2] = (gablen >> 16) & 0xff;
+    gabdata[3] = (gablen >> 8) & 0xff;
+    gabdata[4] = gablen & 0xff;
+    gcry_mpi_print(GCRYMPI_FMT_USG, gabdata+5, gablen, NULL, gab);
+    gcry_mpi_release(gab);
+
+    hashdata = gcry_malloc_secure(20);
+    if (!hashdata) {
+	gcry_free(gabdata);
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+
+    /* Are we the "high" or "low" end of the connection? */
+    if ( gcry_mpi_cmp(kp->pub, y) > 0 ) {
+	sendbyte = 0x01;
+	rcvbyte = 0x02;
+    } else {
+	sendbyte = 0x02;
+	rcvbyte = 0x01;
+    }
+
+    /* Calculate the sending encryption key */
+    gabdata[0] = sendbyte;
+    gcry_md_hash_buffer(GCRY_MD_SHA1, hashdata, gabdata, gablen+5);
+    err = gcry_cipher_open(&(sess->sendenc), GCRY_CIPHER_AES,
+	    GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE);
+    if (err) goto err;
+    err = gcry_cipher_setkey(sess->sendenc, hashdata, 16);
+    if (err) goto err;
+
+    /* Calculate the sending MAC key */
+    gcry_md_hash_buffer(GCRY_MD_SHA1, sess->sendmackey, hashdata, 16);
+    err = gcry_md_open(&(sess->sendmac), GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC);
+    if (err) goto err;
+    err = gcry_md_setkey(sess->sendmac, sess->sendmackey, 20);
+    if (err) goto err;
+
+    /* Calculate the receiving encryption key */
+    gabdata[0] = rcvbyte;
+    gcry_md_hash_buffer(GCRY_MD_SHA1, hashdata, gabdata, gablen+5);
+    err = gcry_cipher_open(&(sess->rcvenc), GCRY_CIPHER_AES,
+	    GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE);
+    if (err) goto err;
+    err = gcry_cipher_setkey(sess->rcvenc, hashdata, 16);
+    if (err) goto err;
+
+    /* Calculate the receiving MAC key (and save it in the DH_sesskeys
+     * struct, so we can reveal it later) */
+    gcry_md_hash_buffer(GCRY_MD_SHA1, sess->rcvmackey, hashdata, 16);
+    err = gcry_md_open(&(sess->rcvmac), GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC);
+    if (err) goto err;
+    err = gcry_md_setkey(sess->rcvmac, sess->rcvmackey, 20);
+    if (err) goto err;
+
+    /* Calculate the extra key (used if applications wish to extract a
+     * symmetric key for transferring files, or something like that) */
+    gabdata[0] = 0xff;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, sess->extrakey, gabdata, gablen+5);
+
+    gcry_free(gabdata);
+    gcry_free(hashdata);
+    return gcry_error(GPG_ERR_NO_ERROR);
+err:
+    otrl_dh_session_free(sess);
+    gcry_free(gabdata);
+    gcry_free(hashdata);
+    return err;
+}
+
+/*
+ * Compute the secure session id, two encryption keys, and four MAC keys
+ * given our DH key and their DH public key.
+ */
+gcry_error_t otrl_dh_compute_v2_auth_keys(const DH_keypair *our_dh,
+	gcry_mpi_t their_pub, unsigned char *sessionid, size_t *sessionidlenp,
+	gcry_cipher_hd_t *enc_c, gcry_cipher_hd_t *enc_cp,
+	gcry_md_hd_t *mac_m1, gcry_md_hd_t *mac_m1p,
+	gcry_md_hd_t *mac_m2, gcry_md_hd_t *mac_m2p)
+{
+    gcry_mpi_t s;
+    size_t slen;
+    unsigned char *sdata;
+    unsigned char *hashdata;
+    unsigned char ctr[16];
+    gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+
+    *enc_c = NULL;
+    *enc_cp = NULL;
+    *mac_m1 = NULL;
+    *mac_m1p = NULL;
+    *mac_m2 = NULL;
+    *mac_m2p = NULL;
+    memset(ctr, 0, 16);
+
+    if (our_dh->groupid != DH1536_GROUP_ID) {
+	/* Invalid group id */
+	return gcry_error(GPG_ERR_INV_VALUE);
+    }
+
+    /* Check that their_pub is in range */
+    if (gcry_mpi_cmp_ui(their_pub, 2) < 0 ||
+	    gcry_mpi_cmp(their_pub, DH1536_MODULUS_MINUS_2) > 0) {
+	/* Invalid pubkey */
+	return gcry_error(GPG_ERR_INV_VALUE);
+    }
+
+    /* Calculate the shared secret MPI */
+    s = gcry_mpi_snew(DH1536_MOD_LEN_BITS);
+    gcry_mpi_powm(s, their_pub, our_dh->priv, DH1536_MODULUS);
+
+    /* Output it in the right format */
+    gcry_mpi_print(GCRYMPI_FMT_USG, NULL, 0, &slen, s);
+    sdata = gcry_malloc_secure(slen + 5);
+    if (!sdata) {
+	gcry_mpi_release(s);
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    sdata[1] = (slen >> 24) & 0xff;
+    sdata[2] = (slen >> 16) & 0xff;
+    sdata[3] = (slen >> 8) & 0xff;
+    sdata[4] = slen & 0xff;
+    gcry_mpi_print(GCRYMPI_FMT_USG, sdata+5, slen, NULL, s);
+    gcry_mpi_release(s);
+
+    /* Calculate the session id */
+    hashdata = gcry_malloc_secure(32);
+    if (!hashdata) {
+	gcry_free(sdata);
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    sdata[0] = 0x00;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, hashdata, sdata, slen+5);
+    memmove(sessionid, hashdata, 8);
+    *sessionidlenp = 8;
+
+    /* Calculate the encryption keys */
+    sdata[0] = 0x01;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, hashdata, sdata, slen+5);
+
+    err = gcry_cipher_open(enc_c, GCRY_CIPHER_AES,
+	    GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE);
+    if (err) goto err;
+    err = gcry_cipher_setkey(*enc_c, hashdata, 16);
+    if (err) goto err;
+    err = gcry_cipher_setctr(*enc_c, ctr, 16);
+    if (err) goto err;
+
+    err = gcry_cipher_open(enc_cp, GCRY_CIPHER_AES,
+	    GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE);
+    if (err) goto err;
+    err = gcry_cipher_setkey(*enc_cp, hashdata+16, 16);
+    if (err) goto err;
+    err = gcry_cipher_setctr(*enc_cp, ctr, 16);
+    if (err) goto err;
+
+    /* Calculate the MAC keys */
+    sdata[0] = 0x02;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, hashdata, sdata, slen+5);
+    err = gcry_md_open(mac_m1, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+    if (err) goto err;
+    err = gcry_md_setkey(*mac_m1, hashdata, 32);
+    if (err) goto err;
+
+    sdata[0] = 0x03;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, hashdata, sdata, slen+5);
+    err = gcry_md_open(mac_m2, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+    if (err) goto err;
+    err = gcry_md_setkey(*mac_m2, hashdata, 32);
+    if (err) goto err;
+
+    sdata[0] = 0x04;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, hashdata, sdata, slen+5);
+    err = gcry_md_open(mac_m1p, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+    if (err) goto err;
+    err = gcry_md_setkey(*mac_m1p, hashdata, 32);
+    if (err) goto err;
+
+    sdata[0] = 0x05;
+    gcry_md_hash_buffer(GCRY_MD_SHA256, hashdata, sdata, slen+5);
+    err = gcry_md_open(mac_m2p, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
+    if (err) goto err;
+    err = gcry_md_setkey(*mac_m2p, hashdata, 32);
+    if (err) goto err;
+
+    gcry_free(sdata);
+    gcry_free(hashdata);
+    return gcry_error(GPG_ERR_NO_ERROR);
+
+err:
+    gcry_cipher_close(*enc_c);
+    gcry_cipher_close(*enc_cp);
+    gcry_md_close(*mac_m1);
+    gcry_md_close(*mac_m1p);
+    gcry_md_close(*mac_m2);
+    gcry_md_close(*mac_m2p);
+    *enc_c = NULL;
+    *enc_cp = NULL;
+    *mac_m1 = NULL;
+    *mac_m1p = NULL;
+    *mac_m2 = NULL;
+    *mac_m2p = NULL;
+    gcry_free(sdata);
+    gcry_free(hashdata);
+    return err;
+}
+
+/*
+ * Compute the secure session id, given our DH key and their DH public
+ * key.
+ */
+gcry_error_t otrl_dh_compute_v1_session_id(const DH_keypair *our_dh,
+	gcry_mpi_t their_pub, unsigned char *sessionid, size_t *sessionidlenp,
+	OtrlSessionIdHalf *halfp)
+{
+    gcry_mpi_t s;
+    size_t slen;
+    unsigned char *sdata;
+    unsigned char *hashdata;
+
+    if (our_dh->groupid != DH1536_GROUP_ID) {
+	/* Invalid group id */
+	return gcry_error(GPG_ERR_INV_VALUE);
+    }
+
+    /* Check that their_pub is in range */
+    if (gcry_mpi_cmp_ui(their_pub, 2) < 0 ||
+	    gcry_mpi_cmp(their_pub, DH1536_MODULUS_MINUS_2) > 0) {
+	/* Invalid pubkey */
+	return gcry_error(GPG_ERR_INV_VALUE);
+    }
+
+    /* Calculate the shared secret MPI */
+    s = gcry_mpi_snew(DH1536_MOD_LEN_BITS);
+    gcry_mpi_powm(s, their_pub, our_dh->priv, DH1536_MODULUS);
+
+    /* Output it in the right format */
+    gcry_mpi_print(GCRYMPI_FMT_USG, NULL, 0, &slen, s);
+    sdata = gcry_malloc_secure(slen + 5);
+    if (!sdata) {
+	gcry_mpi_release(s);
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    sdata[1] = (slen >> 24) & 0xff;
+    sdata[2] = (slen >> 16) & 0xff;
+    sdata[3] = (slen >> 8) & 0xff;
+    sdata[4] = slen & 0xff;
+    gcry_mpi_print(GCRYMPI_FMT_USG, sdata+5, slen, NULL, s);
+    gcry_mpi_release(s);
+
+    /* Calculate the session id */
+    hashdata = gcry_malloc_secure(20);
+    if (!hashdata) {
+	gcry_free(sdata);
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    sdata[0] = 0x00;
+    gcry_md_hash_buffer(GCRY_MD_SHA1, hashdata, sdata, slen+5);
+    memmove(sessionid, hashdata, 20);
+    *sessionidlenp = 20;
+
+    /* Which half should be bold? */
+    if (gcry_mpi_cmp(our_dh->pub, their_pub) > 0) {
+	*halfp = OTRL_SESSIONID_SECOND_HALF_BOLD;
+    } else {
+	*halfp = OTRL_SESSIONID_FIRST_HALF_BOLD;
+    }
+
+    gcry_free(hashdata);
+    gcry_free(sdata);
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/*
+ * Deallocate the contents of a DH_sesskeys (but not the DH_sesskeys
+ * itself)
+ */
+void otrl_dh_session_free(DH_sesskeys *sess)
+{
+    gcry_cipher_close(sess->sendenc);
+    gcry_cipher_close(sess->rcvenc);
+    gcry_md_close(sess->sendmac);
+    gcry_md_close(sess->rcvmac);
+
+    otrl_dh_session_blank(sess);
+}
+
+/*
+ * Blank out the contents of a DH_sesskeys (without releasing it)
+ */
+void otrl_dh_session_blank(DH_sesskeys *sess)
+{
+    sess->sendenc = NULL;
+    sess->sendmac = NULL;
+    sess->rcvenc = NULL;
+    sess->rcvmac = NULL;
+    memset(sess->sendctr, 0, 16);
+    memset(sess->rcvctr, 0, 16);
+    memset(sess->sendmackey, 0, 20);
+    memset(sess->rcvmackey, 0, 20);
+    sess->sendmacused = 0;
+    sess->rcvmacused = 0;
+    memset(sess->extrakey, 0, OTRL_EXTRAKEY_BYTES);
+}
+
+/* Increment the top half of a counter block */
+void otrl_dh_incctr(unsigned char *ctr)
+{
+    int i;
+    for (i=8;i;--i) {
+	if (++ctr[i-1]) break;
+    }
+}
+
+/* Compare two counter values (8 bytes each).  Return 0 if ctr1 == ctr2,
+ * < 0 if ctr1 < ctr2 (as unsigned 64-bit values), > 0 if ctr1 > ctr2. */
+int otrl_dh_cmpctr(const unsigned char *ctr1, const unsigned char *ctr2)
+{
+    int i;
+    for (i=0;i<8;++i) {
+	int c = ctr1[i] - ctr2[i];
+	if (c) return c;
+    }
+    return 0;
+}