commit 380074b53311962690b92670bd640de697b8d9e2 Author: Damian Johnson atagar@torproject.org Date: Mon Feb 23 15:07:20 2015 -0800
Initial hidden service descriptor parser
Quick initial parser for hidden service descriptors...
https://trac.torproject.org/projects/tor/ticket/15004
Things left to do are...
* Issue tickets for some of these 'TODO' notes. * Support the introduction-points field. It's a bit special. * Unit tests for malformed content. * Validate that the signature matches the descriptor content. --- stem/descriptor/__init__.py | 7 + stem/descriptor/hidden_service_descriptor.py | 157 ++++++++++++++++++++ test/settings.cfg | 1 + .../unit/descriptor/data/hidden_service_duckduckgo | 60 ++++++++ test/unit/descriptor/data/hidden_service_facebook | 60 ++++++++ test/unit/descriptor/hidden_service_descriptor.py | 114 ++++++++++++++ 6 files changed, 399 insertions(+)
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py index abcdab2..87f7f73 100644 --- a/stem/descriptor/__init__.py +++ b/stem/descriptor/__init__.py @@ -122,6 +122,7 @@ def parse_file(descriptor_file, descriptor_type = None, validate = False, docume torperf 1.0 **unsupported** bridge-pool-assignment 1.0 **unsupported** tordnsel 1.0 :class:`~stem.descriptor.tordnsel.TorDNSEL` + hidden-service-descriptor 1.0 :class:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor` ========================================= =====
If you're using **python 3** then beware that the open() function defaults to @@ -307,6 +308,11 @@ def _parse_metrics_file(descriptor_type, major_version, minor_version, descripto
for desc in stem.descriptor.tordnsel._parse_file(descriptor_file, validate = validate, **kwargs): yield desc + elif descriptor_type == 'hidden-service-descriptor' and major_version == 1: + document_type = stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor + + for desc in stem.descriptor.hidden_service_descriptor._parse_file(descriptor_file, validate = validate, **kwargs): + yield desc else: raise TypeError("Unrecognized metrics descriptor format. type: '%s', version: '%i.%i'" % (descriptor_type, major_version, minor_version))
@@ -720,3 +726,4 @@ import stem.descriptor.extrainfo_descriptor import stem.descriptor.networkstatus import stem.descriptor.microdescriptor import stem.descriptor.tordnsel +import stem.descriptor.hidden_service_descriptor diff --git a/stem/descriptor/hidden_service_descriptor.py b/stem/descriptor/hidden_service_descriptor.py new file mode 100644 index 0000000..c915f0e --- /dev/null +++ b/stem/descriptor/hidden_service_descriptor.py @@ -0,0 +1,157 @@ +# Copyright 2015, Damian Johnson and The Tor Project +# See LICENSE for licensing information + +""" +Parsing for Tor hidden service descriptors as described in Tor's `rend-spec +https://gitweb.torproject.org/torspec.git/tree/rend-spec.txt`_. + +Unlike other descriptor types these describe a hidden service rather than a +relay. They're created by the service, and can only be fetched via relays with +the HSDir flag. + +**Module Overview:** + +:: + + HiddenServiceDescriptor - Tor hidden service descriptor. +""" + +# TODO: Add a description for how to retrieve them when tor supports that (#14847). + +# TODO: We should add a '@type hidden-service-descriptor 1.0' annotation to +# CollecTor... +# +# https://collector.torproject.org/formats.html + +from stem.descriptor import ( + PGP_BLOCK_END, + Descriptor, + _get_descriptor_components, + _read_until_keywords, + _value, + _parse_simple_line, + _parse_timestamp_line, + _parse_key_block, +) + +REQUIRED_FIELDS = ( + 'rendezvous-service-descriptor', + 'version', + 'permanent-key', + 'secret-id-part', + 'publication-time', + 'protocol-versions', + 'signature', +) + + +def _parse_file(descriptor_file, validate = False, **kwargs): + """ + Iterates over the hidden service descriptors in a file. + + :param file descriptor_file: file with descriptor content + :param bool validate: checks the validity of the descriptor's content if + **True**, skips these checks otherwise + :param dict kwargs: additional arguments for the descriptor constructor + + :returns: iterator for :class:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor` + instances in the file + + :raises: + * **ValueError** if the contents is malformed and validate is **True** + * **IOError** if the file can't be read + """ + + while True: + descriptor_content = _read_until_keywords('signature', descriptor_file) + + # we've reached the 'signature', now include the pgp style block + block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0] + descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True) + + if descriptor_content: + if descriptor_content[0].startswith(b'@type'): + descriptor_content = descriptor_content[1:] + + yield HiddenServiceDescriptor(bytes.join(b'', descriptor_content), validate, **kwargs) + else: + break # done parsing file + + +# TODO: For the 'version' and 'protocol-versions' lines should they be ints +# instead? Presently the spec says it's a 'version-number' which is an +# undefined type. + +def _parse_protocol_versions_line(descriptor, entries): + value = _value('protocol-versions', entries) + descriptor.protocol_versions = value.split(',') + +_parse_rendezvous_service_descriptor_line = _parse_simple_line('rendezvous-service-descriptor', 'descriptor_id') +_parse_version_line = _parse_simple_line('version', 'version') +_parse_permanent_key_line = _parse_key_block('permanent-key', 'permanent_key', 'RSA PUBLIC KEY') +_parse_secret_id_part_line = _parse_simple_line('secret-id-part', 'secret_id_part') +_parse_publication_time_line = _parse_timestamp_line('publication-time', 'published') +_parse_introduction_points_line = _parse_key_block('introduction-points', 'introduction_points_blob', 'MESSAGE') +_parse_signature_line = _parse_key_block('signature', 'signature', 'SIGNATURE') + + +class HiddenServiceDescriptor(Descriptor): + """ + Hidden service descriptor. + + :var str descriptor_id: ***** identifier for this descriptor, this is a base32 hash of several fields + :var str version: ***** hidden service descriptor version + :var str permanent_key: ***** long term key of the hidden service + :var str secret_id_part: ***** hash of the time period, cookie, and replica + values so our descriptor_id can be validated + :var datetime published: ***** time in UTC when this descriptor was made + :var list protocol_versions: ***** versions that are supported when establishing a connection + :var str introduction_points_blob: ***** raw introduction points blob, if + the hidden service uses cookie authentication this is encrypted + :var str signature: signature of the descriptor content + + ***** attribute is either required when we're parsed with validation or has + a default value, others are left as **None** if undefined + """ + + ATTRIBUTES = { + 'descriptor_id': (None, _parse_rendezvous_service_descriptor_line), + 'version': (None, _parse_version_line), + 'permanent_key': (None, _parse_permanent_key_line), + 'secret_id_part': (None, _parse_secret_id_part_line), + 'published': (None, _parse_publication_time_line), + 'protocol_versions': ([], _parse_protocol_versions_line), + 'introduction_points_blob': (None, _parse_introduction_points_line), + 'signature': (None, _parse_signature_line), + } + + PARSER_FOR_LINE = { + 'rendezvous-service-descriptor': _parse_rendezvous_service_descriptor_line, + 'version': _parse_version_line, + 'permanent-key': _parse_permanent_key_line, + 'secret-id-part': _parse_secret_id_part_line, + 'publication-time': _parse_publication_time_line, + 'protocol-versions': _parse_protocol_versions_line, + 'introduction-points': _parse_introduction_points_line, + 'signature': _parse_signature_line, + } + + def __init__(self, raw_contents, validate = False): + super(HiddenServiceDescriptor, self).__init__(raw_contents, lazy_load = not validate) + entries = _get_descriptor_components(raw_contents, validate) + + if validate: + for keyword in REQUIRED_FIELDS: + if keyword not in entries: + raise ValueError("Hidden service descriptor must have a '%s' entry" % keyword) + elif keyword in entries and len(entries[keyword]) > 1: + raise ValueError("The '%s' entry can only appear once in a hidden service descriptor" % keyword) + + if 'rendezvous-service-descriptor' != list(entries.keys())[0]: + raise ValueError("Hidden service descriptor must start with a 'rendezvous-service-descriptor' entry") + elif 'signature' != list(entries.keys())[-1]: + raise ValueError("Hidden service descriptor must end with a 'signature' entry") + + self._parse(entries, validate) + else: + self._entries = entries diff --git a/test/settings.cfg b/test/settings.cfg index c0c8342..21f5eb2 100644 --- a/test/settings.cfg +++ b/test/settings.cfg @@ -172,6 +172,7 @@ test.unit_tests |test.unit.descriptor.networkstatus.document_v2.TestNetworkStatusDocument |test.unit.descriptor.networkstatus.document_v3.TestNetworkStatusDocument |test.unit.descriptor.networkstatus.bridge_document.TestBridgeNetworkStatusDocument +|test.unit.descriptor.hidden_service_descriptor.TestHiddenServiceDescriptor |test.unit.exit_policy.rule.TestExitPolicyRule |test.unit.exit_policy.policy.TestExitPolicy |test.unit.version.TestVersion diff --git a/test/unit/descriptor/data/hidden_service_duckduckgo b/test/unit/descriptor/data/hidden_service_duckduckgo new file mode 100644 index 0000000..f3f4868 --- /dev/null +++ b/test/unit/descriptor/data/hidden_service_duckduckgo @@ -0,0 +1,60 @@ +@type hidden-service-descriptor 1.0 +rendezvous-service-descriptor y3olqqblqw2gbh6phimfuiroechjjafa +version 2 +permanent-key +-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAJ/SzzgrXPxTlFrKVhXh3buCWv2QfcNgncUpDpKouLn3AtPH5Ocys0jE +aZSKdvaiQ62md2gOwj4x61cFNdi05tdQjS+2thHKEm/KsB9BGLSLBNJYY356bupg +I5gQozM65ENelfxYlysBjJ52xSDBd8C4f/p9umdzaaaCmzXG/nhzAgMBAAE= +-----END RSA PUBLIC KEY----- +secret-id-part e24kgecavwsznj7gpbktqsiwgvngsf4e +publication-time 2015-02-23 20:00:00 +protocol-versions 2,3 +introduction-points +-----BEGIN MESSAGE----- +aW50cm9kdWN0aW9uLXBvaW50IGl3a2k3N3h0YnZwNnF2ZWRmcndkem5jeHMzY2th +eWV1CmlwLWFkZHJlc3MgMTc4LjYyLjIyMi4xMjkKb25pb24tcG9ydCA0NDMKb25p +b24ta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFL +OTRCRVlJSFo0S2RFa2V5UGhiTENwUlc1RVNnKzJXUFFock00eXVLWUd1cTh3Rldn +dW1aWVI5CmsvV0EvL0ZZWE1CejBiQitja3Vacy9ZdTluSytITHpwR2FwVjBjbHN0 +NEdVTWNCSW5VQ3pDY3BqSlRRc1FEZ20KMy9ZM2NxaDBXNTVnT0NGaG9tUTQvMVdP +WWc3WUNqazRYWUhKRTIwT2RHMkxsNXpvdEs2ZkFnTUJBQUU9Ci0tLS0tRU5EIFJT +QSBQVUJMSUMgS0VZLS0tLS0Kc2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVC +TElDIEtFWS0tLS0tCk1JR0pBb0dCQUpYbUpiOGxTeWRNTXFDZ0NnZmd2bEIyRTVy +cGQ1N2t6L0FxZzcvZDFIS2MzK2w1UW9Vdkh5dXkKWnNBbHlrYThFdTUzNGhsNDFv +cUVLcEFLWWNNbjFUTTB2cEpFR05WT2MrMDVCSW54STloOWYwTWcwMVBEMHRZdQpH +Y0xIWWdCemNyZkVtS3dNdE04V0VtY01KZDduMnVmZmFBdko4NDZXdWJiZVY3TVcx +WWVoQWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBLRVktLS0tLQppbnRyb2R1 +Y3Rpb24tcG9pbnQgZW00Z2prNmVpaXVhbGhtbHlpaWZyemM3bGJ0cnNiaXAKaXAt +YWRkcmVzcyA0Ni40LjE3NC41Mgpvbmlvbi1wb3J0IDQ0Mwpvbmlvbi1rZXkKLS0t +LS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pBb0dCQUxCbWhkRjV3SHhI +cnBMU21qQVpvdHR4MjIwKzk5NUZkTU9PdFpOalJ3MURCU3ByVVpacXR4V2EKUDhU +S3BIS3p3R0pLQ1ZZSUlqN2xvaGJ2OVQ5dXJtbGZURTA1VVJHZW5ab2lmT0ZOejNZ +d01KVFhTY1FFQkoxMAo5aVdOTERUc2tMekRLQ0FiR2hibi9NS3dPZllHQmhOVGxq +ZHlUbU5ZNUVDUmJSempldjl2QWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBL +RVktLS0tLQpzZXJ2aWNlLWtleQotLS0tLUJFR0lOIFJTQSBQVUJMSUMgS0VZLS0t +LS0KTUlHSkFvR0JBTXhNSG9BbXJiVU1zeGlJQ3AzaVRQWWdobjBZdWVLSHgyMTl3 +dThPL1E1MVF5Y1ZWTHBYMjdkMQpoSlhrUEIzM1hRQlhzQlM3U3hzU3NTQ1EzR0V1 +clFKN0d1QkxwWUlSL3Zxc2FrRS9sOHdjMkNKQzVXVWh5RkZrCisxVFdJVUk1dHhu +WEx5V0NSY0tEVXJqcWRvc0RhRG9zZ0hGZzIzTW54K3hYY2FRL2ZyQi9BZ01CQUFF +PQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCmludHJvZHVjdGlvbi1wb2lu +dCBqcWhmbDM2NHgzdXBlNmxxbnhpem9sZXdsZnJzdzJ6eQppcC1hZGRyZXNzIDYy +LjIxMC44Mi4xNjkKb25pb24tcG9ydCA0NDMKb25pb24ta2V5Ci0tLS0tQkVHSU4g +UlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFQVWtxeGdmWWR3MFBtL2c2TWJo +bVZzR0tsdWppZm1raGRmb0VldXpnbyt3bkVzR3Z3VWVienJ6CmZaSlJ0MGNhWEZo +bkNHZ1FEMklnbWFyVWFVdlAyNGZYby80bVl6TGNQZUk3Z1puZXVBUUpZdm05OFl2 +OXZPSGwKTmFNL1d2RGtDc0ozR1ZOSjFIM3dMUFFSSTN2N0tiTnVjOXRDT1lsL3Iw +OU9oVmFXa3phakFnTUJBQUU9Ci0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K +c2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pB +b0dCQUxieDhMZXFSb1Avcjl3OWhqd0Q0MVlVbTdQbzY5N3hSdHl0RjBNY3lMQ1M3 +R1JpVVluamk3S1kKZmVwWGR2Ti9KbDVxUUtISUJiNjAya3VPVGwwcE44UStZZUZV +U0lJRGNtUEJMcEJEaEgzUHZyUU1jR1ZhaU9XSAo4dzBITVpDeGd3QWNDQzUxdzVW +d2l1bXhFSk5CVmNac094MG16TjFDbG95KzkwcTBsRlhMQWdNQkFBRT0KLS0tLS1F +TkQgUlNBIFBVQkxJQyBLRVktLS0tLQoK +-----END MESSAGE----- +signature +-----BEGIN SIGNATURE----- +VKMmsDIUUFOrpqvcQroIZjDZTKxqNs88a4M9Te8cR/ZvS7H2nffv6iQs0tom5X4D +4Dy4iZiy+pwYxdHfaOxmdpgMCRvgPb34MExWr5YemH0QuGtnlp5Wxr8GYaAQVuZX +cZjQLW0juUYCbgIGdxVEBnlEt2rgBSM9+1oR7EAfV1U= +-----END SIGNATURE----- diff --git a/test/unit/descriptor/data/hidden_service_facebook b/test/unit/descriptor/data/hidden_service_facebook new file mode 100644 index 0000000..705c776 --- /dev/null +++ b/test/unit/descriptor/data/hidden_service_facebook @@ -0,0 +1,60 @@ +@type hidden-service-descriptor 1.0 +rendezvous-service-descriptor utjk4arxqg6s6zzo7n6cjnq6ot34udhr +version 2 +permanent-key +-----BEGIN RSA PUBLIC KEY----- +MIGKAoGBAL7zXRnwSycHlKSqK+B8PFvy7RhkQ+OytYtxBwOLzhD82oY7zbpjSHY4 +BZ+hsnceXVjB+f1mXGjvLY6pnYxuufV4wsMsk7a58aJOqUvZFFI2vXhJtnLICxoZ +AEHWzajz4ULagahB1Vi62loQE84OEcuFBekTvnHca1ZTxwp16aZtAgQvoiLZ +-----END RSA PUBLIC KEY----- +secret-id-part 6355jaerje3bqozopwq2qmpf4iviizdn +publication-time 2014-10-31 23:00:00 +protocol-versions 2,3 +introduction-points +-----BEGIN MESSAGE----- +aW50cm9kdWN0aW9uLXBvaW50IHJ6Y3V3am5jbDdpYXpkbm41NXV5cmEybWVlamxz +eXVoCmlwLWFkZHJlc3MgMTkzLjExLjExNC40Nwpvbmlvbi1wb3J0IDkwMDQKb25p +b24ta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFM +dndQc012TkhqMTY3YlJyLzlLbi9iVGtPUTN0VC9qOFdSOWV5NTlZU2NBVEQ4TXVz +OEV6ZWpNCjc0RjdhTGR1VjZuRk15djlFYmhEbVNIZDZRMmhwNkYwb2FGODB0MHMv +bThXYmVWTUF1aTRvVWRSU1ZRb0drY20KTDJXTlViNy84UWYwb2hFKzZ1K1pTL0FO +U2NEc0FDT2hwNkliMWxHWjhaZGxWZFJzeWxtckFnTUJBQUU9Ci0tLS0tRU5EIFJT +QSBQVUJMSUMgS0VZLS0tLS0Kc2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVC +TElDIEtFWS0tLS0tCk1JR0pBb0dCQU1YVUJRMVd0cXJSZHRkaTdhODkvWU9EQUJ6 +d2U2L0JSS3dEcGFBUENyMC9CZlppVnFadXFFMzcKYmlxbW1pODBPVm5uQzd6eis3 +cDZYQ1QycitEemxtSmFCOGdzTjZCZlU2ek45Wklwd3ptcm1XTy94SGNoZ1BkOQpK +SUNrSUxHWEJlQnorNmtnSTZiZDcrbFZEYmxJYXNRNHkrUjhMWWxTeCtRMk9mK1gy +eDFwQWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBLRVktLS0tLQppbnRyb2R1 +Y3Rpb24tcG9pbnQgcXE0YXZmdnZhcHljMnhyNmNpYmpuaXFoNTdpY2FkMnEKaXAt +YWRkcmVzcyAxODguNDAuODMuMjA4Cm9uaW9uLXBvcnQgNDQ0Cm9uaW9uLWtleQot +LS0tLUJFR0lOIFJTQSBQVUJMSUMgS0VZLS0tLS0KTUlHSkFvR0JBTWZySUJ1TlVF +MGZxa3ZySDlEZzJkNkM5SFUvNWR2TGxQL0NGVGFFYlVhMURvUHhyQm5iQ1BEcQp5 +bHhwNGs3M04yVDM3aXJrN3FacmVZQ3B6aUVPQ3paRU4xdGlkM0t1c0tFczgzL0FQ +aDFreTVRRmpkRmpBU0NxCmovRDl0VXNYYkVaa1FEN203SUlmWU40SG52KzROM0dV +WjVqNkhFMWJvYmxsbURyNkd2U0xBZ01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElD +IEtFWS0tLS0tCnNlcnZpY2Uta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVkt +LS0tLQpNSUdKQW9HQkFNRXMyc2tRLzJsVjRFUGpRTDFrZk0zZjExUzRNZUdPcDFC +aWgrcm1ESGhic09yWDRoZk1GV1NuCjY1dTlZUE9zY1hldUlHbFp4YmFyUUY2Nzkz +bGMrbk9KbjVyQ016a0pYNWt0OHdobkZoVlFXN0ZoejUxRmdMNFkKNUFKKzdkaW9l +WTNKdjVNVE1rMUF4Qk1hbkJsR2YxazZrU05kZWxtMCs2bWtZVmVOcVJzckFnTUJB +QUU9Ci0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0KaW50cm9kdWN0aW9uLXBv +aW50IGI1ZHNpY29oZ2Fqb3Vhc3VxZ3lyNWtwNXIzdzdpaWYyCmlwLWFkZHJlc3Mg +MTg1LjUuNTMuNzIKb25pb24tcG9ydCA5MDAxCm9uaW9uLWtleQotLS0tLUJFR0lO +IFJTQSBQVUJMSUMgS0VZLS0tLS0KTUlHSkFvR0JBTWVUc0VqS1ZMeGsyL3o4bnRG +T2xWU09ZTWFjWTZBeFJTTXdQdkxoT2FsajVkNkRraitnSVA5agpsWDFoekRpZ0VP +b2FBV3FwWjRjRENLa3p6UGdzWEc3ZXVnSmFxTGQydUliZmh4WEdISTJDNWtWQWZZ +VnVyL1N3CnBXNFRRbmhKUytvbG1BWG84b1hvT2JNWGt1YURUdmlLSXZLa3lQWCs0 +dGJ4a1RxWWt5ZURBZ01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0t +CnNlcnZpY2Uta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpNSUdK +QW9HQkFPM05SSGMwTld5MFo5aFQrZEUxZ3hKRnFsb0dhdmdLYzZJZ0E1UU5ORDNO +RHJJakdBYmNsRVh4Ck1tY1BBR2hhc1VlNnJ2aFN0VlRkZEVvN1V0SElCM3F2YXNL +d0NPem82Z2dLSHZtVVRFOWRNTk9LSEcyLy9xTi8KVkducGdJUkRoeDRwbE9DOXV6 +NGFKU0RZdlhiSXVtZmtxdGJzb1BacUZMYUFqdGFWR015VkFnTUJBQUU9Ci0tLS0t +RU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0KCg== +-----END MESSAGE----- +signature +-----BEGIN SIGNATURE----- +tFCNLEOFeqeDZOruKt4SBGvJ2Y2lLo29XUzd09FlIalnwb71TKcKSWi1JVB0vDqF +ftGlP4M+nZGh6YGxdEriV0ikFbcW+F4XOpWR5NEPV5cCoQAMd1a8mqivGjM/lQwe +41p+e4XCex2Brsl3o+pSyHy/U+p+xdQkmNPqrHpXVK8= +-----END SIGNATURE----- diff --git a/test/unit/descriptor/hidden_service_descriptor.py b/test/unit/descriptor/hidden_service_descriptor.py new file mode 100644 index 0000000..579c549 --- /dev/null +++ b/test/unit/descriptor/hidden_service_descriptor.py @@ -0,0 +1,114 @@ +""" +Unit tests for stem.descriptor.hidden_service_descriptor. +""" + +import datetime +import unittest + +import stem.descriptor + +from test.unit.descriptor import get_resource + +EXPECTED_DDG_PERMANENT_KEY = """\ +-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAJ/SzzgrXPxTlFrKVhXh3buCWv2QfcNgncUpDpKouLn3AtPH5Ocys0jE +aZSKdvaiQ62md2gOwj4x61cFNdi05tdQjS+2thHKEm/KsB9BGLSLBNJYY356bupg +I5gQozM65ENelfxYlysBjJ52xSDBd8C4f/p9umdzaaaCmzXG/nhzAgMBAAE= +-----END RSA PUBLIC KEY-----\ +""" + +EXPECTED_DDG_INTRODUCTION_POINTS_BLOB = """\ +-----BEGIN MESSAGE----- +aW50cm9kdWN0aW9uLXBvaW50IGl3a2k3N3h0YnZwNnF2ZWRmcndkem5jeHMzY2th +eWV1CmlwLWFkZHJlc3MgMTc4LjYyLjIyMi4xMjkKb25pb24tcG9ydCA0NDMKb25p +b24ta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFL +OTRCRVlJSFo0S2RFa2V5UGhiTENwUlc1RVNnKzJXUFFock00eXVLWUd1cTh3Rldn +dW1aWVI5CmsvV0EvL0ZZWE1CejBiQitja3Vacy9ZdTluSytITHpwR2FwVjBjbHN0 +NEdVTWNCSW5VQ3pDY3BqSlRRc1FEZ20KMy9ZM2NxaDBXNTVnT0NGaG9tUTQvMVdP +WWc3WUNqazRYWUhKRTIwT2RHMkxsNXpvdEs2ZkFnTUJBQUU9Ci0tLS0tRU5EIFJT +QSBQVUJMSUMgS0VZLS0tLS0Kc2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVC +TElDIEtFWS0tLS0tCk1JR0pBb0dCQUpYbUpiOGxTeWRNTXFDZ0NnZmd2bEIyRTVy +cGQ1N2t6L0FxZzcvZDFIS2MzK2w1UW9Vdkh5dXkKWnNBbHlrYThFdTUzNGhsNDFv +cUVLcEFLWWNNbjFUTTB2cEpFR05WT2MrMDVCSW54STloOWYwTWcwMVBEMHRZdQpH +Y0xIWWdCemNyZkVtS3dNdE04V0VtY01KZDduMnVmZmFBdko4NDZXdWJiZVY3TVcx +WWVoQWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBLRVktLS0tLQppbnRyb2R1 +Y3Rpb24tcG9pbnQgZW00Z2prNmVpaXVhbGhtbHlpaWZyemM3bGJ0cnNiaXAKaXAt +YWRkcmVzcyA0Ni40LjE3NC41Mgpvbmlvbi1wb3J0IDQ0Mwpvbmlvbi1rZXkKLS0t +LS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pBb0dCQUxCbWhkRjV3SHhI +cnBMU21qQVpvdHR4MjIwKzk5NUZkTU9PdFpOalJ3MURCU3ByVVpacXR4V2EKUDhU +S3BIS3p3R0pLQ1ZZSUlqN2xvaGJ2OVQ5dXJtbGZURTA1VVJHZW5ab2lmT0ZOejNZ +d01KVFhTY1FFQkoxMAo5aVdOTERUc2tMekRLQ0FiR2hibi9NS3dPZllHQmhOVGxq +ZHlUbU5ZNUVDUmJSempldjl2QWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBVQkxJQyBL +RVktLS0tLQpzZXJ2aWNlLWtleQotLS0tLUJFR0lOIFJTQSBQVUJMSUMgS0VZLS0t +LS0KTUlHSkFvR0JBTXhNSG9BbXJiVU1zeGlJQ3AzaVRQWWdobjBZdWVLSHgyMTl3 +dThPL1E1MVF5Y1ZWTHBYMjdkMQpoSlhrUEIzM1hRQlhzQlM3U3hzU3NTQ1EzR0V1 +clFKN0d1QkxwWUlSL3Zxc2FrRS9sOHdjMkNKQzVXVWh5RkZrCisxVFdJVUk1dHhu +WEx5V0NSY0tEVXJqcWRvc0RhRG9zZ0hGZzIzTW54K3hYY2FRL2ZyQi9BZ01CQUFF +PQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCmludHJvZHVjdGlvbi1wb2lu +dCBqcWhmbDM2NHgzdXBlNmxxbnhpem9sZXdsZnJzdzJ6eQppcC1hZGRyZXNzIDYy +LjIxMC44Mi4xNjkKb25pb24tcG9ydCA0NDMKb25pb24ta2V5Ci0tLS0tQkVHSU4g +UlNBIFBVQkxJQyBLRVktLS0tLQpNSUdKQW9HQkFQVWtxeGdmWWR3MFBtL2c2TWJo +bVZzR0tsdWppZm1raGRmb0VldXpnbyt3bkVzR3Z3VWVienJ6CmZaSlJ0MGNhWEZo +bkNHZ1FEMklnbWFyVWFVdlAyNGZYby80bVl6TGNQZUk3Z1puZXVBUUpZdm05OFl2 +OXZPSGwKTmFNL1d2RGtDc0ozR1ZOSjFIM3dMUFFSSTN2N0tiTnVjOXRDT1lsL3Iw +OU9oVmFXa3phakFnTUJBQUU9Ci0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K +c2VydmljZS1rZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pB +b0dCQUxieDhMZXFSb1Avcjl3OWhqd0Q0MVlVbTdQbzY5N3hSdHl0RjBNY3lMQ1M3 +R1JpVVluamk3S1kKZmVwWGR2Ti9KbDVxUUtISUJiNjAya3VPVGwwcE44UStZZUZV +U0lJRGNtUEJMcEJEaEgzUHZyUU1jR1ZhaU9XSAo4dzBITVpDeGd3QWNDQzUxdzVW +d2l1bXhFSk5CVmNac094MG16TjFDbG95KzkwcTBsRlhMQWdNQkFBRT0KLS0tLS1F +TkQgUlNBIFBVQkxJQyBLRVktLS0tLQoK +-----END MESSAGE-----\ +""" + +EXPECTED_DDG_SIGNATURE = """\ +-----BEGIN SIGNATURE----- +VKMmsDIUUFOrpqvcQroIZjDZTKxqNs88a4M9Te8cR/ZvS7H2nffv6iQs0tom5X4D +4Dy4iZiy+pwYxdHfaOxmdpgMCRvgPb34MExWr5YemH0QuGtnlp5Wxr8GYaAQVuZX +cZjQLW0juUYCbgIGdxVEBnlEt2rgBSM9+1oR7EAfV1U= +-----END SIGNATURE-----\ +""" + + +class TestHiddenServiceDescriptor(unittest.TestCase): + def test_for_duckduckgo_with_validation(self): + """ + Parse duckduckgo's descriptor. + """ + + descriptor_file = open(get_resource('hidden_service_duckduckgo'), 'rb') + desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True)) + self._assert_matches_duckduckgo(desc) + + def test_for_duckduckgo_without_validation(self): + """ + Parse duckduckgo's descriptor + """ + + descriptor_file = open(get_resource('hidden_service_duckduckgo'), 'rb') + desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = False)) + self._assert_matches_duckduckgo(desc) + + def test_for_facebook(self): + """ + Parse facebook's descriptor. + """ + + descriptor_file = open(get_resource('hidden_service_facebook'), 'rb') + + desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True)) + self.assertEqual('utjk4arxqg6s6zzo7n6cjnq6ot34udhr', desc.descriptor_id) + self.assertEqual('2', desc.version) + self.assertEqual('6355jaerje3bqozopwq2qmpf4iviizdn', desc.secret_id_part) + self.assertEqual(datetime.datetime(2014, 10, 31, 23, 0, 0), desc.published) + self.assertEqual(['2', '3'], desc.protocol_versions) + + def _assert_matches_duckduckgo(self, desc): + self.assertEqual('y3olqqblqw2gbh6phimfuiroechjjafa', desc.descriptor_id) + self.assertEqual('2', desc.version) + self.assertEqual(EXPECTED_DDG_PERMANENT_KEY, desc.permanent_key) + self.assertEqual('e24kgecavwsznj7gpbktqsiwgvngsf4e', desc.secret_id_part) + self.assertEqual(datetime.datetime(2015, 2, 23, 20, 0, 0), desc.published) + self.assertEqual(['2', '3'], desc.protocol_versions) + self.assertEqual(EXPECTED_DDG_INTRODUCTION_POINTS_BLOB, desc.introduction_points_blob) + self.assertEqual(EXPECTED_DDG_SIGNATURE, desc.signature)