Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Identity Key used for Encryption and Signature Creation #291

Open
tynes opened this issue Oct 30, 2019 · 10 comments
Open

Identity Key used for Encryption and Signature Creation #291

tynes opened this issue Oct 30, 2019 · 10 comments
Labels
security improvement classification

Comments

@tynes
Copy link
Contributor

tynes commented Oct 30, 2019

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:

hsd/lib/node/fullnode.js

Lines 84 to 93 in 155051f

this.pool = new Pool({
network: this.network,
logger: this.logger,
chain: this.chain,
mempool: this.mempool,
prefix: this.config.prefix,
selfish: this.config.bool('selfish'),
compact: this.config.bool('compact'),
bip37: this.config.bool('bip37'),
identityKey: this.identityKey,

const peer = Peer.fromOutbound(this.options, addr);

this.identityKey = this.options.identityKey;

this.brontide.connect(socket, this.identityKey, addr.key);


Both the RootServer and the RecursiveServer use the key for authentication, signing messages for SIG0. The codepath:

hsd/lib/node/fullnode.js

Lines 145 to 147 in 155051f

this.ns = new RootServer({
logger: this.logger,
key: this.identityKey,

return hsig.sign(msg, this.key);


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 type KEY (SIG0 specifies that in its RFC).
  • Only the SIG0 key is returned for a DNS request to the Handshake root for RR type KEY
  • Figure out way to deprecate SIG0 for something better long term (leaning towards DNS over HTTPS personally)

cc: @chjj @boymanjor @kilpatty @pinheadmz

@tynes tynes added the security label Oct 30, 2019
@pinheadmz
Copy link
Member

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?

@tynes
Copy link
Contributor Author

tynes commented Oct 30, 2019

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 .

hsd/lib/dns/server.js

Lines 227 to 283 in 155051f

// Our root zone.
if (name === '.') {
const res = new Message();
res.aa = true;
switch (type) {
case types.ANY:
case types.NS:
res.answer.push(this.toNS());
key.signZSK(res.answer, types.NS);
if (IP.family(this.publicHost) === 4) {
res.additional.push(this.toA());
key.signZSK(res.additional, types.A);
} else {
res.additional.push(this.toAAAA());
key.signZSK(res.additional, types.AAAA);
}
break;
case types.SOA:
res.answer.push(this.toSOA());
key.signZSK(res.answer, types.SOA);
res.authority.push(this.toNS());
key.signZSK(res.authority, types.NS);
if (IP.family(this.publicHost) === 4) {
res.additional.push(this.toA());
key.signZSK(res.additional, types.A);
} else {
res.additional.push(this.toAAAA());
key.signZSK(res.additional, types.AAAA);
}
break;
case types.DNSKEY:
res.answer.push(key.ksk.deepClone());
res.answer.push(key.zsk.deepClone());
key.signKSK(res.answer, types.DNSKEY);
break;
case types.DS:
res.answer.push(key.ds.deepClone());
key.signZSK(res.answer, types.DS);
break;
default:
// Empty Proof:
res.authority.push(this.toNSEC());
key.signZSK(res.authority, types.NSEC);
res.authority.push(this.toSOA());
key.signZSK(res.authority, types.SOA);
break;
}
return res;
}

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

@pinheadmz
Copy link
Member

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 ?

@tynes
Copy link
Contributor Author

tynes commented Oct 30, 2019

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.

@tynes
Copy link
Contributor Author

tynes commented Nov 1, 2019

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 #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.

@kilpatty
Copy link
Contributor

kilpatty commented Dec 1, 2019

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.

@james-stevens
Copy link
Contributor

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

@james-stevens
Copy link
Contributor

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

@pinheadmz
Copy link
Member

@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: #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.

@james-stevens
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
security improvement classification
Projects
None yet
Development

No branches or pull requests

5 participants