diff --git a/android/src/main/java/com/oney/WebRTCModule/PeerConnectionObserver.java b/android/src/main/java/com/oney/WebRTCModule/PeerConnectionObserver.java index dfc2901a8..d0af8e7da 100644 --- a/android/src/main/java/com/oney/WebRTCModule/PeerConnectionObserver.java +++ b/android/src/main/java/com/oney/WebRTCModule/PeerConnectionObserver.java @@ -115,6 +115,21 @@ RtpTransceiver addTransceiver(MediaStreamTrack track, RtpTransceiver.RtpTranscei return peerConnection.addTransceiver(track, init); } + + RtpReceiver getReceiver(String id) { + if (this.peerConnection == null) { + return null; + } + + for (RtpReceiver receiver : this.peerConnection.getReceivers()) { + if (receiver.id().equals(id)) { + return receiver; + } + } + + return null; + } + RtpSender getSender(String id) { if (this.peerConnection == null) { return null; diff --git a/android/src/main/java/com/oney/WebRTCModule/RTCFrameCryptor.java b/android/src/main/java/com/oney/WebRTCModule/RTCFrameCryptor.java new file mode 100644 index 000000000..d1e03f429 --- /dev/null +++ b/android/src/main/java/com/oney/WebRTCModule/RTCFrameCryptor.java @@ -0,0 +1,384 @@ +package com.oney.WebRTCModule; + +import android.util.Base64; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; + +import org.webrtc.FrameCryptor; +import org.webrtc.FrameCryptorAlgorithm; +import org.webrtc.FrameCryptorFactory; +import org.webrtc.FrameCryptorKeyProvider; +import org.webrtc.RtpReceiver; +import org.webrtc.RtpSender; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +public class RTCFrameCryptor { + + private static final String TAG = "RTCFrameCryptor"; + private final Map frameCryptos = new HashMap<>(); + private final Map frameCryptoObservers = new HashMap<>(); + private final Map keyProviders = new HashMap<>(); + private final WebRTCModule webRTCModule; + + public RTCFrameCryptor(WebRTCModule webRTCModule) { + this.webRTCModule = webRTCModule; + } + + private void sendEvent(String eventName, WritableMap params) { + webRTCModule.sendEvent(eventName, params); + } + + class FrameCryptorStateObserver implements FrameCryptor.Observer { + public FrameCryptorStateObserver(String frameCryptorId) { + this.frameCryptorId = frameCryptorId; + } + + private final String frameCryptorId; + + private String frameCryptorErrorStateToString(FrameCryptor.FrameCryptionState state) { + switch (state) { + case NEW: + return "new"; + case OK: + return "ok"; + case DECRYPTIONFAILED: + return "decryptionFailed"; + case ENCRYPTIONFAILED: + return "encryptionFailed"; + case INTERNALERROR: + return "internalError"; + case KEYRATCHETED: + return "keyRatcheted"; + case MISSINGKEY: + return "missingKey"; + default: + throw new IllegalArgumentException("Unknown FrameCryptorErrorState: " + state); + } + } + + @Override + public void onFrameCryptionStateChanged(String participantId, FrameCryptor.FrameCryptionState state) { + WritableMap event = Arguments.createMap(); + event.putString("event", "frameCryptionStateChanged"); + event.putString("participantId", participantId); + event.putString("state", frameCryptorErrorStateToString(state)); + event.putString("frameCryptorId", frameCryptorId); + sendEvent("frameCryptionStateChanged", event); + } + } +// + + private FrameCryptorAlgorithm frameCryptorAlgorithmFromInt(int algorithm) { + switch (algorithm) { + case 0: + return FrameCryptorAlgorithm.AES_GCM; + case 1: + return FrameCryptorAlgorithm.AES_CBC; + default: + return FrameCryptorAlgorithm.AES_GCM; + } + } + + public String frameCryptorFactoryCreateFrameCryptor(ReadableMap params) { + String keyProviderId = params.getString("keyProviderId"); + FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId); + if (keyProvider == null) { + Log.w(TAG, "frameCryptorFactoryCreateFrameCryptorFailed: keyProvider not found"); + return null; + } + int peerConnectionId = params.getInt("peerConnectionId"); + PeerConnectionObserver pco = webRTCModule.getPeerConnectionObserver(peerConnectionId); + if (pco == null) { + Log.w(TAG, "frameCryptorFactoryCreateFrameCryptorFailed: peerConnection not found"); + return null; + } + String participantId = params.getString("participantId"); + String type = params.getString("type"); + int algorithm = params.getInt("algorithm"); + String rtpSenderId = params.getString("rtpSenderId"); + String rtpReceiverId = params.getString("rtpReceiverId"); + + if (type == null || !(type.equals("sender") || type.equals("receiver"))){ + Log.w(TAG, "frameCryptorFactoryCreateFrameCryptorFailed: type must be sender or receiver"); + return null; + } else if (type.equals("sender")) { + RtpSender rtpSender = pco.getSender(rtpSenderId); + + FrameCryptor frameCryptor = FrameCryptorFactory.createFrameCryptorForRtpSender(webRTCModule.mFactory, + rtpSender, + participantId, + frameCryptorAlgorithmFromInt(algorithm), + keyProvider); + String frameCryptorId = UUID.randomUUID().toString(); + frameCryptos.put(frameCryptorId, frameCryptor); + FrameCryptorStateObserver observer = new FrameCryptorStateObserver(frameCryptorId); + frameCryptor.setObserver(observer); + frameCryptoObservers.put(frameCryptorId, observer); + + return frameCryptorId; + } else { + RtpReceiver rtpReceiver = pco.getReceiver(rtpReceiverId); + + FrameCryptor frameCryptor = FrameCryptorFactory.createFrameCryptorForRtpReceiver(webRTCModule.mFactory, + rtpReceiver, + participantId, + frameCryptorAlgorithmFromInt(algorithm), + keyProvider); + String frameCryptorId = UUID.randomUUID().toString(); + frameCryptos.put(frameCryptorId, frameCryptor); + FrameCryptorStateObserver observer = new FrameCryptorStateObserver(frameCryptorId); + frameCryptor.setObserver(observer); + frameCryptoObservers.put(frameCryptorId, observer); + + return frameCryptorId; + } + } + + public void frameCryptorSetKeyIndex(ReadableMap params, @NonNull Promise result) { + String frameCryptorId = params.getString("frameCryptorId"); + FrameCryptor frameCryptor = frameCryptos.get(frameCryptorId); + if (frameCryptor == null) { + result.reject("frameCryptorSetKeyIndexFailed", "frameCryptor not found", (Throwable) null); + return; + } + int keyIndex = params.getInt("keyIndex"); + frameCryptor.setKeyIndex(keyIndex); + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putBoolean("result", true); + result.resolve(paramsResult); + } + + public void frameCryptorGetKeyIndex(ReadableMap params, @NonNull Promise result) { + String frameCryptorId = params.getString("frameCryptorId"); + FrameCryptor frameCryptor = frameCryptos.get(frameCryptorId); + if (frameCryptor == null) { + result.reject("frameCryptorGetKeyIndexFailed", "frameCryptor not found", (Throwable) null); + return; + } + int keyIndex = frameCryptor.getKeyIndex(); + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putInt("keyIndex", keyIndex); + result.resolve(paramsResult); + } + + public void frameCryptorSetEnabled(ReadableMap params, @NonNull Promise result) { + String frameCryptorId = params.getString("frameCryptorId"); + FrameCryptor frameCryptor = frameCryptos.get(frameCryptorId); + if (frameCryptor == null) { + result.reject("frameCryptorSetEnabledFailed", "frameCryptor not found", (Throwable) null); + return; + } + boolean enabled = params.getBoolean("enabled"); + frameCryptor.setEnabled(enabled); + + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putBoolean("result", enabled); + result.resolve(paramsResult); + } + + public void frameCryptorGetEnabled(ReadableMap params, @NonNull Promise result) { + String frameCryptorId = params.getString("frameCryptorId"); + FrameCryptor frameCryptor = frameCryptos.get(frameCryptorId); + if (frameCryptor == null) { + result.reject("frameCryptorGetEnabledFailed", "frameCryptor not found", (Throwable) null); + return; + } + boolean enabled = frameCryptor.isEnabled(); + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putBoolean("enabled", enabled); + result.resolve(paramsResult); + } + + public void frameCryptorDispose(ReadableMap params, @NonNull Promise result) { + String frameCryptorId = params.getString("frameCryptorId"); + FrameCryptor frameCryptor = frameCryptos.get(frameCryptorId); + if (frameCryptor == null) { + result.reject("frameCryptorDisposeFailed", "frameCryptor not found", (Throwable) null); + return; + } + frameCryptor.dispose(); + frameCryptos.remove(frameCryptorId); + frameCryptoObservers.remove(frameCryptorId); + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putString("result", "success"); + result.resolve(paramsResult); + } + + @Nullable + public String frameCryptorFactoryCreateKeyProvider(ReadableMap keyProviderOptions) { + String keyProviderId = UUID.randomUUID().toString(); + + if (keyProviderOptions == null) { + Log.w(TAG, "frameCryptorFactoryCreateKeyProvider: keyProviderOptions is null!"); + return null; + } + boolean sharedKey = keyProviderOptions.getBoolean("sharedKey"); + int ratchetWindowSize = keyProviderOptions.getInt("ratchetWindowSize"); + int failureTolerance = keyProviderOptions.getInt("failureTolerance"); + + byte[] ratchetSalt = getBytesFromMap(keyProviderOptions, "ratchetSalt", "ratchetSaltIsBase64"); + + byte[] uncryptedMagicBytes = new byte[0]; + if (keyProviderOptions.hasKey("uncryptedMagicBytes")) { + uncryptedMagicBytes = Base64.decode(keyProviderOptions.getString("uncryptedMagicBytes"), Base64.DEFAULT); + } + int keyRingSize = (int) keyProviderOptions.getInt("keyRingSize"); + boolean discardFrameWhenCryptorNotReady = (boolean) keyProviderOptions.getBoolean("discardFrameWhenCryptorNotReady"); + FrameCryptorKeyProvider keyProvider = FrameCryptorFactory.createFrameCryptorKeyProvider(sharedKey, ratchetSalt, ratchetWindowSize, + uncryptedMagicBytes, failureTolerance, keyRingSize, discardFrameWhenCryptorNotReady); + keyProviders.put(keyProviderId, keyProvider); + return keyProviderId; + } + + public void keyProviderSetSharedKey(ReadableMap params, @NonNull Promise result) { + String keyProviderId = params.getString("keyProviderId"); + FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId); + if (keyProvider == null) { + result.reject("keyProviderSetKeySharedFailed", "keyProvider not found", (Throwable) null); + return; + } + int keyIndex = params.getInt("keyIndex"); + byte[] key = getBytesFromMap(params, "key", "keyIsBase64"); + keyProvider.setSharedKey(keyIndex, key); + + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putBoolean("result", true); + result.resolve(paramsResult); + } + + public void keyProviderRatchetSharedKey(ReadableMap params, @NonNull Promise result) { + String keyProviderId = params.getString("keyProviderId"); + FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId); + if (keyProvider == null) { + result.reject("keyProviderRatchetSharedKeyFailed", "keyProvider not found", (Throwable) null); + return; + } + int keyIndex = params.getInt("keyIndex"); + + byte[] newKey = keyProvider.ratchetSharedKey(keyIndex); + + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putString("result", Base64.encodeToString(newKey, Base64.DEFAULT)); + result.resolve(paramsResult); + } + + public void keyProviderExportSharedKey(ReadableMap params, @NonNull Promise result) { + String keyProviderId = params.getString("keyProviderId"); + FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId); + if (keyProvider == null) { + result.reject("keyProviderExportSharedKeyFailed", "keyProvider not found", (Throwable) null); + return; + } + int keyIndex = params.getInt("keyIndex"); + + byte[] key = keyProvider.exportSharedKey(keyIndex); + + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putString("result", Base64.encodeToString(key, Base64.DEFAULT)); + result.resolve(paramsResult); + } + + public void keyProviderSetKey(ReadableMap params, @NonNull Promise result) { + String keyProviderId = params.getString("keyProviderId"); + FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId); + if (keyProvider == null) { + result.reject("keyProviderSetKeyFailed", "keyProvider not found", (Throwable) null); + return; + } + int keyIndex = params.getInt("keyIndex"); + String participantId = params.getString("participantId"); + byte[] key = getBytesFromMap(params, "key", "keyIsBase64"); + keyProvider.setKey(participantId, keyIndex, key); + + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putBoolean("result", true); + result.resolve(paramsResult); + } + + public void keyProviderRatchetKey(ReadableMap params, @NonNull Promise result) { + String keyProviderId = params.getString("keyProviderId"); + FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId); + if (keyProvider == null) { + result.reject("keyProviderSetKeysFailed", "keyProvider not found", (Throwable) null); + return; + } + String participantId = params.getString("participantId"); + int keyIndex = params.getInt("keyIndex"); + + byte[] newKey = keyProvider.ratchetKey(participantId, keyIndex); + + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putString("result", Base64.encodeToString(newKey, Base64.DEFAULT)); + result.resolve(paramsResult); + } + + public void keyProviderExportKey(ReadableMap params, @NonNull Promise result) { + String keyProviderId = params.getString("keyProviderId"); + FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId); + if (keyProvider == null) { + result.reject("keyProviderExportKeyFailed", "keyProvider not found", (Throwable) null); + return; + } + String participantId = params.getString("participantId"); + int keyIndex = params.getInt("keyIndex"); + + byte[] key = keyProvider.exportKey(participantId, keyIndex); + + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putString("result", Base64.encodeToString(key, Base64.DEFAULT)); + result.resolve(paramsResult); + } + + public void keyProviderSetSifTrailer(ReadableMap params, @NonNull Promise result) { + String keyProviderId = params.getString("keyProviderId"); + FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId); + if (keyProvider == null) { + result.reject("keyProviderSetSifTrailerFailed", "keyProvider not found", (Throwable) null); + return; + } + byte[] sifTrailer = Base64.decode(params.getString("sifTrailer"), Base64.DEFAULT); + keyProvider.setSifTrailer(sifTrailer); + + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putBoolean("result", true); + result.resolve(paramsResult); + } + + public void keyProviderDispose(ReadableMap params, @NonNull Promise result) { + String keyProviderId = params.getString("keyProviderId"); + FrameCryptorKeyProvider keyProvider = keyProviders.get(keyProviderId); + if (keyProvider == null) { + result.reject("keyProviderDisposeFailed", "keyProvider not found", (Throwable) null); + return; + } + keyProvider.dispose(); + keyProviders.remove(keyProviderId); + WritableMap paramsResult = Arguments.createMap(); + paramsResult.putString("result", "success"); + result.resolve(paramsResult); + } + + private byte[] getBytesFromMap(ReadableMap map, String key, String isBase64Key) { + boolean isBase64 = map.getBoolean(isBase64Key); + byte[] bytes; + + if (isBase64) { + bytes = Base64.decode(map.getString(key), Base64.DEFAULT); + } else { + bytes = Objects.requireNonNull(map.getString(key)).getBytes(StandardCharsets.UTF_8); + } + return bytes; + } +} diff --git a/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java b/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java index c508b7c51..876250704 100644 --- a/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java +++ b/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java @@ -24,7 +24,30 @@ import com.oney.WebRTCModule.webrtcutils.H264AndSoftwareVideoDecoderFactory; import com.oney.WebRTCModule.webrtcutils.H264AndSoftwareVideoEncoderFactory; -import org.webrtc.*; +import org.webrtc.AddIceObserver; +import org.webrtc.AudioTrack; +import org.webrtc.CryptoOptions; +import org.webrtc.EglBase; +import org.webrtc.IceCandidate; +import org.webrtc.Loggable; +import org.webrtc.Logging; +import org.webrtc.MediaConstraints; +import org.webrtc.MediaStream; +import org.webrtc.MediaStreamTrack; +import org.webrtc.PeerConnection; +import org.webrtc.PeerConnectionFactory; +import org.webrtc.RTCStatsReport; +import org.webrtc.RtpCapabilities; +import org.webrtc.RtpParameters; +import org.webrtc.RtpSender; +import org.webrtc.RtpTransceiver; +import org.webrtc.SdpObserver; +import org.webrtc.SessionDescription; +import org.webrtc.SoftwareVideoDecoderFactory; +import org.webrtc.SoftwareVideoEncoderFactory; +import org.webrtc.VideoDecoderFactory; +import org.webrtc.VideoEncoderFactory; +import org.webrtc.VideoTrack; import org.webrtc.audio.AudioDeviceModule; import org.webrtc.audio.JavaAudioDeviceModule; @@ -119,6 +142,9 @@ public String getName() { return "WebRTCModule"; } + public PeerConnectionObserver getPeerConnectionObserver(int id) { + return mPeerConnectionObservers.get(id); + } private PeerConnection getPeerConnection(int id) { PeerConnectionObserver pco = mPeerConnectionObservers.get(id); return (pco == null) ? null : pco.getPeerConnection(); @@ -1412,6 +1438,85 @@ public void dataChannelSend(int peerConnectionId, String reactTag, String data, }); } + // Frame Cryptor methods + //////////////////////////////// + RTCFrameCryptor frameCryptor = new RTCFrameCryptor(this); + + @ReactMethod(isBlockingSynchronousMethod = true) + public String frameCryptorFactoryCreateFrameCryptor(ReadableMap config) { + return frameCryptor.frameCryptorFactoryCreateFrameCryptor(config); + } + + @ReactMethod + public void frameCryptorSetKeyIndex(ReadableMap config, Promise promise) { + frameCryptor.frameCryptorSetKeyIndex(config, promise); + } + + @ReactMethod + public void frameCryptorGetKeyIndex(ReadableMap config, Promise promise) { + frameCryptor.frameCryptorGetKeyIndex(config, promise); + } + + @ReactMethod + public void frameCryptorSetEnabled(ReadableMap config, Promise promise) { + frameCryptor.frameCryptorSetEnabled(config, promise); + } + + @ReactMethod + public void frameCryptorGetEnabled(ReadableMap config, Promise promise) { + frameCryptor.frameCryptorGetEnabled(config, promise); + } + + @ReactMethod + public void frameCryptorDispose(ReadableMap config, Promise promise) { + frameCryptor.frameCryptorDispose(config, promise); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public String frameCryptorFactoryCreateKeyProvider(ReadableMap config) { + return frameCryptor.frameCryptorFactoryCreateKeyProvider(config); + } + + @ReactMethod + public void keyProviderSetSharedKey(ReadableMap config, Promise promise) { + frameCryptor.keyProviderSetSharedKey(config, promise); + } + + @ReactMethod + public void keyProviderRatchetSharedKey(ReadableMap config, Promise promise) { + frameCryptor.keyProviderRatchetSharedKey(config, promise); + } + + @ReactMethod + public void keyProviderExportSharedKey(ReadableMap config, Promise promise) { + frameCryptor.keyProviderExportSharedKey(config, promise); + } + + @ReactMethod + public void keyProviderSetKey(ReadableMap config, Promise promise) { + frameCryptor.keyProviderSetKey(config, promise); + } + + @ReactMethod + public void keyProviderRatchetKey(ReadableMap config, Promise promise) { + frameCryptor.keyProviderRatchetKey(config, promise); + } + + @ReactMethod + public void keyProviderExportKey(ReadableMap config, Promise promise) { + frameCryptor.keyProviderExportKey(config, promise); + } + + @ReactMethod + public void keyProviderSetSifTrailer(ReadableMap config, Promise promise) { + frameCryptor.keyProviderSetSifTrailer(config, promise); + } + + @ReactMethod + public void keyProviderDispose(ReadableMap config, Promise promise) { + frameCryptor.keyProviderDispose(config, promise); + } + @ReactMethod public void addListener(String eventName) { // Keep: Required for RN built in Event Emitter Calls. diff --git a/ios/RCTWebRTC/WebRTCModule+RTCFrameCryptor.m b/ios/RCTWebRTC/WebRTCModule+RTCFrameCryptor.m new file mode 100644 index 000000000..783737354 --- /dev/null +++ b/ios/RCTWebRTC/WebRTCModule+RTCFrameCryptor.m @@ -0,0 +1,504 @@ +#include +#import + +#import +#import + +#import "WebRTCModule+RTCPeerConnection.h" +#import "WebRTCModule.h" + +// Key for objc_set/getAssociatedObject, value of NSString* +static char frameCryptorUUIDKey; + +@interface WebRTCModule () +@end + +@implementation WebRTCModule (RTCFrameCryptor) + +- (RTCCryptorAlgorithm)getAlgorithm:(NSNumber *)algorithm { + switch ([algorithm intValue]) { + // case 0: + // return RTCCryptorAlgorithmAesGcm; + // case 1: + // return RTCCryptorAlgorithmAesCbc; + default: + return RTCCryptorAlgorithmAesGcm; + } +} + +- (NSData *)bytesFromMap:(NSDictionary *)map key:(NSString *)key isBase64Key:(NSString *)isBase64Key { + BOOL isBase64 = [map[isBase64Key] boolValue]; + if (isBase64) { + return [[NSData alloc] initWithBase64EncodedString:map[key] options:0]; + } else { + return [map[key] dataUsingEncoding:NSUTF8StringEncoding]; + } +} + +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(frameCryptorFactoryCreateFrameCryptor + : (nonnull NSDictionary *)constraints) { + + __block NSString* frameCryptorId = nil; + dispatch_sync(self.workerQueue, ^{ + NSNumber *peerConnectionId = constraints[@"peerConnectionId"]; + NSNumber *algorithm = constraints[@"algorithm"]; + if (algorithm == nil) { + NSLog(@"frameCryptorFactoryCreateFrameCryptorFailed: Invalid algorithm"); + return; + } + + NSString *participantId = constraints[@"participantId"]; + if (participantId == nil) { + NSLog(@"frameCryptorFactoryCreateFrameCryptorFailed: Invalid participantId"); + return; + } + + NSString *keyProviderId = constraints[@"keyProviderId"]; + if (keyProviderId == nil) { + NSLog(@"frameCryptorFactoryCreateFrameCryptorFailed: Invalid keyProviderId"); + return; + } + + RTCFrameCryptorKeyProvider *keyProvider = self.keyProviders[keyProviderId]; + if (keyProvider == nil) { + NSLog(@"frameCryptorFactoryCreateFrameCryptorFailed: Invalid keyProvider"); + return; + } + + NSString *type = constraints[@"type"]; + NSString *rtpSenderId = constraints[@"rtpSenderId"]; + NSString *rtpReceiverId = constraints[@"rtpReceiverId"]; + + if ([type isEqualToString:@"sender"]) { + RTCRtpSender *sender = [self getSenderByPeerConnectionId:peerConnectionId senderId:rtpSenderId]; + + if (sender == nil) { + NSLog(@"frameCryptorFactoryCreateFrameCryptorFailed: Error: sender not found!"); + return; + } + + RTCFrameCryptor *frameCryptor = [[RTCFrameCryptor alloc] initWithFactory:self.peerConnectionFactory + rtpSender:sender + participantId:participantId + algorithm:[self getAlgorithm:algorithm] + keyProvider:keyProvider]; + frameCryptorId = [[NSUUID UUID] UUIDString]; + + frameCryptor.delegate = self; + + self.frameCryptors[frameCryptorId] = frameCryptor; + objc_setAssociatedObject(frameCryptor, &frameCryptorUUIDKey, frameCryptorId, OBJC_ASSOCIATION_COPY); + return; + } else if ([type isEqualToString:@"receiver"]) { + RTCRtpReceiver *receiver = [self getReceiverByPeerConnectionId:peerConnectionId receiverId:rtpReceiverId]; + if (receiver == nil) { + NSLog(@"frameCryptorFactoryCreateFrameCryptorFailed: Error: receiver not found!"); + return; + } + RTCFrameCryptor *frameCryptor = [[RTCFrameCryptor alloc] initWithFactory:self.peerConnectionFactory + rtpReceiver:receiver + participantId:participantId + algorithm:[self getAlgorithm:algorithm] + keyProvider:keyProvider]; + frameCryptorId = [[NSUUID UUID] UUIDString]; + + frameCryptor.delegate = self; + + self.frameCryptors[frameCryptorId] = frameCryptor; + objc_setAssociatedObject(frameCryptor, &frameCryptorUUIDKey, frameCryptorId, OBJC_ASSOCIATION_COPY); + return; + } else { + NSLog(@"InvalidArgument: Invalid type"); + return; + } + }); + + return frameCryptorId; +} + +RCT_EXPORT_METHOD(frameCryptorSetKeyIndex + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + NSString *frameCryptorId = constraints[@"frameCryptorId"]; + if (frameCryptorId == nil) { + reject(@"frameCryptorSetKeyIndexFailed", @"Invalid frameCryptorId", nil); + return; + } + RTCFrameCryptor *frameCryptor = self.frameCryptors[frameCryptorId]; + if (frameCryptor == nil) { + reject(@"frameCryptorSetKeyIndexFailed", @"Invalid frameCryptor", nil); + return; + } + + NSNumber *keyIndex = constraints[@"keyIndex"]; + if (keyIndex == nil) { + reject(@"frameCryptorSetKeyIndexFailed", @"Invalid keyIndex", nil); + return; + } + [frameCryptor setKeyIndex:[keyIndex intValue]]; + resolve(@{@"result" : @YES}); +} + +RCT_EXPORT_METHOD(frameCryptorGetKeyIndex + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + NSString *frameCryptorId = constraints[@"frameCryptorId"]; + if (frameCryptorId == nil) { + reject(@"frameCryptorGetKeyIndexFailed", @"Invalid frameCryptorId", nil); + return; + } + RTCFrameCryptor *frameCryptor = self.frameCryptors[frameCryptorId]; + if (frameCryptor == nil) { + reject(@"frameCryptorGetKeyIndexFailed", @"Invalid frameCryptor", nil); + return; + } + resolve(@{@"keyIndex" : [NSNumber numberWithInt:frameCryptor.keyIndex]}); +} + +RCT_EXPORT_METHOD(frameCryptorSetEnabled + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + NSString *frameCryptorId = constraints[@"frameCryptorId"]; + if (frameCryptorId == nil) { + reject(@"frameCryptorSetEnabledFailed", @"Invalid frameCryptorId", nil); + return; + } + RTCFrameCryptor *frameCryptor = self.frameCryptors[frameCryptorId]; + if (frameCryptor == nil) { + reject(@"frameCryptorSetEnabledFailed", @"Invalid frameCryptor", nil); + return; + } + + NSNumber *enabled = constraints[@"enabled"]; + if (enabled == nil) { + reject(@"frameCryptorSetEnabledFailed", @"Invalid enabled", nil); + return; + } + frameCryptor.enabled = [enabled boolValue]; + resolve(@{@"result" : enabled}); +} + +RCT_EXPORT_METHOD(frameCryptorGetEnabled + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + NSString *frameCryptorId = constraints[@"frameCryptorId"]; + if (frameCryptorId == nil) { + reject(@"frameCryptorGetEnabledFailed", @"Invalid frameCryptorId", nil); + return; + } + RTCFrameCryptor *frameCryptor = self.frameCryptors[frameCryptorId]; + if (frameCryptor == nil) { + reject(@"frameCryptorGetEnabledFailed", @"Invalid frameCryptor", nil); + return; + } + resolve(@{@"enabled" : [NSNumber numberWithBool:frameCryptor.enabled]}); +} + +RCT_EXPORT_METHOD(frameCryptorDispose + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + NSString *frameCryptorId = constraints[@"frameCryptorId"]; + if (frameCryptorId == nil) { + reject(@"frameCryptorDisposeFailed", @"Invalid frameCryptorId", nil); + return; + } + RTCFrameCryptor *frameCryptor = self.frameCryptors[frameCryptorId]; + if (frameCryptor == nil) { + reject(@"frameCryptorDisposeFailed", @"Invalid frameCryptor", nil); + return; + } + [self.frameCryptors removeObjectForKey:frameCryptorId]; + frameCryptor.enabled = NO; + resolve(@{@"result" : @"success"}); +} + +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(frameCryptorFactoryCreateKeyProvider + : (nonnull NSDictionary *)keyProviderOptions) { + __block NSString *keyProviderId = [[NSUUID UUID] UUIDString]; + + dispatch_sync(self.workerQueue, ^{ + NSNumber *sharedKey = keyProviderOptions[@"sharedKey"]; + if (sharedKey == nil) { + NSLog(@"frameCryptorFactoryCreateKeyProviderFailed: Invalid sharedKey"); + keyProviderId = nil; + return; + } + + if (keyProviderOptions[@"ratchetSalt"] == nil) { + NSLog(@"frameCryptorFactoryCreateKeyProviderFailed: Invalid ratchetSalt"); + keyProviderId = nil; + return; + } + NSData *ratchetSalt = [self bytesFromMap:keyProviderOptions key:@"ratchetSalt" isBase64Key:@"ratchetSaltIsBase64"]; + + NSNumber *ratchetWindowSize = keyProviderOptions[@"ratchetWindowSize"]; + if (ratchetWindowSize == nil) { + NSLog(@"frameCryptorFactoryCreateKeyProviderFailed: Invalid ratchetWindowSize"); + keyProviderId = nil; + return; + } + + NSNumber *failureTolerance = keyProviderOptions[@"failureTolerance"]; + NSData *uncryptedMagicBytes = nil; + + if (keyProviderOptions[@"uncryptedMagicBytes"] != nil) { + uncryptedMagicBytes = [[NSData alloc] initWithBase64EncodedString:keyProviderOptions[@"uncryptedMagicBytes"] + options:0]; + } + + NSNumber *keyRingSize = keyProviderOptions[@"keyRingSize"]; + NSNumber *discardFrameWhenCryptorNotReady = keyProviderOptions[@"discardFrameWhenCryptorNotReady"]; + + RTCFrameCryptorKeyProvider *keyProvider = [[RTCFrameCryptorKeyProvider alloc] initWithRatchetSalt:ratchetSalt + ratchetWindowSize:[ratchetWindowSize intValue] + sharedKeyMode:[sharedKey boolValue] + uncryptedMagicBytes:uncryptedMagicBytes + failureTolerance:failureTolerance != nil ? [failureTolerance intValue] : -1 + keyRingSize:keyRingSize != nil ? [keyRingSize intValue] : 0 + discardFrameWhenCryptorNotReady:discardFrameWhenCryptorNotReady != nil ? [discardFrameWhenCryptorNotReady boolValue] : NO]; + self.keyProviders[keyProviderId] = keyProvider; + return; + }); + return keyProviderId; +} + +- (nullable RTCFrameCryptorKeyProvider *)getKeyProviderForId:(NSString *)keyProviderId + rejecter:(RCTPromiseRejectBlock)reject { + if (keyProviderId == nil) { + reject(@"getKeyProviderForIdFailed", @"Invalid keyProviderId", nil); + return nil; + } + RTCFrameCryptorKeyProvider *keyProvider = self.keyProviders[keyProviderId]; + if (keyProvider == nil) { + reject(@"getKeyProviderForIdFailed", @"Invalid keyProvider", nil); + return nil; + } + return keyProvider; +} + +RCT_EXPORT_METHOD(keyProviderSetSharedKey + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + RTCFrameCryptorKeyProvider *keyProvider = [self getKeyProviderForId:constraints[@"keyProviderId"] rejecter:reject]; + if (keyProvider == nil) { + return; + } + + NSNumber *keyIndex = constraints[@"keyIndex"]; + if (keyIndex == nil) { + reject(@"keyProviderSetSharedKey", @"Invalid keyIndex", nil); + return; + } + + if (constraints[@"key"] == nil) { + reject(@"keyProviderSetSharedKey", @"Invalid key", nil); + return; + } + NSData *key = [self bytesFromMap:constraints key:@"key" isBase64Key:@"keyIsBase64"]; + + [keyProvider setSharedKey:key withIndex:[keyIndex intValue]]; + resolve(@{@"result" : @YES}); +} + +RCT_EXPORT_METHOD(keyProviderRatchetSharedKey + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + RTCFrameCryptorKeyProvider *keyProvider = [self getKeyProviderForId:constraints[@"keyProviderId"] rejecter:reject]; + if (keyProvider == nil) { + return; + } + + NSNumber *keyIndex = constraints[@"keyIndex"]; + if (keyIndex == nil) { + reject(@"keyProviderRatchetSharedKeyFailed", @"Invalid keyIndex", nil); + return; + } + + NSData *newKey = [keyProvider ratchetSharedKey:[keyIndex intValue]]; + resolve(@{@"result" : newKey}); +} + +RCT_EXPORT_METHOD(keyProviderExportSharedKey + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + RTCFrameCryptorKeyProvider *keyProvider = [self getKeyProviderForId:constraints[@"keyProviderId"] rejecter:reject]; + if (keyProvider == nil) { + return; + } + + NSNumber *keyIndex = constraints[@"keyIndex"]; + if (keyIndex == nil) { + reject(@"keyProviderExportSharedKeyFailed", @"Invalid keyIndex", nil); + return; + } + + NSData *key = [keyProvider exportSharedKey:[keyIndex intValue]]; + resolve(@{@"result" : key}); +} + +RCT_EXPORT_METHOD(keyProviderSetKey + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + RTCFrameCryptorKeyProvider *keyProvider = [self getKeyProviderForId:constraints[@"keyProviderId"] rejecter:reject]; + if (keyProvider == nil) { + return; + } + + NSNumber *keyIndex = constraints[@"keyIndex"]; + if (keyIndex == nil) { + reject(@"keyProviderSetKeyFailed", @"Invalid keyIndex", nil); + return; + } + + if (constraints[@"key"] == nil) { + reject(@"keyProviderSetKeyFailed", @"Invalid key", nil); + return; + } + NSData *key = [self bytesFromMap:constraints key:@"key" isBase64Key:@"keyIsBase64"]; + + NSString *participantId = constraints[@"participantId"]; + if (participantId == nil) { + reject(@"keyProviderSetKeyFailed", @"Invalid participantId", nil); + return; + } + + [keyProvider setKey:key withIndex:[keyIndex intValue] forParticipant:participantId]; + resolve(@{@"result" : @YES}); +} + +RCT_EXPORT_METHOD(keyProviderRatchetKey + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + RTCFrameCryptorKeyProvider *keyProvider = [self getKeyProviderForId:constraints[@"keyProviderId"] rejecter:reject]; + if (keyProvider == nil) { + return; + } + + NSNumber *keyIndex = constraints[@"keyIndex"]; + if (keyIndex == nil) { + reject(@"keyProviderRatchetKeyFailed", @"Invalid keyIndex", nil); + return; + } + + NSString *participantId = constraints[@"participantId"]; + if (participantId == nil) { + reject(@"keyProviderRatchetKeyFailed", @"Invalid participantId", nil); + return; + } + + NSData *newKey = [keyProvider ratchetKey:participantId withIndex:[keyIndex intValue]]; + resolve(@{@"result" : newKey}); +} + +RCT_EXPORT_METHOD(keyProviderExportKey + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + RTCFrameCryptorKeyProvider *keyProvider = [self getKeyProviderForId:constraints[@"keyProviderId"] rejecter:reject]; + if (keyProvider == nil) { + return; + } + + NSNumber *keyIndex = constraints[@"keyIndex"]; + if (keyIndex == nil) { + reject(@"keyProviderExportKeyFailed", @"Invalid keyIndex", nil); + return; + } + + NSString *participantId = constraints[@"participantId"]; + if (participantId == nil) { + reject(@"keyProviderExportKeyFailed", @"Invalid participantId", nil); + return; + } + + NSData *key = [keyProvider exportKey:participantId withIndex:[keyIndex intValue]]; + resolve(@{@"result" : key}); +} + +RCT_EXPORT_METHOD(keyProviderSetSifTrailer + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + RTCFrameCryptorKeyProvider *keyProvider = [self getKeyProviderForId:constraints[@"keyProviderId"] rejecter:reject]; + if (keyProvider == nil) { + return; + } + + if (constraints[@"sifTrailer"] == nil) { + reject(@"keyProviderSetSifTrailerFailed", @"Invalid key", nil); + return; + } + NSData *sifTrailer = [[NSData alloc] initWithBase64EncodedString:constraints[@"sifTrailer"] options:0]; + + [keyProvider setSifTrailer:sifTrailer]; + resolve(nil); +} + +RCT_EXPORT_METHOD(keyProviderDispose + : (nonnull NSDictionary *)constraints resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + NSString *keyProviderId = constraints[@"keyProviderId"]; + if (keyProviderId == nil) { + reject(@"getKeyProviderForIdFailed", @"Invalid keyProviderId", nil); + return; + } + [self.keyProviders removeObjectForKey:keyProviderId]; + resolve(@{@"result" : @"success"}); +} + +- (NSString *)stringFromState:(FrameCryptionState)state { + switch (state) { + case FrameCryptionStateNew: + return @"new"; + case FrameCryptionStateOk: + return @"ok"; + case FrameCryptionStateEncryptionFailed: + return @"encryptionFailed"; + case FrameCryptionStateDecryptionFailed: + return @"decryptionFailed"; + case FrameCryptionStateMissingKey: + return @"missingKey"; + case FrameCryptionStateKeyRatcheted: + return @"keyRatcheted"; + case FrameCryptionStateInternalError: + return @"internalError"; + default: + return @"unknown"; + } +} + +#pragma mark - RTCFrameCryptorDelegate methods + +- (void)frameCryptor:(RTC_OBJC_TYPE(RTCFrameCryptor) *)frameCryptor + didStateChangeWithParticipantId:(NSString *)participantId + withState:(FrameCryptionState)stateChanged { + + id frameCryptorId = objc_getAssociatedObject(frameCryptor, &frameCryptorUUIDKey); + + if (![frameCryptorId isKindOfClass:[NSString class]]) { + NSLog(@"Received frameCryptordidStateChangeWithParticipantId event for frame cryptor without UUID!"); + return; + } + + NSDictionary *event = @{ + @"event" : kEventFrameCryptionStateChanged, + @"participantId" : participantId, + @"frameCryptorId" : (NSString *) frameCryptorId, + @"state" : [self stringFromState:stateChanged] + }; + [self sendEventWithName:kEventFrameCryptionStateChanged body:event]; + +} + +@end diff --git a/ios/RCTWebRTC/WebRTCModule+RTCPeerConnection.h b/ios/RCTWebRTC/WebRTCModule+RTCPeerConnection.h index aa4c7aa79..f3123cc36 100644 --- a/ios/RCTWebRTC/WebRTCModule+RTCPeerConnection.h +++ b/ios/RCTWebRTC/WebRTCModule+RTCPeerConnection.h @@ -14,4 +14,11 @@ @interface WebRTCModule (RTCPeerConnection) +-(nullable RTCRtpSender *)getSenderByPeerConnectionId: (nonnull NSNumber *)peerConnectionId + senderId: (nonnull NSString *)senderId; +-(nullable RTCRtpReceiver *)getReceiverByPeerConnectionId: (nonnull NSNumber *)peerConnectionId + receiverId: (nonnull NSString *)receiverId; +-(nullable RTCRtpTransceiver *)getTransceiverByPeerConnectionId: (nonnull NSNumber *)peerConnectionId + transceiverId: (nonnull NSString *)transceiverId; + @end diff --git a/ios/RCTWebRTC/WebRTCModule+RTCPeerConnection.m b/ios/RCTWebRTC/WebRTCModule+RTCPeerConnection.m index 20db4a56e..c50383d06 100644 --- a/ios/RCTWebRTC/WebRTCModule+RTCPeerConnection.m +++ b/ios/RCTWebRTC/WebRTCModule+RTCPeerConnection.m @@ -69,6 +69,61 @@ @implementation WebRTCModule (RTCPeerConnection) int _transceiverNextId = 0; + +-(nullable RTCRtpSender *)getSenderByPeerConnectionId: (nonnull NSNumber *)peerConnectionId + senderId: (nonnull NSString *)senderId { + + RTCPeerConnection *peerConnection = self.peerConnections[peerConnectionId]; + if (!peerConnection) { + RCTLogWarn(@"PeerConnection %@ not found", peerConnectionId); + return nil; + } + RTCRtpSender *sender = nil; + for (RTCRtpSender *s in peerConnection.senders) { + if ([senderId isEqual:s.senderId]) { + sender = s; + break; + } + } + + return sender; +} +-(nullable RTCRtpReceiver *)getReceiverByPeerConnectionId: (nonnull NSNumber *)peerConnectionId + receiverId: (nonnull NSString *)receiverId { + + RTCPeerConnection *peerConnection = self.peerConnections[peerConnectionId]; + if (!peerConnection) { + RCTLogWarn(@"PeerConnection %@ not found", peerConnectionId); + return nil; + } + RTCRtpReceiver *receiver = nil; + for (RTCRtpReceiver *r in peerConnection.receivers) { + if ([receiverId isEqual:r.receiverId]) { + receiver = r; + break; + } + } + + return receiver; +} + +-(nullable RTCRtpTransceiver *)getTransceiverByPeerConnectionId: (nonnull NSNumber *)peerConnectionId + transceiverId: (nonnull NSString *)transceiverId { + RTCPeerConnection *peerConnection = self.peerConnections[peerConnectionId]; + if (!peerConnection) { + RCTLogWarn(@"PeerConnection %@ not found", peerConnectionId); + return nil; + } + RTCRtpTransceiver *transceiver = nil; + for (RTCRtpTransceiver *t in peerConnection.transceivers) { + if ([transceiverId isEqual:t.sender.senderId]) { + transceiver = t; + break; + } + } + + return transceiver; +} /* * This method is synchronous and blocking. This is done so we can implement createDataChannel * in the same way (synchronous) since the peer connection needs to exist before. diff --git a/ios/RCTWebRTC/WebRTCModule.h b/ios/RCTWebRTC/WebRTCModule.h index e1f555428..c06fb661d 100644 --- a/ios/RCTWebRTC/WebRTCModule.h +++ b/ios/RCTWebRTC/WebRTCModule.h @@ -21,6 +21,7 @@ static NSString *const kEventMediaStreamTrackMuteChanged = @"mediaStreamTrackMut static NSString *const kEventMediaStreamTrackEnded = @"mediaStreamTrackEnded"; static NSString *const kEventPeerConnectionOnRemoveTrack = @"peerConnectionOnRemoveTrack"; static NSString *const kEventPeerConnectionOnTrack = @"peerConnectionOnTrack"; +static NSString *const kEventFrameCryptionStateChanged = @"frameCryptionStateChanged"; @interface WebRTCModule : RCTEventEmitter @@ -34,6 +35,10 @@ static NSString *const kEventPeerConnectionOnTrack = @"peerConnectionOnTrack"; @property(nonatomic, strong) NSMutableDictionary *localStreams; @property(nonatomic, strong) NSMutableDictionary *localTracks; + +@property(nonatomic, strong) NSMutableDictionary* frameCryptors; +@property(nonatomic, strong) NSMutableDictionary* keyProviders; + - (RTCMediaStream *)streamForReactTag:(NSString *)reactTag; @end diff --git a/ios/RCTWebRTC/WebRTCModule.m b/ios/RCTWebRTC/WebRTCModule.m index 6917aee12..0672802b6 100644 --- a/ios/RCTWebRTC/WebRTCModule.m +++ b/ios/RCTWebRTC/WebRTCModule.m @@ -90,6 +90,9 @@ - (instancetype)init { _localStreams = [NSMutableDictionary new]; _localTracks = [NSMutableDictionary new]; + _frameCryptors = [NSMutableDictionary new]; + _keyProviders = [NSMutableDictionary new]; + dispatch_queue_attr_t attributes = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, -1); _workerQueue = dispatch_queue_create("WebRTCModule.queue", attributes); @@ -133,7 +136,8 @@ - (dispatch_queue_t)methodQueue { kEventMediaStreamTrackMuteChanged, kEventMediaStreamTrackEnded, kEventPeerConnectionOnRemoveTrack, - kEventPeerConnectionOnTrack + kEventPeerConnectionOnTrack, + kEventFrameCryptionStateChanged ]; } diff --git a/livekit-react-native-webrtc.podspec b/livekit-react-native-webrtc.podspec index 6a584ae2d..54be3db9d 100644 --- a/livekit-react-native-webrtc.podspec +++ b/livekit-react-native-webrtc.podspec @@ -19,5 +19,5 @@ Pod::Spec.new do |s| s.libraries = 'c', 'sqlite3', 'stdc++' s.framework = 'AudioToolbox','AVFoundation', 'CoreAudio', 'CoreGraphics', 'CoreVideo', 'GLKit', 'VideoToolbox' s.dependency 'React-Core' - s.dependency 'WebRTC-SDK', '~>125.6422.05' + s.dependency 'WebRTC-SDK', '~>125.6422.06' end diff --git a/package.json b/package.json index 957907664..6c6d68e4f 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ }, "scripts": { "lint": "eslint --max-warnings 0 . && tsc --noEmit", + "lintfix": "eslint --fix --max-warnings 0 . && tsc --noEmit", "prepare": "husky install && bob build", "format": "tools/format.sh" }, diff --git a/src/EventEmitter.ts b/src/EventEmitter.ts index 48adb14fb..9f8a95198 100644 --- a/src/EventEmitter.ts +++ b/src/EventEmitter.ts @@ -23,6 +23,7 @@ const NATIVE_EVENTS = [ 'dataChannelDidChangeBufferedAmount', 'mediaStreamTrackMuteChanged', 'mediaStreamTrackEnded', + 'frameCryptionStateChanged', ]; const eventEmitter = new EventEmitter(); diff --git a/src/RTCFrameCryptor.ts b/src/RTCFrameCryptor.ts new file mode 100644 index 000000000..f26939270 --- /dev/null +++ b/src/RTCFrameCryptor.ts @@ -0,0 +1,163 @@ +import { Event, EventTarget, defineEventAttribute } from 'event-target-shim'; +import { NativeModules } from 'react-native'; + +import { addListener, removeListener } from './EventEmitter'; +import Logger from './Logger'; +const { WebRTCModule } = NativeModules; + +const log = new Logger('pc'); + +type FRAME_CRYPTOR_EVENTS = 'onframecryptorstatechanged'; + +interface IRTCDataChannelEventInitDict extends Event.EventInit { + frameCryptor: RTCFrameCryptor; + state: RTCFrameCryptorState; +} + +/** + * @eventClass + * This event is fired whenever the RTCDataChannel has changed in any way. + * @param {FRAME_CRYPTOR_EVENTS} type - The type of event. + * @param {IRTCDataChannelEventInitDict} eventInitDict - The event init properties. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel#events MDN} for details. + */ +export class RTCFrameCryptorStateEvent< +TEventType extends FRAME_CRYPTOR_EVENTS +> extends Event { + /** @eventProperty */ + frameCryptor: RTCFrameCryptor; + /** @eventProperty */ + state: RTCFrameCryptorState; + constructor(type: TEventType, eventInitDict: IRTCDataChannelEventInitDict) { + super(type, eventInitDict); + this.frameCryptor = eventInitDict.frameCryptor; + this.state = eventInitDict.state; + } +} + +type RTCFrameCryptorEventMap = { + onframecryptorstatechanged: RTCFrameCryptorStateEvent<'onframecryptorstatechanged'>; +} + +export enum RTCFrameCryptorState { + FrameCryptorStateNew, + FrameCryptorStateOk, + FrameCryptorStateEncryptionFailed, + FrameCryptorStateDecryptionFailed, + FrameCryptorStateMissingKey, + FrameCryptorStateKeyRatcheted, + FrameCryptorStateInternalError, +} + +export default class RTCFrameCryptor extends EventTarget { + private _frameCryptorId: string; + private _participantId: string; + + constructor(frameCryptorId: string, participantId: string) { + super(); + this._frameCryptorId = frameCryptorId; + this._participantId = participantId; + this._registerEvents(); + } + + get id() { + return this._frameCryptorId; + } + + get participantId() { + return this._participantId; + } + + _cryptorStateFromString(str: string): RTCFrameCryptorState { + switch (str) { + case 'new': + return RTCFrameCryptorState.FrameCryptorStateNew; + case 'ok': + return RTCFrameCryptorState.FrameCryptorStateOk; + case 'decryptionFailed': + return RTCFrameCryptorState.FrameCryptorStateDecryptionFailed; + case 'encryptionFailed': + return RTCFrameCryptorState.FrameCryptorStateEncryptionFailed; + case 'internalError': + return RTCFrameCryptorState.FrameCryptorStateInternalError; + case 'keyRatcheted': + return RTCFrameCryptorState.FrameCryptorStateKeyRatcheted; + case 'missingKey': + return RTCFrameCryptorState.FrameCryptorStateMissingKey; + default: + throw 'Unknown FrameCryptorState: $str'; + } + } + + async setKeyIndex(keyIndex: number): Promise { + const params = { + frameCryptorId: this._frameCryptorId, + keyIndex, + }; + + return WebRTCModule.frameCryptorSetKeyIndex(params) + .then(data => data['result']); + } + + async getKeyIndex(): Promise { + const params = { + frameCryptorId: this._frameCryptorId, + }; + + return WebRTCModule.frameCryptorGetKeyIndex(params) + .then(data => data['keyIndex']); + } + + async setEnabled(enabled: boolean): Promise { + const params = { + frameCryptorId: this._frameCryptorId, + enabled, + }; + + return WebRTCModule.frameCryptorSetEnabled(params) + .then(data => data['result']); + } + + async getEnabled(): Promise { + const params = { + frameCryptorId: this._frameCryptorId, + }; + + return WebRTCModule.frameCryptorGetEnabled(params) + .then(data => data['enabled']); + } + + async dispose(): Promise { + const params = { + frameCryptorId: this._frameCryptorId, + }; + + await WebRTCModule.frameCryptorDispose(params); + removeListener(this); + } + + + _registerEvents(): void { + addListener(this, 'frameCryptionStateChanged', (ev: any) => { + if (ev.participantId !== this._participantId || ev.frameCryptorId !== this._frameCryptorId) { + return; + } + + log.debug(`${this.id} frameCryptionStateChanged ${ev.state}`); + + const initDict = { + frameCryptor: this, + state: ev.state, + }; + + this.dispatchEvent(new RTCFrameCryptorStateEvent('onframecryptorstatechanged', initDict)); + }); + } +} + +/** + * Define the `onxxx` event handlers. + */ +const proto = RTCFrameCryptor.prototype; + +defineEventAttribute(proto, 'onframecryptorstatechanged'); \ No newline at end of file diff --git a/src/RTCFrameCryptorFactory.ts b/src/RTCFrameCryptorFactory.ts new file mode 100644 index 000000000..c80669260 --- /dev/null +++ b/src/RTCFrameCryptorFactory.ts @@ -0,0 +1,102 @@ +import * as base64 from 'base64-js'; +import { NativeModules } from 'react-native'; + +import RTCFrameCryptor from './RTCFrameCryptor'; +import RTCKeyProvider from './RTCKeyProvider'; +import RTCRtpReceiver from './RTCRtpReceiver'; +import RTCRtpSender from './RTCRtpSender'; +const { WebRTCModule } = NativeModules; + +export enum RTCFrameCryptorAlgorithm { + kAesGcm = 0, + // kAesCbc = 1, +} + +export type RTCKeyProviderOptions = { + sharedKey: boolean, + ratchetSalt: string | Uint8Array, + ratchetWindowSize: number, + uncryptedMagicBytes?: Uint8Array, + failureTolerance?: number, + keyRingSize?: number, + discardFrameWhenCryptorNotReady?: boolean +} + +export default class RTCFrameCryptorFactory { + static createFrameCryptorForRtpSender( + participantId: string, + sender: RTCRtpSender, + algorithm: RTCFrameCryptorAlgorithm, + keyProvider: RTCKeyProvider + ): RTCFrameCryptor { + const params = { + 'peerConnectionId': sender._peerConnectionId, + 'rtpSenderId': sender._id, + participantId, + 'keyProviderId': keyProvider._id, + 'type': 'sender', + 'algorithm': algorithm + }; + const result = WebRTCModule.frameCryptorFactoryCreateFrameCryptor(params); + + if (!result) { + throw new Error('Error when creating frame cryptor for sender'); + } + + return new RTCFrameCryptor(result, participantId); + } + static createFrameCryptorForRtpReceiver( + participantId: string, + receiver: RTCRtpReceiver, + algorithm: RTCFrameCryptorAlgorithm, + keyProvider: RTCKeyProvider + ): RTCFrameCryptor { + const params = { + 'peerConnectionId': receiver._peerConnectionId, + 'rtpReceiverId': receiver._id, + participantId, + 'keyProviderId': keyProvider._id, + 'type': 'receiver', + 'algorithm': algorithm + }; + const result = WebRTCModule.frameCryptorFactoryCreateFrameCryptor(params); + + if (!result) { + throw new Error('Error when creating frame cryptor for receiver'); + } + + return new RTCFrameCryptor(result, participantId); + } + + static createDefaultKeyProvider(options: RTCKeyProviderOptions): RTCKeyProvider { + const params = { + 'sharedKey': options.sharedKey, + 'ratchetWindowSize': options.ratchetWindowSize, + 'failureTolerance': options.failureTolerance ?? -1, + 'keyRingSize': options.keyRingSize ?? 16, + 'discardFrameWhenCryptorNotReady': options.discardFrameWhenCryptorNotReady ?? false + }; + + if (typeof options.ratchetSalt === 'string') { + params['ratchetSalt'] = options.ratchetSalt; + params['ratchetSaltIsBase64'] = false; + } else { + const bytes = options.ratchetSalt as Uint8Array; + + params['ratchetSalt'] = base64.fromByteArray(bytes); + params['ratchetSaltIsBase64'] = true; + } + + if (options.uncryptedMagicBytes) { + params['uncryptedMagicBytes'] = base64.fromByteArray(options.uncryptedMagicBytes); + } + + const result = WebRTCModule.frameCryptorFactoryCreateKeyProvider(params); + + if (!result) { + throw new Error('Error when creating key provider!'); + } + + return new RTCKeyProvider(result); + } +} \ No newline at end of file diff --git a/src/RTCKeyProvider.ts b/src/RTCKeyProvider.ts new file mode 100644 index 000000000..b2395b080 --- /dev/null +++ b/src/RTCKeyProvider.ts @@ -0,0 +1,117 @@ +import * as base64 from 'base64-js'; +import { NativeModules } from 'react-native'; +const { WebRTCModule } = NativeModules; + +export enum FrameCryptorState { + FrameCryptorStateNew, + FrameCryptorStateOk, + FrameCryptorStateEncryptionFailed, + FrameCryptorStateDecryptionFailed, + FrameCryptorStateMissingKey, + FrameCryptorStateKeyRatcheted, + FrameCryptorStateInternalError, +} + +export default class RTCKeyProvider { + _id: string; + + constructor(keyProviderId: string) { + this._id = keyProviderId; + } + + async setSharedKey(key: string | Uint8Array, keyIndex = 0) { + const params = { + keyProviderId: this._id, + keyIndex, + }; + + if (typeof key === 'string') { + params['key'] = key; + params['keyIsBase64'] = false; + } else { + params['key'] = base64.fromByteArray(key as Uint8Array); + params['keyIsBase64'] = true; + } + + return WebRTCModule.keyProviderSetSharedKey(params) + .then(data => data['result']); + } + + async ratchetSharedKey(keyIndex = 0): Promise { + const params = { + keyProviderId: this._id, + keyIndex, + }; + + return WebRTCModule.keyProviderRatchetSharedKey(params) + .then(data => base64.toByteArray(data['result'])); + } + + async exportSharedKey(keyIndex = 0): Promise { + const params = { + keyProviderId: this._id, + keyIndex, + }; + + return WebRTCModule.keyProviderExportSharedKey(params) + .then(data => base64.toByteArray(data['result'])); + } + + async setKey(participantId: string, key: string | Uint8Array, keyIndex = 0): Promise { + const params = { + keyProviderId: this._id, + participantId, + keyIndex, + }; + + if (typeof key === 'string') { + params['key'] = key; + params['keyIsBase64'] = false; + } else { + params['key'] = base64.fromByteArray(key as Uint8Array); + params['keyIsBase64'] = true; + } + + return WebRTCModule.keyProviderSetKey(params) + .then(data => data['result']); + } + + async ratchetKey(participantId: string, keyIndex = 0): Promise { + const params = { + keyProviderId: this._id, + participantId, + keyIndex, + }; + + return WebRTCModule.keyProviderRatchetKey(params) + .then(data => base64.toByteArray(data['result'])); + } + + async exportKey(participantId: string, keyIndex = 0): Promise { + const params = { + keyProviderId: this._id, + participantId, + keyIndex, + }; + + return WebRTCModule.keyProviderExportKey(params) + .then(data => base64.toByteArray(data['result'])); + } + + async setSifTrailer(trailer: Uint8Array) { + const params = { + keyProviderId: this._id, + 'sifTrailer': base64.fromByteArray(trailer), + }; + + return WebRTCModule.keyProviderSetSifTrailer(params); + } + + async dispose() { + const params = { + keyProviderId: this._id, + }; + + return WebRTCModule.keyProviderDispose(params); + } +} diff --git a/src/index.ts b/src/index.ts index e47c0042f..ac7cf67e4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,10 @@ import MediaStreamTrackEvent from './MediaStreamTrackEvent'; import permissions from './Permissions'; import RTCAudioSession from './RTCAudioSession'; import RTCErrorEvent from './RTCErrorEvent'; +import RTCFrameCryptor, { RTCFrameCryptorState } from './RTCFrameCryptor'; +import RTCFrameCryptorFactory, { RTCFrameCryptorAlgorithm, RTCKeyProviderOptions } from './RTCFrameCryptorFactory'; import RTCIceCandidate from './RTCIceCandidate'; +import RTCKeyProvider from './RTCKeyProvider'; import RTCPeerConnection from './RTCPeerConnection'; import RTCRtpReceiver from './RTCRtpReceiver'; import RTCRtpSender from './RTCRtpSender'; @@ -42,6 +45,12 @@ export { RTCRtpSender, RTCErrorEvent, RTCAudioSession, + RTCFrameCryptor, + RTCFrameCryptorAlgorithm, + RTCFrameCryptorState, + RTCFrameCryptorFactory, + RTCKeyProvider, + RTCKeyProviderOptions, MediaStream, MediaStreamTrack, mediaDevices,