Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ Pending outbound records are contact-bound. A `READY` operation may be offered
once without another ratchet step. Before the external Binder call it is
durably changed to `COMMIT_UNCERTAIN`; this state is never automatically
retried because the host may have accepted text even when its acknowledgement
was lost. The handoff is bound to the pending operation, exact ciphertext,
original host package/UID, input field metadata, selection and exact
`InputBinding.connectionToken`. `LatinIME` performs the only permitted host
was lost. The handoff is bound to the pending operation, exact ciphertext, the
exact live `InputConnection` object, original host package/UID, input field
metadata and selection. `LatinIME` performs the only permitted host
`commitText()` and completes the pending record after an accepted insertion.
Inbound recovery is bound to the digest of the complete ordered ciphertext.
The embedded surface acknowledges the pending display only after its first
Expand Down Expand Up @@ -268,12 +268,15 @@ history, clipboard history, paste and plaintext clipboard fallbacks are
disabled. The IME window sets `FLAG_SECURE`. Password fields require an
explicit warning before activation.

The scope stores the exact non-null `InputBinding.connectionToken` as well as
package/UID and editor metadata. Any field or token change closes the panel and
wipes the draft and embedded plaintext. The Vault-unlock activity is
non-exported and may cause Android to recreate the connection; one explicit,
metadata-matching token rebind is allowed only after its one-shot process-local
handoff reports a successful unlock.
The scope stores the exact live `InputConnection` object as well as package/UID
and editor metadata. When both old and current `InputBinding.connectionToken`
values are available they must also match, but Android may temporarily omit or
replace that token during an IME lifecycle transition. Any unapproved live
connection or field change closes the panel and wipes the draft and embedded
plaintext. The non-exported Vault-unlock activity may cause Android to recreate
the connection; one explicit, metadata-matching live-connection rebind is
allowed only after its one-shot process-local handoff reports a successful
unlock. A locked draft is wiped before that transition.

Android physical-keyboard dispatch is outside this software-key routing
boundary and may deliver characters directly to the focused host view. Private
Expand All @@ -287,7 +290,9 @@ same revision-checked storage operation. `SecureImeBridge` receives a one-shot
process-local claim containing its operation ID and the exact transport text
deterministically reconstructed from the persisted canonical parts and
versioned presentation enum. The claim is scoped to the active embedded host
scope, including the exact connection token. The runtime always produces canonical `CB1` parts with the universal
scope, including the exact live `InputConnection` identity, host UID/package and
editor metadata. A Binder token is only an additional check because Android can
temporarily omit or replace it. The runtime always produces canonical `CB1` parts with the universal
16-KiB core fragmentation profile, then applies the sender's local presentation
preference. The receiver does not need the same preference because decode is
auto-detected.
Expand Down Expand Up @@ -330,7 +335,7 @@ plaintext are never Intent extras. The activity must activate the token once
and complete it once. Cancellation, duplicate activation, timeout, screen-off,
or a failed/mismatched host-scope return invalidates the handoff and closes the
panel. Only then may the exact editor metadata obtain its single authorised
connection-token rebind. `decryptUnlocked()` never presents authentication UI
live-connection rebind. `decryptUnlocked()` never presents authentication UI
and fails closed if the Vault expired between the handoff and worker execution.
Parser and decrypt completions cross to the main thread through an owned-result
handoff. Cancellation invalidates its generation and closes queued or late
Expand Down Expand Up @@ -375,7 +380,7 @@ ratchet revision supplies local concurrency control.
4. In one database transaction, store session revision `n+1` and an encrypted
pending record containing contact ID, canonical `CB1` parts, and the
versioned presentation enum.
5. Only after commit and exact connection-token validation, claim the `READY`
5. Only after commit and exact live-connection validation, claim the `READY`
operation once.
6. Atomically change it to `COMMIT_UNCERTAIN` before crossing the host Binder
boundary.
Expand Down
4 changes: 2 additions & 2 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ The Gradle product identity is centralized in `gradle.properties`:
```text
cipherboard.applicationId=org.cipherboard.securekeyboard
cipherboard.productName=CipherBoard
cipherboard.versionCode=40000
cipherboard.versionName=0.4.0
cipherboard.versionCode=40001
cipherboard.versionName=0.4.1
cipherboard.artifactName=CipherBoard
```

