c++qtcryptography

Verifying signed message with Qt's QCA::SecureMessage


I want to write a Qt application that reads a signed message and verifies its signature through QCA::SecureMessage. Using QCA's examples page and reading their unit tests from the publicly available repository, I came up with the following code:

bool verifyMessage(const QString& messagePath, const QString& pubKeyPath){
    // ... Checking that files exist and can be opened
    QFile messageFile(messagePath);
    messageFile.open(QIODevice::ReadOnly);
    
    // Initialize QCA and store manager
    QCA::Initializer init;
    QCA::KeyStoreManager::start();
    QCA::KeyStoreManager keyStoreManager(this);
    keyStoreManager.waitForBusyFinished();

    // qDebug() << keyStoreManager.keyStores(); // this yields 2 existing stores, ("qca-softstore", "qca-default-systemstore"), both valid and readOnly

    QCA::KeyStore pgpStore(QStringLiteral("qca-default-systemstore"), &keyStoreManager);
    QCA::ConvertResult convResult;
    QCA::PGPKey publicPGPKey = QCA::PGPKey::fromFile(pubKeyPath, &convResult);
    if (convResult != QCA::ConvertGood || publicPGPKey.isNull()) {
        qCWarning(DroneActivationLog) << "Failed to load public key";
        return false;
    }
    // pgpStore.writeEntry(publicPGPKey); // This fails because the store is readonly
    QByteArray signedData = messageFile.readAll(); // messageFile contains the whole signed message, signature is not detached
    
    // Initializing pgp and message
    QCA::OpenPGP pgp;
    QCA::SecureMessageKey key;
    QCA::SecureMessage message(&pgp);
    key.setPGPPublicKey(publicPGPKey);

    message.setRecipient(key); // I suspect this is wrong
    message.setFormat(QCA::SecureMessage::Format::Ascii);
    message.startVerify();
    message.update(signedData);
    message.end();
    message.waitForFinished();

    if (message.verifySuccess()) {
        qDebug() << "VERIFICATION SUCCESS" << message.read();
        return true;
    }
    qDebug() << "VERIFICATION ERROR\n" << message.diagnosticText();
    return false;
}

Although I verified that the message and the key are read correctly from the file, this code always fail the verification with error

VERIFICATION ERROR
"GPGProc: Pipe setup completeGPGProc: Running: [/usr/bin/gpg --no-tty --pinentry-mode loopback --enable-special-filenames --status-fd 16 --command-fd 13 --armor --decrypt]GPGProc: Process started{PLAINTEXT 75 1728306273 }{PLAINTEXT_LENGTH 11}{NEWSIG}{ERRSIG F092BE69AD20348E 1 10 00 1728306273 9 -}{NO_PUBKEY F092BE69AD20348E}GPGProc: Process finished: 2GPGProc: DoneGPG Process Finished: exitStatus=2stderr: [gpg: Signature made Mon 07 Oct 2024 01:04:33 PM UTC
gpg:                using RSA key F092BE69AD20348E
gpg: Can't check signature: No public key
]GpgAction error: ErrorUnknown
wasSigned: verifyResult: VerifyNoKey"

My interpretation is that the public key is not being actually used, and it needs to be imported. In fact, if I manually import the key from the shell, executing

gpg --import pubKey.asc

before executing my application, then it works, as the key is found in the system's readOnly keystore. When I ship this code, though, I will not have access to a command line on the executing machine.

Can I programmatically import the public key into an existing KeyStore? Or should I create my own KeyStore, since the 2 existing ones are both readOnly? In that case, how do I add it to the KeyStoreManager, which appears to have no such method from its documentation? More in general: is this a sane way to go about sending a signed file to a device where my Qt based application can read and verify it knowing only the sender's public key or is there an easier method?


Solution

  • As crhis_se points out in the comments, QCA apparently does not support this specific use case. In order to solve the problem of verifying a signed file, I ended up going for a different solution using QCA::PublicKey to read a RSA public key and verify the message:

    bool verifyMessage(const QString& pubKeyPath, const QString& messagePath, const QString& signaturePath){
        // ... Checking that files exist
        QFile messageFile(messagePath);
        messageFile.open(QIODevice::ReadOnly);
        QFile signatureFile(signaturePath);
        signatureFile.open(QIODevice::ReadOnly);
    
        
        // Initialize QCA
        QCA::Initializer init;
        QCA::ConvertResult convResult;
        QCA::PublicKey publicKey = QCA::RSAPublicKey::fromPEMFile(pubKeyPath, &convResult);
        if (convResult != QCA::ConvertGood) {
            qCWarning(DroneActivationLog) << "Could not read public key";
            return;
        }
    
        QCA::MemoryRegion fileRegion(messageFile.readAll()); // Depending on the format, trimmed() might be necessary to remove the EOF character.
    
        return publicKey.verifyMessage(fileRegion, signatureFile.readAll(), QCA::EMSA3_SHA256);
    }
    

    The disadvantage here is that now the signature is detached, i.e. it exists in a separate file, but at least the message can be verified.