diff --git a/lib/auth/webauthn/httpserver/README.md b/lib/auth/webauthn/httpserver/README.md deleted file mode 100644 index bd7dc6a975389..0000000000000 --- a/lib/auth/webauthn/httpserver/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Webauthn httpserver - -The httpserver package contains a toy Webauthn HTTPS server used to test web -integration and the interplay between web-registered and tsh-registered devices. - -Devices registered via either interface should be able to be used -interchangeably (with the exception of Touch ID, which is always tied to a -specific app). - -This is meant for local testing and debugging only. Keep it far away from any -production uses. - -## Why this exists? - -* It is a simple way to test web integration -* It is a simple way to interact with Touch ID -* Device registration (currently) relies on a streaming RPC, which is difficult - to interact with using pure Js - -Note that browser Webauthn APIs need a secure context (or you have to disable a -bunch of checks in your browser, YMMV). - -## Usage - -1. Start the Teleport service -2. Start the httpserver - -```shell -# Generate TLS certificates for the server -# (Eg, `cd; mkcert localhost 127.0.0.1 ::1`) - -# Start the test server -# cd /path/to/teleport/repo -go run ./lib/auth/webauthn/httpserver/ \ - -cert_file ~/localhost+2.pem \ - -key_file ~/localhost+3-key.pem -``` - -3. Navigate to https://localhost:8080/index.html -4. ? -5. Profit diff --git a/lib/auth/webauthn/httpserver/index.html b/lib/auth/webauthn/httpserver/index.html deleted file mode 100644 index a31d818cbad60..0000000000000 --- a/lib/auth/webauthn/httpserver/index.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - WebAuthn Playground - - - -
-

Login and Registration

- - - -
- - - -
-
-
-

Registration

