244 lines
6.3 KiB
Go
244 lines
6.3 KiB
Go
package dnssec
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"crypto/rsa"
|
|
"encoding/hex"
|
|
"encoding/base64"
|
|
"time"
|
|
"io"
|
|
"big"
|
|
"sort"
|
|
"strings"
|
|
"fmt" //tmp
|
|
"os" //tmp
|
|
"dns"
|
|
)
|
|
|
|
// DNSSEC encryption algorithm codes.
|
|
const (
|
|
// DNSSEC algorithms
|
|
AlgRSAMD5 = 1
|
|
AlgDH = 2
|
|
AlgDSA = 3
|
|
AlgECC = 4
|
|
AlgRSASHA1 = 5
|
|
AlgRSASHA256 = 8
|
|
AlgRSASHA512 = 10
|
|
AlgECCGOST = 12
|
|
)
|
|
|
|
// DNSSEC hashing codes.
|
|
const (
|
|
HashSHA1 = iota
|
|
HashSHA256
|
|
HashGOST94
|
|
)
|
|
|
|
// Convert an DNSKEY record to a DS record.
|
|
func ToDS(k *dns.RR_DNSKEY, hash int) *dns.RR_DS {
|
|
ds := new(dns.RR_DS)
|
|
ds.Hdr.Name = k.Hdr.Name
|
|
ds.Hdr.Class = k.Hdr.Class
|
|
ds.Hdr.Ttl = k.Hdr.Ttl
|
|
ds.Algorithm = k.Algorithm
|
|
ds.DigestType = uint8(hash)
|
|
ds.KeyTag = KeyTag(k)
|
|
|
|
wire, ok := dns.WireRdata(k)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
owner,ok1 := dns.WireDomainName(k.Hdr.Name)
|
|
if !ok1 {
|
|
return nil
|
|
}
|
|
/*
|
|
* from RFC4034
|
|
* digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA);
|
|
* "|" denotes concatenation
|
|
* DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key.
|
|
*/
|
|
// digest buffer
|
|
digest := append(owner, wire...) // another copy TODO(mg)
|
|
|
|
switch hash {
|
|
case HashSHA1:
|
|
s := sha1.New()
|
|
io.WriteString(s, string(digest))
|
|
ds.Digest = hex.EncodeToString(s.Sum())
|
|
case HashSHA256:
|
|
s := sha256.New()
|
|
io.WriteString(s, string(digest))
|
|
ds.Digest = hex.EncodeToString(s.Sum())
|
|
case HashGOST94:
|
|
|
|
default:
|
|
// wrong hash value
|
|
return nil
|
|
}
|
|
return ds
|
|
}
|
|
|
|
// Calculate the keytag of the DNSKEY.
|
|
func KeyTag(k *dns.RR_DNSKEY) uint16 {
|
|
var keytag int
|
|
switch k.Algorithm {
|
|
case AlgRSAMD5:
|
|
println("Keytag RSAMD5. Todo")
|
|
keytag = 0
|
|
default:
|
|
// Might encode header length too, so that
|
|
// we dont need to pack/unpack all the time
|
|
// Or a shadow structure, with the wiredata and header
|
|
wire, ok := dns.WireRdata(k)
|
|
if !ok {
|
|
return 0
|
|
}
|
|
for i, v := range wire {
|
|
if i&1 != 0 {
|
|
keytag += int(v) // must be larger than uint32
|
|
} else {
|
|
keytag += int(v) << 8
|
|
}
|
|
}
|
|
keytag += (keytag >> 16) & 0xFFFF
|
|
keytag &= 0xFFFF
|
|
}
|
|
return uint16(keytag)
|
|
}
|
|
|
|
// Validate an rrset with the signature and key. This is the
|
|
// cryptographic test, the validity period most be check separately.
|
|
func Verify(s *dns.RR_RRSIG, k *dns.RR_DNSKEY, rrset dns.RRset) bool {
|
|
// Frist the easy checks
|
|
if s.KeyTag != KeyTag(k) {
|
|
println(s.KeyTag)
|
|
println(KeyTag(k))
|
|
return false
|
|
}
|
|
if s.Hdr.Class != k.Hdr.Class {
|
|
println("Class")
|
|
return false
|
|
}
|
|
if s.Algorithm != k.Algorithm {
|
|
println("Class")
|
|
return false
|
|
}
|
|
if s.SignerName != k.Hdr.Name {
|
|
println(s.SignerName)
|
|
println(k.Hdr.Name)
|
|
return false
|
|
}
|
|
for _, r := range rrset {
|
|
if r.Header().Class != s.Hdr.Class {
|
|
return false
|
|
}
|
|
if r.Header().Rrtype != s.TypeCovered {
|
|
return false
|
|
}
|
|
// Number of labels. TODO(mg) add helper functions
|
|
}
|
|
sort.Sort(rrset)
|
|
|
|
// RFC 4035 5.3.2. Reconstructing the Signed Data
|
|
signeddata := make([]byte, 10240) // 10 Kb??
|
|
// Copy the sig, except the rrsig data
|
|
// Can this be done easier? TODO(mg)
|
|
s1 := &dns.RR_RRSIG{s.Hdr, s.TypeCovered, s.Algorithm, s.Labels, s.OrigTtl, s.Expiration, s.Inception, s.KeyTag, s.SignerName, ""}
|
|
buf, ok := dns.WireRdata(s1)
|
|
if !ok {
|
|
return false
|
|
}
|
|
copy(signeddata, buf)
|
|
off := len(buf)
|
|
fmt.Fprintf(os.Stderr, "off %d\n", off)
|
|
|
|
for _, r := range rrset {
|
|
h := r.Header()
|
|
// RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase
|
|
name := h.Name
|
|
h.Name = strings.ToLower(h.Name)
|
|
// 6.2. Canonical RR Form. (3) - domain rdata to lowercaser
|
|
switch h.Rrtype {
|
|
case dns.TypeNS, dns.TypeCNAME, dns.TypeSOA, dns.TypeMB, dns.TypeMG, dns.TypeMR, dns.TypePTR:
|
|
case dns.TypeHINFO, dns.TypeMINFO, dns.TypeMX /* dns.TypeRP, dns.TypeAFSDB, dns.TypeRT */ :
|
|
case dns.TypeSIG /* dns.TypePX, dns.TypeNXT /* dns.TypeNAPTR, dns.TypeKX */ :
|
|
case dns.TypeSRV, /* dns.TypeDNAME, dns.TypeA6 */ dns.TypeRRSIG, dns.TypeNSEC:
|
|
/* do something */
|
|
// lower case the strings rdata //
|
|
|
|
}
|
|
// 6.2. Canonical RR Form. (4) - wildcards, don't understand
|
|
// 6.2. Canonical RR Form. (5) - origTTL
|
|
ttl := h.Ttl
|
|
h.Ttl = s.OrigTtl
|
|
wire, ok1 := dns.WireRR(r)
|
|
h.Ttl = ttl // restore the order in the universe
|
|
h.Name = name
|
|
wire = wire // fix this
|
|
if !ok1 {
|
|
println("Failure to pack")
|
|
return false
|
|
}
|
|
}
|
|
signeddata = signeddata[:off]
|
|
keybuf := make([]byte, 1024)
|
|
keybuflen := base64.StdEncoding.DecodedLen(len(k.PubKey))
|
|
base64.StdEncoding.Decode(keybuf[0:keybuflen], []byte(k.PubKey))
|
|
keybuf = keybuf[:keybuflen]
|
|
sigbuf := make([]byte, 1024)
|
|
sigbuflen := base64.StdEncoding.DecodedLen(len(s.Signature))
|
|
base64.StdEncoding.Decode(sigbuf[0:sigbuflen], []byte(s.Signature))
|
|
sigbuf = sigbuf[:sigbuflen]
|
|
fmt.Fprintf(os.Stderr, "len of sigbuf: %d\n", len(sigbuf))
|
|
|
|
switch s.Algorithm {
|
|
case AlgRSASHA1:
|
|
|
|
case AlgRSASHA256:
|
|
// RFC 3110, section 2. RSA Public KEY Resource Records
|
|
// Assume length is in the first byte!
|
|
_E := int(keybuf[3]) <<16
|
|
_E += int(keybuf[2]) <<8
|
|
_E += int(keybuf[1])
|
|
pubkey := new(rsa.PublicKey)
|
|
pubkey.E = _E
|
|
pubkey.N = big.NewInt(0)
|
|
pubkey.N.SetBytes(keybuf[4:])
|
|
fmt.Fprintf(os.Stderr, "%s\n", pubkey.N)
|
|
|
|
err := rsa.VerifyPKCS1v15(pubkey, rsa.HashSHA256, signeddata, sigbuf)
|
|
if err == nil {
|
|
fmt.Fprintf(os.Stderr, "NO SHIT!!\n")
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Using RFC1982 calculate if a signature period is valid
|
|
func PeriodOK(s *dns.RR_RRSIG) bool {
|
|
utc := time.UTC().Seconds()
|
|
modi := (int64(s.Inception) - utc) / dns.Year68
|
|
mode := (int64(s.Expiration) - utc) / dns.Year68
|
|
ti := int64(s.Inception) + (modi * dns.Year68)
|
|
te := int64(s.Expiration) + (mode * dns.Year68)
|
|
return ti <= utc && utc <= te
|
|
}
|
|
|
|
// Map for algorithm names.
|
|
var alg_str = map[uint8]string{
|
|
AlgRSAMD5: "RSAMD5",
|
|
AlgDH: "DH",
|
|
AlgDSA: "DSA",
|
|
AlgRSASHA1: "RSASHA1",
|
|
AlgRSASHA256: "RSASHA256",
|
|
AlgRSASHA512: "RSASHA512",
|
|
AlgECCGOST: "ECC-GOST",
|
|
}
|