Android SDK
OpenMeKit Android, openme Android SDK, KnockService Kotlin, Android SPA library, KDoc reference, openme Kotlin API
OpenMeKit Android
OpenMeKit is the official Android client library for the openme Single Packet Authentication (SPA) protocol.
It provides everything you need to add firewall-knocking to your own Android app: profile management, YAML / QR import, and the full crypto knock stack.
Features
| Capability | Details |
|---|---|
| SPA knock | Ed25519 + X25519 ECDH + HKDF-SHA256 + ChaCha20-Poly1305 over UDP |
| Profile storage | Jetpack DataStore Preferences (app-private, no cloud sync) |
| YAML import | Parses openme add output (go-yaml v2 + v3 indents) |
| QR import | Parses the JSON payload from openme qr |
| Min SDK | Android 10 (API 29) |
| Language | Kotlin 2.0 |
Installation
The library is distributed as a Gradle module. Add it to your project:
settings.gradle.kts
include(":openmekit")
project(":openmekit").projectDir = file("path/to/openmekit")app/build.gradle.kts
dependencies {
implementation(project(":openmekit"))
}Alternatively, publish the library to a local Maven repository with
./gradlew openmekit:publishToMavenLocaland depend on it asimplementation("org.merlos:openmekit:1.0.0").
Quick start
1. Import a profile from YAML
Profiles are created on the server with openme add <name> and appended to ~/.openme/config.yaml. Copy-paste that YAML into the app:
val yaml = """
profiles:
my-server:
server_host: "203.0.113.1"
server_udp_port: 54154
server_pubkey: "<base64>"
private_key: "<base64>"
public_key: "<base64>"
""".trimIndent()
val profiles = ClientConfigParser.parseYaml(yaml)
val store = ProfileStore(context)
store.saveAll(profiles)2. Import a profile from a QR code
QR codes are generated by openme qr <name> on the server. The payload is a compact JSON object:
// json = string decoded from the QR scan
val profile = ClientConfigParser.parseQRPayload(json)
store.save(profile)3. Send a knock
// From a ViewModel / coroutine
val result = KnockManager(context).knock("my-server")
when (result) {
is KnockResult.Success -> showToast("Knock sent!")
is KnockResult.Failure -> showError(result.error.message)
}A Success result only confirms the UDP datagram was dispatched by the OS. It doesn’t guarantee the server received it or that a firewall rule was opened. Verify connectivity by attempting a TCP connection to the service port after knocking.
API Reference
Full KDoc API documentation is generated by Dokka and published alongside this site:
→ OpenMeKit Android KDoc Reference
To generate locally:
cd android
./gradlew openmekit:dokkaHtml
# Output: docs/android-sdk/openmekit/Key classes
KnockService
Pure-Kotlin, static implementation of the 165-byte SPA packet:
KnockService.knock(
serverHost = "203.0.113.1",
serverPort = 54154,
serverPubKeyBase64 = "<base64>",
clientPrivKeyBase64 = "<base64>",
)See the Protocol → Packet Format page for the wire format specification.
KnockManager
Coroutine-friendly wrapper that loads the profile from ProfileStore and calls KnockService on Dispatchers.IO:
class MyViewModel(app: Application) : AndroidViewModel(app) {
private val knockManager = KnockManager(app)
fun knock(profileName: String) = viewModelScope.launch {
when (val r = knockManager.knock(profileName)) {
is KnockResult.Success -> { /* update UI */ }
is KnockResult.Failure -> { /* show error */ }
}
}
}ProfileStore
Reactive DataStore-backed persistence. Observe the profile list with a StateFlow:
val store = ProfileStore(context)
// Collect in a ViewModel:
store.profileEntries
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
.collect { entries -> updateList(entries) }ClientConfigParser
Parses YAML configs and QR JSON payloads:
| Method | Input |
|---|---|
ClientConfigParser.parseYaml(yaml) |
Full config.yaml content |
ClientConfigParser.parseQRPayload(json) |
JSON decoded from a QR code |
Crypto stack
The knock packet uses the same crypto as every other openme client:
| Layer | Algorithm | Library |
|---|---|---|
| Key agreement | X25519 (Curve25519 ECDH) | BouncyCastle bcprov-jdk15to18 |
| Key derivation | HKDF-SHA256 | javax.crypto.Mac (HmacSHA256) |
| Encryption | ChaCha20-Poly1305 | javax.crypto.Cipher (Android API 29+) |
| Signature | Ed25519 | BouncyCastle Ed25519Signer |
See Protocol → Cryptography for the full specification.
Threat model
- Private keys are stored in app-private DataStore (
/data/data/<pkg>/files/datastore/), inaccessible to other apps on non-rooted devices. - Logs never include key material.
- For defence-in-depth on API 23+ you can wrap the DataStore file with
EncryptedFilefrom Jetpack Security.
See Security Model for the full server-side threat model.
Building the Android app
cd android
# Initialise the Gradle wrapper (first time only — requires Gradle installed)
gradle wrapper
# Build debug APK
./gradlew app:assembleDebug
# Install on connected device/emulator
./gradlew app:installDebug
# Run unit tests
./gradlew openmekit:test
# Generate API docs
./gradlew openmekit:dokkaHtmlContribute
Contributions are welcome. The library lives under android/openmekit/ and the app under android/app/. See the top-level README for development setup.