[sip-comm-dev] GSoC 09 - OTR - First term results


#1

Here I present the results from my first term period.

   * Develop a java library that handles encryption (see The current state
of OTR libraries in Java)
         o Build methods to Assemble/Dissasemble OTR Messages and tests to
verify things work properly
               + OTR Query Messages
               + Tagged plaintext messages
               + OTR Error Messages (Completed)
               + D-H Commit Message
               + D-H Key Message
               + Reveal Signature Message
               + Signature Message
               + Data Message (Completed)
         o Handle state transitions for the following actions (detailed
description of state transitions here),build key management infrastructure
(NOTE: key management for the library, not SC) and build tests to verify
things work properly
           In this period functionality for Requesting an OTR conversation,
Authenticated Key Exchange (AKE), and Data Exchange will be added.
               + Plaintext message without the whitespace tag
               + Plaintext message with the whitespace tag
               + Query Message
               + Error Message
               + D-H Commit Message
               + D-H Key Message (Completed)
               + Reveal Signature Message
               + Signature Message
               + Version 1 Key Exchange Message
               + Data Message
               + User requests to start an OTR conversation
               + User requests to end an OTR conversation
               + User types a message to be sent (Completed)
   * Create a transformation operation set (Completed)
   * Implement support for the transformation set in all protocols (Failed)
   * Implement an OTR encryption bundle prototype/proof of concept that
encrypts all conversations (Failed)

I have created a transformation set for jabber as an initial effort to
incorporate otr4j in SIP Communicator. I've chosen jabber because it is an
open standard with server implementation, such as OpenFire, freely
available.

I did not proceed into creating transformation sets for the rest of the
protocols because there is an issue that the DSA signature does not verify
in Pidgin/libotr, this action is taken in the OTR Protocol Draft, High Level
Overview, Authenticated Key Exchange, Step 9. I tried to sign using
BouncyCastle Leightweight API instead of the Standard JCE, but that failed
too.

To make sure the signature I use is correct, I created a set of test cases
to verify signatures created with bouncycastle using the Sun JCE provider
and visa versa, but that failed too.

I posted a question about this issue today in several places (CodeRanch, Sun
Forums, SIP Communicator dev-list), but I have received no answer yet, so I
need to further investigate this issue.

References:
- OTR Protocol Draft: http://www.cypherpunks.ca/otr/Protocol-v2-3.1.0.html
- CodeRunch post:
http://www.coderanch.com/t/453480/Security/DSA-Signature-sign-verify-using
- Sun Forms post: http://forums.sun.com/thread.jspa?threadID=5396849
- SIP Communicator email:
https://sip-communicator.dev.java.net/servlets/ReadMsg?list=dev&msgNo=6617


#2

Goerge,

Geekius Caesar schrieb:

<SNIP --- SNAP>

I did not proceed into creating transformation sets for the rest of the
protocols because there is an issue that the DSA signature does not verify
in Pidgin/libotr, this action is taken in the OTR Protocol Draft, High Level
Overview, Authenticated Key Exchange, Step 9. I tried to sign using
BouncyCastle Leightweight API instead of the Standard JCE, but that failed
too.

To make sure the signature I use is correct, I created a set of test cases
to verify signatures created with bouncycastle using the Sun JCE provider
and visa versa, but that failed too.

As per specification and an according cross-check of libotr implementation
IMHO you cannot use the JCE DSA variant to sign. The JCE variant _always_
hashes the data, then signs it. The OTR specification reads like this:

sigA(MA) (SIG)
    This is the signature, using the private part of the key pubA, of the
    32-byte MA (which does not need to be hashed again to produce the
    signature).

IMHO "which does not need to be hashed" is a bit ambigous and should read
"must not be hased". The libotr implementation also reflects this (see code
snippet below). Each signature value (r and s) is copied into a 20 byte field,
padded with zero if r or s are shorter than 20 bytes. This padding is not
explictily stated in the spec (it should be stated however).

