A Python port of the Invisible Internet Project (I2P)
1"""Tests for RouterIdentity, RouterAddress, RouterInfo."""
2
3import os
4import struct
5
6
7class TestRouterAddress:
8 def test_construct(self):
9 from i2p_data.router import RouterAddress
10 addr = RouterAddress(10, 0, "NTCP2", {"host": "1.2.3.4", "port": "15555"})
11 assert addr.cost == 10
12 assert addr.expiration == 0
13 assert addr.transport == "NTCP2"
14 assert addr.get_host() == "1.2.3.4"
15 assert addr.get_port() == 15555
16
17 def test_roundtrip(self):
18 from i2p_data.router import RouterAddress
19 addr = RouterAddress(5, 0, "SSU2", {"host": "::1", "port": "9999", "key": "abc123"})
20 data = addr.to_bytes()
21 addr2, consumed = RouterAddress.from_bytes(data)
22 assert consumed == len(data)
23 assert addr2.cost == 5
24 assert addr2.transport == "SSU2"
25 assert addr2.get_host() == "::1"
26 assert addr2.get_port() == 9999
27 assert addr2.options["key"] == "abc123"
28
29 def test_no_options(self):
30 from i2p_data.router import RouterAddress
31 addr = RouterAddress(20, 0, "NTCP2")
32 data = addr.to_bytes()
33 addr2, _ = RouterAddress.from_bytes(data)
34 assert addr2.transport == "NTCP2"
35 assert addr2.options == {}
36 assert addr2.get_host() is None
37 assert addr2.get_port() is None
38
39 def test_equality(self):
40 from i2p_data.router import RouterAddress
41 a1 = RouterAddress(10, 0, "NTCP2", {"host": "1.2.3.4"})
42 a2 = RouterAddress(10, 0, "NTCP2", {"host": "1.2.3.4"})
43 a3 = RouterAddress(20, 0, "NTCP2", {"host": "1.2.3.4"})
44 assert a1 == a2
45 assert a1 != a3
46
47 def test_multiple_addresses_roundtrip(self):
48 from i2p_data.router import RouterAddress
49 import io
50 addrs = [
51 RouterAddress(5, 0, "NTCP2", {"host": "10.0.0.1", "port": "443"}),
52 RouterAddress(10, 0, "SSU2", {"host": "10.0.0.1", "port": "9999"}),
53 ]
54 buf = io.BytesIO()
55 for a in addrs:
56 buf.write(a.to_bytes())
57
58 buf.seek(0)
59 recovered = []
60 for _ in range(2):
61 a, _ = RouterAddress.from_stream(buf)
62 recovered.append(a)
63
64 assert recovered[0].transport == "NTCP2"
65 assert recovered[1].transport == "SSU2"
66
67
68class TestRouterIdentity:
69 def _make_router_identity(self):
70 from i2p_data.router import RouterIdentity
71 from i2p_data.key_types import PublicKey, SigningPublicKey, EncType
72 from i2p_data.certificate import Certificate
73 from i2p_crypto.dsa import SigType
74
75 pub = PublicKey(os.urandom(256), EncType.ELGAMAL)
76 sig = SigningPublicKey(os.urandom(128), SigType.DSA_SHA1)
77 cert = Certificate.NULL
78 return RouterIdentity(pub, sig, cert)
79
80 def test_construct(self):
81 from i2p_data.keys_and_cert import KeysAndCert
82 ri = self._make_router_identity()
83 assert isinstance(ri, KeysAndCert)
84 assert len(ri.to_bytes()) == 387
85
86 def test_roundtrip(self):
87 from i2p_data.router import RouterIdentity
88 ri = self._make_router_identity()
89 data = ri.to_bytes()
90 ri2 = RouterIdentity.from_bytes(data)
91 assert ri2.to_bytes() == data
92 assert ri2.public_key == ri.public_key
93
94
95class TestRouterInfo:
96 def _make_signed_router_info(self):
97 """Create a RouterInfo with real EdDSA signature."""
98 from i2p_data.router import RouterIdentity, RouterAddress, RouterInfo
99 from i2p_data.key_types import PublicKey, SigningPublicKey, EncType
100 from i2p_data.certificate import KeyCertificate
101 from i2p_crypto.dsa import SigType, KeyGenerator
102
103 # Generate EdDSA keypair
104 pub_sig, priv_sig = KeyGenerator.generate(SigType.EdDSA_SHA512_Ed25519)
105
106 # Create identity with KEY cert
107 pub_enc = PublicKey(os.urandom(32), EncType.ECIES_X25519)
108 sig_key = SigningPublicKey(pub_sig, SigType.EdDSA_SHA512_Ed25519)
109 cert = KeyCertificate(struct.pack("!HH", 7, 4)) # EdDSA + X25519
110 identity = RouterIdentity(pub_enc, sig_key, cert)
111
112 addresses = [
113 RouterAddress(10, 0, "NTCP2", {"host": "192.168.1.1", "port": "15555"}),
114 ]
115 options = {"router.version": "0.9.62", "caps": "XfR"}
116
117 info = RouterInfo(identity, 1710000000000, addresses, options)
118 info.sign(priv_sig)
119 return info, priv_sig
120
121 def test_sign_and_verify(self):
122 info, _ = self._make_signed_router_info()
123 assert info.verify()
124
125 def test_verify_fails_with_modified_data(self):
126 from i2p_data.router import RouterInfo
127 info, _ = self._make_signed_router_info()
128 # Modify options after signing
129 data = info.to_bytes()
130 # Tamper with a byte in the middle
131 tampered = bytearray(data)
132 tampered[200] ^= 0xFF
133 info2 = RouterInfo.from_bytes(bytes(tampered))
134 assert not info2.verify()
135
136 def test_roundtrip(self):
137 from i2p_data.router import RouterInfo
138 info, _ = self._make_signed_router_info()
139 data = info.to_bytes()
140 info2 = RouterInfo.from_bytes(data)
141 assert info2.identity.to_bytes() == info.identity.to_bytes()
142 assert info2.published == info.published
143 assert len(info2.addresses) == 1
144 assert info2.addresses[0].get_host() == "192.168.1.1"
145 assert info2.options["router.version"] == "0.9.62"
146 assert info2.verify()
147
148 def test_no_addresses(self):
149 from i2p_data.router import RouterIdentity, RouterInfo
150 from i2p_data.key_types import PublicKey, SigningPublicKey, EncType
151 from i2p_data.certificate import KeyCertificate
152 from i2p_crypto.dsa import SigType, KeyGenerator
153
154 pub_sig, priv_sig = KeyGenerator.generate(SigType.EdDSA_SHA512_Ed25519)
155 identity = RouterIdentity(
156 PublicKey(os.urandom(32), EncType.ECIES_X25519),
157 SigningPublicKey(pub_sig, SigType.EdDSA_SHA512_Ed25519),
158 KeyCertificate(struct.pack("!HH", 7, 4))
159 )
160 info = RouterInfo(identity, 1710000000000)
161 info.sign(priv_sig)
162 assert info.verify()
163 assert len(info.addresses) == 0
164
165 def test_unsigned_verify_fails(self):
166 from i2p_data.router import RouterIdentity, RouterInfo
167 from i2p_data.key_types import PublicKey, SigningPublicKey, EncType
168 from i2p_data.certificate import Certificate
169 from i2p_crypto.dsa import SigType
170
171 identity = RouterIdentity(
172 PublicKey(os.urandom(256), EncType.ELGAMAL),
173 SigningPublicKey(os.urandom(128), SigType.DSA_SHA1),
174 Certificate.NULL
175 )
176 info = RouterInfo(identity, 1710000000000)
177 assert not info.verify()