Hi all,

I think we can make next-generation onion (hidden) services (proposal #224) 
more resilient against certain kinds of DoS / client discovery attacks, by 
using a different blinded public key for each HSDir.

Attack Summary:

Once a malicious HSDir receives a descriptor, it can locate other HSDirs with 
that descriptor's blinded public key. It doesn't need to know the onion service 
address or decrypt the descriptor. Each descriptor contains the blinded public 
key:

> The 'certificate' field contains a certificate in the format from proposal 
> 220, with the short-term ed25519 descriptor-signing key signed by the blinded 
> public key. It must contain a ed25519-signing-key extension containing the 
> blinded public key.

(All quotes are from 
https://gitweb.torproject.org/torspec.git/tree/proposals/224-rend-spec-ng.txt 
<https://gitweb.torproject.org/torspec.git/tree/proposals/224-rend-spec-ng.txt> 
)

This allows an attacker to target onion services for DoS, as long as they don't 
care which onion service they're targeting.

The effects of this attack could be:
* if the malicious HSDir also stops serving the descriptor, it denies service 
to some onion service,
* if the malicious HSDir continues to serve the descriptor, it becomes the only 
source of descriptors for some onion service.

I think this enables traffic confirmation attacks on some onion service or its 
clients (by bringing the sole descriptor source up and down). Client descriptor 
downloads could also be subject to traffic fingerprinting or slow 
(byte-at-a-time) delivery.

Attack Details:

Let's assume an attacker who:
* doesn't know any onion service addresses
* runs a malicious HSDir that collects blinded public keys
* can check other HSDirs for descriptors based on blinded public keys
* wants to DoS some onion services, but doesn't care which ones

This attacker then:
1. chooses an arbitrary blinded public key from their HSDir's collection
2. calculates possible HSDirs for the blinded public key
3. checks each of the possible HSDirs to see if they actually have a descriptor 
with that blinded public key
4. runs a DoS on each HSDir with that blinded public key (this is the 
resource-intensive step)

A refinement of the attack is to:
A. find the HSDirs for each blinded public key uploaded to the malicious HSDir 
(steps 1-3)
B. find the blinded public keys with the most vulnerable HSDirs (perhaps using 
the maximum consensus weight among all 5)
C. DoS the blinded public keys with the most vulnerable HSDirs (step 4)

I think this is a plausible attack that could be run repeatedly until the 
attacker happens to hit a high-value onion service, or a high enough number of 
onion services. The targeted onion services would change blinded keys and 
HSDirs with the next shared random period, but they'd be down for up to 24 
hours.

Mitigation Summary:

If each onion service uploads a descriptor with a different blinded public key 
to each HSDir, a malicious HSDir is unable to locate other HSDirs serving the 
same onion service.

Even with this change, an attacker who knows an onion service's address can 
still find all its HSDirs. They can set up a malicious HSDir and wait unit it 
happens to get chosen as the HSDir for that service. But this targeted attack 
requires more resources and time than the unknown target attack I describe 
above. The unknown target attack can also consider hundreds of blinded public 
keys, and target the ones with the weakest HSDirs. The targeted attack is 
unlikely to host a particular descriptor on a malicious HSDir, and also 
simultaneously find that all the other HSDirs for that descriptor are 
vulnerable to a DoS.

Alternative Mitigations:

The current keyblinding nonce is based on the period number and start time. 
This allows the blinded keys to be generated in advance. But the drawback is 
that the blinded key is the same for each HSDir.

> There is a master keypair (sk, pk). Given the keypair and a nonce n, there is 
> a derivation function that gives a new blinded keypair (sk_n, pk_n). This 
> keypair can be used for signing. Given only the public key and the nonce, 
> there is a function that gives pk_n.
> …
> 
> We propose the following scheme for key blinding, based on Ed25519.
> 
>   (This is an ECC group, so remember that scalar multiplication is the
>   trapdoor function, and it's defined in terms of iterated point
>   addition. See the Ed25519 paper [Reference ED25519-REFS] for a fairly
>   clear writeup.)
> 
>   Let the basepoint be written as B. Assume B has prime order l, so
>   lB=0. Let a master keypair be written as (a,A), where a is the private
>   key and A is the public key (A=aB).
> 
>   To derive the key for a nonce N and an optional secret s, compute the
>   blinding factor h as H(A | s, B, N), and let:
> 
>       private key for the period:   a' = h a
>       public key for the period:    A' = h A = (ha)B
> 
> Generating a signature of M: given a deterministic random-looking r (see 
> EdDSA paper), take R=rB, S=r+hash(R,A',M)ah mod l. Send signature (R,S) and 
> public key A'.
> 
> Verifying the signature: Check whether SB = R+hash(R,A',M)A'.
> 
> (If the signature is valid,
> SB = (r + hash(R,A',M)ah)B
>       = rB + (hash(R,A',M)ah)B
>       = R + hash(R,A',M)A' )
> …
> (To use this with Tor, set N = INT_8(period-number) | INT_8(Start of period 
> in seconds since epoch).)

Mitigation #1:

Mitigation #1 generates a blinded key for every replica. (This means the 
attacker can only target a single replica of the descriptor, which they can 
likely do anyway, because the "spread_store" HSDirs are in order on the hash 
ring.)

But it looks like this can't be done in advance, because the number of replicas 
is a consensus parameter.

> The following consensus parameters control where a hidden service descriptor 
> is stored;
>   hsdir_n_replicas = an integer in range [1,16] with default value 2.
>   hsdir_spread_fetch = an integer in range [1,128] with default value 3.
>   hsdir_spread_store = an integer in range [1,128] with default value 3.
>   hsdir_spread_accept = an integer in range [1,128] with default value 8

But we can instead generate a blinded key for every replica mod 
default_hsdir_n_replicas.

Then, if the number of replicas in the consensus increases, the attack can only 
target 1/default_hsdir_n_replicas of the replicas. It simply doesn't know where 
the other replicas are, because they have different blinded keys.

The spec change for Mitigation #1 would look like:

> (To use this with Tor, create a key, blinded_public_key(advance_replicanum) 
> for each N = INT_8(period-number) | INT_8(Start of period in seconds since 
> epoch) | INT_8(advance_replicanum), where advance_replicanum takes every 
> value from 1 to default_hsdir_n_replicas. Each key is used for the replicas 
> where replicanum mod default_hsdir_n_replicas is equal to advance_replicanum.)


The blinded key is then used by both the service and clients to determine the 
descriptor's hashring index:

> To determine where a given hidden service descriptor will be stored in a 
> given period, after the blinded public key for that period is derived, the 
> uploading or downloading party calculate
> for replicanum in 1...hsdir_n_replicas:
>   hs_index(replicanum) = H("store-at-idx" |
>       blinded_public_key(replicanum mod default_hsdir_n_replicas)  |
>       INT_8(replicanum) |
>       INT_8(periodnum) )


For Mitigation #1, including advance_replicanum in the keyblinding does NOT 
remove the need for replicanum in the hs_index(replicanum) hash. 
(advance_replicanum could be lower than replicanum if the hsdir_n_replicas 
consensus parameter is increased from the default.)

There's also a slight clarification to the spread upload wording:
> Finally, for replicanum in 1...hsdir_n_replicas, the hidden service host 
> uploads descriptors for the corresponding replica's blinded signing key to 
> the first hsdir_spread_store nodes whose indices immediately follow 
> hs_index(replicanum).


In summary, by creating a blinded public key for each replica, Mitigation #1 
restricts the attack described to a single descriptor replica per malicious 
HSDir. This significantly increases the resources or knowledge required for the 
attack.

Mitigation #2:

Mitigation #2 generates a blinded key for every spread. (This means the 
attacker can only target a single spread for the descriptor.)

Spread is specified like this in the existing proposal:
> Finally, for replicanum in 1...hsdir_n_replicas, the hidden service host 
> uploads descriptors to the first hsdir_spread_store nodes whose indices 
> immediately follow hs_index(replicanum).


A similar process applies for advance generation of blinded public keys:
> (To use this with Tor, create a key blinded_public_key(advance_spreadnum) for 
> each N = INT_8(period-number) | INT_8(Start of period in seconds since epoch) 
> | INT_8(advance_spreadnum), where advance_spreadnum takes every value from 1 
> to default_hsdir_spread_store. Each key is used for the spreads where 
> spreadnum mod default_hsdir_spread_store is equal to advance_spreadnum.)


Mitigation #2 changes the way the spread indexes are positioned on the 
hashring. Because the blinded key for each advance_spreadnum is different, the 
spreads are distributed around the hashring, rather than appearing on it 
sequentially. To maintain this distribution if the consensus value of 
hsdir_spread_store is increased, we have two options:
* spreadnum can be included in the descriptor index hash (Mitigation #2A), or
* the spread position need only be incremented once for each advance_spreadnum 
spreads (Mitigation #2B).

Mitigation #2A:

spreadnum can be included in the descriptor index hash:

> To determine where a given hidden service descriptor will be stored in a 
> given period, after the blinded public key for that period is derived, the 
> uploading or downloading party calculate
> for replicanum in 1...hsdir_n_replicas:
>   for spreadnum in 1...hsdir_spread_store:
>     hs_index(replicanum, spreadnum) = H("store-at-idx" |
>       blinded_public_key(spreadnum mod default_hsdir_spread_store) |
>       INT_8(replicanum) |
>       INT_8(spreadnum) |
>       INT_8(periodnum) )

There's also a slight clarification to the spread upload wording:
> Finally, for replicanum in 1...hsdir_n_replicas, the hidden service host 
> uploads descriptors for the corresponding spread's blinded signing key to the 
> first node whose index immediately follows hs_index(replicanum, spreadnum). 
> This uploads to hsdir_spread_store nodes per replica.


Mitigation #2B:

The spread position need only be incremented once for each advance_spreadnum 
spreads:

> Finally, for replicanum in 1...hsdir_n_replicas, the hidden service host 
> uploads descriptors to hsdir_spread_store nodes per replica as follows:
> the descriptor for the corresponding spreadnum's blinded signing key is 
> uploaded to the first node whose index immediately follows 
> hs_index(replicanum, spreadnum). If any spreads would upload to the same 
> node, they instead upload to the second and subsequent nodes whose indices 
> immediately follows the hs_index.


But spreadnum is NOT included in the descriptor index hash:

> To determine where a given hidden service descriptor will be stored in a 
> given period, after the blinded public key for that period is derived, the 
> uploading or downloading party calculate
> for replicanum in 1...hsdir_n_replicas:
>   hs_index(replicanum, spreadnum) = H("store-at-idx" |
>       blinded_public_key(spreadnum mod default_hsdir_spread_store) |
>       INT_8(replicanum) |
>       INT_8(periodnum) )


In summary, by creating a blinded public key for each replica, Mitigation #2 
(both variants) restricts the attack described to a single descriptor spread 
per malicious HSDir. This significantly increases the resources or knowledge 
required for the attack.

Mitigation #3:

Alternately, we could double blind each public key: once when it is generated 
from the master key in advance, and again just before it is uploaded to the 
HSDir.

This double-blinding should use two nonces, P and Q:
* P is the advance-blinding nonce based on the period
* Q is the post-blinding nonce based on the replica and/or spread
(I considered and rejected a design where Q is based on the fingerprint of the 
HSDir. This would lead to blinding with an externally-supplied fingerprint, 
potentially chosen by an attacker to reveal information on blinding.)

The blinding would look something like this, where H(Q, H(P, …)) replaces H(N, 
…):

> There is a master keypair (sk, pk). Given the keypair and a nonce n, there is 
> a derivation function that gives a new blinded keypair (sk_n, pk_n). This 
> keypair can be used for signing. Given only the public key and the nonce, 
> there is a function that gives pk_n.
> …
> 
> We propose the following scheme for key blinding, based on Ed25519.
> 
> (This is an ECC group, so remember that scalar multiplication is the trapdoor 
> function, and it's defined in terms of iterated point addition. See the 
> Ed25519 paper [Reference ED25519-REFS] for a fairly clear writeup.)
> 
> Let the basepoint be written as B. Assume B has prime order l, so lB=0. Let a 
> master keypair be written as (a,A), where a is the private key and A is the 
> public key (A=aB). To derive the key for a advance-blinding nonce P, an 
> optional secret s, and a post-blinding nonce Q:
> compute the advance-blinding factor h as H(A | s, B, P), and let:
> 
> advance-blinded private key for the period: a' = h a
> advance-blinded public key for the period: A' = h A = (ha)B
> 

> compute the post-blinding factor j as H(A' | 0, B, Q), and let:
> 

> post-blinded private key for the period: a'' = j a' = j (ha)
> post-blinded public key for the period: A'' = j A' = j ((ha)B)
> 
> Generating a signature of M: given a deterministic random-looking r (see 
> EdDSA paper), take R=rB, S=r+hash(R,A'',M)ahj mod l. Send signature (R,S) and 
> public key A''.
> 
> Verifying the signature: Check whether SB = R+hash(R,A'',M)A''.
> 
> (If the signature is valid,
> SB = (r + hash(R,A'',M)ahj)B
>       = rB + (hash(R,A'',M)ahj)B
>       = R + hash(R,A'',M)A'' )
> ...
> (To use this with Tor, set P = INT_8(period-number) | INT_8(Start of period 
> in seconds since epoch) and Q = INT_8(replicanum) and/or INT_8(spreadnum).


Other Considerations:

Combining Mitigations:

Mitigations #1 and #2 (either variant) can be combined, but this means 
generating 6 blinded keys per period, rather than 1. I don't think the extra 
benefit gained from implementing both outweighs the complexity involved.

Issue with Per-Spread Keys:

One drawback of Mitigations #2 and #3 (different blinding keys for each spread) 
is that the client may need to request multiple blinded keys from each HSDir 
due to relays dropping out of the hash ring.

For example:
1. An onion service uploads descriptors with keys A, B, C to spread servers a, 
b, c respectively
2. Spread server a goes down
3. the client wants the descriptor for the onion service, and tries spread 
servers b, c, d
The client doesn't know how many servers have gone down. Therefore, it will 
need to ask for A, B, C from the first server (actually b, which has B); B, C 
from the second server (actually c, which has C); and C from the third server 
(actually d, which has nothing).

I don't like the extra load involved in this, and I wonder about the potential 
for information leaks to either the HSDir, or an observer of the client's 
connection. (For example, each of the traffic patterns above is distinct, so an 
observer of the client could tell which spread number it used for the 
descriptor.)

Security Proofs:

The modified security proof in Mitigation #3 would need to check out for me to 
be comfortable with this option.

Conclusion:

Can we consider Mitigation #1: creating a different blinded public key for each 
replica?
This would double the number of keys we generate in advance, one for each 
replica.

Alternately, we could use Mitigation #3: double-blinding each key with the 
period in advance, then the replica number just before upload. But we'd need to 
check the modified security proof carefully.

Tim

Tim Wilson-Brown (teor)

teor2345 at gmail dot com
PGP 968F094B

teor at blah dot im
OTR CAD08081 9755866D 89E2A06F E3558B7F B5A9D14F

Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

_______________________________________________
tor-dev mailing list
tor-dev@lists.torproject.org
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-dev

Reply via email to