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?
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