tor-commits
Threads by month
- ----- 2025 -----
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
December 2015
- 18 participants
- 1281 discussions

[stem/master] Revert "Controller method for getting custom config options"
by atagar@torproject.org 22 Dec '15
by atagar@torproject.org 22 Dec '15
22 Dec '15
commit a4b5f9fd39e927f5d92eb1c489601d4889af3870
Author: Damian Johnson <atagar(a)torproject.org>
Date: Tue Dec 22 09:55:21 2015 -0800
Revert "Controller method for getting custom config options"
Well... damn. Just realized a better option for the purposes I wrote this
method for. Still might do something like this in the future but for now lets
not expand our Controller. It already has too many methods. :)
---
docs/change_log.rst | 1 -
stem/…
[View More]control.py | 48 --------------------------------------
test/integ/control/controller.py | 20 ----------------
3 files changed, 69 deletions(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst
index 39d109c..87ea053 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -48,7 +48,6 @@ The following are only available within Stem's `git repository
* Added `stem.manual <api/manual.html>`_, which provides information available about Tor from `its manual <https://www.torproject.org/docs/tor-manual.html.en>`_ (:trac:`8251`)
* :func:`~stem.connection.connect` and :func:`~stem.control.Controller.from_port` now connect to both port 9051 (relay's default) and 9151 (Tor Browser's default) (:trac:`16075`)
* Added `support for NETWORK_LIVENESS events <api/response.html#stem.response.events.NetworkLivenessEvent>`_ (:spec:`44aac63`)
- * Added :func:`~stem.control.Controller.get_custom_conf` to the :class:`~stem.control.Controller`
* Added :func:`~stem.control.Controller.is_user_traffic_allowed` to the :class:`~stem.control.Controller`
* Added the replica attribute to the :class:`~stem.response.events.HSDescEvent` (:spec:`4989e73`)
* IPv6 addresses could trigger errors in :func:`~stem.control.Controller.get_listeners`, :class:`~stem.response.events.ORConnEvent`, and quite a few other things (:trac:`16174`)
diff --git a/stem/control.py b/stem/control.py
index 91c607e..3b1edfa 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -93,7 +93,6 @@ If you're fine with allowing your script to raise exceptions then this can be mo
|
|- get_conf - gets the value of a configuration option
|- get_conf_map - gets the values of multiple configuration options
- |- get_custom_conf - provides configuration options that differ from their defaults
|- set_conf - sets the value of a configuration option
|- reset_conf - reverts configuration options to their default values
|- set_options - sets or resets the values of multiple configuration options
@@ -1987,8 +1986,6 @@ class Controller(BaseController):
def get_conf(self, param, default = UNDEFINED, multiple = False):
"""
- get_conf(param, default = UNDEFINED, multiple = False)
-
Queries the current value for a configuration option. Some configuration
options (like the ExitPolicy) can have multiple values. This provides a
**list** with all of the values if **multiple** is **True**. Otherwise this
@@ -2036,8 +2033,6 @@ class Controller(BaseController):
def get_conf_map(self, params, default = UNDEFINED, multiple = True):
"""
- get_conf_map(params, default = UNDEFINED, multiple = True)
-
Similar to :func:`~stem.control.Controller.get_conf` but queries multiple
configuration options, providing back a mapping of those options to their
values.
@@ -2175,49 +2170,6 @@ class Controller(BaseController):
return return_dict
- @with_default()
- def get_custom_conf(self, default = UNDEFINED, include_values = False):
- """
- get_custom_conf(params, default = UNDEFINED, multiple = True)
-
- Provides tor configuration options that differ from their defaults.
-
- .. versionadded:: 1.5.0
-
- :param object default: response if the query fails
- :param bool include_values: provides full configuration lines that can be
- saved, similar to 'GETINFO config-text'
-
- :returns: **list** of configuration options that have been set, or config
- lines if **include_values** is **True**
-
- :raises: :class:`stem.ControllerError` if the call fails and we weren't
- provided a default response
- """
-
- config_lines = self.get_info('config-text').splitlines()
-
- # Tor provides some config options even if they haven't been set...
- #
- # https://trac.torproject.org/projects/tor/ticket/2362
- # https://trac.torproject.org/projects/tor/ticket/17909
-
- default_lines = (
- 'Log notice stdout',
- 'Log notice file /var/log/tor/log',
- 'DataDirectory /home/%s/.tor' % self.get_user('undefined'),
- 'HiddenServiceStatistics 0',
- )
-
- for line in default_lines:
- if line in config_lines:
- config_lines.remove(line)
-
- if include_values:
- return config_lines
- else:
- return list(set([line.split(' ')[0] for line in config_lines]))
-
def set_conf(self, param, value):
"""
Changes the value of a tor configuration option. Our value can be any of
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index cf604c6..84bf58a 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -189,8 +189,6 @@ class TestController(unittest.TestCase):
self.assertTrue(isinstance(event, stem.response.events.ConfChangedEvent))
- controller.reset_conf('NodeFamily')
-
@require_controller
def test_reattaching_listeners(self):
"""
@@ -470,24 +468,6 @@ class TestController(unittest.TestCase):
self.assertEqual({}, controller.get_conf_map([], 'la-di-dah'))
@require_controller
- def test_get_custom_conf(self):
- """
- Exercises our get_custom_conf() method.
- """
-
- runner = test.runner.get_runner()
-
- with runner.get_tor_controller() as controller:
- custom_options = controller.get_custom_conf()
- self.assertTrue('ControlPort' in custom_options or 'ControlSocket' in custom_options)
- self.assertTrue('DownloadExtraInfo' in custom_options)
- self.assertTrue('SocksListenAddress' in custom_options)
-
- custom_options = controller.get_custom_conf(include_values = True)
- self.assertTrue('DownloadExtraInfo 1' in custom_options)
- self.assertTrue('SocksListenAddress 127.0.0.1:1112' in custom_options)
-
- @require_controller
def test_hidden_services_conf(self):
"""
Exercises the hidden service family of methods (get_hidden_service_conf,
[View Less]
1
0

[stem/master] Controller method for getting custom config options
by atagar@torproject.org 22 Dec '15
by atagar@torproject.org 22 Dec '15
22 Dec '15
commit e1124d2d29f8cd92d88d46f1024c81eafd5595f1
Author: Damian Johnson <atagar(a)torproject.org>
Date: Tue Dec 22 09:28:12 2015 -0800
Controller method for getting custom config options
Tor doesn't provide a reliable method of checking 'which config options have I
set?'. As such, adding one.
---
docs/change_log.rst | 1 +
stem/control.py | 48 ++++++++++++++++++++++++++++++++++++++
test/integ/control/controller.py | 20 +++++++++++++…
[View More]+++
3 files changed, 69 insertions(+)
diff --git a/docs/change_log.rst b/docs/change_log.rst
index 87ea053..39d109c 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -48,6 +48,7 @@ The following are only available within Stem's `git repository
* Added `stem.manual <api/manual.html>`_, which provides information available about Tor from `its manual <https://www.torproject.org/docs/tor-manual.html.en>`_ (:trac:`8251`)
* :func:`~stem.connection.connect` and :func:`~stem.control.Controller.from_port` now connect to both port 9051 (relay's default) and 9151 (Tor Browser's default) (:trac:`16075`)
* Added `support for NETWORK_LIVENESS events <api/response.html#stem.response.events.NetworkLivenessEvent>`_ (:spec:`44aac63`)
+ * Added :func:`~stem.control.Controller.get_custom_conf` to the :class:`~stem.control.Controller`
* Added :func:`~stem.control.Controller.is_user_traffic_allowed` to the :class:`~stem.control.Controller`
* Added the replica attribute to the :class:`~stem.response.events.HSDescEvent` (:spec:`4989e73`)
* IPv6 addresses could trigger errors in :func:`~stem.control.Controller.get_listeners`, :class:`~stem.response.events.ORConnEvent`, and quite a few other things (:trac:`16174`)
diff --git a/stem/control.py b/stem/control.py
index 3b1edfa..91c607e 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -93,6 +93,7 @@ If you're fine with allowing your script to raise exceptions then this can be mo
|
|- get_conf - gets the value of a configuration option
|- get_conf_map - gets the values of multiple configuration options
+ |- get_custom_conf - provides configuration options that differ from their defaults
|- set_conf - sets the value of a configuration option
|- reset_conf - reverts configuration options to their default values
|- set_options - sets or resets the values of multiple configuration options
@@ -1986,6 +1987,8 @@ class Controller(BaseController):
def get_conf(self, param, default = UNDEFINED, multiple = False):
"""
+ get_conf(param, default = UNDEFINED, multiple = False)
+
Queries the current value for a configuration option. Some configuration
options (like the ExitPolicy) can have multiple values. This provides a
**list** with all of the values if **multiple** is **True**. Otherwise this
@@ -2033,6 +2036,8 @@ class Controller(BaseController):
def get_conf_map(self, params, default = UNDEFINED, multiple = True):
"""
+ get_conf_map(params, default = UNDEFINED, multiple = True)
+
Similar to :func:`~stem.control.Controller.get_conf` but queries multiple
configuration options, providing back a mapping of those options to their
values.
@@ -2170,6 +2175,49 @@ class Controller(BaseController):
return return_dict
+ @with_default()
+ def get_custom_conf(self, default = UNDEFINED, include_values = False):
+ """
+ get_custom_conf(params, default = UNDEFINED, multiple = True)
+
+ Provides tor configuration options that differ from their defaults.
+
+ .. versionadded:: 1.5.0
+
+ :param object default: response if the query fails
+ :param bool include_values: provides full configuration lines that can be
+ saved, similar to 'GETINFO config-text'
+
+ :returns: **list** of configuration options that have been set, or config
+ lines if **include_values** is **True**
+
+ :raises: :class:`stem.ControllerError` if the call fails and we weren't
+ provided a default response
+ """
+
+ config_lines = self.get_info('config-text').splitlines()
+
+ # Tor provides some config options even if they haven't been set...
+ #
+ # https://trac.torproject.org/projects/tor/ticket/2362
+ # https://trac.torproject.org/projects/tor/ticket/17909
+
+ default_lines = (
+ 'Log notice stdout',
+ 'Log notice file /var/log/tor/log',
+ 'DataDirectory /home/%s/.tor' % self.get_user('undefined'),
+ 'HiddenServiceStatistics 0',
+ )
+
+ for line in default_lines:
+ if line in config_lines:
+ config_lines.remove(line)
+
+ if include_values:
+ return config_lines
+ else:
+ return list(set([line.split(' ')[0] for line in config_lines]))
+
def set_conf(self, param, value):
"""
Changes the value of a tor configuration option. Our value can be any of
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 84bf58a..cf604c6 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -189,6 +189,8 @@ class TestController(unittest.TestCase):
self.assertTrue(isinstance(event, stem.response.events.ConfChangedEvent))
+ controller.reset_conf('NodeFamily')
+
@require_controller
def test_reattaching_listeners(self):
"""
@@ -468,6 +470,24 @@ class TestController(unittest.TestCase):
self.assertEqual({}, controller.get_conf_map([], 'la-di-dah'))
@require_controller
+ def test_get_custom_conf(self):
+ """
+ Exercises our get_custom_conf() method.
+ """
+
+ runner = test.runner.get_runner()
+
+ with runner.get_tor_controller() as controller:
+ custom_options = controller.get_custom_conf()
+ self.assertTrue('ControlPort' in custom_options or 'ControlSocket' in custom_options)
+ self.assertTrue('DownloadExtraInfo' in custom_options)
+ self.assertTrue('SocksListenAddress' in custom_options)
+
+ custom_options = controller.get_custom_conf(include_values = True)
+ self.assertTrue('DownloadExtraInfo 1' in custom_options)
+ self.assertTrue('SocksListenAddress 127.0.0.1:1112' in custom_options)
+
+ @require_controller
def test_hidden_services_conf(self):
"""
Exercises the hidden service family of methods (get_hidden_service_conf,
[View Less]
1
0
commit 3e38f7440e3fe8679d47c7315efc06d771bb7689
Author: Sebastian Hahn <sebastian(a)torproject.org>
Date: Tue Dec 22 18:14:07 2015 +0100
Make donate-blog-benw page
---
.htaccess | 1 +
1 file changed, 1 insertion(+)
diff --git a/.htaccess b/.htaccess
index b5523e3..15c6cc0 100644
--- a/.htaccess
+++ b/.htaccess
@@ -64,6 +64,7 @@ RewriteRule ^donate/donate-blog-molly(.*) /donate/donate$1 [R=302,L]
RewriteRule ^donate/donate-blog-rabbirob(.*) /donate/donate$1 [R=302,L]
…
[View More]RewriteRule ^donate/donate-blog-cory(.*) /donate/donate$1 [R=302,L]
RewriteRule ^donate/donate-hpbanner(.*) /donate/donate$1 [R=302,L]
+RewriteRule ^donate/donate-blog-benw(.*) /donate/donate$1 [R=302,L]
#keep prefixes of other entries down here...
RewriteRule ^donate/donate-hp(.*) /donate/donate$1 [R=302,L]
diff --git a/donate/en/donate-blog-benw.wml b/donate/en/donate-blog-benw.wml
new file mode 100644
index 0000000..e69de29
[View Less]
1
0
commit a7ec98cd73fe3e8b27b9c98a0389c231b0ff51b1
Author: Damian Johnson <atagar(a)torproject.org>
Date: Tue Dec 22 08:47:10 2015 -0800
Updating with DirCache option
---
stem/cached_tor_manual.cfg | 9 +++++++--
stem/settings.cfg | 1 +
test/integ/manual.py | 2 +-
3 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/stem/cached_tor_manual.cfg b/stem/cached_tor_manual.cfg
index 9a6fed0..a05c4e9 100644
--- a/stem/cached_tor_manual.cfg
+++ b/stem/…
[View More]cached_tor_manual.cfg
@@ -6,8 +6,8 @@ description
|Basically, Tor provides a distributed network of servers or relays ("onion routers"). Users bounce their TCP streams -- web traffic, ftp, ssh, etc. -- around the network, and recipients, observers, and even the relays themselves have difficulty tracking the source of the stream.
|
|By default, tor will only act as a client only. To help the network by providing bandwidth as a relay, change the ORPort configuration option -- see below. Please also consult the documentation on the Tor Project's website.
-man_commit aa4be914f06baa92857de201a212b9fc9856bdb1
-stem_commit d4fac7f81c180cf8e83ea81d4f3f2e8d0d2ce491
+man_commit 997f779a7f05540e5f564b4d121706c4a7069fb2
+stem_commit 4b49f5d1a616acfd7b1e5b9c7c82b3f8bed836d7
commandline_options -f FILE => Specify a new configuration file to contain further Tor configuration options OR pass - to make Tor read its configuration from standard input. (Default: @CONFDIR@/torrc, or $HOME/.torrc if that file is not found)
commandline_options --ignore-missing-torrc => Specifies that Tor should treat a missing torrc file as though it were empty. Ordinarily, Tor does this for missing default torrc files, but not for those specified on the command line.
commandline_options --list-fingerprint => Generate your keys and output your nickname and fingerprint.
@@ -671,6 +671,11 @@ config_options.DirPolicy.name DirPolicy
config_options.DirPolicy.usage policy,policy,...
config_options.DirPolicy.summary Access policy for the DirPort
config_options.DirPolicy.description Set an entrance policy for this server, to limit who can connect to the directory ports. The policies have the same form as exit policies above, except that port specifiers are ignored. Any address not matched by some entry in the policy is accepted.
+config_options.DirCache.category Directory
+config_options.DirCache.name DirCache
+config_options.DirCache.usage 0|1
+config_options.DirCache.summary Provide cached descriptor information to other tor users
+config_options.DirCache.description When this option is set, Tor caches all current directory documents and accepts client requests for them. Setting DirPort is not required for this, because clients connect via the ORPort by default. Setting either DirPort or BridgeRelay and setting DirCache to 0 is not supported. (Default: 1)
config_options.BandwidthRate.category General
config_options.BandwidthRate.name BandwidthRate
config_options.BandwidthRate.usage N bytes|KBytes|MBytes|GBytes|KBits|MBits|GBits
diff --git a/stem/settings.cfg b/stem/settings.cfg
index b590afe..82e8f88 100644
--- a/stem/settings.cfg
+++ b/stem/settings.cfg
@@ -265,6 +265,7 @@ manual.summary.DirPortFrontPage Publish this html file on the DirPort
manual.summary.DirPort Port for directory connections
manual.summary.DirListenAddress Address the directory service is bound to
manual.summary.DirPolicy Access policy for the DirPort
+manual.summary.DirCache Provide cached descriptor information to other tor users
# Directory Authority Server Options
diff --git a/test/integ/manual.py b/test/integ/manual.py
index d0b1bf1..9fccb54 100644
--- a/test/integ/manual.py
+++ b/test/integ/manual.py
@@ -305,4 +305,4 @@ class TestManual(unittest.TestCase):
extra_in_manual = config_options_in_manual.difference(config_options_in_tor)
if extra_in_manual:
- self.fail("The %s config options in our man page aren't presently supported by tor. Maybe we need to remove them?" % ', '.join(extra_in_manual))
+ self.fail("The %s config options in our man page aren't presently supported by tor. Are we using the latest git commit of tor? If so, maybe we need to remove them?" % ', '.join(extra_in_manual))
[View Less]
1
0

22 Dec '15
commit b9596b8fdf5006d650bba0109e5614b5a387dbdf
Author: Nick Mathewson <nickm(a)torproject.org>
Date: Tue Dec 22 11:10:37 2015 -0500
document minimum heartbeatperiod; bug 15638.
---
changes/bug15638 | 2 ++
doc/tor.1.txt | 3 ++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/changes/bug15638 b/changes/bug15638
new file mode 100644
index 0000000..cf5d469
--- /dev/null
+++ b/changes/bug15638
@@ -0,0 +1,2 @@
+ o Documentation:
+ - Document the minimum …
[View More]HeartbeatPeriod value. Closes ticket 15638.
diff --git a/doc/tor.1.txt b/doc/tor.1.txt
index 021353d..3514c4d 100644
--- a/doc/tor.1.txt
+++ b/doc/tor.1.txt
@@ -1750,7 +1750,8 @@ is non-zero):
Log a heartbeat message every **HeartbeatPeriod** seconds. This is
a log level __notice__ message, designed to let you know your Tor
server is still alive and doing useful things. Settings this
- to 0 will disable the heartbeat. (Default: 6 hours)
+ to 0 will disable the heartbeat. Otherwise, it must be at least 30
+ minutes. (Default: 6 hours)
[[AccountingMax]] **AccountingMax** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**|**TBytes**::
Limits the max number of bytes sent and received within a set time period
[View Less]
1
0
commit 45f5e597517abedbf9d650428ac53f03f37089d8
Author: Nick Mathewson <nickm(a)torproject.org>
Date: Tue Dec 22 10:31:26 2015 -0500
Remove extra quotes from log message
Bug 17843; fix on ddc65e2b
---
changes/bug17843 | 3 +++
src/or/config.c | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/changes/bug17843 b/changes/bug17843
new file mode 100644
index 0000000..6cb16a0
--- /dev/null
+++ b/changes/bug17843
@@ -0,0 +1,3 @@
+ o Minor bugfixes (…
[View More]logging):
+ - Remove needless quotes from a log message about unparseable addresses.
+ Fixes bug 17843; bugfix on 0.2.3.3-alpha.
diff --git a/src/or/config.c b/src/or/config.c
index e3d6653..a1f8e49 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -6366,7 +6366,7 @@ parse_port_config(smartlist_t *out,
}
port = ptmp;
} else {
- log_warn(LD_CONFIG, "Couldn't parse address '%s' for %sPort",
+ log_warn(LD_CONFIG, "Couldn't parse address %s for %sPort",
escaped(addrport), portname);
goto err;
}
[View Less]
1
0

22 Dec '15
commit 4ec0f8531ee4f40cfb3eeca4c383c454986a268a
Author: Nick Mathewson <nickm(a)torproject.org>
Date: Tue Dec 22 10:27:04 2015 -0500
Add an unreachable line to make the compiler happy
---
src/common/crypto.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/common/crypto.c b/src/common/crypto.c
index a11ca79..251bbbf 100644
--- a/src/common/crypto.c
+++ b/src/common/crypto.c
@@ -1731,6 +1731,7 @@ crypto_digest_algorithm_get_length(digest_algorithm_t alg)
return …
[View More]DIGEST512_LEN;
default:
tor_assert(0);
+ return 0; /* Unreachable */
}
}
[View Less]
1
0