- - - -
- - - -
-
- - - - - - diff --git a/lib/auth/webauthn/httpserver/main.go b/lib/auth/webauthn/httpserver/main.go deleted file mode 100644 index 4a009d704a40c..0000000000000 --- a/lib/auth/webauthn/httpserver/main.go +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright 2021 Gravitational, Inc -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "bytes" - "context" - _ "embed" // enable embed - "encoding/json" - "flag" - "fmt" - "io" - "log" - "net/http" - "sync" - - "github.com/gravitational/trace" - - apiclient "github.com/gravitational/teleport/api/client" - "github.com/gravitational/teleport/api/client/proto" - wanlib "github.com/gravitational/teleport/lib/auth/webauthn" - libclient "github.com/gravitational/teleport/lib/client" -) - -//go:embed index.html -var indexPage []byte - -var ( - addr = flag.String("addr", "localhost:8080", "Server bind address") - certFile = flag.String("cert_file", "cert.pem", "Cert PEM file") - keyFile = flag.String("key_file", "cert-key.pem", "Key PEM file") - - authAddr = flag.String("auth_addr", "localhost:3025", "Teleport Auth address") - webAddr = flag.String("web_addr", "localhost:3080", "Teleport Web API address") -) - -func main() { - flag.Parse() - if err := run(); err != nil { - log.Fatal(err) - } -} - -func run() error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - fmt.Println("Starting Teleport client") - profile := apiclient.LoadProfile("", "") - teleport, err := apiclient.New(ctx, apiclient.Config{ - Addrs: []string{*authAddr}, - Credentials: []apiclient.Credentials{profile}, - }) - if err != nil { - fmt.Println("Teleport client startup failed, did you run tsh login?") - return trace.Wrap(err) - } - - http.Handle("/", http.RedirectHandler("/index.html", http.StatusSeeOther)) - http.HandleFunc("/index.html", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=UTF-8") - w.WriteHeader(http.StatusOK) - w.Write(indexPage) - }) - - s := &server{ - ctx: ctx, - teleport: teleport, - } - http.HandleFunc("/login/1", s.login1) - http.HandleFunc("/login/2", s.login2) - http.HandleFunc("/register/1", s.register1) - http.HandleFunc("/register/2", s.register2) - - fmt.Printf("Listening at %v\n", *addr) - return http.ListenAndServeTLS(*addr, *certFile, *keyFile, nil /* handler */) -} - -type server struct { - ctx context.Context - teleport *apiclient.Client - - mu sync.Mutex - inFlightAddStream *proto.AuthService_AddMFADeviceClient -} - -type login1Request struct { - User string `json:"user"` - Pass string `json:"pass"` -} - -func (s *server) login1(w http.ResponseWriter, r *http.Request) { - var req login1Request - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Get login challenge. - body, err := json.Marshal(&libclient.MFAChallengeRequest{ - User: req.User, - Pass: req.Pass, - }) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - resp, err := http.Post("https://"+*webAddr+"/webapi/mfa/login/begin", "application/json", bytes.NewReader(body)) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - log.Printf("INFO /mfa/login/begin: %#v", resp) - http.Error(w, "Unexpected status from /mfa/login/begin", http.StatusBadRequest) - } - var challenge libclient.MFAAuthenticateChallenge - if err := json.NewDecoder(resp.Body).Decode(&challenge); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if challenge.WebauthnChallenge == nil { - http.Error(w, "nil credential assertion", http.StatusBadRequest) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(challenge.WebauthnChallenge); err != nil { - log.Println(err) - } -} - -type login2Request struct { - wanlib.CredentialAssertionResponse - User string `json:"user"` -} - -func (s *server) login2(w http.ResponseWriter, r *http.Request) { - // Request body is a wanlib.CredentialAssertionResponse. - // User passed as a query param to make things simpler. - - var req login2Request - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - body, err := json.Marshal(&libclient.AuthenticateWebUserRequest{ - User: req.User, - WebauthnAssertionResponse: &req.CredentialAssertionResponse, - }) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Solve login challenge - resp, err := http.Post("https://"+*webAddr+"/webapi/mfa/login/finishsession", "application/json", bytes.NewReader(body)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - io.Copy(io.Discard, resp.Body) - resp.Body.Close() - if resp.StatusCode != http.StatusOK { - log.Printf("INFO /mfa/login/finishsession: %#v", resp) - http.Error(w, "Unexpected status from /mfa/login/finishsession", http.StatusBadRequest) - } - - // Login OK. - w.WriteHeader(http.StatusOK) -} - -type register1Request struct { - User string `json:"user"` - Pass string `json:"pass"` - DevName string `json:"dev_name"` - TOTPCode string `json:"totp_code"` -} - -func (s *server) register1(w http.ResponseWriter, r *http.Request) { - s.mu.Lock() // Hold lock for the entire time, we don't care. - defer s.mu.Unlock() - - var req register1Request - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Init stream with device name and type. - ctx := s.ctx - stream, err := s.teleport.AddMFADevice(ctx) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if err := stream.Send(&proto.AddMFADeviceRequest{ - Request: &proto.AddMFADeviceRequest_Init{ - Init: &proto.AddMFADeviceRequestInit{ - DeviceName: req.DevName, - DeviceType: proto.DeviceType_DEVICE_TYPE_WEBAUTHN, - }, - }, - }); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Solve authn challenge. - resp, err := stream.Recv() - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - var authResp *proto.MFAAuthenticateResponse - challenge := resp.GetExistingMFAChallenge() - switch { - case challenge.GetTOTP() == nil && challenge.GetWebauthnChallenge() == nil: // aka empty challenge - authResp = &proto.MFAAuthenticateResponse{} - case challenge.GetTOTP() != nil: - authResp = &proto.MFAAuthenticateResponse{ - Response: &proto.MFAAuthenticateResponse_TOTP{ - TOTP: &proto.TOTPResponse{ - Code: req.TOTPCode, - }, - }, - } - default: - http.Error(w, "TOTP challenge not present", http.StatusBadRequest) - return - } - if err := stream.Send(&proto.AddMFADeviceRequest{ - Request: &proto.AddMFADeviceRequest_ExistingMFAResponse{ - ExistingMFAResponse: authResp, - }, - }); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - resp, err = stream.Recv() - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - ccProto := resp.GetNewMFARegisterChallenge().GetWebauthn() - if ccProto == nil { - http.Error(w, "nil credential creation", http.StatusBadRequest) - return - } - - cc := wanlib.CredentialCreationFromProto(ccProto) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(cc); err != nil { - log.Println(err) - } - - s.inFlightAddStream = &stream // Save stream for 2nd step -} - -func (s *server) register2(w http.ResponseWriter, r *http.Request) { - // Request body is a wanlib.CredentialCreationResponse. - - s.mu.Lock() // Hold lock for the entire time, we don't care. - defer s.mu.Unlock() - - if s.inFlightAddStream == nil { - http.Error(w, "In-flight add stream is nil", http.StatusBadRequest) - return - } - stream := *s.inFlightAddStream - - var ccr wanlib.CredentialCreationResponse - if err := json.NewDecoder(r.Body).Decode(&ccr); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Send register response. - if err := stream.Send(&proto.AddMFADeviceRequest{ - Request: &proto.AddMFADeviceRequest_NewMFARegisterResponse{ - NewMFARegisterResponse: &proto.MFARegisterResponse{ - Response: &proto.MFARegisterResponse_Webauthn{ - Webauthn: wanlib.CredentialCreationResponseToProto(&ccr), - }, - }, - }, - }); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - resp, err := stream.Recv() - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if resp.GetAck() == nil { - log.Printf("WARN Expected Ack, got %#v", resp) - } - - s.inFlightAddStream = nil - w.WriteHeader(http.StatusOK) -}