[Pkg-privacy-commits] [libotr] 15/225: All-new implementation of version 2 AKE.
Ximin Luo
infinity0 at moszumanska.debian.org
Sat Aug 22 12:44:45 UTC 2015
This is an automated email from the git hooks/post-receive script.
infinity0 pushed a commit to branch master
in repository libotr.
commit e1b401341e7b3ecad7a01f8c1822eb9c4cd05249
Author: cypherpunk <cypherpunk>
Date: Sun Oct 16 15:51:11 2005 +0000
All-new implementation of version 2 AKE.
---
Protocol => Protocol-v1.txt | 0
Protocol-v2.html | 1187 +++++++++++++++++++++++++++++++++
README | 2 +-
src/Makefile.am | 4 +-
src/auth.c | 1427 ++++++++++++++++++++++++++++++++++++++++
src/auth.h | 160 +++++
src/b64.c | 63 ++
src/b64.h | 15 +
src/context.c | 59 +-
src/context.h | 53 +-
src/dh.c | 232 ++++++-
src/dh.h | 40 +-
src/message.c | 1131 +++++++++++++++++--------------
src/message.h | 34 +-
src/{version.h => privkey-t.h} | 22 +-
src/privkey.c | 191 +++++-
src/privkey.h | 32 +-
src/proto.c | 610 ++++-------------
src/proto.h | 128 ++--
src/serial.h | 85 +++
src/tests.c | 199 ++++++
src/userstate.h | 4 +-
src/version.h | 2 +-
toolkit/otr_modify.c | 2 +-
toolkit/otr_parse.c | 72 +-
toolkit/otr_readforge.c | 2 +-
toolkit/otr_toolkit.1 | 4 +-
toolkit/parse.c | 186 ++++++
toolkit/parse.h | 54 ++
29 files changed, 4808 insertions(+), 1192 deletions(-)
diff --git a/Protocol b/Protocol-v1.txt
similarity index 100%
rename from Protocol
rename to Protocol-v1.txt
diff --git a/Protocol-v2.html b/Protocol-v2.html
new file mode 100644
index 0000000..5ce2d1a
--- /dev/null
+++ b/Protocol-v2.html
@@ -0,0 +1,1187 @@
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html><head>
+<title>Off-the-Record Messaging Protocol version 2 - DRAFT</title>
+<style type="text/css">
+ body { background: white; color: black }
+ h1 { text-align: center }
+ dd ul.note { list-style: none }
+ dl.doublespace dd { margin-bottom: 2ex }
+</style>
+</head><body>
+<h1>Off-the-Record Messaging Protocol version 2</h1>
+<h1>DRAFT 2005101601</h1>
+<p>This document describes version 2 of the Off-the-Record Messaging
+protocol. The main changes over version 1 include:</p>
+<ul>
+<li>Resolving the identity-binding flaw identified by Di Raimondo,
+Gennaro, and Krawczyk</li>
+<li>Not revealing the users' public keys to passive eavesdroppers; this
+could be useful if the application sending the OTR messages is also
+privacy-preserving</li>
+<li>Supporting fragmentation of OTR messages, to support IM networks
+whose maximum message size is very small.</li>
+<li>Better protocol version control, for future extensibility.</li>
+</ul>
+<h2>Very high level overview</h2>
+<p>OTR assumes a network model which provides in-order delivery of
+messages, but that some messages may not get delivered at all
+(for example, if the user disconnects). There may be
+an active attacker, who is allowed to perform a Denial of
+Service attack, but not to learn the contents of messages.</p>
+<ol>
+<li>Alice signals to Bob that she would like (using an OTR Query Message)
+or is willing (using a whitespace-tagged plaintext message) to use OTR
+to communicate. Either mechanism should convey the version(s) of OTR
+that Alice is willing to use.</li>
+<li>Bob initiates the authenticated key exchange (AKE) with Alice.
+Version 2 of OTR uses a variant of the SIGMA protocol as its AKE.</li>
+<li>Alice and Bob exchange Data Messages to send information to each
+other.</li>
+</ol>
+<h2>High level overview</h2>
+<h3>Requesting an OTR conversation</h3>
+<p>There are two ways Alice can inform Bob that she is willing to use
+the OTR protocol to speak with him: by sending him the OTR Query Message,
+or by including a special "tag" consisting of whitespace characters in
+one of her messages to him. Each method also includes a way for Alice
+to communicate to Bob which versions of the OTR protocol she is willing
+to speak with him.</p>
+<p>The semantics of the OTR Query Message are that Alice is
+<em>requesting</em> that Bob start an OTR conversation with her (if, of
+course, he is willing and able to do so). On the other hand, the
+semantics of the whitespace tag are that Alice is merely
+<em>indicating</em> to Bob that she is willing and able to have an OTR
+conversation with him. If Bob has a policy of "only use OTR when it's
+explicitly requested", for example, then he <em>would</em> start an OTR
+conversation upon receiving an OTR Query Message, but <em>would not</em>
+upon receiving the whitespace tag.</p>
+<h3>Authenticated Key Exchange (AKE)</h3>
+<p>This section outlines the version of the SIGMA protocol used as the
+AKE. All exponentiations are done modulo a particular 1536-bit prime,
+and g is a generator of that group, as indicated in the detailed
+description below. Alice and Bob's long-term authentication public keys
+are pub<sub>A</sub> and pub<sub>B</sub>, respectively.</p>
+<p>The general idea is that Alice and Bob do an <em>unauthenticated</em>
+Diffie-Hellman (D-H) key exchange to set up an encrypted channel, and
+then do mutual authentication <em>inside</em> that channel.</p>
+<p>Bob will be initiating the AKE with Alice.</p>
+<ul>
+<li>Bob:
+<ol>
+<li>Picks a random value r (128 bits)</li>
+<li>Picks a random value x (at least 320 bits)</li>
+<li>Sends Alice AES<sub>r</sub>(g<sup>x</sup>), HASH(g<sup>x</sup>)</li>
+</ol></li>
+<li>Alice:
+<ol>
+<li>Picks a random value y (at least 320 bits)</li>
+<li>Sends Bob g<sup>y</sup></li>
+</ol></li>
+<li>Bob:
+<ol>
+<li>Verifies that Alice's g<sup>y</sup> is a legal value (2 <=
+g<sup>y</sup> <= modulus-2)</li>
+<li>Computes s = (g<sup>y</sup>)<sup>x</sup></li>
+<li>Computes two AES keys c, c' and four MAC keys m1, m1', m2, m2' by
+hashing s in various ways</li>
+<li>Picks keyid<sub>B</sub>, a serial number for his D-H key
+g<sup>x</sup></li>
+<li>Computes M<sub>B</sub> = MAC<sub>m1</sub>(g<sup>x</sup>, g<sup>y</sup>,
+pub<sub>B</sub>, keyid<sub>B</sub>)</li>
+<li>Computes X<sub>B</sub> = pub<sub>B</sub>, keyid<sub>B</sub>,
+sig<sub>B</sub>(M<sub>B</sub>)</li>
+<li>Sends Alice r, AES<sub>c</sub>(X<sub>B</sub>),
+MAC<sub>m2</sub>(AES<sub>c</sub>(X<sub>B</sub>))</li>
+</ol></li>
+<li>Alice:
+<ol>
+<li>Uses r to decrypt the value of g<sup>x</sup> sent earlier</li>
+<li>Verifies that HASH(g<sup>x</sup>) matches the value sent earlier</li>
+<li>Verifies that Bob's g<sup>x</sup> is a legal value (2 <=
+g<sup>x</sup> <= modulus-2)</li>
+<li>Computes s = (g<sup>x</sup>)<sup>y</sup> (note that this will be the
+same as the value of s Bob calculated)</li>
+<li>Computes two AES keys c, c' and four MAC keys m1, m1', m2, m2' by
+hashing s in various ways (the same as Bob)</li>
+<li>Uses m2 to verify MAC<sub>m2</sub>(AES<sub>c</sub>(X<sub>B</sub>))</li>
+<li>Uses c to decrypt AES<sub>c</sub>(X<sub>B</sub>) to obtain
+X<sub>B</sub> = pub<sub>B</sub>, keyid<sub>B</sub>,
+sig<sub>B</sub>(M<sub>B</sub>)</li>
+<li>Computes M<sub>B</sub> = MAC<sub>m1</sub>(g<sup>x</sup>,
+g<sup>y</sup>, pub<sub>B</sub>, keyid<sub>B</sub>)</li>
+<li>Uses pub<sub>B</sub> to verify sig<sub>B</sub>(M<sub>B</sub>)</li>
+
+<li>Picks keyid<sub>A</sub>, a serial number for her D-H key
+g<sup>y</sup></li>
+<li>Computes M<sub>A</sub> = MAC<sub>m1'</sub>(g<sup>y</sup>, g<sup>x</sup>,
+pub<sub>A</sub>, keyid<sub>A</sub>)</li>
+<li>Computes X<sub>A</sub> = pub<sub>A</sub>, keyid<sub>A</sub>,
+sig<sub>A</sub>(M<sub>A</sub>)</li>
+<li>Sends Bob AES<sub>c'</sub>(X<sub>A</sub>),
+MAC<sub>m2'</sub>(AES<sub>c'</sub>(X<sub>A</sub>))</li>
+</ol></li>
+<li>Bob:
+<ol>
+<li>Uses m2' to verify MAC<sub>m2'</sub>(AES<sub>c'</sub>(X<sub>A</sub>))</li>
+<li>Uses c' to decrypt AES<sub>c'</sub>(X<sub>A</sub>) to obtain
+X<sub>A</sub> = pub<sub>A</sub>, keyid<sub>A</sub>,
+sig<sub>A</sub>(M<sub>A</sub>)</li>
+<li>Computes M<sub>A</sub> = MAC<sub>m1'</sub>(g<sup>y</sup>,
+g<sup>x</sup>, pub<sub>A</sub>, keyid<sub>A</sub>)</li>
+<li>Uses pub<sub>A</sub> to verify sig<sub>A</sub>(M<sub>A</sub>)</li>
+</ol></li>
+<li>If all of the verifications succeeded, Alice and Bob now know each
+other's Diffie-Hellman public keys, and share the value s. Alice is
+assured that s is known by someone with access to the private key
+corresponding to pub<sub>B</sub>, and similarly for Bob.</li>
+</ul>
+<h3>Exchanging data</h3>
+<p>This section outlines the method used to protect data being exchanged
+between Alice and Bob. As above, all exponentiations are done modulo
+a particular 1536-bit prime, and g is a generator of
+that group, as indicated in the detailed description below.</p>
+<p>Suppose Alice has a message (msg) to send to Bob.</p>
+<ul>
+<li>Alice:
+<ol>
+<li>Picks the most recent of her own D-H encryption keys that Bob has
+acknowledged receiving (by using it in a Data Message, or failing that,
+in the AKE). Let keyid<sub>A</sub> be the serial number of that
+key.</li>
+<li>If the above key is Alice's most recent key, she generates a new D-H key
+(next_dh), to get the serial number keyid<sub>A</sub>+1.</li>
+<li>Picks the most recent of Bob's D-H encryption keys that she has
+received from him (either in a Data Message or in the AKE). Let
+keyid<sub>B</sub> be the serial number of that key.</li>
+<li>Uses Diffie-Hellman to compute a shared secret from the two keys
+labelled by keyid<sub>A</sub> and keyid<sub>B</sub>, and generates the
+sending AES key, ek, and the sending MAC key, mk, as detailed
+below.</li>
+<li>Collects any old MAC keys that were used in previous messages, but
+will never again be used (because their associated D-H keys are no
+longer the most recent ones) into a list, oldmackeys.</li>
+<li>Picks a value of the counter, ctr, so that the triple
+(keyid<sub>A</sub>, keyid<sub>B</sub>, ctr) is never the same for more
+than one Data Message Alice sends to Bob.</li>
+<li>Computes T<sub>A</sub> = (keyid<sub>A</sub>, keyid<sub>B</sub>, next_dh,
+ctr, AES-CTR<sub>ek,ctr</sub>(msg))</li>
+<li>Sends Bob T<sub>A</sub>, MAC<sub>mk</sub>(T<sub>A</sub>),
+oldmackeys</li>
+</ol></li>
+<li>Bob:
+<ol>
+<li>Uses Diffie-Hellman to compute a shared secret from the two keys
+labelled by keyid<sub>A</sub> and keyid<sub>B</sub>, and generates the
+receiving AES key, ek, and the receiving MAC key, mk, as detailed
+below. (These will be the same as the keys Alice generated, above.)</li>
+<li>Uses mk to verify MAC<sub>mk</sub>(T<sub>A</sub>).</li>
+<li>Uses ek and ctr to decrypt
+AES-CTR<sub>ek,ctr</sub>(T<sub>A</sub>).</li>
+</ol>
+</li>
+</ul>
+<h2>Details of the protocol</h2>
+<h3>Unencoded messages</h3>
+<p>This section describes the messages in the OTR protocol that are not
+base-64 encoded binary.</p>
+<h4>OTR Query Messages</h4>
+<p>If Alice wishes to communicate to Bob that she would like to use OTR,
+she sends a message containing the string "?OTR" followed by an
+indication of what versions of OTR she is willing to use with Bob. The
+version string is constructed as follows:</p>
+<ul>
+<li>If she is willing to use OTR version 1, the version string must
+start with "?".</li>
+<li>If she is willing to use OTR versions other than 1, a "v" followed
+by the byte identifiers for the versions in question, followed by "?".
+The byte identifier for OTR version 2 is "2". The order of the
+identifiers between the "v" and the "?" does not matter, but none should
+be listed more than once.</li>
+</ul>
+<p>For example:</p>
+<dl>
+<dt>"?OTR?"</dt>
+<dd>Version 1 only</dd>
+<dt>"?OTRv2?"</dt>
+<dd>Version 2 only</dd>
+<dt>"?OTR?v2?"</dt>
+<dd>Versions 1 and 2</dd>
+<dt>"?OTRv24x?"</dt>
+<dd>Version 2, and hypothetical future versions identified by "4" and
+"x"</dd>
+<dt>"?OTR?v24x?"</dt>
+<dd>Versions 1, 2, and hypothetical future versions identified by "4" and
+"x"</dd>
+<dt>"?OTR?v?"</dt>
+<dd>Also version 1 only</dd>
+<dt>"?OTRv?"</dt>
+<dd>A bizarre claim that Alice would like to start an OTR conversation,
+but is unwilling to speak any version of the protocol</dd>
+</dl>
+<p>These strings may be hidden from the user (for example, in
+an attribute of an HTML tag), and/or may be accompanied by an
+explanitory message ("Alice has requested an Off-the-Record private
+conversation."). If Bob is willing to use OTR with Alice (with a
+protocol version that Alice has offered), he should start the AKE.</p>
+<h4>Tagged plaintext messages</h4>
+<p>If Alice wishes to communicate to Bob that she is willing to use OTR,
+she can attach a special whitespace tag to any plaintext message she
+sends him. This tag may occur anywhere in the message, and may be
+hidden from the user (as in the Query Messages, above).</p>
+<p>The tag consists of the following 16 bytes, followed by one or more
+sets of 8 bytes indicating the version of OTR Alice is willing to
+use:</p>
+<ul>
+<li>Always send "\x20\x09\x20\x20\x09\x09\x09\x09"
+"\x20\x09\x20\x09\x20\x09\x20\x20", followed by one or more of:</li>
+<li>"\x20\x09\x20\x09\x20\x20\x09\x20" to indicate a willingness to use
+OTR version 1 with Bob (note: this string must come before all other
+whitespace version tags, if it is present, for backwards
+compatibility)</li>
+<li>"\x20\x20\x09\x09\x20\x20\x09\x20" to indicate a willingness to use
+OTR version 2 with Bob</li>
+</ul>
+<p>If Bob is willing to use OTR with Alice (with a protocol version that
+Alice has offered), he should start the AKE. On the other hand, if
+Alice receives a plaintext message from Bob (rather than an initiation
+of the AKE), she should stop sending him the whitespace tag.</p>
+<h4>OTR Error Messages</h4>
+<p>Any message containing the string "?OTR Error:" is an OTR Error
+Message. The following part of the message should contain
+human-readable details of the error.</p>
+<h3>Encoded messages</h3>
+<p>This section describes the byte-level format of the base-64 encoded
+binary OTR messages. The binary form of each of the messages is
+described below. To transmit one of these messages, construct the ASCII
+string consisting of the five bytes "?OTR:", followed by the base-64
+encoding of the binary form of the message, followed by the byte
+".".</p>
+<p>For the Diffie-Hellman group computations, the group is the one
+defined in RFC 3526 with 1536-bit modulus (hex, big-endian):</p>
+<blockquote><pre>
+FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
+29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
+EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
+E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
+EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
+C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
+83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
+670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF
+</pre></blockquote>
+<p>and a generator (g) of 2. Note that this means that whenever you see a
+Diffie-Hellman exponentiation in this document, it always means that the
+exponentiation is done modulo the above 1536-bit number.</p>
+<h4>Data types</h4>
+<dl>
+<dt>Bytes (BYTE):</dt>
+<dd> 1 byte unsigned value</dd>
+<dt>Shorts (SHORT):</dt>
+<dd> 2 byte unsigned value, big-endian</dd>
+<dt>Ints (INT):</dt>
+<dd> 4 byte unsigned value, big-endian</dd>
+<dt>Multi-precision integers (MPI):</dt>
+<dd> 4 byte unsigned len, big-endian
+<br /> len byte unsigned value, big-endian
+<br /> (MPIs must use the minimum-length encoding; i.e. no leading 0x00
+ bytes. This is important when calculating public key
+ fingerprints.)</dd>
+<dt>Opaque variable-length data (DATA):</dt>
+<dd> 4 byte unsigned len, big-endian
+<br /> len byte data</dd>
+<dt>Initial CTR-mode counter value (CTR):</dt>
+<dd> 8 bytes data</dd>
+<dt>Message Authentication Code (MAC):</dt>
+<dd> 20 bytes MAC data</dd>
+</dl>
+<h4>Public keys, signatures, and fingerprints</h4>
+<p>OTR users have long-lived public keys that they use for
+authentication (but <em>not</em> encryption). The current version of
+the OTR protocol only supports DSA public keys, but there is a key type
+marker for future extensibility.</p>
+<dl>
+<dt>OTR public authentication DSA key (PUBKEY):</dt>
+<dd>Pubkey type (SHORT)
+<ul class="note"><li>DSA public keys have type 0x0000</li></ul>
+p (MPI)
+<br />q (MPI)
+<br />g (MPI)
+<br />y (MPI)
+<ul class="note"><li>(p,q,g,y) are the DSA public key parameters</li></ul>
+</dd>
+</dl>
+<p>OTR public keys are used to generate <b>signatures</b>; different
+types of keys produce signatures in different formats. The format for a
+signature made by a DSA public key is as follows:</p>
+<dl>
+<dt>DSA signature (SIG):</dt>
+<dd> (len is the length of the DSA public parameter q)
+<br /> len byte unsigned r, big-endian
+<br /> len byte unsigned s, big-endian</dd>
+</dl>
+<p>OTR public keys have <b>fingerprints</b>, which are hex strings that
+serve as identifiers for the public key. The fingerprint is calculated
+by taking the SHA-1 hash of the byte-level representation of the public
+key. However, there is an exception for backwards compatibility: if the
+pubkey type is 0x0000, those two leading 0x00 bytes are omitted from the
+data to be hashed. The encoding assures that, assuming the hash
+function itself has no useful collisions, and DSA keys have length less
+than 524281 bits (500 times larger than most DSA keys), no two public
+keys will have the same fingerprint.</p>
+<h4>D-H Commit Message</h4>
+<p>This is the first message of the AKE. Bob sends it to Alice to
+commit to a choice of D-H encryption key (but the key itself is not yet
+revealed). This allows the secure session id to be much shorter than in
+OTR version 1, while still preventing a man-in-the-middle attack on
+it.</p>
+<dl>
+<dt>Protocol version (SHORT)</dt>
+<dd>The version number of this protocol is 0x0002.</dd>
+<dt>Message type (BYTE)</dt>
+<dd>The D-H Commit Message has type 0x02.</dd>
+<dt>Encrypted g<sup>x</sup> (DATA)</dt>
+<dd>Produce this field as follows:
+<ul>
+<li>Choose a random value r (128 bits)</li>
+<li>Choose a random value x (at least 320 bits)</li>
+<li>Serialize g<sup>x</sup> as an MPI, gxmpi. [gxmpi will probably be
+196 bytes long, starting with "\x00\x00\x00\xc0".]</li>
+<li>Encrypt gxmpi using AES128-CTR, with key r and initial counter value
+0. The result will be the same length as gxmpi.</li>
+<li>Encode this encrypted value as the DATA field.</li>
+</ul></dd>
+<dt>Hashed g<sup>x</sup> (DATA)</dt>
+<dd>This is the SHA-256 hash of gxmpi.</dd>
+</dl>
+<h4>D-H Key Message</h4>
+<p>This is the second message of the AKE. Alice sends it to Bob, and it
+simply consists of Alice's D-H encryption key.</p>
+<dl>
+<dt>Protocol version (SHORT)</dt>
+<dd>The version number of this protocol is 0x0002.</dd>
+<dt>Message type (BYTE)</dt>
+<dd>The D-H Key Message has type 0x0a.</dd>
+<dt>g<sup>y</sup> (MPI)</dt>
+<dd>Choose a random value y (at least 320 bits), and calculate
+g<sup>y</sup>.</dd>
+</dl>
+<h4>Reveal Signature Message</h4>
+<p>This is the third message of the AKE. Bob sends it to Alice,
+revealing his D-H encryption key (and thus opening an encrypted
+channel), and also authenticating himself (and the parameters of the
+channel, preventing a man-in-the-middle attack on the channel itself) to
+Alice.</p>
+<dl>
+<dt>Protocol version (SHORT)</dt>
+<dd>The version number of this protocol is 0x0002.</dd>
+<dt>Message type (BYTE)</dt>
+<dd>The Reveal Signature Message has type 0x11.</dd>
+<dt>Revealed key (DATA)</dt>
+<dd>This is the value r picked earlier.</dd>
+<dt>Encrypted signature (DATA)</dt>
+<dd>This field is calculated as follows:
+<ul>
+<li>Compute the Diffie-Hellman shared secret s.</li>
+<li>Use s to compute an AES key c and two MAC keys m1 and m2, as specified below.</li>
+<li>Select keyid<sub>B</sub>, a serial number for the D-H key computed
+earlier. It is an INT, and must be greater than 0.</li>
+<li>Compute the 32-byte value M<sub>B</sub> to be the SHA256-HMAC of the
+following data, using the key m1:<dl>
+<dt>g<sup>x</sup> (MPI)</dt>
+<dt>g<sup>y</sup> (MPI)</dt>
+<dt>pub<sub>B</sub> (PUBKEY)</dt>
+<dt>keyid<sub>B</sub> (INT)</dt>
+</dl></li>
+<li>Let X<sub>B</sub> be the following structure:<dl>
+<dt>pub<sub>B</sub> (PUBKEY)</dt>
+<dt>keyid<sub>B</sub> (INT)</dt>
+<dt>sig<sub>B</sub>(M<sub>B</sub>) (SIG)</dt>
+<dd>This is the signature, using the private part of the key
+pub<sub>B</sub>, of the 32-byte M<sub>B</sub> (which does not need to be
+hashed again to produce the signature).</dd>
+</dl></li>
+<li>Encrypt X<sub>B</sub> using AES128-CTR with key c and initial
+counter value 0.</li>
+<li>Encode this encrypted value as the DATA field.</li>
+</ul></dd>
+<dt>MAC'd signature (MAC)</dt>
+<dd>This is the SHA256-HMAC-160 (that is, the first 160 bits of the
+SHA256-HMAC) of the encrypted signature field (including the four-byte
+length), using the key m2.</dd>
+</dl>
+<h4>Signature Message</h4>
+<p>This is the final message of the AKE. Alice sends it to Bob,
+authenticating herself and the channel parameters to him.</p>
+<dl>
+<dt>Protocol version (SHORT)</dt>
+<dd>The version number of this protocol is 0x0002.</dd>
+<dt>Message type (BYTE)</dt>
+<dd>The Signature Message has type 0x12.</dd>
+<dt>Encrypted signature (DATA)</dt>
+<dd>This field is calculated as follows:
+<ul>
+<li>Compute the Diffie-Hellman shared secret s.</li>
+<li>Use s to compute an AES key c' and two MAC keys m1' and m2', as specified below.</li>
+<li>Select keyid<sub>A</sub>, a serial number for the D-H key computed
+earlier. It is an INT, and must be greater than 0.</li>
+<li>Compute the 32-byte value M<sub>A</sub> to be the SHA256-HMAC of the
+following data, using the key m1':<dl>
+<dt>g<sup>y</sup> (MPI)</dt>
+<dt>g<sup>x</sup> (MPI)</dt>
+<dt>pub<sub>A</sub> (PUBKEY)</dt>
+<dt>keyid<sub>A</sub> (INT)</dt>
+</dl></li>
+<li>Let X<sub>A</sub> be the following structure:<dl>
+<dt>pub<sub>A</sub> (PUBKEY)</dt>
+<dt>keyid<sub>A</sub> (INT)</dt>
+<dt>sig<sub>A</sub>(M<sub>A</sub>) (SIG)</dt>
+<dd>This is the signature, using the private part of the key
+pub<sub>A</sub>, of the 32-byte M<sub>A</sub> (which does not need to be
+hashed again to produce the signature).</dd>
+</dl></li>
+<li>Encrypt X<sub>A</sub> using AES128-CTR with key c' and initial
+counter value 0.</li>
+<li>Encode this encrypted value as the DATA field.</li>
+</ul></dd>
+<dt>MAC'd signature (MAC)</dt>
+<dd>This is the SHA256-HMAC-160 (that is, the first 160 bits of the
+SHA256-HMAC) of the encrypted signature field (including the four-byte
+length), using the key m2'.</dd>
+</dl>
+<h4>Data Message</h4>
+<p>This message is used to transmit a private message to the
+correspondent. It is also used to reveal old MAC keys.</p>
+<p>The plaintext message (either before encryption, or after decryption)
+consists of a human-readable message (encoded in UTF-8, optionally with
+HTML markup), optionally followed by:</p>
+<ul>
+<li>a single NUL (a BYTE with value 0x00), <b>and</b></li>
+<li>zero or more TLV (type/length/value) records (with no padding
+between them)</li>
+</ul>
+<p>Each TLV record is of the form:</p>
+<dl>
+<dt>Type (SHORT)</dt>
+<dd>The type of this record. Records with unrecognized types should be
+ignored.</dd>
+<dt>Length (SHORT)</dt>
+<dd>The length of the following field</dd>
+<dt>Value (len BYTEs) [where len is the value of the Length field]</dt>
+<dd>Any pertinent data for the record type.</dd>
+</dl>
+<p>Some TLV examples:</p>
+<dl>
+<dt>\x00\x01\x00\x00</dt>
+<dd>A TLV of type 1, containing no data</dd>
+<dt>\x00\x00\x00\x05\x68\x65\x6c\x6c\x6f</dt>
+<dd>A TLV of type 0, containing the value "hello"</dd>
+</dl>
+<p>The currently defined TLV record types are:</p>
+<dl>
+<dt>Type 0: Padding</dt>
+<dd>The value may be an arbitrary amount of data, which should be
+ignored. This type can be used to disguise the length of the plaintext
+message.</dd>
+<dt>Type 1: Disconnected</dt>
+<dd>If the user requests to close the private connection, you may send a
+message (possibly with empty human-readable part) containing a record
+with this TLV type just before you discard the session keys, and
+transition to MSGSTATE_PLAINTEXT (see below). If you receive a TLV
+record of this type, you should transition to MSGSTATE_FINISHED (see
+below), and inform the user that his correspondent has closed his end of
+the private connection, and the user should do the same.</dd>
+</dl>
+<p>A message with an empty human-readable part (the plaintext is of zero
+length, or starts with a NUL) is a "heartbeat" packet, and should not
+be displayed to the user. (But it's still useful to effect key
+rotations.)</p>
+<p>Data Message format:</p>
+<dl>
+<dt>Protocol version (SHORT)</dt>
+<dd>This message has not changed since OTR version 1, so this value
+should be 0x0001.</dd>
+<dt>Message type (BYTE)</dt>
+<dd>The Data Message has type 0x03.</dd>
+<dt>Sender keyid (INT)</dt>
+<dd>Must be strictly greater than 0, and increment by 1 with each key
+change</dd>
+<dt>Recipient keyid (INT)</dt>
+<dd>Must therefore be strictly greater than 0, as the receiver has no
+key with id 0.
+<br />The sender and recipient keyids are those used to encrypt and MAC
+this message.</dd>
+<dt>DH y (MPI)</dt>
+<dd>The *next* [i.e. sender_keyid+1] public key for the sender</dd>
+<dt>Top half of counter init (CTR)</dt>
+<dd>This should monotonically increase (as a big-endian value) for
+ each message sent with the same (sender keyid, recipient keyid)
+ pair, and must not be all 0x00.</dd>
+<dt>Encrypted message (DATA)</dt>
+<dd>Using the appropriate encryption key (see below) derived from the
+ sender's and recipient's DH public keys (with the keyids given in
+ this message), perform AES128 counter-mode (CTR) encryption of the
+ message. The initial counter is a 16-byte value whose first 8
+ bytes are the above "top half of counter init" value, and whose
+ last 8 bytes are all 0x00. Note that counter mode does not change
+ the length of the message, so no message padding needs to be done.
+ If you *want* to do message padding (to disguise the length of
+ your message), use the above TLV of type 0.</dd>
+<dt>Authenticator (MAC)</dt>
+<dd>The SHA1-HMAC, using the appropriate MAC key (see below) of everything
+ from the Protocol version to the end of the encrypted message</dd>
+<dt>Old MAC keys to be revealed (DATA)</dt>
+<dd>See "Revealing MAC Keys", below.</dd>
+</dl>
+<h4>Key Management</h4>
+<p>For each correspondent, keep track of:</p>
+<dl>
+<dt>Your two most recent DH public/private key pairs</dt>
+<dd>our_dh[our_keyid] (most recent) and our_dh[our_keyid-1] (previous)</dd>
+<dt>His two most recent DH public keys</dt>
+<dd>their_y[their_keyid] (most recent) and their_y[their_keyid-1]
+(previous)</dd>
+</dl>
+
+<p>When starting a private conversation with a correspondent, generate
+two DH key pairs for yourself, and set our_keyid = 2. Note that all DH
+key pairs should have a private part that is at least 320 bits long.</p>
+
+<dl class="doublespace">
+<dt>When you send AKE messages:</dt>
+<dd>Send the public part of our_dh[our_keyid-1], with the keyid field,
+ of course, set to (our_keyid-1).</dd>
+
+<dt>Upon completing the AKE:</dt>
+<dd>If the specified keyid equals either their_keyid or their_keyid-1,
+ and the DH pubkey contained in the AKE messages matches the
+ one you've stored for that keyid, that's great. Otherwise, forget
+ all values of their_y[], and of their_keyid, and set their_keyid to
+ the keyid value given in the AKE messages, and
+ their_y[their_keyid] to the DH pubkey value given in the AKE
+ messages. their_y[their_keyid-1] should be set to NULL.</dd>
+
+<dt>When you send a Data Message:</dt>
+<dd>Set the sender keyid to (our_keyid-1), and the recipient keyid to
+ (their_keyid). Set the DH pubkey in the Data message to the public
+ part of our_dh[our_keyid]. Use our_dh[our_keyid-1] and
+ their_y[their_keyid] to calculate session keys, as outlined below.
+ Use the "sending AES key" to encrypt the message, and the "sending
+ MAC key" to calculate its MAC.</dd>
+
+<dt>When you receive a Data Message:</dt>
+<dd>Use the keyids in the message to select which of your DH key pairs
+ and which of his DH pubkeys to use to verify the MAC. If the keyids
+ do not represent either the most recent key or the previous key (for
+ either the sender or receiver), reject the message. Also reject the
+ message if the sender keyid is their_keyid-1, but
+ their_y[their_keyid-1] is NULL.
+
+ <p>Otherwise, calculate the session keys as outlined below. Use the
+ "receiving MAC key" to verify the MAC on the message. If it does not
+ verify, reject the message.</p>
+
+ <p>Check that the counter in the Data message is strictly larger than the
+ last counter you saw using this pair of keys. If not, reject the
+ message.</p>
+
+ <p>If the MAC verifies, decrypt the message using the "receiving AES
+ key".</p>
+
+ <p>Finally, check if keys need rotation:</p>
+ <ul>
+ <li>If the "recipient keyid" in the Data message equals our_keyid, then
+ he's seen the public part of our most recent DH key pair, so you
+ must securely forget our_dh[our_keyid-1], increment our_keyid, and set
+ our_dh[our_keyid] to a new DH key pair which you generate.</li>
+ <li>If the "sender keyid" in the Data message equals their_keyid,
+ increment their_keyid, and set their_y[their_keyid] to the new DH
+ pubkey specified in the Data message.</li>
+ </ul></dd>
+</dl>
+
+<h4>Computing AES keys, MAC keys, and the secure session id</h4>
+<p>OTR uses Diffie-Hellman to calculate shared secrets in the usual way:
+if Bob knows x, and tells Alice g<sup>x</sup>, and Alice knows y, and
+tells Bob g<sup>y</sup>, then they each can calculate s =
+g<sup>xy</sup>: Alice calculates (g<sup>x</sup>)<sup>y</sup>, and Bob
+calculates (g<sup>y</sup>)<sup>x</sup>.</p>
+<p>During the AKE, Alice and Bob each calculate s in this way, and then
+they each compute seven values based on s:</p>
+<ul>
+<li>A 64-bit secure session id, ssid</li>
+<li>Two 128-bit AES encryption keys, c and c'</li>
+<li>Four 256-bit SHA256-HMAC keys, m1, m2, m1', and m2'</li>
+</ul>
+<p>This is done in the following way:</p>
+<ul>
+<li>Write the value of s as a minimum-length MPI, as specified above
+(4-byte big-endian len, len-byte big-endian value). Let this
+(4+len)-byte value be "secbytes".</li>
+<li>For a given byte b, define h2(b) to be the 256-bit output of the
+SHA256 hash of the (5+len) bytes consisting of the byte b, followed by
+secbytes.</li>
+<li>Let ssid be the first 64 bits of h2(0x00).</li>
+<li>Let c be the first 128 bits of h2(0x01), and let c' be the second
+128 bits of h2(0x01).</li>
+<li>Let m1 be h2(0x02).</li>
+<li>Let m2 be h2(0x03).</li>
+<li>Let m1' be h2(0x04).</li>
+<li>Let m2' be h2(0x05).</li>
+</ul>
+<p>c, m1, and m2 are used to create and verify the Reveal Signature
+Message; c', m1', and m2' are used to create and verify the Signature
+message.</p>
+<p>If the user requests to see the secure session id, it should be
+displayed as two 32-bit bigendian unsigned values, in C "%08x" format.
+If the user transmitted the Reveal Signature message during the AKE that
+produced this ssid, then display the first 32 bits in bold, and the
+second 32 bits in non-bold. If the user transmitted the Signature
+message instead, display the first 32 bits in non-bold, and the
+second 32 bits in bold. This session id can be used by the parties to
+verify (say, over the telephone, assuming the parties recognize each
+others' voices) that there is no man-in-the-middle by having each side
+read his bold part to the other. [Note that this only needs to be done
+in the event that the users do not trust that their long-term signature
+keys have not been compromised.]</p>
+<p>During the exchange of Data Messages, Alice and Bob use the keyids
+listed in the Data Message to select Diffie-Hellman keys to use to
+compute s, and the (4+len)-byte value of secbytes, as above.</p>
+<p>From this, they calculate four values:</p>
+<ul>
+<li>Two 128-bit AES encryption keys, the "sending AES key", and the
+"receiving AES key"</li>
+<li>Two 160-bit SHA1-HMAC keys, the "sending MAC key", and the
+"receiving MAC key"</li>
+</ul>
+<p>These keys are calculated as follows:</p>
+<ul>
+<li>Alice (and similarly for Bob) determines if she is the "low" end
+or the "high" end of this Data Message. If Alice's public key is
+numerically greater than Bob's public key, then she
+is the "high" end. Otherwise, she is the "low" end. Note that who is the
+"low" end and who is the "high" end can change every time a new D-H
+public key is exchanged in a Data Message.</li>
+<li>She sets the values of "sendbyte" and "recvbyte" according to
+whether she is the the "low" or the "high" end of the Data Message:
+<ul>
+<li>If she is the "high" end, she sets "sendbyte" to 0x01 and "recvbyte"
+to 0x02.</li>
+<li>If she is the "low" end, she sets "sendbyte" to 0x02 and "recvbyte"
+to 0x01.</li>
+</ul></li>
+<li>For a given byte b, define h1(b) to be the 160-bit output of the
+SHA-1 hash of the (5+len) bytes consisting of the byte b, followed by
+secbytes.</li>
+<li>The "sending AES key" is the first 16 bytes of h1(sendbyte).</li>
+<li>The "sending MAC key" is the 20-byte SHA-1 hash of the 16-byte
+sending AES key.</li>
+<li>The "receiving AES key" is the first 16 bytes of h1(recvbyte).</li>
+<li>The "receiving MAC key" is the 20-byte SHA-1 hash of the 16-byte
+receiving AES key.</li>
+</ul>
+<h4>Revealing MAC keys</h4>
+<p>Whenever you are about to forget either one of your old D-H key pairs, or
+one of your correspondent's old D-H public keys, take all of the MAC keys
+that were generated by that key (note that there are up to four: the
+sending and receiving MAC keys produced by the pairings of that key with
+each of two of the other side's keys; but note that you only need to
+take MAC keys that were actually used to either create a MAC on a
+message, or verify a MAC on a message), and put them (as a set of
+concatenated 20-byte values) into the "Old MAC keys to be revealed"
+section of the next Data Message you send. This in done to allow the
+forgeability of OTR transcripts: once the MAC keys are revealed, anyone
+can modify an OTR message and still have it appear valid. But since we
+don't reveal the MAC keys until their corresponding pubkeys are being
+discarded, there is no danger of accepting a message as valid which
+uses a MAC key which has already been revealed.</p>
+<h4>Fragmentation</h4>
+<p>Some networks may have a maximum message size that is too small to
+contain an encoded OTR message. In that event, the sender may choose
+to split the message into a number of <em>fragments</em>. This section
+describes the format of the fragments. All OTR version 2 clients must
+be able to assemble received fragments, but performing fragmentation on
+outgoing messages is optional.</p>
+
+<dl class="doublespace">
+<dt>Transmitting Fragments</dt>
+<dd>If you have information about the maximum size of message you are
+ able to send (the different IM networks have different limits), you
+ can fragment an encoded OTR message as follows:
+ <ul>
+ <li>Start with the OTR message as you would normally transmit it. For
+ example, a Data Message would start with "?OTR:AAED" and end
+ with ".".</li>
+ <li>Break it up into sufficiently small pieces. Let the number of
+ pieces be (n), and the pieces be
+ piece[1],piece[2],...,piece[n].</li>
+ <li>Transmit (n) messages with the following (printf-like) structure
+ (as k runs from 1 to n inclusive):
+
+ <p>"?OTR,%hu,%hu,%s," , k , n , piece[k]</p></li>
+
+ <li>Note that k and n are unsigned short ints (2 bytes), and each has
+ a maximum value of 65535. Also, each piece[k] must be
+ non-empty.</li>
+ </ul></dd>
+
+<dt>Receiving Fragments:</dt>
+
+<dd>If you receive a message containing "?OTR," (note that you'll need
+ to check for this _before_ checking for any of the other "?OTR:"
+ markers):
+
+ <ul>
+ <li>Parse it as the printf statement above into k, n, and
+ piece.</li>
+ <li>Let (K,N) be your currently stored fragment number, and F be your
+ currently stored fragment. [If you have no currently stored
+ fragment, then K = N = 0 and F = "".]</li>
+
+ <li>If k == 0 or n == 0 or k > n, discard this (illegal)
+ fragment.</li>
+
+ <li>If k == 1:
+ <ul>
+ <li>Forget any stored fragment you may have</li>
+ <li>Store (piece) as F.</li>
+ <li>Store (k,n) as (K,N).</li>
+ </ul></li>
+
+ <li>If n == N and k == K+1:
+ <ul>
+ <li>Append (piece) to F.</li>
+ <li>Store (k,n) as (K,N).</li>
+ </ul></li>
+
+ <li>Otherwise:
+ <ul>
+ <li>Forget any stored fragment you may have</li>
+ <li>Store "" as F.</li>
+ <li>Store (0,0) as (K,N).</li>
+ </ul></li>
+ </ul>
+
+ <p>After this, if N > 0 and K == N, treat F as the received
+ message.</p>
+
+ <p>If you receive a non-OTR message, or an unfragmented message,
+ forget any stored fragment you may have, store "" as F and store
+ (0,0) as (K,N).</p></dd>
+</dl>
+
+<p>For example, here is a Data Message we would like to transmit over a
+network with an unreasonably small maximum message size:</p>
+
+<blockquote><pre>
+?OTR:AAEDAAAAAQAAAAEAAADAVf3Ei72ZgFeKqWvLMnuVPVCwxktsOZ1Qdje
+Lp6jn62mCVtlY9nS6sRkecpjuLYHRxyTdRu2iEVtSsjZqK55ovZ35SfkOPHe
+FYa9BIuxWi9djHMVKQ8KOVGAVLibjZ6P8LreDSKtWDv9YQjIEnkwFVGCPfpB
+q2SX4VTQfJAQXHggR8izKxPvluXUdG9rIPh4cac98++VLdIuFMiEXjUIoTX2
+rEzunaCLMy0VIfowlRsgsKGrwhCCv7hBWyglbzwz+AAAAAAAAAAQAAAF2SOr
+JvPUerB9mtf4bqQDFthfoz/XepysnYuReHHEXKe+BFkaEoMNGiBl4TCLZx72
+DvmZwKCewWRH1+W66ggrXKw2VdVl+vLsmzxNyWChGLfBTL5/3SUF09BfmCEl
+03Ckk7htAgyAQcBf90RJznZndv7HwVAi3syupi0sQDdOKNPyObR5FRtqyqud
+ttWmSdmGCGFcZ/fZqxQNsHB8QuYaBiGL7CDusES+wwfn8Q7BGtoJzOPDDx6K
+yIyox/flPx2DZDJIZrMz9b0V70a9kqKLo/wcGhvHO6coCyMxenBAacLJ1DiI
+NLKoYOoJTM7zcxsGnvCxaDZCvsmjx3j8Yc5r3i3ylllCQH2/lpr/xCvXFarG
+tG7+wts+UqstS9SThLBQ9Ojq4oPsX7HBHKvq19XU3/ChIgWMy+bczc5gpkC/
+eLAIGfJ0D5DJsl68vMXSmCoFK0HTwzzNa7lnZK4IutYPBNBCv0pWORQqDpsk
+Ez96YOGyB8+gtpFgCrkuV1bSB9SRVmEBfDtKPQFhKowAAAAA=.
+</pre></blockquote>
+
+ <p>We could fragment this message into (for example) three
+ pieces:</p>
+
+<blockquote><pre>
+?OTR,1,3,?OTR:AAEDAAAAAQAAAAEAAADAVf3Ei72ZgFeKqWvLMnuVPVCwxk
+tsOZ1QdjeLp6jn62mCVtlY9nS6sRkecpjuLYHRxyTdRu2iEVtSsjZqK55ovZ
+35SfkOPHeFYa9BIuxWi9djHMVKQ8KOVGAVLibjZ6P8LreDSKtWDv9YQjIEnk
+wFVGCPfpBq2SX4VTQfJAQXHggR8izKxPvluXUdG9rIPh4cac98++VLdIuFMi
+EXjUIoTX2rEzunaCLMy0VIfowlRsgsKGrwhCCv7hBWyglbzwz+AAAAAAAAAA
+QAAAF2SOr,
+</pre></blockquote>
+
+<blockquote><pre>
+?OTR,2,3,JvPUerB9mtf4bqQDFthfoz/XepysnYuReHHEXKe+BFkaEoMNGiB
+l4TCLZx72DvmZwKCewWRH1+W66ggrXKw2VdVl+vLsmzxNyWChGLfBTL5/3SU
+F09BfmCEl03Ckk7htAgyAQcBf90RJznZndv7HwVAi3syupi0sQDdOKNPyObR
+5FRtqyqudttWmSdmGCGFcZ/fZqxQNsHB8QuYaBiGL7CDusES+wwfn8Q7BGto
+JzOPDDx6KyIyox/flPx2DZDJIZrMz9b0V70a9kqKLo/wcGhvHO6coCyMxenB
+AacLJ1DiI,
+</pre></blockquote>
+
+<blockquote><pre>
+?OTR,3,3,NLKoYOoJTM7zcxsGnvCxaDZCvsmjx3j8Yc5r3i3ylllCQH2/lpr
+/xCvXFarGtG7+wts+UqstS9SThLBQ9Ojq4oPsX7HBHKvq19XU3/ChIgWMy+b
+czc5gpkC/eLAIGfJ0D5DJsl68vMXSmCoFK0HTwzzNa7lnZK4IutYPBNBCv0p
+WORQqDpskEz96YOGyB8+gtpFgCrkuV1bSB9SRVmEBfDtKPQFhKowAAAAA=.,
+</pre></blockquote>
+<h3>The protocol state machine</h3>
+<p>An OTR client maintains separate state for every correspondent. For
+example, Alice may have an active OTR conversation with Bob, while
+having an unprotected conversation with Charlie. This state consists of
+two main state variables, as well as some other information (such as
+encryption keys). The two main state variables are:</p>
+<h4>Message state</h4>
+<p>The message state variable, msgstate, controls what happens to
+outgoing messages typed by the user. It can take one of three
+values:</p>
+<dl>
+<dt>MSGSTATE_PLAINTEXT</dt>
+<dd>This state indicates that outgoing messages are sent without
+encryption. This is the state that is used before an OTR conversation
+is initiated. This is the initial state, and the only way to
+subsequently enter this state is for the user to explicitly request to
+do so via some UI operation.</dd>
+<dt>MSGSTATE_ENCRYPTED</dt>
+<dd>This state indicates that outgoing messages are sent encrypted.
+This is the state that is used during an OTR conversation. The only way
+to enter this state is for the authentication state machine (below) to
+successfully complete.</dd>
+<dt>MSGSTATE_FINISHED</dt>
+<dd>This state indicates that outgoing messages are not delivered at
+all. This state is entered only when the other party indicates he has
+terminated his side of the OTR conversation. For example, if Alice and
+Bob are having an OTR conversation, and Bob instructs his OTR client to
+end its private session with Alice (for example, by logging out), Alice
+will be notified of this, and <em>her</em> client will switch to
+MSGSTATE_FINISHED mode. This prevents Alice from accidentally sending a
+message to Bob in plaintext. (Consider what happens if Alice was in the
+middle of typing a private message to Bob when he suddenly logs out,
+just as Alice hits Enter.)</dd>
+</dl>
+<h4>Authentication state</h4>
+<p>The authentication state variable, authstate, can take one of four
+values (plus one extra for OTR version 1 compatibility):</p>
+<dl>
+<dt>AUTHSTATE_NONE</dt>
+<dd>This state indicates that the authentication protocol is not
+currently in progress. This is the initial state.</dd>
+<dt>AUTHSTATE_AWAITING_DHKEY</dt>
+<dd>After Bob initiates the authentication protocol by sending Alice
+the D-H Commit Message, he enters this state to await Alice's reply.</dd>
+<dt>AUTHSTATE_AWAITING_REVEALSIG</dt>
+<dd>After Alice receives Bob's D-H Commit Message, and replies with her
+own D-H Key Message, she enters this state to await Bob's reply.</dd>
+<dt>AUTHSTATE_AWAITING_SIG</dt>
+<dd>After Bob receives Alice's D-H Key Message, and replies with his own
+Reveal Signature Message, he enters this state to await Alice's reply.</dd>
+<dt>AUTHSTATE_V1_SETUP</dt>
+<dd>For OTR version 1 compatibility, if Bob sends a version 1 Key
+Exchange Message to Alice, he enters this state to await Alice's
+reply.</dd>
+</dl>
+<p>After:</p>
+<ul>
+<li>Alice (in AUTHSTATE_AWAITING_REVEALSIG) receives Bob's Reveal
+Signature Message (and replies with her own Signature Message),</li>
+<li>Alice (in AUTHSTATE_NONE) receives Bob's Version 1 Key Exchange
+Message (and replies with her own Key Exchange Message),</li>
+<li>Bob (in AUTHSTATE_AWAITING_SIG) receives Alice's Signature Message,
+<b>or</b></li>
+<li>Bob (in AUTHSTATE_V1_SETUP) receives Alice's Version 1 Key Exchange
+Message,</li>
+</ul>
+<p>then,
+assuming the signature verifications succeed, the msgstate
+variable is transitioned to MSGSTATE_ENCRYPTED. Regardless of whether
+the signature verifications succeed, the authstate variable is
+transitioned to AUTHSTATE_NONE.</p>
+<h4>Policies</h4>
+<p>OTR clients can set different <b>policies</b> for different
+correspondents. For example, Alice could set up her client so that it
+speaks only OTR version 2, except with Charlie, who she knows has only
+an old client; so that it will opportunistically start an OTR conversation
+whenever it detects the correspondent supports it; or so that it refuses
+to send non-encrypted messages to Bob, ever.</p>
+<p>The policies that can be set (on a global or per-correspondent basis)
+are any combination of the following boolean flags:</p>
+<dl>
+<dt>ALLOW_V1</dt>
+<dd>Allow version 1 of the OTR protocol to be used.</dd>
+<dt>ALLOW_V2</dt>
+<dd>Allow version 2 of the OTR protocol to be used.</dd>
+<dt>REQUIRE_ENCRYPTION</dt>
+<dd>Refuse to send unencrypted messages.</dd>
+<dt>SEND_WHITESPACE_TAG</dt>
+<dd>Advertise your support of OTR using the whitespace tag.</dd>
+<dt>WHITESPACE_START_AKE</dt>
+<dd>Start the OTR AKE when you receive a whitespace tag.</dd>
+<dt>ERROR_START_AKE</dt>
+<dd>Start the OTR AKE when you receive an OTR Error Message.</dd>
+</dl>
+<p>The four old version 1 policies correspond to the following
+combinations of flags (adding an allowance for version 2 of the
+protocol):</p>
+<dl>
+<dt>NEVER</dt>
+<dd>No flags set</dd>
+<dt>MANUAL</dt>
+<dd>ALLOW_V1 | ALLOW_V2</dd>
+<dt>OPPORTUNISTIC</dt>
+<dd>ALLOW_V1 | ALLOW_V2 | SEND_WHITESPACE_TAG | WHITESPACE_START_AKE |
+ERROR_START_AKE</dd>
+<dt>ALWAYS</dt>
+<dd>ALLOW_V1 | ALLOW_V2 | REQUIRE_ENCRYPTION | WHITESPACE_START_AKE |
+ERROR_START_AKE</dd>
+</dl>
+<p>Note that it is possible for UIs simply to offer the old
+"combinations" of options, and not ask about each one separately.</p>
+<h4>State transitions</h4>
+<p>There are thirteen actions an OTR client must handle:</p>
+<ul>
+<li>Received messages:
+<ul>
+<li>Plaintext without the whitespace tag</li>
+<li>Plaintext with the whitespace tag</li>
+<li>Query Message</li>
+<li>Error Message</li>
+<li>D-H Commit Message</li>
+<li>D-H Key Message</li>
+<li>Reveal Signature Message</li>
+<li>Signature Message</li>
+<li>Version 1 Key Exchange Message</li>
+<li>Data Message</li>
+</ul></li>
+<li>User actions:
+<ul>
+<li>User requests to start an OTR conversation</li>
+<li>User requests to end an OTR conversation</li>
+<li>User types a message to be sent</li>
+</ul></li>
+</ul>
+<p>The following sections will outline what actions to take in each
+case. They all assume that at least one of ALLOW_V1 or ALLOW_V2 is set;
+if not, then OTR is completely disabled, and no special handling of
+messages should be done at all.</p>
+<h4>Receiving plaintext without the whitespace tag</h4>
+<dl>
+<dt>If msgstate is MSGSTATE_PLAINTEXT:</dt>
+<dd>Simply display the message to the user. If REQUIRE_ENCRYPTION is
+set, warn him that the message was received unencrypted.</dd>
+<dt>If msgstate is MSGSTATE_ENCRYPTED or MSGSTATE_FINISHED:</dt>
+<dd>Display the message to the user, but warn him that the message was
+received unencrypted.</dd>
+</dl>
+<h4>Receiving plaintext with the whitespace tag</h4>
+<dl>
+<dt>If msgstate is MSGSTATE_PLAINTEXT:</dt>
+<dd>Remove the whitespace tag and display the message to the user. If
+REQUIRE_ENCRYPTION is set, warn him that the message was received
+unencrypted.</dd>
+<dt>If msgstate is MSGSTATE_ENCRYPTED or MSGSTATE_FINISHED:</dt>
+<dd>Remove the whitespace tag and display the message to the user, but
+warn him that the message was received unencrypted.</dd>
+</dl>
+<p>In any event, if WHITESPACE_START_AKE is set:</p>
+<dl>
+<dt>If the tag offers OTR version 2 and ALLOW_V2 is set:</dt>
+<dd>Send a D-H Commit Message, and transition authstate to
+AUTHSTATE_AWAITING_DHKEY.</dd>
+<dt>Otherwise, if the tag offers OTR version 1 and ALLOW_V1 is set:</dt>
+<dd>Send a Version 1 Key Exchange Message, and transition authstate to
+AUTHSTATE_V1_SETUP.</dd>
+</dl>
+<h4>Receiving a Query Message</h4>
+<dl>
+<dt>If the Query Message offers OTR version 2 and ALLOW_V2 is set:</dt>
+<dd>Send a D-H Commit Message, and transition authstate to
+AUTHSTATE_AWAITING_DHKEY.</dd>
+<dt>Otherwise, if the message offers OTR version 1 and ALLOW_V1 is set:</dt>
+<dd>Send a Version 1 Key Exchange Message, and transition authstate to
+AUTHSTATE_V1_SETUP.</dd>
+</dl>
+<h4>Receiving an Error Message</h4>
+<p>Display the message to the user. If ERROR_START_AKE is set, reply
+with a Query Message.</p>
+<h4>User requests to start an OTR conversation</h4>
+<p>Send an OTR Query Message to the correspondent.</p>
+<h4>Receiving a D-H Commit Message</h4>
+<p>If ALLOW_V2 is not set, ignore this message. Otherwise:</p>
+<dl>
+<dt>If authstate is AUTHSTATE_NONE:</dt>
+<dd>Reply with a D-H Key Message, and transition authstate to
+AUTHSTATE_AWAITING_REVEALSIG.</dd>
+<dt>If authstate is AUTHSTATE_AWAITING_DHKEY:</dt>
+<dd>This is the trickiest transition in the whole protocol. It
+indicates that you have already sent a D-H Commit message to your
+correspondent, but that he either didn't receive it, or just didn't
+receive it <em>yet</em>, and has sent you one as well. The symmetry
+will be broken by comparing the hashed g<sup>x</sup> you sent in your
+D-H Commit Message with the one you received, considered as 32-byte
+unsigned big-endian values.
+<dl>
+<dt>If yours is the higher hash value:</dt>
+<dd>Ignore the incoming D-H Commit message, but resend your D-H
+Commit message.</dd>
+<dt>Otherwise:</dt>
+<dd>Forget your old g<sup>x</sup> value that you sent (encrypted)
+earlier, and pretend you're in AUTHSTATE_NONE; i.e. reply with a D-H Key
+Message, and transition authstate to AUTHSTATE_AWAITING_REVEALSIG.</dd>
+</dl></dd>
+<dt>If authstate is AUTHSTATE_AWAITING_REVEALSIG:</dt>
+<dd>Retransmit your D-H Key Message (the same
+one as you sent when you entered AUTHSTATE_AWAITING_REVEALSIG). Forget
+the old D-H Commit message, and use this new one instead. There
+are a number of reasons this might happen, including:
+<ul>
+<li>Your correspondent simply started a new AKE.</li>
+<li>Your correspondent resent his D-H Commit message, as specified
+above.</li>
+<li>On some networks, like AIM, if your correspondent is logged in
+multiple times, each of his clients will send a D-H Commit Message in
+response to a Query Message; resending the same D-H Key Message in
+response to each of those messages will prevent compounded confusion,
+since each of his clients will see each of the D-H Key Messages you
+send. [And the problem gets even worse if you are <em>each</em> logged
+in multiple times.]</li>
+</ul></dd>
+<dt>If authstate is AUTHSTATE_AWAITING_SIG or AUTHSTATE_V1_SETUP:</dt>
+<dd>Reply with a new D-H Key message, and transition authstate to
+AUTHSTATE_AWAITING_REVEALSIG.</dd>
+</dl>
+<h4>Receiving a D-H Key Message</h4>
+<p>If ALLOW_V2 is not set, ignore this message. Otherwise:</p>
+<dl>
+<dt>If authstate is AUTHSTATE_AWAITING_DHKEY:</dt>
+<dd>Reply with a Reveal Signature Message and transition authstate to
+AUTHSTATE_AWAITING_SIG.</dd>
+<dt>If authstate is AUTHSTATE_AWAITING_SIG:</dt>
+<dd>
+<dl>
+<dt>If this D-H Key message is the same the one you received earlier
+(when you entered AUTHSTATE_AWAITING_SIG):</dt>
+<dd>Retransmit your Reveal Signature Message.</dd>
+<dt>Otherwise:</dt>
+<dd>Ignore the message.</dd>
+</dl></dd>
+<dt>If authstate is AUTHSTATE_NONE, AUTHSTATE_AWAITING_REVEALSIG, or
+AUTHSTATE_V1_SETUP:</dt>
+<dd>Ignore the message.</dd>
+</dl>
+<h4>Receiving a Reveal Signature Message</h4>
+<p>If ALLOW_V2 is not set, ignore this message. Otherwise:</p>
+<dl>
+<dt>If authstate is AUTHSTATE_AWAITING_REVEALSIG:</dt>
+<dd>Use the received value of r to decrypt the value of g<sup>x</sup>
+received in the D-H Commit Message, and verify the hash therein.
+Decrypt the encrypted signature, and verify the signature and the MACs.
+If everything checks out:
+<ul>
+<li>Reply with a Signature Message.</li>
+<li>Transition authstate to AUTHSTATE_NONE.</li>
+<li>Transition msgstate to MSGSTATE_ENCRYPTED.</li>
+<li>If there is a recent stored message, encrypt it and send it as a
+Data Message.</li>
+</ul>
+Otherwise, ignore the message.</dd>
+<dt>If authstate is AUTHSTATE_NONE, AUTHSTATE_AWAITING_DHKEY,
+AUTHSTATE_AWAITING_SIG, or AUTHSTATE_V1_SETUP:</dt>
+<dd>Ignore the message.</dd>
+</dl>
+<h4>Receiving a Signature Message</h4>
+<p>If ALLOW_V2 is not set, ignore this message. Otherwise:</p>
+<dl>
+<dt>If authstate is AUTHSTATE_AWAITING_SIG:</dt>
+<dd>Decrypt the encrypted signature, and verify the signature and the MACs.
+If everything checks out:
+<ul>
+<li>Transition authstate to AUTHSTATE_NONE.</li>
+<li>Transition msgstate to MSGSTATE_ENCRYPTED.</li>
+<li>If there is a recent stored message, encrypt it and send it as a
+Data Message.</li>
+</ul>
+Otherwise, ignore the message.</dd>
+<dt>If authstate is AUTHSTATE_NONE, AUTHSTATE_AWAITING_DHKEY,
+AUTHSTATE_AWAITING_REVEALSIG, or AUTHSTATE_V1_SETUP:</dt>
+<dd>Ignore the message.</dd>
+</dl>
+<h4>Receiving a Version 1 Key Exchange Message</h4>
+<p>If ALLOW_V1 is not set, ignore this message. Otherwise:</p>
+<dl>
+<dt>If authstate is AUTHSTATE_NONE, AUTHSTATE_AWAITING_DHKEY,
+AUTHSTATE_AWAITING_REVEALSIG, or AUTHSTATE_AWAITING_SIG:</dt>
+<dd><dl><dt>If the reply field is not set to 0x01:</dt>
+<dd>Verify the information in the Key Exchange Message. If the
+verification succeeds:
+<ul>
+<li>Reply with a Key Exchange Message with the reply field set to
+0x01.</li>
+<li>Transition authstate to AUTHSTATE_NONE.</li>
+<li>Transition msgstate to MSGSTATE_ENCRYPTED.</li>
+<li>If there is a recent stored message, encrypt it and send it as a
+Data Message.</li>
+</ul>
+Otherwise, ignore the message.</dd>
+<dt>Otherwise, ignore the message.</dt></dl></dd>
+<dt>If authstate is AUTHSTATE_V1_SETUP:</dt>
+<dd>Verify the information in the Key Exchange Message. If the
+verification succeeds:
+<ul>
+<li>If the received Key Exchange Message did not have the reply field
+set to 0x01, reply with a Key Exchange Message with the reply field set
+to 0x01.</li>
+<li>Transition authstate to AUTHSTATE_NONE.</li>
+<li>Transition msgstate to MSGSTATE_ENCRYPTED.</li>
+<li>If there is a recent stored message, encrypt it and send it as a
+Data Message.</li>
+</ul>
+Otherwise, ignore the message.</dd>
+</dl>
+<h4>User types a message to be sent</h4>
+<dl>
+<dt>If msgstate is MSGSTATE_PLAINTEXT:</dt>
+<dd><dl><dt>If REQUIRE_ENCRYPTION is set:</dt>
+<dd>Store the plaintext message for possible retransmission, and send a
+Query Message.</dd>
+<dt>Otherwise:</dt>
+<dd>If SEND_WHITESPACE_TAG is set, and you have not received a plaintext
+message from this correspondent since last entering MSGSTATE_PLAINTEXT,
+attach the whitespace tag to the message. Send the (possibly modified)
+message as plaintext.</dd></dl></dd>
+<dt>If msgstate is MSGSTATE_ENCRYPTED:</dt>
+<dd>Encrypt the message, and send it as a Data Message. Store the
+plaintext message for possible retransmission.</dd>
+<dt>If msgstate is MSGSTATE_FINISHED:</dt>
+<dd>Inform the user that the message cannot be sent at this time. Store
+the plaintext message for possible retransmission.</dd>
+</dl>
+<h4>Receiving a Data Message</h4>
+<dl>
+<dt>If msgstate is MSGSTATE_ENCRYPTED:</dt>
+<dd>Verify the information (MAC, keyids, ctr value, etc.) in the
+message.
+<dl>
+<dt>If the verification succeeds:</dt>
+<dd>
+<ul>
+<li>Decrypt the message and display the human-readable part (if
+non-empty) to the user.</li>
+<li>Update the D-H encryption keys, if necessary.</li>
+<li>If you have not sent a message to this correspondent in some
+(configurable) time, send a "heartbeat" message, consisting of a Data
+Message encoding an empty plaintext.</li>
+<li>If the received message contains a TLV type 1, forget all encryption
+keys for this correspondent, and transition msgstate to
+MSGSTATE_FINISHED.</li>
+</ul>
+</dd>
+<dt>Otherwise, inform the user that an unreadable encrypted message was
+received, and reply with an Error Message.</dt>
+</dl></dd>
+<dt>If msgstate is MSGSTATE_PLAINTEXT or MSGSTATE_FINISHED:</dt>
+<dd>Inform the user that an unreadable encrypted message was received,
+and reply with an Error Message.</dd>
+</dl>
+<h4>User requests to end an OTR conversation</h4>
+<dl>
+<dt>If msgstate is MSGSTATE_PLAINTEXT:</dt>
+<dd>Do nothing.</dd>
+<dt>If msgstate is MSGSTATE_ENCRYPTED:</dt>
+<dd>Send a Data Message, encoding a message with an empty human-readable
+part, and TLV type 1. Transition msgstate to MSGSTATE_PLAINTEXT.</dd>
+<dt>If msgstate is MSGSTATE_FINISHED:</dt>
+<dd>Transition msgstate to MSGSTATE_PLAINTEXT.</dd>
+</dl>
+</body></html>
diff --git a/README b/README
index 1879d1a..2dfb4c1 100644
--- a/README
+++ b/README
@@ -179,7 +179,7 @@ Here are the six programs in the toolkit:
- otr_parse
- Parse OTR messages given on stdin, showing the values of all the
- fields in OTR Key Exchange Messages and OTR Data Messages.
+ fields in OTR protocol messages.
- otr_sesskeys our_privkey their_pubkey
- Shows our public key, the session id, two AES and two MAC keys
diff --git a/src/Makefile.am b/src/Makefile.am
index 04d7d05..933aa11 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -3,11 +3,11 @@ INCLUDES = @LIBGCRYPT_CFLAGS@
lib_LTLIBRARIES = libotr.la
libotr_la_SOURCES = privkey.c context.c proto.c b64.c dh.c mem.c message.c \
- userstate.c tlv.c
+ userstate.c tlv.c auth.c
libotr_la_LDFLAGS = -version-info @LIBOTR_LIBTOOL_VERSION@ @LIBS@ @LIBGCRYPT_LIBS@
otrincdir = $(includedir)/libotr
otrinc_HEADERS = b64.h context.h dh.h mem.h message.h privkey.h proto.h \
- version.h userstate.h tlv.h
+ version.h userstate.h tlv.h serial.h auth.h privkey-t.h
diff --git a/src/auth.c b/src/auth.c
new file mode 100644
index 0000000..067beb7
--- /dev/null
+++ b/src/auth.c
@@ -0,0 +1,1427 @@
+/*
+ * Off-the-Record Messaging library
+ * Copyright (C) 2004-2005 Nikita Borisov and Ian Goldberg
+ * <otr at 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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"
+
+/*
+ * Initialize the fields of an OtrlAuthInfo (already allocated).
+ */
+void otrl_auth_new(OtrlAuthInfo *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;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * Start a fresh AKE (version 2) 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). If no error is
+ * returned, the message to transmit will be contained in
+ * auth->lastauthmsg.
+ */
+gcry_error_t otrl_auth_start_v2(OtrlAuthInfo *auth, DH_keypair *our_dh,
+ unsigned int our_keyid)
+{
+ 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;
+
+ /* 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;
+ }
+
+ /* 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 = 3 + 4 + auth->encgx_len + 4 + 32;
+ bufp = malloc(lenp);
+ if (bufp == NULL) goto memerr;
+ buf = bufp;
+ buflen = lenp;
+
+ memmove(bufp, "\x00\x02\x02", 3); /* header */
+ debug_data("Header", bufp, 3);
+ bufp += 3; lenp -= 3;
+
+ /* 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 = 3 + 4 + npub;
+ buf = malloc(buflen);
+ if (buf == NULL) goto memerr;
+ bufp = buf;
+ lenp = buflen;
+
+ memmove(bufp, "\x00\x02\x0a", 3); /* header */
+ debug_data("Header", bufp, 3);
+ bufp += 3; lenp -= 3;
+
+ /* 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. If non-NULL, use
+ * a copy of the given D-H keypair, with the given keyid.
+ */
+gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth, const char *commitmsg,
+ DH_keypair *our_dh, unsigned int our_keyid)
+{
+ 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;
+
+ res = otrl_base64_otr_decode(commitmsg, &buf, &buflen);
+ if (res == -1) goto memerr;
+ if (res == -2) goto invval;
+
+ bufp = buf;
+ lenp = buflen;
+
+ /* Header */
+ require_len(3);
+ if (memcmp(bufp, "\x00\x02\x02", 3)) goto invval;
+ bufp += 3; lenp -= 3;
+
+ /* 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);
+ 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;
+ }
+ 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. Compare the hashgx values to see which one wins. */
+ if (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);
+ 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;
+ }
+ 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) >> 16) & 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) >> 16) & 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 >> 16) & 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 = 3 + 4 + 16 + 4 + authlen + 20;
+ buf = malloc(buflen);
+ if (buf == NULL) goto memerr;
+
+ bufp = buf;
+ lenp = buflen;
+
+ memmove(bufp, "\x00\x02\x11", 3); /* header */
+ debug_data("Header", bufp, 3);
+ bufp += 3; lenp -= 3;
+
+ /* 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 = 3 + 4 + authlen + 20;
+ buf = malloc(buflen);
+ if (buf == NULL) goto memerr;
+
+ bufp = buf;
+ lenp = buflen;
+
+ memmove(bufp, "\x00\x02\x12", 3); /* header */
+ debug_data("Header", bufp, 3);
+ bufp += 3; lenp -= 3;
+
+ /* 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;
+
+ *havemsgp = 0;
+
+ res = otrl_base64_otr_decode(keymsg, &buf, &buflen);
+ if (res == -1) goto memerr;
+ if (res == -2) goto invval;
+
+ bufp = buf;
+ lenp = buflen;
+
+ /* Header */
+ if (memcmp(bufp, "\x00\x02\x0a", 3)) goto invval;
+ bufp += 3; lenp -= 3;
+
+ /* g^y */
+ read_mpi(incoming_pub);
+
+ if (lenp != 0) goto invval;
+ free(buf);
+ buf = NULL;
+
+ switch(auth->authstate) {
+ case OTRL_AUTHSTATE_AWAITING_DHKEY:
+ /* 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;
+
+ *havemsgp = 0;
+
+ res = otrl_base64_otr_decode(revealmsg, &buf, &buflen);
+ if (res == -1) goto memerr;
+ if (res == -2) goto invval;
+
+ bufp = buf;
+ lenp = buflen;
+
+ /* Header */
+ if (memcmp(bufp, "\x00\x02\x11", 3)) goto invval;
+ bufp += 3; lenp -= 3;
+
+ /* 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);
+ if (memcmp(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 (memcmp(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->protocol_version = 2;
+ 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;
+
+ *havemsgp = 0;
+
+ res = otrl_base64_otr_decode(sigmsg, &buf, &buflen);
+ if (res == -1) goto memerr;
+ if (res == -2) goto invval;
+
+ bufp = buf;
+ lenp = buflen;
+
+ /* Header */
+ if (memcmp(bufp, "\x00\x02\x12", 3)) goto invval;
+ bufp += 3; lenp -= 3;
+
+ /* 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 (memcmp(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->protocol_version = 2;
+ auth->session_id_half = OTRL_SESSIONID_FIRST_HALF_BOLD;
+ if (auth_succeeded) err = auth_succeeded(auth, asdata);
+ free(auth->lastauthmsg);
+ auth->lastauthmsg = NULL;
+ *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_REVEALSIG:
+ case OTRL_AUTHSTATE_V1_SETUP:
+ /* Ignore this message */
+ *havemsgp = 0;
+ 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;
+
+ /* 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;
+ 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;
+ }
+
+ /* 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;
+}
+
+#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_v2(&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
diff --git a/src/auth.h b/src/auth.h
new file mode 100644
index 0000000..08744c0
--- /dev/null
+++ b/src/auth.h
@@ -0,0 +1,160 @@
+/*
+ * Off-the-Record Messaging library
+ * Copyright (C) 2004-2005 Nikita Borisov and Ian Goldberg
+ * <otr at 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __AUTH_H__
+#define __AUTH_H__
+
+#include <gcrypt.h>
+#include "dh.h"
+
+typedef enum {
+ OTRL_AUTHSTATE_NONE,
+ OTRL_AUTHSTATE_AWAITING_DHKEY,
+ OTRL_AUTHSTATE_AWAITING_REVEALSIG,
+ OTRL_AUTHSTATE_AWAITING_SIG,
+ OTRL_AUTHSTATE_V1_SETUP
+} OtrlAuthState;
+
+typedef struct {
+ OtrlAuthState authstate; /* Our state */
+
+ DH_keypair our_dh; /* Our D-H key */
+ unsigned int our_keyid; /* ...and its keyid */
+
+ unsigned char *encgx; /* The encrypted value of g^x */
+ size_t encgx_len; /* ...and its length */
+ unsigned char r[16]; /* The encryption key */
+
+ unsigned char hashgx[32]; /* SHA256(g^x) */
+
+ gcry_mpi_t their_pub; /* Their D-H public key */
+ unsigned int their_keyid; /* ...and its keyid */
+
+ gcry_cipher_hd_t enc_c, enc_cp; /* c and c' encryption keys */
+ gcry_md_hd_t mac_m1, mac_m1p; /* m1 and m1' MAC keys */
+ gcry_md_hd_t mac_m2, mac_m2p; /* m2 and m2' MAC keys */
+
+ unsigned char their_fingerprint[20]; /* The fingerprint of their
+ long-term signing key */
+
+ int initiated; /* Did we initiate this
+ authentication? */
+
+ unsigned int protocol_version; /* The protocol version number
+ used to authenticate. */
+
+ unsigned char secure_session_id[20]; /* The secure session id */
+ size_t secure_session_id_len; /* And its actual length,
+ which may be either 20 (for
+ v1) or 8 (for v2) */
+ OtrlSessionIdHalf session_id_half; /* Which half of the session
+ id gets shown in bold */
+
+ char *lastauthmsg; /* The last auth message
+ (base-64 encoded) we sent,
+ in case we need to
+ retransmit it. */
+} OtrlAuthInfo;
+
+#include "privkey-t.h"
+
+/*
+ * Initialize the fields of an OtrlAuthInfo (already allocated).
+ */
+void otrl_auth_new(OtrlAuthInfo *auth);
+
+/*
+ * Clear the fields of an OtrlAuthInfo (but leave it allocated).
+ */
+void otrl_auth_clear(OtrlAuthInfo *auth);
+
+/*
+ * Start a fresh AKE (version 2) 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). If no error is
+ * returned, the message to transmit will be contained in
+ * auth->lastauthmsg.
+ */
+gcry_error_t otrl_auth_start_v2(OtrlAuthInfo *auth, DH_keypair *our_dh,
+ unsigned int our_keyid);
+
+/*
+ * Handle an incoming D-H Commit Message. If no error is returned, the
+ * message to send will be left in auth->lastauthmsg. If non-NULL, use
+ * a copy of the given D-H keypair, with the given keyid.
+ */
+gcry_error_t otrl_auth_handle_commit(OtrlAuthInfo *auth, const char *commitmsg,
+ DH_keypair *our_dh, unsigned int our_keyid);
+
+/*
+ * 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);
+
+/*
+ * 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);
+
+/*
+ * 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);
+
+/*
+ * 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);
+
+/*
+ * 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);
+
+#endif
diff --git a/src/b64.c b/src/b64.c
index d1bb487..a461c88 100644
--- a/src/b64.c
+++ b/src/b64.c
@@ -56,6 +56,7 @@ VERSION HISTORY:
/* system headers */
#include <stdlib.h>
+#include <string.h>
/* libotr headers */
#include "b64.h"
@@ -185,3 +186,65 @@ size_t otrl_base64_decode(char *data, const unsigned char *base64data,
return datalen;
}
+
+/*
+ * Base64-encode a block of data, stick "?OTR:" and "." around it, and
+ * return the result, or NULL in the event of a memory error. The
+ * caller must free() the return value.
+ */
+char *otrl_base64_otr_encode(const unsigned char *buf, size_t buflen)
+{
+ char *base64buf;
+ size_t base64len;
+
+ /* Make the base64-encoding. */
+ base64len = ((buflen + 2) / 3) * 4;
+ base64buf = malloc(5 + base64len + 1 + 1);
+ if (base64buf == NULL) {
+ return NULL;
+ }
+ memmove(base64buf, "?OTR:", 5);
+ otrl_base64_encode(base64buf+5, buf, buflen);
+ base64buf[5 + base64len] = '.';
+ base64buf[5 + base64len + 1] = '\0';
+
+ return base64buf;
+}
+
+/*
+ * Base64-decode the portion of the given message between "?OTR:" and
+ * ".". Set *bufp to the decoded data, and set *lenp to its length.
+ * The caller must free() the result. Return 0 on success, -1 on a
+ * memory error, or -2 on invalid input.
+ */
+int otrl_base64_otr_decode(const char *msg, unsigned char **bufp,
+ size_t *lenp)
+{
+ char *otrtag, *endtag;
+ size_t msglen, rawlen;
+ unsigned char *rawmsg;
+
+ otrtag = strstr(msg, "?OTR:");
+ if (!otrtag) {
+ return -2;
+ }
+ endtag = strchr(otrtag, '.');
+ if (endtag) {
+ msglen = endtag-otrtag;
+ } else {
+ return -2;
+ }
+
+ /* Base64-decode the message */
+ rawlen = ((msglen-5) / 4) * 3; /* maximum possible */
+ rawmsg = malloc(rawlen);
+ if (!rawmsg && rawlen > 0) {
+ return -1;
+ }
+ rawlen = otrl_base64_decode(rawmsg, otrtag+5, msglen-5); /* actual size */
+
+ *bufp = rawmsg;
+ *lenp = rawlen;
+
+ return 0;
+}
diff --git a/src/b64.h b/src/b64.h
index 5bd4f80..03d9b21 100644
--- a/src/b64.h
+++ b/src/b64.h
@@ -39,4 +39,19 @@ size_t otrl_base64_encode(char *base64data, const unsigned char *data,
size_t otrl_base64_decode(char *data, const unsigned char *base64data,
size_t base64len);
+/*
+ * Base64-encode a block of data, stick "?OTR:" and "." around it, and
+ * return the result, or NULL in the event of a memory error.
+ */
+char *otrl_base64_otr_encode(const unsigned char *buf, size_t buflen);
+
+/*
+ * Base64-decode the portion of the given message between "?OTR:" and
+ * ".". Set *bufp to the decoded data, and set *lenp to its length.
+ * The caller must free() the result. Return 0 on success, -1 on a
+ * memory error, or -2 on invalid input.
+ */
+int otrl_base64_otr_decode(const char *msg, unsigned char **bufp,
+ size_t *lenp);
+
#endif
diff --git a/src/context.c b/src/context.c
index 6d53210..2e699c6 100644
--- a/src/context.c
+++ b/src/context.c
@@ -27,10 +27,6 @@
/* libotr headers */
#include "context.h"
-/* Strings describing the connection states */
-const char *otrl_context_statestr[] =
- { "Not private", "Setting up", "Private" };
-
/* Create a new connection context. */
static ConnContext * new_context(const char * user, const char * accountname,
const char * protocol)
@@ -45,7 +41,8 @@ static ConnContext * new_context(const char * user, const char * accountname,
context->fragment_len = 0;
context->fragment_n = 0;
context->fragment_k = 0;
- context->state = CONN_UNCONNECTED;
+ context->msgstate = OTRL_MSGSTATE_PLAINTEXT;
+ otrl_auth_new(&(context->auth));
context->fingerprint_root.fingerprint = NULL;
context->fingerprint_root.context = context;
context->fingerprint_root.next = NULL;
@@ -66,6 +63,7 @@ static ConnContext * new_context(const char * user, const char * accountname,
otrl_dh_session_blank(&(context->sesskeys[1][0]));
otrl_dh_session_blank(&(context->sesskeys[1][1]));
memset(context->sessionid, 0, 20);
+ context->sessionid_len = 0;
context->numsavedkeys = 0;
context->preshared_secret = NULL;
context->preshared_secret_len = 0;
@@ -187,11 +185,11 @@ void otrl_context_set_preshared_secret(ConnContext *context,
}
}
-/* Force a context into the CONN_SETUP state (so that it only has local
- * DH keys). */
-void otrl_context_force_setup(ConnContext *context)
+/* Force a context into the OTRL_MSGSTATE_FINISHED state. */
+void otrl_context_force_finished(ConnContext *context)
{
- context->state = CONN_SETUP;
+ context->msgstate = OTRL_MSGSTATE_FINISHED;
+ otrl_auth_clear(&(context->auth));
free(context->fragment);
context->fragment = NULL;
context->fragment_len = 0;
@@ -203,11 +201,15 @@ void otrl_context_force_setup(ConnContext *context)
context->their_y = NULL;
gcry_mpi_release(context->their_old_y);
context->their_old_y = NULL;
+ context->our_keyid = 0;
+ otrl_dh_keypair_free(&(context->our_dh_key));
+ otrl_dh_keypair_free(&(context->our_old_dh_key));
otrl_dh_session_free(&(context->sesskeys[0][0]));
otrl_dh_session_free(&(context->sesskeys[0][1]));
otrl_dh_session_free(&(context->sesskeys[1][0]));
otrl_dh_session_free(&(context->sesskeys[1][1]));
memset(context->sessionid, 0, 20);
+ context->sessionid_len = 0;
free(context->preshared_secret);
context->preshared_secret = NULL;
context->preshared_secret_len = 0;
@@ -219,34 +221,32 @@ void otrl_context_force_setup(ConnContext *context)
context->may_retransmit = 0;
}
-/* Force a context into the CONN_UNCONNECTED state. */
-void otrl_context_force_disconnect(ConnContext *context)
+/* Force a context into the OTRL_MSGSTATE_PLAINTEXT state. */
+void otrl_context_force_plaintext(ConnContext *context)
{
- /* First clean up everything we'd need to do for the SETUP state */
- otrl_context_force_setup(context);
+ /* First clean up everything we'd need to do for the FINISHED state */
+ otrl_context_force_finished(context);
- /* Now clean up our pubkeys, too */
- context->state = CONN_UNCONNECTED;
- context->our_keyid = 0;
- otrl_dh_keypair_free(&(context->our_dh_key));
- otrl_dh_keypair_free(&(context->our_old_dh_key));
+ /* And just set the state properly */
+ context->msgstate = OTRL_MSGSTATE_PLAINTEXT;
}
/* Forget a fingerprint (so long as it's not the active one. If it's a
* fingerprint_root, forget the whole context (as long as
- * and_maybe_context is set, and it's UNCONNECTED). Also, if it's not
+ * and_maybe_context is set, and it's PLAINTEXT). Also, if it's not
* the fingerprint_root, but it's the only fingerprint, and we're
- * UNCONNECTED, forget the whole context if and_maybe_context is set. */
+ * PLAINTEXT, forget the whole context if and_maybe_context is set. */
void otrl_context_forget_fingerprint(Fingerprint *fprint,
int and_maybe_context)
{
ConnContext *context = fprint->context;
if (fprint == &(context->fingerprint_root)) {
- if (context->state == CONN_UNCONNECTED && and_maybe_context) {
+ if (context->msgstate == OTRL_MSGSTATE_PLAINTEXT &&
+ and_maybe_context) {
otrl_context_forget(context);
}
} else {
- if (context->state != CONN_CONNECTED ||
+ if (context->msgstate != OTRL_MSGSTATE_PLAINTEXT ||
context->active_fingerprint != fprint) {
free(fprint->fingerprint);
free(fprint->trust);
@@ -255,7 +255,7 @@ void otrl_context_forget_fingerprint(Fingerprint *fprint,
fprint->next->tous = fprint->tous;
}
free(fprint);
- if (context->state == CONN_UNCONNECTED &&
+ if (context->msgstate == OTRL_MSGSTATE_PLAINTEXT &&
context->fingerprint_root.next == NULL &&
and_maybe_context) {
/* We just deleted the only fingerprint. Forget the
@@ -266,14 +266,14 @@ void otrl_context_forget_fingerprint(Fingerprint *fprint,
}
}
-/* Forget a whole context, so long as it's UNCONNECTED. */
+/* Forget a whole context, so long as it's PLAINTEXT. */
void otrl_context_forget(ConnContext *context)
{
- if (context->state != CONN_UNCONNECTED) return;
+ if (context->msgstate != OTRL_MSGSTATE_PLAINTEXT) return;
- /* Just to be safe, force a disconnect. This also frees any
+ /* Just to be safe, force to plaintext. This also frees any
* extraneous data lying around. */
- otrl_context_force_disconnect(context);
+ otrl_context_force_plaintext(context);
/* First free all the Fingerprints */
while(context->fingerprint_root.next) {
@@ -302,12 +302,11 @@ void otrl_context_forget(ConnContext *context)
free(context);
}
-/* Forget all the contexts in a given OtrlUserState, forcing them to
- * UNCONNECTED. */
+/* Forget all the contexts in a given OtrlUserState. */
void otrl_context_forget_all(OtrlUserState us)
{
while (us->context_root) {
- otrl_context_force_disconnect(us->context_root);
+ otrl_context_force_plaintext(us->context_root);
otrl_context_forget(us->context_root);
}
}
diff --git a/src/context.h b/src/context.h
index fbe095a..640c953 100644
--- a/src/context.h
+++ b/src/context.h
@@ -20,14 +20,23 @@
#ifndef __CONTEXT_H__
#define __CONTEXT_H__
-#include "gcrypt.h"
+#include <gcrypt.h>
+
#include "dh.h"
+#include "auth.h"
typedef enum {
- CONN_UNCONNECTED,
- CONN_SETUP,
- CONN_CONNECTED
-} ConnectionState;
+ OTRL_MSGSTATE_PLAINTEXT, /* Not yet started an encrypted
+ conversation */
+ OTRL_MSGSTATE_ENCRYPTED, /* Currently in an encrypted
+ conversation */
+ OTRL_MSGSTATE_FINISHED /* The remote side has sent us a
+ notification that he has ended
+ his end of the encrypted
+ conversation; prevent any
+ further messages from being
+ sent to him. */
+} OtrlMessageState;
typedef struct fingerprint {
struct fingerprint *next; /* The next fingerprint in the list */
@@ -55,8 +64,11 @@ typedef struct context {
we've seen so far for this
message */
- ConnectionState state; /* The state of our connection to this
- user */
+ OtrlMessageState msgstate; /* The state of message disposition
+ with this user */
+ OtrlAuthInfo auth; /* The state of ongoing
+ authentication with this user */
+
Fingerprint fingerprint_root; /* The root of a linked list of
Fingerprints entries */
Fingerprint *active_fingerprint; /* Which fingerprint is in use now?
@@ -76,9 +88,9 @@ typedef struct context {
derived from DH key[our_keyid-i]
and mpi Y[their_keyid-j] */
- unsigned char sessionid[20]; /* The sessionid and direction */
- SessionDirection sessiondir; /* determined when this private
- connection was established. */
+ unsigned char sessionid[20]; /* The sessionid and bold half */
+ size_t sessionid_len; /* determined when this private */
+ OtrlSessionIdHalf sessionid_half; /* connection was established. */
unsigned char *preshared_secret; /* A secret you share with this
user, in order to do
@@ -114,9 +126,6 @@ typedef struct context {
#include "userstate.h"
-/* Strings describing the connection states */
-extern const char *otrl_context_statestr[];
-
/* Look up a connection context by name/account/protocol from the given
* OtrlUserState. If add_if_missing is true, allocate and return a new
* context if one does not currently exist. In that event, call
@@ -141,26 +150,24 @@ void otrl_context_set_trust(Fingerprint *fprint, const char *trust);
void otrl_context_set_preshared_secret(ConnContext *context,
const unsigned char *secret, size_t secret_len);
-/* Force a context into the CONN_SETUP state (so that it only has local
- * DH keys). */
-void otrl_context_force_setup(ConnContext *context);
+/* Force a context into the OTRL_MSGSTATE_FINISHED state. */
+void otrl_context_force_finished(ConnContext *context);
-/* Force a context into the CONN_UNCONNECTED state. */
-void otrl_context_force_disconnect(ConnContext *context);
+/* Force a context into the OTRL_MSGSTATE_PLAINTEXT state. */
+void otrl_context_force_plaintext(ConnContext *context);
/* Forget a fingerprint (so long as it's not the active one. If it's a
* fingerprint_root, forget the whole context (as long as
- * and_maybe_context is set, and it's UNCONNECTED). Also, if it's not
+ * and_maybe_context is set, and it's PLAINTEXT). Also, if it's not
* the fingerprint_root, but it's the only fingerprint, and we're
- * UNCONNECTED, forget the whole context if and_maybe_context is set. */
+ * PLAINTEXT, forget the whole context if and_maybe_context is set. */
void otrl_context_forget_fingerprint(Fingerprint *fprint,
int and_maybe_context);
-/* Forget a whole context, so long as it's UNCONNECTED. */
+/* Forget a whole context, so long as it's PLAINTEXT. */
void otrl_context_forget(ConnContext *context);
-/* Forget all the contexts in a given OtrlUserState, forcing them to
- * UNCONNECTED. */
+/* Forget all the contexts in a given OtrlUserState. */
void otrl_context_forget_all(OtrlUserState us);
#endif
diff --git a/src/dh.c b/src/dh.c
index a159b5f..9c22601 100644
--- a/src/dh.c
+++ b/src/dh.c
@@ -40,6 +40,7 @@ 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;
/*
@@ -51,6 +52,28 @@ void otrl_dh_init(void)
gcry_mpi_scan(&DH1536_MODULUS, GCRYMPI_FMT_HEX, DH1536_MODULUS_S, 0, NULL);
gcry_mpi_scan(&DH1536_GENERATOR, GCRYMPI_FMT_HEX, 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);
}
/*
@@ -129,22 +152,17 @@ gcry_error_t otrl_dh_session(DH_sesskeys *sess, const DH_keypair *kp,
gcry_mpi_print(GCRYMPI_FMT_USG, gabdata+5, gablen, NULL, gab);
gcry_mpi_release(gab);
- /* Calculate the session id */
hashdata = gcry_malloc_secure(20);
if (!hashdata) {
gcry_free(gabdata);
return gcry_error(GPG_ERR_ENOMEM);
}
- gabdata[0] = 0x00;
- gcry_md_hash_buffer(GCRY_MD_SHA1, sess->dhsecureid, gabdata, gablen+5);
/* Are we the "high" or "low" end of the connection? */
if ( gcry_mpi_cmp(kp->pub, y) > 0 ) {
- sess->dir = SESS_DIR_HIGH;
sendbyte = 0x01;
rcvbyte = 0x02;
} else {
- sess->dir = SESS_DIR_LOW;
sendbyte = 0x02;
rcvbyte = 0x01;
}
@@ -193,6 +211,209 @@ 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_new(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_new(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)
*/
@@ -215,7 +436,6 @@ void otrl_dh_session_blank(DH_sesskeys *sess)
sess->sendmac = NULL;
sess->rcvenc = NULL;
sess->rcvmac = NULL;
- memset(sess->dhsecureid, 0, 20);
memset(sess->sendctr, 0, 16);
memset(sess->rcvctr, 0, 16);
memset(sess->sendmackey, 0, 20);
diff --git a/src/dh.h b/src/dh.h
index 1ed1982..f41b023 100644
--- a/src/dh.h
+++ b/src/dh.h
@@ -27,17 +27,13 @@ typedef struct {
gcry_mpi_t priv, pub;
} DH_keypair;
+/* Which half of the secure session id should be shown in bold? */
typedef enum {
- SESS_DIR_LOW,
- SESS_DIR_HIGH
-} SessionDirection;
+ OTRL_SESSIONID_FIRST_HALF_BOLD,
+ OTRL_SESSIONID_SECOND_HALF_BOLD
+} OtrlSessionIdHalf;
typedef struct {
- SessionDirection dir;
- unsigned char dhsecureid[20]; /* Don't display this value to the
- user when she asks to see the
- secure session id. Display
- context->sessionid instead. */
unsigned char sendctr[16];
unsigned char rcvctr[16];
gcry_cipher_hd_t sendenc;
@@ -57,6 +53,16 @@ typedef struct {
void otrl_dh_init(void);
/*
+ * Initialize the fields of a DH keypair.
+ */
+void otrl_dh_keypair_init(DH_keypair *kp);
+
+/*
+ * Copy a DH_keypair.
+ */
+void otrl_dh_keypair_copy(DH_keypair *dst, const DH_keypair *src);
+
+/*
* Deallocate the contents of a DH_keypair (but not the DH_keypair
* itself)
*/
@@ -75,6 +81,24 @@ gcry_error_t otrl_dh_session(DH_sesskeys *sess, const DH_keypair *kp,
gcry_mpi_t y);
/*
+ * 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);
+
+/*
+ * 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);
+
+/*
* Deallocate the contents of a DH_sesskeys (but not the DH_sesskeys
* itself)
*/
diff --git a/src/message.c b/src/message.c
index 8c6ec92..a3fe279 100644
--- a/src/message.c
+++ b/src/message.c
@@ -28,6 +28,7 @@
/* libotr headers */
#include "privkey.h"
#include "proto.h"
+#include "auth.h"
#include "message.h"
/* How long after sending a packet should we wait to send a heartbeat? */
@@ -97,246 +98,389 @@ gcry_error_t otrl_message_sending(OtrlUserState us,
}
/* Should we go on at all? */
- if (policy == OTRL_POLICY_NEVER) {
+ if ((policy & OTRL_POLICY_VERSION_MASK) == 0) {
return gcry_error(GPG_ERR_NO_ERROR);
}
/* If this is an OTR Query message, don't encrypt it. */
- if (otrl_proto_message_type(message) == OTR_QUERY) {
+ if (otrl_proto_message_type(message) == OTRL_MSGTYPE_QUERY) {
/* Replace the "?OTR?" with a custom message */
- char *bettermsg = otrl_proto_default_query_msg(accountname);
+ char *bettermsg = otrl_proto_default_query_msg(accountname, policy);
if (bettermsg) {
*messagep = bettermsg;
}
return gcry_error(GPG_ERR_NO_ERROR);
}
- if (policy == OTRL_POLICY_ALWAYS && context->state != CONN_CONNECTED) {
- /* We're trying to send an unencrypted message with policy
- * ALWAYS. Don't do that, but try to start up OTR instead. */
- if (context->lastmessage) {
- gcry_free(context->lastmessage);
- if (ops->notify) {
- const char *format = "You attempted to send another "
- "unencrypted message to %s";
- char *primary = malloc(strlen(format) + strlen(recipient) - 1);
- if (primary) {
- sprintf(primary, format, recipient);
- ops->notify(opdata, OTRL_NOTIFY_ERROR, accountname,
- protocol, recipient, "OTR Policy Violation",
- primary,
- "Unencrypted messages to this recipient are not "
- "allowed. Attempting to start a private "
- "conversation.\n\nYour message will be "
- "retransmitted when the private conversation "
- "starts, but the previously saved message has "
- "been discarded.");
- free(primary);
+ /* What is the current message disposition? */
+ switch(context->msgstate) {
+ case OTRL_MSGSTATE_PLAINTEXT:
+ if ((policy & OTRL_POLICY_REQUIRE_ENCRYPTION)) {
+ /* We're trying to send an unencrypted message with a policy
+ * that disallows that. Don't do that, but try to start
+ * up OTR instead. */
+ if ((!(ops->display_otr_message) ||
+ ops->display_otr_message(opdata, accountname,
+ protocol, recipient, "Attempting to start a "
+ "private conversation...")) && ops->notify) {
+ const char *format = "You attempted to send an "
+ "unencrypted message to %s";
+ char *primary = malloc(strlen(format) +
+ strlen(recipient) - 1);
+ if (primary) {
+ sprintf(primary, format, recipient);
+ ops->notify(opdata, OTRL_NOTIFY_WARNING, accountname,
+ protocol, recipient, "OTR Policy Violation",
+ primary,
+ "Unencrypted messages to this recipient are "
+ "not allowed. Attempting to start a private "
+ "conversation.\n\nYour message will be "
+ "retransmitted when the private conversation "
+ "starts.");
+ free(primary);
+ }
}
- }
- } else {
- if (ops->notify) {
- const char *format = "You attempted to send an "
- "unencrypted message to %s";
- char *primary = malloc(strlen(format) + strlen(recipient) - 1);
- if (primary) {
- sprintf(primary, format, recipient);
- ops->notify(opdata, OTRL_NOTIFY_WARNING, accountname,
- protocol, recipient, "OTR Policy Violation",
- primary,
- "Unencrypted messages to this recipient are not "
- "allowed. Attempting to start a private "
- "conversation.\n\nYour message will be "
- "retransmitted when the private conversation "
- "starts.");
- free(primary);
+ context->lastmessage = gcry_malloc_secure(strlen(message) + 1);
+ if (context->lastmessage) {
+ char *bettermsg = otrl_proto_default_query_msg(accountname,
+ policy);
+ strcpy(context->lastmessage, message);
+ context->lastsent = time(NULL);
+ context->may_retransmit = 2;
+ if (bettermsg) {
+ *messagep = bettermsg;
+ } else {
+ return gcry_error(GPG_ERR_ENOMEM);
+ }
+ }
+ } else {
+ if ((policy & OTRL_POLICY_SEND_WHITESPACE_TAG) &&
+ context->otr_offer != OFFER_REJECTED) {
+ /* See if this user can speak OTR. Append the
+ * OTR_MESSAGE_TAG to the plaintext message, and see
+ * if he responds. */
+ size_t msglen = strlen(message);
+ size_t basetaglen = strlen(OTRL_MESSAGE_TAG_BASE);
+ size_t v1taglen = (policy & OTRL_POLICY_ALLOW_V1) ?
+ strlen(OTRL_MESSAGE_TAG_V1) : 0;
+ size_t v2taglen = (policy & OTRL_POLICY_ALLOW_V2) ?
+ strlen(OTRL_MESSAGE_TAG_V2) : 0;
+ char *taggedmsg = malloc(msglen + basetaglen + v1taglen
+ +v2taglen + 1);
+ if (taggedmsg) {
+ strcpy(taggedmsg, message);
+ strcpy(taggedmsg + msglen, OTRL_MESSAGE_TAG_BASE);
+ if (v1taglen) {
+ strcpy(taggedmsg + msglen + basetaglen,
+ OTRL_MESSAGE_TAG_V1);
+ }
+ if (v2taglen) {
+ strcpy(taggedmsg + msglen + basetaglen + v1taglen,
+ OTRL_MESSAGE_TAG_V2);
+ }
+ *messagep = taggedmsg;
+ if (context) {
+ context->otr_offer = OFFER_SENT;
+ }
+ }
}
}
- }
- context->lastmessage = gcry_malloc_secure(strlen(message) + 1);
- if (context->lastmessage) {
- char *bettermsg = otrl_proto_default_query_msg(accountname);
- strcpy(context->lastmessage, message);
- context->lastsent = time(NULL);
- context->may_retransmit = 2;
- if (bettermsg) {
- *messagep = bettermsg;
- return gcry_error(GPG_ERR_NO_ERROR);
+ break;
+ case OTRL_MSGSTATE_ENCRYPTED:
+ /* Create the new, encrypted message */
+ err = otrl_proto_create_data(&msgtosend, context, message, tlvs);
+ if (!err) {
+ context->lastsent = time(NULL);
+ *messagep = msgtosend;
} else {
- return gcry_error(GPG_ERR_ENOMEM);
+ /* Uh, oh. Whatever we do, *don't* send the message in the
+ * clear. */
+ *messagep = strdup("?OTR Error: Error occurred encrypting "
+ "message");
+ if ((!(ops->display_otr_message) ||
+ ops->display_otr_message(opdata, accountname,
+ protocol, recipient, "An error occurred when "
+ "encrypting your message. The message was not "
+ "sent.")) && ops->notify) {
+ ops->notify(opdata, OTRL_NOTIFY_ERROR,
+ accountname, protocol, recipient,
+ "Error encrypting message",
+ "An error occurred when encrypting your message",
+ "The message was not sent.");
+ }
+ if (!(*messagep)) {
+ return gcry_error(GPG_ERR_ENOMEM);
+ }
}
- }
- }
-
- if (policy == OTRL_POLICY_OPPORTUNISTIC || policy == OTRL_POLICY_ALWAYS) {
- if (context->state == CONN_UNCONNECTED &&
- context->otr_offer != OFFER_REJECTED) {
- /* See if this user can speak OTR. Append the OTR_MESSAGE_TAG
- * to the plaintext message, and see if he responds. */
- size_t msglen = strlen(message);
- char *taggedmsg = malloc(msglen + strlen(OTR_MESSAGE_TAG) + 1);
- if (taggedmsg) {
- strcpy(taggedmsg, message);
- strcpy(taggedmsg + msglen, OTR_MESSAGE_TAG);
- *messagep = taggedmsg;
- if (context) {
- context->otr_offer = OFFER_SENT;
+ break;
+ case OTRL_MSGSTATE_FINISHED:
+ *messagep = strdup("");
+ if ((!(ops->display_otr_message) ||
+ ops->display_otr_message(opdata, accountname,
+ protocol, recipient, "Your message was not sent. "
+ "Either end your private conversation, or restart "
+ "it.")) && ops->notify) {
+ const char *fmt = "%s has already closed his private "
+ "connection to you";
+ char *primary = malloc(strlen(fmt) + strlen(recipient) - 1);
+ if (primary) {
+ sprintf(primary, fmt, recipient);
+ ops->notify(opdata, OTRL_NOTIFY_ERROR,
+ accountname, protocol, recipient,
+ "Private connection closed", primary,
+ "Your message was not sent. Either close your "
+ "private connection to him, or refresh it.");
}
}
- }
- }
-
- /* If we're not going to encrypt anything, just return here. */
- if (context->state != CONN_CONNECTED) {
- return gcry_error(GPG_ERR_NO_ERROR);
- }
-
- /* If the other side has disconnected, inform the user and don't
- * send the message. */
- if (context->their_keyid == 0) {
- *messagep = strdup("");
- if (ops->notify) {
- const char *fmt = "%s has already closed his private connection "
- "to you";
- char *primary = malloc(strlen(fmt) + strlen(recipient) - 1);
- if (primary) {
- sprintf(primary, fmt, recipient);
- ops->notify(opdata, OTRL_NOTIFY_ERROR,
- accountname, protocol, recipient,
- "Private connection closed", primary,
- "Your message was not sent. Either close your "
- "private connection to him, or refresh it.");
+ if (!(*messagep)) {
+ return gcry_error(GPG_ERR_ENOMEM);
}
- }
- if (!(*messagep)) {
- return gcry_error(GPG_ERR_ENOMEM);
- }
- return gcry_error(GPG_ERR_NO_ERROR);
+ break;
}
- /* Create the new, encrypted message */
- err = otrl_proto_create_data(&msgtosend, context, message, tlvs);
- if (!err) {
- context->lastsent = time(NULL);
- *messagep = msgtosend;
- } else {
- /* Uh, oh. Whatever we do, *don't* send the message in the
- * clear. */
- *messagep = strdup("?OTR Error: Error occurred encrypting message");
- if (ops->notify) {
- ops->notify(opdata, OTRL_NOTIFY_ERROR,
- accountname, protocol, recipient,
- "Error encrypting message",
- "An error occurred when encrypting your message",
- "The message was not sent.");
- }
- if (!(*messagep)) {
- return gcry_error(GPG_ERR_ENOMEM);
- }
- }
- return err;
+ return gcry_error(GPG_ERR_NO_ERROR);
}
-/* If err == 0, send the message to the given user. Otherwise, display
- * an appripriate error dialog. Return the value of err that was
- * passed. */
-static gcry_error_t send_or_error(const OtrlMessageAppOps *ops, void *opdata,
- gcry_error_t err, const char *accountname, const char *protocol,
- const char *who, const char *msg)
+/* If err == 0, send the last auth message for the given context to the
+ * appropriate user. Otherwise, display an appripriate error dialog.
+ * Return the value of err that was passed. */
+static gcry_error_t send_or_error_auth(const OtrlMessageAppOps *ops,
+ void *opdata, gcry_error_t err, ConnContext *context)
{
if (!err) {
+ const char *msg = context->auth.lastauthmsg;
if (msg && *msg) {
if (ops->inject_message) {
- ops->inject_message(opdata, accountname, protocol, who, msg);
+ ops->inject_message(opdata, context->accountname,
+ context->protocol, context->username, msg);
}
}
} else {
- const char *buf_format = "Error creating OTR Key "
- "Exchange Message: %s";
- const char *strerr = gcry_strerror(err);
- char *buf = malloc(strlen(buf_format) + strlen(strerr) - 1);
+ const char *buf_format = "Error setting up private conversation: %s";
+ const char *strerr;
+ char *buf;
+
+ switch(gcry_err_code(err)) {
+ case GPG_ERR_INV_VALUE:
+ strerr = "Malformed message received";
+ break;
+ default:
+ strerr = gcry_strerror(err);
+ break;
+ }
+ buf = malloc(strlen(buf_format) + strlen(strerr) - 1);
if (buf) {
sprintf(buf, buf_format, strerr);
}
- if (ops->notify) {
- ops->notify(opdata, OTRL_NOTIFY_ERROR, accountname, protocol,
- who, "OTR error", buf, NULL);
+ if ((!(ops->display_otr_message) ||
+ ops->display_otr_message(opdata, context->accountname,
+ context->protocol, context->username, buf))
+ && ops->notify) {
+ ops->notify(opdata, OTRL_NOTIFY_ERROR, context->accountname,
+ context->protocol, context->username, "OTR error",
+ buf, NULL);
}
free(buf);
}
return err;
}
-/* Return 1 if this Key Exchange Message caused us to rekey; that is,
- * either we were in CONN_CONNECTED, and we're again in CONN_CONNECTED,
- * but with new keys, or else we weren't in CONN_CONNECTED before and
- * now we are. */
-static int process_kem(OtrlUserState us, const OtrlMessageAppOps *ops,
- void *opdata, ConnContext *context, Fingerprint *fprint,
- OTRKeyExchangeMsg kem)
+typedef struct {
+ int gone_encrypted;
+ OtrlUserState us;
+ const OtrlMessageAppOps *ops;
+ void *opdata;
+ ConnContext *context;
+ int ignore_message;
+ char **messagep;
+} EncrData;
+
+static gcry_error_t go_encrypted(const OtrlAuthInfo *auth, void *asdata)
{
- gcry_error_t err;
- char *msgtosend;
- ConnectionState state = context->state;
- unsigned int generation = context->generation;
- int retval = 0;
-
- if (fprint == NULL) {
- /* We now need to add this fingerprint */
- int added = 0;
- fprint = otrl_context_find_fingerprint(context, kem->key_fingerprint,
- 1, &added);
- if (added) {
- /* This may not be the case, in the event that multiple
- * dialogs for the same fingerprint are open at the same
- * time. */
- if (ops->write_fingerprints) {
- ops->write_fingerprints(opdata);
- }
+ EncrData *edata = asdata;
+ gcry_error_t err = gcry_error(GPG_ERR_NO_ERROR);
+ Fingerprint *found_print = NULL;
+ int fprint_added = 0;
+
+ /* See if we're talking to ourselves */
+ if (!gcry_mpi_cmp(auth->their_pub, auth->our_dh.pub)) {
+ /* Yes, we are. */
+ if ((!(edata->ops->display_otr_message) ||
+ edata->ops->display_otr_message(edata->opdata,
+ edata->context->accountname, edata->context->protocol,
+ edata->context->username,
+ "We are receiving our own OTR messages. "
+ "You are either trying to talk to yourself, "
+ "or someone is reflecting your messages back "
+ "at you.")) && edata->ops->notify) {
+ edata->ops->notify(edata->opdata, OTRL_NOTIFY_ERROR,
+ edata->context->accountname, edata->context->protocol,
+ edata->context->username, "OTR Error",
+ "We are receiving our own OTR messages.",
+ "You are either trying to talk to yourself, "
+ "or someone is reflecting your messages back "
+ "at you.");
}
- }
-
- /* OK, we've received a Key Exchange Message, with a known
- * fingerprint. */
- err = otrl_proto_accept_key_exchange(us, context, fprint, kem, &msgtosend,
- ops->create_privkey, opdata);
- send_or_error(ops, opdata, err, context->accountname, context->protocol,
- context->username, msgtosend);
- free(msgtosend);
- if (ops->update_context_list) {
- ops->update_context_list(opdata);
+ edata->ignore_message = 1;
+ return gcry_error(GPG_ERR_NO_ERROR);
}
- /* See if we need to inform the user of a change to a secure state */
- if ((state != CONN_CONNECTED && context->state == CONN_CONNECTED)
- || generation != context->generation) {
- if (ops->gone_secure) {
- ops->gone_secure(opdata, context);
+ found_print = otrl_context_find_fingerprint(edata->context,
+ edata->context->auth.their_fingerprint, 1, &fprint_added);
+
+ if (fprint_added) {
+ /* Inform the user of the new fingerprint */
+ if (edata->ops->new_fingerprint) {
+ edata->ops->new_fingerprint(edata->opdata, edata->us,
+ edata->context->accountname, edata->context->protocol,
+ edata->context->username,
+ edata->context->auth.their_fingerprint);
+ }
+ /* Arrange that the new fingerprint be written to disk */
+ if (edata->ops->write_fingerprints) {
+ edata->ops->write_fingerprints(edata->opdata);
}
- retval = 1;
}
- /* See if we need to inform the user of a change out of a secure
- * state. (This should only happen if we're in the CONNECTED state,
- * the correspondent has disconnected (and lost session state), we
- * receive a new Key Exchange message from him, but there's some
- * sort of error when setting up our new connection state. */
- if (state == CONN_CONNECTED && context->state != CONN_CONNECTED) {
- if (ops->gone_insecure) {
- ops->gone_insecure(opdata, context);
+ /* Is this a new session or just a refresh of an existing one? */
+ if (edata->context->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
+ edata->context->our_keyid - 1 == edata->context->auth.our_keyid &&
+ !gcry_mpi_cmp(edata->context->our_old_dh_key.pub,
+ edata->context->auth.our_dh.pub) &&
+ ((edata->context->their_keyid > 0 &&
+ edata->context->their_keyid ==
+ edata->context->auth.their_keyid &&
+ !gcry_mpi_cmp(edata->context->their_y,
+ edata->context->auth.their_pub)) ||
+ (edata->context->their_keyid > 1 &&
+ edata->context->their_keyid - 1 ==
+ edata->context->auth.their_keyid &&
+ edata->context->their_old_y != NULL &&
+ !gcry_mpi_cmp(edata->context->their_old_y,
+ edata->context->auth.their_pub)))) {
+ /* This is just a refresh of the existing session. */
+ if (edata->ops->still_secure) {
+ edata->ops->still_secure(edata->opdata, edata->context,
+ edata->context->auth.initiated,
+ edata->context->auth.protocol_version);
}
+ edata->ignore_message = 1;
+ return gcry_error(GPG_ERR_NO_ERROR);
}
- /* See if we need to inform the user that a Key Exchange has been
- * received, but it's just for the (unchanged) old connection. */
- if (state == CONN_CONNECTED && context->state == CONN_CONNECTED &&
- generation == context->generation) {
- if (ops->still_secure) {
- ops->still_secure(opdata, context, kem->is_reply);
- }
+ /* Copy the information from the auth into the context */
+ memmove(edata->context->sessionid,
+ edata->context->auth.secure_session_id, 20);
+ edata->context->sessionid_len =
+ edata->context->auth.secure_session_id_len;
+ edata->context->sessionid_half =
+ edata->context->auth.session_id_half;
+
+ edata->context->their_keyid = edata->context->auth.their_keyid;
+ gcry_mpi_release(edata->context->their_y);
+ gcry_mpi_release(edata->context->their_old_y);
+ edata->context->their_y = gcry_mpi_copy(edata->context->auth.their_pub);
+ edata->context->their_old_y = NULL;
+
+ if (edata->context->our_keyid - 1 != edata->context->auth.our_keyid ||
+ gcry_mpi_cmp(edata->context->our_old_dh_key.pub,
+ edata->context->auth.our_dh.pub)) {
+ otrl_dh_keypair_free(&(edata->context->our_dh_key));
+ otrl_dh_keypair_free(&(edata->context->our_old_dh_key));
+ otrl_dh_keypair_copy(&(edata->context->our_old_dh_key),
+ &(edata->context->auth.our_dh));
+ otrl_dh_gen_keypair(edata->context->our_old_dh_key.groupid,
+ &(edata->context->our_dh_key));
+ edata->context->our_keyid = edata->context->auth.our_keyid + 1;
+ }
+
+ /* Create the session keys from the DH keys */
+ otrl_dh_session_free(&(edata->context->sesskeys[0][0]));
+ err = otrl_dh_session(&(edata->context->sesskeys[0][0]),
+ &(edata->context->our_dh_key), edata->context->their_y);
+ if (err) return err;
+ otrl_dh_session_free(&(edata->context->sesskeys[1][0]));
+ err = otrl_dh_session(&(edata->context->sesskeys[1][0]),
+ &(edata->context->our_old_dh_key), edata->context->their_y);
+ if (err) return err;
+
+ edata->context->generation++;
+ edata->context->active_fingerprint = found_print;
+ edata->context->msgstate = OTRL_MSGSTATE_ENCRYPTED;
+
+ if (edata->ops->update_context_list) {
+ edata->ops->update_context_list(edata->opdata);
}
+ if (edata->ops->gone_secure) {
+ edata->ops->gone_secure(edata->opdata, edata->context,
+ edata->context->auth.protocol_version);
+ }
+
+ edata->gone_encrypted = 1;
+
+ return gpg_error(GPG_ERR_NO_ERROR);
+}
- return retval;
+static void maybe_resend(EncrData *edata)
+{
+ gcry_error_t err;
+ time_t now;
+
+ if (!edata->gone_encrypted) return;
+
+ /* See if there's a message we sent recently that should be resent. */
+ now = time(NULL);
+ if (edata->context->lastmessage != NULL &&
+ edata->context->may_retransmit &&
+ edata->context->lastsent >= (now - RESEND_INTERVAL)) {
+ char *resendmsg;
+ int resending = (edata->context->may_retransmit == 1);
+
+ /* Re-encrypt the message with the new keys */
+ err = otrl_proto_create_data(&resendmsg,
+ edata->context, edata->context->lastmessage, NULL);
+ if (!err) {
+ const char *format = "<b>The last message "
+ "to %s was resent.</b>";
+ char *buf;
+
+ /* Resend the message */
+ if (edata->ops->inject_message) {
+ edata->ops->inject_message(edata->opdata,
+ edata->context->accountname, edata->context->protocol,
+ edata->context->username, resendmsg);
+ }
+ free(resendmsg);
+ edata->context->lastsent = now;
+
+ if (!resending) {
+ /* We're actually just sending it
+ * for the first time. */
+ edata->ignore_message = 1;
+ } else {
+ /* Let the user know we resent it */
+ buf = malloc(strlen(format) +
+ strlen(edata->context->username) - 1);
+ if (buf) {
+ sprintf(buf, format, edata->context->username);
+ if (edata->ops->display_otr_message) {
+ if (!edata->ops->display_otr_message(
+ edata->opdata, edata->context->accountname,
+ edata->context->protocol,
+ edata->context->username, buf)) {
+ edata->ignore_message = 1;
+ }
+ }
+ if (edata->ignore_message != 1) {
+ *(edata->messagep) = buf;
+ edata->ignore_message = 0;
+ } else {
+ free(buf);
+ }
+ }
+ }
+ }
+ }
}
/* Handle a message just received from the network. It is safe to pass
@@ -370,14 +514,14 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
void (*add_appdata)(void *data, ConnContext *context),
void *data)
{
- ConnContext * context;
- OTRMessageType msgtype;
+ ConnContext *context;
+ OtrlMessageType msgtype;
int context_added = 0;
- ConnectionState state;
+ OtrlMessageState msgstate;
OtrlPolicy policy = OTRL_POLICY_DEFAULT;
- int ignore_message = -1;
int fragment_assembled = 0;
char *unfragmessage = NULL;
+ EncrData edata;
if (!accountname || !protocol || !sender || !message || !newmessagep)
return 0;
@@ -400,7 +544,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
}
/* Should we go on at all? */
- if (policy == OTRL_POLICY_NEVER) {
+ if ((policy & OTRL_POLICY_VERSION_MASK) == 0) {
return 0;
}
@@ -423,195 +567,212 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
/* What type of message is it? Note that this just checks the
* header; it's not necessarily a _valid_ message of this type. */
msgtype = otrl_proto_message_type(message);
- state = context->state;
+ msgstate = context->msgstate;
/* See if they responded to our OTR offer */
- if (policy == OTRL_POLICY_OPPORTUNISTIC || policy == OTRL_POLICY_ALWAYS) {
- if (msgtype != OTR_NOTOTR) {
+ if ((policy & OTRL_POLICY_SEND_WHITESPACE_TAG)) {
+ if (msgtype != OTRL_MSGTYPE_NOTOTR) {
context->otr_offer = OFFER_ACCEPTED;
} else if (context->otr_offer == OFFER_SENT) {
context->otr_offer = OFFER_REJECTED;
}
}
+ edata.gone_encrypted = 0;
+ edata.us = us;
+ edata.context = context;
+ edata.ops = ops;
+ edata.opdata = opdata;
+ edata.ignore_message = -1;
+ edata.messagep = newmessagep;
+
switch(msgtype) {
- char *tag;
- case OTR_QUERY:
- switch(state) {
- char *msgtosend;
- gcry_error_t err;
- case CONN_UNCONNECTED:
- case CONN_SETUP:
- err = otrl_proto_create_key_exchange(us, &msgtosend,
- context, 0, ops->create_privkey, opdata);
-
- if (!send_or_error(ops, opdata, err, accountname,
- protocol, sender, msgtosend)) {
- context->state = CONN_SETUP;
- if (ops->update_context_list) {
- ops->update_context_list(opdata);
+ unsigned int bestversion;
+ const char *startwhite, *endwhite;
+ DH_keypair *our_dh;
+ unsigned int our_keyid;
+ OtrlPrivKey *privkey;
+ gcry_error_t err;
+ int haveauthmsg;
+ case OTRL_MSGTYPE_QUERY:
+ /* See if we should use an existing DH keypair, or generate
+ * a fresh one. */
+ if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+ our_dh = &(context->our_old_dh_key);
+ our_keyid = context->our_keyid - 1;
+ } else {
+ our_dh = NULL;
+ our_keyid = 0;
+ }
+
+ /* Find the best version of OTR that we both speak */
+ switch(otrl_proto_query_bestversion(message, policy)) {
+ case 2:
+ err = otrl_auth_start_v2(&(context->auth), our_dh,
+ our_keyid);
+ send_or_error_auth(ops, opdata, err, context);
+ break;
+ case 1:
+ /* Get our private key */
+ privkey = otrl_privkey_find(us, context->accountname,
+ context->protocol);
+ if (privkey == NULL) {
+ /* We've got no private key! */
+ if (ops->create_privkey) {
+ ops->create_privkey(opdata, context->accountname,
+ context->protocol);
+ privkey = otrl_privkey_find(us,
+ context->accountname, context->protocol);
}
}
- free(msgtosend);
+ if (privkey) {
+ err = otrl_auth_start_v1(&(context->auth), our_dh,
+ our_keyid, privkey);
+ send_or_error_auth(ops, opdata, err, context);
+ }
break;
- case CONN_CONNECTED:
- /* Just reply with a Key Exchange message, but stay
- * in the CONNECTED state. */
- err = otrl_proto_create_key_exchange(us, &msgtosend,
- context, 0, ops->create_privkey, opdata);
- send_or_error(ops, opdata, err, accountname, protocol,
- sender, msgtosend);
- free(msgtosend);
+ default:
+ /* Just ignore this message */
break;
}
/* Don't display the Query message to the user. */
- if (ignore_message == -1) ignore_message = 1;
+ if (edata.ignore_message == -1) edata.ignore_message = 1;
break;
- case OTR_KEYEXCH:
- switch(state) {
- gcry_error_t err;
- OTRKeyExchangeMsg kem;
- Fingerprint *found_print;
- case CONN_UNCONNECTED:
- case CONN_SETUP:
- case CONN_CONNECTED:
- /* Even if we're currently CONNECTED, receiving a
- * Key Exchange message means the other side has
- * lost the connection for some reason. (Or else
- * that we sent them an explicit OTR Query message.)
- * Just accept the message as usual (with all the
- * fingerprint checks, and
- * otrl_proto_accept_key_exchange() will deal with
- * keeping the state consistent. */
- err = otrl_proto_parse_key_exchange(&kem, message);
- found_print = NULL;
- if (err) {
- const char *buf_format = "We received a malformed "
- "Key Exchange message from %s.";
- char *buf = malloc(strlen(buf_format) + strlen(sender)
- - 1);
- if (buf) {
- sprintf(buf, buf_format, sender);
- }
- if (ops->notify) {
- ops->notify(opdata, OTRL_NOTIFY_ERROR,
- accountname, protocol, sender,
- "OTR Error", buf, NULL);
- }
- free(buf);
- ignore_message = 1;
- break;
+ case OTRL_MSGTYPE_DH_COMMIT:
+ if ((policy & OTRL_POLICY_ALLOW_V2)) {
+ /* See if we should use an existing DH keypair, or generate
+ * a fresh one. */
+ if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+ our_dh = &(context->our_old_dh_key);
+ our_keyid = context->our_keyid - 1;
+ } else {
+ our_dh = NULL;
+ our_keyid = 0;
+ }
+
+ err = otrl_auth_handle_commit(&(context->auth), message,
+ our_dh, our_keyid);
+ send_or_error_auth(ops, opdata, err, context);
+ }
+
+ if (edata.ignore_message == -1) edata.ignore_message = 1;
+ break;
+
+ case OTRL_MSGTYPE_DH_KEY:
+ if ((policy & OTRL_POLICY_ALLOW_V2)) {
+ /* Get our private key */
+ privkey = otrl_privkey_find(us, context->accountname,
+ context->protocol);
+ if (privkey == NULL) {
+ /* We've got no private key! */
+ if (ops->create_privkey) {
+ ops->create_privkey(opdata, context->accountname,
+ context->protocol);
+ privkey = otrl_privkey_find(us,
+ context->accountname, context->protocol);
}
+ }
+ if (privkey) {
+ err = otrl_auth_handle_key(&(context->auth), message,
+ &haveauthmsg, privkey);
+ if (err || haveauthmsg) {
+ send_or_error_auth(ops, opdata, err, context);
+ }
+ }
+ }
- /* See if we're talking to ourselves */
- if ((context->state == CONN_SETUP ||
- context->state == CONN_CONNECTED) &&
- (!gcry_mpi_cmp(kem->dh_pubkey,
- context->our_old_dh_key.pub)))
- {
- /* Yes, we are. */
- if (ops->notify) {
- ops->notify(opdata, OTRL_NOTIFY_ERROR,
- accountname, protocol, sender, "OTR Error",
- "We are receiving our own OTR messages.",
- "You are either trying to talk to yourself, "
- "or someone is reflecting your messages back "
- "at you.");
- }
- ignore_message = 1;
- break;
+ if (edata.ignore_message == -1) edata.ignore_message = 1;
+ break;
+
+ case OTRL_MSGTYPE_REVEALSIG:
+ if ((policy & OTRL_POLICY_ALLOW_V2)) {
+ /* Get our private key */
+ privkey = otrl_privkey_find(us, context->accountname,
+ context->protocol);
+ if (privkey == NULL) {
+ /* We've got no private key! */
+ if (ops->create_privkey) {
+ ops->create_privkey(opdata, context->accountname,
+ context->protocol);
+ privkey = otrl_privkey_find(us,
+ context->accountname, context->protocol);
+ }
+ }
+ if (privkey) {
+ err = otrl_auth_handle_revealsig(&(context->auth),
+ message, &haveauthmsg, privkey, go_encrypted,
+ &edata);
+ if (err || haveauthmsg) {
+ send_or_error_auth(ops, opdata, err, context);
+ maybe_resend(&edata);
}
+ }
+ }
- found_print = otrl_context_find_fingerprint(context,
- kem->key_fingerprint, 0, NULL);
+ if (edata.ignore_message == -1) edata.ignore_message = 1;
+ break;
- if (!found_print) {
- /* Inform the user of the new fingerprint */
- if (ops->new_fingerprint) {
- ops->new_fingerprint(opdata, us,
- accountname, protocol, sender, kem);
- }
- process_kem(us, ops, opdata, context, NULL, kem);
- otrl_proto_free_key_exchange(kem);
- } else {
- time_t now;
- int rekeyed = process_kem(us, ops, opdata,
- context, found_print, kem);
- otrl_proto_free_key_exchange(kem);
-
- /* If we just rekeyed in response to a Key
- * Exchange Message, see if there's a message
- * we sent recently that should be resent. */
- if (rekeyed) now = time(NULL);
- if (rekeyed && context->lastmessage != NULL &&
- context->their_keyid > 0 &&
- context->may_retransmit &&
- context->lastsent >= (now - RESEND_INTERVAL)) {
- char *resendmsg;
- int resending = (context->may_retransmit == 1);
-
- /* Re-encrypt the message with the new keys */
- err = otrl_proto_create_data(&resendmsg,
- context, context->lastmessage, NULL);
- if (!err) {
- const char *format = "<b>The last message "
- "to %s was resent.</b>";
- char *buf;
+ case OTRL_MSGTYPE_SIGNATURE:
+ if ((policy & OTRL_POLICY_ALLOW_V2)) {
+ err = otrl_auth_handle_signature(&(context->auth),
+ message, &haveauthmsg, go_encrypted, &edata);
+ if (err || haveauthmsg) {
+ send_or_error_auth(ops, opdata, err, context);
+ maybe_resend(&edata);
+ }
+ }
+
+ if (edata.ignore_message == -1) edata.ignore_message = 1;
+ break;
- /* Resend the message */
- if (ops->inject_message) {
- ops->inject_message(opdata, accountname,
- protocol, sender, resendmsg);
- }
- free(resendmsg);
- context->lastsent = now;
+ case OTRL_MSGTYPE_V1_KEYEXCH:
+ if ((policy & OTRL_POLICY_ALLOW_V1)) {
+ /* See if we should use an existing DH keypair, or generate
+ * a fresh one. */
+ if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+ our_dh = &(context->our_old_dh_key);
+ our_keyid = context->our_keyid - 1;
+ } else {
+ our_dh = NULL;
+ our_keyid = 0;
+ }
- if (!resending) {
- /* We're actually just sending it
- * for the first time. */
- ignore_message = 1;
- } else {
- /* Let the user know we resent it */
- buf = malloc(strlen(format) +
- strlen(context->username) - 1);
- if (buf) {
- sprintf(buf, format,
- context->username);
- if (ops->display_otr_message) {
- if (!ops->display_otr_message(
- opdata, accountname,
- protocol, sender,
- buf)) {
- ignore_message = 1;
- }
- }
- if (ignore_message != 1) {
- *newmessagep = buf;
- ignore_message = 0;
- } else {
- free(buf);
- }
- }
- }
- }
- }
+ /* Get our private key */
+ privkey = otrl_privkey_find(us, context->accountname,
+ context->protocol);
+ if (privkey == NULL) {
+ /* We've got no private key! */
+ if (ops->create_privkey) {
+ ops->create_privkey(opdata, context->accountname,
+ context->protocol);
+ privkey = otrl_privkey_find(us,
+ context->accountname, context->protocol);
}
- break;
+ }
+ if (privkey) {
+ err = otrl_auth_handle_v1_key_exchange(&(context->auth),
+ message, &haveauthmsg, privkey, our_dh, our_keyid,
+ go_encrypted, &edata);
+ if (err || haveauthmsg) {
+ send_or_error_auth(ops, opdata, err, context);
+ maybe_resend(&edata);
+ }
+ }
}
- /* Don't deliver the Key Exchange message to the user */
- if (ignore_message == -1) ignore_message = 1;
+
+ if (edata.ignore_message == -1) edata.ignore_message = 1;
break;
- case OTR_DATA:
- switch(state) {
+
+ case OTRL_MSGTYPE_DATA:
+ switch(context->msgstate) {
gcry_error_t err;
OtrlTLV *tlvs;
char *plaintext;
char *buf;
char *format;
- char *msgtosend;
- case CONN_UNCONNECTED:
- case CONN_SETUP:
+ case OTRL_MSGSTATE_PLAINTEXT:
+ case OTRL_MSGSTATE_FINISHED:
/* Don't use g_strdup_printf here, because someone
* (not us) is going to free() the *newmessagep pointer,
* not g_free() it. */
@@ -625,12 +786,12 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
if (ops->display_otr_message) {
if (!ops->display_otr_message(opdata, accountname,
protocol, sender, buf)) {
- ignore_message = 1;
+ edata.ignore_message = 1;
}
}
- if (ignore_message != 1) {
+ if (edata.ignore_message != 1) {
*newmessagep = buf;
- ignore_message = 0;
+ edata.ignore_message = 0;
} else {
free(buf);
}
@@ -648,33 +809,24 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
free(buf);
}
- if (policy == OTRL_POLICY_OPPORTUNISTIC ||
- policy == OTRL_POLICY_ALWAYS) {
- /* Send a key exchange message to try to start up
- * the secure conversation */
- err = otrl_proto_create_key_exchange(us, &msgtosend,
- context, 0, ops->create_privkey, opdata);
- if (!send_or_error(ops, opdata, err, accountname,
- protocol, sender, msgtosend)) {
- context->state = CONN_SETUP;
- if (ops->update_context_list) {
- ops->update_context_list(opdata);
- }
- }
- free(msgtosend);
- }
-
break;
- case CONN_CONNECTED:
+
+ case OTRL_MSGSTATE_ENCRYPTED:
err = otrl_proto_accept_data(&plaintext, &tlvs, context,
message);
if (err) {
- format = "We received a malformed "
- "data message from %s.";
+ int is_conflict =
+ (gpg_err_code(err) == GPG_ERR_CONFLICT);
+ format = is_conflict ? "We received an unreadable "
+ "encrypted messahe from %s." :
+ "We received a malformed data message from %s.";
buf = malloc(strlen(format) + strlen(sender) - 1);
if (buf) {
sprintf(buf, format, sender);
- if (ops->notify) {
+ if ((!(ops->display_otr_message) ||
+ ops->display_otr_message(opdata,
+ accountname, protocol, sender,
+ buf)) && ops->notify) {
ops->notify(opdata, OTRL_NOTIFY_ERROR,
accountname, protocol, sender,
"OTR Error", buf, NULL);
@@ -683,10 +835,13 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
}
if (ops->inject_message) {
ops->inject_message(opdata, accountname, protocol,
- sender, "?OTR Error: You transmitted "
+ sender, is_conflict ? "?OTR Error: "
+ "You transmitted an unreadable "
+ "encrypted message." :
+ "?OTR Error: You transmitted "
"a malformed data message");
}
- ignore_message = 1;
+ edata.ignore_message = 1;
break;
}
@@ -694,7 +849,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
* private connection, make a note of that so we
* don't try sending anything else to him. */
if (otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED)) {
- context->their_keyid = 0;
+ otrl_context_force_finished(context);
}
if (plaintext[0] == '\0') {
@@ -709,8 +864,8 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
}
free(buf);
}
- ignore_message = 1;
- } else if (ignore_message == 0 &&
+ edata.ignore_message = 1;
+ } else if (edata.ignore_message == 0 &&
context->their_keyid > 0) {
/* If it's *not* a heartbeat, and we haven't
* sent anything in a while, also send a
@@ -755,43 +910,34 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
otrl_tlv_free(tlvs);
}
- if (ignore_message != 1) {
+ if (edata.ignore_message != 1) {
*newmessagep = plaintext;
- ignore_message = 0;
+ edata.ignore_message = 0;
} else {
free(plaintext);
}
break;
}
break;
- case OTR_ERROR:
- switch(state) {
- gcry_error_t err;
- char *msgtosend;
- case CONN_UNCONNECTED:
- case CONN_SETUP:
- if (policy == OTRL_POLICY_OPPORTUNISTIC ||
- policy == OTRL_POLICY_ALWAYS) {
- /* The other end clearly supports OTR, so try to
- * start up a private conversation */
- err = otrl_proto_create_key_exchange(us, &msgtosend,
- context, 0, ops->create_privkey, opdata);
- if (!send_or_error(ops, opdata, err, accountname,
- protocol, sender, msgtosend)) {
- context->state = CONN_SETUP;
- if (ops->update_context_list) {
- ops->update_context_list(opdata);
- }
- }
- free(msgtosend);
- }
- break;
- case CONN_CONNECTED:
- /* Mark the last message we sent as eligible for
- * retransmission */
- context->may_retransmit = 1;
- break;
+
+ case OTRL_MSGTYPE_ERROR:
+ if ((policy & OTRL_POLICY_ERROR_START_AKE)) {
+ char *msgtosend = otrl_proto_default_query_msg(
+ context->accountname, policy);
+ if (msgtosend && ops->inject_message) {
+ ops->inject_message(opdata, context->accountname,
+ context->protocol, context->username,
+ msgtosend);
+ }
+ free(msgtosend);
+ }
+
+ if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+ /* Mark the last message we sent as eligible for
+ * retransmission */
+ context->may_retransmit = 1;
}
+
/* In any event, display the error message, with the
* display_otr_message callback, if possible */
if (ops->display_otr_message) {
@@ -804,99 +950,97 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
}
if (!ops->display_otr_message(opdata, accountname, protocol,
sender, otrerror)) {
- ignore_message = 1;
+ edata.ignore_message = 1;
}
}
break;
- case OTR_TAGGEDPLAINTEXT:
+
+ case OTRL_MSGTYPE_TAGGEDPLAINTEXT:
/* Strip the tag from the message */
- tag = strstr(message, OTR_MESSAGE_TAG);
- if (tag) {
- size_t taglen = strlen(OTR_MESSAGE_TAG);
- size_t restlen = strlen(tag + taglen);
- memmove(tag, tag+taglen, restlen+1);
+ bestversion = otrl_proto_whitespace_bestversion(message,
+ &startwhite, &endwhite, policy);
+ if (startwhite && endwhite) {
+ size_t restlen = strlen(endwhite);
+ char *strippedmsg = strdup(message);
+
+ if (strippedmsg) {
+ memmove(strippedmsg + (startwhite - message),
+ strippedmsg + (endwhite - message), restlen+1);
+ *newmessagep = strippedmsg;
+ edata.ignore_message = 0;
+ }
}
- /* FALLTHROUGH */
- case OTR_NOTOTR:
- switch(state) {
- char *buf;
- char *format;
- case CONN_UNCONNECTED:
- case CONN_SETUP:
- if (policy == OTRL_POLICY_OPPORTUNISTIC ||
- policy == OTRL_POLICY_ALWAYS) {
- if (msgtype == OTR_TAGGEDPLAINTEXT) {
- /* Send a Key Exchange in response */
-
- char *msgtosend;
- gcry_error_t err;
-
- err = otrl_proto_create_key_exchange(us,
- &msgtosend, context, 0,
- ops->create_privkey, opdata);
-
- if (!send_or_error(ops, opdata, err, accountname,
- protocol, sender, msgtosend)) {
- context->state = CONN_SETUP;
- if (ops->update_context_list) {
- ops->update_context_list(opdata);
- }
+ if (bestversion && context->msgstate != OTRL_MSGSTATE_ENCRYPTED
+ && (policy & OTRL_POLICY_WHITESPACE_START_AKE)) {
+ switch(bestversion) {
+ case 2:
+ err = otrl_auth_start_v2(&(context->auth), NULL, 0);
+ send_or_error_auth(ops, opdata, err, context);
+ break;
+ case 1:
+ /* Get our private key */
+ privkey = otrl_privkey_find(us, context->accountname,
+ context->protocol);
+ if (privkey == NULL) {
+ /* We've got no private key! */
+ if (ops->create_privkey) {
+ ops->create_privkey(opdata,
+ context->accountname,
+ context->protocol);
+ privkey = otrl_privkey_find(us,
+ context->accountname,
+ context->protocol);
}
- free(msgtosend);
}
- }
-
- /* If the policy is ALWAYS, we must warn about
- * receiving an unencrypted message, so just
- * FALLTHROUGH. */
-
- if (policy != OTRL_POLICY_ALWAYS) {
- /* Just display the message. */
+ if (privkey) {
+ err = otrl_auth_start_v1(&(context->auth), NULL, 0,
+ privkey);
+ send_or_error_auth(ops, opdata, err, context);
+ }
break;
- }
- case CONN_CONNECTED:
- /* Not fine. Let both users know. */
+ default:
+ /* Don't start the AKE */
+ break;
+ }
+ }
- /* Don't use g_strdup_printf here, because someone
- * (not us) is going to free() the *message pointer,
- * not g_free() it. */
- format = "<b>The following message received from %s was "
- "<i>not</i> encrypted: [</b>%s<b>]</b>";
- buf = malloc(strlen(format) + strlen(context->username)
- + strlen(message) - 3);
- /* Remove "%s%s", add username + message + '\0' */
- if (buf) {
- sprintf(buf, format, context->username, message);
- if (ops->display_otr_message) {
- if (!ops->display_otr_message(opdata, accountname,
- protocol, sender, buf)) {
- ignore_message = 1;
- }
- }
- if (ignore_message != 1) {
- *newmessagep = buf;
- ignore_message = 0;
- } else {
- free(buf);
+ /* FALLTHROUGH */
+ case OTRL_MSGTYPE_NOTOTR:
+ if (context->msgstate != OTRL_MSGSTATE_PLAINTEXT ||
+ (policy & OTRL_POLICY_REQUIRE_ENCRYPTION)) {
+ /* Not fine. Let the user know. */
+
+ /* Don't use g_strdup_printf here, because someone
+ * (not us) is going to free() the *message pointer,
+ * not g_free() it. */
+ const char *plainmsg = (*newmessagep) ? *newmessagep : message;
+ const char *format = "<b>The following message received "
+ "from %s was <i>not</i> encrypted: [</b>%s<b>]</b>";
+ char *buf = malloc(strlen(format) + strlen(context->username)
+ + strlen(plainmsg) - 3);
+ /* Remove "%s%s", add username + message + '\0' */
+ if (buf) {
+ sprintf(buf, format, context->username, plainmsg);
+ if (ops->display_otr_message) {
+ if (!ops->display_otr_message(opdata, accountname,
+ protocol, sender, buf)) {
+ free(*newmessagep);
+ *newmessagep = NULL;
+ edata.ignore_message = 1;
}
}
- format = "?OTR Error: You sent unencrypted data to %s, "
- "who was expecting encrypted messages from you.";
- buf = malloc(strlen(format) + strlen(context->accountname)
- - 1);
- if (buf) {
- sprintf(buf, format, context->accountname);
- if (ops->inject_message) {
- ops->inject_message(opdata, accountname, protocol,
- sender, buf);
- }
+ if (edata.ignore_message != 1) {
+ free(*newmessagep);
+ *newmessagep = buf;
+ edata.ignore_message = 0;
+ } else {
free(buf);
}
-
- break;
+ }
}
break;
- case OTR_UNKNOWN:
+
+ case OTRL_MSGTYPE_UNKNOWN:
/* We received an OTR message we didn't recognize. Ignore
* it, but make a log entry. */
if (ops->log_message) {
@@ -909,7 +1053,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
free(buf);
}
}
- if (ignore_message == -1) ignore_message = 1;
+ if (edata.ignore_message == -1) edata.ignore_message = 1;
break;
}
@@ -919,12 +1063,12 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
free(unfragmessage);
}
- if (ignore_message == -1) ignore_message = 0;
- return ignore_message;
+ if (edata.ignore_message == -1) edata.ignore_message = 0;
+ return edata.ignore_message;
}
-/* Put a connection into the UNCONNECTED state, first sending the
- * other side a notice that we're doing so if we're currently CONNECTED,
+/* Put a connection into the PLAINTEXT state, first sending the
+ * other side a notice that we're doing so if we're currently ENCRYPTED,
* and we think he's logged in. */
void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
void *opdata, const char *accountname, const char *protocol,
@@ -935,7 +1079,8 @@ void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
if (!context) return;
- if (context->state == CONN_CONNECTED && context->their_keyid > 0 &&
+ if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
+ context->their_keyid > 0 &&
ops->is_logged_in &&
ops->is_logged_in(opdata, accountname, protocol, username) == 1) {
if (ops->inject_message) {
@@ -952,7 +1097,7 @@ void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
}
}
- otrl_context_force_disconnect(context);
+ otrl_context_force_plaintext(context);
if (ops->update_context_list) {
ops->update_context_list(opdata);
}
diff --git a/src/message.h b/src/message.h
index 2a47ec3..986aa80 100644
--- a/src/message.h
+++ b/src/message.h
@@ -26,15 +26,6 @@ typedef enum {
OTRL_NOTIFY_INFO
} OtrlNotifyLevel;
-typedef enum {
- OTRL_POLICY_OPPORTUNISTIC,
- OTRL_POLICY_NEVER,
- OTRL_POLICY_MANUAL,
- OTRL_POLICY_ALWAYS
-} OtrlPolicy;
-
-#define OTRL_POLICY_DEFAULT OTRL_POLICY_OPPORTUNISTIC
-
typedef struct s_OtrlMessageAppOps {
/* Return the OTR policy for the given context. */
OtrlPolicy (*policy)(void *opdata, ConnContext *context);
@@ -69,7 +60,8 @@ typedef struct s_OtrlMessageAppOps {
* protocol / username conversation. Return 0 if you are able to
* successfully display it. If you return non-0 (or if this
* function is NULL), the control message will be displayed inline,
- * as a received message. */
+ * as a received message, or else by using the above notify()
+ * callback. */
int (*display_otr_message)(void *opdata, const char *accountname,
const char *protocol, const char *username, const char *msg);
@@ -87,21 +79,25 @@ typedef struct s_OtrlMessageAppOps {
/* A new fingerprint for the given user has been received. */
void (*new_fingerprint)(void *opdata, OtrlUserState us,
const char *accountname, const char *protocol,
- const char *username, OTRKeyExchangeMsg kem);
+ const char *username, unsigned char fingerprint[20]);
/* The list of known fingerprints has changed. Write them to disk. */
void (*write_fingerprints)(void *opdata);
- /* A ConnContext has entered a secure state. */
- void (*gone_secure)(void *opdata, ConnContext *context);
+ /* A ConnContext has entered a secure state. protocol_version is
+ * the version of the OTR protocol used for the authentication. */
+ void (*gone_secure)(void *opdata, ConnContext *context,
+ int protocol_version);
/* A ConnContext has left a secure state. */
void (*gone_insecure)(void *opdata, ConnContext *context);
- /* A ConnContext has received a Key Exchange Message, which is the
- * same as the one we already knew. is_reply indicates whether the
- * Key Exchange Message is a reply to one that we sent to them. */
- void (*still_secure)(void *opdata, ConnContext *context, int is_reply);
+ /* We have completed an authentication, using the D-H keys we
+ * already knew. is_reply indicates whether we initiated the AKE.
+ * protocol_version is the version of the OTR protocol used for the
+ * authentication. */
+ void (*still_secure)(void *opdata, ConnContext *context, int is_reply,
+ int protocol_version);
/* Log a message. The passed message will end in "\n". */
void (*log_message)(void *opdata, const char *message);
@@ -170,8 +166,8 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
void (*add_appdata)(void *data, ConnContext *context),
void *data);
-/* Put a connection into the DISCONNECTED state, first sending the
- * other side a notice that we're doing so if we're currently CONNECTED,
+/* Put a connection into the PLAINTEXT state, first sending the
+ * other side a notice that we're doing so if we're currently ENCRYPTED,
* and we think he's logged in. */
void otrl_message_disconnect(OtrlUserState us, const OtrlMessageAppOps *ops,
void *opdata, const char *accountname, const char *protocol,
diff --git a/src/version.h b/src/privkey-t.h
similarity index 69%
copy from src/version.h
copy to src/privkey-t.h
index 77187c1..57dc116 100644
--- a/src/version.h
+++ b/src/privkey-t.h
@@ -17,13 +17,23 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
-#ifndef __VERSION_H__
-#define __VERSION_H__
+#ifndef __PRIVKEY_T_H__
+#define __PRIVKEY_T_H__
-#define OTRL_VERSION "3.0.0"
+#include <gcrypt.h>
-#define OTRL_VERSION_MAJOR 3
-#define OTRL_VERSION_MINOR 0
-#define OTRL_VERSION_SUB 0
+typedef struct s_OtrlPrivKey {
+ struct s_OtrlPrivKey *next;
+ struct s_OtrlPrivKey **tous;
+
+ char *accountname;
+ char *protocol;
+ unsigned short pubkey_type;
+ gcry_sexp_t privkey;
+ unsigned char *pubkey_data;
+ size_t pubkey_datalen;
+} OtrlPrivKey;
+
+#define OTRL_PUBKEY_TYPE_DSA 0x0000
#endif
diff --git a/src/privkey.c b/src/privkey.c
index 80879a7..2d4a79d 100644
--- a/src/privkey.c
+++ b/src/privkey.c
@@ -27,8 +27,8 @@
#include <gcrypt.h>
/* libotr headers */
-#include "proto.h"
#include "privkey.h"
+#include "serial.h"
/* Convert a 20-byte hash value to a 45-byte human-readable value */
void otrl_privkey_hash_to_human(char human[45], const unsigned char hash[20])
@@ -55,7 +55,7 @@ char *otrl_privkey_fingerprint(OtrlUserState us, char fingerprint[45],
const char *accountname, const char *protocol)
{
unsigned char hash[20];
- PrivKey *p = otrl_privkey_find(us, accountname, protocol);
+ OtrlPrivKey *p = otrl_privkey_find(us, accountname, protocol);
if (p) {
/* Calculate the hash */
@@ -71,6 +71,87 @@ char *otrl_privkey_fingerprint(OtrlUserState us, char fingerprint[45],
return fingerprint;
}
+/* Create a public key block from a private key */
+static gcry_error_t make_pubkey(unsigned char **pubbufp, size_t *publenp,
+ gcry_sexp_t privkey)
+{
+ gcry_mpi_t p,q,g,y;
+ gcry_sexp_t dsas,ps,qs,gs,ys;
+ size_t np,nq,ng,ny;
+ enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+ unsigned char *bufp;
+ size_t lenp;
+
+ *pubbufp = NULL;
+ *publenp = 0;
+
+ /* Extract the public parameters */
+ dsas = gcry_sexp_find_token(privkey, "dsa", 0);
+ if (dsas == NULL) {
+ return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+ }
+ ps = gcry_sexp_find_token(dsas, "p", 0);
+ qs = gcry_sexp_find_token(dsas, "q", 0);
+ gs = gcry_sexp_find_token(dsas, "g", 0);
+ ys = gcry_sexp_find_token(dsas, "y", 0);
+ gcry_sexp_release(dsas);
+ if (!ps || !qs || !gs || !ys) {
+ gcry_sexp_release(ps);
+ gcry_sexp_release(qs);
+ gcry_sexp_release(gs);
+ gcry_sexp_release(ys);
+ return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+ }
+ p = gcry_sexp_nth_mpi(ps, 1, GCRYMPI_FMT_USG);
+ gcry_sexp_release(ps);
+ q = gcry_sexp_nth_mpi(qs, 1, GCRYMPI_FMT_USG);
+ gcry_sexp_release(qs);
+ g = gcry_sexp_nth_mpi(gs, 1, GCRYMPI_FMT_USG);
+ gcry_sexp_release(gs);
+ y = gcry_sexp_nth_mpi(ys, 1, GCRYMPI_FMT_USG);
+ gcry_sexp_release(ys);
+ if (!p || !q || !g || !y) {
+ gcry_mpi_release(p);
+ gcry_mpi_release(q);
+ gcry_mpi_release(g);
+ gcry_mpi_release(y);
+ return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+ }
+
+ *publenp = 0;
+ gcry_mpi_print(format, NULL, 0, &np, p);
+ *publenp += np + 4;
+ gcry_mpi_print(format, NULL, 0, &nq, q);
+ *publenp += nq + 4;
+ gcry_mpi_print(format, NULL, 0, &ng, g);
+ *publenp += ng + 4;
+ gcry_mpi_print(format, NULL, 0, &ny, y);
+ *publenp += ny + 4;
+
+ *pubbufp = malloc(*publenp);
+ if (*pubbufp == NULL) {
+ gcry_mpi_release(p);
+ gcry_mpi_release(q);
+ gcry_mpi_release(g);
+ gcry_mpi_release(y);
+ return gcry_error(GPG_ERR_ENOMEM);
+ }
+ bufp = *pubbufp;
+ lenp = *publenp;
+
+ write_mpi(p,np,"P");
+ write_mpi(q,nq,"Q");
+ write_mpi(g,ng,"G");
+ write_mpi(y,ny,"Y");
+
+ gcry_mpi_release(p);
+ gcry_mpi_release(q);
+ gcry_mpi_release(g);
+ gcry_mpi_release(y);
+
+ return gcry_error(GPG_ERR_NO_ERROR);
+}
+
/* Read a sets of private DSA keys from a file on disk into the given
* OtrlUserState. */
gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
@@ -134,7 +215,7 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
gcry_sexp_t names, protos, privs;
char *name, *proto;
gcry_sexp_t accounts;
- PrivKey *p;
+ OtrlPrivKey *p;
/* Get the ith "account" S-exp */
accounts = gcry_sexp_nth(allkeys, i);
@@ -143,6 +224,7 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
token = gcry_sexp_nth_data(accounts, 0, &tokenlen);
if (tokenlen != 7 || strncmp(token, "account", 7)) {
gcry_sexp_release(accounts);
+ gcry_sexp_release(allkeys);
return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
}
/* Extract the name, protocol, and privkey S-exps */
@@ -154,6 +236,7 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
gcry_sexp_release(names);
gcry_sexp_release(protos);
gcry_sexp_release(privs);
+ gcry_sexp_release(allkeys);
return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
}
/* Extract the actual name and protocol */
@@ -162,6 +245,7 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
gcry_sexp_release(names);
gcry_sexp_release(protos);
gcry_sexp_release(privs);
+ gcry_sexp_release(allkeys);
return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
}
name = malloc(tokenlen + 1);
@@ -169,6 +253,7 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
gcry_sexp_release(names);
gcry_sexp_release(protos);
gcry_sexp_release(privs);
+ gcry_sexp_release(allkeys);
return gcry_error(GPG_ERR_ENOMEM);
}
memmove(name, token, tokenlen);
@@ -180,6 +265,7 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
free(name);
gcry_sexp_release(protos);
gcry_sexp_release(privs);
+ gcry_sexp_release(allkeys);
return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
}
proto = malloc(tokenlen + 1);
@@ -187,24 +273,27 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
free(name);
gcry_sexp_release(protos);
gcry_sexp_release(privs);
+ gcry_sexp_release(allkeys);
return gcry_error(GPG_ERR_ENOMEM);
}
memmove(proto, token, tokenlen);
proto[tokenlen] = '\0';
gcry_sexp_release(protos);
- /* Make a new PrivKey entry */
+ /* Make a new OtrlPrivKey entry */
p = malloc(sizeof(*p));
if (!p) {
free(name);
free(proto);
gcry_sexp_release(privs);
+ gcry_sexp_release(allkeys);
return gcry_error(GPG_ERR_ENOMEM);
}
/* Fill it in and link it up */
p->accountname = name;
p->protocol = proto;
+ p->pubkey_type = OTRL_PUBKEY_TYPE_DSA;
p->privkey = privs;
p->next = us->privkey_root;
if (p->next) {
@@ -212,13 +301,14 @@ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
}
p->tous = &(us->privkey_root);
us->privkey_root = p;
- err = otrl_proto_make_pubkey(&(p->pubkey_data), &(p->pubkey_datalen),
- p->privkey);
+ err = make_pubkey(&(p->pubkey_data), &(p->pubkey_datalen), p->privkey);
if (err) {
+ gcry_sexp_release(allkeys);
otrl_privkey_forget(p);
return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
}
}
+ gcry_sexp_release(allkeys);
return gcry_error(GPG_ERR_NO_ERROR);
}
@@ -279,7 +369,7 @@ gcry_error_t otrl_privkey_generate(OtrlUserState us, const char *filename,
mode_t oldmask;
#endif
static const char *parmstr = "(genkey (dsa (nbits 4:1024)))";
- PrivKey *p;
+ OtrlPrivKey *p;
/* Create a DSA key */
err = gcry_sexp_new(&parms, parmstr, strlen(parmstr), 0);
@@ -452,10 +542,10 @@ gcry_error_t otrl_privkey_write_fingerprints(OtrlUserState us,
/* Fetch the private key from the given OtrlUserState associated with
* the given account */
-PrivKey *otrl_privkey_find(OtrlUserState us, const char *accountname,
+OtrlPrivKey *otrl_privkey_find(OtrlUserState us, const char *accountname,
const char *protocol)
{
- PrivKey *p;
+ OtrlPrivKey *p;
if (!accountname || !protocol) return NULL;
for(p=us->privkey_root; p; p=p->next) {
@@ -468,7 +558,7 @@ PrivKey *otrl_privkey_find(OtrlUserState us, const char *accountname,
}
/* Forget a private key */
-void otrl_privkey_forget(PrivKey *privkey)
+void otrl_privkey_forget(OtrlPrivKey *privkey)
{
free(privkey->accountname);
free(privkey->protocol);
@@ -492,3 +582,84 @@ void otrl_privkey_forget_all(OtrlUserState us)
otrl_privkey_forget(us->privkey_root);
}
}
+
+/* Sign data using a private key. The data must be small enough to be
+ * signed (i.e. already hashed, if necessary). The signature will be
+ * returned in *sigp, which the caller must free(). Its length will be
+ * returned in *siglenp. */
+gcry_error_t otrl_privkey_sign(unsigned char **sigp, size_t *siglenp,
+ OtrlPrivKey *privkey, const unsigned char *data, size_t len)
+{
+ gcry_mpi_t r,s, datampi;
+ gcry_sexp_t dsas, rs, ss, sigs, datas;
+ size_t nr, ns;
+ const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+
+ if (privkey->pubkey_type != OTRL_PUBKEY_TYPE_DSA)
+ return gcry_error(GPG_ERR_INV_VALUE);
+
+ *sigp = malloc(40);
+ if (sigp == NULL) return gcry_error(GPG_ERR_ENOMEM);
+ *siglenp = 40;
+
+ if (len) {
+ gcry_mpi_scan(&datampi, GCRYMPI_FMT_USG, data, len, NULL);
+ } else {
+ datampi = gcry_mpi_set_ui(NULL, 0);
+ }
+ gcry_sexp_build(&datas, NULL, "(%m)", datampi);
+ gcry_mpi_release(datampi);
+ gcry_pk_sign(&sigs, datas, privkey->privkey);
+ gcry_sexp_release(datas);
+ dsas = gcry_sexp_find_token(sigs, "dsa", 0);
+ gcry_sexp_release(sigs);
+ rs = gcry_sexp_find_token(dsas, "r", 0);
+ ss = gcry_sexp_find_token(dsas, "s", 0);
+ gcry_sexp_release(dsas);
+ r = gcry_sexp_nth_mpi(rs, 1, GCRYMPI_FMT_USG);
+ gcry_sexp_release(rs);
+ s = gcry_sexp_nth_mpi(ss, 1, GCRYMPI_FMT_USG);
+ gcry_sexp_release(ss);
+ gcry_mpi_print(format, NULL, 0, &nr, r);
+ gcry_mpi_print(format, NULL, 0, &ns, s);
+ memset(*sigp, 0, 40);
+ gcry_mpi_print(format, (*sigp)+(20-nr), nr, NULL, r);
+ gcry_mpi_print(format, (*sigp)+20+(20-ns), ns, NULL, s);
+ gcry_mpi_release(r);
+ gcry_mpi_release(s);
+
+ return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/* Verify a signature on data using a public key. The data must be
+ * small enough to be signed (i.e. already hashed, if necessary). */
+gcry_error_t otrl_privkey_verify(const unsigned char *sigbuf, size_t siglen,
+ unsigned short pubkey_type, gcry_sexp_t pubs,
+ const unsigned char *data, size_t len)
+{
+ gcry_error_t err;
+ gcry_mpi_t datampi,r,s;
+ gcry_sexp_t datas, sigs;
+
+ if (pubkey_type != OTRL_PUBKEY_TYPE_DSA || siglen != 40)
+ return gcry_error(GPG_ERR_INV_VALUE);
+
+ if (len) {
+ gcry_mpi_scan(&datampi, GCRYMPI_FMT_USG, data, len, NULL);
+ } else {
+ datampi = gcry_mpi_set_ui(NULL, 0);
+ }
+ gcry_sexp_build(&datas, NULL, "(%m)", datampi);
+ gcry_mpi_release(datampi);
+ gcry_mpi_scan(&r, GCRYMPI_FMT_USG, sigbuf, 20, NULL);
+ gcry_mpi_scan(&s, GCRYMPI_FMT_USG, sigbuf+20, 20, NULL);
+ gcry_sexp_build(&sigs, NULL, "(sig-val (dsa (r %m)(s %m)))", r, s);
+ gcry_mpi_release(r);
+ gcry_mpi_release(s);
+
+ err = gcry_pk_verify(sigs, datas, pubs);
+ gcry_sexp_release(datas);
+ gcry_sexp_release(sigs);
+
+ return err;
+}
diff --git a/src/privkey.h b/src/privkey.h
index d4a399d..7b63e46 100644
--- a/src/privkey.h
+++ b/src/privkey.h
@@ -20,20 +20,7 @@
#ifndef __PRIVKEY_H__
#define __PRIVKEY_H__
-#include <gcrypt.h>
-
-typedef struct s_PrivKey {
- struct s_PrivKey *next;
- struct s_PrivKey **tous;
-
- char *accountname;
- char *protocol;
- gcry_sexp_t privkey;
- unsigned char *pubkey_data;
- size_t pubkey_datalen;
-} PrivKey;
-
-#include "context.h"
+#include "privkey-t.h"
#include "userstate.h"
/* Convert a 20-byte hash value to a 45-byte human-readable value */
@@ -69,13 +56,26 @@ gcry_error_t otrl_privkey_write_fingerprints(OtrlUserState us,
/* Fetch the private key from the given OtrlUserState associated with
* the given account */
-PrivKey *otrl_privkey_find(OtrlUserState us, const char *accountname,
+OtrlPrivKey *otrl_privkey_find(OtrlUserState us, const char *accountname,
const char *protocol);
/* Forget a private key */
-void otrl_privkey_forget(PrivKey *privkey);
+void otrl_privkey_forget(OtrlPrivKey *privkey);
/* Forget all private keys in a given OtrlUserState. */
void otrl_privkey_forget_all(OtrlUserState us);
+/* Sign data using a private key. The data must be small enough to be
+ * signed (i.e. already hashed, if necessary). The signature will be
+ * returned in *sigp, which the caller must free(). Its length will be
+ * returned in *siglenp. */
+gcry_error_t otrl_privkey_sign(unsigned char **sigp, size_t *siglenp,
+ OtrlPrivKey *privkey, const unsigned char *data, size_t len);
+
+/* Verify a signature on data using a public key. The data must be
+ * small enough to be signed (i.e. already hashed, if necessary). */
+gcry_error_t otrl_privkey_verify(const unsigned char *sigbuf, size_t siglen,
+ unsigned short pubkey_type, gcry_sexp_t pubs,
+ const unsigned char *data, size_t len);
+
#endif
diff --git a/src/proto.c b/src/proto.c
index 2ecd24b..53fa11e 100644
--- a/src/proto.c
+++ b/src/proto.c
@@ -35,64 +35,7 @@
#include "mem.h"
#include "version.h"
#include "tlv.h"
-
-#undef DEBUG
-
-#ifdef DEBUG
-static void debug_data(const char *title, const unsigned char *data,
- size_t len)
-{
- size_t i;
- fprintf(stderr, "%s: ", title);
- for(i=0;i<len;++i) {
- fprintf(stderr, "%02x", data[i]);
- }
- fprintf(stderr, "\n");
-}
-
-static void debug_int(const char *title, const unsigned char *data)
-{
- unsigned int v =
- (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
- fprintf(stderr, "%s: %u (0x%x)\n", title, v, v);
-}
-#else
-#define debug_data(t,b,l)
-#define debug_int(t,b)
-#endif
-
-#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,nx,dx) do { \
- write_int(nx); \
- gcry_mpi_print(format, bufp, lenp, NULL, (x)); \
- debug_data((dx), bufp, (nx)); \
- bufp += (nx); lenp -= (nx); \
- } while(0)
-
-#define require_len(l) do { \
- if (lenp < (l)) goto invval; \
- } while(0)
-
-#define read_int(x) do { \
- require_len(4); \
- (x) = (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)
+#include "serial.h"
/* Initialize the OTR library. Pass the version of the API you are
* using. */
@@ -123,87 +66,6 @@ const char *otrl_version(void)
return OTRL_VERSION;
}
-/* Create a public key block from a private key */
-gcry_error_t otrl_proto_make_pubkey(unsigned char **pubbufp, size_t *publenp,
- gcry_sexp_t privkey)
-{
- gcry_mpi_t p,q,g,y;
- gcry_sexp_t dsas,ps,qs,gs,ys;
- size_t np,nq,ng,ny;
- enum gcry_mpi_format format = GCRYMPI_FMT_USG;
- unsigned char *bufp;
- size_t lenp;
-
- *pubbufp = NULL;
- *publenp = 0;
-
- /* Extract the public parameters */
- dsas = gcry_sexp_find_token(privkey, "dsa", 0);
- if (dsas == NULL) {
- return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
- }
- ps = gcry_sexp_find_token(dsas, "p", 0);
- qs = gcry_sexp_find_token(dsas, "q", 0);
- gs = gcry_sexp_find_token(dsas, "g", 0);
- ys = gcry_sexp_find_token(dsas, "y", 0);
- gcry_sexp_release(dsas);
- if (!ps || !qs || !gs || !ys) {
- gcry_sexp_release(ps);
- gcry_sexp_release(qs);
- gcry_sexp_release(gs);
- gcry_sexp_release(ys);
- return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
- }
- p = gcry_sexp_nth_mpi(ps, 1, GCRYMPI_FMT_USG);
- gcry_sexp_release(ps);
- q = gcry_sexp_nth_mpi(qs, 1, GCRYMPI_FMT_USG);
- gcry_sexp_release(qs);
- g = gcry_sexp_nth_mpi(gs, 1, GCRYMPI_FMT_USG);
- gcry_sexp_release(gs);
- y = gcry_sexp_nth_mpi(ys, 1, GCRYMPI_FMT_USG);
- gcry_sexp_release(ys);
- if (!p || !q || !g || !y) {
- gcry_mpi_release(p);
- gcry_mpi_release(q);
- gcry_mpi_release(g);
- gcry_mpi_release(y);
- return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
- }
-
- *publenp = 0;
- gcry_mpi_print(format, NULL, 0, &np, p);
- *publenp += np + 4;
- gcry_mpi_print(format, NULL, 0, &nq, q);
- *publenp += nq + 4;
- gcry_mpi_print(format, NULL, 0, &ng, g);
- *publenp += ng + 4;
- gcry_mpi_print(format, NULL, 0, &ny, y);
- *publenp += ny + 4;
-
- *pubbufp = malloc(*publenp);
- if (*pubbufp == NULL) {
- gcry_mpi_release(p);
- gcry_mpi_release(q);
- gcry_mpi_release(g);
- gcry_mpi_release(y);
- return gcry_error(GPG_ERR_ENOMEM);
- }
- bufp = *pubbufp;
- lenp = *publenp;
-
- write_mpi(p,np,"P");
- write_mpi(q,nq,"Q");
- write_mpi(g,ng,"G");
- write_mpi(y,ny,"Y");
-
- gcry_mpi_release(p);
- gcry_mpi_release(q);
- gcry_mpi_release(g);
- gcry_mpi_release(y);
-
- return gcry_error(GPG_ERR_NO_ERROR);
-}
-
/* Store some MAC keys to be revealed later */
static gcry_error_t reveal_macs(ConnContext *context,
DH_sesskeys *sess1, DH_sesskeys *sess2)
@@ -322,400 +184,157 @@ static gcry_error_t rotate_y_keys(ConnContext *context, gcry_mpi_t new_y)
/* Return a pointer to a newly-allocated OTR query message, customized
* with our name. The caller should free() the result when he's done
* with it. */
-char *otrl_proto_default_query_msg(const char *ourname)
+char *otrl_proto_default_query_msg(const char *ourname, OtrlPolicy policy)
{
char *msg;
+ int v1_supported, v2_supported;
+ const char *version_tag;
/* Don't use g_strdup_printf here, because someone (not us) is going
* to free() the *message pointer, not g_free() it. We can't
* require that they g_free() it, because this pointer will probably
* get passed to the main IM application for processing (and
* free()ing). */
- const char *format = "?OTR?\n<b>%s</b> has requested an "
+ const char *format = "?OTR%s\n<b>%s</b> has requested an "
"<a href=\"http://www.cypherpunks.ca/otr/\">Off-the-Record "
"private conversation</a>. However, you do not have a plugin "
"to support that.\nSee <a href=\"http://www.cypherpunks.ca/otr/\">"
"http://www.cypherpunks.ca/otr/</a> for more information.";
- /* Remove "%s", add '\0' */
- msg = malloc(strlen(format) + strlen(ourname) - 1);
- if (!msg) return NULL;
- sprintf(msg, format, ourname);
- return msg;
-}
-
-/* Return the Message type of the given message. */
-OTRMessageType otrl_proto_message_type(const char *message)
-{
- char *otrtag;
-
- otrtag = strstr(message, "?OTR");
-
- if (!otrtag) {
- if (strstr(message, OTR_MESSAGE_TAG)) {
- return OTR_TAGGEDPLAINTEXT;
+ /* Figure out the version tag */
+ v1_supported = (policy & OTRL_POLICY_ALLOW_V1);
+ v2_supported = (policy & OTRL_POLICY_ALLOW_V2);
+ if (v1_supported) {
+ if (v2_supported) {
+ version_tag = "?v2?";
} else {
- return OTR_NOTOTR;
+ version_tag = "?";
+ }
+ } else {
+ if (v2_supported) {
+ version_tag = "v2?";
+ } else {
+ version_tag = "v?";
}
}
- if (!strncmp(otrtag, "?OTR?", 5)) return OTR_QUERY;
- if (!strncmp(otrtag, "?OTR:AAEK", 9)) return OTR_KEYEXCH;
- if (!strncmp(otrtag, "?OTR:AAED", 9)) return OTR_DATA;
- if (!strncmp(otrtag, "?OTR Error:", 11)) return OTR_ERROR;
-
- return OTR_UNKNOWN;
+ /* Remove two "%s", add '\0' */
+ msg = malloc(strlen(format) + strlen(version_tag) + strlen(ourname) - 3);
+ if (!msg) return NULL;
+ sprintf(msg, format, version_tag, ourname);
+ return msg;
}
-/* Create a Key Exchange message for our correspondent. If we need a
- * private key and don't have one, create_privkey will be called. Use
- * the privkeys from the given OtrlUserState. */
-gcry_error_t otrl_proto_create_key_exchange(OtrlUserState us,
- char **messagep, ConnContext *context, unsigned char is_reply,
- void (*create_privkey)(void *create_privkey_data,
- const char *accountname, const char *protocol),
- void *create_privkey_data)
+/* Return the best version of OTR support by both sides, given an OTR
+ * Query Message and the local policy. */
+unsigned int otrl_proto_query_bestversion(const char *querymsg,
+ OtrlPolicy policy)
{
- gcry_mpi_t r, s;
- gcry_sexp_t dsas, rs, ss;
- gcry_sexp_t sigs, hashs;
- size_t nr, ns, buflen, lenp;
- unsigned char *buf, *bufp;
- enum gcry_mpi_format format = GCRYMPI_FMT_USG;
- unsigned char digest[20];
- gcry_mpi_t digestmpi;
- char *base64buf;
- size_t base64len;
- size_t pubkeylen;
- PrivKey *privkey =
- otrl_privkey_find(us, context->accountname, context->protocol);
-
- *messagep = NULL;
-
- if (privkey == NULL) {
- /* We've got no private key! */
- if (create_privkey) {
- create_privkey(create_privkey_data, context->accountname,
- context->protocol);
- privkey =
- otrl_privkey_find(us, context->accountname, context->protocol);
+ char *otrtag;
+ unsigned int query_versions = 0;
+
+ otrtag = strstr(querymsg, "?OTR");
+ otrtag += 4;
+ if (*otrtag == '?') {
+ query_versions = (1<<0);
+ ++otrtag;
+ }
+ if (*otrtag == 'v') {
+ for(++otrtag; *otrtag && *otrtag != '?'; ++otrtag) {
+ switch(*otrtag) {
+ case '2':
+ query_versions |= (1<<1);
+ break;
+ }
}
}
- if (privkey == NULL) {
- /* We've still got no private key! */
- return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
- }
-
- /* Make sure we have two keys */
- while (context->our_keyid < 2) {
- rotate_dh_keys(context);
- }
- buflen = 3 + 1 + privkey->pubkey_datalen + 4 + 40;
- /* header, is_reply, pubkey, keyid, sig */
- gcry_mpi_print(format, NULL, 0, &pubkeylen, context->our_old_dh_key.pub);
- buflen += pubkeylen + 4;
- buf = malloc(buflen);
- if (buf == NULL) {
- return gcry_error(GPG_ERR_ENOMEM);
+ if ((policy & OTRL_POLICY_ALLOW_V2) && (query_versions & (1<<1))) {
+ return 2;
}
- bufp = buf;
- lenp = buflen;
- memmove(bufp, "\x00\x01\x0a", 3); /* header */
- debug_data("Header", bufp, 3);
- bufp += 3; lenp -= 3;
-
- *bufp = is_reply; /* is_reply */
- debug_data("Reply", bufp, 1);
- bufp += 1; lenp -= 1;
-
- /* DSA pubkey */
- memmove(bufp, privkey->pubkey_data, privkey->pubkey_datalen);
- debug_data("DSA key", bufp, privkey->pubkey_datalen);
- bufp += privkey->pubkey_datalen; lenp -= privkey->pubkey_datalen;
-
- /* keyid */
- write_int(context->our_keyid - 1);
- debug_int("Keyid", bufp - 4);
-
- /* DH pubkey */
- write_mpi(context->our_old_dh_key.pub, pubkeylen, "Pubkey");
-
- /* Get a hash of the data to be signed */
- gcry_md_hash_buffer(GCRY_MD_SHA1, digest, buf, bufp-buf);
- gcry_mpi_scan(&digestmpi, GCRYMPI_FMT_USG, digest, 20, NULL);
-
- /* Calculate the sig */
- gcry_sexp_build(&hashs, NULL, "(%m)", digestmpi);
- gcry_mpi_release(digestmpi);
- gcry_pk_sign(&sigs, hashs, privkey->privkey);
- gcry_sexp_release(hashs);
- dsas = gcry_sexp_find_token(sigs, "dsa", 0);
- gcry_sexp_release(sigs);
- rs = gcry_sexp_find_token(dsas, "r", 0);
- ss = gcry_sexp_find_token(dsas, "s", 0);
- gcry_sexp_release(dsas);
- r = gcry_sexp_nth_mpi(rs, 1, GCRYMPI_FMT_USG);
- gcry_sexp_release(rs);
- s = gcry_sexp_nth_mpi(ss, 1, GCRYMPI_FMT_USG);
- gcry_sexp_release(ss);
- gcry_mpi_print(format, NULL, 0, &nr, r);
- gcry_mpi_print(format, NULL, 0, &ns, s);
- memset(bufp, 0, 40);
- gcry_mpi_print(format, bufp+(20-nr), lenp, NULL, r);
- debug_data("R", bufp, 20);
- bufp += 20; lenp -= 20;
- gcry_mpi_print(format, bufp+(20-ns), lenp, NULL, s);
- debug_data("S", bufp, 20);
- bufp += 20; lenp -= 20;
-
- assert(lenp == 0);
-
- gcry_mpi_release(r);
- gcry_mpi_release(s);
-
- /* Make the base64-encoding. */
- base64len = ((buflen + 2) / 3) * 4;
- base64buf = malloc(5 + base64len + 1 + 1);
- assert(base64buf != NULL);
- memmove(base64buf, "?OTR:", 5);
- otrl_base64_encode(base64buf+5, buf, buflen);
- base64buf[5 + base64len] = '.';
- base64buf[5 + base64len + 1] = '\0';
-
- free(buf);
-
- *messagep = base64buf;
-
- return gcry_error(GPG_ERR_NO_ERROR);
+ if ((policy & OTRL_POLICY_ALLOW_V1) && (query_versions & (1<<0))) {
+ return 1;
+ }
+ return 0;
}
-/* Deallocate an OTRKeyExchangeMsg returned from proto_parse_key_exchange */
-void otrl_proto_free_key_exchange(OTRKeyExchangeMsg kem)
+/* Locate any whitespace tag in this message, and return the best
+ * version of OTR support on both sides. Set *starttagp and *endtagp to
+ * the start and end of the located tag, so that it can be snipped out. */
+unsigned int otrl_proto_whitespace_bestversion(const char *msg,
+ const char **starttagp, const char **endtagp, OtrlPolicy policy)
{
- if (!kem) return;
- gcry_sexp_release(kem->digest_sexp);
- gcry_sexp_release(kem->dsa_pubkey);
- gcry_mpi_release(kem->dh_pubkey);
- gcry_sexp_release(kem->dsa_sig);
- free(kem);
-}
+ const char *starttag, *endtag;
+ unsigned int query_versions = 0;
-/* Parse a purported Key Exchange message. Possible error code portions
- * of the return value:
- * GPG_ERR_NO_ERROR: Success
- * GPG_ERR_ENOMEM: Out of memory condition
- * GPG_ERR_INV_VALUE: The message was not a well-formed Key Exchange
- * message
- * GPG_ERR_BAD_SIGNATURE: The signature on the message didn't verify
- */
-gcry_error_t otrl_proto_parse_key_exchange(OTRKeyExchangeMsg *kemp,
- const char *msg)
-{
- char *otrtag, *endtag;
- unsigned char *rawmsg, *bufp;
- size_t msglen, rawlen, lenp;
- gcry_mpi_t p,q,g,e,r,s;
- unsigned char hash_of_msg[20];
- gcry_mpi_t hashmpi;
- const unsigned char *fingerprintstart, *fingerprintend;
- const unsigned char *signaturestart, *signatureend;
- OTRKeyExchangeMsg kem = calloc(1, sizeof(struct s_OTRKeyExchangeMsg));
-
- if (!kem) {
- *kemp = NULL;
- return gcry_error(GPG_ERR_ENOMEM);
- }
+ *starttagp = NULL;
+ *endtagp = NULL;
- otrtag = strstr(msg, "?OTR:");
- if (!otrtag) {
- *kemp = NULL;
- otrl_proto_free_key_exchange(kem);
- return gcry_error(GPG_ERR_INV_VALUE);
- }
- endtag = strchr(otrtag, '.');
- if (endtag) {
- msglen = endtag-otrtag;
- } else {
- msglen = strlen(otrtag);
- }
-
- /* Base64-decode the message */
- rawlen = ((msglen-5) / 4) * 3; /* maximum possible */
- rawmsg = malloc(rawlen);
- if (!rawmsg && rawlen > 0) {
- *kemp = NULL;
- otrl_proto_free_key_exchange(kem);
- return gcry_error(GPG_ERR_ENOMEM);
- }
- rawlen = otrl_base64_decode(rawmsg, otrtag+5, msglen-5); /* actual size */
+ starttag = strstr(msg, OTRL_MESSAGE_TAG_BASE);
+ if (!starttag) return 0;
- bufp = rawmsg;
- lenp = rawlen;
-
- signaturestart = bufp;
-
- require_len(3);
- if (memcmp(bufp, "\x00\x01\x0a", 3)) {
- /* Invalid header */
- goto invval;
- }
- bufp += 3; lenp -= 3;
+ endtag = starttag + strlen(OTRL_MESSAGE_TAG_BASE);
- require_len(1);
- kem->is_reply = *bufp;
- if (kem->is_reply != 0 && kem->is_reply != 1) {
- /* Malformed is_reply field */
- goto invval;
- }
- bufp += 1; lenp -= 1;
-
- fingerprintstart = bufp;
- /* Read the DSA public key and calculate its fingerprint. */
- read_mpi(p);
- read_mpi(q);
- read_mpi(g);
- read_mpi(e);
- fingerprintend = bufp;
- gcry_md_hash_buffer(GCRY_MD_SHA1, kem->key_fingerprint,
- fingerprintstart, fingerprintend-fingerprintstart);
-
- /* Create the pubkey S-expression. */
- gcry_sexp_build(&(kem->dsa_pubkey), NULL,
- "(public-key (dsa (p %m)(q %m)(g %m)(y %m)))",
- p, q, g, e);
- gcry_mpi_release(p);
- gcry_mpi_release(q);
- gcry_mpi_release(g);
- gcry_mpi_release(e);
-
- /* Read the key id */
- read_int(kem->keyid);
- if (kem->keyid == 0) {
- /* Not a legal value */
- goto invval;
+ /* Look for groups of 8 spaces and/or tabs */
+ while(1) {
+ int i;
+ int allwhite = 1;
+ for(i=0;i<8;++i) {
+ if (endtag[i] != ' ' && endtag[i] != '\t') {
+ allwhite = 0;
+ break;
+ }
+ }
+ if (allwhite) {
+ if (!strncmp(endtag, OTRL_MESSAGE_TAG_V1, 8)) {
+ query_versions |= (1<<0);
+ }
+ if (!strncmp(endtag, OTRL_MESSAGE_TAG_V2, 8)) {
+ query_versions |= (1<<1);
+ }
+ endtag += 8;
+ } else {
+ break;
+ }
}
- /* Read the DH public key */
- read_mpi(kem->dh_pubkey);
-
- /* Hash the message so we can check the signature */
- signatureend = bufp;
- gcry_md_hash_buffer(GCRY_MD_SHA1, hash_of_msg,
- signaturestart, signatureend-signaturestart);
- /* Turn the hash into an mpi, then into a sexp */
- gcry_mpi_scan(&hashmpi, GCRYMPI_FMT_USG, hash_of_msg, 20, NULL);
- gcry_sexp_build(&(kem->digest_sexp), NULL, "(%m)", hashmpi);
- gcry_mpi_release(hashmpi);
-
- /* Read the signature */
- require_len(40);
- gcry_mpi_scan(&r, GCRYMPI_FMT_USG, bufp, 20, NULL);
- gcry_mpi_scan(&s, GCRYMPI_FMT_USG, bufp+20, 20, NULL);
- lenp -= 40;
- gcry_sexp_build(&(kem->dsa_sig), NULL, "(sig-val (dsa (r %m)(s %m)))",
- r, s);
- gcry_mpi_release(r);
- gcry_mpi_release(s);
-
- /* That should be everything */
- if (lenp != 0) goto invval;
+ *starttagp = starttag;
+ *endtagp = endtag;
- /* Verify the signature */
- if (gcry_pk_verify(kem->dsa_sig, kem->digest_sexp, kem->dsa_pubkey)) {
- /* It failed! */
- otrl_proto_free_key_exchange(kem);
- free(rawmsg);
- *kemp = NULL;
- return gcry_error(GPG_ERR_BAD_SIGNATURE);
+ if ((policy & OTRL_POLICY_ALLOW_V2) && (query_versions & (1<<1))) {
+ return 2;
}
-
- free(rawmsg);
- *kemp = kem;
- return gcry_error(GPG_ERR_NO_ERROR);
-invval:
- otrl_proto_free_key_exchange(kem);
- free(rawmsg);
- *kemp = NULL;
- return gcry_error(GPG_ERR_INV_VALUE);
+ if ((policy & OTRL_POLICY_ALLOW_V1) && (query_versions & (1<<0))) {
+ return 1;
+ }
+ return 0;
}
-/* Deal with a Key Exchange Message once it's been received and passed
- * all the validity and UI ("accept this fingerprint?") tests.
- * context/fprint is the ConnContext and Fingerprint to which it
- * belongs. Use the given OtrlUserState to look up any necessary
- * private keys. It is the caller's responsibility to
- * otrl_proto_free_key_exchange(kem) when we're done. If *messagep gets
- * set to non-NULL by this function, then it's a message that needs to
- * get sent to the correspondent. If we need a private key and don't
- * have one, create_privkey will be called. */
-gcry_error_t otrl_proto_accept_key_exchange(OtrlUserState us,
- ConnContext *context, Fingerprint *fprint, OTRKeyExchangeMsg kem,
- char **messagep,
- void (*create_privkey)(void *create_privkey_data,
- const char *accountname, const char *protocol),
- void *create_privkey_data)
+/* Return the Message type of the given message. */
+OtrlMessageType otrl_proto_message_type(const char *message)
{
- gcry_error_t err;
- char *savedmessage = context->lastmessage;
- int savedmay_retransmit = context->may_retransmit;
- time_t savedtime = context->lastsent;
- ConnectionState state = context->state;
- *messagep = NULL;
- context->lastmessage = NULL;
+ char *otrtag;
- switch(state) {
- case CONN_CONNECTED:
- if (kem->is_reply == 0) {
- /* Send a Key Exchange message to the correspondent */
- err = otrl_proto_create_key_exchange(us, messagep, context, 1,
- create_privkey, create_privkey_data);
- if (err) return err;
- }
- if (context->their_keyid > 0 &&
- ((kem->keyid == context->their_keyid &&
- !gcry_mpi_cmp(kem->dh_pubkey, context->their_y))
- || (kem->keyid == (context->their_keyid - 1) &&
- !gcry_mpi_cmp(kem->dh_pubkey, context->their_old_y)))) {
- /* We've already seen this key of theirs, so all is
- * good. */
- break;
- }
- /* It's an entirely different session; our correspondent has
- * gone away and come back. */
- otrl_context_force_setup(context);
-
- /* FALLTHROUGH */
- case CONN_UNCONNECTED:
- case CONN_SETUP:
- if (state == CONN_UNCONNECTED ||
- (state == CONN_SETUP && kem->is_reply == 0)) {
- /* Send a Key Exchange message to the correspondent */
- err = otrl_proto_create_key_exchange(us, messagep, context, 1,
- create_privkey, create_privkey_data);
- if (err) return err;
- }
- context->their_keyid = kem->keyid;
- gcry_mpi_release(context->their_y);
- context->their_y = gcry_mpi_copy(kem->dh_pubkey);
- err = otrl_dh_session(&(context->sesskeys[0][0]),
- &(context->our_dh_key), context->their_y);
- if (err) return err;
- err = otrl_dh_session(&(context->sesskeys[1][0]),
- &(context->our_old_dh_key), context->their_y);
- if (err) return err;
- context->state = CONN_CONNECTED;
- memmove(context->sessionid, context->sesskeys[1][0].dhsecureid,
- 20);
- context->sessiondir = context->sesskeys[1][0].dir;
- context->active_fingerprint = fprint;
- context->generation++;
- context->lastmessage = savedmessage;
- context->may_retransmit = savedmay_retransmit;
- context->lastsent = savedtime;
- break;
+ otrtag = strstr(message, "?OTR");
+
+ if (!otrtag) {
+ if (strstr(message, OTRL_MESSAGE_TAG_BASE)) {
+ return OTRL_MSGTYPE_TAGGEDPLAINTEXT;
+ } else {
+ return OTRL_MSGTYPE_NOTOTR;
+ }
}
- return gcry_error(GPG_ERR_NO_ERROR);
+ if (!strncmp(otrtag, "?OTR?", 5)) return OTRL_MSGTYPE_QUERY;
+ if (!strncmp(otrtag, "?OTRv", 5)) return OTRL_MSGTYPE_QUERY;
+ if (!strncmp(otrtag, "?OTR:AAIC", 9)) return OTRL_MSGTYPE_DH_COMMIT;
+ if (!strncmp(otrtag, "?OTR:AAIK", 9)) return OTRL_MSGTYPE_DH_KEY;
+ if (!strncmp(otrtag, "?OTR:AAIR", 9)) return OTRL_MSGTYPE_REVEALSIG;
+ if (!strncmp(otrtag, "?OTR:AAIS", 9)) return OTRL_MSGTYPE_SIGNATURE;
+ if (!strncmp(otrtag, "?OTR:AAEK", 9)) return OTRL_MSGTYPE_V1_KEYEXCH;
+ if (!strncmp(otrtag, "?OTR:AAED", 9)) return OTRL_MSGTYPE_DATA;
+ if (!strncmp(otrtag, "?OTR Error:", 11)) return OTRL_MSGTYPE_ERROR;
+
+ return OTRL_MSGTYPE_UNKNOWN;
}
/* Create an OTR Data message. Pass the plaintext as msg, and an
@@ -741,7 +360,8 @@ gcry_error_t otrl_proto_create_data(char **encmessagep, ConnContext *context,
char *msgdup;
/* Make sure we're actually supposed to be able to encrypt */
- if (context->state != CONN_CONNECTED || context->their_keyid == 0) {
+ if (context->msgstate != OTRL_MSGSTATE_ENCRYPTED ||
+ context->their_keyid == 0) {
return gcry_error(GPG_ERR_CONFLICT);
}
@@ -1101,7 +721,7 @@ OtrlFragmentResult otrl_proto_fragment_accumulate(char **unfragmessagep,
res = OTRL_FRAGMENT_COMPLETE;
}
} else {
- /* Unfragmented message, so discard anyy fragment we may have */
+ /* Unfragmented message, so discard any fragment we may have */
free(context->fragment);
context->fragment = NULL;
context->fragment_len = 0;
diff --git a/src/proto.h b/src/proto.h
index 86f5fba..03adecf 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -26,19 +26,57 @@
/* If we ever see this sequence in a plaintext message, we'll assume the
* other side speaks OTR, and try to establish a connection. */
-#define OTR_MESSAGE_TAG " \t \t\t\t\t \t \t \t \t \t \t "
+#define OTRL_MESSAGE_TAG_BASE " \t \t\t\t\t \t \t \t "
+/* The following must each be of length 8 */
+#define OTRL_MESSAGE_TAG_V1 " \t \t \t "
+#define OTRL_MESSAGE_TAG_V2 " \t\t \t "
+
/* This is the bit sequence of the string "OTR", encoded in tabs and
* spaces. */
+typedef unsigned int OtrlPolicy;
+
+#define OTRL_POLICY_ALLOW_V1 0x01
+#define OTRL_POLICY_ALLOW_V2 0x02
+#define OTRL_POLICY_REQUIRE_ENCRYPTION 0x04
+#define OTRL_POLICY_SEND_WHITESPACE_TAG 0x08
+#define OTRL_POLICY_WHITESPACE_START_AKE 0x08
+#define OTRL_POLICY_ERROR_START_AKE 0x10
+
+#define OTRL_POLICY_VERSION_MASK (OTRL_POLICY_ALLOW_V1 | OTRL_POLICY_ALLOW_V2)
+
+/* For v1 compatibility */
+#define OTRL_POLICY_NEVER 0x00
+#define OTRL_POLICY_OPPORTUNISTIC \
+ ( OTRL_POLICY_ALLOW_V1 | \
+ OTRL_POLICY_ALLOW_V2 | \
+ OTRL_POLICY_SEND_WHITESPACE_TAG | \
+ OTRL_POLICY_WHITESPACE_START_AKE | \
+ OTRL_POLICY_ERROR_START_AKE )
+#define OTRL_POLICY_MANUAL \
+ ( OTRL_POLICY_ALLOW_V1 | \
+ OTRL_POLICY_ALLOW_V2 )
+#define OTRL_POLICY_ALWAYS \
+ ( OTRL_POLICY_ALLOW_V1 | \
+ OTRL_POLICY_ALLOW_V2 | \
+ OTRL_POLICY_REQUIRE_ENCRYPTION | \
+ OTRL_POLICY_WHITESPACE_START_AKE | \
+ OTRL_POLICY_ERROR_START_AKE )
+#define OTRL_POLICY_DEFAULT OTRL_POLICY_OPPORTUNISTIC
+
typedef enum {
- OTR_NOTOTR,
- OTR_TAGGEDPLAINTEXT,
- OTR_QUERY,
- OTR_KEYEXCH,
- OTR_DATA,
- OTR_ERROR,
- OTR_UNKNOWN
-} OTRMessageType;
+ OTRL_MSGTYPE_NOTOTR,
+ OTRL_MSGTYPE_TAGGEDPLAINTEXT,
+ OTRL_MSGTYPE_QUERY,
+ OTRL_MSGTYPE_DH_COMMIT,
+ OTRL_MSGTYPE_DH_KEY,
+ OTRL_MSGTYPE_REVEALSIG,
+ OTRL_MSGTYPE_SIGNATURE,
+ OTRL_MSGTYPE_V1_KEYEXCH,
+ OTRL_MSGTYPE_DATA,
+ OTRL_MSGTYPE_ERROR,
+ OTRL_MSGTYPE_UNKNOWN
+} OtrlMessageType;
typedef enum {
OTRL_FRAGMENT_UNFRAGMENTED,
@@ -46,20 +84,6 @@ typedef enum {
OTRL_FRAGMENT_COMPLETE
} OtrlFragmentResult;
-typedef struct s_OTRKeyExchangeMsg {
- gcry_sexp_t digest_sexp; /* SHA-1 hash of the raw message,
- except for the DSA sig; used
- for checking the sig */
- unsigned char is_reply; /* Was this a reply to a Key
- Exchange Message we sent
- them? */
- unsigned char key_fingerprint[20]; /* The key fingerprint */
- gcry_sexp_t dsa_pubkey; /* DSA public key */
- unsigned int keyid; /* DH key id */
- gcry_mpi_t dh_pubkey; /* DH public key */
- gcry_sexp_t dsa_sig; /* Signature on packet */
-} * OTRKeyExchangeMsg;
-
/* Initialize the OTR library. Pass the version of the API you are
* using. */
void otrl_init(unsigned int ver_major, unsigned int ver_minor,
@@ -74,56 +98,24 @@ void otrl_init(unsigned int ver_major, unsigned int ver_minor,
* the OTR library. */
const char *otrl_version(void);
-/* Create a public key block from a private key */
-gcry_error_t otrl_proto_make_pubkey(unsigned char **pubbufp, size_t *publenp,
- gcry_sexp_t privkey);
-
/* Return a pointer to a newly-allocated OTR query message, customized
* with our name. The caller should free() the result when he's done
* with it. */
-char *otrl_proto_default_query_msg(const char *ourname);
+char *otrl_proto_default_query_msg(const char *ourname, OtrlPolicy policy);
+
+/* Return the best version of OTR support by both sides, given an OTR
+ * Query Message and the local policy. */
+unsigned int otrl_proto_query_bestversion(const char *querymsg,
+ OtrlPolicy policy);
+
+/* Locate any whitespace tag in this message, and return the best
+ * version of OTR support on both sides. Set *starttagp and *endtagp to
+ * the start and end of the located tag, so that it can be snipped out. */
+unsigned int otrl_proto_whitespace_bestversion(const char *msg,
+ const char **starttagp, const char **endtagp, OtrlPolicy policy);
/* Return the Message type of the given message. */
-OTRMessageType otrl_proto_message_type(const char *message);
-
-/* Create a Key Exchange message for our correspondent. If we need a
- * private key and don't have one, create_privkey will be called. Use
- * the privkeys from the given OtrlUserState. */
-gcry_error_t otrl_proto_create_key_exchange(OtrlUserState us,
- char **messagep, ConnContext *context, unsigned char is_reply,
- void (*create_privkey)(void *create_privkey_data,
- const char *accountname, const char *protocol),
- void *create_privkey_data);
-
-/* Deallocate an OTRKeyExchangeMsg returned from proto_parse_key_exchange */
-void otrl_proto_free_key_exchange(OTRKeyExchangeMsg kem);
-
-/* Parse a purported Key Exchange message. Possible error code portions
- * of the return value:
- * GPG_ERR_NO_ERROR: Success
- * GPG_ERR_ENOMEM: Out of memory condition
- * GPG_ERR_INV_VALUE: The message was not a well-formed Key Exchange
- * message
- * GPG_ERR_BAD_SIGNATURE: The signature on the message didn't verify
- */
-gcry_error_t otrl_proto_parse_key_exchange(OTRKeyExchangeMsg *kemp,
- const char *msg);
-
-/* Deal with a Key Exchange Message once it's been received and passed
- * all the validity and UI ("accept this fingerprint?") tests.
- * context/fprint is the ConnContext and Fingerprint to which it
- * belongs. Use the given OtrlUserState to look up any necessary
- * private keys. It is the caller's responsibility to
- * otrl_proto_free_key_exchange(kem) when we're done. If *messagep gets
- * set to non-NULL by this function, then it's a message that needs to
- * get sent to the correspondent. If we need a private key and don't
- * have one, create_privkey will be called. */
-gcry_error_t otrl_proto_accept_key_exchange(OtrlUserState us,
- ConnContext *context, Fingerprint *fprint, OTRKeyExchangeMsg kem,
- char **messagep,
- void (*create_privkey)(void *create_privkey_data,
- const char *accountname, const char *protocol),
- void *create_privkey_data);
+OtrlMessageType otrl_proto_message_type(const char *message);
/* Create an OTR Data message. Pass the plaintext as msg, and an
* optional chain of TLVs. A newly-allocated string will be returned in
diff --git a/src/serial.h b/src/serial.h
new file mode 100644
index 0000000..981a9e4
--- /dev/null
+++ b/src/serial.h
@@ -0,0 +1,85 @@
+/*
+ * Off-the-Record Messaging library
+ * Copyright (C) 2004-2005 Nikita Borisov and Ian Goldberg
+ * <otr at 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __SERIAL_H__
+#define __SERIAL_H__
+
+#undef DEBUG
+
+#ifdef DEBUG
+
+#include <stdio.h>
+
+#define debug_data(t,b,l) do { const unsigned char *data = (b); size_t i; \
+ fprintf(stderr, "%s: ", (t)); \
+ for(i=0;i<(l);++i) { \
+ fprintf(stderr, "%02x", data[i]); \
+ } \
+ fprintf(stderr, "\n"); \
+ } while(0)
+
+#define debug_int(t,b) do { const unsigned char *data = (b); \
+ unsigned int v = \
+ (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; \
+ fprintf(stderr, "%s: %u (0x%x)\n", (t), v, v); \
+ } while(0)
+
+#else
+#define debug_data(t,b,l)
+#define debug_int(t,b)
+#endif
+
+#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,nx,dx) do { \
+ write_int(nx); \
+ gcry_mpi_print(format, bufp, lenp, NULL, (x)); \
+ debug_data((dx), bufp, (nx)); \
+ bufp += (nx); lenp -= (nx); \
+ } while(0)
+
+#define require_len(l) do { \
+ if (lenp < (l)) goto invval; \
+ } while(0)
+
+#define read_int(x) do { \
+ require_len(4); \
+ (x) = (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); \
+ if (mpilen) { \
+ require_len(mpilen); \
+ gcry_mpi_scan(&(x), GCRYMPI_FMT_USG, bufp, mpilen, NULL); \
+ } else { \
+ (x) = gcry_mpi_set_ui(NULL, 0); \
+ } \
+ bufp += mpilen; lenp -= mpilen; \
+ } while(0)
+
+#endif
diff --git a/src/tests.c b/src/tests.c
new file mode 100644
index 0000000..ef6e72a
--- /dev/null
+++ b/src/tests.c
@@ -0,0 +1,199 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "proto.h"
+#include "privkey.h"
+#include "message.h"
+
+#define ALICE "oneeyedian"
+#define BOB "otr4ian"
+#define PROTO "prpl-oscar"
+
+static OtrlPolicy ALICEPOLICY = OTRL_POLICY_DEFAULT &~ OTRL_POLICY_ALLOW_V1;
+static OtrlPolicy BOBPOLICY = OTRL_POLICY_DEFAULT;
+
+void receiving(const char *from, const char *to, const char *msg);
+
+typedef struct s_node {
+ struct s_node *next;
+ char *from, *to, *msg;
+} MsgNode;
+
+static MsgNode *noderoot = NULL;
+static MsgNode **nodeend = &noderoot;
+
+static void dispatch(void)
+{
+ while(noderoot) {
+ MsgNode *node = noderoot;
+
+ receiving(node->from, node->to, node->msg);
+ free(node->from);
+ free(node->to);
+ free(node->msg);
+ noderoot = node->next;
+ free(node);
+ if (noderoot == NULL) nodeend = &noderoot;
+ }
+}
+
+static void inject(const char *from, const char *to, const char *msg)
+{
+ MsgNode *node = malloc(sizeof(*node));
+ node->from = strdup(from);
+ node->to = strdup(to);
+ node->msg = strdup(msg);
+ node->next = NULL;
+ *nodeend = node;
+ nodeend = &(node->next);
+ printf("[%s->%s: %s]\n\n", from, to, msg);
+}
+
+static OtrlPolicy op_policy(void *opdata, ConnContext *context)
+{
+ if (!strcmp(context->accountname, ALICE)) return ALICEPOLICY;
+ if (!strcmp(context->accountname, BOB)) return BOBPOLICY;
+ return OTRL_POLICY_DEFAULT;
+}
+
+static void op_inject(void *opdata, const char *accountname,
+ const char *protocol, const char *recipient, const char *message)
+{
+ inject(accountname, recipient, message);
+}
+
+static void op_notify(void *opdata, OtrlNotifyLevel level,
+ const char *accountname, const char *protocol,
+ const char *username, const char *title,
+ const char *primary, const char *secondary)
+{
+}
+
+static int op_display_otr_message(void *opdata, const char *accountname,
+ const char *protocol, const char *username, const char *msg)
+{
+ return -1;
+}
+
+static void op_gone_secure(void *opdata, ConnContext *context,
+ int protocol_version)
+{
+ printf("SECURE (%d): %s / %s\n\n", protocol_version, context->accountname,
+ context->username);
+}
+
+static void op_gone_insecure(void *opdata, ConnContext *context)
+{
+ printf("INSECURE: %s / %s\n\n", context->accountname, context->username);
+}
+
+static void op_still_secure(void *opdata, ConnContext *context, int is_reply,
+ int protocol_version)
+{
+ printf("REFRESH (%d/%d): %s / %s\n\n", is_reply, protocol_version,
+ context->accountname, context->username);
+}
+
+static OtrlMessageAppOps ops = {
+ op_policy,
+ NULL,
+ NULL,
+ op_inject,
+ op_notify,
+ op_display_otr_message,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ op_gone_secure,
+ op_gone_insecure,
+ op_still_secure,
+ NULL
+};
+
+static OtrlUserState us;
+
+void receiving(const char *from, const char *to, const char *msg)
+{
+ int ignore;
+ char *newmsg;
+ OtrlTLV *tlvs;
+
+ ignore = otrl_message_receiving(us, &ops, NULL, to, PROTO, from, msg,
+ &newmsg, &tlvs, NULL, NULL);
+
+ if (!ignore) {
+ printf("%s> %s\n\n", from, newmsg ? newmsg : msg);
+ }
+
+ otrl_message_free(newmsg);
+ otrl_tlv_free(tlvs);
+}
+
+static void sending(const char *from, const char *to, const char *msg)
+{
+ gcry_error_t err;
+ OtrlTLV *tlvs = NULL;
+ char *newmsg;
+
+ err = otrl_message_sending(us, &ops, NULL, from, PROTO, to, msg,
+ tlvs, &newmsg, NULL, NULL);
+
+ if (!err) {
+ inject(from, to, newmsg ? newmsg : msg);
+ }
+
+ otrl_message_free(newmsg);
+ otrl_tlv_free(tlvs);
+}
+
+void test(int vers, int both)
+{
+ printf("\n\n*** Testing version %d, %s ***\n\n", vers,
+ both ? "simultaneous start" : "Alice start");
+
+ otrl_context_forget_all(us);
+ ALICEPOLICY = vers == 1 ? (OTRL_POLICY_DEFAULT &~ OTRL_POLICY_ALLOW_V2) :
+ OTRL_POLICY_DEFAULT;
+ sending(ALICE, BOB, "?OTR?");
+ if (both) {
+ sending(BOB, ALICE, "?OTR?");
+ }
+ dispatch();
+ sending(ALICE, BOB, "Hi there");
+ dispatch();
+}
+
+static void test_unreadable(void)
+{
+ ConnContext *bobcontext;
+
+ printf("\n\n*** Testing Bob receiving unreadable messages from "
+ "Alice ***\n\n");
+
+ bobcontext = otrl_context_find(us, ALICE, BOB, PROTO, 0, NULL, NULL, NULL);
+ otrl_context_force_plaintext(bobcontext);
+ sending(ALICE, BOB, "unreadable text");
+ dispatch();
+
+}
+
+int main(int argc, char **argv)
+{
+ OTRL_INIT;
+ us = otrl_userstate_create();
+
+ otrl_privkey_read(us, "/home/iang/.gaim/otr.private_key");
+
+ test(1,0);
+ test(2,0);
+ test(1,1);
+ test(2,1);
+ test_unreadable();
+
+
+ otrl_userstate_free(us);
+
+ return 0;
+}
diff --git a/src/userstate.h b/src/userstate.h
index 31df2f4..2a69376 100644
--- a/src/userstate.h
+++ b/src/userstate.h
@@ -23,11 +23,11 @@
typedef struct s_OtrlUserState* OtrlUserState;
#include "context.h"
-#include "privkey.h"
+#include "privkey-t.h"
struct s_OtrlUserState {
ConnContext *context_root;
- PrivKey *privkey_root;
+ OtrlPrivKey *privkey_root;
};
/* Create a new OtrlUserState. Most clients will only need one of
diff --git a/src/version.h b/src/version.h
index 77187c1..2020e97 100644
--- a/src/version.h
+++ b/src/version.h
@@ -20,7 +20,7 @@
#ifndef __VERSION_H__
#define __VERSION_H__
-#define OTRL_VERSION "3.0.0"
+#define OTRL_VERSION "3.0.0 beta 1"
#define OTRL_VERSION_MAJOR 3
#define OTRL_VERSION_MINOR 0
diff --git a/toolkit/otr_modify.c b/toolkit/otr_modify.c
index fe8fe98..776889e 100644
--- a/toolkit/otr_modify.c
+++ b/toolkit/otr_modify.c
@@ -89,7 +89,7 @@ int main(int argc, char **argv)
exit(1);
}
- if (otrl_proto_message_type(otrmsg) != OTR_DATA) {
+ if (otrl_proto_message_type(otrmsg) != OTRL_MSGTYPE_DATA) {
fprintf(stderr, "OTR Non-Data Message found on stdin.\n");
exit(1);
}
diff --git a/toolkit/otr_parse.c b/toolkit/otr_parse.c
index ae92d38..9a88659 100644
--- a/toolkit/otr_parse.c
+++ b/toolkit/otr_parse.c
@@ -30,15 +30,71 @@
static void parse(const char *msg)
{
- OTRMessageType mtype = otrl_proto_message_type(msg);
+ OtrlMessageType mtype = otrl_proto_message_type(msg);
+ CommitMsg cmsg;
+ KeyMsg kmsg;
+ RevealSigMsg rmsg;
+ SignatureMsg smsg;
KeyExchMsg keyexch;
DataMsg datamsg;
switch(mtype) {
- case OTR_QUERY:
+ case OTRL_MSGTYPE_QUERY:
printf("OTR Query:\n\t%s\n\n", msg);
break;
- case OTR_KEYEXCH:
+ case OTRL_MSGTYPE_DH_COMMIT:
+ cmsg = parse_commit(msg);
+ if (!cmsg) {
+ printf("Invalid D-H Commit Message\n\n");
+ break;
+ }
+ printf("D-H Commit Message:\n");
+ dump_data(stdout, "\tEncrypted Key", cmsg->enckey,
+ cmsg->enckeylen);
+ dump_data(stdout, "\tHashed Key", cmsg->hashkey,
+ cmsg->hashkeylen);
+ printf("\n");
+ free_commit(cmsg);
+ break;
+ case OTRL_MSGTYPE_DH_KEY:
+ kmsg = parse_key(msg);
+ if (!kmsg) {
+ printf("Invalid D-H Key Message\n\n");
+ break;
+ }
+ printf("D-H Key Message:\n");
+ dump_mpi(stdout, "\tD-H Key", kmsg->y);
+ printf("\n");
+ free_key(kmsg);
+ break;
+ case OTRL_MSGTYPE_REVEALSIG:
+ rmsg = parse_revealsig(msg);
+ if (!rmsg) {
+ printf("Invalid Reveal Signature Message\n\n");
+ break;
+ }
+ printf("Reveal Signature Message:\n");
+ dump_data(stdout, "\tKey", rmsg->key, rmsg->keylen);
+ dump_data(stdout, "\tEncrypted Signature",
+ rmsg->encsig, rmsg->encsiglen);
+ dump_data(stdout, "\tMAC", rmsg->mac, 20);
+ printf("\n");
+ free_revealsig(rmsg);
+ break;
+ case OTRL_MSGTYPE_SIGNATURE:
+ smsg = parse_signature(msg);
+ if (!smsg) {
+ printf("Invalid Signature Message\n\n");
+ break;
+ }
+ printf("Signature Message:\n");
+ dump_data(stdout, "\tEncrypted Signature",
+ smsg->encsig, smsg->encsiglen);
+ dump_data(stdout, "\tMAC", smsg->mac, 20);
+ printf("\n");
+ free_signature(smsg);
+ break;
+ case OTRL_MSGTYPE_V1_KEYEXCH:
keyexch = parse_keyexch(msg);
if (!keyexch) {
printf("Invalid Key Exchange Message\n\n");
@@ -57,7 +113,7 @@ static void parse(const char *msg)
printf("\n");
free_keyexch(keyexch);
break;
- case OTR_DATA:
+ case OTRL_MSGTYPE_DATA:
datamsg = parse_datamsg(msg);
if (!datamsg) {
printf("Invalid Data Message\n\n");
@@ -88,16 +144,16 @@ static void parse(const char *msg)
printf("\n");
free_datamsg(datamsg);
break;
- case OTR_ERROR:
+ case OTRL_MSGTYPE_ERROR:
printf("OTR Error:\n\t%s\n\n", msg);
break;
- case OTR_TAGGEDPLAINTEXT:
+ case OTRL_MSGTYPE_TAGGEDPLAINTEXT:
printf("Tagged plaintext message:\n\t%s\n\n", msg);
break;
- case OTR_NOTOTR:
+ case OTRL_MSGTYPE_NOTOTR:
printf("Not an OTR message:\n\t%s\n\n", msg);
break;
- case OTR_UNKNOWN:
+ case OTRL_MSGTYPE_UNKNOWN:
printf("Unrecognized OTR message:\n\t%s\n\n", msg);
break;
}
diff --git a/toolkit/otr_readforge.c b/toolkit/otr_readforge.c
index 61b98f9..edf4b07 100644
--- a/toolkit/otr_readforge.c
+++ b/toolkit/otr_readforge.c
@@ -71,7 +71,7 @@ int main(int argc, char **argv)
exit(1);
}
- if (otrl_proto_message_type(otrmsg) != OTR_DATA) {
+ if (otrl_proto_message_type(otrmsg) != OTRL_MSGTYPE_DATA) {
fprintf(stderr, "OTR Non-Data Message found on stdin.\n");
exit(1);
}
diff --git a/toolkit/otr_toolkit.1 b/toolkit/otr_toolkit.1
index d684e58..6224918 100644
--- a/toolkit/otr_toolkit.1
+++ b/toolkit/otr_toolkit.1
@@ -2,7 +2,7 @@
.\" First parameter, NAME, should be all caps
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
.\" other parameters are allowed: see man(7), man(1)
-.TH OTR_PARSE 1 "December 2, 2004"
+.TH OTR_PARSE 1 "October 16, 2005"
.\" Please adjust this date whenever revising the manpage.
.\"
.\" Some roff macros, for reference:
@@ -67,7 +67,7 @@ Here are the six programs in the toolkit:
- otr_parse
- Parse OTR messages given on stdin, showing the values of all the
- fields in OTR Key Exchange Messages and OTR Data Messages.
+ fields in OTR protocol messages.
- otr_sesskeys our_privkey their_pubkey
- Shows our public key, the session id, two AES and two MAC keys
diff --git a/toolkit/parse.c b/toolkit/parse.c
index 0f58fbb..456754c 100644
--- a/toolkit/parse.c
+++ b/toolkit/parse.c
@@ -182,6 +182,192 @@ void free_keyexch(KeyExchMsg keyexch)
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);
+ if (memcmp(bufp, "\x00\x02\x02", 3)) goto inv;
+ bufp += 3; lenp -= 3;
+
+ 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);
+ if (memcmp(bufp, "\x00\x02\x0a", 3)) goto inv;
+ bufp += 3; lenp -= 3;
+
+ 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);
+ if (memcmp(bufp, "\x00\x02\x11", 3)) goto inv;
+ bufp += 3; lenp -= 3;
+
+ 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);
+ if (memcmp(bufp, "\x00\x02\x12", 3)) goto inv;
+ bufp += 3; lenp -= 3;
+
+ 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)
{
diff --git a/toolkit/parse.h b/toolkit/parse.h
index f5362d4..9329289 100644
--- a/toolkit/parse.h
+++ b/toolkit/parse.h
@@ -49,6 +49,35 @@ typedef struct s_DataMsg {
unsigned char *macend; /* free() these. */
} * DataMsg;
+typedef struct s_CommitMsg {
+ unsigned char *raw; /* The base64-decoded data; must be free()d */
+ unsigned char *enckey;
+ size_t enckeylen;
+ unsigned char *hashkey;
+ size_t hashkeylen;
+} * CommitMsg;
+
+typedef struct s_KeyMsg {
+ unsigned char *raw; /* The base64-decoded data; must be free()d */
+ gcry_mpi_t y;
+} * KeyMsg;
+
+typedef struct s_RevealSigMsg {
+ unsigned char *raw; /* The base64-decoded data; must be free()d */
+ unsigned char *key;
+ size_t keylen;
+ unsigned char *encsig;
+ size_t encsiglen;
+ unsigned char mac[20];
+} * RevealSigMsg;
+
+typedef struct s_SignatureMsg {
+ unsigned char *raw; /* The base64-decoded data; must be free()d */
+ unsigned char *encsig;
+ size_t encsiglen;
+ unsigned char mac[20];
+} * SignatureMsg;
+
/* Dump an unsigned int to a FILE * */
void dump_int(FILE *stream, const char *title, unsigned int val);
@@ -65,9 +94,34 @@ KeyExchMsg parse_keyexch(const char *msg);
/* Deallocate a KeyExchMsg and all of the data it points to */
void free_keyexch(KeyExchMsg keyexch);
+/* Parse a D-H Commit Message into a newly-allocated CommitMsg structure */
+CommitMsg parse_commit(const char *msg);
+
/* Parse a Data Message into a newly-allocated DataMsg structure */
DataMsg parse_datamsg(const char *msg);
+/* Deallocate a CommitMsg and all of the data it points to */
+void free_commit(CommitMsg cmsg);
+
+/* Parse a Reveal Signature Message into a newly-allocated RevealSigMsg
+ * structure */
+RevealSigMsg parse_revealsig(const char *msg);
+
+/* Deallocate a RevealSigMsg and all of the data it points to */
+void free_revealsig(RevealSigMsg rmsg);
+
+/* Parse a Signature Message into a newly-allocated SignatureMsg structure */
+SignatureMsg parse_signature(const char *msg);
+
+/* Deallocate a SignatureMsg and all of the data it points to */
+void free_signature(SignatureMsg smsg);
+
+/* Parse a D-H Key Message into a newly-allocated KeyMsg structure */
+KeyMsg parse_key(const char *msg);
+
+/* Deallocate a KeyMsg and all of the data it points to */
+void free_key(KeyMsg cmsg);
+
/* 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
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/libotr.git
More information about the Pkg-privacy-commits
mailing list