tor-commits
Threads by month
- ----- 2025 -----
- 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
August 2019
- 19 participants
- 2737 discussions

[translation/tails-misc_release] Update translations for tails-misc_release
by translation@torproject.org 25 Aug '19
by translation@torproject.org 25 Aug '19
25 Aug '19
commit bfaa98e0810eb3302e84496495dc7daad0b8f81a
Author: Translation commit bot <translation(a)torproject.org>
Date: Sun Aug 25 12:50:56 2019 +0000
Update translations for tails-misc_release
---
de.po | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/de.po b/de.po
index 170e4f1c6..d2d51560b 100644
--- a/de.po
+++ b/de.po
@@ -37,8 +37,8 @@ msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-08-14 14:11+0200\n"
-"PO-Revision-Date: 2019-08-24 12:02+0000\n"
-"Last-Translator: Ettore Atalan <atalanttore(a)googlemail.com>\n"
+"PO-Revision-Date: 2019-08-25 12:50+0000\n"
+"Last-Translator: Curtis Baltimore <curtisbaltimore(a)protonmail.com>\n"
"Language-Team: German (http://www.transifex.com/otf/torproject/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
1
0

[translation/snowflakeaddon-messages.json_completed] Update translations for snowflakeaddon-messages.json_completed
by translation@torproject.org 25 Aug '19
by translation@torproject.org 25 Aug '19
25 Aug '19
commit bbf11bb0c9f1aca4f6b18c6505645f85e2fa1986
Author: Translation commit bot <translation(a)torproject.org>
Date: Sun Aug 25 12:48:47 2019 +0000
Update translations for snowflakeaddon-messages.json_completed
---
de/messages.json | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/de/messages.json b/de/messages.json
index de5d474d6..f9de9d4cc 100644
--- a/de/messages.json
+++ b/de/messages.json
@@ -1,26 +1,32 @@
{
"appDesc": {
- "message": "Snowflake ist ein WebRTC basierter plaggable Transport für Tor."
+ "message": "Snowflake is a WebRTC pluggable transport for Tor."
},
"popupTurnOn": {
- "message": "Einschalten"
+ "message": "Turn On"
},
"popupTurnOff": {
- "message": "Ausschalten"
+ "message": "Turn Off"
},
"popupLearnMore": {
- "message": "Erfahre mehr"
+ "message": "Learn more"
},
"popupStatusOff": {
- "message": "Snowflake ist ausgeschaltet"
+ "message": "Snowflake is off"
},
"popupStatusOn": {
- "message": "$1 Nutzer verbunden."
+ "message": "Number of users currently connected: $1"
+ },
+ "popupStatusReady": {
+ "message": "Your Snowflake is ready to help users circumvent censorship!"
},
"popupWebRTCOff": {
- "message": "WebRTC-Fähigkeit nicht erkannt."
+ "message": "WebRTC feature is not detected."
},
"popupDescOn": {
- "message": "Ihre Snowflake hat $1 Nutzern in den letzten 24 Stunden geholfen zensur zu umgehen."
+ "message": "Number of users your Snowflake has helped circumvent censorship in the last 24 hours: $1"
+ },
+ "badgeCookiesOff": {
+ "message": "Cookies are not enabled."
}
}
1
0

