package main import ( "crypto/ed25519" "crypto/rand" "crypto/sha256" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/hex" "errors" "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 var ErrTOFUViolation = errors.New("certificate for this domain has changed") 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 ErrTOFUViolation } 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 } } snLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, snLimit) if err != nil { return nil, err } template := &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{CommonName: commonName}, NotAfter: expiration, KeyUsage: x509.KeyUsageDigitalSignature, 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, } }