Skip to content

Commit

Permalink
Merge pull request #103 from cviecco/adding-ed25519-certsigner-handler
Browse files Browse the repository at this point in the history
Adding ed25519 certsigner handler
  • Loading branch information
rgooch committed Mar 26, 2021
2 parents 3f04ab3 + f3e481d commit d12ffc0
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 62 deletions.
118 changes: 56 additions & 62 deletions cmd/keymasterd/certgen.go
Expand Up @@ -101,28 +101,18 @@ func (state *RuntimeState) certGenHandler(w http.ResponseWriter, r *http.Request
}
logger.Debugf(3, "auth succedded for %s", authUser)

switch r.Method {
case "GET":
logger.Debugf(3, "Got client GET connection")
err = r.ParseForm()
if err != nil {
logger.Println(err)
state.writeFailureResponse(w, r, http.StatusBadRequest, "Error parsing form")
return
}
case "POST":
logger.Debugf(3, "Got client POST connection")
err = r.ParseMultipartForm(1e7)
if err != nil {
logger.Println(err)
state.writeFailureResponse(w, r, http.StatusBadRequest, "Error parsing form")
return
}
default:
if r.Method != "POST" {
state.writeFailureResponse(w, r, http.StatusMethodNotAllowed, "")
return
}

logger.Debugf(3, "Got client POST connection")
err = r.ParseMultipartForm(1e7)
if err != nil {
logger.Println(err)
state.writeFailureResponse(w, r, http.StatusBadRequest, "Error parsing form")
return
}
duration := time.Duration(24 * time.Hour)
if formDuration, ok := r.Form["duration"]; ok {
stringDuration := formDuration[0]
Expand All @@ -149,7 +139,7 @@ func (state *RuntimeState) certGenHandler(w http.ResponseWriter, r *http.Request

switch certType {
case "ssh":
state.postAuthSSHCertHandler(w, r, targetUser, keySigner, duration)
state.postAuthSSHCertHandler(w, r, targetUser, duration)
return
case "x509":
state.postAuthX509CertHandler(w, r, targetUser, keySigner, duration, false)
Expand Down Expand Up @@ -194,63 +184,67 @@ func getValidSSHPublicKey(userPubKey string) (ssh.PublicKey, error, error) {

func (state *RuntimeState) postAuthSSHCertHandler(
w http.ResponseWriter, r *http.Request, targetUser string,
keySigner crypto.Signer, duration time.Duration) {
signer, err := ssh.NewSignerFromSigner(keySigner)
if err != nil {
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
logger.Printf("Signer failed to load")
return
}
duration time.Duration) {

var certString string
var cert ssh.Certificate
switch r.Method {
case "GET":
certString, cert, err = certgen.GenSSHCertFileStringFromSSSDPublicKey(targetUser, signer, state.HostIdentity, duration)
if err != nil {
http.NotFound(w, r)
return
}
case "POST":
file, _, err := r.FormFile("pubkeyfile")
if err != nil {
logger.Println(err)
state.writeFailureResponse(w, r, http.StatusBadRequest, "Missing public key file")
return
}
defer file.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(file)
userPubKey := buf.String()
if r.Method != "POST" {
state.writeFailureResponse(w, r, http.StatusMethodNotAllowed, "")
return
}

_, userErr, err := getValidSSHPublicKey(userPubKey)
if err != nil {
logger.Println(err)
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
return
}
if userErr != nil {
logger.Printf("validating Error err: %s", userErr)
state.writeFailureResponse(w, r, http.StatusBadRequest, userErr.Error())
return
}
file, _, err := r.FormFile("pubkeyfile")
if err != nil {
logger.Println(err)
state.writeFailureResponse(w, r, http.StatusBadRequest, "Missing public key file")
return
}
defer file.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(file)
userPubKey := buf.String()

certString, cert, err = certgen.GenSSHCertFileString(targetUser, userPubKey, signer, state.HostIdentity, duration)
if err != nil {
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
logger.Printf("signUserPubkey Err")
sshUserPublicKey, userErr, err := getValidSSHPublicKey(userPubKey)
if err != nil {
logger.Println(err)
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
return
}
if userErr != nil {
logger.Printf("validating Error err: %s", userErr)
state.writeFailureResponse(w, r, http.StatusBadRequest, userErr.Error())
return
}
var cryptoSigner crypto.Signer
switch sshUserPublicKey.Type() {
case ssh.KeyAlgoED25519:
if state.Ed25519Signer == nil {
logger.Printf("requesting an Ed25519 cert, but no such ca defined")
state.writeFailureResponse(w, r, http.StatusUnprocessableEntity, "key type not allowed")
return
}

cryptoSigner = state.Ed25519Signer
default:
state.writeFailureResponse(w, r, http.StatusMethodNotAllowed, "")
cryptoSigner = state.Signer
}
signer, err := ssh.NewSignerFromSigner(cryptoSigner)
if err != nil {
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
logger.Printf("Signer failed to load")
return
}

certString, cert, err = certgen.GenSSHCertFileString(targetUser, userPubKey, signer, state.HostIdentity, duration)
if err != nil {
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
logger.Printf("signUserPubkey Err")
return
}

eventNotifier.PublishSSH(cert.Marshal())
metricLogCertDuration("ssh", "granted", float64(duration.Seconds()))

w.Header().Set("Content-Disposition", `attachment; filename="id_rsa-cert.pub"`)
w.Header().Set("Content-Disposition", "attachment; filename=\""+cert.Type()+"-cert.pub\"")
w.WriteHeader(200)
fmt.Fprintf(w, "%s", certString)
logger.Printf("Generated SSH Certifcate for %s. Serial:%d", targetUser, cert.Serial)
Expand Down
55 changes: 55 additions & 0 deletions cmd/keymasterd/certgen_test.go
Expand Up @@ -7,9 +7,11 @@ import (
"encoding/pem"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -39,6 +41,10 @@ Unc9jsYhX7DR3SV8vcFqduUmSH8vdc/zJEk76T2D+qe1aWqtr84QpxXBTrIKvSXD
igkmavdG2gu3SpbFzNxuVCrxQ88Kte0xYJTe7vY=
-----END CERTIFICATE-----`

// Ed25519 ones is copied from lib/certgent_test.go

const testEd25519PublicSSH = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDdNbfR67CJ0/iB5a5lQfZowi3VTrkDu7/rpMNKfHFPs cviecco@cviecco--MacBookPro15`

// we do not support dsa
const dsaPublicSSH = `ssh-dss AAAAB3NzaC1kc3MAAACBALd5BLQoXxeJHHMQpJzk283nbne65LQiFNPeH6VuNiNEGZI6N3KlQsijYK1oJX2R3oTDEhqEjQsdNa6s++eGbh2z6U3Xwu34odNCFJekKB3qZN7/gqWXzBcgFvir//edTCrN0evzbTedtjz3pB5KlB6OSsnntm/y6E/j45Q3ijGTAAAAFQCjyfpjPi4gmdskz5/cQZbGirVzmwAAAIEAr/LZ7rvsgdnQ1/x5NpJAGEy7QlxfjGfIUo2a57WpDvcjiQmpa9VRCF0ziF3XSv2iDfWZ19qPrbxAp4FIe+xXF3kR0XMmDQzeEZsBzl8pNe7ZxLBHKFX8ZL66VBngYJL2a4v84QoPCpXDJ1hWd7t+okqkFj/a+99cuWj65jk2zLkAAACAPbtpnU39ZioS+9HolaGqudhTfToNAVsVPwj7uiuqiR2OTywbR0WpDPs7zrYsJTzIviuuEXzTVLFWBDR6EwXQdg9Acz+uRRiiZ58e7kN7qv+hQ3FBT3W214A0EVkRJMozowYhzS4HM0x/LrxlNHHFpzMu/njkNfNYDJTK4I47BO0= cviecco@cviecco--MacBookPro15`

Expand Down Expand Up @@ -171,3 +177,52 @@ func TestGetValidSSHPublicKey(t *testing.T) {
}

}

func TestGenSSHEd25519(t *testing.T) {
state, passwdFile, err := setupValidRuntimeStateSigner(t)
if err != nil {
t.Fatal(err)
}
defer os.Remove(passwdFile.Name()) // clean up
err = state.loadSignersFromPemData([]byte(testSignerPrivateKey), []byte(pkcs8Ed25519PrivateKey))
if err != nil {
t.Fatal(err)
}
state.Config.Base.AllowedAuthBackendsForCerts = append(state.Config.Base.AllowedAuthBackendsForCerts, proto.AuthTypePassword)
state.Config.Base.AllowedAuthBackendsForWebUI = []string{"password"}

// Get request
req, err := createKeyBodyRequest("POST", "/certgen/username", testEd25519PublicSSH, "")
if err != nil {
t.Fatal(err)
}
cookieVal, err := state.setNewAuthCookie(nil, "username", AuthTypePassword)
if err != nil {
t.Fatal(err)
}
authCookie := http.Cookie{Name: authCookieName, Value: cookieVal}
req.AddCookie(&authCookie)

rr, err := checkRequestHandlerCode(req, state.certGenHandler, http.StatusOK)
if err != nil {
t.Fatal(err)
}

resp := rr.Result()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}

t.Logf("EdCert=%s", string(body))
if !strings.HasPrefix(string(body), "ssh-ed25519-cert-v01@openssh.com") {
t.Fatal("Return valued does not look like ed25519 cert")
}
// Now we disable the Ed signer and it should fail
state.Ed25519Signer = nil
_, err = checkRequestHandlerCode(req, state.certGenHandler, http.StatusUnprocessableEntity)
if err != nil {
t.Fatal(err)
}

}

0 comments on commit d12ffc0

Please sign in to comment.