this repo has no description

implement two-way signature verification

Akshay f9cea457 0d751a41

Changed files
+62 -18
appview
db
state
cmd
knotserver
knotserver
+4 -2
appview/db/db.go
··· 108 109 var secret string 110 err := res.Scan(&secret) 111 - if err != nil { 112 - return "", nil 113 } 114 115 return secret, nil 116 }
··· 108 109 var secret string 110 err := res.Scan(&secret) 111 + if err != nil || secret == "" { 112 + return "", err 113 } 114 + 115 + log.Println("domain, secret: ", domain, secret) 116 117 return secret, nil 118 }
+45 -12
appview/state/state.go
··· 4 "crypto/hmac" 5 "crypto/sha256" 6 "encoding/hex" 7 "log" 8 "net/http" 9 - "net/url" 10 "time" 11 12 "github.com/go-chi/chi/v5" ··· 83 84 // check if domain is valid url, and strip extra bits down to just host 85 domain := r.FormValue("domain") 86 - url, err := url.Parse(domain) 87 if domain == "" || err != nil { 88 http.Error(w, "Invalid form", http.StatusBadRequest) 89 return 90 } 91 92 - key, err := s.Db.GenerateRegistrationKey(url.Host, did) 93 94 if err != nil { 95 log.Println(err) ··· 112 return 113 } 114 115 secret, err := s.Db.GetRegistrationKey(domain) 116 if err != nil { 117 log.Printf("no key found for domain %s: %s\n", domain, err) 118 return 119 } 120 - 121 - hmac := hmac.New(sha256.New, []byte(secret)) 122 - signature := hex.EncodeToString(hmac.Sum(nil)) 123 124 // make a request do the knotserver with an empty body and above signature 125 - url, _ := url.Parse(domain) 126 - url = url.JoinPath("check") 127 - pingRequest, err := http.NewRequest("GET", url.String(), nil) 128 if err != nil { 129 - log.Println("failed to create ping request for ", url.String()) 130 return 131 } 132 - pingRequest.Header.Set("X-Signature", signature) 133 134 client := &http.Client{ 135 Timeout: 5 * time.Second, ··· 142 } 143 144 if resp.StatusCode != http.StatusOK { 145 - log.Println("status nok") 146 w.Write([]byte("no dice")) 147 return 148 } 149 w.Write([]byte("check success")) 150 151 // mark as registered 152 s.Db.Register(domain) 153 154 return 155 } 156 157 func (s *State) Router() http.Handler {
··· 4 "crypto/hmac" 5 "crypto/sha256" 6 "encoding/hex" 7 + "fmt" 8 "log" 9 "net/http" 10 "time" 11 12 "github.com/go-chi/chi/v5" ··· 83 84 // check if domain is valid url, and strip extra bits down to just host 85 domain := r.FormValue("domain") 86 if domain == "" || err != nil { 87 + log.Println(err) 88 http.Error(w, "Invalid form", http.StatusBadRequest) 89 return 90 } 91 92 + key, err := s.Db.GenerateRegistrationKey(domain, did) 93 94 if err != nil { 95 log.Println(err) ··· 112 return 113 } 114 115 + log.Println("checking ", domain) 116 + 117 secret, err := s.Db.GetRegistrationKey(domain) 118 if err != nil { 119 log.Printf("no key found for domain %s: %s\n", domain, err) 120 return 121 } 122 + log.Println("has secret ", secret) 123 124 // make a request do the knotserver with an empty body and above signature 125 + url := fmt.Sprintf("http://%s/internal/health", domain) 126 + 127 + pingRequest, err := buildPingRequest(url, secret) 128 if err != nil { 129 + log.Println("failed to build ping request", err) 130 return 131 } 132 133 client := &http.Client{ 134 Timeout: 5 * time.Second, ··· 141 } 142 143 if resp.StatusCode != http.StatusOK { 144 + log.Println("status nok", resp.StatusCode) 145 w.Write([]byte("no dice")) 146 return 147 } 148 + 149 + // verify response mac 150 + signature := resp.Header.Get("X-Signature") 151 + signatureBytes, err := hex.DecodeString(signature) 152 + if err != nil { 153 + return 154 + } 155 + 156 + expectedMac := hmac.New(sha256.New, []byte(secret)) 157 + expectedMac.Write([]byte("ok")) 158 + 159 + if !hmac.Equal(expectedMac.Sum(nil), signatureBytes) { 160 + log.Printf("response body signature mismatch: %x\n", signatureBytes) 161 + return 162 + } 163 + 164 w.Write([]byte("check success")) 165 166 // mark as registered 167 s.Db.Register(domain) 168 169 return 170 + } 171 + 172 + func buildPingRequest(url, secret string) (*http.Request, error) { 173 + pingRequest, err := http.NewRequest("GET", url, nil) 174 + if err != nil { 175 + return nil, err 176 + } 177 + 178 + timestamp := time.Now().Format(time.RFC3339) 179 + mac := hmac.New(sha256.New, []byte(secret)) 180 + message := pingRequest.Method + pingRequest.URL.Path + timestamp 181 + mac.Write([]byte(message)) 182 + signature := hex.EncodeToString(mac.Sum(nil)) 183 + 184 + pingRequest.Header.Set("X-Signature", signature) 185 + pingRequest.Header.Set("X-Timestamp", timestamp) 186 + 187 + return pingRequest, nil 188 } 189 190 func (s *State) Router() http.Handler {
+3 -4
cmd/knotserver/main.go
··· 7 "log/slog" 8 "net/http" 9 "os" 10 - "os/signal" 11 - "syscall" 12 13 "github.com/icyphox/bild/knotserver" 14 "github.com/icyphox/bild/knotserver/config" 15 ) 16 17 func main() { 18 - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) 19 - defer stop() 20 21 slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil))) 22
··· 7 "log/slog" 8 "net/http" 9 "os" 10 11 "github.com/icyphox/bild/knotserver" 12 "github.com/icyphox/bild/knotserver/config" 13 ) 14 15 func main() { 16 + ctx := context.Background() 17 + // ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) 18 + // defer stop() 19 20 slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil))) 21
+2
knotserver/middleware.go
··· 4 "crypto/hmac" 5 "crypto/sha256" 6 "encoding/hex" 7 "net/http" 8 "time" 9 ) ··· 11 func (h *Handle) VerifySignature(next http.Handler) http.Handler { 12 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 13 signature := r.Header.Get("X-Signature") 14 if signature == "" || !h.verifyHMAC(signature, r) { 15 writeError(w, "signature verification failed", http.StatusForbidden) 16 return
··· 4 "crypto/hmac" 5 "crypto/sha256" 6 "encoding/hex" 7 + "log" 8 "net/http" 9 "time" 10 ) ··· 12 func (h *Handle) VerifySignature(next http.Handler) http.Handler { 13 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 signature := r.Header.Get("X-Signature") 15 + log.Println(signature) 16 if signature == "" || !h.verifyHMAC(signature, r) { 17 writeError(w, "signature verification failed", http.StatusForbidden) 18 return
+8
knotserver/routes.go
··· 2 3 import ( 4 "compress/gzip" 5 "encoding/json" 6 "errors" 7 "fmt" ··· 404 // } 405 406 func (h *Handle) Health(w http.ResponseWriter, r *http.Request) { 407 w.Write([]byte("ok")) 408 }
··· 2 3 import ( 4 "compress/gzip" 5 + "crypto/hmac" 6 + "crypto/sha256" 7 + "encoding/hex" 8 "encoding/json" 9 "errors" 10 "fmt" ··· 407 // } 408 409 func (h *Handle) Health(w http.ResponseWriter, r *http.Request) { 410 + log.Println("got health check") 411 + mac := hmac.New(sha256.New, []byte(h.c.Secret)) 412 + mac.Write([]byte("ok")) 413 + w.Header().Add("X-Signature", hex.EncodeToString(mac.Sum(nil))) 414 + 415 w.Write([]byte("ok")) 416 }