/*
* Off-the-Record Messaging Toolkit
* Copyright (C) 2004-2012 Ian Goldberg, Rob Smits, Chris Alexander,
* Nikita Borisov
* <otr@cypherpunks.ca>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/* system headers */
#include <stdio.h>
#include <stdlib.h>
/* libotr headers */
#include "b64.h"
/* toolkit headers */
#include "sha1hmac.h"
#include "parse.h"
/* Dump an unsigned int to a FILE * */
void dump_int(FILE *stream, const char *title, unsigned int val)
{
fprintf(stream, "%s: %u\n", title, val);
}
/* Dump an mpi to a FILE * */
void dump_mpi(FILE *stream, const char *title, gcry_mpi_t val)
{
size_t plen;
unsigned char *d;
gcry_mpi_print(GCRYMPI_FMT_USG, NULL, 0, &plen, val);
d = malloc(plen);
gcry_mpi_print(GCRYMPI_FMT_USG, d, plen, NULL, val);
dump_data(stream, title, d, plen);
free(d);
}
/* Dump data to a FILE * */
void dump_data(FILE *stream, const char *title, const unsigned char *data,
size_t datalen)
{
size_t i;
fprintf(stream, "%s: ", title);
for(i=0;i<datalen;++i) {
fprintf(stream, "%02x", data[i]);
}
fprintf(stream, "\n");
}
/* base64 decode the message, and put the resulting size into *lenp */
static unsigned char *decode(const char *msg, size_t *lenp)
{
const char *header, *footer;
unsigned char *raw;
size_t rawlen;
/* Find the header */
header = strstr(msg, "?OTR:");
if (!header) return NULL;
/* Skip the header */
header += 5;
/* Find the trailing '.' */
footer = strchr(header, '.');
if (!footer) footer = header + strlen(header);
rawlen = OTRL_B64_MAX_DECODED_SIZE(footer-header);
raw = malloc(rawlen);
if (raw == NULL && rawlen > 0) return NULL;
*lenp = otrl_base64_decode(raw, header, footer-header);
return raw;
}
#define require_len(l) do { if (lenp < (l)) goto inv; } while(0)
#define read_int(x) do { \
require_len(4); \
(x) = (((unsigned int)bufp[0]) << 24) | (bufp[1] << 16) | (bufp[2] << 8 ) | bufp[3]; \
bufp += 4; lenp -= 4; \
} while(0)
#define read_mpi(x) do { \
size_t mpilen; \
read_int(mpilen); \
require_len(mpilen); \
gcry_mpi_scan(&(x), GCRYMPI_FMT_USG, bufp, mpilen, NULL); \
bufp += mpilen; lenp -= mpilen; \
} while(0)
#define read_raw(b, l) do { \
if (l) { \
require_len(l); \
memmove((b), bufp, (l)); \
bufp += (l); lenp -= (l); \
} \
} while(0)
#define write_int(x) do { \
bufp[0] = ((x) >> 24) & 0xff; \
bufp[1] = ((x) >> 16) & 0xff; \
bufp[2] = ((x) >> 8) & 0xff; \
bufp[3] = (x) & 0xff; \
bufp += 4; lenp -= 4; \
} while(0)
#define write_mpi(x,l) do { \
write_int(l); \
gcry_mpi_print(GCRYMPI_FMT_USG, bufp, lenp, NULL, (x)); \
bufp += (l); lenp -= (l); \
} while(0)
#define write_raw(x,l) do { \
memmove(bufp, (x), (l)); \
bufp += (l); lenp -= (l); \
} while(0)
/* Parse a Key Exchange Message into a newly-allocated KeyExchMsg structure */
KeyExchMsg parse_keyexch(const char *msg)
{
KeyExchMsg kem = NULL;
size_t lenp;
unsigned char *raw = decode(msg, &lenp);
unsigned char *bufp = raw;
if (!raw) goto inv;
kem = calloc(1, sizeof(struct s_KeyExchMsg));
if (!kem) {
free(raw);
goto inv;
}
kem->raw = raw;
kem->sigstart = bufp;
require_len(3);
if (memcmp(bufp, "\x00\x01\x0a", 3)) goto inv;
bufp += 3; lenp -= 3;
require_len(1);
kem->reply = *bufp;
bufp += 1; lenp -= 1;
read_mpi(kem->p);
read_mpi(kem->q);
read_mpi(kem->g);
read_mpi(kem->e);
read_int(kem->keyid);
read_mpi(kem->y);
kem->sigend = bufp;
require_len(40);
gcry_mpi_scan(&kem->r, GCRYMPI_FMT_USG, bufp, 20, NULL);
gcry_mpi_scan(&kem->s, GCRYMPI_FMT_USG, bufp+20, 20, NULL);
bufp += 40; lenp -= 40;
if (lenp != 0) goto inv;
return kem;
inv:
free_keyexch(kem);
return NULL;
}
/* Deallocate a KeyExchMsg and all of the data it points to */
void free_keyexch(KeyExchMsg keyexch)
{
if (!keyexch) return;
free(keyexch->raw);
gcry_mpi_release(keyexch->p);
gcry_mpi_release(keyexch->q);
gcry_mpi_release(keyexch->g);
gcry_mpi_release(keyexch->e);
gcry_mpi_release(keyexch->y);
gcry_mpi_release(keyexch->r);
gcry_mpi_release(keyexch->s);
free(keyexch);
}
/* Parse a D-H Commit Message into a newly-allocated CommitMsg structure */
CommitMsg parse_commit(const char *msg)
{
CommitMsg cmsg = NULL;
size_t lenp;
unsigned char *raw = decode(msg, &lenp);
unsigned char *bufp = raw;
if (!raw) goto inv;
cmsg = calloc(1, sizeof(struct s_CommitMsg));
if (!cmsg) {
free(raw);
goto inv;
}
cmsg->raw = raw;
require_len(3);
cmsg->version = bufp[1];
if (!memcmp(bufp, "\x00\x03\x02", 3)) {
bufp += 3; lenp -= 3;
read_int(cmsg->sender_instance);
read_int(cmsg->receiver_instance);
} else if (!memcmp(bufp, "\x00\x02\x02", 3)) {
bufp += 3; lenp -= 3;
cmsg->sender_instance = 0;
cmsg->receiver_instance = 0;
} else goto inv;
read_int(cmsg->enckeylen);
cmsg->enckey = malloc(cmsg->enckeylen);
if (!cmsg->enckey && cmsg->enckeylen > 0) goto inv;
read_raw(cmsg->enckey, cmsg->enckeylen);
read_int(cmsg->hashkeylen);
cmsg->hashkey = malloc(cmsg->hashkeylen);
if (!cmsg->hashkey && cmsg->hashkeylen > 0) goto inv;
read_raw(cmsg->hashkey, cmsg->hashkeylen);
if (lenp != 0) goto inv;
return cmsg;
inv:
free_commit(cmsg);
return NULL;
}
/* Deallocate a CommitMsg and all of the data it points to */
void free_commit(CommitMsg cmsg)
{
if (!cmsg) return;
free(cmsg->raw);
free(cmsg->enckey);
free(cmsg->hashkey);
free(cmsg);
}
/* Parse a D-H Key Message into a newly-allocated KeyMsg structure */
KeyMsg parse_key(const char *msg)
{
KeyMsg kmsg = NULL;
size_t lenp;
unsigned char *raw = decode(msg, &lenp);
unsigned char *bufp = raw;
if (!raw) goto inv;
kmsg = calloc(1, sizeof(struct s_KeyMsg));
if (!kmsg) {
free(raw);
goto inv;
}
kmsg->raw = raw;
require_len(3);
kmsg->version = bufp[1];
if (!memcmp(bufp, "\x00\x03\x0a", 3)) {
bufp += 3; lenp -= 3;
read_int(kmsg->sender_instance);
read_int(kmsg->receiver_instance);
} else if (!memcmp(bufp, "\x00\x02\x0a", 3)) {
bufp += 3; lenp -= 3;
kmsg->sender_instance = 0;
kmsg->receiver_instance = 0;
} else goto inv;
read_mpi(kmsg->y);
if (lenp != 0) goto inv;
return kmsg;
inv:
free_key(kmsg);
return NULL;
}
/* Deallocate a KeyMsg and all of the data it points to */
void free_key(KeyMsg kmsg)
{
if (!kmsg) return;
free(kmsg->raw);
gcry_mpi_release(kmsg->y);
free(kmsg);
}
/* Parse a Reveal Signature Message into a newly-allocated RevealSigMsg
* structure */
RevealSigMsg parse_revealsig(const char *msg)
{
RevealSigMsg rmsg = NULL;
size_t lenp;
unsigned char *raw = decode(msg, &lenp);
unsigned char *bufp = raw;
if (!raw) goto inv;
rmsg = calloc(1, sizeof(struct s_RevealSigMsg));
if (!rmsg) {
free(raw);
goto inv;
}
rmsg->raw = raw;
require_len(3);
rmsg->version = bufp[1];
if (!memcmp(bufp, "\x00\x03\x11", 3)) {
bufp += 3; lenp -= 3;
read_int(rmsg->sender_instance);
read_int(rmsg->receiver_instance);
} else if (!memcmp(bufp, "\x00\x02\x11", 3)) {
bufp += 3; lenp -= 3;
rmsg->sender_instance = 0;
rmsg->receiver_instance = 0;
} else goto inv;
read_int(rmsg->keylen);
rmsg->key = malloc(rmsg->keylen);
if (!rmsg->key && rmsg->keylen > 0) goto inv;
read_raw(rmsg->key, rmsg->keylen);
read_int(rmsg->encsiglen);
rmsg->encsig = malloc(rmsg->encsiglen);
if (!rmsg->encsig && rmsg->encsiglen > 0) goto inv;
read_raw(rmsg->encsig, rmsg->encsiglen);
read_raw(rmsg->mac, 20);
if (lenp != 0) goto inv;
return rmsg;
inv:
free_revealsig(rmsg);
return NULL;
}
/* Deallocate a RevealSigMsg and all of the data it points to */
void free_revealsig(RevealSigMsg rmsg)
{
if (!rmsg) return;
free(rmsg->raw);
free(rmsg->key);
free(rmsg->encsig);
free(rmsg);
}
/* Parse a Signature Message into a newly-allocated SignatureMsg structure */
SignatureMsg parse_signature(const char *msg)
{
SignatureMsg smsg = NULL;
size_t lenp;
unsigned char *raw = decode(msg, &lenp);
unsigned char *bufp = raw;
if (!raw) goto inv;
smsg = calloc(1, sizeof(struct s_SignatureMsg));
if (!smsg) {
free(raw);
goto inv;
}
smsg->raw = raw;
require_len(3);
smsg->version = bufp[1];
if (!memcmp(bufp, "\x00\x03\x12", 3)) {
bufp += 3; lenp -= 3;
read_int(smsg->sender_instance);
read_int(smsg->receiver_instance);
} else if (!memcmp(bufp, "\x00\x02\x12", 3)) {
bufp += 3; lenp -= 3;
smsg->sender_instance = 0;
smsg->receiver_instance = 0;
} else goto inv;
read_int(smsg->encsiglen);
smsg->encsig = malloc(smsg->encsiglen);
if (!smsg->encsig && smsg->encsiglen > 0) goto inv;
read_raw(smsg->encsig, smsg->encsiglen);
read_raw(smsg->mac, 20);
if (lenp != 0) goto inv;
return smsg;
inv:
free_signature(smsg);
return NULL;
}
/* Deallocate a SignatureMsg and all of the data it points to */
void free_signature(SignatureMsg smsg)
{
if (!smsg) return;
free(smsg->raw);
free(smsg->encsig);
free(smsg);
}
/* Parse a Data Message into a newly-allocated DataMsg structure */
DataMsg parse_datamsg(const char *msg)
{
DataMsg datam = NULL;
size_t lenp;
unsigned char *raw = decode(msg, &lenp);
unsigned char *bufp = raw;
unsigned char version;
if (!raw) goto inv;
datam = calloc(1, sizeof(struct s_DataMsg));
if (!datam) {
free(raw);
goto inv;
}
datam->raw = raw;
datam->rawlen = lenp;
datam->macstart = bufp;
require_len(3);
if (memcmp(bufp, "\x00\x01\x03", 3) && memcmp(bufp, "\x00\x03\x03", 3) &&
memcmp(bufp, "\x00\x02\x03", 3)) goto inv;
version = bufp[1];
datam->sender_instance = 0;
datam->receiver_instance = 0;
datam->version = version;
datam->flags = -1;
bufp += 3; lenp -= 3;
if (version == 3) {
read_int(datam->sender_instance);
read_int(datam->receiver_instance);
}
if (version == 2 || version == 3) {
require_len(1);
datam->flags = bufp[0];
bufp += 1; lenp -= 1;
}
read_int(datam->sender_keyid);
read_int(datam->rcpt_keyid);
read_mpi(datam->y);
read_raw(datam->ctr, 8);
read_int(datam->encmsglen);
datam->encmsg = malloc(datam->encmsglen);
if (!datam->encmsg && datam->encmsglen > 0) goto inv;
read_raw(datam->encmsg, datam->encmsglen);
datam->macend = bufp;
read_raw(datam->mac, 20);
read_int(datam->mackeyslen);
datam->mackeys = malloc(datam->mackeyslen);
if (!datam->mackeys && datam->mackeyslen > 0) goto inv;
read_raw(datam->mackeys, datam->mackeyslen);
if (lenp != 0) goto inv;
return datam;
inv:
free_datamsg(datam);
return NULL;
}
/* Recalculate the MAC on the message, base64-encode the resulting MAC'd
* message, and put on the appropriate header and footer. Return a
* newly-allocated pointer to the result, which the caller will have to
* free(). */
char *remac_datamsg(DataMsg datamsg, unsigned char mackey[20])
{
size_t rawlen, lenp;
size_t ylen;
size_t base64len;
char *outmsg;
unsigned char *raw, *bufp;
unsigned char version = datamsg->version;
/* Calculate the size of the message that will result */
gcry_mpi_print(GCRYMPI_FMT_USG, NULL, 0, &ylen, datamsg->y);
rawlen = 3 + (version == 3 ? 8 : 0) + (version == 2 ||
version == 3 ? 1 : 0) + 4 + 4 + 4 + ylen + 8 + 4 +
datamsg->encmsglen + 20 + 4 + datamsg->mackeyslen;
/* Construct the new raw message (note that some of the pieces may
* have been altered, so we construct it from scratch). */
raw = malloc(rawlen);
if (!raw) {
fprintf(stderr, "Out of memory!\n");
exit(1);
}
bufp = raw;
lenp = rawlen;
datamsg->macstart = raw;
datamsg->macend = NULL;
free(datamsg->raw);
datamsg->raw = raw;
datamsg->rawlen = rawlen;
memmove(bufp, "\x00", 1);
memmove(bufp+1, &version, 1);
memmove(bufp+2, "\x03", 1);
bufp += 3; lenp -= 3;
if (version == 3) {
write_int(datamsg->sender_instance);
write_int(datamsg->receiver_instance);
}
if (version == 2 || version == 3) {
bufp[0] = datamsg->flags;
bufp += 1; lenp -= 1;
}
write_int(datamsg->sender_keyid);
write_int(datamsg->rcpt_keyid);
write_mpi(datamsg->y, ylen);
write_raw(datamsg->ctr, 8);
write_int(datamsg->encmsglen);
write_raw(datamsg->encmsg, datamsg->encmsglen);
datamsg->macend = bufp;
/* Recalculate the MAC */
sha1hmac(datamsg->mac, mackey, datamsg->macstart,
datamsg->macend - datamsg->macstart);
write_raw(datamsg->mac, 20);
write_int(datamsg->mackeyslen);
write_raw(datamsg->mackeys, datamsg->mackeyslen);
if (lenp != 0) {
fprintf(stderr, "Error creating OTR Data Message.\n");
exit(1);
}
base64len = 5 + ((datamsg->rawlen + 2) / 3) * 4 + 1 + 1;
outmsg = malloc(base64len);
if (!outmsg) return NULL;
memmove(outmsg, "?OTR:", 5);
otrl_base64_encode(outmsg + 5, datamsg->raw, datamsg->rawlen);
strcpy(outmsg + base64len - 2, ".");
return outmsg;
}
/* Assemble a new Data Message from its pieces. Return a
* newly-allocated string containing the base64 representation. */
char *assemble_datamsg(unsigned char mackey[20],
unsigned char version, unsigned int sender_instance,
unsigned int receiver_instance, int flags, unsigned int sender_keyid,
unsigned int rcpt_keyid, gcry_mpi_t y,
unsigned char ctr[8], unsigned char *encmsg, size_t encmsglen,
unsigned char *mackeys, size_t mackeyslen)
{
DataMsg datam = calloc(1, sizeof(struct s_DataMsg));
char *newmsg = NULL;
if (!datam) goto inv;
datam->version = version;
datam->flags = flags;
datam->sender_instance = sender_instance;
datam->receiver_instance = receiver_instance;
datam->sender_keyid = sender_keyid;
datam->rcpt_keyid = rcpt_keyid;
datam->y = gcry_mpi_copy(y);
memmove(datam->ctr, ctr, 8);
datam->encmsg = malloc(encmsglen);
if (!datam->encmsg && encmsglen > 0) goto inv;
memmove(datam->encmsg, encmsg, encmsglen);
datam->encmsglen = encmsglen;
datam->mackeys = malloc(mackeyslen);
if (!datam->mackeys && mackeyslen > 0) goto inv;
memmove(datam->mackeys, mackeys, mackeyslen);
datam->mackeyslen = mackeyslen;
/* Recalculate the MAC and base64-encode the result */
newmsg = remac_datamsg(datam, mackey);
free_datamsg(datam);
return newmsg;
inv:
free_datamsg(datam);
return NULL;
}
/* Deallocate a DataMsg and all of the data it points to */
void free_datamsg(DataMsg datamsg)
{
if (!datamsg) return;
free(datamsg->raw);
gcry_mpi_release(datamsg->y);
free(datamsg->encmsg);
free(datamsg->mackeys);
free(datamsg);
}
static int ctoh(char c)
{
if (c >= '0' && c <= '9') return (c-'0');
if (c >= 'a' && c <= 'f') return (c-'a'+10);
if (c >= 'A' && c <= 'F') return (c-'A'+10);
return -1;
}
/* Convert a string of hex chars to a buffer of unsigned chars. */
void argv_to_buf(unsigned char **bufp, size_t *lenp, char *arg)
{
unsigned char *buf;
size_t len, i;
*bufp = NULL;
*lenp = 0;
len = strlen(arg);
if (len % 2) {
fprintf(stderr, "Argument ``%s'' must have even length.\n", arg);
return;
}
buf = malloc(len/2);
if (buf == NULL && len > 0) {
fprintf(stderr, "Out of memory!\n");
return;
}
for(i=0;i<len/2;++i) {
int hi = ctoh(arg[2*i]);
int lo = ctoh(arg[2*i+1]);
if (hi < 0 || lo < 0) {
free(buf);
fprintf(stderr, "Illegal hex char in argument ``%s''.\n", arg);
return;
}
buf[i] = (hi << 4) + lo;
}
*bufp = buf;
*lenp = len/2;
}