Search code examples
c++crypto++ecdsa

How to get signature length from signature body in Crypto++ ECDSA


I use Crypto++ (libcrypto++ 1.11) to embed JWT in my application. I made methods to sign and verify messages with CryptoPP::ECDSA<CryptoPP::ECP, CryptoPP::SHA256> algorithm (with secp256r1 curve). Tokens for verification can come from the outer world, so I need to verify token contents (textual data) signature knowing the public key.

The problem is that Crypto++ can cause SegFault on invalid signatures, which gives me a lot of pain in my web server.

I hoped that signatures in BER format (default serialization format in the library) have fixed length, so all I need is to compare the length of signature with some constant. However, I found out larger contents enables larger signatures, so a deeper approach is needed.

bool ES256Verifier::Verify(const std::string& data,
                           const std::string& signature) {
    bool result = false;
    try {
      CryptoPP::StringSource ss(
          signature + data, true,
          new CryptoPP::SignatureVerificationFilter(
              verifier_,
              new CryptoPP::ArraySink((byte*)&result, sizeof(result))));
    } catch (const CryptoPP::BERDecodeErr& err) {
      LOG_WARNING() << "Signature `" << signature << "` has invalid (non-BER) format";
    } catch (const CryptoPP::Exception& ex) {
      LOG_WARNING() << "Signature verification has failed: " << ex.what();
    }
    return result;
}

Verifier verifier_ is initialized correctly (and verifies tokens successfully apart from SegFaults), but given data = "" and signature = "", for example, I always get SegFault:

__memmove_avx_unaligned_erms 0x00007fb4b9da6b38
CryptoPP::ArraySink::Put2(unsigned char const*, unsigned long, int, bool) 0x00007fb4ba414fb2
CryptoPP::BufferedTransformation::ChannelPut2(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned char const*, unsigned long, int, bool) 0x00007fb4ba3acedc
CryptoPP::StringStore::CopyRangeTo2(CryptoPP::BufferedTransformation&, unsigned long long&, unsigned long long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool) const 0x00007fb4ba414e02
CryptoPP::BufferedTransformation::Peek(unsigned char*, unsigned long) const 0x00007fb4ba3ad74a
CryptoPP::Integer::Decode(CryptoPP::BufferedTransformation&, unsigned long, CryptoPP::Integer::Signedness) 0x00007fb4ba45885c
CryptoPP::Integer::Decode(unsigned char const*, unsigned long, CryptoPP::Integer::Signedness) 0x00007fb4ba458c16
CryptoPP::DL_VerifierBase<CryptoPP::ECPPoint>::InputSignature pubkey.h:1560
CryptoPP::SignatureVerificationFilter::LastPut(unsigned char const*, unsigned long) 0x00007fb4ba4159a0
CryptoPP::FilterWithBufferedInput::PutMaybeModifiable(unsigned char*, unsigned long, int, bool, bool) 0x00007fb4ba418107
CryptoPP::BufferedTransformation::ChannelPut2(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned char const*, unsigned long, int, bool) 0x00007fb4ba3acedc
CryptoPP::BufferedTransformation::TransferMessagesTo2(CryptoPP::BufferedTransformation&, unsigned int&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool) 0x00007fb4ba3ad8fa
CryptoPP::BufferedTransformation::TransferAllTo2(CryptoPP::BufferedTransformation&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool) 0x00007fb4ba3adb21
CryptoPP::SourceTemplate<CryptoPP::StringStore>::PumpAll2 filters.h:1238
CryptoPP::Source::PumpAll filters.h:1182
CryptoPP::Source::SourceInitialize filters.h:1215
CryptoPP::StringSource::StringSource filters.h:1271
jwt::signature::algorithm::ES256Verifier::Verify es256_verifier.cpp:40
ES256_SignatureTest_Test::TestBody es256_test.cpp:29
...

So, is there a way to look at the data and signature and decide if this particular combination is going to cause SegFault due to invalid signature length?


Solution

  • Here's some sample code to determine the signature length using the Field Element, Signer and Verifier. The first output prints the element length and r||s length because r||s is the signature in P1363 format.

    The second and third output just print the result of SignatureLength(). Your program should reject a signature shorter than SignatureLength(). There is no sense in even trying to verify a short signature since it is no good.

    Note well: this only work for the DL_* signature schemes (based on discrete logs). It does not apply to TF_* signature schemes (based on trapdoor functions).

    #include "cryptlib.h"
    #include "eccrypto.h"
    #include "osrng.h"
    #include "oids.h"
    
    #include <iostream>
    
    int main(int argc, char* argv[])
    {
        using namespace CryptoPP;
        AutoSeededRandomPool prng;
    
        ///// Element
        DL_GroupParameters_EC<ECP> params(ASN1::secp256r1());
    
        unsigned int elemLength = params.GetCurve().GetField().MaxElementByteLength();
        std::cout << "Element length: " << elemLength << std::endl;
        std::cout << "r||s length: " << 2*elemLength << std::endl;
    
        ///// Signer
        ECDSA<ECP, SHA256>::Signer signer;
        signer.AccessKey().Initialize(prng, params);
    
        unsigned int signerLength = signer.SignatureLength();
        std::cout << "Signer signature length: " << signerLength << std::endl;
    
        ///// Verifier
        ECDSA<ECP, SHA256>::Verifier verifier(signer);
    
        unsigned int verifierLength = verifier.SignatureLength();
        std::cout << "Verifier signature length: " << verifierLength << std::endl;
    
        return 0;
    }
    

    Running the program results in the following.

    $ ./test.exe
    Element length: 32
    r||s length: 64
    Signer signature length: 64
    Verifier signature length: 64
    

    And if you switch curves to ASN1::secp521r1(), then running the program results in the following.

    $ ./test.exe
    Element length: 66
    r||s length: 132
    Signer signature length: 132
    Verifier signature length: 132