IMHO you need to rebuild the specific DSA signature mode using the
appropriate BC functions (SHA256 HMAC, DSA) to be compliant to the spec and
libotr. As said, the JCE methods cannot be used because of their "embedded"
hash mechanism.

Here the function from libotr that performs the signature (inline comments
added by me):

/* 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; //wd: raw int format

    if (privkey->pubkey_type != OTRL_PUBKEY_TYPE_DSA)
  return gcry_error(GPG_ERR_INV_VALUE);

    *sigp = malloc(40); //wd: get signature storage
    if (sigp == NULL) return gcry_error(GPG_ERR_ENOMEM);
    *siglenp = 40;

    if (len) { //wd: transform raw data into a gcrypt multi-precision int
  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); //wd: build s-expression that holds data now
    gcry_mpi_release(datampi);
    gcry_pk_sign(&sigs, datas, privkey->privkey); //wd: sign the s-expr, return a s-expr in sigs
    gcry_sexp_release(datas);
    dsas = gcry_sexp_find_token(sigs, "dsa", 0); //wd: get "dsa" sub s-expr (contains r and s)
    gcry_sexp_release(sigs);
    rs = gcry_sexp_find_token(dsas, "r", 0); //wd: get r s-expr
    ss = gcry_sexp_find_token(dsas, "s", 0); //wd: get s s-expr
    gcry_sexp_release(dsas);
    r = gcry_sexp_nth_mpi(rs, 1, GCRYMPI_FMT_USG); //wd: get r MPI
    gcry_sexp_release(rs);
    s = gcry_sexp_nth_mpi(ss, 1, GCRYMPI_FMT_USG); //wd: get s MPI
    gcry_sexp_release(ss);
    gcry_mpi_print(format, NULL, 0, &nr, r); //wd: dummy print just to get lentth in "nr"
    gcry_mpi_print(format, NULL, 0, &ns, s); //wd: dummy print just to get length in "ns"
    memset(*sigp, 0, 40); //wd: fill with zero to assure zero padding
    gcry_mpi_print(format, (*sigp)+(20-nr), nr, NULL, r); //wd: extract data from MPI into signature buffer
    gcry_mpi_print(format, (*sigp)+20+(20-ns), ns, NULL, s); //wd: with length as computed above
    gcry_mpi_release(r);
    gcry_mpi_release(s);

    return gcry_error(GPG_ERR_NO_ERROR);
}

Verification is just the reversed way :slight_smile: .

Regards,
Werner

···

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@sip-communicator.dev.java.net
For additional commands, e-mail: dev-help@sip-communicator.dev.java.net


#3

Hello Werner,

Thank you for your reply, I have modified the code based on your
suggestions. The problem now is that the calculated v is not equal with r
(libgcrypt-1.4.1/cipher/dsa.c:380) so verification fails again.

I use a custom build libgcrypt-1.4.1 and I have inserted the following calls
in libgcrypt-1.4.1/cipher/dsa.c:verify

log_mpidump (" r:", r);
log_mpidump (" s:", s);
log_mpidump (" hash:", hash);
log_mpidump (" q:" , pkey->q);
log_mpidump (" p:" , pkey->p);
log_mpidump (" g:" , pkey->g);
log_mpidump (" y:" , pkey->y);

I do the same in otr4j (
http://code.google.com/p/otr4j/source/browse/src/net/java/otr4j/StateMachine.java#787
)

This way I can verify r in otr4j/bc is the same as r in libotr/libgcrypt, s
in otr4j/bc is the same as s in libotr/libgcrypt, e.t.c.

I will insert more logging calls in libgcrypt/bouncycastle to see why
exactly the resulting v is not equal with r.

kind regards,
George.

···

On Sun, Jul 12, 2009 at 2:48 PM, Werner Dittmann < Werner.Dittmann@t-online.de> wrote:

Goerge,

Geekius Caesar schrieb:

<SNIP --- SNAP>

>
> I did not proceed into creating transformation sets for the rest of the
> protocols because there is an issue that the DSA signature does not
verify
> in Pidgin/libotr, this action is taken in the OTR Protocol Draft, High
Level
> Overview, Authenticated Key Exchange, Step 9. I tried to sign using
> BouncyCastle Leightweight API instead of the Standard JCE, but that
failed
> too.
>
> To make sure the signature I use is correct, I created a set of test
cases
> to verify signatures created with bouncycastle using the Sun JCE provider
> and visa versa, but that failed too.

As per specification and an according cross-check of libotr implementation
IMHO you cannot use the JCE DSA variant to sign. The JCE variant _always_
hashes the data, then signs it. The OTR specification reads like this:

sigA(MA) (SIG)
   This is the signature, using the private part of the key pubA, of the
   32-byte MA (which does not need to be hashed again to produce the
   signature).

IMHO "which does not need to be hashed" is a bit ambigous and should read
"must not be hased". The libotr implementation also reflects this (see code
snippet below). Each signature value (r and s) is copied into a 20 byte
field,
padded with zero if r or s are shorter than 20 bytes. This padding is not
explictily stated in the spec (it should be stated however).

IMHO you need to rebuild the specific DSA signature mode using the
appropriate BC functions (SHA256 HMAC, DSA) to be compliant to the spec and
libotr. As said, the JCE methods cannot be used because of their "embedded"
hash mechanism.

Here the function from libotr that performs the signature (inline comments
added by me):

/* 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; //wd: raw int
format

   if (privkey->pubkey_type != OTRL_PUBKEY_TYPE_DSA)
       return gcry_error(GPG_ERR_INV_VALUE);

   *sigp = malloc(40); //wd: get signature storage
   if (sigp == NULL) return gcry_error(GPG_ERR_ENOMEM);
   *siglenp = 40;

   if (len) { //wd: transform raw data into a gcrypt
multi-precision int
       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); //wd: build
s-expression that holds data now
   gcry_mpi_release(datampi);
   gcry_pk_sign(&sigs, datas, privkey->privkey); //wd: sign the s-expr,
return a s-expr in sigs
   gcry_sexp_release(datas);
   dsas = gcry_sexp_find_token(sigs, "dsa", 0); //wd: get "dsa" sub
s-expr (contains r and s)
   gcry_sexp_release(sigs);
   rs = gcry_sexp_find_token(dsas, "r", 0); //wd: get r s-expr
   ss = gcry_sexp_find_token(dsas, "s", 0); //wd: get s s-expr
   gcry_sexp_release(dsas);
   r = gcry_sexp_nth_mpi(rs, 1, GCRYMPI_FMT_USG); //wd: get r MPI
   gcry_sexp_release(rs);
   s = gcry_sexp_nth_mpi(ss, 1, GCRYMPI_FMT_USG); //wd: get s MPI
   gcry_sexp_release(ss);
   gcry_mpi_print(format, NULL, 0, &nr, r); //wd: dummy print just
to get lentth in "nr"
   gcry_mpi_print(format, NULL, 0, &ns, s); //wd: dummy print just
to get length in "ns"
   memset(*sigp, 0, 40); //wd: fill with zero to
assure zero padding
   gcry_mpi_print(format, (*sigp)+(20-nr), nr, NULL, r); //wd: extract data
from MPI into signature buffer
   gcry_mpi_print(format, (*sigp)+20+(20-ns), ns, NULL, s); //wd: with
length as computed above
   gcry_mpi_release(r);
   gcry_mpi_release(s);

   return gcry_error(GPG_ERR_NO_ERROR);
}

Verification is just the reversed way :slight_smile: .

Regards,
Werner

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@sip-communicator.dev.java.net
For additional commands, e-mail: dev-help@sip-communicator.dev.java.net