Change package name to gmi

This commit is contained in:
adnano 2020-09-27 20:20:59 -04:00
parent a4a8d49ca7
commit 38fe1f21dc
11 changed files with 64 additions and 68 deletions

View File

@ -59,7 +59,7 @@ gemini.Send(req)
Clients can also load their own list of known hosts: Clients can also load their own list of known hosts:
```go ```go
client := &Client{} client := &gmi.Client{}
if err := client.KnownHosts.LoadFrom("path/to/my/known_hosts"); err != nil { if err := client.KnownHosts.LoadFrom("path/to/my/known_hosts"); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -69,7 +69,7 @@ Clients can then specify how to trust certificates in the `TrustCertificate`
field: field:
```go ```go
client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gemini.KnownHosts) error { client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gmi.KnownHosts) error {
// If the certificate is in the known hosts list, allow the connection // If the certificate is in the known hosts list, allow the connection
return knownHosts.Lookup(hostname, cert) return knownHosts.Lookup(hostname, cert)
} }

View File

@ -1,4 +1,4 @@
package gemini package gmi
import ( import (
"bytes" "bytes"

View File

@ -1,5 +1,5 @@
// Package gemini implements the Gemini protocol. // Package gemini implements the Gemini protocol.
package gemini package gmi
import ( import (
"bufio" "bufio"

View File

@ -39,12 +39,12 @@ func main() {
// //
// go run -tags=example ../cert // go run -tags=example ../cert
// //
cert, err := tls.LoadX509KeyPair("examples/client/localhost.crt", "examples/client/localhost.key") cert, err := tls.LoadX509KeyPair("examples/server/localhost.crt", "examples/server/localhost.key")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
handler := &gemini.ServeMux{} handler := &gmi.ServeMux{}
handler.HandleFunc("", welcome) handler.HandleFunc("", welcome)
handler.HandleFunc("/login", login) handler.HandleFunc("/login", login)
handler.HandleFunc("/login/password", loginPassword) handler.HandleFunc("/login/password", loginPassword)
@ -52,7 +52,7 @@ func main() {
handler.HandleFunc("/admin", admin) handler.HandleFunc("/admin", admin)
handler.HandleFunc("/logout", logout) handler.HandleFunc("/logout", logout)
server := &gemini.Server{ server := &gmi.Server{
Certificate: cert, Certificate: cert,
Handler: handler, Handler: handler,
} }
@ -63,102 +63,98 @@ func main() {
} }
func getSession(crt *x509.Certificate) (*session, bool) { func getSession(crt *x509.Certificate) (*session, bool) {
fingerprint := gemini.Fingerprint(crt) fingerprint := gmi.Fingerprint(crt)
session, ok := sessions[fingerprint] session, ok := sessions[fingerprint]
return session, ok return session, ok
} }
func welcome(rw *gemini.ResponseWriter, req *gemini.Request) { func welcome(rw *gmi.ResponseWriter, req *gmi.Request) {
rw.WriteHeader(gemini.StatusSuccess, "text/gemini") rw.WriteHeader(gmi.StatusSuccess, "text/gemini")
rw.Write([]byte("Welcome to this example.\n=> /login Login\n")) rw.Write([]byte("Welcome to this example.\n=> /login Login\n"))
} }
func login(rw *gemini.ResponseWriter, req *gemini.Request) { func login(rw *gmi.ResponseWriter, req *gmi.Request) {
if len(req.TLS.PeerCertificates) > 0 { if len(req.TLS.PeerCertificates) > 0 {
if username := req.URL.RawQuery; username == "" { if username := req.URL.RawQuery; username == "" {
rw.WriteHeader(gemini.StatusInput, "Username") rw.WriteHeader(gmi.StatusInput, "Username")
} else { } else {
fingerprint := gemini.Fingerprint(req.TLS.PeerCertificates[0]) fingerprint := gmi.Fingerprint(req.TLS.PeerCertificates[0])
sessions[fingerprint] = &session{ sessions[fingerprint] = &session{
username: username, username: username,
} }
// TODO: Remove scheme and host once example client can handle relative redirects // TODO: Remove scheme and host once example client can handle relative redirects
rw.WriteHeader(gemini.StatusRedirectTemporary, "gemini://localhost/login/password") rw.WriteHeader(gmi.StatusRedirect, "gemini://localhost/login/password")
} }
} else { } else {
rw.WriteHeader(gemini.StatusClientCertificateRequired, "Certificate required") rw.WriteHeader(gmi.StatusClientCertificateRequired, "Certificate required")
} }
} }
func loginPassword(rw *gemini.ResponseWriter, req *gemini.Request) { func loginPassword(rw *gmi.ResponseWriter, req *gmi.Request) {
if len(req.TLS.PeerCertificates) > 0 { if len(req.TLS.PeerCertificates) > 0 {
session, ok := getSession(req.TLS.PeerCertificates[0]) session, ok := getSession(req.TLS.PeerCertificates[0])
if !ok { if !ok {
rw.WriteHeader(gemini.StatusCertificateNotAuthorised, "Not authorized") rw.WriteHeader(gmi.StatusCertificateNotAuthorised, "Not authorized")
return return
} }
if password := req.URL.RawQuery; password == "" { if password := req.URL.RawQuery; password == "" {
rw.WriteHeader(gemini.StatusInput, "Password") rw.WriteHeader(gmi.StatusInput, "Password")
} else { } else {
expected := logins[session.username].password expected := logins[session.username].password
if password == expected { if password == expected {
// TODO: Remove scheme and host once example client can handle relative redirects // TODO: Remove scheme and host once example client can handle relative redirects
session.authorized = true session.authorized = true
rw.WriteHeader(gemini.StatusRedirectTemporary, "gemini://localhost/profile") rw.WriteHeader(gmi.StatusRedirect, "gemini://localhost/profile")
} else { } else {
rw.WriteHeader(gemini.StatusInput, "Wrong password. Please try again") rw.WriteHeader(gmi.StatusInput, "Wrong password. Please try again")
} }
} }
} else { } else {
rw.WriteHeader(gemini.StatusClientCertificateRequired, "Certificate required") rw.WriteHeader(gmi.StatusClientCertificateRequired, "Certificate required")
} }
} }
func logout(rw *gemini.ResponseWriter, req *gemini.Request) { func logout(rw *gmi.ResponseWriter, req *gmi.Request) {
if len(req.TLS.PeerCertificates) > 0 { if len(req.TLS.PeerCertificates) > 0 {
fingerprint := gemini.Fingerprint(req.TLS.PeerCertificates[0]) fingerprint := gmi.Fingerprint(req.TLS.PeerCertificates[0])
delete(sessions, fingerprint) delete(sessions, fingerprint)
} }
rw.WriteHeader(gemini.StatusSuccess, "text/gemini") rw.WriteHeader(gmi.StatusSuccess, "text/gemini")
rw.Write([]byte("Successfully logged out.\n")) rw.Write([]byte("Successfully logged out.\n"))
} }
func badLogin(rw *gemini.ResponseWriter, req *gemini.Request) { func profile(rw *gmi.ResponseWriter, req *gmi.Request) {
}
func profile(rw *gemini.ResponseWriter, req *gemini.Request) {
if len(req.TLS.PeerCertificates) > 0 { if len(req.TLS.PeerCertificates) > 0 {
session, ok := getSession(req.TLS.PeerCertificates[0]) session, ok := getSession(req.TLS.PeerCertificates[0])
if !ok { if !ok {
rw.WriteHeader(gemini.StatusCertificateNotAuthorised, "Certificate not authorized") rw.WriteHeader(gmi.StatusCertificateNotAuthorised, "Certificate not authorized")
return return
} }
user := logins[session.username] user := logins[session.username]
profile := fmt.Sprintf("Username: %s\nAdmin: %t\n=> /logout Logout", session.username, user.admin) profile := fmt.Sprintf("Username: %s\nAdmin: %t\n=> /logout Logout", session.username, user.admin)
rw.WriteHeader(gemini.StatusSuccess, "text/gemini") rw.WriteHeader(gmi.StatusSuccess, "text/gemini")
rw.Write([]byte(profile)) rw.Write([]byte(profile))
} else { } else {
rw.WriteHeader(gemini.StatusClientCertificateRequired, "Certificate required") rw.WriteHeader(gmi.StatusClientCertificateRequired, "Certificate required")
} }
} }
func admin(rw *gemini.ResponseWriter, req *gemini.Request) { func admin(rw *gmi.ResponseWriter, req *gmi.Request) {
if len(req.TLS.PeerCertificates) > 0 { if len(req.TLS.PeerCertificates) > 0 {
session, ok := getSession(req.TLS.PeerCertificates[0]) session, ok := getSession(req.TLS.PeerCertificates[0])
if !ok { if !ok {
rw.WriteHeader(gemini.StatusCertificateNotAuthorised, "Certificate not authorized") rw.WriteHeader(gmi.StatusCertificateNotAuthorised, "Certificate not authorized")
return return
} }
user := logins[session.username] user := logins[session.username]
if !user.admin { if !user.admin {
rw.WriteHeader(gemini.StatusCertificateNotAuthorised, "Admins only!") rw.WriteHeader(gmi.StatusCertificateNotAuthorised, "Admins only!")
return return
} }
rw.WriteHeader(gemini.StatusSuccess, "text/gemini") rw.WriteHeader(gmi.StatusSuccess, "text/gemini")
rw.Write([]byte("Welcome to the admin portal.\n")) rw.Write([]byte("Welcome to the admin portal.\n"))
} else { } else {
rw.WriteHeader(gemini.StatusClientCertificateRequired, "Certificate required") rw.WriteHeader(gmi.StatusClientCertificateRequired, "Certificate required")
} }
} }

View File

@ -13,22 +13,22 @@ import (
var ( var (
scanner = bufio.NewScanner(os.Stdin) scanner = bufio.NewScanner(os.Stdin)
client *gemini.Client client *gmi.Client
) )
func init() { func init() {
// Initialize the client // Initialize the client
client = &gemini.Client{} client = &gmi.Client{}
client.KnownHosts.Load() // Load known hosts client.KnownHosts.Load() // Load known hosts
client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gemini.KnownHosts) error { client.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *gmi.KnownHosts) error {
err := knownHosts.Lookup(hostname, cert) err := knownHosts.Lookup(hostname, cert)
if err != nil { if err != nil {
switch err { switch err {
case gemini.ErrCertificateNotTrusted: case gmi.ErrCertificateNotTrusted:
// Alert the user that the certificate is not trusted // Alert the user that the certificate is not trusted
fmt.Printf("Warning: Certificate for %s is not trusted!\n", hostname) fmt.Printf("Warning: Certificate for %s is not trusted!\n", hostname)
fmt.Println("This could indicate a Man-in-the-Middle attack.") fmt.Println("This could indicate a Man-in-the-Middle attack.")
case gemini.ErrUnknownCertificate: case gmi.ErrUnknownCertificate:
// Prompt the user to trust the certificate // Prompt the user to trust the certificate
trust := trustCertificate(cert) trust := trustCertificate(cert)
switch trust { switch trust {
@ -48,33 +48,33 @@ func init() {
} }
// sendRequest sends a request to the given url. // sendRequest sends a request to the given url.
func sendRequest(req *gemini.Request) error { func sendRequest(req *gmi.Request) error {
resp, err := client.Send(req) resp, err := client.Send(req)
if err != nil { if err != nil {
return err return err
} }
switch resp.Status / 10 { switch resp.Status / 10 {
case gemini.StatusClassInput: case gmi.StatusClassInput:
fmt.Printf("%s: ", resp.Meta) fmt.Printf("%s: ", resp.Meta)
scanner.Scan() scanner.Scan()
req.URL.RawQuery = scanner.Text() req.URL.RawQuery = scanner.Text()
return sendRequest(req) return sendRequest(req)
case gemini.StatusClassSuccess: case gmi.StatusClassSuccess:
fmt.Print(string(resp.Body)) fmt.Print(string(resp.Body))
return nil return nil
case gemini.StatusClassRedirect: case gmi.StatusClassRedirect:
fmt.Println("Redirecting to ", resp.Meta) fmt.Println("Redirecting to ", resp.Meta)
req, err := gemini.NewRequest(resp.Meta) req, err := gmi.NewRequest(resp.Meta)
if err != nil { if err != nil {
return err return err
} }
return sendRequest(req) return sendRequest(req)
case gemini.StatusClassTemporaryFailure: case gmi.StatusClassTemporaryFailure:
return fmt.Errorf("Temporary failure: %s", resp.Meta) return fmt.Errorf("Temporary failure: %s", resp.Meta)
case gemini.StatusClassPermanentFailure: case gmi.StatusClassPermanentFailure:
return fmt.Errorf("Permanent failure: %s", resp.Meta) return fmt.Errorf("Permanent failure: %s", resp.Meta)
case gemini.StatusClassClientCertificateRequired: case gmi.StatusClassClientCertificateRequired:
fmt.Println("Generating client certificate for", req.Hostname()) fmt.Println("Generating client certificate for", req.Hostname())
return nil // TODO: Generate and store client certificate return nil // TODO: Generate and store client certificate
} }
@ -99,7 +99,7 @@ Otherwise, this should be safe to trust.
=> ` => `
func trustCertificate(cert *x509.Certificate) trust { func trustCertificate(cert *x509.Certificate) trust {
fmt.Printf(trustPrompt, gemini.Fingerprint(cert)) fmt.Printf(trustPrompt, gmi.Fingerprint(cert))
scanner.Scan() scanner.Scan()
switch scanner.Text() { switch scanner.Text() {
case "t": case "t":
@ -118,7 +118,7 @@ func main() {
} }
url := os.Args[1] url := os.Args[1]
req, err := gemini.NewRequest(url) req, err := gmi.NewRequest(url)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@ -20,10 +20,10 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
mux := &gemini.ServeMux{} mux := &gmi.ServeMux{}
mux.Handle("/", gemini.FileServer(gemini.Dir("/var/www"))) mux.Handle("/", gmi.FileServer(gmi.Dir("/var/www")))
server := gemini.Server{ server := gmi.Server{
Handler: mux, Handler: mux,
Certificate: cert, Certificate: cert,
} }

View File

@ -1,4 +1,4 @@
package gemini package gmi
import ( import (
"crypto/x509" "crypto/x509"

View File

@ -1,4 +1,4 @@
package gemini package gmi
import ( import (
"math/rand" "math/rand"

View File

@ -1,4 +1,4 @@
package gemini package gmi
import ( import (
"bufio" "bufio"

View File

@ -1,4 +1,4 @@
package gemini package gmi
import ( import (
"bufio" "bufio"

View File

@ -1,4 +1,12 @@
package gemini // Hostname verification code from the crypto/x509 package.
// Modified to allow Common Names in the short term, until new certificates
// can be issued with SANs.
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gmi
import ( import (
"crypto/x509" "crypto/x509"
@ -9,14 +17,6 @@ import (
"unicode/utf8" "unicode/utf8"
) )
// Hostname verification code from the crypto/x509 package.
// Modified to allow Common Names in the short term, until new certificates
// can be issued with SANs.
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
var ( var (
oidExtensionSubjectAltName = []int{2, 5, 29, 17} oidExtensionSubjectAltName = []int{2, 5, 29, 17}
) )