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.
ncdns/tlsoverridefirefox/firefox.go

125 lines
3.1 KiB
Go

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, ":")
}