A Python port of the Invisible Internet Project (I2P)
at main 217 lines 7.5 kB view raw
1"""Tests for i2p_data.certificate — Certificate type system.""" 2import io 3import struct 4 5import pytest 6 7from i2p_data.certificate import ( 8 Certificate, 9 CertificateType, 10 KeyCertificate, 11) 12 13 14class TestCertificateType: 15 """CertificateType enum values match the I2P spec.""" 16 17 def test_null_is_zero(self): 18 assert CertificateType.NULL == 0 19 20 def test_hashcash_is_one(self): 21 assert CertificateType.HASHCASH == 1 22 23 def test_hidden_is_two(self): 24 assert CertificateType.HIDDEN == 2 25 26 def test_signed_is_three(self): 27 assert CertificateType.SIGNED == 3 28 29 def test_multiple_is_four(self): 30 assert CertificateType.MULTIPLE == 4 31 32 def test_key_is_five(self): 33 assert CertificateType.KEY == 5 34 35 36class TestCertificateNull: 37 """NULL certificate serialization.""" 38 39 def test_null_to_bytes(self): 40 assert Certificate.NULL.to_bytes() == b"\x00\x00\x00" 41 42 def test_null_from_bytes_roundtrip(self): 43 cert = Certificate.from_bytes(b"\x00\x00\x00") 44 assert cert.cert_type == CertificateType.NULL 45 assert cert.payload == b"" 46 assert cert.to_bytes() == b"\x00\x00\x00" 47 48 def test_null_len(self): 49 assert len(Certificate.NULL) == 3 50 51 52class TestCertificateWithPayload: 53 """Certificate with a payload roundtrips correctly.""" 54 55 def test_hashcash_roundtrip(self): 56 payload = b"hashcash-stamp-data" 57 cert = Certificate(CertificateType.HASHCASH, payload) 58 serialized = cert.to_bytes() 59 restored = Certificate.from_bytes(serialized) 60 assert restored.cert_type == CertificateType.HASHCASH 61 assert restored.payload == payload 62 63 def test_serialized_length(self): 64 payload = b"abcdef" 65 cert = Certificate(CertificateType.HASHCASH, payload) 66 assert len(cert) == 3 + len(payload) 67 assert len(cert.to_bytes()) == len(cert) 68 69 70class TestCertificateFromStream: 71 """from_stream reads correctly and positions the stream after the cert.""" 72 73 def test_stream_position_after_read(self): 74 cert_bytes = b"\x00\x00\x00" 75 trailing = b"\xDE\xAD" 76 stream = io.BytesIO(cert_bytes + trailing) 77 cert = Certificate.from_stream(stream) 78 assert cert.cert_type == CertificateType.NULL 79 assert stream.read() == trailing 80 81 def test_stream_with_payload(self): 82 payload = b"\x01\x02\x03\x04" 83 data = struct.pack("!BH", CertificateType.HASHCASH, len(payload)) + payload 84 trailing = b"\xFF" 85 stream = io.BytesIO(data + trailing) 86 cert = Certificate.from_stream(stream) 87 assert cert.cert_type == CertificateType.HASHCASH 88 assert cert.payload == payload 89 assert stream.read() == trailing 90 91 def test_stream_short_header_raises(self): 92 with pytest.raises(ValueError, match="3 bytes"): 93 Certificate.from_stream(io.BytesIO(b"\x00\x00")) 94 95 def test_stream_short_payload_raises(self): 96 # Header says 5 bytes payload but only 2 available 97 data = struct.pack("!BH", CertificateType.HASHCASH, 5) + b"\x01\x02" 98 with pytest.raises(ValueError, match="payload bytes"): 99 Certificate.from_stream(io.BytesIO(data)) 100 101 102class TestKeyCertificate: 103 """KeyCertificate structured access.""" 104 105 def _make_key_cert_payload(self, sig_code: int, enc_code: int, 106 extra: bytes = b"") -> bytes: 107 return struct.pack("!HH", sig_code, enc_code) + extra 108 109 def test_sig_and_enc_type_codes(self): 110 # EdDSA_SHA512_Ed25519 = code 7, ECIES_X25519 = code 4 111 payload = self._make_key_cert_payload(7, 4) 112 kc = KeyCertificate(payload) 113 assert kc.get_sig_type_code() == 7 114 assert kc.get_enc_type_code() == 4 115 116 def test_get_sig_type_returns_enum(self): 117 payload = self._make_key_cert_payload(7, 4) 118 kc = KeyCertificate(payload) 119 from i2p_crypto.dsa import SigType 120 assert kc.get_sig_type() == SigType.EdDSA_SHA512_Ed25519 121 122 def test_extra_key_data(self): 123 extra = b"\xAA\xBB\xCC" 124 payload = self._make_key_cert_payload(7, 4, extra) 125 kc = KeyCertificate(payload) 126 assert kc.get_extra_key_data() == extra 127 128 def test_no_extra_key_data(self): 129 payload = self._make_key_cert_payload(1, 0) 130 kc = KeyCertificate(payload) 131 assert kc.get_extra_key_data() == b"" 132 133 def test_payload_too_short_raises(self): 134 with pytest.raises(ValueError, match=">= 4 bytes"): 135 KeyCertificate(b"\x00\x01") 136 137 def test_payload_exactly_four_bytes(self): 138 payload = self._make_key_cert_payload(0, 0) 139 kc = KeyCertificate(payload) 140 assert kc.get_sig_type_code() == 0 141 assert kc.get_enc_type_code() == 0 142 143 def test_cert_type_is_key(self): 144 payload = self._make_key_cert_payload(7, 4) 145 kc = KeyCertificate(payload) 146 assert kc.cert_type == CertificateType.KEY 147 148 149class TestKeyCertificateAutoDetection: 150 """Certificate.from_bytes auto-creates KeyCertificate for type KEY.""" 151 152 def test_from_bytes_returns_key_certificate(self): 153 payload = struct.pack("!HH", 7, 4) 154 data = struct.pack("!BH", CertificateType.KEY, len(payload)) + payload 155 cert = Certificate.from_bytes(data) 156 assert isinstance(cert, KeyCertificate) 157 assert cert.get_sig_type_code() == 7 158 159 def test_from_bytes_non_key_returns_certificate(self): 160 data = struct.pack("!BH", CertificateType.HASHCASH, 0) 161 cert = Certificate.from_bytes(data) 162 assert type(cert) is Certificate 163 assert not isinstance(cert, KeyCertificate) 164 165 166class TestCertificateEqualityAndHash: 167 """Equality and hashing.""" 168 169 def test_equal_certificates(self): 170 a = Certificate(CertificateType.HASHCASH, b"\x01\x02") 171 b = Certificate(CertificateType.HASHCASH, b"\x01\x02") 172 assert a == b 173 174 def test_unequal_type(self): 175 a = Certificate(CertificateType.HASHCASH, b"") 176 b = Certificate(CertificateType.HIDDEN, b"") 177 assert a != b 178 179 def test_unequal_payload(self): 180 a = Certificate(CertificateType.HASHCASH, b"\x01") 181 b = Certificate(CertificateType.HASHCASH, b"\x02") 182 assert a != b 183 184 def test_hash_equal_for_equal_certs(self): 185 a = Certificate(CertificateType.NULL) 186 b = Certificate(CertificateType.NULL) 187 assert hash(a) == hash(b) 188 189 def test_usable_in_set(self): 190 a = Certificate(CertificateType.NULL) 191 b = Certificate(CertificateType.NULL) 192 c = Certificate(CertificateType.HASHCASH, b"\x01") 193 s = {a, b, c} 194 assert len(s) == 2 195 196 def test_not_equal_to_non_certificate(self): 197 assert Certificate(CertificateType.NULL) != "not a cert" 198 199 200class TestKeyCertificateRoundtrip: 201 """KeyCertificate serialization roundtrip.""" 202 203 def test_roundtrip_with_extra_data(self): 204 extra = b"\x01\x02\x03\x04\x05" 205 payload = struct.pack("!HH", 7, 4) + extra 206 kc = KeyCertificate(payload) 207 serialized = kc.to_bytes() 208 restored = Certificate.from_bytes(serialized) 209 assert isinstance(restored, KeyCertificate) 210 assert restored.get_sig_type_code() == 7 211 assert restored.get_enc_type_code() == 4 212 assert restored.get_extra_key_data() == extra 213 214 def test_len_includes_header_and_payload(self): 215 payload = struct.pack("!HH", 7, 4) + b"\xAA" 216 kc = KeyCertificate(payload) 217 assert len(kc) == 3 + len(payload)