Add basic key registration

This commit is contained in:
cheddar 2025-02-17 20:50:48 -05:00
parent 3dfe5b8558
commit 949d1fc2ad
No known key found for this signature in database
6 changed files with 138 additions and 55 deletions

View file

@ -0,0 +1,12 @@
package keydirectory
import (
"crypto"
"github.com/common-fate/httpsig/verifier"
)
type RegistrationDirectory interface {
verifier.KeyDirectory
RegisterKey(key crypto.PublicKey, alg string, userId string) (string, error)
}

61
main.go
View file

@ -21,35 +21,46 @@ import (
func main() { func main() {
useClient := flag.Bool("c", false, "Run client") useClient := flag.Bool("c", false, "Run client")
keyPath := flag.String("key", "", "Path to the private key (client mode) or public key (server mode) to use - Required") register := flag.Bool("r", false, "Register a key")
user := flag.String("user", "", "Username to register")
keyPath := flag.String("key", "", "Path to the private key (client mode) or public key (registration mode) to use")
simulateCaddy := flag.Bool("caddy", false, "Simulate caddy reverse proxy") simulateCaddy := flag.Bool("caddy", false, "Simulate caddy reverse proxy")
flag.Parse() flag.Parse()
if *keyPath == "" {
flag.PrintDefaults()
return
}
if *useClient { if *useClient {
runClient(keyPath, *simulateCaddy) if *keyPath == "" || *user == "" {
flag.PrintDefaults()
return
}
runClient(*keyPath, *user, *simulateCaddy)
} else if *register {
if *keyPath == "" || *user == "" {
flag.PrintDefaults()
return
}
registerKey(*keyPath, *user)
} else { } else {
runServer(keyPath, *simulateCaddy) runServer(*simulateCaddy)
} }
} }
func runClient(keyFile *string, simulateCaddy bool) { func runClient(keyFile string, user string, simulateCaddy bool) {
testData := map[string]string{"hello": "world"} testData := map[string]string{"hello": "world"}
json_data, _ := json.Marshal(testData) json_data, _ := json.Marshal(testData)
key, err := loadPrivateKey(*keyFile) key, err := loadPrivateKey(keyFile)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
client, err := client.GetSigningClient(key, "test-id") client, err := client.GetSigningClient(key, user)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -92,14 +103,8 @@ func runClient(keyFile *string, simulateCaddy bool) {
fmt.Println(string(out[:])) fmt.Println(string(out[:]))
} }
func runServer(keyFile *string, simulateCaddy bool) { func runServer(simulateCaddy bool) {
key, alg, err := loadPublicKey(*keyFile) keyDir := sqlite_directory.CreateDirectory()
if err != nil {
log.Fatal(err)
}
keyDir := sqlite_directory.CreateDirectory(alg, key)
server.Start(simulateCaddy, keyDir) server.Start(simulateCaddy, keyDir)
} }
@ -114,21 +119,21 @@ func loadPrivateKey(keyFile string) (crypto.PrivateKey, error) {
return ssh.ParseRawPrivateKey(keyBytes) return ssh.ParseRawPrivateKey(keyBytes)
} }
func loadPublicKey(keyFile string) (crypto.PublicKey, string, error) { func registerKey(keyFile string, userId string) {
keyBytes, err := os.ReadFile(keyFile) keyBytes, err := os.ReadFile(keyFile)
if err != nil { if err != nil {
return nil, "", err log.Fatal(err)
} }
pk, _, _, _, err := ssh.ParseAuthorizedKey(keyBytes) keyText := string(keyBytes)
var alg string request := server.RegisterRequest{
UserId: userId,
switch pk.Type() { Key: keyText,
case "ssh-ed25519":
alg = "ed25519"
} }
return pk.(ssh.CryptoPublicKey).CryptoPublicKey(), alg, err json_data, _ := json.Marshal(request)
http.DefaultClient.Post("http://localhost:8080/register", "application/json", bytes.NewBuffer(json_data))
} }

View file

@ -0,0 +1,6 @@
package server
type RegisterRequest struct {
UserId string
Key string
}

View file

@ -2,15 +2,18 @@ package server
import ( import (
"context" "context"
"crypto"
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"crispbyte.dev/sig-auth/keydirectory"
"github.com/common-fate/httpsig" "github.com/common-fate/httpsig"
"github.com/common-fate/httpsig/inmemory" "github.com/common-fate/httpsig/inmemory"
"github.com/common-fate/httpsig/verifier" "golang.org/x/crypto/ssh"
) )
func Start(isCaddyAuth bool, keyDir verifier.KeyDirectory) error { func Start(isCaddyAuth bool, keyDir keydirectory.RegistrationDirectory) error {
mux := http.NewServeMux() mux := http.NewServeMux()
verifier := httpsig.Middleware(httpsig.MiddlewareOpts{ verifier := httpsig.Middleware(httpsig.MiddlewareOpts{
@ -29,16 +32,7 @@ func Start(isCaddyAuth bool, keyDir verifier.KeyDirectory) error {
}, },
}) })
verifyHandler := verifier(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { verifyHandler := verifier(getDefaultHandler(isCaddyAuth))
attr := httpsig.AttributesFromContext(r.Context()).(string)
if isCaddyAuth {
w.Header().Add("Remote-User", attr)
} else {
msg := fmt.Sprintf("hello, %s!", attr)
w.Write([]byte(msg))
}
}))
var handler http.Handler var handler http.Handler
@ -49,8 +43,68 @@ func Start(isCaddyAuth bool, keyDir verifier.KeyDirectory) error {
} }
mux.Handle("/", handler) mux.Handle("/", handler)
mux.Handle("/register", getRegistrationHandler(keyDir))
err := http.ListenAndServe("localhost:8080", mux) err := http.ListenAndServe("localhost:8080", mux)
return err return err
} }
func getDefaultHandler(isCaddyAuth bool) http.Handler {
handler := func(w http.ResponseWriter, r *http.Request) {
attr := httpsig.AttributesFromContext(r.Context()).(string)
if isCaddyAuth {
w.Header().Add("Remote-User", attr)
} else {
msg := fmt.Sprintf("hello, %s!", attr)
w.Write([]byte(msg))
}
}
return http.HandlerFunc(handler)
}
func getRegistrationHandler(keyDir keydirectory.RegistrationDirectory) http.Handler {
handler := func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Bad request", 400)
return
}
var request RegisterRequest
err := json.NewDecoder(r.Body).Decode(&request)
if err != nil {
http.Error(w, fmt.Sprintf("Bad request - %s", err), 400)
return
}
key, alg, err := parsePublicKey(request.Key)
if err != nil {
http.Error(w, fmt.Sprintf("Bad request - %s", err), 400)
return
}
fmt.Printf("Registering %s key for %s\n", alg, request.UserId)
keyDir.RegisterKey(key, alg, request.UserId)
}
return http.HandlerFunc(handler)
}
func parsePublicKey(input string) (crypto.PublicKey, string, error) {
pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(input))
var alg string
switch pk.Type() {
case "ssh-ed25519":
alg = "ed25519"
}
return pk.(ssh.CryptoPublicKey).CryptoPublicKey(), alg, err
}

