Ура прошли все тесты новой версии авторификации!!

Осталось что то доделать поправить с лишними закрытиями сервака
This commit is contained in:
AidarKC 2026-01-23 22:10:14 +03:00
parent 4430615117
commit 43b0efb4d3
5 changed files with 93 additions and 30 deletions

View File

@ -35,7 +35,7 @@ import java.util.Base64;
* - Сервер сохраняет sessionPubKeyB64 в active_sessions.session_key. * - Сервер сохраняет sessionPubKeyB64 в active_sessions.session_key.
* *
* Подпись deviceKey (Ed25519) проверяется над строкой (UTF-8): * Подпись deviceKey (Ed25519) проверяется над строкой (UTF-8):
* AUTH_CREATE_SESSION:{login}:{timeMs}:{authNonce}:{sessionPubKeyB64}:{storagePwd} * AUTH_CREATE_SESSION:{login}:{timeMs}:{authNonce}
* *
* На выходе: * На выходе:
* - создаётся запись active_sessions * - создаётся запись active_sessions
@ -106,7 +106,7 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
return err; return err;
} }
// Проверим, что ключ декодируется в 32 байта // Проверим, что sessionPubKeyB64 декодируется в 32 байта
byte[] sessionPubKey32; byte[] sessionPubKey32;
try { try {
sessionPubKey32 = decodeBase64Any(sessionPubKeyB64); sessionPubKey32 = decodeBase64Any(sessionPubKeyB64);
@ -183,8 +183,6 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
login, login,
authNonce, authNonce,
timeMs, timeMs,
sessionPubKeyB64,
storagePwd,
signatureB64 signatureB64
); );
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
@ -288,15 +286,14 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
String login, String login,
String authNonce, String authNonce,
long timeMs, long timeMs,
String sessionPubKeyB64,
String storagePwd,
String signatureB64 String signatureB64
) throws IllegalArgumentException { ) throws IllegalArgumentException {
// deviceKey (pub, 32)
byte[] publicKey32 = Ed25519Util.keyFromBase64(user.getDeviceKey()); byte[] publicKey32 = Ed25519Util.keyFromBase64(user.getDeviceKey());
byte[] signature64 = decodeBase64Any(signatureB64); byte[] signature64 = decodeBase64Any(signatureB64);
String preimageStr = "AUTH_CREATE_SESSION:" + login + ":" + timeMs + ":" + authNonce + ":" + sessionPubKeyB64 + ":" + storagePwd; String preimageStr = "AUTH_CREATE_SESSION:" + login + ":" + timeMs + ":" + authNonce;
byte[] preimage = preimageStr.getBytes(StandardCharsets.UTF_8); byte[] preimage = preimageStr.getBytes(StandardCharsets.UTF_8);
return Ed25519Util.verify(preimage, signature64, publicKey32); return Ed25519Util.verify(preimage, signature64, publicKey32);
@ -309,11 +306,15 @@ public class Net_CreateAuthSession__Handler implements JsonMessageHandler {
} }
private static byte[] decodeBase64Any(String s) throws IllegalArgumentException { private static byte[] decodeBase64Any(String s) throws IllegalArgumentException {
if (s == null) throw new IllegalArgumentException("base64 is null");
String x = s.trim();
if (x.isEmpty()) throw new IllegalArgumentException("base64 is empty");
// сначала url-safe, потом обычный // сначала url-safe, потом обычный
try { try {
return Base64.getUrlDecoder().decode(s); return Base64.getUrlDecoder().decode(x);
} catch (IllegalArgumentException ignore) { } catch (IllegalArgumentException ignore) {
return Base64.getDecoder().decode(s); return Base64.getDecoder().decode(x);
} }
} }
} }

View File

@ -0,0 +1,6 @@
ура всё прошло.
Другой надеюсь крайний на сегодня вопрос .
Сформулируй Подробное туду с тем что переделать сервак что бы он не обрывал соединение сразу из за любой ошибки (а видимо только в каких то случах гд енадо ... но в каких :)

View File

