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.
|
// Client represents a Gemini client.
|
||||||
type Client struct {
|
type Client interface {
|
||||||
// VerifyCertificate, if not nil, will be called to verify the server certificate.
|
// VerifyCertificate will be called to verify the server certificate.
|
||||||
// If error is not nil, the connection will be aborted.
|
// 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.
|
// 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
|
// Connect to the host
|
||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
|
@ -14,12 +14,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
client = &gemini.Client{
|
client = &gemini.TOFUClient{
|
||||||
VerifyCertificate: func(cert *x509.Certificate, req *gemini.Request) error {
|
Trusts: func(cert *x509.Certificate, req *gemini.Request) bool {
|
||||||
return nil
|
// Trust all certificates
|
||||||
|
return true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cert tls.Certificate
|
cert tls.Certificate
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ func init() {
|
|||||||
//
|
//
|
||||||
// openssl genrsa -out client.key 2048
|
// openssl genrsa -out client.key 2048
|
||||||
// openssl ecparam -genkey -name secp384r1 -out client.key
|
// 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
|
var err error
|
||||||
cert, err = tls.LoadX509KeyPair("examples/client/client.crt", "examples/client/client.key")
|
cert, err = tls.LoadX509KeyPair("examples/client/client.crt", "examples/client/client.key")
|
||||||
@ -45,13 +45,11 @@ func makeRequest(url string) {
|
|||||||
}
|
}
|
||||||
req.Certificate = cert
|
req.Certificate = cert
|
||||||
|
|
||||||
resp, err := client.Send(req)
|
resp, err := gemini.Send(client, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(gemini.Fingerprint(resp.TLS.PeerCertificates[0]))
|
|
||||||
|
|
||||||
fmt.Println("Status code:", resp.Status)
|
fmt.Println("Status code:", resp.Status)
|
||||||
fmt.Println("Meta:", resp.Meta)
|
fmt.Println("Meta:", resp.Meta)
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ func main() {
|
|||||||
//
|
//
|
||||||
// openssl genrsa -out server.key 2048
|
// openssl genrsa -out server.key 2048
|
||||||
// openssl ecparam -genkey -name secp384r1 -out server.key
|
// 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")
|
cert, err := tls.LoadX509KeyPair("examples/server/server.crt", "examples/server/server.key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -27,9 +27,6 @@ func main() {
|
|||||||
rw.WriteHeader(gemini.StatusSuccess, "text/gemini")
|
rw.WriteHeader(gemini.StatusSuccess, "text/gemini")
|
||||||
rw.Write([]byte("You requested " + req.URL.String()))
|
rw.Write([]byte("You requested " + req.URL.String()))
|
||||||
log.Printf("Request from %s for %s", req.RemoteAddr.String(), req.URL)
|
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{
|
server := gemini.Server{
|
||||||
|
52
gemini.go
52
gemini.go
@ -1,5 +1,13 @@
|
|||||||
package gemini
|
package gemini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
// Status codes.
|
// Status codes.
|
||||||
const (
|
const (
|
||||||
StatusInput = 10
|
StatusInput = 10
|
||||||
@ -35,3 +43,47 @@ const (
|
|||||||
var (
|
var (
|
||||||
crlf = []byte("\r\n")
|
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
|
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.
|
// Write writes the known host to the provided io.Writer.
|
||||||
func (k KnownHost) Write(w io.Writer) (int, error) {
|
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))
|
return w.Write([]byte(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user