Skip to content

Commit

Permalink
Merge pull request #9 from aakso/feat/challenge-response-ca-add
Browse files Browse the repository at this point in the history
feat(ca add): implement challenge-response based CA key add
  • Loading branch information
aakso authored Aug 11, 2021
2 parents 186e8e5 + a37e957 commit 7524ffd
Show file tree
Hide file tree
Showing 34 changed files with 2,423 additions and 238 deletions.
30 changes: 24 additions & 6 deletions cliclient/cmd/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import (
"fmt"
"strings"

"github.com/aakso/ssh-inscribe/pkg/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh"

"github.com/aakso/ssh-inscribe/pkg/client"
)

var principals []string
Expand Down Expand Up @@ -43,13 +43,13 @@ var ShowCaCmd = &cobra.Command{
}

var AddCaCmd = &cobra.Command{
Use: "add",
Use: "add [caKeyFile]",
Short: "Add CA private key from file",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("specify ca key file")
if len(args) > 0 {
ClientConfig.CAKeyFile = args[0]
}
ClientConfig.CAKeyFile = args[0]
c := &client.Client{
Config: ClientConfig,
}
Expand All @@ -58,6 +58,19 @@ var AddCaCmd = &cobra.Command{
},
}

var ResponseCmd = &cobra.Command{
Use: "response",
Short: "Send a response to a CA challenge in order to decrypt and add the CA key",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
c := &client.Client{
Config: ClientConfig,
}
defer c.Close()
return c.ChallengeResponse()
},
}

func init() {
RootCmd.AddCommand(CaCmd)
CaCmd.AddCommand(ShowCaCmd)
Expand All @@ -69,5 +82,10 @@ func init() {
"Format ca public key with allowed principals for use with authorized_keys",
)
_ = ShowCaCmd.RegisterFlagCompletionFunc("principals", noCompletion)

AddCaCmd.Flags().BoolVarP(&ClientConfig.CAChallenge, "challenge", "c", false,
"Use challenge mode to decrypt an encrypted private key")

CaCmd.AddCommand(AddCaCmd)
CaCmd.AddCommand(ResponseCmd)
}
119 changes: 116 additions & 3 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/rsa"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
Expand Down Expand Up @@ -94,9 +95,25 @@ func (c *Client) AddCA() error {
if err := c.authenticate(); err != nil {
return errors.Wrap(err, "could not add ca")
}
if c.Config.CAChallenge {
return c.caChallenge()
}
return c.addCAKey()
}

func (c *Client) ChallengeResponse() error {
if err := c.initREST(); err != nil {
return errors.Wrap(err, "could not add ca")
}
if err := c.checkVersion(); err != nil {
return errors.Wrap(err, "could not add ca")
}
if err := c.authenticate(); err != nil {
return errors.Wrap(err, "could not add ca")
}
return c.sendChallengeResponse()
}

