x-1/tls.go

142 lines
3.1 KiB
Go

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,
}
}