/* Find the instance tags in this message */ gcry_error_t otrl_proto_instance(const char *otrmsg, unsigned int *instance_from, unsigned int *instance_to) { gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR); const char *otrtag = otrmsg; unsigned char *bufp = NULL; unsigned char *bufp_head = NULL; size_t lenp; if (!otrtag || strncmp(otrtag, "?OTR:AAM", 8)) { goto invval; } if (strlen(otrtag) < 21 ) goto invval; /* Decode and extract instance tag */ bufp = malloc(OTRL_B64_MAX_DECODED_SIZE(12)); bufp_head = bufp; lenp = otrl_base64_decode(bufp, otrtag+9, 12); read_int(*instance_from); read_int(*instance_to); free(bufp_head); return gcry_error(GPG_ERR_NO_ERROR); invval: free(bufp_head); err = gcry_error(GPG_ERR_INV_VALUE); return err; }
/* Extract the flags from an otherwise unreadable Data Message. */ gcry_error_t otrl_proto_data_read_flags(const char *datamsg, unsigned char *flagsp) { char *otrtag, *endtag; unsigned char *rawmsg = NULL; unsigned char *bufp; size_t msglen, rawlen, lenp; unsigned char version; if (flagsp) *flagsp = 0; otrtag = strstr(datamsg, "?OTR:"); if (!otrtag) { goto invval; } endtag = strchr(otrtag, '.'); if (endtag) { msglen = endtag-otrtag; } else { msglen = strlen(otrtag); } /* Skip over the "?OTR:" */ otrtag += 5; msglen -= 5; /* Base64-decode the message */ rawlen = OTRL_B64_MAX_DECODED_SIZE(msglen); /* maximum possible */ rawmsg = malloc(rawlen); if (!rawmsg && rawlen > 0) { return gcry_error(GPG_ERR_ENOMEM); } rawlen = otrl_base64_decode(rawmsg, otrtag, msglen); /* actual size */ bufp = rawmsg; lenp = rawlen; require_len(3); version = bufp[1]; skip_header('\x03'); if (version == 3) { require_len(8); bufp += 8; lenp -= 8; } if (version == 2 || version == 3) { require_len(1); if (flagsp) *flagsp = bufp[0]; bufp += 1; lenp -= 1; } free(rawmsg); return gcry_error(GPG_ERR_NO_ERROR); invval: free(rawmsg); return gcry_error(GPG_ERR_INV_VALUE); }
/* 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; }
/* Accept an OTR Data Message in datamsg. Decrypt it and put the * plaintext into *plaintextp, and any TLVs into tlvsp. Put any * received flags into *flagsp (if non-NULL). Put the current extra * symmetric key into extrakey (if non-NULL). */ gcry_error_t otrl_proto_accept_data(char **plaintextp, OtrlTLV **tlvsp, ConnContext *context, const char *datamsg, unsigned char *flagsp, unsigned char *extrakey) { char *otrtag, *endtag; gcry_error_t err; unsigned char *rawmsg = NULL; size_t msglen, rawlen, lenp; unsigned char *macstart, *macend; unsigned char *bufp; unsigned int sender_keyid, recipient_keyid; gcry_mpi_t sender_next_y = NULL; unsigned char ctr[8]; size_t datalen, reveallen; unsigned char *data = NULL; unsigned char *nul = NULL; unsigned char givenmac[20]; DH_sesskeys *sess; unsigned char version; *plaintextp = NULL; *tlvsp = NULL; if (flagsp) *flagsp = 0; otrtag = strstr(datamsg, "?OTR:"); if (!otrtag) { goto invval; } endtag = strchr(otrtag, '.'); if (endtag) { msglen = endtag-otrtag; } else { msglen = strlen(otrtag); } /* Skip over the "?OTR:" */ otrtag += 5; msglen -= 5; /* Base64-decode the message */ rawlen = OTRL_B64_MAX_DECODED_SIZE(msglen); /* maximum possible */ rawmsg = malloc(rawlen); if (!rawmsg && rawlen > 0) { err = gcry_error(GPG_ERR_ENOMEM); goto err; } rawlen = otrl_base64_decode(rawmsg, otrtag, msglen); /* actual size */ bufp = rawmsg; lenp = rawlen; macstart = bufp; require_len(3); version = bufp[1]; skip_header('\x03'); if (version == 3) { require_len(8); bufp += 8; lenp -= 8; } if (version == 2 || version == 3) { require_len(1); if (flagsp) *flagsp = bufp[0]; bufp += 1; lenp -= 1; } read_int(sender_keyid); read_int(recipient_keyid); read_mpi(sender_next_y); require_len(8); memmove(ctr, bufp, 8); bufp += 8; lenp -= 8; read_int(datalen); require_len(datalen); data = malloc(datalen+1); if (!data) { err = gcry_error(GPG_ERR_ENOMEM); goto err; } memmove(data, bufp, datalen); data[datalen] = '\0'; bufp += datalen; lenp -= datalen; macend = bufp; require_len(20); memmove(givenmac, bufp, 20); bufp += 20; lenp -= 20; read_int(reveallen); require_len(reveallen); /* Just skip over the revealed MAC keys, which we don't need. They * were published for deniability of transcripts. */ bufp += reveallen; lenp -= reveallen; /* That should be everything */ if (lenp != 0) goto invval; /* We don't take any action on this message (especially rotating * keys) until we've verified the MAC on this message. To that end, * we need to know which keys this message is claiming to use. */ if (context->context_priv->their_keyid == 0 || (sender_keyid != context->context_priv->their_keyid && sender_keyid != context->context_priv->their_keyid - 1) || (recipient_keyid != context->context_priv->our_keyid && recipient_keyid != context->context_priv->our_keyid - 1) || sender_keyid == 0 || recipient_keyid == 0) { goto conflict; } if (sender_keyid == context->context_priv->their_keyid - 1 && context->context_priv->their_old_y == NULL) { goto conflict; } /* These are the session keys this message is claiming to use. */ sess = &(context->context_priv->sesskeys [context->context_priv->our_keyid - recipient_keyid] [context->context_priv->their_keyid - sender_keyid]); gcry_md_reset(sess->rcvmac); gcry_md_write(sess->rcvmac, macstart, macend-macstart); if (otrl_mem_differ(givenmac, gcry_md_read(sess->rcvmac, GCRY_MD_SHA1), 20)) { /* The MACs didn't match! */ goto conflict; } sess->rcvmacused = 1; /* Check to see that the counter is increasing; i.e. that this isn't * a replay. */ if (otrl_dh_cmpctr(ctr, sess->rcvctr) <= 0) { goto conflict; } /* Decrypt the message */ memmove(sess->rcvctr, ctr, 8); err = gcry_cipher_reset(sess->rcvenc); if (err) goto err; err = gcry_cipher_setctr(sess->rcvenc, sess->rcvctr, 16); if (err) goto err; err = gcry_cipher_decrypt(sess->rcvenc, data, datalen, NULL, 0); if (err) goto err; /* Save a copy of the current extra key */ if (extrakey) { memmove(extrakey, sess->extrakey, OTRL_EXTRAKEY_BYTES); } /* See if either set of keys needs rotating */ if (recipient_keyid == context->context_priv->our_keyid) { /* They're using our most recent key, so generate a new one */ err = rotate_dh_keys(context); if (err) goto err; } if (sender_keyid == context->context_priv->their_keyid) { /* They've sent us a new public key */ err = rotate_y_keys(context, sender_next_y); if (err) goto err; } gcry_mpi_release(sender_next_y); *plaintextp = (char *)data; /* See if there are TLVs */ nul = data; while (nul < data+datalen && *nul) ++nul; /* If we stopped before the end, skip the NUL we stopped at */ if (nul < data+datalen) ++nul; *tlvsp = otrl_tlv_parse(nul, (data+datalen)-nul); free(rawmsg); return gcry_error(GPG_ERR_NO_ERROR); invval: err = gcry_error(GPG_ERR_INV_VALUE); goto err; conflict: err = gcry_error(GPG_ERR_CONFLICT); goto err; err: gcry_mpi_release(sender_next_y); free(data); free(rawmsg); return err; }