diff --git a/README.md b/README.md index 0a1e222..9a4d8db 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,12 @@ ## JSI -If you want to use with `JSI` instead of `NativeModules` you need to set +If you want to use with `NativeModules` instead of `JSI` you need to set ```typescript import RSA from "react-native-fast-rsa"; -RSA.useJSI = true; +RSA.useJSI = false; ``` if you need to use generate methods it is a good idea to disable it, because for now JSI will block your UI but it is faster compared to NativeModules @@ -78,15 +78,27 @@ class RSA { static decryptOAEP(message: string, label: string, hashName: Hash, privateKey: string): Promise static decryptPKCS1v15(message: string, privateKey: string,): Promise + static decryptOAEPBytes(message: Uint8Array, label: string, hashName: Hash, privateKey: string): Promise + static decryptPKCS1v15Bytes(message: Uint8Array, privateKey: string,): Promise + static encryptOAEP(message: string,label: string, hashName: Hash, publicKey: string): Promise static encryptPKCS1v15(message: string, publicKey: string): Promise + static encryptOAEPBytes(message: Uint8Array,label: string, hashName: Hash, publicKey: string): Promise + static encryptPKCS1v15Bytes(message: Uint8Array, publicKey: string): Promise + static signPSS(message: string, hashName: Hash, saltLengthName: SaltLength, privateKey: string): Promise static signPKCS1v15(message: string, hashName: Hash, privateKey: string): Promise + static signPSSBytes(message: Uint8Array, hashName: Hash, saltLengthName: SaltLength, privateKey: string): Promise + static signPKCS1v15Bytes(message: Uint8Array, hashName: Hash, privateKey: string): Promise + static verifyPSS(signature: string, message: string, hashName: Hash, saltLengthName: SaltLength, publicKey: string): Promise static verifyPKCS1v15(signature: string, message: string, hashName: Hash, publicKey: string): Promise + static verifyPSSBytes(signature: Uint8Array, message: Uint8Array, hashName: Hash, saltLengthName: SaltLength, publicKey: string): Promise + static verifyPKCS1v15Bytes(signature: Uint8Array, message: Uint8Array, hashName: Hash, publicKey: string): Promise + static hash(message: string, name: Hash): Promise static base64(message: string): Promise diff --git a/android/fast-rsa-adapter.cpp b/android/fast-rsa-adapter.cpp index 88c3566..385ca95 100644 --- a/android/fast-rsa-adapter.cpp +++ b/android/fast-rsa-adapter.cpp @@ -93,4 +93,97 @@ Java_com_fastrsa_FastRsaModule_callNative(JNIEnv* env, free(response); return result; +} + +extern "C" JNIEXPORT jbyteArray JNICALL +Java_com_fastrsa_FastRsaModule_encodeTextNative(JNIEnv* env, jobject thiz, jstring input, jstring encoding) { + if (input == nullptr || encoding == nullptr) { + jclass Exception = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(Exception, "Input parameters 'input' or 'encoding' cannot be null"); + return nullptr; + } + + // Convert Java Strings to C Strings + const char* inputCStr = env->GetStringUTFChars(input, nullptr); + const char* encodingCStr = env->GetStringUTFChars(encoding, nullptr); + + if (inputCStr == nullptr || encodingCStr == nullptr) { + jclass Exception = env->FindClass("java/lang/OutOfMemoryError"); + env->ThrowNew(Exception, "Failed to allocate memory for 'input' or 'encoding'"); + return nullptr; + } + + // Call the shared library function + BytesReturn* response = RSAEncodeText(const_cast(inputCStr), const_cast(encodingCStr)); + + // Release allocated resources + env->ReleaseStringUTFChars(input, inputCStr); + env->ReleaseStringUTFChars(encoding, encodingCStr); + + if (response->error != nullptr) { + jclass Exception = env->FindClass("java/lang/Exception"); + env->ThrowNew(Exception, response->error); + free(response); + return nullptr; + } + + // Create a new byte array to return the encoded data + jbyteArray result = env->NewByteArray(response->size); + if (result == nullptr) { + free(response); + jclass Exception = env->FindClass("java/lang/OutOfMemoryError"); + env->ThrowNew(Exception, "Failed to allocate memory for result"); + return nullptr; + } + + env->SetByteArrayRegion(result, 0, response->size, reinterpret_cast(response->message)); + free(response); + + return result; +} + +extern "C" JNIEXPORT jstring JNICALL +Java_com_fastrsa_FastRsaModule_decodeTextNative(JNIEnv* env, jobject thiz, jbyteArray input, jstring encoding, + jint fatal, jint ignoreBOM, jint stream) { + if (input == nullptr || encoding == nullptr) { + jclass Exception = env->FindClass("java/lang/NullPointerException"); + env->ThrowNew(Exception, "Input parameters 'input' or 'encoding' cannot be null"); + return nullptr; + } + + // Convert Java Strings to C Strings + const char* encodingCStr = env->GetStringUTFChars(encoding, nullptr); + if (encodingCStr == nullptr) { + jclass Exception = env->FindClass("java/lang/OutOfMemoryError"); + env->ThrowNew(Exception, "Failed to allocate memory for 'encoding'"); + return nullptr; + } + + // Convert Java byte array to C byte array + jsize size = env->GetArrayLength(input); + jbyte* inputBytes = env->GetByteArrayElements(input, nullptr); + if (inputBytes == nullptr) { + env->ReleaseStringUTFChars(encoding, encodingCStr); + jclass Exception = env->FindClass("java/lang/OutOfMemoryError"); + env->ThrowNew(Exception, "Failed to allocate memory for 'input'"); + return nullptr; + } + + // Call the shared library function + char* decodedString = RSADecodeText(inputBytes, size, const_cast(encodingCStr), fatal, ignoreBOM, stream); + + // Release resources + env->ReleaseStringUTFChars(encoding, encodingCStr); + env->ReleaseByteArrayElements(input, inputBytes, JNI_ABORT); + + if (decodedString == nullptr) { + jclass Exception = env->FindClass("java/lang/Exception"); + env->ThrowNew(Exception, "Decoding failed"); + return nullptr; + } + + // Convert C string to Java string and return + jstring result = env->NewStringUTF(decodedString); + free(decodedString); + return result; } \ No newline at end of file diff --git a/android/src/main/java/com/fastrsa/FastRsaModule.kt b/android/src/main/java/com/fastrsa/FastRsaModule.kt index 1eeb912..0716067 100644 --- a/android/src/main/java/com/fastrsa/FastRsaModule.kt +++ b/android/src/main/java/com/fastrsa/FastRsaModule.kt @@ -11,6 +11,8 @@ internal class FastRsaModule(reactContext: ReactApplicationContext) : external fun initialize(jsContext: Long) external fun destruct(); external fun callNative(name: String, payload: ByteArray): ByteArray; + external fun encodeTextNative(input: String, encoding: String): ByteArray + external fun decodeTextNative(input: ByteArray, encoding: String, fatal: Int, ignoreBOM: Int, stream: Int): String companion object { init { @@ -41,6 +43,32 @@ internal class FastRsaModule(reactContext: ReactApplicationContext) : }.start() } + @ReactMethod(isBlockingSynchronousMethod = true) + fun encodeText(input: String, encoding: String): WritableArray { + return try { + val result = encodeTextNative(input, encoding) + Arguments.createArray().apply { + result.forEach { byteValue: Byte -> pushInt(byteValue.toInt() and 0xFF) } + } + } catch (e: Exception) { + Log.e(TAG, "Encoding error", e) + throw RuntimeException("ENCODE_ERROR: Failed to encode text") + } + } + + @ReactMethod(isBlockingSynchronousMethod = true) + fun decodeText(input: ReadableArray, encoding: String, fatal: Boolean, ignoreBOM: Boolean, stream: Boolean): String { + return try { + val bytes = ByteArray(input.size()) { index -> + input.getInt(index).toByte() + } + decodeTextNative(bytes, encoding, if (fatal) 1 else 0, if (ignoreBOM) 1 else 0, if (stream) 1 else 0) + } catch (e: Exception) { + Log.e(TAG, "Decoding error", e) + throw RuntimeException("DECODE_ERROR: Failed to decode text") + } + } + @ReactMethod(isBlockingSynchronousMethod = true) fun install(): Boolean { Log.d(TAG, "Attempting to install JSI bindings...") diff --git a/android/src/main/jniLibs/arm64-v8a/librsa_bridge.h b/android/src/main/jniLibs/arm64-v8a/librsa_bridge.h index ff18cc0..8d24849 100644 --- a/android/src/main/jniLibs/arm64-v8a/librsa_bridge.h +++ b/android/src/main/jniLibs/arm64-v8a/librsa_bridge.h @@ -81,6 +81,8 @@ extern "C" { #endif extern BytesReturn* RSABridgeCall(char* name, void* payload, int payloadSize); +extern BytesReturn* RSAEncodeText(char* input, char* encoding); +extern char* RSADecodeText(void* input, int size, char* encoding, int fatal, int ignoreBOM, int stream); #ifdef __cplusplus } diff --git a/android/src/main/jniLibs/arm64-v8a/librsa_bridge.so b/android/src/main/jniLibs/arm64-v8a/librsa_bridge.so index 4522f16..4791421 100644 Binary files a/android/src/main/jniLibs/arm64-v8a/librsa_bridge.so and b/android/src/main/jniLibs/arm64-v8a/librsa_bridge.so differ diff --git a/android/src/main/jniLibs/armeabi-v7a/librsa_bridge.h b/android/src/main/jniLibs/armeabi-v7a/librsa_bridge.h index ca87c85..76535ca 100644 --- a/android/src/main/jniLibs/armeabi-v7a/librsa_bridge.h +++ b/android/src/main/jniLibs/armeabi-v7a/librsa_bridge.h @@ -81,6 +81,8 @@ extern "C" { #endif extern BytesReturn* RSABridgeCall(char* name, void* payload, int payloadSize); +extern BytesReturn* RSAEncodeText(char* input, char* encoding); +extern char* RSADecodeText(void* input, int size, char* encoding, int fatal, int ignoreBOM, int stream); #ifdef __cplusplus } diff --git a/android/src/main/jniLibs/armeabi-v7a/librsa_bridge.so b/android/src/main/jniLibs/armeabi-v7a/librsa_bridge.so index a4981d6..81bebd5 100644 Binary files a/android/src/main/jniLibs/armeabi-v7a/librsa_bridge.so and b/android/src/main/jniLibs/armeabi-v7a/librsa_bridge.so differ diff --git a/android/src/main/jniLibs/x86/librsa_bridge.h b/android/src/main/jniLibs/x86/librsa_bridge.h index ca87c85..76535ca 100644 --- a/android/src/main/jniLibs/x86/librsa_bridge.h +++ b/android/src/main/jniLibs/x86/librsa_bridge.h @@ -81,6 +81,8 @@ extern "C" { #endif extern BytesReturn* RSABridgeCall(char* name, void* payload, int payloadSize); +extern BytesReturn* RSAEncodeText(char* input, char* encoding); +extern char* RSADecodeText(void* input, int size, char* encoding, int fatal, int ignoreBOM, int stream); #ifdef __cplusplus } diff --git a/android/src/main/jniLibs/x86/librsa_bridge.so b/android/src/main/jniLibs/x86/librsa_bridge.so index 06c804f..ee1c862 100644 Binary files a/android/src/main/jniLibs/x86/librsa_bridge.so and b/android/src/main/jniLibs/x86/librsa_bridge.so differ diff --git a/android/src/main/jniLibs/x86_64/librsa_bridge.h b/android/src/main/jniLibs/x86_64/librsa_bridge.h index ff18cc0..8d24849 100644 --- a/android/src/main/jniLibs/x86_64/librsa_bridge.h +++ b/android/src/main/jniLibs/x86_64/librsa_bridge.h @@ -81,6 +81,8 @@ extern "C" { #endif extern BytesReturn* RSABridgeCall(char* name, void* payload, int payloadSize); +extern BytesReturn* RSAEncodeText(char* input, char* encoding); +extern char* RSADecodeText(void* input, int size, char* encoding, int fatal, int ignoreBOM, int stream); #ifdef __cplusplus } diff --git a/android/src/main/jniLibs/x86_64/librsa_bridge.so b/android/src/main/jniLibs/x86_64/librsa_bridge.so index bf4aee0..ab391db 100644 Binary files a/android/src/main/jniLibs/x86_64/librsa_bridge.so and b/android/src/main/jniLibs/x86_64/librsa_bridge.so differ diff --git a/cpp/librsa_bridge.h b/cpp/librsa_bridge.h index 6212809..fd55b61 100644 --- a/cpp/librsa_bridge.h +++ b/cpp/librsa_bridge.h @@ -10,6 +10,8 @@ typedef struct { extern "C" { #endif extern BytesReturn* RSABridgeCall(char* p0, void* p1, int p2); +extern BytesReturn* RSAEncodeText(char* input, char* encoding); +extern char* RSADecodeText(void* input, int size, char* encoding, int fatal, int ignoreBOM, int stream); #ifdef __cplusplus } #endif diff --git a/cpp/react-native-fast-rsa.cpp b/cpp/react-native-fast-rsa.cpp index 20c78ae..f2a2645 100644 --- a/cpp/react-native-fast-rsa.cpp +++ b/cpp/react-native-fast-rsa.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "librsa_bridge.h" @@ -13,6 +12,61 @@ using namespace facebook; namespace fastRSA { + +jsi::Value encodeText(jsi::Runtime &runtime, const jsi::String &inputValue, const jsi::String &encodingValue) { + std::string inputString = inputValue.utf8(runtime); + std::string encodingString = encodingValue.utf8(runtime); + + std::vector mutableInput(inputString.begin(), inputString.end()); + mutableInput.push_back('\0'); + std::vector mutableEncoding(encodingString.begin(), encodingString.end()); + mutableEncoding.push_back('\0'); + + auto response = RSAEncodeText(mutableInput.data(), mutableEncoding.data()); + if (response->error != nullptr) { + std::string errorMessage(response->error); + free(response); + throw jsi::JSError(runtime, errorMessage); + } + + auto uint8ArrayConstructor = runtime.global().getPropertyAsFunction(runtime, "Uint8Array"); + jsi::Object uint8ArrayObject = uint8ArrayConstructor.callAsConstructor(runtime, response->size).getObject(runtime); + jsi::ArrayBuffer arrayBuffer = uint8ArrayObject.getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime); + memcpy(arrayBuffer.data(runtime), response->message, response->size); + + free(response); + return uint8ArrayObject; +} + +jsi::Value decodeText(jsi::Runtime &runtime, const jsi::Object &inputObject, const jsi::String &encodingValue, + bool fatal, bool ignoreBOM, bool stream) { + auto uint8ArrayConstructor = runtime.global().getPropertyAsFunction(runtime, "Uint8Array"); + if (!inputObject.instanceOf(runtime, uint8ArrayConstructor)) { + throw jsi::JSError(runtime, "First argument must be a Uint8Array"); + } + + // Get Uint8Array data + jsi::ArrayBuffer arrayBuffer = inputObject.getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime); + int byteOffset = inputObject.getProperty(runtime, "byteOffset").asNumber(); + int length = inputObject.getProperty(runtime, "byteLength").asNumber(); + + uint8_t *dataPointer = static_cast(arrayBuffer.data(runtime)) + byteOffset; + + std::string encodingString = encodingValue.utf8(runtime); + std::vector mutableEncoding(encodingString.begin(), encodingString.end()); + mutableEncoding.push_back('\0'); + + char *decodedString = RSADecodeText(dataPointer, length, mutableEncoding.data(), fatal ? 1 : 0, ignoreBOM ? 1 : 0, stream ? 1 : 0); + if (!decodedString) { + throw jsi::JSError(runtime, "Failed to decode text"); + } + + jsi::String result = jsi::String::createFromUtf8(runtime, decodedString); + free(decodedString); + return result; +} + + jsi::Value call(jsi::Runtime &runtime, const jsi::String &nameValue, const jsi::Object &payloadObject) { // Extract and validate name @@ -140,6 +194,8 @@ void install(jsi::Runtime &jsiRuntime) { reject.call(runtime, error.value()); } catch (const std::exception &e) { reject.call(runtime, jsi::String::createFromUtf8(runtime, e.what())); + } catch (...) { + reject.call(runtime, jsi::String::createFromUtf8(runtime, "Unknown error occurred")); } return jsi::Value::undefined(); @@ -152,6 +208,35 @@ void install(jsi::Runtime &jsiRuntime) { return promise; }); + auto encodeTextFunc = jsi::Function::createFromHostFunction( + jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "encodeText"), 2, + [](jsi::Runtime &runtime, const jsi::Value & /*thisValue*/, const jsi::Value *arguments, size_t count) -> jsi::Value { + if (count != 2) { + throw jsi::JSError(runtime, "encodeText expects exactly 2 arguments: (string input, string encoding)"); + } + if (!arguments[0].isString() || !arguments[1].isString()) { + throw jsi::JSError(runtime, "Both arguments must be strings"); + } + return encodeText(runtime, arguments[0].getString(runtime), arguments[1].getString(runtime)); + }); + + auto decodeTextFunc = jsi::Function::createFromHostFunction( + jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "decodeText"), 5, + [](jsi::Runtime &runtime, const jsi::Value & /*thisValue*/, const jsi::Value *arguments, size_t count) -> jsi::Value { + if (count != 5) { + throw jsi::JSError(runtime, "decodeText expects exactly 5 arguments: (Uint8Array input, string encoding, bool fatal, bool ignoreBOM, bool stream)"); + } + if (!arguments[0].isObject() || !arguments[0].getObject(runtime).instanceOf(runtime, runtime.global().getPropertyAsFunction(runtime, "Uint8Array")) || + !arguments[1].isString() || !arguments[2].isBool() || !arguments[3].isBool() || !arguments[4].isBool()) { + throw jsi::JSError(runtime, "Invalid argument types"); + } + + return decodeText(runtime, arguments[0].getObject(runtime), + arguments[1].getString(runtime), arguments[2].getBool(), arguments[3].getBool(), arguments[4].getBool()); + }); + + jsiRuntime.global().setProperty(jsiRuntime, "FastRSAEncodeText", std::move(encodeTextFunc)); + jsiRuntime.global().setProperty(jsiRuntime, "FastRSADecodeText", std::move(decodeTextFunc)); jsiRuntime.global().setProperty(jsiRuntime, "FastRSACallPromise", std::move(bridgeCallPromise)); jsiRuntime.global().setProperty(jsiRuntime, "FastRSACallSync", std::move(bridgeCallSync)); } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index ef54c43..4bf785d 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -315,7 +315,7 @@ PODS: - React-jsinspector (0.72.6) - React-logger (0.72.6): - glog - - react-native-fast-rsa (2.5.0): + - react-native-fast-rsa (2.6.0): - RCT-Folly (= 2021.07.22.00) - React-Core - React-NativeModulesApple (0.72.6): @@ -578,23 +578,23 @@ SPEC CHECKSUMS: RCTTypeSafety: e9c6c409fca2cc584e5b086862d562540cb38d29 React: 769f469909b18edfe934f0539fffb319c4c61043 React-callinvoker: e48ce12c83706401251921896576710d81e54763 - React-Codegen: a136b8094d39fd071994eaa935366e6be2239cb1 - React-Core: e548a186fb01c3a78a9aeeffa212d625ca9511bf - React-CoreModules: d226b22d06ea1bc4e49d3c073b2c6cbb42265405 - React-cxxreact: 44a3560510ead6633b6e02f9fbbdd1772fb40f92 + React-Codegen: 09c4a6d0a6ba9f7d008ac3a5e7c202899178ce20 + React-Core: 4d03a504696413bc97ef8a9085f642555867eed8 + React-CoreModules: 93be55ffed8b41ed664b51072498a23d9261410b + React-cxxreact: ac5508f2887a7eeb93dd9675e2cfc2b20b379936 React-debug: 238501490155574ae9f3f8dd1c74330eba30133e - React-hermes: 46e66dc854124d7645c20bfec0a6be9542826ecd - React-jsi: fbdaf4166bae60524b591b18c851b530c8cdb90c - React-jsiexecutor: 3bf18ff7cb03cd8dfdce08fbbc0d15058c1d71ae + React-hermes: 2dc01922de59f0e21230aedc51d5adef7d7f76f6 + React-jsi: 11012bf34366812063bd16ca739ca219af26680f + React-jsiexecutor: 5b6dd84e7461ae0bd999922cafe1fe3f98ced073 React-jsinspector: 194e32c6aab382d88713ad3dd0025c5f5c4ee072 - React-logger: cebf22b6cf43434e471dc561e5911b40ac01d289 - react-native-fast-rsa: a2151e7c9167ad4d2cd1c9592787d37de493e269 - React-NativeModulesApple: 02e35e9a51e10c6422f04f5e4076a7c02243fff2 + React-logger: 39440454dfd719978689203a9d18b94e98de09eb + react-native-fast-rsa: 80bc0b93c19bcf2b732522c38185ddb2c33ea332 + React-NativeModulesApple: 90508a0d94b0b66bb2ba14bc3bef65a00f5a8efb React-perflogger: e3596db7e753f51766bceadc061936ef1472edc3 React-RCTActionSheet: 17ab132c748b4471012abbcdcf5befe860660485 React-RCTAnimation: c8bbaab62be5817d2a31c36d5f2571e3f7dcf099 - React-RCTAppDelegate: af1c7dace233deba4b933cd1d6491fe4e3584ad1 - React-RCTBlob: 1bcf3a0341eb8d6950009b1ddb8aefaf46996b8c + React-RCTAppDelegate: c06dfd41e63ef630220dc1892f77c277bba84a98 + React-RCTBlob: 56f3c13b80c8b415ad1f81881fcab44904abe07c React-RCTImage: 670a3486b532292649b1aef3ffddd0b495a5cee4 React-RCTLinking: bd7ab853144aed463903237e615fd91d11b4f659 React-RCTNetwork: be86a621f3e4724758f23ad1fdce32474ab3d829 @@ -603,9 +603,9 @@ SPEC CHECKSUMS: React-RCTVibration: 6bd85328388ac2e82ae0ca11afe48ad5555b483a React-rncore: fda7b1ae5918fa7baa259105298a5487875a57c8 React-runtimeexecutor: 57d85d942862b08f6d15441a0badff2542fd233c - React-runtimescheduler: f23e337008403341177fc52ee4ca94e442c17ede - React-utils: fa59c9a3375fb6f4aeb66714fd3f7f76b43a9f16 - ReactCommon: dd03c17275c200496f346af93a7b94c53f3093a4 + React-runtimescheduler: 8d48e9f0cf62099f501fcd52234240006fa12c8f + React-utils: ee4e6bf16d6882500500d517d36f9035874c5f6a + ReactCommon: 2060ee7b68e71d23849968f6406550ba2df71bdb SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Yoga: b76f1acfda8212aa16b7e26bcce3983230c82603 diff --git a/example/src/App.tsx b/example/src/App.tsx index 81834cd..6933b04 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -23,6 +23,7 @@ import ConvertPrivate from './modules/ConvertPrivate'; import ConvertJWT from './modules/ConvertJWT'; import ConvertKeyPair from './modules/ConvertKeyPair'; import RSA from 'react-native-fast-rsa'; +import EncryptDecryptOAEPBytes from './modules/EncryptDecryptOAEPBytes'; const passphrase = 'test'; const privateKey = `-----BEGIN RSA PRIVATE KEY----- @@ -88,6 +89,11 @@ const App = () => { privateKey={privateKey} passphrase={passphrase} /> + (); + const [encrypted, setEncrypted] = useState(); + const [decrypted, setDecrypted] = useState(); + + return + + Encrypt OAEP Bytes + { + const encoded = new global.TextEncoder().encode(text); + setInput(encoded); + }} + style={{backgroundColor: Colors.white, borderRadius: 4}} + placeholder={"insert message here"} + /> +