Login with device & virtual ipc
This commit is contained in:
parent
187ee16adf
commit
6e3859dd43
|
@ -28,7 +28,15 @@ func handleLogin(msg ipc.IPCMessage, cfg *config.Config, vault *vault.Vault, cal
|
||||||
req := msg.ParsedPayload().(ipc.DoLoginRequest)
|
req := msg.ParsedPayload().(ipc.DoLoginRequest)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
token, masterKey, masterpasswordHash, err := bitwarden.LoginWithMasterpassword(ctx, req.Email, cfg, vault)
|
var token bitwarden.LoginResponseToken
|
||||||
|
var masterKey crypto.MasterKey
|
||||||
|
var masterpasswordHash string
|
||||||
|
|
||||||
|
if req.Passwordless {
|
||||||
|
token, masterKey, masterpasswordHash, err = bitwarden.LoginWithDevice(ctx, req.Email, cfg, vault)
|
||||||
|
} else {
|
||||||
|
token, masterKey, masterpasswordHash, err = bitwarden.LoginWithMasterpassword(ctx, req.Email, cfg, vault)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var payload = ipc.ActionResponse{
|
var payload = ipc.ActionResponse{
|
||||||
Success: false,
|
Success: false,
|
||||||
|
|
|
@ -4,16 +4,20 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net/url"
|
"net/url"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/LlamaNite/llamalog"
|
"github.com/LlamaNite/llamalog"
|
||||||
"github.com/awnumar/memguard"
|
"github.com/awnumar/memguard"
|
||||||
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
||||||
|
"github.com/quexten/goldwarden/agent/bitwarden/twofactor"
|
||||||
"github.com/quexten/goldwarden/agent/config"
|
"github.com/quexten/goldwarden/agent/config"
|
||||||
"github.com/quexten/goldwarden/agent/systemauth"
|
"github.com/quexten/goldwarden/agent/systemauth"
|
||||||
"github.com/quexten/goldwarden/agent/vault"
|
"github.com/quexten/goldwarden/agent/vault"
|
||||||
|
@ -99,11 +103,11 @@ func LoginWithMasterpassword(ctx context.Context, email string, cfg *config.Conf
|
||||||
err = authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values)
|
err = authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values)
|
||||||
errsc, ok := err.(*errStatusCode)
|
errsc, ok := err.(*errStatusCode)
|
||||||
if ok && bytes.Contains(errsc.body, []byte("TwoFactor")) {
|
if ok && bytes.Contains(errsc.body, []byte("TwoFactor")) {
|
||||||
var twoFactor TwoFactorResponse
|
var twoFactor twofactor.TwoFactorResponse
|
||||||
if err := json.Unmarshal(errsc.body, &twoFactor); err != nil {
|
if err := json.Unmarshal(errsc.body, &twoFactor); err != nil {
|
||||||
return LoginResponseToken{}, crypto.MasterKey{}, "", err
|
return LoginResponseToken{}, crypto.MasterKey{}, "", err
|
||||||
}
|
}
|
||||||
provider, token, err := performSecondFactor(&twoFactor, cfg)
|
provider, token, err := twofactor.PerformSecondFactor(&twoFactor, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not obtain two-factor auth token: %v", err)
|
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("could not obtain two-factor auth token: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -126,6 +130,62 @@ func LoginWithMasterpassword(ctx context.Context, email string, cfg *config.Conf
|
||||||
return loginResponseToken, masterKey, hashedPassword, nil
|
return loginResponseToken, masterKey, hashedPassword, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LoginWithDevice(ctx context.Context, email string, cfg *config.Config, vault *vault.Vault) (LoginResponseToken, crypto.MasterKey, string, error) {
|
||||||
|
timeout := 120 * time.Second
|
||||||
|
|
||||||
|
// 25 random letters & numbers
|
||||||
|
alphabet := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
accessCode := ""
|
||||||
|
for i := 0; i < 25; i++ {
|
||||||
|
accessCode += string(alphabet[rand.Intn(len(alphabet))])
|
||||||
|
}
|
||||||
|
publicKey, err := crypto.GenerateAsymmetric()
|
||||||
|
if err != nil {
|
||||||
|
return LoginResponseToken{}, crypto.MasterKey{}, "", err
|
||||||
|
}
|
||||||
|
data, err := CreateAuthRequest(ctx, accessCode, cfg.ConfigFile.DeviceUUID, email, base64.StdEncoding.EncodeToString(publicKey.PublicBytes()), cfg)
|
||||||
|
|
||||||
|
timeoutChan := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
time.Sleep(timeout)
|
||||||
|
timeoutChan <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-timeoutChan:
|
||||||
|
return LoginResponseToken{}, crypto.MasterKey{}, "", fmt.Errorf("timed out waiting for device to be authorized")
|
||||||
|
default:
|
||||||
|
authRequestData, err := GetAuthRequest(ctx, data.ID, cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Could not get auth request: ", err)
|
||||||
|
}
|
||||||
|
if authRequestData.RequestApproved {
|
||||||
|
masterKey, err := crypto.DecryptWithAsymmetric([]byte(authRequestData.Key), publicKey)
|
||||||
|
masterPasswordHash, err := crypto.DecryptWithAsymmetric([]byte(authRequestData.MasterPasswordHash), publicKey)
|
||||||
|
values := urlValues(
|
||||||
|
"grant_type", "password",
|
||||||
|
"username", email,
|
||||||
|
"password", string(accessCode),
|
||||||
|
"authRequest", authRequestData.ID,
|
||||||
|
"scope", loginScope,
|
||||||
|
"client_id", "connector",
|
||||||
|
"deviceType", deviceType(),
|
||||||
|
"deviceName", deviceName,
|
||||||
|
"deviceIdentifier", cfg.ConfigFile.DeviceUUID,
|
||||||
|
)
|
||||||
|
|
||||||
|
var loginResponseToken LoginResponseToken
|
||||||
|
err = authenticatedHTTPPost(ctx, cfg.ConfigFile.IdentityUrl+"/connect/token", &loginResponseToken, values)
|
||||||
|
if err != nil {
|
||||||
|
return LoginResponseToken{}, crypto.MasterKey{}, "", err
|
||||||
|
}
|
||||||
|
return loginResponseToken, crypto.MasterKeyFromBytes(masterKey), string(masterPasswordHash), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func RefreshToken(ctx context.Context, cfg *config.Config) bool {
|
func RefreshToken(ctx context.Context, cfg *config.Config) bool {
|
||||||
authLog.Info("Refreshing token")
|
authLog.Info("Refreshing token")
|
||||||
|
|
||||||
|
@ -157,3 +217,16 @@ func RefreshToken(ctx context.Context, cfg *config.Config) bool {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func urlValues(pairs ...string) url.Values {
|
||||||
|
if len(pairs)%2 != 0 {
|
||||||
|
panic("pairs must be of even length")
|
||||||
|
}
|
||||||
|
vals := make(url.Values)
|
||||||
|
for i := 0; i < len(pairs); i += 2 {
|
||||||
|
vals.Set(pairs[i], pairs[i+1])
|
||||||
|
}
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
var b64enc = base64.StdEncoding.Strict()
|
||||||
|
|
|
@ -5,7 +5,9 @@ import (
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
cryptorand "crypto/rand"
|
cryptorand "crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -53,6 +55,20 @@ func AssymmetricEncryptionKeyFromBytes(key []byte) (AsymmetricEncryptionKey, err
|
||||||
return AsymmetricEncryptionKey{k}, nil
|
return AsymmetricEncryptionKey{k}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (key AsymmetricEncryptionKey) PublicBytes() []byte {
|
||||||
|
buffer, err := key.encKey.Open()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
privateKey, err := x509.ParsePKCS8PrivateKey(buffer.Bytes())
|
||||||
|
pub := (privateKey.(*rsa.PrivateKey)).Public()
|
||||||
|
publicKeyBytes, err := x509.MarshalPKIXPublicKey(pub)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return publicKeyBytes
|
||||||
|
}
|
||||||
|
|
||||||
func isMacValid(message, messageMAC, key []byte) bool {
|
func isMacValid(message, messageMAC, key []byte) bool {
|
||||||
mac := hmac.New(sha256.New, key)
|
mac := hmac.New(sha256.New, key)
|
||||||
mac.Write(message)
|
mac.Write(message)
|
||||||
|
|
|
@ -210,6 +210,20 @@ func EncryptWith(data []byte, typ EncStringType, key SymmetricEncryptionKey) (En
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenerateAsymmetric() (AsymmetricEncryptionKey, error) {
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return AsymmetricEncryptionKey{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encKey, err := x509.MarshalPKCS8PrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return AsymmetricEncryptionKey{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return AssymmetricEncryptionKeyFromBytes(encKey)
|
||||||
|
}
|
||||||
|
|
||||||
func DecryptWithAsymmetric(s []byte, asymmetrickey AsymmetricEncryptionKey) ([]byte, error) {
|
func DecryptWithAsymmetric(s []byte, asymmetrickey AsymmetricEncryptionKey) ([]byte, error) {
|
||||||
key, err := asymmetrickey.encKey.Open()
|
key, err := asymmetrickey.encKey.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -60,3 +60,7 @@ func DeriveMasterKey(password memguard.LockedBuffer, email string, kdfConfig KDF
|
||||||
|
|
||||||
return MasterKey{memguard.NewEnclave(key)}, nil
|
return MasterKey{memguard.NewEnclave(key)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MasterKeyFromBytes(key []byte) MasterKey {
|
||||||
|
return MasterKey{memguard.NewEnclave(key)}
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,14 @@ import (
|
||||||
"github.com/quexten/goldwarden/agent/config"
|
"github.com/quexten/goldwarden/agent/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CreateAuthRequestData struct {
|
||||||
|
AccessCode string `json:"accessCode"`
|
||||||
|
DeviceIdentifier string `json:"deviceIdentifier"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
PublicKey string `json:"publicKey"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
type AuthRequestData struct {
|
type AuthRequestData struct {
|
||||||
CreationDate time.Time `json:"creationDate"`
|
CreationDate time.Time `json:"creationDate"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
@ -80,3 +88,20 @@ func CreateAuthResponse(ctx context.Context, authRequest AuthRequestData, keyrin
|
||||||
})
|
})
|
||||||
return authRequestResponse, err
|
return authRequestResponse, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateAuthRequest(ctx context.Context, code string, deviceIdentifier string, email string, publicKey string, config *config.Config) (AuthRequestData, error) {
|
||||||
|
var authrequestData AuthRequestData
|
||||||
|
err := authenticatedHTTPPost(ctx, config.ConfigFile.ApiUrl+"/auth-requests/", &authrequestData, &CreateAuthRequestData{
|
||||||
|
AccessCode: code,
|
||||||
|
DeviceIdentifier: deviceIdentifier,
|
||||||
|
Email: email,
|
||||||
|
PublicKey: publicKey,
|
||||||
|
Type: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return AuthRequestData{}, err
|
||||||
|
} else {
|
||||||
|
return authrequestData, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
package bitwarden
|
//go:build !nofido2
|
||||||
|
|
||||||
|
package twofactor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/keys-pub/go-libfido2"
|
"github.com/keys-pub/go-libfido2"
|
||||||
"github.com/quexten/goldwarden/agent/config"
|
"github.com/quexten/goldwarden/agent/config"
|
||||||
"github.com/quexten/goldwarden/agent/systemauth"
|
"github.com/quexten/goldwarden/agent/systemauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const isFido2Enabled = true
|
||||||
|
|
||||||
type Fido2Response struct {
|
type Fido2Response struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
RawId string `json:"rawId"`
|
RawId string `json:"rawId"`
|
||||||
|
@ -50,7 +52,7 @@ func Fido2TwoFactor(challengeB64 string, credentials []string, config *config.Co
|
||||||
for i, cred := range credentials {
|
for i, cred := range credentials {
|
||||||
decodedPublicKey, err := base64.RawURLEncoding.DecodeString(cred)
|
decodedPublicKey, err := base64.RawURLEncoding.DecodeString(cred)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
websocketLog.Fatal(err.Error())
|
twofactorLog.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
creds[i] = decodedPublicKey
|
creds[i] = decodedPublicKey
|
||||||
}
|
}
|
||||||
|
@ -61,7 +63,7 @@ func Fido2TwoFactor(challengeB64 string, credentials []string, config *config.Co
|
||||||
|
|
||||||
pin, err := systemauth.GetPassword("Fido2 PIN", "Enter your token's PIN")
|
pin, err := systemauth.GetPassword("Fido2 PIN", "Enter your token's PIN")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
websocketLog.Fatal(err.Error())
|
twofactorLog.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
assertion, err := device.Assertion(
|
assertion, err := device.Assertion(
|
||||||
|
@ -101,71 +103,3 @@ func Fido2TwoFactor(challengeB64 string, credentials []string, config *config.Co
|
||||||
respjson, err := json.Marshal(resp)
|
respjson, err := json.Marshal(resp)
|
||||||
return string(respjson), nil
|
return string(respjson), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func performSecondFactor(resp *TwoFactorResponse, cfg *config.Config) (TwoFactorProvider, []byte, error) {
|
|
||||||
if resp.TwoFactorProviders2[WebAuthn] != nil {
|
|
||||||
chall := resp.TwoFactorProviders2[WebAuthn]["challenge"].(string)
|
|
||||||
|
|
||||||
var creds []string
|
|
||||||
for _, credential := range resp.TwoFactorProviders2[WebAuthn]["allowCredentials"].([]interface{}) {
|
|
||||||
publicKey := credential.(map[string]interface{})["id"].(string)
|
|
||||||
creds = append(creds, publicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := Fido2TwoFactor(chall, creds, cfg)
|
|
||||||
if err != nil {
|
|
||||||
return WebAuthn, nil, err
|
|
||||||
}
|
|
||||||
return WebAuthn, []byte(result), err
|
|
||||||
}
|
|
||||||
if resp.TwoFactorProviders2[Authenticator] != nil {
|
|
||||||
token, err := systemauth.GetPassword("Authenticator Second Factor", "Enter your two-factor auth code")
|
|
||||||
return Authenticator, []byte(token), err
|
|
||||||
}
|
|
||||||
if resp.TwoFactorProviders2[Email] != nil {
|
|
||||||
token, err := systemauth.GetPassword("Email Second Factor", "Enter your two-factor auth code")
|
|
||||||
return Email, []byte(token), err
|
|
||||||
}
|
|
||||||
|
|
||||||
return Authenticator, []byte{}, errors.New("no second factor available")
|
|
||||||
}
|
|
||||||
|
|
||||||
type TwoFactorProvider int
|
|
||||||
|
|
||||||
const (
|
|
||||||
Authenticator TwoFactorProvider = 0
|
|
||||||
Email TwoFactorProvider = 1
|
|
||||||
Duo TwoFactorProvider = 2 //Not supported
|
|
||||||
YubiKey TwoFactorProvider = 3 //Not supported
|
|
||||||
U2f TwoFactorProvider = 4 //Not supported
|
|
||||||
Remember TwoFactorProvider = 5 //Not supported
|
|
||||||
OrganizationDuo TwoFactorProvider = 6 //Not supported
|
|
||||||
WebAuthn TwoFactorProvider = 7
|
|
||||||
_TwoFactorProviderMax = 8 //Not supported
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t *TwoFactorProvider) UnmarshalText(text []byte) error {
|
|
||||||
i, err := strconv.Atoi(string(text))
|
|
||||||
if err != nil || i < 0 || i >= _TwoFactorProviderMax {
|
|
||||||
return fmt.Errorf("invalid two-factor auth provider: %q", text)
|
|
||||||
}
|
|
||||||
*t = TwoFactorProvider(i)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TwoFactorResponse struct {
|
|
||||||
TwoFactorProviders2 map[TwoFactorProvider]map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func urlValues(pairs ...string) url.Values {
|
|
||||||
if len(pairs)%2 != 0 {
|
|
||||||
panic("pairs must be of even length")
|
|
||||||
}
|
|
||||||
vals := make(url.Values)
|
|
||||||
for i := 0; i < len(pairs); i += 2 {
|
|
||||||
vals.Set(pairs[i], pairs[i+1])
|
|
||||||
}
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
var b64enc = base64.StdEncoding.Strict()
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
//go:build nofido2
|
||||||
|
|
||||||
|
package twofactor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/quexten/goldwarden/agent/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const isFido2Enabled = false
|
||||||
|
|
||||||
|
func Fido2TwoFactor(challengeB64 string, credentials []string, config *config.Config) (string, error) {
|
||||||
|
return "", errors.New("Fido2 is not enabled")
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package twofactor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/LlamaNite/llamalog"
|
||||||
|
"github.com/quexten/goldwarden/agent/config"
|
||||||
|
"github.com/quexten/goldwarden/agent/systemauth"
|
||||||
|
)
|
||||||
|
|
||||||
|
var twofactorLog = llamalog.NewLogger("Goldwarden", "TwoFactor")
|
||||||
|
|
||||||
|
func PerformSecondFactor(resp *TwoFactorResponse, cfg *config.Config) (TwoFactorProvider, []byte, error) {
|
||||||
|
if resp.TwoFactorProviders2[WebAuthn] != nil {
|
||||||
|
if isFido2Enabled {
|
||||||
|
chall := resp.TwoFactorProviders2[WebAuthn]["challenge"].(string)
|
||||||
|
|
||||||
|
var creds []string
|
||||||
|
for _, credential := range resp.TwoFactorProviders2[WebAuthn]["allowCredentials"].([]interface{}) {
|
||||||
|
publicKey := credential.(map[string]interface{})["id"].(string)
|
||||||
|
creds = append(creds, publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := Fido2TwoFactor(chall, creds, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return WebAuthn, nil, err
|
||||||
|
}
|
||||||
|
return WebAuthn, []byte(result), err
|
||||||
|
} else {
|
||||||
|
twofactorLog.Warn("WebAuthn is enabled for the account but goldwarden is not compiled with FIDO2 support")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if resp.TwoFactorProviders2[Authenticator] != nil {
|
||||||
|
token, err := systemauth.GetPassword("Authenticator Second Factor", "Enter your two-factor auth code")
|
||||||
|
return Authenticator, []byte(token), err
|
||||||
|
}
|
||||||
|
if resp.TwoFactorProviders2[Email] != nil {
|
||||||
|
token, err := systemauth.GetPassword("Email Second Factor", "Enter your two-factor auth code")
|
||||||
|
return Email, []byte(token), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Authenticator, []byte{}, errors.New("no second factor available")
|
||||||
|
}
|
||||||
|
|
||||||
|
type TwoFactorProvider int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Authenticator TwoFactorProvider = 0
|
||||||
|
Email TwoFactorProvider = 1
|
||||||
|
Duo TwoFactorProvider = 2 //Not supported
|
||||||
|
YubiKey TwoFactorProvider = 3 //Not supported
|
||||||
|
U2f TwoFactorProvider = 4 //Not supported
|
||||||
|
Remember TwoFactorProvider = 5 //Not supported
|
||||||
|
OrganizationDuo TwoFactorProvider = 6 //Not supported
|
||||||
|
WebAuthn TwoFactorProvider = 7
|
||||||
|
_TwoFactorProviderMax = 8 //Not supported
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *TwoFactorProvider) UnmarshalText(text []byte) error {
|
||||||
|
i, err := strconv.Atoi(string(text))
|
||||||
|
if err != nil || i < 0 || i >= _TwoFactorProviderMax {
|
||||||
|
return fmt.Errorf("invalid two-factor auth provider: %q", text)
|
||||||
|
}
|
||||||
|
*t = TwoFactorProvider(i)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TwoFactorResponse struct {
|
||||||
|
TwoFactorProviders2 map[TwoFactorProvider]map[string]interface{}
|
||||||
|
}
|
|
@ -2,10 +2,13 @@ package systemauth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/twpayne/go-pinentry"
|
"github.com/twpayne/go-pinentry"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var authDisabled = false
|
||||||
|
|
||||||
func GetPassword(title string, description string) (string, error) {
|
func GetPassword(title string, description string) (string, error) {
|
||||||
client, err := pinentry.NewClient(
|
client, err := pinentry.NewClient(
|
||||||
pinentry.WithBinaryNameFromGnuPGAgentConf(),
|
pinentry.WithBinaryNameFromGnuPGAgentConf(),
|
||||||
|
@ -37,6 +40,11 @@ func GetPassword(title string, description string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetApproval(title string, description string) (bool, error) {
|
func GetApproval(title string, description string) (bool, error) {
|
||||||
|
if authDisabled {
|
||||||
|
log.Info("Skipping approval because system auth is disabled")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
client, err := pinentry.NewClient(
|
client, err := pinentry.NewClient(
|
||||||
pinentry.WithBinaryNameFromGnuPGAgentConf(),
|
pinentry.WithBinaryNameFromGnuPGAgentConf(),
|
||||||
pinentry.WithGPGTTY(),
|
pinentry.WithGPGTTY(),
|
||||||
|
@ -62,3 +70,10 @@ func GetApproval(title string, description string) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
envAuthDisabled := os.Getenv("GOLDWARDEN_SYSTEM_AUTH_DISABLED")
|
||||||
|
if envAuthDisabled == "true" {
|
||||||
|
authDisabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -76,6 +76,9 @@ func (a Approval) String() string {
|
||||||
|
|
||||||
func CheckBiometrics(approvalType Approval) bool {
|
func CheckBiometrics(approvalType Approval) bool {
|
||||||
log.Info("Checking biometrics for %s", approvalType.String())
|
log.Info("Checking biometrics for %s", approvalType.String())
|
||||||
|
if authDisabled {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
authority, err := polkit.NewAuthority()
|
authority, err := polkit.NewAuthority()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -102,7 +102,7 @@ type AgentState struct {
|
||||||
config *config.ConfigFile
|
config *config.ConfigFile
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartUnixAgent(path string) error {
|
func StartUnixAgent(path string, websocket bool, sshAgent bool) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// check if exists
|
// check if exists
|
||||||
|
@ -134,8 +134,11 @@ func StartUnixAgent(path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
disableDumpable()
|
disableDumpable()
|
||||||
|
if websocket {
|
||||||
go bitwarden.RunWebsocketDaemon(ctx, vault, &cfg)
|
go bitwarden.RunWebsocketDaemon(ctx, vault, &cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sshAgent {
|
||||||
vaultAgent := ssh.NewVaultAgent(vault)
|
vaultAgent := ssh.NewVaultAgent(vault)
|
||||||
vaultAgent.SetUnlockRequestAction(func() bool {
|
vaultAgent.SetUnlockRequestAction(func() bool {
|
||||||
err := cfg.TryUnlock(vault)
|
err := cfg.TryUnlock(vault)
|
||||||
|
@ -161,6 +164,7 @@ func StartUnixAgent(path string) error {
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
go vaultAgent.Serve()
|
go vaultAgent.Serve()
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
|
@ -0,0 +1,135 @@
|
||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os/user"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/quexten/goldwarden/agent/actions"
|
||||||
|
"github.com/quexten/goldwarden/agent/bitwarden"
|
||||||
|
"github.com/quexten/goldwarden/agent/bitwarden/crypto"
|
||||||
|
"github.com/quexten/goldwarden/agent/config"
|
||||||
|
"github.com/quexten/goldwarden/agent/sockets"
|
||||||
|
"github.com/quexten/goldwarden/agent/vault"
|
||||||
|
"github.com/quexten/goldwarden/ipc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeErrorToLog(err error) {
|
||||||
|
log.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveVirtualAgent(recv chan []byte, send chan []byte, ctx context.Context, vault *vault.Vault, cfg *config.Config) {
|
||||||
|
for {
|
||||||
|
data := <-recv
|
||||||
|
|
||||||
|
var msg ipc.IPCMessage
|
||||||
|
err := json.Unmarshal(data, &msg)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorToLog(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
responseBytes := []byte{}
|
||||||
|
if action, actionFound := actions.AgentActionsRegistry.Get(msg.Type); actionFound {
|
||||||
|
user, _ := user.Current()
|
||||||
|
process := "goldwarden"
|
||||||
|
parent := "SINGLE_PROC_MODE"
|
||||||
|
grandparent := "SINGLE_PROC_MODE"
|
||||||
|
callingContext := sockets.CallingContext{
|
||||||
|
UserName: user.Name,
|
||||||
|
ProcessName: process,
|
||||||
|
ParentProcessName: parent,
|
||||||
|
GrandParentProcessName: grandparent,
|
||||||
|
}
|
||||||
|
payload, err := action(msg, cfg, vault, callingContext)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorToLog(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
responseBytes, err = json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorToLog(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
payload := ipc.ActionResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "Action not found",
|
||||||
|
}
|
||||||
|
payloadBytes, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorToLog(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
responseBytes = payloadBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
send <- responseBytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartVirtualAgent() (chan []byte, chan []byte) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// check if exists
|
||||||
|
keyring := crypto.NewKeyring(nil)
|
||||||
|
var vault = vault.NewVault(&keyring)
|
||||||
|
cfg, err := config.ReadConfig()
|
||||||
|
if err != nil {
|
||||||
|
var cfg = config.DefaultConfig()
|
||||||
|
cfg.WriteConfig()
|
||||||
|
}
|
||||||
|
if !cfg.IsLocked() {
|
||||||
|
log.Warn("Config is not locked. SET A PIN!!")
|
||||||
|
token, err := cfg.GetToken()
|
||||||
|
if err == nil {
|
||||||
|
if token.AccessToken != "" {
|
||||||
|
bitwarden.RefreshToken(ctx, &cfg)
|
||||||
|
userSymmetricKey, err := cfg.GetUserSymmetricKey()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
protectedUserSymetricKey, err := crypto.SymmetricEncryptionKeyFromBytes(userSymmetricKey)
|
||||||
|
|
||||||
|
err = bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token.AccessToken), vault, &cfg, &protectedUserSymetricKey, true)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disableDumpable()
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(TokenRefreshInterval)
|
||||||
|
if !cfg.IsLocked() {
|
||||||
|
bitwarden.RefreshToken(ctx, &cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(FullSyncInterval)
|
||||||
|
if !cfg.IsLocked() {
|
||||||
|
token, err := cfg.GetToken()
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Could not get token: %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bitwarden.DoFullSync(context.WithValue(ctx, bitwarden.AuthToken{}, token), vault, &cfg, nil, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
recv := make(chan []byte)
|
||||||
|
send := make(chan []byte)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
go serveVirtualAgent(recv, send, ctx, vault, &cfg)
|
||||||
|
}()
|
||||||
|
return recv, send
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build autofill
|
//go:build !noautofill
|
||||||
|
|
||||||
package autofill
|
package autofill
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build autofill
|
//go:build !noautofill
|
||||||
|
|
||||||
package autofill
|
package autofill
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ func handlePayloadMessage(msg PayloadMessage, appID string) {
|
||||||
case "biometricUnlock":
|
case "biometricUnlock":
|
||||||
logging.Debugf("Biometric unlock requested")
|
logging.Debugf("Biometric unlock requested")
|
||||||
// logging.Debugf("Biometrics authorized: %t", isAuthorized)
|
// logging.Debugf("Biometrics authorized: %t", isAuthorized)
|
||||||
result, err := client.SendToAgent(ipc.GetBiometricsKeyRequest{})
|
result, err := client.NewUnixSocketClient().SendToAgent(ipc.GetBiometricsKeyRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Errorf("Unable to send message to agent: %s", err.Error())
|
logging.Errorf("Unable to send message to agent: %s", err.Error())
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,56 +1,5 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
type Client interface {
|
||||||
"io"
|
SendToAgent(request interface{}) (interface{}, error)
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/quexten/goldwarden/ipc"
|
|
||||||
)
|
|
||||||
|
|
||||||
const READ_BUFFER = 1 * 1024 * 1024 // 1MB
|
|
||||||
|
|
||||||
func reader(r io.Reader) interface{} {
|
|
||||||
buf := make([]byte, READ_BUFFER)
|
|
||||||
for {
|
|
||||||
n, err := r.Read(buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
message, err := ipc.UnmarshalJSON(buf[0:n])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SendToAgent(request interface{}) (interface{}, error) {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := net.Dial("unix", home+"/.goldwarden.sock")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
message, err := ipc.IPCMessageFromPayload(request)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
messageJson, err := message.MarshallToJson()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.Write(messageJson)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("write error:", err)
|
|
||||||
}
|
|
||||||
result := reader(c)
|
|
||||||
return result.(ipc.IPCMessage).ParsedPayload(), nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/quexten/goldwarden/ipc"
|
||||||
|
)
|
||||||
|
|
||||||
|
const READ_BUFFER = 1 * 1024 * 1024 // 1MB
|
||||||
|
|
||||||
|
type UnixSocketClient struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUnixSocketClient() UnixSocketClient {
|
||||||
|
return UnixSocketClient{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reader(r io.Reader) interface{} {
|
||||||
|
buf := make([]byte, READ_BUFFER)
|
||||||
|
for {
|
||||||
|
n, err := r.Read(buf[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
message, err := ipc.UnmarshalJSON(buf[0:n])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client UnixSocketClient) SendToAgent(request interface{}) (interface{}, error) {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := net.Dial("unix", home+"/.goldwarden.sock")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
message, err := ipc.IPCMessageFromPayload(request)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
messageJson, err := message.MarshallToJson()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.Write(messageJson)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("write error:", err)
|
||||||
|
}
|
||||||
|
result := reader(c)
|
||||||
|
return result.(ipc.IPCMessage).ParsedPayload(), nil
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/quexten/goldwarden/ipc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewVirtualClient(recv chan []byte, send chan []byte) VirtualClient {
|
||||||
|
return VirtualClient{
|
||||||
|
recv,
|
||||||
|
send,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type VirtualClient struct {
|
||||||
|
recv chan []byte
|
||||||
|
send chan []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func virtualReader(recv chan []byte) interface{} {
|
||||||
|
for {
|
||||||
|
message, err := ipc.UnmarshalJSON(<-recv)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client VirtualClient) SendToAgent(request interface{}) (interface{}, error) {
|
||||||
|
message, err := ipc.IPCMessageFromPayload(request)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
messageJson, err := message.MarshallToJson()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.send <- messageJson
|
||||||
|
result := virtualReader(client.recv)
|
||||||
|
return result.(ipc.IPCMessage).ParsedPayload(), nil
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/quexten/goldwarden/client"
|
|
||||||
"github.com/quexten/goldwarden/ipc"
|
"github.com/quexten/goldwarden/ipc"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -19,7 +18,7 @@ var setApiUrlCmd = &cobra.Command{
|
||||||
request := ipc.SetApiURLRequest{}
|
request := ipc.SetApiURLRequest{}
|
||||||
request.Value = url
|
request.Value = url
|
||||||
|
|
||||||
result, err := client.SendToAgent(request)
|
result, err := commandClient.SendToAgent(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Error: " + err.Error())
|
println("Error: " + err.Error())
|
||||||
println("Is the daemon running?")
|
println("Is the daemon running?")
|
||||||
|
@ -53,7 +52,7 @@ var setIdentityURLCmd = &cobra.Command{
|
||||||
request := ipc.SetIdentityURLRequest{}
|
request := ipc.SetIdentityURLRequest{}
|
||||||
request.Value = url
|
request.Value = url
|
||||||
|
|
||||||
result, err := client.SendToAgent(request)
|
result, err := commandClient.SendToAgent(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Error: " + err.Error())
|
println("Error: " + err.Error())
|
||||||
println("Is the daemon running?")
|
println("Is the daemon running?")
|
||||||
|
|
|
@ -15,6 +15,16 @@ var daemonizeCmd = &cobra.Command{
|
||||||
Long: `Starts the agent as a daemon. The agent will run in the background and will
|
Long: `Starts the agent as a daemon. The agent will run in the background and will
|
||||||
run in the background until it is stopped.`,
|
run in the background until it is stopped.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
websocketDisabled := os.Getenv("GOLDWARDEN_WEBSOCKET_DISABLED") == "true"
|
||||||
|
if websocketDisabled {
|
||||||
|
println("Websocket disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
sshDisabled := os.Getenv("GOLDWARDEN_SSH_DISABLED") == "true"
|
||||||
|
if sshDisabled {
|
||||||
|
println("SSH agent disabled")
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
signalChannel := make(chan os.Signal, 1)
|
signalChannel := make(chan os.Signal, 1)
|
||||||
signal.Notify(signalChannel, os.Interrupt)
|
signal.Notify(signalChannel, os.Interrupt)
|
||||||
|
@ -25,7 +35,7 @@ var daemonizeCmd = &cobra.Command{
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = agent.StartUnixAgent(home + "/.goldwarden.sock")
|
err = agent.StartUnixAgent(home+"/.goldwarden.sock", websocketDisabled, sshDisabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ Copyright © 2023 NAME HERE <EMAIL ADDRESS>
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/quexten/goldwarden/client"
|
|
||||||
"github.com/quexten/goldwarden/ipc"
|
"github.com/quexten/goldwarden/ipc"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -18,8 +17,10 @@ var loginCmd = &cobra.Command{
|
||||||
request := ipc.DoLoginRequest{}
|
request := ipc.DoLoginRequest{}
|
||||||
email, _ := cmd.Flags().GetString("email")
|
email, _ := cmd.Flags().GetString("email")
|
||||||
request.Email = email
|
request.Email = email
|
||||||
|
passwordless, _ := cmd.Flags().GetBool("passwordless")
|
||||||
|
request.Passwordless = passwordless
|
||||||
|
|
||||||
result, err := client.SendToAgent(request)
|
result, err := commandClient.SendToAgent(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Error: " + err.Error())
|
println("Error: " + err.Error())
|
||||||
println("Is the daemon running?")
|
println("Is the daemon running?")
|
||||||
|
@ -43,4 +44,5 @@ func init() {
|
||||||
vaultCmd.AddCommand(loginCmd)
|
vaultCmd.AddCommand(loginCmd)
|
||||||
loginCmd.PersistentFlags().String("email", "", "")
|
loginCmd.PersistentFlags().String("email", "", "")
|
||||||
loginCmd.MarkFlagRequired("email")
|
loginCmd.MarkFlagRequired("email")
|
||||||
|
loginCmd.PersistentFlags().Bool("passwordless", false, "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/quexten/goldwarden/client"
|
|
||||||
"github.com/quexten/goldwarden/ipc"
|
"github.com/quexten/goldwarden/ipc"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -17,7 +16,7 @@ var setPinCmd = &cobra.Command{
|
||||||
Short: "Set a new pin",
|
Short: "Set a new pin",
|
||||||
Long: `Set a new pin. The pin is used to unlock the vault.`,
|
Long: `Set a new pin. The pin is used to unlock the vault.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
result, err := client.SendToAgent(ipc.UpdateVaultPINRequest{})
|
result, err := commandClient.SendToAgent(ipc.UpdateVaultPINRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Error: " + err.Error())
|
println("Error: " + err.Error())
|
||||||
println("Is the daemon running?")
|
println("Is the daemon running?")
|
||||||
|
@ -42,7 +41,7 @@ var pinStatusCmd = &cobra.Command{
|
||||||
Short: "Check if a pin is set",
|
Short: "Check if a pin is set",
|
||||||
Long: `Check if a pin is set. The pin is used to unlock the vault.`,
|
Long: `Check if a pin is set. The pin is used to unlock the vault.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
result, err := client.SendToAgent(ipc.GetVaultPINRequest{})
|
result, err := commandClient.SendToAgent(ipc.GetVaultPINRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Error: " + err.Error())
|
println("Error: " + err.Error())
|
||||||
println("Is the daemon running?")
|
println("Is the daemon running?")
|
||||||
|
|
12
cmd/root.go
12
cmd/root.go
|
@ -3,9 +3,13 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/quexten/goldwarden/agent"
|
||||||
|
"github.com/quexten/goldwarden/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var commandClient client.Client
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "goldwarden",
|
Use: "goldwarden",
|
||||||
Short: "OS level integration for Bitwarden",
|
Short: "OS level integration for Bitwarden",
|
||||||
|
@ -22,5 +26,13 @@ func Execute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
goldwardenSingleProcess := os.Getenv("GOLDWARDEN_SINGLE_PROCESS")
|
||||||
|
if goldwardenSingleProcess == "true" {
|
||||||
|
recv, send := agent.StartVirtualAgent()
|
||||||
|
commandClient = client.NewVirtualClient(send, recv)
|
||||||
|
} else {
|
||||||
|
commandClient = client.NewUnixSocketClient()
|
||||||
|
}
|
||||||
|
|
||||||
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/quexten/goldwarden/client"
|
|
||||||
"github.com/quexten/goldwarden/ipc"
|
"github.com/quexten/goldwarden/ipc"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -29,7 +28,7 @@ var runCmd = &cobra.Command{
|
||||||
|
|
||||||
env := []string{}
|
env := []string{}
|
||||||
|
|
||||||
result, err := client.SendToAgent(ipc.GetCLICredentialsRequest{
|
result, err := commandClient.SendToAgent(ipc.GetCLICredentialsRequest{
|
||||||
ApplicationName: executable,
|
ApplicationName: executable,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/atotto/clipboard"
|
"github.com/atotto/clipboard"
|
||||||
"github.com/quexten/goldwarden/client"
|
|
||||||
"github.com/quexten/goldwarden/ipc"
|
"github.com/quexten/goldwarden/ipc"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -31,7 +30,7 @@ var sshAddCmd = &cobra.Command{
|
||||||
name, _ := cmd.Flags().GetString("name")
|
name, _ := cmd.Flags().GetString("name")
|
||||||
copyToClipboard, _ := cmd.Flags().GetBool("clipboard")
|
copyToClipboard, _ := cmd.Flags().GetBool("clipboard")
|
||||||
|
|
||||||
result, err := client.SendToAgent(ipc.CreateSSHKeyRequest{
|
result, err := commandClient.SendToAgent(ipc.CreateSSHKeyRequest{
|
||||||
Name: name,
|
Name: name,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -61,7 +60,7 @@ var listSSHCmd = &cobra.Command{
|
||||||
Short: "Lists all SSH keys in your vault",
|
Short: "Lists all SSH keys in your vault",
|
||||||
Long: `Lists all SSH keys in your vault.`,
|
Long: `Lists all SSH keys in your vault.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
result, err := client.SendToAgent(ipc.GetSSHKeysRequest{})
|
result, err := commandClient.SendToAgent(ipc.GetSSHKeysRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Error: " + err.Error())
|
println("Error: " + err.Error())
|
||||||
println("Is the daemon running?")
|
println("Is the daemon running?")
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/quexten/goldwarden/client"
|
|
||||||
"github.com/quexten/goldwarden/ipc"
|
"github.com/quexten/goldwarden/ipc"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -19,7 +18,7 @@ var unlockCmd = &cobra.Command{
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
request := ipc.UnlockVaultRequest{}
|
request := ipc.UnlockVaultRequest{}
|
||||||
|
|
||||||
result, err := client.SendToAgent(request)
|
result, err := commandClient.SendToAgent(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Error: " + err.Error())
|
println("Error: " + err.Error())
|
||||||
println("Is the daemon running?")
|
println("Is the daemon running?")
|
||||||
|
@ -46,7 +45,7 @@ var lockCmd = &cobra.Command{
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
request := ipc.LockVaultRequest{}
|
request := ipc.LockVaultRequest{}
|
||||||
|
|
||||||
result, err := client.SendToAgent(request)
|
result, err := commandClient.SendToAgent(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Error: " + err.Error())
|
println("Error: " + err.Error())
|
||||||
println("Is the daemon running?")
|
println("Is the daemon running?")
|
||||||
|
@ -73,7 +72,7 @@ var purgeCmd = &cobra.Command{
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
request := ipc.WipeVaultRequest{}
|
request := ipc.WipeVaultRequest{}
|
||||||
|
|
||||||
result, err := client.SendToAgent(request)
|
result, err := commandClient.SendToAgent(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Error: " + err.Error())
|
println("Error: " + err.Error())
|
||||||
println("Is the daemon running?")
|
println("Is the daemon running?")
|
||||||
|
|
|
@ -369,6 +369,7 @@ func IPCMessageFromPayload(payload interface{}) (IPCMessage, error) {
|
||||||
type DoLoginRequest struct {
|
type DoLoginRequest struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
Passwordless bool `json:"passwordless"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LockVaultRequest struct {
|
type LockVaultRequest struct {
|
||||||
|
|
Loading…
Reference in New Issue