[webwml/master] add a redirect for the george torwell donations drive
by arma@torproject.org 22 Dec '15
by arma@torproject.org 22 Dec '15
22 Dec '15
commit aeffd059b8082e8318d16f7c64b0b8eeb05def68
Author: Roger Dingledine <arma(a)torproject.org>
Date: Tue Dec 22 00:49:09 2015 -0500
add a redirect for the george torwell donations drive
---
.htaccess | 1 +
1 file changed, 1 insertion(+)
diff --git a/.htaccess b/.htaccess
index b3992a7..b5523e3 100644
--- a/.htaccess
+++ b/.htaccess
@@ -59,6 +59,7 @@ RewriteRule ^donate/donate-download(.*) /donate/donate$1 [R=302,L]
RewriteRule ^donate/donate-button(.*) /donate/donate$1 [R=…
[View More]302,L]
RewriteRule ^donate/giving-tue-blog(.*) /donate/donate$1 [R=302,L]
RewriteRule ^donate/donate-roger(.*) /donate/donate$1 [R=302,L]
+RewriteRule ^donate/donate-george-torwell(.*) /donate/donate$1 [R=302,L]
RewriteRule ^donate/donate-blog-molly(.*) /donate/donate$1 [R=302,L]
RewriteRule ^donate/donate-blog-rabbirob(.*) /donate/donate$1 [R=302,L]
RewriteRule ^donate/donate-blog-cory(.*) /donate/donate$1 [R=302,L]
diff --git a/donate/en/donate-george-torwell.wml b/donate/en/donate-george-torwell.wml
new file mode 100644
index 0000000..e69de29
[View Less]
1
0