@ -15,8 +15,17 @@ import java.util.List;
*/ */
public class IT_RunAllMain { public class IT_RunAllMain {
/**
* Настройка поведения прогона:
* - true : остановить запуск сразу после первого упавшего теста
* - false : прогнать все тесты до конца, даже если некоторые упали
*/
private static final boolean STOP_ON_FIRST_FAIL = true;
public static void main(String[] args) { public static void main(String[] args) {
int failed = runAll(); int failed = runAll();
// при желании можно вернуть код выхода ОС:
// System.exit(failed == 0 ? 0 : 1);
} }
public static int runAll() { public static int runAll() {
@ -24,13 +33,30 @@ public class IT_RunAllMain {
List<String> summaries = new ArrayList<>(); List<String> summaries = new ArrayList<>();
int failed = 0; int failed = 0;
TestLog.title("IT RUN: запуск всех тестов подряд"); TestLog.title("IT RUN: запуск всех тестов подряд"
+ (STOP_ON_FIRST_FAIL ? " (STOP_ON_FIRST_FAIL=ON)" : " (STOP_ON_FIRST_FAIL=OFF)"));
String s1 = IT_01_AddUser.run(); summaries.add(s1); if (s1.contains("FAIL:")) failed++; String s1 = IT_01_AddUser.run(); summaries.add(s1);
String s2 = IT_02_Sessions.run(); summaries.add(s2); if (s2.contains("FAIL:")) failed++; if (s1.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); }
String s3 = IT_03_AddBlock_NoAuth.run(); summaries.add(s3); if (s3.contains("FAIL:")) failed++;
String s4 = IT_04_UserParams_NoAuth.run(); summaries.add(s4); if (s4.contains("FAIL:")) failed++;
String s2 = IT_02_Sessions.run(); summaries.add(s2);
if (s2.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); }
String s3 = IT_03_AddBlock_NoAuth.run(); summaries.add(s3);
if (s3.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); }
String s4 = IT_04_UserParams_NoAuth.run(); summaries.add(s4);
if (s4.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); }
return finish(summaries, failed);
}
private static int finishEarly(List<String> summaries, int failed) {
TestLog.boom("⛔ Остановка прогона: найден FAIL, STOP_ON_FIRST_FAIL=ON");
return finish(summaries, failed);
}
private static int finish(List<String> summaries, int failed) {
TestLog.title("IT RUN RESULT (per test)"); TestLog.title("IT RUN RESULT (per test)");
for (String s : summaries) System.out.println(s); for (String s : summaries) System.out.println(s);
@ -39,4 +65,4 @@ public class IT_RunAllMain {
return failed; return failed;
} }
} }

View File

