Skip to content

Commit

Permalink
ncdumpzone: Add Firefox mode.
Browse files Browse the repository at this point in the history
This mode outputs a cert_override.txt file (based on TLSA records) that Firefox will accept.
  • Loading branch information
JeremyRand committed Mar 24, 2018
1 parent 3792dd3 commit 2e50c75
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 1 deletion.
14 changes: 13 additions & 1 deletion ncdumpzone/ncdumpzone.go
Expand Up @@ -3,6 +3,7 @@ package main
import "gopkg.in/alecthomas/kingpin.v2"
import "github.com/namecoin/ncdns/ncdomain"
import "github.com/namecoin/ncdns/namecoin"
import "github.com/namecoin/ncdns/tlsoverridefirefox"
import "github.com/namecoin/ncdns/util"
import "github.com/hlandau/xlog"
import "strings"
Expand All @@ -14,6 +15,9 @@ var (
rpchostFlag = kingpin.Flag("rpchost", "Namecoin RPC host:port").Default("127.0.0.1:8336").String()
rpcuserFlag = kingpin.Flag("rpcuser", "Namecoin RPC username").String()
rpcpassFlag = kingpin.Flag("rpcpass", "Namecoin RPC password").String()
formatFlag = kingpin.Flag("format", "Output format. \"zonefile\" = "+
"DNS zone file. \"firefox-override\" = Firefox "+
"cert_override.txt format.").Default("zonefile").String()
)

var conn namecoin.Conn
Expand Down Expand Up @@ -79,7 +83,15 @@ func main() {
log.Warne(err, "error generating RRs")

for _, rr := range rrs {
fmt.Print(rr.String(), "\n")
if *formatFlag == "zonefile" {
fmt.Print(rr.String(), "\n")
} else if *formatFlag == "firefox-override" {
result, err := tlsoverridefirefox.OverrideFromRR(rr)
if err != nil {
panic(err)
}
fmt.Print(result)
}
}
}

Expand Down
124 changes: 124 additions & 0 deletions tlsoverridefirefox/firefox.go
@@ -0,0 +1,124 @@
package tlsoverridefirefox

import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"strings"

"github.com/miekg/dns"
"github.com/namecoin/ncdns/util"
)

// OverrideFromRR returns a Firefox certificate override (in cert_override.txt
// format) derived from rr. If no such override can be derived, returns an
// empty string.
func OverrideFromRR(rr dns.RR) (string, error) {
tlsa, ok := rr.(*dns.TLSA)

if !ok {
return "", nil
}

portLabel, protocolLabelAndHost := util.SplitDomainTail(tlsa.Hdr.Name)
protocolLabel, hostFQDN := util.SplitDomainTail(protocolLabelAndHost)

if protocolLabel != "_tcp" {
return "", nil
}

if !strings.HasPrefix(portLabel, "_") {
return "", nil
}

port := strings.TrimPrefix(portLabel, "_")

if !strings.HasSuffix(hostFQDN, ".") {
return "", fmt.Errorf("TLSA not a FQDN")
}

host := strings.TrimSuffix(hostFQDN, ".")

// SHA256, as per https://dxr.mozilla.org/mozilla-central/source/security/manager/ssl/nsCertOverrideService.cpp
fingerprintAlgo := "OID.2.16.840.1.101.3.4.2.1"

// Possible Usage values:
// 0: CA constraint. No override is necessary.
// 1: Service certificate constraint. No override is necessary.
// 2: Trust anchor assertion. Firefox doesn't support these.
// 3: Domain-issued certificate. Do an override in this case.

if tlsa.Usage != 3 {
return "", nil
}

// Only a full certificate selector can yield a SHA256 certificate
// fingerprint.
if tlsa.Selector != 0 {
return "", nil
}

fingerprint, err := getFingerprint(tlsa)
if err != nil {
return "", nil
}

overrideMask := "U"

// Format documented in https://dxr.mozilla.org/mozilla-central/source/security/manager/ssl/nsNSSCertificate.cpp
// However, it looks empirically like we can just use 0-length serial
// number and 0-length DN, and Firefox doesn't care.
dbKey := base64.StdEncoding.EncodeToString([]byte{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})

return host + ":" + port + "\t" + fingerprintAlgo + "\t" +
fingerprint + "\t" + overrideMask + "\t" + dbKey + "\n", nil
}

func getFingerprint(tlsa *dns.TLSA) (string, error) {
var fingerprint string

// SHA512 fingerprint can't yield a SHA256 fingerprint
if tlsa.MatchingType == 2 {
return "", fmt.Errorf("SHA512 fingerprint can't yield a " +
"SHA256 fingerprint")
}

if tlsa.MatchingType == 1 {
// SHA256 fingerprint

fingerprintBytes, err := hex.DecodeString(tlsa.Certificate)
if err != nil {
return "", err
}

fingerprint = insertColons(fingerprintBytes)
} else if tlsa.MatchingType == 0 {
// Exact match

certificateBytes, err := hex.DecodeString(tlsa.Certificate)
if err != nil {
return "", err
}

fingerprintArray := sha256.Sum256(certificateBytes)

fingerprint = insertColons(fingerprintArray[:])
} else {
// Unknown MatchingType
return "", fmt.Errorf("Unknown MatchingType")
}

return strings.ToUpper(fingerprint), nil
}

// Based on FingerprintLegacyMD5 from
// https://github.com/golang/crypto/blob/master/ssh/keys.go
func insertColons(input []byte) string {
hexarray := make([]string, len(input))
for i, c := range input {
hexarray[i] = hex.EncodeToString([]byte{c})
}
return strings.Join(hexarray, ":")
}

0 comments on commit 2e50c75

Please sign in to comment.