2024-01-05 19:19:40 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-01-08 18:10:24 +00:00
|
|
|
"crypto/ed25519"
|
|
|
|
"crypto/rand"
|
2024-01-05 19:19:40 +00:00
|
|
|
"crypto/sha256"
|
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
2024-01-08 18:10:24 +00:00
|
|
|
"crypto/x509/pkix"
|
2024-01-05 19:19:40 +00:00
|
|
|
"encoding/hex"
|
2024-01-10 18:56:08 +00:00
|
|
|
"fmt"
|
2024-01-08 18:10:24 +00:00
|
|
|
"math/big"
|
|
|
|
"os"
|
|
|
|
"time"
|
2024-01-05 19:19:40 +00:00
|
|
|
)
|
|
|
|
|
2024-01-08 18:10:24 +00:00
|
|
|
func tlsConfig(state *BrowserState) *tls.Config {
|
|
|
|
if ident := state.Identities.Get(state.Url); ident != nil {
|
|
|
|
return ident
|
2024-01-05 19:19:40 +00:00
|
|
|
}
|
2024-01-08 18:10:24 +00:00
|
|
|
return anonymousTLS
|
2024-01-05 19:19:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var tofuStore map[string]string
|
|
|
|
|
2024-01-10 18:56:08 +00:00
|
|
|
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)
|
|
|
|
}
|
2024-01-05 19:19:40 +00:00
|
|
|
|
2024-01-08 18:10:24 +00:00
|
|
|
var anonymousTLS = &tls.Config{
|
|
|
|
InsecureSkipVerify: true,
|
|
|
|
VerifyConnection: tofuVerify,
|
|
|
|
}
|
|
|
|
|
2024-01-05 19:19:40 +00:00
|
|
|
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 {
|
2024-01-10 18:56:08 +00:00
|
|
|
return &TOFUViolation{
|
|
|
|
domain: connState.ServerName,
|
|
|
|
expected: expected,
|
|
|
|
got: certhash,
|
|
|
|
}
|
2024-01-05 19:19:40 +00:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
2024-01-08 18:10:24 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-16 16:44:52 +00:00
|
|
|
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
2024-01-08 18:10:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
template := &x509.Certificate{
|
|
|
|
SerialNumber: serialNumber,
|
|
|
|
Subject: pkix.Name{CommonName: commonName},
|
|
|
|
NotAfter: expiration,
|
|
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
2024-01-10 18:04:52 +00:00
|
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
2024-01-08 18:10:24 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|