[metrics-lib/master] Parse Ed25519 and SHA-256 elements in descriptors.
by karsten@torproject.org 21 Dec '15
by karsten@torproject.org 21 Dec '15
21 Dec '15
commit 39a9c496effde2bd083df0095006ca299f9af2d5
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Wed Dec 16 10:41:28 2015 +0100
Parse Ed25519 and SHA-256 elements in descriptors.
More precisely,
- support Ed25519 certificates and Ed25519 master keys as well as
SHA-256 digests and Ed25519 signatures thereof in server
descriptors and extra-info descriptors,
- parse RSA-1024 signatures of SHA-1 digests of extra-info
descriptors,
- …
[View More]parse Ed25519 master keys in votes, and
- parse Ed25519 and RSA-1024 identity digests in microdescriptors.
This patch is based on metrics-db's bridge descriptor sanitizer.
---
CHANGELOG.md | 5 +
.../torproject/descriptor/ExtraInfoDescriptor.java | 26 +++
src/org/torproject/descriptor/Microdescriptor.java | 10 ++
.../torproject/descriptor/NetworkStatusEntry.java | 5 +
.../torproject/descriptor/ServerDescriptor.java | 27 +++
.../descriptor/impl/ExtraInfoDescriptorImpl.java | 157 ++++++++++++++++-
.../descriptor/impl/MicrodescriptorImpl.java | 29 +++-
.../descriptor/impl/NetworkStatusEntryImpl.java | 19 +++
.../torproject/descriptor/impl/ParseHelper.java | 73 ++++++++
.../descriptor/impl/ServerDescriptorImpl.java | 137 ++++++++++++++-
.../impl/ExtraInfoDescriptorImplTest.java | 176 +++++++++++++++++++-
.../descriptor/impl/MicrodescriptorImplTest.java | 82 +++++++++
.../impl/RelayNetworkStatusVoteImplTest.java | 54 +++++-
.../descriptor/impl/ServerDescriptorImplTest.java | 117 +++++++++++++
14 files changed, 902 insertions(+), 15 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5456795..1f2bc0d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,11 @@
existing types, ServerDescriptor and ExtraInfoDescriptor, are
still usable and will not be deprecated, because applications may
not care whether a relay or a bridge published a descriptor.
+ - Support Ed25519 certificates, Ed25519 master keys, SHA-256
+ digests, and Ed25519 signatures thereof in server descriptors and
+ extra-info descriptors, and support Ed25519 master keys in votes.
+ - Include RSA-1024 signatures of SHA-1 digests of extra-info
+ descriptors, which were parsed and discarded before.
# Changes in version 1.0.0 - 2015-12-05
diff --git a/src/org/torproject/descriptor/ExtraInfoDescriptor.java b/src/org/torproject/descriptor/ExtraInfoDescriptor.java
index 380be00..1b978a4 100644
--- a/src/org/torproject/descriptor/ExtraInfoDescriptor.java
+++ b/src/org/torproject/descriptor/ExtraInfoDescriptor.java
@@ -12,6 +12,10 @@ public interface ExtraInfoDescriptor extends Descriptor {
* extra-info descriptor in a server descriptor. */
public String getExtraInfoDigest();
+ /* Return the base64-encoded SHA-256 descriptor digest that may be used
+ * to reference this extra-info descriptor in a server descriptor. */
+ public String getExtraInfoDigestSha256();
+
/* Return the relay's nickname. */
public String getNickname();
@@ -260,5 +264,27 @@ public interface ExtraInfoDescriptor extends Descriptor {
/* Return the (possibly empty) list of transports supported by this
* bridge. */
public List<String> getTransports();
+
+ /* Return the signature of the PKCS1-padded extra-info descriptor
+ * digest, or null if the descriptor doesn't contain a signature (which
+ * is the case in sanitized bridge descriptors). */
+ public String getRouterSignature();
+
+ /* Return the base64-encoded Ed25519 certificate, or null if the
+ * descriptor doesn't contain one. */
+ public String getIdentityEd25519();
+
+ /* Return the base64-encoded Ed25519 master key, which may either be
+ * parsed from the optional "master-key-ed25519" line or derived from
+ * the (likewise optional) Ed25519 certificate following the
+ * "identity-ed25519" line, or null if the descriptor contains neither
+ * Ed25519 master key nor Ed25519 certificate. */
+ public String getMasterKeyEd25519();
+
+ /* Return the base64-encoded Ed25519 signature of a SHA-256 digest of
+ * the entire descriptor, from the first character up to and including
+ * the first space after the "router-sig-ed25519" string, prefixed with
+ * the string "Tor router descriptor signature v1". */
+ public String getRouterSignatureEd25519();
}
diff --git a/src/org/torproject/descriptor/Microdescriptor.java b/src/org/torproject/descriptor/Microdescriptor.java
index 7715d35..3cebeb6 100644
--- a/src/org/torproject/descriptor/Microdescriptor.java
+++ b/src/org/torproject/descriptor/Microdescriptor.java
@@ -43,5 +43,15 @@ public interface Microdescriptor extends Descriptor {
/* Return the port list of the IPv6 port summary or null if the
* microdescriptor didn't contain an IPv6 port summary line. */
public String getIpv6PortList();
+
+ /* Return the optional, base64-encoded RSA-1024 identity that is only
+ * included to prevent collisions between microdescriptors, or null if
+ * no such identity is included. */
+ public String getRsa1024Identity();
+
+ /* Return the optional, base64-encoded Ed25519 identity that is only
+ * included to prevent collisions between microdescriptors, or null if
+ * no such identity is included. */
+ public String getEd25519Identity();
}
diff --git a/src/org/torproject/descriptor/NetworkStatusEntry.java b/src/org/torproject/descriptor/NetworkStatusEntry.java
index fb9163e..584f07b 100644
--- a/src/org/torproject/descriptor/NetworkStatusEntry.java
+++ b/src/org/torproject/descriptor/NetworkStatusEntry.java
@@ -75,5 +75,10 @@ public interface NetworkStatusEntry {
/* Return the port list of the port summary or null if the status entry
* didn't contain a port summary line. */
public String getPortList();
+
+ /* Return the relay's base64-encoded Ed25519 master key, "none" if the
+ * relay doesn't have an Ed25519 identity, or null if the status entry
+ * didn't contain this information. Only included in votes. */
+ public String getMasterKeyEd25519();
}
diff --git a/src/org/torproject/descriptor/ServerDescriptor.java b/src/org/torproject/descriptor/ServerDescriptor.java
index 598b9b5..3266e9d 100644
--- a/src/org/torproject/descriptor/ServerDescriptor.java
+++ b/src/org/torproject/descriptor/ServerDescriptor.java
@@ -11,6 +11,10 @@ public interface ServerDescriptor extends Descriptor {
* descriptor in a network status. */
public String getServerDescriptorDigest();
+ /* Return the base64-encoded SHA-256 descriptor digest that may be used
+ * to reference this server descriptor in a network status. */
+ public String getServerDescriptorDigestSha256();
+
/* Return the relay's nickname. */
public String getNickname();
@@ -119,6 +123,12 @@ public interface ServerDescriptor extends Descriptor {
* the relay did not upload a corresponding extra-info descriptor. */
public String getExtraInfoDigest();
+ /* Return the base64-encoded SHA-256 digest of the extra-info descriptor
+ * referenced from this server descriptor, or null if the relay either
+ * did not upload a corresponding extra-info descriptor or did not refer
+ * to it using a SHA-256 digest. */
+ public String getExtraInfoDigestSha256();
+
/* Return the hidden service descriptor version(s) that this relay
* stores and serves, or null if it doesn't store and serve any hidden
* service descriptors. */
@@ -147,5 +157,22 @@ public interface ServerDescriptor extends Descriptor {
/* Return the ntor onion key base64 string with padding omitted, or null
* if the server descriptors didn't contain an ntor onion key line. */
public String getNtorOnionKey();
+
+ /* Return the base64-encoded Ed25519 certificate, or null if the
+ * descriptor doesn't contain one. */
+ public String getIdentityEd25519();
+
+ /* Return the base64-encoded Ed25519 master key, which may either be
+ * parsed from the optional "master-key-ed25519" line or derived from
+ * the (likewise optional) Ed25519 certificate following the
+ * "identity-ed25519" line, or null if the descriptor contains neither
+ * Ed25519 master key nor Ed25519 certificate. */
+ public String getMasterKeyEd25519();
+
+ /* Return the base64-encoded Ed25519 signature of a SHA-256 digest of
+ * the entire descriptor, from the first character up to and including
+ * the first space after the "router-sig-ed25519" string, prefixed with
+ * the string "Tor router descriptor signature v1". */
+ public String getRouterSignatureEd25519();
}
diff --git a/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java b/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java
index 61b8d13..4abace6 100644
--- a/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java
+++ b/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java
@@ -31,6 +31,7 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
super(descriptorBytes, failUnrecognizedDescriptorLines, false);
this.parseDescriptorBytes();
this.calculateDigest();
+ this.calculateDigestSha256();
Set<String> exactlyOnceKeywords = new HashSet<String>(Arrays.asList((
"extra-info,published").split(",")));
this.checkExactlyOnceKeywords(exactlyOnceKeywords);
@@ -52,8 +53,9 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
Set<String> bridgeStatsKeywords = new HashSet<String>(Arrays.asList(
"bridge-stats-end,bridge-stats-ips".split(",")));
Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList((
- "read-history,write-history,dirreq-read-history,"
- + "dirreq-write-history,geoip-db-digest,router-signature,"
+ "identity-ed25519,master-key-ed25519,read-history,write-history,"
+ + "dirreq-read-history,dirreq-write-history,geoip-db-digest,"
+ + "router-sig-ed25519,router-signature,router-digest-sha256,"
+ "router-digest").split(",")));
atMostOnceKeywords.addAll(dirreqStatsKeywords);
atMostOnceKeywords.addAll(entryStatsKeywords);
@@ -75,7 +77,8 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
private void parseDescriptorBytes() throws DescriptorParseException {
Scanner s = new Scanner(new String(this.rawDescriptorBytes)).
useDelimiter("\n");
- boolean skipCrypto = false;
+ String nextCrypto = null;
+ List<String> cryptoLines = null;
while (s.hasNext()) {
String line = s.next();
String lineNoOpt = line.startsWith("opt ") ?
@@ -162,15 +165,49 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
this.parseBridgeIpTransportsLine(line, lineNoOpt, partsNoOpt);
} else if (keyword.equals("transport")) {
this.parseTransportLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("identity-ed25519")) {
+ this.parseIdentityEd25519Line(line, lineNoOpt, partsNoOpt);
+ nextCrypto = "identity-ed25519";
+ } else if (keyword.equals("master-key-ed25519")) {
+ this.parseMasterKeyEd25519Line(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("router-sig-ed25519")) {
+ this.parseRouterSigEd25519Line(line, lineNoOpt, partsNoOpt);
} else if (keyword.equals("router-signature")) {
this.parseRouterSignatureLine(line, lineNoOpt, partsNoOpt);
+ nextCrypto = "router-signature";
} else if (keyword.equals("router-digest")) {
this.parseRouterDigestLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("router-digest-sha256")) {
+ this.parseRouterDigestSha256Line(line, lineNoOpt, partsNoOpt);
} else if (line.startsWith("-----BEGIN")) {
- skipCrypto = true;
+ cryptoLines = new ArrayList<String>();
+ cryptoLines.add(line);
} else if (line.startsWith("-----END")) {
- skipCrypto = false;
- } else if (!skipCrypto) {
+ cryptoLines.add(line);
+ StringBuilder sb = new StringBuilder();
+ for (String cryptoLine : cryptoLines) {
+ sb.append("\n" + cryptoLine);
+ }
+ String cryptoString = sb.toString().substring(1);
+ if ("router-signature".equals(nextCrypto)) {
+ this.routerSignature = cryptoString;
+ } else if ("identity-ed25519".equals(nextCrypto)) {
+ this.identityEd25519 = cryptoString;
+ this.parseIdentityEd25519CryptoBlock(cryptoString);
+ } else if (this.failUnrecognizedDescriptorLines) {
+ throw new DescriptorParseException("Unrecognized crypto "
+ + "block '" + cryptoString + "' in extra-info descriptor.");
+ } else {
+ if (this.unrecognizedLines == null) {
+ this.unrecognizedLines = new ArrayList<String>();
+ }
+ this.unrecognizedLines.addAll(cryptoLines);
+ }
+ cryptoLines = null;
+ nextCrypto = null;
+ } else if (cryptoLines != null) {
+ cryptoLines.add(line);
+ } else {
ParseHelper.parseKeyword(line, partsNoOpt[0]);
if (this.failUnrecognizedDescriptorLines) {
throw new DescriptorParseException("Unrecognized line '"
@@ -610,7 +647,6 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
if (!lineNoOpt.equals("router-signature")) {
throw new DescriptorParseException("Illegal line '" + line + "'.");
}
- /* Not parsing crypto parts (yet). */
}
private void parseRouterDigestLine(String line, String lineNoOpt,
@@ -622,6 +658,57 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
partsNoOpt[1]);
}
+ private void parseIdentityEd25519Line(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 1) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ }
+
+ private void parseIdentityEd25519CryptoBlock(String cryptoString)
+ throws DescriptorParseException {
+ String masterKeyEd25519FromIdentityEd25519 =
+ ParseHelper.parseMasterKeyEd25519FromIdentityEd25519CryptoBlock(
+ cryptoString);
+ if (this.masterKeyEd25519 != null && !this.masterKeyEd25519.equals(
+ masterKeyEd25519FromIdentityEd25519)) {
+ throw new DescriptorParseException("Mismatch between "
+ + "identity-ed25519 and master-key-ed25519.");
+ }
+ this.masterKeyEd25519 = masterKeyEd25519FromIdentityEd25519;
+ }
+
+ private void parseMasterKeyEd25519Line(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ String masterKeyEd25519FromMasterKeyEd25519Line = partsNoOpt[1];
+ if (this.masterKeyEd25519 != null && !masterKeyEd25519.equals(
+ masterKeyEd25519FromMasterKeyEd25519Line)) {
+ throw new DescriptorParseException("Mismatch between "
+ + "identity-ed25519 and master-key-ed25519.");
+ }
+ this.masterKeyEd25519 = masterKeyEd25519FromMasterKeyEd25519Line;
+ }
+
+ private void parseRouterSigEd25519Line(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ this.routerSignatureEd25519 = partsNoOpt[1];
+ }
+
+ private void parseRouterDigestSha256Line(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ ParseHelper.parseThirtyTwoByteBase64String(line, partsNoOpt[1]);
+ this.extraInfoDigestSha256 = partsNoOpt[1];
+ }
+
private void calculateDigest() throws DescriptorParseException {
if (this.extraInfoDigest != null) {
/* We already learned the descriptor digest of this bridge
@@ -653,11 +740,47 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
}
}
+ private void calculateDigestSha256() throws DescriptorParseException {
+ if (this.extraInfoDigestSha256 != null) {
+ /* We already learned the descriptor digest of this bridge
+ * descriptor from a "router-digest-sha256" line. */
+ return;
+ }
+ try {
+ String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");
+ String startToken = "extra-info ";
+ String sigToken = "\n-----END SIGNATURE-----\n";
+ int start = ascii.indexOf(startToken);
+ int sig = ascii.indexOf(sigToken) + sigToken.length();
+ if (start >= 0 && sig >= 0 && sig > start) {
+ byte[] forDigest = new byte[sig - start];
+ System.arraycopy(this.getRawDescriptorBytes(), start, forDigest,
+ 0, sig - start);
+ this.extraInfoDigestSha256 = DatatypeConverter.printBase64Binary(
+ MessageDigest.getInstance("SHA-256").digest(forDigest)).
+ replaceAll("=", "");
+ }
+ } catch (UnsupportedEncodingException e) {
+ /* Handle below. */
+ } catch (NoSuchAlgorithmException e) {
+ /* Handle below. */
+ }
+ if (this.extraInfoDigestSha256 == null) {
+ throw new DescriptorParseException("Could not calculate extra-info "
+ + "descriptor SHA-256 digest.");
+ }
+ }
+
private String extraInfoDigest;
public String getExtraInfoDigest() {
return this.extraInfoDigest;
}
+ private String extraInfoDigestSha256;
+ public String getExtraInfoDigestSha256() {
+ return this.extraInfoDigestSha256;
+ }
+
private String nickname;
public String getNickname() {
return this.nickname;
@@ -933,5 +1056,25 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
public List<String> getTransports() {
return new ArrayList<String>(this.transports);
}
+
+ private String routerSignature;
+ public String getRouterSignature() {
+ return this.routerSignature;
+ }
+
+ private String identityEd25519;
+ public String getIdentityEd25519() {
+ return this.identityEd25519;
+ }
+
+ private String masterKeyEd25519;
+ public String getMasterKeyEd25519() {
+ return this.masterKeyEd25519;
+ }
+
+ private String routerSignatureEd25519;
+ public String getRouterSignatureEd25519() {
+ return this.routerSignatureEd25519;
+ }
}
diff --git a/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java b/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java
index ffa5a0f..1987659 100644
--- a/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java
+++ b/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java
@@ -48,7 +48,7 @@ public class MicrodescriptorImpl extends DescriptorImpl
"onion-key".split(",")));
this.checkExactlyOnceKeywords(exactlyOnceKeywords);
Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList((
- "ntor-onion-key,family,p,p6").split(",")));
+ "ntor-onion-key,family,p,p6,id").split(",")));
this.checkAtMostOnceKeywords(atMostOnceKeywords);
this.checkFirstKeyword("onion-key");
this.clearParsedKeywords();
@@ -80,6 +80,8 @@ public class MicrodescriptorImpl extends DescriptorImpl
this.parsePLine(line, parts);
} else if (keyword.equals("p6")) {
this.parseP6Line(line, parts);
+ } else if (keyword.equals("id")) {
+ this.parseIdLine(line, parts);
} else if (line.startsWith("-----BEGIN")) {
crypto = new StringBuilder();
crypto.append(line + "\n");
@@ -196,6 +198,21 @@ public class MicrodescriptorImpl extends DescriptorImpl
}
}
+ private void parseIdLine(String line, String[] parts)
+ throws DescriptorParseException {
+ if (parts.length != 3) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ } else if ("ed25519".equals(parts[1])) {
+ ParseHelper.parseThirtyTwoByteBase64String(line, parts[2]);
+ this.ed25519Identity = parts[2];
+ } else if ("rsa1024".equals(parts[1])) {
+ ParseHelper.parseTwentyByteBase64String(line, parts[2]);
+ this.rsa1024Identity = parts[2];
+ } else {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ }
+
private void calculateDigest() throws DescriptorParseException {
try {
String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");
@@ -265,5 +282,15 @@ public class MicrodescriptorImpl extends DescriptorImpl
public String getIpv6PortList() {
return this.ipv6PortList;
}
+
+ private String rsa1024Identity;
+ public String getRsa1024Identity() {
+ return this.rsa1024Identity;
+ }
+
+ private String ed25519Identity;
+ public String getEd25519Identity() {
+ return this.ed25519Identity;
+ }
}
diff --git a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java
index dac6052..94575c6 100644
--- a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java
+++ b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java
@@ -91,6 +91,8 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
this.parsePLine(line, parts);
} else if (keyword.equals("m")) {
this.parseMLine(line, parts);
+ } else if (keyword.equals("id")) {
+ this.parseIdLine(line, parts);
} else if (this.failUnrecognizedDescriptorLines) {
throw new DescriptorParseException("Unrecognized line '" + line
+ "' in status entry.");
@@ -237,6 +239,18 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
}
}
+ private void parseIdLine(String line, String[] parts)
+ throws DescriptorParseException {
+ if (parts.length != 3 || !"ed25519".equals(parts[1])) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ } else if ("none".equals(parts[2])) {
+ this.masterKeyEd25519 = "none";
+ } else {
+ ParseHelper.parseThirtyTwoByteBase64String(line, parts[2]);
+ this.masterKeyEd25519 = parts[2];
+ }
+ }
+
private void clearAtMostOnceKeywords() {
this.atMostOnceKeywords = null;
}
@@ -328,5 +342,10 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
public String getPortList() {
return this.portList;
}
+
+ private String masterKeyEd25519;
+ public String getMasterKeyEd25519() {
+ return this.masterKeyEd25519;
+ }
}
diff --git a/src/org/torproject/descriptor/impl/ParseHelper.java b/src/org/torproject/descriptor/impl/ParseHelper.java
index 4e91e92..a354831 100644
--- a/src/org/torproject/descriptor/impl/ParseHelper.java
+++ b/src/org/torproject/descriptor/impl/ParseHelper.java
@@ -434,5 +434,78 @@ public class ParseHelper {
}
return result;
}
+
+ public static String
+ parseMasterKeyEd25519FromIdentityEd25519CryptoBlock(
+ String identityEd25519CryptoBlock) throws DescriptorParseException {
+ String identityEd25519CryptoBlockNoNewlines =
+ identityEd25519CryptoBlock.replaceAll("\n", "");
+ String beginEd25519CertLine = "-----BEGIN ED25519 CERT-----",
+ endEd25519CertLine = "-----END ED25519 CERT-----";
+ if (!identityEd25519CryptoBlockNoNewlines.startsWith(
+ beginEd25519CertLine)) {
+ throw new DescriptorParseException("Illegal start of "
+ + "identity-ed25519 crypto block '" + identityEd25519CryptoBlock
+ + "'.");
+ }
+ if (!identityEd25519CryptoBlockNoNewlines.endsWith(
+ endEd25519CertLine)) {
+ throw new DescriptorParseException("Illegal end of "
+ + "identity-ed25519 crypto block '" + identityEd25519CryptoBlock
+ + "'.");
+ }
+ String identityEd25519Base64 = identityEd25519CryptoBlockNoNewlines.
+ substring(beginEd25519CertLine.length(),
+ identityEd25519CryptoBlock.length()
+ - endEd25519CertLine.length()).replaceAll("=", "");
+ byte[] identityEd25519 = DatatypeConverter.parseBase64Binary(
+ identityEd25519Base64);
+ if (identityEd25519.length < 40) {
+ throw new DescriptorParseException("Invalid length of "
+ + "identity-ed25519 (in bytes): " + identityEd25519.length);
+ } else if (identityEd25519[0] != 0x01) {
+ throw new DescriptorParseException("Unknown version in "
+ + "identity-ed25519: " + identityEd25519[0]);
+ } else if (identityEd25519[1] != 0x04) {
+ throw new DescriptorParseException("Unknown cert type in "
+ + "identity-ed25519: " + identityEd25519[1]);
+ } else if (identityEd25519[6] != 0x01) {
+ throw new DescriptorParseException("Unknown certified key type in "
+ + "identity-ed25519: " + identityEd25519[1]);
+ } else if (identityEd25519[39] == 0x00) {
+ throw new DescriptorParseException("No extensions in "
+ + "identity-ed25519 (which would contain the encoded "
+ + "master-key-ed25519): " + identityEd25519[39]);
+ } else {
+ int extensionStart = 40;
+ for (int i = 0; i < (int) identityEd25519[39]; i++) {
+ if (identityEd25519.length < extensionStart + 4) {
+ throw new DescriptorParseException("Invalid extension with id "
+ + i + " in identity-ed25519.");
+ }
+ int extensionLength = identityEd25519[extensionStart];
+ extensionLength <<= 8;
+ extensionLength += identityEd25519[extensionStart + 1];
+ int extensionType = identityEd25519[extensionStart + 2];
+ if (extensionLength == 32 && extensionType == 4) {
+ if (identityEd25519.length < extensionStart + 4 + 32) {
+ throw new DescriptorParseException("Invalid extension with "
+ + "id " + i + " in identity-ed25519.");
+ }
+ byte[] masterKeyEd25519 = new byte[32];
+ System.arraycopy(identityEd25519, extensionStart + 4,
+ masterKeyEd25519, 0, masterKeyEd25519.length);
+ String masterKeyEd25519Base64 = DatatypeConverter.
+ printBase64Binary(masterKeyEd25519).replaceAll("=", "");
+ String masterKeyEd25519Base64NoTrailingEqualSigns =
+ masterKeyEd25519Base64.replaceAll("=", "");
+ return masterKeyEd25519Base64NoTrailingEqualSigns;
+ }
+ extensionStart += 4 + extensionLength;
+ }
+ }
+ throw new DescriptorParseException("Unable to locate "
+ + "master-key-ed25519 in identity-ed25519.");
+ }
}
diff --git a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java
index 426f4d0..3dd6c40 100644
--- a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java
+++ b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java
@@ -28,15 +28,18 @@ public abstract class ServerDescriptorImpl extends DescriptorImpl
super(descriptorBytes, failUnrecognizedDescriptorLines, false);
this.parseDescriptorBytes();
this.calculateDigest();
+ this.calculateDigestSha256();
Set<String> exactlyOnceKeywords = new HashSet<String>(Arrays.asList(
"router,bandwidth,published".split(",")));
this.checkExactlyOnceKeywords(exactlyOnceKeywords);
Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList((
- "platform,fingerprint,hibernating,uptime,contact,family,"
- + "read-history,write-history,eventdns,caches-extra-info,"
- + "extra-info-digest,hidden-service-dir,protocols,"
- + "allow-single-hop-exits,onion-key,signing-key,ipv6-policy,"
- + "ntor-onion-key,router-signature,router-digest").split(",")));
+ "identity-ed25519,master-key-ed25519,platform,fingerprint,"
+ + "hibernating,uptime,contact,family,read-history,write-history,"
+ + "eventdns,caches-extra-info,extra-info-digest,"
+ + "hidden-service-dir,protocols,allow-single-hop-exits,onion-key,"
+ + "signing-key,ipv6-policy,ntor-onion-key,router-sig-ed25519,"
+ + "router-signature,router-digest-sha256,router-digest").
+ split(",")));
this.checkAtMostOnceKeywords(atMostOnceKeywords);
this.checkFirstKeyword("router");
if (this.getKeywordCount("accept") == 0 &&
@@ -115,10 +118,19 @@ public abstract class ServerDescriptorImpl extends DescriptorImpl
this.parseDircacheportLine(line, lineNoOpt, partsNoOpt);
} else if (keyword.equals("router-digest")) {
this.parseRouterDigestLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("router-digest-sha256")) {
+ this.parseRouterDigestSha256Line(line, lineNoOpt, partsNoOpt);
} else if (keyword.equals("ipv6-policy")) {
this.parseIpv6PolicyLine(line, lineNoOpt, partsNoOpt);
} else if (keyword.equals("ntor-onion-key")) {
this.parseNtorOnionKeyLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("identity-ed25519")) {
+ this.parseIdentityEd25519Line(line, lineNoOpt, partsNoOpt);
+ nextCrypto = "identity-ed25519";
+ } else if (keyword.equals("master-key-ed25519")) {
+ this.parseMasterKeyEd25519Line(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("router-sig-ed25519")) {
+ this.parseRouterSigEd25519Line(line, lineNoOpt, partsNoOpt);
} else if (line.startsWith("-----BEGIN")) {
cryptoLines = new ArrayList<String>();
cryptoLines.add(line);
@@ -135,6 +147,9 @@ public abstract class ServerDescriptorImpl extends DescriptorImpl
this.signingKey = cryptoString;
} else if ("router-signature".equals(nextCrypto)) {
this.routerSignature = cryptoString;
+ } else if ("identity-ed25519".equals(nextCrypto)) {
+ this.identityEd25519 = cryptoString;
+ this.parseIdentityEd25519CryptoBlock(cryptoString);
} else if (this.failUnrecognizedDescriptorLines) {
throw new DescriptorParseException("Unrecognized crypto "
+ "block '" + cryptoString + "' in server descriptor.");
@@ -392,6 +407,10 @@ public abstract class ServerDescriptorImpl extends DescriptorImpl
}
this.extraInfoDigest = ParseHelper.parseTwentyByteHexString(line,
partsNoOpt[1]);
+ if (partsNoOpt.length >= 3) {
+ ParseHelper.parseThirtyTwoByteBase64String(line, partsNoOpt[2]);
+ this.extraInfoDigestSha256 = partsNoOpt[2];
+ }
}
private void parseHiddenServiceDirLine(String line, String lineNoOpt,
@@ -508,6 +527,57 @@ public abstract class ServerDescriptorImpl extends DescriptorImpl
this.ntorOnionKey = partsNoOpt[1].replaceAll("=", "");
}
+ private void parseIdentityEd25519Line(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 1) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ }
+
+ private void parseIdentityEd25519CryptoBlock(String cryptoString)
+ throws DescriptorParseException {
+ String masterKeyEd25519FromIdentityEd25519 =
+ ParseHelper.parseMasterKeyEd25519FromIdentityEd25519CryptoBlock(
+ cryptoString);
+ if (this.masterKeyEd25519 != null && !this.masterKeyEd25519.equals(
+ masterKeyEd25519FromIdentityEd25519)) {
+ throw new DescriptorParseException("Mismatch between "
+ + "identity-ed25519 and master-key-ed25519.");
+ }
+ this.masterKeyEd25519 = masterKeyEd25519FromIdentityEd25519;
+ }
+
+ private void parseMasterKeyEd25519Line(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ String masterKeyEd25519FromMasterKeyEd25519Line = partsNoOpt[1];
+ if (this.masterKeyEd25519 != null && !masterKeyEd25519.equals(
+ masterKeyEd25519FromMasterKeyEd25519Line)) {
+ throw new DescriptorParseException("Mismatch between "
+ + "identity-ed25519 and master-key-ed25519.");
+ }
+ this.masterKeyEd25519 = masterKeyEd25519FromMasterKeyEd25519Line;
+ }
+
+ private void parseRouterSigEd25519Line(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ this.routerSignatureEd25519 = partsNoOpt[1];
+ }
+
+ private void parseRouterDigestSha256Line(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ ParseHelper.parseThirtyTwoByteBase64String(line, partsNoOpt[1]);
+ this.serverDescriptorDigestSha256 = partsNoOpt[1];
+ }
+
private void calculateDigest() throws DescriptorParseException {
if (this.serverDescriptorDigest != null) {
/* We already learned the descriptor digest of this bridge
@@ -539,11 +609,48 @@ public abstract class ServerDescriptorImpl extends DescriptorImpl
}
}
+ private void calculateDigestSha256() throws DescriptorParseException {
+ if (this.serverDescriptorDigestSha256 != null) {
+ /* We already learned the descriptor digest of this bridge
+ * descriptor from a "router-digest-sha256" line. */
+ return;
+ }
+ try {
+ String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");
+ String startToken = "router ";
+ String sigToken = "\n-----END SIGNATURE-----\n";
+ int start = ascii.indexOf(startToken);
+ int sig = ascii.indexOf(sigToken) + sigToken.length();
+ if (start >= 0 && sig >= 0 && sig > start) {
+ byte[] forDigest = new byte[sig - start];
+ System.arraycopy(this.getRawDescriptorBytes(), start, forDigest,
+ 0, sig - start);
+ this.serverDescriptorDigestSha256 =
+ DatatypeConverter.printBase64Binary(
+ MessageDigest.getInstance("SHA-256").digest(forDigest)).
+ replaceAll("=", "");
+ }
+ } catch (UnsupportedEncodingException e) {
+ /* Handle below. */
+ } catch (NoSuchAlgorithmException e) {
+ /* Handle below. */
+ }
+ if (this.serverDescriptorDigestSha256 == null) {
+ throw new DescriptorParseException("Could not calculate server "
+ + "descriptor SHA-256 digest.");
+ }
+ }
+
private String serverDescriptorDigest;
public String getServerDescriptorDigest() {
return this.serverDescriptorDigest;
}
+ private String serverDescriptorDigestSha256;
+ public String getServerDescriptorDigestSha256() {
+ return this.serverDescriptorDigestSha256;
+ }
+
private String nickname;
public String getNickname() {
return this.nickname;
@@ -670,6 +777,11 @@ public abstract class ServerDescriptorImpl extends DescriptorImpl
return this.extraInfoDigest;
}
+ private String extraInfoDigestSha256;
+ public String getExtraInfoDigestSha256() {
+ return this.extraInfoDigestSha256;
+ }
+
private Integer[] hiddenServiceDirVersions;
public List<Integer> getHiddenServiceDirVersions() {
return this.hiddenServiceDirVersions == null ? null :
@@ -707,5 +819,20 @@ public abstract class ServerDescriptorImpl extends DescriptorImpl
public String getNtorOnionKey() {
return this.ntorOnionKey;
}
+
+ private String identityEd25519;
+ public String getIdentityEd25519() {
+ return this.identityEd25519;
+ }
+
+ private String masterKeyEd25519;
+ public String getMasterKeyEd25519() {
+ return this.masterKeyEd25519;
+ }
+
+ private String routerSignatureEd25519;
+ public String getRouterSignatureEd25519() {
+ return this.routerSignatureEd25519;
+ }
}
diff --git a/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java b/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java
index 4483af2..d70ac39 100644
--- a/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java
+++ b/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java
@@ -2,7 +2,6 @@
* See LICENSE for licensing information */
package org.torproject.descriptor.impl;
-import org.torproject.descriptor.DescriptorParseException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -16,7 +15,10 @@ import java.util.Map;
import java.util.SortedMap;
import org.junit.Test;
+import org.torproject.descriptor.BridgeExtraInfoDescriptor;
+import org.torproject.descriptor.DescriptorParseException;
import org.torproject.descriptor.ExtraInfoDescriptor;
+import org.torproject.descriptor.RelayExtraInfoDescriptor;
/* Test parsing of extra-info descriptors. */
public class ExtraInfoDescriptorImplTest {
@@ -166,11 +168,28 @@ public class ExtraInfoDescriptorImplTest {
db.routerSignatureLines = line;
return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true);
}
+ private String identityEd25519Lines = null,
+ masterKeyEd25519Line = null, routerSigEd25519Line = null;
+ private static ExtraInfoDescriptor createWithEd25519Lines(
+ String identityEd25519Lines, String masterKeyEd25519Line,
+ String routerSigEd25519Line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.identityEd25519Lines = identityEd25519Lines;
+ db.masterKeyEd25519Line = masterKeyEd25519Line;
+ db.routerSigEd25519Line = routerSigEd25519Line;
+ return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true);
+ }
private byte[] buildDescriptor() {
StringBuilder sb = new StringBuilder();
if (this.extraInfoLine != null) {
sb.append(this.extraInfoLine + "\n");
}
+ if (this.identityEd25519Lines != null) {
+ sb.append(this.identityEd25519Lines + "\n");
+ }
+ if (this.masterKeyEd25519Line != null) {
+ sb.append(this.masterKeyEd25519Line + "\n");
+ }
if (this.publishedLine != null) {
sb.append(this.publishedLine + "\n");
}
@@ -230,6 +249,9 @@ public class ExtraInfoDescriptorImplTest {
return null;
}
}
+ if (this.routerSigEd25519Line != null) {
+ sb.append(this.routerSigEd25519Line + "\n");
+ }
if (this.routerSignatureLines != null) {
sb.append(this.routerSignatureLines + "\n");
}
@@ -1430,5 +1452,157 @@ public class ExtraInfoDescriptorImplTest {
unrecognizedLines.add(unrecognizedLine);
assertEquals(unrecognizedLines, descriptor.getUnrecognizedLines());
}
+
+ private static final String IDENTITY_ED25519_LINES =
+ "identity-ed25519\n"
+ + "-----BEGIN ED25519 CERT-----\n"
+ + "AQQABiX1AVGv5BuzJroQXbOh6vv1nbwc5rh2S13PyRFuLhTiifK4AQAgBACBCMwr"
+ + "\n4qgIlFDIzoC9ieJOtSkwrK+yXJPKlP8ojvgkx8cGKvhokOwA1eYDombzfwHcJ1"
+ + "EV\nbhEn/6g8i7wzO3LoqefIUrSAeEExOAOmm5mNmUIzL8EtnT6JHCr/sqUTUgA="
+ + "\n"
+ + "-----END ED25519 CERT-----";
+
+ private static final String MASTER_KEY_ED25519_LINE =
+ "master-key-ed25519 gQjMK+KoCJRQyM6AvYniTrUpMKyvslyTypT/KI74JMc";
+
+ private static final String ROUTER_SIG_ED25519_LINE =
+ "router-sig-ed25519 y7WF9T2GFwkSDPZEhB55HgquIFOl5uXUFMYJPq3CXXUTKeJ"
+ + "kSrtaZUB5s34fWdHQNtl84mH4dVaFMunHnwgYAw";
+
+ @Test()
+ public void testEd25519() throws DescriptorParseException {
+ ExtraInfoDescriptor descriptor =
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
+ MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE);
+ assertEquals(IDENTITY_ED25519_LINES.substring(
+ IDENTITY_ED25519_LINES.indexOf("\n") + 1),
+ descriptor.getIdentityEd25519());
+ assertEquals(MASTER_KEY_ED25519_LINE.substring(
+ MASTER_KEY_ED25519_LINE.indexOf(" ") + 1),
+ descriptor.getMasterKeyEd25519());
+ assertEquals(ROUTER_SIG_ED25519_LINE.substring(
+ ROUTER_SIG_ED25519_LINE.indexOf(" ") + 1),
+ descriptor.getRouterSignatureEd25519());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testEd25519IdentityMasterKeyMismatch()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
+ "master-key-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+ ROUTER_SIG_ED25519_LINE);
+ }
+
+ @Test()
+ public void testEd25519IdentityMissing()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines(null,
+ MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testEd25519IdentityDuplicate()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES + "\n"
+ + IDENTITY_ED25519_LINES, MASTER_KEY_ED25519_LINE,
+ ROUTER_SIG_ED25519_LINE);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testEd25519IdentityEmptyCrypto()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines("identity-ed25519\n"
+ + "-----BEGIN ED25519 CERT-----\n-----END ED25519 CERT-----",
+ MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE);
+ }
+
+ @Test()
+ public void testEd25519MasterKeyMissing()
+ throws DescriptorParseException {
+ ExtraInfoDescriptor descriptor =
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
+ null, ROUTER_SIG_ED25519_LINE);
+ assertEquals(MASTER_KEY_ED25519_LINE.substring(
+ MASTER_KEY_ED25519_LINE.indexOf(" ") + 1),
+ descriptor.getMasterKeyEd25519());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testEd25519MasterKeyDuplicate()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
+ MASTER_KEY_ED25519_LINE + "\n" + MASTER_KEY_ED25519_LINE,
+ ROUTER_SIG_ED25519_LINE);
+ }
+
+ @Test()
+ public void testEd25519RouterSigMissing()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
+ MASTER_KEY_ED25519_LINE, null);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testEd25519RouterSigDuplicate()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
+ MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE + "\n"
+ + ROUTER_SIG_ED25519_LINE);
+ }
+
+ @Test()
+ public void testExtraInfoDigestSha256Relay()
+ throws DescriptorParseException {
+ byte[] descriptorBytes = ("extra-info Unnamed "
+ + "EA5B335055D2F03013FF030381F02B1C631EC723\n"
+ + "identity-ed25519\n"
+ + "-----BEGIN ED25519 CERT-----\n"
+ + "AQQABiZRAenzZorGtx6xapoEeaqcLLOk3uWwJXTvOVLluSXXbRSZAQAgBADLN5"
+ + "wp\nCEOrRbshSbj1NDAUgc6cxU65M/Vx1x+b5+EXbkQZ5uiyB4pphVF5kPPT1P"
+ + "SleYqM\n8j+tlKh2i6+Xr0xScSPpmtG00/D0MoRlT7ZdaaaT5iw1DWDQCZ8BHG"
+ + "lAZwU=\n"
+ + "-----END ED25519 CERT-----\n"
+ + "published 2015-12-01 04:38:12\n"
+ + "write-history 2015-12-01 01:40:37 (14400 s) 88704000,60825600,"
+ + "61747200,76953600,61516800,59443200\n"
+ + "read-history 2015-12-01 01:40:37 (14400 s) 87321600,59443200,"
+ + "59904000,74880000,60364800,58060800\n"
+ + "router-sig-ed25519 c6eUeJs/SVjun3JhmjByEeWdRDyunSMAnGVhx71JiRj"
+ + "YzR8x5IcPebylG7m10wiolFxinvw78UhrrGo9Sq5ZBw\n"
+ + "router-signature\n"
+ + "-----BEGIN SIGNATURE-----\n"
+ + "oC2qFHCDOKSRoIPR86jdRxEYia390Z4d8fT0yr/1mg4RQ7lHmxlzFT6QxCswdX"
+ + "Ry\nvGNGR0wARySgyE+YKKWYn/Hp547JhhWd9Oc7BuFMY0XMvl/HOo+B9VjW+l"
+ + "nv6UBE\niqxx3C3Iw0ymohvOenyCUa/7TmsT7eVotDO57uIoGEc=\n"
+ + "-----END SIGNATURE-----\n"
+ + "").getBytes();
+ RelayExtraInfoDescriptor descriptor =
+ new RelayExtraInfoDescriptorImpl(descriptorBytes, true);
+ assertEquals("Pt1BtzfRwhYqGCDo8jjchS8nJP3ovrDyHGn+dqPbMgw",
+ descriptor.getExtraInfoDigestSha256());
+ }
+
+ @Test()
+ public void testExtraInfoDigestSha256Bridge()
+ throws DescriptorParseException {
+ byte[] descriptorBytes = ("extra-info idideditheconfig "
+ + "DC28749EC9E26E61DE492E46CD830379E9931B09\n"
+ + "master-key-ed25519 "
+ + "38FzmOIE6Mm85Ytx0MhFM6X9EuxWRUgb6HjyMGuO2AU\n"
+ + "published 2015-12-03 13:23:19\n"
+ + "write-history 2015-12-03 09:59:32 (14400 s) 53913600,52992000,"
+ + "53222400,53222400,53452800,53222400\n"
+ + "read-history 2015-12-03 09:59:32 (14400 s) 61056000,60364800,"
+ + "60364800,60134400,60595200,60364800\n"
+ + "geoip-db-digest 5BF366AD4A0572D82A1A0F6628AF8EF7725E3AB9\n"
+ + "geoip6-db-digest 212DE17D5A368DCAFA19B95F168BFFA101145A93\n"
+ + "router-digest-sha256 "
+ + "TvrqpjI7OmCtwGwair/NHUxg5ROVVQYz6/EDyXsDHR4\n"
+ + "router-digest 00B98F076B586272C3172B7F3DA29ADEE75F2ED8\n").getBytes();
+ BridgeExtraInfoDescriptor descriptor =
+ new BridgeExtraInfoDescriptorImpl(descriptorBytes, true);
+ assertEquals("TvrqpjI7OmCtwGwair/NHUxg5ROVVQYz6/EDyXsDHR4",
+ descriptor.getExtraInfoDigestSha256());
+ }
}
diff --git a/test/org/torproject/descriptor/impl/MicrodescriptorImplTest.java b/test/org/torproject/descriptor/impl/MicrodescriptorImplTest.java
new file mode 100644
index 0000000..ab4b8c8
--- /dev/null
+++ b/test/org/torproject/descriptor/impl/MicrodescriptorImplTest.java
@@ -0,0 +1,82 @@
+package org.torproject.descriptor.impl;
+
+import org.junit.Test;
+import org.torproject.descriptor.DescriptorParseException;
+import org.torproject.descriptor.Microdescriptor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class MicrodescriptorImplTest {
+
+ /* Helper class to build a microdescriptor based on default data and
+ * modifications requested by test methods. */
+ private static class DescriptorBuilder {
+ private String onionKeyLines = "onion-key\n"
+ + "-----BEGIN RSA PUBLIC KEY-----\n"
+ + "MIGJAoGBALNZ4pNsHHkl7a+kFWbBmPHNAepjjvuhjTr1TaMB3UKuCRaXJmS2Qr"
+ + "CW\nkTmINqdQUccwb3ghb7EBZfDtCUvjcwMSEsRRTVIZqVQsYj6m3n1CegOc4o"
+ + "UutXaZ\nfkyty5XOgV4Qucx9wokzTMCHlO0V0x9y0FwFsK5Nb6ugqfQLLQ6XAg"
+ + "MBAAE=\n"
+ + "-----END RSA PUBLIC KEY-----";
+ private static Microdescriptor createWithDefaultLines()
+ throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ return new MicrodescriptorImpl(db.buildDescriptor(), true);
+ }
+ private String ntorOnionKeyLine =
+ "ntor-onion-key PXLa7IGE+TzPDMsM5j9rFnDa37rd6kfZa5QuzqqJukw=";
+ private String idLine = "id rsa1024 bvegfGxp8k7T9QFpjPTrPaJTa/8";
+ private static Microdescriptor createWithIdLine(String line)
+ throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.idLine = line;
+ return new MicrodescriptorImpl(db.buildDescriptor(), true);
+ }
+ private byte[] buildDescriptor() {
+ StringBuilder sb = new StringBuilder();
+ if (this.onionKeyLines != null) {
+ sb.append(this.onionKeyLines + "\n");
+ }
+ if (this.ntorOnionKeyLine != null) {
+ sb.append(this.ntorOnionKeyLine + "\n");
+ }
+ if (this.idLine != null) {
+ sb.append(this.idLine + "\n");
+ }
+ return sb.toString().getBytes();
+ }
+ }
+
+ @Test()
+ public void testDefaults() throws DescriptorParseException {
+ DescriptorBuilder.createWithDefaultLines();
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testIdRsa1024TooShort() throws DescriptorParseException {
+ DescriptorBuilder.createWithIdLine("id rsa1024 AAAA");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testIdRsa1024TooLong() throws DescriptorParseException {
+ DescriptorBuilder.createWithIdLine("id ed25519 AAAAAAAAAAAAAAAAAAAAAA"
+ + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testIdRsa512() throws DescriptorParseException {
+ DescriptorBuilder.createWithIdLine("id rsa512 "
+ + "bvegfGxp8k7T9QFpjPTrPaJTa/8");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testIdEd25519Duplicate() throws DescriptorParseException {
+ DescriptorBuilder.createWithIdLine(
+ "id rsa1024 bvegfGxp8k7T9QFpjPTrPaJTa/8\n"
+ + "id rsa1024 bvegfGxp8k7T9QFpjPTrPaJTa/8");
+ }
+}
diff --git a/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java b/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
index a200dc4..46688d6 100644
--- a/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
+++ b/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
@@ -267,7 +267,13 @@ public class RelayNetworkStatusVoteImplTest {
vb.dirKeyCertificationLines = lines;
return new RelayNetworkStatusVoteImpl(vb.buildVote(), true);
}
- private List<String> statusEntries = new ArrayList<String>();
+ private List<String> statusEntries = null;
+ private static RelayNetworkStatusVote createWithStatusEntries(
+ List<String> statusEntries) throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.statusEntries = statusEntries;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote(), true);
+ }
private String directoryFooterLine = "directory-footer";
private static RelayNetworkStatusVote
createWithDirectoryFooterLine(String line)
@@ -343,6 +349,10 @@ public class RelayNetworkStatusVoteImplTest {
}
private VoteBuilder() {
+ if (this.statusEntries != null) {
+ return;
+ }
+ this.statusEntries = new ArrayList<>();
this.statusEntries.add("r right2privassy3 "
+ "ADQ6gCT3DiFHKPDFr3rODBUI8HM lJY5Vf7kXec+VdkGW2flEsfkFC8 "
+ "2011-11-12 00:03:40 50.63.8.215 9023 0\n"
@@ -1181,5 +1191,47 @@ public class RelayNetworkStatusVoteImplTest {
unrecognizedLines.add(unrecognizedLine);
assertEquals(unrecognizedLines, vote.getUnrecognizedLines());
}
+
+ @Test()
+ public void testIdEd25519MasterKey()
+ throws DescriptorParseException {
+ String masterKey25519 = "8RH34kO07Pp+XYwzdoATVyCibIvmbslUjRkAm7J4IA8";
+ List<String> statusEntries = new ArrayList<>();
+ statusEntries.add("r PDrelay1 AAFJ5u9xAqrKlpDW6N0pMhJLlKs "
+ + "bgJiI/la3e9u0K7cQ5pMSXhigHI 2015-12-01 04:54:30 95.215.44.189 "
+ + "8080 0\n"
+ + "id ed25519 " + masterKey25519);
+ RelayNetworkStatusVote vote =
+ VoteBuilder.createWithStatusEntries(statusEntries);
+ String fingerprint = vote.getStatusEntries().firstKey();
+ assertEquals(masterKey25519,
+ vote.getStatusEntry(fingerprint).getMasterKeyEd25519());
+ }
+
+ @Test()
+ public void testIdEd25519None()
+ throws DescriptorParseException {
+ List<String> statusEntries = new ArrayList<>();
+ statusEntries.add("r MathematicalApology AAPJIrV9nhfgTYQs0vsTghFaP2A "
+ + "uA7p0m68O8ILXsf3aLZUj0EvDNE 2015-12-01 18:01:49 172.99.69.177 "
+ + "443 9030\n"
+ + "id ed25519 none");
+ RelayNetworkStatusVote vote =
+ VoteBuilder.createWithStatusEntries(statusEntries);
+ String fingerprint = vote.getStatusEntries().firstKey();
+ assertEquals("none",
+ vote.getStatusEntry(fingerprint).getMasterKeyEd25519());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testIdRsa1024None()
+ throws DescriptorParseException {
+ List<String> statusEntries = new ArrayList<>();
+ statusEntries.add("r MathematicalApology AAPJIrV9nhfgTYQs0vsTghFaP2A "
+ + "uA7p0m68O8ILXsf3aLZUj0EvDNE 2015-12-01 18:01:49 172.99.69.177 "
+ + "443 9030\n"
+ + "id rsa1024 none");
+ VoteBuilder.createWithStatusEntries(statusEntries);
+ }
}
diff --git a/test/org/torproject/descriptor/impl/ServerDescriptorImplTest.java b/test/org/torproject/descriptor/impl/ServerDescriptorImplTest.java
index d2d03f3..a98df9f 100644
--- a/test/org/torproject/descriptor/impl/ServerDescriptorImplTest.java
+++ b/test/org/torproject/descriptor/impl/ServerDescriptorImplTest.java
@@ -228,11 +228,28 @@ public class ServerDescriptorImplTest {
return new RelayServerDescriptorImpl(db.buildDescriptor(),
failUnrecognizedDescriptorLines);
}
+ private String identityEd25519Lines = null,
+ masterKeyEd25519Line = null, routerSigEd25519Line = null;
+ private static ServerDescriptor createWithEd25519Lines(
+ String identityEd25519Lines, String masterKeyEd25519Line,
+ String routerSigEd25519Line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.identityEd25519Lines = identityEd25519Lines;
+ db.masterKeyEd25519Line = masterKeyEd25519Line;
+ db.routerSigEd25519Line = routerSigEd25519Line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor(), true);
+ }
private byte[] buildDescriptor() {
StringBuilder sb = new StringBuilder();
if (this.routerLine != null) {
sb.append(this.routerLine + "\n");
}
+ if (this.identityEd25519Lines != null) {
+ sb.append(this.identityEd25519Lines + "\n");
+ }
+ if (this.masterKeyEd25519Line != null) {
+ sb.append(this.masterKeyEd25519Line + "\n");
+ }
if (this.bandwidthLine != null) {
sb.append(this.bandwidthLine + "\n");
}
@@ -313,6 +330,9 @@ public class ServerDescriptorImplTest {
return null;
}
}
+ if (this.routerSigEd25519Line != null) {
+ sb.append(this.routerSigEd25519Line + "\n");
+ }
if (this.routerSignatureLines != null) {
sb.append(this.routerSignatureLines + "\n");
}
@@ -1364,5 +1384,102 @@ public class ServerDescriptorImplTest {
createWithUnrecognizedLine(sb.toString().substring(1), false);
assertEquals(unrecognizedLines, descriptor.getUnrecognizedLines());
}
+
+ private static final String IDENTITY_ED25519_LINES =
+ "identity-ed25519\n"
+ + "-----BEGIN ED25519 CERT-----\n"
+ + "AQQABiX1AVGv5BuzJroQXbOh6vv1nbwc5rh2S13PyRFuLhTiifK4AQAgBACBCMwr"
+ + "\n4qgIlFDIzoC9ieJOtSkwrK+yXJPKlP8ojvgkx8cGKvhokOwA1eYDombzfwHcJ1"
+ + "EV\nbhEn/6g8i7wzO3LoqefIUrSAeEExOAOmm5mNmUIzL8EtnT6JHCr/sqUTUgA="
+ + "\n"
+ + "-----END ED25519 CERT-----";
+
+ private static final String MASTER_KEY_ED25519_LINE =
+ "master-key-ed25519 gQjMK+KoCJRQyM6AvYniTrUpMKyvslyTypT/KI74JMc";
+
+ private static final String ROUTER_SIG_ED25519_LINE =
+ "router-sig-ed25519 y7WF9T2GFwkSDPZEhB55HgquIFOl5uXUFMYJPq3CXXUTKeJ"
+ + "kSrtaZUB5s34fWdHQNtl84mH4dVaFMunHnwgYAw";
+
+ @Test()
+ public void testEd25519() throws DescriptorParseException {
+ ServerDescriptor descriptor =
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
+ MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE);
+ assertEquals(IDENTITY_ED25519_LINES.substring(
+ IDENTITY_ED25519_LINES.indexOf("\n") + 1),
+ descriptor.getIdentityEd25519());
+ assertEquals(MASTER_KEY_ED25519_LINE.substring(
+ MASTER_KEY_ED25519_LINE.indexOf(" ") + 1),
+ descriptor.getMasterKeyEd25519());
+ assertEquals(ROUTER_SIG_ED25519_LINE.substring(
+ ROUTER_SIG_ED25519_LINE.indexOf(" ") + 1),
+ descriptor.getRouterSignatureEd25519());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testEd25519IdentityMasterKeyMismatch()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
+ "master-key-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+ ROUTER_SIG_ED25519_LINE);
+ }
+
+ @Test()
+ public void testEd25519IdentityMissing()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines(null,
+ MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testEd25519IdentityDuplicate()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES + "\n"
+ + IDENTITY_ED25519_LINES, MASTER_KEY_ED25519_LINE,
+ ROUTER_SIG_ED25519_LINE);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testEd25519IdentityEmptyCrypto()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines("identity-ed25519\n"
+ + "-----BEGIN ED25519 CERT-----\n-----END ED25519 CERT-----",
+ MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE);
+ }
+
+ @Test()
+ public void testEd25519MasterKeyMissing()
+ throws DescriptorParseException {
+ ServerDescriptor descriptor =
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
+ null, ROUTER_SIG_ED25519_LINE);
+ assertEquals(MASTER_KEY_ED25519_LINE.substring(
+ MASTER_KEY_ED25519_LINE.indexOf(" ") + 1),
+ descriptor.getMasterKeyEd25519());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testEd25519MasterKeyDuplicate()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
+ MASTER_KEY_ED25519_LINE + "\n" + MASTER_KEY_ED25519_LINE,
+ ROUTER_SIG_ED25519_LINE);
+ }
+
+ @Test()
+ public void testEd25519RouterSigMissing()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
+ MASTER_KEY_ED25519_LINE, null);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testEd25519RouterSigDuplicate()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES,
+ MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE + "\n"
+ + ROUTER_SIG_ED25519_LINE);
+ }
}
[View Less]
1
0

