diff --git a/.eslintrc.json b/.eslintrc.json
index 54b3bec..3faa9d8 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -23,7 +23,7 @@
"ignoreReadBeforeAssign": false
}
],
- "curly": ["error", "multi", "consistent"],
+ "curly": ["error", "multi-line", "consistent"],
"no-var": "error",
"prefer-template": 2,
"require-atomic-updates": "off",
diff --git a/README.md b/README.md
index bf570e2..cd6ea10 100644
--- a/README.md
+++ b/README.md
@@ -2,14 +2,15 @@

-React Native TCP socket API for Android & iOS. It allows you to create TCP clients and servers sockets, simulating node's [net](https://nodejs.org/api/net.html) API.
+React Native TCP socket API for Android & iOS with **client SSL/TLS support**. It allows you to create TCP clients and servers sockets, imitating some of node's [net](https://nodejs.org/api/net.html) API functionalities (check the available [API](#api) for more information).
## Table of Contents
- [Getting started](#getting-started)
+ - [Self-Signed SSL](#self-signed-ssl-only-available-for-react-native--060)
- [Compatibility](#react-native-compatibility)
- [Usage](#usage)
-- [API](#icon-component)
+- [API](#api)
- [Client](#client)
- [Server](#server)
- [Maintainers](#maintainers)
@@ -48,11 +49,23 @@ Linking the package manually is not required anymore with [Autolinking](https://
}
```
- Modify your **`android/app/src/main/AndroidManifest.xml`** and add the following:
- ```
-
- ```
-
+#### Self-Signed SSL (only available for React Native > 0.60)
+You will need a [metro.config.js](https://facebook.github.io/metro/docs/en/configuration.html) file in order to use a self-signed SSL certificate. You should already have this file in your root project directory, but if you don't, create it.
+Inside a `module.exports` object, create a key called `resolver` with another object called `assetExts`. The value of `assetExts` should be an array of the resource file extensions you want to support.
+
+If you want to support `.pem` files (plus all the already supported files), your `metro.config.js` would like like this:
+```javascript
+const {getDefaultConfig} = require('metro-config');
+const defaultConfig = getDefaultConfig.getDefaultValues(__dirname);
+
+module.exports = {
+ resolver: {
+ assetExts: [...defaultConfig.resolver.assetExts, 'pem'],
+ },
+ // ...
+};
+```
+
#### Using React Native < 0.60
@@ -105,7 +118,7 @@ import TcpSocket from 'react-native-tcp-socket';
### Client
```javascript
// Create socket
-var client = TcpSocket.createConnection(options);
+const client = TcpSocket.createConnection(options);
client.on('data', function(data) {
console.log('message was received', data);
@@ -125,9 +138,10 @@ client.write('Hello server!');
// Close socket
client.destroy();
```
+
### Server
```javascript
-var server = TcpSocket.createServer(function(socket) {
+const server = TcpSocket.createServer(function(socket) {
socket.on('data', (data) => {
socket.write('Echo server', data);
});
@@ -150,6 +164,20 @@ server.on('close', () => {
});
```
+### SSL Client
+```javascript
+const client = TcpSocket.createConnection({
+ port: 8443,
+ host: "example.com",
+ tls: true,
+ // tlsCheckValidity: false, // Disable validity checking
+ // tlsCert: require('./selfmade.pem') // Self-signed certificate
+});
+
+// ...
+```
+_Note: In order to use self-signed certificates make sure to [update your metro.config.js configuration](#self-signed-ssl-only-available-for-react-native--060)._
+
## API
### Client
* **Methods:**
@@ -170,6 +198,9 @@ server.on('close', () => {
| `localPort` | `` | ✅ | ✅ | Local port the socket should connect from. If not specified, the OS will decide. |
| `interface`| `` | ❌ | ✅ | Interface the socket should connect from. If not specified, it will use the current active connection. The options are: `'wifi', 'ethernet', 'cellular'`. |
| `reuseAddress`| `` | ❌ | ✅ | Enable/disable the reuseAddress socket option. **Default**: `true`. |
+| `tls`| `` | ✅ | ✅ | Enable/disable SSL/TLS socket creation. **Default**: `false`. |
+| `tlsCheckValidity`| `` | ✅ | ✅ | Enable/disable SSL/TLS certificate validity check. **Default**: `true`. |
+| `tlsCert`| `` | ✅ | ✅ | CA file (.pem format) to trust. If `null`, it will use the device's default SSL trusted list. Useful for self-signed certificates. _See [example](#ssl-client) for more info_. **Default**: `null`. |
**Note**: The platforms marked as ❌ use the default value.
@@ -201,7 +232,6 @@ server.on('close', () => {
**Note**: The platforms marked as ❌ use the default value.
## Maintainers
-Looking for maintainers!
* [Rapsssito](https://github.com/rapsssito)
diff --git a/android/src/main/java/com/asterinet/react/tcpsocket/SSLCertificateHelper.java b/android/src/main/java/com/asterinet/react/tcpsocket/SSLCertificateHelper.java
new file mode 100644
index 0000000..b938d75
--- /dev/null
+++ b/android/src/main/java/com/asterinet/react/tcpsocket/SSLCertificateHelper.java
@@ -0,0 +1,94 @@
+package com.asterinet.react.tcpsocket;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RawRes;
+
+final class SSLCertificateHelper {
+ /**
+ * Creates an SSLSocketFactory instance for use with all CAs provided.
+ *
+ * @return An SSLSocketFactory which trusts all CAs when provided to network clients
+ */
+ static SSLSocketFactory createBlindSocketFactory() throws GeneralSecurityException {
+ SSLContext ctx = SSLContext.getInstance("TLS");
+ ctx.init(null, new TrustManager[]{new BlindTrustManager()}, null);
+ return ctx.getSocketFactory();
+ }
+
+ /**
+ * Creates an SSLSocketFactory instance for use with the CA provided in the resource file.
+ *
+ * @param context Context used to open up the CA file
+ * @param rawResourceUri Raw resource file to the CA (in .crt or .cer format, for instance)
+ * @return An SSLSocketFactory which trusts the provided CA when provided to network clients
+ */
+ static SSLSocketFactory createCustomTrustedSocketFactory(@NonNull final Context context, @NonNull final String rawResourceUri) throws IOException, GeneralSecurityException {
+ InputStream caInput = getRawResourceStream(context, rawResourceUri);
+ // Generate the CA Certificate from the raw resource file
+ Certificate ca = CertificateFactory.getInstance("X.509").generateCertificate(caInput);
+ caInput.close();
+ // Load the key store using the CA
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ keyStore.load(null, null);
+ keyStore.setCertificateEntry("ca", ca);
+
+ // Initialize the TrustManager with this CA
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(keyStore);
+
+ // Create an SSL context that uses the created trust manager
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
+ return sslContext.getSocketFactory();
+ }
+
+ private static InputStream getRawResourceStream(@NonNull final Context context, @NonNull final String resourceUri) throws IOException {
+ final int resId = getResourceId(context, resourceUri);
+ if (resId == 0)
+ return URI.create(resourceUri).toURL().openStream(); // From metro on development
+ else return context.getResources().openRawResource(resId); // From bundle in production
+ }
+
+ @RawRes
+ private static int getResourceId(@NonNull final Context context, @NonNull final String resourceUri) {
+ String name = resourceUri.toLowerCase().replace("-", "_");
+ try {
+ return Integer.parseInt(name);
+ } catch (NumberFormatException ex) {
+ return context.getResources().getIdentifier(name, "raw", context.getPackageName());
+ }
+ }
+
+ private static class BlindTrustManager implements X509TrustManager {
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+
+ @SuppressLint("TrustAllX509TrustManager")
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ @SuppressLint("TrustAllX509TrustManager")
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ }
+ }
+}
diff --git a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketClient.java b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketClient.java
index 4db6067..ce259e4 100644
--- a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketClient.java
+++ b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketClient.java
@@ -1,5 +1,6 @@
package com.asterinet.react.tcpsocket;
+import android.content.Context;
import android.net.Network;
import android.os.AsyncTask;
import android.util.Pair;
@@ -11,17 +12,21 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
+import java.security.GeneralSecurityException;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
class TcpSocketClient {
+ private final int id;
private TcpReceiverTask receiverTask;
private Socket socket;
private TcpReceiverTask.OnDataReceivedListener mReceiverListener;
- private final int id;
-
TcpSocketClient(final int id) {
this.id = id;
}
@@ -42,9 +47,23 @@ public Socket getSocket() {
return socket;
}
- public void connect(@NonNull final String address, @NonNull final Integer port, @NonNull final ReadableMap options, @Nullable final Network network) throws IOException {
+ public void connect(@NonNull final Context context, @NonNull final String address, @NonNull final Integer port, @NonNull final ReadableMap options, @Nullable final Network network) throws IOException, GeneralSecurityException {
if (socket != null) throw new IOException("Already connected");
- socket = new Socket();
+ final boolean isTls = options.hasKey("tls") && options.getBoolean("tls");
+ if (isTls) {
+ SocketFactory sf;
+ if (options.hasKey("tlsCheckValidity") && !options.getBoolean("tlsCheckValidity")){
+ sf = SSLCertificateHelper.createBlindSocketFactory();
+ } else {
+ final String customTlsCert = options.hasKey("tlsCert") ? options.getString("tlsCert") : null;
+ sf = customTlsCert != null ? SSLCertificateHelper.createCustomTrustedSocketFactory(context, customTlsCert) : SSLSocketFactory.getDefault();
+ }
+ final SSLSocket sslSocket = (SSLSocket) sf.createSocket();
+ sslSocket.setUseClientMode(true);
+ socket = sslSocket;
+ } else {
+ socket = new Socket();
+ }
// Get the addresses
final String localAddress = options.hasKey("localAddress") ? options.getString("localAddress") : "0.0.0.0";
final InetAddress localInetAddress = InetAddress.getByName(localAddress);
@@ -63,9 +82,11 @@ public void connect(@NonNull final String address, @NonNull final Integer port,
// bind
socket.bind(new InetSocketAddress(localInetAddress, localPort));
socket.connect(new InetSocketAddress(remoteInetAddress, port));
+ if (isTls) ((SSLSocket) socket).startHandshake();
startListening();
}
+ @SuppressWarnings("WeakerAccess")
public void startListening() {
//noinspection unchecked
receiverTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Pair<>(this, mReceiverListener));
@@ -77,10 +98,11 @@ public void startListening() {
* @param data data to be sent
*/
public void write(final byte[] data) throws IOException {
- if (socket != null && !socket.isClosed()) {
- OutputStream output = socket.getOutputStream();
- output.write(data);
+ if (socket == null) {
+ throw new IOException("Socket is not connected.");
}
+ OutputStream output = socket.getOutputStream();
+ output.write(data);
}
/**
diff --git a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java
index bce0750..abbccca 100644
--- a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java
+++ b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java
@@ -83,7 +83,7 @@ protected void doInBackgroundGuarded(Void... params) {
selectNetwork(iface, localAddress);
client = new TcpSocketClient(TcpSocketModule.this, cId, null);
socketClients.put(cId, client);
- client.connect(host, port, options, currentNetwork.getNetwork());
+ client.connect(mReactContext, host, port, options, currentNetwork.getNetwork());
onConnect(cId, host, port);
} catch (Exception e) {
onError(cId, e.getMessage());
@@ -288,7 +288,7 @@ public void onConnection(Integer serverId, Integer clientId, InetSocketAddress s
sendEvent("connection", eventParams);
}
- private class CurrentNetwork {
+ private static class CurrentNetwork {
@Nullable
Network network = null;
diff --git a/examples/tcpsockets/.gitignore b/examples/tcpsockets/.gitignore
index 57331d6..ea0571e 100644
--- a/examples/tcpsockets/.gitignore
+++ b/examples/tcpsockets/.gitignore
@@ -59,3 +59,6 @@ buck-out/
# CocoaPods
/ios/Pods/
+
+# TLS Cert
+*.pem
\ No newline at end of file
diff --git a/examples/tcpsockets/android/app/src/main/java/com/tcpsockets/ReactNativeFlipper.java b/examples/tcpsockets/android/app/src/debug/java/com/tcpsockets/ReactNativeFlipper.java
similarity index 100%
rename from examples/tcpsockets/android/app/src/main/java/com/tcpsockets/ReactNativeFlipper.java
rename to examples/tcpsockets/android/app/src/debug/java/com/tcpsockets/ReactNativeFlipper.java
diff --git a/examples/tcpsockets/android/app/src/main/AndroidManifest.xml b/examples/tcpsockets/android/app/src/main/AndroidManifest.xml
index 21fa74a..826a0a2 100644
--- a/examples/tcpsockets/android/app/src/main/AndroidManifest.xml
+++ b/examples/tcpsockets/android/app/src/main/AndroidManifest.xml
@@ -2,7 +2,6 @@
package="com.tcpsockets">
-
({
transform: {
diff --git a/examples/tcpsockets/package.json b/examples/tcpsockets/package.json
index 416fb7f..a4d7b29 100644
--- a/examples/tcpsockets/package.json
+++ b/examples/tcpsockets/package.json
@@ -14,7 +14,7 @@
"dependencies": {
"react": "16.11.0",
"react-native": "0.62.1",
- "react-native-tcp-socket": "^3.4.1"
+ "react-native-tcp-socket": "https://github.com/Rapsssito/react-native-tcp-socket#tls"
},
"devDependencies": {
"@babel/core": "^7.7.2",
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index bb7c0f4..fc16322 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -2,14 +2,14 @@ PODS:
- boost-for-react-native (1.63.0)
- CocoaAsyncSocket (7.6.3)
- DoubleConversion (1.1.6)
- - FBLazyVector (0.61.4)
- - FBReactNativeSpec (0.61.4):
+ - FBLazyVector (0.61.5)
+ - FBReactNativeSpec (0.61.5):
- Folly (= 2018.10.22.00)
- - RCTRequired (= 0.61.4)
- - RCTTypeSafety (= 0.61.4)
- - React-Core (= 0.61.4)
- - React-jsi (= 0.61.4)
- - ReactCommon/turbomodule/core (= 0.61.4)
+ - RCTRequired (= 0.61.5)
+ - RCTTypeSafety (= 0.61.5)
+ - React-Core (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - ReactCommon/turbomodule/core (= 0.61.5)
- Folly (2018.10.22.00):
- boost-for-react-native
- DoubleConversion
@@ -20,204 +20,204 @@ PODS:
- DoubleConversion
- glog
- glog (0.3.5)
- - RCTRequired (0.61.4)
- - RCTTypeSafety (0.61.4):
- - FBLazyVector (= 0.61.4)
+ - RCTRequired (0.61.5)
+ - RCTTypeSafety (0.61.5):
+ - FBLazyVector (= 0.61.5)
- Folly (= 2018.10.22.00)
- - RCTRequired (= 0.61.4)
- - React-Core (= 0.61.4)
- - React (0.61.4):
- - React-Core (= 0.61.4)
- - React-Core/DevSupport (= 0.61.4)
- - React-Core/RCTWebSocket (= 0.61.4)
- - React-RCTActionSheet (= 0.61.4)
- - React-RCTAnimation (= 0.61.4)
- - React-RCTBlob (= 0.61.4)
- - React-RCTImage (= 0.61.4)
- - React-RCTLinking (= 0.61.4)
- - React-RCTNetwork (= 0.61.4)
- - React-RCTSettings (= 0.61.4)
- - React-RCTText (= 0.61.4)
- - React-RCTVibration (= 0.61.4)
- - React-Core (0.61.4):
+ - RCTRequired (= 0.61.5)
+ - React-Core (= 0.61.5)
+ - React (0.61.5):
+ - React-Core (= 0.61.5)
+ - React-Core/DevSupport (= 0.61.5)
+ - React-Core/RCTWebSocket (= 0.61.5)
+ - React-RCTActionSheet (= 0.61.5)
+ - React-RCTAnimation (= 0.61.5)
+ - React-RCTBlob (= 0.61.5)
+ - React-RCTImage (= 0.61.5)
+ - React-RCTLinking (= 0.61.5)
+ - React-RCTNetwork (= 0.61.5)
+ - React-RCTSettings (= 0.61.5)
+ - React-RCTText (= 0.61.5)
+ - React-RCTVibration (= 0.61.5)
+ - React-Core (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- - React-Core/Default (= 0.61.4)
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-Core/Default (= 0.61.5)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-Core/CoreModulesHeaders (0.61.4):
+ - React-Core/CoreModulesHeaders (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-Core/Default (0.61.4):
+ - React-Core/Default (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-Core/DevSupport (0.61.4):
+ - React-Core/DevSupport (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- - React-Core/Default (= 0.61.4)
- - React-Core/RCTWebSocket (= 0.61.4)
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
- - React-jsinspector (= 0.61.4)
+ - React-Core/Default (= 0.61.5)
+ - React-Core/RCTWebSocket (= 0.61.5)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
+ - React-jsinspector (= 0.61.5)
- Yoga
- - React-Core/RCTActionSheetHeaders (0.61.4):
+ - React-Core/RCTActionSheetHeaders (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-Core/RCTAnimationHeaders (0.61.4):
+ - React-Core/RCTAnimationHeaders (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-Core/RCTBlobHeaders (0.61.4):
+ - React-Core/RCTBlobHeaders (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-Core/RCTImageHeaders (0.61.4):
+ - React-Core/RCTImageHeaders (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-Core/RCTLinkingHeaders (0.61.4):
+ - React-Core/RCTLinkingHeaders (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-Core/RCTNetworkHeaders (0.61.4):
+ - React-Core/RCTNetworkHeaders (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-Core/RCTSettingsHeaders (0.61.4):
+ - React-Core/RCTSettingsHeaders (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-Core/RCTTextHeaders (0.61.4):
+ - React-Core/RCTTextHeaders (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-Core/RCTVibrationHeaders (0.61.4):
+ - React-Core/RCTVibrationHeaders (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-Core/RCTWebSocket (0.61.4):
+ - React-Core/RCTWebSocket (0.61.5):
- Folly (= 2018.10.22.00)
- glog
- - React-Core/Default (= 0.61.4)
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsiexecutor (= 0.61.4)
+ - React-Core/Default (= 0.61.5)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsiexecutor (= 0.61.5)
- Yoga
- - React-CoreModules (0.61.4):
- - FBReactNativeSpec (= 0.61.4)
+ - React-CoreModules (0.61.5):
+ - FBReactNativeSpec (= 0.61.5)
- Folly (= 2018.10.22.00)
- - RCTTypeSafety (= 0.61.4)
- - React-Core/CoreModulesHeaders (= 0.61.4)
- - React-RCTImage (= 0.61.4)
- - ReactCommon/turbomodule/core (= 0.61.4)
- - React-cxxreact (0.61.4):
+ - RCTTypeSafety (= 0.61.5)
+ - React-Core/CoreModulesHeaders (= 0.61.5)
+ - React-RCTImage (= 0.61.5)
+ - ReactCommon/turbomodule/core (= 0.61.5)
+ - React-cxxreact (0.61.5):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- - React-jsinspector (= 0.61.4)
- - React-jsi (0.61.4):
+ - React-jsinspector (= 0.61.5)
+ - React-jsi (0.61.5):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- - React-jsi/Default (= 0.61.4)
- - React-jsi/Default (0.61.4):
+ - React-jsi/Default (= 0.61.5)
+ - React-jsi/Default (0.61.5):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- - React-jsiexecutor (0.61.4):
+ - React-jsiexecutor (0.61.5):
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-jsinspector (0.61.4)
- - React-RCTActionSheet (0.61.4):
- - React-Core/RCTActionSheetHeaders (= 0.61.4)
- - React-RCTAnimation (0.61.4):
- - React-Core/RCTAnimationHeaders (= 0.61.4)
- - React-RCTBlob (0.61.4):
- - React-Core/RCTBlobHeaders (= 0.61.4)
- - React-Core/RCTWebSocket (= 0.61.4)
- - React-jsi (= 0.61.4)
- - React-RCTNetwork (= 0.61.4)
- - React-RCTImage (0.61.4):
- - React-Core/RCTImageHeaders (= 0.61.4)
- - React-RCTNetwork (= 0.61.4)
- - React-RCTLinking (0.61.4):
- - React-Core/RCTLinkingHeaders (= 0.61.4)
- - React-RCTNetwork (0.61.4):
- - React-Core/RCTNetworkHeaders (= 0.61.4)
- - React-RCTSettings (0.61.4):
- - React-Core/RCTSettingsHeaders (= 0.61.4)
- - React-RCTText (0.61.4):
- - React-Core/RCTTextHeaders (= 0.61.4)
- - React-RCTVibration (0.61.4):
- - React-Core/RCTVibrationHeaders (= 0.61.4)
- - ReactCommon/jscallinvoker (0.61.4):
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-jsinspector (0.61.5)
+ - React-RCTActionSheet (0.61.5):
+ - React-Core/RCTActionSheetHeaders (= 0.61.5)
+ - React-RCTAnimation (0.61.5):
+ - React-Core/RCTAnimationHeaders (= 0.61.5)
+ - React-RCTBlob (0.61.5):
+ - React-Core/RCTBlobHeaders (= 0.61.5)
+ - React-Core/RCTWebSocket (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - React-RCTNetwork (= 0.61.5)
+ - React-RCTImage (0.61.5):
+ - React-Core/RCTImageHeaders (= 0.61.5)
+ - React-RCTNetwork (= 0.61.5)
+ - React-RCTLinking (0.61.5):
+ - React-Core/RCTLinkingHeaders (= 0.61.5)
+ - React-RCTNetwork (0.61.5):
+ - React-Core/RCTNetworkHeaders (= 0.61.5)
+ - React-RCTSettings (0.61.5):
+ - React-Core/RCTSettingsHeaders (= 0.61.5)
+ - React-RCTText (0.61.5):
+ - React-Core/RCTTextHeaders (= 0.61.5)
+ - React-RCTVibration (0.61.5):
+ - React-Core/RCTVibrationHeaders (= 0.61.5)
+ - ReactCommon/jscallinvoker (0.61.5):
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- - React-cxxreact (= 0.61.4)
- - ReactCommon/turbomodule/core (0.61.4):
+ - React-cxxreact (= 0.61.5)
+ - ReactCommon/turbomodule/core (0.61.5):
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- - React-Core (= 0.61.4)
- - React-cxxreact (= 0.61.4)
- - React-jsi (= 0.61.4)
- - ReactCommon/jscallinvoker (= 0.61.4)
+ - React-Core (= 0.61.5)
+ - React-cxxreact (= 0.61.5)
+ - React-jsi (= 0.61.5)
+ - ReactCommon/jscallinvoker (= 0.61.5)
- Yoga (1.14.0)
DEPENDENCIES:
@@ -312,30 +312,30 @@ SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
CocoaAsyncSocket: eafaa68a7e0ec99ead0a7b35015e0bf25d2c8987
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
- FBLazyVector: feb35a6b7f7b50f367be07f34012f34a79282fa3
- FBReactNativeSpec: 51477b84b1bf7ab6f9ef307c24e3dd675391be44
+ FBLazyVector: aaeaf388755e4f29cd74acbc9e3b8da6d807c37f
+ FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
- RCTRequired: f3b3fb6f4723e8e52facb229d0c75fdc76773849
- RCTTypeSafety: 2ec60de6abb1db050b56ecc4b60188026078fd10
- React: 10e0130b57e55a7cd8c3dee37c1261102ce295f4
- React-Core: 636212410772d05f3a1eb79d965df2962ca1c70b
- React-CoreModules: 6f70d5e41919289c582f88c9ad9923fe5c87400a
- React-cxxreact: ddecbe9157ec1743f52ea17bf8d95debc0d6e846
- React-jsi: ca921f4041505f9d5197139b2d09eeb020bb12e8
- React-jsiexecutor: 8dfb73b987afa9324e4009bdce62a18ce23d983c
- React-jsinspector: d15478d0a8ada19864aa4d1cc1c697b41b3fa92f
- React-RCTActionSheet: 7369b7c85f99b6299491333affd9f01f5a130c22
- React-RCTAnimation: d07be15b2bd1d06d89417eb0343f98ffd2b099a7
- React-RCTBlob: 8e0b23d95c9baa98f6b0e127e07666aaafd96c34
- React-RCTImage: 443050d14a66e8c2332e9c055f45689d23e15cc7
- React-RCTLinking: ce9a90ba155aec41be49e75ec721bbae2d48a47e
- React-RCTNetwork: 41fe54bacc67dd00e6e4c4d30dd98a13e4beabc8
- React-RCTSettings: 45e3e0a6470310b2dab2ccc6d1d73121ba3ea936
- React-RCTText: 21934e0a51d522abcd0a275407e80af45d6fd9ec
- React-RCTVibration: 0f76400ee3cec6edb9c125da49fed279340d145a
- ReactCommon: a6a294e7028ed67b926d29551aa9394fd989c24c
- Yoga: ba3d99dbee6c15ea6bbe3783d1f0cb1ffb79af0f
+ RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
+ RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320
+ React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78
+ React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04
+ React-CoreModules: d04f8494c1a328b69ec11db9d1137d667f916dcb
+ React-cxxreact: d0f7bcafa196ae410e5300736b424455e7fb7ba7
+ React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7
+ React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386
+ React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0
+ React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76
+ React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360
+ React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72
+ React-RCTImage: 6b8e8df449eb7c814c99a92d6b52de6fe39dea4e
+ React-RCTLinking: 121bb231c7503cf9094f4d8461b96a130fabf4a5
+ React-RCTNetwork: fb353640aafcee84ca8b78957297bd395f065c9a
+ React-RCTSettings: 8db258ea2a5efee381fcf7a6d5044e2f8b68b640
+ React-RCTText: 9ccc88273e9a3aacff5094d2175a605efa854dbe
+ React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad
+ ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
+ Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b
PODFILE CHECKSUM: c17fa9414d86be6752251d37471f1e2e374a215b
diff --git a/ios/TcpSocketClient.h b/ios/TcpSocketClient.h
index 1f760c0..5706f0b 100644
--- a/ios/TcpSocketClient.h
+++ b/ios/TcpSocketClient.h
@@ -1,8 +1,3 @@
-/**
- * Copyright (c) 2015-present, Peel Technologies, Inc.
- * All rights reserved.
- */
-
#import
#import "CocoaAsyncSocket/GCDAsyncSocket.h"
diff --git a/ios/TcpSocketClient.m b/ios/TcpSocketClient.m
index 9f6d1c6..4df8096 100644
--- a/ios/TcpSocketClient.m
+++ b/ios/TcpSocketClient.m
@@ -1,8 +1,3 @@
-/**
- * Copyright (c) 2015-present, Peel Technologies, Inc.
- * All rights reserved.
- */
-
#import
#import
#import "TcpSocketClient.h"
@@ -14,6 +9,9 @@
@interface TcpSocketClient()
{
@private
+ BOOL _tls;
+ BOOL _checkValidity;
+ NSString *_certPath;
GCDAsyncSocket *_tcpSocket;
NSMutableDictionary *_pendingSends;
NSLock *_lock;
@@ -67,8 +65,8 @@ - (BOOL)connect:(NSString *)host port:(int)port withOptions:(NSDictionary *)opti
BOOL result = false;
- NSString *localAddress = (options?options[@"localAddress"]:nil);
- NSNumber *localPort = (options?options[@"localPort"]:nil);
+ NSString *localAddress = options[@"localAddress"];
+ NSNumber *localPort = options[@"localPort"];
if (!localAddress && !localPort) {
result = [_tcpSocket connectToHost:host onPort:port error:error];
@@ -84,7 +82,25 @@ - (BOOL)connect:(NSString *)host port:(int)port withOptions:(NSDictionary *)opti
withTimeout:-1
error:error];
}
-
+ _tls = (options[@"tls"]?[options[@"tls"] boolValue]:false);
+ if (result && _tls){
+ NSMutableDictionary *settings = [NSMutableDictionary dictionary];
+ NSString *certResourcePath = options[@"tlsCert"];
+ BOOL checkValidity = (options[@"tlsCheckValidity"]?[options[@"tlsCheckValidity"] boolValue]:true);
+ if (!checkValidity) {
+ // Do not validate
+ _checkValidity = false;
+ [settings setObject:[NSNumber numberWithBool:YES] forKey:GCDAsyncSocketManuallyEvaluateTrust];
+ } else if (certResourcePath != nil) {
+ // Self-signed certificate
+ _certPath = certResourcePath;
+ [settings setObject:[NSNumber numberWithBool:YES] forKey:GCDAsyncSocketManuallyEvaluateTrust];
+ } else {
+ // Default certificates
+ [settings setObject:host forKey:(NSString *) kCFStreamSSLPeerName];
+ }
+ [_tcpSocket startTLS:settings];
+ }
return result;
}
@@ -225,15 +241,84 @@ - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSo
[newSocket readDataWithTimeout:-1 tag:inComing.id.longValue];
}
-- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
+- (void)socketDidSecure:(GCDAsyncSocket *)sock
{
+ // Only for TLS
if (!_clientDelegate) {
- RCTLogWarn(@"didConnectToHost with nil clientDelegate for %@", [sock userData]);
+ RCTLogWarn(@"socketDidSecure with nil clientDelegate for %@", [sock userData]);
return;
}
[_clientDelegate onConnect:self];
+}
+
+- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler {
+ // Check if we should check the validity
+ if (!_checkValidity) {
+ completionHandler(YES);
+ return;
+ }
+
+ // Server certificate
+ SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(trust, 0);
+ CFDataRef serverCertificateData = SecCertificateCopyData(serverCertificate);
+ const UInt8* const serverData = CFDataGetBytePtr(serverCertificateData);
+ const CFIndex serverDataSize = CFDataGetLength(serverCertificateData);
+ NSData* cert1 = [NSData dataWithBytes:serverData length:(NSUInteger)serverDataSize];
+
+ // Local certificate
+ NSURL *certUrl = [[NSURL alloc] initWithString:_certPath];
+ NSString *pem = [[NSString alloc] initWithContentsOfURL:certUrl encoding:NSUTF8StringEncoding error:NULL];
+
+ // Strip PEM header and footers. We don't support multi-certificate PEM.
+ NSMutableString *pemMutable = [pem stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet].mutableCopy;
+
+ // Strip PEM header and footer
+ [pemMutable replaceOccurrencesOfString:@"-----BEGIN CERTIFICATE-----"
+ withString:@""
+ options:(NSStringCompareOptions)(NSAnchoredSearch | NSLiteralSearch)
+ range:NSMakeRange(0, pemMutable.length)];
+
+ [pemMutable replaceOccurrencesOfString:@"-----END CERTIFICATE-----"
+ withString:@""
+ options:(NSStringCompareOptions)(NSAnchoredSearch | NSBackwardsSearch | NSLiteralSearch)
+ range:NSMakeRange(0, pemMutable.length)];
+
+ NSData *pemData = [[NSData alloc] initWithBase64EncodedString:pemMutable options:NSDataBase64DecodingIgnoreUnknownCharacters];
+ SecCertificateRef localCertificate = SecCertificateCreateWithData(NULL, (CFDataRef)pemData);
+ if (!localCertificate)
+ {
+ [NSException raise:@"Configuration invalid" format:@"Failed to parse PEM certificate"];
+ }
+
+ CFDataRef myCertData = SecCertificateCopyData(localCertificate);
+ const UInt8* const localData = CFDataGetBytePtr(myCertData);
+ const CFIndex localDataSize = CFDataGetLength(myCertData);
+ NSData* cert2 = [NSData dataWithBytes:localData length:(NSUInteger)localDataSize];
+
+ if (cert1 == nil || cert2 == nil) {
+ RCTLogWarn(@"BAD SSL CERTIFICATE");
+ completionHandler(NO);
+ return;
+ }
+ if ([cert1 isEqualToData:cert2]) {
+ completionHandler(YES);
+ }else {
+ completionHandler(NO);
+ }
+}
+- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
+{
+ if (!_clientDelegate) {
+ RCTLogWarn(@"didConnectToHost with nil clientDelegate for %@", [sock userData]);
+ return;
+ }
+
+ // Show up if SSL handsake is done
+ if (!_tls) {
+ [_clientDelegate onConnect:self];
+ }
[sock readDataWithTimeout:-1 tag:_id.longValue];
}
diff --git a/ios/TcpSockets.h b/ios/TcpSockets.h
index 47ba018..cf63914 100644
--- a/ios/TcpSockets.h
+++ b/ios/TcpSockets.h
@@ -1,8 +1,3 @@
-/**
- * Copyright (c) 2015-present, Peel Technologies, Inc.
- * All rights reserved.
- */
-
#import "TcpSocketClient.h"
#import
diff --git a/ios/TcpSockets.m b/ios/TcpSockets.m
index 1135f51..81473cb 100644
--- a/ios/TcpSockets.m
+++ b/ios/TcpSockets.m
@@ -1,8 +1,3 @@
-/**
- * Copyright (c) 2015-present, Peel Technologies, Inc.
- * All rights reserved.
- */
-
#import
#import
#import
diff --git a/package.json b/package.json
index 4fddf6b..5a0dabd 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "react-native-tcp-socket",
"title": "React Native Tcp Socket",
"version": "3.4.2",
- "description": "React Native TCP socket API for Android & iOS",
+ "description": "React Native TCP socket API for Android & iOS with SSL/TLS support",
"main": "src/index.js",
"types": "lib/types/index.d.ts",
"scripts": {
@@ -34,6 +34,8 @@
"iOS",
"Android",
"tcp-socket",
+ "tls",
+ "ssl",
"tcp-server",
"tcp-client",
"tcp",
diff --git a/src/TcpSocket.js b/src/TcpSocket.js
index 1c585de..a08f818 100644
--- a/src/TcpSocket.js
+++ b/src/TcpSocket.js
@@ -1,6 +1,6 @@
'use strict';
-import { NativeModules } from 'react-native';
+import { NativeModules, Image } from 'react-native';
const Buffer = (global.Buffer = global.Buffer || require('buffer').Buffer);
const Sockets = NativeModules.TcpSockets;
@@ -32,7 +32,18 @@ class RemovableListener {
}
/**
- * @typedef {{ port: number; host?: string; timeout?: number; localAddress?: string, localPort?: number, interface?: 'wifi' | 'cellular' | 'ethernet', reuseAddress?: boolean}} ConnectionOptions
+ * @typedef {{
+ * port: number;
+ * host?: string;
+ * timeout?: number;
+ * localAddress?: string,
+ * localPort?: number,
+ * interface?: 'wifi' | 'cellular' | 'ethernet',
+ * reuseAddress?: boolean,
+ * tls?: boolean,
+ * tlsCheckValidity?: boolean,
+ * tlsCert?: any,
+ * }} ConnectionOptions
*/
export default class TcpSocket {
/**
@@ -121,18 +132,25 @@ export default class TcpSocket {
*/
connect(options, callback) {
this._registerEvents();
+ const customOptions = { ...options };
// Normalize args
- options.host = options.host || 'localhost';
- options.port = Number(options.port) || 0;
+ customOptions.host = customOptions.host || 'localhost';
+ customOptions.port = Number(customOptions.port) || 0;
const connectListener = this.on('connect', (ev) => {
connectListener.remove();
if (callback) callback(ev.address);
});
- if (options.timeout) this.setTimeout(options.timeout);
+ // Timeout
+ if (customOptions.timeout) this.setTimeout(customOptions.timeout);
else if (this._timeout) this._activeTimer(this._timeout.msecs);
+ // TLS Cert
+ if (customOptions.tlsCert) {
+ customOptions.tlsCert = Image.resolveAssetSource(customOptions.tlsCert).uri;
+ }
+ // console.log(getAndroidResourceIdentifier(customOptions.tlsCert));
this._state = STATE.CONNECTING;
this._destroyed = false;
- Sockets.connect(this._id, options.host, options.port, options);
+ Sockets.connect(this._id, customOptions.host, customOptions.port, customOptions);
return this;
}
@@ -293,13 +311,17 @@ export default class TcpSocket {
* @param {BufferEncoding} [encoding]
*/
_generateSendBuffer(buffer, encoding) {
- if (typeof buffer === 'string') return Buffer.from(buffer, encoding);
- else if (Buffer.isBuffer(buffer)) return buffer;
- else if (buffer instanceof Uint8Array || Array.isArray(buffer)) return Buffer.from(buffer);
- else
+ if (typeof buffer === 'string') {
+ return Buffer.from(buffer, encoding);
+ } else if (Buffer.isBuffer(buffer)) {
+ return buffer;
+ } else if (buffer instanceof Uint8Array || Array.isArray(buffer)) {
+ return Buffer.from(buffer);
+ } else {
throw new TypeError(
`Invalid data, chunk must be a string or buffer, not ${typeof buffer}`
);
+ }
}
/**