This one took me a considerable amount of hours to figure out so here it is.
While trying to automate DNS zone generation I had to calculate some of the values programmatically. Two of the auto-generated values had to do with DNSSEC entries: The key tag (or keyid) and the DS record’s signatures.
The required details on how these are calculated are found in the following places:
- Key tag: RFC4034 – Appendix B
- DNSKEY RDATA wire format: RFC4034 – Section 2.1
- DS’ digest field: RFC4034 – Section 5.1.4
- Wire format of names: RFC1035 – Section 3.1
For the calculations you need to provide the following:
- For the key tag: flags, protocol, algorithm, public key
- For the DS signatures: owner (the domain name), flags, protocol, algorithm, public key
Code
I used python for this but the approach is the same for other languages since the algorithms are the same.
So here it is:
import struct import hashlib import base64 def calc_keyid(flags, protocol, algorithm, st): """ @param owner The corresponding domain @param flags The flags of the entry (256 or 257) @param protocol Should always be 3 @param algorithm Should always be 5 @param st The public key as listed in the DNSKEY record. Spaces are removed. @return The key tag """ # Remove spaces and create the wire format st0=st.replace(' ', '') st2=struct.pack('!HBB', int(flags), int(protocol), int(algorithm)) st2+=base64.b64decode(st0) # Calculate the tag cnt=0 for idx in xrange(len(st2)): s=struct.unpack('B', st2[idx])[0] if (idx % 2) == 0: cnt+=s<<8 else: cnt+=s ret=((cnt & 0xFFFF) + (cnt>>16)) & 0xFFFF return(ret) def calc_ds(owner, flags, protocol, algorithm, st): """ @param flags Usually it is 257 or something that indicates a KSK. It can be 256 though. @param protocol Should always be 3 @param algorithm Should always be 5 @param st The public key as listed in the DNSKEY record. Spaces are removed. @return A dictionary of hashes where the key is the hashing algorithm. """ # Remove spaces and create the wire format st0=st.replace(' ', '') st2=struct.pack('!HBB', int(flags), int(protocol), int(algorithm)) st2+=base64.b64decode(st0) # Ensure a trailing dot if owner[-1]=='.': owner2=owner else: owner2=owner+'.' # Create the name wire format owner3='' for i in owner2.split('.'): owner3+=struct.pack('B', len(i))+i # Calculate the hashes st3=owner3+st2 ret={ 'sha1': hashlib.sha1(st3).hexdigest().upper(), 'sha256': hashlib.sha256(st3).hexdigest().upper(), } return(ret)
Data
The following were created by bind’s dnssec tools:
$ cat Ktest.hell.gr.+005+33630.key ; This is a zone-signing key, keyid 33630, for test.hell.gr. ; Created: 20101007114826 (Thu Oct 7 14:48:26 2010) ; Publish: 20101007114826 (Thu Oct 7 14:48:26 2010) ; Activate: 20101007114826 (Thu Oct 7 14:48:26 2010) test.hell.gr. IN DNSKEY 256 3 5 AwEAAb+lTDjZCfq7D5N9cNd1ug30wLrbCXB9mVJJQGlQQHpiHHlMaLGG sV2/j5+eojHp+WQUzNpOzrULF6msbEvUuV2gSEnpbueRV4twO8muGE+x eUuseSoHh/aTpA8Z9SPubb01mduqqaUEN5Juz2Q4hF0dSUSJYlJPKhp6 NrOgoeyj $ cat dsset-test.hell.gr. test.hell.gr.      IN DS 33630 5 1 A2AD2648B353365631EBC9C70EDA1E0C04563FCC test.hell.gr.      IN DS 33630 5 2 4177EAEC09A37178357871EBE3FB361CABB2861F12A1D51DDE18CBA2 439BB5C1
Result
>>> domain='test.hell.gr' >>> flags=256 >>> protocol=3 >>> algorithm=5 >>> key='AwEAAb+...goeyj' # Truncated >>> calc_keyid(flags, protocol, algorithm, key) 33630 >>> r=calc_ds(domain, flags, protocol, algorithm, key) >>> r['sha1'] 'A2AD2648B353365631EBC9C70EDA1E0C04563FCC' >>> r['sha256'] '4177EAEC09A37178357871EBE3FB361CABB2861F12A1D51DDE18CBA2439BB5C1'
Legal
You can use the above under the MIT license. If it doesn’t fit your needs let me know. My intention is to make this usable by anyone for any kind of use with no obligation.