0

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.

Franbede
  • 418
  • 1
  • 6
  • 11

1 Answers1

2

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));
Eyal Cinamon
  • 939
  • 5
  • 16
  • Hi @eyal-cinamon, thanks for your answer. If I follow your suggestion, then I get `Cannot initialize a parameter of type 'const char *' with an lvalue of type 'jstring' (aka '_jstring *')` error. If I follow Android Studio suggestion and cast the expression to `const char*`, which then changes to `env->SetObjectArrayElement(returnPair, 0, env->NewStringUTF(reinterpret_cast(public_key_text)));`, then the returned value is empty (""), which is not the intended outcome :( – Franbede Dec 30 '20 at 12:21
  • In fact, both `public_key_text` and `private_key_text` are jstring objects, which I think that they are the C representantion of Java strings, am I correct? – Franbede Dec 30 '20 at 12:24
  • You're using an not-created JSTRING OBJECT as C++ argument, and this is wrong: (1) because jstring variables were not created and (2) because "BIO_get_mem_data()" expects a C++ argument and not a Java Object. So "jstring" became "char *" (a C++ "object") when declaring variables, and in the Array you have to put JSTRING Objects – emandt Dec 30 '20 at 12:59
  • @Franbede You're supposed to change the type of `private_key_text` and `public_key_text` to `char*` instead of `jstring` (as in my example.) `NewStringUTF()` converts a C string to a java string. – Eyal Cinamon Dec 30 '20 at 16:59
  • Thanks @EyalCinamon and @emandt for your replies. Sorry I missed the declaration of public_key_text as `char *`, now it does not crash, but instead empty strings are returned ("") when adding public_key_text through `env->NewStringUTF()`. I can't understand why it does not return the values correctly, but `__android_log_print()` does show them in the Android Studio Logcat... – Franbede Dec 31 '20 at 16:55
  • Try to put all methods that are freeing memory AFTER the methods that set Array0 and Array1....maybe those methods empty the pointer_to_char... – emandt Dec 31 '20 at 23:45
  • That was indeed the problem, thanks @emandt, now it works fine with a small modification in the keys declaration: I need to put _public_key_text_ and _private_key_text_ declarations as `const char*`, not just `char*` in order to make it work. I you put modifications as new answer I can mark it as solved, for anyone with the same problem to check this solution – Franbede Jan 02 '21 at 09:15