Search code examples
c++cmakelinkercrypto++

CMake throws exception because of undefined reference in CryptoPP


I am getting this error:

CMakeFiles/Athena.dir/Startup/main.cpp.o: In function `CryptoPP::ClonableImpl<CryptoPP::BlockCipherFinal<(CryptoPP::CipherDir)0, CryptoPP::Rijndael::Enc>, CryptoPP::Rijndael::Enc>::ClonableImpl()':
/home/dev/workspace/Athena/lib/cryptopp/simple.h:26: undefined reference to `CryptoPP::Rijndael::Enc::Enc()'
collect2: error: ld returned 1 exit status
CMakeFiles/Athena.dir/build.make:434: recipe for target 'Athena'

when I try to link CryptoPP to my programm.

This is my CMakeLists.txt:

cmake_minimum_required(VERSION 3.8)
project(Athena)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -fpermissive -Wno-deprecated -Wno-int-to-pointer-cast -Wno-deprecated-declarations")

include_directories(lib/SQLiteCpp/include)
add_subdirectory(lib/SQLiteCpp)

include_directories(lib/cryptopp)
add_subdirectory(lib/cryptopp)

include_directories(lib/json/src)
add_subdirectory(lib/json)

set(SOURCE_FILES
    Startup/main.cpp)

add_executable(Athena ${SOURCE_FILES})
target_link_libraries(Athena SQLiteCpp sqlite3 pthread dl cryptopp)

Here are my includes:

#include "modes.h" // For CTR_Mode
#include "filters.h" //For StringSource
#include "aes.h" // For AES

I already tried linking cryptopp by find_package, by find_library and I even tried writing a script to download and make cryptopp manually. I am really out of ideas and I´m also pretty sure that it´s not my code (I pasted in an example from the cryptopp wiki).


Solution

  • Your CMakeLists.txt looks incorrect. You should do two things. First, you should duplicate the exact list of the source files the GNUmakefile uses for a target. Second, you should use the same flags the GNUmakefile uses. You have to run the project's makefile or msbuild files to get those.

    You should also consider using ExternalProject_Add; and stop trying to get Cmake to build the library. The library already supplies a Makefile and MSBuild file to build the library on all platforms it supports, so Cmake is superfluous and not needed. There's no reason to have Cmake create build files with incorrect options and flags since we already supply one with the correct options. Also see How to use CMake ExternalProject_Add or alternatives in a cross platform way? on Stack Overflow.

    After a year of trying to support Cmake in Crypto++, I'm speaking with a lot of experience. Also see Cmake on the Crypto++ wiki.


    CMakeFiles/Athena.dir/Startup/main.cpp.o: In function `CryptoPP::ClonableImpl<CryptoPP::BlockCipherFinal<(CryptoPP::CipherDir)0, CryptoPP::Rijndael::Enc>, CryptoPP::Rijndael::Enc>::ClonableImpl()':
    /home/dev/workspace/Athena/lib/cryptopp/simple.h:26: undefined reference to `CryptoPP::Rijndael::Enc::Enc()'
    collect2: error: ld returned 1 exit status
    CMakeFiles/Athena.dir/build.make:434: recipe for target 'Athena'
    

    You should show the command line you used to build the library. This is yet another reason Crypto++ withdrew support for Cmake - it hides necessary information used for troubleshooting, like the command line used to build a source file. It resulted in awful bug reports like this.

    Rijndael::Enc::Enc() is used to enable code to harden against side channel attacks when unaligned data accesses are in effect. The constructor itself is not used; but its needed for the code path because a few values are set in it.

    If you look at rijndael.h you will see the constructor is enabled when the platform is IA-32, ARM or PowerPC. But the source file of interest is really rijndael.cpp, around line 1070:

    #if CRYPTOPP_BOOL_X64 || CRYPTOPP_BOOL_X32 || CRYPTOPP_BOOL_X86
    
    static inline bool AliasedWithTable(const byte *begin, const byte *end)
    {
        ptrdiff_t s0 = uintptr_t(begin)%4096, s1 = uintptr_t(end)%4096;
        ptrdiff_t t0 = uintptr_t(Te)%4096, t1 = (uintptr_t(Te)+sizeof(Te))%4096;
        if (t1 > t0)
            return (s0 >= t0 && s0 < t1) || (s1 > t0 && s1 <= t1);
        else
            return (s0 < t1 || s1 <= t1) || (s0 >= t0 || s1 > t0);
    }
    
    struct Locals
    {
        word32 subkeys[4*12], workspace[8];
        const byte *inBlocks, *inXorBlocks, *outXorBlocks;
        byte *outBlocks;
        size_t inIncrement, inXorIncrement, outXorIncrement, outIncrement;
        size_t regSpill, lengthAndCounterFlag, keysBegin;
    };
    
    const size_t s_aliasPageSize = 4096;
    const size_t s_aliasBlockSize = 256;
    const size_t s_sizeToAllocate = s_aliasPageSize + s_aliasBlockSize + sizeof(Locals);
    
    Rijndael::Enc::Enc() : m_aliasBlock(s_sizeToAllocate) { }
    
    #endif  // CRYPTOPP_BOOL_X64 || CRYPTOPP_BOOL_X32 || CRYPTOPP_BOOL_X86
    
    #if CRYPTOPP_BOOL_ARM32 || CRYPTOPP_BOOL_ARM64 || CRYPTOPP_BOOL_PPC32 || CRYPTOPP_BOOL_PPC64
    // Do nothing
    Rijndael::Enc::Enc() { }
    #endif
    

    You need to figure out why Cmake is mucking with the defines for CRYPTOPP_BOOL_X86, CRYPTOPP_BOOL_X64 and friends. To do that, you need to see the command line, but Cmake hides that from you.


    Related, we do test C++17 and you should be OK with that flag. However, based on experience this may cause problems: set(CMAKE_CXX_STANDARD 17). Cmake does not have good C++ support. We could not perform a simple project(cryptopp, CXX). Also see Tell CMake to use C++ compiler for C files coming from CMake?.


    I'll look into removing the constructor for all builds; and set m_aliasBlock in UncheckedSetKey. That may help with the issue. But its just a band-aid for a bigger engineering problem. The bigger engineering problems created by Cmake is the reason we withdrew support for it.