From 71c53e6e5d04779ef77e4924cf3b6909e78fa0e7 Mon Sep 17 00:00:00 2001 From: cviecco Date: Thu, 1 Aug 2024 09:27:51 -0700 Subject: [PATCH] Prepare for optional ed25519 x509certs (#242) * initial changes * cleanup + fnew sshca endpoint --- cmd/keymasterd/adminHandlers_test.go | 3 +- cmd/keymasterd/app.go | 56 ++++++++++++++++++++++++---- cmd/keymasterd/awsRole.go | 8 +++- cmd/keymasterd/certgen.go | 17 ++++++++- cmd/keymasterd/config.go | 41 +++++++++++++------- cmd/keymasterd/main_test.go | 5 ++- 6 files changed, 102 insertions(+), 28 deletions(-) diff --git a/cmd/keymasterd/adminHandlers_test.go b/cmd/keymasterd/adminHandlers_test.go index de502ab2..e766e0de 100644 --- a/cmd/keymasterd/adminHandlers_test.go +++ b/cmd/keymasterd/adminHandlers_test.go @@ -46,10 +46,11 @@ func testCreateRuntimeStateWithBothCAs(t *testing.T) ( return nil, "", err } state.Signer = signer - state.caCertDer, err = generateCADer(state, state.Signer) + caCertDer, err := generateCADer(state, state.Signer) if err != nil { return nil, "", err } + state.caCertDer = append(state.caCertDer, caCertDer) state.signerPublicKeyToKeymasterKeys() state.totpLocalRateLimit = make(map[string]totpRateLimitInfo) if err := initDB(state); err != nil { diff --git a/cmd/keymasterd/app.go b/cmd/keymasterd/app.go index 8a57e045..8d883c9f 100644 --- a/cmd/keymasterd/app.go +++ b/cmd/keymasterd/app.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "crypto" "crypto/rand" "crypto/tls" @@ -30,6 +31,7 @@ import ( texttemplate "text/template" "time" + "golang.org/x/crypto/ssh" "golang.org/x/net/context" "golang.org/x/time/rate" @@ -193,7 +195,7 @@ type RuntimeState struct { ClientCAPool *x509.CertPool HostIdentity string KerberosRealm *string - caCertDer []byte + caCertDer [][]byte certManager *certmanager.CertificateManager vipPushCookie map[string]pushPollTransaction localAuthData map[string]localUserData @@ -1033,6 +1035,7 @@ func (state *RuntimeState) publicPathHandler(w http.ResponseWriter, r *http.Requ target := r.URL.Path[len(publicPath):] + caPubMaxSeconds := 30 switch target { case "loginForm": //fmt.Fprintf(w, "%s", loginFormText) @@ -1040,11 +1043,46 @@ func (state *RuntimeState) publicPathHandler(w http.ResponseWriter, r *http.Requ state.writeHTMLLoginPage(w, r, 200, "", profilePath, "") return case "x509ca": - pemCert := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: state.caCertDer})) + var outCABuf bytes.Buffer + for _, derCert := range state.caCertDer { + err := pem.Encode(&outCABuf, &pem.Block{Type: "CERTIFICATE", Bytes: derCert}) + if err != nil { + state.writeFailureResponse(w, r, http.StatusInternalServerError, "") + logger.Printf("Error computing pemCA") + return + } + } + w.Header().Add("Cache-Control", + fmt.Sprintf("max-age=%d, public, must-revalidate, proxy-revalidate", + caPubMaxSeconds)) + w.Header().Set("Content-Disposition", `attachment; filename=keymasterx509CA.pem"`) + w.WriteHeader(200) + outCABuf.WriteTo(w) + case "sshca": + var outCABuf bytes.Buffer + for _, pub := range state.KeymasterPublicKeys { + sshPub, err := ssh.NewPublicKey(pub) + if err != nil { + state.writeFailureResponse(w, r, http.StatusInternalServerError, "") + logger.Printf("Error computing sshCA") + return + } + pubBytes := ssh.MarshalAuthorizedKey(sshPub) + _, err = fmt.Fprintf(&outCABuf, "%s", pubBytes) + if err != nil { + state.writeFailureResponse(w, r, http.StatusInternalServerError, "") + logger.Printf("Error computing sshCA") + return + } - w.Header().Set("Content-Disposition", `attachment; filename="id_rsa-cert.pub"`) + } + w.Header().Add("Cache-Control", + fmt.Sprintf("max-age=%d, public, must-revalidate, proxy-revalidate", + caPubMaxSeconds)) + w.Header().Set("Content-Disposition", `attachment; filename=keymastersshCCA.pub"`) w.WriteHeader(200) - fmt.Fprintf(w, "%s", pemCert) + outCABuf.WriteTo(w) + default: state.writeFailureResponse(w, r, http.StatusNotFound, "") return @@ -1959,11 +1997,13 @@ func main() { if runtimeState.ClientCAPool == nil { runtimeState.ClientCAPool = x509.NewCertPool() } - myCert, err := x509.ParseCertificate(runtimeState.caCertDer) - if err != nil { - panic(err) + for _, derCert := range runtimeState.caCertDer { + myCert, err := x509.ParseCertificate(derCert) + if err != nil { + panic(err) + } + runtimeState.ClientCAPool.AddCert(myCert) } - runtimeState.ClientCAPool.AddCert(myCert) // Safari in MacOS 10.12.x required a cert to be presented by the user even // when optional. // Our usage shows this is less than 1% of users so we are now mandating diff --git a/cmd/keymasterd/awsRole.go b/cmd/keymasterd/awsRole.go index 1066cb22..79ae6d53 100644 --- a/cmd/keymasterd/awsRole.go +++ b/cmd/keymasterd/awsRole.go @@ -178,12 +178,16 @@ func (state *RuntimeState) generateRoleCert(template *x509.Certificate, if !strong { return nil, fmt.Errorf("key too weak") } - caCert, err := x509.ParseCertificate(state.caCertDer) + signer, caCertDer, err := state.getSignerX509CAForPublic(publicKey) + if err != nil { + return nil, err + } + caCert, err := x509.ParseCertificate(caCertDer) if err != nil { return nil, err } certDER, err := x509.CreateCertificate(rand.Reader, template, caCert, - publicKey, state.Signer) + publicKey, signer) if err != nil { return nil, err } diff --git a/cmd/keymasterd/certgen.go b/cmd/keymasterd/certgen.go index 98886781..f7fa5932 100644 --- a/cmd/keymasterd/certgen.go +++ b/cmd/keymasterd/certgen.go @@ -232,6 +232,13 @@ func (state *RuntimeState) expandSSHExtensions(username string) (map[string]stri return userExtensions, nil } +func (state *RuntimeState) getSignerX509CAForPublic(pub interface{}) (crypto.Signer, []byte, error) { + //v0... always returnt the primary signer + baseIndex := len(state.caCertDer) - 1 + return state.Signer, state.caCertDer[baseIndex], nil + +} + func (state *RuntimeState) postAuthSSHCertHandler( w http.ResponseWriter, r *http.Request, targetUser string, duration time.Duration) { @@ -448,14 +455,20 @@ func (state *RuntimeState) postAuthX509CertHandler( logger.Printf("Invalid File, Check Key strength/key type") return } - caCert, err := x509.ParseCertificate(state.caCertDer) + signer, caCertDer, err := state.getSignerX509CAForPublic(userPub) + if err != nil { + state.writeFailureResponse(w, r, http.StatusInternalServerError, "") + logger.Printf("Error Finding Cert for public key: %s\n data", err) + return + } + caCert, err := x509.ParseCertificate(caCertDer) if err != nil { state.writeFailureResponse(w, r, http.StatusInternalServerError, "") logger.Printf("Cannot parse CA Der: %s\n data", err) return } derCert, err := certgen.GenUserX509Cert(targetUser, userPub, caCert, - keySigner, state.KerberosRealm, duration, groups, organizations, + signer, state.KerberosRealm, duration, groups, organizations, serviceMethods, logger) if err != nil { state.writeFailureResponse(w, r, http.StatusInternalServerError, "") diff --git a/cmd/keymasterd/config.go b/cmd/keymasterd/config.go index 6252ca3b..bd456539 100644 --- a/cmd/keymasterd/config.go +++ b/cmd/keymasterd/config.go @@ -3,6 +3,7 @@ package main import ( "bufio" "bytes" + "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rand" @@ -261,23 +262,30 @@ func (state *RuntimeState) loadTemplates() (err error) { func (state *RuntimeState) signerPublicKeyToKeymasterKeys() error { state.logger.Debugf(3, "number of pk known=%d", len(state.KeymasterPublicKeys)) - signerPKFingerprint, err := getKeyFingerprint(state.Signer.Public()) - if err != nil { - return err + var localSigners []crypto.Signer + if state.Ed25519Signer != nil { + localSigners = append(localSigners, state.Ed25519Signer) } - found := false - for _, key := range state.KeymasterPublicKeys { - fp, err := getKeyFingerprint(key) + localSigners = append(localSigners, state.Signer) + for _, signer := range localSigners { + signerPKFingerprint, err := getKeyFingerprint(signer.Public()) if err != nil { return err } - if signerPKFingerprint == fp { - found = true + found := false + for _, key := range state.KeymasterPublicKeys { + fp, err := getKeyFingerprint(key) + if err != nil { + return err + } + if signerPKFingerprint == fp { + found = true + } + } + if !found { + state.KeymasterPublicKeys = append(state.KeymasterPublicKeys, + signer.Public()) } - } - if !found { - state.KeymasterPublicKeys = append(state.KeymasterPublicKeys, - state.Signer.Public()) } state.logger.Debugf(3, "number of pk known=%d", len(state.KeymasterPublicKeys)) @@ -311,6 +319,12 @@ func (state *RuntimeState) loadSignersFromPemData(signerPem, ed25519Pem []byte) default: return fmt.Errorf("Ed2559 configred file is not really an Ed25519 key. Type is %T!\n", v) } + ed25519CaCertDer, err := generateCADer(state, edSigner) + if err != nil { + state.logger.Printf("Cannot generate Ed25519 CA DER") + return err + } + state.caCertDer = append(state.caCertDer, ed25519CaCertDer) state.Ed25519Signer = edSigner } signer, err := getSignerFromPEMBytes(signerPem) @@ -326,11 +340,12 @@ func (state *RuntimeState) loadSignersFromPemData(signerPem, ed25519Pem []byte) default: return fmt.Errorf("Signer file is a valid Signer key. Type is %T!\n", v) } - state.caCertDer, err = generateCADer(state, signer) + caCertDer, err := generateCADer(state, signer) if err != nil { state.logger.Printf("Cannot generate CA DER") return err } + state.caCertDer = append(state.caCertDer, caCertDer) // Assignment of signer MUST be the last operation after // all error checks state.Signer = signer diff --git a/cmd/keymasterd/main_test.go b/cmd/keymasterd/main_test.go index cc590e0a..44652bd4 100644 --- a/cmd/keymasterd/main_test.go +++ b/cmd/keymasterd/main_test.go @@ -185,10 +185,11 @@ func setupValidRuntimeStateSigner(t *testing.T) ( state.signerPublicKeyToKeymasterKeys() //for x509 - state.caCertDer, err = generateCADer(&state, signer) + caCertDer, err := generateCADer(&state, signer) if err != nil { return nil, nil, err } + state.caCertDer = append(state.caCertDer, caCertDer) passwdFile, err := setupPasswdFile() if err != nil { @@ -512,7 +513,7 @@ func TestPublicHandleLoginForm(t *testing.T) { } state.Signer = signer state.signerPublicKeyToKeymasterKeys() - urlList := []string{"/public/loginForm", "/public/x509ca"} + urlList := []string{"/public/loginForm", "/public/x509ca", "/public/sshca"} err = state.loadTemplates() if err != nil { t.Fatal(err)