Compare commits

...

10 Commits

Author SHA1 Message Date
Bernd Schoolmann
1c9bf6101b
Fix incorrectly named method 2024-06-02 23:23:07 +02:00
Bernd Schoolmann
9a0ffded83
Merge pull request #233 from quexten/feature/import-ssh
Implement ssh import
2024-06-02 23:20:03 +02:00
Bernd Schoolmann
97a485712e
Implement ssh import 2024-06-02 23:18:37 +02:00
Bernd Schoolmann
79bfc43ab8
Update websocket.go 2024-06-02 21:45:12 +02:00
Bernd Schoolmann
d32147bdf8
Merge pull request #228 from quexten/feature/macos
Mac Support
2024-06-02 21:13:59 +02:00
Bernd Schoolmann
53271c3331
Implement mac main 2024-06-02 18:54:15 +02:00
Bernd Schoolmann
5af6771460
Fix autotype on mac 2024-06-02 17:17:15 +02:00
Bernd Schoolmann
2651a36d3c
Fix pinentry 2024-06-01 15:18:11 +02:00
Bernd Schoolmann
a41c91ca6b
Add macos startup scripts 2024-06-01 14:32:25 +02:00
Bernd Schoolmann
275ba9a3d2
Fix crash on daemon not running 2024-06-01 14:32:15 +02:00
13 changed files with 250 additions and 21 deletions

View File

@ -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))
}

View File

@ -255,12 +255,11 @@ func parseMessageTypeFromMessagePack(messagePack []byte) (int8, string, bool) {
return 0, "", false
}
if len(value) < 5 {
websocketLog.Warn("Invalid message received, length too short")
return 0, "", false
}
value, success := value[4].([]interface{})
if len(value) < 1 || !success {
websocketLog.Warn("Invalid message received, length too short")
websocketLog.Warn("Invalid message received, value length less than 1")
return 0, "", false
}
value1, success := value[0].(map[string]interface{})

View File

@ -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
}

View File

@ -1,16 +1,22 @@
//go:build freebsd || linux
//go:build freebsd || linux || darwin
package pinentry
import (
"errors"
"runtime"
"github.com/twpayne/go-pinentry"
)
func getPassword(title string, description string) (string, error) {
binaryClientOption := pinentry.WithBinaryNameFromGnuPGAgentConf()
if runtime.GOOS == "darwin" {
binaryClientOption = pinentry.WithBinaryName("pinentry-mac")
}
client, err := pinentry.NewClient(
pinentry.WithBinaryNameFromGnuPGAgentConf(),
binaryClientOption,
pinentry.WithGPGTTY(),
pinentry.WithTitle(title),
pinentry.WithDesc(description),

View File

@ -1,4 +1,4 @@
//go:build windows || darwin
//go:build windows
package pinentry

View File

@ -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)
}

View File

@ -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{})
}

View File

@ -1,4 +1,12 @@
#!/usr/bin/env python3
import src.linux.main as linux_main
linux_main.main()
import platform
if platform.system() == 'Darwin':
import src.macos.main as macos_main
macos_main.main()
elif platform.system() == 'Linux':
import src.linux.main as linux_main
linux_main.main()
else:
print("Unsupported OS " + platform.system() + "... exiting...")

View File

@ -1,10 +1,10 @@
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
gi.require_version('Notify', '0.7')
# gi.require_version('Notify', '0.7')
import gc
import time
from gi.repository import Gtk, Adw, GLib, Notify, Gdk
from gi.repository import Gtk, Adw, GLib, Gdk
from ..services import goldwarden
from ..services.autotype import autotype
from threading import Thread
@ -12,7 +12,6 @@ from .resource_loader import load_template
import sys
import os
from ..services import totp
Notify.init("Goldwarden")
class GoldwardenQuickAccessApp(Adw.Application):
def __init__(self, **kwargs):
@ -115,8 +114,8 @@ class GoldwardenQuickAccessApp(Adw.Application):
def run_autotype(self, text):
def perform_autotype(text):
self.window.hide()
time.sleep(0.1)
GLib.idle_add(self.window.hide)
time.sleep(2)
autotype.autotype(text)
time.sleep(0.1)
os._exit(0)
@ -148,7 +147,7 @@ class GoldwardenQuickAccessApp(Adw.Application):
self.filtered_logins = self.filtered_logins[0:7]
def render_list(self):
if len(self.filtered_logins) > 1:
if len(self.filtered_logins) > 0:
self.results_list.set_visible(True)
while self.results_list.get_first_child() != None:
self.results_list.remove(self.results_list.get_first_child())

View File

@ -101,8 +101,7 @@ class GoldwardenSettingsApp(Adw.Application):
if status == None:
is_daemon_running = goldwarden.is_daemon_running()
if not is_daemon_running:
self.status_row.set_subtitle("Daemon not running")
self.vault_status_icon.set_icon("dialog-error", "error")
print("Daemon not running")
return
logged_in = status["loggedIn"]

30
gui/src/macos/main.py Normal file
View File

@ -0,0 +1,30 @@
import sys
import subprocess
import os
import secrets
from src.services import goldwarden
from src.services import pinentry
import time
root_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir))
def main():
token = secrets.token_hex(32)
if not os.environ.get("GOLDWARDEN_DAEMON_AUTH_TOKEN") == None:
token = os.environ["GOLDWARDEN_DAEMON_AUTH_TOKEN"]
print("Starting Goldwarden GUI")
goldwarden.run_daemon_background(token)
time.sleep(1)
#pinentry.daemonize()
if not "--hidden" in sys.argv:
p = subprocess.Popen(["python3", "-m", "src.gui.settings"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=root_path, start_new_session=True)
p.stdin.write(f"{token}\n".encode())
p.stdin.flush()
# print stdout
while True:
line = p.stderr.readline()
if not line:
break
print(line.decode().strip())
while True:
time.sleep(60)

View File

@ -1,5 +1,5 @@
from ..goldwarden import autotype
def libportal_autotype(text):
def autotype_libportal(text):
print("autotypeing with libportal")
goldwarden.autotype(text)

View File

@ -2,4 +2,7 @@ import pyautogui
def autotype_pyautogui(text):
print("autotypeing with pyautogui")
pyautogui.write(text, interval=0.02)
pyautogui.write(text, interval=0.02)
if __name__ == "__main__":
autotype_pyautogui("hello world")