Problem using Sailfish Secrets to store / update secret

Hi there,

I played around with Sailfish Secrets to securely store a secret token in my app. I used parts of the code from the example in the repository.
This worked the first time, but when I try to update the token because it changes in a defined period I am not able to update it. I get an UNIQUE constraint error:

Unable to execute insert secret query: UNIQUE constraint failed: Secrets.CollectionName, Secrets.SecretName Unable to fetch row

It seems that you can insert it only one time and then it is fixed?!? Is this the right way at all to store my token?

Here is my code:

Wallet.h

#ifndef WALLET_H
#define WALLET_H

#include <QObject>

#include <Sailfish/Secrets/secretmanager.h>

class Wallet : public QObject
{
    Q_OBJECT
public:
    explicit Wallet(QObject *parent = nullptr);

public slots:
   void storeToken(const QString &token);

private:
    void createCollection();

    Sailfish::Secrets::SecretManager m_secretManager;
    Sailfish::Secrets::Secret::Identifier m_token;

};

Wallet.cpp

#include "wallet.h"

#include <Sailfish/Secrets/createcollectionrequest.h>
#include <Sailfish/Secrets/storesecretrequest.h>
#include <Sailfish/Secrets/storedsecretrequest.h>

Wallet::Wallet(QObject *parent) :
    QObject(parent)
{
    m_token = Sailfish::Secrets::Secret::Identifier(
                QStringLiteral("token"),
                QStringLiteral(WALLET_COLLECTION_NAME),
                Sailfish::Secrets::SecretManager::DefaultEncryptedStoragePluginName);

    createCollection();
}

void Wallet::storeToken(const QString &token)
{
    // store data in wallet
    Sailfish::Secrets::Secret secret(m_token);
    secret.setData(token.toLatin1());

    Sailfish::Secrets::StoreSecretRequest storeCode;
    storeCode.setManager(&m_secretManager);
    storeCode.setSecretStorageType(Sailfish::Secrets::StoreSecretRequest::CollectionSecret);
    storeCode.setUserInteractionMode(Sailfish::Secrets::SecretManager::SystemInteraction);
    storeCode.setAccessControlMode(Sailfish::Secrets::SecretManager::OwnerOnlyMode);
    storeCode.setSecret(secret);
    storeCode.startRequest();
    storeCode.waitForFinished();

}

void Wallet::createCollection()
{
    Sailfish::Secrets::CreateCollectionRequest createCollection;
    createCollection.setManager(&m_secretManager);
    createCollection.setCollectionLockType(Sailfish::Secrets::CreateCollectionRequest::DeviceLock);
    createCollection.setDeviceLockUnlockSemantic(Sailfish::Secrets::SecretManager::DeviceLockKeepUnlocked);
    createCollection.setAccessControlMode(Sailfish::Secrets::SecretManager::OwnerOnlyMode);
    createCollection.setUserInteractionMode(Sailfish::Secrets::SecretManager::SystemInteraction);
    createCollection.setCollectionName(WALLET_COLLECTION_NAME);
    createCollection.setStoragePluginName(Sailfish::Secrets::SecretManager::DefaultEncryptedStoragePluginName);
    createCollection.setEncryptionPluginName(Sailfish::Secrets::SecretManager::DefaultEncryptedStoragePluginName);
    createCollection.startRequest();
    createCollection.waitForFinished();

}
2 Likes

I’ve also noticed this, which is odd because the implementation seems to update instead of insert when the secret already exists:

const QString updateSecretQuery = QStringLiteral(
             "UPDATE Secrets"
             " SET Secret = ?"
             "   , Timestamp = date('now')"
             " WHERE SecretName = ?;"
         );
const QString insertSecretQuery = QStringLiteral(
            "INSERT INTO Secrets ("
              "SecretName,"
              "Secret,"
              "Timestamp"
            ")"
            " VALUES ("
              "?,?,date('now')"
            ");");

Daemon::Sqlite::Database::Query iq = db->prepare(found ? updateSecretQuery : insertSecretQuery, &errorText);
if (!errorText.isEmpty()) {
    db->rollbackTransaction();
    return Result(Result::DatabaseQueryError,
                  QString::fromUtf8("SQLCipher plugin unable to prepare insert secret query: %1").arg(errorText));
}

Maybe you could have a look in that code, I can’t immediately find something wrong there, and I’m also not sure the code in that repository matches what is running on the device (i’m just a newbie here).

1 Like

Did you ever get any further with this?

Yes, I used sailfish secrets in several apps (SailHub / Quartermaster / etc.) a long time but removed it, because it seems that with sailjail you lose access to it.
It is not possible to update a password, you need to delete the old entry and create a new one.
But if you like I can provide a sample class i used.

1 Like

I was just reading through: https://github.com/sailfishos/sailfish-secrets/blob/48a1d118e81b8e89c69649575a1f9b7a5b83e9d1/tests/Secrets/tst_secrets/tst_secrets.cpp

And it occurred to me it’s just a delete and setSecret to update, but, as you noted, it’s not listed in the Permissions at https://github.com/sailfishos/sailjail-permissions/ which is really odd. Unstable api, I guess.

Can someone confirm if this is a temporary affair because the api is considered unstable? @chris.adams and perhaps @slava (noticed Foil apps all have custom implementations?) you can shed some light?

I can only say that Secrets has never been an option for the Foil apps because those apps appeared before this Secrets thing and are still compatible with pre-Secrets versions of Sailfish OS.

1 Like

Ok, thanks. I wasn’t sure just how ‘old’ / ‘stable’ Secrets is. That clarifies it.

Idk if it’s still helpful here, here’s the wrapper I’ve written for Sailfish-Secrets.

I can’t understand why they made a C-Style API for SFSecrets when they never made a python lib for it, now everyone has to create a similar-looking wrapper to have an object oriented API. SFSecrets is C++, not C.

2 Likes