Skip to content

Commit 6477764

Browse files
committed
feat(websockets): add ssl pinning
1 parent dde1f43 commit 6477764

File tree

6 files changed

+45
-8
lines changed

6 files changed

+45
-8
lines changed

packages/nativescript-websockets/bridge.android.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { HeaderType } from './common';
2-
import { NativeBridgeDefinition } from './websocket.definition';
2+
import { NativeBridgeDefinition, WebSocketBridgeConnectOptions } from './websocket.definition';
33

44
interface ExtendedArrayBuffer extends ArrayBuffer {
55
from?(nativeBuffer: java.nio.ByteBuffer): ArrayBuffer;
@@ -72,7 +72,7 @@ export class NativeBridge extends NativeBridgeDefinition {
7272
nativeWs!: okhttp3.WebSocket;
7373
startLooper?: android.os.Looper;
7474
handler?: android.os.Handler;
75-
connect(url: string, protocols: string[], headers: HeaderType): void {
75+
connect({ url, protocols, headers }: WebSocketBridgeConnectOptions): void {
7676
this.startLooper = android.os.Looper.myLooper();
7777
this.handler = new android.os.Handler(this.startLooper);
7878
protocols = protocols || [];

packages/nativescript-websockets/bridge.ios.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010

1111
import { HeaderType } from './common';
12-
import { NativeBridgeDefinition } from './websocket.definition';
12+
import { NativeBridgeDefinition, WebSocketBridgeConnectOptions } from './websocket.definition';
1313

1414
@NativeClass
1515
class RCTSRWebSocketDelegateImpl extends NSObject implements RCTSRWebSocketDelegate {
@@ -41,7 +41,7 @@ export class NativeBridge extends NativeBridgeDefinition {
4141
// store the delegate so it isn't garbage collected
4242
// TODO: fix the iOS runtime so we don't need this
4343
delegate!: RCTSRWebSocketDelegateImpl;
44-
connect(url: string, protocols: string[], headers: HeaderType) {
44+
connect({ url, protocols, headers, pinnedCertificates }: WebSocketBridgeConnectOptions): void {
4545
const nativeUrl = NSURL.URLWithString(url);
4646
const request = NSMutableURLRequest.requestWithURL(nativeUrl);
4747
// NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
@@ -63,6 +63,24 @@ export class NativeBridge extends NativeBridgeDefinition {
6363
for (const k of Object.keys(headers.headers)) {
6464
request.addValueForHTTPHeaderField(`${headers.headers[k]}`, k);
6565
}
66+
if (pinnedCertificates) {
67+
const sslArray = NSMutableArray.new();
68+
69+
for (const c of pinnedCertificates) {
70+
// convert from pem to der (base64)
71+
const der = c
72+
.replace(/-----BEGIN CERTIFICATE-----/g, '')
73+
.replace(/-----END CERTIFICATE-----/g, '')
74+
.replace(/\r?\n/g, '');
75+
const cert = SecCertificateCreateWithData(null, NSData.alloc().initWithBase64EncodedStringOptions(der, NSDataBase64DecodingOptions.IgnoreUnknownCharacters));
76+
if (cert) {
77+
sslArray.addObject(cert);
78+
} else {
79+
console.warn('Unable to create certificate from pem');
80+
}
81+
}
82+
request.RCTSR_SSLPinnedCertificates = sslArray;
83+
}
6684

6785
const webSocket = RCTSRWebSocket.alloc().initWithURLRequestProtocols(request, protocols);
6886
this.nativeSocket = webSocket;

packages/nativescript-websockets/types/objc!NativeScriptWebSockets.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ declare const enum RCTSRStatusCode {
3535
CodeMessageTooBig = 1009
3636
}
3737

38+
interface NSMutableURLRequest {
39+
RCTSR_SSLPinnedCertificates: NSArray<any>;
40+
}
41+
3842
declare class RCTSRWebSocket extends NSObject implements NSStreamDelegate {
3943

4044
static alloc(): RCTSRWebSocket; // inherited from NSObject

packages/nativescript-websockets/websocket.definition.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@ export interface WebSocketPolyfill {
77
_websocketFailed(message: string): void;
88
}
99

10+
export interface WebSocketBridgeConnectOptions {
11+
url: string;
12+
protocols: string[];
13+
headers: HeaderType;
14+
pinnedCertificates?: string[];
15+
}
16+
1017
export abstract class NativeBridgeDefinition {
1118
public handleThreading = true;
1219
constructor(protected ws: WebSocketPolyfill) {}
13-
abstract connect(url: string, protocols: string | string[], headers: HeaderType): void;
20+
abstract connect(options: WebSocketBridgeConnectOptions): void;
1421
abstract send(data: string | ArrayBuffer | ArrayBufferView | Blob): void;
1522
abstract closeWithCodeReason(statusCode: number, closeReason: string): void;
1623
abstract sendPing(): void;

packages/nativescript-websockets/websocket.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ const openWebsockets = new Set<WebSocketPolyfill>();
3737
// websocketFailed: [{ id: number; message: string }];
3838
// };
3939

40+
interface ExtraWebsocketOptions {
41+
headers: { origin?: string; [key: string]: unknown };
42+
nativescript: { handleThreading: boolean };
43+
pinnedCertificates?: string[];
44+
[key: string]: unknown;
45+
}
46+
4047
/**
4148
* Browser-compatible WebSockets implementation.
4249
*
@@ -81,7 +88,7 @@ export class WebSocket implements WebSocketPolyfill {
8188
message: new Map(),
8289
};
8390

84-
constructor(url: string, protocols?: string | Array<string>, options?: { headers: { origin?: string; [key: string]: unknown }; nativescript: { handleThreading: boolean }; [key: string]: unknown }) {
91+
constructor(url: string, protocols?: string | Array<string>, options?: ExtraWebsocketOptions) {
8592
this.nativeBridge = new NativeBridge(this);
8693
this.url = url;
8794
if (typeof protocols === 'string') {
@@ -91,7 +98,7 @@ export class WebSocket implements WebSocketPolyfill {
9198
protocols = [];
9299
}
93100

94-
const { headers = {}, nativescript = { handleThreading: true }, ...unrecognized } = options || ({} as { headers: { origin?: string; [key: string]: unknown }; nativescript: { handleThreading: boolean }; [key: string]: unknown });
101+
const { headers = {}, nativescript = { handleThreading: true }, pinnedCertificates, ...unrecognized } = options || ({} as ExtraWebsocketOptions);
95102
this.nativeBridge.handleThreading = nativescript?.handleThreading ?? WebSocket.HANDLE_THREADING;
96103
this._binaryType = 'arraybuffer';
97104

@@ -119,7 +126,7 @@ export class WebSocket implements WebSocketPolyfill {
119126
// Platform.OS !== 'ios' ? null : NativeWebSocketModule,
120127
// );
121128
this._registerEvents();
122-
this.nativeBridge.connect(url, protocols, { headers });
129+
this.nativeBridge.connect({ url, protocols, headers: { headers }, pinnedCertificates });
123130
openWebsockets.add(this);
124131
// NativeWebSocketModule.connect(url, protocols, {headers}, this._socketId);
125132
}

references.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/// <reference path="./node_modules/@nativescript/types-ios/index.d.ts" />
2+
/// <reference path="./node_modules/@nativescript/types-ios/lib/ios/objc-x86_64/objc!Security.d.ts" />
23
/// <reference path="./node_modules/@nativescript/types-android/lib/android-29.d.ts" />

0 commit comments

Comments
 (0)