From 949d1fc2adec4846ba9c5f2ce1efdc207a4380e6 Mon Sep 17 00:00:00 2001 From: cheddar Date: Mon, 17 Feb 2025 20:50:48 -0500 Subject: [PATCH] Add basic key registration --- keydirectory/registration.go | 12 +++++ main.go | 61 ++++++++++++---------- server/register_request.go | 6 +++ server/server.go | 78 +++++++++++++++++++++++----- sqlite_directory/create_directory.go | 16 +----- sqlite_directory/sqlite_directory.go | 20 ++++++- 6 files changed, 138 insertions(+), 55 deletions(-) create mode 100644 keydirectory/registration.go create mode 100644 server/register_request.go diff --git a/keydirectory/registration.go b/keydirectory/registration.go new file mode 100644 index 0000000..e3ec100 --- /dev/null +++ b/keydirectory/registration.go @@ -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) +} diff --git a/main.go b/main.go index dfe9e74..5b11a21 100644 --- a/main.go +++ b/main.go @@ -21,35 +21,46 @@ import ( func main() { 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") flag.Parse() - if *keyPath == "" { - flag.PrintDefaults() - return - } - 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 { - 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"} json_data, _ := json.Marshal(testData) - key, err := loadPrivateKey(*keyFile) + key, err := loadPrivateKey(keyFile) if err != nil { log.Fatal(err) } - client, err := client.GetSigningClient(key, "test-id") + client, err := client.GetSigningClient(key, user) if err != nil { log.Fatal(err) @@ -92,14 +103,8 @@ func runClient(keyFile *string, simulateCaddy bool) { fmt.Println(string(out[:])) } -func runServer(keyFile *string, simulateCaddy bool) { - key, alg, err := loadPublicKey(*keyFile) - - if err != nil { - log.Fatal(err) - } - - keyDir := sqlite_directory.CreateDirectory(alg, key) +func runServer(simulateCaddy bool) { + keyDir := sqlite_directory.CreateDirectory() server.Start(simulateCaddy, keyDir) } @@ -114,21 +119,21 @@ func loadPrivateKey(keyFile string) (crypto.PrivateKey, error) { return ssh.ParseRawPrivateKey(keyBytes) } -func loadPublicKey(keyFile string) (crypto.PublicKey, string, error) { +func registerKey(keyFile string, userId string) { keyBytes, err := os.ReadFile(keyFile) if err != nil { - return nil, "", err + log.Fatal(err) } - pk, _, _, _, err := ssh.ParseAuthorizedKey(keyBytes) + keyText := string(keyBytes) - var alg string - - switch pk.Type() { - case "ssh-ed25519": - alg = "ed25519" + request := server.RegisterRequest{ + UserId: userId, + Key: keyText, } - 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)) } diff --git a/server/register_request.go b/server/register_request.go new file mode 100644 index 0000000..69fbe17 --- /dev/null +++ b/server/register_request.go @@ -0,0 +1,6 @@ +package server + +type RegisterRequest struct { + UserId string + Key string +} diff --git a/server/server.go b/server/server.go index fa289f5..6d3e558 100644 --- a/server/server.go +++ b/server/server.go @@ -2,15 +2,18 @@ package server import ( "context" + "crypto" + "encoding/json" "fmt" "net/http" + "crispbyte.dev/sig-auth/keydirectory" "github.com/common-fate/httpsig" "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() 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) { - 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)) - } - })) + verifyHandler := verifier(getDefaultHandler(isCaddyAuth)) var handler http.Handler @@ -49,8 +43,68 @@ func Start(isCaddyAuth bool, keyDir verifier.KeyDirectory) error { } mux.Handle("/", handler) + mux.Handle("/register", getRegistrationHandler(keyDir)) err := http.ListenAndServe("localhost:8080", mux) 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 +} diff --git a/sqlite_directory/create_directory.go b/sqlite_directory/create_directory.go index 034e002..06414d8 100644 --- a/sqlite_directory/create_directory.go +++ b/sqlite_directory/create_directory.go @@ -1,23 +1,11 @@ package sqlite_directory import ( - "crypto" - "crispbyte.dev/sig-auth/keydirectory" ) -func CreateDirectory(alg string, publicKey crypto.PublicKey) InMemoryDirectory { - keyDir := InMemoryDirectory{ +func CreateDirectory() InMemoryDirectory { + return InMemoryDirectory{ records: map[string]keydirectory.KeyEntry{}, } - - keyId := "test-id" - - keyDir.records[keyId] = keydirectory.KeyEntry{ - Alg: alg, - PublicKey: publicKey, - UserId: "test_user", - } - - return keyDir } diff --git a/sqlite_directory/sqlite_directory.go b/sqlite_directory/sqlite_directory.go index ea0d632..a1a362a 100644 --- a/sqlite_directory/sqlite_directory.go +++ b/sqlite_directory/sqlite_directory.go @@ -2,7 +2,9 @@ package sqlite_directory import ( "context" + "crypto" "crypto/ed25519" + "errors" "fmt" "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) { - 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 err error @@ -33,3 +39,15 @@ func (dir InMemoryDirectory) GetKey(ctx context.Context, keyId string, _ string) 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 +}