til

Build Variant Support for Android NDK

As part of the "Protect Secrets in your app" (Part 1, Part 2, Part 3) I have shown how to use Android NDK to improve the protection of secrets included in your App.

One of the downsides from the resulting sample was, that it would also enforce and require an implemented keystore fingerprint for debug builds, which in teams is unlikely (unless you share the same keystore throughout your team).

Or you simply want to differentiate keys for different environments.

For this situation it is beneficial to define a different logic to retrieve key and secret.

The first idea which comes to mind may be to specify a different externalNativeBuild block, pointing at a different variants path. But sadly this is not supported.

debug {
     externalNativeBuild {
          ndkBuild {
               // not allowed
               path 'src/main/jni/Android.mk'
          }
     }
}
A problem occurred evaluating project ':app'.
> No signature of method: build_dn90m3wwrxt4hnmpusnescafr.android() is applicable for argument types: (build_dn90m3wwrxt4hnmpusnescafr$_run_closure1) values: [build_dn90m3wwrxt4hnmpusnescafr$_run_closure1@955369f]

While the path is not supported to be different per variant ndk build arguments are. This comes in handy as it gives us the possibility to inject arguments adjusting the source location.

debug {
    externalNativeBuild {
        ndkBuild {
            arguments "BUILD_VARIANT=debug"
        }
    }
}

The same is afterwards accessible from within the Android.mk

# specify the src files to include in the native lib, including the variant folder
LOCAL_SRC_FILES := $(LOCAL_PATH)/../../$(BUILD_VARIANT)/jni/protected.c

The above snippet retrieves the LOCAL_PATH will step 2 levels out and then go into the folder named debug (BUILD_VARIANT) and retrieve the protected.c class.

Originally this .c class was located in src/main/jni.

Let's move it into src/release/jni and create a stripped down variant for src/debug/jni.

For debug we may simply return the credentials without any further validation.

// src/debug/jni/protected.c
#include <jni.h>

JNIEXPORT jstring JNICALL
Java_com_mikepenz_credsshowcase_CustomApplication_getSdkKey(JNIEnv *env, jobject thiz, jobject context) {
    return (*env)->NewStringUTF(env, "amazing-key-dev");
}

JNIEXPORT jstring JNICALL
Java_com_mikepenz_credsshowcase_CustomApplication_getSdkSecret(JNIEnv *env, jobject thiz, jobject context) {
    return (*env)->NewStringUTF(env, "super-secure-secret-dev");
}

And that's it. Run the sample app, and it should return amazing-key-dev and super-secure-secret-dev for the debug variant, and the previously defined key and secret for the release variant.

Sample code for this part

Feedback

Got thoughts, feedback, improvements, suggestions or comments?

Let me know @mike_penz

Attribution

Link preview photo by Thomas Knorr / Unsplash