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?
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.