From 97a485712ee23305d0f5e2b24cea878a00c96038 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sun, 2 Jun 2024 23:18:37 +0200 Subject: [PATCH] Implement ssh import --- cli/agent/actions/ssh.go | 58 +++++++++++++++++++++++++++++++++++-- cli/agent/ssh/keys.go | 51 ++++++++++++++++++++++++++++++-- cli/cmd/ssh.go | 56 +++++++++++++++++++++++++++++++++++ cli/ipc/messages/sshkeys.go | 28 ++++++++++++++++++ 4 files changed, 189 insertions(+), 4 deletions(-) diff --git a/cli/agent/actions/ssh.go b/cli/agent/actions/ssh.go index 8dc9677..647be9e 100644 --- a/cli/agent/actions/ssh.go +++ b/cli/agent/actions/ssh.go @@ -16,12 +16,24 @@ import ( func handleAddSSH(msg messages.IPCMessage, cfg *config.Config, vault *vault.Vault, callingContext *sockets.CallingContext) (response messages.IPCMessage, err error) { req := messages.ParsePayload(msg).(messages.CreateSSHKeyRequest) - cipher, publicKey := ssh.NewSSHKeyCipher(req.Name, vault.Keyring) + cipher, publicKey, err := ssh.NewSSHKeyCipher(req.Name, vault.Keyring) + if err != nil { + response, err = messages.IPCMessageFromPayload(messages.ActionResponse{ + Success: false, + Message: err.Error(), + }) + return + } + _, err = messages.IPCMessageFromPayload(messages.ActionResponse{ Success: true, }) if err != nil { - panic(err) + response, err = messages.IPCMessageFromPayload(messages.ActionResponse{ + Success: false, + Message: err.Error(), + }) + return } token, err := cfg.GetToken() @@ -56,7 +68,49 @@ func handleListSSH(msg messages.IPCMessage, cfg *config.Config, vault *vault.Vau return } +func handleImportSSH(msg messages.IPCMessage, cfg *config.Config, vault *vault.Vault, callingContext *sockets.CallingContext) (response messages.IPCMessage, err error) { + req := messages.ParsePayload(msg).(messages.ImportSSHKeyRequest) + + cipher, _, err := ssh.SSHKeyCipherFromKey(req.Name, req.Key, vault.Keyring) + if err != nil { + response, err = messages.IPCMessageFromPayload(messages.ActionResponse{ + Success: false, + Message: err.Error(), + }) + return + } + + _, err = messages.IPCMessageFromPayload(messages.ActionResponse{ + Success: true, + }) + if err != nil { + response, err = messages.IPCMessageFromPayload(messages.ActionResponse{ + Success: false, + Message: err.Error(), + }) + return + } + + token, err := cfg.GetToken() + if err != nil { + actionsLog.Warn(err.Error()) + } + ctx := context.WithValue(context.TODO(), bitwarden.AuthToken{}, token.AccessToken) + postedCipher, err := bitwarden.PostCipher(ctx, cipher, cfg) + if err == nil { + vault.AddOrUpdateSecureNote(postedCipher) + } else { + actionsLog.Warn("Error posting ssh key cipher: " + err.Error()) + } + + response, err = messages.IPCMessageFromPayload(messages.ImportSSHKeyResponse{ + Success: true, + }) + return +} + func init() { AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.CreateSSHKeyRequest{}), ensureEverything(systemauth.SSHKey, handleAddSSH)) AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.GetSSHKeysRequest{}), ensureIsNotLocked(ensureIsLoggedIn(handleListSSH))) + AgentActionsRegistry.Register(messages.MessageTypeForEmptyPayload(messages.ImportSSHKeyRequest{}), ensureEverything(systemauth.SSHKey, handleImportSSH)) } diff --git a/cli/agent/ssh/keys.go b/cli/agent/ssh/keys.go index 5bf11ac..1af9139 100644 --- a/cli/agent/ssh/keys.go +++ b/cli/agent/ssh/keys.go @@ -12,8 +12,55 @@ import ( "golang.org/x/crypto/ssh" ) -func NewSSHKeyCipher(name string, keyring *crypto.Keyring) (models.Cipher, string) { +// todo refactor to share code +func SSHKeyCipherFromKey(name string, privateKey string, keyring *crypto.Keyring) (models.Cipher, string, error) { + signer, err := ssh.ParsePrivateKey([]byte(privateKey)) + if err != nil { + return models.Cipher{}, "", err + } + pubKey := signer.PublicKey() + encryptedName, _ := crypto.EncryptWith([]byte(name), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey()) + encryptedPublicKeyKey, _ := crypto.EncryptWith([]byte("public-key"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey()) + encryptedPublicKeyValue, _ := crypto.EncryptWith([]byte(string(ssh.MarshalAuthorizedKey(pubKey))), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey()) + encryptedCustomTypeKey, _ := crypto.EncryptWith([]byte("custom-type"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey()) + encryptedCustomTypeValue, _ := crypto.EncryptWith([]byte("ssh-key"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey()) + encryptedPrivateKeyKey, _ := crypto.EncryptWith([]byte("private-key"), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey()) + encryptedPrivateKeyValue, _ := crypto.EncryptWith([]byte(privateKey), crypto.AesCbc256_HmacSha256_B64, keyring.GetAccountKey()) + + cipher := models.Cipher{ + Type: models.CipherNote, + Name: encryptedName, + Notes: &encryptedPublicKeyValue, + ID: nil, + Favorite: false, + OrganizationID: nil, + SecureNote: &models.SecureNoteCipher{ + Type: 0, + }, + Fields: []models.Field{ + { + Type: 0, + Name: encryptedCustomTypeKey, + Value: encryptedCustomTypeValue, + }, + { + Type: 0, + Name: encryptedPublicKeyKey, + Value: encryptedPublicKeyValue, + }, + { + Type: 1, + Name: encryptedPrivateKeyKey, + Value: encryptedPrivateKeyValue, + }, + }, + } + + return cipher, string(ssh.MarshalAuthorizedKey(pubKey)), nil +} + +func NewSSHKeyCipher(name string, keyring *crypto.Keyring) (models.Cipher, string, error) { var reader io.Reader = rand.Reader pub, priv, err := ed25519.GenerateKey(reader) @@ -72,5 +119,5 @@ func NewSSHKeyCipher(name string, keyring *crypto.Keyring) (models.Cipher, strin }, } - return cipher, string(ssh.MarshalAuthorizedKey(publicKey)) + return cipher, string(ssh.MarshalAuthorizedKey(publicKey)), nil } diff --git a/cli/cmd/ssh.go b/cli/cmd/ssh.go index d1f18a4..a474008 100644 --- a/cli/cmd/ssh.go +++ b/cli/cmd/ssh.go @@ -1,6 +1,7 @@ package cmd import ( + "bytes" "fmt" "os" @@ -92,6 +93,59 @@ var listSSHCmd = &cobra.Command{ }, } +var importSSHCmd = &cobra.Command{ + Use: "import", + Short: "Imports an SSH key into your vault", + Long: `Imports an SSH key into your vault.`, + Run: func(cmd *cobra.Command, args []string) { + filename := args[0] + fmt.Println("Importing SSH key from " + filename) + + name, _ := cmd.Flags().GetString("name") + if name == "" { + name = "Imported SSH Key" + } + + if _, err := os.Stat(filename); os.IsNotExist(err) { + fmt.Println("Error: File does not exist") + return + } + + file, err := os.Open(filename) + if err != nil { + fmt.Println("Error: " + err.Error()) + return + } + + buf := new(bytes.Buffer) + buf.ReadFrom(file) + key := buf.String() + + result, err := commandClient.SendToAgent(messages.ImportSSHKeyRequest{ + Key: key, + Name: name, + }) + if err != nil { + handleSendToAgentError(err) + return + } + + switch result.(type) { + case messages.ImportSSHKeyResponse: + response := result.(messages.ImportSSHKeyResponse) + if response.Success { + fmt.Println("Success") + } else { + fmt.Println("Error: " + response.ErrorMsg) + } + return + case messages.ActionResponse: + fmt.Println("Error: " + result.(messages.ActionResponse).Message) + return + } + }, +} + func init() { rootCmd.AddCommand(sshCmd) sshCmd.AddCommand(sshAddCmd) @@ -99,4 +153,6 @@ func init() { _ = sshAddCmd.MarkFlagRequired("name") sshAddCmd.PersistentFlags().Bool("clipboard", false, "Copy the public key to the clipboard") sshCmd.AddCommand(listSSHCmd) + importSSHCmd.PersistentFlags().String("name", "", "") + sshCmd.AddCommand(importSSHCmd) } diff --git a/cli/ipc/messages/sshkeys.go b/cli/ipc/messages/sshkeys.go index 0339a38..d6860f8 100644 --- a/cli/ipc/messages/sshkeys.go +++ b/cli/ipc/messages/sshkeys.go @@ -17,6 +17,16 @@ type GetSSHKeysResponse struct { Keys []string } +type ImportSSHKeyRequest struct { + Key string + Name string +} + +type ImportSSHKeyResponse struct { + Success bool + ErrorMsg string +} + func init() { registerPayloadParser(func(payload []byte) (interface{}, error) { var req CreateSSHKeyRequest @@ -53,4 +63,22 @@ func init() { } return req, nil }, GetSSHKeysResponse{}) + + registerPayloadParser(func(payload []byte) (interface{}, error) { + var req ImportSSHKeyRequest + err := json.Unmarshal(payload, &req) + if err != nil { + panic("Unmarshal: " + err.Error()) + } + return req, nil + }, ImportSSHKeyRequest{}) + + registerPayloadParser(func(payload []byte) (interface{}, error) { + var req ImportSSHKeyResponse + err := json.Unmarshal(payload, &req) + if err != nil { + panic("Unmarshal: " + err.Error()) + } + return req, nil + }, ImportSSHKeyResponse{}) }