Files
n_signer/tools/http_test.py

142 lines
4.8 KiB
Python

#!/usr/bin/env python3
"""
http_test.py — test the HTTP listener mode with curl-like requests.
Starts nsigner in HTTP mode, sends requests via urllib, and verifies
the responses.
"""
import json
import os
import subprocess
import sys
import time
import urllib.request
NSIGNER = "./build/nsigner"
PAD_DIR = "/media/user/Music/pads"
PAD_SPEC = "333e9902db839d9d"
MNEMONIC_FILE = ".test_mnemonic"
MNEMONIC_TMP = ".test_mnemonic_http.tmp"
PORT = 11111
def main():
# Reset pad offset
state_path = f"{PAD_DIR}/333e9902db839d9d7f1f6aaa30f392a77c9abd011dd6274d9d3cf167361a789e.state"
with open(state_path, "w") as f:
f.write("offset=32\n")
# Prepare mnemonic temp file
with open(MNEMONIC_FILE) as f:
mnemonic = f.read().strip()
with open(MNEMONIC_TMP, "w") as f:
f.write(mnemonic + "\n")
# Start nsigner in HTTP mode
shell_cmd = (
f"exec 3<{MNEMONIC_TMP} 2>/dev/null; "
f"exec {NSIGNER} --listen http:127.0.0.1:{PORT} --mnemonic-fd 3 "
f"--otp-pad-dir {PAD_DIR} --otp-pad {PAD_SPEC} "
f"--otp-allow-blkback --allow-all"
)
proc = subprocess.Popen(
["bash", "-c", shell_cmd],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
)
time.sleep(2.0) # let the signer start
if proc.poll() is not None:
err = proc.stderr.read().decode()
print(f"ERROR: nsigner exited early (code {proc.returncode})")
print(f"stderr: {err}")
try:
os.unlink(MNEMONIC_TMP)
except OSError:
pass
return 1
try:
url = f"http://127.0.0.1:{PORT}/"
# Test 1: get_public_key
print("=== Test 1: get_public_key via HTTP ===")
req_data = json.dumps({
"id": "1",
"method": "get_public_key",
"params": [{"role": "main"}],
}).encode()
req = urllib.request.Request(url, data=req_data,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=5) as resp:
result = json.loads(resp.read().decode())
print(f"Response: {json.dumps(result)[:120]}...")
if "result" not in result:
print("ERROR: no result in get_public_key response")
return 1
pubkey = result["result"].strip('"')
print(f"Public key: {pubkey}")
# Test 2: otp_encrypt
print("\n=== Test 2: otp_encrypt via HTTP ===")
import base64
pt_b64 = base64.b64encode(b"Hello, OTP via HTTP!").decode()
req_data = json.dumps({
"id": "2",
"method": "otp_encrypt",
"params": [pt_b64, {"encoding": "ascii"}],
}).encode()
req = urllib.request.Request(url, data=req_data,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=5) as resp:
result = json.loads(resp.read().decode())
print(f"Response: {json.dumps(result)[:200]}...")
if "result" not in result:
print("ERROR: no result in otp_encrypt response")
return 1
enc_result = json.loads(result["result"])
ciphertext = enc_result["ciphertext"]
print(f"Pad offset: {enc_result['pad_offset_before']} -> {enc_result['pad_offset_after']}")
print(f"Ciphertext (first 60 chars): {ciphertext[:60]}...")
# Test 3: otp_decrypt
print("\n=== Test 3: otp_decrypt via HTTP ===")
req_data = json.dumps({
"id": "3",
"method": "otp_decrypt",
"params": [ciphertext, {"encoding": "ascii"}],
}).encode()
req = urllib.request.Request(url, data=req_data,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=5) as resp:
result = json.loads(resp.read().decode())
print(f"Response: {json.dumps(result)[:200]}...")
if "result" not in result:
print("ERROR: no result in otp_decrypt response")
return 1
dec_result = json.loads(result["result"])
recovered = base64.b64decode(dec_result["plaintext"]).decode()
print(f"Recovered plaintext: {recovered}")
if recovered == "Hello, OTP via HTTP!":
print("\n=== HTTP ROUND-TRIP SUCCESS ===")
return 0
else:
print("\n=== HTTP ROUND-TRIP FAILED ===")
print(f"Expected: Hello, OTP via HTTP!")
print(f"Got: {recovered}")
return 1
finally:
proc.terminate()
try:
proc.wait(timeout=3)
except subprocess.TimeoutExpired:
proc.kill()
try:
os.unlink(MNEMONIC_TMP)
except OSError:
pass
if __name__ == "__main__":
sys.exit(main())