Android SDK

Keywords

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:publishToMavenLocal and depend on it as implementation("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)
}
Note

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 EncryptedFile from 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:dokkaHtml

Contribute

Contributions are welcome. The library lives under android/openmekit/ and the app under android/app/. See the top-level README for development setup.