appending values each time when calling JNI method - android

i am using a JNI to fetch signature of apk and i am getting it very well. when i calling this method from java first time i am getting the exact value. calling it again i am getting appended values with exact value (eg 1234456123456). PFB the code which i am using
char* getSignatureMd5(JNIEnv* env, jobject obj)
{
char* sign = loadSignature(env, obj);
MD5_CTX context = { 0 };
MD5Init(&context);
MD5Update(&context, (unsigned char*)sign, strlen(sign));
unsigned char dest[16] = { 0 };
MD5Final(dest, &context);
int i;
static char destination[32]={0};
for (i = 0; i < 16; i++) {
sprintf(destination, "%s%02x", destination, dest[i]);
}
return destination;
}
getToken JNI method
JNIEXPORT jstring JNICALL Java_com_sign_signaturecapturesbi_MyAdapter_getToken(JNIEnv *env, jobject obj)
{
char* signValue = getSignatureMd5(env, obj);
__android_log_print(ANDROID_LOG_VERBOSE, "MyApp", "signValue %s", signValue);
return (*env)->NewStringUTF(env, signValue);
}

These lines cause undefined behavior:
for (i = 0; i < 16; i++) {
sprintf(destination, "%s%02x", destination, dest[i]);
}
man 3 printf:
C99 and POSIX.1-2001 specify that the results are undefined if a call
to sprintf(), snprintf(), vsprintf(), or vsnprintf() would cause
copying to take place between objects that overlap (e.g., if the
target string array and one of the supplied input arguments refer to
the same buffer).
Moreover destination is static and because of this it keeps its content between calls. Together these points give you such a weird behavior.
Since dest size is well known, you can simply unroll loop, also don't forget to add one extra cell to destination for terminating \0. And, if possible, you should use snprintf() instead:
static char destination[33];
snprintf(destination, sizeof destination,
"%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x",
dest[0], dest[1], dest[2], dest[3],
dest[4], dest[5], dest[6], dest[7],
dest[8], dest[9], dest[10], dest[11],
dest[12], dest[13], dest[14], dest[15]);
In this case you can leave destination as static one, since your code doesn't relay on its content anymore. But note that getSignatureMd5() returns pointer to the same buffer each time you call it, as result subsequent calls erase result obtained by previous calls.

Related

JNI DETECTED ERROR IN APPLICATION: use of invalid jobject when returning Array of String

Description of the intended goal
I'm trying to implement OpenSSL-generated public/private key pairs in Android/Kotlin using JNI, in order to implement user encryption on the information stored to the cloud server. I've compiled all OpenSSL source code and generated all .so files correcly.
The code (C++)
The C++ code to use OpenSSL is shown below. CmakeLists.txt and NDK configuration is working fine.
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_eu_joober_ui_entry_SplashFragment_generateRSAKeyPair(JNIEnv *env, jobject thiz) {
int ret = 0;
RSA *r = nullptr;
BIGNUM *bne = nullptr;
BIO *bp_public = nullptr, *bp_private = nullptr;
int bits = 2048;
unsigned long e = RSA_F4;
jstring public_key_text;
jstring private_key_text;
jobjectArray returnPair = env->NewObjectArray(2, env->FindClass("java/lang/String"),nullptr);
// 1. generate rsa key
bne = BN_new();
ret = BN_set_word(bne,e);
if(ret != 1){
goto free_all;
}
r = RSA_new();
ret = RSA_generate_key_ex(r, bits, bne, nullptr);
if(ret != 1){
goto free_all;
}
// 2. get public key
bp_public = BIO_new(BIO_s_mem());
ret = PEM_write_bio_RSAPublicKey(bp_public, r);
BIO_get_mem_data(bp_public, &public_key_text);
if(ret != 1){
goto free_all;
}
// 3. get private key
bp_private = BIO_new(BIO_s_mem());
ret = PEM_write_bio_RSAPrivateKey(bp_private, r, nullptr, nullptr, 0, nullptr, nullptr);
BIO_get_mem_data(bp_private, &private_key_text);
// Check public and private keys were generated correctly
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Public key is: \n%s",public_key_text);
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Private key is: \n%s",private_key_text);
// 4. free
free_all:
BIO_free_all(bp_public);
BIO_free_all(bp_private);
RSA_free(r);
BN_free(bne);
// 5. Return strings using jobjectArray
if (ret == 1) {
env->SetObjectArrayElement(returnPair, 0, public_key_text);
env->SetObjectArrayElement(returnPair, 1, private_key_text);
return returnPair;
}
else {
return nullptr;
}
}
The error
If I check the Android Logcat, both public and private key seem to be generated correctly (as per __android_log_print output) but the app crashes with the following error when env->SetObjectArrayElement(returnPair, 0, public_key_text); is called:
JNI DETECTED ERROR IN APPLICATION: use of invalid jobject
The IDE (Android Studio) does not complain on any error, and the log suggests that key pair is being generated correctly, but I don't know why the keys are not being stored in the jobjectArray correctly. In fact, if I just simply put:
env->SetObjectArrayElement(returnPair, 0, env->NewStringUTF("Hello"));
env->SetObjectArrayElement(returnPair, 1, env->NewStringUTF("World"));
the code works completely fine, my Kotlin code gets the Strings correctly ("Hello" and "World"), and the app does not crash, which makes me think that problem is only on the C++ side.
The question
What I am doing wrong? I have checked other SO questions like JNI converting jstring to char * or jstring return in JNI program with slight modifications and combinations with no luck.
SIDE NOTE: I'm using OpenSSL implementation with C++ because Android/Kotlin code does not provide the private key generated using KeyPairGenerator.getInstance() and generatePair() (only public key can be retrieved from Keystore), which I need to be stored in a different place so that user information can be retrieved even if the app is uninstalled, as every subsequent call to generatePair() will lead to a different key pair. If you know a different approach to this problem I am more than welcome to any suggestions you may provide.
You never created a java string out of public_key_text
Try
char * public_key_text;
...
BIO_get_mem_data(bp_public, &public_key_text);
...
env->SetObjectArrayElement(returnPair, 0, env->NewStringUTF(public_key_text));

