QtPass Developer documentation
QtPass test coverage
Current view: top level - src/gpgidsigner.h Coverage Total Hit
Test: QtPass Lines: 100.0 % 1 1
Test Date: 2026-11-14 13:19:18 Functions: - 0 0

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : /**
       4              :  * @file
       5              :  * @brief GpgIdSigner, which signs a store's `.gpg-id` and verifies an existing
       6              :  * `.gpg-id.sig` against the configured signing keys.
       7              :  */
       8              : #ifndef SRC_GPGIDSIGNER_H_
       9              : #define SRC_GPGIDSIGNER_H_
      10              : 
      11              : #include <QString>
      12              : #include <QStringList>
      13              : #include <functional>
      14              : 
      15              : /**
      16              :  * @class GpgIdSigner
      17              :  * @ingroup keys
      18              :  * @brief The detached signature on a store's `.gpg-id`: whether the
      19              :  * configured signing key is usable, signing the file, and checking that an
      20              :  * existing `.gpg-id.sig` was made by one of the configured keys.
      21              :  *
      22              :  * Every gpg run goes through the @c Exec given at construction, so the
      23              :  * re-encryption worker can hand in its cancellable runner and tests a fake.
      24              :  * With no signing keys configured signing is off and verification passes:
      25              :  * `pass` itself only checks signatures when `PASSWORD_STORE_SIGNING_KEY` is
      26              :  * set.
      27              :  */
      28              : class GpgIdSigner {
      29              : public:
      30              :   /**
      31              :    * @brief Most bytes of a .gpg-id.sig verify() hands to gpg.
      32              :    *
      33              :    * A detached signature is well under a KiB; far beyond any real one.
      34              :    */
      35              :   static constexpr qint64 kMaxSignatureBytes = 64 * 1024;
      36              : 
      37              :   /**
      38              :    * @brief Blocking process runner: Executor::executeBlocking()'s shape.
      39              :    * @return The exit code, or -1 when the process could not run.
      40              :    */
      41              :   using Exec =
      42              :       std::function<int(const QString &app, const QStringList &args,
      43              :                         const QString &input, QString *out, QString *err)>;
      44              : 
      45              :   /**
      46              :    * @brief Create a signer.
      47              :    * @param gpgExecutable The gpg binary; a `wsl ` prefix routes file paths
      48              :    *        through wslpath.
      49              :    * @param signingKeys Full fingerprints from the settings (40 hex characters
      50              :    *        for v4 keys, 64 for v5/v6), as pass's PASSWORD_STORE_SIGNING_KEY
      51              :    *        requires: gpg reports fingerprints in its KEY_CONSIDERED and
      52              :    *        VALIDSIG status lines, so a short key ID would never pass
      53              :    *        haveSecretKey() or verify(). The first one signs, any of them
      54              :    *        verifies.
      55              :    * @param exec Process runner; Executor::executeBlocking() when empty.
      56              :    */
      57              :   GpgIdSigner(QString gpgExecutable, QStringList signingKeys,
      58              :               Exec exec = Exec());
      59              : 
      60              :   /**
      61              :    * @brief Split the settings value into keys, upper-cased the way gpg
      62              :    * prints fingerprints.
      63              :    * @param passSigningKey The space-separated setting.
      64              :    * @return The keys, without empties.
      65              :    */
      66              :   static auto keysFromSetting(const QString &passSigningKey) -> QStringList;
      67              : 
      68              :   /**
      69              :    * @brief Whether any signing key is configured at all.
      70              :    * @return true when sign() will do something.
      71              :    */
      72          128 :   auto enabled() const -> bool { return !m_keys.isEmpty(); }
      73              : 
      74              :   /**
      75              :    * @brief The keys this signer was given.
      76              :    * @return The list from the constructor.
      77              :    */
      78              :   auto keys() const -> const QStringList & { return m_keys; }
      79              : 
      80              :   /**
      81              :    * @brief Whether gpg has the secret key that sign() will use, i.e. the
      82              :    * first configured key.
      83              :    * @return true when signing can work; false when no key is configured.
      84              :    */
      85              :   auto haveSecretKey() const -> bool;
      86              : 
      87              :   /**
      88              :    * @brief Write `<file>.sig`, a detached signature by the first key over
      89              :    * @p contents, the bytes the caller just wrote as @p gpgIdFile.
      90              :    *
      91              :    * gpg signs what it is fed on stdin, not what is under the file's name by
      92              :    * the time it reads it (a co-writer of the store could have swapped the
      93              :    * list between the write and the signing, and the user's key would have
      94              :    * vouched for theirs), and writes the signature into a directory of
      95              :    * QtPass's own; the signature then goes next to the list through
      96              :    * Util::writeFileReplacing(), never by opening the `.sig` name.
      97              :    *
      98              :    * Only the first key signs, as `--local-user`: gpg then fails when that
      99              :    * secret key is missing, where `--default-key` would quietly sign with
     100              :    * another one. The new signature is verified against @p contents the way
     101              :    * verify() does before it is placed; one that is not by a configured key
     102              :    * is not written.
     103              :    * @param gpgIdFile The `.gpg-id` the signature is for; `<gpgIdFile>.sig`
     104              :    *        is written.
     105              :    * @param contents The exact bytes of that file, as written. UTF-8 text,
     106              :    *        as a recipient list is; anything else is refused, as verify()
     107              :    *        refuses it.
     108              :    * @param error Receives gpg's stderr, or the reason, when it fails.
     109              :    * @return true when the signature is in place and verified. Does nothing
     110              :    *         and returns true when no key is configured.
     111              :    */
     112              :   auto sign(const QString &gpgIdFile, const QByteArray &contents,
     113              :             QString *error = nullptr) const -> bool;
     114              : 
     115              :   /**
     116              :    * @brief Check a detached signature against the given bytes: gpg must
     117              :    * report a VALIDSIG whose key or primary-key fingerprint is one of the
     118              :    * configured keys.
     119              :    *
     120              :    * The data goes to gpg on stdin, so the caller verifies exactly the bytes
     121              :    * it is about to use as the recipient list; verifying a path and reading
     122              :    * it again afterwards would let anyone who can write to the store swap
     123              :    * the file in between (#1842).
     124              :    * @param contents The `.gpg-id` bytes.
     125              :    * @param signatureFile The `.gpg-id.sig` next to it.
     126              :    * @return true when the signature is good, or when no key is configured.
     127              :    */
     128              :   auto verify(const QByteArray &contents, const QString &signatureFile) const
     129              :       -> bool;
     130              : 
     131              :   /**
     132              :    * @brief Read @p gpgIdFile and verify it against `<file>.sig`.
     133              :    * @param gpgIdFile The `.gpg-id`.
     134              :    * @param contents Receives the bytes that were verified, so the caller can
     135              :    *        parse those and nothing else.
     136              :    * @return true when the signature is good, or when no key is configured.
     137              :    *         false when the file cannot be read.
     138              :    */
     139              :   auto verifyFile(const QString &gpgIdFile, QByteArray *contents) const -> bool;
     140              : 
     141              :   /**
     142              :    * @brief Pick the fingerprints out of gpg's `--status-fd` VALIDSIG line.
     143              :    * @param statusOutput gpg's status output.
     144              :    * @return The signing key's fingerprint and the primary key's fingerprint,
     145              :    *         or an empty list when there is no VALIDSIG, or when gpg says the
     146              :    *         key that made that signature is revoked (REVKEYSIG, KEYREVOKED).
     147              :    */
     148              :   static auto validSigFingerprints(const QString &statusOutput) -> QStringList;
     149              : 
     150              : private:
     151              :   /** @brief The gpg executable, as configured. */
     152              :   QString m_gpg;
     153              :   /**
     154              :    * @brief The configured signing keys. The first one signs; a signature by
     155              :    * any of them verifies.
     156              :    */
     157              :   QStringList m_keys;
     158              :   /** @brief How gpg is run; replaced by a fake in the unit tests. */
     159              :   Exec m_exec;
     160              : 
     161              :   /**
     162              :    * @brief Run gpg with @p args through m_exec.
     163              :    * @param args Arguments for gpg.
     164              :    * @param out If non-null, receives stdout.
     165              :    * @param err If non-null, receives stderr.
     166              :    * @param input Data for gpg's stdin; empty for none.
     167              :    * @return gpg's exit code.
     168              :    */
     169              :   auto run(const QStringList &args, QString *out = nullptr,
     170              :            QString *err = nullptr, const QString &input = QString()) const
     171              :       -> int;
     172              : };
     173              : 
     174              : #endif // SRC_GPGIDSIGNER_H_
        

Generated by: LCOV version 2.0-1