[metrics-lib/master] Parse hidserv-stats in extra-info descriptors.
by karsten@torproject.org 21 Dec '15
by karsten@torproject.org 21 Dec '15
21 Dec '15
commit f9762314f297d184a5c6e0b0f5209815c18a29bf
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Wed Dec 16 12:06:41 2015 +0100
Parse hidserv-stats in extra-info descriptors.
This patch is loosely based on metrics-web's hidserv module.
---
CHANGELOG.md | 1 +
.../torproject/descriptor/ExtraInfoDescriptor.java | 32 +++++
.../descriptor/impl/ExtraInfoDescriptorImpl.java | 81 ++++++++++++
.../torproject/descriptor/…
[View More]impl/ParseHelper.java | 34 ++++++
.../impl/ExtraInfoDescriptorImplTest.java | 129 ++++++++++++++++++++
5 files changed, 277 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f2bc0d..bfda75e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@
extra-info descriptors, and support Ed25519 master keys in votes.
- Include RSA-1024 signatures of SHA-1 digests of extra-info
descriptors, which were parsed and discarded before.
+ - Support hidden-service statistics in extra-info descriptors.
# Changes in version 1.0.0 - 2015-12-05
diff --git a/src/org/torproject/descriptor/ExtraInfoDescriptor.java b/src/org/torproject/descriptor/ExtraInfoDescriptor.java
index 1b978a4..ed0141d 100644
--- a/src/org/torproject/descriptor/ExtraInfoDescriptor.java
+++ b/src/org/torproject/descriptor/ExtraInfoDescriptor.java
@@ -3,6 +3,7 @@
package org.torproject.descriptor;
import java.util.List;
+import java.util.Map;
import java.util.SortedMap;
/* Contains a relay or bridge extra-info descriptor. */
@@ -265,6 +266,37 @@ public interface ExtraInfoDescriptor extends Descriptor {
* bridge. */
public List<String> getTransports();
+ /* Return the end of the included hidden-service statistics, or -1 if no
+ * hidden-service statistics are included. */
+ public long getHidservStatsEndMillis();
+
+ /* Return the interval length of the included hidden-service statistics
+ * in seconds, or -1 if no hidden-service statistics are included. */
+ public long getHidservStatsIntervalLength();
+
+ /* Return the approximate number of RELAY cells seen in either direction
+ * on a circuit after receiving and successfully processing a
+ * RENDEZVOUS1 cell, or null if no hidden-service statistics are
+ * included. */
+ public Double getHidservRendRelayedCells();
+
+ /* Return the obfuscation parameters applied to the original measurement
+ * value of RELAY cells seen in either direction on a circuit after
+ * receiving and successfully processing a RENDEZVOUS1 cell, or null if
+ * no hidden-service statistics are included.. */
+ public Map<String, Double> getHidservRendRelayedCellsParameters();
+
+ /* Return the approximate number of unique hidden-service identities
+ * seen in descriptors published to and accepted by this hidden-service
+ * directory, or null if no hidden-service statistics are included. */
+ public Double getHidservDirOnionsSeen();
+
+ /* Return the obfuscation parameters applied to the original measurement
+ * value of unique hidden-service identities seen in descriptors
+ * published to and accepted by this hidden-service directory, or null
+ * if no hidden-service statistics are included. */
+ public Map<String, Double> getHidservDirOnionsSeenParameters();
+
/* Return the signature of the PKCS1-padded extra-info descriptor
* digest, or null if the descriptor doesn't contain a signature (which
* is the case in sanitized bridge descriptors). */
diff --git a/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java b/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java
index 4abace6..ef0c82c 100644
--- a/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java
+++ b/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java
@@ -9,8 +9,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.SortedMap;
@@ -165,6 +167,13 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
this.parseBridgeIpTransportsLine(line, lineNoOpt, partsNoOpt);
} else if (keyword.equals("transport")) {
this.parseTransportLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("hidserv-stats-end")) {
+ this.parseHidservStatsEndLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("hidserv-rend-relayed-cells")) {
+ this.parseHidservRendRelayedCellsLine(line, lineNoOpt,
+ partsNoOpt);
+ } else if (keyword.equals("hidserv-dir-onions-seen")) {
+ this.parseHidservDirOnionsSeenLine(line, lineNoOpt, partsNoOpt);
} else if (keyword.equals("identity-ed25519")) {
this.parseIdentityEd25519Line(line, lineNoOpt, partsNoOpt);
nextCrypto = "identity-ed25519";
@@ -642,6 +651,46 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
this.transports.add(partsNoOpt[1]);
}
+ private void parseHidservStatsEndLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt,
+ 5);
+ this.hidservStatsEndMillis = parsedStatsEndData[0];
+ this.hidservStatsIntervalLength = parsedStatsEndData[1];
+ }
+
+ private void parseHidservRendRelayedCellsLine(String line,
+ String lineNoOpt, String[] partsNoOpt)
+ throws DescriptorParseException {
+ if (partsNoOpt.length < 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ try {
+ this.hidservRendRelayedCells = Double.parseDouble(partsNoOpt[1]);
+ } catch (NumberFormatException e) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ this.hidservRendRelayedCellsParameters =
+ ParseHelper.parseSpaceSeparatedStringKeyDoubleValueMap(line,
+ partsNoOpt, 2);
+ }
+
+ private void parseHidservDirOnionsSeenLine(String line,
+ String lineNoOpt, String[] partsNoOpt)
+ throws DescriptorParseException {
+ if (partsNoOpt.length < 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ try {
+ this.hidservDirOnionsSeen = Double.parseDouble(partsNoOpt[1]);
+ } catch (NumberFormatException e) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ this.hidservDirOnionsSeenParameters =
+ ParseHelper.parseSpaceSeparatedStringKeyDoubleValueMap(line,
+ partsNoOpt, 2);
+ }
+
private void parseRouterSignatureLine(String line, String lineNoOpt,
String[] partsNoOpt) throws DescriptorParseException {
if (!lineNoOpt.equals("router-signature")) {
@@ -1057,6 +1106,38 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
return new ArrayList<String>(this.transports);
}
+ private long hidservStatsEndMillis = -1L;
+ public long getHidservStatsEndMillis() {
+ return this.hidservStatsEndMillis;
+ }
+
+ private long hidservStatsIntervalLength = -1L;
+ public long getHidservStatsIntervalLength() {
+ return this.hidservStatsIntervalLength;
+ }
+
+ private Double hidservRendRelayedCells;
+ public Double getHidservRendRelayedCells() {
+ return this.hidservRendRelayedCells;
+ }
+
+ private Map<String, Double> hidservRendRelayedCellsParameters;
+ public Map<String, Double> getHidservRendRelayedCellsParameters() {
+ return this.hidservRendRelayedCellsParameters == null ? null :
+ new HashMap<>(this.hidservRendRelayedCellsParameters);
+ }
+
+ private Double hidservDirOnionsSeen;
+ public Double getHidservDirOnionsSeen() {
+ return this.hidservDirOnionsSeen;
+ }
+
+ private Map<String, Double> hidservDirOnionsSeenParameters;
+ public Map<String, Double> getHidservDirOnionsSeenParameters() {
+ return this.hidservDirOnionsSeenParameters == null ? null :
+ new HashMap<>(this.hidservDirOnionsSeenParameters);
+ }
+
private String routerSignature;
public String getRouterSignature() {
return this.routerSignature;
diff --git a/src/org/torproject/descriptor/impl/ParseHelper.java b/src/org/torproject/descriptor/impl/ParseHelper.java
index a354831..15de5ee 100644
--- a/src/org/torproject/descriptor/impl/ParseHelper.java
+++ b/src/org/torproject/descriptor/impl/ParseHelper.java
@@ -6,6 +6,7 @@ import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
@@ -435,6 +436,39 @@ public class ParseHelper {
return result;
}
+ protected static Map<String, Double>
+ parseSpaceSeparatedStringKeyDoubleValueMap(String line,
+ String[] partsNoOpt, int startIndex)
+ throws DescriptorParseException {
+ Map<String, Double> result = new LinkedHashMap<>();
+ if (partsNoOpt.length < startIndex) {
+ throw new DescriptorParseException("Line '" + line + "' does not "
+ + "contain a key-value list starting at index " + startIndex
+ + ".");
+ }
+ for (int i = startIndex; i < partsNoOpt.length; i++) {
+ String listElement = partsNoOpt[i];
+ String[] keyAndValue = listElement.split("=");
+ String key = null;
+ Double value = null;
+ if (keyAndValue.length == 2) {
+ try {
+ value = Double.parseDouble(keyAndValue[1]);
+ key = keyAndValue[0];
+ } catch (NumberFormatException e) {
+ /* Handle below. */
+ }
+ }
+ if (key == null) {
+ throw new DescriptorParseException("Line '" + line + "' contains "
+ + "an illegal key or value in list element '" + listElement
+ + "'.");
+ }
+ result.put(key, value);
+ }
+ return result;
+ }
+
public static String
parseMasterKeyEd25519FromIdentityEd25519CryptoBlock(
String identityEd25519CryptoBlock) throws DescriptorParseException {
diff --git a/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java b/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java
index d70ac39..55e0578 100644
--- a/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java
+++ b/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java
@@ -138,6 +138,13 @@ public class ExtraInfoDescriptorImplTest {
db.bridgeStatsLines = lines;
return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true);
}
+ private String hidservStatsLines = null;
+ private static ExtraInfoDescriptor createWithHidservStatsLines(
+ String lines) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.hidservStatsLines = lines;
+ return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true);
+ }
private String unrecognizedLine = null;
private static ExtraInfoDescriptor createWithUnrecognizedLine(
String line, boolean failUnrecognizedDescriptorLines)
@@ -232,6 +239,9 @@ public class ExtraInfoDescriptorImplTest {
if (this.bridgeStatsLines != null) {
sb.append(this.bridgeStatsLines + "\n");
}
+ if (this.hidservStatsLines != null) {
+ sb.append(this.hidservStatsLines + "\n");
+ }
if (this.unrecognizedLine != null) {
sb.append(this.unrecognizedLine + "\n");
}
@@ -733,6 +743,62 @@ public class ExtraInfoDescriptorImplTest {
}
}
+ /* Helper class to build a set of hidserv-stats lines based on default
+ * data and modifications requested by test methods. */
+ private static class HidservStatsBuilder {
+ private String hidservStatsEndLine = "hidserv-stats-end 2015-12-03 "
+ + "14:26:56 (86400 s)";
+ private static ExtraInfoDescriptor createWithHidservStatsEndLine(
+ String line) throws DescriptorParseException {
+ HidservStatsBuilder hsb = new HidservStatsBuilder();
+ hsb.hidservStatsEndLine = line;
+ return DescriptorBuilder.createWithHidservStatsLines(
+ hsb.buildHidservStatsLines());
+ }
+ private String hidservRendRelayedCellsLine =
+ "hidserv-rend-relayed-cells 36474281 delta_f=2048 epsilon=0.30 "
+ + "bin_size=1024";
+ private static ExtraInfoDescriptor
+ createWithHidservRendRelayedCellsLine(String line)
+ throws DescriptorParseException {
+ HidservStatsBuilder hsb = new HidservStatsBuilder();
+ hsb.hidservRendRelayedCellsLine = line;
+ return DescriptorBuilder.createWithHidservStatsLines(
+ hsb.buildHidservStatsLines());
+ }
+ private String hidservDirOnionsSeenLine = "hidserv-dir-onions-seen "
+ + "-3 delta_f=8 epsilon=0.30 bin_size=8";
+ private static ExtraInfoDescriptor createWithHidservDirOnionsSeenLine(
+ String line) throws DescriptorParseException {
+ HidservStatsBuilder hsb = new HidservStatsBuilder();
+ hsb.hidservDirOnionsSeenLine = line;
+ return DescriptorBuilder.createWithHidservStatsLines(
+ hsb.buildHidservStatsLines());
+ }
+ private static ExtraInfoDescriptor createWithDefaultLines()
+ throws DescriptorParseException {
+ return DescriptorBuilder.createWithHidservStatsLines(
+ new HidservStatsBuilder().buildHidservStatsLines());
+ }
+ private String buildHidservStatsLines() {
+ StringBuilder sb = new StringBuilder();
+ if (this.hidservStatsEndLine != null) {
+ sb.append(this.hidservStatsEndLine + "\n");
+ }
+ if (this.hidservRendRelayedCellsLine != null) {
+ sb.append(this.hidservRendRelayedCellsLine + "\n");
+ }
+ if (this.hidservDirOnionsSeenLine != null) {
+ sb.append(this.hidservDirOnionsSeenLine + "\n");
+ }
+ String lines = sb.toString();
+ if (lines.endsWith("\n")) {
+ lines = lines.substring(0, lines.length() - 1);
+ }
+ return lines;
+ }
+ }
+
@Test()
public void testSampleDescriptor() throws DescriptorParseException {
DescriptorBuilder db = new DescriptorBuilder();
@@ -1415,6 +1481,69 @@ public class ExtraInfoDescriptorImplTest {
}
@Test()
+ public void testHidservStatsValid() throws DescriptorParseException {
+ ExtraInfoDescriptor descriptor = HidservStatsBuilder.
+ createWithDefaultLines();
+ assertEquals(1449152816000L, descriptor.getHidservStatsEndMillis());
+ assertEquals(86400L, descriptor.getHidservStatsIntervalLength());
+ assertEquals(36474281.0, descriptor.getHidservRendRelayedCells(),
+ 0.0001);
+ Map<String, Double> params =
+ descriptor.getHidservRendRelayedCellsParameters();
+ assertTrue(params.containsKey("delta_f"));
+ assertEquals(2048.0, params.remove("delta_f"), 0.0001);
+ assertTrue(params.containsKey("epsilon"));
+ assertEquals(0.3, params.remove("epsilon"), 0.0001);
+ assertTrue(params.containsKey("bin_size"));
+ assertEquals(1024.0, params.remove("bin_size"), 0.0001);
+ assertTrue(params.isEmpty());
+ assertEquals(-3.0, descriptor.getHidservDirOnionsSeen(), 0.0001);
+ params = descriptor.getHidservDirOnionsSeenParameters();
+ assertTrue(params.containsKey("delta_f"));
+ assertEquals(8.0, params.remove("delta_f"), 0.0001);
+ assertTrue(params.containsKey("epsilon"));
+ assertEquals(0.3, params.remove("epsilon"), 0.0001);
+ assertTrue(params.containsKey("bin_size"));
+ assertEquals(8.0, params.remove("bin_size"), 0.0001);
+ assertTrue(params.isEmpty());
+ }
+
+ @Test()
+ public void testHidservStatsEndLineMissing()
+ throws DescriptorParseException {
+ ExtraInfoDescriptor descriptor =
+ HidservStatsBuilder.createWithHidservStatsEndLine(null);
+ assertEquals(-1L, descriptor.getHidservStatsEndMillis());
+ assertEquals(-1L, descriptor.getHidservStatsIntervalLength());
+ }
+
+ @Test()
+ public void testHidservRendRelayedCellsNoParams()
+ throws DescriptorParseException {
+ ExtraInfoDescriptor descriptor =
+ HidservStatsBuilder.createWithHidservRendRelayedCellsLine(
+ "hidserv-rend-relayed-cells 36474281");
+ assertEquals(36474281.0, descriptor.getHidservRendRelayedCells(),
+ 0.0001);
+ assertTrue(
+ descriptor.getHidservRendRelayedCellsParameters().isEmpty());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testHidservDirOnionsSeenCommaSeparatedParams()
+ throws DescriptorParseException {
+ HidservStatsBuilder.createWithHidservDirOnionsSeenLine(
+ "hidserv-dir-onions-seen -3 delta_f=8,epsilon=0.30,bin_size=8");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testHidservDirOnionsSeenNoDoubleParams()
+ throws DescriptorParseException {
+ HidservStatsBuilder.createWithHidservDirOnionsSeenLine(
+ "hidserv-dir-onions-seen -3 delta_f=A epsilon=B bin_size=C");
+ }
+
+ @Test()
public void testRouterSignatureOpt()
throws DescriptorParseException {
DescriptorBuilder.createWithRouterSignatureLines("opt "
[View Less]
1
0