How to copy a JNI jstring into a C++ std::string [duplicate]

I have a Java instance method which returns a String and I'm calling this method through JNI in C++. I have written the following code:
const char *DiagLayerContainer_getDESC(JNIEnv *env, jobject diagLayer) {
jclass diagLayerClass = env->FindClass(PARSER_CLASS);
jmethodID getDESCDiagLayerMethodID = env->GetMethodID(diagLayerClass, "getDESCDiagLayer", "(Ljava/lang/Object;)Ljava/lang/String;");
jstring returnString = (jstring) env->CallObjectMethod(diagLayer, getDESCDiagLayerMethodID);
return env->GetStringUTFChars(returnString, JNI_FALSE);
}
How do I get the string and convert it to a const char *?
My program crashes on the last line with access violation to 0x00000000. returnString is not NULL.
According to GetStringUTFChars, the last parameter is a pointer to jboolean.
Change
return env->GetStringUTFChars(returnString, JNI_FALSE);
to
return env->GetStringUTFChars(returnString, NULL);
Or better yet, return a std::string
std::string DiagLayerContainer_getDESC(...) {
...
const char *js = env->GetStringUTFChars(returnString, NULL);
std::string cs(js);
env->ReleaseStringUTFChars(returnString, js);
return cs;
}
I've built a similar simple example and the code as is, seems fine so far.
Although, there are two possible error sources.
The first one is the method signature. Try "()Ljava/lang/String;" instead of "(Ljava/lang/Object;)Ljava/lang/String;".
The second one is in the java source itself. If the java method returns a null string, CallObjectMethod() will return a NULL jstring and GetStringUTFChars() fails.
Add a
if (returnString == NULL)
return NULL;
after CallObjectMethod().
So look into the java source and see, whether the method getDESCDiagLayer() might return a null string.

Send Array of Strings to Native Code in Android

I call a function in C++ from java. In java I have an array of Strings that I want to use in my C++-function.
I have in C++:
std::string names[6]; // Global variable
extern "C"
JNIEXPORT void JNICALL
Java_com_erikbylow_mycamera3_JNIUtils_updateStandingBoard(JNIEnv *env, jobject type, std::string *names, jint nbrElements){
memcpy(standingText, names, 6* sizeof(std::string));
nbrStandText = nbrElements;
}
In `Java`:
public static void updateStanding( String resultArray[]){
updateStandingBoard(resultArray, resultArray.length);
}
What is the simplest way of achieving what I want? When I try this and different variants it either crashes or yields nonsense data.
JNI is a primarily a C API, it doesn't know anything about std::string as you can validate by calling javah on the Java source file contained the native methods declaration.
Also Java isn't C, there is no need to pass the array size as additional parameter.
So your native void updateStandingBoard(String[] result, int size) should actually be native void updateStandingBoard(String[] result)
With this in mind, the JNI code should be
std::vector<std::string> names; // much safer or use std::array as alternative
extern "C"
JNIEXPORT void JNICALL
Java_com_erikbylow_mycamera3_JNIUtils_updateStandingBoard(JNIEnv *env, jobject type, jobjectArray names) {
jint nbrElements = env->GetArrayLength(names);
// now copy the strings into C++ world
for (int i = 0; i < nbrElements ; i++) {
// access the current string element
jobject elem = env->GetObjectArrayElement(names, i);
jsize length = env->GetStringLength(elem);
// pin it to avoid GC moving it around during the copy
const jchar *str = env->GetStringChars(elem, nullptr);
names.push_back(std::string(str, length));
// make it available again to the VM
env->ReleaseStringChars(elem, str);
}
}
This was just for the basic strings, if you are interested in UTF strings, then you should make use of std::wstring and the UTF variants of the above JNI functions.

