Понадобилась криптография в приложении на Qt для смартфона. Беглый гуглеж привел к статье на Хабре. Скачал исходники, собрал под linux и начал пробовать переносить это все на смартфон.
Первая проблема — собирать надо для другой архитектуры. Качаем Qt, Android-NDK… Заставляем это все работать вместе. Настройку среды я пропущу, так как инструкций в сети полно.
На этом этапе у нас уже есть настроенный и работающий компилятор. Теперь надо попробовать собрать библиотеку QCA. Только вот для работы нужно предварительно собрать ещё и OpenSSL.
export ANDROID_SYSROOT=~/Android/Sdk/ndk-bundle/platforms/android-21/arch-arm
export ANDROID_NDK_HOME=~/Android/Sdk/ndk-bundle/
./Configure shared android-arm -D__ANDROID_API__=21
Далее необходимо немного подправить Makefile проекта. Нужно указать, чтобы при сборке не добавлялась версия к имени файла. По умолчанию libssl.so, являющийся ссылкой на libssl.so.<version>. В результате в зависимостях библиотеки QCA будет указана именно libssl.so.<version>, которая попадет в APK файл с именем libssl.so. В результате приложение не запустится.
Ищем строку SHLIB_EXT=.so.$(SHLIB_VERSION_NUMBER)
и заменяем удаляем все, что идет после .so
. К сожалению, не понял, как попросить на этапе конфигурации сделать это автоматически. Остается только запустить процесс сборки.
make -j 8
Теперь нужно собрать библиотеку QCA. Для этого используется cmake. Чтобы было проще работать, оформил небольшой скрипт, который вызовет cmake с параметрами сборки под android. Может потом ещё пригодится.
QT_DIR=~/Qt/5.14.1/android
export PATH=$QT_DIR/bin/:$PATH
export OPENSSL_ROOT_DIR=~/openssl-1.1.1g
cmake ../ \
-DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a \
-DANDROID_NATIVE_API_LEVEL=21 \
-DCMAKE_ANDROID_NDK=/home/walhi/Android/Sdk/ndk-bundle123 \
-DCMAKE_ANDROID_STL_TYPE=gnustl_static\
-DCMAKE_TOOLCHAIN_FILE=~/Android/Sdk/ndk-bundle/build/cmake/android.toolchain.cmake\
-DQt5_DIR=$QT_DIR/lib/cmake/Qt5/\
-DQt5Core_DIR=$QT_DIR/lib/cmake/Qt5Core/\
-DQt5Network_DIR=$QT_DIR/lib/cmake/Qt5Network/\
-DQt5Test_DIR=$QT_DIR/lib/cmake/Qt5Test/\
-DOPENSSL_ROOT_DIR=$OPENSSL_ROOT_DIR\
-DOPENSSL_SSL_LIBRARY=$OPENSSL_ROOT_DIR/libssl.so\
-DOPENSSL_CRYPTO_LIBRARY=$OPENSSL_ROOT_DIR/libcrypto.so\
-DOPENSSL_INCLUDE_DIR=$OPENSSL_ROOT_DIR/include\
-DWITH_botan_PLUGIN=no
Если все прошло нормально (нужно смотреть вывод cmake), то собираем библиотеку командой make
и радуемся жизни.
Теперь нужно подключить библиотеку к проекту. Делается это в .pro файле.
QCA_PATH = <абсолютный путь>/qca-2.3.0/
OSSL_PATH = <абсолютный путь>/openssl-1.1.1g
INCLUDEPATH += $$QCA_PATH/include/QtCrypto/
INCLUDEPATH += $$QCA_PATH
ANDROID_EXTRA_LIBS += $$QCA_PATH/lib/libqca-qt5_armeabi-v7a.so
ANDROID_EXTRA_LIBS += $$QCA_PATH/lib/qca-qt5/crypto/libqca-gnupg_armeabi-v7a.so
ANDROID_EXTRA_LIBS += $$QCA_PATH/lib/qca-qt5/crypto/libqca-logger_armeabi-v7a.so
ANDROID_EXTRA_LIBS += $$QCA_PATH/lib/qca-qt5/crypto/libqca-ossl_armeabi-v7a.so
ANDROID_EXTRA_LIBS += $$QCA_PATH/lib/qca-qt5/crypto/libqca-softstore_armeabi-v7a.so
ANDROID_EXTRA_LIBS += $$OSSL_PATH/libssl.so
ANDROID_EXTRA_LIBS += $$OSSL_PATH/libcrypto.so
LIBS += -L$$QCA_PATH/lib
LIBS += -L$$QCA_PATH/lib/qca-qt5/crypto/
LIBS += -L$(OSSL_PATH)
LIBS += -lqca-qt5 $$QCA_PATH/lib/libqca-qt5_armeabi-v7a.so
android
{
qca_plugins.path = /assets/crypto
qca_plugins.files = $$QCA_PATH/lib/qca-qt5/crypto/*
INSTALLS += qca_plugins
}
Эти строки указывают где искать заголовочные файлы, библиотеки для линковки и просят добавить .so файлы в результирующий APK файл. Однако, с магией на этом не закончено. Приложение будет собираться, но при запуске свалится в segfalt, так как не сможет найти библиотеки.
Функционал OpenSSL оформлен в QCA как плагин. Файлы .so
плагинов должны быть расположены строго в папке с именем crypto. Где именно расположена эта папка можно указать в переменной окружения QCA_PLUGIN_PATH
.
К сожалению, не удалось попросить систему сборки положить .so
файлы в определенный каталог, однако никто не запрещает самому приложению перед использованием переместить эти файлы. Это решение найдено на StackOverflow (ссылка потеряна).
void copyPlugin(const QDir &dir, const QString&file) {
auto dataLocation = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
QDir destDir(dataLocation + "/crypto/");
if(!destDir.exists()) {
qDebug() << "QCA_PLUGINS create dst dir: " << destDir.absolutePath();
QDir(dataLocation).mkdir("crypto");
}
QString destPluginName = destDir.absolutePath() + "/" + file;
if(QFile(destPluginName).exists()) {
qDebug() << "QCA_PLUGINS file already exists: " << destPluginName;
return;
}
qDebug() << "QCA_PLUGINS Copy file: " << destPluginName;
QFile sourcePlugin(QFileInfo(dir,file).filePath());
sourcePlugin.copy(destPluginName);
QFile destPlugin(destPluginName);
destPlugin.setPermissions(QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeOther | QFileDevice::ExeGroup |
QFileDevice::ReadOwner | QFileDevice::ReadUser | QFileDevice::ReadOther | QFileDevice::ReadGroup |
QFileDevice::WriteOwner| QFileDevice::WriteUser | QFileDevice::WriteOther | QFileDevice::WriteGroup);
}
void copyPlugins(const QDir &dir, const QStringList &filesList) {
for(const auto file : filesList) {
copyPlugin(dir,file);
}
}
int main(int argc, char *argv[])
{
// QCA plugins костыль
QDir dir("assets:/crypto");
if (dir.exists()){
QStringList items = dir.entryList();
copyPlugins(dir, items);
setenv( "QCA_PLUGIN_PATH", QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation).toUtf8().constData(), 1 );
}
Initializer init;
...
}
Данный код как раз это и делает. Главное, выполнить все операции до инициализации библиотеки QCA. После чего все должно заработать. Делать это нужно один раз при первом запуске. Файлы .so не будут удаляться.