package main import ( "crypto/ed25519" "crypto/rand" "crypto/sha256" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/hex" "fmt" "math/big" "os" "time" ) func tlsConfig(state *BrowserState) *tls.Config { if ident := state.Identities.Get(state.Url); ident != nil { return ident } return anonymousTLS } var tofuStore map[string]string type TOFUViolation struct { domain string expected string got string } func (tv *TOFUViolation) Error() string { return fmt.Sprintf("certificate for domain %s has changed from %s to %s", tv.domain, tv.expected, tv.got) } var anonymousTLS = &tls.Config{ InsecureSkipVerify: true, VerifyConnection: tofuVerify, } func tofuVerify(connState tls.ConnectionState) error { certhash, err := hashCert(connState.PeerCertificates[0]) if err != nil { return err } expected, ok := tofuStore[connState.ServerName] if !ok { tofuStore[connState.ServerName] = certhash return saveTofuStore(tofuStore) } if certhash != expected { return &TOFUViolation{ domain: connState.ServerName, expected: expected, got: certhash, } } return nil } func hashCert(cert *x509.Certificate) (string, error) { pubkeybytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey) if err != nil { return "", err } hash := sha256.Sum256(pubkeybytes) return hex.EncodeToString(hash[:]), nil } func createIdentity(state *BrowserState, name string) (*tls.Config, error) { pubkey, privkey, err := ed25519.GenerateKey(rand.Reader) if err != nil { return nil, err } rawprivkey, err := x509.MarshalPKCS8PrivateKey(privkey) if err != nil { return nil, err } commonName := name state.Readline.SetPrompt("Common Name [" + name + "]: ") if line, err := state.Readline.Readline(); err != nil { return nil, err } else if line != "" { commonName = line } expiration := time.Date(9999, 12, 31, 0, 0, 0, 0, time.UTC) state.Readline.SetPrompt("Expiration (yyyy-mm-dd) [9999-12-31]: ") if line, err := state.Readline.Readline(); err != nil { return nil, err } else if line != "" { expiration, err = time.ParseInLocation(time.DateOnly, line, time.UTC) if err != nil { return nil, err } } serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return nil, err } template := &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{CommonName: commonName}, NotAfter: expiration, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, BasicConstraintsValid: true, } rawcert, err := x509.CreateCertificate(rand.Reader, template, template, pubkey, privkey) if err != nil { return nil, err } identFile, err := saveIdentity(name, rawprivkey, rawcert) if err != nil { return nil, err } cert, err := tls.LoadX509KeyPair(identFile, identFile) if err != nil { _ = os.Remove(identFile) return nil, err } return identityForCert(cert), nil } func identityForCert(cert tls.Certificate) *tls.Config { return &tls.Config{ Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true, VerifyConnection: tofuVerify, } }