SHA256
Добавить SendSignal и remote AddBlock через homeserver
This commit is contained in:
+4
@@ -89,6 +89,7 @@ import server.logic.ws_protocol.JSON.handlers.connections.entyties.Net_ListConta
|
||||
import server.logic.ws_protocol.JSON.messages.Net_AckSessionDelivery_Handler;
|
||||
import server.logic.ws_protocol.JSON.messages.Net_CallInviteBroadcast_Handler;
|
||||
import server.logic.ws_protocol.JSON.messages.Net_CallSignalToSession_Handler;
|
||||
import server.logic.ws_protocol.JSON.messages.Net_SendSignal_Handler;
|
||||
import server.logic.ws_protocol.JSON.messages.Net_ReceiveIncomingMessage_Handler;
|
||||
import server.logic.ws_protocol.JSON.messages.Net_SendDirectMessage_Handler;
|
||||
import server.logic.ws_protocol.JSON.messages.Net_SendMessagePair_Handler;
|
||||
@@ -97,6 +98,7 @@ import server.logic.ws_protocol.JSON.messages.Net_UpsertPushToken_Handler;
|
||||
import server.logic.ws_protocol.JSON.messages.entyties.Net_AckSessionDelivery_Request;
|
||||
import server.logic.ws_protocol.JSON.messages.entyties.Net_CallInviteBroadcast_Request;
|
||||
import server.logic.ws_protocol.JSON.messages.entyties.Net_CallSignalToSession_Request;
|
||||
import server.logic.ws_protocol.JSON.messages.entyties.Net_SendSignal_Request;
|
||||
import server.logic.ws_protocol.JSON.messages.entyties.Net_ReceiveIncomingMessage_Request;
|
||||
import server.logic.ws_protocol.JSON.messages.entyties.Net_SendDirectMessage_Request;
|
||||
import server.logic.ws_protocol.JSON.messages.entyties.Net_SendMessagePair_Request;
|
||||
@@ -196,6 +198,7 @@ public final class JsonHandlerRegistry {
|
||||
Map.entry("AckSessionDelivery", new Net_AckSessionDelivery_Handler()),
|
||||
Map.entry("CallInviteBroadcast", new Net_CallInviteBroadcast_Handler()),
|
||||
Map.entry("CallSignalToSession", new Net_CallSignalToSession_Handler()),
|
||||
Map.entry("SendSignal", new Net_SendSignal_Handler()),
|
||||
|
||||
// --- system ---
|
||||
Map.entry("Ping", new Net_Ping_Handler()),
|
||||
@@ -274,6 +277,7 @@ public final class JsonHandlerRegistry {
|
||||
Map.entry("AckSessionDelivery", Net_AckSessionDelivery_Request.class),
|
||||
Map.entry("CallInviteBroadcast", Net_CallInviteBroadcast_Request.class),
|
||||
Map.entry("CallSignalToSession", Net_CallSignalToSession_Request.class),
|
||||
Map.entry("SendSignal", Net_SendSignal_Request.class),
|
||||
|
||||
// --- system ---
|
||||
Map.entry("Ping", Net_Ping_Request.class),
|
||||
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
package server.logic.ws_protocol.JSON.messages;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.Base64Ws;
|
||||
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.messages.entyties.Net_SendSignal_Request;
|
||||
import server.logic.ws_protocol.JSON.messages.entyties.Net_SendSignal_Response;
|
||||
import server.logic.ws_protocol.JSON.push.WsEventSender;
|
||||
import server.logic.ws_protocol.JSON.utils.AuthKeyUtils;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.dao.SolanaUsersDAO;
|
||||
import shine.db.entities.ActiveSessionEntry;
|
||||
import shine.db.entities.SolanaUserEntry;
|
||||
import utils.crypto.Ed25519Util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class Net_SendSignal_Handler implements JsonMessageHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(Net_SendSignal_Handler.class);
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
private static final String TARGET_MODE_SINGLE = "single_session";
|
||||
private static final String TARGET_MODE_ALL = "all_sessions";
|
||||
private static final long ALLOWED_SKEW_MS = 30_000L;
|
||||
|
||||
@Override
|
||||
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) throws Exception {
|
||||
Net_SendSignal_Request req = (Net_SendSignal_Request) baseRequest;
|
||||
if (ctx == null || !ctx.isAuthenticatedUser() || ctx.getActiveSession() == null) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.UNVERIFIED, "NOT_AUTHENTICATED", "Требуется авторизация");
|
||||
}
|
||||
|
||||
String fromLogin = safe(ctx.getLogin());
|
||||
String fromSessionId = safe(ctx.getSessionId());
|
||||
ActiveSessionEntry activeSession = ctx.getActiveSession();
|
||||
|
||||
String toRequest = safe(req.getToLogin());
|
||||
String targetMode = safe(req.getTargetMode()).toLowerCase();
|
||||
String targetSessionId = safe(req.getTargetSessionId());
|
||||
String signalType = safe(req.getSignalType());
|
||||
String signalRequestId = safe(req.getSignalRequestId());
|
||||
String data = req.getData() == null ? "" : req.getData();
|
||||
Long timeMs = req.getTimeMs();
|
||||
String sessionSignatureB64 = safe(req.getSessionSignatureB64());
|
||||
String clientSignatureB64 = safe(req.getClientSignatureB64());
|
||||
|
||||
if (toRequest.isBlank() || signalType.isBlank() || signalRequestId.isBlank() || timeMs == null || sessionSignatureB64.isBlank()) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_FIELDS", "toLogin/targetMode/signalType/signalRequestId/timeMs/sessionSignatureB64 обязательны");
|
||||
}
|
||||
if (!TARGET_MODE_SINGLE.equals(targetMode) && !TARGET_MODE_ALL.equals(targetMode)) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_TARGET_MODE", "Поддерживаются targetMode=single_session или all_sessions");
|
||||
}
|
||||
if (TARGET_MODE_SINGLE.equals(targetMode) && targetSessionId.isBlank()) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_FIELDS", "Для single_session нужен targetSessionId");
|
||||
}
|
||||
if (TARGET_MODE_ALL.equals(targetMode) && !targetSessionId.isBlank()) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_FIELDS", "Для all_sessions нельзя передавать targetSessionId");
|
||||
}
|
||||
|
||||
long nowMs = System.currentTimeMillis();
|
||||
if (Math.abs(nowMs - timeMs) > ALLOWED_SKEW_MS) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "TIME_SKEW", "Время клиента отличается от сервера более чем на 30 секунд");
|
||||
}
|
||||
|
||||
SolanaUserEntry senderUser = ctx.getSolanaUser();
|
||||
if (senderUser == null || senderUser.getClientKey() == null || senderUser.getClientKey().isBlank()) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.SERVER_DATA_ERROR, "NO_CLIENT_KEY", "Для пользователя не найден client key");
|
||||
}
|
||||
|
||||
SolanaUserEntry targetUser = SolanaUsersDAO.getInstance().getByLogin(toRequest);
|
||||
if (targetUser == null) {
|
||||
return NetExceptionResponseFactory.error(req, 404, "USER_NOT_FOUND", "Пользователь не найден");
|
||||
}
|
||||
String toLogin = safe(targetUser.getLogin());
|
||||
|
||||
String digestB64;
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
digestB64 = Base64Ws.encode(digest.digest(data.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (Exception e) {
|
||||
log.warn("SendSignal: failed to hash signal data (fromLogin={}, signalType={})", fromLogin, signalType, e);
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_DATA", "Не удалось обработать data");
|
||||
}
|
||||
|
||||
String sessionPreimage = buildSessionPreimage(fromLogin, fromSessionId, toLogin, targetMode, targetSessionId, signalType, signalRequestId, timeMs, digestB64);
|
||||
if (!verifySignature(activeSession.getSessionKey(), sessionPreimage, sessionSignatureB64, "sessionKey")) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_SESSION_SIGNATURE", "Некорректная подпись session key");
|
||||
}
|
||||
|
||||
if (!clientSignatureB64.isBlank()) {
|
||||
String clientPreimage = buildClientPreimage(fromLogin, fromSessionId, toLogin, targetMode, targetSessionId, signalType, signalRequestId, timeMs, digestB64);
|
||||
if (!verifySignature(senderUser.getClientKey(), clientPreimage, clientSignatureB64, "clientKey")) {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_CLIENT_SIGNATURE", "Некорректная подпись client key");
|
||||
}
|
||||
}
|
||||
|
||||
List<ConnectionContext> targets = resolveTargets(targetMode, toLogin, targetSessionId);
|
||||
if (targets.isEmpty()) {
|
||||
String code = TARGET_MODE_SINGLE.equals(targetMode) ? "SESSION_NOT_FOUND" : "NO_TARGET_SESSIONS";
|
||||
String msg = TARGET_MODE_SINGLE.equals(targetMode) ? "Целевая сессия не найдена" : "Нет активных сессий для доставки сигнала";
|
||||
return NetExceptionResponseFactory.error(req, 404, code, msg);
|
||||
}
|
||||
|
||||
List<String> deliveredSessionIds = new ArrayList<>();
|
||||
for (ConnectionContext targetCtx : targets) {
|
||||
String eventId = server.logic.ws_protocol.JSON.utils.NetIdGenerator.eventId("evt");
|
||||
ObjectNode payload = MAPPER.createObjectNode();
|
||||
payload.put("eventId", eventId);
|
||||
payload.put("fromLogin", fromLogin);
|
||||
payload.put("fromSessionId", fromSessionId);
|
||||
payload.put("toLogin", toLogin);
|
||||
payload.put("targetMode", targetMode);
|
||||
payload.put("targetSessionId", safe(targetCtx.getSessionId()));
|
||||
payload.put("signalType", signalType);
|
||||
payload.put("signalRequestId", signalRequestId);
|
||||
payload.put("data", data);
|
||||
payload.put("timeMs", timeMs);
|
||||
payload.put("sessionSignatureB64", sessionSignatureB64);
|
||||
payload.put("clientSignatureB64", clientSignatureB64);
|
||||
payload.put("dataSha256B64", digestB64);
|
||||
if (WsEventSender.sendEvent(targetCtx, "IncomingSignal", eventId, payload)) {
|
||||
deliveredSessionIds.add(safe(targetCtx.getSessionId()));
|
||||
}
|
||||
}
|
||||
|
||||
if (deliveredSessionIds.isEmpty()) {
|
||||
return NetExceptionResponseFactory.error(req, 404, "DELIVERY_FAILED", "Не удалось доставить сигнал ни в одну целевую сессию");
|
||||
}
|
||||
|
||||
Net_SendSignal_Response resp = new Net_SendSignal_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
resp.setDeliveredCount(deliveredSessionIds.size());
|
||||
resp.setDeliveredSessionIds(deliveredSessionIds);
|
||||
return resp;
|
||||
}
|
||||
|
||||
private static String buildSessionPreimage(String fromLogin,
|
||||
String fromSessionId,
|
||||
String toLogin,
|
||||
String targetMode,
|
||||
String targetSessionId,
|
||||
String signalType,
|
||||
String signalRequestId,
|
||||
long timeMs,
|
||||
String dataSha256B64) {
|
||||
return "SEND_SIGNAL_SESSION:"
|
||||
+ fromLogin + ":"
|
||||
+ fromSessionId + ":"
|
||||
+ toLogin + ":"
|
||||
+ targetMode + ":"
|
||||
+ targetSessionId + ":"
|
||||
+ signalType + ":"
|
||||
+ signalRequestId + ":"
|
||||
+ timeMs + ":"
|
||||
+ dataSha256B64;
|
||||
}
|
||||
|
||||
private static String buildClientPreimage(String fromLogin,
|
||||
String fromSessionId,
|
||||
String toLogin,
|
||||
String targetMode,
|
||||
String targetSessionId,
|
||||
String signalType,
|
||||
String signalRequestId,
|
||||
long timeMs,
|
||||
String dataSha256B64) {
|
||||
return "SEND_SIGNAL_CLIENT:"
|
||||
+ fromLogin + ":"
|
||||
+ fromSessionId + ":"
|
||||
+ toLogin + ":"
|
||||
+ targetMode + ":"
|
||||
+ targetSessionId + ":"
|
||||
+ signalType + ":"
|
||||
+ signalRequestId + ":"
|
||||
+ timeMs + ":"
|
||||
+ dataSha256B64;
|
||||
}
|
||||
|
||||
private static boolean verifySignature(String publicKeyValue, String preimage, String signatureB64, String fieldName) throws Exception {
|
||||
byte[] publicKey32 = AuthKeyUtils.parseEd25519PublicKey(publicKeyValue, fieldName);
|
||||
byte[] signature64 = Base64Ws.decodeLen(signatureB64, 64, "signatureB64");
|
||||
return Ed25519Util.verify(preimage.getBytes(StandardCharsets.UTF_8), signature64, publicKey32);
|
||||
}
|
||||
|
||||
private static List<ConnectionContext> resolveTargets(String targetMode, String toLogin, String targetSessionId) {
|
||||
List<ConnectionContext> targets = new ArrayList<>();
|
||||
if (TARGET_MODE_SINGLE.equals(targetMode)) {
|
||||
ConnectionContext targetCtx = ActiveConnectionsRegistry.getInstance().getBySessionId(targetSessionId);
|
||||
if (targetCtx != null && toLogin.equalsIgnoreCase(safe(targetCtx.getLogin()))) {
|
||||
targets.add(targetCtx);
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
Set<ConnectionContext> onlineTargets = ActiveConnectionsRegistry.getInstance().getByLogin(toLogin);
|
||||
onlineTargets.stream()
|
||||
.filter(item -> item != null && item.getWsSession() != null && item.getWsSession().isOpen())
|
||||
.sorted(Comparator.comparing(ConnectionContext::getSessionId, Comparator.nullsLast(String::compareTo)))
|
||||
.forEach(targets::add);
|
||||
return targets;
|
||||
}
|
||||
|
||||
private static String safe(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package server.logic.ws_protocol.JSON.messages.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
|
||||
public class Net_SendSignal_Request extends Net_Request {
|
||||
private String toLogin;
|
||||
private String targetMode;
|
||||
private String targetSessionId;
|
||||
private String signalType;
|
||||
private String signalRequestId;
|
||||
private String data;
|
||||
private Long timeMs;
|
||||
private String sessionSignatureB64;
|
||||
private String clientSignatureB64;
|
||||
|
||||
public String getToLogin() {
|
||||
return toLogin;
|
||||
}
|
||||
|
||||
public void setToLogin(String toLogin) {
|
||||
this.toLogin = toLogin;
|
||||
}
|
||||
|
||||
public String getTargetMode() {
|
||||
return targetMode;
|
||||
}
|
||||
|
||||
public void setTargetMode(String targetMode) {
|
||||
this.targetMode = targetMode;
|
||||
}
|
||||
|
||||
public String getTargetSessionId() {
|
||||
return targetSessionId;
|
||||
}
|
||||
|
||||
public void setTargetSessionId(String targetSessionId) {
|
||||
this.targetSessionId = targetSessionId;
|
||||
}
|
||||
|
||||
public String getSignalType() {
|
||||
return signalType;
|
||||
}
|
||||
|
||||
public void setSignalType(String signalType) {
|
||||
this.signalType = signalType;
|
||||
}
|
||||
|
||||
public String getSignalRequestId() {
|
||||
return signalRequestId;
|
||||
}
|
||||
|
||||
public void setSignalRequestId(String signalRequestId) {
|
||||
this.signalRequestId = signalRequestId;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Long getTimeMs() {
|
||||
return timeMs;
|
||||
}
|
||||
|
||||
public void setTimeMs(Long timeMs) {
|
||||
this.timeMs = timeMs;
|
||||
}
|
||||
|
||||
public String getSessionSignatureB64() {
|
||||
return sessionSignatureB64;
|
||||
}
|
||||
|
||||
public void setSessionSignatureB64(String sessionSignatureB64) {
|
||||
this.sessionSignatureB64 = sessionSignatureB64;
|
||||
}
|
||||
|
||||
public String getClientSignatureB64() {
|
||||
return clientSignatureB64;
|
||||
}
|
||||
|
||||
public void setClientSignatureB64(String clientSignatureB64) {
|
||||
this.clientSignatureB64 = clientSignatureB64;
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package server.logic.ws_protocol.JSON.messages.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Net_SendSignal_Response extends Net_Response {
|
||||
private int deliveredCount;
|
||||
private List<String> deliveredSessionIds = new ArrayList<>();
|
||||
|
||||
public int getDeliveredCount() {
|
||||
return deliveredCount;
|
||||
}
|
||||
|
||||
public void setDeliveredCount(int deliveredCount) {
|
||||
this.deliveredCount = deliveredCount;
|
||||
}
|
||||
|
||||
public List<String> getDeliveredSessionIds() {
|
||||
return deliveredSessionIds;
|
||||
}
|
||||
|
||||
public void setDeliveredSessionIds(List<String> deliveredSessionIds) {
|
||||
this.deliveredSessionIds = deliveredSessionIds;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user