28auto withGpgHome(QProcessEnvironment env,
const AppSettings &s)
29 -> QProcessEnvironment {
30 if (!s.gpgHome.isEmpty()) {
34 env.insert(QStringLiteral(
"GNUPGHOME"), home.path());
45auto gitEnvironment(
const QString &dir,
const AppSettings &s)
46 -> QProcessEnvironment {
47 QProcessEnvironment env =
48 withGpgHome(QProcessEnvironment::systemEnvironment(), s);
51 env.insert(QStringLiteral(
"GIT_LITERAL_PATHSPECS"), QStringLiteral(
"1"));
52 if (!s.gitUseParentRepository) {
53 for (
const auto &[name, value] :
55 env.insert(name, value);
63auto commitArgs(QProcess &git,
const AppSettings &s) -> QStringList {
68 {QStringLiteral(
"config"), QStringLiteral(
"--bool"),
69 QStringLiteral(
"--get"), QStringLiteral(
"pass.signcommits")},
70 QString(), &out,
nullptr) == 0 &&
71 out.trimmed() == QLatin1String(
"true");
72 return sign ? QStringList{QStringLiteral(
"commit"), QStringLiteral(
"-S")}
73 : QStringList{QStringLiteral(
"commit")};
78 return withGpgHome(QProcessEnvironment::systemEnvironment(), s);
89 return !dir.exists(
".gpg-id");
96 QString &out = note !=
nullptr ? *note : scratch;
99 const QDir folder(dir);
101 out = tr(
"Could not create %1.").arg(folder.absolutePath());
104 const QString gpgIdFile = folder.filePath(QStringLiteral(
".gpg-id"));
106 if (!
writeGpgId(gpgIdFile, users, !s.passSigningKey.trimmed().isEmpty(), &out,
111 if (!s.passSigningKey.trimmed().isEmpty()) {
112 if (!
signGpgId(gpgIdFile, written, s, &out)) {
115 sigFile = gpgIdFile + QStringLiteral(
".sig");
118 !
commitGpgId(folder.absolutePath(), gpgIdFile, sigFile, s, &out)) {
127 const QString root = QDir::cleanPath(folder.absolutePath());
128 const bool viaPass = s.usePass;
129 const QString rootList =
130 QFileInfo(QDir(root).filePath(QStringLiteral(
".gpg-id")))
131 .canonicalFilePath();
132 const auto underSubStore = [&root, &rootList, viaPass](
const QString &entry) {
133 QString dir = QFileInfo(entry).absolutePath();
134 while (dir != root && dir.startsWith(root + QLatin1Char(
'/'))) {
141 const QFileInfo list(QDir(dir).filePath(QStringLiteral(
".gpg-id")));
142 if (viaPass && list.isFile() && !rootList.isEmpty() &&
143 list.canonicalFilePath() == rootList) {
147 (viaPass || (!list.isSymLink() && !list.isJunction()))) {
150 dir = QFileInfo(dir).absolutePath();
154 QStringList existing =
157 std::remove_if(existing.begin(), existing.end(), underSubStore),
159 if (!existing.isEmpty()) {
160 out = tr(
"%1 already contains %n encrypted file(s); they were not "
161 "re-encrypted. Switch to the profile and open Users to do that.",
162 nullptr,
static_cast<int>(existing.size()))
163 .arg(folder.absolutePath());
169 const QList<UserInfo> &users,
bool signed_,
170 QString *note, QByteArray *written) ->
bool {
172 for (
const UserInfo &user : users) {
178 *note = tr(
"No recipient selected; %1 was not written.").arg(gpgIdFile);
184 QByteArray contents =
185 (ids.join(QLatin1Char(
'\n')) + QLatin1Char(
'\n')).toUtf8();
192 QFileInfo(gpgIdFile).absolutePath(), std::nullopt, &why);
197 const std::optional<qint64> generation =
211 if (written !=
nullptr) {
219 qCWarning(lcQtPass) <<
"Could not record the written .gpg-id:" << why;
227 QString *note) ->
bool {
232 [&s](
const QString &app,
const QStringList &args,
const QString &input,
233 QString *out, QString *err) {
235 gpg.setProcessEnvironment(
236 withGpgHome(QProcessEnvironment::systemEnvironment(), s));
237 return Executor::executeBlocking(gpg, app, args, input, out, err);
240 if (!signer.
sign(gpgIdFile, contents, &err)) {
241 *note = err.trimmed().isEmpty()
242 ? tr(
"Could not sign %1 with %2.")
243 .arg(gpgIdFile, signer.
keys().first())
244 : tr(
"Could not sign %1 with %2: %3")
245 .arg(gpgIdFile, signer.
keys().first(), err.trimmed());
248 if (!s.gpgHome.isEmpty()) {
249 QDir home(s.gpgHome);
251 if (!home.exists()) {
254 tr(
"The configured GPG home %1 does not exist.").arg(home.path());
264 QString *note) ->
bool {
268 git.setWorkingDirectory(dir);
269 git.setProcessEnvironment(gitEnvironment(dir, s));
270 auto run = [&](
const QStringList &args) ->
bool {
273 QString(),
nullptr, &err);
276 tr(
"git %1 failed in %2: %3").arg(args.first(), dir, err.trimmed());
281 QStringList files{QFileInfo(gpgIdFile).fileName()};
282 if (!sigFile.isEmpty()) {
283 files << QFileInfo(sigFile).fileName();
285 const QStringList add =
286 QStringList{QStringLiteral(
"add"), QStringLiteral(
"--")} + files;
287 if (!run({QStringLiteral(
"init")}) || !run(add)) {
290 return run(commitArgs(git, s) +
291 QStringList{QStringLiteral(
"-m"),
292 QStringLiteral(
"Added .gpg-id using QtPass."),
293 QStringLiteral(
"--")} +
298 QString *note) ->
bool {
300 git.setWorkingDirectory(dir);
301 git.setProcessEnvironment(gitEnvironment(dir, s));
302 auto run = [&](
const QStringList &args) ->
bool {
305 QString(),
nullptr, &err);
308 tr(
"git %1 failed in %2: %3").arg(args.first(), dir, err.trimmed());
317 const QDir base(dir);
322 {QStringLiteral(
"*.gpg"), QStringLiteral(
".gpg-id"),
323 QStringLiteral(
".gpg-id.sig")},
325 for (
const QString &path : found) {
326 files << base.relativeFilePath(path);
328 if (!run({QStringLiteral(
"init")})) {
331 if (!files.isEmpty() &&
332 !run(QStringList{QStringLiteral(
"add"), QStringLiteral(
"--")} + files)) {
335 return run(commitArgs(git, s) +
336 QStringList{QStringLiteral(
"--allow-empty"), QStringLiteral(
"-m"),
337 QStringLiteral(
"Added password store using QtPass.")});
343 git.setWorkingDirectory(dir);
344 git.setProcessEnvironment(gitEnvironment(dir, s));
345 for (
const char *key : {
"user.name",
"user.email"}) {
348 {QStringLiteral(
"config"),
349 QStringLiteral(
"--get"),
350 QString::fromLatin1(key)},
351 QString(), &out,
nullptr) != 0 ||
352 out.trimmed().isEmpty()) {
AppSettings, the value object holding every flat QtPass setting.
static auto executeBlocking(const QString &app, const QStringList &args, const QString &input=QString(), QString *process_out=nullptr, QString *process_err=nullptr) -> int
Run a command synchronously and return its exit code.
static auto reserveNext(const QString &gpgIdFile, std::optional< qint64 > verifiedOnDisk, QString *error=nullptr) -> std::optional< qint64 >
Reserve the generation to write next for gpgIdFile: one above both what this device remembers and ver...
static auto recordWritten(const QString &gpgIdFile, const QByteArray &contents, QString *error=nullptr) -> bool
Record the bytes just written for gpgIdFile, so that this device recognises its own list afterwards a...
static auto withHeader(qint64 generation, const QString &folder, const QByteArray &recipients, const QString &store={}) -> QByteArray
A complete list: the header in front of the recipients.
static auto storeIdFor(const QString &storeRoot, const std::optional< QString > &onDisk, QString *error=nullptr) -> std::optional< QString >
The store id to write into a list of storeRoot: the one this device knows the store by; else onDisk,...
The detached signature on a store's .gpg-id: whether the configured signing key is usable,...
auto sign(const QString &gpgIdFile, const QByteArray &contents, QString *error=nullptr) const -> bool
Write <file>.sig, a detached signature by the first key over contents, the bytes the caller just wrot...
auto keys() const -> const QStringList &
The keys this signer was given.
static auto keysFromSetting(const QString &passSigningKey) -> QStringList
Split the settings value into keys, upper-cased the way gpg prints fingerprints.
static auto needsInit(const QString &path) -> bool
Check if a profile path needs initialization.
static auto initGit(const QString &dir, const AppSettings &s, QString *note) -> bool
Put an existing store under Git: git init, stage the .gpg entries and the .gpg-id files (nothing else...
static auto commitGpgId(const QString &dir, const QString &gpgIdFile, const QString &sigFile, const AppSettings &s, QString *note) -> bool
git init in dir, stage the .gpg-id and its signature, and commit them (signed when pass....
static auto gpgEnvironment(const AppSettings &s) -> QProcessEnvironment
The environment QtPass runs gpg in outside the active store's backend: the system's,...
static auto gitIdentityConfigured(const QString &dir, const AppSettings &s) -> bool
Whether git has a name and e-mail to commit with, i.e. user.name and user.email resolve (globally or ...
static auto initialise(const QString &dir, const QList< UserInfo > &users, const AppSettings &s, bool useGit, QString *note) -> bool
Write the recipients and, if wanted, put the folder under git.
static auto writeGpgId(const QString &gpgIdFile, const QList< UserInfo > &users, bool signed_, QString *note, QByteArray *written=nullptr) -> bool
Write the enabled recipients to gpgIdFile, whole or not at all; with signed_ the list carries the gen...
static auto signGpgId(const QString &gpgIdFile, const QByteArray &contents, const AppSettings &s, QString *note) -> bool
Sign the list just written as gpgIdFile, over the bytes contents that were written.
static auto regularFilesUnder(const QString &dir, const QStringList &nameFilters, QStringList *skipped=nullptr, bool hiddenFiles=false) -> QStringList
Regular files under dir whose name matches one of nameFilters, found by walking only real directories...
static auto writeFileReplacing(const QString &path, const QByteArray &bytes, bool replace, QString *error=nullptr) -> bool
Write bytes as the file path through stageFileReplacing().
static auto gitStoreVariables(const QString &store, const QString &git={}, std::optional< uint > asUser=std::nullopt) -> QList< std::pair< QString, QString > >
The variables that keep git in store's own repository, never one above it: GIT_CEILING_DIRECTORIES (g...
static auto mkpathForStore(const QString &path) -> bool
Create path and any missing parents as pass would.
Executor, the FIFO queue every gpg, git and pass child process of QtPass goes through,...
Rollback detection for signed .gpg-id files: the generation counter and the record of what was last s...
GpgIdSigner, which signs a store's .gpg-id and verifies an existing .gpg-id.sig against the configure...
Initialising a new profile's store (.gpg-id, signature, Git) with blocking processes,...
The "qtpass" logging category.
Plain value-object holding all flat QtPass configuration.
Stores GPG key info including validity, creation date, and more.
UserInfo, one GPG key as listed by gpg, with the selection flag the recipient dialogs use.
Util: static helpers for the process environment, binary discovery, store paths, URL safety and the l...