From a40a44c831bff84d1aa9a4599e867ea4f3cb0061 Mon Sep 17 00:00:00 2001 From: cviecco Date: Wed, 12 Jun 2024 07:34:45 -0700 Subject: [PATCH] webauthn tests p1 (#230) * first test round inital part of webauthn registration. Needed as part of webauthn migration to new lib * addressing comments --- cmd/keymasterd/2fa_webauthn.go | 17 ++--- cmd/keymasterd/2fa_webauthn_test.go | 97 +++++++++++++++++++++++++++++ cmd/keymasterd/userProfile.go | 4 +- 3 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 cmd/keymasterd/2fa_webauthn_test.go diff --git a/cmd/keymasterd/2fa_webauthn.go b/cmd/keymasterd/2fa_webauthn.go index 7b010432..5582cac7 100644 --- a/cmd/keymasterd/2fa_webauthn.go +++ b/cmd/keymasterd/2fa_webauthn.go @@ -34,7 +34,7 @@ const webAutnRegististerRequestPath = "/webauthn/RegisterRequest/" // RegisterRequest? func (state *RuntimeState) webauthnBeginRegistration(w http.ResponseWriter, r *http.Request) { - logger.Debugf(3, "top of webauthnBeginRegistration") + state.logger.Debugf(3, "top of webauthnBeginRegistration") if state.sendFailureToClientIfLocked(w, r) { return } @@ -47,16 +47,18 @@ func (state *RuntimeState) webauthnBeginRegistration(w http.ResponseWriter, r *h if len(pieces) >= 4 { assumedUser = pieces[3] } else { - logger.Debugf(1, "webauthnBeginRegistration: bad number of pieces") + state.logger.Debugf(1, "webauthnBeginRegistration: bad number of pieces") http.Error(w, "error", http.StatusBadRequest) return } + state.logger.Debugf(3, "top of webauthnBeginRegistration: after piece processing ") // TODO(camilo_viecco1): reorder checks so that simple checks are done before checking user creds authData, err := state.checkAuth(w, r, state.getRequiredWebUIAuthLevel()) if err != nil { - logger.Debugf(1, "%v", err) + state.logger.Debugf(1, "webauthnBeginRegistration: checkAuth Failed %v", err) return } + state.logger.Debugf(3, "top of webauthnBeginRegistration: after authentication ") w.(*instrumentedwriter.LoggingWriter).SetUsername(authData.Username) // Check that they can change other users @@ -68,20 +70,21 @@ func (state *RuntimeState) webauthnBeginRegistration(w http.ResponseWriter, r *h profile, _, fromCache, err := state.LoadUserProfile(assumedUser) if err != nil { - logger.Printf("webauthnBeginRegistration: loading profile error: %v", err) + state.logger.Printf("webauthnBeginRegistration: loading profile error: %v", err) http.Error(w, "error", http.StatusInternalServerError) return } if fromCache { - logger.Printf("DB is being cached and requesting registration aborting it") + state.logger.Printf("DB is being cached and requesting registration aborting it") http.Error(w, "db backend is offline for writes", http.StatusServiceUnavailable) return } + state.logger.Debugf(3, "top of webauthnBeginRegistration: after profile loadingn ") profile.FixupCredential(assumedUser, assumedUser) - logger.Debugf(2, "webauthnBeginRegistration profile=%+v", profile) - logger.Debugf(2, "webauthnBeginRegistration: About to begin BeginRegistration") + state.logger.Debugf(2, "webauthnBeginRegistration profile=%+v", profile) + state.logger.Debugf(2, "webauthnBeginRegistration: About to begin BeginRegistration") options, sessionData, err := state.webAuthn.BeginRegistration(profile) if err != nil { state.logger.Printf("webauthnBeginRegistration: begin login failed %s", err) diff --git a/cmd/keymasterd/2fa_webauthn_test.go b/cmd/keymasterd/2fa_webauthn_test.go new file mode 100644 index 00000000..8db7dffc --- /dev/null +++ b/cmd/keymasterd/2fa_webauthn_test.go @@ -0,0 +1,97 @@ +package main + +import ( + "bytes" + "io" + "net/http" + "os" + "testing" + + "github.com/Cloud-Foundations/keymaster/lib/webapi/v0/proto" + + "github.com/duo-labs/webauthn/webauthn" +) + +func TestWebAuthnRegistrationBegin(t *testing.T) { + + state, passwdFile, err := setupValidRuntimeStateSigner(t) + if err != nil { + t.Fatal(err) + } + defer os.Remove(passwdFile.Name()) // clean up + + state.Config.Base.AllowedAuthBackendsForWebUI = append(state.Config.Base.AllowedAuthBackendsForWebUI, proto.AuthTypeU2F) + + state.signerPublicKeyToKeymasterKeys() + + // cviecco -> probablt dont need tempdir + dir, err := os.MkdirTemp("", "example") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) // clean up + state.Config.Base.DataDirectory = dir + err = initDB(state) + if err != nil { + t.Fatal(err) + } + state.HostIdentity = "testHost" + // end of copy + logger = state.logger + + u2fAppID = "https://" + state.HostIdentity // this should include the port...but not needed for this test as we assume 443 + state.webAuthn, err = webauthn.New(&webauthn.Config{ + RPDisplayName: "Keymaster Server", // Display Name for your site + RPID: state.HostIdentity, // Generally the domain name for your site + RPOrigin: u2fAppID, // The origin URL for WebAuthn requests + // RPIcon: "https://duo.com/logo.png", // Optional icon URL for your site + }) + if err != nil { + t.Fatal(err) + } + + // end of setup + + req, err := http.NewRequest("GET", webAutnRegististerRequestPath+"username", nil) + if err != nil { + t.Fatal(err) + //return nil, err + } + cookieVal, err := state.setNewAuthCookie(nil, "username", AuthTypeU2F) + if err != nil { + t.Fatal(err) + } + authCookie := http.Cookie{Name: authCookieName, Value: cookieVal} + req.AddCookie(&authCookie) + + regData, err := checkRequestHandlerCode(req, state.webauthnBeginRegistration, http.StatusOK) + if err != nil { + t.Fatal(err) + } + /* + resultAccessToken := newTOTPPageTemplateData{} + */ + body := regData.Result().Body + var b bytes.Buffer + _, err = io.Copy(&b, body) + if err != nil { + t.Fatal(err) + } + t.Logf("regdata=%s\n", b.String()) + + /* + err = json.NewDecoder(body).Decode(&resultAccessToken) + if err != nil { + t.Fatal(err) + } + t.Logf("totpDataToken='%+v'", resultAccessToken) + */ + + /* + Example post for finalization: + { + "{\"id\":\"_N2M7t9Qe2rwS4asNZ15I4Thd-nkXow6_lyDT6CURM3gD1sAq0FyMnf8NDOARMWMjjNgPfeHpPWP0Q8nkx-v7pNRuR0IwRHkvZeZxaV3Ql3HFigByVOhuB3OCq2em8Ve\",\"rawId\":\"_N2M7t9Qe2rwS4asNZ15I4Thd-nkXow6_lyDT6CURM3gD1sAq0FyMnf8NDOARMWMjjNgPfeHpPWP0Q8nkx-v7pNRuR0IwRHkvZeZxaV3Ql3HFigByVOhuB3OCq2em8Ve\",\"type\":\"public-key\",\"response\":{\"attestationObject\":\"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjkSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NBAAADlwAAAAAAAAAAAAAAAAAAAAAAYPzdjO7fUHtq8EuGrDWdeSOE4Xfp5F6MOv5cg0-glETN4A9bAKtBcjJ3_DQzgETFjI4zYD33h6T1j9EPJ5Mfr-6TUbkdCMER5L2XmcWld0JdxxYoAclTobgdzgqtnpvFXqUBAgMmIAEhWCBwm_S46LuncSKubWLGS7236xBQyY-Ptg0dTKpOmddRMCJYIG02ZJischNpyUqMXRdiJfBW2kDmG3TROzKzHHBHmLlp\",\"clientDataJSON\":\"eyJjaGFsbGVuZ2UiOiJlTW1Ca0gxQ05KZzFsbGRQb3ZXQUN6R0pMZUpYRHZndmViUXIycDRxdWNVIiwib3JpZ2luIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6MzM0NDMiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0\"}}": "" + } + */ + +} diff --git a/cmd/keymasterd/userProfile.go b/cmd/keymasterd/userProfile.go index 25b8c789..cfbedd86 100644 --- a/cmd/keymasterd/userProfile.go +++ b/cmd/keymasterd/userProfile.go @@ -33,6 +33,7 @@ func (u *userProfile) WebAuthnIcon() string { // This function is needed to create a unified view of all webauthn credentials func (u *userProfile) WebAuthnCredentials() []webauthn.Credential { + logger.Debugf(3, "top of profile.WebAuthnCredentials ") var rvalue []webauthn.Credential for _, authData := range u.WebauthnData { if !authData.Enabled { @@ -81,6 +82,7 @@ func (u *userProfile) WebAuthnCredentials() []webauthn.Credential { // This function will eventualy also do migration of credential data if needed func (u *userProfile) FixupCredential(username string, displayname string) { + logger.Debugf(3, "top of profile.FixupCredential ") if u.DisplayName == "" { u.DisplayName = displayname } @@ -98,7 +100,7 @@ func (u *userProfile) FixupCredential(username string, displayname string) { } } -/// next are not actually from there... but make it simpler +// next are not actually from there... but make it simpler func (u *userProfile) AddWebAuthnCredential(cred webauthn.Credential) error { index := time.Now().Unix() authData := webauthAuthData{