I'm performing certificate pinning in flutter by securely storing the certificate in JNI and fetching it during run time. But I get BAD_PKCS12_DATA(pkcs8_x509.c:626), errno = 0) when I fetch the data from JNI. The pinning works if I set it directly in flutter code though like
List<int> _crt = <int>[45, 45, 45, 45, 45, 66, 69, 71, 73, 78, ...]
Here is the JNI method:
extern "C" JNIEXPORT jintArray JNICALL Java_com_package_android_MainActivity_getCert
(JNIEnv *env, jobject This)
{
int a[] ={45,45,45,45,45,...};
jintArray ret = env->NewIntArray(sizeof(a));
env->SetIntArrayRegion(ret, 0, 6, a);
return ret;
}
MainActivity.kt:
external fun getCert(): IntArray
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
if (call.method == "cert") {
result.success(getCert())
}
}
}
Flutter code:
List<int> _crt;
_crt = await _platform.invokeMethod("cert");
//print("CRT: " + _crt.length);
SecurityContext context = SecurityContext(withTrustedRoots: true);
context.setTrustedCertificatesBytes(_crt);
httpClient = new HttpClient(context: context);
I'm confused why the returned int array from JNI doesnt work but would have no problem if I set it directly in flutter?
sizeof(a) doesn't give you the number of elements in a. It gives you total size of all the elements, in bytes.
What you want is sizeof(a) / sizeof(int) or sizeof(a) / sizeof(a[0]).
Related
I have a code that queries for a static field "READ_PHONE_STATE" from "android/Manifest$permission" via JNI interface:
env->GetStaticObjectField
This results in jobject. In all the examples I saw jobject was castable to jstring. They are both just c pointers (even tho value is "fake") so c-style cast shouldn't be a problem. However, I keep on getting crash. Information:
I can cast jobject to any other derived class or even void* or int*. It crashes only when casting to jstring
it crashes on jstring regardless of the order I put the casts in, even if I return jobject and do the cast in caller function.
This happens only when I run Android API 28 emulator. Doesnt happen on Android API 27 emulator or Android API 16. I currently have no means to check on Android API 29+ or real hardware.
Call GetStaticObjectField DOES return a value of 0xe1.
Crash happens only when debugging
Here is the stack:
art_sigsegv_fault 0x00000000f1fcb1d0
art::FaultManager::HandleFault(int, siginfo*, void*) 0x00000000f1fcb774
art::art_fault_handler(int, siginfo*, void*) (.llvm.650222801) 0x00000000f1fcb49b
___lldb_unnamed_symbol22$$app_process32 0x0000000058a1a6af
___lldb_unnamed_symbol2$$libc.so 0x00000000f34a6c50
art::Thread::DecodeJObject(_jobject*) const 0x00000000f234c9b5
<unknown> 0x00000000f5a3f05d
dlsym 0x0000000058a19530
Here are the casts that I have done.
jobject fieldObject = iface().env->GetStaticObjectField(iface().manifestpermissionClass,
iface().READ_PHONE_STATEFieldID);
log("fieldObject: %p", fieldObject);
void* castedToVoidPtr = (void*) fieldObject;
log("castedToVoidPtr : %p", castedToVoidPtr);
{
jobject a = (jobject) fieldObject;
log("a: %p", a);
}
{
jarray a = (jarray) fieldObject;
log("a: %p", a);
}
{
jobjectArray a = (jobjectArray) fieldObject;
log("a: %p", a);
}
{
jbooleanArray a = (jbooleanArray) fieldObject;
log("a: %p", a);
}
{
jbyteArray a = (jbyteArray) fieldObject;
log("a: %p", a);
}
{
jcharArray a = (jcharArray) fieldObject;
log("a: %p", a);
}
{
jshortArray a = (jshortArray) fieldObject;
log("a: %p", a);
}
{
jstring a = (jstring) fieldObject; // Crashes only when casting to jstring regardless of the order of casts and even if I move the cast to different function
log("a: %p", a);
}
{
jintArray a = (jintArray) fieldObject;
log("a: %p", a);
}
{
jlongArray a = (jlongArray) fieldObject;
log("a: %p", a);
}
{
jfloatArray a = (jfloatArray) fieldObject;
log("a: %p", a);
}
{
jdoubleArray a = (jdoubleArray) fieldObject;
log("a: %p", a);
}
{
jthrowable a = (jthrowable) fieldObject;
log("a: %p", a);
}
{
jweak a = (jweak) fieldObject;
log("a: %p", a);
}
Does anyone have an idea what is the issue and where is the bug?
I'm testing Rust with JNI async execution. I want to do execute requests in Rust and return the result to Android asynchronously with callback. I'm testing code to execute the request in the command line and it works fine.
That is how it works on command line:
Callback struck:
struct Processor {
pub(crate) callback: Box<dyn FnMut(String)>,
}
impl Processor {
fn set_callback(&mut self, c: impl FnMut(String) + 'static) {
self.callback = Box::new(c);
}
fn process_events(&mut self, result: String) {
(self.callback)(result);
}
}
Tokio/reqwest:
const DATA_URL: &str = "https://pokeapi.co/api/v2/pokemon/1/";
#[tokio::main]
pub async fn load_swapi_async_with_cb(callback: Box<dyn FnMut(String)>) -> Result<(), Box<dyn std::error::Error>> {
println!("load_swload_swapi_async_with_cbapi_async started");
let mut cb = Processor {
callback: Box::new(callback),
};
let body = reqwest::get(DATA_URL)
.await?
.json::<HashMap<String, String>>()
.await?;
//println!("{:#?}", body);
let name = match body.get("name") {
Some(name) => name,
None => "Failed to parse"
}.to_string();
println!("Name is: {} ", name);
cb.process_events(name);
Ok(())
}
And JNI part:
#[no_mangle]
#[allow(non_snake_case)]
pub extern "C" fn Java_com_omg_app_greetings_MainActivity_callback(env: JNIEnv,
_class: JClass,
callback: JObject) {
static callback: dyn FnMut(String) + 'static = |name| {
let response = env.new_string(&name).expect("Couldn't create java string!");
env.call_method(callback, "rustCallbackResult", "(Ljava/lang/String;)V",
&[JValue::from(JObject::from(response))]).unwrap();
};
pokemon_api(callback);
}
And pokemon API method:
#[no_mangle]
pub extern fn pokemon_api(callback: impl FnMut(String) + 'static) {
let cb_box = Box::new(callback);
swapi::load_swapi_async_with_cb(cb_box);
}
The error I'm facing:
JNI ENV env non-constant value:
let response = env.new_string(&name).expect("Couldn't create java string!");
| ^^^ non-constant value
callback - doesn't have a size known at compile-time:
static callback: dyn FnMut(String) + 'static = |name| {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
I was checking how this working, but example seems to be out of date:
* https://github.com/mozilla/rust-android-gradle/blob/master/samples/rust/src/lib.rs
I fixed with my problem with the usage of the https://github.com/Dushistov/rust_swig
After you integrate it, it will autogenerate code and you can check how it does it.
It basically boils down to: if I have 4000 files in a directory, the File.isDirectory() function takes 1ms to execute, so the directory takes 4s to compute (too slow [ 1 ]).
I haven't got the most complete knowledge of the filesystem, but I think that isDirectory() can be batched for all the elements in the directory (reading a chunk of data, and then separating the individual file's metadatas). C/C++ code is acceptable (it can be run with the JNI) but should be left as a last resource.
I have found FileVisitor, but it doesn't seem that it is the best solution to my problem, as I don't have to visit the entire file tree. I also found BasicFileAttributeView but it seems it has the same problem. This is a related question but there aren't answers that provide a significant solution.
[ 1 ]: Because it is not the only thing I do it ends up being like 17s.
Edit: Code:
internal fun workFrom(unit: ProcessUnit<D>) {
launch {
var somethingAddedToPreload = false
val file = File(unit.first)
....
//Load children folders
file.listFiles(FileFilter {
it.isDirectory
})?.forEach {
getPreloadMapMutex().withLock {
if (getPreloadMap()[it.path] == null) {
val subfiles = it.list() ?: arrayOf()
for (filename in subfiles) {
addToProcess(it.path, ProcessUnit(it.path + DIVIDER + filename, unit.second))
}
getPreloadMap()[it.path] = PreloadedFolder(subfiles.size)
if (getPreloadMap().size > PRELOADED_MAP_MAXIMUM) cleanOldEntries()
getDeleteQueue().add(it.path)
somethingAddedToPreload = somethingAddedToPreload || subfiles.isNotEmpty()
}
}
}
...
if(somethingAddedToPreload) {
work()
}
}
}
private fun addToProcess(path: String, unit: ProcessUnit<D>) {
val f: () -> Pair<String, FetcherFunction<D>> = { load(path, unit) }
preloadList.add(f)
}
private suspend fun work() {
preloadListMutex.withLock {
preloadList.forEach {
launch {
val (path, data) = it.invoke()
if (FilePreloader.DEBUG) {
Log.d("FilePreloader.Processor", "Loading from $path: $data")
}
val list = getPreloadMap()[path]
?: throw IllegalStateException("A list has been deleted before elements were added. We are VERY out of memory!")
list.add(data)
}
}
preloadList.clear()
}
}
PS: I will remove the coroutines in work before implementing an optimization, complete code is here.
You could run a ls -F and check in the output if the file is a directory by looking at the last character, directories will end with /. E.g.
val cmd = "ls -F ${myFile.absolutePath}"
val process = Runtime.getRuntime().exec(cmd)
val files = process.inputStream
.bufferedReader()
.use(BufferedReader::readText)
.lines()
for (fileName in files) {
val isDir = fileName.endsWith("/")
}
I run a quick test on an emulator, with 4000 files and 4000 directories it's taking ~150ms for the whole thing.
Years ago, I had to write a JNI interface to opendir()/readdir()/closedir()/rewinddir() to address a similar performance issue. It's a bit of a hack, as it uses a jlong to hold a DIR * pointer from opendir() and pass it to subsequent readdir() and closedir() calls, but it was probably several orders of magnitude faster than Java's listFiles() on large directories.
It does require a JNI library, but you may find it useful:
/*
* Class: path_to_jni_ReadDir
* Method: opendir
* Signature: (Ljava/lang/String;)J
*/
JNIEXPORT jlong JNICALL Java_path_to_jni_ReadDir_opendir
(JNIEnv *env, jclass cl, jstring jdirname )
{
const char *cdirname;
jboolean copy;
jlong dirp;
if ( NULL == jdirname )
{
return( ( jlong ) NULL );
}
cdirname= ( env )->GetStringUTFChars( jdirname , © );
if ( NULL == cdirname )
{
return( ( jlong ) NULL );
}
if ( 0 == ::strlen( cdirname ) )
{
( env )->ReleaseStringUTFChars( jdirname , cdirname );
return( ( jlong ) NULL );
}
dirp = ( jlong ) ::opendir( cdirname );
( env )->ReleaseStringUTFChars( jdirname , cdirname );
return( dirp );
}
/*
* Class: path_to_jni_ReadDir
* Method: readdir
* Signature: (J)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_path_to_jni_ReadDir_readdir
(JNIEnv *env, jclass cl, jlong dirp )
{
struct dirent *dentp;
struct dirent *dentbuffer;
char buffer[ 8192 ];
jstring jfilename;
int rc;
dentbuffer = ( struct dirent * ) buffer;
dentp = NULL;
rc = ::readdir_r( ( DIR * ) dirp, dentbuffer, &dentp );
if ( ( SUCCESS != rc ) || ( NULL == dentp ) )
{
return( NULL );
}
jfilename = env->newStringUTF( dentp->d_name );
return( jfilename );
}
/*
* Class: path_to_jni_ReadDir
* Method: closedir
* Signature: (J)I
*/
JNIEXPORT jint JNICALL Java_path_to_jni_ReadDir_closedir
(JNIEnv *env, jclass cl, jlong dirp )
{
jint rc;
rc = ::closedir( ( DIR * ) dirp );
return( rc );
}
/*
* Class: path_to_jni_ReadDir
* Method: rewinddir
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_path_to_jni_ReadDir_rewinddir
(JNIEnv *env, jclass cl, jlong dirp )
{
::rewinddir( ( DIR * ) dirp );
return;
}
I've removed customer-identifying information from the code, so it's not exactly as delivered and may have some typos.
Given the Android dirent structure is
struct dirent {
uint64_t d_ino;
int64_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[256];
};
You can modify the JNI readdir method to add filters based on the d_type field, which contains one of the following values:
#define DT_UNKNOWN 0
#define DT_FIFO 1
#define DT_CHR 2
#define DT_DIR 4
#define DT_BLK 6
#define DT_REG 8
#define DT_LNK 10
#define DT_SOCK 12
#define DT_WHT 14
For example, if you're looking for directories, you can add a loop to keep calling ::readdir_r() until it either returns NULL or the d_type field is DT_DIR:
for ( ;; )
{
rc = ::readdir_r( ( DIR * ) dirp, dentbuffer, &dentp );
if ( ( SUCCESS != rc ) || ( NULL == dentp ) )
{
return( NULL );
}
if ( dentp->d_type == DT_DIR )
{
break;
}
}
I'm building a C++ library with Visual Studio and using it in an Android Studio project that I run in an emulator. If I don't use the problem code below, the rest of the project works fine.
In my java class:
public native int androidmain();
public void aFunction() {
androidmain();
}
And in my C++ library:
#if defined( PLATFORM_ANDROID )
extern "C" {
JNIEXPORT jint JNICALL Java_com_TestsRunnerAndroid2015_TestsRunnerAndroid2015_androidmain( JNIEnv * env, jobject, jobjectArray argsObj ) {
OutputString( "androidmain called" ); // ALMODEBUG
jsize stringCount = 0;
if ( env == nullptr ) {
OutputString( "env is null" );
} else {
OutputString( "env is not null" );
}
if ( argsObj != nullptr ) {
OutputString( "argsObj was not null" );
stringCount = env->GetArrayLength( argsObj ); // CRASH HERE
OutputString( "Got argsObj length." );
} else {
OutputString( "argsObj was null" );
}
...
The output I get from this is:
D/TAG: androidmain called
D/TAG: env is not null
D/TAG: argsObj was not null
W/art: Suspending all threads took: 94.170ms
A/art: art/runtime/java_vm_ext.cc:470] JNI DETECTED ERROR IN APPLICATION: use of invalid jobject 0x12c62000
A/art: art/runtime/java_vm_ext.cc:470] from int com.TestsRunnerAndroid2015.TestsRunnerAndroid2015.androidmain()
Additional comment: I'm aware I'm sending no arguments, yet parsing an argument array. I'm expecting the count of objects to be zero.
In Java code you declare androidmain() as not taking any arguments, but in C code you declare Java_com_TestsRunnerAndroid2015_TestsRunnerAndroid2015_androidmain(...) as taking an array of objects as an argument.
In Java code, when calling androidmain() you are not supplying argsObj argument for Java_com_TestsRunnerAndroid2015_TestsRunnerAndroid2015_androidmain(...)
Try declaring androidmain as public native int androidmain(Object[] argsObj); and when you call it -- pass that argument.
I have already tried NDK simple examples such as displaying data from a native code to android's java code such as this:
#include <jni.h>
extern "C" {
JNIEXPORT jstring JNICALL
Java_com_example_JNIActivity_stringFromJNI( JNIEnv* env,
jobject thiz )
{
return env->NewStringUTF("I'm C++! What the hell am I doing here in android?!");
}
}
However, I want an application wherein user will have to enter data from a textfield and have this data be pass from it's java code to the native code where calculations will be done. How will I do this?
In your Java native method, include a formal String argument that accepts the textfield value.
In C/C++, access the characters from the jstring with JNI methods like these:
jsize GetStringLength(JNIEnv *env, jstring string);
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
When you're done with the characters, be sure to release them.
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
See the documentation for these methods in the Java Native Interface Specification.