Building the RDATA part of DNSKEY RR

411 Views Asked by At

I am in the process of finishing a Domain Registrar plugin for a billing system (which also manages domain provisioning in this case), and the last bit is related to implementing DNSSEC support.

The Billing system is sending the following SAMPLE DNSSEC related data to my PHP plugin:

'dnsSecInfo' =>
  array (
    0 =>
    array (
      'keyAlg' => 5,
      'digestAlg' => 1,
      'digest' => '1d181b34061ee98088b7a9e6db6e41a130674df0',
      'key' => 'AwEAAaqZeENizOE6uvpDtIfQBB26YebvRdZA/ZjXjKLZdMmMK641sBIvho+yrTveIYclM+8lEVHiq64MY8R2G1IPmKDKXG26rM7NVE0Qx1KL2wRVbRrduRdBmKgJo3XQ3niueviKYXXmeVIO3EhrJsCq272Tm3DaDvng/M7uw1vDnanR2pYNcxI08fZOA6PLGDoUWlDNLGAHHkCvfdWUktVt1DA0GtL/qE/WUgxK6hJyeaXXb0+yq3qCMZh48WgluMFib54D0GN3PI3ZZvBMblAZHmFGqgyVwtPKEimXm/VREe2QtZy3cRgPbfOuiQi8gRhzO+/If8Wi9YnyLovjdsSjRsE=',
    ),
  ),

RFC 4034 has the following:

2.1.  DNSKEY RDATA Wire Format

   The RDATA for a DNSKEY RR consists of a 2 octet Flags Field, a 1
   octet Protocol Field, a 1 octet Algorithm Field, and the Public Key
   Field.

                        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                         /
   /                                                               /
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Appendix B of the same protocol:

The input is the wire
format of the RDATA portion of the DNSKEY RR.
.....
  unsigned int
   keytag (
           unsigned char key[],  /* the RDATA part of the DNSKEY RR */
           unsigned int keysize  /* the RDLENGTH */
          )
   {
           unsigned long ac;     /* assumed to be 32 bits or larger */
           int i;                /* loop index */

           for ( ac = 0, i = 0; i < keysize; ++i )
                   ac += (i & 1) ? key[i] : key[i] << 8;
           ac += (ac >> 16) & 0xFFFF;
           return ac & 0xFFFF;
   }

The Registry where a domain is being registered, takes 4 mandatory fields:

  1. Keytag
  2. Algorithm (believed to be equal to keyAlg in plugin inputs)
  3. Digest Type (I believe its called digestAlg in the plugin inputs)
  4. Digest (same as digest probably)

Other Optional fields are: Flags, Protocol, Algorithm, Public Key (which is 'key' in Plugin...)

Now this is where I am getting lost... How do I implement the above C function in PHP?

  1. How do I construct the "DNSKEY RDATA" which is the key char array? (I am GUESSING that Flags octets are default values, like0, 256 or 257 not sure yet), then Protocol octet is the keyAlg value 5 in the example, followed by Algorithm octet that is always 3 and finally - the key octet is the key. Is that guess correct??)
  2. Is the char key[] array RDATA, a binary array? or ascii chars? (meaning, once constructed, I dont have to convert it to binary bits first?)
  3. What is & 0xFFFF purpose in Algorithm? what is the php equivalent? I am inclined to think its pretty much the same, as PHP is C based in syntax... but without a correct input/output examples, it is going to be difficult to be sure I got it correctly...
1

There are 1 best solutions below

0
Patrick Mevzek On

Your question is not very clear. Are you building a registrar system or something that connects to a registrar? Also your title speaks about building the DNSKEY RDATA but then the whole content of your question deals with computing the keyid/keytag, which is not published with the DNSKEY record anyway (only published with RRSIG records, but tools like dig can recompute it to show it as comment to help you when you see DNSKEY records).

In either case, you should not have to deal with DNSSEC data wire format. In the first case (registrar system), you interface with the registry typically with EPP that has a specific extension for DNSSEC data, called secDNS. See RFC5910

Now, as for DNSSEC itself. It uses cryptography, so it is better not to try to redo things manuall. It seems PHP has Net_DNS2 that could help.

But what I do not understand is why you have to muck wit the values. If you are a registrar you pass to the registry the values your clients gave you; you can verify their syntax a little but besides that you just pass them. If you are submitting data to a registrar, as a client, again you got this data from somewhere, I do not understand why you have to act on it.

Now you speak about a registry so for now I will imagine you are a registrar. Start by reading the RFC5910. You will see that there are two interfaces and in fact 3 cases, in the order from most often used right now to less often:

  1. the dsData interface where you as a registrar give the registry a DS record basically that the registry will publish; this data (4 fields indeed, you list them in your first set) is constructed from a key that you, or the hosting company will publish in the domain zonefile as a DNSKEY record
  2. the keyData interface, again with 4 fields, but not the same as the 4 previous ones (there are in your second set, or the PHP structure at top of your post), where in fact you send the registry the key (its public part), from which the registry will compute the DS record itself
  3. and a mixed case where it is basically dsData with a keyData inside which means you send both the DS to publish and the related key, that one being useless, but the registry can redo the DS computation to check, going from the key.

If you read the RFC you will have explanations on the 2 sets of 4 fields, and their meanings.

For some of them, there are only a few discrete values you can use:

As for your specific questions, which seem you are trying to compute the keyID/keytag from the key content (amazingly it has been discovered that this algorithm has flaws, but anyway), as I said previously you should not try to redo this yourself. If possible, try to find a PHP library that does it for you, or at the very least use existing tools, but this depends on how your keys are generated, where do you get them from, etc... See for example this tool: https://linux.die.net/man/8/dnssec-keygen

Otherwise you have this code: https://www.v13.gr/blog/?p=239 It is in Python, but you could derive a PHP version from it. Remember that the keytag depends only on the key content where the DS hash value depends on both the key and the domain name (so even if you use the same key for different domain names, the DS value will be different).

So for your 1) + 2) which are about the 4 fields needed for a keyData interface:

  • flag: is either 256 or 257 depending on if the key is used as a KSK or a ZSK; as a registrar submitting key values to registries it should be only KSKs, so value 257.
  • protocol: is always the value 3, see 2.1.2. of RFC4034
  • algorithm: is from Appendix 1 of the same RFC, which is basically the first link of the two above going to iana.org
  • the input of the algorithm is the value of the DNSKEY RDATA so as written in section 2.2: "The Public Key field MUST be represented as a Base64 encoding of the Public Key.", so it is a list of chars there.

As for 3) : doing & 0xFFFF means taking the 16 bits least significant (typically the right most 16 bits when you write them), because & is a logical AND and 0xFFFF is 2^16 - 1 (65535), that is 16 bits set at value 1. Said otherwise, if the final value is over 65535, we keep only part of it with that operation, as the keytag is defined to be a 16 bits value.

By the way, you can use the dnssec-keygen command to create keys and their keytags, to verify your own algorithm.

dnssec-keygen -a RSASHA256 -b 2048 test1 will produce:

Generating key pair.......+++ ............................+++ 
Ktest1.+008+05433

In the generated file name, 008 is the algorithm (from RSASHA256) and 05433 is the keyId (keytag) computed. If you look at the file computed finishing in .key you have inside the full DNSKEY record, with the key encoded as Base64, per the specification (which is the input of the algorithm to compute the keyid).

I hope you will find at least some useful ideas in the stuff above, but I fear not to have understood your question well enough to be more specific.