1"""
2Unit tests for stem.descriptor.hidden_service for version 2.
3"""
4
5import datetime
6import functools
7import unittest
8
9import stem.descriptor
10import stem.prereq
11import test.require
12
13from stem.descriptor.hidden_service import (
14  REQUIRED_V2_FIELDS,
15  DecryptionFailure,
16  HiddenServiceDescriptorV2,
17)
18
19from test.unit.descriptor import (
20  get_resource,
21  base_expect_invalid_attr,
22  base_expect_invalid_attr_for_text,
23)
24
25MESSAGE_BLOCK = """
26-----BEGIN MESSAGE-----
27%s
28-----END MESSAGE-----\
29"""
30
31EXPECTED_DDG_PERMANENT_KEY = """\
32-----BEGIN RSA PUBLIC KEY-----
33MIGJAoGBAJ/SzzgrXPxTlFrKVhXh3buCWv2QfcNgncUpDpKouLn3AtPH5Ocys0jE
34aZSKdvaiQ62md2gOwj4x61cFNdi05tdQjS+2thHKEm/KsB9BGLSLBNJYY356bupg
35I5gQozM65ENelfxYlysBjJ52xSDBd8C4f/p9umdzaaaCmzXG/nhzAgMBAAE=
36-----END RSA PUBLIC KEY-----\
37"""
38
39EXPECTED_DDG_INTRODUCTION_POINTS_ENCODED = """\
40-----BEGIN MESSAGE-----
41aW50cm9kdWN0aW9uLXBvaW50IGl3a2k3N3h0YnZwNnF2ZWRmcndkem5jeHMzY2th
42eWV1CmlwLWFkZHJlc3MgMTc4LjYyLjIyMi4xMjkKb25pb24tcG9ydCA0NDMKb25p
43b24ta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFL
44OTRCRVlJSFo0S2RFa2V5UGhiTENwUlc1RVNnKzJXUFFock00eXVLWUd1cTh3Rldn
45dW1aWVI5CmsvV0EvL0ZZWE1CejBiQitja3Vacy9ZdTluSytITHpwR2FwVjBjbHN0
46NEdVTWNCSW5VQ3pDY3BqSlRRc1FEZ20KMy9ZM2NxaDBXNTVnT0NGaG9tUTQvMVdP
47WWc3WUNqazRYWUhKRTIwT2RHMkxsNXpvdEs2ZkFnTUJBQUU9Ci0tLS0tRU5EIFJT
48QSBQVUJMSUMgS0VZLS0tLS0Kc2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVC
49TElDIEtFWS0tLS0tCk1JR0pBb0dCQUpYbUpiOGxTeWRNTXFDZ0NnZmd2bEIyRTVy
50cGQ1N2t6L0FxZzcvZDFIS2MzK2w1UW9Vdkh5dXkKWnNBbHlrYThFdTUzNGhsNDFv
51cUVLcEFLWWNNbjFUTTB2cEpFR05WT2MrMDVCSW54STloOWYwTWcwMVBEMHRZdQpH
52Y0xIWWdCemNyZkVtS3dNdE04V0VtY01KZDduMnVmZmFBdko4NDZXdWJiZVY3TVcx
53WWVoQWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBLRVktLS0tLQppbnRyb2R1
54Y3Rpb24tcG9pbnQgZW00Z2prNmVpaXVhbGhtbHlpaWZyemM3bGJ0cnNiaXAKaXAt
55YWRkcmVzcyA0Ni40LjE3NC41Mgpvbmlvbi1wb3J0IDQ0Mwpvbmlvbi1rZXkKLS0t
56LS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pBb0dCQUxCbWhkRjV3SHhI
57cnBMU21qQVpvdHR4MjIwKzk5NUZkTU9PdFpOalJ3MURCU3ByVVpacXR4V2EKUDhU
58S3BIS3p3R0pLQ1ZZSUlqN2xvaGJ2OVQ5dXJtbGZURTA1VVJHZW5ab2lmT0ZOejNZ
59d01KVFhTY1FFQkoxMAo5aVdOTERUc2tMekRLQ0FiR2hibi9NS3dPZllHQmhOVGxq
60ZHlUbU5ZNUVDUmJSempldjl2QWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBL
61RVktLS0tLQpzZXJ2aWNlLWtleQotLS0tLUJFR0lOIFJTQSBQVUJMSUMgS0VZLS0t
62LS0KTUlHSkFvR0JBTXhNSG9BbXJiVU1zeGlJQ3AzaVRQWWdobjBZdWVLSHgyMTl3
63dThPL1E1MVF5Y1ZWTHBYMjdkMQpoSlhrUEIzM1hRQlhzQlM3U3hzU3NTQ1EzR0V1
64clFKN0d1QkxwWUlSL3Zxc2FrRS9sOHdjMkNKQzVXVWh5RkZrCisxVFdJVUk1dHhu
65WEx5V0NSY0tEVXJqcWRvc0RhRG9zZ0hGZzIzTW54K3hYY2FRL2ZyQi9BZ01CQUFF
66PQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCmludHJvZHVjdGlvbi1wb2lu
67dCBqcWhmbDM2NHgzdXBlNmxxbnhpem9sZXdsZnJzdzJ6eQppcC1hZGRyZXNzIDYy
68LjIxMC44Mi4xNjkKb25pb24tcG9ydCA0NDMKb25pb24ta2V5Ci0tLS0tQkVHSU4g
69UlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFQVWtxeGdmWWR3MFBtL2c2TWJo
70bVZzR0tsdWppZm1raGRmb0VldXpnbyt3bkVzR3Z3VWVienJ6CmZaSlJ0MGNhWEZo
71bkNHZ1FEMklnbWFyVWFVdlAyNGZYby80bVl6TGNQZUk3Z1puZXVBUUpZdm05OFl2
72OXZPSGwKTmFNL1d2RGtDc0ozR1ZOSjFIM3dMUFFSSTN2N0tiTnVjOXRDT1lsL3Iw
73OU9oVmFXa3phakFnTUJBQUU9Ci0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K
74c2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pB
75b0dCQUxieDhMZXFSb1Avcjl3OWhqd0Q0MVlVbTdQbzY5N3hSdHl0RjBNY3lMQ1M3
76R1JpVVluamk3S1kKZmVwWGR2Ti9KbDVxUUtISUJiNjAya3VPVGwwcE44UStZZUZV
77U0lJRGNtUEJMcEJEaEgzUHZyUU1jR1ZhaU9XSAo4dzBITVpDeGd3QWNDQzUxdzVW
78d2l1bXhFSk5CVmNac094MG16TjFDbG95KzkwcTBsRlhMQWdNQkFBRT0KLS0tLS1F
79TkQgUlNBIFBVQkxJQyBLRVktLS0tLQoK
80-----END MESSAGE-----\
81"""
82
83EXPECTED_DDG_INTRODUCTION_POINTS_CONTENT = b"""\
84introduction-point iwki77xtbvp6qvedfrwdzncxs3ckayeu
85ip-address 178.62.222.129
86onion-port 443
87onion-key
88-----BEGIN RSA PUBLIC KEY-----
89MIGJAoGBAK94BEYIHZ4KdEkeyPhbLCpRW5ESg+2WPQhrM4yuKYGuq8wFWgumZYR9
90k/WA//FYXMBz0bB+ckuZs/Yu9nK+HLzpGapV0clst4GUMcBInUCzCcpjJTQsQDgm
913/Y3cqh0W55gOCFhomQ4/1WOYg7YCjk4XYHJE20OdG2Ll5zotK6fAgMBAAE=
92-----END RSA PUBLIC KEY-----
93service-key
94-----BEGIN RSA PUBLIC KEY-----
95MIGJAoGBAJXmJb8lSydMMqCgCgfgvlB2E5rpd57kz/Aqg7/d1HKc3+l5QoUvHyuy
96ZsAlyka8Eu534hl41oqEKpAKYcMn1TM0vpJEGNVOc+05BInxI9h9f0Mg01PD0tYu
97GcLHYgBzcrfEmKwMtM8WEmcMJd7n2uffaAvJ846WubbeV7MW1YehAgMBAAE=
98-----END RSA PUBLIC KEY-----
99introduction-point em4gjk6eiiualhmlyiifrzc7lbtrsbip
100ip-address 46.4.174.52
101onion-port 443
102onion-key
103-----BEGIN RSA PUBLIC KEY-----
104MIGJAoGBALBmhdF5wHxHrpLSmjAZottx220+995FdMOOtZNjRw1DBSprUZZqtxWa
105P8TKpHKzwGJKCVYIIj7lohbv9T9urmlfTE05URGenZoifOFNz3YwMJTXScQEBJ10
1069iWNLDTskLzDKCAbGhbn/MKwOfYGBhNTljdyTmNY5ECRbRzjev9vAgMBAAE=
107-----END RSA PUBLIC KEY-----
108service-key
109-----BEGIN RSA PUBLIC KEY-----
110MIGJAoGBAMxMHoAmrbUMsxiICp3iTPYghn0YueKHx219wu8O/Q51QycVVLpX27d1
111hJXkPB33XQBXsBS7SxsSsSCQ3GEurQJ7GuBLpYIR/vqsakE/l8wc2CJC5WUhyFFk
112+1TWIUI5txnXLyWCRcKDUrjqdosDaDosgHFg23Mnx+xXcaQ/frB/AgMBAAE=
113-----END RSA PUBLIC KEY-----
114introduction-point jqhfl364x3upe6lqnxizolewlfrsw2zy
115ip-address 62.210.82.169
116onion-port 443
117onion-key
118-----BEGIN RSA PUBLIC KEY-----
119MIGJAoGBAPUkqxgfYdw0Pm/g6MbhmVsGKlujifmkhdfoEeuzgo+wnEsGvwUebzrz
120fZJRt0caXFhnCGgQD2IgmarUaUvP24fXo/4mYzLcPeI7gZneuAQJYvm98Yv9vOHl
121NaM/WvDkCsJ3GVNJ1H3wLPQRI3v7KbNuc9tCOYl/r09OhVaWkzajAgMBAAE=
122-----END RSA PUBLIC KEY-----
123service-key
124-----BEGIN RSA PUBLIC KEY-----
125MIGJAoGBALbx8LeqRoP/r9w9hjwD41YUm7Po697xRtytF0McyLCS7GRiUYnji7KY
126fepXdvN/Jl5qQKHIBb602kuOTl0pN8Q+YeFUSIIDcmPBLpBDhH3PvrQMcGVaiOWH
1278w0HMZCxgwAcCC51w5VwiumxEJNBVcZsOx0mzN1Cloy+90q0lFXLAgMBAAE=
128-----END RSA PUBLIC KEY-----
129
130"""
131
132EXPECTED_DDG_SIGNATURE = """\
133-----BEGIN SIGNATURE-----
134VKMmsDIUUFOrpqvcQroIZjDZTKxqNs88a4M9Te8cR/ZvS7H2nffv6iQs0tom5X4D
1354Dy4iZiy+pwYxdHfaOxmdpgMCRvgPb34MExWr5YemH0QuGtnlp5Wxr8GYaAQVuZX
136cZjQLW0juUYCbgIGdxVEBnlEt2rgBSM9+1oR7EAfV1U=
137-----END SIGNATURE-----\
138"""
139
140EXPECT_POINT_1_ONION_KEY = """\
141-----BEGIN RSA PUBLIC KEY-----
142MIGJAoGBAK94BEYIHZ4KdEkeyPhbLCpRW5ESg+2WPQhrM4yuKYGuq8wFWgumZYR9
143k/WA//FYXMBz0bB+ckuZs/Yu9nK+HLzpGapV0clst4GUMcBInUCzCcpjJTQsQDgm
1443/Y3cqh0W55gOCFhomQ4/1WOYg7YCjk4XYHJE20OdG2Ll5zotK6fAgMBAAE=
145-----END RSA PUBLIC KEY-----\
146"""
147
148EXPECT_POINT_1_SERVICE_KEY = """\
149-----BEGIN RSA PUBLIC KEY-----
150MIGJAoGBAJXmJb8lSydMMqCgCgfgvlB2E5rpd57kz/Aqg7/d1HKc3+l5QoUvHyuy
151ZsAlyka8Eu534hl41oqEKpAKYcMn1TM0vpJEGNVOc+05BInxI9h9f0Mg01PD0tYu
152GcLHYgBzcrfEmKwMtM8WEmcMJd7n2uffaAvJ846WubbeV7MW1YehAgMBAAE=
153-----END RSA PUBLIC KEY-----\
154"""
155
156EXPECT_POINT_2_ONION_KEY = """\
157-----BEGIN RSA PUBLIC KEY-----
158MIGJAoGBALBmhdF5wHxHrpLSmjAZottx220+995FdMOOtZNjRw1DBSprUZZqtxWa
159P8TKpHKzwGJKCVYIIj7lohbv9T9urmlfTE05URGenZoifOFNz3YwMJTXScQEBJ10
1609iWNLDTskLzDKCAbGhbn/MKwOfYGBhNTljdyTmNY5ECRbRzjev9vAgMBAAE=
161-----END RSA PUBLIC KEY-----\
162"""
163
164EXPECT_POINT_2_SERVICE_KEY = """\
165-----BEGIN RSA PUBLIC KEY-----
166MIGJAoGBAMxMHoAmrbUMsxiICp3iTPYghn0YueKHx219wu8O/Q51QycVVLpX27d1
167hJXkPB33XQBXsBS7SxsSsSCQ3GEurQJ7GuBLpYIR/vqsakE/l8wc2CJC5WUhyFFk
168+1TWIUI5txnXLyWCRcKDUrjqdosDaDosgHFg23Mnx+xXcaQ/frB/AgMBAAE=
169-----END RSA PUBLIC KEY-----\
170"""
171
172EXPECT_POINT_3_ONION_KEY = """\
173-----BEGIN RSA PUBLIC KEY-----
174MIGJAoGBAPUkqxgfYdw0Pm/g6MbhmVsGKlujifmkhdfoEeuzgo+wnEsGvwUebzrz
175fZJRt0caXFhnCGgQD2IgmarUaUvP24fXo/4mYzLcPeI7gZneuAQJYvm98Yv9vOHl
176NaM/WvDkCsJ3GVNJ1H3wLPQRI3v7KbNuc9tCOYl/r09OhVaWkzajAgMBAAE=
177-----END RSA PUBLIC KEY-----\
178"""
179
180EXPECT_POINT_3_SERVICE_KEY = """\
181-----BEGIN RSA PUBLIC KEY-----
182MIGJAoGBALbx8LeqRoP/r9w9hjwD41YUm7Po697xRtytF0McyLCS7GRiUYnji7KY
183fepXdvN/Jl5qQKHIBb602kuOTl0pN8Q+YeFUSIIDcmPBLpBDhH3PvrQMcGVaiOWH
1848w0HMZCxgwAcCC51w5VwiumxEJNBVcZsOx0mzN1Cloy+90q0lFXLAgMBAAE=
185-----END RSA PUBLIC KEY-----\
186"""
187
188EXPECTED_BASIC_AUTH_INTRODUCTION_POINTS_ENCODED = """\
189-----BEGIN MESSAGE-----
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
236-----END MESSAGE-----\
237"""
238
239expect_invalid_attr = functools.partial(base_expect_invalid_attr, HiddenServiceDescriptorV2, 'descriptor_id', 'y3olqqblqw2gbh6phimfuiroechjjafa')
240expect_invalid_attr_for_text = functools.partial(base_expect_invalid_attr_for_text, HiddenServiceDescriptorV2, 'descriptor_id', 'y3olqqblqw2gbh6phimfuiroechjjafa')
241
242
243class TestHiddenServiceDescriptorV2(unittest.TestCase):
244  def test_from_str(self):
245    sig = HiddenServiceDescriptorV2.create()
246    self.assertEqual(sig, HiddenServiceDescriptorV2.from_str(str(sig)))
247
248  def test_for_duckduckgo_with_validation(self):
249    """
250    Parse duckduckgo's descriptor.
251    """
252
253    with open(get_resource('hidden_service_duckduckgo'), 'rb') as descriptor_file:
254      desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
255      self._assert_matches_duckduckgo(desc)
256
257  def test_for_duckduckgo_without_validation(self):
258    """
259    Parse duckduckgo's descriptor
260    """
261
262    with open(get_resource('hidden_service_duckduckgo'), 'rb') as descriptor_file:
263      desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = False))
264      self._assert_matches_duckduckgo(desc)
265
266  def test_for_facebook(self):
267    """
268    Parse facebook's descriptor.
269    """
270
271    with open(get_resource('hidden_service_facebook'), 'rb') as descriptor_file:
272      desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
273
274    self.assertEqual('utjk4arxqg6s6zzo7n6cjnq6ot34udhr', desc.descriptor_id)
275    self.assertEqual(2, desc.version)
276    self.assertEqual('6355jaerje3bqozopwq2qmpf4iviizdn', desc.secret_id_part)
277    self.assertEqual(datetime.datetime(2014, 10, 31, 23, 0, 0), desc.published)
278    self.assertEqual([2, 3], desc.protocol_versions)
279
280  @test.require.cryptography
281  def test_descriptor_signing(self):
282    self.assertRaisesWith(NotImplementedError, 'Signing of HiddenServiceDescriptorV2 not implemented', HiddenServiceDescriptorV2.create, sign = True)
283
284  @test.require.cryptography
285  def test_with_basic_auth(self):
286    """
287    Parse a descriptor with introduction-points encrypted with basic auth.
288    """
289
290    with open(get_resource('hidden_service_basic_auth'), 'rb') as descriptor_file:
291      desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
292
293    self.assertEqual('yfmvdrkdbyquyqk5vygyeylgj2qmrvrd', desc.descriptor_id)
294    self.assertEqual(2, desc.version)
295    self.assertEqual('fluw7z3s5cghuuirq3imh5jjj5ljips6', desc.secret_id_part)
296    self.assertEqual(datetime.datetime(2015, 2, 24, 20, 0, 0), desc.published)
297    self.assertEqual([2, 3], desc.protocol_versions)
298    self.assertEqual(EXPECTED_BASIC_AUTH_INTRODUCTION_POINTS_ENCODED, desc.introduction_points_encoded)
299    self.assertEqual([], desc.introduction_points_auth)
300
301    self.assertRaises(DecryptionFailure, desc.introduction_points)
302    self.assertRaises(DecryptionFailure, desc.introduction_points, 'aCmx3qIvArbil8A0KM4KgQ==')
303
304    introduction_points = desc.introduction_points('dCmx3qIvArbil8A0KM4KgQ==')
305    self.assertEqual(3, len(introduction_points))
306
307    point = introduction_points[0]
308    self.assertEqual('hmtvoobwglmmec26alnvl7x7mgmmr7xv', point.identifier)
309    self.assertEqual('195.154.82.88', point.address)
310    self.assertEqual(443, point.port)
311    self.assertTrue('MIGJAoGBANbPRD07T' in point.onion_key)
312    self.assertTrue('MIGJAoGBAN+LAdZP/' in point.service_key)
313    self.assertEqual([], point.intro_authentication)
314
315    point = introduction_points[1]
316    self.assertEqual('q5w6l2f4g5zw4rkr56fkyovbkkrnzcj5', point.identifier)
317    self.assertEqual('37.252.190.133', point.address)
318    self.assertEqual(9001, point.port)
319    self.assertTrue('MIGJAoGBAKmsbKrtt' in point.onion_key)
320    self.assertTrue('MIGJAoGBANwczLtzR' in point.service_key)
321    self.assertEqual([], point.intro_authentication)
322
323    point = introduction_points[2]
324    self.assertEqual('qcvprvmvnjb4dfyqjtxskugniliwlrx3', point.identifier)
325    self.assertEqual('193.11.114.45', point.address)
326    self.assertEqual(9002, point.port)
327    self.assertTrue('MIGJAoGBAM1ILL+7P' in point.onion_key)
328    self.assertTrue('MIGJAoGBAM7B/cymp' in point.service_key)
329    self.assertEqual([], point.intro_authentication)
330
331  @test.require.cryptography
332  def test_with_stealth_auth(self):
333    """
334    Parse a descriptor with introduction-points encrypted with stealth auth.
335    """
336
337    with open(get_resource('hidden_service_stealth_auth'), 'rb') as descriptor_file:
338      desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
339
340    self.assertEqual('ubf3xeibzlfil6s4larq6y5peup2z3oj', desc.descriptor_id)
341    self.assertEqual(2, desc.version)
342    self.assertEqual('jczvydhzetbpdiylj3d5nsnjvaigs7xm', desc.secret_id_part)
343    self.assertEqual(datetime.datetime(2015, 2, 24, 20, 0, 0), desc.published)
344    self.assertEqual([2, 3], desc.protocol_versions)
345    self.assertEqual([], desc.introduction_points_auth)
346
347    self.assertRaises(DecryptionFailure, desc.introduction_points)
348    self.assertRaises(DecryptionFailure, desc.introduction_points, 'aCmx3qIvArbil8A0KM4KgQ==')
349
350    introduction_points = desc.introduction_points('dCmx3qIvArbil8A0KM4KgQ==')
351    self.assertEqual(3, len(introduction_points))
352
353    point = introduction_points[0]
354    self.assertEqual('6h4bkedts3yz2exl3vu4lsyiwkjrx5ff', point.identifier)
355    self.assertEqual('95.85.60.23', point.address)
356    self.assertEqual(443, point.port)
357    self.assertTrue('MIGJAoGBAMX5hO5hQ' in point.onion_key)
358    self.assertTrue('MIGJAoGBAMNSjfydv' in point.service_key)
359    self.assertEqual([], point.intro_authentication)
360
361    point = introduction_points[1]
362    self.assertEqual('4ghasjftsdfbbycafvlfx7czln3hrk53', point.identifier)
363    self.assertEqual('178.254.55.101', point.address)
364    self.assertEqual(9901, point.port)
365    self.assertTrue('MIGJAoGBAL2v/KNEY' in point.onion_key)
366    self.assertTrue('MIGJAoGBAOXiuIgBr' in point.service_key)
367    self.assertEqual([], point.intro_authentication)
368
369    point = introduction_points[2]
370    self.assertEqual('76tsxvudxqx47gedk3tl5qpesdzrh6yh', point.identifier)
371    self.assertEqual('193.11.164.243', point.address)
372    self.assertEqual(9001, point.port)
373    self.assertTrue('MIGJAoGBALca3zEoS' in point.onion_key)
374    self.assertTrue('MIGJAoGBAL3rWIAQ6' in point.service_key)
375    self.assertEqual([], point.intro_authentication)
376
377  def _assert_matches_duckduckgo(self, desc):
378    self.assertEqual('y3olqqblqw2gbh6phimfuiroechjjafa', desc.descriptor_id)
379    self.assertEqual(2, desc.version)
380    self.assertEqual(EXPECTED_DDG_PERMANENT_KEY, desc.permanent_key)
381    self.assertEqual('e24kgecavwsznj7gpbktqsiwgvngsf4e', desc.secret_id_part)
382    self.assertEqual(datetime.datetime(2015, 2, 23, 20, 0, 0), desc.published)
383    self.assertEqual([2, 3], desc.protocol_versions)
384    self.assertEqual(EXPECTED_DDG_INTRODUCTION_POINTS_ENCODED, desc.introduction_points_encoded)
385    self.assertEqual([], desc.introduction_points_auth)
386    self.assertEqual(EXPECTED_DDG_INTRODUCTION_POINTS_CONTENT, desc.introduction_points_content)
387    self.assertEqual(EXPECTED_DDG_SIGNATURE, desc.signature)
388
389    introduction_points = desc.introduction_points()
390    self.assertEqual(3, len(introduction_points))
391
392    point = introduction_points[0]
393    self.assertEqual('iwki77xtbvp6qvedfrwdzncxs3ckayeu', point.identifier)
394    self.assertEqual('178.62.222.129', point.address)
395    self.assertEqual(443, point.port)
396    self.assertEqual(EXPECT_POINT_1_ONION_KEY, point.onion_key)
397    self.assertEqual(EXPECT_POINT_1_SERVICE_KEY, point.service_key)
398    self.assertEqual([], point.intro_authentication)
399
400    point = introduction_points[1]
401    self.assertEqual('em4gjk6eiiualhmlyiifrzc7lbtrsbip', point.identifier)
402    self.assertEqual('46.4.174.52', point.address)
403    self.assertEqual(443, point.port)
404    self.assertEqual(EXPECT_POINT_2_ONION_KEY, point.onion_key)
405    self.assertEqual(EXPECT_POINT_2_SERVICE_KEY, point.service_key)
406    self.assertEqual([], point.intro_authentication)
407
408    point = introduction_points[2]
409    self.assertEqual('jqhfl364x3upe6lqnxizolewlfrsw2zy', point.identifier)
410    self.assertEqual('62.210.82.169', point.address)
411    self.assertEqual(443, point.port)
412    self.assertEqual(EXPECT_POINT_3_ONION_KEY, point.onion_key)
413    self.assertEqual(EXPECT_POINT_3_SERVICE_KEY, point.service_key)
414    self.assertEqual([], point.intro_authentication)
415
416  def test_minimal_hidden_service_descriptor(self):
417    """
418    Basic sanity check that we can parse a hidden service descriptor with minimal attributes.
419    """
420
421    desc = HiddenServiceDescriptorV2.create()
422
423    self.assertEqual('y3olqqblqw2gbh6phimfuiroechjjafa', desc.descriptor_id)
424    self.assertEqual(2, desc.version)
425    self.assertEqual('e24kgecavwsznj7gpbktqsiwgvngsf4e', desc.secret_id_part)
426    self.assertEqual([2, 3], desc.protocol_versions)
427    self.assertEqual('-----BEGIN MESSAGE-----\n-----END MESSAGE-----', desc.introduction_points_encoded)
428    self.assertEqual([], desc.introduction_points_auth)
429    self.assertEqual(b'', desc.introduction_points_content)
430    self.assertEqual([], desc.introduction_points())
431    self.assertEqual('@type hidden-service-descriptor 1.0', str(desc.type_annotation()))
432
433  def test_unrecognized_line(self):
434    """
435    Includes unrecognized content in the descriptor.
436    """
437
438    desc = HiddenServiceDescriptorV2.create({'pepperjack': 'is oh so tasty!'})
439    self.assertEqual(['pepperjack is oh so tasty!'], desc.get_unrecognized_lines())
440
441  def test_proceeding_line(self):
442    """
443    Includes a line prior to the 'rendezvous-service-descriptor' entry.
444    """
445
446    expect_invalid_attr_for_text(self, b'hibernate 1\n' + HiddenServiceDescriptorV2.content())
447
448  def test_trailing_line(self):
449    """
450    Includes a line after the 'router-signature' entry.
451    """
452
453    expect_invalid_attr_for_text(self, HiddenServiceDescriptorV2.content() + b'\nhibernate 1')
454
455  def test_required_fields(self):
456    """
457    Check that we require the mandatory fields.
458    """
459
460    line_to_attr = {
461      'rendezvous-service-descriptor': 'descriptor_id',
462      'version': 'version',
463      'permanent-key': 'permanent_key',
464      'secret-id-part': 'secret_id_part',
465      'publication-time': 'published',
466      'introduction-points': 'introduction_points_encoded',
467      'protocol-versions': 'protocol_versions',
468      'signature': 'signature',
469    }
470
471    for line in REQUIRED_V2_FIELDS:
472      desc_text = HiddenServiceDescriptorV2.content(exclude = (line,))
473
474      expected = [] if line == 'protocol-versions' else None
475      expect_invalid_attr_for_text(self, desc_text, line_to_attr[line], expected)
476
477  def test_invalid_version(self):
478    """
479    Checks that our version field expects a numeric value.
480    """
481
482    test_values = (
483      '',
484      '-10',
485      'hello',
486    )
487
488    for test_value in test_values:
489      expect_invalid_attr(self, {'version': test_value}, 'version')
490
491  def test_invalid_protocol_versions(self):
492    """
493    Checks that our protocol-versions field expects comma separated numeric
494    values.
495    """
496
497    test_values = (
498      '',
499      '-10',
500      'hello',
501      '10,',
502      ',10',
503      '10,-10',
504      '10,hello',
505    )
506
507    for test_value in test_values:
508      expect_invalid_attr(self, {'protocol-versions': test_value}, 'protocol_versions', [])
509
510  def test_introduction_points_when_empty(self):
511    """
512    It's valid to advertise zero introduciton points. I'm not clear if this
513    would mean an empty protocol-versions field or that it's omitted but either
514    are valid according to the spec.
515    """
516
517    missing_field_desc = HiddenServiceDescriptorV2.create(exclude = ('introduction-points',))
518
519    self.assertEqual(None, missing_field_desc.introduction_points_encoded)
520    self.assertEqual([], missing_field_desc.introduction_points_auth)
521    self.assertEqual(None, missing_field_desc.introduction_points_content)
522    self.assertEqual([], missing_field_desc.introduction_points())
523
524    empty_field_desc = HiddenServiceDescriptorV2.create({'introduction-points': MESSAGE_BLOCK % ''})
525
526    self.assertEqual((MESSAGE_BLOCK % '').strip(), empty_field_desc.introduction_points_encoded)
527    self.assertEqual([], empty_field_desc.introduction_points_auth)
528    self.assertEqual(b'', empty_field_desc.introduction_points_content)
529    self.assertEqual([], empty_field_desc.introduction_points())
530
531  def test_introduction_points_when_not_base64(self):
532    """
533    Checks the introduction-points field when the content isn't base64 encoded.
534    """
535
536    test_values = (
537      MESSAGE_BLOCK % '12345',
538      MESSAGE_BLOCK % 'hello',
539    )
540
541    for test_value in test_values:
542      desc = expect_invalid_attr(self, {'introduction-points': test_value}, 'introduction_points_encoded', test_value.strip())
543      self.assertEqual([], desc.introduction_points_auth)
544      self.assertEqual(None, desc.introduction_points_content)
545      self.assertEqual([], desc.introduction_points())
546