weidai11 / cryptopp

free C++ class library of cryptographic schemes
https://cryptopp.com
Other
4.83k stars 1.49k forks source link

Using elliptic curve Diffie-Hellman without RTTI #1168

Closed janblome closed 2 years ago

janblome commented 2 years ago

Hi, I'm trying to use the elliptic curve Diffie-Hellman class (ECDH<ECP>) in a program compiled without RTTI. This results in a compiler warning when using MSVC (full program and output at the end of the issue):

cryptopp\8.7.0\include\cryptopp\pubkey.h(1658): warning C4541: 'dynamic_cast' used on polymorphic type 'CryptoPP::DL_ElgamalLikeSignatureAlgorithm<T>' with /GR-; unpredictable behavior may result

I looked into that a bit and saw that in pubkey.h:1658 the DL_SignerBase::SignAndRestart method uses a dynamic_cast to crosscast a DL_ElgamalLikeSignatureAlgorithm pointer to a DeterministicSignatureAlgorithm pointer. A cast like that is as far as I know not possible at all without RTTI.

Is making this work without RTTI something you would consider?

Possible Solution

For my use-case I patched CryptoPP by adding a virtual method to the DL_ElgamalLikeSignatureAlgorithm class to return a pointer to itself as a DeterministicSignatureAlgorithm. It returns a nullptr if the conversion wouldn't be possible. This is also the default, just like IsDeterministic returns false by default:

/// \brief DeterministicSignatureAlgorithm instance, if applicable
/// \return A pointer to this object as DeterministicSignatureAlgorithm if the signature scheme is deterministic, null otherwise
virtual const DeterministicSignatureAlgorithm * GetDeterministic() const
    {return nullptr;}

This can then be overridden in DL_Algorithm_DSA_RFC6979, which is the only class I found where IsDeterministic returned true:

const DeterministicSignatureAlgorithm * GetDeterministic() const
        {return this;}

Since the correct pointer is now determined statically this solves my issue. I attached my complete changes as patch, if this approach is something you would be interested in I would also be happy to create a PR for further discussion 🙂.

Full Patch ```diff diff --git a/gfpcrypt.h b/gfpcrypt.h index f8fc3c5e..ee570f62 100644 --- a/gfpcrypt.h +++ b/gfpcrypt.h @@ -354,6 +354,8 @@ public: {return false;} bool IsDeterministic() const {return true;} + const DeterministicSignatureAlgorithm * GetDeterministic() const + {return this;} // Deterministic K Integer GenerateRandom(const Integer &x, const Integer &q, const Integer &e) const diff --git a/pubkey.h b/pubkey.h index efa607b6..09035930 100644 --- a/pubkey.h +++ b/pubkey.h @@ -1399,6 +1399,9 @@ private: template DL_PublicKeyImpl::~DL_PublicKeyImpl() {} +// Forward declaration because it's used in DL_ElgamalLikeSignatureAlgorithm +class DeterministicSignatureAlgorithm; + /// \brief Interface for Elgamal-like signature algorithms /// \tparam T Field element type or class /// \details Field element T can be Integer, ECP or EC2N. @@ -1452,6 +1455,11 @@ public: /// \details IsDeterministic() is provided for DL signers. It is used by RFC 6979 signature schemes. virtual bool IsDeterministic() const {return false;} + + /// \brief DeterministicSignatureAlgorithm instance, if applicable + /// \return A pointer to this object as DeterministicSignatureAlgorithm if the signature scheme is deterministic, null otherwise + virtual const DeterministicSignatureAlgorithm * GetDeterministic() const + {return nullptr;} }; /// \brief Interface for deterministic signers @@ -1586,6 +1594,7 @@ protected: // true if the scheme conforms to RFC 6979 virtual bool IsDeterministic() const {return false;} + virtual const DeterministicSignatureAlgorithm * GetDeterministic() const {return nullptr;} virtual const DL_ElgamalLikeSignatureAlgorithm & GetSignatureAlgorithm() const =0; virtual const PK_SignatureMessageEncodingMethod & GetMessageEncodingInterface() const =0; @@ -1655,8 +1664,8 @@ public: if (alg.IsDeterministic()) { const Integer& x = key.GetPrivateExponent(); - const DeterministicSignatureAlgorithm& det = dynamic_cast(alg); - k = det.GenerateRandom(x, q, e); + const DeterministicSignatureAlgorithm* det = alg.GetDeterministic(); + k = det->GenerateRandom(x, q, e); } else { ```

Minimal Program

I tried this with the Visual Studio 2022 (Compiler Version 19.33) and using CryptoPP 8.7.0 (retrieved via Conan, paths shortened for clarity).

Code

