Skip to content

Commit

Permalink
Fix bug in client which duplicates port numbers when provided.
Browse files Browse the repository at this point in the history
  • Loading branch information
a-h committed Sep 7, 2020
1 parent 4cefa05 commit e8c1a5d
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 14 deletions.
13 changes: 12 additions & 1 deletion browse/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package main

import (
"bufio"
"crypto/tls"
"fmt"
"io"
"net/url"
"os"
"strings"
"time"
"unicode"

"github.com/a-h/gemini"
"github.com/a-h/gemini/cert"
"github.com/gdamore/tcell"
"github.com/mattn/go-runewidth"
)
Expand Down Expand Up @@ -132,7 +135,15 @@ func main() {
askForURL = false
continue
case "Create (Temporary)":
//TODO: Add a certificate to the client.
// Add a certificate to the client.
cert, key, _ := cert.Generate("", "", "", time.Hour*24)
keyPair, err := tls.X509KeyPair(cert, key)
if err != nil {
NewOptions(s, fmt.Sprintf("Failed to create certificate: %v", err), "OK").Focus()
askForURL = true
continue
}
client.AddCertificateForURLPrefix(u.String(), keyPair)
askForURL = false
continue
case "Cancel":
Expand Down
77 changes: 77 additions & 0 deletions cert/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cert

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"strings"
"time"
)

// Generate a pair of keys. These keys can be loaded with
// cert, err = tls.LoadX509KeyPair("cert.pem", "key.pem")
func Generate(organization, commonName, hosts string, duration time.Duration) (cert, key []byte, err error) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
err = fmt.Errorf("cert: failed to generate private key: %w", err)
return
}

// ECDSA, ED25519 and RSA subject keys should have the DigitalSignature
// KeyUsage bits set in the x509.Certificate template
keyUsage := x509.KeyUsageDigitalSignature

notBefore := time.Now().Add(time.Hour * -24)
notAfter := notBefore.Add(duration)

serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
err = fmt.Errorf("cert: failed to generate serial number: %w", err)
return
}

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{organization},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: keyUsage,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}

if hosts != "" {
for _, h := range strings.Split("hosts", ",") {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}
}

derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
err = fmt.Errorf("cert: failed to create certificate: %w", err)
return
}
cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})

privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
err = fmt.Errorf("cert: failed to marshal private key: %v", err)
return
}
key = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})

return
}
4 changes: 2 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func (client *Client) RequestNoTLS(u *url.URL) (resp *Response, err error) {
if port == "" {
port = "1965"
}
conn, err := net.Dial("tcp", u.Host+":"+port)
conn, err := net.Dial("tcp", u.Hostname()+":"+port)
if err != nil {
err = fmt.Errorf("gemini: error connecting: %w", err)
return
Expand All @@ -191,7 +191,7 @@ func (client *Client) RequestURL(u *url.URL) (resp *Response, certificates []str
if port == "" {
port = "1965"
}
conn, err := tls.Dial("tcp", u.Host+":"+port, config)
conn, err := tls.Dial("tcp", u.Hostname()+":"+port, config)
if err != nil {
err = fmt.Errorf("gemini: error connecting: %w", err)
return
Expand Down
6 changes: 4 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,13 @@ func serve(args []string) {
os.Exit(1)
}
h := gemini.FileSystemHandler(gemini.Dir(*pathFlag))
dh, err := gemini.NewDomainHandler(*domainFlag, *certFileFlag, *keyFileFlag, h)

cert, err := tls.LoadX509KeyPair(*certFileFlag, *keyFileFlag)
if err != nil {
fmt.Printf("error: failed to create handler: %v\n", err)
fmt.Printf("error: failed to load certificates: %v\n", err)
os.Exit(1)
}
dh := gemini.NewDomainHandler(*domainFlag, cert, h)
ctx := context.Background()
domainToHandler := map[string]*gemini.DomainHandler{
*domainFlag: dh,
Expand Down
4 changes: 2 additions & 2 deletions example/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ func main() {

// Set up the domain handlers.
ctx := context.Background()
a, err := gemini.NewDomainHandler("a.gemini", "a.crt", "a.key", routerA)
a, err := gemini.NewDomainHandlerFromFiles("a.gemini", "a.crt", "a.key", routerA)
if err != nil {
log.Fatal("error creating domain handler A:", err)
}
b, err := gemini.NewDomainHandler("b.gemini", "b.crt", "b.key", handlerB)
b, err := gemini.NewDomainHandlerFromFiles("b.gemini", "b.crt", "b.key", handlerB)
if err != nil {
log.Fatal("error creating domain handler B:", err)
}
Expand Down
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ module github.com/a-h/gemini

go 1.14

require github.com/rivo/tview v0.0.0-20200818120338-53d50e499bf9 // indirect
require (
github.com/gdamore/tcell v1.3.0
github.com/mattn/go-runewidth v0.0.9
github.com/rivo/tview v0.0.0-20200818120338-53d50e499bf9 // indirect
)
23 changes: 17 additions & 6 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,21 +362,32 @@ type DomainHandler struct {
}

// NewDomainHandler creates a new handler to listen for Gemini requests using TLS.
// The cert can be generated by the github.com/a-h/gemini/cert.Generate package,
// or can generated using openssl:
// keyFile:
// openssl ecparam -genkey -name secp384r1 -out server.key
// certFile:
// openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
func NewDomainHandler(serverName string, cert tls.Certificate, handler Handler) *DomainHandler {
return &DomainHandler{
ServerName: serverName,
KeyPair: cert,
Handler: handler,
}
}

// NewDomainHandlerFromFiles creates a new handler to listen for Gemini requests using TLS.
// certFile / keyFile are links to the X509 keypair. This can be generated using openssl:
// keyFile:
// openssl ecparam -genkey -name secp384r1 -out server.key
// certFile:
// openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
func NewDomainHandler(serverName, certFile, keyFile string, handler Handler) (*DomainHandler, error) {
func NewDomainHandlerFromFiles(serverName, certFile, keyFile string, handler Handler) (*DomainHandler, error) {
keyPair, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
return &DomainHandler{
ServerName: serverName,
KeyPair: keyPair,
Handler: handler,
}, nil
return NewDomainHandler(serverName, keyPair, handler), nil
}

// ListenAndServe starts up a new server to handle multiple domains with a specific certFile, keyFile and handler.
Expand Down

0 comments on commit e8c1a5d

Please sign in to comment.