@ -9,13 +9,14 @@ import java.util.concurrent.ConcurrentHashMap;
/** /**
* TestConfig конфиг IT тестов: * TestConfig конфиг IT тестов:
* - 3 пользователя (TestUser1/2/3) * - 3 пользователя (TestUser1/2/3)
* - ключи по login через map (device/solana/blockchain) * - ключи по login через map (device/solana/blockchain/session)
* - blockchainName = login + "-" + "001" * - blockchainName = login + "-" + "001"
* *
* Важно: * Важно:
* - privateKey = Ed25519Util.generatePrivateKeyFromString(login) (sha256, 32 bytes) * - privateKey = Ed25519Util.generatePrivateKeyFromString(seed) (sha256(seed) => 32 bytes)
* - publicKey = Ed25519Util.derivePublicKey(privateKey) * - publicKey = Ed25519Util.derivePublicKey(privateKey)
* - пока device/solana/blockchain ключи одинаковые (один seed на login) * - device/solana/blockchain ключи пока одинаковые (seed = login)
* - session ключ отдельный (seed = "session:" + login) чтобы SessionLogin был честнее.
*/ */
public final class TestConfig { public final class TestConfig {
@ -58,6 +59,10 @@ public final class TestConfig {
private static final Map<String, byte[]> bchPriv = new ConcurrentHashMap<>(); private static final Map<String, byte[]> bchPriv = new ConcurrentHashMap<>();
private static final Map<String, byte[]> bchPub = new ConcurrentHashMap<>(); private static final Map<String, byte[]> bchPub = new ConcurrentHashMap<>();
// NEW: session keys (для SessionLogin v2)
private static final Map<String, byte[]> sessionPriv = new ConcurrentHashMap<>();
private static final Map<String, byte[]> sessionPub = new ConcurrentHashMap<>();
static { static {
initUserKeys(LOGIN()); initUserKeys(LOGIN());
initUserKeys(LOGIN2()); initUserKeys(LOGIN2());
@ -65,6 +70,7 @@ public final class TestConfig {
} }
private static void initUserKeys(String login) { private static void initUserKeys(String login) {
// seed = login
byte[] priv = Ed25519Util.generatePrivateKeyFromString(login); // sha256(login) => 32 bytes byte[] priv = Ed25519Util.generatePrivateKeyFromString(login); // sha256(login) => 32 bytes
byte[] pub = Ed25519Util.derivePublicKey(priv); byte[] pub = Ed25519Util.derivePublicKey(priv);
@ -77,6 +83,13 @@ public final class TestConfig {
bchPriv.put(login, priv); bchPriv.put(login, priv);
bchPub.put(login, pub); bchPub.put(login, pub);
// session seed = "session:" + login (отдельно!)
byte[] sPriv = Ed25519Util.generatePrivateKeyFromString("session:" + login);
byte[] sPub = Ed25519Util.derivePublicKey(sPriv);
sessionPriv.put(login, sPriv);
sessionPub.put(login, sPub);
} }
// ============ requested getters (with your names) ============ // ============ requested getters (with your names) ============
@ -90,11 +103,18 @@ public final class TestConfig {
public static byte[] getBlockchainPrivatKey(String login) { return cloneOrThrow(bchPriv.get(login), "bchPriv", login); } public static byte[] getBlockchainPrivatKey(String login) { return cloneOrThrow(bchPriv.get(login), "bchPriv", login); }
public static byte[] getBlockchainPublicKey(String login) { return cloneOrThrow(bchPub.get(login), "bchPub", login); } public static byte[] getBlockchainPublicKey(String login) { return cloneOrThrow(bchPub.get(login), "bchPub", login); }
// NEW: session getters
public static byte[] getSessionPrivatKey(String login) { return cloneOrThrow(sessionPriv.get(login), "sessionPriv", login); }
public static byte[] getSessionPublicKey(String login) { return cloneOrThrow(sessionPub.get(login), "sessionPub", login); }
// ============ base64 helpers ============ // ============ base64 helpers ============
public static String devicePublicKeyB64(String login) { return Base64.getEncoder().encodeToString(getDevicePublicKey(login)); } public static String devicePublicKeyB64(String login) { return Base64.getEncoder().encodeToString(getDevicePublicKey(login)); }
public static String solanaPublicKeyB64(String login) { return Base64.getEncoder().encodeToString(getSolanaPublicKey(login)); } public static String solanaPublicKeyB64(String login) { return Base64.getEncoder().encodeToString(getSolanaPublicKey(login)); }
public static String blockchainPublicKeyB64(String login) { return Base64.getEncoder().encodeToString(getBlockchainPublicKey(login)); } public static String blockchainPublicKeyB64(String login) { return Base64.getEncoder().encodeToString(getBlockchainPublicKey(login)); }
// NEW: session pub b64 helper
public static String sessionPublicKeyB64(String login) { return Base64.getEncoder().encodeToString(getSessionPublicKey(login)); }
// ============ backward-compatible helpers for "user1" ============ // ============ backward-compatible helpers for "user1" ============
public static String BCH_NAME() { return getBlockchainName(LOGIN()); } public static String BCH_NAME() { return getBlockchainName(LOGIN()); }
public static String BCH_NAME2() { return getBlockchainName(LOGIN2()); } public static String BCH_NAME2() { return getBlockchainName(LOGIN2()); }
@ -114,6 +134,11 @@ public final class TestConfig {
public static String DEVICE2_PUBKEY_B64() { return devicePublicKeyB64(LOGIN2()); } public static String DEVICE2_PUBKEY_B64() { return devicePublicKeyB64(LOGIN2()); }
public static String DEVICE3_PUBKEY_B64() { return devicePublicKeyB64(LOGIN3()); } public static String DEVICE3_PUBKEY_B64() { return devicePublicKeyB64(LOGIN3()); }
// NEW: session pub b64 compat
public static String SESSION_PUBKEY_B64() { return sessionPublicKeyB64(LOGIN()); }
public static String SESSION2_PUBKEY_B64() { return sessionPublicKeyB64(LOGIN2()); }
public static String SESSION3_PUBKEY_B64() { return sessionPublicKeyB64(LOGIN3()); }
// ============ misc ============ // ============ misc ============
public static String fakeStoragePwd() { public static String fakeStoragePwd() {
return "pwd-" + System.nanoTime(); return "pwd-" + System.nanoTime();

View File

@ -59,13 +59,20 @@ public final class JsonBuilders {
} }
// ---------------- CreateAuthSession (v2) ---------------- // ---------------- CreateAuthSession (v2) ----------------
// v2: sessionKey генерируется на клиенте, на сервер отправляем только sessionPubKey (base64). // v2: sessionKey генерируется/хранится на клиенте, на сервер отправляем sessionPubKeyB64 (base64).
// подпись шага CreateAuthSession всё ещё делается deviceKey: "AUTHORIFICATED:" + timeMs + authNonce //
// ВАЖНО (новое правило):
// Подпись CreateAuthSession делается ТОЛЬКО deviceKey над строкой:
// preimage = "AUTH_CREATE_SESSION:" + login + ":" + timeMs + ":" + authNonce
//
// storagePwd и sessionPubKeyB64 НЕ входят в preimage.
public static String createAuthSessionV2(String login, String authNonce, String storagePwd, String sessionPubKeyB64) { public static String createAuthSessionV2(String login, String authNonce, String storagePwd, String sessionPubKeyB64) {
long timeMs = System.currentTimeMillis(); long timeMs = System.currentTimeMillis();
// подпись делаем devicePrivKey
byte[] devicePriv = TestConfig.getDevicePrivatKey(login); byte[] devicePriv = TestConfig.getDevicePrivatKey(login);
String sigB64 = signAuthorificated(authNonce, timeMs, devicePriv); String sigB64 = signAuthCreateSession(login, timeMs, authNonce, devicePriv);
String requestId = TestIds.next("create"); String requestId = TestIds.next("create");
return """ return """
@ -106,6 +113,8 @@ public final class JsonBuilders {
} }
// ---------------- SessionLogin (v2) ---------------- // ---------------- SessionLogin (v2) ----------------
// Подпись SessionLogin по-прежнему делается sessionPrivKey:
// preimage = "SESSION_LOGIN:" + sessionId + ":" + timeMs + ":" + nonce
public static String sessionLogin(String sessionId, String nonce, byte[] sessionPrivKey) { public static String sessionLogin(String sessionId, String nonce, byte[] sessionPrivKey) {
long timeMs = System.currentTimeMillis(); long timeMs = System.currentTimeMillis();
@ -136,8 +145,6 @@ public final class JsonBuilders {
"op": "ListSessions", "op": "ListSessions",
"requestId": "%s", "requestId": "%s",
"payload": { "payload": {
"timeMs": %d,
"signatureB64": "%s"
} }
} }
""".formatted(requestId, timeMs, signatureB64); """.formatted(requestId, timeMs, signatureB64);
@ -153,9 +160,7 @@ public final class JsonBuilders {
"op": "CloseActiveSession", "op": "CloseActiveSession",
"requestId": "%s", "requestId": "%s",
"payload": { "payload": {
"sessionId": "%s", "sessionId": "%s"
"timeMs": %d,
"signatureB64": "%s"
} }
} }
""".formatted(requestId, sessionId, timeMs, signatureB64); """.formatted(requestId, sessionId, timeMs, signatureB64);
@ -175,12 +180,12 @@ public final class JsonBuilders {
} }
/** /**
* Подпись для режима AUTH_IN_PROGRESS: * Подпись CreateAuthSession(v2):
* preimage = "AUTHORIFICATED:" + timeMs + authNonce * preimage = "AUTH_CREATE_SESSION:" + login + ":" + timeMs + ":" + authNonce
* подписываем devicePrivKey. * подписываем devicePrivKey.
*/ */
public static String signAuthorificated(String authNonce, long timeMs, byte[] devicePrivKey) { public static String signAuthCreateSession(String login, long timeMs, String authNonce, byte[] devicePrivKey) {
String preimageStr = "AUTHORIFICATED:" + timeMs + authNonce; String preimageStr = "AUTH_CREATE_SESSION:" + login + ":" + timeMs + ":" + authNonce;
byte[] preimage = preimageStr.getBytes(StandardCharsets.UTF_8); byte[] preimage = preimageStr.getBytes(StandardCharsets.UTF_8);
byte[] sig = Ed25519Util.sign(preimage, devicePrivKey); byte[] sig = Ed25519Util.sign(preimage, devicePrivKey);
return Base64.getEncoder().encodeToString(sig); return Base64.getEncoder().encodeToString(sig);