Wallet-session pairing и browser plugin wallet, оплаты пока не работают

This commit is contained in:
AidarKC
2026-06-16 16:23:08 +04:00
parent 5c155ef503
commit 3efa8bb7ee
41 changed files with 3260 additions and 37 deletions
@@ -4,6 +4,7 @@ import org.eclipse.jetty.websocket.api.Session;
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
import server.logic.ws_protocol.JSON.ConnectionContext;
import shine.db.entities.ActiveSessionEntry;
import utils.crypto.HashSHA256Util;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@@ -29,6 +30,8 @@ final class EspPairingSupport {
static final String STATE_REJECTED = "rejected";
static final String STATE_CANCELED = "canceled";
static final String STATE_EXPIRED = "expired";
static final String PASSWORD_HASH_PREFIX = "sha256$";
static final String PASSWORD_HASH_VERSION = "shine-pairing";
private static final SecureRandom RANDOM = new SecureRandom();
private static final char[] BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
@@ -77,6 +80,30 @@ final class EspPairingSupport {
return value;
}
static String normalizePasswordHash(String raw) {
String value = normalizeOpaqueHash(raw);
if (value == null) return null;
if (!value.regionMatches(true, 0, PASSWORD_HASH_PREFIX, 0, PASSWORD_HASH_PREFIX.length())) {
return null;
}
String hex = value.substring(PASSWORD_HASH_PREFIX.length()).trim().toLowerCase(Locale.ROOT);
if (hex.length() != 64) return null;
for (int i = 0; i < hex.length(); i++) {
char ch = hex.charAt(i);
boolean ok = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f');
if (!ok) return null;
}
return PASSWORD_HASH_PREFIX + hex;
}
static String derivePasswordHash(String loginRaw, String passwordRaw) {
String login = loginRaw == null ? "" : loginRaw.trim().toLowerCase(Locale.ROOT);
String password = passwordRaw == null ? "" : passwordRaw;
String preimage = PASSWORD_HASH_VERSION + "|" + login + "|" + password;
byte[] digest = HashSHA256Util.sha256(preimage.getBytes(StandardCharsets.UTF_8));
return PASSWORD_HASH_PREFIX + toHexLower(digest);
}
static String normalizeEncryptedPayload(String raw) {
if (raw == null) return null;
String value = raw.trim();
@@ -149,5 +176,14 @@ final class EspPairingSupport {
return remainder;
}
private static String toHexLower(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(Character.forDigit((b >>> 4) & 0x0F, 16));
sb.append(Character.forDigit(b & 0x0F, 16));
}
return sb.toString();
}
record PairingFingerprint(String shortCode, String fingerprintB58) {}
}
@@ -54,7 +54,11 @@ public class Net_StartEspPairing_Handler implements JsonMessageHandler {
if (!EspPairingSupport.isSupportedPayloadType(payloadType)) {
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_PAYLOAD_TYPE", "payloadType должен быть 1, 2 или 3");
}
String passwordHash = EspPairingSupport.normalizeOpaqueHash(req.getPasswordHash());
String rawPasswordHash = EspPairingSupport.normalizeOpaqueHash(req.getPasswordHash());
String passwordHash = EspPairingSupport.normalizePasswordHash(rawPasswordHash);
if (rawPasswordHash != null && passwordHash == null) {
return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_PASSWORD_HASH_FORMAT", "passwordHash должен быть пустым или иметь формат sha256$<64 hex>");
}
SolanaUserEntry user = SolanaUsersDAO.getInstance().getByLogin(login);
if (user == null) {
@@ -27,7 +27,16 @@ public class Net_UpsertEspPairingSettings_Handler implements JsonMessageHandler
}
boolean enabled = req.getEnabled() != null && req.getEnabled();
String passwordHash = EspPairingSupport.normalizeOpaqueHash(req.getPasswordHash());
String rawPasswordHash = EspPairingSupport.normalizeOpaqueHash(req.getPasswordHash());
String passwordHash = EspPairingSupport.normalizePasswordHash(rawPasswordHash);
if (rawPasswordHash != null && passwordHash == null) {
return NetExceptionResponseFactory.error(
req,
WireCodes.Status.BAD_REQUEST,
"BAD_PASSWORD_HASH_FORMAT",
"passwordHash должен быть пустым или иметь формат sha256$<64 hex>"
);
}
int ttlSeconds = EspPairingSupport.normalizeTtlSeconds(req.getTtlSeconds());
long now = System.currentTimeMillis();
@@ -9,9 +9,11 @@ import test.it.utils.ws.WsSession;
import utils.crypto.Ed25519Util;
import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.SolanaUserEntry;
import utils.crypto.HashSHA256Util;
import java.time.Duration;
import java.util.Base64;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.*;
@@ -38,7 +40,7 @@ public class IT_07_EspPairing {
sessionLogin2Steps(clientWs, clientSession, 1, "Web", t, r);
String passwordHash = "argon2id$v=19$m=65536,t=2,p=1$test$esp_pairing_hash";
String passwordHash = derivePairingHash(LOGIN, "test-pairing-password");
String upsertResp = clientWs.call(
"UpsertEspPairingSettings",
JsonBuilders.upsertEspPairingSettings(true, passwordHash, 180),
@@ -218,6 +220,17 @@ public class IT_07_EspPairing {
SolanaUsersDAO.getInstance().insert(entry);
}
private static String derivePairingHash(String login, String password) {
String preimage = "shine-pairing|" + login.trim().toLowerCase() + "|" + password;
byte[] digest = HashSHA256Util.sha256(preimage.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder(64);
for (byte b : digest) {
sb.append(Character.forDigit((b >>> 4) & 0x0F, 16));
sb.append(Character.forDigit(b & 0x0F, 16));
}
return "sha256$" + sb;
}
private record Session(String sessionId, String sessionKey, byte[] sessionPrivKey, String storagePwd) {}
private record SessionMaterial(String sessionKey, byte[] sessionPrivKey) {}
}