#include <cryptopp/eccrypto.h>
#include <cryptopp/oids.h>
#include <cryptopp/osrng.h>

int main()
{
    CryptoPP::ECDH<CryptoPP::ECP>::Domain ecdh(CryptoPP::ASN1::secp521r1());
    CryptoPP::AutoSeededRandomPool rng;
    CryptoPP::SecByteBlock sk(ecdh.PrivateKeyLength());
    CryptoPP::SecByteBlock pk(ecdh.PublicKeyLength());
    ecdh.GenerateKeyPair(rng, sk, pk);

    return 0;
}

Compiler Invocation

cl /c /W4 /WX /GR- /I "cryptopp\8.7.0\include" "main.cpp"

Compiler Output

Full Output ``` cryptopp\8.7.0\include\cryptopp\queue.h(222): error C2220: the following warning is treated as an error cryptopp\8.7.0\include\cryptopp\queue.h(222): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc cryptopp\8.7.0\include\cryptopp\pubkey.h(1658): warning C4541: 'dynamic_cast' used on polymorphic type 'CryptoPP::DL_ElgamalLikeSignatureAlgorithm' with /GR-; unpredictable behavior may result with [ T=CryptoPP::Integer ] cryptopp\8.7.0\include\cryptopp\pubkey.h(1631): note: while compiling class template member function 'size_t CryptoPP::DL_SignerBase::SignAndRestart(CryptoPP::RandomNumberGenerator &,CryptoPP::PK_MessageAccumulator &,CryptoPP::byte *,bool) const' cryptopp\8.7.0\include\cryptopp\simple.h(40): note: see reference to class template instantiation 'CryptoPP::DL_SignerBase' being compiled cryptopp\8.7.0\include\cryptopp\pubkey.h(1957): note: see reference to class template instantiation 'CryptoPP::AlgorithmImpl>' being compiled with [ BASE=CryptoPP::DL_SignerBase ] cryptopp\8.7.0\include\cryptopp\pubkey.h(1997): note: see reference to class template instantiation 'CryptoPP::DL_ObjectImplBase' being compiled with [ BASE=CryptoPP::DL_SignerBase, SCHEME_OPTIONS=CryptoPP::DL_SignatureSchemeOptions,CryptoPP::DL_Keys_DSA,CryptoPP::DL_Algorithm_GDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA1>, KEY=CryptoPP::DL_PrivateKey_WithSignaturePairwiseConsistencyTest,CryptoPP::DSA2> ] cryptopp\8.7.0\include\cryptopp\pubkey.h(2022): note: see reference to class template instantiation 'CryptoPP::DL_ObjectImpl,SCHEME_OPTIONS,CryptoPP::DL_PrivateKey_WithSignaturePairwiseConsistencyTest,CryptoPP::DSA2>>' being compiled with [ SCHEME_OPTIONS=CryptoPP::DL_SignatureSchemeOptions,CryptoPP::DL_Keys_DSA,CryptoPP::DL_Algorithm_GDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA1> ] cryptopp\8.7.0\include\cryptopp\pubkey.h(2198): note: see reference to class template instantiation 'CryptoPP::DL_SignerImpl>' being compiled with [ ALG_INFO=CryptoPP::DSA2, KEYS=CryptoPP::DL_Keys_DSA, SA=CryptoPP::DL_Algorithm_GDSA, MEM=CryptoPP::DL_SignatureMessageEncodingMethod_DSA, H=CryptoPP::SHA1 ] cryptopp\8.7.0\include\cryptopp\pubkey.h(1325): note: see reference to class template instantiation 'CryptoPP::PK_FinalTemplate>>' being compiled with [ ALG_INFO=CryptoPP::DSA2, KEYS=CryptoPP::DL_Keys_DSA, SA=CryptoPP::DL_Algorithm_GDSA, MEM=CryptoPP::DL_SignatureMessageEncodingMethod_DSA, H=CryptoPP::SHA1 ] cryptopp\8.7.0\include\cryptopp\pubkey.h(1320): note: while compiling class template member function 'void CryptoPP::DL_PrivateKey_WithSignaturePairwiseConsistencyTest,CryptoPP::DSA2>::GenerateRandom(CryptoPP::RandomNumberGenerator &,const CryptoPP::NameValuePairs &)' cryptopp\8.7.0\include\cryptopp\gfpcrypt.h(855): note: see reference to class template instantiation 'CryptoPP::DL_PrivateKey_WithSignaturePairwiseConsistencyTest,CryptoPP::DSA2>' being compiled cryptopp\8.7.0\include\cryptopp\pubkey.h(1658): warning C4541: 'dynamic_cast' used on polymorphic type 'CryptoPP::DL_ElgamalLikeSignatureAlgorithm' with /GR-; unpredictable behavior may result with [ T=CryptoPP::ECP::Point ] cryptopp\8.7.0\include\cryptopp\pubkey.h(1631): note: while compiling class template member function 'size_t CryptoPP::DL_SignerBase::SignAndRestart(CryptoPP::RandomNumberGenerator &,CryptoPP::PK_MessageAccumulator &,CryptoPP::byte *,bool) const' cryptopp\8.7.0\include\cryptopp\simple.h(40): note: see reference to class template instantiation 'CryptoPP::DL_SignerBase' being compiled cryptopp\8.7.0\include\cryptopp\pubkey.h(1957): note: see reference to class template instantiation 'CryptoPP::AlgorithmImpl>' being compiled with [ BASE=CryptoPP::DL_SignerBase, KEYS=CryptoPP::DL_Keys_ECDSA, SA=CryptoPP::DL_Algorithm_ECDSA, MEM=CryptoPP::DL_SignatureMessageEncodingMethod_DSA, H=CryptoPP::SHA256 ] cryptopp\8.7.0\include\cryptopp\pubkey.h(1997): note: see reference to class template instantiation 'CryptoPP::DL_ObjectImplBase' being compiled with [ BASE=CryptoPP::DL_SignerBase, SCHEME_OPTIONS=CryptoPP::DL_SignatureSchemeOptions,CryptoPP::DL_Algorithm_ECDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA256,int>,CryptoPP::DL_Keys_ECDSA,CryptoPP::DL_Algorithm_ECDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA256>, KEY=CryptoPP::DL_PrivateKey_WithSignaturePairwiseConsistencyTest,CryptoPP::ECDSA> ] cryptopp\8.7.0\include\cryptopp\pubkey.h(2022): note: see reference to class template instantiation 'CryptoPP::DL_ObjectImpl,SCHEME_OPTIONS,CryptoPP::DL_PrivateKey_WithSignaturePairwiseConsistencyTest,CryptoPP::ECDSA>>' being compiled with [ SCHEME_OPTIONS=CryptoPP::DL_SignatureSchemeOptions,CryptoPP::DL_Algorithm_ECDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA256,int>,CryptoPP::DL_Keys_ECDSA,CryptoPP::DL_Algorithm_ECDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA256> ] cryptopp\8.7.0\include\cryptopp\pubkey.h(2198): note: see reference to class template instantiation 'CryptoPP::DL_SignerImpl>' being compiled with [ ALG_INFO=CryptoPP::DL_SS,CryptoPP::DL_Algorithm_ECDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA256,int>, KEYS=CryptoPP::DL_Keys_ECDSA, SA=CryptoPP::DL_Algorithm_ECDSA, MEM=CryptoPP::DL_SignatureMessageEncodingMethod_DSA, H=CryptoPP::SHA256 ] cryptopp\8.7.0\include\cryptopp\pubkey.h(1325): note: see reference to class template instantiation 'CryptoPP::PK_FinalTemplate>>' being compiled with [ ALG_INFO=CryptoPP::DL_SS,CryptoPP::DL_Algorithm_ECDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA256,int>, KEYS=CryptoPP::DL_Keys_ECDSA, SA=CryptoPP::DL_Algorithm_ECDSA, MEM=CryptoPP::DL_SignatureMessageEncodingMethod_DSA, H=CryptoPP::SHA256 ] cryptopp\8.7.0\include\cryptopp\pubkey.h(1320): note: while compiling class template member function 'void CryptoPP::DL_PrivateKey_WithSignaturePairwiseConsistencyTest,CryptoPP::ECDSA>::GenerateRandom(CryptoPP::RandomNumberGenerator &,const CryptoPP::NameValuePairs &)' cryptopp/8.7.0/include\cryptopp/eccrypto.h(677): note: see reference to class template instantiation 'CryptoPP::DL_PrivateKey_WithSignaturePairwiseConsistencyTest,CryptoPP::ECDSA>' being compiled cryptopp\8.7.0\include\cryptopp\pubkey.h(1658): warning C4541: 'dynamic_cast' used on polymorphic type 'CryptoPP::DL_ElgamalLikeSignatureAlgorithm' with /GR-; unpredictable behavior may result with [ T=CryptoPP::EC2N::Point ] cryptopp\8.7.0\include\cryptopp\pubkey.h(1631): note: while compiling class template member function 'size_t CryptoPP::DL_SignerBase::SignAndRestart(CryptoPP::RandomNumberGenerator &,CryptoPP::PK_MessageAccumulator &,CryptoPP::byte *,bool) const' cryptopp\8.7.0\include\cryptopp\simple.h(40): note: see reference to class template instantiation 'CryptoPP::DL_SignerBase' being compiled cryptopp\8.7.0\include\cryptopp\pubkey.h(1957): note: see reference to class template instantiation 'CryptoPP::AlgorithmImpl>' being compiled with [ BASE=CryptoPP::DL_SignerBase, KEYS=CryptoPP::DL_Keys_ECDSA, SA=CryptoPP::DL_Algorithm_ECDSA, MEM=CryptoPP::DL_SignatureMessageEncodingMethod_DSA, H=CryptoPP::SHA256 ] cryptopp\8.7.0\include\cryptopp\pubkey.h(1997): note: see reference to class template instantiation 'CryptoPP::DL_ObjectImplBase' being compiled with [ BASE=CryptoPP::DL_SignerBase, SCHEME_OPTIONS=CryptoPP::DL_SignatureSchemeOptions,CryptoPP::DL_Algorithm_ECDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA256,int>,CryptoPP::DL_Keys_ECDSA,CryptoPP::DL_Algorithm_ECDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA256>, KEY=CryptoPP::DL_PrivateKey_WithSignaturePairwiseConsistencyTest,CryptoPP::ECDSA> ] cryptopp\8.7.0\include\cryptopp\pubkey.h(2022): note: see reference to class template instantiation 'CryptoPP::DL_ObjectImpl,SCHEME_OPTIONS,CryptoPP::DL_PrivateKey_WithSignaturePairwiseConsistencyTest,CryptoPP::ECDSA>>' being compiled with [ SCHEME_OPTIONS=CryptoPP::DL_SignatureSchemeOptions,CryptoPP::DL_Algorithm_ECDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA256,int>,CryptoPP::DL_Keys_ECDSA,CryptoPP::DL_Algorithm_ECDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA256> ] cryptopp\8.7.0\include\cryptopp\pubkey.h(2198): note: see reference to class template instantiation 'CryptoPP::DL_SignerImpl>' being compiled with [ ALG_INFO=CryptoPP::DL_SS,CryptoPP::DL_Algorithm_ECDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA256,int>, KEYS=CryptoPP::DL_Keys_ECDSA, SA=CryptoPP::DL_Algorithm_ECDSA, MEM=CryptoPP::DL_SignatureMessageEncodingMethod_DSA, H=CryptoPP::SHA256 ] cryptopp\8.7.0\include\cryptopp\pubkey.h(1325): note: see reference to class template instantiation 'CryptoPP::PK_FinalTemplate>>' being compiled with [ ALG_INFO=CryptoPP::DL_SS,CryptoPP::DL_Algorithm_ECDSA,CryptoPP::DL_SignatureMessageEncodingMethod_DSA,CryptoPP::SHA256,int>, KEYS=CryptoPP::DL_Keys_ECDSA, SA=CryptoPP::DL_Algorithm_ECDSA, MEM=CryptoPP::DL_SignatureMessageEncodingMethod_DSA, H=CryptoPP::SHA256 ] cryptopp\8.7.0\include\cryptopp\pubkey.h(1320): note: while compiling class template member function 'void CryptoPP::DL_PrivateKey_WithSignaturePairwiseConsistencyTest,CryptoPP::ECDSA>::GenerateRandom(CryptoPP::RandomNumberGenerator &,const CryptoPP::NameValuePairs &)' cryptopp/8.7.0/include\cryptopp/eccrypto.h(678): note: see reference to class template instantiation 'CryptoPP::DL_PrivateKey_WithSignaturePairwiseConsistencyTest,CryptoPP::ECDSA>' being compiled ```
noloader commented 2 years ago

What is the problem you are reporting? You have to use RTTI for Crypto++.

janblome commented 2 years ago

My specific problem is that I'm using CryptoPP in an application that is itself not compiled with RTTI. This application links to a static build of CryptoPP, presumably built with RTTI. This approach seemed to work fine, but now that I'm trying to use the ECDH class I'm facing what seems to be undefined behavior. I think this is because the DL_SignerBase::SignAndRestart method is defined in the header, so it doesn't get compiled as part of the static library. Sorry, I missed mentioning that part in the issue.

It is of course valid to say any program using CryptoPP has to also be itself built with RTTI 🙂. In my case I'm hesitant to change that for the application where I'm using CryptoPP, so that's why I patched it on my end.

noloader commented 2 years ago

You should probably use a C-based library, like OpenSSL or Mbed-TLS.

C++ libraries like Botan and Crypto++ need full C++ features. I am not aware of any embedded C++ crypto libraries. Embedded C++ lacks exceptions, RTTI, virtual bases, multiple inheritance, and a lot of the stream stuff with language support via char traits.