Библиотека QCA в приложениях для Android

Автор: | 17.12.2019

Понадобилась криптография в приложении на 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 не будут удаляться.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *