libtom / libtomcrypt

LibTomCrypt is a fairly comprehensive, modular and portable cryptographic toolkit that provides developers with a vast array of well known published block ciphers, one-way hash functions, chaining modes, pseudo-random number generators, public key cryptography and a plethora of other routines.

Home Page:https://www.libtom.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Convert old DSA key format to DER sequence

mabuchner opened this issue · comments

I'm trying to update our code base from libtomcrypt v0.93 to v1.18.2. After some struggling, I've finally managed to compile libtommath and libtommath on all of our supported platforms.

Unfortunately, now our tests are failing with "Invalid input packet" errors. It seems like the key format of the dsa_import function (and probably also of the dsa_export function) was changed in v1.03 (see here). The first byte of our public key is 0x93, but the new code seems to expect 0x30 or 0x31.

Is there some way to convert keys from the old format to the new format?

The changes in dsa_test.c aren't helping. It didn't store a key in the old format.

I've written a function to import a DSA key in the old format

static int read_old_header(const unsigned char* in, const unsigned char* end, int* bytesRead)
{
    const int PACKET_SECT_DSA_OLD = 3;
    const int PACKET_SUB_SIGNED = 2;

    *bytesRead = 0;

    const int headerSize = 4;
    if (in + headerSize > end)
    {
        return CRYPT_INVALID_PACKET;
    }

    const unsigned long version = in[1] << 8 | in[0];
    if (version >= 0x0103)
    {
        // libtomcrypt v1.03 uses a different format
        return CRYPT_INVALID_PACKET;
    }
    *bytesRead += 2;

    const int section = in[2];
    if (section != PACKET_SECT_DSA_OLD)
    {
        return CRYPT_INVALID_PACKET;
    }
    *bytesRead += 1;

    const int subsection = in[3];
    if (subsection != PACKET_SUB_SIGNED)
    {
        return CRYPT_INVALID_PACKET;
    }
    *bytesRead += 1;

    return CRYPT_OK;
}

static int
    read_old_big_num(const unsigned char* sig, const unsigned char* end, void* num, int* bytesRead)
{
    *bytesRead = 0;

    const int numHeaderSize = 2;
    if (sig + numHeaderSize > end)
    {
        return CRYPT_INVALID_PACKET;
    }

    const int numLength = (sig[0] << 8) | sig[1];
    *bytesRead += numHeaderSize;
    sig += numHeaderSize;

    if (sig + numLength > end)
    {
        return CRYPT_INVALID_PACKET;
    }

    const int err = mp_read_unsigned_bin(num, (unsigned char*)sig, numLength);
    // Unfortunately, the const cast is necessary, because the input pointer
    // is declared as non-const. The libtommath implementation of this
    // function then uses a const pointer.
    if (err != CRYPT_OK)
    {
        return err;
    }
    *bytesRead += numLength;

    return CRYPT_OK;
}

int dsa_verify_hash_old(const unsigned char* sig, unsigned long siglen,
                    const unsigned char *hash, unsigned long hashlen,
                    int* stat, dsa_key* key)
{
    *stat = 0;

    const unsigned char* end = sig + siglen;
    int headerSize = 0;
    const int readHeaderResult = read_old_header(sig, end, &headerSize);
    if (readHeaderResult != CRYPT_OK)
    {
        return readHeaderResult;
    }
    sig += headerSize;

    void* r = NULL;
    void* s = NULL;
    int initMultiResult = CRYPT_ERROR;
    if ((initMultiResult = mp_init_multi(&r, &s, NULL)) != CRYPT_OK)
    {
        return initMultiResult;
    }

    int bytesRead = 0;
    if (read_old_big_num(sig, end, r, &bytesRead) != CRYPT_OK)
    {
        mp_clear_multi(r, s, NULL);
        return CRYPT_INVALID_PACKET;
    }
    sig += bytesRead;

    if (read_old_big_num(sig, end, s, &bytesRead) != CRYPT_OK)
    {
        mp_clear_multi(r, s, NULL);
        return CRYPT_INVALID_PACKET;
    }

    if (dsa_verify_hash_raw(r, s, hash, hashlen, stat, key) != CRYPT_OK)
    {
        mp_clear_multi(r, s, NULL);
        return CRYPT_INVALID_PACKET;
    }

    mp_clear_multi(r, s, NULL);
    return CRYPT_OK;
}

