Working prototype using RO as a remote Signer for SSH authentication

This commit is contained in:
Andrew Buss
2015-11-30 06:03:08 -08:00
committed by Kyle Isom
parent 5b9a4b2fcb
commit a45f80f613
3 changed files with 241 additions and 5 deletions

View File

@@ -254,6 +254,22 @@ func (c *RemoteServer) Decrypt(req core.DecryptRequest) (*core.ResponseData, err
}
// Decrypt issues an decrypt request to the remote server
func (c *RemoteServer) DecryptSign(req core.DecryptSignRequest) (*core.ResponseData, error) {
reqBytes, err := json.Marshal(req)
if err != nil {
return nil, err
}
respBytes, err := c.doAction("decrypt-sign", reqBytes)
if err != nil {
return nil, err
}
return unmarshalResponseData(respBytes)
}
// DecryptIntoData issues an decrypt request to the remote server and extract
// the decrypted data from the response
func (c *RemoteServer) DecryptIntoData(req core.DecryptRequest) ([]byte, error) {

View File

@@ -6,25 +6,32 @@ import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"os/signal"
"path"
"strings"
"syscall"
"time"
"github.com/cloudflare/redoctober/client"
"github.com/cloudflare/redoctober/cmd/ro/gopass"
"github.com/cloudflare/redoctober/cmd/ro/roagent"
"github.com/cloudflare/redoctober/core"
"github.com/cloudflare/redoctober/cryptor"
"github.com/cloudflare/redoctober/msp"
"github.com/cloudflare/redoctober/order"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
var action, user, pswd, userEnv, pswdEnv, server, caPath string
var action, user, pswd, userEnv, pswdEnv, server, caPath, pubKeyPath string
var owners, lefters, righters, inPath, labels, outPath, outEnv string
var owners, lefters, righters, inPath, labels, usages, outPath, outEnv string
var uses, minUsers int
@@ -46,6 +53,8 @@ var commandSet = map[string]command{
"delegate": command{Run: runDelegate, Desc: "do decryption delegation"},
"encrypt": command{Run: runEncrypt, Desc: "encrypt a file"},
"decrypt": command{Run: runDecrypt, Desc: "decrypt a file"},
"ssh": command{Run: runSSH, Desc: "a wrapper for SSH using an RO-encrypted private key"},
"ssh-agent": command{Run: runSSHAgent, Desc: "act as an SSH agent"},
"re-encrypt": command{Run: runReEncrypt, Desc: "re-encrypt a file"},
"order": command{Run: runOrder, Desc: "place an order for delegations"},
"owners": command{Run: runOwner, Desc: "show owners list"},
@@ -65,6 +74,7 @@ func registerFlags() {
flag.StringVar(&lefters, "left", "", "comma separated left owners")
flag.StringVar(&righters, "right", "", "comma separated right owners")
flag.StringVar(&labels, "labels", "", "comma separated labels")
flag.StringVar(&usages, "usages", "", "comma separated usages")
flag.StringVar(&inPath, "in", "", "input data file")
flag.StringVar(&outPath, "out", "", "output data file")
flag.StringVar(&outEnv, "outenv", "", "env variable for output data")
@@ -75,6 +85,7 @@ func registerFlags() {
flag.StringVar(&userEnv, "userenv", "RO_USER", "env variable for user name")
flag.StringVar(&pswdEnv, "pswdenv", "RO_PASS", "env variable for user password")
flag.DurationVar(&pollInterval, "poll-interval", time.Second, "interval for polling an outstanding order (set 0 to disable polling)")
flag.StringVar(&pubKeyPath, "pubkey", "id_rsa.pub", "path to SSH public key")
}
func readLine(prompt string) (line string, err error) {
@@ -201,6 +212,7 @@ func runEncrypt() {
Password: pswd,
Minimum: minUsers,
Owners: processCSL(owners),
Usages: processCSL(usages),
LeftOwners: processCSL(lefters),
RightOwners: processCSL(righters),
Labels: processCSL(labels),
@@ -386,6 +398,86 @@ func runResetPersisted() {
fmt.Println(resp)
}
func runSSHAgent() {
inBytes, err := ioutil.ReadFile(inPath)
processError(err)
// base64 decode the input
encBytes, err := base64.StdEncoding.DecodeString(string(inBytes))
if err != nil {
log.Println("failed to base64 decode the data, proceeding with raw data")
encBytes = inBytes
}
inBytes, err = ioutil.ReadFile(pubKeyPath)
processError(err)
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(inBytes)
if err != nil {
log.Fatal("failed to parse SSH public key", err)
}
roagent := roagent.NewROAgent(roServer, pubKey, encBytes, user, pswd)
authSockPath := os.Getenv("SSH_AUTH_SOCK")
if authSockPath == "" {
log.Fatal("SSH_AUTH_SOCK not set")
}
listener, err := net.ListenUnix("unix", &net.UnixAddr{Name: authSockPath, Net: "unix"})
if err != nil {
log.Fatal("error listening on $SSH_AUTH_SOCK", err)
}
defer os.Remove(authSockPath)
conn, err := listener.AcceptUnix()
if err != nil {
log.Fatal("error accepting socket connection", err)
}
err = agent.ServeAgent(roagent, conn)
if err != nil && err != io.EOF {
log.Fatal("error serving socket protocol", err)
}
}
func runSSH() {
// First pick a path for our socket
// TempDir will ensure that the directory is created with the correct permissions
dir, err := ioutil.TempDir("", "ro_ssh_")
if err != nil {
log.Fatal("error getting temporary directory for SSH auth socket ", err)
}
defer os.RemoveAll(dir)
os.Setenv("SSH_AUTH_SOCK", path.Join(dir, "roagent.sock"))
go runSSHAgent()
var sshPath string
sshPath, err = exec.LookPath("ssh")
if err != nil {
log.Fatal("error finding path to ssh binary ", err)
}
var p *os.Process
p, err = os.StartProcess(sshPath, flag.Args(),
&os.ProcAttr{
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
},
)
if err != nil {
log.Fatal("error starting ssh ", err)
}
_, err = p.Wait()
if err != nil {
log.Fatal("error waiting on ssh ", err)
}
}
func main() {
flag.Usage = func() {
fmt.Println("Usage: ro [options] subcommand")
@@ -401,13 +493,13 @@ func main() {
registerFlags()
flag.Parse()
if flag.NArg() != 1 {
action := flag.Arg(0)
if flag.NArg() != 1 && action != "ssh" {
flag.Usage()
os.Exit(1)
}
action := flag.Arg(0)
cmd, found := commandSet[action]
if !found {
fmt.Println("Unsupported subcommand:", action)

128
cmd/ro/roagent/roagent.go Normal file
View File

@@ -0,0 +1,128 @@
package roagent
import (
"bytes"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"github.com/cloudflare/redoctober/client"
"github.com/cloudflare/redoctober/core"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
type ROSigner struct {
server *client.RemoteServer
pub ssh.PublicKey
encryptedKey []byte
user string
pswd string
}
func (signer ROSigner) PublicKey() ssh.PublicKey {
return signer.pub
}
func (signer ROSigner) Sign(rand io.Reader, msg []byte) (signature *ssh.Signature, err error) {
req := core.DecryptSignRequest{
Name: signer.user,
Password: signer.pswd,
Data: signer.encryptedKey,
TBSData: msg,
}
resp, err := signer.server.DecryptSign(req)
if err != nil {
return nil, err
}
if resp.Status != "ok" {
log.Fatal("response status error:", resp.Status)
return nil, errors.New("response status error")
}
fmt.Println("Response Status:", resp.Status)
var respMsg core.DecryptSignWithDelegates
err = json.Unmarshal(resp.Response, &respMsg)
if err != nil {
return nil, err
}
var respSignature ssh.Signature
err = json.Unmarshal(resp.Response, &respSignature)
if err != nil {
return nil, err
}
return &respSignature, nil
}
type ROAgent struct {
signer ROSigner
}
func NewROAgent(server *client.RemoteServer, pubKey ssh.PublicKey, encryptedPrivKey []byte, user, pswd string) agent.Agent {
return &ROAgent{
ROSigner{
server,
pubKey,
encryptedPrivKey,
user,
pswd,
},
}
}
func (r *ROAgent) RemoveAll() error {
return nil
}
// Remove removes all identities with the given public key.
func (r *ROAgent) Remove(key ssh.PublicKey) error {
return nil
}
// Lock locks the agent. Sign and Remove will fail, and List will empty an empty list.
func (r *ROAgent) Lock(passphrase []byte) error {
return nil
}
// Unlock undoes the effect of Lock
func (r *ROAgent) Unlock(passphrase []byte) error {
return nil
}
// List returns the identities known to the agent.
func (r *ROAgent) List() ([]*agent.Key, error) {
return []*agent.Key{
{
Format: r.signer.PublicKey().Type(),
Blob: r.signer.PublicKey().Marshal(),
Comment: "",
},
}, nil
}
// Insert adds a private key to the ROAgent. If a certificate
// is given, that certificate is added as public key. Note that
// any constraints given are ignored.
func (r *ROAgent) Add(key agent.AddedKey) error {
return nil
}
// Sign returns a signature for the data.
func (r *ROAgent) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
wanted := key.Marshal()
if bytes.Equal(r.signer.PublicKey().Marshal(), wanted) {
return r.signer.Sign(rand.Reader, data)
}
return nil, errors.New("wrong key requested")
}
// Signers returns signers for all the known keys.
func (r *ROAgent) Signers() ([]ssh.Signer, error) {
return []ssh.Signer{r.signer}, nil
}