mirror of https://github.com/namecoin/ncdns
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
4.8 KiB
Go
202 lines
4.8 KiB
Go
//go:build !no_namecoin_tls
|
|
// +build !no_namecoin_tls
|
|
|
|
package ncdomain
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
"github.com/namecoin/ncdns/certdehydrate"
|
|
"github.com/namecoin/ncdns/util"
|
|
x509_compressed "github.com/namecoin/x509-compressed/x509"
|
|
)
|
|
|
|
type Value struct {
|
|
valueWithoutTLSA
|
|
TLSAGenerated []x509.Certificate // Certs can be dehydrated in the blockchain, they will be put here without SAN values. SAN must be filled in before use.
|
|
}
|
|
|
|
func (v *Value) appendTLSA(out []dns.RR, suffix, apexSuffix string) ([]dns.RR, error) {
|
|
for _, tlsa := range v.TLSA {
|
|
out = append(out, tlsa)
|
|
}
|
|
|
|
for _, cert := range v.TLSAGenerated {
|
|
|
|
template := cert
|
|
|
|
_, nameNoPort := util.SplitDomainTail(suffix)
|
|
_, nameNoPortOrProtocol := util.SplitDomainTail(nameNoPort)
|
|
|
|
if !strings.HasSuffix(nameNoPortOrProtocol, ".") {
|
|
continue
|
|
}
|
|
nameNoPortOrProtocol = strings.TrimSuffix(nameNoPortOrProtocol, ".")
|
|
|
|
derBytes, err := certdehydrate.FillRehydratedCertTemplate(template, nameNoPortOrProtocol)
|
|
if err != nil {
|
|
// TODO: add debug output here
|
|
continue
|
|
}
|
|
|
|
derBytesHex := hex.EncodeToString(derBytes)
|
|
|
|
out = append(out, &dns.TLSA{
|
|
Hdr: dns.RR_Header{Name: "", Rrtype: dns.TypeTLSA, Class: dns.ClassINET,
|
|
Ttl: defaultTTL},
|
|
Usage: uint8(3),
|
|
Selector: uint8(0),
|
|
MatchingType: uint8(0),
|
|
Certificate: strings.ToUpper(derBytesHex),
|
|
})
|
|
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func parseTLSADehydrated(tlsa1dehydrated interface{}, v *Value) error {
|
|
dehydrated, err := certdehydrate.ParseDehydratedCert(tlsa1dehydrated)
|
|
if err != nil {
|
|
return fmt.Errorf("Error parsing dehydrated certificate: %s", err)
|
|
}
|
|
|
|
template, err := certdehydrate.RehydrateCert(dehydrated)
|
|
if err != nil {
|
|
return fmt.Errorf("Error rehydrating certificate: %s", err)
|
|
}
|
|
|
|
v.TLSAGenerated = append(v.TLSAGenerated, *template)
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseTLSADANE(tlsa1dane interface{}, v *Value) error {
|
|
if tlsa, ok := tlsa1dane.([]interface{}); ok {
|
|
// Format: [1, 2, 3, "base64 certificate data"]
|
|
if len(tlsa) < 4 {
|
|
return fmt.Errorf("TLSA item must have four items")
|
|
}
|
|
|
|
a1, ok := tlsa[0].(float64)
|
|
if !ok {
|
|
return fmt.Errorf("First item in TLSA value must be an integer (usage)")
|
|
}
|
|
|
|
a2, ok := tlsa[1].(float64)
|
|
if !ok {
|
|
return fmt.Errorf("Second item in TLSA value must be an integer (selector)")
|
|
}
|
|
|
|
a3, ok := tlsa[2].(float64)
|
|
if !ok {
|
|
return fmt.Errorf("Third item in TLSA value must be an integer (match type)")
|
|
}
|
|
|
|
a4, ok := tlsa[3].(string)
|
|
if !ok {
|
|
return fmt.Errorf("Fourth item in TLSA value must be a string (certificate)")
|
|
}
|
|
|
|
a4b, err := base64.StdEncoding.DecodeString(a4)
|
|
if err != nil {
|
|
return fmt.Errorf("Fourth item in TLSA value must be valid base64: %v", err)
|
|
}
|
|
|
|
a4h := hex.EncodeToString(a4b)
|
|
|
|
v.TLSA = append(v.TLSA, &dns.TLSA{
|
|
Hdr: dns.RR_Header{Name: "", Rrtype: dns.TypeTLSA, Class: dns.ClassINET,
|
|
Ttl: defaultTTL},
|
|
Usage: uint8(a1),
|
|
Selector: uint8(a2),
|
|
MatchingType: uint8(a3),
|
|
Certificate: strings.ToUpper(a4h),
|
|
})
|
|
|
|
// Handle compressed public keys specially
|
|
// Check if this TLSA is a public key preimage
|
|
if uint8(a2) == 1 && uint8(a3) == 0 {
|
|
pubDecompressed, err := x509_compressed.ParsePKIXPublicKey(a4b)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
pubDecompressedBytes, err := x509.MarshalPKIXPublicKey(pubDecompressed)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
pubDecompressedHex := hex.EncodeToString(pubDecompressedBytes)
|
|
|
|
if pubDecompressedHex == a4h {
|
|
// The pubkey wasn't compressed, so decompressing had no impact.
|
|
return nil
|
|
}
|
|
|
|
v.TLSA = append(v.TLSA, &dns.TLSA{
|
|
Hdr: dns.RR_Header{Name: "", Rrtype: dns.TypeTLSA, Class: dns.ClassINET,
|
|
Ttl: defaultTTL},
|
|
Usage: uint8(a1),
|
|
Selector: uint8(a2),
|
|
MatchingType: uint8(a3),
|
|
Certificate: strings.ToUpper(pubDecompressedHex),
|
|
})
|
|
}
|
|
|
|
return nil
|
|
} else {
|
|
return fmt.Errorf("TLSA item must be an array")
|
|
}
|
|
}
|
|
|
|
func parseTLSA(rv map[string]interface{}, v *Value, errFunc ErrorFunc) {
|
|
tlsa, ok := rv["tls"]
|
|
if !ok || tlsa == nil {
|
|
return
|
|
}
|
|
|
|
v.TLSA = nil
|
|
|
|
if tlsaa, ok := tlsa.([]interface{}); ok {
|
|
for _, tlsa1 := range tlsaa {
|
|
var tlsa1m map[string]interface{}
|
|
|
|
if _, ok := tlsa1.([]interface{}); ok {
|
|
tlsa1m = map[string]interface{}{
|
|
"dane": tlsa1,
|
|
}
|
|
} else {
|
|
tlsa1m = tlsa1.(map[string]interface{})
|
|
}
|
|
|
|
if tlsa1dehydrated, ok := tlsa1m["d8"]; ok {
|
|
err := parseTLSADehydrated(tlsa1dehydrated, v)
|
|
if err == nil {
|
|
continue
|
|
}
|
|
errFunc.add(err)
|
|
}
|
|
|
|
if tlsa1dane, ok := tlsa1m["dane"]; ok {
|
|
err := parseTLSADANE(tlsa1dane, v)
|
|
if err == nil {
|
|
continue
|
|
}
|
|
errFunc.add(err)
|
|
}
|
|
|
|
errFunc.add(fmt.Errorf("Unknown TLSA item format"))
|
|
}
|
|
return
|
|
}
|
|
|
|
errFunc.add(fmt.Errorf("Malformed TLSA field format"))
|
|
}
|