gemini.go (view raw)
1package main
2
3import (
4 "bytes"
5 "crypto/tls"
6 "crypto/x509" // todo move into cert file
7 "encoding/pem"
8 // "fmt"
9 "git.sr.ht/~adnano/gmi"
10 "log"
11 "os"
12 // "path"
13 "text/template"
14 "time"
15)
16
17func gmiIndex(w *gmi.ResponseWriter, r *gmi.Request) {
18 t, err := template.ParseFiles("templates/index.gmi")
19 if err != nil {
20 log.Fatal(err)
21 }
22 files, _ := getIndexFiles()
23 users, _ := getUsers()
24 data := struct {
25 Domain string
26 Files []*File
27 Users []string
28 }{
29 Domain: "flounder.online",
30 Files: files,
31 Users: users,
32 }
33 t.Execute(w, data)
34}
35
36func gmiPage(w *gmi.ResponseWriter, r *gmi.Request) {
37}
38
39func runGeminiServer(config *Config) {
40 var server gmi.Server
41
42 if err := server.CertificateStore.Load("./tmpcerts"); err != nil {
43 log.Fatal(err)
44 }
45 server.GetCertificate = func(hostname string, store *gmi.CertificateStore) *tls.Certificate {
46 cert, err := store.Lookup(hostname)
47 if err != nil {
48 switch err {
49 case gmi.ErrCertificateExpired:
50 // Generate a new certificate if the current one is expired.
51 log.Print("Old certificate expired, creating new one")
52 fallthrough
53 case gmi.ErrCertificateUnknown:
54 // Generate a certificate if one does not exist.
55 cert, err := gmi.NewCertificate(hostname, time.Minute)
56 if err != nil {
57 // Failed to generate new certificate, abort
58 return nil
59 }
60 // Store and return the new certificate
61 err = writeCertificate("./tmpcerts/"+hostname, cert)
62 if err != nil {
63 return nil
64 }
65 store.Add(hostname, cert)
66 return &cert
67 }
68 }
69 return cert
70 }
71
72 // replace with wildcard cert
73 server.HandleFunc("localhost", gmiIndex)
74
75 server.ListenAndServe()
76}
77
78// writeCertificate writes the provided certificate and private key
79// to path.crt and path.key respectively.
80func writeCertificate(path string, cert tls.Certificate) error {
81 crt, err := marshalX509Certificate(cert.Leaf.Raw)
82 if err != nil {
83 return err
84 }
85 key, err := marshalPrivateKey(cert.PrivateKey)
86 if err != nil {
87 return err
88 }
89
90 // Write the certificate
91 crtPath := path + ".crt"
92 crtOut, err := os.OpenFile(crtPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
93 if err != nil {
94 return err
95 }
96 if _, err := crtOut.Write(crt); err != nil {
97 return err
98 }
99
100 // Write the private key
101 keyPath := path + ".key"
102 keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
103 if err != nil {
104 return err
105 }
106 if _, err := keyOut.Write(key); err != nil {
107 return err
108 }
109 return nil
110}
111
112// marshalX509Certificate returns a PEM-encoded version of the given raw certificate.
113func marshalX509Certificate(cert []byte) ([]byte, error) {
114 var b bytes.Buffer
115 if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil {
116 return nil, err
117 }
118 return b.Bytes(), nil
119}
120
121// marshalPrivateKey returns PEM encoded versions of the given certificate and private key.
122func marshalPrivateKey(priv interface{}) ([]byte, error) {
123 var b bytes.Buffer
124 privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
125 if err != nil {
126 return nil, err
127 }
128 if err := pem.Encode(&b, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
129 return nil, err
130 }
131 return b.Bytes(), nil
132}