diff --git a/tlsrestrict_chromium/chromium.go b/tlsrestrict_chromium/chromium.go new file mode 100644 index 0000000..dcb8bac --- /dev/null +++ b/tlsrestrict_chromium/chromium.go @@ -0,0 +1,96 @@ +package tlsrestrict_chromium + +import ( + "encoding/base64" + "math/big" + "github.com/miekg/dns" + "encoding/hex" + "fmt" + "encoding/json" + "crypto/sha256" +) + +func DnsHash(fqdn string) (string, error) { + domainNamePacked := make([]byte, 256) + offset, err := dns.PackDomainName(fqdn, domainNamePacked, 0, nil, false) + if err != nil { + return "", fmt.Errorf("Error packing domain name %s: %s", fqdn, err) + } + + domainNameHashBytes := sha256.Sum256(domainNamePacked[:offset]) + domainNameHashB64String := string(base64.StdEncoding.EncodeToString(domainNameHashBytes[:])) + + return domainNameHashB64String, nil +} + +func BlockAllCAs() (map[string]interface{}, error) { + ruleJson := `{ + "dynamic_spki_hashes": [ "" ], + "dynamic_spki_hashes_expiry": 99999999999.9999, + "expiry": 99999999999.9999, + "mode": "force-https", + "pkp_include_subdomains": true, + "pkp_observed": 1.0000, + "report-uri": "", + "sts_include_subdomains": false, + "sts_observed": 1.0000 + }` + + var rule map[string]interface{} + + err := json.Unmarshal([]byte(ruleJson), &rule) + if err != nil { + return nil, fmt.Errorf("Error parsing BlockAllCAs rule: %s", err) + } + + sleeve, err := Sleeve256() + if err != nil { + return nil, fmt.Errorf("Error generating sleeve256: %s", err) + } + rule["dynamic_spki_hashes"].([]interface{})[0] = "sha256/" + sleeve + + return rule, nil +} + +// Calculates floor(2**256/pi), and encodes the result as base64. +// Intended to be used as a SHA256 hash where I don't have a preimage up my sleeve. +// Python2 version originally by Ryan Castellucci. Go port, pi sourcing, and base64 output by Jeremy Rand. +func Sleeve256() (string, error) { + var pi big.Float + var exp256Float big.Float + var fraction big.Float + + var exp256 big.Int + var fractionFloored big.Int + + // 1024 bits of precision + pi.SetPrec(1024) + exp256Float.SetPrec(1024) + fraction.SetPrec(1024) + + // 1000 digits of pi from https://www.angio.net/pi/digits/1000.txt (first HTTPS result in Startpage results for "digits of pi") + // (retrieved 2017 May 13.) + piString := "3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420198" + + _, _, err := pi.Parse(piString, 10) + if err != nil { + return "", fmt.Errorf("Error parsing pi: %s", err) + } + + exp256.Exp(big.NewInt(2), big.NewInt(256), nil) + exp256Float.SetInt(&exp256) + + fraction.Quo(&exp256Float, &pi) + fraction.Int(&fractionFloored) + + resultHex := fractionFloored.Text(16) + + resultBytes, err := hex.DecodeString(resultHex) + if err != nil { + return "", fmt.Errorf("Error decoding hex: %s", err) + } + + resultB64 := string(base64.StdEncoding.EncodeToString(resultBytes)) + + return resultB64, nil +} diff --git a/tlsrestrict_chromium/chromium_test.go b/tlsrestrict_chromium/chromium_test.go new file mode 100644 index 0000000..9a4a19c --- /dev/null +++ b/tlsrestrict_chromium/chromium_test.go @@ -0,0 +1,34 @@ +package tlsrestrict_chromium_test + +import ( + "testing" + "github.com/namecoin/ncdns/tlsrestrict_chromium" +) + +func TestDnsHash(t *testing.T) { + bitHash, err := tlsrestrict_chromium.DnsHash("bit.") + if err != nil { + t.Error(err) + } + + reference := "nprXwzm7mHINFnNah1Seo5SG0Pz9vW6dTXsYyvEC/PQ=" + + if bitHash != reference { + t.Error("Wrong DNS hash of 'bit.' calculated: ", bitHash, " should be ", reference) + } +} + +func TestSleeve256(t *testing.T) { + sleeve, err := tlsrestrict_chromium.Sleeve256() + if err != nil { + t.Error(err) + } + + // To reproduce this with web-based tools, use https://www.wolframalpha.com/input/?i=hex%28floor%282^256%2Fpi%29%29 + // and then convert from hex to base64 via https://holtstrom.com/michael/tools/hextopem.php + reference := "UXzBtyciCpT+E6vo+ppu4G2xSsyeIcgg/yix1e9d4rA=" + + if sleeve != reference { + t.Error("Wrong sleeve256 calculated: ", sleeve, " should be ", reference) + } +} diff --git a/tlsrestrict_chromium/tlsrestrict_chromium_tool/main.go b/tlsrestrict_chromium/tlsrestrict_chromium_tool/main.go new file mode 100644 index 0000000..c897529 --- /dev/null +++ b/tlsrestrict_chromium/tlsrestrict_chromium_tool/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "gopkg.in/hlandau/easyconfig.v1/cflag" + "gopkg.in/hlandau/easyconfig.v1" + "io/ioutil" + "encoding/json" + "log" + "github.com/namecoin/ncdns/tlsrestrict_chromium" +) + +var ( + flagGroup = cflag.NewGroup(nil, "tlsrestrict") + transportSecurityPathFlag = cflag.String(flagGroup, "chromium-ts-path", "", "Path to the TransportSecurity file in Chromium's profile folder. Make sure that no running instance of Chromium is using this profile folder; profile corruption could result otherwise.") + domainFlag = cflag.String(flagGroup, "domain", "bit.", "Block built-in CA's from signing for any subdomains of this fully-qualified domain name.") +) + +func main() { + config := easyconfig.Configurator{ + ProgramName: "tlsrestrict_chromium", + } + config.Parse(nil) + + transportSecurityPath := transportSecurityPathFlag.Value() + domain := domainFlag.Value() + + if transportSecurityPath == "" { + log.Fatalf("Missing required --tlsrestrict.chromium-ts-path parameter") + } + + rawIn, err := ioutil.ReadFile(transportSecurityPath) + if err != nil { + log.Fatalf("Couldn't read file %s: %s", transportSecurityPath, err) + } + + var data map[string]interface{} + + err = json.Unmarshal(rawIn, &data) + if err != nil { + log.Fatalf("Couldn't parse file %s: %s", transportSecurityPath, err) + } + + // Chromium's TransportSecurity database uses keys of the form base64(sha256(dnsPack(fqdn))) + domainDnsHashB64String, err := tlsrestrict_chromium.DnsHash(domain) + if err != nil { + log.Fatalf("Couldn't hash domain name %s: %s", domain, err) + } + + data[domainDnsHashB64String], err = tlsrestrict_chromium.BlockAllCAs() + if err != nil { + log.Fatalf("Couldn't assign BlockAllCAs: %s", err) + } + + rawOut, err := json.Marshal(data) + if err != nil { + log.Fatalf("Couldn't marshal data: %s", err) + } + + // 0600 seems to be the default mode in Chromium on Fedora + err = ioutil.WriteFile(transportSecurityPath, rawOut, 0600) + if err != nil { + log.Fatalf("Couldn't write file %s: %s", transportSecurityPath, err) + } +}