DNS in IPFS

Over the past couple of years, I’ve been thinking about things that could be used to replace parts of the internet and web we currently use. There are projects like cjdns that are looking to replace the network routing layer of the internet with a system that does not require a centralized authority to issue IP addresses.

There are other parts of the web stack that are looking to be replaced (IPFS is one of them, looking to replace HTTP(S)), but the one I will be looking at in this post is the Domain Name System (DNS).

There have been a couple of attempts to get a blockchain DNS system replacement, like Namecoin and ENS that look to provide a blockchain-backed top level domain (TLD). Personally, I think these are going to have limited utility because they cannot tie into the existing domain name system in a secure manner and are an all-or-nothing prospect. The system I am going to detail in this post would allow a gradual phase-in on both the general public and the domain registrars.

The Idea

The main core is to keep the DNS system infrastructure more or less intact and serve the zones over both the existing DNS infrastructure and thru IPFS/IPNS, and have pointers between the two systems whenever possible, but treat the version in IPFS as authoritative. Then a system that only supports traditional DNS can use a server that may resolve domains using either DNS or IPFS, and probably both.

To get started, a root zone needs to be created and served via IPFS that initially points to the existing DNS root zone servers. That root zone file would then be incrementally extended with links to other zone files for the TLDs, which would then include subdomains, and so on, always with links back to the existing system everywhere there are not DNS-Over-IPFS zone files available. DNS servers with native support for IPFS zone files can be referenced with an NS record in both databases. In this manner the system can be gradually phased in without breaking existing applications and with only the DNS servers needing to be upgraded.

Additionally, because this is only a way of storing the DNS zones in IPFS, existing security measures like DNSSEC will continue to work exactly the same way as they currently do.

There is a small issue, however, in that anyone could enumerate the entire domain space, and this has network security implications as it exposes information about system infrastructure. But that is solvable with a bit of encryption.

Privacy Thru Encryption

Privacy for subdomains can be achieved efficiently thru the use of perfect hashing and encryption.

First, a given zone file is split between public records and private records. Public records would include such items as the Start of Authority (SOA) records for a zone, mail servers, name servers and any record referenced by a standard. Everything else would be considered private.

Each record has a salt value chosen and combined with the domain and record type to create a symmetric encryption key for the record values. The subdomain name itself is never included in the zone file and instead must be provided as part of a query. I expect something like

key = Digest::Sha512.hexdigest( salt + Digest::Sha512.hexdigest( domain ) )

or a similarly cryptographically secure hash function is used to generate the encryption key.

A perfect hash would be used so that we can use a hash table to store the domain records while being able to safely assume there are zero collisions. The wikipedia page on perfect hashes is pretty much useless to use for implementation, but this site has a practical implementation using two levels of hash arrays.

For the encryption algorithm, I’ve been partial to XOR’ing the data with the output of a stream generated by a Blum Blum Shub cryptographically secure pseudo-random number generator because it is conceptually simple, and thus easy to implement, while also being cryptographically secure.

Proof of Concept

I created a proof of concept encoder for this scheme. I use the following algorithms:

The source code can be found here.

Starting Zone File:

$ORIGIN	example.com
$TTL		3600
example.com.  IN  SOA   ns.example.com. username.example.com. ( 2020091025 7200 3600 1209600 3600 )
example.com.  IN  NS    ns                    ; ns.example.com is a nameserver for example.com
example.com.  IN  NS    ns.somewhere.example. ; ns.somewhere.example is a backup nameserver for example.com
example.com.  IN  MX    10 mail.example.com.  ; mail.example.com is the mailserver for example.com
@             IN  MX    20 mail2.example.com. ; equivalent to above line, "@" represents zone origin
@             IN  MX    50 mail3              ; equivalent to above line, but using a relative host name
example.com.  IN  A     192.0.2.1             ; IPv4 address for example.com
              IN  AAAA  2001:db8:10::1        ; IPv6 address for example.com
ns            IN  A     192.0.2.2             ; IPv4 address for ns.example.com
              IN  AAAA  2001:db8:10::2        ; IPv6 address for ns.example.com

;PRIVATE
notpr0n       IN  TXT   "http://notpron.org/notpron/"
rick          IN  TXT   "https://d.tube/#!/v/agentgrillo1914/QmTMM6qNvamhPxAFop8jYj86f4qnGhxWm3n3CboShkN9WT"
ref           IN  TXT   "http://stevehanov.ca/blog/?id=119"
namecoin      IN  TXT   "https://www.namecoin.org/"
home          IN  A     127.0.0.1

Example Encoding:

{
  "pub": [
    "SOA|example.com.|ns.example.com. username.example.com. ( 2020091025 7200 3600 1209600 3600 )",
    "NS|example.com.|ns",
    "NS|example.com.|ns.somewhere.example.",
    "MX|example.com.|10 mail.example.com.",
    "MX|example.com.|20 mail2.example.com.",
    "MX|example.com.|50 mail3",
    "A|example.com.|192.0.2.1",
    "AAAA|example.com.|2001:db8:10::1",
    "A|ns.example.com.|192.0.2.2",
    "AAAA|ns.example.com.|2001:db8:10::2"
  ],
  "priv": {
    "ht": [
      7,
      0,
      1
    ],
    "rr": [
      "kapsseBwprp0e|GbthPI9BQcXx3gQNR99m/GNUdCTXXXa8TQ/hwfAZ2GTB",
      "krikwjk0fvyql|Ao/1Ad/q1i/3dER+/Xq1UYj1NWLxIIvDpL2uWDugMg==",
      "Bueggxiqjkvaw|Bn7ElPSbXAVMT4oxRVgPtJgQRLGHJKzi2qCV9wtcysKO0C1XnRew",
      "pgpsqjfspekxi|W62XoNW1xvJAL/kMkA==",
      "lxbsfiBfBvhbo|ApooLp9VbWMWW065n8seldeIMNQ8T5lrDctA6d6Q7O5Y959783S0XW285H9t\nUWgLzK844NfImG/k3H6y7UT0SladTBEE1yTaEshngN5yvVfa9rm9i1UQnQ=="
    ]
  }
}

Side Note

I did discover a bug exists in my git repo publishing code: it doesn’t properly handle adding a repository under a directory that hasn’t previously existing in the repository. It resulted in my repo directory tree being destroyed and forcing me to rebuild the repo with ipfs add -r public.

Comments