Expand Down
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@ All notable CipherBoard changes are documented in this file. The project uses
[Semantic Versioning](https://semver.org/spec/v2.0.0.html) from version 0.1.0.
Pre-1.0 releases may contain compatibility changes that require re-pairing.

## [0.4.1] - 2026-07-14

### Fixed

- The language key now switches between enabled CipherBoard layouts while the
embedded Private panel is active, including while the Vault is locked or the
panel is temporarily unable to accept plaintext. It never opens another IME
from the protected editor.
- Private mode can open after returning from another Android keyboard when the
framework supplies a live `InputConnection` but temporarily omits its Binder
connection token. `TYPE_NULL`, text, numeric, multiline and password editors
all use the same live-editor path; password editors retain their explicit
warning.

### Security Notes

- The null-token fallback is bound to the exact live `InputConnection` object,
host UID/package and editor metadata. A matching client Binder token alone
cannot authorize ciphertext insertion into a different field.
- The one-shot ciphertext handoff independently requires the same live
`InputConnection`, so a lifecycle or focus change fails closed before
`commitText()`.

## [0.4.0] - 2026-07-14

### Added
Expand Down Expand Up @@ -206,6 +229,7 @@ Pre-1.0 releases may contain compatibility changes that require re-pairing.
Android security audit. Physical GrapheneOS, StrongBox, TEE-only, live-camera
pairing, and hostile-device validation remain necessary before high-risk use.

[0.4.1]: https://github.com/bglglzd/CipherBoard/releases/tag/v0.4.1
[0.4.0]: https://github.com/bglglzd/CipherBoard/releases/tag/v0.4.0
[0.3.0]: https://github.com/bglglzd/CipherBoard/releases/tag/v0.3.0
[0.2.0]: https://github.com/bglglzd/CipherBoard/releases/tag/v0.2.0
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ HeliBoard release and is not endorsed or supported by the HeliBoard project.

| Project fact | Current value |
| --- | --- |
| Maturity | Pre-1.0; current version `0.4.0` |
| Maturity | Pre-1.0; current version `0.4.1` |
| Application ID | `org.cipherboard.securekeyboard` |
| Android baseline | `minSdk 23`, `targetSdk 36`; acceptance target is current GrapheneOS |
| Release ABI | `arm64-v8a`; debug builds also include `x86_64` for emulators |
| Runtime network | No Internet or network-state permission; no runtime network feature |
| Interface languages | English and Russian |

See the bilingual [CipherBoard 0.4.0 release notes](docs/releases/v0.4.0.md).
See the bilingual [CipherBoard 0.4.1 release notes](docs/releases/v0.4.1.md).

## What It Does

Expand Down Expand Up @@ -156,14 +156,14 @@ project and assume it is CipherBoard.
Verify the release checksum before installation:

```sh
sha256sum --check CipherBoard-0.4.0-release.apk.sha256
sha256sum --check CipherBoard-0.4.1-release.apk.sha256
```

On Windows PowerShell:

```powershell
(Get-FileHash .\CipherBoard-0.4.0-release.apk -Algorithm SHA256).Hash.ToLowerInvariant()
Get-Content .\CipherBoard-0.4.0-release.apk.sha256
(Get-FileHash .\CipherBoard-0.4.1-release.apk -Algorithm SHA256).Hash.ToLowerInvariant()
Get-Content .\CipherBoard-0.4.1-release.apk.sha256
```

If Android Build Tools are installed, also verify the APK signature and compare
Expand All @@ -172,13 +172,13 @@ the reported SHA-256 certificate digest with
digest through a channel you trust independently of the APK download.

```sh
apksigner verify --verbose --print-certs CipherBoard-0.4.0-release.apk
apksigner verify --verbose --print-certs CipherBoard-0.4.1-release.apk
```

Install or update the verified APK:

```sh
adb install -r CipherBoard-0.4.0-release.apk
adb install -r CipherBoard-0.4.1-release.apk
```

Debug APKs are developer artifacts signed with a public debug key. Do not use
Expand Down
6 changes: 3 additions & 3 deletions SECURITY_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,15 @@ still unverified.
| --- | --- | --- | --- | --- |
| IME-01 | Ordinary HeliBoard English/Russian/emoji/symbol/Unicode input remains functional | 5, 26, 31 | Pending | Debug app survives the fixed locale change before IME selection; actual ordinary-keyboard regression/instrumentation suite remains |
| IME-02 | Shield action toggles a CipherBoard-owned Private panel above the keys without navigating away from the host | 11 | Implemented | Embedded controller/layout and emulator visual inspection show an in-IME panel; complete UI instrumentation pending |
| IME-03 | Software-key plaintext stays in the local draft and never reaches host `InputConnection`, composing region, or simulated key events | 11, 31 | Implemented | Bounded local connection, early routing gates and exact-token host scope have unit/source coverage; hostile host `EditText` instrumentation remains required; hardware keyboards are explicitly unsupported for Private drafts |
| IME-04 | Only exact persisted ciphertext is committed with `commitText()` after explicit encryption | 11 | Implemented | One-shot pending handoff is bound to originating host package/UID/editor and exact `InputBinding.connectionToken`; framework capture test pending |
| IME-03 | Software-key plaintext stays in the local draft and never reaches host `InputConnection`, composing region, or simulated key events | 11, 31 | Implemented | Bounded local connection, early routing gates and exact live-connection host scope have unit/source coverage; hostile host `EditText` instrumentation remains required; hardware keyboards are explicitly unsupported for Private drafts |
| IME-04 | Only exact persisted ciphertext is committed with `commitText()` after explicit encryption | 11 | Implemented | One-shot pending handoff is bound to the originating live `InputConnection`, host package/UID and editor metadata; a missing or rotated Binder token cannot authorize another connection; framework capture test pending |
| IME-05 | Plaintext remains visible after successful ciphertext insertion and clears on explicit clear/close, host-field change, lock, screen-off, or IME destruction | 11, 15 | Implemented | Embedded controller owns and wipes the bounded draft; lifecycle/heap/field-switch instrumentation pending |
| IME-06 | Private mode disables personalized learning, user dictionary, input history, persistent drafts, and clipboard history | 11, 23 | Implemented | Central secure marker disables learning/suggestions/history and blocks copy/cut/paste/share/voice/IME-picker paths; preference-enabled device sentinel test pending |
| IME-07 | Plaintext is absent from saved state, long-lived ViewModels, intents, preferences, files, cache, and database | 11, 23 | Implemented | No-save/no-ViewModel/no-plaintext-extra/persistence path by source; sentinel scan pending |
| IME-08 | Embedded Private panel shows contact verification, vault state, size estimates, clear/close/encrypt controls | 11 | Implemented | Controls/statuses present even without paired contacts; UI/accessibility test pending |
| IME-09 | Password fields show an explicit warning; no automatic encryption/commit occurs | 26 | Implemented | Embedded controller requires acknowledgement; instrumentation pending |
| IME-10 | Layout handles large fonts, landscape, light/dark theme, optional dynamic color, English/Russian strings, and RTL rendering | 21, 22 | Pending | Screenshot/accessibility/localization matrix |
| IME-11 | Exact host binding token scopes the Private panel; only one metadata-matching token rebind is allowed after non-exported Vault unlock | 11, 17 | Implemented | `EmbeddedHostScope` unit coverage exists; Android reconnect/field-collision instrumentation pending |
| IME-11 | Exact live `InputConnection`, host UID/package and editor metadata scope the Private panel; a Binder token is checked when both values exist; only one metadata-matching live-connection rebind is allowed after non-exported Vault unlock | 11, 17 | Implemented | `EmbeddedHostScope` unit coverage exists; Android reconnect/field-collision instrumentation pending |
| IME-12 | Private panel uses `FLAG_SECURE`; its RAM/UI clearing is documented as best effort, not guaranteed zeroization | 15, 24 | Implemented | Emulator capture was black and documentation states JVM/UI limits; physical screenshot/heap evidence pending |
| IME-13 | Physical keyboard bypass is not treated as protected Private input | 11, 26 | Implemented | UI/docs instruct use of on-screen keys; host-level physical-key sentinel and GrapheneOS device test pending |

Expand Down
20 changes: 16 additions & 4 deletions SECURITY_REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ physical-device or GrapheneOS evidence.

## Evidence Observed

- The 0.4.1 IME lifecycle fix accepts Android's temporarily missing
`InputBinding.connectionToken` only when the exact live `InputConnection`
object, host UID/package and editor metadata still match. Unit tests reject a
different connection even with the same Binder token and metadata, and the
one-shot `SecureImeBridge` independently enforces the same referential
connection identity before ciphertext publication.
- API 36 manual regression testing confirmed that the embedded Private panel
switches RU to EN locally while the Vault is locked. Static policy tests keep
the external IME picker blocked and allow only the local language action and
explicit shield close while plaintext input is unavailable.

- `vodozemac 0.10.0` is exact-versioned with default features disabled and Olm
`SessionConfig::version_2()` enforced. Rust Cargo lockfiles are checked in.
- The latest native test run reported 43 passing tests and the JNI crate
Expand Down Expand Up @@ -83,8 +94,9 @@ physical-device or GrapheneOS evidence.
positive-duration legacy key is generated.
- The v0.2 source replaces shield-to-activity navigation with an embedded
Private mode panel. Software-key and IME edit paths target a bounded local
draft, the active host is scoped by exact `InputBinding.connectionToken`, and
personalized learning/clipboard-history paths are disabled. Detailed
draft, the active host is scoped by the exact live `InputConnection`, host
UID/package and editor metadata, and personalized learning/clipboard-history
paths are disabled. Detailed
inherited InputLogic diagnostics are unconditionally suppressed for the
secure editor, including chosen words, n-gram context and code points; a
policy regression test covers this gate. Visual emulator
Expand Down Expand Up @@ -161,9 +173,9 @@ physical-device or GrapheneOS evidence.
| Replay | 4096 IDs in serialized session plus per-contact 8192-marker SQLite bound committed with inbound state | inbound SIGKILL/reopen test passes; wider long-run/device restart matrix remains |
| Storage | AES-256-GCM records with type/key/schema/revision AAD; random nonces; no-backup CE location | stolen-DB, WAL/SHM, corruption and backup/transfer device tests |
| Keystore | non-exportable AES wrapping key; StrongBox-first; only reported TEE accepted as fallback; software/unknown rejected; user authentication | real StrongBox/TEE/invalidation/reboot tests |
| Send atomicity | advanced ratchet plus contact-bound exact pending ciphertext commit; `READY` changes durably to `COMMIT_UNCERTAIN` before one exact-token-scoped host commit; uncertain delivery cannot auto-retry | existing SIGKILL commit-boundary tests and new codec/store/bridge unit coverage; individual SQLite statements and real host-ack window remain |
| Send atomicity | advanced ratchet plus contact-bound exact pending ciphertext commit; `READY` changes durably to `COMMIT_UNCERTAIN` before one exact-live-connection-scoped host commit; uncertain delivery cannot auto-retry | existing SIGKILL commit-boundary tests and new codec/store/bridge unit coverage; individual SQLite statements and real host-ack window remain |
| Receive atomicity | replay, advanced ratchet and encrypted pending display commit together; exact-ciphertext digest recovery; pre-first-draw abandon retains record; first allowed draw acknowledges the lease | real post-commit SIGKILL and close/reopen plus targeted first-draw tests pass; in-transaction and post-draw/pre-close kill failpoints remain |
| Private panel | shield toggles an embedded `FLAG_SECURE` IME panel; bounded Encrypt draft; software keys/edit actions route locally; Decrypt hides keys; no saved state/plaintext copy/share/learning/clipboard history; exact connection-token scope and lifecycle clearing | locked Encrypt and idle Decrypt states fit API 36 landscape at font scale 2.0 in English/Russian; paired-contact/long-text matrix, hostile-host, hardware-keyboard and physical GrapheneOS evidence remain |
| Private panel | shield toggles an embedded `FLAG_SECURE` IME panel; bounded Encrypt draft; software keys/edit actions route locally; Decrypt hides keys; no saved state/plaintext copy/share/learning/clipboard history; exact live-connection scope and lifecycle clearing | locked Encrypt and idle Decrypt states fit API 36 landscape at font scale 2.0 in English/Russian; paired-contact/long-text matrix, hostile-host, hardware-keyboard and physical GrapheneOS evidence remain |
| Decrypt/viewer | explicit bounded ciphertext clipboard read; clipboard unchanged; owned result handoff; one-shot unlock token; drawing-only inaccessible embedded/activity text; render-time Vault gate; background cancellation; local opaque reply capability | targeted v0.3 race/surface tests and 7/7 API 36 process-text/FLAG_SECURE/background-wipe/clipboard instrumentation pass; embedded paired-contact E2E, screenshot/recents/Assistant/Accessibility/screen-lock evidence remains |
| Pairing/contact | signed native offer/response; encrypted one-shot state; bounded orphan cleanup; explicit comparison; changed identity blocks use until verification | live two-device, camera permission, lifecycle, process-kill and hostile-QR instrumentation |
| QR | local ZXing codec and lifecycle-bound CameraX scanner with bounded ASCII payloads; Camera requested only by the Scan actions | real permission grant/deny/revoke and two-device camera evidence |
Expand Down
Loading