Skip to content

Commit

Permalink
Prepare for optional ed25519 x509certs (#242)
Browse files Browse the repository at this point in the history
* initial changes

* cleanup + fnew sshca endpoint
  • Loading branch information
cviecco authored Aug 1, 2024
1 parent fb0dbe7 commit 71c53e6
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 28 deletions.
3 changes: 2 additions & 1 deletion cmd/keymasterd/adminHandlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
56 changes: 48 additions & 8 deletions cmd/keymasterd/app.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bytes"
"crypto"
"crypto/rand"
"crypto/tls"
Expand Down Expand Up @@ -30,6 +31,7 @@ import (
texttemplate "text/template"
"time"

"golang.org/x/crypto/ssh"
"golang.org/x/net/context"
"golang.org/x/time/rate"

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1033,18 +1035,54 @@ 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)
setSecurityHeaders(w)
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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions cmd/keymasterd/awsRole.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
17 changes: 15 additions & 2 deletions cmd/keymasterd/certgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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, "")
Expand Down
41 changes: 28 additions & 13 deletions cmd/keymasterd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bufio"
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions cmd/keymasterd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 71c53e6

Please sign in to comment.