func (c *Client) GetCA() (ssh.PublicKey, error) {
if err := c.initREST(); err != nil {
return nil, errors.Wrap(err, "could not get ca")
Expand Down Expand Up @@ -220,9 +237,25 @@ func (c *Client) Login() error {
func (c *Client) addCAKey() error {
log := Log.WithField("action", "addCAKey")
log.Debug("reading ca key file")
content, err := ioutil.ReadFile(c.Config.CAKeyFile)
if err != nil {
return errors.Wrap(err, "could not open ca key file")
var (
content []byte
err error
)
if c.Config.CAKeyFile == "" {
fmt.Println("Paste in the private key. End of file terminates (Ctrl+d).")
err = readMultipleLines("> ", func(l []byte) bool {
content = append(content, l...)
content = append(content, '\n')
return true
})
if err != nil {
return err
}
} else {
content, err = ioutil.ReadFile(c.Config.CAKeyFile)
if err != nil {
return errors.Wrap(err, "could not open ca key file")
}
}
key, err := c.parsePrivateKey(content, "CA private key")
if err != nil {
Expand Down Expand Up @@ -254,6 +287,66 @@ func (c *Client) addCAKey() error {
return nil
}

func (c *Client) caChallenge() error {
log := Log.WithField("action", "addCAKeyViaChallenge")
log.Debug("reading ca key file")
var (
content []byte
err error
)
if c.Config.CAKeyFile == "" {
fmt.Println("Paste in the private key. End of file terminates (Ctrl+d).")
err = readMultipleLines("> ", func(l []byte) bool {
content = append(content, l...)
content = append(content, '\n')
return true
})
if err != nil {
return err
}
} else {
content, err = ioutil.ReadFile(c.Config.CAKeyFile)
if err != nil {
return errors.Wrap(err, "could not open ca key file")
}
}
log.Debug("sending ca key to the server to initiate challenge")
log.Debug(string(content))
res, err := c.newReq().
SetHeader("X-Auth", fmt.Sprintf("Bearer %s", c.signerToken)).
SetBody(content).
Post(c.urlFor("ca") + "?init_challenge=true")
if err != nil {
return errors.Wrap(err, "could not send key")
}
if res.StatusCode() != http.StatusAccepted {
return errors.Errorf("could not send key, got code %d and message: %s", res.StatusCode(), res.Body())
}
log.Debug("initiated a CA challenge")
fmt.Printf("Challenge:\n%v\n", string(res.Body()))
return nil
}

func (c *Client) sendChallengeResponse() error {
log := Log.WithField("action", "sendChallengeResponse")
challenge := c.getCredential("ca-challenge", "signer", "challenge", "")
response := c.getCredential("ca-challenge-response", "signer", CredentialTypePassword, "")
log.Debug("sending challenge-response to the server")
res, err := c.newReq().
SetHeader("X-Auth", fmt.Sprintf("Bearer %s", c.signerToken)).
SetBody(challenge).
SetBasicAuth("", string(response)).
Post(c.urlFor("ca/response"))
if err != nil {
return errors.Wrap(err, "could not send challenge-response")
}
if res.StatusCode() != http.StatusOK {
return errors.Errorf("could not send challenge-response, got code %d and message: %s", res.StatusCode(), res.Body())
}
log.Debug("sent challenge-response to the server")
return nil
}

// Generate ad-hoc keypair
func (c *Client) generate() error {
var (
Expand Down Expand Up @@ -1106,3 +1199,23 @@ func shallowCertChecker(cert *ssh.Certificate, authority ssh.PublicKey) bool {
}
return true
}

func readMultipleLines(prompt string, cb func(l []byte) bool) error {
rl, err := readline.New(prompt)
if err != nil {
return err
}
for {
line, err := rl.ReadSlice()
if err != nil {
if err == io.EOF {
break
}
return err
}
if !cb(line) {
break
}
}
return nil
}
3 changes: 3 additions & 0 deletions pkg/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ type Config struct {
// Path to CA private key file. Only used when adding initial signing key to the server
CAKeyFile string

// Whether to request challenge for an encrypted CA private key
CAChallenge bool

// Generate ad-hoc keypair
GenerateKeypair bool

Expand Down
12 changes: 10 additions & 2 deletions pkg/keysigner/keysigner.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func (ks *KeySignerService) removeSmartcard(id string) error {
return errors.New("agent: failure")
}

func (ks *KeySignerService) AddSigningKey(pemKey []byte, comment string) error {
func (ks *KeySignerService) AddSigningKey(encodedKey []byte, passphrase []byte, comment string) error {
ks.Lock()
defer ks.Unlock()
if ks.selectedSigningKey != nil {
Expand All @@ -225,7 +225,15 @@ func (ks *KeySignerService) AddSigningKey(pemKey []byte, comment string) error {
if !ks.agentPing() {
return errors.New("cannot add signing key: agent is not responding")
}
key, err := ssh.ParseRawPrivateKey(pemKey)
var (
key interface{}
err error
)
if len(passphrase) > 0 {
key, err = ssh.ParseRawPrivateKeyWithPassphrase(encodedKey, passphrase)
} else {
key, err = ssh.ParseRawPrivateKey(encodedKey)
}
if err != nil {
return errors.Wrap(err, "cannot add signing key")
}
Expand Down
Loading

0 comments on commit 7524ffd

Please sign in to comment.