[tor-commits] [stem/master] Support for ed25519 descriptor fields
atagar at torproject.org
atagar at torproject.org
Sun Aug 23 21:58:43 UTC 2015
commit 353200c26399e5727a6865ddcd83b1820f3e1b6f
Author: Damian Johnson <atagar at 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=5a79d67a45454ab5b7413478702acb93dfa867e2
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 @@
+ at 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):
"""
More information about the tor-commits
mailing list