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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user