Šepot — end-to-end encrypted messenger
Initial public release. Anonymous on-device identity (no phone number, e-mail, or password), Signal-protocol cryptography (X3DH + PQXDH, Double Ratchet, sealed sender) via libsignal, a metadata-minimizing relay, group chats, disappearing messages, encrypted local storage, optional Tor transport, and a UI localized into Czech, English, German, Russian, and Italian. Monorepo: Flutter client (app/), Rust relay + wire types (rust/), protobuf definitions (proto/), self-host deploy (deploy/). Licensed under AGPL-3.0 with an app-store distribution exception (§7).
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
@Tags(['tor'])
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:sepot/core/constants/app_constants.dart';
|
||||
import 'package:sepot/core/network/relay_transport.dart';
|
||||
|
||||
/// Živý smoke [TorTransport] proti REÁLNÉMU onion relayi přes Tor.
|
||||
/// Vyžaduje běžící SOCKS proxy na 127.0.0.1:9050 (Orbot / lokální tor) —
|
||||
/// jinak se testy SAMY skipnou, aby `flutter test` na strojích bez toru
|
||||
/// neselhal.
|
||||
/// flutter test test/network/tor_transport_live_test.dart --tags tor
|
||||
void main() {
|
||||
const socksPort = 9050;
|
||||
|
||||
Future<bool> socksUp() async {
|
||||
try {
|
||||
final s = await Socket.connect('127.0.0.1', socksPort,
|
||||
timeout: const Duration(seconds: 1));
|
||||
s.destroy();
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
test('REST /v1/server-info přes onion → 200 + trust_root', () async {
|
||||
if (!await socksUp()) {
|
||||
markTestSkipped('Tor SOCKS 127.0.0.1:$socksPort neběží');
|
||||
return;
|
||||
}
|
||||
final t = TorTransport(socksPort: socksPort);
|
||||
final resp = await t
|
||||
.httpClient()
|
||||
.get(Uri.parse('${AppConstants.relayOnion}/v1/server-info'))
|
||||
.timeout(const Duration(seconds: 60));
|
||||
expect(resp.statusCode, 200);
|
||||
final body = jsonDecode(resp.body) as Map<String, dynamic>;
|
||||
expect(body['trust_root'], isNotNull);
|
||||
expect(body['capabilities'], contains('sealed-sender'));
|
||||
t.close();
|
||||
}, timeout: const Timeout(Duration(seconds: 90)));
|
||||
|
||||
test('WS /v1/ws přes onion → upgrade projde (obousměrný tunel)', () async {
|
||||
if (!await socksUp()) {
|
||||
markTestSkipped('Tor SOCKS 127.0.0.1:$socksPort neběží');
|
||||
return;
|
||||
}
|
||||
final t = TorTransport(socksPort: socksPort);
|
||||
final wsUrl = AppConstants.relayOnion.replaceFirst('http', 'ws');
|
||||
final ch = t.connectWs(Uri.parse('$wsUrl/v1/ws'));
|
||||
// Dokončení `ready` = HTTP Upgrade (101) prošel skrz Tor tunel k onion.
|
||||
await ch.ready.timeout(const Duration(seconds: 60));
|
||||
expect(ch.closeCode, isNull); // spojení stojí
|
||||
await ch.sink.close();
|
||||
t.close();
|
||||
}, timeout: const Timeout(Duration(seconds: 90)));
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:sepot/core/network/relay_transport.dart';
|
||||
import 'package:socks5_proxy/socks_server.dart';
|
||||
|
||||
/// Deterministické ověření [TorTransport]: REST i WS musí projít lokálním
|
||||
/// SOCKS5 serverem k lokálnímu cílovému serveru. Žádná síť ven, žádný
|
||||
/// libsignal — jen transportní vrstva. Doplňuje živý smoke proti onion
|
||||
/// (`tor_transport_live_test.dart`, tag `tor`).
|
||||
void main() {
|
||||
group('TorTransport přes lokální SOCKS5 proxy', () {
|
||||
late SocksServer proxy;
|
||||
late int proxyPort;
|
||||
late HttpServer target;
|
||||
late int targetPort;
|
||||
|
||||
setUp(() async {
|
||||
// SOCKS5 proxy, která každý požadavek prostě forwarduje na cíl.
|
||||
proxy = SocksServer();
|
||||
proxy.connections.listen((c) async {
|
||||
try {
|
||||
await c.forward();
|
||||
} catch (_) {
|
||||
// klient zavřel dřív — pro test nepodstatné
|
||||
}
|
||||
}).onError((_) {});
|
||||
final proxySock = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
|
||||
proxyPort = proxySock.port;
|
||||
await proxy.addServerSocket(proxySock);
|
||||
|
||||
// Cílový server: echo HTTP + echo WebSocket.
|
||||
target = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
|
||||
targetPort = target.port;
|
||||
target.listen((req) async {
|
||||
if (WebSocketTransformer.isUpgradeRequest(req)) {
|
||||
final ws = await WebSocketTransformer.upgrade(req);
|
||||
ws.listen((data) => ws.add(data)); // echo zpět
|
||||
} else {
|
||||
req.response
|
||||
..statusCode = 200
|
||||
..write('ozvěna:${req.uri.path}');
|
||||
await req.response.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await target.close(force: true);
|
||||
await proxy.stop();
|
||||
});
|
||||
|
||||
test('REST GET projde přes proxy k cíli', () async {
|
||||
final t = TorTransport(socksHost: '127.0.0.1', socksPort: proxyPort);
|
||||
final resp = await t
|
||||
.httpClient()
|
||||
.get(Uri.parse('http://127.0.0.1:$targetPort/ahoj'))
|
||||
.timeout(const Duration(seconds: 10));
|
||||
expect(resp.statusCode, 200);
|
||||
expect(resp.body, 'ozvěna:/ahoj');
|
||||
t.close();
|
||||
});
|
||||
|
||||
test('WebSocket se otevře přes proxy a echuje', () async {
|
||||
final t = TorTransport(socksHost: '127.0.0.1', socksPort: proxyPort);
|
||||
final ch = t.connectWs(Uri.parse('ws://127.0.0.1:$targetPort/v1/ws'));
|
||||
await ch.ready.timeout(const Duration(seconds: 10));
|
||||
ch.sink.add('ping-přes-tor');
|
||||
final echo = await ch.stream.first.timeout(const Duration(seconds: 10));
|
||||
expect(echo, 'ping-přes-tor');
|
||||
await ch.sink.close();
|
||||
t.close();
|
||||
});
|
||||
|
||||
test('DirectTransport míří na cíl přímo (kontrolní vzorek)', () async {
|
||||
final t = DirectTransport();
|
||||
final resp = await t
|
||||
.httpClient()
|
||||
.get(Uri.parse('http://127.0.0.1:$targetPort/primo'))
|
||||
.timeout(const Duration(seconds: 10));
|
||||
expect(resp.statusCode, 200);
|
||||
expect(resp.body, 'ozvěna:/primo');
|
||||
t.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user