Implement basic TOFU
This commit is contained in:
parent
4a95fe4a90
commit
b4295dd2dc
@ -163,14 +163,14 @@ func (resp *Response) read(r *bufio.Reader) error {
|
||||
}
|
||||
|
||||
// Client represents a Gemini client.
|
||||
type Client struct {
|
||||
// VerifyCertificate, if not nil, will be called to verify the server certificate.
|
||||
type Client interface {
|
||||
// VerifyCertificate will be called to verify the server certificate.
|
||||
// If error is not nil, the connection will be aborted.
|
||||
VerifyCertificate func(cert *x509.Certificate, req *Request) error
|
||||
VerifyCertificate(cert *x509.Certificate, req *Request) error
|
||||
}
|
||||
|
||||
// Send sends a Gemini request and returns a Gemini response.
|
||||
func (c *Client) Send(req *Request) (*Response, error) {
|
||||
func Send(c Client, req *Request) (*Response, error) {
|
||||
// Connect to the host
|
||||
config := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
|
@ -14,12 +14,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
client = &gemini.Client{
|
||||
VerifyCertificate: func(cert *x509.Certificate, req *gemini.Request) error {
|
||||
return nil
|
||||
client = &gemini.TOFUClient{
|
||||
Trusts: func(cert *x509.Certificate, req *gemini.Request) bool {
|
||||
// Trust all certificates
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
cert tls.Certificate
|
||||
)
|
||||
|
||||
@ -29,7 +29,7 @@ func init() {
|
||||
//
|
||||
// openssl genrsa -out client.key 2048
|
||||
// openssl ecparam -genkey -name secp384r1 -out client.key
|
||||
// openssl req -new -x509 -sha256 -key client.key -out client.crt -days 3650
|
||||
// openssl req -new -x509 -sha512 -key client.key -out client.crt -days 365
|
||||
//
|
||||
var err error
|
||||
cert, err = tls.LoadX509KeyPair("examples/client/client.crt", "examples/client/client.key")
|
||||
@ -45,13 +45,11 @@ func makeRequest(url string) {
|
||||
}
|
||||
req.Certificate = cert
|
||||
|
||||
resp, err := client.Send(req)
|
||||
resp, err := gemini.Send(client, req)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(gemini.Fingerprint(resp.TLS.PeerCertificates[0]))
|
||||
|
||||
fmt.Println("Status code:", resp.Status)
|
||||
fmt.Println("Meta:", resp.Meta)
|
||||
|
||||
|
@ -15,7 +15,7 @@ func main() {
|
||||
//
|
||||
// openssl genrsa -out server.key 2048
|
||||
// openssl ecparam -genkey -name secp384r1 -out server.key
|
||||
// openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
|
||||
// openssl req -new -x509 -sha512 -key server.key -out server.crt -days 365
|
||||
//
|
||||
cert, err := tls.LoadX509KeyPair("examples/server/server.crt", "examples/server/server.key")
|
||||
if err != nil {
|
||||
@ -27,9 +27,6 @@ func main() {
|
||||
rw.WriteHeader(gemini.StatusSuccess, "text/gemini")
|
||||
rw.Write([]byte("You requested " + req.URL.String()))
|
||||
log.Printf("Request from %s for %s", req.RemoteAddr.String(), req.URL)
|
||||
if len(req.TLS.PeerCertificates) != 0 {
|
||||
log.Print("Client certificate: ", gemini.Fingerprint(req.TLS.PeerCertificates[0]))
|
||||
}
|
||||
})
|
||||
|
||||
server := gemini.Server{
|
||||
|
52
gemini.go
52
gemini.go
@ -1,5 +1,13 @@
|
||||
package gemini
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Status codes.
|
||||
const (
|
||||
StatusInput = 10
|
||||
@ -35,3 +43,47 @@ const (
|
||||
var (
|
||||
crlf = []byte("\r\n")
|
||||
)
|
||||
|
||||
// TOFUClient is a client that implements Trust-On-First-Use.
|
||||
type TOFUClient struct {
|
||||
// Trusts, if not nil, will be called to determine whether the client should
|
||||
// trust the provided certificate.
|
||||
Trusts func(cert *x509.Certificate, req *Request) bool
|
||||
}
|
||||
|
||||
func (t *TOFUClient) VerifyCertificate(cert *x509.Certificate, req *Request) error {
|
||||
if knownHosts.Has(req.URL.Host, cert) {
|
||||
return nil
|
||||
}
|
||||
if t.Trusts != nil && t.Trusts(cert, req) {
|
||||
host := NewKnownHost(cert)
|
||||
knownHosts = append(knownHosts, host)
|
||||
knownHostsFile, err := os.OpenFile(knownHostsPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
if _, err := host.Write(knownHostsFile); err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New("gemini: certificate not trusted")
|
||||
}
|
||||
|
||||
var (
|
||||
knownHosts KnownHosts
|
||||
knownHostsPath string
|
||||
knownHostsFile *os.File
|
||||
)
|
||||
|
||||
func init() {
|
||||
configDir, err := os.UserConfigDir()
|
||||
knownHostsPath = filepath.Join(configDir, "gemini")
|
||||
os.MkdirAll(knownHostsPath, 0755)
|
||||
knownHostsPath = filepath.Join(knownHostsPath, "known_hosts")
|
||||
knownHostsFile, err = os.OpenFile(knownHostsPath, os.O_CREATE|os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
knownHosts = ParseKnownHosts(knownHostsFile)
|
||||
}
|
||||
|
11
tofu.go
11
tofu.go
@ -71,9 +71,18 @@ type KnownHost struct {
|
||||
Expires int64 // unix time of certificate notAfter date
|
||||
}
|
||||
|
||||
func NewKnownHost(cert *x509.Certificate) KnownHost {
|
||||
return KnownHost{
|
||||
Hostname: cert.Subject.CommonName,
|
||||
Algorithm: "SHA-512",
|
||||
Fingerprint: Fingerprint(cert),
|
||||
Expires: cert.NotAfter.Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
// Write writes the known host to the provided io.Writer.
|
||||
func (k KnownHost) Write(w io.Writer) (int, error) {
|
||||
s := fmt.Sprintf("\n%s %s %s %d", k.Hostname, k.Algorithm, k.Fingerprint, k.Expires)
|
||||
s := fmt.Sprintf("%s %s %s %d\n", k.Hostname, k.Algorithm, k.Fingerprint, k.Expires)
|
||||
return w.Write([]byte(s))
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user