Change package name to gmi
This commit is contained in:
parent
a4a8d49ca7
commit
38fe1f21dc
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Package gemini implements the Gemini protocol.
|
// Package gemini implements the Gemini protocol.
|
||||||
package gemini
|
package gmi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package gemini
|
package gmi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package gemini
|
package gmi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
18
verify.go
18
verify.go
|
@ -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}
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user