├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── misc ├── 2015-04-11-19-00-00-consensus ├── distance-2500-10000000.png └── rend-spec.txt └── src ├── cmd ├── announce.go ├── announce.html ├── brute.go ├── curiosity.go ├── grind.go ├── lookmeup.go ├── montecarlo.go ├── preprocess.go └── scrolls.go ├── go_reduced_rsa_primalty.diff ├── hstools ├── brute.go ├── brute_test.go ├── consensus.go ├── consensus_test.go ├── descid.go ├── descid_test.go ├── format.go ├── format_test.go ├── hashring.go ├── hashring_test.go ├── keysdb.go ├── stats.go └── stats_test.go └── retrieve_hs_descriptor.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | /bin 25 | /pkg 26 | /data 27 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tor"] 2 | path = tor 3 | url = https://git.torproject.org/tor.git 4 | [submodule "src/github.com/boltdb/bolt"] 5 | path = src/github.com/boltdb/bolt 6 | url = https://github.com/boltdb/bolt 7 | [submodule "src/github.com/ActiveState/tail"] 8 | path = src/github.com/ActiveState/tail 9 | url = https://github.com/ActiveState/tail 10 | [submodule "src/github.com/howeyc/fsnotify"] 11 | path = src/github.com/howeyc/fsnotify 12 | url = https://github.com/howeyc/fsnotify 13 | [submodule "src/gopkg.in/tomb.v1"] 14 | path = src/gopkg.in/tomb.v1 15 | url = https://gopkg.in/tomb.v1 16 | [submodule "src/golang.org/x/net"] 17 | path = src/golang.org/x/net 18 | url = https://go.googlesource.com/net 19 | [submodule "src/github.com/mgutz/ansi"] 20 | path = src/github.com/mgutz/ansi 21 | url = https://github.com/mgutz/ansi 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Filippo Valsorda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test clean all 2 | .PHONY: preprocess brute lookmeup announce grind curiosity scrolls montecarlo 3 | all: preprocess brute lookmeup announce grind curiosity scrolls 4 | 5 | GO ?= go 6 | 7 | montecarlo: 8 | GOPATH="$(CURDIR)" $(GO) build -o bin/montecarlo src/cmd/montecarlo.go 9 | 10 | preprocess: 11 | GOPATH="$(CURDIR)" $(GO) build -o bin/preprocess src/cmd/preprocess.go 12 | 13 | brute: 14 | GOPATH="$(CURDIR)" $(GO) build -o bin/brute src/cmd/brute.go 15 | 16 | lookmeup: 17 | GOPATH="$(CURDIR)" $(GO) build -o bin/lookmeup src/cmd/lookmeup.go 18 | 19 | announce: 20 | GOPATH="$(CURDIR)" $(GO) build -o bin/announce src/cmd/announce.go 21 | 22 | grind: 23 | GOPATH="$(CURDIR)" $(GO) build -o bin/grind src/cmd/grind.go 24 | 25 | curiosity: 26 | GOPATH="$(CURDIR)" $(GO) build -o bin/curiosity src/cmd/curiosity.go 27 | 28 | scrolls: 29 | GOPATH="$(CURDIR)" $(GO) build -o bin/scrolls src/cmd/scrolls.go 30 | 31 | test: 32 | GOPATH="$(CURDIR)" $(GO) test hstools -race -v -short 33 | 34 | clean: 35 | - rm -r bin pkg 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a Go library and a collection of tools to interact with and analyze Tor HSDirs. 2 | 3 | ## Bruteforcing tool 4 | 5 | `brute` will bruteforce Identity Keys that will be the 6 HSDir for the given onion 6 | at the given time, considering the given consensus state (which should probably just be the most 7 | [recent](https://collector.torproject.org/recent/relay-descriptors/consensuses/). 8 | 9 | ./bin/brute 2015-05-23-23-00-00-consensus facebookcorewwwi.onion 2015-05-29T12:00:00Z 10 | 11 | You should really patch your Go standard library with the provided reduced-primalty patch, it will make the bruteforce 12 | 20 times as fast and complete checks are performed anyway by the tool itself (ProTip: have a separate Go tree for this 13 | security-shattering patch!) 14 | 15 | ## Preprocessing 16 | 17 | First, download consensus files from the following two addresses: 18 | 19 | https://collector.torproject.org/archive/relay-descriptors/consensuses/ 20 | https://collector.torproject.org/recent/relay-descriptors/consensuses/ 21 | 22 | and make sure they are named like this (this is what you will get by just unpacking the tarballs): 23 | 24 | $DIR/consensuses-2015-01/01/2015-01-01-00-00-00-consensus 25 | 26 | Then there are two steps to preprocessing: 27 | 28 | preprocess $DIR 2015-01-01-00 2015-05-28-10 29 | grind pckcns.dat > stats.jsonl 30 | 31 | This should leave you with the following three files: `pckcns.dat`, `keys.db` and `stats.jsonl` 32 | 33 | ## Analysis tools 34 | 35 | ### scrolls 36 | 37 | `scrolls` will go through consensuses in a time frame and print the HSDir set every time it changed, 38 | with color-coded suspiciousness scores. 39 | 40 | ./bin/scrolls pckcns.dat keys.db stats.jsonl facebookcorewwwi.onion 2015-05-01-01 2015-05-28-10 41 | 42 | ![image](https://cloud.githubusercontent.com/assets/1225294/7873542/e206bdf6-05a3-11e5-99db-d9d89a2bf4ec.png) 43 | 44 | * "Dist score" is a normalized score of how near the first HSDir is to the Descriptor ID, compared to the 45 | expected mean and average deviation on the ring. 46 | * "Dist4 score" is the same, but for the distance of the 4th node. 47 | * "Age" is how many hours before the node got the HSDir flag (or appeared). ∞ means since the beginning of the time frame. 48 | * "Long" is how many hours after the node lost the HSDir flag (or disappeared). ∞ means until the end of the time frame. 49 | * "Colo keys" is how many other Identity Keys have been observed on IPs that hosted this Identity Key. 50 | 51 | ### curiosity 52 | 53 | `curiosity` will generate statistics about correlations between IPs and multiple Identity Keys. 54 | 55 | ./bin/curiosity data/keys.db colocated | sort -g 56 | 57 | [...] 58 | 57 keys on 1 IPs - EFA4BF05D097357832B8C7EE3391C8A534CB06F1 [178.32.143.175] 59 | 57 keys on 1 IPs - FADD1E620505CD8B1E22DC9CC503D06592C24EF9 [178.32.143.175] 60 | 57 keys on 1 IPs - FFCCB39F28DB9926F9D34D97F1D98EEA834EED38 [178.32.143.175] 61 | 57 keys on 3 IPs - 0045D088EB8E8E3634F3C82A8010A50BC18938D4 [54.242.144.249 23.20.33.81 23.22.192.88] 62 | 57 keys on 3 IPs - 01120D3FA96BA5B5F5FBB5E1581E8C73E0EFE24D [54.242.144.249 23.20.33.81 23.22.192.88] 63 | 57 keys on 3 IPs - 01C5B224668F77D3662956DDEC15DA46A252CC23 [54.242.144.249 23.20.33.81 23.22.192.88] 64 | 57 keys on 3 IPs - 027B74EEB1A43CFA490D3AE7C4CCC11F5F26A3A7 [54.242.144.249 23.20.33.81 23.22.192.88] 65 | 57 keys on 3 IPs - 03D401D7B671098945BD5B14E3D54278CBCF5067 [54.242.144.249 23.20.33.81 23.22.192.88] 66 | 67 | ## Coming soon 68 | 69 | * automated orchestration tool to easily deploy defensive arrays of HSDirs 70 | * real-time targeted and generic statistical monitoring tools 71 | -------------------------------------------------------------------------------- /misc/distance-2500-10000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FiloSottile/hstools/83d0a4452594f0b1057d93577730c6747777e45f/misc/distance-2500-10000000.png -------------------------------------------------------------------------------- /misc/rend-spec.txt: -------------------------------------------------------------------------------- 1 | 2 | Tor Rendezvous Specification 3 | 4 | 0. Overview and preliminaries 5 | 6 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL 7 | NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and 8 | "OPTIONAL" in this document are to be interpreted as described in 9 | RFC 2119. 10 | 11 | Read 12 | https://svn.torproject.org/svn/projects/design-paper/tor-design.html#sec:rendezvous 13 | before you read this specification. It will make more sense. 14 | 15 | Rendezvous points provide location-hidden services (server 16 | anonymity) for the onion routing network. With rendezvous points, 17 | Bob can offer a TCP service (say, a webserver) via the onion 18 | routing network, without revealing the IP of that service. 19 | 20 | Bob does this by anonymously advertising a public key for his 21 | service, along with a list of onion routers to act as "Introduction 22 | Points" for his service. He creates forward circuits to those 23 | introduction points, and tells them about his service. To 24 | connect to Bob, Alice first builds a circuit to an OR to act as 25 | her "Rendezvous Point." She then connects to one of Bob's chosen 26 | introduction points, and asks it to tell him about her Rendezvous 27 | Point (RP). If Bob chooses to answer, he builds a circuit to her 28 | RP, and tells it to connect him to Alice. The RP joins their 29 | circuits together, and begins relaying cells. Alice's 'BEGIN' 30 | cells are received directly by Bob's OP, which passes data to 31 | and from the local server implementing Bob's service. 32 | 33 | Below we describe a network-level specification of this service, 34 | along with interfaces to make this process transparent to Alice 35 | (so long as she is using an OP). 36 | 37 | 0.1. Notation, conventions and prerequisites 38 | 39 | In the specifications below, we use the same notation and terminology 40 | as in "tor-spec.txt". The service specified here also requires the 41 | existence of an onion routing network as specified in that file. 42 | 43 | H(x) is a SHA1 digest of x. 44 | PKSign(SK,x) is a PKCS.1-padded RSA signature of x with SK. 45 | PKEncrypt(SK,x) is a PKCS.1-padded RSA encryption of x with SK. 46 | Public keys are all RSA, and encoded in ASN.1. 47 | All integers are stored in network (big-endian) order. 48 | All symmetric encryption uses AES in counter mode, except where 49 | otherwise noted. 50 | 51 | In all discussions, "Alice" will refer to a user connecting to a 52 | location-hidden service, and "Bob" will refer to a user running a 53 | location-hidden service. 54 | 55 | An OP is (as defined elsewhere) an "Onion Proxy" or Tor client. 56 | 57 | An OR is (as defined elsewhere) an "Onion Router" or Tor server. 58 | 59 | An "Introduction point" is a Tor server chosen to be Bob's medium-term 60 | 'meeting place'. A "Rendezvous point" is a Tor server chosen by Alice to 61 | be a short-term communication relay between her and Bob. All Tor servers 62 | potentially act as introduction and rendezvous points. 63 | 64 | 0.2. Protocol outline 65 | 66 | 1. Bob->Bob's OP: "Offer IP:Port as public-key-name:Port". [configuration] 67 | (We do not specify this step; it is left to the implementor of 68 | Bob's OP.) 69 | 70 | 2. Bob's OP generates a long-term keypair. 71 | 72 | 3. Bob's OP->Introduction point via Tor: [introduction setup] 73 | "This public key is (currently) associated to me." 74 | 75 | 4. Bob's OP->directory service via Tor: publishes Bob's service descriptor 76 | [advertisement] 77 | "Meet public-key X at introduction point A, B, or C." (signed) 78 | 79 | 5. Out of band, Alice receives a z.onion:port address. 80 | She opens a SOCKS connection to her OP, and requests z.onion:port. 81 | 82 | 6. Alice's OP retrieves Bob's descriptor via Tor. [descriptor lookup.] 83 | 84 | 7. Alice's OP chooses a rendezvous point, opens a circuit to that 85 | rendezvous point, and establishes a rendezvous circuit. [rendezvous 86 | setup.] 87 | 88 | 8. Alice connects to the Introduction point via Tor, and tells it about 89 | her rendezvous point. (Encrypted to Bob.) [Introduction 1] 90 | 91 | 9. The Introduction point passes this on to Bob's OP via Tor, along the 92 | introduction circuit. [Introduction 2] 93 | 94 | 10. Bob's OP decides whether to connect to Alice, and if so, creates a 95 | circuit to Alice's RP via Tor. Establishes a shared circuit. 96 | [Rendezvous 1] 97 | 98 | 11. The Rendezvous point forwards Bob's confirmation to Alice's OP. 99 | [Rendezvous 2] 100 | 101 | 12. Alice's OP sends begin cells to Bob's OP. [Connection] 102 | 103 | 0.3. Constants and new cell types 104 | 105 | Relay cell types 106 | 32 -- RELAY_COMMAND_ESTABLISH_INTRO 107 | 33 -- RELAY_COMMAND_ESTABLISH_RENDEZVOUS 108 | 34 -- RELAY_COMMAND_INTRODUCE1 109 | 35 -- RELAY_COMMAND_INTRODUCE2 110 | 36 -- RELAY_COMMAND_RENDEZVOUS1 111 | 37 -- RELAY_COMMAND_RENDEZVOUS2 112 | 38 -- RELAY_COMMAND_INTRO_ESTABLISHED 113 | 39 -- RELAY_COMMAND_RENDEZVOUS_ESTABLISHED 114 | 40 -- RELAY_COMMAND_INTRODUCE_ACK 115 | 116 | 0.4. Version overview 117 | 118 | There are several parts in the hidden service protocol that have 119 | changed over time, each of them having its own version number, whereas 120 | other parts remained the same. The following list of potentially 121 | versioned protocol parts should help reduce some confusion: 122 | 123 | - Hidden service descriptor: the binary-based v0 was the default for a 124 | long time, and an ASCII-based v2 has been added by proposal 114. The 125 | v0 descriptor format has been deprecated in 0.2.2.1-alpha. See 1.3. 126 | 127 | - Hidden service descriptor propagation mechanism: currently related to 128 | the hidden service descriptor version -- v0 publishes to the original 129 | hs directory authorities, whereas v2 publishes to a rotating subset 130 | of relays with the "HSDir" flag; see 1.4 and 1.6. 131 | 132 | - Introduction protocol for how to generate an introduction cell: 133 | v0 specified a nickname for the rendezvous point and assumed the 134 | relay would know about it, whereas v2 now specifies IP address, 135 | port, and onion key so the relay doesn't need to already recognize 136 | it. See 1.8. 137 | 138 | 1. The Protocol 139 | 140 | 1.1. Bob configures his local OP. 141 | 142 | We do not specify a format for the OP configuration file. However, 143 | OPs SHOULD allow Bob to provide more than one advertised service 144 | per OP, and MUST allow Bob to specify one or more virtual ports per 145 | service. Bob provides a mapping from each of these virtual ports 146 | to a local IP:Port pair. 147 | 148 | 1.2. Bob's OP establishes his introduction points. 149 | 150 | The first time the OP provides an advertised service, it generates 151 | a public/private keypair (stored locally). 152 | 153 | The OP chooses a small number of Tor servers as introduction points. 154 | The OP establishes a new introduction circuit to each introduction 155 | point. These circuits MUST NOT be used for anything but hidden service 156 | introduction. To establish the introduction, Bob sends a 157 | RELAY_COMMAND_ESTABLISH_INTRO cell, containing: 158 | 159 | KL Key length [2 octets] 160 | PK Bob's public key or service key [KL octets] 161 | HS Hash of session info [20 octets] 162 | SIG Signature of above information [variable] 163 | 164 | KL is the length of PK, in octets. 165 | 166 | To prevent replay attacks, the HS field contains a SHA-1 hash based on the 167 | shared secret KH between Bob's OP and the introduction point, as 168 | follows: 169 | HS = H(KH | "INTRODUCE") 170 | That is: 171 | HS = H(KH | [49 4E 54 52 4F 44 55 43 45]) 172 | (KH, as specified in tor-spec.txt, is H(g^xy | [00]) .) 173 | 174 | Upon receiving such a cell, the OR first checks that the signature is 175 | correct with the included public key. If so, it checks whether HS is 176 | correct given the shared state between Bob's OP and the OR. If either 177 | check fails, the OP discards the cell; otherwise, it associates the 178 | circuit with Bob's public key, and dissociates any other circuits 179 | currently associated with PK. On success, the OR sends Bob a 180 | RELAY_COMMAND_INTRO_ESTABLISHED cell with an empty payload. 181 | 182 | Bob's OP uses either Bob's public key or a freshly generated, single-use 183 | service key in the RELAY_COMMAND_ESTABLISH_INTRO cell, depending on the 184 | configured hidden service descriptor version. The public key is used for 185 | v0 descriptors, the service key for v2 descriptors. In the latter case, the 186 | service keys of all introduction points are included in the v2 hidden 187 | service descriptor together with the other introduction point information. 188 | The reason is that the introduction point does not need to and therefore 189 | should not know for which hidden service it works, so as to prevent it from 190 | tracking the hidden service's activity. If the hidden service is configured 191 | to publish both v0 and v2 descriptors, two separate sets of introduction 192 | points are established. 193 | 194 | 1.3. Bob's OP generates service descriptors. 195 | 196 | For versions before 0.2.2.1-alpha, Bob's OP periodically generates and 197 | publishes a descriptor of type "V0". 198 | 199 | The "V0" descriptor contains: 200 | 201 | KL Key length [2 octets] 202 | PK Bob's public key [KL octets] 203 | TS A timestamp [4 octets] 204 | NI Number of introduction points [2 octets] 205 | Ipt A list of NUL-terminated ORs [variable] 206 | SIG Signature of above fields [variable] 207 | 208 | TS is the number of seconds elapsed since Jan 1, 1970. 209 | 210 | The members of Ipt may be either (a) nicknames, or (b) identity key 211 | digests, encoded in hex, and prefixed with a '$'. Clients must 212 | accept both forms. Services must only generate the second form. 213 | Once 0.0.9.x is obsoleted, we can drop the first form. 214 | 215 | [It's ok for Bob to advertise 0 introduction points. He might want 216 | to do that if he previously advertised some introduction points, 217 | and now he doesn't have any. -RD] 218 | 219 | Beginning with 0.2.0.10-alpha, Bob's OP encodes "V2" descriptors in 220 | addition to (or instead of) "V0" descriptors. The format of a "V2" 221 | descriptor is as follows: 222 | 223 | "rendezvous-service-descriptor" SP descriptor-id NL 224 | 225 | [At start, exactly once] 226 | 227 | Indicates the beginning of the descriptor. "descriptor-id" is a 228 | periodically changing identifier of 160 bits formatted as 32 base32 229 | chars that is calculated by the hidden service and its clients. The 230 | "descriptor-id" is calculated by performing the following operation: 231 | 232 | descriptor-id = 233 | H(permanent-id | H(time-period | descriptor-cookie | replica)) 234 | 235 | "permanent-id" is the permanent identifier of the hidden service, 236 | consisting of 80 bits. It can be calculated by computing the hash value 237 | of the public hidden service key and truncating after the first 80 bits: 238 | 239 | permanent-id = H(public-key)[:10] 240 | 241 | Note: If Bob's OP has "stealth" authorization enabled (see Section 2.2), 242 | it uses the client key in place of the public hidden service key. 243 | 244 | "H(time-period | descriptor-cookie | replica)" is the (possibly 245 | secret) id part that is necessary to verify that the hidden service is 246 | the true originator of this descriptor and that is therefore contained 247 | in the descriptor, too. The descriptor ID can only be created by the 248 | hidden service and its clients, but the "signature" below can only be 249 | created by the service. 250 | 251 | "time-period" changes periodically as a function of time and 252 | "permanent-id". The current value for "time-period" can be calculated 253 | using the following formula: 254 | 255 | time-period = (current-time + permanent-id-byte * 86400 / 256) 256 | / 86400 257 | 258 | "current-time" contains the current system time in seconds since 259 | 1970-01-01 00:00, e.g. 1188241957. "permanent-id-byte" is the first 260 | (unsigned) byte of the permanent identifier (which is in network 261 | order), e.g. 143. Adding the product of "permanent-id-byte" and 262 | 86400 (seconds per day), divided by 256, prevents "time-period" from 263 | changing for all descriptors at the same time of the day. The result 264 | of the overall operation is a (network-ordered) 32-bit integer, e.g. 265 | 13753 or 0x000035B9 with the example values given above. 266 | 267 | "descriptor-cookie" is an optional secret password of 128 bits that 268 | is shared between the hidden service provider and its clients. If the 269 | descriptor-cookie is left out, the input to the hash function is 128 270 | bits shorter. 271 | 272 | "replica" denotes the number of the replica. A service publishes 273 | multiple descriptors with different descriptor IDs in order to 274 | distribute them to different places on the ring. 275 | 276 | "version" SP version-number NL 277 | 278 | [Exactly once] 279 | 280 | The version number of this descriptor's format. Version numbers are a 281 | positive integer. 282 | 283 | "permanent-key" NL a public key in PEM format 284 | 285 | [Exactly once] 286 | 287 | The public key of the hidden service which is required to verify the 288 | "descriptor-id" and the "signature". 289 | 290 | "secret-id-part" SP secret-id-part NL 291 | 292 | [Exactly once] 293 | 294 | The result of the following operation as explained above, formatted as 295 | 32 base32 chars. Using this secret id part, everyone can verify that 296 | the signed descriptor belongs to "descriptor-id". 297 | 298 | secret-id-part = H(time-period | descriptor-cookie | replica) 299 | 300 | "publication-time" SP YYYY-MM-DD HH:MM:SS NL 301 | 302 | [Exactly once] 303 | 304 | A timestamp when this descriptor has been created. It should be 305 | rounded down to the nearest hour. 306 | 307 | "protocol-versions" SP version-string NL 308 | 309 | [Exactly once] 310 | 311 | A comma-separated list of recognized and permitted version numbers 312 | for use in INTRODUCE cells; these versions are described in section 313 | 1.8 below. Version numbers are positive integers. 314 | 315 | "introduction-points" NL encrypted-string 316 | 317 | [At most once] 318 | 319 | A list of introduction points. If the optional "descriptor-cookie" is 320 | used, this list is encrypted with AES in CTR mode with a random 321 | initialization vector of 128 bits that is written to 322 | the beginning of the encrypted string, and the "descriptor-cookie" as 323 | secret key of 128 bits length. 324 | 325 | The string containing the introduction point data (either encrypted 326 | or not) is encoded in base64, and surrounded with 327 | "-----BEGIN MESSAGE-----" and "-----END MESSAGE-----". 328 | 329 | The unencrypted string may begin with: 330 | 331 | "service-authentication" auth-type auth-data NL 332 | 333 | [Any number] 334 | 335 | The service-specific authentication data can be used to perform 336 | client authentication. This data is independent of the selected 337 | introduction point as opposed to "intro-authentication" below. The 338 | format of auth-data (base64-encoded or PEM format) depends on 339 | auth-type. See section 2 of this document for details on auth 340 | mechanisms. 341 | 342 | Subsequently, an arbitrary number of introduction point entries may 343 | follow, each containing the following data: 344 | 345 | "introduction-point" SP identifier NL 346 | 347 | [At start, exactly once] 348 | 349 | The identifier of this introduction point: the base32 encoded 350 | hash of this introduction point's identity key. 351 | 352 | "ip-address" SP ip4 NL 353 | 354 | [Exactly once] 355 | 356 | The IP address of this introduction point. 357 | 358 | "onion-port" SP port NL 359 | 360 | [Exactly once] 361 | 362 | The TCP port on which the introduction point is listening for 363 | incoming onion requests. 364 | 365 | "onion-key" NL a public key in PEM format 366 | 367 | [Exactly once] 368 | 369 | The public key that can be used to encrypt messages to this 370 | introduction point. 371 | 372 | "service-key" NL a public key in PEM format 373 | 374 | [Exactly once] 375 | 376 | The public key that can be used to encrypt messages to the hidden 377 | service. 378 | 379 | "intro-authentication" auth-type auth-data NL 380 | 381 | [Any number] 382 | 383 | The introduction-point-specific authentication data can be used 384 | to perform client authentication. This data depends on the 385 | selected introduction point as opposed to "service-authentication" 386 | above. The format of auth-data (base64-encoded or PEM format) 387 | depends on auth-type. See section 2 of this document for details 388 | on auth mechanisms. 389 | 390 | (This ends the fields in the encrypted portion of the descriptor.) 391 | 392 | [It's ok for Bob to advertise 0 introduction points. He might want 393 | to do that if he previously advertised some introduction points, 394 | and now he doesn't have any. -RD] 395 | 396 | "signature" NL signature-string 397 | 398 | [At end, exactly once] 399 | 400 | A signature of all fields above with the private key of the hidden 401 | service. 402 | 403 | 1.3.1. Other descriptor formats we don't use. 404 | 405 | Support for the V0 descriptor format was dropped in 0.2.2.0-alpha-dev: 406 | 407 | KL Key length [2 octets] 408 | PK Bob's public key [KL octets] 409 | TS A timestamp [4 octets] 410 | NI Number of introduction points [2 octets] 411 | Ipt A list of NUL-terminated ORs [variable] 412 | SIG Signature of above fields [variable] 413 | 414 | KL is the length of PK, in octets. 415 | TS is the number of seconds elapsed since Jan 1, 1970. 416 | 417 | The members of Ipt may be either (a) nicknames, or (b) identity key 418 | digests, encoded in hex, and prefixed with a '$'. 419 | 420 | The V1 descriptor format was understood and accepted from 421 | 0.1.1.5-alpha-cvs to 0.2.0.6-alpha-dev, but no Tors generated it and 422 | it was removed: 423 | 424 | V Format byte: set to 255 [1 octet] 425 | V Version byte: set to 1 [1 octet] 426 | KL Key length [2 octets] 427 | PK Bob's public key [KL octets] 428 | TS A timestamp [4 octets] 429 | PROTO Protocol versions: bitmask [2 octets] 430 | NI Number of introduction points [2 octets] 431 | For each introduction point: (as in INTRODUCE2 cells) 432 | IP Introduction point's address [4 octets] 433 | PORT Introduction point's OR port [2 octets] 434 | ID Introduction point identity ID [20 octets] 435 | KLEN Length of onion key [2 octets] 436 | KEY Introduction point onion key [KLEN octets] 437 | SIG Signature of above fields [variable] 438 | 439 | A hypothetical "V1" descriptor, that has never been used but might 440 | be useful for historical reasons, contains: 441 | 442 | V Format byte: set to 255 [1 octet] 443 | V Version byte: set to 1 [1 octet] 444 | KL Key length [2 octets] 445 | PK Bob's public key [KL octets] 446 | TS A timestamp [4 octets] 447 | PROTO Rendezvous protocol versions: bitmask [2 octets] 448 | NA Number of auth mechanisms accepted [1 octet] 449 | For each auth mechanism: 450 | AUTHT The auth type that is supported [2 octets] 451 | AUTHL Length of auth data [1 octet] 452 | AUTHD Auth data [variable] 453 | NI Number of introduction points [2 octets] 454 | For each introduction point: (as in INTRODUCE2 cells) 455 | ATYPE An address type (typically 4) [1 octet] 456 | ADDR Introduction point's IP address [4 or 16 octets] 457 | PORT Introduction point's OR port [2 octets] 458 | AUTHT The auth type that is supported [2 octets] 459 | AUTHL Length of auth data [1 octet] 460 | AUTHD Auth data [variable] 461 | ID Introduction point identity ID [20 octets] 462 | KLEN Length of onion key [2 octets] 463 | KEY Introduction point onion key [KLEN octets] 464 | SIG Signature of above fields [variable] 465 | 466 | AUTHT specifies which authentication/authorization mechanism is 467 | required by the hidden service or the introduction point. AUTHD 468 | is arbitrary data that can be associated with an auth approach. 469 | Currently only AUTHT of [00 00] is supported, with an AUTHL of 0. 470 | See section 2 of this document for details on auth mechanisms. 471 | 472 | 1.4. Bob's OP advertises his service descriptor(s). 473 | 474 | Bob's OP advertises his service descriptor to a fixed set of v0 hidden 475 | service directory servers and/or a changing subset of all v2 hidden service 476 | directories. 477 | 478 | For versions before 0.2.2.1-alpha, Bob's OP opens a stream to each v0 479 | directory server's directory port via Tor. (He may re-use old circuits for 480 | this.) Over this stream, Bob's OP makes an HTTP 'POST' request, to a URL 481 | "/tor/rendezvous/publish" relative to the directory server's root, 482 | containing as its body Bob's service descriptor. 483 | 484 | Upon receiving a descriptor, the directory server checks the signature, 485 | and discards the descriptor if the signature does not match the enclosed 486 | public key. Next, the directory server checks the timestamp. If the 487 | timestamp is more than 24 hours in the past or more than 1 hour in the 488 | future, or the directory server already has a newer descriptor with the 489 | same public key, the server discards the descriptor. Otherwise, the 490 | server discards any older descriptors with the same public key and 491 | version format, and associates the new descriptor with the public key. 492 | The directory server remembers this descriptor for at least 24 hours 493 | after its timestamp. At least every 18 hours, Bob's OP uploads a 494 | fresh descriptor. 495 | 496 | If Bob's OP is configured to publish v2 descriptors, it does so to a 497 | changing subset of all v2 hidden service directories instead of the 498 | authoritative directory servers. Therefore, Bob's OP opens a stream via 499 | Tor to each responsible hidden service directory. (He may re-use old 500 | circuits for this.) Over this stream, Bob's OP makes an HTTP 'POST' 501 | request to a URL "/tor/rendezvous2/publish" relative to the hidden service 502 | directory's root, containing as its body Bob's service descriptor. 503 | 504 | [XXX022 Reusing old circuits for HS dir posts is very bad. Do we really 505 | do that? --RR] 506 | 507 | At any time, there are 6 hidden service directories responsible for 508 | keeping replicas of a descriptor; they consist of 2 sets of 3 hidden 509 | service directories with consecutive onion IDs. Bob's OP learns about 510 | the complete list of hidden service directories by filtering the 511 | consensus status document received from the directory authorities. A 512 | hidden service directory is deemed responsible for a descriptor ID if 513 | it has the HSDir flag and its identity digest is one of the first three 514 | identity digests of HSDir relays following the descriptor ID in a 515 | circular list. A hidden service directory will only accept a descriptor 516 | whose timestamp is no more than three days before or one day after the 517 | current time according to the directory's clock. 518 | 519 | Bob's OP publishes a new v2 descriptor once an hour or whenever its 520 | content changes. V2 descriptors can be found by clients within a given 521 | time period of 24 hours, after which they change their ID as described 522 | under 1.3. If a published descriptor would be valid for less than 60 523 | minutes (= 2 x 30 minutes to allow the server to be 30 minutes behind 524 | and the client 30 minutes ahead), Bob's OP publishes the descriptor 525 | under the ID of both, the current and the next publication period. 526 | 527 | 1.5. Alice receives a z.onion address. 528 | 529 | When Alice receives a pointer to a location-hidden service, it is as a 530 | hostname of the form "z.onion", where z is a base32 encoding of a 531 | 10-octet hash of Bob's service's public key, computed as follows: 532 | 533 | 1. Let H = H(PK). 534 | 2. Let H' = the first 80 bits of H, considering each octet from 535 | most significant bit to least significant bit. 536 | 3. Generate a 16-character encoding of H', using base32 as defined 537 | in RFC 4648. 538 | 539 | (We only use 80 bits instead of the 160 bits from SHA1 because we 540 | don't need to worry about arbitrary collisions, and because it will 541 | make handling the url's more convenient.) 542 | 543 | [Yes, numbers are allowed at the beginning. See RFC 1123. -NM] 544 | 545 | 1.6. Alice's OP retrieves a service descriptor. 546 | 547 | Alice's OP fetches the service descriptor from the fixed set of v0 hidden 548 | service directory servers and/or a changing subset of all v2 hidden service 549 | directories. 550 | 551 | For versions before 0.2.2.1-alpha, Alice's OP opens a stream to a directory 552 | server via Tor, and makes an HTTP GET request for the document 553 | '/tor/rendezvous/', where '' is replaced with the encoding of Bob's 554 | public key as described above. (She may re-use old circuits for this.) The 555 | directory replies with a 404 HTTP response if it does not recognize , 556 | and otherwise returns Bob's most recently uploaded service descriptor. 557 | 558 | If Alice's OP receives a 404 response, it tries the other directory 559 | servers, and only fails the lookup if none recognize the public key hash. 560 | 561 | Upon receiving a service descriptor, Alice verifies with the same process 562 | as the directory server uses, described above in section 1.4. 563 | 564 | The directory server gives a 400 response if it cannot understand Alice's 565 | request. 566 | 567 | Alice should cache the descriptor locally, but should not use 568 | descriptors that are more than 24 hours older than their timestamp. 569 | [Caching may make her partitionable, but she fetched it anonymously, 570 | and we can't very well *not* cache it. -RD] 571 | 572 | If Alice's OP is running 0.2.1.10-alpha or higher, it fetches v2 hidden 573 | service descriptors. Versions before 0.2.2.1-alpha are fetching both v0 and 574 | v2 descriptors in parallel. Similar to the description in section 1.4, 575 | Alice's OP fetches a v2 descriptor from a randomly chosen hidden service 576 | directory out of the changing subset of 6 nodes. If the request is 577 | unsuccessful, Alice retries the other remaining responsible hidden service 578 | directories in a random order. Alice relies on Bob to care about a potential 579 | clock skew between the two by possibly storing two sets of descriptors (see 580 | end of section 1.4). 581 | 582 | Alice's OP opens a stream via Tor to the chosen v2 hidden service 583 | directory. (She may re-use old circuits for this.) Over this stream, 584 | Alice's OP makes an HTTP 'GET' request for the document 585 | "/tor/rendezvous2/", where z is replaced with the encoding of the 586 | descriptor ID. The directory replies with a 404 HTTP response if it does 587 | not recognize , and otherwise returns Bob's most recently uploaded 588 | service descriptor. 589 | 590 | 1.7. Alice's OP establishes a rendezvous point. 591 | 592 | When Alice requests a connection to a given location-hidden service, 593 | and Alice's OP does not have an established circuit to that service, 594 | the OP builds a rendezvous circuit. It does this by establishing 595 | a circuit to a randomly chosen OR, and sending a 596 | RELAY_COMMAND_ESTABLISH_RENDEZVOUS cell to that OR. The body of that cell 597 | contains: 598 | 599 | RC Rendezvous cookie [20 octets] 600 | 601 | The rendezvous cookie is an arbitrary 20-byte value, chosen randomly by 602 | Alice's OP. Alice SHOULD choose a new rendezvous cookie for each new 603 | connection attempt. 604 | 605 | Upon receiving a RELAY_COMMAND_ESTABLISH_RENDEZVOUS cell, the OR associates 606 | the RC with the circuit that sent it. It replies to Alice with an empty 607 | RELAY_COMMAND_RENDEZVOUS_ESTABLISHED cell to indicate success. 608 | 609 | Alice's OP MUST NOT use the circuit which sent the cell for any purpose 610 | other than rendezvous with the given location-hidden service. 611 | 612 | 1.8. Introduction: from Alice's OP to Introduction Point 613 | 614 | Alice builds a separate circuit to one of Bob's chosen introduction 615 | points, and sends it a RELAY_COMMAND_INTRODUCE1 cell containing: 616 | 617 | Cleartext 618 | PK_ID Identifier for Bob's PK [20 octets] 619 | Encrypted to Bob's PK: (in the v0 intro protocol) 620 | RP Rendezvous point's nickname [20 octets] 621 | RC Rendezvous cookie [20 octets] 622 | g^x Diffie-Hellman data, part 1 [128 octets] 623 | OR (in the v1 intro protocol) 624 | VER Version byte: set to 1. [1 octet] 625 | RP Rendezvous point nick or ID [42 octets] 626 | RC Rendezvous cookie [20 octets] 627 | g^x Diffie-Hellman data, part 1 [128 octets] 628 | OR (in the v2 intro protocol) 629 | VER Version byte: set to 2. [1 octet] 630 | IP Rendezvous point's address [4 octets] 631 | PORT Rendezvous point's OR port [2 octets] 632 | ID Rendezvous point identity ID [20 octets] 633 | KLEN Length of onion key [2 octets] 634 | KEY Rendezvous point onion key [KLEN octets] 635 | RC Rendezvous cookie [20 octets] 636 | g^x Diffie-Hellman data, part 1 [128 octets] 637 | OR (in the v3 intro protocol) 638 | VER Version byte: set to 3. [1 octet] 639 | AUTHT The auth type that is used [1 octet] 640 | If AUTHT != [00]: 641 | AUTHL Length of auth data [2 octets] 642 | AUTHD Auth data [variable] 643 | TS A timestamp [4 octets] 644 | IP Rendezvous point's address [4 octets] 645 | PORT Rendezvous point's OR port [2 octets] 646 | ID Rendezvous point identity ID [20 octets] 647 | KLEN Length of onion key [2 octets] 648 | KEY Rendezvous point onion key [KLEN octets] 649 | RC Rendezvous cookie [20 octets] 650 | g^x Diffie-Hellman data, part 1 [128 octets] 651 | 652 | PK_ID is the hash of Bob's public key or the service key, depending on the 653 | hidden service descriptor version. In case of a v0 descriptor, Alice's OP 654 | uses Bob's public key. If Alice has downloaded a v2 descriptor, she uses 655 | the contained public key ("service-key"). 656 | 657 | RP is NUL-padded and terminated. In version 0 of the intro protocol, RP 658 | must contain a nickname. In version 1, it must contain EITHER a nickname or 659 | an identity key digest that is encoded in hex and prefixed with a '$'. 660 | 661 | The hybrid encryption to Bob's PK works just like the hybrid 662 | encryption in CREATE cells (see tor-spec). Thus the payload of the 663 | version 0 RELAY_COMMAND_INTRODUCE1 cell on the wire will contain 664 | 20+42+16+20+20+128=246 bytes, and the version 1 and version 2 665 | introduction formats have other sizes. 666 | 667 | Through Tor 0.2.0.6-alpha, clients only generated the v0 introduction 668 | format, whereas hidden services have understood and accepted v0, 669 | v1, and v2 since 0.1.1.x. As of Tor 0.2.0.7-alpha and 0.1.2.18, 670 | clients switched to using the v2 intro format. 671 | 672 | The Timestampe (TS) field is no longer used in Tor 0.2.3.9-alpha and 673 | later. Clients MAY refrain from sending it; see the 674 | "Support022HiddenServices" parameter in dir-spec.txt. Clients SHOULD 675 | NOT send a precise timestamp, and should instead round to the nearest 676 | 10 minutes. 677 | 678 | 1.9. Introduction: From the Introduction Point to Bob's OP 679 | 680 | If the Introduction Point recognizes PK_ID as a public key which has 681 | established a circuit for introductions as in 1.2 above, it sends the body 682 | of the cell in a new RELAY_COMMAND_INTRODUCE2 cell down the corresponding 683 | circuit. (If the PK_ID is unrecognized, the RELAY_COMMAND_INTRODUCE1 cell is 684 | discarded.) 685 | 686 | After sending the RELAY_COMMAND_INTRODUCE2 cell to Bob, the OR replies to 687 | Alice with an empty RELAY_COMMAND_INTRODUCE_ACK cell. If no 688 | RELAY_COMMAND_INTRODUCE2 cell can be sent, the OR replies to Alice with a 689 | non-empty cell to indicate an error. (The semantics of the cell body may be 690 | determined later; the current implementation sends a single '1' byte on 691 | failure.) 692 | 693 | When Bob's OP receives the RELAY_COMMAND_INTRODUCE2 cell, it first checks 694 | for a replay. Because of the (undesirable!) malleability of the hybrid 695 | encryption, Bob's OP should only check whether the RSA-encrypted part is 696 | replayed. It does this by keeping, for each introduction key, a list of 697 | cryptographic digests of all the RSA-encrypted parts of the INTRODUCE2 698 | cells that it's seen, and dropping any INTRODUCE2 cell whose RSA-encrypted 699 | part it has seen before. When Bob's OP stops using a given introduction 700 | key, it drops the replay cache corresponding to that key. 701 | 702 | (Versions of Tor before 0.2.3.9-alpha used the timestamp in the INTRODUCE2 703 | cell to limit the lifetime of entries in the replay cache. This proved to 704 | be fragile, due to insufficiently synchronized clients.) 705 | 706 | Assuming that the cell has not been replayed, Bob's server decrypts it 707 | with the private key for the corresponding hidden service, and extracts the 708 | rendezvous point's nickname, the rendezvous cookie, and the value of g^x 709 | chosen by Alice. 710 | 711 | 1.10. Rendezvous 712 | 713 | Bob's OP builds a new Tor circuit ending at Alice's chosen rendezvous 714 | point, and sends a RELAY_COMMAND_RENDEZVOUS1 cell along this circuit, 715 | containing: 716 | RC Rendezvous cookie [20 octets] 717 | g^y Diffie-Hellman [128 octets] 718 | KH Handshake digest [20 octets] 719 | 720 | (Bob's OP MUST NOT use this circuit for any other purpose.) 721 | 722 | (By default, Bob builds a circuit of at least three hops, *not including* 723 | Alice's chosen rendezvous point.) 724 | 725 | If the RP recognizes RC, it relays the rest of the cell down the 726 | corresponding circuit in a RELAY_COMMAND_RENDEZVOUS2 cell, containing: 727 | 728 | g^y Diffie-Hellman [128 octets] 729 | KH Handshake digest [20 octets] 730 | 731 | (If the RP does not recognize the RC, it discards the cell and 732 | tears down the circuit.) 733 | 734 | When Alice's OP receives a RELAY_COMMAND_RENDEZVOUS2 cell on a circuit which 735 | has sent a RELAY_COMMAND_ESTABLISH_RENDEZVOUS cell but which has not yet 736 | received a reply, it uses g^y and H(g^xy) to complete the handshake as in 737 | the Tor circuit extend process: they establish a 60-octet string as 738 | K = SHA1(g^xy | [00]) | SHA1(g^xy | [01]) | SHA1(g^xy | [02]) 739 | and generate KH, Df, Db, Kf, and Kb as in the KDF-TOR key derivation 740 | approach documented in tor-spec.txt. 741 | 742 | As in the TAP handshake, if the KH value derived from KDF-Tor does not 743 | match the value in the RENDEZVOUS2 cell, the client must close the 744 | circuit. 745 | 746 | Subsequently, the rendezvous point passes relay cells, unchanged, from 747 | each of the two circuits to the other. When Alice's OP sends RELAY cells 748 | along the circuit, it authenticates with Df, and encrypts them with the 749 | Kf, then with all of the keys for the ORs in Alice's side of the circuit; 750 | and when Alice's OP receives RELAY cells from the circuit, it decrypts 751 | them with the keys for the ORs in Alice's side of the circuit, then 752 | decrypts them with Kb, and checks integrity with Db. Bob's OP does the 753 | same, with Kf and Kb interchanged. 754 | 755 | 1.11. Creating streams 756 | 757 | To open TCP connections to Bob's location-hidden service, Alice's OP sends 758 | a RELAY_COMMAND_BEGIN cell along the established circuit, using the special 759 | address "", and a chosen port. Bob's OP chooses a destination IP and 760 | port, based on the configuration of the service connected to the circuit, 761 | and opens a TCP stream. From then on, Bob's OP treats the stream as an 762 | ordinary exit connection. 763 | [ Except he doesn't include addr in the connected cell or the end 764 | cell. -RD] 765 | 766 | Alice MAY send multiple RELAY_COMMAND_BEGIN cells along the circuit, to open 767 | multiple streams to Bob. Alice SHOULD NOT send RELAY_COMMAND_BEGIN cells 768 | for any other address along her circuit to Bob; if she does, Bob MUST reject 769 | them. 770 | 771 | 1.12. Closing streams 772 | 773 | The payload of a RELAY_END cell begins with a single 'reason' byte to 774 | describe why the stream is closing, plus optional data (depending on the 775 | reason.) These can be found in section 6.3 of tor-spec. The following 776 | describes some of the hidden service related reasons. 777 | 778 | 1 -- REASON_MISC 779 | 780 | Catch-all for unlisted reasons. Shouldn't happen much in practice. 781 | 782 | 2 -- REASON_RESOLVEFAILED 783 | 784 | Tor tried to fetch the hidden service descriptor from the hsdirs but 785 | none of them had it. This implies that the hidden service has not 786 | been running in the past 24 hours. 787 | 788 | 3 -- REASON_CONNECTREFUSED 789 | 790 | Every step of the rendezvous worked great, and that the hidden 791 | service is indeed up and running and configured to use the virtual 792 | port you asked for, but there was nothing listening on the other end 793 | of that virtual port. For example, the HS's Tor client is running 794 | fine but its apache service is down. 795 | 796 | 4 -- REASON_EXITPOLICY 797 | 798 | The destination port that you tried is not configured on the hidden 799 | service side. That is, the hidden service was up and reachable, but 800 | it isn't listening on this port. Since Tor 0.2.6.2-alpha and later 801 | hidden service don't send this error code; instead they send back an 802 | END cell with reason DONE reason then close the circuit on you. This 803 | behavior can be controlled by a config option. 804 | 805 | 5 -- REASON_DESTROY 806 | 807 | The circuit closed before you could get a response back -- transient 808 | failure, e.g. a relay went down unexpectedly. Trying again might 809 | work. 810 | 811 | 6 -- REASON_DONE 812 | 813 | Anonymized TCP connection was closed. If you get an END cell with 814 | reason DONE, *before* you've gotten your CONNECTED cell, that 815 | indicates a similar situation to EXITPOLICY, but the hidden service 816 | is running 0.2.6.2-alpha or later, and it has now closed the circuit 817 | on you. 818 | 819 | 7 -- REASON_TIMEOUT 820 | 821 | Either like CONNECTREFUSED above but connect() got the ETIMEDOUT 822 | errno, or the client-side timeout of 120 seconds kicked in and we 823 | gave up. 824 | 825 | 8 -- REASON_NOROUTE 826 | 827 | Like CONNECTREFUSED except the errno at the hidden service when 828 | trying to connect() to the service was ENETUNREACH, EHOSTUNREACH, 829 | EACCES, or EPERM. 830 | 831 | 10 -- REASON_INTERNAL 832 | 833 | Internal error inside the Tor client -- hopefully you will not see 834 | this much. Please report if you do! 835 | 836 | 12 -- REASON_CONNRESET 837 | 838 | Like CONNECTREFUSED except the errno at the hidden service when 839 | trying to connect() to the service was ECONNRESET. 840 | 841 | 2. Authentication and authorization. 842 | 843 | The rendezvous protocol as described in Section 1 provides a few options 844 | for implementing client-side authorization. There are two steps in the 845 | rendezvous protocol that can be used for performing client authorization: 846 | when downloading and decrypting parts of the hidden service descriptor and 847 | at Bob's Tor client before contacting the rendezvous point. A service 848 | provider can restrict access to his service at these two points to 849 | authorized clients only. 850 | 851 | There are currently two authorization protocols specified that are 852 | described in more detail below: 853 | 854 | 1. The first protocol allows a service provider to restrict access 855 | to clients with a previously received secret key only, but does not 856 | attempt to hide service activity from others. 857 | 858 | 2. The second protocol, albeit being feasible for a limited set of about 859 | 16 clients, performs client authorization and hides service activity 860 | from everyone but the authorized clients. 861 | 862 | 2.1. Service with large-scale client authorization 863 | 864 | The first client authorization protocol aims at performing access control 865 | while consuming as few additional resources as possible. This is the "basic" 866 | authorization protocol. A service provider should be able to permit access 867 | to a large number of clients while denying access for everyone else. 868 | However, the price for scalability is that the service won't be able to hide 869 | its activity from unauthorized or formerly authorized clients. 870 | 871 | The main idea of this protocol is to encrypt the introduction-point part 872 | in hidden service descriptors to authorized clients using symmetric keys. 873 | This ensures that nobody else but authorized clients can learn which 874 | introduction points a service currently uses, nor can someone send a 875 | valid INTRODUCE1 message without knowing the introduction key. Therefore, 876 | a subsequent authorization at the introduction point is not required. 877 | 878 | A service provider generates symmetric "descriptor cookies" for his 879 | clients and distributes them outside of Tor. The suggested key size is 880 | 128 bits, so that descriptor cookies can be encoded in 22 base64 chars 881 | (which can hold up to 22 * 5 = 132 bits, leaving 4 bits to encode the 882 | authorization type (here: "0") and allow a client to distinguish this 883 | authorization protocol from others like the one proposed below). 884 | Typically, the contact information for a hidden service using this 885 | authorization protocol looks like this: 886 | 887 | v2cbb2l4lsnpio4q.onion Ll3X7Xgz9eHGKCCnlFH0uz 888 | 889 | When generating a hidden service descriptor, the service encrypts the 890 | introduction-point part with a single randomly generated symmetric 891 | 128-bit session key using AES-CTR as described for v2 hidden service 892 | descriptors in rend-spec. Afterwards, the service encrypts the session 893 | key to all descriptor cookies using AES. Authorized client should be able 894 | to efficiently find the session key that is encrypted for him/her, so 895 | that 4 octet long client ID are generated consisting of descriptor cookie 896 | and initialization vector. Descriptors always contain a number of 897 | encrypted session keys that is a multiple of 16 by adding fake entries. 898 | Encrypted session keys are ordered by client IDs in order to conceal 899 | addition or removal of authorized clients by the service provider. 900 | 901 | ATYPE Authorization type: set to 1. [1 octet] 902 | ALEN Number of clients := 1 + ((clients - 1) div 16) [1 octet] 903 | for each symmetric descriptor cookie: 904 | ID Client ID: H(descriptor cookie | IV)[:4] [4 octets] 905 | SKEY Session key encrypted with descriptor cookie [16 octets] 906 | (end of client-specific part) 907 | RND Random data [(15 - ((clients - 1) mod 16)) * 20 octets] 908 | IV AES initialization vector [16 octets] 909 | IPOS Intro points, encrypted with session key [remaining octets] 910 | 911 | An authorized client needs to configure Tor to use the descriptor cookie 912 | when accessing the hidden service. Therefore, a user adds the contact 913 | information that she received from the service provider to her torrc 914 | file. Upon downloading a hidden service descriptor, Tor finds the 915 | encrypted introduction-point part and attempts to decrypt it using the 916 | configured descriptor cookie. (In the rare event of two or more client 917 | IDs being equal a client tries to decrypt all of them.) 918 | 919 | Upon sending the introduction, the client includes her descriptor cookie 920 | as auth type "1" in the INTRODUCE2 cell that she sends to the service. 921 | The hidden service checks whether the included descriptor cookie is 922 | authorized to access the service and either responds to the introduction 923 | request, or not. 924 | 925 | 2.2. Authorization for limited number of clients 926 | 927 | A second, more sophisticated client authorization protocol goes the extra 928 | mile of hiding service activity from unauthorized clients. This is the 929 | "stealth" authorization protocol. With all else being equal to the preceding 930 | authorization protocol, the second protocol publishes hidden service 931 | descriptors for each user separately and gets along with encrypting the 932 | introduction-point part of descriptors to a single client. This allows the 933 | service to stop publishing descriptors for removed clients. As long as a 934 | removed client cannot link descriptors issued for other clients to the 935 | service, it cannot derive service activity any more. The downside of this 936 | approach is limited scalability. Even though the distributed storage of 937 | descriptors (cf. proposal 114) tackles the problem of limited scalability to 938 | a certain extent, this protocol should not be used for services with more 939 | than 16 clients. (In fact, Tor should refuse to advertise services for more 940 | than this number of clients.) 941 | 942 | A hidden service generates an asymmetric "client key" and a symmetric 943 | "descriptor cookie" for each client. The client key is used as 944 | replacement for the service's permanent key, so that the service uses a 945 | different identity for each of his clients. The descriptor cookie is used 946 | to store descriptors at changing directory nodes that are unpredictable 947 | for anyone but service and client, to encrypt the introduction-point 948 | part, and to be included in INTRODUCE2 cells. Once the service has 949 | created client key and descriptor cookie, he tells them to the client 950 | outside of Tor. The contact information string looks similar to the one 951 | used by the preceding authorization protocol (with the only difference 952 | that it has "1" encoded as auth-type in the remaining 4 of 132 bits 953 | instead of "0" as before). 954 | 955 | When creating a hidden service descriptor for an authorized client, the 956 | hidden service uses the client key and descriptor cookie to compute 957 | secret ID part and descriptor ID: 958 | 959 | secret-id-part = H(time-period | descriptor-cookie | replica) 960 | 961 | descriptor-id = H(client-key[:10] | secret-id-part) 962 | 963 | The hidden service also replaces permanent-key in the descriptor with 964 | client-key and encrypts introduction-points with the descriptor cookie. 965 | 966 | ATYPE Authorization type: set to 2. [1 octet] 967 | IV AES initialization vector [16 octets] 968 | IPOS Intro points, encr. with descriptor cookie [remaining octets] 969 | 970 | When uploading descriptors, the hidden service needs to make sure that 971 | descriptors for different clients are not uploaded at the same time (cf. 972 | Section 1.1) which is also a limiting factor for the number of clients. 973 | 974 | When a client is requested to establish a connection to a hidden service 975 | it looks up whether it has any authorization data configured for that 976 | service. If the user has configured authorization data for authorization 977 | protocol "2", the descriptor ID is determined as described in the last 978 | paragraph. Upon receiving a descriptor, the client decrypts the 979 | introduction-point part using its descriptor cookie. Further, the client 980 | includes its descriptor cookie as auth-type "2" in INTRODUCE2 cells that 981 | it sends to the service. 982 | 983 | 2.3. Hidden service configuration 984 | 985 | A hidden service that is meant to perform client authorization adds a 986 | new option HiddenServiceAuthorizeClient to its hidden service 987 | configuration. This option contains the authorization type which is 988 | either "basic" for the protocol described in 2.1 or "stealth" for the 989 | protocol in 2.2 and a comma-separated list of human-readable client 990 | names, so that Tor can create authorization data for these clients: 991 | 992 | HiddenServiceAuthorizeClient auth-type client-name,client-name,... 993 | 994 | If this option is configured, HiddenServiceVersion is automatically 995 | reconfigured to contain only version numbers of 2 or higher. There is 996 | a maximum of 512 client names for basic auth and a maximum of 16 for 997 | stealth auth. 998 | 999 | Tor stores all generated authorization data for the authorization 1000 | protocols described in Sections 2.1 and 2.2 in a new file using the 1001 | following file format: 1002 | 1003 | "client-name" human-readable client identifier NL 1004 | "descriptor-cookie" 128-bit key ^= 22 base64 chars NL 1005 | 1006 | If the authorization protocol of Section 2.2 is used, Tor also generates 1007 | and stores the following data: 1008 | 1009 | "client-key" NL a public key in PEM format 1010 | 1011 | 2.4. Client configuration 1012 | 1013 | To specify the cookie to use to access a given hidden service, 1014 | clients use the following syntax: 1015 | 1016 | HidServAuth onion-address auth-cookie [service-name]: 1017 | 1018 | Valid onion addresses contain 16 characters in a-z2-7 plus 1019 | ".onion", and valid auth cookies contain 22 characters in 1020 | A-Za-z0-9+/. The service name is only used for internal purposes, 1021 | e.g., for Tor controllers; nothing in Tor itself requires or uses 1022 | it. 1023 | 1024 | 1025 | 3. Hidden service directory operation 1026 | 1027 | This section has been introduced with the v2 hidden service descriptor 1028 | format. It describes all operations of the v2 hidden service descriptor 1029 | fetching and propagation mechanism that are required for the protocol 1030 | described in section 1 to succeed with v2 hidden service descriptors. 1031 | 1032 | 3.1. Configuring as hidden service directory 1033 | 1034 | Every onion router that has its directory port open can decide whether it 1035 | wants to store and serve hidden service descriptors. An onion router which 1036 | is configured as such includes the "hidden-service-dir" flag in its router 1037 | descriptors that it sends to directory authorities. 1038 | 1039 | The directory authorities include a new flag "HSDir" for routers that 1040 | decided to provide storage for hidden service descriptors and that 1041 | have been running for at least 24 hours. 1042 | 1043 | 3.2. Accepting publish requests 1044 | 1045 | Hidden service directory nodes accept publish requests for v2 hidden service 1046 | descriptors and store them to their local memory. (It is not necessary to 1047 | make descriptors persistent, because after restarting, the onion router 1048 | would not be accepted as a storing node anyway, because it has not been 1049 | running for at least 24 hours.) All requests and replies are formatted as 1050 | HTTP messages. Requests are initiated via BEGIN_DIR cells directed to 1051 | the router's directory port, and formatted as HTTP POST requests to the URL 1052 | "/tor/rendezvous2/publish" relative to the hidden service directory's root, 1053 | containing as its body a v2 service descriptor. 1054 | 1055 | A hidden service directory node parses every received descriptor and only 1056 | stores it when it thinks that it is responsible for storing that descriptor 1057 | based on its own routing table. See section 1.4 for more information on how 1058 | to determine responsibility for a certain descriptor ID. 1059 | 1060 | 3.3. Processing fetch requests 1061 | 1062 | Hidden service directory nodes process fetch requests for hidden service 1063 | descriptors by looking them up in their local memory. (They do not need to 1064 | determine if they are responsible for the passed ID, because it does no harm 1065 | if they deliver a descriptor for which they are not (any more) responsible.) 1066 | All requests and replies are formatted as HTTP messages. Requests are 1067 | initiated via BEGIN_DIR cells directed to the router's directory port, 1068 | and formatted as HTTP GET requests for the document "/tor/rendezvous2/", 1069 | where z is replaced with the encoding of the descriptor ID. 1070 | 1071 | -------------------------------------------------------------------------------- /src/cmd/announce.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "hstools" 5 | "io" 6 | "log" 7 | "net/http" 8 | "os" 9 | "strings" 10 | "sync" 11 | "time" 12 | 13 | "github.com/ActiveState/tail" 14 | "golang.org/x/net/websocket" 15 | ) 16 | 17 | var channelsLock sync.RWMutex 18 | var channels = make(map[*websocket.Conn]chan string) 19 | 20 | func WSServer(ws *websocket.Conn) { 21 | onion := make([]byte, 16) 22 | if _, err := io.ReadFull(ws, onion); err != nil { 23 | log.Println(err) 24 | return 25 | } 26 | log.Printf("%s", string(onion)) 27 | res, err := hstools.OnionToDescID(string(onion), time.Now()) 28 | if err != nil { 29 | log.Println(err) 30 | return 31 | } 32 | log.Printf("%s %s", hstools.ToBase32(res[0]), hstools.ToBase32(res[1])) 33 | announce := make(chan string) 34 | channelsLock.Lock() 35 | channels[ws] = announce 36 | channelsLock.Unlock() 37 | defer func() { 38 | channelsLock.Lock() 39 | delete(channels, ws) 40 | channelsLock.Unlock() 41 | }() 42 | for desc := range announce { 43 | if hstools.ToBase32(res[0]) != desc && hstools.ToBase32(res[1]) != desc { 44 | continue 45 | } 46 | if _, err := ws.Write([]byte("1")); err != nil { 47 | log.Println(err) 48 | return 49 | } 50 | } 51 | } 52 | 53 | func main() { 54 | defer tail.Cleanup() 55 | if len(os.Args) < 4 { 56 | log.Fatal("usage: announce cert.pem key.pem logfile") 57 | } 58 | 59 | http.Handle("/announce", websocket.Handler(WSServer)) 60 | go func() { 61 | log.Fatal(http.ListenAndServeTLS("0.0.0.0:14242", os.Args[1], os.Args[2], nil)) 62 | }() 63 | 64 | t, err := tail.TailFile(os.Args[3], tail.Config{ 65 | Location: &tail.SeekInfo{ 66 | Offset: 0, 67 | Whence: os.SEEK_END, 68 | }, 69 | MustExist: true, 70 | Follow: true, 71 | ReOpen: true, 72 | }) 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | for line := range t.Lines { 77 | if strings.Contains(line.Text, "Got a v2 rendezvous descriptor request for ID") { 78 | i := strings.Index(line.Text, `'"`) + 2 79 | log.Println(line.Text[i : i+32]) 80 | channelsLock.RLock() 81 | for _, ch := range channels { 82 | ch <- line.Text[i : i+32] 83 | } 84 | channelsLock.RUnlock() 85 | } 86 | } 87 | log.Fatal(t.Err()) 88 | } 89 | -------------------------------------------------------------------------------- /src/cmd/announce.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebSocket Test 7 | 52 | 63 | 64 | 65 | 66 |
67 | 68 | 69 | -------------------------------------------------------------------------------- /src/cmd/brute.go: -------------------------------------------------------------------------------- 1 | // +build manually 2 | 3 | // brute: bruteforce Identity Keys that will be the 6 HSDir for the given onion 4 | // at the given time, considering the given consensus state 5 | package main 6 | 7 | import ( 8 | "crypto/rsa" 9 | "crypto/x509" 10 | "encoding/pem" 11 | "hstools" 12 | "log" 13 | "math/big" 14 | "net/http" 15 | _ "net/http/pprof" 16 | "os" 17 | "runtime" 18 | "time" 19 | ) 20 | 21 | func fatalIfErr(err error) { 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | } 26 | 27 | func main() { 28 | runtime.GOMAXPROCS(runtime.NumCPU()) 29 | 30 | go func() { 31 | log.Println(http.ListenAndServe("localhost:6060", nil)) 32 | }() 33 | 34 | if len(os.Args) < 4 { 35 | log.Fatal("usage: brute consensus onion RFC3339time") 36 | } 37 | 38 | log.Println("[*] Computing HS descriptors...") 39 | t, err := time.Parse(time.RFC3339, os.Args[3]) 40 | fatalIfErr(err) 41 | desc, err := hstools.OnionToDescID(os.Args[2], t) 42 | fatalIfErr(err) 43 | var keyA, keyB hstools.Hash 44 | copy(keyA[:], desc[0]) 45 | copy(keyB[:], desc[1]) 46 | log.Printf(" Onion '%s' at time '%s'\n", os.Args[2], t) 47 | log.Println(" Descriptor A:", hstools.ToHex(keyA[:])) 48 | log.Println(" Descriptor B:", hstools.ToHex(keyB[:])) 49 | 50 | log.Println("[*] Loading consensus...") 51 | // Note: this should maybe also consider potential future HSDir 52 | c, err := hstools.ParseConsensus(os.Args[1]) 53 | fatalIfErr(err) 54 | hashring := hstools.NewHashring(hstools.HashesToIntSlice(c.K)) 55 | nextA := hstools.IntToHash(hashring.Next(new(big.Int).SetBytes(keyA[:]))) 56 | nextB := hstools.IntToHash(hashring.Next(new(big.Int).SetBytes(keyB[:]))) 57 | log.Println(" First HSDir A:", hstools.ToHex(nextA[:])) 58 | log.Println(" First HSDir B:", hstools.ToHex(nextB[:])) 59 | 60 | log.Println("[*] Starting bruteforce...") 61 | keysA, keysB := hstools.Brute(keyA, keyB, nextA, nextB, 3, 62 | runtime.NumCPU(), log.Println) 63 | 64 | log.Println("[*] Done!") 65 | 66 | for _, keys := range [][]*rsa.PrivateKey{keysA, keysB} { 67 | for i := 0; i < 3; i++ { 68 | if err := pem.Encode(os.Stderr, &pem.Block{ 69 | Type: "RSA PRIVATE KEY", 70 | Bytes: x509.MarshalPKCS1PrivateKey(keys[i]), 71 | }); err != nil { 72 | log.Fatal(err) 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/cmd/curiosity.go: -------------------------------------------------------------------------------- 1 | // +build manually 2 | 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "hstools" 9 | "log" 10 | "net/http" 11 | _ "net/http/pprof" 12 | "os" 13 | "runtime" 14 | 15 | "github.com/boltdb/bolt" 16 | ) 17 | 18 | func main() { 19 | runtime.GOMAXPROCS(runtime.NumCPU()) 20 | 21 | go func() { 22 | log.Println(http.ListenAndServe("localhost:6060", nil)) 23 | }() 24 | 25 | if len(os.Args) != 3 { 26 | log.Fatal("usage: curiosity keys.db {ip,key,colocated}") 27 | } 28 | 29 | keysDB, err := hstools.OpenKeysDb(os.Args[1]) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | keysDB.View(func(tx *bolt.Tx) error { 35 | switch os.Args[2] { 36 | case "ip": 37 | c := tx.Bucket([]byte("IPs")).Cursor() 38 | for k, v := c.First(); k != nil; k, v = c.Next() { 39 | var res hstools.IPMeta 40 | if err := json.Unmarshal(v, &res); err != nil { 41 | log.Fatal(err) 42 | } 43 | fmt.Printf("%d %s\n", len(res.Keys), k) 44 | } 45 | case "keys": 46 | c := tx.Bucket([]byte("Keys")).Cursor() 47 | for k, v := c.First(); k != nil; k, v = c.Next() { 48 | var res hstools.KeyMeta 49 | if err := json.Unmarshal(v, &res); err != nil { 50 | log.Fatal(err) 51 | } 52 | fmt.Printf("%d %s\n", len(res.IPs), hstools.ToHex(k)) 53 | } 54 | case "colocated": 55 | c := tx.Bucket([]byte("Keys")).Cursor() 56 | for k, _ := c.First(); k != nil; k, _ = c.Next() { 57 | coloNum, ips := hstools.ColocatedKeys(k, keysDB) 58 | fmt.Printf("%d keys on %d IPs - %s %v\n", 59 | coloNum, len(ips), hstools.ToHex(k), ips) 60 | } 61 | default: 62 | log.Fatal("usage: curiosity keys.db {ip,key,colocated}") 63 | } 64 | 65 | return nil 66 | }) 67 | 68 | if err := keysDB.Close(); err != nil { 69 | log.Fatal(err) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/cmd/grind.go: -------------------------------------------------------------------------------- 1 | // +build manually 2 | 3 | // grind: compute the average distance to 1st and 4th node and its average 4 | // deviation and output it as JSON lines 5 | package main 6 | 7 | import ( 8 | "bufio" 9 | "encoding/json" 10 | "hstools" 11 | "log" 12 | "net/http" 13 | _ "net/http/pprof" 14 | "os" 15 | "runtime" 16 | ) 17 | 18 | func fatalIfErr(err error) { 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | } 23 | 24 | func main() { 25 | runtime.GOMAXPROCS(runtime.NumCPU()) 26 | 27 | go func() { 28 | log.Println(http.ListenAndServe("localhost:6060", nil)) 29 | }() 30 | 31 | if len(os.Args) != 2 { 32 | log.Fatal("usage: grind pckcns.dat > stats.jsonl") 33 | } 34 | 35 | w := bufio.NewWriterSize(os.Stdout, 1024*1024) 36 | jsonEncoder := json.NewEncoder(w) 37 | r := hstools.NewPackReader(os.Args[1]) 38 | for i := 0; r.Load(); i++ { 39 | c := r.Consensus() 40 | h := hstools.NewHashring(hstools.HashesToIntSlice(c.K)) 41 | 42 | fatalIfErr(jsonEncoder.Encode(hstools.AnalyzedConsensus{ 43 | T: c.Time, 44 | Distance: hstools.AnalyzePartitionData(h.DistanceData()), 45 | Distance4: hstools.AnalyzePartitionData(h.Distance4Data()), 46 | })) 47 | 48 | if i%1000 == 0 { 49 | log.Println(hstools.HourToTime(c.Time)) 50 | } 51 | } 52 | fatalIfErr(r.Err()) 53 | fatalIfErr(w.Flush()) 54 | } 55 | -------------------------------------------------------------------------------- /src/cmd/lookmeup.go: -------------------------------------------------------------------------------- 1 | // +build manually 2 | 3 | // lookmeup: bruteforce a onion address that when looked up will have the given 4 | // HSDir, so that you can look it up and see it in the logs of the HSDir 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "crypto/rand" 10 | "hstools" 11 | "log" 12 | "math/big" 13 | "net/http" 14 | _ "net/http/pprof" 15 | "os" 16 | "runtime" 17 | "time" 18 | ) 19 | 20 | func fatalIfErr(err error) { 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | } 25 | 26 | func main() { 27 | runtime.GOMAXPROCS(runtime.NumCPU()) 28 | 29 | go func() { 30 | log.Println(http.ListenAndServe("localhost:6060", nil)) 31 | }() 32 | 33 | if len(os.Args) < 3 { 34 | log.Fatal("usage: lookmeup consensus fingerprint") 35 | } 36 | 37 | var hash hstools.Hash 38 | h, err := hstools.FromHex(os.Args[2]) 39 | fatalIfErr(err) 40 | copy(hash[:], h[:]) 41 | log.Println("[*] Your node is", hstools.ToHex(hash[:])) 42 | 43 | log.Println("[*] Loading consensus...") 44 | c, err := hstools.ParseConsensus(os.Args[1]) 45 | fatalIfErr(err) 46 | hashring := hstools.NewHashring(hstools.HashesToIntSlice(c.K)) 47 | hsDirInt := hashring.Prev(new(big.Int).SetBytes(hash[:])) // first HSDir 48 | hsDirInt = hashring.Prev(hsDirInt) // second HSDir 49 | hsDirInt = hashring.Prev(hsDirInt) // third HSDir 50 | hsDir := hstools.IntToHash(hsDirInt) 51 | log.Println(" Lowest HSDir:", hstools.ToHex(hsDir[:])) 52 | 53 | log.Println("[*] Starting bruteforce...") 54 | b := make([]byte, 10) 55 | for { 56 | _, err := rand.Read(b) 57 | fatalIfErr(err) 58 | onion := hstools.ToBase32(b) 59 | res, err := hstools.OnionToDescID(onion, time.Now()) 60 | fatalIfErr(err) 61 | if bytes.Compare(hsDir[:], res[0]) < 0 && bytes.Compare(res[0], hash[:]) < 0 { 62 | log.Println(" Here, use this:", onion+".onion") 63 | break 64 | } 65 | } 66 | 67 | log.Println("[*] Done!") 68 | } 69 | -------------------------------------------------------------------------------- /src/cmd/montecarlo.go: -------------------------------------------------------------------------------- 1 | // +build manually 2 | 3 | package main 4 | 5 | import ( 6 | "hstools" 7 | "log" 8 | "math/big" 9 | "math/rand" 10 | "os" 11 | "strconv" 12 | "time" 13 | 14 | "code.google.com/p/plotinum/plot" 15 | "code.google.com/p/plotinum/plotter" 16 | ) 17 | 18 | var random = rand.New(rand.NewSource(time.Now().UnixNano())) 19 | 20 | func RandomDistance(ring *hstools.Hashring) *big.Int { 21 | origin := new(big.Int).Rand(random, hstools.HashringLimit) 22 | return ring.Distance(origin) 23 | } 24 | 25 | func plotDistance() { 26 | if len(os.Args) < 3 { 27 | log.Fatal("usage: montecarlo HSDirs RUNS") 28 | } 29 | 30 | hsdirs, err := strconv.Atoi(os.Args[1]) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | runs, err := strconv.Atoi(os.Args[2]) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | ring := hstools.RandomHashring(hsdirs) 40 | 41 | v := make(plotter.Values, runs) 42 | for i := 0; i < runs; i++ { 43 | d := RandomDistance(ring) 44 | // keep 32 bits of precision 45 | v[i] = float64(d.Rsh(d, 160-32).Int64()) 46 | } 47 | 48 | p, err := plot.New() 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | p.Title.Text = "Distance" 53 | 54 | h, err := plotter.NewHist(v, 100) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | p.Add(h) 59 | 60 | if err := p.Save(20, 5, "distance.png"); err != nil { 61 | log.Fatal(err) 62 | } 63 | } 64 | 65 | func main() { 66 | plotDistance() 67 | } 68 | -------------------------------------------------------------------------------- /src/cmd/preprocess.go: -------------------------------------------------------------------------------- 1 | // +build manually 2 | 3 | package main 4 | 5 | import ( 6 | "hstools" 7 | "log" 8 | "net/http" 9 | _ "net/http/pprof" 10 | "os" 11 | "runtime" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | func main() { 17 | runtime.GOMAXPROCS(runtime.NumCPU()) 18 | 19 | go func() { 20 | log.Println(http.ListenAndServe("localhost:6060", nil)) 21 | }() 22 | 23 | if len(os.Args) != 4 { 24 | log.Fatal("usage: preprocess /data/dir/ 2014-01-01-00 2014-01-31-23") 25 | } 26 | 27 | pckFile, err := os.Create("pckcns.dat") 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | keysDB, err := hstools.OpenKeysDb("keys.db") 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | 36 | since, err := time.Parse("2006-01-02-15", os.Args[2]) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | until, err := time.Parse("2006-01-02-15", os.Args[3]) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | wg := &sync.WaitGroup{} 46 | 47 | ch := hstools.ReadConsensuses(os.Args[1], 48 | hstools.Hour(since.Unix()/3600), hstools.Hour(until.Unix()/3600)) 49 | for c := range ch { 50 | if c.Error != nil { 51 | log.Println(c.Error) 52 | continue 53 | } 54 | 55 | if err := hstools.WritePackedConsensus(pckFile, c); err != nil { 56 | log.Fatal(err) 57 | } 58 | 59 | keysDB.Seen(c.K, c.IP, c.Time, wg) 60 | 61 | log.Println(c.Filename, len(c.K)) 62 | } 63 | 64 | wg.Wait() 65 | if err := keysDB.Close(); err != nil { 66 | log.Fatal(err) 67 | } 68 | if err := pckFile.Close(); err != nil { 69 | log.Fatal(err) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/cmd/scrolls.go: -------------------------------------------------------------------------------- 1 | // +build manually 2 | 3 | package main 4 | 5 | import ( 6 | "bufio" 7 | "bytes" 8 | "encoding/json" 9 | "fmt" 10 | "hstools" 11 | "log" 12 | "math/big" 13 | "net/http" 14 | _ "net/http/pprof" 15 | "os" 16 | "runtime" 17 | "runtime/debug" 18 | "time" 19 | 20 | "github.com/mgutz/ansi" 21 | ) 22 | 23 | var ( 24 | yellow = ansi.ColorFunc("yellow+bh") 25 | red = ansi.ColorFunc("red+bh") 26 | black = ansi.ColorFunc("red+b:white+h") 27 | ) 28 | 29 | func fatalIfErr(err error) { 30 | if err != nil { 31 | debug.PrintStack() 32 | log.Fatal(err) 33 | } 34 | } 35 | 36 | func getAge(h *hstools.Hash, now hstools.Hour, since time.Time, keysDB *hstools.KeysDB) string { 37 | v, err := keysDB.Lookup(*h) 38 | fatalIfErr(err) 39 | if hstools.HourToTime(v.FirstSeen).Before(since) || hstools.HourToTime(v.FirstSeen).Equal(since) { 40 | return "∞" 41 | } 42 | age := now - v.FirstSeen 43 | switch { 44 | case age < 24: 45 | return black(fmt.Sprintf("%d", age)) 46 | case age < 7*24: 47 | return red(fmt.Sprintf("%d", age)) 48 | case age < 15*24: 49 | return yellow(fmt.Sprintf("%d", age)) 50 | default: 51 | return fmt.Sprintf("%d", age) 52 | } 53 | } 54 | 55 | func getLongevity(h *hstools.Hash, now hstools.Hour, until time.Time, keysDB *hstools.KeysDB) string { 56 | v, err := keysDB.Lookup(*h) 57 | fatalIfErr(err) 58 | if hstools.HourToTime(v.LastSeen).After(until) || hstools.HourToTime(v.LastSeen).Equal(until) { 59 | return "∞" 60 | } 61 | long := v.LastSeen - now 62 | switch { 63 | case long < 24: 64 | return black(fmt.Sprintf("%d", long)) 65 | case long < 7*24: 66 | return red(fmt.Sprintf("%d", long)) 67 | case long < 15*24: 68 | return yellow(fmt.Sprintf("%d", long)) 69 | default: 70 | return fmt.Sprintf("%d", long) 71 | } 72 | } 73 | 74 | func getColo(h *hstools.Hash, keysDB *hstools.KeysDB) string { 75 | res, _ := hstools.ColocatedKeys(h[:], keysDB) 76 | switch { 77 | case res > 20: 78 | return black(fmt.Sprintf("%d", res)) 79 | case res > 10: 80 | return red(fmt.Sprintf("%d", res)) 81 | case res > 5: 82 | return yellow(fmt.Sprintf("%d", res)) 83 | default: 84 | return fmt.Sprintf("%d", res) 85 | } 86 | } 87 | 88 | func colorScore(v int64) string { 89 | switch { 90 | case v > 200: 91 | return black(fmt.Sprintf("%d", v)) 92 | case v > 150: 93 | return red(fmt.Sprintf("%d", v)) 94 | case v > 100: 95 | return yellow(fmt.Sprintf("%d", v)) 96 | default: 97 | return fmt.Sprintf("%d", v) 98 | } 99 | } 100 | 101 | func main() { 102 | runtime.GOMAXPROCS(runtime.NumCPU()) 103 | 104 | log.SetFlags(0) 105 | 106 | go func() { 107 | log.Println(http.ListenAndServe("localhost:6060", nil)) 108 | }() 109 | 110 | if len(os.Args) != 7 { 111 | log.Fatal("usage: scavenge pckcns.dat keys.db stats.jsonl xxx.onion 2015-05-01-01 2015-05-31-23") 112 | } 113 | 114 | keysDB, err := hstools.OpenKeysDb(os.Args[2]) 115 | fatalIfErr(err) 116 | 117 | since, err := time.Parse("2006-01-02-15", os.Args[5]) 118 | if err != nil { 119 | log.Fatal(err) 120 | } 121 | until, err := time.Parse("2006-01-02-15", os.Args[6]) 122 | if err != nil { 123 | log.Fatal(err) 124 | } 125 | 126 | statsFile, err := os.Open(os.Args[3]) 127 | fatalIfErr(err) 128 | jsonStats := json.NewDecoder(bufio.NewReaderSize(statsFile, 1024*1024)) 129 | var metrics hstools.AnalyzedConsensus 130 | var last0, last1 []byte 131 | 132 | r := hstools.NewPackReader(os.Args[1]) 133 | for r.Load() { 134 | c := r.Consensus() 135 | jsonStats.Decode(&metrics) 136 | if metrics.T != c.Time { 137 | log.Fatal("unaligned data") 138 | } 139 | 140 | if since.After(hstools.HourToTime(c.Time)) { 141 | continue 142 | } 143 | if until.Before(hstools.HourToTime(c.Time)) { 144 | break 145 | } 146 | 147 | h := hstools.NewHashring(hstools.HashesToIntSlice(c.K)) 148 | 149 | desc, err := hstools.OnionToDescID(os.Args[4], hstools.HourToTime(c.Time)) 150 | fatalIfErr(err) 151 | desc0 := new(big.Int).SetBytes(desc[0]) 152 | desc1 := new(big.Int).SetBytes(desc[1]) 153 | 154 | hsDir0 := hstools.IntsToHashSlice(h.Next3(desc0)) 155 | allHSDir0 := append(append(append([]byte{}, hsDir0[0][:]...), hsDir0[1][:]...), hsDir0[2][:]...) 156 | hsDir1 := hstools.IntsToHashSlice(h.Next3(desc1)) 157 | allHSDir1 := append(append(append([]byte{}, hsDir1[0][:]...), hsDir1[1][:]...), hsDir1[2][:]...) 158 | if bytes.Equal(last0, allHSDir0) && bytes.Equal(last1, allHSDir1) { 159 | continue 160 | } 161 | last0, last1 = allHSDir0, allHSDir1 162 | 163 | // Metric 1. Age 164 | // age0, err := h.Age(desc0, c.Time, keysDB) 165 | // fatalIfErr(err) 166 | // age1, err := h.Age(desc1, c.Time, keysDB) 167 | // fatalIfErr(err) 168 | 169 | // Metric 2. Longevity 170 | // long0, err := h.Longevity(desc0, c.Time, keysDB) 171 | // fatalIfErr(err) 172 | // long1, err := h.Longevity(desc1, c.Time, keysDB) 173 | // fatalIfErr(err) 174 | 175 | // Metric 3. Distance 176 | dist0 := hstools.Score(h.Distance(desc0), metrics.Distance) 177 | dist1 := hstools.Score(h.Distance(desc1), metrics.Distance) 178 | 179 | // Metric 4. Distance4 180 | dist40 := hstools.Score(h.Distance4(desc0), metrics.Distance4) 181 | dist41 := hstools.Score(h.Distance4(desc1), metrics.Distance4) 182 | 183 | // Metric 5. Colocated keys 184 | // colo0 := h.Colocated(desc0, keysDB) 185 | // colo1 := h.Colocated(desc1, keysDB) 186 | 187 | fmt.Printf(` 188 | ###### %s 189 | ###### Replica 0 - Dist score %s - Dist4 score %s 190 | %s - Age %s - Long %s - Colo keys %s 191 | %s - Age %s - Long %s - Colo keys %s 192 | %s - Age %s - Long %s - Colo keys %s 193 | ###### Replica 1 - Dist score %s - Dist4 score %s 194 | %s - Age %s - Long %s - Colo keys %s 195 | %s - Age %s - Long %s - Colo keys %s 196 | %s - Age %s - Long %s - Colo keys %s 197 | `, 198 | hstools.HourToTime(c.Time), colorScore(dist0), colorScore(dist40), 199 | 200 | hstools.ToHex(hsDir0[0][:]), getAge(hsDir0[0], c.Time, since, keysDB), 201 | getLongevity(hsDir0[0], c.Time, until, keysDB), getColo(hsDir0[0], keysDB), 202 | 203 | hstools.ToHex(hsDir0[1][:]), getAge(hsDir0[1], c.Time, since, keysDB), 204 | getLongevity(hsDir0[1], c.Time, until, keysDB), getColo(hsDir0[1], keysDB), 205 | 206 | hstools.ToHex(hsDir0[2][:]), getAge(hsDir0[2], c.Time, since, keysDB), 207 | getLongevity(hsDir0[2], c.Time, until, keysDB), getColo(hsDir0[2], keysDB), 208 | 209 | colorScore(dist1), colorScore(dist41), 210 | 211 | hstools.ToHex(hsDir1[0][:]), getAge(hsDir1[0], c.Time, since, keysDB), 212 | getLongevity(hsDir1[0], c.Time, until, keysDB), getColo(hsDir1[0], keysDB), 213 | 214 | hstools.ToHex(hsDir1[1][:]), getAge(hsDir1[1], c.Time, since, keysDB), 215 | getLongevity(hsDir1[1], c.Time, until, keysDB), getColo(hsDir1[1], keysDB), 216 | 217 | hstools.ToHex(hsDir1[2][:]), getAge(hsDir1[2], c.Time, since, keysDB), 218 | getLongevity(hsDir1[2], c.Time, until, keysDB), getColo(hsDir1[2], keysDB), 219 | ) 220 | 221 | // if colo0 > 20 || colo1 > 20 { 222 | // log.Println(hstools.HourToTime(c.Time), colo0, colo1) 223 | // } 224 | 225 | // if dist0 > 200 || dist1 > 200 || dist40 > 250 || dist41 > 250 || 226 | // age0 < 24*7 || age1 < 24*7 || long0 < 24*3 || long1 < 24*3 || 227 | // colo0 > 20 || colo1 > 20 { 228 | // log.Println(hstools.HourToTime(c.Time), 229 | // "# AGE", age0, age1, 230 | // "# LONG", long0, long1, 231 | // "# DIST", dist0, dist1, 232 | // "# DIST4", dist40, dist41, 233 | // "# COLO", colo0, colo1) 234 | // } 235 | } 236 | fatalIfErr(r.Err()) 237 | } 238 | -------------------------------------------------------------------------------- /src/go_reduced_rsa_primalty.diff: -------------------------------------------------------------------------------- 1 | diff --git a/src/crypto/rand/util.go b/src/crypto/rand/util.go 2 | index 5f74407..f6d7bbc 100644 3 | --- a/src/crypto/rand/util.go 4 | +++ b/src/crypto/rand/util.go 5 | @@ -96,7 +96,7 @@ func Prime(rand io.Reader, bits int) (p *big.Int, err error) { 6 | // There is a tiny possibility that, by adding delta, we caused 7 | // the number to be one bit too long. Thus we check BitLen 8 | // here. 9 | - if p.ProbablyPrime(20) && p.BitLen() == bits { 10 | + if p.ProbablyPrime(1) && p.BitLen() == bits { 11 | return 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/hstools/brute.go: -------------------------------------------------------------------------------- 1 | package hstools 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/sha1" 8 | "encoding/asn1" 9 | ) 10 | 11 | func HashIdentity(pk rsa.PublicKey) Hash { 12 | // tor-spec.txt#n108 13 | // When we refer to "the hash of a public key", we mean the SHA-1 hash of the 14 | // DER encoding of an ASN.1 RSA public key (as specified in PKCS.1). 15 | // rfc3447#appendix-A.1.1 16 | // RSAPublicKey ::= SEQUENCE { 17 | // modulus INTEGER, -- n 18 | // publicExponent INTEGER -- e 19 | // } 20 | der, err := asn1.Marshal(pk) 21 | if err != nil { 22 | panic(err) 23 | } 24 | return Hash(sha1.Sum(der)) 25 | } 26 | 27 | func checkKey(key *rsa.PrivateKey) bool { 28 | for _, p := range key.Primes { 29 | if !p.ProbablyPrime(20) { 30 | return false 31 | } 32 | } 33 | return true 34 | } 35 | 36 | func Brute(targetA, targetB, maxA, maxB Hash, numKeys, numP int, 37 | log func(v ...interface{})) (a []*rsa.PrivateKey, b []*rsa.PrivateKey) { 38 | finished := false 39 | keys := make(chan *rsa.PrivateKey) 40 | for p := 0; p < numP; p++ { 41 | go func(p int) { 42 | for i := 0; i%100 != 0 || !finished; i++ { 43 | // We generate real keys because e++ ones are detectable 44 | // Set the ProbablyPrime rounds to 1 in rand.Prime, we check later 45 | key, err := rsa.GenerateKey(rand.Reader, 1024) 46 | if err != nil { 47 | panic(err) 48 | } 49 | id := HashIdentity(key.PublicKey) 50 | 51 | if (bytes.Compare(targetA[:], id[:]) < 0 && bytes.Compare(id[:], maxA[:]) < 0) || 52 | (bytes.Compare(targetB[:], id[:]) < 0 && bytes.Compare(id[:], maxB[:]) < 0) { 53 | keys <- key 54 | } 55 | 56 | if i%1000 == 0 && i != 0 { 57 | log("Process #", p, "- iteration #", i) 58 | } 59 | } 60 | }(p) 61 | } 62 | for { 63 | key := <-keys 64 | if !checkKey(key) { 65 | log("scrapped bad key") 66 | continue 67 | } 68 | id := HashIdentity(key.PublicKey) 69 | switch { 70 | case bytes.Compare(targetA[:], id[:]) < 0 && bytes.Compare(id[:], maxA[:]) < 0: 71 | a = append(a, key) 72 | case bytes.Compare(targetB[:], id[:]) < 0 && bytes.Compare(id[:], maxB[:]) < 0: 73 | b = append(b, key) 74 | default: 75 | log("weird, this key is not valid anymore?") 76 | continue 77 | } 78 | log("FOUND ONE!", "A", len(a), "B", len(b)) 79 | if len(a) >= numKeys && len(b) >= numKeys { 80 | finished = true 81 | return 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/hstools/brute_test.go: -------------------------------------------------------------------------------- 1 | package hstools 2 | 3 | import ( 4 | "bytes" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "testing" 8 | ) 9 | 10 | func TestHashIdentity(t *testing.T) { 11 | block, _ := pem.Decode([]byte(`-----BEGIN RSA PRIVATE KEY----- 12 | MIICXAIBAAKBgQDX11Z88VBf+4ZJiczyTjTHMS9x1ZbC5qBLQj4LhOWkKJZe9ObK 13 | lcbGd+oyVNip4FTaY5RFenMYOt1ESlYn8jaU/vAi0IMA/E70x9c0p6eLwSr+zCEU 14 | CL/S6ISxwnaYiP92fLfL9keGErKoMbN3t01tAmaDN5jdaaiREVGsHgFVoQIDAQAB 15 | AoGALUw6EHqsfZhR9HkBFBEprmw6Is/KlhjEp0a9srkvYKZL+J25GecZEmn0Mp/v 16 | 4Kb9599iLLqoEPu5mC1pq3R/055F97x/IGxxhP/80LmXLCIeeNG+m3s/ezwUNgny 17 | jT+rsCQAxs/r6sjIcCIAfM8rKtXuqcgUew+d8G3hoSwYv+kCQQD9Fc79mdV8sL/c 18 | ChCY9ryxFwofSn8Ljpm4SJ1RssBsXF3+RnG/G6P80k3/wcae/1w1m/KpoqvZT0Qw 19 | fUfMe87XAkEA2lO4P+2oNkjlaqHVlBJYShBm4QoBPls0boX4aB4hjb+AlQ8P024+ 20 | Pis7qXa4glxlumlDL6CXQx/cRjsdXyXIRwJBAPEeI/SM6U5Afqm+lQ2GlUMKtkQV 21 | j3CNTXq7A9bgPF+AqLQmnRv704J9Qn6WOQsmMs2IY+ql5p/E2yxvT0ZL9kUCQDkC 22 | bXU8AJWUOVu7wIJ2u9kzKToQG70Foc5Oa0v8ujRCUjgaA77o5ZXkQiMBHjLkH6gq 23 | fmG8ZGMhuaoZG5VRz1cCQGox7SskO48AyaynCKNXM3+vWDNtiwrsxBeX3T2nIWWO 24 | x8IyevfhgPIzX0bajUEqm+phNXWBMUTobyTJbkJQ4NQ= 25 | -----END RSA PRIVATE KEY-----`)) 26 | if block.Type != "RSA PRIVATE KEY" { 27 | t.Fatal("wrong type") 28 | } 29 | key, err := x509.ParsePKCS1PrivateKey(block.Bytes) 30 | exitIfErr(t, err) 31 | fing, err := FromHex("EC816FBE76CD94C9064C8F22AF5A468CC46953EA") 32 | exitIfErr(t, err) 33 | res := HashIdentity(key.PublicKey) 34 | if !bytes.Equal(res[:], fing) { 35 | t.Fail() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/hstools/consensus.go: -------------------------------------------------------------------------------- 1 | package hstools 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/binary" 7 | "fmt" 8 | "io" 9 | "os" 10 | "path/filepath" 11 | ) 12 | 13 | const consensusFilename = "consensuses-2006-01/02/2006-01-02-15-00-00-consensus" 14 | 15 | // Hour is just a Unix timestamp divided by 3600, a unique index for an hour 16 | type Hour int32 17 | 18 | type Consensus struct { 19 | Time Hour 20 | Filename string 21 | Error error 22 | K []Hash 23 | IP []string 24 | } 25 | 26 | // ReadConsensuses reads consensus files from a folder structure like 27 | // DIR/consensuses-2011-02/04/2011-02-04-02-00-00-consensus and sends them 28 | // on the returned channel. From since to until included. 29 | func ReadConsensuses(dir string, since, until Hour) chan *Consensus { 30 | ch := make(chan *Consensus) 31 | go func() { 32 | for h := since; h <= until; h++ { 33 | filename := HourToTime(h).Format(consensusFilename) 34 | filename = filepath.Join(dir, filename) 35 | 36 | c, err := ParseConsensus(filename) 37 | if err != nil { 38 | c = &Consensus{Error: err} 39 | } else { 40 | c.Time = h 41 | } 42 | 43 | ch <- c 44 | } 45 | close(ch) 46 | }() 47 | return ch 48 | } 49 | 50 | // ParseConsensus parses a consensus file and extracts the HSDir Hashring 51 | func ParseConsensus(filename string) (*Consensus, error) { 52 | f, err := os.Open(filename) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | var fingerprint, ip string 58 | c := &Consensus{Filename: filename} 59 | 60 | scanner := bufio.NewScanner(bufio.NewReaderSize(f, 2000000)) 61 | for scanner.Scan() { 62 | b := scanner.Bytes() 63 | 64 | if bytes.Equal(b[:2], []byte("r ")) { 65 | parts := bytes.Split(b, []byte(" ")) 66 | fingerprint = string(parts[2]) 67 | ip = string(parts[6]) 68 | continue 69 | } 70 | 71 | if bytes.Equal(b[:2], []byte("s ")) && bytes.Contains(b, []byte("HSDir")) { 72 | f, err := FromBase64(fingerprint) 73 | if err != nil { 74 | return nil, fmt.Errorf("%v (%s)", err, fingerprint) 75 | } 76 | var k Hash 77 | copy(k[len(k)-len(f):], f) 78 | c.K = append(c.K, k) 79 | c.IP = append(c.IP, ip) 80 | } 81 | } 82 | if err := scanner.Err(); err != nil { 83 | return nil, err 84 | } 85 | 86 | return c, nil 87 | } 88 | 89 | type PackedConsensusHdr struct { 90 | Time Hour 91 | Len int32 92 | } 93 | 94 | func WritePackedConsensus(w io.Writer, c *Consensus) error { 95 | hdr := PackedConsensusHdr{Time: c.Time, Len: int32(len(c.K))} 96 | if err := binary.Write(w, binary.BigEndian, hdr); err != nil { 97 | return err 98 | } 99 | for _, k := range c.K { 100 | if _, err := w.Write(k[:]); err != nil { 101 | return err 102 | } 103 | } 104 | return nil 105 | } 106 | 107 | type PackReader struct { 108 | rd io.Reader 109 | cl io.Closer 110 | c *Consensus 111 | err error 112 | } 113 | 114 | func NewPackReader(filename string) (p *PackReader) { 115 | f, err := os.Open(filename) 116 | if err != nil { 117 | return &PackReader{ 118 | err: err, 119 | } 120 | } 121 | return &PackReader{ 122 | rd: bufio.NewReaderSize(f, 1024*1024*100), 123 | cl: f, 124 | } 125 | } 126 | 127 | func (p *PackReader) Load() bool { 128 | var hdr PackedConsensusHdr 129 | if err := binary.Read(p.rd, binary.BigEndian, &hdr); err == io.EOF { 130 | p.cl.Close() 131 | return false 132 | } else if err != nil { 133 | p.err = err 134 | p.cl.Close() 135 | return false 136 | } 137 | p.c = &Consensus{ 138 | Time: hdr.Time, 139 | K: make([]Hash, hdr.Len), 140 | } 141 | for i := int32(0); i < hdr.Len; i++ { 142 | _, err := io.ReadFull(p.rd, p.c.K[i][:]) 143 | if err != nil { 144 | p.err = err 145 | p.cl.Close() 146 | return false 147 | } 148 | } 149 | return true 150 | } 151 | 152 | func (p *PackReader) Consensus() *Consensus { 153 | return p.c 154 | } 155 | 156 | func (p *PackReader) Err() error { 157 | return p.err 158 | } 159 | -------------------------------------------------------------------------------- /src/hstools/consensus_test.go: -------------------------------------------------------------------------------- 1 | package hstools 2 | 3 | import ( 4 | "log" 5 | "math/big" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestParseConsensus(t *testing.T) { 11 | c, err := ParseConsensus("../../misc/2015-04-11-19-00-00-consensus") 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | h := NewHashring(HashesToIntSlice(c.K)) 16 | if h.Len() != 2983 { 17 | t.Fatalf("wrong number of points: %d", h.Len()) 18 | } 19 | 20 | if len(c.IP) != len(c.K) { 21 | log.Fatal("mismatch keys to IPs") 22 | } 23 | 24 | tt, _ := time.Parse(time.RFC3339, "2015-04-11T19:30:00Z") 25 | desc, err := OnionToDescID("facebookcorewwwi.onion", tt) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | hsdir := h.Next(new(big.Int).SetBytes(desc[0])).Bytes() 31 | if ToHex(hsdir) != "274D66DC037FE344C58371B17C606988CBC37DFB" { 32 | t.Fatalf("wrong hsdir: %s", hsdir) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/hstools/descid.go: -------------------------------------------------------------------------------- 1 | // Tool for predicting the HSDirs responsible for a particular hidden service at a 2 | // given time using the rendevous v2 scheme as specified in rend-spec.txt. Heavily 3 | // influenced by Tor source and Donncha O'Cearbhaill's retrieve_hs_descriptor.py 4 | // 5 | // author: George Tankersley 6 | // author: Filippo Valsorda 7 | 8 | package hstools 9 | 10 | import ( 11 | "crypto/sha1" 12 | "encoding/binary" 13 | "errors" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | const ( 19 | REPLICAS = 2 20 | REND_TIME_PERIOD_V2_DESC_VALIDITY = 24 * 60 * 60 // 86400 21 | ) 22 | 23 | func OnionToDescID(onion string, t time.Time) ([][]byte, error) { 24 | onion = strings.ToLower(onion) 25 | 26 | switch len(onion) { 27 | case 16 + len(".onion"): 28 | if onion[16:] == ".onion" { 29 | onion = onion[:16] 30 | } else { 31 | return nil, errors.New("wrong suffix") 32 | } 33 | case 16: 34 | // good like this 35 | default: 36 | return nil, errors.New("wrong length") 37 | } 38 | 39 | first, err := ComputeRendV2DescID(onion, 0, t.Unix(), "") 40 | if err != nil { 41 | return nil, err 42 | } 43 | second, err := ComputeRendV2DescID(onion, 1, t.Unix(), "") 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return [][]byte{first, second}, nil 49 | } 50 | 51 | func ComputeRendV2DescID(serviceID string, replica byte, time int64, descCookie string) ([]byte, error) { 52 | // Convert service ID to binary. 53 | serviceIDBinary, err := FromBase32(serviceID) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | // Calculate current time-period. 59 | timePeriod := getTimePeriod(time, 0, serviceIDBinary) 60 | 61 | // Calculate secret-id-part = h(time-period | cookie | replica). 62 | secretIDPart := getSecretIDPartBytes(timePeriod, descCookie, replica) 63 | 64 | // Calculate descriptor ID. 65 | descID := rendGetDescriptorIDBytes(serviceIDBinary, secretIDPart) 66 | 67 | return descID, nil 68 | } 69 | 70 | func getTimePeriod(time int64, deviation int64, serviceIDBinary []byte) int64 { 71 | return (time+int64(serviceIDBinary[0])*REND_TIME_PERIOD_V2_DESC_VALIDITY/256)/REND_TIME_PERIOD_V2_DESC_VALIDITY + int64(deviation) 72 | } 73 | 74 | func getSecretIDPartBytes(timePeriod int64, descCookie string, replica byte) []byte { 75 | h := sha1.New() 76 | htonlTime := make([]byte, 4) 77 | binary.BigEndian.PutUint32(htonlTime, uint32(timePeriod)) 78 | h.Write(htonlTime) 79 | if descCookie != "" { 80 | h.Write([]byte(descCookie)) 81 | } 82 | h.Write([]byte{replica}) 83 | return h.Sum(nil) 84 | } 85 | 86 | func rendGetDescriptorIDBytes(serviceIDBinary, secretIDPart []byte) []byte { 87 | h := sha1.New() 88 | h.Write(serviceIDBinary) 89 | h.Write(secretIDPart) 90 | return h.Sum(nil) 91 | } 92 | -------------------------------------------------------------------------------- /src/hstools/descid_test.go: -------------------------------------------------------------------------------- 1 | package hstools 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestFacebookOnion(t *testing.T) { 9 | tt, _ := time.Parse(time.RFC3339, "2015-04-11T19:30:00Z") 10 | desc, err := OnionToDescID("facebookcorewwwi.onion", tt) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | if ToBase32(desc[0]) != "e4jiuabozanwqxdobx44w47mx2hi2auz" { 15 | t.Errorf("Wrong desc[0]: %v (!= e4jiuabozanwqxdobx44w47mx2hi2auz)", ToBase32(desc[0])) 16 | } 17 | if ToBase32(desc[1]) != "tyvtyaqd4trmgoopqktv4aawelu6skes" { 18 | t.Errorf("Wrong desc[0]: %v (!= tyvtyaqd4trmgoopqktv4aawelu6skes)", ToBase32(desc[1])) 19 | } 20 | } 21 | 22 | func TestCurrentOnion(t *testing.T) { 23 | if testing.Short() { 24 | t.SkipNow() 25 | } 26 | desc, err := OnionToDescID("facebookcorewwwi.onion", time.Now()) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | t.Log(ToBase32(desc[0]), ToBase32(desc[1])) 31 | } 32 | -------------------------------------------------------------------------------- /src/hstools/format.go: -------------------------------------------------------------------------------- 1 | package hstools 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/base32" 6 | "encoding/base64" 7 | "encoding/hex" 8 | "math" 9 | "math/big" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | type Hash [20]byte 15 | 16 | // Distance efficiently calculates the difference (b - a) mod 2^160, or 17 | // distance a -> b on a 20-byte ring and stores it in d. a and b unchanged. 18 | func (a *Hash) Distance(b, d *Hash) { 19 | var carry bool 20 | for i := len(a) - 1; i >= 0; i-- { 21 | B := b[i] 22 | if carry { 23 | B-- 24 | } 25 | d[i] = B - a[i] 26 | carry = B < a[i] || (carry && B == math.MaxUint8) 27 | } 28 | } 29 | 30 | func SHA1(data []byte) *Hash { 31 | h := Hash(sha1.Sum(data)) 32 | return &h 33 | } 34 | 35 | func ToBase32(b []byte) string { 36 | return strings.ToLower(base32.StdEncoding.EncodeToString(b)) 37 | } 38 | 39 | func FromBase32(s string) ([]byte, error) { 40 | return base32.StdEncoding.DecodeString(strings.ToUpper(s)) 41 | } 42 | 43 | func ToHex(b []byte) string { 44 | return strings.ToUpper(hex.EncodeToString(b)) 45 | } 46 | 47 | func FromHex(s string) ([]byte, error) { 48 | return hex.DecodeString(s) 49 | } 50 | 51 | func FromBase64(s string) ([]byte, error) { 52 | if r := len(s) % 4; r != 0 { 53 | s += strings.Repeat("=", 4-r) 54 | } 55 | return base64.StdEncoding.DecodeString(s) 56 | } 57 | 58 | func HourToTime(h Hour) time.Time { 59 | return time.Unix(int64(h*3600), 0) 60 | } 61 | 62 | func HashesToIntSlice(keys []Hash) []*big.Int { 63 | ints := make([]*big.Int, len(keys)) 64 | for i, k := range keys { 65 | ints[i] = new(big.Int).SetBytes(k[:]) 66 | } 67 | return ints 68 | } 69 | 70 | func IntsToHashSlice(ints []*big.Int) []*Hash { 71 | hashes := make([]*Hash, len(ints)) 72 | for n, i := range ints { 73 | var k Hash 74 | b := i.Bytes() 75 | copy(k[len(k)-len(b):], b) 76 | hashes[n] = &k 77 | } 78 | return hashes 79 | } 80 | 81 | func IntToHash(i *big.Int) Hash { 82 | var k Hash 83 | b := i.Bytes() 84 | copy(k[len(k)-len(b):], b) 85 | return k 86 | } 87 | 88 | func HashToInt(k Hash) *big.Int { 89 | return new(big.Int).SetBytes(k[:]) 90 | } 91 | -------------------------------------------------------------------------------- /src/hstools/format_test.go: -------------------------------------------------------------------------------- 1 | package hstools 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "math/big" 7 | "testing" 8 | ) 9 | 10 | func exitIfErr(t *testing.T, err error) { 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | } 15 | 16 | func TestHashDistance(t *testing.T) { 17 | var a, b, A, B, d Hash 18 | var aInt, bInt = new(big.Int), new(big.Int) 19 | var dInt, DInt = new(big.Int), new(big.Int) 20 | for i := 0; i < 10000; i++ { 21 | _, err := rand.Read(a[:]) 22 | exitIfErr(t, err) 23 | copy(A[:], a[:]) 24 | _, err = rand.Read(b[:]) 25 | exitIfErr(t, err) 26 | copy(B[:], b[:]) 27 | 28 | a.Distance(&b, &d) 29 | DInt = DInt.SetBytes(d[:]) 30 | 31 | if !bytes.Equal(a[:], A[:]) || !bytes.Equal(b[:], B[:]) { 32 | t.Fatal("input changed") 33 | } 34 | 35 | // Compute the distance by big.Int to check 36 | aInt = aInt.SetBytes(a[:]) 37 | bInt = bInt.SetBytes(b[:]) 38 | if aInt.Cmp(bInt) < 0 { 39 | dInt = dInt.Sub(bInt, aInt) 40 | } else { 41 | dInt = dInt.Sub(HashringLimit, aInt) 42 | dInt = dInt.Add(dInt, bInt) 43 | } 44 | 45 | if DInt.Cmp(dInt) != 0 { 46 | t.Log(DInt.Bytes()) 47 | t.Log(dInt.Bytes()) 48 | t.Fatal("different result") 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/hstools/hashring.go: -------------------------------------------------------------------------------- 1 | package hstools 2 | 3 | import ( 4 | "math/big" 5 | "math/rand" 6 | "sort" 7 | "time" 8 | ) 9 | 10 | var ( 11 | bigOne = big.NewInt(1) 12 | bigTwo = big.NewInt(2) 13 | bigThree = big.NewInt(3) 14 | 15 | HashringLimit = new(big.Int).Lsh(bigOne, 160) 16 | 17 | random = rand.New(rand.NewSource(time.Now().UnixNano())) 18 | ) 19 | 20 | type Hashring struct { 21 | // points is the sorted list of values present on the ring 22 | points []*big.Int 23 | } 24 | 25 | type bigIntSlice []*big.Int 26 | 27 | func (b bigIntSlice) Len() int { return len(b) } 28 | func (b bigIntSlice) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 29 | func (b bigIntSlice) Less(i, j int) bool { return b[i].Cmp(b[j]) < 0 } 30 | 31 | // NewHashring returns a Hashring with the given unsorted points. 32 | func NewHashring(points []*big.Int) *Hashring { 33 | h := &Hashring{ 34 | points: make([]*big.Int, len(points)), 35 | } 36 | copy(h.points, points) 37 | sort.Sort(bigIntSlice(h.points)) 38 | return h 39 | } 40 | 41 | func RandomHashring(entries int) *Hashring { 42 | points := make([]*big.Int, entries) 43 | for i := 0; i < entries; i++ { 44 | points[i] = new(big.Int).Rand(random, HashringLimit) 45 | } 46 | 47 | return NewHashring(points) 48 | } 49 | 50 | func (h *Hashring) Len() int { 51 | return len(h.points) 52 | } 53 | 54 | func (h *Hashring) Next(p *big.Int) *big.Int { 55 | i := sort.Search(len(h.points), func(i int) bool { 56 | return h.points[i].Cmp(p) > 0 57 | }) 58 | return h.points[i%len(h.points)] 59 | } 60 | 61 | func (h *Hashring) Next3(p *big.Int) []*big.Int { 62 | i := sort.Search(len(h.points), func(i int) bool { 63 | return h.points[i].Cmp(p) > 0 64 | }) 65 | return []*big.Int{ 66 | h.points[i%len(h.points)], 67 | h.points[(i+1)%len(h.points)], 68 | h.points[(i+2)%len(h.points)], 69 | } 70 | } 71 | 72 | func (h *Hashring) Fourth(p *big.Int) *big.Int { 73 | i := sort.Search(len(h.points), func(i int) bool { 74 | return h.points[i].Cmp(p) > 0 75 | }) 76 | return h.points[(i+3)%len(h.points)] 77 | } 78 | 79 | func (h *Hashring) Prev(p *big.Int) *big.Int { 80 | i := sort.Search(len(h.points), func(i int) bool { 81 | return h.points[i].Cmp(p) >= 0 82 | }) 83 | return h.points[(i-1)%len(h.points)] 84 | } 85 | 86 | func (*Hashring) Diff(from, to *big.Int) *big.Int { 87 | if from.Cmp(to) < 0 { 88 | return new(big.Int).Sub(to, from) 89 | } else { 90 | res := new(big.Int).Sub(HashringLimit, from) 91 | return res.Add(res, to) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/hstools/hashring_test.go: -------------------------------------------------------------------------------- 1 | package hstools 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | ) 7 | 8 | func TestHashringNext(t *testing.T) { 9 | h := NewHashring([]*big.Int{ 10 | big.NewInt(500), 11 | big.NewInt(200), 12 | big.NewInt(300), 13 | big.NewInt(400), 14 | big.NewInt(100), 15 | }) 16 | 17 | for i := int64(1); i <= 5; i++ { 18 | expected := i * 100 19 | n := h.Next(big.NewInt(expected - 50)) 20 | if n.Int64() != expected { 21 | t.Fatal(n.Int64(), expected) 22 | } 23 | } 24 | 25 | n := h.Next(big.NewInt(550)) 26 | if n.Int64() != 100 { 27 | t.Fatal(n.Int64(), 100) 28 | } 29 | } 30 | 31 | func TestHashringDistance(t *testing.T) { 32 | h := NewHashring([]*big.Int{ 33 | big.NewInt(500), 34 | big.NewInt(200), 35 | big.NewInt(300), 36 | big.NewInt(400), 37 | big.NewInt(100), 38 | }) 39 | 40 | n := h.Distance(big.NewInt(450)) 41 | if n.Int64() != 50 { 42 | t.Fatal(n.Int64()) 43 | } 44 | 45 | n = h.Distance(big.NewInt(550)) 46 | exp := new(big.Int).Sub(HashringLimit, big.NewInt(550-100)) 47 | if n.Cmp(exp) != 0 { 48 | t.Fatal(n, exp) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/hstools/keysdb.go: -------------------------------------------------------------------------------- 1 | package hstools 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "log" 7 | "sync" 8 | "time" 9 | 10 | "github.com/boltdb/bolt" 11 | ) 12 | 13 | type KeyMeta struct { 14 | FirstSeen Hour 15 | LastSeen Hour 16 | IPs []string 17 | } 18 | 19 | type IPMeta struct { 20 | Keys [][]byte 21 | } 22 | 23 | type KeysDB struct { 24 | db *bolt.DB 25 | } 26 | 27 | func OpenKeysDb(filename string) (*KeysDB, error) { 28 | d := &KeysDB{} 29 | var err error 30 | 31 | if d.db, err = bolt.Open(filename, 0664, nil); err != nil { 32 | return nil, err 33 | } 34 | 35 | d.db.MaxBatchDelay = 5 * time.Second 36 | 37 | if err = d.db.Update(func(tx *bolt.Tx) error { 38 | if _, err := tx.CreateBucketIfNotExists([]byte("Keys")); err != nil { 39 | return err 40 | } 41 | if _, err := tx.CreateBucketIfNotExists([]byte("IPs")); err != nil { 42 | return err 43 | } 44 | return nil 45 | }); err != nil { 46 | return nil, err 47 | } 48 | 49 | return d, nil 50 | } 51 | 52 | func (d *KeysDB) Seen(keys []Hash, ips []string, h Hour, wg *sync.WaitGroup) { 53 | fn := func(tx *bolt.Tx) error { 54 | for i, k := range keys { 55 | ip := ips[i] 56 | b := tx.Bucket([]byte("Keys")) 57 | oldJSON := b.Get(k[:]) 58 | meta := KeyMeta{ 59 | FirstSeen: h, 60 | LastSeen: h, 61 | IPs: []string{ip}, 62 | } 63 | if oldJSON != nil { 64 | var oldMeta KeyMeta 65 | if err := json.Unmarshal(oldJSON, &oldMeta); err != nil { 66 | return err 67 | } 68 | if oldMeta.FirstSeen < meta.FirstSeen { 69 | meta.FirstSeen = oldMeta.FirstSeen 70 | } 71 | if oldMeta.LastSeen > meta.LastSeen { 72 | meta.LastSeen = oldMeta.LastSeen 73 | } 74 | for _, oldIP := range oldMeta.IPs { 75 | if oldIP != ip { 76 | meta.IPs = append(meta.IPs, oldIP) 77 | } 78 | } 79 | } 80 | encoded, err := json.Marshal(meta) 81 | if err != nil { 82 | return err 83 | } 84 | if err := b.Put(k[:], encoded); err != nil { 85 | return err 86 | } 87 | 88 | b = tx.Bucket([]byte("IPs")) 89 | oldJSON = b.Get([]byte(ip)) 90 | ipMeta := IPMeta{ 91 | Keys: [][]byte{k[:]}, 92 | } 93 | if oldJSON != nil { 94 | var oldMeta IPMeta 95 | if err := json.Unmarshal(oldJSON, &oldMeta); err != nil { 96 | return err 97 | } 98 | for _, oldKey := range oldMeta.Keys { 99 | if !bytes.Equal(oldKey, k[:]) { 100 | ipMeta.Keys = append(ipMeta.Keys, oldKey) 101 | } 102 | } 103 | } 104 | encoded, err = json.Marshal(ipMeta) 105 | if err != nil { 106 | return err 107 | } 108 | if err := b.Put([]byte(ip), encoded); err != nil { 109 | return err 110 | } 111 | } 112 | return nil 113 | } 114 | go func() { 115 | wg.Add(1) 116 | if err := d.db.Batch(fn); err != nil { 117 | log.Fatal(err) 118 | } else { 119 | log.Println("recorded", HourToTime(h)) 120 | wg.Done() 121 | } 122 | }() 123 | } 124 | 125 | func (d *KeysDB) Lookup(key Hash) (res KeyMeta, err error) { 126 | err = d.db.View(func(tx *bolt.Tx) error { 127 | b := tx.Bucket([]byte("Keys")) 128 | v := b.Get([]byte(key[:])) 129 | if err := json.Unmarshal(v, &res); err != nil { 130 | return err 131 | } 132 | return nil 133 | }) 134 | return 135 | } 136 | 137 | func (d *KeysDB) View(fn func(tx *bolt.Tx) error) error { 138 | return d.db.View(fn) 139 | } 140 | 141 | func (d *KeysDB) Close() error { 142 | return d.db.Close() 143 | } 144 | -------------------------------------------------------------------------------- /src/hstools/stats.go: -------------------------------------------------------------------------------- 1 | package hstools 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "math/big" 7 | 8 | "github.com/boltdb/bolt" 9 | ) 10 | 11 | type MetricData struct { 12 | Mean *big.Int 13 | AbsDev *big.Int 14 | } 15 | 16 | type AnalyzedConsensus struct { 17 | T Hour 18 | Distance *MetricData 19 | Distance4 *MetricData 20 | } 21 | 22 | type PartitionData struct { 23 | x0 *big.Int 24 | x1 *big.Int 25 | l *big.Int 26 | } 27 | 28 | func bigMean(nums []*big.Int) *big.Int { 29 | avg := big.NewInt(0) 30 | size := big.NewInt(int64(len(nums))) 31 | for _, n := range nums { 32 | avg.Add(avg, new(big.Int).Div(n, size)) 33 | } 34 | return avg 35 | } 36 | 37 | func bigSqrt(n *big.Int) *big.Int { 38 | // adapted from mini-gmp 39 | u, t := new(big.Int), new(big.Int) 40 | t.SetBit(t, n.BitLen()/2+1, 1) 41 | for { 42 | u.Set(t) 43 | t.Quo(n, u) 44 | t.Add(t, u) 45 | t.Rsh(t, 1) 46 | if t.Cmp(u) >= 0 { 47 | return u 48 | } 49 | } 50 | } 51 | 52 | func bigCubeRoot(n *big.Int) *big.Int { 53 | // http://math.stackexchange.com/a/263113 54 | cube, x := new(big.Int), new(big.Int) 55 | 56 | a := new(big.Int).Set(n) 57 | for cube.Exp(a, bigThree, nil).Cmp(n) > 0 { 58 | // a = (2*a + n/a^2) / 3 59 | x.Quo(n, x.Mul(a, a)) 60 | x.Add(x.Add(x, a), a) 61 | a.Quo(x, bigThree) 62 | } 63 | 64 | return a 65 | } 66 | 67 | func bigStdDev(nums []*big.Int, mean *big.Int) *big.Int { 68 | avg := big.NewInt(0) 69 | size := big.NewInt(int64(len(nums)) - 1) 70 | for _, n := range nums { 71 | d := new(big.Int) 72 | d.Exp(d.Sub(n, mean), bigTwo, nil) 73 | avg.Add(avg, d.Div(d, size)) 74 | } 75 | return bigSqrt(avg) 76 | } 77 | 78 | // func bigAbsDev(nums []*big.Int, mean *big.Int) *big.Int { 79 | // avg := big.NewInt(0) 80 | // size := big.NewInt(int64(len(nums))) 81 | // for _, n := range nums { 82 | // d := new(big.Int) 83 | // d.Abs(d.Sub(mean, n)) 84 | // avg.Add(avg, d.Div(d, size)) 85 | // } 86 | // return avg 87 | // } 88 | 89 | func bigAbsDev(nums []*big.Int, mean *big.Int) *big.Int { 90 | devs := make([]*big.Int, len(nums)) 91 | for i, n := range nums { 92 | devs[i] = new(big.Int).Sub(n, mean) 93 | devs[i].Abs(devs[i]) 94 | } 95 | return bigMean(devs) 96 | } 97 | 98 | // The SampleX functions have been replaced by the analytical (not random) 99 | // XData + AnalyzePartitionData functions 100 | const ROUNDS = 100000 101 | 102 | func (h *Hashring) Distance(p *big.Int) *big.Int { 103 | return h.Diff(p, h.Next(p)) 104 | } 105 | 106 | func SampleDistance(h *Hashring) (res *MetricData) { 107 | samples := make([]*big.Int, ROUNDS) 108 | origin := new(big.Int) 109 | for i := 0; i < ROUNDS; i++ { 110 | origin.Rand(random, HashringLimit) 111 | samples[i] = h.Distance(origin) 112 | } 113 | mean := bigMean(samples) 114 | return &MetricData{ 115 | Mean: mean, 116 | // StdDev: bigStdDev(samples, mean), 117 | AbsDev: bigAbsDev(samples, mean), 118 | } 119 | } 120 | 121 | func (h *Hashring) Distance4(p *big.Int) *big.Int { 122 | return h.Diff(p, h.Fourth(p)) 123 | } 124 | 125 | func SampleDistance4(h *Hashring) (res *MetricData) { 126 | samples := make([]*big.Int, ROUNDS) 127 | origin := new(big.Int) 128 | for i := 0; i < ROUNDS; i++ { 129 | origin.Rand(random, HashringLimit) 130 | samples[i] = h.Distance4(origin) 131 | } 132 | mean := bigMean(samples) 133 | return &MetricData{ 134 | Mean: mean, 135 | // StdDev: bigStdDev(samples, mean), 136 | AbsDev: bigAbsDev(samples, mean), 137 | } 138 | } 139 | 140 | func (h *Hashring) Distance4Data() (res []*PartitionData) { 141 | for i, p := range h.points { 142 | x0 := h.Diff(p, h.points[(i+4)%len(h.points)]) 143 | l := h.Diff(p, h.points[(i+1)%len(h.points)]) 144 | x1 := new(big.Int).Sub(x0, l) 145 | res = append(res, &PartitionData{ 146 | x0: x0, x1: x1, l: l, 147 | }) 148 | } 149 | return 150 | } 151 | 152 | func (h *Hashring) DistanceData() (res []*PartitionData) { 153 | for i, p := range h.points { 154 | x0 := h.Diff(p, h.points[(i+1)%len(h.points)]) 155 | res = append(res, &PartitionData{ 156 | x0: x0, x1: big.NewInt(0), l: x0, 157 | }) 158 | } 159 | return 160 | } 161 | 162 | func AnalyzePartitionData(data []*PartitionData) *MetricData { 163 | m := &MetricData{ 164 | Mean: new(big.Int), 165 | AbsDev: new(big.Int), 166 | } 167 | for _, part := range data { 168 | u := new(big.Int).Add(part.x0, part.x1) 169 | u.Abs(u.Div(u, bigTwo)) 170 | u.Div(u.Mul(u, part.l), HashringLimit) 171 | m.Mean.Add(m.Mean, u) 172 | } 173 | for _, part := range data { 174 | d0 := new(big.Int).Sub(part.x0, m.Mean) 175 | d1 := new(big.Int).Sub(part.x1, m.Mean) 176 | if d0.Sign() == d1.Sign() { 177 | w := new(big.Int).Add(d0, d1) 178 | w.Abs(w.Div(w, bigTwo)) 179 | w.Div(w.Mul(w, part.l), HashringLimit) 180 | m.AbsDev.Add(m.AbsDev, w) 181 | } else { 182 | // Assumes l = x0 - x1 and x0 > x1 183 | if part.l.Cmp(new(big.Int).Sub(part.x0, part.x1)) != 0 || 184 | !(part.x0.Cmp(part.x1) > 0) { 185 | log.Fatal(part.l, part.x0, part.x1) 186 | } 187 | d1.Abs(d1) 188 | 189 | w0 := new(big.Int).Div(d0, bigTwo) 190 | w0.Div(w0.Mul(w0.Abs(w0), d0), HashringLimit) 191 | m.AbsDev.Add(m.AbsDev, w0) 192 | 193 | w1 := new(big.Int).Div(d1, bigTwo) 194 | w1.Div(w1.Mul(w1.Abs(w1), d1), HashringLimit) 195 | m.AbsDev.Add(m.AbsDev, w1) 196 | } 197 | } 198 | return m 199 | } 200 | 201 | var mask = new(big.Int).Exp(big.NewInt(2), big.NewInt(160-30), nil) 202 | 203 | func Score(v *big.Int, res *MetricData) int64 { 204 | dev := new(big.Int).Sub(res.Mean, v) 205 | return dev.Div(dev.Mul(dev, big.NewInt(100)), res.AbsDev).Int64() 206 | } 207 | 208 | func (h *Hashring) Age(p *big.Int, now Hour, keysDB *KeysDB) (Hour, error) { 209 | var res Hour 210 | for _, p := range h.Next3(p) { 211 | v, err := keysDB.Lookup(IntToHash(p)) 212 | if err != nil { 213 | return 0, err 214 | } 215 | res += now - v.FirstSeen 216 | } 217 | return res / 3, nil 218 | } 219 | 220 | func (h *Hashring) Longevity(p *big.Int, now Hour, keysDB *KeysDB) (Hour, error) { 221 | var res Hour 222 | for _, p := range h.Next3(p) { 223 | v, err := keysDB.Lookup(IntToHash(p)) 224 | if err != nil { 225 | return 0, err 226 | } 227 | res += v.LastSeen - now 228 | } 229 | return res / 3, nil 230 | } 231 | 232 | func (h *Hashring) Colocated(p *big.Int, keysDB *KeysDB) int { 233 | var tot int 234 | for _, p := range h.Next3(p) { 235 | h := IntToHash(p) 236 | n, _ := ColocatedKeys(h[:], keysDB) 237 | tot += n 238 | } 239 | return tot - 3 240 | } 241 | 242 | // AgeData is not really in use, Age and Longevity are assessed in absolute 243 | func (h *Hashring) AgeData(now Hour, keysDB *KeysDB) (res []*PartitionData, err error) { 244 | for i, p := range h.points { 245 | age, err := h.Age(p, now, keysDB) 246 | if err != nil { 247 | return nil, err 248 | } 249 | l := h.Diff(p, h.points[(i+1)%len(h.points)]) 250 | res = append(res, &PartitionData{ 251 | x0: big.NewInt(int64(age)), x1: big.NewInt(int64(age)), l: l, 252 | }) 253 | } 254 | return 255 | } 256 | 257 | func ColocatedKeys(k []byte, keysDB *KeysDB) (coloNum int, ips []string) { 258 | if err := keysDB.View(func(tx *bolt.Tx) error { 259 | b := tx.Bucket([]byte("Keys")) 260 | var res KeyMeta 261 | if err := json.Unmarshal(b.Get(k), &res); err != nil { 262 | return err 263 | } 264 | colocated := make(map[string]struct{}) 265 | for _, ip := range res.IPs { 266 | ipMetaJSON := tx.Bucket([]byte("IPs")).Get([]byte(ip)) 267 | var ipMeta IPMeta 268 | if err := json.Unmarshal(ipMetaJSON, &ipMeta); err != nil { 269 | return err 270 | } 271 | for _, key := range ipMeta.Keys { 272 | colocated[ToHex(key)] = struct{}{} 273 | } 274 | } 275 | ips = res.IPs 276 | coloNum = len(colocated) 277 | return nil 278 | }); err != nil { 279 | log.Fatal(err) 280 | } 281 | return 282 | } 283 | -------------------------------------------------------------------------------- /src/hstools/stats_test.go: -------------------------------------------------------------------------------- 1 | package hstools 2 | 3 | import "testing" 4 | 5 | func TestAnalyzePartitionData(t *testing.T) { 6 | h := RandomHashring(3000) 7 | res := AnalyzePartitionData(h.Distance4Data()) 8 | t.Log(res) 9 | 10 | var meanHi, meanLo, devHi, devLo bool 11 | for i := 0; i < 10; i++ { 12 | resSample := SampleDistance4(h) 13 | t.Log(resSample) 14 | meanHi = meanHi || resSample.Mean.Cmp(res.Mean) > 0 15 | meanLo = meanLo || resSample.Mean.Cmp(res.Mean) < 0 16 | devHi = devHi || resSample.AbsDev.Cmp(res.AbsDev) > 0 17 | devLo = devLo || resSample.AbsDev.Cmp(res.AbsDev) < 0 18 | } 19 | 20 | if !(meanHi && meanLo && devHi && devLo) { 21 | t.Failed() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/retrieve_hs_descriptor.py: -------------------------------------------------------------------------------- 1 | """ 2 | List a Hidden Service HSDir for a given consensus or at the current instant. 3 | 4 | - Donncha O'Cearbhaill - donncha@donncha.is 5 | - Filippo Valsorda 6 | """ 7 | 8 | from time import mktime, time 9 | from base64 import b32encode, b32decode 10 | from hashlib import sha1 11 | from struct import pack, unpack 12 | from stem.descriptor import parse_file, DocumentHandler 13 | from stem.descriptor.remote import DescriptorDownloader 14 | import argparse 15 | from bisect import bisect_left 16 | 17 | # Returns base_32 encode desc_id - descriptor-id = H(permanent-id | H(time-period | descriptor-cookie | replica)) 18 | def rend_compute_v2_desc_id(service_id_base32, replica, time, descriptor_cookie = ""):# 19 | service_id = b32decode(service_id_base32, 1) 20 | time_period = get_time_period(time, 0, service_id) 21 | secret_id_part = get_secret_id_part_bytes(time_period, descriptor_cookie, replica) 22 | desc_id = rend_get_descriptor_id_bytes(service_id, secret_id_part) 23 | return b32encode(desc_id).lower() 24 | 25 | # Calculates time period - time-period = (current-time + permanent-id-byte * 86400 / 256) / 86400 26 | def get_time_period(time, deviation, service_id): 27 | REND_TIME_PERIOD_V2_DESC_VALIDITY = 24 * 60 * 60 28 | return int(((time + ((unpack('B', service_id[0])[0] * REND_TIME_PERIOD_V2_DESC_VALIDITY) ) / 256) ) / REND_TIME_PERIOD_V2_DESC_VALIDITY + deviation) 29 | 30 | # Calculate secret_id_part - secret-id-part = H(time-period | descriptor-cookie | replica) 31 | def get_secret_id_part_bytes(time_period, descriptor_cookie, replica): 32 | secret_id_part = sha1() 33 | secret_id_part.update(pack('>I', time_period)[:4]); 34 | if descriptor_cookie: 35 | secret_id_part.update(descriptor_cookie) 36 | secret_id_part.update('{0:02X}'.format(replica).decode('hex')) 37 | return secret_id_part.digest() 38 | 39 | def rend_get_descriptor_id_bytes(service_id, secret_id_part): 40 | descriptor_id = sha1() 41 | descriptor_id.update(service_id) 42 | descriptor_id.update(secret_id_part) 43 | return descriptor_id.digest() 44 | 45 | def find_responsible_HSDir(descriptor_id, consensus): 46 | fingerprint_list = [] 47 | for _, router in consensus.routers.items(): 48 | if "HSDir" in router.flags: 49 | fingerprint_list.append(router.fingerprint.decode("hex")) 50 | fingerprint_list.sort() 51 | 52 | descriptor_position = bisect_left(fingerprint_list, b32decode(descriptor_id, 1)) 53 | 54 | responsible_HSDirs = [] 55 | for i in range(0, 3): 56 | fingerprint = fingerprint_list[descriptor_position + i] 57 | router = consensus.routers[fingerprint.encode("hex").upper()] 58 | responsible_HSDirs.append({ 59 | 'nickname': router.nickname, 60 | 'fingerprint': router.fingerprint, 61 | 'address': router.address, 62 | 'dir_port': router.dir_port, 63 | 'descriptor_id': descriptor_id 64 | }) 65 | 66 | return responsible_HSDirs 67 | 68 | def main(): 69 | REPLICAS = 2 70 | 71 | parser = argparse.ArgumentParser() 72 | parser.add_argument('onion_address', help='The hidden service address - e.g. (idnxcnkne4qt76tg.onion)') 73 | parser.add_argument('--consensus', help='The optional consensus file', required=False) 74 | args = parser.parse_args() 75 | 76 | if args.consensus is None: 77 | downloader = DescriptorDownloader() 78 | consensus = downloader.get_consensus(document_handler = DocumentHandler.DOCUMENT).run()[0] 79 | t = time() 80 | else: 81 | with open(args.consensus) as f: 82 | consensus = next(parse_file(f, 'network-status-consensus-3 1.0', document_handler = DocumentHandler.DOCUMENT)) 83 | t = mktime(consensus.valid_after.timetuple()) 84 | 85 | service_id, tld = args.onion_address.split(".") 86 | if tld == 'onion' and len(service_id) == 16 and service_id.isalnum(): 87 | for replica in range(0, REPLICAS): 88 | descriptor_id = rend_compute_v2_desc_id(service_id, replica, t) 89 | print descriptor_id + '\t' + b32decode(descriptor_id, True).encode('hex') 90 | for router in find_responsible_HSDir(descriptor_id, consensus): 91 | print router['fingerprint'] + '\t' + router['nickname'] 92 | 93 | else: 94 | print "[!] The onion address you provided is not valid" 95 | 96 | if __name__ == '__main__': 97 | main() 98 | --------------------------------------------------------------------------------