commit 353200c26399e5727a6865ddcd83b1820f3e1b6f Author: Damian Johnson atagar@torproject.org Date: Sun Aug 23 14:55:55 2015 -0700
Support for ed25519 descriptor fields
The descriptor fields for ed25519 finally made it into the spec...
https://trac.torproject.org/projects/tor/ticket/16235 https://gitweb.torproject.org/torspec.git/commit/?id=5a79d67a45454ab5b741347...
This included fields in four descriptor types: server, extrainfo, microdescriptors, and router status entries. One leftover bit though is to support bridge sanitization...
https://trac.torproject.org/projects/tor/ticket/16359 https://collector.torproject.org/formats.html#bridge-descriptors
These descriptions aren't quite enough for me to be sure what's up so gonna check with Karsten for clarification. --- docs/change_log.rst | 1 + stem/descriptor/extrainfo_descriptor.py | 14 +++ stem/descriptor/router_status_entry.py | 39 ++++++- stem/descriptor/server_descriptor.py | 40 +++++++ .../data/extrainfo_descriptor_with_ed25519 | 26 +++++ .../descriptor/data/server_descriptor_with_ed25519 | 80 +++++++++----- test/unit/descriptor/extrainfo_descriptor.py | 14 +++ test/unit/descriptor/router_status_entry.py | 112 ++++++++++++++++++-- test/unit/descriptor/server_descriptor.py | 60 +++++++---- 9 files changed, 327 insertions(+), 59 deletions(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst index 5eade67..a52359e 100644 --- a/docs/change_log.rst +++ b/docs/change_log.rst @@ -53,6 +53,7 @@ The following are only available within Stem's `git repository
* **Descriptors**
+ * Support for ed25519 descriptor fields (:spec:`5a79d67`) * Server descriptor validation fails with 'extra-info-digest line had an invalid value' from additions in proposal 228 (:trac:`16227`)
* **Website** diff --git a/stem/descriptor/extrainfo_descriptor.py b/stem/descriptor/extrainfo_descriptor.py index 56c042a..7373620 100644 --- a/stem/descriptor/extrainfo_descriptor.py +++ b/stem/descriptor/extrainfo_descriptor.py @@ -83,6 +83,7 @@ from stem.descriptor import ( _get_descriptor_components, _value, _values, + _parse_simple_line, _parse_timestamp_line, _parse_forty_character_hex, _parse_key_block, @@ -533,6 +534,7 @@ def _parse_hs_stats(keyword, stat_attribute, extra_attribute, descriptor, entrie setattr(descriptor, extra_attribute, extra)
+_parse_identity_ed25519_line = _parse_key_block('identity-ed25519', 'ed25519_certificate', 'ED25519 CERT') _parse_geoip_db_digest_line = _parse_forty_character_hex('geoip-db-digest', 'geoip_db_digest') _parse_geoip6_db_digest_line = _parse_forty_character_hex('geoip6-db-digest', 'geoip6_db_digest') _parse_dirreq_v2_resp_line = functools.partial(_parse_dirreq_line, 'dirreq-v2-resp', 'dir_v2_responses', 'dir_v2_responses_unknown') @@ -570,6 +572,7 @@ _parse_dirreq_v3_reqs_line = functools.partial(_parse_geoip_to_count_line, 'dirr _parse_geoip_client_origins_line = functools.partial(_parse_geoip_to_count_line, 'geoip-client-origins', 'geoip_client_origins') _parse_entry_ips_line = functools.partial(_parse_geoip_to_count_line, 'entry-ips', 'entry_ips') _parse_bridge_ips_line = functools.partial(_parse_geoip_to_count_line, 'bridge-ips', 'bridge_ips') +_parse_router_sig_ed25519_line = _parse_simple_line('router-sig-ed25519', 'ed25519_signature') _parse_router_digest_line = _parse_forty_character_hex('router-digest', '_digest') _parse_router_signature_line = _parse_key_block('router-signature', 'signature', 'SIGNATURE')
@@ -587,6 +590,9 @@ class ExtraInfoDescriptor(Descriptor): port, args) tuple, these usually appear on bridges in which case all of those are **None**
+ :var ed25519_certificate str: base64 encoded ed25519 certificate + :var ed25519_signature str: signature of this document using ed25519 + **Bi-directional connection usage:**
:var datetime conn_bi_direct_end: end of the sampling interval @@ -689,6 +695,9 @@ class ExtraInfoDescriptor(Descriptor): .. versionchanged:: 1.4.0 Added the hs_stats_end, hs_rend_cells, hs_rend_cells_attr, hs_dir_onions_seen, and hs_dir_onions_seen_attr attributes. + + .. versionchanged:: 1.5.0 + Added the ed25519_certificate and ed25519_signature attributes. """
ATTRIBUTES = { @@ -699,6 +708,9 @@ class ExtraInfoDescriptor(Descriptor): 'geoip6_db_digest': (None, _parse_geoip6_db_digest_line), 'transport': ({}, _parse_transport_line),
+ 'ed25519_certificate': (None, _parse_identity_ed25519_line), + 'ed25519_signature': (None, _parse_router_sig_ed25519_line), + 'conn_bi_direct_end': (None, _parse_conn_bi_direct_line), 'conn_bi_direct_interval': (None, _parse_conn_bi_direct_line), 'conn_bi_direct_below': (None, _parse_conn_bi_direct_line), @@ -778,6 +790,8 @@ class ExtraInfoDescriptor(Descriptor):
PARSER_FOR_LINE = { 'extra-info': _parse_extra_info_line, + 'identity-ed25519': _parse_identity_ed25519_line, + 'router-sig-ed25519': _parse_router_sig_ed25519_line, 'geoip-db-digest': _parse_geoip_db_digest_line, 'geoip6-db-digest': _parse_geoip6_db_digest_line, 'transport': _parse_transport_line, diff --git a/stem/descriptor/router_status_entry.py b/stem/descriptor/router_status_entry.py index c33baa3..1d0305a 100644 --- a/stem/descriptor/router_status_entry.py +++ b/stem/descriptor/router_status_entry.py @@ -265,8 +265,11 @@ def _parse_w_line(descriptor, entries):
def _parse_p_line(descriptor, entries): # "p" ("accept" / "reject") PortList - # p reject 1-65535 - # example: p accept 80,110,143,443,993,995,6660-6669,6697,7000-7001 + # + # examples: + # + # p accept 80,110,143,443,993,995,6660-6669,6697,7000-7001 + # p reject 1-65535
value = _value('p', entries)
@@ -276,6 +279,30 @@ def _parse_p_line(descriptor, entries): raise ValueError('%s exit policy is malformed (%s): p %s' % (descriptor._name(), exc, value))
+def _parse_id_line(descriptor, entries): + # "id" "ed25519" ed25519-identity + # + # examples: + # + # id ed25519 none + # id ed25519 8RH34kO07Pp+XYwzdoATVyCibIvmbslUjRkAm7J4IA8 + + value = _value('id', entries) + + if value: + if not (descriptor.document and descriptor.document.is_vote): + vote_status = 'vote' if descriptor.document else '<undefined document>' + raise ValueError("%s 'id' line should only appear in votes (appeared in a %s): id %s" % (descriptor._name(), vote_status, value)) + + value_comp = value.split() + + if len(value_comp) >= 2: + descriptor.identifier_type = value_comp[0] + descriptor.identifier = value_comp[1] + else: + raise ValueError("'id' lines should contain both the key type and digest: id %s" % value) + + def _parse_m_line(descriptor, entries): # "m" methods 1*(algorithm "=" digest) # example: m 8,9,10,11,12 sha256=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs @@ -512,6 +539,8 @@ class RouterStatusEntryV3(RouterStatusEntry):
:var list or_addresses: ***** relay's OR addresses, this is a tuple listing of the form (address (**str**), port (**int**), is_ipv6 (**bool**)) + :var str identifier_type: identity digest key type + :var str identifier: base64 encoded identity digest :var str digest: ***** router's upper-case hex digest
:var int bandwidth: bandwidth claimed by the relay (in kb/s) @@ -531,11 +560,16 @@ class RouterStatusEntryV3(RouterStatusEntry):
***** attribute is either required when we're parsed with validation or has a default value, others are left as **None** if undefined + + .. versionchanged:: 1.5.0 + Added the identifier and identifier_type attributes. """
ATTRIBUTES = dict(RouterStatusEntry.ATTRIBUTES, **{ 'digest': (None, _parse_r_line), 'or_addresses': ([], _parse_a_line), + 'identifier_type': (None, _parse_id_line), + 'identifier': (None, _parse_id_line),
'bandwidth': (None, _parse_w_line), 'measured': (None, _parse_w_line), @@ -550,6 +584,7 @@ class RouterStatusEntryV3(RouterStatusEntry): 'a': _parse_a_line, 'w': _parse_w_line, 'p': _parse_p_line, + 'id': _parse_id_line, 'm': _parse_m_line, })
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py index 22e6f40..872806b 100644 --- a/stem/descriptor/server_descriptor.py +++ b/stem/descriptor/server_descriptor.py @@ -78,6 +78,8 @@ REQUIRED_FIELDS = (
# optional entries that can appear at most once SINGLE_FIELDS = ( + 'identity-ed25519', + 'master-key-ed25519', 'platform', 'fingerprint', 'hibernating', @@ -92,7 +94,10 @@ SINGLE_FIELDS = ( 'hidden-service-dir', 'protocols', 'allow-single-hop-exits', + 'onion-key-crosscert', 'ntor-onion-key', + 'ntor-onion-key-crosscert', + 'router-sig-ed25519', )
DEFAULT_IPV6_EXIT_POLICY = stem.exit_policy.MicroExitPolicy('reject 1-65535') @@ -382,6 +387,8 @@ def _parse_exit_policy(descriptor, entries): del descriptor._unparsed_exit_policy
+_parse_identity_ed25519_line = _parse_key_block('identity-ed25519', 'ed25519_certificate', 'ED25519 CERT') +_parse_master_key_ed25519_line = _parse_simple_line('master-key-ed25519', 'ed25519_master_key') _parse_contact_line = _parse_bytes_line('contact', 'contact') _parse_published_line = _parse_timestamp_line('published', 'published') _parse_read_history_line = functools.partial(_parse_history_line, 'read-history', 'read_history_end', 'read_history_interval', 'read_history_values') @@ -392,9 +399,12 @@ _parse_caches_extra_info_line = lambda descriptor, entries: setattr(descriptor, _parse_family_line = lambda descriptor, entries: setattr(descriptor, 'family', set(_value('family', entries).split(' '))) _parse_eventdns_line = lambda descriptor, entries: setattr(descriptor, 'eventdns', _value('eventdns', entries) == '1') _parse_onion_key_line = _parse_key_block('onion-key', 'onion_key', 'RSA PUBLIC KEY') +_parse_onion_key_crosscert_line = _parse_key_block('onion-key-crosscert', 'onion_key_crosscert', 'CROSSCERT') _parse_signing_key_line = _parse_key_block('signing-key', 'signing_key', 'RSA PUBLIC KEY') _parse_router_signature_line = _parse_key_block('router-signature', 'signature', 'SIGNATURE') _parse_ntor_onion_key_line = _parse_simple_line('ntor-onion-key', 'ntor_onion_key') +_parse_ntor_onion_key_crosscert_line = _parse_key_block('ntor-onion-key-crosscert', 'ntor_onion_key_crosscert', 'ED25519 CERT', 'ntor_onion_key_crosscert_sign') +_parse_router_sig_ed25519_line = _parse_simple_line('router-sig-ed25519', 'ed25519_signature') _parse_router_digest_line = _parse_forty_character_hex('router-digest', '_digest')
@@ -411,6 +421,10 @@ class ServerDescriptor(Descriptor): :var int socks_port: ***** port used as client (deprecated, always **None**) :var int dir_port: ***** port used for descriptor mirroring
+ :var ed25519_certificate str: base64 encoded ed25519 certificate + :var ed25519_master_key str: base64 encoded master key for our ed25519 certificate + :var ed25519_signature str: signature of this document using ed25519 + :var bytes platform: line with operating system and tor version :var stem.version.Version tor_version: version of tor :var str operating_system: operating system @@ -434,6 +448,9 @@ class ServerDescriptor(Descriptor): :var list or_addresses: ***** alternative for our address/or_port attributes, each entry is a tuple of the form (address (**str**), port (**int**), is_ipv6 (**bool**)) + :var str onion_key_crosscert: signature generated using the onion_key + :var str ntor_onion_key_crosscert: signature generated using the ntor-onion-key + :var str ntor_onion_key_crosscert_sign: sign of the corresponding ed25519 public key
Deprecated, moved to extra-info descriptor...
@@ -447,6 +464,11 @@ class ServerDescriptor(Descriptor):
***** attribute is either required when we're parsed with validation or has a default value, others are left as **None** if undefined + + .. versionchanged:: 1.5.0 + Added the ed25519_certificate, ed25519_master_key, ed25519_signature, + onion_key_crosscert, ntor_onion_key_crosscert, and + ntor_onion_key_crosscert_sign attributes. """
ATTRIBUTES = { @@ -461,6 +483,10 @@ class ServerDescriptor(Descriptor): 'socks_port': (None, _parse_router_line), 'dir_port': (None, _parse_router_line),
+ 'ed25519_certificate': (None, _parse_identity_ed25519_line), + 'ed25519_master_key': (None, _parse_master_key_ed25519_line), + 'ed25519_signature': (None, _parse_router_sig_ed25519_line), + 'platform': (None, _parse_platform_line), 'tor_version': (None, _parse_platform_line), 'operating_system': (None, _parse_platform_line), @@ -481,6 +507,9 @@ class ServerDescriptor(Descriptor): 'hidden_service_dir': (None, _parse_hidden_service_dir_line), 'eventdns': (None, _parse_eventdns_line), 'or_addresses': ([], _parse_or_address_line), + 'onion_key_crosscert': (None, _parse_onion_key_crosscert_line), + 'ntor_onion_key_crosscert': (None, _parse_ntor_onion_key_crosscert_line), + 'ntor_onion_key_crosscert_sign': (None, _parse_ntor_onion_key_crosscert_line),
'read_history_end': (None, _parse_read_history_line), 'read_history_interval': (None, _parse_read_history_line), @@ -493,6 +522,9 @@ class ServerDescriptor(Descriptor):
PARSER_FOR_LINE = { 'router': _parse_router_line, + 'identity-ed25519': _parse_identity_ed25519_line, + 'master-key-ed25519': _parse_master_key_ed25519_line, + 'router-sig-ed25519': _parse_router_sig_ed25519_line, 'bandwidth': _parse_bandwidth_line, 'platform': _parse_platform_line, 'published': _parse_published_line, @@ -504,6 +536,8 @@ class ServerDescriptor(Descriptor): 'uptime': _parse_uptime_line, 'protocols': _parse_protocols_line, 'or-address': _parse_or_address_line, + 'onion-key-crosscert': _parse_onion_key_crosscert_line, + 'ntor-onion-key-crosscert': _parse_ntor_onion_key_crosscert_line, 'read-history': _parse_read_history_line, 'write-history': _parse_write_history_line, 'ipv6-policy': _parse_ipv6_policy_line, @@ -636,6 +670,12 @@ class ServerDescriptor(Descriptor): if not self.exit_policy: raise ValueError("Descriptor must have at least one 'accept' or 'reject' entry")
+ if self.ed25519_certificate: + if not self.onion_key_crosscert: + raise ValueError("Descriptor must have a 'onion-key-crosscert' when identity-ed25519 is present") + elif not self.ed25519_signature: + raise ValueError("Descriptor must have a 'router-sig-ed25519' when identity-ed25519 is present") + # Constraints that the descriptor must meet to be valid. These can be None if # not applicable.
diff --git a/test/unit/descriptor/data/extrainfo_descriptor_with_ed25519 b/test/unit/descriptor/data/extrainfo_descriptor_with_ed25519 new file mode 100644 index 0000000..d49ff7d --- /dev/null +++ b/test/unit/descriptor/data/extrainfo_descriptor_with_ed25519 @@ -0,0 +1,26 @@ +@type extra-info 1.0 +extra-info silverfoxden 4970B1DC3DBC8D82D7F1E43FF44B28DBF4765A4E +identity-ed25519 +-----BEGIN ED25519 CERT----- +AQQABhz0AQFcf5tGWLvPvr1sktoezBB95j6tAWSECa3Eo2ZuBtRNAQAgBABFAwSN +GcRlGIte4I1giLvQSTcXefT93rvx2PZ8wEDewxWdy6tzcLouPfE3Beu/eUyg8ntt +YuVlzi50WXzGlGnPmeounGLo0EDHTGzcLucFWpe0g/0ia6UDqgQiAySMBwI= +-----END ED25519 CERT----- +published 2015-08-22 19:21:12 +write-history 2015-08-22 19:20:44 (14400 s) 14409728,23076864,7756800,6234112,7446528,12290048 +read-history 2015-08-22 19:20:44 (14400 s) 20449280,23888896,9099264,7185408,8880128,13230080 +geoip-db-digest 6882B8663F74C23E26E3C2274C24CAB2E82D67A2 +geoip6-db-digest F063BD5247EB9829E6B9E586393D7036656DAF44 +dirreq-stats-end 2015-08-22 11:58:30 (86400 s) +dirreq-v3-ips +dirreq-v3-reqs +dirreq-v3-resp ok=0,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0 +dirreq-v3-direct-dl complete=0,timeout=0,running=0 +dirreq-v3-tunneled-dl complete=0,timeout=0,running=0 +router-sig-ed25519 g6Zg7Er8K7C1etmt7p20INE1ExIvMRPvhwt6sjbLqEK+EtQq8hT+86hQ1xu7cnz6bHee+Zhhmcc4JamV4eiMAw +router-signature +-----BEGIN SIGNATURE----- +R7kNaIWZrg3n3FWFBRMlEK2cbnha7gUIs8ToksLe+SF0dgoZiLyV3GKrnzdE/K6D +qdiOMN7eK04MOZVlgxkA5ayi61FTYVveK1HrDbJ+sEUwsviVGdif6kk/9DXOiyIJ +7wP/tofgHj/aCbFZb1PGU0zrEVLa72hVJ6cCW8w/t1s= +-----END SIGNATURE----- diff --git a/test/unit/descriptor/data/server_descriptor_with_ed25519 b/test/unit/descriptor/data/server_descriptor_with_ed25519 index 2f43ced..87d1d24 100644 --- a/test/unit/descriptor/data/server_descriptor_with_ed25519 +++ b/test/unit/descriptor/data/server_descriptor_with_ed25519 @@ -1,50 +1,72 @@ @type server-descriptor 1.0 -router Truie 198.50.156.78 9001 0 9030 +router destiny 94.242.246.23 9001 0 443 identity-ed25519 -----BEGIN ED25519 CERT----- -AQQABhWIAZTz0r0KRagr6X9SHfm4oiIuMLVhJQQmNchtkBuR5SuFAQAgBAAVkw7m -0YJgO/A8VMioco097sIOutDiM7UqqPvoIyKErk1akOm3f6VAO/juOzxEeAgzgfA7 -DiRsSjeVjp0xUdE43bXhK/8Uh+SPMwYKj47drjgTHGgzjTmlY9B/jFJ1Wgs= +AQQABhtZAaW2GoBED1IjY3A6f6GNqBEl5A83fD2Za9upGke51JGqAQAgBABnprVR +ptIr43bWPo2fIzo3uOywfoMrryprpbm4HhCkZMaO064LP+1KNuLvlc8sGG8lTjx1 +g4k3ELuWYgHYWU5rAia7nl4gUfBZOEfHAfKES7l3d63dBEjEX98Ljhdp2w4= -----END ED25519 CERT----- -platform Tor 0.2.7.1-alpha-dev on Linux +master-key-ed25519 Z6a1UabSK+N21j6NnyM6N7jssH6DK68qa6W5uB4QpGQ +or-address [2a01:608:ffff:ff07::1:23]:9003 +platform Tor 0.2.7.2-alpha-dev on Linux protocols Link 1 2 Circuit 1 -published 2015-05-28 15:44:47 -fingerprint A692 21A7 EC74 98D2 F88A 0FB7 9526 1013 FA36 CAAE -uptime 61 -bandwidth 1073741824 1073741824 9506816 -extra-info-digest 0879DB7B765218D7B3AE7557669D20307BB21CAA V609l+N6ActBveebfNbH5lQ6wHDNstDkFgyqEhBHwtA +published 2015-08-22 15:21:45 +fingerprint F65E 0196 C94D FFF4 8AFB F2F5 F9E3 E19A AE58 3FD0 +uptime 1362680 +bandwidth 149715200 1048576000 51867731 +extra-info-digest 44E9B679AF0B4EB09296985BAF4066AE9CA5BB93 r+roMxhsjd1GPpn5knQoBvtE9Rhsv8zQHCqiYL6u2CA onion-key -----BEGIN RSA PUBLIC KEY----- -MIGJAoGBALbTpnPvhaGET+2ACtLdG6jhQXN8uVJ0iF9RwMh2hwu351yp3eVPt7os -ditUF6w7KV+6emkvLu9EBpNN7vWrpDAhRNOGTOZhZKLnGFaxp+eGNX6+5AhmiWYt -/+w+f6dvVKEjsaX3XZsMqcTBjw2hzVpHxh/AjgDx/b9mJKC85vENAgMBAAE= +MIGJAoGBAKpPOeBPFBZhH32k0CmIVsXMi4mbbkpEAYpZD0Z3/zLc9k05qAvhE55h ++LXqG6C6k23JnR7H1a4EtFU0UQVWxUa4xUL9pi/0tj3Zsu842Z18K3sL8hYWDw6x +b6afVdSKIcY6guG5fevmobUd/6437oSwM7IeXrWy28s0PtWKHhQzAgMBAAE= -----END RSA PUBLIC KEY----- signing-key -----BEGIN RSA PUBLIC KEY----- -MIGJAoGBALDSt2G+Zjl20a59HZsuag913ONdnnNa/uVMRbsZZkbnNRONf2aXBGgu -wrW7XtPLeAKl+d0d5g9XnePVvefcEdKvoKNCFv6s8s3S2KB/CEkeyE7Lxx1Pc6Qx -f/jgS3T3TFHUlvtZvHLZ/3WaXMyuTTRlGadpzDkQx5oWR6aNn065AgMBAAE= +MIGJAoGBAOUS7xm+1d/FAk7VHx2SaYzjYoGpNaCHHWXlmDz2+iWEqcDRjjnVFekV +sfAPysNnB0a/lHdrqzyKjCkzAoeut5Ts3bj6eMrF3psFian2IqdlqsFaAcBov7fo +J6ipwr8lP72LOMHlB2AwP3BEWtHZX7nmARV7ekbPs21R06lEhzLLAgMBAAE= -----END RSA PUBLIC KEY----- onion-key-crosscert -----BEGIN CROSSCERT----- -TCcCIv38fGcSzUO+DKxudFme2XBRuDkf5FjEr+6UbtDyuDjvjJDFYagN+zMJf/4K -RyBScjyKYK6MVMxAmf25QjAGx3KHV00ozVSzlN3WDAS2iicuKYvBsehG9g/tr6mI -luS5EoSKJIlmM2jOhN1QyR+Rpi37z/E6VTksk/bd69A= +iW8BqwH5VKqZaiMgPcuHIQFpiQnRsd2b1zc+PXVN3AFT0cQx6J4rZhIdxiqHeNqj +fVEoi4+iHkbksGABZKlB/x7Kv2Kvbj3ZH46m22KEASkRL+i9EhCYdf3Ju7czIi/7 +U/jQTwhn7+o8LCLsLhw3aV/v/sXEtbxePhMbCMHI7hE= -----END CROSSCERT----- ntor-onion-key-crosscert 0 -----BEGIN ED25519 CERT----- -AQoABhNgARWTDubRgmA78DxUyKhyjT3uwg660OIztSqo++gjIoSuAEW8gwMcFUSD -mfkijKN6KyZxHloENGcgJMeJsR9kvfYp/u7O+VoPQ1kTxaw1lajTrnGQF+PV1MlK -niid4Nq5ZgM= +AQoABhtwAWemtVGm0ivjdtY+jZ8jOje47LB+gyuvKmulubgeEKRkAHj4IPqm+osx +vbKfvRHeZ0uaghFPZr76UVPYwuK4N+VcW75yq2vuFSsFTCJqamPB3PIdSz6rbx4U +4F3iroztLAQ= -----END ED25519 CERT----- +family $379FB450010D17078B3766C2273303C358C3A442 $3EB46C1D8D8B1C0BBCB6E4F08301EF68B7F5308D $B0279A521375F3CB2AE210BDBFC645FDD2E1973A $EC116BCB80565A408CE67F8EC3FE3B0B02C3A065 hidden-service-dir -contact 0x11F48D36 David Goulet <dgoulet AT ev0ke dot net> -ntor-onion-key qDcuoDpDD36bIapIbXBVhkIoiuMIXD9jNfjF1+7Vaks= -reject *:* -router-sig-ed25519 AxqrLz7QL/e+xGhhihs/rNzWsBW0Qla7Cwru1q88A5i+pcQBgfzfECiecptqYbDAsUPXMtwFsLp7Ls2BMOzvCQ +contact 0x02225522 Frenn vun der Enn (FVDE) <info AT enn DOT lu> +ntor-onion-key JCj8BOqk0Khfp1hfoJaDbSTzNgeA/u2pSAXnaR3vhl0= +reject 0.0.0.0/8:* +reject 169.254.0.0/16:* +reject 127.0.0.0/8:* +reject 192.168.0.0/16:* +reject 10.0.0.0/8:* +reject 172.16.0.0/12:* +reject 94.242.246.23:* +reject *:25 +reject *:587 +reject *:465 +reject 176.67.160.187:* +reject 185.35.77.160:* +reject 185.35.77.250:* +reject *:10000 +reject *:14464 +reject 94.100.180.202:* +reject 217.69.139.215:* +reject 217.69.140.233:* +accept *:* +ipv6-policy reject 25,465,587,10000,14464 +router-sig-ed25519 w+cKNZTlL7vz/4WgYdFUblzJy3VdTw0mfFK4N3SPFCt20fNKt9SgiZ5V/2ai3kgGsc6oCsyUesSiYtPcTXMLCw router-signature -----BEGIN SIGNATURE----- -mSkveaqx79vzXLc6yC2+x8yZMQPe74ihw9tZJDdSOK5VqhzZOKHFM+JoD12noxQd -wgxa+IX0RG65KlguYE7NEZ7M6JOwr6r0zK/pWSZE8ZeHyt7FDx9ygc3k2ybQ6RWE -Hd7QXPiyVgs9cIgnvGFVt/5vzjMV+BELpOtehBrUJbs= +y72z1dZOYxVQVLRMvEJOn9lOFxBsjojpwiYxw+3vWFHnhkOdGqolxJ6gTLhiIXNu +ckBPqxjbpFbmt6qgk0oeivwyLo9o4nZT737d3tx1EuBmxo+gqzNtukXWzJzZFIj5 +xE0eo9e/zKPSCF/LK6zv0FSefdBpnEkYYFuGN0BCrZo= -----END SIGNATURE----- diff --git a/test/unit/descriptor/extrainfo_descriptor.py b/test/unit/descriptor/extrainfo_descriptor.py index 5cb3205..b4bc34f 100644 --- a/test/unit/descriptor/extrainfo_descriptor.py +++ b/test/unit/descriptor/extrainfo_descriptor.py @@ -137,6 +137,20 @@ k0d2aofcVbHr4fPQOSST0LXDrhFl5Fqo5um296zpJGvRUeO6S44U/EfJAGShtqWw self.assertEqual('478B4CB438302981DE9AAF246F48DBE57F69050A', desc_list[4].fingerprint) self.assertEqual('25D9D52A0350B42E69C8AB7CE945DB1CA38DA0CF', desc_list[5].fingerprint)
+ def test_with_ed25519(self): + """ + Parses a descriptor with a ed25519 identity key. + """ + + with open(get_resource('extrainfo_descriptor_with_ed25519'), 'rb') as descriptor_file: + desc = next(stem.descriptor.parse_file(descriptor_file, 'extra-info 1.0', validate = True)) + + self.assertEqual('silverfoxden', desc.nickname) + self.assertEqual('4970B1DC3DBC8D82D7F1E43FF44B28DBF4765A4E', desc.fingerprint) + self.assertTrue('AQQABhz0AQFcf5tGWLvPvr' in desc.ed25519_certificate) + self.assertEqual('g6Zg7Er8K7C1etmt7p20INE1ExIvMRPvhwt6sjbLqEK+EtQq8hT+86hQ1xu7cnz6bHee+Zhhmcc4JamV4eiMAw', desc.ed25519_signature) + self.assertEqual([], desc.get_unrecognized_lines()) + def test_minimal_extrainfo_descriptor(self): """ Basic sanity check that we can parse an extrainfo descriptor with minimal diff --git a/test/unit/descriptor/router_status_entry.py b/test/unit/descriptor/router_status_entry.py index 14df828..774a3e4 100644 --- a/test/unit/descriptor/router_status_entry.py +++ b/test/unit/descriptor/router_status_entry.py @@ -17,6 +17,39 @@ from test.mocking import ( ROUTER_STATUS_ENTRY_V3_HEADER, )
+ENTRY_WITHOUT_ED25519 = """\ +r seele AAoQ1DAR6kkoo19hBAX5K0QztNw m0ynPuwzSextzsiXYJYA0Hce+Cs 2015-08-23 00:26:35 73.15.150.172 9001 0 +s Running Stable Valid +v Tor 0.2.6.10 +w Bandwidth=102 Measured=31 +p reject 1-65535 +id ed25519 none +m 13,14,15 sha256=uaAYTOVuYRqUwJpNfP2WizjzO0FiNQB4U97xSQu+vMc +m 16,17 sha256=G6FmPe/ehgfb6tsRzFKDCwvvae+RICeP1MaP0vWDGyI +m 18,19,20,21 sha256=/XhIMOnhElo2UiKjL2S10uRka/fhg1CFfNd+9wgUwEE +""" + +ENTRY_WITH_ED25519 = """\ +r PDrelay1 AAFJ5u9xAqrKlpDW6N0pMhJLlKs yrJ6b/73pmHBiwsREgw+inf8WFw 2015-08-23 16:52:37 95.215.44.189 8080 0 +s Fast Running Stable Valid +v Tor 0.2.7.2-alpha-dev +w Bandwidth=608 Measured=472 +p reject 1-65535 +id ed25519 8RH34kO07Pp+XYwzdoATVyCibIvmbslUjRkAm7J4IA8 +m 13 sha256=PTSHzE7RKnRGZMRmBddSzDiZio254FUhv9+V4F5zq8s +m 14,15 sha256=0wsEwBbxJ8RtPmGYwilHQTVEw2pWzUBEVlSgEO77OyU +m 16,17 sha256=JK2xhYr/VsCF60px+LsT990BCpfKfQTeMxRbD63o2vE +m 18,19,20 sha256=AkZH3gIvz3wunsroqh5izBJizdYuR7kn2oVbsvqgML8 +m 21 sha256=AVp41YVxKEJCaoEf0+77Cdvyw5YgpyDXdob0+LSv/pE +""" + + +def vote_document(): + mock_document = lambda x: x # just need anything with a __dict__ + setattr(mock_document, 'is_vote', True) + setattr(mock_document, 'is_consensus', False) + return mock_document +
class TestRouterStatusEntry(unittest.TestCase): def test_fingerprint_decoding(self): @@ -86,6 +119,8 @@ class TestRouterStatusEntry(unittest.TestCase): self.assertEqual([], entry.unrecognized_bandwidth_entries) self.assertEqual(None, entry.exit_policy) self.assertEqual([], entry.microdescriptor_hashes) + self.assertEqual(None, entry.identifier_type) + self.assertEqual(None, entry.identifier) self.assertEqual([], entry.get_unrecognized_lines())
def test_minimal_micro_v3(self): @@ -109,6 +144,72 @@ class TestRouterStatusEntry(unittest.TestCase): self.assertEqual('6A252497006BB9AF36A1B1B902C4D7FA2129923400DBE0101F167B1B031F63BD', entry.digest) self.assertEqual([], entry.get_unrecognized_lines())
+ def test_without_ed25519(self): + """ + Parses a router status entry without a ed25519 value. + """ + + microdescriptor_hashes = [ + ([13, 14, 15], {'sha256': 'uaAYTOVuYRqUwJpNfP2WizjzO0FiNQB4U97xSQu+vMc'}), + ([16, 17], {'sha256': 'G6FmPe/ehgfb6tsRzFKDCwvvae+RICeP1MaP0vWDGyI'}), + ([18, 19, 20, 21], {'sha256': '/XhIMOnhElo2UiKjL2S10uRka/fhg1CFfNd+9wgUwEE'}), + ] + + entry = RouterStatusEntryV3(ENTRY_WITHOUT_ED25519, document = vote_document(), validate = True) + self.assertEqual('seele', entry.nickname) + self.assertEqual('000A10D43011EA4928A35F610405F92B4433B4DC', entry.fingerprint) + self.assertEqual(datetime.datetime(2015, 8, 23, 0, 26, 35), entry.published) + self.assertEqual('73.15.150.172', entry.address) + self.assertEqual(9001, entry.or_port) + self.assertEqual(None, entry.dir_port) + self.assertEqual(set([Flag.RUNNING, Flag.STABLE, Flag.VALID]), set(entry.flags)) + self.assertEqual('Tor 0.2.6.10', entry.version_line) + self.assertEqual(Version('0.2.6.10'), entry.version) + self.assertEqual(102, entry.bandwidth) + self.assertEqual(31, entry.measured) + self.assertEqual(False, entry.is_unmeasured) + self.assertEqual([], entry.unrecognized_bandwidth_entries) + self.assertEqual(MicroExitPolicy('reject 1-65535'), entry.exit_policy) + self.assertEqual(microdescriptor_hashes, entry.microdescriptor_hashes) + self.assertEqual('ed25519', entry.identifier_type) + self.assertEqual('none', entry.identifier) + self.assertEqual('9B4CA73EEC3349EC6DCEC897609600D0771EF82B', entry.digest) + self.assertEqual([], entry.get_unrecognized_lines()) + + def test_with_ed25519(self): + """ + Parses a router status entry with a ed25519 value. + """ + + microdescriptor_hashes = [ + ([13], {'sha256': 'PTSHzE7RKnRGZMRmBddSzDiZio254FUhv9+V4F5zq8s'}), + ([14, 15], {'sha256': '0wsEwBbxJ8RtPmGYwilHQTVEw2pWzUBEVlSgEO77OyU'}), + ([16, 17], {'sha256': 'JK2xhYr/VsCF60px+LsT990BCpfKfQTeMxRbD63o2vE'}), + ([18, 19, 20], {'sha256': 'AkZH3gIvz3wunsroqh5izBJizdYuR7kn2oVbsvqgML8'}), + ([21], {'sha256': 'AVp41YVxKEJCaoEf0+77Cdvyw5YgpyDXdob0+LSv/pE'}), + ] + + entry = RouterStatusEntryV3(ENTRY_WITH_ED25519, document = vote_document(), validate = True) + self.assertEqual('PDrelay1', entry.nickname) + self.assertEqual('000149E6EF7102AACA9690D6E8DD2932124B94AB', entry.fingerprint) + self.assertEqual(datetime.datetime(2015, 8, 23, 16, 52, 37), entry.published) + self.assertEqual('95.215.44.189', entry.address) + self.assertEqual(8080, entry.or_port) + self.assertEqual(None, entry.dir_port) + self.assertEqual(set([Flag.FAST, Flag.RUNNING, Flag.STABLE, Flag.VALID]), set(entry.flags)) + self.assertEqual('Tor 0.2.7.2-alpha-dev', entry.version_line) + self.assertEqual(Version('0.2.7.2-alpha-dev'), entry.version) + self.assertEqual(608, entry.bandwidth) + self.assertEqual(472, entry.measured) + self.assertEqual(False, entry.is_unmeasured) + self.assertEqual([], entry.unrecognized_bandwidth_entries) + self.assertEqual(MicroExitPolicy('reject 1-65535'), entry.exit_policy) + self.assertEqual(microdescriptor_hashes, entry.microdescriptor_hashes) + self.assertEqual('ed25519', entry.identifier_type) + self.assertEqual('8RH34kO07Pp+XYwzdoATVyCibIvmbslUjRkAm7J4IA8', entry.identifier) + self.assertEqual('CAB27A6FFEF7A661C18B0B11120C3E8A77FC585C', entry.digest) + self.assertEqual([], entry.get_unrecognized_lines()) + def test_missing_fields(self): """ Parses a router status entry that's missing fields. @@ -478,14 +579,9 @@ class TestRouterStatusEntry(unittest.TestCase): [([8, 9, 10, 11, 12], {'sha256': 'g1vx9si329muxV', 'md5': '3tquWIXXySNOIwRGMeAESKs/v4DWs'})], }
- # we need a document that's a vote - mock_document = lambda x: x # just need anything with a __dict__ - setattr(mock_document, 'is_vote', True) - setattr(mock_document, 'is_consensus', False) - for m_line, expected in test_values.items(): content = get_router_status_entry_v3({'m': m_line}, content = True) - entry = RouterStatusEntryV3(content, document = mock_document) + entry = RouterStatusEntryV3(content, document = vote_document()) self.assertEqual(expected, entry.microdescriptor_hashes)
# try with multiple 'm' lines @@ -499,7 +595,7 @@ class TestRouterStatusEntry(unittest.TestCase): ([31, 32], {'sha512': 'g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs'}), ]
- entry = RouterStatusEntryV3(content, document = mock_document) + entry = RouterStatusEntryV3(content, document = vote_document()) self.assertEqual(expected, entry.microdescriptor_hashes)
# try without a document @@ -515,7 +611,7 @@ class TestRouterStatusEntry(unittest.TestCase):
for m_line in test_values: content = get_router_status_entry_v3({'m': m_line}, content = True) - self.assertRaises(ValueError, RouterStatusEntryV3, content, True, mock_document) + self.assertRaises(ValueError, RouterStatusEntryV3, content, True, vote_document())
def _expect_invalid_attr(self, content, attr = None, expected_value = None): """ diff --git a/test/unit/descriptor/server_descriptor.py b/test/unit/descriptor/server_descriptor.py index 4ee60f3..1218bee 100644 --- a/test/unit/descriptor/server_descriptor.py +++ b/test/unit/descriptor/server_descriptor.py @@ -109,6 +109,9 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= self.assertEqual(9001, desc.or_port) self.assertEqual(None, desc.socks_port) self.assertEqual(None, desc.dir_port) + self.assertEqual(None, desc.ed25519_certificate) + self.assertEqual(None, desc.ed25519_master_key) + self.assertEqual(None, desc.ed25519_signature) self.assertEqual(b'Tor 0.2.1.30 on Linux x86_64', desc.platform) self.assertEqual(stem.version.Version('0.2.1.30'), desc.tor_version) self.assertEqual('Linux x86_64', desc.operating_system) @@ -128,6 +131,9 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= self.assertEqual(104590, desc.observed_bandwidth) self.assertEqual(stem.exit_policy.ExitPolicy('reject *:*'), desc.exit_policy) self.assertEqual(expected_onion_key, desc.onion_key) + self.assertEqual(None, desc.onion_key_crosscert) + self.assertEqual(None, desc.ntor_onion_key_crosscert) + self.assertEqual(None, desc.onion_key_crosscert) self.assertEqual(expected_signing_key, desc.signing_key) self.assertEqual(expected_signature, desc.signature) self.assertEqual([], desc.get_unrecognized_lines()) @@ -245,35 +251,49 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= with open(get_resource('server_descriptor_with_ed25519'), 'rb') as descriptor_file: desc = next(stem.descriptor.parse_file(descriptor_file, 'server-descriptor 1.0', validate = True))
- self.assertEqual('Truie', desc.nickname) - self.assertEqual('A69221A7EC7498D2F88A0FB795261013FA36CAAE', desc.fingerprint) - self.assertEqual('198.50.156.78', desc.address) + family = set([ + '$379FB450010D17078B3766C2273303C358C3A442', + '$3EB46C1D8D8B1C0BBCB6E4F08301EF68B7F5308D', + '$B0279A521375F3CB2AE210BDBFC645FDD2E1973A', + '$EC116BCB80565A408CE67F8EC3FE3B0B02C3A065', + ]) + + self.assertEqual('destiny', desc.nickname) + self.assertEqual('F65E0196C94DFFF48AFBF2F5F9E3E19AAE583FD0', desc.fingerprint) + self.assertEqual('94.242.246.23', desc.address) self.assertEqual(9001, desc.or_port) self.assertEqual(None, desc.socks_port) - self.assertEqual(9030, desc.dir_port) - self.assertEqual(b'Tor 0.2.7.1-alpha-dev on Linux', desc.platform) - self.assertEqual(stem.version.Version('0.2.7.1-alpha-dev'), desc.tor_version) + self.assertEqual(443, desc.dir_port) + self.assertTrue('bWPo2fIzo3uOywfoM' in desc.ed25519_certificate) + self.assertEqual('Z6a1UabSK+N21j6NnyM6N7jssH6DK68qa6W5uB4QpGQ', desc.ed25519_master_key) + self.assertEqual('w+cKNZTlL7vz/4WgYdFUblzJy3VdTw0mfFK4N3SPFCt20fNKt9SgiZ5V/2ai3kgGsc6oCsyUesSiYtPcTXMLCw', desc.ed25519_signature) + self.assertEqual(b'Tor 0.2.7.2-alpha-dev on Linux', desc.platform) + self.assertEqual(stem.version.Version('0.2.7.2-alpha-dev'), desc.tor_version) self.assertEqual('Linux', desc.operating_system) - self.assertEqual(61, desc.uptime) - self.assertEqual(datetime.datetime(2015, 5, 28, 15, 44, 47), desc.published) - self.assertEqual(b'0x11F48D36 David Goulet <dgoulet AT ev0ke dot net>', desc.contact) + self.assertEqual(1362680, desc.uptime) + self.assertEqual(datetime.datetime(2015, 8, 22, 15, 21, 45), desc.published) + self.assertEqual(b'0x02225522 Frenn vun der Enn (FVDE) <info AT enn DOT lu>', desc.contact) self.assertEqual(['1', '2'], desc.link_protocols) self.assertEqual(['1'], desc.circuit_protocols) self.assertEqual(False, desc.hibernating) self.assertEqual(False, desc.allow_single_hop_exits) self.assertEqual(False, desc.extra_info_cache) - self.assertEqual('0879DB7B765218D7B3AE7557669D20307BB21CAA', desc.extra_info_digest) + self.assertEqual('44E9B679AF0B4EB09296985BAF4066AE9CA5BB93', desc.extra_info_digest) self.assertEqual(['2'], desc.hidden_service_dir) - self.assertEqual(set(), desc.family) - self.assertEqual(1073741824, desc.average_bandwidth) - self.assertEqual(1073741824, desc.burst_bandwidth) - self.assertEqual(9506816, desc.observed_bandwidth) - self.assertEqual(stem.exit_policy.ExitPolicy('reject *:*'), desc.exit_policy) - self.assertTrue('MIGJAoGBALbTpn' in desc.onion_key) - self.assertTrue('MIGJAoGBALDSt2' in desc.signing_key) - self.assertTrue('mSkveaqx79vzX' in desc.signature) - self.assertEqual(4, len(desc.get_unrecognized_lines())) - self.assertEqual('B0445BC590F004B8FD3BE922EB19EC490DBA9077', desc.digest()) + self.assertEqual(family, desc.family) + self.assertEqual(149715200, desc.average_bandwidth) + self.assertEqual(1048576000, desc.burst_bandwidth) + self.assertEqual(51867731, desc.observed_bandwidth) + self.assertTrue(desc.exit_policy is not None) + self.assertEqual(stem.exit_policy.MicroExitPolicy('reject 25,465,587,10000,14464'), desc.exit_policy_v6) + self.assertTrue('MIGJAoGBAKpPOe' in desc.onion_key) + self.assertTrue('iW8BqwH5VKqZai' in desc.onion_key_crosscert) + self.assertTrue('AQoABhtwAWemtV' in desc.ntor_onion_key_crosscert) + self.assertEqual('0', desc.ntor_onion_key_crosscert_sign) + self.assertTrue('MIGJAoGBAOUS7x' in desc.signing_key) + self.assertTrue('y72z1dZOYxVQVL' in desc.signature) + self.assertEqual([], desc.get_unrecognized_lines()) + self.assertEqual('B5E441051D139CCD84BC765D130B01E44DAC29AD', desc.digest())
def test_cr_in_contact_line(self): """
tor-commits@lists.torproject.org