The format of the signatures also has changed. Here is a function to verify a signed hash with the old signature format.

static int read_old_header(const unsigned char* in, const unsigned char* end, int* bytesRead)
{
    const int PACKET_SECT_DSA_OLD = 3;
    const int PACKET_SUB_SIGNED = 2;

    *bytesRead = 0;

    const int headerSize = 4;
    if (in + headerSize > end)
    {
        return CRYPT_INVALID_PACKET;
    }

    const unsigned long version = in[1] << 8 | in[0];
    if (version >= 0x0103)
    {
        // libtomcrypt v1.03 uses a different format
        return CRYPT_INVALID_PACKET;
    }
    *bytesRead += 2;

    const int section = in[2];
    if (section != PACKET_SECT_DSA_OLD)
    {
        return CRYPT_INVALID_PACKET;
    }
    *bytesRead += 1;

    const int subsection = in[3];
    if (subsection != PACKET_SUB_SIGNED)
    {
        return CRYPT_INVALID_PACKET;
    }
    *bytesRead += 1;

    return CRYPT_OK;
}

static int
    read_old_big_num(const unsigned char* sig, const unsigned char* end, void* num, int* bytesRead)
{
    *bytesRead = 0;

    const int numHeaderSize = 2;
    if (sig + numHeaderSize > end)
    {
        return CRYPT_INVALID_PACKET;
    }

    const int numLength = (sig[0] << 8) | sig[1];
    *bytesRead += numHeaderSize;
    sig += numHeaderSize;

    if (sig + numLength > end)
    {
        return CRYPT_INVALID_PACKET;
    }

    const int err = mp_read_unsigned_bin(num, (unsigned char*)sig, numLength);
    // Unfortunately, the const cast is necessary, because the input pointer
    // is declared as non-const. The libtommath implementation of this
    // function then uses a const pointer.
    if (err != CRYPT_OK)
    {
        return err;
    }
    *bytesRead += numLength;

    return CRYPT_OK;
}

int dsa_verify_hash_old(const unsigned char* sig, unsigned long siglen,
                    const unsigned char *hash, unsigned long hashlen,
                    int* stat, dsa_key* key)
{
    *stat = 0;

    const unsigned char* end = sig + siglen;
    int headerSize = 0;
    const int readHeaderResult = read_old_header(sig, end, &headerSize);
    if (readHeaderResult != CRYPT_OK)
    {
        return readHeaderResult;
    }
    sig += headerSize;

    void* r = NULL;
    void* s = NULL;
    int initMultiResult = CRYPT_ERROR;
    if ((initMultiResult = mp_init_multi(&r, &s, NULL)) != CRYPT_OK)
    {
        return initMultiResult;
    }

    int bytesRead = 0;
    if (read_old_big_num(sig, end, r, &bytesRead) != CRYPT_OK)
    {
        mp_clear_multi(r, s, NULL);
        return CRYPT_INVALID_PACKET;
    }
    sig += bytesRead;

    if (read_old_big_num(sig, end, s, &bytesRead) != CRYPT_OK)
    {
        mp_clear_multi(r, s, NULL);
        return CRYPT_INVALID_PACKET;
    }

    if (dsa_verify_hash_raw(r, s, hash, hashlen, stat, key) != CRYPT_OK)
    {
        mp_clear_multi(r, s, NULL);
        return CRYPT_INVALID_PACKET;
    }

    mp_clear_multi(r, s, NULL);
    return CRYPT_OK;
}

Now fighting with the changes from #133.

A dirty workaround is to add

key->qord = hashlen;

in front of dsa_verify_hash_raw in dsa_verify_hash_old.

I will close this issue, as my solution from above seems to work.