# DNSSEC validation in Go for fun and profit


Doing cryptography is hard, luckily there are enough libraries 
out there that help you with it. [OpenSSL](http://www.openssl.org) is
probably one of the best (known).
Go has its own [crypto library](http://golang.org/pkg/crypto/), which is 
written in pure Go.

Now with these aids crypto becomes doable for mere mortals, but
all these libraries work with buffers which hold the data, the
signature and sometimes the key also. Off-by-one errors in composing
these buffers leads to a "Bogus signature" error (in DNSSEC). The problem
here is that you don't get any other clue on what went wrong. For me as
a programmer an error such as "Shift buffer A one byte to the
left and you're OK", would be much better. But due to the nature of crypto 
these kind of errors are not possible, nor desirable.

So you are then faced with the problem of getting 3 buffers filled with 
the right content and rightly aligned and the only feedback you get
is OK or not OK. Clearly this is a nasty problem.

# Filling buffers
In [RFC 4035](https://datatracker.ietf.org/doc/rfc4035/?include_text=1),
section "5.3.2.  Reconstructing the Signed Data" says the following:

        signed_data = RRSIG_RDATA | RR(1) | RR(2)...  where

            "|" denotes concatenation

            RRSIG_RDATA is the wire format of the RRSIG RDATA fields
               with the Signature field excluded and the Signer's Name
               in canonical form.

            RR(i) = name | type | class | OrigTTL | RDATA length | RDATA

               name is calculated according to the function below
               class is the RRset's class
               type is the RRset type and all RRs in the class
               OrigTTL is the value from the RRSIG Original TTL field
               All names in the RDATA field are in canonical form

Which is pretty strait forward, but it needs a lot of conversions from
my local representation to the wire format and concatenating buffers.
Needless to say, this can go wrong in many places.

 Going forward
So how do figure out if you're getting close to successfully validating a
signature in DNSSEC? I used the following procedure:

* Debug the *hell* out of your program;
* Find some *other* piece of software that successfully! performs the same function;
* Debug the *hell* out of this other piece of software;
* Hopefully you'll get some insights on where your program is failing.

 Actual steps taken
The other software I used is [ldns](http://nlnetlabs.nl/projects/ldns) as 
I'm very familiar with its source code.

Next I wrote I little C program (`ldns-verify`), that does signature validation, and
also prints the RRs (as an extra control measure).

        /* ... */
        ldns_rr_new_frm_str(&dnskey, "miek.nl. IN DNSKEY 256 3 8
        AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5EC
        IoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXH
        Py7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz", 14400, NULL, NULL);

        ldns_rr_new_frm_str(&soa, "miek.nl. IN SOA open.nlnetlabs.nl.
        miekg.atoom.net. 1293945905 14400 3600 604800 86400", 14400,
        NULL, NULL);

        ldns_rr_new_frm_str(&rrsig, "miek.nl. IN RRSIG SOA 8 2 14400
        20110201042505 20110102042505 12051 miek.nl.
        oMCbslaAVIp/8kVtLSms3tDABpcPRUgHLrOR48OOplkYo+8TeEGWwkSwaz/MRo2fB4FxW0qj
        /hTlIjUGuACSd+b1wKdH5GvzRJc2pFmxtCbm55ygAh4EUL0F6U5cKtGJGSXxxg6UFCQ0doJC
        miGFa78LolaUOXImJrk6AFrGa0M=", 14400, NULL, NULL);

        ldns_rr_print(stdout, rrsig);

        ldns_rr_list_push_rr(soalist, soa);
        ldns_rr_list_push_rr(keylist, dnskey);
        ldns_rr_list_print(stdout, soalist);
        ldns_rr_list_print(stdout, keylist);

        s = 0;
        s = ldns_verify_rrsig_keylist_notime(soalist, rrsig, keylist, goodkeys);
        printf("WTF: %s\n", ldns_get_errorstr_by_id(s));

With this I found my first mistake. Which was that I didn't take the
timezone (UTC) into account when setting the signature inception and
expiration times in my test program.

Next I put the ldns library itself full of debugging statements, for instance in
the file `dnssec_verify.c` in the function `ldns_verify_rrsig_evp_raw`,
that print the raw content of the buffers in decimal:

         fprintf(stderr, "RRSETBUF\n");
         size_t i;
         for(i = 0; i < ldns_buffer_position(rrset); i++) {
              fprintf(stderr, "%d ", ldns_buffer_read_u8_at(rrset, i));
         }
         fprintf(stderr, "\n");
 
         fprintf(stderr, "SIGBUF\n");
         for(i = 0; i < siglen; i++) {
              fprintf(stderr, "%d ", sig[i]);
         }
         fprintf(stderr, "\n");

Then I put a bunch of `fmt.Printf`s in my Go code, which also prints
the buffers just before the signature validation.

        fmt.Printf("SIGBUF %v", sigbuf)

Next run `ldns-verify` with the patched library:

        LD_LIBRARY_PATH=/tmp/ldns-1.4.6/.libs ./ldns-verify

And after watching, counting and checking buffers like this all 
afternoon (from `ldns-verify`):

    RRSETBUF
    0 6 8 2 0 0 56 64 77 71 139 33 77 31 254 33 47 19 4 109 105 101 107 2
    110 108 0 4 109 105 101 107 2 110 108 0 0 6 0 1 0 0 56 64 0 56 4 111
    112 101 110 9 110 108 110 101 116 108 97 98 115 2 110 108 0 5 109
    105 101 107 103 5 97 116 111 111 109 3 110 101 116 0 77 32 12
    49 0 0 56 64 0 0 14 16 0 9 58 128 0 1 81 128

And this from my Go test prog:

    SIGNEDDATA BUF 
    [0 6 8 2 0 0 56 64 77 71 139 33 77 31 254 33 47 19 4 109 105 101 107 2 
    110 108 0 4 109 105 101 107 2 110 108 0 0 6 0 1 0 0 56 64 0 56 4 111 
    112 101 110 9 110 108 110 101 116 108 97 98 115 2 110 108 0 5 109 
    105 101 107 103 5 97 116 111 111 109 3 110 101 116 0 77 32 12 
    49 0 0 56 64 0 0 14 16 0 9 58 128 0 1 81 128]

I fixed the bugs, fixed the buffers and got the `RSASHA256` validation
working! Now only `RSASHA1` and `RSAMD5` and I'm done :-)

 Go code
The actual validation code looks something like this (still needs to be
cleaned up):

        case AlgRSASHA256:
                // RFC 3110, section 2. RSA Public KEY Resource Records
                // Assume length is in the first byte!
                // keybuf[1]
                _E := int(keybuf[3]) <<16
                _E += int(keybuf[2]) <<8 
                _E += int(keybuf[1])
                println("_E", _E) 
                pubkey := new(rsa.PublicKey)
                pubkey.E = _E
                pubkey.N = big.NewInt(0)
                pubkey.N.SetBytes(keybuf[4:])
                fmt.Fprintf(os.Stderr, "keybug len %d", len(keybuf[4:]))
                fmt.Fprintf(os.Stderr, "PubKey %s\n", pubkey.N)

        // Hash the signeddata
        s := sha256.New()
        io.WriteString(s, string(signeddata))
        sighash := s.Sum()
        println("sig hash", len(sighash))

                err := rsa.VerifyPKCS1v15(pubkey, rsa.HashSHA256, sighash, sigbuf)
                if err == nil {
                        fmt.Fprintf(os.Stderr, "NO SHIT Sherlock!!\n")
                } else {
                        fmt.Fprintf(os.Stderr, "*********** %v\n", err)
                }   