[translation/snowflakeaddon-messages.json] Update translations for snowflakeaddon-messages.json
by translation@torproject.org 25 Aug '19
by translation@torproject.org 25 Aug '19
25 Aug '19
commit b0cca7c61d37bdf9d67f098aa7504b44e8e4ce16
Author: Translation commit bot <translation(a)torproject.org>
Date: Sun Aug 25 12:48:38 2019 +0000
Update translations for snowflakeaddon-messages.json
---
de/messages.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/de/messages.json b/de/messages.json
index 29a479767..1f607dd9a 100644
--- a/de/messages.json
+++ b/de/messages.json
@@ -1,6 +1,6 @@
{
"appDesc": {
- "message": "Snowflake is a WebRTC pluggable transport for Tor."
+ "message": "Snowflake ist eine WebRTC basierte austauschbare Übertragungsart für Tor."
},
"popupTurnOn": {
"message": "Einschalten"
@@ -18,13 +18,13 @@
"message": "Anzahl der derzeit verbundenen Benutzer: $1"
},
"popupStatusReady": {
- "message": "Your Snowflake is ready to help users circumvent censorship!"
+ "message": "Dein Snowflake ist bereit, Benutzern zu helfen, die Zensur zu umgehen!"
},
"popupWebRTCOff": {
"message": "WebRTC-Fähigkeit nicht erkannt."
},
"popupDescOn": {
- "message": "Number of users your Snowflake has helped circumvent censorship in the last 24 hours: $1"
+ "message": "Anzahl der Benutzer, die dein Snowflake in den letzten 24 Stunden geholfen hat, die Zensur zu umgehen: $1"
},
"badgeCookiesOff": {
"message": "Cookies sind nicht aktiviert."
1
0

[translation/communitytpo-contentspot_completed] Update translations for communitytpo-contentspot_completed
by translation@torproject.org 25 Aug '19
by translation@torproject.org 25 Aug '19
25 Aug '19
commit ad0c3e5ce8edb23df97c2ed708f46469b95404a7
Author: Translation commit bot <translation(a)torproject.org>
Date: Sun Aug 25 08:45:35 2019 +0000
Update translations for communitytpo-contentspot_completed
---
contents+en.po | 43 +++++++++++++++++++++++++++++++------------
contents.pot | 43 +++++++++++++++++++++++++++++++------------
2 files changed, 62 insertions(+), 24 deletions(-)
diff --git a/contents+en.po b/contents+en.po
index 780e511e0..0f144c85f 100644
--- a/contents+en.po
+++ b/contents+en.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-15 17:04+CET\n"
+"POT-Creation-Date: 2019-08-25 10:23+CET\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: en <LL(a)li.org>\n"
@@ -3588,11 +3588,11 @@ msgstr "### Reporting an error with a translation"
#: (content/localization/translation-problem/contents+en.lrpage.body)
msgid ""
"* If you are already a [Tor translator](becoming-tor-translator), you can "
-"simply find the string and fix it "
+"simply find the string and fix it in "
"[transifex](https://www.transifex.com/otf/torproject/)."
msgstr ""
"* If you are already a [Tor translator](becoming-tor-translator), you can "
-"simply find the string and fix it "
+"simply find the string and fix it in "
"[transifex](https://www.transifex.com/otf/torproject/)."
#: https//community.torproject.org/localization/translation-problem/
@@ -10565,6 +10565,17 @@ msgstr "Log notice file /var/log/tor/notices.log"
#: https//community.torproject.org/relay/setup/bridge/freebsd/
#: (content/relay-operations/technical-setup/bridge/freebsd/contents+en.lrpage.body)
+msgid ""
+"* Are you using FreeBSD's firewall with a \"default deny\" policy? If so, "
+"make sure that your obfs4proxy can talk to your Tor process over the "
+"loopback interface. Don't forget to whitelist Tor's `ExtORPort`."
+msgstr ""
+"* Are you using FreeBSD's firewall with a \"default deny\" policy? If so, "
+"make sure that your obfs4proxy can talk to your Tor process over the "
+"loopback interface. Don't forget to whitelist Tor's `ExtORPort`."
+
+#: https//community.torproject.org/relay/setup/bridge/freebsd/
+#: (content/relay-operations/technical-setup/bridge/freebsd/contents+en.lrpage.body)
msgid "### 3. Ensure that the `random_id` sysctl setting is enabled:"
msgstr "### 3. Ensure that the `random_id` sysctl setting is enabled:"
@@ -11351,8 +11362,8 @@ msgid "Your customer, [User]"
msgstr "Your customer, [User]"
#: lego/templates/footer.html:9 lego/templates/footer.html:18
-#: lego/templates/navbar.html:66 templates/footer.html:9
-#: templates/footer.html:18 templates/navbar.html:57
+#: lego/templates/navbar.html:71 templates/footer.html:9
+#: templates/footer.html:18 templates/navbar.html:71
msgid "Download Tor Browser"
msgstr "Download Tor Browser"
@@ -11380,8 +11391,8 @@ msgstr ""
"availability and use, and furthering their scientific and popular "
"understanding."
-#: lego/templates/footer.html:49 lego/templates/navbar.html:12
-#: templates/footer.html:49 templates/navbar.html:12
+#: lego/templates/footer.html:49 lego/templates/navbar.html:15
+#: templates/footer.html:49 templates/navbar.html:15
msgid "Donate Now"
msgstr "Donate Now"
@@ -11406,7 +11417,7 @@ msgstr ""
"Trademark, copyright notices, and rules for use by third parties can be "
"found in our %(link_to_faq)s"
-#: lego/templates/navbar.html:15
+#: lego/templates/navbar.html:18 templates/navbar.html:18
msgid "Menu"
msgstr "Menu"
@@ -11436,10 +11447,6 @@ msgstr ""
msgid "Translators mailing list"
msgstr "Translators mailing list"
-#: templates/navbar.html:10
-msgid "Tor Logo"
-msgstr "Tor Logo"
-
#: templates/onion-services.html:10
msgid "Learn more about onion services here"
msgstr "Learn more about onion services here"
@@ -11532,6 +11539,18 @@ msgstr ""
msgid "Community mailing list"
msgstr "Community mailing list"
+#: templates/two-columns-page.html:19
+msgid "Contributors to this page:"
+msgstr "Contributors to this page:"
+
+#: templates/two-columns-page.html:21
+msgid "Back to previous page: "
+msgstr "Back to previous page: "
+
+#: templates/two-columns-page.html:21
+msgid "Edit this page"
+msgstr "Edit this page"
+
#: templates/user-research.html:10
msgid "Want to know more? Take a look at our process"
msgstr "Want to know more? Take a look at our process"
diff --git a/contents.pot b/contents.pot
index 780e511e0..0f144c85f 100644
--- a/contents.pot
+++ b/contents.pot
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-15 17:04+CET\n"
+"POT-Creation-Date: 2019-08-25 10:23+CET\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: en <LL(a)li.org>\n"
@@ -3588,11 +3588,11 @@ msgstr "### Reporting an error with a translation"
#: (content/localization/translation-problem/contents+en.lrpage.body)
msgid ""
"* If you are already a [Tor translator](becoming-tor-translator), you can "
-"simply find the string and fix it "
+"simply find the string and fix it in "
"[transifex](https://www.transifex.com/otf/torproject/)."
msgstr ""
"* If you are already a [Tor translator](becoming-tor-translator), you can "
-"simply find the string and fix it "
+"simply find the string and fix it in "
"[transifex](https://www.transifex.com/otf/torproject/)."
#: https//community.torproject.org/localization/translation-problem/
@@ -10565,6 +10565,17 @@ msgstr "Log notice file /var/log/tor/notices.log"
#: https//community.torproject.org/relay/setup/bridge/freebsd/
#: (content/relay-operations/technical-setup/bridge/freebsd/contents+en.lrpage.body)
+msgid ""
+"* Are you using FreeBSD's firewall with a \"default deny\" policy? If so, "
+"make sure that your obfs4proxy can talk to your Tor process over the "
+"loopback interface. Don't forget to whitelist Tor's `ExtORPort`."
+msgstr ""
+"* Are you using FreeBSD's firewall with a \"default deny\" policy? If so, "
+"make sure that your obfs4proxy can talk to your Tor process over the "
+"loopback interface. Don't forget to whitelist Tor's `ExtORPort`."
+
+#: https//community.torproject.org/relay/setup/bridge/freebsd/
+#: (content/relay-operations/technical-setup/bridge/freebsd/contents+en.lrpage.body)
msgid "### 3. Ensure that the `random_id` sysctl setting is enabled:"
msgstr "### 3. Ensure that the `random_id` sysctl setting is enabled:"
@@ -11351,8 +11362,8 @@ msgid "Your customer, [User]"
msgstr "Your customer, [User]"
#: lego/templates/footer.html:9 lego/templates/footer.html:18
-#: lego/templates/navbar.html:66 templates/footer.html:9
-#: templates/footer.html:18 templates/navbar.html:57
+#: lego/templates/navbar.html:71 templates/footer.html:9
+#: templates/footer.html:18 templates/navbar.html:71
msgid "Download Tor Browser"
msgstr "Download Tor Browser"
@@ -11380,8 +11391,8 @@ msgstr ""
"availability and use, and furthering their scientific and popular "
"understanding."
-#: lego/templates/footer.html:49 lego/templates/navbar.html:12
-#: templates/footer.html:49 templates/navbar.html:12
+#: lego/templates/footer.html:49 lego/templates/navbar.html:15
+#: templates/footer.html:49 templates/navbar.html:15
msgid "Donate Now"
msgstr "Donate Now"
@@ -11406,7 +11417,7 @@ msgstr ""
"Trademark, copyright notices, and rules for use by third parties can be "
"found in our %(link_to_faq)s"
-#: lego/templates/navbar.html:15
+#: lego/templates/navbar.html:18 templates/navbar.html:18
msgid "Menu"
msgstr "Menu"
@@ -11436,10 +11447,6 @@ msgstr ""
msgid "Translators mailing list"
msgstr "Translators mailing list"
-#: templates/navbar.html:10
-msgid "Tor Logo"
-msgstr "Tor Logo"
-
#: templates/onion-services.html:10
msgid "Learn more about onion services here"
msgstr "Learn more about onion services here"
@@ -11532,6 +11539,18 @@ msgstr ""
msgid "Community mailing list"
msgstr "Community mailing list"
+#: templates/two-columns-page.html:19
+msgid "Contributors to this page:"
+msgstr "Contributors to this page:"
+
+#: templates/two-columns-page.html:21
+msgid "Back to previous page: "
+msgstr "Back to previous page: "
+
+#: templates/two-columns-page.html:21
+msgid "Edit this page"
+msgstr "Edit this page"
+
#: templates/user-research.html:10
msgid "Want to know more? Take a look at our process"
msgstr "Want to know more? Take a look at our process"
1
0

[translation/communitytpo-contentspot] Update translations for communitytpo-contentspot
by translation@torproject.org 25 Aug '19
by translation@torproject.org 25 Aug '19
25 Aug '19
commit 264c93c2914aee5ce9776a1adff65c2223418cc2
Author: Translation commit bot <translation(a)torproject.org>
Date: Sun Aug 25 08:45:26 2019 +0000
Update translations for communitytpo-contentspot
---
contents+en.po | 43 +++++++++++++++++++++++++++++++------------
contents.pot | 43 +++++++++++++++++++++++++++++++------------
2 files changed, 62 insertions(+), 24 deletions(-)
diff --git a/contents+en.po b/contents+en.po
index 780e511e0..0f144c85f 100644
--- a/contents+en.po
+++ b/contents+en.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-15 17:04+CET\n"
+"POT-Creation-Date: 2019-08-25 10:23+CET\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: en <LL(a)li.org>\n"
@@ -3588,11 +3588,11 @@ msgstr "### Reporting an error with a translation"
#: (content/localization/translation-problem/contents+en.lrpage.body)
msgid ""
"* If you are already a [Tor translator](becoming-tor-translator), you can "
-"simply find the string and fix it "
+"simply find the string and fix it in "
"[transifex](https://www.transifex.com/otf/torproject/)."
msgstr ""
"* If you are already a [Tor translator](becoming-tor-translator), you can "
-"simply find the string and fix it "
+"simply find the string and fix it in "
"[transifex](https://www.transifex.com/otf/torproject/)."
#: https//community.torproject.org/localization/translation-problem/
@@ -10565,6 +10565,17 @@ msgstr "Log notice file /var/log/tor/notices.log"
#: https//community.torproject.org/relay/setup/bridge/freebsd/
#: (content/relay-operations/technical-setup/bridge/freebsd/contents+en.lrpage.body)
+msgid ""
+"* Are you using FreeBSD's firewall with a \"default deny\" policy? If so, "
+"make sure that your obfs4proxy can talk to your Tor process over the "
+"loopback interface. Don't forget to whitelist Tor's `ExtORPort`."
+msgstr ""
+"* Are you using FreeBSD's firewall with a \"default deny\" policy? If so, "
+"make sure that your obfs4proxy can talk to your Tor process over the "
+"loopback interface. Don't forget to whitelist Tor's `ExtORPort`."
+
+#: https//community.torproject.org/relay/setup/bridge/freebsd/
+#: (content/relay-operations/technical-setup/bridge/freebsd/contents+en.lrpage.body)
msgid "### 3. Ensure that the `random_id` sysctl setting is enabled:"
msgstr "### 3. Ensure that the `random_id` sysctl setting is enabled:"
@@ -11351,8 +11362,8 @@ msgid "Your customer, [User]"
msgstr "Your customer, [User]"
#: lego/templates/footer.html:9 lego/templates/footer.html:18
-#: lego/templates/navbar.html:66 templates/footer.html:9
-#: templates/footer.html:18 templates/navbar.html:57
+#: lego/templates/navbar.html:71 templates/footer.html:9
+#: templates/footer.html:18 templates/navbar.html:71
msgid "Download Tor Browser"
msgstr "Download Tor Browser"
@@ -11380,8 +11391,8 @@ msgstr ""
"availability and use, and furthering their scientific and popular "
"understanding."
-#: lego/templates/footer.html:49 lego/templates/navbar.html:12
-#: templates/footer.html:49 templates/navbar.html:12
+#: lego/templates/footer.html:49 lego/templates/navbar.html:15
+#: templates/footer.html:49 templates/navbar.html:15
msgid "Donate Now"
msgstr "Donate Now"
@@ -11406,7 +11417,7 @@ msgstr ""
"Trademark, copyright notices, and rules for use by third parties can be "
"found in our %(link_to_faq)s"
-#: lego/templates/navbar.html:15
+#: lego/templates/navbar.html:18 templates/navbar.html:18
msgid "Menu"
msgstr "Menu"
@@ -11436,10 +11447,6 @@ msgstr ""
msgid "Translators mailing list"
msgstr "Translators mailing list"
-#: templates/navbar.html:10
-msgid "Tor Logo"
-msgstr "Tor Logo"
-
#: templates/onion-services.html:10
msgid "Learn more about onion services here"
msgstr "Learn more about onion services here"
@@ -11532,6 +11539,18 @@ msgstr ""
msgid "Community mailing list"
msgstr "Community mailing list"
+#: templates/two-columns-page.html:19
+msgid "Contributors to this page:"
+msgstr "Contributors to this page:"
+
+#: templates/two-columns-page.html:21
+msgid "Back to previous page: "
+msgstr "Back to previous page: "
+
+#: templates/two-columns-page.html:21
+msgid "Edit this page"
+msgstr "Edit this page"
+
#: templates/user-research.html:10
msgid "Want to know more? Take a look at our process"
msgstr "Want to know more? Take a look at our process"
diff --git a/contents.pot b/contents.pot
index 780e511e0..0f144c85f 100644
--- a/contents.pot
+++ b/contents.pot
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-08-15 17:04+CET\n"
+"POT-Creation-Date: 2019-08-25 10:23+CET\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: en <LL(a)li.org>\n"
@@ -3588,11 +3588,11 @@ msgstr "### Reporting an error with a translation"
#: (content/localization/translation-problem/contents+en.lrpage.body)
msgid ""
"* If you are already a [Tor translator](becoming-tor-translator), you can "
-"simply find the string and fix it "
+"simply find the string and fix it in "
"[transifex](https://www.transifex.com/otf/torproject/)."
msgstr ""
"* If you are already a [Tor translator](becoming-tor-translator), you can "
-"simply find the string and fix it "
+"simply find the string and fix it in "
"[transifex](https://www.transifex.com/otf/torproject/)."
#: https//community.torproject.org/localization/translation-problem/
@@ -10565,6 +10565,17 @@ msgstr "Log notice file /var/log/tor/notices.log"
#: https//community.torproject.org/relay/setup/bridge/freebsd/
#: (content/relay-operations/technical-setup/bridge/freebsd/contents+en.lrpage.body)
+msgid ""
+"* Are you using FreeBSD's firewall with a \"default deny\" policy? If so, "
+"make sure that your obfs4proxy can talk to your Tor process over the "
+"loopback interface. Don't forget to whitelist Tor's `ExtORPort`."
+msgstr ""
+"* Are you using FreeBSD's firewall with a \"default deny\" policy? If so, "
+"make sure that your obfs4proxy can talk to your Tor process over the "
+"loopback interface. Don't forget to whitelist Tor's `ExtORPort`."
+
+#: https//community.torproject.org/relay/setup/bridge/freebsd/
+#: (content/relay-operations/technical-setup/bridge/freebsd/contents+en.lrpage.body)
msgid "### 3. Ensure that the `random_id` sysctl setting is enabled:"
msgstr "### 3. Ensure that the `random_id` sysctl setting is enabled:"
@@ -11351,8 +11362,8 @@ msgid "Your customer, [User]"
msgstr "Your customer, [User]"
#: lego/templates/footer.html:9 lego/templates/footer.html:18
-#: lego/templates/navbar.html:66 templates/footer.html:9
-#: templates/footer.html:18 templates/navbar.html:57
+#: lego/templates/navbar.html:71 templates/footer.html:9
+#: templates/footer.html:18 templates/navbar.html:71
msgid "Download Tor Browser"
msgstr "Download Tor Browser"
@@ -11380,8 +11391,8 @@ msgstr ""
"availability and use, and furthering their scientific and popular "
"understanding."
-#: lego/templates/footer.html:49 lego/templates/navbar.html:12
-#: templates/footer.html:49 templates/navbar.html:12
+#: lego/templates/footer.html:49 lego/templates/navbar.html:15
+#: templates/footer.html:49 templates/navbar.html:15
msgid "Donate Now"
msgstr "Donate Now"
@@ -11406,7 +11417,7 @@ msgstr ""
"Trademark, copyright notices, and rules for use by third parties can be "
"found in our %(link_to_faq)s"
-#: lego/templates/navbar.html:15
+#: lego/templates/navbar.html:18 templates/navbar.html:18
msgid "Menu"
msgstr "Menu"
@@ -11436,10 +11447,6 @@ msgstr ""
msgid "Translators mailing list"
msgstr "Translators mailing list"
-#: templates/navbar.html:10
-msgid "Tor Logo"
-msgstr "Tor Logo"
-
#: templates/onion-services.html:10
msgid "Learn more about onion services here"
msgstr "Learn more about onion services here"
@@ -11532,6 +11539,18 @@ msgstr ""
msgid "Community mailing list"
msgstr "Community mailing list"
+#: templates/two-columns-page.html:19
+msgid "Contributors to this page:"
+msgstr "Contributors to this page:"
+
+#: templates/two-columns-page.html:21
+msgid "Back to previous page: "
+msgstr "Back to previous page: "
+
+#: templates/two-columns-page.html:21
+msgid "Edit this page"
+msgstr "Edit this page"
+
#: templates/user-research.html:10
msgid "Want to know more? Take a look at our process"
msgstr "Want to know more? Take a look at our process"
1
0
commit 26ed95d4b09505fa1320b0e8de0cc271776fe95d
Author: Damian Johnson <atagar(a)torproject.org>
Date: Thu Aug 22 15:55:46 2019 -0700
Stub hidden service descriptor v3
Doesn't do anything yet - just getting the class and such wired up.
---
stem/descriptor/__init__.py | 13 +-
stem/descriptor/hidden_service.py | 32 +++-
test/settings.cfg | 1 +
test/unit/descriptor/data/hidden_service_v3 | 223 ++++++++++++++++++++++++++++
test/unit/descriptor/hidden_service_v2.py | 26 ++--
test/unit/descriptor/hidden_service_v3.py | 19 +++
6 files changed, 288 insertions(+), 26 deletions(-)
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index 9415454c..1b5e53bc 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -532,18 +532,19 @@ def _parse_metrics_file(descriptor_type, major_version, minor_version, descripto
for desc in stem.descriptor.networkstatus._parse_file(descriptor_file, document_type, validate = validate, document_handler = document_handler, **kwargs):
yield desc
elif descriptor_type == stem.descriptor.tordnsel.TorDNSEL.TYPE_ANNOTATION_NAME and major_version == 1:
- document_type = stem.descriptor.tordnsel.TorDNSEL
-
for desc in stem.descriptor.tordnsel._parse_file(descriptor_file, validate = validate, **kwargs):
yield desc
elif descriptor_type == stem.descriptor.hidden_service.HiddenServiceDescriptorV2.TYPE_ANNOTATION_NAME and major_version == 1:
- document_type = stem.descriptor.hidden_service.HiddenServiceDescriptorV2
+ desc_type = stem.descriptor.hidden_service.HiddenServiceDescriptorV2
- for desc in stem.descriptor.hidden_service._parse_file(descriptor_file, validate = validate, **kwargs):
+ for desc in stem.descriptor.hidden_service._parse_file(descriptor_file, desc_type, validate = validate, **kwargs):
yield desc
- elif descriptor_type == stem.descriptor.bandwidth_file.BandwidthFile.TYPE_ANNOTATION_NAME and major_version == 1:
- document_type = stem.descriptor.bandwidth_file.BandwidthFile
+ elif descriptor_type == stem.descriptor.hidden_service.HiddenServiceDescriptorV3.TYPE_ANNOTATION_NAME and major_version == 1:
+ desc_type = stem.descriptor.hidden_service.HiddenServiceDescriptorV3
+ for desc in stem.descriptor.hidden_service._parse_file(descriptor_file, desc_type, validate = validate, **kwargs):
+ yield desc
+ elif descriptor_type == stem.descriptor.bandwidth_file.BandwidthFile.TYPE_ANNOTATION_NAME and major_version == 1:
for desc in stem.descriptor.bandwidth_file._parse_file(descriptor_file, validate = validate, **kwargs):
yield desc
else:
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index ab5699f6..1ed100ef 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -17,7 +17,8 @@ These are only available through the Controller's
::
BaseHiddenServiceDescriptor - Common parent for hidden service descriptors
- +- HiddenServiceDescriptorV2 - Version 2 hidden service descriptor
+ |- HiddenServiceDescriptorV2 - Version 2 hidden service descriptor
+ +- HiddenServiceDescriptorV3 - Version 3 hidden service descriptor
.. versionadded:: 1.4.0
"""
@@ -103,11 +104,12 @@ class DecryptionFailure(Exception):
"""
-def _parse_file(descriptor_file, validate = False, **kwargs):
+def _parse_file(descriptor_file, desc_type = None, validate = False, **kwargs):
"""
Iterates over the hidden service descriptors in a file.
:param file descriptor_file: file with descriptor content
+ :param class desc_type: BaseHiddenServiceDescriptor subclass
:param bool validate: checks the validity of the descriptor's content if
**True**, skips these checks otherwise
:param dict kwargs: additional arguments for the descriptor constructor
@@ -120,18 +122,24 @@ def _parse_file(descriptor_file, validate = False, **kwargs):
* **IOError** if the file can't be read
"""
+ if desc_type is None:
+ desc_type = HiddenServiceDescriptorV2
+
+ # Hidden service v3 ends with a signature line, whereas v2 has a pgp style
+ # block following it.
+
while True:
descriptor_content = _read_until_keywords('signature', descriptor_file)
- # we've reached the 'signature', now include the pgp style block
- block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0]
- descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True)
+ if desc_type == HiddenServiceDescriptorV2:
+ block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0]
+ descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True)
if descriptor_content:
if descriptor_content[0].startswith(b'@type'):
descriptor_content = descriptor_content[1:]
- yield HiddenServiceDescriptorV2(bytes.join(b'', descriptor_content), validate, **kwargs)
+ yield desc_type(bytes.join(b'', descriptor_content), validate, **kwargs)
else:
break # done parsing file
@@ -194,7 +202,7 @@ class BaseHiddenServiceDescriptor(Descriptor):
class HiddenServiceDescriptorV2(BaseHiddenServiceDescriptor):
"""
- Hidden service descriptor.
+ Version 2 hidden service descriptor.
:var str descriptor_id: **\\*** identifier for this descriptor, this is a base32 hash of several fields
:var int version: **\\*** hidden service descriptor version
@@ -452,6 +460,16 @@ class HiddenServiceDescriptorV2(BaseHiddenServiceDescriptor):
return introduction_points
+class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
+ """
+ Version 3 hidden service descriptor.
+ """
+
+ # TODO: requested this @type on https://trac.torproject.org/projects/tor/ticket/31481
+
+ TYPE_ANNOTATION_NAME = 'hidden-service-descriptor-3'
+
+
# TODO: drop this alias in stem 2.x
HiddenServiceDescriptor = HiddenServiceDescriptorV2
diff --git a/test/settings.cfg b/test/settings.cfg
index 2e6b29dd..1b971bb4 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -259,6 +259,7 @@ test.unit_tests
|test.unit.descriptor.networkstatus.document_v3.TestNetworkStatusDocument
|test.unit.descriptor.networkstatus.bridge_document.TestBridgeNetworkStatusDocument
|test.unit.descriptor.hidden_service_v2.TestHiddenServiceDescriptorV2
+|test.unit.descriptor.hidden_service_v3.TestHiddenServiceDescriptorV3
|test.unit.descriptor.certificate.TestEd25519Certificate
|test.unit.descriptor.bandwidth_file.TestBandwidthFile
|test.unit.exit_policy.rule.TestExitPolicyRule
diff --git a/test/unit/descriptor/data/hidden_service_v3 b/test/unit/descriptor/data/hidden_service_v3
new file mode 100644
index 00000000..c67ae292
--- /dev/null
+++ b/test/unit/descriptor/data/hidden_service_v3
@@ -0,0 +1,223 @@
+hs-descriptor 3
+descriptor-lifetime 180
+descriptor-signing-key-cert
+-----BEGIN ED25519 CERT-----
+AQgABqKwAQVql1QZETyEwJjg+Cv6f2w/cp+c3juj01NPBaJqihboAQAgBACx+FKK
+oDrFE1+ztSxzN8sApKOb5UuDtoe/E03DxZU5+r/K5AV6G0hYn21V7Xbu2pZHvIkT
+2oVY4hypWNJE58eFBRFRzBA0J2h0GyFs1pIuRh5QDJuxB5j92V0aRCNZFgM=
+-----END ED25519 CERT-----
+revision-counter 15
+superencrypted
+-----BEGIN MESSAGE-----
+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
+usMPytqLsZg2WXj4ikmGug==
+-----END MESSAGE-----
+signature wdc7ffr+dPZJ/mIQ1l4WYqNABcmsm6SHW/NL3M3wG7bjjqOJWoPR5TimUXxH52n5Zk0Gc7hl/hz3YYmAx5MvAg
diff --git a/test/unit/descriptor/hidden_service_v2.py b/test/unit/descriptor/hidden_service_v2.py
index 8442376b..ac64fa19 100644
--- a/test/unit/descriptor/hidden_service_v2.py
+++ b/test/unit/descriptor/hidden_service_v2.py
@@ -1,5 +1,5 @@
"""
-Unit tests for stem.descriptor.hidden_service.
+Unit tests for stem.descriptor.hidden_service for version 2.
"""
import datetime
@@ -250,27 +250,27 @@ class TestHiddenServiceDescriptorV2(unittest.TestCase):
Parse duckduckgo's descriptor.
"""
- descriptor_file = open(get_resource('hidden_service_duckduckgo'), 'rb')
- desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
- self._assert_matches_duckduckgo(desc)
+ with open(get_resource('hidden_service_duckduckgo'), 'rb') as descriptor_file:
+ desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
+ self._assert_matches_duckduckgo(desc)
def test_for_duckduckgo_without_validation(self):
"""
Parse duckduckgo's descriptor
"""
- descriptor_file = open(get_resource('hidden_service_duckduckgo'), 'rb')
- desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = False))
- self._assert_matches_duckduckgo(desc)
+ with open(get_resource('hidden_service_duckduckgo'), 'rb') as descriptor_file:
+ desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = False))
+ self._assert_matches_duckduckgo(desc)
def test_for_facebook(self):
"""
Parse facebook's descriptor.
"""
- descriptor_file = open(get_resource('hidden_service_facebook'), 'rb')
+ with open(get_resource('hidden_service_facebook'), 'rb') as descriptor_file:
+ desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
- desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
self.assertEqual('utjk4arxqg6s6zzo7n6cjnq6ot34udhr', desc.descriptor_id)
self.assertEqual(2, desc.version)
self.assertEqual('6355jaerje3bqozopwq2qmpf4iviizdn', desc.secret_id_part)
@@ -287,9 +287,9 @@ class TestHiddenServiceDescriptorV2(unittest.TestCase):
Parse a descriptor with introduction-points encrypted with basic auth.
"""
- descriptor_file = open(get_resource('hidden_service_basic_auth'), 'rb')
+ with open(get_resource('hidden_service_basic_auth'), 'rb') as descriptor_file:
+ desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
- desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
self.assertEqual('yfmvdrkdbyquyqk5vygyeylgj2qmrvrd', desc.descriptor_id)
self.assertEqual(2, desc.version)
self.assertEqual('fluw7z3s5cghuuirq3imh5jjj5ljips6', desc.secret_id_part)
@@ -334,9 +334,9 @@ class TestHiddenServiceDescriptorV2(unittest.TestCase):
Parse a descriptor with introduction-points encrypted with stealth auth.
"""
- descriptor_file = open(get_resource('hidden_service_stealth_auth'), 'rb')
+ with open(get_resource('hidden_service_stealth_auth'), 'rb') as descriptor_file:
+ desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
- desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor 1.0', validate = True))
self.assertEqual('ubf3xeibzlfil6s4larq6y5peup2z3oj', desc.descriptor_id)
self.assertEqual(2, desc.version)
self.assertEqual('jczvydhzetbpdiylj3d5nsnjvaigs7xm', desc.secret_id_part)
diff --git a/test/unit/descriptor/hidden_service_v3.py b/test/unit/descriptor/hidden_service_v3.py
new file mode 100644
index 00000000..757c1944
--- /dev/null
+++ b/test/unit/descriptor/hidden_service_v3.py
@@ -0,0 +1,19 @@
+"""
+Unit tests for stem.descriptor.hidden_service for version 3.
+"""
+
+import unittest
+
+import stem.descriptor
+
+from test.unit.descriptor import get_resource
+
+
+class TestHiddenServiceDescriptorV3(unittest.TestCase):
+ def test_stub(self):
+ # TODO: replace with actual field assertions as the class gets implemented
+
+ with open(get_resource('hidden_service_v3'), 'rb') as descriptor_file:
+ desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor-3 1.0', validate = True))
+
+ self.assertTrue('hs-descriptor 3' in str(desc))
1
0
commit 6176a1759fadd4625ff8e1c9155053916202c1ee
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Aug 21 15:01:20 2019 -0700
Base hidden service descriptor class
Common parent for v2 and v3 hidden service descriptors. I'm not yet sure what
(if anything) they'll share, but this class is proper regardless for object
orientation (see router status entries for what this will exemplify).
Existing usage of the HiddenServiceDescriptor class name is unfortunate since
that is what our common base class should be called. Oh well - yet another
thing to correct when we break backward compatibility in Stem 2.x.
---
stem/control.py | 2 +-
stem/descriptor/__init__.py | 6 ++--
stem/descriptor/hidden_service.py | 32 ++++++++++++++++------
stem/response/events.py | 2 +-
test/settings.cfg | 2 +-
.../{hidden_service.py => hidden_service_v2.py} | 28 +++++++++----------
6 files changed, 44 insertions(+), 28 deletions(-)
diff --git a/stem/control.py b/stem/control.py
index 81cb1682..86f4e787 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -2136,7 +2136,7 @@ class Controller(BaseController):
:param list servers: requrest the descriptor from these specific servers
:param float timeout: seconds to wait when **await_result** is **True**
- :returns: :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptor`
+ :returns: :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptorV2`
for the given service if **await_result** is **True**, or **None** otherwise
:raises:
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index fd96c042..9415454c 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -329,7 +329,7 @@ def parse_file(descriptor_file, descriptor_type = None, validate = False, docume
torperf 1.0 **unsupported**
bridge-pool-assignment 1.0 **unsupported**
tordnsel 1.0 :class:`~stem.descriptor.tordnsel.TorDNSEL`
- hidden-service-descriptor 1.0 :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptor`
+ hidden-service-descriptor 1.0 :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptorV2`
========================================= =====
If you're using **python 3** then beware that the open() function defaults to
@@ -536,8 +536,8 @@ def _parse_metrics_file(descriptor_type, major_version, minor_version, descripto
for desc in stem.descriptor.tordnsel._parse_file(descriptor_file, validate = validate, **kwargs):
yield desc
- elif descriptor_type == stem.descriptor.hidden_service.HiddenServiceDescriptor.TYPE_ANNOTATION_NAME and major_version == 1:
- document_type = stem.descriptor.hidden_service.HiddenServiceDescriptor
+ elif descriptor_type == stem.descriptor.hidden_service.HiddenServiceDescriptorV2.TYPE_ANNOTATION_NAME and major_version == 1:
+ document_type = stem.descriptor.hidden_service.HiddenServiceDescriptorV2
for desc in stem.descriptor.hidden_service._parse_file(descriptor_file, validate = validate, **kwargs):
yield desc
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index 665d8664..ab5699f6 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -16,7 +16,8 @@ These are only available through the Controller's
::
- HiddenServiceDescriptor - Tor hidden service descriptor.
+ BaseHiddenServiceDescriptor - Common parent for hidden service descriptors
+ +- HiddenServiceDescriptorV2 - Version 2 hidden service descriptor
.. versionadded:: 1.4.0
"""
@@ -111,7 +112,7 @@ def _parse_file(descriptor_file, validate = False, **kwargs):
**True**, skips these checks otherwise
:param dict kwargs: additional arguments for the descriptor constructor
- :returns: iterator for :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptor`
+ :returns: iterator for :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptorV2`
instances in the file
:raises:
@@ -130,7 +131,7 @@ def _parse_file(descriptor_file, validate = False, **kwargs):
if descriptor_content[0].startswith(b'@type'):
descriptor_content = descriptor_content[1:]
- yield HiddenServiceDescriptor(bytes.join(b'', descriptor_content), validate, **kwargs)
+ yield HiddenServiceDescriptorV2(bytes.join(b'', descriptor_content), validate, **kwargs)
else:
break # done parsing file
@@ -181,7 +182,17 @@ _parse_publication_time_line = _parse_timestamp_line('publication-time', 'publis
_parse_signature_line = _parse_key_block('signature', 'signature', 'SIGNATURE')
-class HiddenServiceDescriptor(Descriptor):
+class BaseHiddenServiceDescriptor(Descriptor):
+ """
+ Hidden service descriptor.
+
+ .. versionadded:: 1.8.0
+ """
+
+ # TODO: rename this class to HiddenServiceDescriptor in stem 2.x
+
+
+class HiddenServiceDescriptorV2(BaseHiddenServiceDescriptor):
"""
Hidden service descriptor.
@@ -261,7 +272,7 @@ class HiddenServiceDescriptor(Descriptor):
return cls(cls.content(attr, exclude, sign), validate = validate, skip_crypto_validation = not sign)
def __init__(self, raw_contents, validate = False, skip_crypto_validation = False):
- super(HiddenServiceDescriptor, self).__init__(raw_contents, lazy_load = not validate)
+ super(HiddenServiceDescriptorV2, self).__init__(raw_contents, lazy_load = not validate)
entries = _descriptor_components(raw_contents, validate, non_ascii_fields = ('introduction-points'))
if validate:
@@ -317,9 +328,9 @@ class HiddenServiceDescriptor(Descriptor):
authentication_type = int(binascii.hexlify(content[0:1]), 16)
if authentication_type == BASIC_AUTH:
- content = HiddenServiceDescriptor._decrypt_basic_auth(content, authentication_cookie)
+ content = HiddenServiceDescriptorV2._decrypt_basic_auth(content, authentication_cookie)
elif authentication_type == STEALTH_AUTH:
- content = HiddenServiceDescriptor._decrypt_stealth_auth(content, authentication_cookie)
+ content = HiddenServiceDescriptorV2._decrypt_stealth_auth(content, authentication_cookie)
else:
raise DecryptionFailure("Unrecognized authentication type '%s', currently we only support basic auth (%s) and stealth auth (%s)" % (authentication_type, BASIC_AUTH, STEALTH_AUTH))
@@ -328,7 +339,7 @@ class HiddenServiceDescriptor(Descriptor):
elif not content.startswith(b'introduction-point '):
raise DecryptionFailure('introduction-points content is encrypted, you need to provide its authentication_cookie')
- return HiddenServiceDescriptor._parse_introduction_points(content)
+ return HiddenServiceDescriptorV2._parse_introduction_points(content)
@staticmethod
def _decrypt_basic_auth(content, authentication_cookie):
@@ -439,3 +450,8 @@ class HiddenServiceDescriptor(Descriptor):
introduction_points.append(IntroductionPoints(**attr))
return introduction_points
+
+
+# TODO: drop this alias in stem 2.x
+
+HiddenServiceDescriptor = HiddenServiceDescriptorV2
diff --git a/stem/response/events.py b/stem/response/events.py
index 7a5f749b..27a3e405 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -701,7 +701,7 @@ class HSDescContentEvent(Event):
:var str directory: hidden service directory servicing the request
:var str directory_fingerprint: hidden service directory's finterprint
:var str directory_nickname: hidden service directory's nickname if it was provided
- :var stem.descriptor.hidden_service.HiddenServiceDescriptor descriptor: descriptor that was retrieved
+ :var stem.descriptor.hidden_service.HiddenServiceDescriptorV2 descriptor: descriptor that was retrieved
"""
_VERSION_ADDED = stem.version.Requirement.EVENT_HS_DESC_CONTENT
diff --git a/test/settings.cfg b/test/settings.cfg
index 3308c104..2e6b29dd 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -258,7 +258,7 @@ test.unit_tests
|test.unit.descriptor.networkstatus.document_v2.TestNetworkStatusDocument
|test.unit.descriptor.networkstatus.document_v3.TestNetworkStatusDocument
|test.unit.descriptor.networkstatus.bridge_document.TestBridgeNetworkStatusDocument
-|test.unit.descriptor.hidden_service.TestHiddenServiceDescriptor
+|test.unit.descriptor.hidden_service_v2.TestHiddenServiceDescriptorV2
|test.unit.descriptor.certificate.TestEd25519Certificate
|test.unit.descriptor.bandwidth_file.TestBandwidthFile
|test.unit.exit_policy.rule.TestExitPolicyRule
diff --git a/test/unit/descriptor/hidden_service.py b/test/unit/descriptor/hidden_service_v2.py
similarity index 95%
rename from test/unit/descriptor/hidden_service.py
rename to test/unit/descriptor/hidden_service_v2.py
index c49741d3..8442376b 100644
--- a/test/unit/descriptor/hidden_service.py
+++ b/test/unit/descriptor/hidden_service_v2.py
@@ -13,7 +13,7 @@ import test.require
from stem.descriptor.hidden_service import (
REQUIRED_FIELDS,
DecryptionFailure,
- HiddenServiceDescriptor,
+ HiddenServiceDescriptorV2,
)
from test.unit.descriptor import (
@@ -236,14 +236,14 @@ lj/7xMZWDrfyw5H86L0QiaZnkmD+nig1+S+Rn39mmuEgl2iwZO/ihlncUJQTEULb
-----END MESSAGE-----\
"""
-expect_invalid_attr = functools.partial(base_expect_invalid_attr, HiddenServiceDescriptor, 'descriptor_id', 'y3olqqblqw2gbh6phimfuiroechjjafa')
-expect_invalid_attr_for_text = functools.partial(base_expect_invalid_attr_for_text, HiddenServiceDescriptor, 'descriptor_id', 'y3olqqblqw2gbh6phimfuiroechjjafa')
+expect_invalid_attr = functools.partial(base_expect_invalid_attr, HiddenServiceDescriptorV2, 'descriptor_id', 'y3olqqblqw2gbh6phimfuiroechjjafa')
+expect_invalid_attr_for_text = functools.partial(base_expect_invalid_attr_for_text, HiddenServiceDescriptorV2, 'descriptor_id', 'y3olqqblqw2gbh6phimfuiroechjjafa')
-class TestHiddenServiceDescriptor(unittest.TestCase):
+class TestHiddenServiceDescriptorV2(unittest.TestCase):
def test_from_str(self):
- sig = HiddenServiceDescriptor.create()
- self.assertEqual(sig, HiddenServiceDescriptor.from_str(str(sig)))
+ sig = HiddenServiceDescriptorV2.create()
+ self.assertEqual(sig, HiddenServiceDescriptorV2.from_str(str(sig)))
def test_for_duckduckgo_with_validation(self):
"""
@@ -279,7 +279,7 @@ class TestHiddenServiceDescriptor(unittest.TestCase):
@test.require.cryptography
def test_descriptor_signing(self):
- self.assertRaisesWith(NotImplementedError, 'Signing of HiddenServiceDescriptor not implemented', HiddenServiceDescriptor.create, sign = True)
+ self.assertRaisesWith(NotImplementedError, 'Signing of HiddenServiceDescriptorV2 not implemented', HiddenServiceDescriptorV2.create, sign = True)
@test.require.cryptography
def test_with_basic_auth(self):
@@ -418,7 +418,7 @@ class TestHiddenServiceDescriptor(unittest.TestCase):
Basic sanity check that we can parse a hidden service descriptor with minimal attributes.
"""
- desc = HiddenServiceDescriptor.create()
+ desc = HiddenServiceDescriptorV2.create()
self.assertEqual('y3olqqblqw2gbh6phimfuiroechjjafa', desc.descriptor_id)
self.assertEqual(2, desc.version)
@@ -435,7 +435,7 @@ class TestHiddenServiceDescriptor(unittest.TestCase):
Includes unrecognized content in the descriptor.
"""
- desc = HiddenServiceDescriptor.create({'pepperjack': 'is oh so tasty!'})
+ desc = HiddenServiceDescriptorV2.create({'pepperjack': 'is oh so tasty!'})
self.assertEqual(['pepperjack is oh so tasty!'], desc.get_unrecognized_lines())
def test_proceeding_line(self):
@@ -443,14 +443,14 @@ class TestHiddenServiceDescriptor(unittest.TestCase):
Includes a line prior to the 'rendezvous-service-descriptor' entry.
"""
- expect_invalid_attr_for_text(self, b'hibernate 1\n' + HiddenServiceDescriptor.content())
+ expect_invalid_attr_for_text(self, b'hibernate 1\n' + HiddenServiceDescriptorV2.content())
def test_trailing_line(self):
"""
Includes a line after the 'router-signature' entry.
"""
- expect_invalid_attr_for_text(self, HiddenServiceDescriptor.content() + b'\nhibernate 1')
+ expect_invalid_attr_for_text(self, HiddenServiceDescriptorV2.content() + b'\nhibernate 1')
def test_required_fields(self):
"""
@@ -469,7 +469,7 @@ class TestHiddenServiceDescriptor(unittest.TestCase):
}
for line in REQUIRED_FIELDS:
- desc_text = HiddenServiceDescriptor.content(exclude = (line,))
+ desc_text = HiddenServiceDescriptorV2.content(exclude = (line,))
expected = [] if line == 'protocol-versions' else None
expect_invalid_attr_for_text(self, desc_text, line_to_attr[line], expected)
@@ -514,14 +514,14 @@ class TestHiddenServiceDescriptor(unittest.TestCase):
are valid according to the spec.
"""
- missing_field_desc = HiddenServiceDescriptor.create(exclude = ('introduction-points',))
+ missing_field_desc = HiddenServiceDescriptorV2.create(exclude = ('introduction-points',))
self.assertEqual(None, missing_field_desc.introduction_points_encoded)
self.assertEqual([], missing_field_desc.introduction_points_auth)
self.assertEqual(None, missing_field_desc.introduction_points_content)
self.assertEqual([], missing_field_desc.introduction_points())
- empty_field_desc = HiddenServiceDescriptor.create({'introduction-points': MESSAGE_BLOCK % ''})
+ empty_field_desc = HiddenServiceDescriptorV2.create({'introduction-points': MESSAGE_BLOCK % ''})
self.assertEqual((MESSAGE_BLOCK % '').strip(), empty_field_desc.introduction_points_encoded)
self.assertEqual([], empty_field_desc.introduction_points_auth)
1
0

[stem/master] Rename stem.descriptor.hidden_service_descriptor module
by atagar@torproject.org 25 Aug '19
by atagar@torproject.org 25 Aug '19
25 Aug '19
commit f307434824f74550b836927f3c02a4bac53c6c7c
Author: Damian Johnson <atagar(a)torproject.org>
Date: Tue Aug 20 15:23:58 2019 -0700
Rename stem.descriptor.hidden_service_descriptor module
Dropping the redundant '_descriptor' suffix from this module name. The old name
still works as an alias.
---
docs/api.rst | 2 +-
docs/api/descriptor/hidden_service.rst | 5 +
docs/api/descriptor/hidden_service_descriptor.rst | 5 -
docs/change_log.rst | 7 +-
docs/contents.rst | 2 +-
docs/tutorials/mirror_mirror_on_the_wall.rst | 2 +-
docs/tutorials/over_the_river.rst | 2 +-
stem/control.py | 2 +-
stem/descriptor/__init__.py | 12 +-
stem/descriptor/hidden_service.py | 441 ++++++++++++++++++++
stem/descriptor/hidden_service_descriptor.py | 446 +--------------------
stem/response/events.py | 4 +-
test/settings.cfg | 10 +-
...den_service_descriptor.py => hidden_service.py} | 4 +-
14 files changed, 474 insertions(+), 470 deletions(-)
diff --git a/docs/api.rst b/docs/api.rst
index a8ba7e24..cbbf0dd0 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -35,7 +35,7 @@ remotely like Tor does.
* `stem.descriptor.microdescriptor <api/descriptor/microdescriptor.html>`_ - Minimalistic counterpart for server descriptors.
* `stem.descriptor.networkstatus <api/descriptor/networkstatus.html>`_ - Network status documents which make up the Tor consensus.
* `stem.descriptor.router_status_entry <api/descriptor/router_status_entry.html>`_ - Relay entries within a network status document.
- * `stem.descriptor.hidden_service_descriptor <api/descriptor/hidden_service_descriptor.html>`_ - Descriptors generated for hidden services.
+ * `stem.descriptor.hidden_service <api/descriptor/hidden_service.html>`_ - Descriptors generated for hidden services.
* `stem.descriptor.bandwidth_file <api/descriptor/bandwidth_file.html>`_ - Bandwidth authority metrics.
* `stem.descriptor.tordnsel <api/descriptor/tordnsel.html>`_ - `TorDNSEL <https://www.torproject.org/projects/tordnsel.html.en>`_ exit lists.
* `stem.descriptor.certificate <api/descriptor/certificate.html>`_ - `Ed25519 certificates <https://gitweb.torproject.org/torspec.git/tree/cert-spec.txt>`_.
diff --git a/docs/api/descriptor/hidden_service.rst b/docs/api/descriptor/hidden_service.rst
new file mode 100644
index 00000000..21b9bd7b
--- /dev/null
+++ b/docs/api/descriptor/hidden_service.rst
@@ -0,0 +1,5 @@
+Hidden Service Descriptor
+=========================
+
+.. automodule:: stem.descriptor.hidden_service
+
diff --git a/docs/api/descriptor/hidden_service_descriptor.rst b/docs/api/descriptor/hidden_service_descriptor.rst
deleted file mode 100644
index 145203e6..00000000
--- a/docs/api/descriptor/hidden_service_descriptor.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Hidden Service Descriptor
-=========================
-
-.. automodule:: stem.descriptor.hidden_service_descriptor
-
diff --git a/docs/change_log.rst b/docs/change_log.rst
index c244d7d2..d9c6bfa3 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -52,16 +52,16 @@ The following are only available within Stem's `git repository
* Controller events could fail to be delivered in a timely fashion (:trac:`27173`)
* Adjusted :func:`~stem.control.Controller.get_microdescriptors` fallback to also use '.new' cache files (:trac:`28508`)
* ExitPolicies could raise TypeError when read concurrently (:trac:`29899`)
- * **STALE_DESC** :data:`~stem.Flag` (:spec:`d14164d8`)
+ * **STALE_DESC** :data:`~stem.Flag` (:spec:`d14164d`)
* **DORMANT** and **ACTIVE** :data:`~stem.Signal` (:spec:`4421149`)
* **QUERY_RATE_LIMITED** :data:`~stem.HSDescReason` (:spec:`bd80679`)
- * **EXTOR** and **HTTPTUNNEL** :data:`~stem.Listener`
+ * **EXTOR** and **HTTPTUNNEL** :data:`~stem.control.Listener`
* **Descriptors**
* Added the `stem.descriptor.collector <api/descriptor/collector.html>`_ module (:trac:`17979`)
* `Bandwidth file support <api/descriptor/bandwidth_file.html>`_ (:trac:`29056`)
- * `stem.descriptor.remote <api/descriptor/remote.html>`_ now raise :class:`stem.DownloadFailed`
+ * `stem.descriptor.remote <api/descriptor/remote.html>`_ methods now raise :class:`stem.DownloadFailed`
* Check Ed25519 validity though the cryptography module rather than PyNaCl (:trac:`22022`)
* Download compressed descriptors by default (:trac:`29186`)
* Added :class:`~stem.descriptor.Compression` class
@@ -78,6 +78,7 @@ The following are only available within Stem's `git repository
* Replaced the **digest** attribute of :class:`~stem.descriptor.microdescriptor.Microdescriptor` with a method by the same name (:trac:`28398`)
* Default the **version_flavor** attribute of :class:`~stem.descriptor.networkstatus.NetworkStatusDocumentV3` to 'ns' (:spec:`d97f8d9`)
* DescriptorDownloader crashed if **use_mirrors** is set (:trac:`28393`)
+ * Renamed stem.descriptor.hidden_service_descriptor to stem.descriptor.hidden_service
* Don't download from Serge, a bridge authority that frequently timeout
* Updated dizum authority's address (:trac:`31406`)
diff --git a/docs/contents.rst b/docs/contents.rst
index 267979e0..98e80a5f 100644
--- a/docs/contents.rst
+++ b/docs/contents.rst
@@ -50,7 +50,7 @@ Contents
api/descriptor/microdescriptor
api/descriptor/networkstatus
api/descriptor/router_status_entry
- api/descriptor/hidden_service_descriptor
+ api/descriptor/hidden_service
api/descriptor/tordnsel
api/descriptor/export
diff --git a/docs/tutorials/mirror_mirror_on_the_wall.rst b/docs/tutorials/mirror_mirror_on_the_wall.rst
index 04cc86de..699625e4 100644
--- a/docs/tutorials/mirror_mirror_on_the_wall.rst
+++ b/docs/tutorials/mirror_mirror_on_the_wall.rst
@@ -34,7 +34,7 @@ Descriptor Type
`Microdescriptor <../api/descriptor/microdescriptor.html>`_ Minimalistic document that just includes the information necessary for Tor clients to work.
`Network Status Document <../api/descriptor/networkstatus.html>`_ Though Tor relays are decentralized, the directories that track the overall network are not. These central points are called **directory authorities**, and every hour they publish a document called a **consensus** (aka, network status document). The consensus in turn is made up of **router status entries**.
`Router Status Entry <../api/descriptor/router_status_entry.html>`_ Relay information provided by the directory authorities including flags, heuristics used for relay selection, etc.
-`Hidden Service Descriptor <../api/descriptor/hidden_service_descriptor.html>`_ Information pertaining to a `Hidden Service <https://www.torproject.org/docs/hidden-services.html.en>`_. These can only be `queried through the tor process <over_the_river.html#hidden-service-descriptors>`_.
+`Hidden Service Descriptor <../api/descriptor/hidden_service.html>`_ Information pertaining to a `Hidden Service <https://www.torproject.org/docs/hidden-services.html.en>`_. These can only be `queried through the tor process <over_the_river.html#hidden-service-descriptors>`_.
================================================================================ ===========
.. _where-do-descriptors-come-from:
diff --git a/docs/tutorials/over_the_river.rst b/docs/tutorials/over_the_river.rst
index dac78827..ff8c7feb 100644
--- a/docs/tutorials/over_the_river.rst
+++ b/docs/tutorials/over_the_river.rst
@@ -171,7 +171,7 @@ its :func:`~stem.control.Controller.get_hidden_service_descriptor` method...
A hidden service's introduction points are a base64 encoded field that's
possibly encrypted. These can be decoded (and decrypted if necessary) with the
descriptor's
-:func:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor.introduction_points`
+:func:`~stem.descriptor.hidden_service.HiddenServiceDescriptor.introduction_points`
method.
.. literalinclude:: /_static/example/introduction_points.py
diff --git a/stem/control.py b/stem/control.py
index d8423ffa..81cb1682 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -2136,7 +2136,7 @@ class Controller(BaseController):
:param list servers: requrest the descriptor from these specific servers
:param float timeout: seconds to wait when **await_result** is **True**
- :returns: :class:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor`
+ :returns: :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptor`
for the given service if **await_result** is **True**, or **None** otherwise
:raises:
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index c099ca86..fd96c042 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -118,7 +118,7 @@ __all__ = [
'collector',
'export',
'extrainfo_descriptor',
- 'hidden_service_descriptor',
+ 'hidden_service',
'microdescriptor',
'networkstatus',
'reader',
@@ -329,7 +329,7 @@ def parse_file(descriptor_file, descriptor_type = None, validate = False, docume
torperf 1.0 **unsupported**
bridge-pool-assignment 1.0 **unsupported**
tordnsel 1.0 :class:`~stem.descriptor.tordnsel.TorDNSEL`
- hidden-service-descriptor 1.0 :class:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor`
+ hidden-service-descriptor 1.0 :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptor`
========================================= =====
If you're using **python 3** then beware that the open() function defaults to
@@ -536,10 +536,10 @@ def _parse_metrics_file(descriptor_type, major_version, minor_version, descripto
for desc in stem.descriptor.tordnsel._parse_file(descriptor_file, validate = validate, **kwargs):
yield desc
- elif descriptor_type == stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor.TYPE_ANNOTATION_NAME and major_version == 1:
- document_type = stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor
+ elif descriptor_type == stem.descriptor.hidden_service.HiddenServiceDescriptor.TYPE_ANNOTATION_NAME and major_version == 1:
+ document_type = stem.descriptor.hidden_service.HiddenServiceDescriptor
- for desc in stem.descriptor.hidden_service_descriptor._parse_file(descriptor_file, validate = validate, **kwargs):
+ for desc in stem.descriptor.hidden_service._parse_file(descriptor_file, validate = validate, **kwargs):
yield desc
elif descriptor_type == stem.descriptor.bandwidth_file.BandwidthFile.TYPE_ANNOTATION_NAME and major_version == 1:
document_type = stem.descriptor.bandwidth_file.BandwidthFile
@@ -1521,7 +1521,7 @@ def _descriptor_components(raw_contents, validate, extra_keywords = (), non_asci
import stem.descriptor.bandwidth_file
import stem.descriptor.extrainfo_descriptor
-import stem.descriptor.hidden_service_descriptor
+import stem.descriptor.hidden_service
import stem.descriptor.microdescriptor
import stem.descriptor.networkstatus
import stem.descriptor.server_descriptor
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
new file mode 100644
index 00000000..665d8664
--- /dev/null
+++ b/stem/descriptor/hidden_service.py
@@ -0,0 +1,441 @@
+# Copyright 2015-2019, Damian Johnson and The Tor Project
+# See LICENSE for licensing information
+
+"""
+Parsing for Tor hidden service descriptors as described in Tor's `rend-spec
+<https://gitweb.torproject.org/torspec.git/tree/rend-spec.txt>`_.
+
+Unlike other descriptor types these describe a hidden service rather than a
+relay. They're created by the service, and can only be fetched via relays with
+the HSDir flag.
+
+These are only available through the Controller's
+:func:`~stem.control.get_hidden_service_descriptor` method.
+
+**Module Overview:**
+
+::
+
+ HiddenServiceDescriptor - Tor hidden service descriptor.
+
+.. versionadded:: 1.4.0
+"""
+
+import base64
+import binascii
+import collections
+import hashlib
+import io
+
+import stem.prereq
+import stem.util.connection
+import stem.util.str_tools
+
+from stem.descriptor import (
+ PGP_BLOCK_END,
+ Descriptor,
+ _descriptor_content,
+ _descriptor_components,
+ _read_until_keywords,
+ _bytes_for_block,
+ _value,
+ _parse_simple_line,
+ _parse_timestamp_line,
+ _parse_key_block,
+ _random_date,
+ _random_crypto_blob,
+)
+
+if stem.prereq._is_lru_cache_available():
+ from functools import lru_cache
+else:
+ from stem.util.lru_cache import lru_cache
+
+REQUIRED_FIELDS = (
+ 'rendezvous-service-descriptor',
+ 'version',
+ 'permanent-key',
+ 'secret-id-part',
+ 'publication-time',
+ 'protocol-versions',
+ 'signature',
+)
+
+INTRODUCTION_POINTS_ATTR = {
+ 'identifier': None,
+ 'address': None,
+ 'port': None,
+ 'onion_key': None,
+ 'service_key': None,
+ 'intro_authentication': [],
+}
+
+# introduction-point fields that can only appear once
+
+SINGLE_INTRODUCTION_POINT_FIELDS = [
+ 'introduction-point',
+ 'ip-address',
+ 'onion-port',
+ 'onion-key',
+ 'service-key',
+]
+
+BASIC_AUTH = 1
+STEALTH_AUTH = 2
+
+
+class IntroductionPoints(collections.namedtuple('IntroductionPoints', INTRODUCTION_POINTS_ATTR.keys())):
+ """
+ :var str identifier: hash of this introduction point's identity key
+ :var str address: address of this introduction point
+ :var int port: port where this introduction point is listening
+ :var str onion_key: public key for communicating with this introduction point
+ :var str service_key: public key for communicating with this hidden service
+ :var list intro_authentication: tuples of the form (auth_type, auth_data) for
+ establishing a connection
+ """
+
+
+class DecryptionFailure(Exception):
+ """
+ Failure to decrypt the hidden service descriptor's introduction-points.
+ """
+
+
+def _parse_file(descriptor_file, validate = False, **kwargs):
+ """
+ Iterates over the hidden service descriptors in a file.
+
+ :param file descriptor_file: file with descriptor content
+ :param bool validate: checks the validity of the descriptor's content if
+ **True**, skips these checks otherwise
+ :param dict kwargs: additional arguments for the descriptor constructor
+
+ :returns: iterator for :class:`~stem.descriptor.hidden_service.HiddenServiceDescriptor`
+ instances in the file
+
+ :raises:
+ * **ValueError** if the contents is malformed and validate is **True**
+ * **IOError** if the file can't be read
+ """
+
+ while True:
+ descriptor_content = _read_until_keywords('signature', descriptor_file)
+
+ # we've reached the 'signature', now include the pgp style block
+ block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0]
+ descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True)
+
+ if descriptor_content:
+ if descriptor_content[0].startswith(b'@type'):
+ descriptor_content = descriptor_content[1:]
+
+ yield HiddenServiceDescriptor(bytes.join(b'', descriptor_content), validate, **kwargs)
+ else:
+ break # done parsing file
+
+
+def _parse_version_line(descriptor, entries):
+ value = _value('version', entries)
+
+ if value.isdigit():
+ descriptor.version = int(value)
+ else:
+ raise ValueError('version line must have a positive integer value: %s' % value)
+
+
+def _parse_protocol_versions_line(descriptor, entries):
+ value = _value('protocol-versions', entries)
+
+ try:
+ versions = [int(entry) for entry in value.split(',')]
+ except ValueError:
+ raise ValueError('protocol-versions line has non-numeric versoins: protocol-versions %s' % value)
+
+ for v in versions:
+ if v <= 0:
+ raise ValueError('protocol-versions must be positive integers: %s' % value)
+
+ descriptor.protocol_versions = versions
+
+
+def _parse_introduction_points_line(descriptor, entries):
+ _, block_type, block_contents = entries['introduction-points'][0]
+
+ if not block_contents or block_type != 'MESSAGE':
+ raise ValueError("'introduction-points' should be followed by a MESSAGE block, but was a %s" % block_type)
+
+ descriptor.introduction_points_encoded = block_contents
+ descriptor.introduction_points_auth = [] # field was never implemented in tor (#15190)
+
+ try:
+ descriptor.introduction_points_content = _bytes_for_block(block_contents)
+ except TypeError:
+ raise ValueError("'introduction-points' isn't base64 encoded content:\n%s" % block_contents)
+
+
+_parse_rendezvous_service_descriptor_line = _parse_simple_line('rendezvous-service-descriptor', 'descriptor_id')
+_parse_permanent_key_line = _parse_key_block('permanent-key', 'permanent_key', 'RSA PUBLIC KEY')
+_parse_secret_id_part_line = _parse_simple_line('secret-id-part', 'secret_id_part')
+_parse_publication_time_line = _parse_timestamp_line('publication-time', 'published')
+_parse_signature_line = _parse_key_block('signature', 'signature', 'SIGNATURE')
+
+
+class HiddenServiceDescriptor(Descriptor):
+ """
+ Hidden service descriptor.
+
+ :var str descriptor_id: **\\*** identifier for this descriptor, this is a base32 hash of several fields
+ :var int version: **\\*** hidden service descriptor version
+ :var str permanent_key: **\\*** long term key of the hidden service
+ :var str secret_id_part: **\\*** hash of the time period, cookie, and replica
+ values so our descriptor_id can be validated
+ :var datetime published: **\\*** time in UTC when this descriptor was made
+ :var list protocol_versions: **\\*** list of **int** versions that are supported when establishing a connection
+ :var str introduction_points_encoded: raw introduction points blob
+ :var list introduction_points_auth: **\\*** tuples of the form
+ (auth_method, auth_data) for our introduction_points_content
+ (**deprecated**, always **[]**)
+ :var bytes introduction_points_content: decoded introduction-points content
+ without authentication data, if using cookie authentication this is
+ encrypted
+ :var str signature: signature of the descriptor content
+
+ **\\*** attribute is either required when we're parsed with validation or has
+ a default value, others are left as **None** if undefined
+
+ .. versionchanged:: 1.6.0
+ Moved from the deprecated `pycrypto
+ <https://www.dlitz.net/software/pycrypto/>`_ module to `cryptography
+ <https://pypi.org/project/cryptography/>`_ for validating signatures.
+
+ .. versionchanged:: 1.6.0
+ Added the **skip_crypto_validation** constructor argument.
+ """
+
+ TYPE_ANNOTATION_NAME = 'hidden-service-descriptor'
+
+ ATTRIBUTES = {
+ 'descriptor_id': (None, _parse_rendezvous_service_descriptor_line),
+ 'version': (None, _parse_version_line),
+ 'permanent_key': (None, _parse_permanent_key_line),
+ 'secret_id_part': (None, _parse_secret_id_part_line),
+ 'published': (None, _parse_publication_time_line),
+ 'protocol_versions': ([], _parse_protocol_versions_line),
+ 'introduction_points_encoded': (None, _parse_introduction_points_line),
+ 'introduction_points_auth': ([], _parse_introduction_points_line),
+ 'introduction_points_content': (None, _parse_introduction_points_line),
+ 'signature': (None, _parse_signature_line),
+ }
+
+ PARSER_FOR_LINE = {
+ 'rendezvous-service-descriptor': _parse_rendezvous_service_descriptor_line,
+ 'version': _parse_version_line,
+ 'permanent-key': _parse_permanent_key_line,
+ 'secret-id-part': _parse_secret_id_part_line,
+ 'publication-time': _parse_publication_time_line,
+ 'protocol-versions': _parse_protocol_versions_line,
+ 'introduction-points': _parse_introduction_points_line,
+ 'signature': _parse_signature_line,
+ }
+
+ @classmethod
+ def content(cls, attr = None, exclude = (), sign = False):
+ if sign:
+ raise NotImplementedError('Signing of %s not implemented' % cls.__name__)
+
+ return _descriptor_content(attr, exclude, (
+ ('rendezvous-service-descriptor', 'y3olqqblqw2gbh6phimfuiroechjjafa'),
+ ('version', '2'),
+ ('permanent-key', _random_crypto_blob('RSA PUBLIC KEY')),
+ ('secret-id-part', 'e24kgecavwsznj7gpbktqsiwgvngsf4e'),
+ ('publication-time', _random_date()),
+ ('protocol-versions', '2,3'),
+ ('introduction-points', '\n-----BEGIN MESSAGE-----\n-----END MESSAGE-----'),
+ ), (
+ ('signature', _random_crypto_blob('SIGNATURE')),
+ ))
+
+ @classmethod
+ def create(cls, attr = None, exclude = (), validate = True, sign = False):
+ return cls(cls.content(attr, exclude, sign), validate = validate, skip_crypto_validation = not sign)
+
+ def __init__(self, raw_contents, validate = False, skip_crypto_validation = False):
+ super(HiddenServiceDescriptor, self).__init__(raw_contents, lazy_load = not validate)
+ entries = _descriptor_components(raw_contents, validate, non_ascii_fields = ('introduction-points'))
+
+ if validate:
+ for keyword in REQUIRED_FIELDS:
+ if keyword not in entries:
+ raise ValueError("Hidden service descriptor must have a '%s' entry" % keyword)
+ elif keyword in entries and len(entries[keyword]) > 1:
+ raise ValueError("The '%s' entry can only appear once in a hidden service descriptor" % keyword)
+
+ if 'rendezvous-service-descriptor' != list(entries.keys())[0]:
+ raise ValueError("Hidden service descriptor must start with a 'rendezvous-service-descriptor' entry")
+ elif 'signature' != list(entries.keys())[-1]:
+ raise ValueError("Hidden service descriptor must end with a 'signature' entry")
+
+ self._parse(entries, validate)
+
+ if not skip_crypto_validation and stem.prereq.is_crypto_available():
+ signed_digest = self._digest_for_signature(self.permanent_key, self.signature)
+ digest_content = self._content_range('rendezvous-service-descriptor ', '\nsignature\n')
+ content_digest = hashlib.sha1(digest_content).hexdigest().upper()
+
+ if signed_digest != content_digest:
+ raise ValueError('Decrypted digest does not match local digest (calculated: %s, local: %s)' % (signed_digest, content_digest))
+ else:
+ self._entries = entries
+
+ @lru_cache()
+ def introduction_points(self, authentication_cookie = None):
+ """
+ Provided this service's introduction points.
+
+ :returns: **list** of :class:`~stem.descriptor.hidden_service.IntroductionPoints`
+
+ :raises:
+ * **ValueError** if the our introduction-points is malformed
+ * **DecryptionFailure** if unable to decrypt this field
+ """
+
+ content = self.introduction_points_content
+
+ if not content:
+ return []
+ elif authentication_cookie:
+ if not stem.prereq.is_crypto_available():
+ raise DecryptionFailure('Decrypting introduction-points requires the cryptography module')
+
+ try:
+ missing_padding = len(authentication_cookie) % 4
+ authentication_cookie = base64.b64decode(stem.util.str_tools._to_bytes(authentication_cookie) + b'=' * missing_padding)
+ except TypeError as exc:
+ raise DecryptionFailure('authentication_cookie must be a base64 encoded string (%s)' % exc)
+
+ authentication_type = int(binascii.hexlify(content[0:1]), 16)
+
+ if authentication_type == BASIC_AUTH:
+ content = HiddenServiceDescriptor._decrypt_basic_auth(content, authentication_cookie)
+ elif authentication_type == STEALTH_AUTH:
+ content = HiddenServiceDescriptor._decrypt_stealth_auth(content, authentication_cookie)
+ else:
+ raise DecryptionFailure("Unrecognized authentication type '%s', currently we only support basic auth (%s) and stealth auth (%s)" % (authentication_type, BASIC_AUTH, STEALTH_AUTH))
+
+ if not content.startswith(b'introduction-point '):
+ raise DecryptionFailure('Unable to decrypt the introduction-points, maybe this is the wrong key?')
+ elif not content.startswith(b'introduction-point '):
+ raise DecryptionFailure('introduction-points content is encrypted, you need to provide its authentication_cookie')
+
+ return HiddenServiceDescriptor._parse_introduction_points(content)
+
+ @staticmethod
+ def _decrypt_basic_auth(content, authentication_cookie):
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+ from cryptography.hazmat.backends import default_backend
+
+ try:
+ client_blocks = int(binascii.hexlify(content[1:2]), 16)
+ except ValueError:
+ raise DecryptionFailure("When using basic auth the content should start with a number of blocks but wasn't a hex digit: %s" % binascii.hexlify(content[1:2]))
+
+ # parse the client id and encrypted session keys
+
+ client_entries_length = client_blocks * 16 * 20
+ client_entries = content[2:2 + client_entries_length]
+ client_keys = [(client_entries[i:i + 4], client_entries[i + 4:i + 20]) for i in range(0, client_entries_length, 4 + 16)]
+
+ iv = content[2 + client_entries_length:2 + client_entries_length + 16]
+ encrypted = content[2 + client_entries_length + 16:]
+
+ client_id = hashlib.sha1(authentication_cookie + iv).digest()[:4]
+
+ for entry_id, encrypted_session_key in client_keys:
+ if entry_id != client_id:
+ continue # not the session key for this client
+
+ # try decrypting the session key
+
+ cipher = Cipher(algorithms.AES(authentication_cookie), modes.CTR(b'\x00' * len(iv)), default_backend())
+ decryptor = cipher.decryptor()
+ session_key = decryptor.update(encrypted_session_key) + decryptor.finalize()
+
+ # attempt to decrypt the intro points with the session key
+
+ cipher = Cipher(algorithms.AES(session_key), modes.CTR(iv), default_backend())
+ decryptor = cipher.decryptor()
+ decrypted = decryptor.update(encrypted) + decryptor.finalize()
+
+ # check if the decryption looks correct
+
+ if decrypted.startswith(b'introduction-point '):
+ return decrypted
+
+ return content # nope, unable to decrypt the content
+
+ @staticmethod
+ def _decrypt_stealth_auth(content, authentication_cookie):
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+ from cryptography.hazmat.backends import default_backend
+
+ # byte 1 = authentication type, 2-17 = input vector, 18 on = encrypted content
+ iv, encrypted = content[1:17], content[17:]
+ cipher = Cipher(algorithms.AES(authentication_cookie), modes.CTR(iv), default_backend())
+ decryptor = cipher.decryptor()
+
+ return decryptor.update(encrypted) + decryptor.finalize()
+
+ @staticmethod
+ def _parse_introduction_points(content):
+ """
+ Provides the parsed list of IntroductionPoints for the unencrypted content.
+ """
+
+ introduction_points = []
+ content_io = io.BytesIO(content)
+
+ while True:
+ content = b''.join(_read_until_keywords('introduction-point', content_io, ignore_first = True))
+
+ if not content:
+ break # reached the end
+
+ attr = dict(INTRODUCTION_POINTS_ATTR)
+ entries = _descriptor_components(content, False)
+
+ for keyword, values in list(entries.items()):
+ value, block_type, block_contents = values[0]
+
+ if keyword in SINGLE_INTRODUCTION_POINT_FIELDS and len(values) > 1:
+ raise ValueError("'%s' can only appear once in an introduction-point block, but appeared %i times" % (keyword, len(values)))
+
+ if keyword == 'introduction-point':
+ attr['identifier'] = value
+ elif keyword == 'ip-address':
+ if not stem.util.connection.is_valid_ipv4_address(value):
+ raise ValueError("'%s' is an invalid IPv4 address" % value)
+
+ attr['address'] = value
+ elif keyword == 'onion-port':
+ if not stem.util.connection.is_valid_port(value):
+ raise ValueError("'%s' is an invalid port" % value)
+
+ attr['port'] = int(value)
+ elif keyword == 'onion-key':
+ attr['onion_key'] = block_contents
+ elif keyword == 'service-key':
+ attr['service_key'] = block_contents
+ elif keyword == 'intro-authentication':
+ auth_entries = []
+
+ for auth_value, _, _ in values:
+ if ' ' not in auth_value:
+ raise ValueError("We expected 'intro-authentication [auth_type] [auth_data]', but had '%s'" % auth_value)
+
+ auth_type, auth_data = auth_value.split(' ')[:2]
+ auth_entries.append((auth_type, auth_data))
+
+ introduction_points.append(IntroductionPoints(**attr))
+
+ return introduction_points
diff --git a/stem/descriptor/hidden_service_descriptor.py b/stem/descriptor/hidden_service_descriptor.py
index 99d6414e..d77d88aa 100644
--- a/stem/descriptor/hidden_service_descriptor.py
+++ b/stem/descriptor/hidden_service_descriptor.py
@@ -1,444 +1,4 @@
-# Copyright 2015-2019, Damian Johnson and The Tor Project
-# See LICENSE for licensing information
+# TODO: This module (hidden_service_descriptor) is a temporary alias for
+# hidden_service. This alias will be removed in Stem 2.x.
-"""
-Parsing for Tor hidden service descriptors as described in Tor's `rend-spec
-<https://gitweb.torproject.org/torspec.git/tree/rend-spec.txt>`_.
-
-Unlike other descriptor types these describe a hidden service rather than a
-relay. They're created by the service, and can only be fetched via relays with
-the HSDir flag.
-
-These are only available through the Controller's
-:func:`~stem.control.get_hidden_service_descriptor` method.
-
-**Module Overview:**
-
-::
-
- HiddenServiceDescriptor - Tor hidden service descriptor.
-
-.. versionadded:: 1.4.0
-"""
-
-# TODO: In stem 2.x rename this module to 'hidden_service' (ie, drop the
-# redundant '_descriptor' suffix).
-
-import base64
-import binascii
-import collections
-import hashlib
-import io
-
-import stem.prereq
-import stem.util.connection
-import stem.util.str_tools
-
-from stem.descriptor import (
- PGP_BLOCK_END,
- Descriptor,
- _descriptor_content,
- _descriptor_components,
- _read_until_keywords,
- _bytes_for_block,
- _value,
- _parse_simple_line,
- _parse_timestamp_line,
- _parse_key_block,
- _random_date,
- _random_crypto_blob,
-)
-
-if stem.prereq._is_lru_cache_available():
- from functools import lru_cache
-else:
- from stem.util.lru_cache import lru_cache
-
-REQUIRED_FIELDS = (
- 'rendezvous-service-descriptor',
- 'version',
- 'permanent-key',
- 'secret-id-part',
- 'publication-time',
- 'protocol-versions',
- 'signature',
-)
-
-INTRODUCTION_POINTS_ATTR = {
- 'identifier': None,
- 'address': None,
- 'port': None,
- 'onion_key': None,
- 'service_key': None,
- 'intro_authentication': [],
-}
-
-# introduction-point fields that can only appear once
-
-SINGLE_INTRODUCTION_POINT_FIELDS = [
- 'introduction-point',
- 'ip-address',
- 'onion-port',
- 'onion-key',
- 'service-key',
-]
-
-BASIC_AUTH = 1
-STEALTH_AUTH = 2
-
-
-class IntroductionPoints(collections.namedtuple('IntroductionPoints', INTRODUCTION_POINTS_ATTR.keys())):
- """
- :var str identifier: hash of this introduction point's identity key
- :var str address: address of this introduction point
- :var int port: port where this introduction point is listening
- :var str onion_key: public key for communicating with this introduction point
- :var str service_key: public key for communicating with this hidden service
- :var list intro_authentication: tuples of the form (auth_type, auth_data) for
- establishing a connection
- """
-
-
-class DecryptionFailure(Exception):
- """
- Failure to decrypt the hidden service descriptor's introduction-points.
- """
-
-
-def _parse_file(descriptor_file, validate = False, **kwargs):
- """
- Iterates over the hidden service descriptors in a file.
-
- :param file descriptor_file: file with descriptor content
- :param bool validate: checks the validity of the descriptor's content if
- **True**, skips these checks otherwise
- :param dict kwargs: additional arguments for the descriptor constructor
-
- :returns: iterator for :class:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor`
- instances in the file
-
- :raises:
- * **ValueError** if the contents is malformed and validate is **True**
- * **IOError** if the file can't be read
- """
-
- while True:
- descriptor_content = _read_until_keywords('signature', descriptor_file)
-
- # we've reached the 'signature', now include the pgp style block
- block_end_prefix = PGP_BLOCK_END.split(' ', 1)[0]
- descriptor_content += _read_until_keywords(block_end_prefix, descriptor_file, True)
-
- if descriptor_content:
- if descriptor_content[0].startswith(b'@type'):
- descriptor_content = descriptor_content[1:]
-
- yield HiddenServiceDescriptor(bytes.join(b'', descriptor_content), validate, **kwargs)
- else:
- break # done parsing file
-
-
-def _parse_version_line(descriptor, entries):
- value = _value('version', entries)
-
- if value.isdigit():
- descriptor.version = int(value)
- else:
- raise ValueError('version line must have a positive integer value: %s' % value)
-
-
-def _parse_protocol_versions_line(descriptor, entries):
- value = _value('protocol-versions', entries)
-
- try:
- versions = [int(entry) for entry in value.split(',')]
- except ValueError:
- raise ValueError('protocol-versions line has non-numeric versoins: protocol-versions %s' % value)
-
- for v in versions:
- if v <= 0:
- raise ValueError('protocol-versions must be positive integers: %s' % value)
-
- descriptor.protocol_versions = versions
-
-
-def _parse_introduction_points_line(descriptor, entries):
- _, block_type, block_contents = entries['introduction-points'][0]
-
- if not block_contents or block_type != 'MESSAGE':
- raise ValueError("'introduction-points' should be followed by a MESSAGE block, but was a %s" % block_type)
-
- descriptor.introduction_points_encoded = block_contents
- descriptor.introduction_points_auth = [] # field was never implemented in tor (#15190)
-
- try:
- descriptor.introduction_points_content = _bytes_for_block(block_contents)
- except TypeError:
- raise ValueError("'introduction-points' isn't base64 encoded content:\n%s" % block_contents)
-
-
-_parse_rendezvous_service_descriptor_line = _parse_simple_line('rendezvous-service-descriptor', 'descriptor_id')
-_parse_permanent_key_line = _parse_key_block('permanent-key', 'permanent_key', 'RSA PUBLIC KEY')
-_parse_secret_id_part_line = _parse_simple_line('secret-id-part', 'secret_id_part')
-_parse_publication_time_line = _parse_timestamp_line('publication-time', 'published')
-_parse_signature_line = _parse_key_block('signature', 'signature', 'SIGNATURE')
-
-
-class HiddenServiceDescriptor(Descriptor):
- """
- Hidden service descriptor.
-
- :var str descriptor_id: **\\*** identifier for this descriptor, this is a base32 hash of several fields
- :var int version: **\\*** hidden service descriptor version
- :var str permanent_key: **\\*** long term key of the hidden service
- :var str secret_id_part: **\\*** hash of the time period, cookie, and replica
- values so our descriptor_id can be validated
- :var datetime published: **\\*** time in UTC when this descriptor was made
- :var list protocol_versions: **\\*** list of **int** versions that are supported when establishing a connection
- :var str introduction_points_encoded: raw introduction points blob
- :var list introduction_points_auth: **\\*** tuples of the form
- (auth_method, auth_data) for our introduction_points_content
- (**deprecated**, always **[]**)
- :var bytes introduction_points_content: decoded introduction-points content
- without authentication data, if using cookie authentication this is
- encrypted
- :var str signature: signature of the descriptor content
-
- **\\*** attribute is either required when we're parsed with validation or has
- a default value, others are left as **None** if undefined
-
- .. versionchanged:: 1.6.0
- Moved from the deprecated `pycrypto
- <https://www.dlitz.net/software/pycrypto/>`_ module to `cryptography
- <https://pypi.org/project/cryptography/>`_ for validating signatures.
-
- .. versionchanged:: 1.6.0
- Added the **skip_crypto_validation** constructor argument.
- """
-
- TYPE_ANNOTATION_NAME = 'hidden-service-descriptor'
-
- ATTRIBUTES = {
- 'descriptor_id': (None, _parse_rendezvous_service_descriptor_line),
- 'version': (None, _parse_version_line),
- 'permanent_key': (None, _parse_permanent_key_line),
- 'secret_id_part': (None, _parse_secret_id_part_line),
- 'published': (None, _parse_publication_time_line),
- 'protocol_versions': ([], _parse_protocol_versions_line),
- 'introduction_points_encoded': (None, _parse_introduction_points_line),
- 'introduction_points_auth': ([], _parse_introduction_points_line),
- 'introduction_points_content': (None, _parse_introduction_points_line),
- 'signature': (None, _parse_signature_line),
- }
-
- PARSER_FOR_LINE = {
- 'rendezvous-service-descriptor': _parse_rendezvous_service_descriptor_line,
- 'version': _parse_version_line,
- 'permanent-key': _parse_permanent_key_line,
- 'secret-id-part': _parse_secret_id_part_line,
- 'publication-time': _parse_publication_time_line,
- 'protocol-versions': _parse_protocol_versions_line,
- 'introduction-points': _parse_introduction_points_line,
- 'signature': _parse_signature_line,
- }
-
- @classmethod
- def content(cls, attr = None, exclude = (), sign = False):
- if sign:
- raise NotImplementedError('Signing of %s not implemented' % cls.__name__)
-
- return _descriptor_content(attr, exclude, (
- ('rendezvous-service-descriptor', 'y3olqqblqw2gbh6phimfuiroechjjafa'),
- ('version', '2'),
- ('permanent-key', _random_crypto_blob('RSA PUBLIC KEY')),
- ('secret-id-part', 'e24kgecavwsznj7gpbktqsiwgvngsf4e'),
- ('publication-time', _random_date()),
- ('protocol-versions', '2,3'),
- ('introduction-points', '\n-----BEGIN MESSAGE-----\n-----END MESSAGE-----'),
- ), (
- ('signature', _random_crypto_blob('SIGNATURE')),
- ))
-
- @classmethod
- def create(cls, attr = None, exclude = (), validate = True, sign = False):
- return cls(cls.content(attr, exclude, sign), validate = validate, skip_crypto_validation = not sign)
-
- def __init__(self, raw_contents, validate = False, skip_crypto_validation = False):
- super(HiddenServiceDescriptor, self).__init__(raw_contents, lazy_load = not validate)
- entries = _descriptor_components(raw_contents, validate, non_ascii_fields = ('introduction-points'))
-
- if validate:
- for keyword in REQUIRED_FIELDS:
- if keyword not in entries:
- raise ValueError("Hidden service descriptor must have a '%s' entry" % keyword)
- elif keyword in entries and len(entries[keyword]) > 1:
- raise ValueError("The '%s' entry can only appear once in a hidden service descriptor" % keyword)
-
- if 'rendezvous-service-descriptor' != list(entries.keys())[0]:
- raise ValueError("Hidden service descriptor must start with a 'rendezvous-service-descriptor' entry")
- elif 'signature' != list(entries.keys())[-1]:
- raise ValueError("Hidden service descriptor must end with a 'signature' entry")
-
- self._parse(entries, validate)
-
- if not skip_crypto_validation and stem.prereq.is_crypto_available():
- signed_digest = self._digest_for_signature(self.permanent_key, self.signature)
- digest_content = self._content_range('rendezvous-service-descriptor ', '\nsignature\n')
- content_digest = hashlib.sha1(digest_content).hexdigest().upper()
-
- if signed_digest != content_digest:
- raise ValueError('Decrypted digest does not match local digest (calculated: %s, local: %s)' % (signed_digest, content_digest))
- else:
- self._entries = entries
-
- @lru_cache()
- def introduction_points(self, authentication_cookie = None):
- """
- Provided this service's introduction points.
-
- :returns: **list** of :class:`~stem.descriptor.hidden_service_descriptor.IntroductionPoints`
-
- :raises:
- * **ValueError** if the our introduction-points is malformed
- * **DecryptionFailure** if unable to decrypt this field
- """
-
- content = self.introduction_points_content
-
- if not content:
- return []
- elif authentication_cookie:
- if not stem.prereq.is_crypto_available():
- raise DecryptionFailure('Decrypting introduction-points requires the cryptography module')
-
- try:
- missing_padding = len(authentication_cookie) % 4
- authentication_cookie = base64.b64decode(stem.util.str_tools._to_bytes(authentication_cookie) + b'=' * missing_padding)
- except TypeError as exc:
- raise DecryptionFailure('authentication_cookie must be a base64 encoded string (%s)' % exc)
-
- authentication_type = int(binascii.hexlify(content[0:1]), 16)
-
- if authentication_type == BASIC_AUTH:
- content = HiddenServiceDescriptor._decrypt_basic_auth(content, authentication_cookie)
- elif authentication_type == STEALTH_AUTH:
- content = HiddenServiceDescriptor._decrypt_stealth_auth(content, authentication_cookie)
- else:
- raise DecryptionFailure("Unrecognized authentication type '%s', currently we only support basic auth (%s) and stealth auth (%s)" % (authentication_type, BASIC_AUTH, STEALTH_AUTH))
-
- if not content.startswith(b'introduction-point '):
- raise DecryptionFailure('Unable to decrypt the introduction-points, maybe this is the wrong key?')
- elif not content.startswith(b'introduction-point '):
- raise DecryptionFailure('introduction-points content is encrypted, you need to provide its authentication_cookie')
-
- return HiddenServiceDescriptor._parse_introduction_points(content)
-
- @staticmethod
- def _decrypt_basic_auth(content, authentication_cookie):
- from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
- from cryptography.hazmat.backends import default_backend
-
- try:
- client_blocks = int(binascii.hexlify(content[1:2]), 16)
- except ValueError:
- raise DecryptionFailure("When using basic auth the content should start with a number of blocks but wasn't a hex digit: %s" % binascii.hexlify(content[1:2]))
-
- # parse the client id and encrypted session keys
-
- client_entries_length = client_blocks * 16 * 20
- client_entries = content[2:2 + client_entries_length]
- client_keys = [(client_entries[i:i + 4], client_entries[i + 4:i + 20]) for i in range(0, client_entries_length, 4 + 16)]
-
- iv = content[2 + client_entries_length:2 + client_entries_length + 16]
- encrypted = content[2 + client_entries_length + 16:]
-
- client_id = hashlib.sha1(authentication_cookie + iv).digest()[:4]
-
- for entry_id, encrypted_session_key in client_keys:
- if entry_id != client_id:
- continue # not the session key for this client
-
- # try decrypting the session key
-
- cipher = Cipher(algorithms.AES(authentication_cookie), modes.CTR(b'\x00' * len(iv)), default_backend())
- decryptor = cipher.decryptor()
- session_key = decryptor.update(encrypted_session_key) + decryptor.finalize()
-
- # attempt to decrypt the intro points with the session key
-
- cipher = Cipher(algorithms.AES(session_key), modes.CTR(iv), default_backend())
- decryptor = cipher.decryptor()
- decrypted = decryptor.update(encrypted) + decryptor.finalize()
-
- # check if the decryption looks correct
-
- if decrypted.startswith(b'introduction-point '):
- return decrypted
-
- return content # nope, unable to decrypt the content
-
- @staticmethod
- def _decrypt_stealth_auth(content, authentication_cookie):
- from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
- from cryptography.hazmat.backends import default_backend
-
- # byte 1 = authentication type, 2-17 = input vector, 18 on = encrypted content
- iv, encrypted = content[1:17], content[17:]
- cipher = Cipher(algorithms.AES(authentication_cookie), modes.CTR(iv), default_backend())
- decryptor = cipher.decryptor()
-
- return decryptor.update(encrypted) + decryptor.finalize()
-
- @staticmethod
- def _parse_introduction_points(content):
- """
- Provides the parsed list of IntroductionPoints for the unencrypted content.
- """
-
- introduction_points = []
- content_io = io.BytesIO(content)
-
- while True:
- content = b''.join(_read_until_keywords('introduction-point', content_io, ignore_first = True))
-
- if not content:
- break # reached the end
-
- attr = dict(INTRODUCTION_POINTS_ATTR)
- entries = _descriptor_components(content, False)
-
- for keyword, values in list(entries.items()):
- value, block_type, block_contents = values[0]
-
- if keyword in SINGLE_INTRODUCTION_POINT_FIELDS and len(values) > 1:
- raise ValueError("'%s' can only appear once in an introduction-point block, but appeared %i times" % (keyword, len(values)))
-
- if keyword == 'introduction-point':
- attr['identifier'] = value
- elif keyword == 'ip-address':
- if not stem.util.connection.is_valid_ipv4_address(value):
- raise ValueError("'%s' is an invalid IPv4 address" % value)
-
- attr['address'] = value
- elif keyword == 'onion-port':
- if not stem.util.connection.is_valid_port(value):
- raise ValueError("'%s' is an invalid port" % value)
-
- attr['port'] = int(value)
- elif keyword == 'onion-key':
- attr['onion_key'] = block_contents
- elif keyword == 'service-key':
- attr['service_key'] = block_contents
- elif keyword == 'intro-authentication':
- auth_entries = []
-
- for auth_value, _, _ in values:
- if ' ' not in auth_value:
- raise ValueError("We expected 'intro-authentication [auth_type] [auth_data]', but had '%s'" % auth_value)
-
- auth_type, auth_data = auth_value.split(' ')[:2]
- auth_entries.append((auth_type, auth_data))
-
- introduction_points.append(IntroductionPoints(**attr))
-
- return introduction_points
+from stem.descriptor.hidden_service import *
diff --git a/stem/response/events.py b/stem/response/events.py
index a9f563c6..7a5f749b 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -701,7 +701,7 @@ class HSDescContentEvent(Event):
:var str directory: hidden service directory servicing the request
:var str directory_fingerprint: hidden service directory's finterprint
:var str directory_nickname: hidden service directory's nickname if it was provided
- :var stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor descriptor: descriptor that was retrieved
+ :var stem.descriptor.hidden_service.HiddenServiceDescriptor descriptor: descriptor that was retrieved
"""
_VERSION_ADDED = stem.version.Requirement.EVENT_HS_DESC_CONTENT
@@ -726,7 +726,7 @@ class HSDescContentEvent(Event):
self.descriptor = None
if desc_content:
- self.descriptor = list(stem.descriptor.hidden_service_descriptor._parse_file(io.BytesIO(desc_content)))[0]
+ self.descriptor = list(stem.descriptor.hidden_service._parse_file(io.BytesIO(desc_content)))[0]
class LogEvent(Event):
diff --git a/test/settings.cfg b/test/settings.cfg
index 1bdb1a0a..3308c104 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -172,7 +172,7 @@ pycodestyle.ignore E722
pycodestyle.ignore stem/__init__.py => E402: import stem.util.connection
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.bandwidth_file
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.extrainfo_descriptor
-pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.hidden_service_descriptor
+pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.hidden_service
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.microdescriptor
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.networkstatus
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.server_descriptor
@@ -184,9 +184,7 @@ pycodestyle.ignore test/unit/util/connection.py => W291: _tor tor 158
# issue.
pyflakes.ignore run_tests.py => 'unittest' imported but unused
-pyflakes.ignore stem/client/datatype.py => redefinition of unused 'pop' from *
pyflakes.ignore stem/control.py => undefined name 'controller'
-pyflakes.ignore stem/interpreter/__init__.py => undefined name 'raw_input'
pyflakes.ignore stem/manual.py => undefined name 'unichr'
pyflakes.ignore stem/prereq.py => 'int_to_bytes' imported but unused
pyflakes.ignore stem/prereq.py => 'int_from_bytes' imported but unused
@@ -210,6 +208,10 @@ pyflakes.ignore stem/prereq.py => 'cryptography.hazmat.primitives.ciphers.modes'
pyflakes.ignore stem/prereq.py => 'cryptography.hazmat.primitives.ciphers.Cipher' imported but unused
pyflakes.ignore stem/prereq.py => 'cryptography.hazmat.primitives.ciphers.algorithms' imported but unused
pyflakes.ignore stem/prereq.py => 'lzma' imported but unused
+pyflakes.ignore stem/client/datatype.py => redefinition of unused 'pop' from *
+pyflakes.ignore stem/descriptor/hidden_service_descriptor.py => 'stem.descriptor.hidden_service.*' imported but unused
+pyflakes.ignore stem/descriptor/hidden_service_descriptor.py => 'from stem.descriptor.hidden_service import *' used; unable to detect undefined names
+pyflakes.ignore stem/interpreter/__init__.py => undefined name 'raw_input'
pyflakes.ignore stem/response/events.py => undefined name 'long'
pyflakes.ignore stem/util/__init__.py => undefined name 'long'
pyflakes.ignore stem/util/__init__.py => undefined name 'unicode'
@@ -256,7 +258,7 @@ test.unit_tests
|test.unit.descriptor.networkstatus.document_v2.TestNetworkStatusDocument
|test.unit.descriptor.networkstatus.document_v3.TestNetworkStatusDocument
|test.unit.descriptor.networkstatus.bridge_document.TestBridgeNetworkStatusDocument
-|test.unit.descriptor.hidden_service_descriptor.TestHiddenServiceDescriptor
+|test.unit.descriptor.hidden_service.TestHiddenServiceDescriptor
|test.unit.descriptor.certificate.TestEd25519Certificate
|test.unit.descriptor.bandwidth_file.TestBandwidthFile
|test.unit.exit_policy.rule.TestExitPolicyRule
diff --git a/test/unit/descriptor/hidden_service_descriptor.py b/test/unit/descriptor/hidden_service.py
similarity index 99%
rename from test/unit/descriptor/hidden_service_descriptor.py
rename to test/unit/descriptor/hidden_service.py
index e9ba012b..c49741d3 100644
--- a/test/unit/descriptor/hidden_service_descriptor.py
+++ b/test/unit/descriptor/hidden_service.py
@@ -1,5 +1,5 @@
"""
-Unit tests for stem.descriptor.hidden_service_descriptor.
+Unit tests for stem.descriptor.hidden_service.
"""
import datetime
@@ -10,7 +10,7 @@ import stem.descriptor
import stem.prereq
import test.require
-from stem.descriptor.hidden_service_descriptor import (
+from stem.descriptor.hidden_service import (
REQUIRED_FIELDS,
DecryptionFailure,
HiddenServiceDescriptor,
1
0
commit eb36c093b44950a784e102ef18029aa3915ad9c7
Author: Damian Johnson <atagar(a)torproject.org>
Date: Thu Aug 22 17:03:19 2019 -0700
Parse lifetime field
Trivial from a parsing perspective but I gotta admit, I'm kinda confused how
this is useful since unlike v2 the descriptor doesn't note its publication
time.
---
stem/descriptor/hidden_service.py | 14 +++++++++++++-
test/unit/descriptor/hidden_service_v3.py | 15 +++++++++++++++
2 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index 4baad548..44bf114b 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -157,7 +157,16 @@ def _parse_version_line(descriptor, entries):
if value.isdigit():
descriptor.version = int(value)
else:
- raise ValueError('version line must have a positive integer value: %s' % value)
+ raise ValueError('%s line must have a positive integer value: %s' % (keyword, value))
+
+
+def _parse_lifetime(descriptor, entries):
+ value = _value('descriptor-lifetime', entries)
+
+ if value.isdigit():
+ descriptor.lifetime = int(value)
+ else:
+ raise ValueError('descriptor-lifetime line must have a positive integer value: %s' % value)
def _parse_protocol_versions_line(descriptor, entries):
@@ -472,6 +481,7 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
Version 3 hidden service descriptor.
:var int version: **\\*** hidden service descriptor version
+ :var int lifetime: **\\*** minutes after publication this descriptor is valid
**\\*** attribute is either required when we're parsed with validation or has
a default value, others are left as **None** if undefined
@@ -483,10 +493,12 @@ class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
ATTRIBUTES = {
'version': (None, _parse_version_line),
+ 'lifetime': (None, _parse_lifetime),
}
PARSER_FOR_LINE = {
'hs-descriptor': _parse_version_line,
+ 'descriptor-lifetime': _parse_lifetime,
}
@classmethod
diff --git a/test/unit/descriptor/hidden_service_v3.py b/test/unit/descriptor/hidden_service_v3.py
index 04a95a6d..b7a23c86 100644
--- a/test/unit/descriptor/hidden_service_v3.py
+++ b/test/unit/descriptor/hidden_service_v3.py
@@ -29,6 +29,7 @@ class TestHiddenServiceDescriptorV3(unittest.TestCase):
desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor-3 1.0', validate = True))
self.assertEqual(3, desc.version)
+ self.assertEqual(180, desc.lifetime)
def test_invalid_version(self):
"""
@@ -43,3 +44,17 @@ class TestHiddenServiceDescriptorV3(unittest.TestCase):
for test_value in test_values:
expect_invalid_attr(self, {'hs-descriptor': test_value}, 'version')
+
+ def test_invalid_lifetime(self):
+ """
+ Checks that our lifetime field expects a numeric value.
+ """
+
+ test_values = (
+ '',
+ '-10',
+ 'hello',
+ )
+
+ for test_value in test_values:
+ expect_invalid_attr(self, {'descriptor-lifetime': test_value}, 'lifetime')
1
0
commit e47abb9f6ed81f39bcb09cd4e8bfb202488f695f
Author: Damian Johnson <atagar(a)torproject.org>
Date: Thu Aug 22 16:48:04 2019 -0700
Parse version field
Trivial parsing for the first field. Mostly still just wiring things up.
---
stem/descriptor/hidden_service.py | 49 ++++++++++++++++++++++++++++++-
test/unit/descriptor/hidden_service_v3.py | 34 ++++++++++++++++++---
2 files changed, 78 insertions(+), 5 deletions(-)
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index 1ed100ef..4baad548 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -145,7 +145,14 @@ def _parse_file(descriptor_file, desc_type = None, validate = False, **kwargs):
def _parse_version_line(descriptor, entries):
- value = _value('version', entries)
+ if isinstance(descriptor, HiddenServiceDescriptorV2):
+ keyword = 'version'
+ elif isinstance(descriptor, HiddenServiceDescriptorV3):
+ keyword = 'hs-descriptor'
+ else:
+ raise ValueError('BUG: unexpected descriptor type (%s)' % type(descriptor).__name__)
+
+ value = _value(keyword, entries)
if value.isdigit():
descriptor.version = int(value)
@@ -463,12 +470,52 @@ class HiddenServiceDescriptorV2(BaseHiddenServiceDescriptor):
class HiddenServiceDescriptorV3(BaseHiddenServiceDescriptor):
"""
Version 3 hidden service descriptor.
+
+ :var int version: **\\*** hidden service descriptor version
+
+ **\\*** attribute is either required when we're parsed with validation or has
+ a default value, others are left as **None** if undefined
"""
# TODO: requested this @type on https://trac.torproject.org/projects/tor/ticket/31481
TYPE_ANNOTATION_NAME = 'hidden-service-descriptor-3'
+ ATTRIBUTES = {
+ 'version': (None, _parse_version_line),
+ }
+
+ PARSER_FOR_LINE = {
+ 'hs-descriptor': _parse_version_line,
+ }
+
+ @classmethod
+ def content(cls, attr = None, exclude = (), sign = False):
+ if sign:
+ raise NotImplementedError('Signing of %s not implemented' % cls.__name__)
+
+ return _descriptor_content(attr, exclude, (
+ ('hs-descriptor', '3'),
+ ('descriptor-lifetime', '180'),
+ ('descriptor-signing-key-cert', _random_crypto_blob('ED25519 CERT')),
+ ('revision-counter', '15'),
+ ('superencrypted', _random_crypto_blob('MESSAGE')),
+ ('signature', 'wdc7ffr+dPZJ/mIQ1l4WYqNABcmsm6SHW/NL3M3wG7bjjqOJWoPR5TimUXxH52n5Zk0Gc7hl/hz3YYmAx5MvAg'),
+ ), ())
+
+ @classmethod
+ def create(cls, attr = None, exclude = (), validate = True, sign = False):
+ return cls(cls.content(attr, exclude, sign), validate = validate, skip_crypto_validation = not sign)
+
+ def __init__(self, raw_contents, validate = False, skip_crypto_validation = False):
+ super(HiddenServiceDescriptorV3, self).__init__(raw_contents, lazy_load = not validate)
+ entries = _descriptor_components(raw_contents, validate)
+
+ if validate:
+ self._parse(entries, validate)
+ else:
+ self._entries = entries
+
# TODO: drop this alias in stem 2.x
diff --git a/test/unit/descriptor/hidden_service_v3.py b/test/unit/descriptor/hidden_service_v3.py
index 757c1944..04a95a6d 100644
--- a/test/unit/descriptor/hidden_service_v3.py
+++ b/test/unit/descriptor/hidden_service_v3.py
@@ -2,18 +2,44 @@
Unit tests for stem.descriptor.hidden_service for version 3.
"""
+import functools
import unittest
import stem.descriptor
-from test.unit.descriptor import get_resource
+from stem.descriptor.hidden_service import HiddenServiceDescriptorV3
+
+from test.unit.descriptor import (
+ get_resource,
+ base_expect_invalid_attr,
+)
+
+expect_invalid_attr = functools.partial(base_expect_invalid_attr, HiddenServiceDescriptorV3, 'version', 3)
class TestHiddenServiceDescriptorV3(unittest.TestCase):
- def test_stub(self):
- # TODO: replace with actual field assertions as the class gets implemented
+ def test_for_riseup(self):
+ """
+ Parse riseup's descriptor...
+
+ vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion
+ """
with open(get_resource('hidden_service_v3'), 'rb') as descriptor_file:
desc = next(stem.descriptor.parse_file(descriptor_file, 'hidden-service-descriptor-3 1.0', validate = True))
- self.assertTrue('hs-descriptor 3' in str(desc))
+ self.assertEqual(3, desc.version)
+
+ def test_invalid_version(self):
+ """
+ Checks that our version field expects a numeric value.
+ """
+
+ test_values = (
+ '',
+ '-10',
+ 'hello',
+ )
+
+ for test_value in test_values:
+ expect_invalid_attr(self, {'hs-descriptor': test_value}, 'version')
1
0