cannot initialize a variable of type 'jstring' (aka '_jstring *') with an rvalue of type 'jobject' (aka '_jobject *')

Does anyone know how to solve the error?
JNIEXPORT jstring JNICALL JAVA_com_pfc_camera_ndkmain_MainActivity_compresion(JNIEnv* env, jobject obj, jobjectArray jargv){
//jargv is a Java array of Java strings
int argc = env->GetArrayLength(jargv);
typedef char *pchar;
pchar *argv = new pchar[argc];
int i;
for(i=0; i<argc; i++)
{
jstring js = env->GetObjectArrayElement(jargv, i); //A Java string
const char *pjc = env->GetStringUTFChars(js); //A pointer to a Java-managed char buffer
size_t jslen = strlen(pjc);
argv[i] = new char[jslen+1]; //Extra char for the terminating null
strcpy(argv[i], pjc); //Copy to *our* buffer. We could omit that, but IMHO this is cleaner. Also, const correctness.
env->ReleaseStringUTFChars(js, pjc);
}
//Call main
Principal *pa=Principal::CreateInstance(argc,argv);
pa->Run();
pa->FreeInstance();
//Now free the array
for(i=0;i<argc;i++)
delete [] argv[i];
delete [] argv;
}
I understand that the error can come from not doing a casting but I do not have it very clear
[ https://i.stack.imgur.com/bOWKZ.png][1]
It seems that the problem has been solved, now I get another error but I do not understand if I'm passing two arguments js and pjc
[ https://i.stack.imgur.com/UHCAR.png][1]
In C++ you have to use explicit conversion to your desired type.
jstring js = (jstring)env->GetObjectArrayElement(jargv, i);
You can learn about jni programming here
Regarding your other question do the following:
const jbyte *pjc = env->GetStringUTFChars(js, NULL);

Access array in native code (NDK) from Java (SDK) via JNI

I want to access an array that is created and updated in the native C code efficiently. If need be, i could send a pointer or reference from Java code to the native side and have the C-code populate it so that i can read it from SDK side when it's ready to be consumed.
Currently, this is how i am doing it. But i think there can be better ways to do it, since i am doing one copy in C-side and then there an object that is created every time i issue a read on the Java-side.
My Java code:
double[] valuesFromNative = getValues();
public static native double[] getValues();
static { System.loadLibrary("test-jni"); }
My native (C and not C++) code:
#define LEN 18
double testDoubleArr[LEN];
jdoubleArray Java_com_test_testActivity_getValues(JNIEnv *env, jclass clazz) {
jboolean isCopy;
int i;
jdoubleArray result = (*env)->NewDoubleArray(env, LEN);
jdouble* destArrayElems = (*env)->GetDoubleArrayElements(env, result, &isCopy);
for (i = 0; i < LEN; i++) {
destArrayElems[i] = testDoubleArr[i];
}
if(isCopy == JNI_TRUE) {
// isCopy should NEVER be JNI_TRUE in this case, right?
// so, i could as well replace this condition with
// assert(isCopy == JNI_FALSE)?
}
return result;
}
This code snippet works - so, i am looking at more efficient or rather correct way to achieve the same thing.
Thanks for sharing your thoughts.
I think SetDoubleArrayRegion() would be faster. Less code and less JNI calls, that's for sure.
jdoubleArray result = (*env)->NewDoubleArray(env, LEN);
(*env)->SetDoubleArrayRegion(env, result, 0, LEN, testDoubleArr);
You don't even have to create the array on the C++ side. Declare the method like this:
public static native void getValues(double[] a);
Implement like this:
void Java_com_test_testActivity_getValues(JNIEnv *env, jclass clazz, jdoubleArray a)
{//...
Create the array on the Java side, cache it in an instance variable or something, and pass it to JNI to be filled whenever needed. Make sure the assumptions about array size are the same on the Java side and on the C side.

Categories

Resources