- Rust 64%
- QML 34.9%
- JavaScript 0.7%
- Shell 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .cargo | ||
| .forgejo | ||
| .githooks | ||
| assets | ||
| docs | ||
| passcore | ||
| po | ||
| qml | ||
| src | ||
| tests/qml | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| clickable.yaml | ||
| clippy.toml | ||
| LICENSE | ||
| Makefile.toml | ||
| manifest.json | ||
| README.md | ||
| touchpass.apparmor | ||
| touchpass.desktop | ||
TouchPass
A native Ubuntu Touch (Lomiri) client for the pass
password store, in the spirit of Android's Password Store app. Rust + QML via
qmetaobject-rs, built with
clickable.
What works so far
- Fully confined (standard AppArmor policy, passes
click-review), so it qualifies for the OpenStore without manual review. - First-run wizard: generate a key (passphrase optional) or import an exported
one through the system file picker (ContentHub), then create a new store.
Or clone one: the wizard asks for the key it is encrypted to, sets up a
clone without
.gpg-id(e.g. an empty repository) in place, and can start over (only if the remote has everything) after cloning the wrong one. - Browses the store folder by folder; search finds entries in the current folder and below.
- Tapping an entry decrypts it in-process. A protected key's passphrase is asked once and remembered in memory until Lock or the app exits.
- Shows the password (hidden until toggled) and the remaining lines (user, URL, notes).
- Copy password puts it on the system clipboard with Lomiri's native
ClipboardAPI, so any app can paste it. It is cleared as soon as you come back to TouchPass from pasting it, or after 45 s if you stay. Lomiri suspends TouchPass while you are in another app, so until you come back the password stays pasteable. - Follows the system light/dark theme, live.
- About (☰ menu on the entry list): version and the commit it was built from, for bug reports, plus author, license and a link to the source.
- Add and edit entries, with a password generator (length, character
classes, your own symbol set, look-alike characters left out). Username
and notes lines keep their layout, so entries edited on the phone still
read the same in
passand browserpass. - Rename and delete entries. Change the name in the editor to rename
(moved like
pass mv, so an edit made on another device follows it); delete from the entry, after a confirmation. Both are refused if a sync changed the entry since you opened it. - Git sync with the remote the store was cloned from: on open, after every change, and retried with backoff (and as soon as the phone is back online) until it goes through. Edits of the same entry on two devices resolve to the newer one; the other stays in git history.
Layout
| Path | What |
|---|---|
passcore/ |
Pure-Rust library: store, entry format, password generator, Keyring (rPGP: key generation, import, encryption, decryption, recipient checks), git clone and sync (fetch/merge/push) with the re-encryption guard. No Qt, testable on any Linux box. |
src/bridge.rs, src/sync_service.rs |
The PassStore and SyncService QObjects exposed to QML. OpenPGP and network work runs on a worker thread and reports back via signals. |
qml/ |
One file per page, dialog or non-visual helper (SyncScheduler.qml decides when to sync); Main.qml only starts the app and holds the app-wide objects. |
tests/qml/ |
QML unit tests (Qt Quick Test) for SyncScheduler and EntryEditor, against fake SyncService/PassStore objects. |
touchpass.apparmor |
Confined: connectivity, content_exchange and networking policy groups. |
Design notes
- App-owned data. A confined app may only write under its own directories,
so everything lives in
~/.local/share/touchpass.aps/:store/(a normal pass store) andkeys/(one armored secret key per file, as exported by gpg, mode 0600).~/.password-storeand~/.gnupgare not visible to the app;passin the Terminal keeps using those. - OpenPGP in-process with rPGP. Confined
apps cannot exec
/usr/bin/gpgor reach gpg-agent. The tests check interoperability both ways against real GnuPG: entries written bypass(compressed or not) decrypt here, entries written here decrypt in gpg, and keys exported bygpg --export-secret-keysor--export-secret-subkeys(GnuPG "stub" primary) import fine. Encryption uses SEIPDv1, which all GnuPG versions read. - Passphrase cache. Without gpg-agent, the app keeps the last working passphrase in memory only. Nothing is written to disk.
- Sync. Every change is committed at once, so unpushed commits are the
queue of pending changes; nothing else is persisted. Conflicts resolve to
the newer change (per entry, by the time of the last commit that really
changed it; merges and re-encryptions don't count), deletes included, with
a tie between an edit and a delete keeping the edit;
an entry renamed differently on two devices keeps both names, and one
added on both keeps the local one as
<name>-conflict. Saving an edit is refused if a sync changed the entry since it was opened. After a merge, entries whose recipients no longer match their.gpg-idare re-encrypted (the local refrefs/touchpass/checkedtracks how far that was verified). Syncs only run while the app is open: Lomiri suspends apps in the background. - Framework
ubuntu-touch-24.04-1.x. Check that the phone supports it withls /usr/share/click/frameworks/on the device.
Build & run
Same conventions as the other Rust projects in ~/dev: rustfmt defaults,
clippy lints in [workspace.lints] with thresholds in clippy.toml, tasks in
Makefile.toml (cargo-make),
and the same checks in Forgejo CI (.forgejo/workflows/ci.yml).
cargo make install-hooks # once per clone: format on every commit
cargo make # = cargo make ci: fmt check, clippy -D warnings,
# tests + coverage, release check, cargo audit
cargo make qml-test # QML tests in clickable's amd64 image (Docker)
cargo make click # build the arm64 .click (clickable; runs click-review)
cargo make click-install # ... and install + launch it on the phone over adb
clickable logs --arch arm64 # follow the app's output on the phone
The host checks need Qt 5 dev files (qmake) and gettext for the app crate;
passcore's tests use gpg and git, if installed, as reference
implementations. Accepted cargo audit advisories are listed, with reasons,
in .cargo/audit.toml.
The QML tests need Lomiri, which most desktops don't package, so they run
in clickable's image (tests/qml/run.sh under Xvfb; CI's qml job does the
same). Not clickable test: it reports success even when tests fail. Pass a
test to run just that one, e.g. cargo make qml-test SyncScheduler::test_syncs_on_start.
Roadmap
- Folder navigation, with search scoped to the current folder.
- Clone an existing store from Git (git2, SSH deploy key) — the way to bring an existing store in.
- Git sync (pull/push), automatic with retries.
- Wizard: handle a store that exists but has no matching key yet (import it, or start over).
- Cache unlocked keys instead of the passphrase, to skip the S2K on every open.
- Add / edit / generate entries (encrypt to the recipients in
.gpg-id). - Delete / rename entries.
- Import other people's public keys (shared stores).
- OTP (
otpauth://lines) support.
Credits
The app icon (assets/logo.svg) is public domain. Its key is "Simple key" by rygle
from OpenClipart (public domain); the folder and background were drawn for this project.
License
Copyright (C) 2026 Austin Schaefer
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, 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 this program. If not, see http://www.gnu.org/licenses/.