Skip to content

Commit 6d2fcae

Browse files
authored
feat(iOS): Add more NodeJS TLS options (#208)
1 parent 4284f91 commit 6d2fcae

File tree

12 files changed

+734
-81
lines changed

12 files changed

+734
-81
lines changed

android/src/main/java/com/asterinet/react/tcpsocket/SSLCertificateHelper.java

+49-21
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,27 @@ static SSLServerSocketFactory createServerSocketFactory(Context context, @NonNul
8080
return sslContext.getServerSocketFactory();
8181
}
8282

83+
static boolean hasIdentity(ReadableMap options) {
84+
try {
85+
final String keystoreName = options.hasKey("androidKeyStore") ?
86+
options.getString("androidKeyStore") : KeyStore.getDefaultType();
87+
final String keyAlias = options.hasKey("keyAlias") ?
88+
options.getString("keyAlias") : "";
89+
90+
if (keyAlias.isEmpty()) {
91+
return false;
92+
}
93+
94+
KeyStore keyStore = KeyStore.getInstance(keystoreName);
95+
keyStore.load(null, null);
96+
97+
// Check if key entry exists with its certificate chain
98+
return keyStore.isKeyEntry(keyAlias);
99+
} catch (Exception e) {
100+
return false;
101+
}
102+
}
103+
83104
public static PrivateKey getPrivateKeyFromPEM(InputStream keyStream) {
84105
try (PemReader pemReader = new PemReader(new InputStreamReader(keyStream))) {
85106
PemObject pemObject = pemReader.readPemObject();
@@ -127,37 +148,45 @@ static SSLSocketFactory createCustomTrustedSocketFactory(
127148
final KeystoreInfo keystoreInfo) throws IOException, GeneralSecurityException {
128149

129150
SSLSocketFactory ssf = null;
130-
if (optionResCert != null && optionResKey != null) {
131-
final String keyStoreName = keystoreInfo.getKeystoreName().isEmpty() ?
151+
152+
KeyStore keyStore = null;
153+
final String keyStoreName = keystoreInfo.getKeystoreName().isEmpty() ?
132154
KeyStore.getDefaultType() :
133155
keystoreInfo.getKeystoreName();
134-
KeyStore keyStore = KeyStore.getInstance(keyStoreName);
135-
keyStore.load(null, null);
156+
String keyAlias = keystoreInfo.getKeyAlias();
136157

137-
// Check if cert and key if already registered inside our keystore
138-
// If one is missing we insert again
139-
boolean hasCertInStore = keyStore.isCertificateEntry(keystoreInfo.getCertAlias());
140-
boolean hasKeyInStore = keyStore.isKeyEntry(keystoreInfo.getKeyAlias());
141-
if (!hasCertInStore || !hasKeyInStore) {
142-
InputStream certInput = getResolvableinputStream(context, optionResCert);
143-
Certificate cert = CertificateFactory.getInstance("X.509").generateCertificate(certInput);
144-
keyStore.setCertificateEntry(keystoreInfo.getCertAlias(), cert);
145-
146-
InputStream keyInput = getResolvableinputStream(context, optionResKey);
147-
PrivateKey privateKey = getPrivateKeyFromPEM(keyInput);
148-
keyStore.setKeyEntry(keystoreInfo.getKeyAlias(), privateKey, null, new Certificate[]{cert});
158+
// if user provides keyAlias without key it means an identity(cert+key) has already been
159+
// inserted in keychain.
160+
if (keyAlias != null && !keyAlias.isEmpty() && optionResKey == null) {
161+
keyStore = KeyStore.getInstance(keyStoreName);
162+
keyStore.load(null, null);
163+
if (!keyStore.isKeyEntry(keyAlias)) {
164+
keyStore = null;
149165
}
166+
} else if (optionResCert != null && optionResKey != null) {
167+
168+
keyStore = KeyStore.getInstance(keyStoreName);
169+
keyStore.load(null, null);
150170

151-
boolean hasCaInStore = keyStore.isCertificateEntry(keystoreInfo.getCaAlias());
152-
if (optionResCa != null && !hasCaInStore) {
171+
InputStream certInput = getResolvableinputStream(context, optionResCert);
172+
Certificate cert = CertificateFactory.getInstance("X.509").generateCertificate(certInput);
173+
keyStore.setCertificateEntry(keystoreInfo.getCertAlias(), cert);
174+
175+
InputStream keyInput = getResolvableinputStream(context, optionResKey);
176+
PrivateKey privateKey = getPrivateKeyFromPEM(keyInput);
177+
keyStore.setKeyEntry(keystoreInfo.getKeyAlias(), privateKey, null, new Certificate[]{cert});
178+
179+
if (optionResCa != null) {
153180
InputStream caInput = getResolvableinputStream(context, optionResCa);
154181
// Generate the CA Certificate from the raw resource file
155182
Certificate ca = CertificateFactory.getInstance("X.509").generateCertificate(caInput);
156183
caInput.close();
157184
// Load the key store using the CA
158185
keyStore.setCertificateEntry(keystoreInfo.getCaAlias(), ca);
159186
}
160-
187+
}
188+
189+
if (keyStore != null) {
161190
// Initialize the KeyManagerFactory with this cert
162191
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
163192
keyManagerFactory.init(keyStore, new char[0]);
@@ -166,15 +195,14 @@ static SSLSocketFactory createCustomTrustedSocketFactory(
166195
SSLContext sslContext = SSLContext.getInstance("TLS");
167196
sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[]{new BlindTrustManager()}, null);
168197
return sslContext.getSocketFactory();
169-
170198
} else {
171199
// Keep old behavior
172200
InputStream caInput = getResolvableinputStream(context, optionResCa);
173201
// Generate the CA Certificate from the raw resource file
174202
Certificate ca = CertificateFactory.getInstance("X.509").generateCertificate(caInput);
175203
caInput.close();
176204
// Load the key store using the CA
177-
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
205+
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
178206
keyStore.load(null, null);
179207
keyStore.setCertificateEntry("ca", ca);
180208

android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketClient.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,13 @@ private boolean containsKey(ReadableArray array, String key) {
8787
}
8888
return false;
8989
}
90+
9091
private ResolvableOption getResolvableOption(ReadableMap tlsOptions, String key) {
9192
if (tlsOptions.hasKey(key)) {
9293
String value = tlsOptions.getString(key);
94+
if (value == null || value.isEmpty()) {
95+
return null;
96+
}
9397
ReadableArray resolvedKeys = tlsOptions.hasKey("resolvedKeys") ? tlsOptions.getArray("resolvedKeys") : null;
9498
boolean needsResolution = resolvedKeys != null && containsKey(resolvedKeys, key);
9599
return new ResolvableOption(value, needsResolution);
@@ -110,7 +114,8 @@ private SSLSocketFactory getSSLSocketFactory(Context context, ReadableMap tlsOpt
110114
final KeystoreInfo keystoreInfo = new KeystoreInfo(keystoreName, caAlias, certAlias, keyAlias);
111115

112116
if (tlsOptions.hasKey("rejectUnauthorized") && !tlsOptions.getBoolean("rejectUnauthorized")) {
113-
if (customTlsKey != null && customTlsCert != null ) {
117+
if ((customTlsKey != null && customTlsCert != null) ||
118+
(keyAlias != null && !keyAlias.isEmpty() && customTlsKey == null) ) {
114119
ssf = SSLCertificateHelper.createCustomTrustedSocketFactory(
115120
context,
116121
customTlsCa,

android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java

+10
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,16 @@ private TcpSocketServer getTcpServer(final int id) {
472472
return (TcpSocketServer) socket;
473473
}
474474

475+
@SuppressWarnings("unused")
476+
@ReactMethod
477+
public void hasIdentity(@NonNull final ReadableMap options, Promise promise) {
478+
try {
479+
promise.resolve(SSLCertificateHelper.hasIdentity(options));
480+
} catch (Exception e) {
481+
promise.reject(e);
482+
}
483+
}
484+
475485
@SuppressWarnings("unused")
476486
@ReactMethod
477487
public void getPeerCertificate(final int cId, Promise promise) {

ios/TcpSocketClient.h

+26
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ typedef enum RCTTCPError RCTTCPError;
1919

2020
@class TcpSocketClient;
2121

22+
// Add ResolvableOption interface here
23+
@interface ResolvableOption : NSObject
24+
25+
@property (nonatomic, strong, readonly) NSString *value;
26+
@property (nonatomic, readonly) BOOL needsResolution;
27+
28+
- (instancetype)initWithValue:(NSString *)value needsResolution:(BOOL)needsResolution;
29+
+ (instancetype)optionWithValue:(NSString *)value needsResolution:(BOOL)needsResolution;
30+
- (NSString *)resolve;
31+
32+
@end
33+
2234
@protocol SocketClientDelegate <NSObject>
2335

2436
- (void)addClient:(TcpSocketClient *)client;
@@ -115,4 +127,18 @@ typedef enum RCTTCPError RCTTCPError;
115127

116128
- (void)resume;
117129

130+
+ (BOOL)hasIdentity:(NSDictionary *)aliases;
131+
132+
/**
133+
* Get peer certificate information
134+
* @return NSDictionary with certificate information or nil if not available
135+
*/
136+
- (NSDictionary *)getPeerCertificate;
137+
138+
/**
139+
* Get local certificate information
140+
* @return NSDictionary with certificate information or nil if not available
141+
*/
142+
- (NSDictionary *)getCertificate;
143+
118144
@end

0 commit comments

Comments
 (0)