From ba340ddb8f07587673c254b622ffef2025ad0745 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 12 Jun 2024 09:38:25 +0100 Subject: [PATCH 1/2] feat: support setting ICE ufrag and pwd, and missing config values Updates the implementation to match the latest libdatachannel with features for setting ICE ufrag/pwd and reading the remote cert fingerprint. Also adds pass-through for missing config values. --- lib/index.d.ts | 24 ++++++++++- src/peer-connection-wrapper.cpp | 75 ++++++++++++++++++++++++++++++++- src/peer-connection-wrapper.h | 1 + 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index 717c313..d9979bf 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -54,12 +54,17 @@ export interface RtcConfig { enableIceTcp?: boolean; enableIceUdpMux?: boolean; disableAutoNegotiation?: boolean; + disableFingerprintVerification?: boolean; + disableAutoGathering?: boolean; forceMediaTransport?: boolean; portRangeBegin?: number; portRangeEnd?: number; maxMessageSize?: number; mtu?: number; iceTransportPolicy?: TransportPolicy; + certificatePemFile?: string; + keyPemFile?: string; + keyPemPass?: string; } // Lowercase to match the description type string from libdatachannel @@ -77,6 +82,11 @@ export const enum ReliabilityType { Timed = 2, } +export interface LocalDescriptionInit { + iceUfrag?: string; + icePwd?: string; +} + export interface DataChannelInitConfig { protocol?: string; negotiated?: boolean; @@ -277,13 +287,25 @@ export class WebSocketServer { onClient(cb: (ws: WebSocket) => void): void; } +export interface CertificateFingerprint { + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/RTCCertificate/getFingerprints#value + */ + value: string; + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/RTCCertificate/getFingerprints#algorithm + */ + algorithm: 'sha-1' | 'sha-224' | 'sha-256' | 'sha-384' | 'sha-512' | 'md5' | 'md2'; +} + export class PeerConnection { constructor(peerName: string, config: RtcConfig); close(): void; - setLocalDescription(type?: DescriptionType): void; + setLocalDescription(type?: DescriptionType, init?: LocalDescriptionInit): void; setRemoteDescription(sdp: string, type: DescriptionType): void; localDescription(): { type: string; sdp: string } | null; remoteDescription(): { type: string; sdp: string } | null; + remoteFingerprint(): CertificateFingerprint; addRemoteCandidate(candidate: string, mid: string): void; createDataChannel(label: string, config?: DataChannelInitConfig): DataChannel; addTrack(media: Video | Audio): Track; diff --git a/src/peer-connection-wrapper.cpp b/src/peer-connection-wrapper.cpp index cbce8cd..1db9848 100644 --- a/src/peer-connection-wrapper.cpp +++ b/src/peer-connection-wrapper.cpp @@ -41,6 +41,7 @@ Napi::Object PeerConnectionWrapper::Init(Napi::Env env, Napi::Object exports) InstanceMethod("setRemoteDescription", &PeerConnectionWrapper::setRemoteDescription), InstanceMethod("localDescription", &PeerConnectionWrapper::localDescription), InstanceMethod("remoteDescription", &PeerConnectionWrapper::remoteDescription), + InstanceMethod("remoteFingerprint", &PeerConnectionWrapper::remoteFingerprint), InstanceMethod("addRemoteCandidate", &PeerConnectionWrapper::addRemoteCandidate), InstanceMethod("createDataChannel", &PeerConnectionWrapper::createDataChannel), InstanceMethod("addTrack", &PeerConnectionWrapper::addTrack), @@ -202,6 +203,10 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N if (config.Get("disableAutoNegotiation").IsBoolean()) rtcConfig.disableAutoNegotiation = config.Get("disableAutoNegotiation").As(); + // disableAutoGathering option + if (config.Get("disableAutoGathering").IsBoolean()) + rtcConfig.disableAutoGathering = config.Get("disableAutoGathering").As(); + // forceMediaTransport option if (config.Get("forceMediaTransport").IsBoolean()) rtcConfig.forceMediaTransport = config.Get("forceMediaTransport").As(); @@ -234,6 +239,22 @@ PeerConnectionWrapper::PeerConnectionWrapper(const Napi::CallbackInfo &info) : N } } + // Allow skipping fingerprint validation + if (config.Get("disableFingerprintVerification").IsBoolean()) { + rtcConfig.disableFingerprintVerification = config.Get("disableFingerprintVerification").As(); + } + + // Specify certificate to use if set + if (config.Get("certificatePemFile").IsString()) { + rtcConfig.certificatePemFile = config.Get("certificatePemFile").As().ToString(); + } + if (config.Get("keyPemFile").IsString()) { + rtcConfig.keyPemFile = config.Get("keyPemFile").As().ToString(); + } + if (config.Get("keyPemPass").IsString()) { + rtcConfig.keyPemPass = config.Get("keyPemPass").As().ToString(); + } + // Create peer-connection try { @@ -314,6 +335,7 @@ void PeerConnectionWrapper::setLocalDescription(const Napi::CallbackInfo &info) } rtc::Description::Type type = rtc::Description::Type::Unspec; + rtc::LocalDescriptionInit init; // optional if (length > 0) @@ -339,7 +361,29 @@ void PeerConnectionWrapper::setLocalDescription(const Napi::CallbackInfo &info) type = rtc::Description::Type::Rollback; } - mRtcPeerConnPtr->setLocalDescription(type); + // optional + if (length > 1) + { + PLOG_DEBUG << "setLocalDescription() called with LocalDescriptionInit"; + + if (info[1].IsObject()) + { + PLOG_DEBUG << "setLocalDescription() called with LocalDescriptionInit as object"; + Napi::Object obj = info[1].As(); + + if (obj.Get("iceUfrag").IsString()) { + PLOG_DEBUG << "setLocalDescription() has ufrag"; + init.iceUfrag = obj.Get("iceUfrag").As(); + } + + if (obj.Get("icePwd").IsString()) { + PLOG_DEBUG << "setLocalDescription() has password"; + init.icePwd = obj.Get("icePwd").As(); + } + } + } + + mRtcPeerConnPtr->setLocalDescription(type, init); } void PeerConnectionWrapper::setRemoteDescription(const Napi::CallbackInfo &info) @@ -1049,7 +1093,34 @@ Napi::Value PeerConnectionWrapper::maxMessageSize(const Napi::CallbackInfo &info try { - return Napi::Number::New(env, mRtcPeerConnPtr->remoteMaxMessageSize()); + return Napi::Array::New(env, mRtcPeerConnPtr->remoteMaxMessageSize()); + } + catch (std::exception &ex) + { + Napi::Error::New(env, std::string("libdatachannel error: ") + ex.what()).ThrowAsJavaScriptException(); + return Napi::Number::New(info.Env(), 0); + } +} + +Napi::Value PeerConnectionWrapper::remoteFingerprint(const Napi::CallbackInfo &info) +{ + PLOG_DEBUG << "remoteFingerprints() called"; + Napi::Env env = info.Env(); + + if (!mRtcPeerConnPtr) + { + return Napi::Number::New(info.Env(), 0); + } + + try + { + auto fingerprint = mRtcPeerConnPtr->remoteFingerprint(); + + Napi::Object fingerprintObject = Napi::Object::New(env); + fingerprintObject.Set("value", fingerprint.value); + fingerprintObject.Set("algorithm", rtc::CertificateFingerprint::AlgorithmIdentifier(fingerprint.algorithm)); + + return fingerprintObject; } catch (std::exception &ex) { diff --git a/src/peer-connection-wrapper.h b/src/peer-connection-wrapper.h index 5896e30..3865c08 100644 --- a/src/peer-connection-wrapper.h +++ b/src/peer-connection-wrapper.h @@ -33,6 +33,7 @@ class PeerConnectionWrapper : public Napi::ObjectWrap Napi::Value iceState(const Napi::CallbackInfo &info); Napi::Value signalingState(const Napi::CallbackInfo &info); Napi::Value gatheringState(const Napi::CallbackInfo &info); + Napi::Value remoteFingerprint(const Napi::CallbackInfo &info); // Callbacks void onLocalDescription(const Napi::CallbackInfo &info); From 10051a80cd3fb69a851c63c81c7a5916db342b06 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Wed, 12 Jun 2024 09:47:39 +0100 Subject: [PATCH 2/2] chore: update api docs --- API.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/API.md b/API.md index 4660a64..a32619e 100644 --- a/API.md +++ b/API.md @@ -14,11 +14,18 @@ export interface RtcConfig { bindAddress?: string; enableIceTcp?: boolean; enableIceUdpMux?: boolean; + disableAutoNegotiation?: boolean; + disableFingerprintVerification?: boolean; + disableAutoGathering?: boolean; + forceMediaTransport?: boolean; portRangeBegin?: number; portRangeEnd?: number; maxMessageSize?: number; mtu?: number; iceTransportPolicy?: TransportPolicy; + certificatePemFile?: string; + keyPemFile?: string; + keyPemPass?: string; } export const enum RelayType { @@ -65,6 +72,27 @@ export const enum DescriptionType { } ``` +**setLocalDescription: (sdp: string, init?: LocalDescriptionInit) => void** + +Set Local Description and optionally the ICE ufrag/pwd to use. These should not +be set as they will be generated automatically as per the spec. +``` +export interface LocalDescriptionInit { + iceUfrag?: string; + icePwd?: string; +} +``` + +**remoteFingerprint: () => CertificateFingerprint** + +Returns the certificate fingerprint used by the remote peer +``` +export interface CertificateFingerprint { + value: string; + algorithm: 'sha-1' | 'sha-224' | 'sha-256' | 'sha-384' | 'sha-512' | 'md5' | 'md2'; +} +``` + **addRemoteCandidate: (candidate: string, mid: string) => void** Add remote candidate info