Обмен ключами шифрования DH
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
import { Call } from "@/app/components/Call/Call";
|
||||
import { createContext, useState } from "react";
|
||||
import { useConsoleLogger } from "@/app/hooks/useConsoleLogger";
|
||||
import { createContext, useEffect, useRef, useState } from "react";
|
||||
import nacl from 'tweetnacl';
|
||||
import { useSender } from "../ProtocolProvider/useSender";
|
||||
import { PacketSignal, SignalType } from "../ProtocolProvider/protocol/packets/packet.signal";
|
||||
import { usePacket } from "../ProtocolProvider/usePacket";
|
||||
import { usePublicKey } from "../AccountProvider/usePublicKey";
|
||||
|
||||
|
||||
export interface CallContextValue {
|
||||
@@ -13,15 +19,29 @@ export interface CallContextValue {
|
||||
setSound: (sound: boolean) => void;
|
||||
duration: number;
|
||||
setShowCallView: (show: boolean) => void;
|
||||
getKeyCast: () => string;
|
||||
accept: () => void;
|
||||
}
|
||||
|
||||
export enum CallState {
|
||||
CONNECTING,
|
||||
KEY_EXCHANGE,
|
||||
ACTIVE,
|
||||
ENDED,
|
||||
INCOMING
|
||||
}
|
||||
|
||||
export enum CallRole {
|
||||
/**
|
||||
* Вызывающая сторона, которая инициирует звонок
|
||||
*/
|
||||
CALLER,
|
||||
/**
|
||||
* Принимающая сторона, которая отвечает на звонок и принимает его
|
||||
*/
|
||||
CALLEE
|
||||
}
|
||||
|
||||
export const CallContext = createContext<CallContextValue | null>(null);
|
||||
export interface CallProviderProps {
|
||||
children: React.ReactNode;
|
||||
@@ -34,18 +54,142 @@ export function CallProvider(props : CallProviderProps) {
|
||||
const [sound, setSound] = useState<boolean>(true);
|
||||
const [duration, setDuration] = useState<number>(0);
|
||||
const [showCallView, setShowCallView] = useState<boolean>(callState == CallState.INCOMING);
|
||||
const {info} = useConsoleLogger("CallProvider");
|
||||
const [sessionKeys, setSessionKeys] = useState<nacl.BoxKeyPair | null>(null);
|
||||
const send = useSender();
|
||||
const publicKey = usePublicKey();
|
||||
|
||||
const roleRef = useRef<CallRole | null>(null);
|
||||
const [sharedSecret, setSharedSecret] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
console.info("TRACE -> ", sharedSecret)
|
||||
}, [sharedSecret]);
|
||||
|
||||
usePacket(26, (packet: PacketSignal) => {
|
||||
const signalType = packet.getSignalType();
|
||||
if(activeCall){
|
||||
/**
|
||||
* У нас уже есть активный звонок, игнорируем все сигналы, кроме сигналов от текущего звонка
|
||||
*/
|
||||
if(packet.getSrc() != activeCall){
|
||||
info("Received signal for another call, ignoring");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(signalType == SignalType.CALL){
|
||||
/**
|
||||
* Нам поступает звонок
|
||||
*/
|
||||
setActiveCall(packet.getSrc());
|
||||
setCallState(CallState.INCOMING);
|
||||
setShowCallView(true);
|
||||
}
|
||||
if(signalType == SignalType.KEY_EXCHANGE && roleRef.current == CallRole.CALLER){
|
||||
/**
|
||||
* Другая сторона сгенерировала ключи для сессии и отправила нам публичную часть,
|
||||
* теперь мы можем создать общую секретную сессию для шифрования звонка
|
||||
*/
|
||||
const sharedPublic = packet.getSharedPublic();
|
||||
if(!sharedPublic){
|
||||
info("Received key exchange signal without shared public key");
|
||||
return;
|
||||
}
|
||||
const sessionKeys = generateSessionKeys();
|
||||
const computedSharedSecret = nacl.box.before(Buffer.from(sharedPublic, 'hex'), sessionKeys.secretKey);
|
||||
setSharedSecret(Buffer.from(computedSharedSecret).toString('hex'));
|
||||
info("Generated shared secret for call session: " + Buffer.from(computedSharedSecret).toString('hex'));
|
||||
/**
|
||||
* Нам нужно отправить свой публичный ключ другой стороне, чтобы она тоже могла создать общую секретную сессию
|
||||
*/
|
||||
const signalPacket = new PacketSignal();
|
||||
signalPacket.setSrc(publicKey);
|
||||
signalPacket.setDst(packet.getDst());
|
||||
signalPacket.setSignalType(SignalType.KEY_EXCHANGE);
|
||||
signalPacket.setSharedPublic(Buffer.from(sessionKeys.publicKey).toString('hex'));
|
||||
send(signalPacket);
|
||||
}
|
||||
if(signalType == SignalType.KEY_EXCHANGE && roleRef.current == CallRole.CALLEE){
|
||||
/**
|
||||
* Мы отправили свою публичную часть ключа другой стороне,
|
||||
* теперь мы получили ее публичную часть и можем создать общую
|
||||
* секретную сессию для шифрования звонка
|
||||
*/
|
||||
const sharedPublic = packet.getSharedPublic();
|
||||
if(!sharedPublic){
|
||||
info("Received key exchange signal without shared public key");
|
||||
return;
|
||||
}
|
||||
if(!sessionKeys){
|
||||
info("Received key exchange signal but session keys are not generated");
|
||||
return;
|
||||
}
|
||||
const computedSharedSecret = nacl.box.before(Buffer.from(sharedPublic, 'hex'), sessionKeys.secretKey);
|
||||
info("Generated shared secret for call session: " + Buffer.from(computedSharedSecret).toString('hex'));
|
||||
setSharedSecret(Buffer.from(computedSharedSecret).toString('hex'));
|
||||
}
|
||||
if(signalType == SignalType.ACTIVE_CALL) {
|
||||
setCallState(CallState.ACTIVE);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const generateSessionKeys = () => {
|
||||
const sessionKeys = nacl.box.keyPair();
|
||||
info("Generated keys for call session, len: " + sessionKeys.publicKey.length);
|
||||
setSessionKeys(sessionKeys);
|
||||
return sessionKeys;
|
||||
}
|
||||
|
||||
const call = (dialog: string) => {
|
||||
setActiveCall(dialog);
|
||||
setCallState(CallState.CONNECTING);
|
||||
setShowCallView(true);
|
||||
const signalPacket = new PacketSignal();
|
||||
signalPacket.setDst(dialog);
|
||||
signalPacket.setSignalType(SignalType.CALL);
|
||||
send(signalPacket);
|
||||
roleRef.current = CallRole.CALLER;
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
setActiveCall("");
|
||||
setCallState(CallState.ENDED);
|
||||
setShowCallView(false);
|
||||
setSessionKeys(null);
|
||||
setDuration(0);
|
||||
roleRef.current = null;
|
||||
}
|
||||
|
||||
const accept = () => {
|
||||
if(callState != CallState.INCOMING){
|
||||
/**
|
||||
* Нечего принимать
|
||||
*/
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Звонок принят, генерируем ключи для сессии и отправляем их другой стороне для установления защищенного канала связи
|
||||
*/
|
||||
const keys = generateSessionKeys();
|
||||
const signalPacket = new PacketSignal();
|
||||
signalPacket.setDst(activeCall);
|
||||
signalPacket.setSignalType(SignalType.KEY_EXCHANGE);
|
||||
signalPacket.setSharedPublic(Buffer.from(keys.publicKey).toString('hex'));
|
||||
send(signalPacket);
|
||||
setCallState(CallState.KEY_EXCHANGE);
|
||||
roleRef.current = CallRole.CALLEE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает слепок ключа для отображения в UI
|
||||
* чтобы не показывать настоящий ключ
|
||||
* @returns
|
||||
*/
|
||||
const getKeyCast = () => {
|
||||
if(!sessionKeys){
|
||||
return "";
|
||||
}
|
||||
return Buffer.from(sessionKeys.secretKey).toString('hex');
|
||||
}
|
||||
|
||||
const context = {
|
||||
@@ -58,7 +202,9 @@ export function CallProvider(props : CallProviderProps) {
|
||||
setMuted,
|
||||
setSound,
|
||||
duration,
|
||||
setShowCallView
|
||||
setShowCallView,
|
||||
getKeyCast,
|
||||
accept
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user