From 04ba4b949e49a5df5c2ad2d9e04ba1f89ddea845 Mon Sep 17 00:00:00 2001 From: Alex Radetsky Date: Thu, 20 Oct 2022 15:41:29 +0300 Subject: [PATCH] Redesigned the framework to make it less vulnerable to native exceptions. (#955) * redesigned the framework to make it less vulnerable to native exceptions. Second try. * restore previous format * no more short names --- .../com/reactnativethemis/ThemisModule.java | 537 +++++++---------- .../android/src/main/main.iml | 11 + .../react-native-themis/ios/RCTThemis.h | 18 +- .../react-native-themis/ios/RCTThemis.m | 553 +++++++++--------- .../themis/react-native-themis/package.json | 5 +- .../themis/react-native-themis/src/index.d.ts | 9 +- .../themis/react-native-themis/src/index.js | 194 ++---- .../themis/react-native-themis/src/index.tsx | 219 +++---- 8 files changed, 664 insertions(+), 882 deletions(-) create mode 100644 src/wrappers/themis/react-native-themis/android/src/main/main.iml diff --git a/src/wrappers/themis/react-native-themis/android/src/main/java/com/reactnativethemis/ThemisModule.java b/src/wrappers/themis/react-native-themis/android/src/main/java/com/reactnativethemis/ThemisModule.java index 24e70e4c1..486195a55 100644 --- a/src/wrappers/themis/react-native-themis/android/src/main/java/com/reactnativethemis/ThemisModule.java +++ b/src/wrappers/themis/react-native-themis/android/src/main/java/com/reactnativethemis/ThemisModule.java @@ -17,7 +17,6 @@ import com.cossacklabs.themis.SecureMessage; import com.cossacklabs.themis.SecureCompare; - import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; @@ -33,11 +32,7 @@ import java.util.Map; import java.util.UUID; - - - public class ThemisModule extends ReactContextBaseJavaModule { - public static class ByteOverflowException extends Exception { public ByteOverflowException(String errorMessage) { super(errorMessage); @@ -91,6 +86,7 @@ private static int compareResultSerialize(SecureCompare.CompareResult result) default: return comparator_error; } } + private static WritableArray dataSerialize(byte[] data) { if (data == null) { @@ -108,7 +104,7 @@ private static byte[] dataDeserialize(ReadableArray serializedData) throws ByteO } byte[] data = new byte[serializedData.size()]; for (int i = 0; i < serializedData.size(); i++) { - if(serializedData.getInt(i) >= -128 && serializedData.getInt(i) <= 255) { + if (serializedData.getInt(i) >= 0 && serializedData.getInt(i) <= 255) { byte j = (byte) serializedData.getInt(i); data[i] = j; } else { @@ -163,355 +159,286 @@ public void symmetricKey(Callback callback) @ReactMethod public void secureCellSealWithSymmetricKeyEncrypt(ReadableArray symmetricKey, - String plaintext, - String context, - Callback successCallback, - Callback errorCallback) + String plaintext, + String context, + Callback successCallback, + Callback errorCallback) { - SecureCell.Seal cell = null; try { - cell = SecureCell.SealWithKey(dataDeserialize(symmetricKey)); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; + byte[] symmetricKeyBinary = dataDeserialize(symmetricKey); + SecureCell.Seal cell = SecureCell.SealWithKey(symmetricKeyBinary); + byte[] plaintextBinary = plaintext.getBytes(StandardCharsets.UTF_8); + byte[] contextBinary = context.getBytes(StandardCharsets.UTF_8); + byte[] encrypted = cell.encrypt(plaintextBinary, contextBinary); + WritableArray response = dataSerialize(encrypted); + successCallback.invoke(response); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } - - byte[] txt = plaintext.getBytes(StandardCharsets.UTF_8); - byte[] ctx = context.getBytes(StandardCharsets.UTF_8); - byte[] encrypted = cell.encrypt(txt, ctx); - WritableArray response = dataSerialize(encrypted); - successCallback.invoke(response); } @ReactMethod public void secureCellSealWithSymmetricKeyDecrypt(ReadableArray symmetricKey, - ReadableArray encrypted, - String context, - Callback successCallback, - Callback errorCallback) + ReadableArray encrypted, + String context, + Callback successCallback, + Callback errorCallback) { - SecureCell.Seal cell = null; try { - cell = SecureCell.SealWithKey(dataDeserialize(symmetricKey)); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; - } - - byte[] enc; - try { - enc = dataDeserialize(encrypted); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; - } - - byte[] ctx = context.getBytes(StandardCharsets.UTF_8); - try { - byte[] decrypted = cell.decrypt(enc, ctx); + byte[] symmetricKeyBinary = dataDeserialize(symmetricKey); + SecureCell.Seal cell = SecureCell.SealWithKey(symmetricKeyBinary); + byte[] encryptedBinary = dataDeserialize(encrypted); + byte[] contextBinary = context.getBytes(StandardCharsets.UTF_8); + byte[] decrypted = cell.decrypt(encryptedBinary, contextBinary); WritableArray response = dataSerialize(decrypted); successCallback.invoke(response); - } catch (SecureCellException e) { - errorCallback.invoke(e.getMessage()); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } - } @ReactMethod public void secureCellSealWithPassphraseEncrypt(String passphrase, - String plaintext, - String context, - Callback callback) + String plaintext, + String context, + Callback successCallback, + Callback errorCallback) { - SecureCell.Seal cell = SecureCell.SealWithPassphrase(passphrase); - byte[] txt = plaintext.getBytes(StandardCharsets.UTF_8); - byte[] ctx = context.getBytes(StandardCharsets.UTF_8); - byte[] encrypted = cell.encrypt(txt, ctx); - WritableArray response = dataSerialize(encrypted); - callback.invoke(response); + try { + SecureCell.Seal cell = SecureCell.SealWithPassphrase(passphrase); + byte[] plaintextBinary = plaintext.getBytes(StandardCharsets.UTF_8); + byte[] contextBinary = context.getBytes(StandardCharsets.UTF_8); + byte[] encrypted = cell.encrypt(plaintextBinary, contextBinary); + WritableArray response = dataSerialize(encrypted); + successCallback.invoke(response); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); + } } @ReactMethod public void secureCellSealWithPassphraseDecrypt(String passphrase, - ReadableArray encrypted, - String context, - Callback successCallback, - Callback errorCallback) + ReadableArray encrypted, + String context, + Callback successCallback, + Callback errorCallback) { - SecureCell.Seal cell = SecureCell.SealWithPassphrase(passphrase); - byte[] enc; - try { - enc = dataDeserialize(encrypted); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; - } - byte[] ctx = context.getBytes(StandardCharsets.UTF_8); try { - byte[] decrypted = cell.decrypt(enc, ctx); + SecureCell.Seal cell = SecureCell.SealWithPassphrase(passphrase); + byte[] encryptedBinary = dataDeserialize(encrypted); + byte[] contextBinary = context.getBytes(StandardCharsets.UTF_8); + byte[] decrypted = cell.decrypt(encryptedBinary, contextBinary); WritableArray response = dataSerialize(decrypted); successCallback.invoke(response); - } catch (SecureCellException e) { - errorCallback.invoke(e.getMessage()); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } } /* MARK: Token protect mode */ @ReactMethod public void secureCellTokenProtectEncrypt(ReadableArray symmetricKey, - String plaintext, - String context, - Callback successCallback, - Callback errorCallback) + String plaintext, + String context, + Callback successCallback, + Callback errorCallback) { - byte[] bkey; try { - bkey = dataDeserialize(symmetricKey); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; + byte[] symmetricKeyBinary = dataDeserialize(symmetricKey); + SecureCell.TokenProtect cell = SecureCell.TokenProtectWithKey(symmetricKeyBinary); + byte[] plaintextBinary = plaintext.getBytes(StandardCharsets.UTF_8); + byte[] contextBinary = context.getBytes(StandardCharsets.UTF_8); + SecureCellData result = cell.encrypt(plaintextBinary, contextBinary); + byte[] encrypted = result.getProtectedData(); + byte[] authToken = result.getAdditionalData(); + WritableMap response = new WritableNativeMap(); + response.putArray("encrypted", dataSerialize(encrypted)); + response.putArray("token", dataSerialize(authToken)); + successCallback.invoke(response); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } - SecureCell.TokenProtect cell = SecureCell.TokenProtectWithKey(bkey); - byte[] txt = plaintext.getBytes(StandardCharsets.UTF_8); - byte[] ctx = context.getBytes(StandardCharsets.UTF_8); - SecureCellData result = cell.encrypt(txt, ctx); - byte[] encrypted = result.getProtectedData(); - byte[] authToken = result.getAdditionalData(); - - WritableMap response = new WritableNativeMap(); - response.putArray("encrypted", dataSerialize(encrypted)); - response.putArray("token", dataSerialize(authToken)); - - successCallback.invoke(response); } @ReactMethod public void secureCellTokenProtectDecrypt(ReadableArray symmetricKey, - ReadableArray encrypted, - ReadableArray token, - String context, - Callback successCallback, - Callback errorCallback) + ReadableArray encrypted, + ReadableArray token, + String context, + Callback successCallback, + Callback errorCallback) { - byte[] bkey; - byte[] enc; - byte[] tkn; - try { - bkey = dataDeserialize(symmetricKey); - enc = dataDeserialize(encrypted); - tkn = dataDeserialize(token); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; - } - SecureCell.TokenProtect cell = SecureCell.TokenProtectWithKey(bkey); - byte[] ctx = context.getBytes(StandardCharsets.UTF_8); - try { - byte[] decrypted = cell.decrypt(enc, tkn, ctx); + byte[] symmetricKeyBinary = dataDeserialize(symmetricKey); + byte[] encryptedBinary = dataDeserialize(encrypted); + byte[] tokenBinary = dataDeserialize(token); + SecureCell.TokenProtect cell = SecureCell.TokenProtectWithKey(symmetricKeyBinary); + byte[] contextBinary = context.getBytes(StandardCharsets.UTF_8); + byte[] decrypted = cell.decrypt(encryptedBinary, tokenBinary, contextBinary); WritableArray response = dataSerialize(decrypted); successCallback.invoke(response); - } catch (SecureCellException e) { - errorCallback.invoke(e.getMessage()); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } } /* Context imprint mode */ - @ReactMethod public void secureCellContextImprintEncrypt(ReadableArray symmetricKey, - String plaintext, - String context, - Callback successCallback, - Callback errorCallback) + String plaintext, + String context, + Callback successCallback, + Callback errorCallback) { if (context == null || context.length() == 0) { - errorCallback.invoke(contextRequired); + WritableMap error = new WritableNativeMap(); + error.putString("message", contextRequired); + errorCallback.invoke(error); return; } - - byte[] bkey; try { - bkey = dataDeserialize(symmetricKey); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; + byte[] symmetricKeyBinary = dataDeserialize(symmetricKey); + SecureCell.ContextImprint cell = SecureCell.ContextImprintWithKey(symmetricKeyBinary); + byte[] plaintextBinary = plaintext.getBytes(StandardCharsets.UTF_8); + byte[] contextBinary = context.getBytes(StandardCharsets.UTF_8); + byte[] encrypted = cell.encrypt(plaintextBinary, contextBinary); + WritableArray response = dataSerialize(encrypted); + successCallback.invoke(response); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } - SecureCell.ContextImprint cell = SecureCell.ContextImprintWithKey(bkey); - - byte[] txt = plaintext.getBytes(StandardCharsets.UTF_8); - byte[] ctx = context.getBytes(StandardCharsets.UTF_8); - byte[] encrypted = cell.encrypt(txt, ctx); - - WritableArray response = dataSerialize(encrypted); - successCallback.invoke(response); } @ReactMethod public void secureCellContextImprintDecrypt(ReadableArray symmetricKey, - ReadableArray encrypted, - String context, - Callback successCallback, - Callback errorCallback) + ReadableArray encrypted, + String context, + Callback successCallback, + Callback errorCallback) { if (context == null || context.length() == 0) { - errorCallback.invoke(contextRequired); + WritableMap error = new WritableNativeMap(); + error.putString("message", contextRequired); + errorCallback.invoke(error); return; } - - byte[] bkey; - byte[] enc; try { - bkey = dataDeserialize(symmetricKey); - enc = dataDeserialize(encrypted); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; + byte[] symmetricKeyBinary = dataDeserialize(symmetricKey); + byte[] encryptedBinary = dataDeserialize(encrypted); + SecureCell.ContextImprint cell = SecureCell.ContextImprintWithKey(symmetricKeyBinary); + byte[] contextBinary = context.getBytes(StandardCharsets.UTF_8); + byte[] decrypted = cell.decrypt(encryptedBinary, contextBinary); + WritableArray response = dataSerialize(decrypted); + successCallback.invoke(response); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } - SecureCell.ContextImprint cell = SecureCell.ContextImprintWithKey(bkey); - - byte[] ctx = context.getBytes(StandardCharsets.UTF_8); - byte[] decrypted = cell.decrypt(enc, ctx); - - WritableArray response = dataSerialize(decrypted); - successCallback.invoke(response); } /* Secure Message */ @ReactMethod public void secureMessageSign(String message, - ReadableArray privateKey, - ReadableArray publicKey, - Callback successCallback, - Callback errorCallback) + ReadableArray privateKey, + Callback successCallback, + Callback errorCallback) { if (privateKey == null || privateKey.size() == 0) { - errorCallback.invoke(privateKeyRequired); - return; - } - byte[] bkey; - try { - bkey = dataDeserialize(privateKey); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); + WritableMap error = new WritableNativeMap(); + error.putString("message", privateKeyRequired); + errorCallback.invoke(error); return; } - PrivateKey pvtKey = new PrivateKey(bkey); - SecureMessage secureMessage; - byte[] msg = message.getBytes(StandardCharsets.UTF_8); - - if (publicKey == null ) { - secureMessage = new SecureMessage(pvtKey); - } else { - try { - bkey = dataDeserialize(publicKey); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; - } - PublicKey pubKey = new PublicKey(bkey); - secureMessage = new SecureMessage(pvtKey,pubKey); - } - try { - byte[] signedMessage = secureMessage.sign(msg); + byte[] privateKeyBinary = dataDeserialize(privateKey); + PrivateKey keyPrivateKey = new PrivateKey(privateKeyBinary); + byte[] messageBinary = message.getBytes(StandardCharsets.UTF_8); + SecureMessage secureMessage = new SecureMessage(keyPrivateKey); + byte[] signedMessage = secureMessage.sign(messageBinary); WritableArray response = dataSerialize(signedMessage); successCallback.invoke(response); - } catch (NullArgumentException | SecureMessageWrapException e) { - errorCallback.invoke(e.getMessage()); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } - } @ReactMethod public void secureMessageVerify(ReadableArray message, - ReadableArray privateKey, ReadableArray publicKey, - Callback successCallback, - Callback errorCallback) + Callback successCallback, + Callback errorCallback) { - if (publicKey == null || publicKey.size() == 0) { - errorCallback.invoke(publicKeyRequired); + WritableMap error = new WritableNativeMap(); + error.putString("message", publicKeyRequired); + errorCallback.invoke(error); return; } - - byte[] bkey; - byte[] msg; - PrivateKey pvtKey; - PublicKey pubKey; try { - bkey = dataDeserialize(publicKey); - pubKey = new PublicKey(bkey); - msg = dataDeserialize(message); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; - } - - SecureMessage secureMessage; - - if ( privateKey == null || privateKey.size() == 0) { - secureMessage = new SecureMessage(pubKey); - } else { - try { - bkey = dataDeserialize(privateKey); - pvtKey = new PrivateKey(bkey); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; - } - - secureMessage = new SecureMessage(pvtKey, pubKey); - } - try { - byte[] verifiedMessage = secureMessage.verify(msg, pubKey); + byte[] publicKeyBinary = dataDeserialize(publicKey); + PublicKey keyPublicKey = new PublicKey(publicKeyBinary); + byte[] messageBinary = dataDeserialize(message); + SecureMessage secureMessage = new SecureMessage(keyPublicKey); + byte[] verifiedMessage = secureMessage.verify(messageBinary, keyPublicKey); WritableArray response = dataSerialize(verifiedMessage); successCallback.invoke(response); - } catch (NullArgumentException | SecureMessageWrapException e) { - errorCallback.invoke(e.getMessage()); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } } @ReactMethod public void secureMessageEncrypt(String message, - ReadableArray privateKey, - ReadableArray publicKey, - Callback successCallback, - Callback errorCallback) + ReadableArray privateKey, + ReadableArray publicKey, + Callback successCallback, + Callback errorCallback) { - - if ( privateKey == null || privateKey.size() == 0) { - errorCallback.invoke(privateKeyRequired); - return; - } - if ( publicKey == null || publicKey.size() == 0 ) { - errorCallback.invoke(publicKeyRequired); + if (privateKey == null || privateKey.size() == 0) { + WritableMap error = new WritableNativeMap(); + error.putString("message", privateKeyRequired); + errorCallback.invoke(error); return; } - - byte[] bkey; - PrivateKey pvtKey; - PublicKey pubKey; - try { - bkey = dataDeserialize(privateKey); - pvtKey = new PrivateKey(bkey); - bkey = dataDeserialize(publicKey); - pubKey = new PublicKey(bkey); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); + if (publicKey == null || publicKey.size() == 0) { + WritableMap error = new WritableNativeMap(); + error.putString("message", publicKeyRequired); + errorCallback.invoke(error); return; } - - byte[] msg = message.getBytes(StandardCharsets.UTF_8); - - SecureMessage secureMessage = new SecureMessage(pvtKey, pubKey); try { - byte[] encrypted = secureMessage.wrap(msg, pubKey); - WritableArray response = dataSerialize(encrypted); + byte[] privateKeyBinary = dataDeserialize(privateKey); + PrivateKey keyPrivateKey = new PrivateKey(privateKeyBinary); + byte[] publicKeyBinary = dataDeserialize(publicKey); + PublicKey keyPublicKey = new PublicKey(publicKeyBinary); + byte[] messageBinary = message.getBytes(StandardCharsets.UTF_8); + SecureMessage secureMessage = new SecureMessage(keyPrivateKey, keyPublicKey); + byte[] encryptedMessage = secureMessage.wrap(messageBinary); + WritableArray response = dataSerialize(encryptedMessage); successCallback.invoke(response); - } catch (NullArgumentException | SecureMessageWrapException e) { - errorCallback.invoke(e.getMessage()); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } } @@ -519,65 +446,59 @@ public void secureMessageEncrypt(String message, public void secureMessageDecrypt(ReadableArray message, ReadableArray privateKey, ReadableArray publicKey, - Callback successCallback, - Callback errorCallback) + Callback successCallback, + Callback errorCallback) { - - if ( privateKey == null || privateKey.size() == 0) { - errorCallback.invoke(privateKeyRequired); - return; - } - if ( publicKey == null || publicKey.size() == 0 ) { - errorCallback.invoke(publicKeyRequired); + if (privateKey == null || privateKey.size() == 0) { + WritableMap error = new WritableNativeMap(); + error.putString("message", privateKeyRequired); + errorCallback.invoke(error); return; } - - byte[] bkey; - byte[] msg; - PrivateKey pvtKey; - PublicKey pubKey; - try { - bkey = dataDeserialize(privateKey); - pvtKey = new PrivateKey(bkey); - bkey = dataDeserialize(publicKey); - pubKey = new PublicKey(bkey); - msg = dataDeserialize(message); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); + if (publicKey == null || publicKey.size() == 0) { + WritableMap error = new WritableNativeMap(); + error.putString("message", publicKeyRequired); + errorCallback.invoke(error); return; } - - SecureMessage secureMessage = new SecureMessage(pvtKey, pubKey); try { - byte[] decrypted = secureMessage.unwrap(msg, pubKey); + byte[] privateKeyBinary = dataDeserialize(privateKey); + PrivateKey keyPrivateKey = new PrivateKey(privateKeyBinary); + byte[] publicKeyBinary = dataDeserialize(publicKey); + PublicKey keyPublicKey = new PublicKey(publicKeyBinary); + byte[] messageBinary = dataDeserialize(message); + SecureMessage secureMessage = new SecureMessage(keyPrivateKey, keyPublicKey); + byte[] decrypted = secureMessage.unwrap(messageBinary, keyPublicKey); WritableArray response = dataSerialize(decrypted); successCallback.invoke(response); - } catch (NullArgumentException | SecureMessageWrapException e) { - errorCallback.invoke(e.getMessage()); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } } @ReactMethod public void initComparator(ReadableArray sharedSecret, - Callback successCallback, - Callback errorCallback) + Callback successCallback, + Callback errorCallback) { - byte[] sharedSecretData; try { - sharedSecretData = dataDeserialize(sharedSecret); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; + byte[] sharedSecretData = dataDeserialize(sharedSecret); + SecureCompare cmp = new SecureCompare(sharedSecretData); + final String uuid = UUID.randomUUID().toString(); + cmprs.put(uuid, cmp); + successCallback.invoke(uuid); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } - SecureCompare cmp = new SecureCompare(sharedSecretData); - final String uuid = UUID.randomUUID().toString(); - cmprs.put(uuid, cmp); - successCallback.invoke(uuid); } @ReactMethod public void statusOfComparator(String uuid, - Callback callback) + Callback callback) { SecureCompare cmp = cmprs.get(uuid); if (cmp == null) { @@ -591,8 +512,8 @@ public void statusOfComparator(String uuid, @ReactMethod public void beginCompare(String uuid, - Callback successCallback, - Callback errorCallback) + Callback successCallback, + Callback errorCallback) { SecureCompare cmp = cmprs.get(uuid); if (cmp == null) { @@ -606,24 +527,17 @@ public void beginCompare(String uuid, @ReactMethod public void proceedCompare(String uuid, - ReadableArray previous, - Callback successCallback, - Callback errorCallback) + ReadableArray previous, + Callback successCallback, + Callback errorCallback) { SecureCompare cmp = cmprs.get(uuid); if (cmp == null) { - errorCallback.invoke(comparator_error); - return; - } - - byte[] data; - try { - data = dataDeserialize(previous); - } catch (ByteOverflowException e) { - errorCallback.invoke(e.getMessage()); - return; + errorCallback.invoke(comparator_error); + return; } try { + byte[] data = dataDeserialize(previous); data = cmp.proceed(data); WritableArray response = dataSerialize(data); SecureCompare.CompareResult result = cmp.getResult(); @@ -632,9 +546,10 @@ public void proceedCompare(String uuid, cmprs.remove(uuid); } successCallback.invoke(response, status); - } catch (SecureCompareException e) { - errorCallback.invoke(e.getMessage()); + } catch (Exception e) { + WritableMap error = new WritableNativeMap(); + error.putString("message", e.toString()); + errorCallback.invoke(error); } } - } diff --git a/src/wrappers/themis/react-native-themis/android/src/main/main.iml b/src/wrappers/themis/react-native-themis/android/src/main/main.iml new file mode 100644 index 000000000..908ad4f52 --- /dev/null +++ b/src/wrappers/themis/react-native-themis/android/src/main/main.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/wrappers/themis/react-native-themis/ios/RCTThemis.h b/src/wrappers/themis/react-native-themis/ios/RCTThemis.h index b9a884ed9..32829b570 100644 --- a/src/wrappers/themis/react-native-themis/ios/RCTThemis.h +++ b/src/wrappers/themis/react-native-themis/ios/RCTThemis.h @@ -10,19 +10,23 @@ #import -#define BYTEOVERFLOW 255 -#define CONTEXTREQUIRED 254 -#define PUBLICKEYREQUIRED 253 -#define PRIVATEKEYREQUIRED 252 - +#define BYTEOVERFLOW 255 +#define CONTEXTREQUIRED 254 +#define PUBLICKEYREQUIRED 253 +#define PRIVATEKEYREQUIRED 252 +#define DESERIALIZE_ERROR 251 +#define DESERIALIZE_MEMORY 250 + +#define BYTEOVERFLOWREASON "Byte overflow: value is out of range" #define CONTEXTREQUIREDREASON "Context required" #define PUBLICKEYREQUIREDREASON "Public key can not be null or blank" #define PRIVATEKEYREQUIREDREASON "Private key can not be null or blank" +#define DESERIALIZE_ERRORREASON "Deserialize error: empty input data" +#define DESERIALIZE_MEMORYREASON "Deserialize error: can not allocate memory" -#define KEYTYPE_EC 0 +#define KEYTYPE_EC 0 #define KEYTYPE_RSA 1 - @interface RCTThemis : NSObject @end diff --git a/src/wrappers/themis/react-native-themis/ios/RCTThemis.m b/src/wrappers/themis/react-native-themis/ios/RCTThemis.m index ebf197a8e..9df265036 100644 --- a/src/wrappers/themis/react-native-themis/ios/RCTThemis.m +++ b/src/wrappers/themis/react-native-themis/ios/RCTThemis.m @@ -18,10 +18,16 @@ @implementation RCTThemis // To save the comparators objects NSMutableDictionary* cmprs; +NSNumber *uchar_min; +NSNumber *uchar_max; + - (instancetype)init { self = [super init]; cmprs = [[NSMutableDictionary alloc] init]; + uchar_min = [NSNumber numberWithInt:0]; + uchar_max = [NSNumber numberWithInt:255]; + return self; } @@ -37,10 +43,10 @@ - (NSDictionary *)constantsToExport }; } -// It required by constantsToExport and iOS +// It required by constantsToExport and iOS + (BOOL)requiresMainQueueSetup { - return YES; + return YES; } /***********************************************/ @@ -50,11 +56,11 @@ + (BOOL)requiresMainQueueSetup + (NSArray*)dataSerialize:(NSData*) data { - if (data == nil) return nil; - + if (data == nil || data.length == 0 ) return nil; + const char* buffer = (const char*) data.bytes; NSMutableArray *array = [[NSMutableArray alloc] init]; - + for (NSInteger i = 0; i < data.length; i++) { NSNumber *num = [[NSNumber alloc] initWithUnsignedChar: buffer[i]]; [array addObject:num]; @@ -67,31 +73,30 @@ + (NSArray*)dataSerialize:(NSData*) data /* NSData <-- NSArray[NSNumber(unsigned char)] */ /* */ - -+ (NSData*)dataDeserialize:(NSArray*) data ++ (NSData*)dataDeserialize: (NSArray*) data error:(NSError**) error { - if (data == nil) return nil; - + if ( data == nil || data.count == 0 ) { + *error = SCERROR(DESERIALIZE_ERROR, @DESERIALIZE_ERRORREASON); + return nil; + } + char* buffer = (char*)malloc(data.count); if (buffer == nil) { - return nil; // malloc returns nil + *error = SCERROR(DESERIALIZE_MEMORY, @DESERIALIZE_MEMORYREASON); + return nil; } - + for (NSInteger i = 0; i < data.count; i++) { NSNumber *num = data[i]; - const unsigned char c = num.unsignedCharValue; - if (c < 0 || c > 255) { - NSException *e = [NSException - exceptionWithName:@"ByteOverflowException" - reason:@"Value is out of range" - userInfo:nil]; - @throw e; + /* Check int value before casting to char */ + if ([num compare:uchar_min] == NSOrderedAscending || [num compare:uchar_max] == NSOrderedDescending) { + free(buffer); // did not forget to free allocated memory + *error = SCERROR(BYTEOVERFLOW, @BYTEOVERFLOWREASON); + return nil; } - buffer[i] = c; + buffer[i] = num.unsignedCharValue; } - NSData *result = [NSData dataWithBytesNoCopy:buffer length:data.count freeWhenDone:YES]; - return result; } @@ -99,14 +104,15 @@ + (NSData*)dataDeserialize:(NSArray*) data callback:(RCTResponseSenderBlock)callback ) { - NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding]; - NSArray *data2 = [RCTThemis dataSerialize: data]; + NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding]; + NSArray *data2 = [RCTThemis dataSerialize: data]; callback(@[data2]); } RCT_EXPORT_METHOD(keyPair:(nonnull NSNumber*) algorithm - callback:(RCTResponseSenderBlock)callback) + successCallback: (RCTResponseSenderBlock)successCallback + ) { TSKeyGen *keypair; switch (algorithm.intValue) { @@ -120,59 +126,57 @@ + (NSData*)dataDeserialize:(NSArray*) data NSArray *privateKey = [RCTThemis dataSerialize: keypair.privateKey]; NSArray *publicKey = [RCTThemis dataSerialize: keypair.publicKey]; - + NSDictionary *dictionary = @{ - @"private" : privateKey, - @"public" : publicKey + @"private" : privateKey, + @"public" : publicKey }; - callback(@[dictionary]); + successCallback(@[dictionary]); } RCT_EXPORT_METHOD(symmetricKey:(RCTResponseSenderBlock)callback) { NSData *symmetricKey = TSGenerateSymmetricKey(); - NSArray *masterKey = [RCTThemis dataSerialize: symmetricKey]; + NSArray *masterKey = [RCTThemis dataSerialize: symmetricKey]; callback(@[masterKey]); } -- (TSCellSeal *)newSealMode: (NSArray*) symmetricKey +- (TSCellSeal *)newSealMode: (NSArray*) symmetricKey error:(NSError**) error { - @try { - NSData *masterKey = [RCTThemis dataDeserialize: symmetricKey]; - TSCellSeal *cell = [[TSCellSeal alloc] initWithKey:masterKey]; - return cell; - } - @catch (NSException *e) { - @throw e; // rethrow to catch in final function + NSData *masterKey = [RCTThemis dataDeserialize: symmetricKey error:error]; + if (masterKey == nil) { + return nil; } - + TSCellSeal *cell = [[TSCellSeal alloc] initWithKey:masterKey]; + return cell; } - - - RCT_EXPORT_METHOD(secureCellSealWithSymmetricKeyEncrypt:(NSArray*) symmetricKey plaintext: (NSString*)plaintext context: (NSString*)context successCallback: (RCTResponseSenderBlock)successCallback errorCallback: (RCTResponseErrorBlock)errorCallback) { - TSCellSeal *cell; - @try { - cell = [self newSealMode:symmetricKey]; + NSError *error; + + cell = [self newSealMode:symmetricKey error:&error]; + if (cell == nil) { + errorCallback(error); + return; } - @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); + + NSData *plaintextBinary = [plaintext dataUsingEncoding:NSUTF8StringEncoding]; + NSData *contextBinary = [context dataUsingEncoding:NSUTF8StringEncoding]; + NSData *encrypted = [cell encrypt:plaintextBinary + context:contextBinary + error:&error]; + if (encrypted == nil) { errorCallback(error); return; } - - NSData *txt = [plaintext dataUsingEncoding:NSUTF8StringEncoding]; - NSData *ctx = [context dataUsingEncoding:NSUTF8StringEncoding]; - NSData *encrypted = [cell encrypt:txt context:ctx]; NSArray *result = [RCTThemis dataSerialize:encrypted]; successCallback(@[result]); } @@ -186,31 +190,31 @@ - (TSCellSeal *)newSealMode: (NSArray*) symmetricKey { TSCellSeal *cell; - NSData *enc; - - @try { - cell = [self newSealMode:symmetricKey]; - enc = [RCTThemis dataDeserialize:encrypted]; + NSData *encryptedBinary; + NSError *error; + + cell = [self newSealMode:symmetricKey error:&error]; + if (cell == nil) { + errorCallback(error); + return; } - @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); + + encryptedBinary = [RCTThemis dataDeserialize:encrypted error:&error]; + if (encryptedBinary == nil) { errorCallback(error); return; } - - NSData *ctx = [context dataUsingEncoding:NSUTF8StringEncoding]; - - NSError *error; - NSData *decrypted = [cell decrypt:enc - context:ctx - error:&error]; - if (error) { + + NSData *contextBinary = [context dataUsingEncoding:NSUTF8StringEncoding]; + NSData *decrypted = [cell decrypt:encryptedBinary + context:contextBinary + error:&error]; + if (decrypted == nil) { errorCallback(error); } else { NSArray* result = [RCTThemis dataSerialize:decrypted]; successCallback(@[result]); } - } @@ -224,18 +228,23 @@ - (TSCellSeal *)newSealModeWithPassphrase: (NSString*) passphrase RCT_EXPORT_METHOD(secureCellSealWithPassphraseEncrypt:(NSString*) passphrase plaintext: (NSString*)plaintext context: (NSString*)context - callback: (RCTResponseSenderBlock)callback) + successCallback: (RCTResponseSenderBlock)successCallback + errorCallback: (RCTResponseErrorBlock)errorCallback + ) { + TSCellSeal *cell = [self newSealModeWithPassphrase:passphrase]; + NSData *plaintextBinary = [plaintext dataUsingEncoding:NSUTF8StringEncoding]; + NSData *contextBinary = [context dataUsingEncoding:NSUTF8StringEncoding]; - TSCellSeal *cell = [self newSealModeWithPassphrase:passphrase]; - - NSData *txt = [plaintext dataUsingEncoding:NSUTF8StringEncoding]; - NSData *ctx = [context dataUsingEncoding:NSUTF8StringEncoding]; - NSData *encrypted = [cell encrypt:txt context:ctx]; - - NSArray *result = [RCTThemis dataSerialize:encrypted]; + NSError *error; + NSData *encrypted = [cell encrypt:plaintextBinary context:contextBinary error:&error]; - callback(@[result]); + if (encrypted == nil) { + errorCallback(error); + } else { + NSArray *result = [RCTThemis dataSerialize:encrypted]; + successCallback(@[result]); + } } RCT_EXPORT_METHOD(secureCellSealWithPassphraseDecrypt:(NSString*) passphrase @@ -246,43 +255,39 @@ - (TSCellSeal *)newSealModeWithPassphrase: (NSString*) passphrase ) { - TSCellSeal *cell = [self newSealModeWithPassphrase:passphrase]; - NSData *enc; - - @try { - enc = [RCTThemis dataDeserialize:encrypted]; - } @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); - errorCallback(error); - } - - NSData *ctx = [context dataUsingEncoding:NSUTF8StringEncoding]; - + TSCellSeal *cell = [self newSealModeWithPassphrase:passphrase]; + NSData *encryptedBinary; NSError *error; - NSData *decrypted = [cell decrypt:enc - context:ctx - error:&error]; - if (error) { + + encryptedBinary = [RCTThemis dataDeserialize:encrypted error:&error]; + if (encryptedBinary == nil) { + errorCallback(error); + return; + } + + NSData *contextBinary = [context dataUsingEncoding:NSUTF8StringEncoding]; + NSData *decrypted = [cell decrypt:encryptedBinary + context:contextBinary + error:&error]; + if (decrypted == nil) { errorCallback(error); } else { NSArray* result = [RCTThemis dataSerialize:decrypted]; successCallback(@[result]); } - } /* MARK: Token protect mode */ -- (TSCellToken *)newTokenMode:(NSArray*) symmetricKey +- (TSCellToken *)newTokenMode:(NSArray*) symmetricKey error:(NSError**) error { - @try { - NSData *masterKey = [RCTThemis dataDeserialize: symmetricKey]; - TSCellToken *cell = [[TSCellToken alloc] initWithKey:masterKey]; - return cell; - } - @catch (NSException *e) { - @throw e; + NSData *masterKey = [RCTThemis dataDeserialize: symmetricKey error:error]; + if (masterKey == nil) { + return nil; } + + TSCellToken *cell = [[TSCellToken alloc] initWithKey:masterKey]; + return cell; } RCT_EXPORT_METHOD(secureCellTokenProtectEncrypt:(NSArray*) symmetricKey @@ -291,28 +296,30 @@ - (TSCellToken *)newTokenMode:(NSArray*) symmetricKey successCallback: (RCTResponseSenderBlock)successCallback errorCallback: (RCTResponseErrorBlock)errorCallback) { - + NSError *error; TSCellToken *cell; - - @try { - cell = [self newTokenMode:symmetricKey]; + + cell = [self newTokenMode:symmetricKey error:&error]; + if (cell == nil) { + errorCallback(error); + return; } - @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); + + NSData *plaintextBinary = [plaintext dataUsingEncoding:NSUTF8StringEncoding]; + NSData *contextBinary = [context dataUsingEncoding:NSUTF8StringEncoding]; + + TSCellTokenEncryptedResult *result = [cell encrypt:plaintextBinary context:contextBinary error:&error]; + if (result == nil ) { errorCallback(error); return; } - - NSData *txt = [plaintext dataUsingEncoding:NSUTF8StringEncoding]; - NSData *ctx = [context dataUsingEncoding:NSUTF8StringEncoding]; - TSCellTokenEncryptedResult *result = [cell encrypt:txt context:ctx]; - + NSArray *encrypted = [RCTThemis dataSerialize: result.encrypted]; NSArray *token = [RCTThemis dataSerialize: result.token]; - + NSDictionary *dictionary = @{ - @"encrypted" : encrypted, - @"token" : token + @"encrypted" : encrypted, + @"token" : token }; successCallback(@[dictionary]); } @@ -327,49 +334,52 @@ - (TSCellToken *)newTokenMode:(NSArray*) symmetricKey { TSCellToken *cell; - NSData *enc; - NSData *tkn; - - @try { - cell = [self newTokenMode:symmetricKey]; - enc = [RCTThemis dataDeserialize:encrypted]; - tkn = [RCTThemis dataDeserialize:token]; + NSData *encryptedBinary; + NSData *tokenBinary; + NSError *error; + + cell = [self newTokenMode:symmetricKey error:&error]; + if (cell == nil) { + errorCallback(error); + return; } - @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); + + encryptedBinary = [RCTThemis dataDeserialize:encrypted error:&error]; + if (encryptedBinary == nil) { errorCallback(error); return; } - - NSData *ctx = [context dataUsingEncoding:NSUTF8StringEncoding]; - - NSError *error; - NSData *decrypted = [cell decrypt:enc - token:tkn - context:ctx - error:&error]; - if (error) { + + tokenBinary = [RCTThemis dataDeserialize:token error:&error]; + if (tokenBinary == nil) { + errorCallback(error); + return; + } + + NSData *contextBinary = [context dataUsingEncoding:NSUTF8StringEncoding]; + NSData *decrypted = [cell decrypt:encryptedBinary + token:tokenBinary + context:contextBinary + error:&error]; + if (decrypted == nil) { errorCallback(error); } else { NSArray* result = [RCTThemis dataSerialize:decrypted]; successCallback(@[result]); } - } /* MARK: Context imprint mode */ -- (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey +- (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey error: (NSError**) error { - @try { - NSData *masterKey = [RCTThemis dataDeserialize: symmetricKey]; - TSCellContextImprint *cell = [[TSCellContextImprint alloc] initWithKey:masterKey]; - return cell; - } - @catch (NSException *e) { - @throw e; + NSData *masterKey = [RCTThemis dataDeserialize: symmetricKey error:error]; + if (masterKey == nil) { + return nil; } + TSCellContextImprint *cell = [[TSCellContextImprint alloc] initWithKey:masterKey]; + return cell; } RCT_EXPORT_METHOD(secureCellContextImprintEncrypt:(NSArray*) symmetricKey @@ -383,24 +393,25 @@ - (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey errorCallback(error); return; } - + TSCellContextImprint *cell; - @try { - cell = [self newContextImprint:symmetricKey]; + NSError *error; + + cell = [self newContextImprint:symmetricKey error:&error]; + if (cell == nil) { + errorCallback(error); + return; } - @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); + + NSData *plaintextBinary = [plaintext dataUsingEncoding:NSUTF8StringEncoding]; + NSData *contextBinary = [context dataUsingEncoding:NSUTF8StringEncoding]; + NSData *encrypted = [cell encrypt:plaintextBinary context:contextBinary error:&error]; + if (encrypted == nil) { errorCallback(error); return; } - - - NSData *txt = [plaintext dataUsingEncoding:NSUTF8StringEncoding]; - NSData *ctx = [context dataUsingEncoding:NSUTF8StringEncoding]; - NSData *encrypted = [cell encrypt:txt context:ctx]; - - NSArray *result = [RCTThemis dataSerialize:encrypted]; + NSArray *result = [RCTThemis dataSerialize:encrypted]; successCallback(@[result]); } @@ -410,28 +421,38 @@ - (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey successCallback:(RCTResponseSenderBlock)successCallback errorCallback:(RCTResponseErrorBlock)errorCallback) { - if (context == nil || [context isEqual: @""]) { NSError* error = SCERROR(CONTEXTREQUIRED, @CONTEXTREQUIREDREASON); errorCallback(error); return; } - + TSCellContextImprint *cell; - NSData *enc; - @try { - cell = [self newContextImprint:symmetricKey]; - enc = [RCTThemis dataDeserialize:encrypted]; + NSData *encryptedBinary; + NSError *error; + + cell = [self newContextImprint:symmetricKey error:&error]; + if (cell == nil) { + errorCallback(error); + return; } - @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); + encryptedBinary = [RCTThemis dataDeserialize:encrypted error:&error]; + if (encryptedBinary == nil) { + errorCallback(error); + return; + } + + NSData *contextBinary = [context dataUsingEncoding:NSUTF8StringEncoding]; + NSData *decrypted = [cell decrypt:encryptedBinary + context:contextBinary + error:&error + ]; + + if (decrypted == nil) { errorCallback(error); return; } - NSData *ctx = [context dataUsingEncoding:NSUTF8StringEncoding]; - NSData *decrypted = [cell decrypt:enc - context:ctx]; NSArray* result = [RCTThemis dataSerialize:decrypted]; successCallback(@[result]); } @@ -439,49 +460,39 @@ - (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey /* MARK: Secure Message */ RCT_EXPORT_METHOD(secureMessageSign:(NSString*) message privateKey:(NSArray*) privateKey - publicKey:(NSArray*) publicKey successCallback:(RCTResponseSenderBlock)successCallback errorCallback:(RCTResponseErrorBlock) errorCallback ) { - if (privateKey == nil || privateKey.count == 0) { NSError* error = SCERROR(PRIVATEKEYREQUIRED, @PRIVATEKEYREQUIREDREASON); errorCallback(error); return; } - - NSData* pvtKey; - NSData* pubKey; - @try { - pvtKey = [RCTThemis dataDeserialize:privateKey]; - pubKey = [RCTThemis dataDeserialize:publicKey]; - } - @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); + + NSData *privateKeyBinary; + NSError *error; + + privateKeyBinary = [RCTThemis dataDeserialize:privateKey error:&error]; + if (privateKeyBinary == nil) { errorCallback(error); return; } - - NSData* msg = [message dataUsingEncoding:NSUTF8StringEncoding]; - TSMessage *secureMessage = [[TSMessage alloc] initInSignVerifyModeWithPrivateKey:pvtKey - peerPublicKey:pubKey]; - - NSError *error; - NSData *signedMessage = [secureMessage wrapData:msg error:&error]; - - if (error) { + NSData* messageBinary = [message dataUsingEncoding:NSUTF8StringEncoding]; + TSMessage *secureMessage = [[TSMessage alloc] initInSignVerifyModeWithPrivateKey:privateKeyBinary + peerPublicKey:nil]; + + NSData *signedMessage = [secureMessage wrapData:messageBinary error:&error]; + if (signedMessage == nil) { errorCallback(error); } else { NSArray* result = [RCTThemis dataSerialize:signedMessage]; successCallback(@[result]); } - } RCT_EXPORT_METHOD(secureMessageVerify:(NSArray*) message - privateKey:(NSArray*) privateKey publicKey:(NSArray*) publicKey successCallback:(RCTResponseSenderBlock)successCallback errorCallback:(RCTResponseErrorBlock)errorCallback @@ -493,29 +504,28 @@ - (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey errorCallback(error); return; } - - NSData* pvtKey; - NSData* pubKey; - NSData* msg; - - @try { - pvtKey = [RCTThemis dataDeserialize:privateKey]; - pubKey = [RCTThemis dataDeserialize:publicKey]; - msg = [RCTThemis dataDeserialize:message]; + + NSData *publicKeyBinary; + NSData *messageBinary; + NSError *error; + + publicKeyBinary = [RCTThemis dataDeserialize:publicKey error:&error]; + if (publicKeyBinary == nil) { + errorCallback(error); + return; } - @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); + + messageBinary = [RCTThemis dataDeserialize:message error:&error]; + if (messageBinary == nil) { errorCallback(error); return; } - - TSMessage *secureMessage = [[TSMessage alloc] initInSignVerifyModeWithPrivateKey:pvtKey - peerPublicKey:pubKey]; - - NSError *error; - NSData *verifiedMessage = [secureMessage unwrapData:msg error:&error]; - if (error) { + TSMessage *secureMessage = [[TSMessage alloc] initInSignVerifyModeWithPrivateKey:nil + peerPublicKey:publicKeyBinary]; + + NSData *verifiedMessage = [secureMessage unwrapData:messageBinary error:&error]; + if (verifiedMessage == nil) { errorCallback(error); } else { NSArray* result = [RCTThemis dataSerialize:verifiedMessage]; @@ -531,7 +541,6 @@ - (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey ) { - if (privateKey == nil || privateKey.count == 0) { NSError* error = SCERROR(PRIVATEKEYREQUIRED, @PRIVATEKEYREQUIREDREASON); errorCallback(error); @@ -539,39 +548,37 @@ - (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey } if (publicKey == nil || publicKey.count == 0) { - NSError* error = SCERROR(PUBLICKEYREQUIRED, @PUBLICKEYREQUIREDREASON); - errorCallback(error); - return; + NSError* error = SCERROR(PUBLICKEYREQUIRED, @PUBLICKEYREQUIREDREASON); + errorCallback(error); + return; } - NSData* pvtKey; - NSData* pubKey; - - @try { - pvtKey = [RCTThemis dataDeserialize:privateKey]; - pubKey = [RCTThemis dataDeserialize:publicKey]; + NSData *privateKeyBinary; + NSData *publicKeyBinary; + NSError *error; + + privateKeyBinary = [RCTThemis dataDeserialize:privateKey error:&error]; + if (privateKeyBinary == nil) { + errorCallback(error); + return; } - @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); + publicKeyBinary = [RCTThemis dataDeserialize:publicKey error:&error]; + if (publicKeyBinary == nil) { errorCallback(error); return; } - - NSData* msg = [message dataUsingEncoding:NSUTF8StringEncoding]; - TSMessage *secureMessage = [[TSMessage alloc] initInEncryptModeWithPrivateKey:pvtKey - peerPublicKey:pubKey]; - - NSError *error; - NSData *encryptedMessage = [secureMessage wrapData:msg error:&error]; - - if (error) { + NSData* messageBinary = [message dataUsingEncoding:NSUTF8StringEncoding]; + TSMessage *secureMessage = [[TSMessage alloc] initInEncryptModeWithPrivateKey:privateKeyBinary + peerPublicKey:publicKeyBinary]; + + NSData *encryptedMessage = [secureMessage wrapData:messageBinary error:&error]; + if (encryptedMessage == nil) { errorCallback(error); } else { NSArray* result = [RCTThemis dataSerialize:encryptedMessage]; successCallback(@[result]); } - } RCT_EXPORT_METHOD(secureMessageDecrypt:(NSArray*) message @@ -581,7 +588,6 @@ - (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey errorCallback:(RCTResponseErrorBlock) errorCallback ) { - if (privateKey == nil || privateKey.count == 0) { NSError* error = SCERROR(PRIVATEKEYREQUIRED, @PRIVATEKEYREQUIREDREASON); errorCallback(error); @@ -593,35 +599,38 @@ - (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey errorCallback(error); return; } - - NSData* pvtKey; - NSData* pubKey; - NSData* msg; - - @try { - pvtKey = [RCTThemis dataDeserialize:privateKey]; - pubKey = [RCTThemis dataDeserialize:publicKey]; - msg = [RCTThemis dataDeserialize:message]; + + NSData *privateKeyBinary; + NSData *publicKeyBinary; + NSData *messageBinary; + NSError *error; + + privateKeyBinary = [RCTThemis dataDeserialize:privateKey error:&error]; + if (privateKeyBinary == nil) { + errorCallback(error); + return; } - @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); + publicKeyBinary = [RCTThemis dataDeserialize:publicKey error:&error]; + if (publicKeyBinary == nil) { errorCallback(error); return; } - - TSMessage *secureMessage = [[TSMessage alloc] initInEncryptModeWithPrivateKey:pvtKey - peerPublicKey:pubKey]; - - NSError *error; - NSData *decryptedMessage = [secureMessage unwrapData:msg error:&error]; - - if (error) { + messageBinary = [RCTThemis dataDeserialize:message error:&error]; + if (messageBinary == nil) { + errorCallback(error); + return; + } + + TSMessage *secureMessage = [[TSMessage alloc] initInEncryptModeWithPrivateKey:privateKeyBinary + peerPublicKey:publicKeyBinary]; + + NSData *decryptedMessage = [secureMessage unwrapData:messageBinary error:&error]; + if (decryptedMessage == nil) { errorCallback(error); } else { NSArray* result = [RCTThemis dataSerialize:decryptedMessage]; successCallback(@[result]); } - } /* MARK: Comparator */ @@ -630,16 +639,14 @@ - (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey successCallback:(RCTResponseSenderBlock) successCallback errorCallback:(RCTResponseErrorBlock) errorCallback) { - NSData* sharedSecretData; - - @try { - sharedSecretData = [RCTThemis dataDeserialize:sharedSecret]; - } - @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); + NSData *sharedSecretData; + NSError *error; + sharedSecretData = [RCTThemis dataDeserialize:sharedSecret error:&error]; + if (sharedSecretData == nil) { errorCallback(error); return; } + TSComparator* cmp = [[TSComparator alloc] initWithMessageToCompare:sharedSecretData]; NSString *uuid = [[NSUUID UUID] UUIDString]; cmprs[uuid] = cmp; @@ -659,23 +666,20 @@ - (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey } } - - RCT_EXPORT_METHOD(beginCompare:(NSString*) uuid successCallback:(RCTResponseSenderBlock) successCallback errorCallback:(RCTResponseErrorBlock) errorCallback ) { - - TSComparator* cmp = cmprs[uuid]; + TSComparator *cmp = cmprs[uuid]; if (cmp == nil) { errorCallback(nil); return; } - NSError* error; - NSData* data = [cmp beginCompare:&error]; - if (error) { + NSError *error; + NSData *data = [cmp beginCompare:&error]; + if (data == nil) { errorCallback(error); } else { NSArray* result = [RCTThemis dataSerialize:data]; @@ -691,24 +695,22 @@ - (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey { TSComparator* cmp = cmprs[uuid]; if ( cmp == nil ) { - errorCallback(nil); + NSError* error = SCERROR(-1, @"Comparator does not exist"); + errorCallback(error); return; } - - NSData* data; - - @try { - data = [RCTThemis dataDeserialize:previous]; - } - @catch (NSException *e) { - NSError* error = SCERROR(BYTEOVERFLOW, e.reason); + + NSData *data; + NSError *error; + + data = [RCTThemis dataDeserialize:previous error:&error]; + if (data == nil) { errorCallback(error); return; } - NSError* error; data = [cmp proceedCompare:data error:&error]; - if (error) { + if (data == nil) { errorCallback(error); } else { NSArray* result = [RCTThemis dataSerialize:data]; @@ -716,9 +718,12 @@ - (TSCellContextImprint *)newContextImprint:(NSArray*) symmetricKey if (cmp.status != TSComparatorNotReady) { [cmprs removeObjectForKey:uuid]; } - successCallback(@[result, status]); + if (result != nil) { + successCallback(@[result, status]); + } else { + successCallback(@[@"", status]); + } } - } diff --git a/src/wrappers/themis/react-native-themis/package.json b/src/wrappers/themis/react-native-themis/package.json index 330b6fade..92cb2b817 100644 --- a/src/wrappers/themis/react-native-themis/package.json +++ b/src/wrappers/themis/react-native-themis/package.json @@ -1,6 +1,6 @@ { "name": "react-native-themis", - "version": "0.14.7", + "version": "0.14.10", "description": "Themis React Native is a convenient cryptographic library for data protection", "react-native": "src/index", "source": "src/index", @@ -21,7 +21,8 @@ ], "scripts": { "test": "jest", - "typescript": "tsc --noEmit", + "typescript": "tsc", + "declaration": "tsc --declaration --emitDeclarationOnly", "lint": "eslint \"**/*.{js,ts,tsx}\"", "prepare": "bob build", "release": "release-it" diff --git a/src/wrappers/themis/react-native-themis/src/index.d.ts b/src/wrappers/themis/react-native-themis/src/index.d.ts index 380c88515..b1cfc7995 100644 --- a/src/wrappers/themis/react-native-themis/src/index.d.ts +++ b/src/wrappers/themis/react-native-themis/src/index.d.ts @@ -1,5 +1,7 @@ export declare const COMPARATOR_NOT_READY: any, COMPARATOR_NOT_MATCH: any, COMPARATOR_MATCH: any, COMPARATOR_ERROR: any, KEYTYPE_RSA: any, KEYTYPE_EC: any; -export declare function keyPair64(typeOfKey: any): Promise; +export declare function isBase64(str: String): boolean; +export declare function string64(input: String): String; +export declare function keyPair64(typeOfKey?: any): Promise; export declare function symmetricKey64(): Promise; export declare function secureCellSealWithSymmetricKeyEncrypt64(symmetricKey64: String, plaintext: String, context?: String): Promise; export declare function secureCellSealWithSymmetricKeyDecrypt64(symmetricKey64: String, encrypted64: String, context?: String): Promise; @@ -9,11 +11,10 @@ export declare function secureCellTokenProtectEncrypt64(symmetricKey64: String, export declare function secureCellTokenProtectDecrypt64(symmetricKey64: String, encrypted64: String, token64: String, context?: String): Promise; export declare function secureCellContextImprintEncrypt64(symmetricKey64: String, plaintext: String, context: String): Promise; export declare function secureCellContextImprintDecrypt64(symmetricKey64: String, encrypted64: String, context: String): Promise; -export declare function secureMessageSign64(plaintext: String, privateKey64: String, publicKey64: String): Promise; -export declare function secureMessageVerify64(signed64: String, privateKey64: String, publicKey64: String): Promise; +export declare function secureMessageSign64(plaintext: String, privateKey64: String, _publicKey64?: String): Promise; +export declare function secureMessageVerify64(signed64: String, _privateKey64: String | undefined, publicKey64: String): Promise; export declare function secureMessageEncrypt64(plaintext: String, privateKey64: String, publicKey64: String): Promise; export declare function secureMessageDecrypt64(encrypted64: String, privateKey64: String, publicKey64: String): Promise; -export declare function string64(input: String): String; export declare function comparatorInit64(data64: String): Promise; export declare function comparatorBegin(uuidStr: String): Promise; export declare function comparatorProceed64(uuidStr: String, data64: String): Promise; diff --git a/src/wrappers/themis/react-native-themis/src/index.js b/src/wrappers/themis/react-native-themis/src/index.js index 0cf72885f..c5351d3c3 100644 --- a/src/wrappers/themis/react-native-themis/src/index.js +++ b/src/wrappers/themis/react-native-themis/src/index.js @@ -12,7 +12,27 @@ const Themis = NativeModules.Themis }, }); export const { COMPARATOR_NOT_READY, COMPARATOR_NOT_MATCH, COMPARATOR_MATCH, COMPARATOR_ERROR, KEYTYPE_RSA, KEYTYPE_EC } = Themis.getConstants(); -export function keyPair64(typeOfKey) { +export function isBase64(str) { + const regex64 = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/; + return regex64.test(str); +} +export function string64(input) { + return Buffer.from(input).toString('base64'); +} +const checkInput = (param, name) => { + if (param === "" || param === undefined || param === null) { + throw new Error(`Parameter ${name} can not be empty`); + } + return param; +}; +const convertInputBase64 = (param, name) => { + checkInput(param, name); + if (!isBase64(param)) { + throw new Error(`Parameter ${name} is not base64 encoded`); + } + return Array.from(Buffer.from(param, 'base64')); +}; +export function keyPair64(typeOfKey = KEYTYPE_EC) { if (typeOfKey !== KEYTYPE_RSA && typeOfKey !== KEYTYPE_EC) { throw new Error('Invalid key type'); } @@ -37,13 +57,8 @@ export function symmetricKey64() { } ; export function secureCellSealWithSymmetricKeyEncrypt64(symmetricKey64, plaintext, context = "") { - if (plaintext === "" || plaintext === undefined || plaintext === null) { - throw new Error("Parameter plaintext can not be empty"); - } - if (symmetricKey64 === "" || symmetricKey64 === undefined || symmetricKey64 === null) { - throw new Error("Parameter symmetricKey64 can not be empty"); - } - const symmetricKey = Array.from(Buffer.from(symmetricKey64, 'base64')); + checkInput(plaintext, "plaintext"); // check plaintext is not empty + const symmetricKey = convertInputBase64(symmetricKey64, "symmetricKey64"); // check symmetricKey64 is not empty and base64 encoded return new Promise((resolve, reject) => { Themis.secureCellSealWithSymmetricKeyEncrypt(symmetricKey, plaintext, context, (encrypted) => { resolve(Buffer.from(new Uint8Array(encrypted)).toString("base64")); @@ -54,14 +69,8 @@ export function secureCellSealWithSymmetricKeyEncrypt64(symmetricKey64, plaintex } ; export function secureCellSealWithSymmetricKeyDecrypt64(symmetricKey64, encrypted64, context = "") { - if (symmetricKey64 === "" || symmetricKey64 === undefined || symmetricKey64 === null) { - throw new Error("Parameter symmetricKey64 can not be empty"); - } - if (encrypted64 === "" || encrypted64 === undefined || encrypted64 === null) { - throw new Error("Parameter encrypted64 can not be empty"); - } - const symmetricKey = Array.from(Buffer.from(symmetricKey64, 'base64')); - const encrypted = Array.from(Buffer.from(encrypted64, 'base64')); + const symmetricKey = convertInputBase64(symmetricKey64, "symmetricKey64"); + const encrypted = convertInputBase64(encrypted64, "encrypted64"); return new Promise((resolve, reject) => { Themis.secureCellSealWithSymmetricKeyDecrypt(symmetricKey, encrypted, context, (decrypted) => { resolve(Buffer.from(new Uint8Array(decrypted)).toString()); @@ -72,27 +81,20 @@ export function secureCellSealWithSymmetricKeyDecrypt64(symmetricKey64, encrypte } ; export function secureCellSealWithPassphraseEncrypt64(passphrase, plaintext, context = "") { - if (passphrase === "" || passphrase === undefined || passphrase === null) { - throw new Error("Parameter passphrase can not be empty"); - } - if (plaintext === "" || plaintext === undefined || plaintext === null) { - throw new Error("Parameter plaintext can not be empty"); - } - return new Promise((resolve) => { + checkInput(plaintext, "plaintext"); + checkInput(passphrase, "passphrase"); + return new Promise((resolve, reject) => { Themis.secureCellSealWithPassphraseEncrypt(passphrase, plaintext, context, (encrypted) => { resolve(Buffer.from(new Uint8Array(encrypted)).toString("base64")); + }, (error) => { + reject(error); }); }); } ; export function secureCellSealWithPassphraseDecrypt64(passphrase, encrypted64, context = "") { - if (passphrase === "" || passphrase === undefined || passphrase === null) { - throw new Error("Parameter passphrase can not be empty"); - } - if (encrypted64 === "" || encrypted64 === undefined || encrypted64 === null) { - throw new Error("Parameter encrypted64 can not be empty"); - } - const encrypted = Array.from(Buffer.from(encrypted64, 'base64')); + checkInput(passphrase, "passphrase"); + const encrypted = convertInputBase64(encrypted64, "encrypted64"); return new Promise((resolve, reject) => { Themis.secureCellSealWithPassphraseDecrypt(passphrase, encrypted, context, (decrypted) => { resolve(Buffer.from(new Uint8Array(decrypted)).toString()); @@ -102,13 +104,8 @@ export function secureCellSealWithPassphraseDecrypt64(passphrase, encrypted64, c }); } export function secureCellTokenProtectEncrypt64(symmetricKey64, plaintext, context = "") { - if (symmetricKey64 === "" || symmetricKey64 === undefined || symmetricKey64 === null) { - throw new Error("Parameter symmetricKey64 can not be empty"); - } - if (plaintext === "" || plaintext === undefined || plaintext === null) { - throw new Error("Parameter plaintext can not be empty"); - } - const symmetricKey = Array.from(Buffer.from(symmetricKey64, 'base64')); + checkInput(plaintext, "plaintext"); + const symmetricKey = convertInputBase64(symmetricKey64, "symmetricKey64"); return new Promise((resolve, reject) => { Themis.secureCellTokenProtectEncrypt(symmetricKey, plaintext, context, (encrypted) => { const data = Buffer.from(new Uint8Array(encrypted.encrypted)).toString("base64"); @@ -123,18 +120,9 @@ export function secureCellTokenProtectEncrypt64(symmetricKey64, plaintext, conte }); } export function secureCellTokenProtectDecrypt64(symmetricKey64, encrypted64, token64, context = "") { - if (symmetricKey64 === "" || symmetricKey64 === undefined || symmetricKey64 === null) { - throw new Error("Parameter symmetricKey64 can not be empty"); - } - if (encrypted64 === "" || encrypted64 === undefined || encrypted64 === null) { - throw new Error("Parameter encrypted64 can not be empty"); - } - if (token64 === "" || token64 === undefined || token64 === null) { - throw new Error("Parameter token64 can not be empty"); - } - const symmetricKey = Array.from(Buffer.from(symmetricKey64, 'base64')); - const encrypted = Array.from(Buffer.from(encrypted64, 'base64')); - const token = Array.from(Buffer.from(token64, 'base64')); + const symmetricKey = convertInputBase64(symmetricKey64, "symmetricKey64"); + const encrypted = convertInputBase64(encrypted64, "encrypted64"); + const token = convertInputBase64(token64, "token64"); return new Promise((resolve, reject) => { Themis.secureCellTokenProtectDecrypt(symmetricKey, encrypted, token, context, (decrypted) => { resolve(Buffer.from(new Uint8Array(decrypted)).toString()); @@ -145,16 +133,9 @@ export function secureCellTokenProtectDecrypt64(symmetricKey64, encrypted64, tok } // context imprint encrypt and decrypt export function secureCellContextImprintEncrypt64(symmetricKey64, plaintext, context) { - if (symmetricKey64 === "" || symmetricKey64 === undefined || symmetricKey64 === null) { - throw new Error("Parameter symmetricKey64 can not be empty"); - } - if (plaintext === "" || plaintext === undefined || plaintext === null) { - throw new Error("Parameter plaintext can not be empty"); - } - if (context === "" || context === undefined || context === null) { - throw new Error("Parameter context can not be empty"); - } - const symmetricKey = Array.from(Buffer.from(symmetricKey64, 'base64')); + checkInput(plaintext, "plaintext"); + checkInput(context, "context"); + const symmetricKey = convertInputBase64(symmetricKey64, "symmetricKey64"); return new Promise((resolve, reject) => { Themis.secureCellContextImprintEncrypt(symmetricKey, plaintext, context, (encrypted) => { resolve(Buffer.from(new Uint8Array(encrypted)).toString("base64")); @@ -164,17 +145,9 @@ export function secureCellContextImprintEncrypt64(symmetricKey64, plaintext, con }); } export function secureCellContextImprintDecrypt64(symmetricKey64, encrypted64, context) { - if (symmetricKey64 === "" || symmetricKey64 === undefined || symmetricKey64 === null) { - throw new Error("Parameter symmetricKey64 can not be empty"); - } - if (encrypted64 === "" || encrypted64 === undefined || encrypted64 === null) { - throw new Error("Parameter encrypted64 can not be empty"); - } - if (context === "" || context === undefined || context === null) { - throw new Error("Parameter context can not be empty"); - } - const symmetricKey = Array.from(Buffer.from(symmetricKey64, 'base64')); - const encrypted = Array.from(Buffer.from(encrypted64, 'base64')); + checkInput(context, "context"); + const symmetricKey = convertInputBase64(symmetricKey64, "symmetricKey64"); + const encrypted = convertInputBase64(encrypted64, "encrypted64"); return new Promise((resolve, reject) => { Themis.secureCellContextImprintDecrypt(symmetricKey, encrypted, context, (decrypted) => { resolve(Buffer.from(new Uint8Array(decrypted)).toString()); @@ -184,37 +157,22 @@ export function secureCellContextImprintDecrypt64(symmetricKey64, encrypted64, c }); } // secure message sign and verify -export function secureMessageSign64(plaintext, privateKey64, publicKey64) { - if (plaintext === "" || plaintext === undefined || plaintext === null) { - throw new Error("Parameter plaintext can not be empty"); - } - if (privateKey64 === "" || privateKey64 === undefined || privateKey64 === null) { - throw new Error("Parameter privateKey64 can not be empty"); - } - const privateKey = Array.from(Buffer.from(privateKey64, 'base64')); - const publicKey = publicKey64 !== null && publicKey64 !== "" ? - Array.from(Buffer.from(publicKey64, 'base64')) : null; +export function secureMessageSign64(plaintext, privateKey64, _publicKey64 = "") { + checkInput(plaintext, "plaintext"); + const privateKey = convertInputBase64(privateKey64, "privateKey64"); return new Promise((resolve, reject) => { - Themis.secureMessageSign(plaintext, privateKey, publicKey, (signed) => { + Themis.secureMessageSign(plaintext, privateKey, (signed) => { resolve(Buffer.from(new Uint8Array(signed)).toString("base64")); }, (error) => { reject(error); }); }); } -export function secureMessageVerify64(signed64, privateKey64, publicKey64) { - if (signed64 === "" || signed64 === undefined || signed64 === null) { - throw new Error("Parameter signed64 can not be empty"); - } - if (publicKey64 === "" || publicKey64 === undefined || publicKey64 === null) { - throw new Error("Parameter publicKey64 can not be empty"); - } - const signed = Array.from(Buffer.from(signed64, 'base64')); - const privateKey = privateKey64 !== null && privateKey64 !== "" ? - Array.from(Buffer.from(privateKey64, 'base64')) : null; - const publicKey = Array.from(Buffer.from(publicKey64, 'base64')); +export function secureMessageVerify64(signed64, _privateKey64 = "", publicKey64) { + const signed = convertInputBase64(signed64, "signed64"); + const publicKey = convertInputBase64(publicKey64, "publicKey64"); return new Promise((resolve, reject) => { - Themis.secureMessageVerify(signed, privateKey, publicKey, (verified) => { + Themis.secureMessageVerify(signed, publicKey, (verified) => { resolve(Buffer.from(new Uint8Array(verified)).toString()); }, (error) => { reject(error); @@ -223,17 +181,9 @@ export function secureMessageVerify64(signed64, privateKey64, publicKey64) { } // secure message encrypt and decrypt export function secureMessageEncrypt64(plaintext, privateKey64, publicKey64) { - if (plaintext === "" || plaintext === undefined || plaintext === null) { - throw new Error("Parameter plaintext can not be empty"); - } - if (privateKey64 === "" || privateKey64 === undefined || privateKey64 === null) { - throw new Error("Parameter privateKey64 can not be empty"); - } - if (publicKey64 === "" || publicKey64 === undefined || publicKey64 === null) { - throw new Error("Parameter publicKey64 can not be empty"); - } - const privateKey = Array.from(Buffer.from(privateKey64, 'base64')); - const publicKey = Array.from(Buffer.from(publicKey64, 'base64')); + checkInput(plaintext, "plaintext"); + const privateKey = convertInputBase64(privateKey64, "privateKey64"); + const publicKey = convertInputBase64(publicKey64, "publicKey64"); return new Promise((resolve, reject) => { Themis.secureMessageEncrypt(plaintext, privateKey, publicKey, (encrypted) => { resolve(Buffer.from(new Uint8Array(encrypted)).toString("base64")); @@ -243,18 +193,9 @@ export function secureMessageEncrypt64(plaintext, privateKey64, publicKey64) { }); } export function secureMessageDecrypt64(encrypted64, privateKey64, publicKey64) { - if (encrypted64 === "" || encrypted64 === undefined || encrypted64 === null) { - throw new Error("Parameter encrypted64 can not be empty"); - } - if (privateKey64 === "" || privateKey64 === undefined || privateKey64 === null) { - throw new Error("Parameter privateKey64 can not be empty"); - } - if (publicKey64 === "" || publicKey64 === undefined || publicKey64 === null) { - throw new Error("Parameter publicKey64 can not be empty"); - } - const encrypted = Array.from(Buffer.from(encrypted64, 'base64')); - const privateKey = Array.from(Buffer.from(privateKey64, 'base64')); - const publicKey = Array.from(Buffer.from(publicKey64, 'base64')); + const encrypted = convertInputBase64(encrypted64, "encrypted64"); + const privateKey = convertInputBase64(privateKey64, "privateKey64"); + const publicKey = convertInputBase64(publicKey64, "publicKey64"); return new Promise((resolve, reject) => { Themis.secureMessageDecrypt(encrypted, privateKey, publicKey, (decrypted) => { resolve(Buffer.from(new Uint8Array(decrypted)).toString()); @@ -263,15 +204,9 @@ export function secureMessageDecrypt64(encrypted64, privateKey64, publicKey64) { }); }); } -export function string64(input) { - return Buffer.from(input).toString('base64'); -} /* Returns UUID in string value that corresponds to new comparator */ export function comparatorInit64(data64) { - if (data64 === "" || data64 === undefined || data64 === null) { - throw new Error("Parameter data64 can not be empty"); - } - const data = Array.from(Buffer.from(data64, 'base64')); + const data = convertInputBase64(data64, "data64"); return new Promise((resolve, reject) => { Themis.initComparator(data, (comparator) => { resolve(comparator); @@ -281,9 +216,7 @@ export function comparatorInit64(data64) { }); } export function comparatorBegin(uuidStr) { - if (uuidStr === "" || uuidStr === undefined || uuidStr === null) { - throw new Error("Parameter uuidStr can not be empty"); - } + checkInput(uuidStr, "uuidStr"); return new Promise((resolve, reject) => { Themis.beginCompare(uuidStr, (data) => { resolve(Buffer.from(new Uint8Array(data)).toString("base64")); @@ -294,13 +227,8 @@ export function comparatorBegin(uuidStr) { } /* Returns next part of data and current status */ export function comparatorProceed64(uuidStr, data64) { - if (uuidStr === "" || uuidStr === undefined || uuidStr === null) { - throw new Error("Parameter uuidStr can not be empty"); - } - if (data64 === "" || data64 === undefined || data64 === null) { - throw new Error("Parameter data64 can not be empty"); - } - const data = Array.from(Buffer.from(data64, 'base64')); + checkInput(uuidStr, "uuidStr"); + const data = convertInputBase64(data64, "data64"); return new Promise((resolve, reject) => { Themis.proceedCompare(uuidStr, data, (nextData, status) => { const nextdata64 = Buffer.from(new Uint8Array(nextData)).toString("base64"); diff --git a/src/wrappers/themis/react-native-themis/src/index.tsx b/src/wrappers/themis/react-native-themis/src/index.tsx index 80aeadb9d..93719f72c 100644 --- a/src/wrappers/themis/react-native-themis/src/index.tsx +++ b/src/wrappers/themis/react-native-themis/src/index.tsx @@ -28,7 +28,32 @@ export const { KEYTYPE_EC } = Themis.getConstants() -export function keyPair64(typeOfKey: any): Promise { + +export function isBase64(str: String): boolean { + const regex64 = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/; + return regex64.test(str as string); +} + +export function string64(input: String): String { + return Buffer.from(input).toString('base64') +} + +const checkInput = (param: String, name: String) => { + if (param === "" || param === undefined || param === null) { + throw new Error(`Parameter ${name} can not be empty`); + } + return param; +} + +const convertInputBase64 = (param: String, name: String): any => { + checkInput(param, name); + if (!isBase64(param)) { + throw new Error(`Parameter ${name} is not base64 encoded`); + } + return Array.from(Buffer.from(param, 'base64')); +} + +export function keyPair64(typeOfKey: any = KEYTYPE_EC): Promise { if (typeOfKey !== KEYTYPE_RSA && typeOfKey !== KEYTYPE_EC) { throw new Error('Invalid key type'); } @@ -58,14 +83,8 @@ export function secureCellSealWithSymmetricKeyEncrypt64( plaintext: String, context: String = ""): Promise { - if (plaintext === "" || plaintext === undefined || plaintext === null) { - throw new Error("Parameter plaintext can not be empty"); - } - if (symmetricKey64 === "" || symmetricKey64 === undefined || symmetricKey64 === null) { - throw new Error("Parameter symmetricKey64 can not be empty"); - } - - const symmetricKey = Array.from(Buffer.from(symmetricKey64, 'base64')); + checkInput(plaintext, "plaintext"); // check plaintext is not empty + const symmetricKey = convertInputBase64(symmetricKey64, "symmetricKey64"); // check symmetricKey64 is not empty and base64 encoded return new Promise((resolve, reject) => { Themis.secureCellSealWithSymmetricKeyEncrypt(symmetricKey, plaintext, context, (encrypted: any) => { @@ -81,16 +100,8 @@ export function secureCellSealWithSymmetricKeyDecrypt64( encrypted64: String, context: String = ""): Promise { - if (symmetricKey64 === "" || symmetricKey64 === undefined || symmetricKey64 === null) { - throw new Error("Parameter symmetricKey64 can not be empty"); - } - - if (encrypted64 === "" || encrypted64 === undefined || encrypted64 === null) { - throw new Error("Parameter encrypted64 can not be empty"); - } - - const symmetricKey = Array.from(Buffer.from(symmetricKey64, 'base64')); - const encrypted = Array.from(Buffer.from(encrypted64, 'base64')); + const symmetricKey = convertInputBase64(symmetricKey64, "symmetricKey64"); + const encrypted = convertInputBase64(encrypted64, "encrypted64"); return new Promise((resolve, reject) => { Themis.secureCellSealWithSymmetricKeyDecrypt(symmetricKey, encrypted, context, (decrypted: any) => { @@ -106,16 +117,14 @@ export function secureCellSealWithPassphraseEncrypt64( plaintext: String, context: String = ""): Promise { - if (passphrase === "" || passphrase === undefined || passphrase === null) { - throw new Error("Parameter passphrase can not be empty"); - } - if (plaintext === "" || plaintext === undefined || plaintext === null) { - throw new Error("Parameter plaintext can not be empty"); - } + checkInput(plaintext, "plaintext"); + checkInput(passphrase, "passphrase"); - return new Promise((resolve) => { + return new Promise((resolve, reject) => { Themis.secureCellSealWithPassphraseEncrypt(passphrase, plaintext, context, (encrypted: any) => { resolve(Buffer.from(new Uint8Array(encrypted)).toString("base64")) + }, (error: any) => { + reject(error) }); }); }; @@ -125,21 +134,14 @@ export function secureCellSealWithPassphraseDecrypt64( encrypted64: String, context: String = ""): Promise { - if (passphrase === "" || passphrase === undefined || passphrase === null) { - throw new Error("Parameter passphrase can not be empty"); - } - - if (encrypted64 === "" || encrypted64 === undefined || encrypted64 === null) { - throw new Error("Parameter encrypted64 can not be empty"); - } - - const encrypted = Array.from(Buffer.from(encrypted64, 'base64')); + checkInput(passphrase, "passphrase"); + const encrypted = convertInputBase64(encrypted64, "encrypted64"); return new Promise((resolve, reject) => { Themis.secureCellSealWithPassphraseDecrypt(passphrase, encrypted, context, (decrypted: any) => { - resolve(Buffer.from(new Uint8Array(decrypted)).toString()) + resolve(Buffer.from(new Uint8Array(decrypted)).toString()); }, (error: any) => { - reject(error) + reject(error); }) }); } @@ -149,14 +151,8 @@ export function secureCellTokenProtectEncrypt64( plaintext: String, context: String = ""): Promise { - if (symmetricKey64 === "" || symmetricKey64 === undefined || symmetricKey64 === null) { - throw new Error("Parameter symmetricKey64 can not be empty"); - } - if (plaintext === "" || plaintext === undefined || plaintext === null) { - throw new Error("Parameter plaintext can not be empty"); - } - - const symmetricKey = Array.from(Buffer.from(symmetricKey64, 'base64')); + checkInput(plaintext, "plaintext"); + const symmetricKey = convertInputBase64(symmetricKey64, "symmetricKey64"); return new Promise((resolve, reject) => { Themis.secureCellTokenProtectEncrypt(symmetricKey, plaintext, context, (encrypted: any) => { @@ -178,19 +174,9 @@ export function secureCellTokenProtectDecrypt64( token64: String, context: String = ""): Promise { - if (symmetricKey64 === "" || symmetricKey64 === undefined || symmetricKey64 === null) { - throw new Error("Parameter symmetricKey64 can not be empty"); - } - if (encrypted64 === "" || encrypted64 === undefined || encrypted64 === null) { - throw new Error("Parameter encrypted64 can not be empty"); - } - if (token64 === "" || token64 === undefined || token64 === null) { - throw new Error("Parameter token64 can not be empty"); - } - - const symmetricKey = Array.from(Buffer.from(symmetricKey64, 'base64')); - const encrypted = Array.from(Buffer.from(encrypted64, 'base64')); - const token = Array.from(Buffer.from(token64, 'base64')); + const symmetricKey = convertInputBase64(symmetricKey64, "symmetricKey64"); + const encrypted = convertInputBase64(encrypted64, "encrypted64"); + const token = convertInputBase64(token64, "token64"); return new Promise((resolve, reject) => { Themis.secureCellTokenProtectDecrypt(symmetricKey, encrypted, token, context, (decrypted: any) => { @@ -207,17 +193,10 @@ export function secureCellContextImprintEncrypt64( plaintext: String, context: String): Promise { - if (symmetricKey64 === "" || symmetricKey64 === undefined || symmetricKey64 === null) { - throw new Error("Parameter symmetricKey64 can not be empty"); - } - if (plaintext === "" || plaintext === undefined || plaintext === null) { - throw new Error("Parameter plaintext can not be empty"); - } - if (context === "" || context === undefined || context === null) { - throw new Error("Parameter context can not be empty"); - } + checkInput(plaintext, "plaintext"); + checkInput(context, "context"); + const symmetricKey = convertInputBase64(symmetricKey64, "symmetricKey64"); - const symmetricKey = Array.from(Buffer.from(symmetricKey64, 'base64')); return new Promise((resolve, reject) => { Themis.secureCellContextImprintEncrypt(symmetricKey, plaintext, context, (encrypted: any) => { resolve(Buffer.from(new Uint8Array(encrypted)).toString("base64")) @@ -232,18 +211,9 @@ export function secureCellContextImprintDecrypt64( encrypted64: String, context: String): Promise { - if (symmetricKey64 === "" || symmetricKey64 === undefined || symmetricKey64 === null) { - throw new Error("Parameter symmetricKey64 can not be empty"); - } - if (encrypted64 === "" || encrypted64 === undefined || encrypted64 === null) { - throw new Error("Parameter encrypted64 can not be empty"); - } - if (context === "" || context === undefined || context === null) { - throw new Error("Parameter context can not be empty"); - } - - const symmetricKey = Array.from(Buffer.from(symmetricKey64, 'base64')); - const encrypted = Array.from(Buffer.from(encrypted64, 'base64')); + checkInput(context, "context"); + const symmetricKey = convertInputBase64(symmetricKey64, "symmetricKey64"); + const encrypted = convertInputBase64(encrypted64, "encrypted64"); return new Promise((resolve, reject) => { Themis.secureCellContextImprintDecrypt(symmetricKey, encrypted, context, (decrypted: any) => { @@ -258,21 +228,13 @@ export function secureCellContextImprintDecrypt64( export function secureMessageSign64( plaintext: String, privateKey64: String, - publicKey64: String): Promise { + _publicKey64: String = ""): Promise { - if (plaintext === "" || plaintext === undefined || plaintext === null) { - throw new Error("Parameter plaintext can not be empty"); - } - if (privateKey64 === "" || privateKey64 === undefined || privateKey64 === null) { - throw new Error("Parameter privateKey64 can not be empty"); - } - - const privateKey = Array.from(Buffer.from(privateKey64, 'base64')); - const publicKey = publicKey64 !== null && publicKey64 !== "" ? - Array.from(Buffer.from(publicKey64, 'base64')) : null; + checkInput(plaintext, "plaintext"); + const privateKey = convertInputBase64(privateKey64, "privateKey64"); return new Promise((resolve, reject) => { - Themis.secureMessageSign(plaintext, privateKey, publicKey, (signed: any) => { + Themis.secureMessageSign(plaintext, privateKey, (signed: any) => { resolve(Buffer.from(new Uint8Array(signed)).toString("base64")) }, (error: any) => { reject(error) @@ -282,23 +244,14 @@ export function secureMessageSign64( export function secureMessageVerify64( signed64: String, - privateKey64: String, + _privateKey64: String = "", publicKey64: String): Promise { - if (signed64 === "" || signed64 === undefined || signed64 === null) { - throw new Error("Parameter signed64 can not be empty"); - } - if (publicKey64 === "" || publicKey64 === undefined || publicKey64 === null) { - throw new Error("Parameter publicKey64 can not be empty"); - } - - const signed = Array.from(Buffer.from(signed64, 'base64')); - const privateKey = privateKey64 !== null && privateKey64 !== "" ? - Array.from(Buffer.from(privateKey64, 'base64')) : null; - const publicKey = Array.from(Buffer.from(publicKey64, 'base64')); + const signed = convertInputBase64(signed64, "signed64"); + const publicKey = convertInputBase64(publicKey64, "publicKey64"); return new Promise((resolve, reject) => { - Themis.secureMessageVerify(signed, privateKey, publicKey, (verified: any) => { + Themis.secureMessageVerify(signed, publicKey, (verified: any) => { resolve(Buffer.from(new Uint8Array(verified)).toString()) }, (error: any) => { reject(error) @@ -312,19 +265,9 @@ export function secureMessageEncrypt64( privateKey64: String, publicKey64: String): Promise { - if (plaintext === "" || plaintext === undefined || plaintext === null) { - throw new Error("Parameter plaintext can not be empty"); - } - if (privateKey64 === "" || privateKey64 === undefined || privateKey64 === null) { - throw new Error("Parameter privateKey64 can not be empty"); - } - if (publicKey64 === "" || publicKey64 === undefined || publicKey64 === null) { - throw new Error("Parameter publicKey64 can not be empty"); - } - - - const privateKey = Array.from(Buffer.from(privateKey64, 'base64')); - const publicKey = Array.from(Buffer.from(publicKey64, 'base64')); + checkInput(plaintext, "plaintext"); + const privateKey = convertInputBase64(privateKey64, "privateKey64"); + const publicKey = convertInputBase64(publicKey64, "publicKey64"); return new Promise((resolve, reject) => { Themis.secureMessageEncrypt(plaintext, privateKey, publicKey, (encrypted: any) => { @@ -340,20 +283,9 @@ export function secureMessageDecrypt64( privateKey64: String, publicKey64: String): Promise { - if (encrypted64 === "" || encrypted64 === undefined || encrypted64 === null) { - throw new Error("Parameter encrypted64 can not be empty"); - } - if (privateKey64 === "" || privateKey64 === undefined || privateKey64 === null) { - throw new Error("Parameter privateKey64 can not be empty"); - } - if (publicKey64 === "" || publicKey64 === undefined || publicKey64 === null) { - throw new Error("Parameter publicKey64 can not be empty"); - } - - - const encrypted = Array.from(Buffer.from(encrypted64, 'base64')); - const privateKey = Array.from(Buffer.from(privateKey64, 'base64')); - const publicKey = Array.from(Buffer.from(publicKey64, 'base64')); + const encrypted = convertInputBase64(encrypted64, "encrypted64"); + const privateKey = convertInputBase64(privateKey64, "privateKey64"); + const publicKey = convertInputBase64(publicKey64, "publicKey64"); return new Promise((resolve, reject) => { Themis.secureMessageDecrypt(encrypted, privateKey, publicKey, (decrypted: any) => { @@ -364,18 +296,12 @@ export function secureMessageDecrypt64( }) } -export function string64(input: String): String { - return Buffer.from(input).toString('base64') -} /* Returns UUID in string value that corresponds to new comparator */ export function comparatorInit64(data64: String): Promise { - if (data64 === "" || data64 === undefined || data64 === null) { - throw new Error("Parameter data64 can not be empty"); - } + const data = convertInputBase64(data64, "data64"); - const data = Array.from(Buffer.from(data64, 'base64')) return new Promise((resolve, reject) => { Themis.initComparator(data, (comparator: string) => { resolve(comparator) @@ -386,10 +312,7 @@ export function comparatorInit64(data64: String): Promise { } export function comparatorBegin(uuidStr: String): Promise { - - if (uuidStr === "" || uuidStr === undefined || uuidStr === null) { - throw new Error("Parameter uuidStr can not be empty"); - } + checkInput(uuidStr, "uuidStr"); return new Promise((resolve, reject) => { Themis.beginCompare(uuidStr, (data: any) => { @@ -405,15 +328,9 @@ export function comparatorProceed64( uuidStr: String, data64: String): Promise { - if (uuidStr === "" || uuidStr === undefined || uuidStr === null) { - throw new Error("Parameter uuidStr can not be empty"); - } - if (data64 === "" || data64 === undefined || data64 === null) { - throw new Error("Parameter data64 can not be empty"); - } - + checkInput(uuidStr, "uuidStr"); + const data = convertInputBase64(data64, "data64"); - const data = Array.from(Buffer.from(data64, 'base64')) return new Promise((resolve, reject) => { Themis.proceedCompare(uuidStr, data, (nextData: any, status: Number) => { const nextdata64 = Buffer.from(new Uint8Array(nextData)).toString("base64")