hsd
hsd copied to clipboard
Identity Key used for Encryption and Signature Creation
Overview
Edited 11/1/19
Right now the identity key (secp256k1 keypair) is used in multiple locations for different purposes.
The Pool
uses it for brontide
. It is used for encryption and authentication in this context. See bolt #8 for more details. The codepath:
https://github.com/handshake-org/hsd/blob/155051f7f9af115dc2b8a7bef90963e54e87c5ed/lib/node/fullnode.js#L84-L93
https://github.com/handshake-org/hsd/blob/155051f7f9af115dc2b8a7bef90963e54e87c5ed/lib/net/pool.js#L1112
https://github.com/handshake-org/hsd/blob/155051f7f9af115dc2b8a7bef90963e54e87c5ed/lib/net/peer.js#L64
https://github.com/handshake-org/hsd/blob/155051f7f9af115dc2b8a7bef90963e54e87c5ed/lib/net/peer.js#L310
Both the RootServer
and the RecursiveServer
use the key for authentication, signing messages for SIG0. The codepath:
https://github.com/handshake-org/hsd/blob/155051f7f9af115dc2b8a7bef90963e54e87c5ed/lib/node/fullnode.js#L145-L147
https://github.com/handshake-org/hsd/blob/155051f7f9af115dc2b8a7bef90963e54e87c5ed/lib/dns/server.js#L584
I have read that it is not the best idea to use the same key for both encryption and in digital signature algorithms as it exposes interactions between the algorithms. The attacker can choose the text that is signed and collect many signatures, since every DNS request that they send will be signed with the key. It is possible that a side channel attack is possible here, such as the recently discovered Minerva. If such a side channel existed, it would be bad if the attacker was able to decrypt the p2p messages between two nodes. I believe an attacker may be able to do this if they were able to collect all traffic between the two nodes and pull the side channel attack on both of the nodes.
Proposal
- Use different key pairs for
SIG0
and for brontide. - Allow them to be separately configured at runtime.
- ~~Both the
SIG0
key and the brontide key returned for a DNS request to the Handshake root.
for the resource record typeKEY
(SIG0
specifies that in its RFC).~~ - Only the
SIG0
key is returned for a DNS request to the Handshake root for RR typeKEY
- Figure out way to deprecate
SIG0
for something better long term (leaning towards DNS over HTTPS personally)
cc: @chjj @boymanjor @kilpatty @pinheadmz
Both the SIG0 key and the brontide key returned for a DNS request to the Handshake root . for the resource record type KEY (SIG0 specifies that in its RFC).
Can you expand on this a little? What is the client/server dialog? Are the keys part of hns
or dns
protocols? What is actually returned?
You can see the code responsible for creating a DNS response message when a DNS request is sent to the Handshake Zone authoritative server for .
https://github.com/handshake-org/hsd/blob/155051f7f9af115dc2b8a7bef90963e54e87c5ed/lib/dns/server.js#L227-L283
Notice that if you send a DNS query to the root for DNSKEY
, it returns the KSK and the ZSK. Handshake implements SIG0 DNS message authentication, but does not handle responding to requests for KEY
resource records. There is no corresponding case
in the switch
statement for types.KEY
. The SIG0 RFC says that the corresponding public key should be returned in a KEY
record from the zone responsible for the SIG0 signing.
Are the keys part of hns or dns protocols?
The key pair is being used in both, which is the reason I am opening this issue. In the Handshake p2p protocol, the key is being used for encryption/authentication in brontide and in the DNS protocol it is used in a digital signature algorithm for SIG0.
What is actually returned?
The definition of a KEY
RR Record, from RFC 3445
1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| flags | protocol | algorithm |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| /
/ public key /
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
KEY RR Format
---------------------------------------------------------------------
Each integer along the top represents a single bit. The /
represents that it can be variable length and does not follow the bit axis along the top. All of the bits in the flag field would be 0. The protocol field would be set to 3. See this snippet from RFC 3445. Note that this RFC updates RFC 2535, which defined the KEY
RR differently.
In the flags field, all bits except bit 7 are reserved and MUST be
zero. If Bit 7 (Zone bit) is set to 1, then the KEY is a DNS Zone
key. If Bit 7 is set to 0, the KEY is not a zone key. SIG(0)/TKEY
are examples of DNSSEC keys that are not zone keys.
The protocol field MUST be set to 3.
The algorithm and public key fields are not changed.
The algorithm field is defined in RFC 2535, Section 3.2
3.2 The KEY Algorithm Number Specification
This octet is the key algorithm parallel to the same field for the
SIG resource as described in Section 4.1. The following values are
assigned:
VALUE Algorithm
0 - reserved, see Section 11
1 RSA/MD5 [RFC 2537] - recommended
2 Diffie-Hellman [RFC 2539] - optional, key only
3 DSA [RFC 2536] - MANDATORY
4 reserved for elliptic curve crypto
5-251 - available, see Section 11
252 reserved for indirect keys
253 private - domain name (see below)
254 private - OID (see below)
255 - reserved, see Section 11
Extra information below:
Algorithm numbers 253 and 254 are reserved for private use and will
never be assigned a specific algorithm. For number 253, the public
key area and the signature begin with a wire encoded domain name.
Only local domain name compression is permitted. The domain name
indicates the private algorithm to use and the remainder of the
public key area is whatever is required by that algorithm.
You can see that bns
uses sig0.algs.PRIVATEDNS
or 253
, which corresponds to the RFC above:
hsig.createKey = function createKey(pub) {
return sig0.createKey(sig0.algs.PRIVATEDNS, pub);
};
PRIVATEDNS: 253, // Private (experimental keys)
https://github.com/chjj/bns/blob/6d517b4b4dbdab1fe289347e9b1ebd79e9b0dcb5/lib/hsig.js#L41
https://github.com/chjj/bns/blob/6d517b4b4dbdab1fe289347e9b1ebd79e9b0dcb5/lib/constants.js#L535
I thought the key
used to zone-sign and key-sign was from the hard-coded file https://github.com/handshake-org/hsd/blob/155051f7f9af115dc2b8a7bef90963e54e87c5ed/lib/dns/key.js ?
I thought the
key
used to zone-sign and key-sign was from the hard-coded file https://github.com/handshake-org/hsd/blob/155051f7f9af115dc2b8a7bef90963e54e87c5ed/lib/dns/key.js ?
That is for DNSSEC, it creates a RRSIG
RR in the response. That is different than SIG0. Please see the SIG0 RFC.
$ dig @8.8.8.8 +dnssec internetsociety.org
; <<>> DiG 9.14.6 <<>> @8.8.8.8 +dnssec internetsociety.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 699
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;internetsociety.org. IN A
;; ANSWER SECTION:
internetsociety.org. 284 IN A 46.43.36.212
internetsociety.org. 284 IN RRSIG A 5 2 300 20191113084004 20191030084004 18312 internetsociety.org. Un8cI85Cg/B0LiltV9sik+nBpigGfgfX30O845l36FLUtjC4AgExOSOW OqxgNLwmQvS5KqmPeqWfd5hrFPGOS36wtHOHj5NZlNCGQ/1npzEI555i 8iSpxytg1BZblaye1wNrQJtJVUTSnb29/kqkcvOEHS+2U9DYcvjtb75P tUA=
;; Query time: 29 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Wed Oct 30 13:14:11 PDT 2019
;; MSG SIZE rcvd: 243
Example from hsd
$ dig @127.0.0.1 -p 25350 google.com
; <<>> DiG 9.14.6 <<>> @127.0.0.1 -p 25350 google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 38990
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 2
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 12556fb2c139b189 (echoed)
;; QUESTION SECTION:
;google.com. IN A
;; ANSWER SECTION:
google.com. 287 IN A 172.217.6.78
;; SIG0 PSEUDOSECTION:
. 0 ANY SIG 0 253 0 0 20191031021621 20191030141621 29010 . Nh4iwSmDc8g9+3vEtfPIdQlAFAj06K7fp2crG2CGeAJi6m53vnp5rD4a JltSg2xQrS/HHxAfM+2hj9YBaxGNbQ==
;; Query time: 1 msec
;; SERVER: 127.0.0.1#25350(127.0.0.1)
;; WHEN: Wed Oct 30 13:16:21 PDT 2019
;; MSG SIZE rcvd: 161
Notice the SIG0 PSEUDOSECTION
This does raise the issue though of how to differentiate the SIG0 key and the brontide key if they are both made available via the DNS server. If they are both KEY
records, then it will be unclear to the client to know which key to use to verify the SIG0 signature.
After talking to @boymanjor, we are thinking that there should be a clean split between the protocol layer and the DNS application layer. The brontide p2p identity key should NOT be made available via DNS. This would simplify my above proposal, as the SIG0
key could be made available as a KEY
RR and the client wouldn't be confused by having multiple different keys returned.
@boymanjor also pointed out that in https://github.com/handshake-org/hsd/issues/126, it mentions:
Drop SIG(0) in favor of a cache/proxy-friendly signing mechanism.
Note that this is fairly offtopic from this issue and we can discuss the removal of SIG0
in a different issue.
I am in the process of researching what makes SIG0
not cache friendly, right now I am thinking that it is due to non-canonical signatures. Since we are using ECDSA and have RFC 6979 implemented in both the native and JS backends of bcrypto
, this shouldn't be a problem for us (RRSIG
records have signatures in them). So it must be something else. Having a timestamp or a TTL that is altered could be another source of cache unfriendliness. Really anything that alters between DNS requests that is committed to in the SIG0
signature.
It also mentions:
Note that anything DNS-related is essentially policy and can be changed post-mainnet.
Seems like the consensus here is a split between ID key and anything used for DNS. I was working on implementing some of the security considerations from the noise protocol paper into brontide today, and just came across this:
Static key reuse: A static key pair used with Noise should be used with a
single hash algorithm. The key pair should not be used outside of Noise,
nor with multiple hash algorithms. It is acceptable to use the static key
pair with different Noise protocols, provided the same hash
algorithm is used in all of them. (Reusing a Noise static key pair
outside of Noise would require extremely careful analysis to ensure
the uses don't compromise each other, and security proofs are preserved).
Source: https://noiseprotocol.org/noise.html#security-considerations
Just wanted to bump this thread with that consideration. I also am for keeping the Identity key *only for brontide, and having separate signing keys for DNS.
First, my 2c on the question at hand - yes, use separate keys (subject to dropping SIG0
)
Drop SIG(0) in favor of a cache/proxy-friendly signing mechanism
I think this is the problem I have hit - unless the other side has the key, it can't verify the SIG0 record, so just rejects it. This is a guess, but seems about right. The SIG0
RFC talks about KEY RRs
, but I couldn't see how it was meant to access them, and bind was definitely not trying to query for them (tcdump).
we are thinking that there should be a clean split between the protocol layer and the DNS application layer
Personally, I think there is good case to say it is not necessary to implement a DNS layer at all.
You could A) produce a text zone file in RFC format, then any DNS server could run with that, signed or not
B) use PowerDNS in one of two ways
.. 1) Implement hsd
as a back-end storage mechanism directly into PowerDNS
.. 2) Use the PowerDNS rest/api to push updates from hsd
into the zone
Any of the above gives you a DNS mechaism that is capable of scaled performance & full RFC compliance.
If you just have hsd
as an authritative server, you could also use bind as a resolver and forward
zone "."
queries to hsd
- currently I can't get this working as I can't get the SIG0
validation to work, so bind just rejects all hsd
responses.
If hsd
supported zone transfer (it might, but I couldn't find any docs), then you could do something like this https://www.isc.org/docs/Apricot2017.pdf - slave the root zone into a bind resolver.
However, your DNSSEC is currently not RFC compliant, so if you put it into bind, I suspect DNSSEC validation wouldn't work.
Personally, I would think decentralised signing, as per the Yeti project, would be a more interesting way forward, as it removes the single point of failure of the root keys. Yeti: https://apan.net/meetings/apan46/files/22/22-04-03-01.pdf
Yeti TL;DR - the point of the Yeti project was to show that if all ROOT operators used the same algorithm, then each ROOT operator could individually sign the ROOT zone with their own keys, with all 13 DS records included. So far (since 2015) it seems to work.
So make it such that hsd
only produces an unsigned zone, using RFC XFR, which has been validated by the blockchain anyway, then various "[root] operators" could take the zone, sign it & serve it signed with their own keys and people could either choose who & how many they want to trust - the only requirement here is that all "root operators" use the same algorithm, say ecdsa256
Any / all operators could also make the zone available for XFR, which could then be used as per the ISC doc above, for a more robust service.
Any / all of these suggestions would make it far easier to create a resolver that uses the handshake
ROOT, instead of the ICANN ROOT
My 2c
About me For 10 yrs I was CTO at the dot-IO domain registry, until it was sold to Afilias in 2016 I've spoken at various DNS conferences like OARC & CENTR (catch-up on your sleep, people) Checkout my open source PowerDNS WebUI - 100% javascript :) I also have a Python/Flask DoH proxy that might be intersting
I would also recommend you change from a YYYYMMDDXX
SOA serial to UNIXTIME
as the old format gags badly if you go over 100 updates in a day.
Presumably the blocks in the chain have a time-stamp you could use, which would be a nice confirmation of where in the chain the zone file is fresh to.
Would also be nice to include some CHAOS
class queries for debugging etc. - you could add some to give out some block-chain info
@james-stevens thanks for all the great insight, I'd love to chat with you on #handshake on freenode about some of these details sometime. I'll try to address a few of your points but I don't have the background you do.
Re: CHAOS class: I had an idea about this as well: https://github.com/handshake-org/hsd/issues/443
This would mainly apply to hnsd the light client because it does not have any other interface besides DNS. hsd full nodes have a vast API for blockchain info already, and I'm not entirely sure that making chain status available to public queries is secure, private, DDoS resistant, etc.
Re: SIG(0): This issue is a little outdated now since hsd full nodes no longer use Brontide (peer to peer encrypted communication) by default any more. There were issues with invalid public keys being gossiped around the network and the way it works now is that a user doesn't connect with Brontide unless they obtain the public key for that node out-of-band. SO for example, I would set up my own cluster of hsd full nodes to use for HNS resolution on my own computers, and plug the nodes' public keys into hdns to verify SIG(0). An example of that process is demonstrated in this article.
This way, I can trust the answers from that node, and since I have confidence that this node is on the best valid proof-of-work chain, my system does not require a signed root zone.
Interestingly, hsd full nodes also have a hard-coded private KSK/ZSK pair that is public! So of course the signatures are insecure but if you trust the resolver you can trick your legacy system into accepting the root keys for DNSSEC proofs.
Re: Root Zone Dump: Another thing you bring up is the HNS resolver is missing some functionality, especially zone transfers. The HNS resolver is based on chjj/bns and I use it in several other projects. I recently discovered that zone transfer queries are not yet implemented and so I had to switch libraries in some cases. A pull request to bns would be super awesome to add this function in. There has also been discussion and a WIP branch to implement root-zone dumps from hsd.
Re: SOA serial numbers: Another property of HNS you might not be aware is that the DNS records are all stored in a novel data structure called the Urkel Tree as described in the whitepaper, Urkel allows very fast compact proofs including exclusion proofs which make the light client hnsd
highly portable. The Urkel tree is actually only updated 4 times a day (every 36 blocks) and this is why HNS root zone records all have a hard-coded TTL of 6 hours. So I don't know if this addresses your comment but seems like it might.
However, your DNSSEC is currently not RFC compliant, so if you put it into bind, I suspect DNSSEC validation wouldn't work.
Could you expand on this a little more?
If you just have hsd as an authritative server, you could also use bind as a resolver and forward zone "." queries to hsd - currently I can't get this working as I can't get the SIG0 validation to work, so bind just rejects all hsd responses.
I've had some issues on Ubuntu where NetworkManger and its stub resolver running internally at 127.0.0.53
does not like being connected to an HNS resolver. I was able to disable NetworkManager and just hard-code nameserver <My HNS full node public IP>
in resolv.conf
. But I was wondering why NetworkManager wasn't happy - could be the SIG(0) issue. I've also had an issue with '.'
using dig @ HNS, but I'm not sure if it's related.
Again thanks so much for your time and interest in Handshake, if you want to join us on IRC or Telegram https://t.me/hns_tech I'd be so happy to pick your brain about these details.
However, your DNSSEC is currently not RFC compliant, so if you put it into bind, I suspect DNSSEC validation wouldn't work.
Could you expand on this a little more?
Quite a few of the proofs I looked at are not RFC compliant - for example NXDOMAIN
& NODATA
, both (at least partly) caused by the fact you seem to only have one single NSEC
record in the entire zone, but you should have one per name.
Look at the difference between
dig +dnssec @e.root-servers.net. zw ds
and
dig +dnssec -p 5300 @127.0.0.1 js. ds
dig +dnssec @e.root-servers.net. zz ds
and
dig +dnssec -p 5300 @127.0.0.1 qq-qq-qq. ds
and I hope you will see what I mean.
If your DNSSEC was compliant, it would be easy to get bind
to trust your keys, for example by using trusted-keys { ... };
But, like I said, I think spending time writing yet another DNSSEC compliant DNS signer & server is an unnecessary use of resources when there is already a ton of stuff out there that does a pretty fine job.
And yes, shipping out the private keys to everybody isn't great, although you could argue the contents of the ROOT zone is intended to be public knowledge anyway - but that's a whole different rabbit hole. But hard-coding them is a bad idea becuase - key rollover.
I think decentralising signing and making the signed zones available for XFR, and letting people chose who they trust, would be a good solution in terms of the aims of the project.
Re: Root Zone Dump
A dump an unsigned zone to text file would be better than nothing - it would be easy to integrate that into PowerDNS
or bind
. For me, this would be what I'd look to implement on day-1, maybe in a week - I will see how time goes
Of course XFR would be nicer, but using bind
to pick up the text file and give an XFR is trivial (ditto PowerDNS).
Re: SOA serial numbers
Ok - what you were doing wasn't immediately apparent as the last two digits of the SOA Serial appear to suggest more then 4 updates per day - e.g. 2020062514
.. it tracks the hour of the day, by the looks.
The point more generally I was trying to make was I think you are unnecessarily trying to reinvent a wheel that not only doesn't need reinventing, but has already been reinvented more than enough times - i.e there is already far more choice of DNS server software than is really necessary - bind, unbound, nsd, PowerDNS & Knot are all pretty good.
Obviously bind
is not a work of art, but can't be beat in terms of configurability and flexibility. When I last looked it was the only one that could be recursive & authoritative, which isn't to be recommended for a public server, but definitely has its uses.
I've actually written a DNSSEC compliant authoritative DNS server before - its still live on 194.0.1.0/24
& 194.0.2.0/24
serving quite a few ICANN TLDs, but it was designed to take a zone of up to 500M names with 0.1ms response time, which none of the others can. Otherwise, there's no way I would have bothered.
So, if it was me, I would dump the unsigned zone to a text file and be done. The rest can be handled perfectly well by existing s/w
If what you have can't integrate with existing DNS s/w then you must realise this limits its use and I suspect that scaling this to realistically high query volumes just isn't possible.
The other point was to decouple the DNS from the blockchain, by either dumping a zone or XFR - there's no need to have them so integrated.