Files
btest-rs/tests/ecsrp5_test.rs
Siavash Sameni 58274da859
All checks were successful
CI / test (push) Successful in 1m18s
Add EC-SRP5 authentication (RouterOS >= 6.43)
Client: auto-detects 03 response and performs EC-SRP5 handshake
Server: --ecsrp5 flag enables Curve25519 Weierstrass EC-SRP5 auth
  btest -s -a admin -p password --ecsrp5

Protocol: [len][payload] framing (no 0x06 handler, unlike Winbox)
Crypto: Curve25519 in Weierstrass form, SHA256, SRP key exchange

Based on MarginResearch/mikrotik_authentication (Apache 2.0).
Verified against MikroTik RouterOS 7.x via MITM protocol analysis.

34 tests (10 unit, 6 EC-SRP5 integration, 8 base integration, 10 doc-tests).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:56:38 +04:00

185 lines
4.5 KiB
Rust

use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
const SERVER_PORT: u16 = 13000;
async fn start_ecsrp5_server(port: u16) {
tokio::spawn(async move {
let _ = btest_rs::server::run_server(
port,
Some("testuser".into()),
Some("testpass".into()),
true, // ecsrp5
)
.await;
});
tokio::time::sleep(Duration::from_millis(200)).await;
}
async fn start_md5_server(port: u16) {
tokio::spawn(async move {
let _ = btest_rs::server::run_server(
port,
Some("testuser".into()),
Some("testpass".into()),
false, // md5
)
.await;
});
tokio::time::sleep(Duration::from_millis(200)).await;
}
async fn start_noauth_server(port: u16) {
tokio::spawn(async move {
let _ = btest_rs::server::run_server(port, None, None, false).await;
});
tokio::time::sleep(Duration::from_millis(200)).await;
}
#[tokio::test]
async fn test_ecsrp5_server_sends_03_response() {
let port = SERVER_PORT;
start_ecsrp5_server(port).await;
let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port))
.await
.unwrap();
// Read HELLO
let mut buf = [0u8; 4];
stream.read_exact(&mut buf).await.unwrap();
assert_eq!(buf, [0x01, 0x00, 0x00, 0x00]);
// Send command (TCP, server TX)
let cmd = btest_rs::protocol::Command::new(
btest_rs::protocol::CMD_PROTO_TCP,
btest_rs::protocol::CMD_DIR_TX,
);
stream.write_all(&cmd.serialize()).await.unwrap();
stream.flush().await.unwrap();
// Should receive EC-SRP5 auth required
stream.read_exact(&mut buf).await.unwrap();
assert_eq!(buf, [0x03, 0x00, 0x00, 0x00], "Expected EC-SRP5 auth response");
}
#[tokio::test]
async fn test_ecsrp5_full_client_auth() {
let port = SERVER_PORT + 1;
start_ecsrp5_server(port).await;
// Use our client with EC-SRP5
let handle = tokio::spawn(async move {
btest_rs::client::run_client(
"127.0.0.1",
port,
btest_rs::protocol::CMD_DIR_TX, // server TX = client RX
false,
0,
0,
Some("testuser".into()),
Some("testpass".into()),
false,
)
.await
});
tokio::time::sleep(Duration::from_secs(3)).await;
handle.abort();
// If we got here without panic, EC-SRP5 auth + data transfer worked
}
#[tokio::test]
async fn test_ecsrp5_wrong_password_fails() {
let port = SERVER_PORT + 2;
start_ecsrp5_server(port).await;
let result = btest_rs::client::run_client(
"127.0.0.1",
port,
btest_rs::protocol::CMD_DIR_TX,
false,
0,
0,
Some("testuser".into()),
Some("wrongpass".into()),
false,
)
.await;
assert!(result.is_err(), "Wrong password should fail");
}
#[tokio::test]
async fn test_md5_auth_still_works() {
let port = SERVER_PORT + 3;
start_md5_server(port).await;
let handle = tokio::spawn(async move {
btest_rs::client::run_client(
"127.0.0.1",
port,
btest_rs::protocol::CMD_DIR_TX,
false,
0,
0,
Some("testuser".into()),
Some("testpass".into()),
false,
)
.await
});
tokio::time::sleep(Duration::from_secs(2)).await;
handle.abort();
}
#[tokio::test]
async fn test_noauth_still_works() {
let port = SERVER_PORT + 4;
start_noauth_server(port).await;
let handle = tokio::spawn(async move {
btest_rs::client::run_client(
"127.0.0.1",
port,
btest_rs::protocol::CMD_DIR_TX,
false,
0,
0,
None,
None,
false,
)
.await
});
tokio::time::sleep(Duration::from_secs(2)).await;
handle.abort();
}
#[tokio::test]
async fn test_ecsrp5_udp_bidirectional() {
let port = SERVER_PORT + 5;
start_ecsrp5_server(port).await;
let handle = tokio::spawn(async move {
btest_rs::client::run_client(
"127.0.0.1",
port,
btest_rs::protocol::CMD_DIR_BOTH,
true, // UDP
0,
0,
Some("testuser".into()),
Some("testpass".into()),
false,
)
.await
});
tokio::time::sleep(Duration::from_secs(3)).await;
handle.abort();
}