View file

@ -1,23 +1,11 @@
package sqlite_directory package sqlite_directory
import ( import (
"crypto"
"crispbyte.dev/sig-auth/keydirectory" "crispbyte.dev/sig-auth/keydirectory"
) )
func CreateDirectory(alg string, publicKey crypto.PublicKey) InMemoryDirectory { func CreateDirectory() InMemoryDirectory {
keyDir := InMemoryDirectory{ return InMemoryDirectory{
records: map[string]keydirectory.KeyEntry{}, records: map[string]keydirectory.KeyEntry{},
} }
keyId := "test-id"
keyDir.records[keyId] = keydirectory.KeyEntry{
Alg: alg,
PublicKey: publicKey,
UserId: "test_user",
}
return keyDir
} }

View file

@ -2,7 +2,9 @@ package sqlite_directory
import ( import (
"context" "context"
"crypto"
"crypto/ed25519" "crypto/ed25519"
"errors"
"fmt" "fmt"
"github.com/common-fate/httpsig/alg_ed25519" "github.com/common-fate/httpsig/alg_ed25519"
@ -16,7 +18,11 @@ type InMemoryDirectory struct {
} }
func (dir InMemoryDirectory) GetKey(ctx context.Context, keyId string, _ string) (verifier.Algorithm, error) { func (dir InMemoryDirectory) GetKey(ctx context.Context, keyId string, _ string) (verifier.Algorithm, error) {
entry := dir.records[keyId] entry, ok := dir.records[keyId]
if !ok {
return nil, errors.New("key not found in directory")
}
var alg verifier.Algorithm var alg verifier.Algorithm
var err error var err error
@ -33,3 +39,15 @@ func (dir InMemoryDirectory) GetKey(ctx context.Context, keyId string, _ string)
return alg, err return alg, err
} }
func (dir InMemoryDirectory) RegisterKey(key crypto.PublicKey, alg string, userId string) (string, error) {
keyId := userId
dir.records[keyId] = keydirectory.KeyEntry{
Alg: alg,
PublicKey: key,
UserId: userId,
}
return keyId, nil
}