This crate provides storage and management of Keyring credentials in Android's native SharedPreferences store, securing all passwords and secrets using encryption via credentials in Android’s native Keystore. Once this library has been loaded and initialized (as described below) by your Android-native application, other Rust code linked into your application can use this credential store.
As usual for Keyring credential stores, you create a credential store by invoking Store::new (or Store::new_with_configuration). In order for this to work, however, your application must first have initialized the application_context object provided by the ndk-context crate and also loaded this library.
A number of Android/Rust application frameworks, such as Dioxus Mobile, Tauri Mobile and the android-activity crate, already provide this initialization for you. If your framework does not, then this crate also provides an initialization function that you can invoke at your application's startup. To do this, you add a Kotlin class io.crates.keyring.Keyring to your main application with the following content:
package io.crates.keyring;
import android.content.Context
class Keyring {
companion object {
init {
// see Note 1, below
System.loadLibrary("android_native_keyring_store")
}
external fun initializeNdkContext(context: Context);
}
}Then, from your main activity’s onCreate method, you have your app load the library and initialize the ndk-context:
class MainActivity : ComponentActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Keyring.initializeNdkContext(this.applicationContext);
...
}
...Note 1: This code expects that a library file libandroid_native_keyring_store.so was compiled from this crate and attached to your application. See the next section for details on how to do that. It’s possible that your application framework may already provide a way to attach and pre-load external libraries. If so, you won’t need the init section above that loads the library.
Because the Android/Rust ecosystem is still relatively new, there is a lot of conflicting and outdated information about how to build Rust code for Android. At the time of this writing, there are quite a few Gradle plugins that try to automate this process, as well as a number of Rust crates that try to make the process easier. But if you are new to Android programming or are having trouble getting these solutions to work, here is a bare-bones guide that shows how to build and attach this crate’s library manually to your application.
-
Start by getting the released sources of the version of this crate you want to use. In what follows, we’ll assume they’re in the folder named
~/src/android-native-keyring-store. -
Rust libraries for use in Android applications are invoked from the application via JNI. This means they must be compiled as dynamic libraries (
.sofiles) that can be loaded by the Android runtime. This crate is already setup to build a.solibrary as well as a (Rust-only)rlib. -
One way to attach
.sofiles to an Android application is to add anapp/src/main/jniLibsfolder to your source tree, with subfolders for each of the Android API targets you wish to support (e.g.,arm64-v8aand/orx86-64). Do this unless your application framework specifies some other way. -
Now you need to build this crate specifying the appropriate
--targetfor each of the Android API targets you are supporting. For example, if you want to support thearm64-v8aAndroid target, you would specify the command:cargo build --target aarch64-linux-android --release
There is a lot of consistent documentation on the web about which Android targets correspond to which cargo targets, so we won’t give more examples here.
-
Note that step (4) is likely to be a cross-compilation, because your dev machine is probably not an Android machine. This means that you will need both the appropriate rust standard libraries for that target and a linker for that target to be available:
-
The libaries are provided by the Rust team and can be installed via
rustup. For example, to add the libraries needed in the example of step (4), you would invoke:rustup target add aarch64-linux-android
Once you’ve installed the appropriate target libraries, Rust will know to use them.
-
The linker is provided by the Android NDK. In your NDK root folder you will find a folder called
toolchainscontaining a folder calledllvmcontaining a folder calledprebuiltthat finally contains a folder named for your development platform (e.g., mine starts withdarwin). Inside that is a folder namedbinthat contains all the relevant linkers. Each of the linkers is named<target-architecture><sdk-version>-clang, wheretarget-architectureis the Rust triple you build andsdk-versionis the Android SDK generation you are building for. You will want to use the appropriateclanglinker for the target architecture and sdk. -
Now you have to tell Rust which linker to use for each target. To do this, create a
.cargosubdirectory of your source directory~/src/android_native_keyring_store, and create aconfig.tomlfile in that directory. To this new file, add a pair of lines for each target you will be building. They each will look something like this (but with a path and target of your choosing):[target.aarch64-linux-android] linker = "$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android29-clang"
(You can also create these entries in your global
~/.cargo/config.tomldirectory, in which case these linkers will be known to all projects on your machine. But since this entry has an SDK-version-specific value in it, you might not want to do that.)
-
-
Now that you’ve built for each of your desired Android targets, you need to copy the built
.sofiles into the appropriatejniLibssubdirectory of your Android project. To avoid having to repeat this step every time you do a new build, it may be easier (if your dev platform supports it) to just place symbolic links in thejniLibssubdirectories. For example, you could change into thejniLibs/arm64-v8afolder and give the command:ln -s ~/android_native_keyring_store/target/aarch64-linux-android/release/libandroid_native_keyring_store.so libandroid_native_keyring_store.so(Note that this symlink is to the release build of the library, which is probably the one you want to use in your application.)
There are two sample applications available that run on Android and use this crate.
The first is the Keyring Tester application that is built according to the instructions here and whose source is available in the keyring-tester folder of this crate’s source repository.
The second is the Keyring Demo Tauri 2.0 application whose source is available in the Keyring Demo repository. This app is currently in closed pre-release on the Google Play Store, and is actively looking for beta testers; send an email to keyring-demo at brotsky.com if you would like to try it.
Starting with release v0.6.0 of this crate, the storage conventions for credentials have changed. If your application has stored credentials using an earlier version of this crate, you can still access those credentials by using the function LegacyStore::from_ndk_context to create your credential store (rather then Store::new as you did before).
However! In order to get access to the LegacyStore implementation, you will need to compile this crate with the legacy feature enabled, and you are strongly encouraged not to keep using the legacy implementation as it is deprecated and may be removed in future versions. Instead, your application should use the default store based on the current implementation (or create a named store of its own to use), and it should migrate any needed credentials from the legacy store to the current one.
See the crate docs for more about this.
See the release history on GitHub.
Licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions.