aboutsummaryrefslogtreecommitdiffstats
path: root/src/m_im/ImToken.h
diff options
context:
space:
mode:
authorsaturneric <[email protected]>2026-09-24 22:36:31 +0200
committersaturneric <[email protected]>2026-09-24 22:36:31 +0200
commitcd318335f9700c5740166fba519a0e1f118c6a36 (patch)
treeca06efa18ef4eeaffb978f82c5f1a236bb5d8727 /src/m_im/ImToken.h
parentfeat(im): add IM module to sandbox build (diff)
downloadmodules-cd318335f9700c5740166fba519a0e1f118c6a36.tar.gz
modules-cd318335f9700c5740166fba519a0e1f118c6a36.zip
feat(im): add instant messaging token module
- add encrypted Base58 token encoding with shared book phrases - provide IM encrypt and encrypt-and-sign editor commands - add settings UI for phrase management and fingerprints - register the module and its SDK-free codec tests - register existing email module tests with CTest
Diffstat (limited to 'src/m_im/ImToken.h')
-rw-r--r--src/m_im/ImToken.h143
1 files changed, 143 insertions, 0 deletions
diff --git a/src/m_im/ImToken.h b/src/m_im/ImToken.h
new file mode 100644
index 0000000..866ed8a
--- /dev/null
+++ b/src/m_im/ImToken.h
@@ -0,0 +1,143 @@
+/**
+ * Copyright (C) 2021-2024 Saturneric <[email protected]>
+ *
+ * This file is part of GpgFrontend.
+ *
+ * GpgFrontend is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GpgFrontend is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * The initial version of the source code is inherited from
+ * the gpg4usb project, which is under GPL-3.0-or-later.
+ *
+ * All the source code of GpgFrontend was modified and released by
+ * Saturneric <[email protected]> starting on May 12, 2021.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#pragma once
+
+#include <QByteArray>
+#include <QString>
+#include <cstddef>
+
+/**
+ * @file ImToken.h
+ *
+ * @brief Compact, instant-messaging-friendly container for an OpenPGP message,
+ * emitted as a single Base58 word so it survives pasting into any chat app.
+ *
+ * A normal ASCII-armored PGP message is verbose and multi-line; pasted into a
+ * messenger its boilerplate, line-wraps and CRC footer get mangled. This codec
+ * emits one unbreakable Base58 word (the Bitcoin/IPFS alphabet, no ambiguous
+ * 0 O I l and nothing a messenger turns into markdown, a link or a soft-wrap).
+ *
+ * There is deliberately no marker, prefix or magic on the wire: the whole
+ * container is "whitened" with the shared password book. A per-message random
+ * seed and the book derive — via a memory-hard KDF — a set of secret bytes;
+ * part of them re-shuffles the byte order, the rest XORs the payload, and a
+ * secret-controlled amount of random padding is interleaved to hide the true
+ * length. The GpgFrontend recognition tag is a secret check value derived from
+ * the book (not a fixed constant), so there is no known-plaintext to search
+ * for; it lives inside this whitened blob, invisible on the wire and revealed
+ * only after a receiver un-whitens with the same book.
+ *
+ * The consequence: without the book, a token is indistinguishable from random
+ * Base58. Even *detecting* whether a token is one of ours costs the adversary a
+ * memory-hard derivation per candidate book, since there is no cheap
+ * pre-filter.
+ *
+ * Decode() does apply a shape test before that derivation — length bounds and
+ * Base58 alphabet membership — but only over information an observer already
+ * has for free, so it does not lower the adversary's per-candidate cost. It
+ * exists so that ordinary text a user hits Decrypt on stops at its first space
+ * or punctuation mark rather than paying the KDF.
+ *
+ * The shared "password book" is a 256-byte table derived from an optional
+ * phrase (Argon2id) or a fixed default. The empty-phrase default only hides the
+ * format from naive scanners; real indistinguishability against a GpgFrontend-
+ * aware adversary requires both sides to share a secret phrase.
+ */
+namespace ImToken {
+
+/// The result of decoding a token.
+struct DecodeResult {
+ bool ok{false}; ///< the token was one of ours and un-whitened
+ QByteArray pgp_message; ///< the wrapped OpenPGP message to decrypt
+};
+
+/**
+ * @brief Wrap an already-OpenPGP-encrypted message as one whitened Base58
+ * token, with the book @p phrase names (empty: the default book).
+ *
+ * @return the Base58 token, or empty on failure
+ */
+auto Encode(const QByteArray& pgp_message, const QString& phrase) -> QString;
+
+/**
+ * @brief Un-whiten @p text with the book @p phrase names and recover the
+ * wrapped OpenPGP message. @c ok is false when @p text is not one of our
+ * tokens (plain chat, or a different book).
+ */
+auto Decode(const QString& text, const QString& phrase) -> DecodeResult;
+
+/// The current container format version (the inner tag's version).
+auto FormatVersion() -> int;
+
+/**
+ * @brief The largest OpenPGP message Encode() will wrap, in bytes.
+ *
+ * Derived from the token-length bound Decode() enforces, so any token this
+ * codec produces is one this codec accepts.
+ */
+auto MaxPayloadBytes() -> int;
+
+/**
+ * @brief A fresh book phrase from the platform CSPRNG: 256 characters over the
+ * Base58 alphabet, or empty if the CSPRNG was unavailable.
+ */
+auto GeneratePhrase() -> QString;
+
+/**
+ * @brief A short, domain-separated digest (e.g. "3F9A-1C4E") of the book
+ * @p phrase produces, for peers to confirm by eye that they share a book.
+ *
+ * @warning Identifies the book: anyone who learns it can test candidate
+ * phrases offline. Display it locally; never put it on the wire. Costs one
+ * Argon2id (64 MiB) per distinct phrase; call it off the UI thread.
+ */
+auto BookFingerprintOf(const QString& phrase) -> QString;
+
+/// The per-message Argon2id cost, as (ops, memory in bytes).
+struct KdfCost {
+ unsigned long long ops;
+ size_t mem;
+};
+
+/// The per-message Argon2id cost the format ships with.
+auto DefaultKdfCost() -> KdfCost;
+
+/// The per-message Argon2id cost currently in effect.
+auto CurrentKdfCost() -> KdfCost;
+
+/**
+ * @brief Override the per-message Argon2id cost. **Tests only.**
+ *
+ * The cost is a protocol constant rather than something carried in the token,
+ * so Encode() and Decode() must agree on it, and it is the feature's whole
+ * security argument: lowering it anywhere but a test removes the protection.
+ */
+void SetKdfCostForTesting(KdfCost cost);
+
+} // namespace ImToken