From cb0c15f3c749c49435abd4acfe021c239e0ffa9e Mon Sep 17 00:00:00 2001 From: Claas Lisowski Date: Thu, 17 Dec 2020 18:19:41 +0100 Subject: [PATCH] Fix/encryption and sync (#58) * Fix encryption process and disable sync per default. --- .gitignore | 2 ++ README.md | 65 +++++++++++++++++++++++---------------------- bitwarden.go | 22 +++++++++------ cli.go | 8 +++--- config.go | 16 ++++++++--- crypt.go | 63 ++++++++++++++++++++++++++++++------------- crypt_util.go | 38 ++++++++++++++++++++------ items.go | 2 +- utils.go | 12 ++++++++- workflow/info.plist | 6 +++-- 10 files changed, 157 insertions(+), 77 deletions(-) diff --git a/.gitignore b/.gitignore index 890361e..be5a37e 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ workflow/bitwarden-alfred-workflow workflow/icons workflow/assets workflow/README.html +workflow/List Filter Images +workflow/bw_cache_update.sh diff --git a/README.md b/README.md index 82d2fe5..297d4f0 100644 --- a/README.md +++ b/README.md @@ -54,38 +54,39 @@ You can change the search-/filtermode yourself easily. This gif shows the 3 step - Configurable [workflow environment variables](https://www.alfredapp.com/help/workflows/advanced/variables/#environment) -| Name | Comment | Default Value | -|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| -| 2FA_ENABLED | enables or disables 2FA for login (can be set via .bwconfig ) | true | -| 2FA_NODE | sets the mode for the 2FA (can be set via .bwconfig ), 0 app, 1, email (not tested), 2 duo (not tested), 3 yubikey (not tested), 4 U2F (not tested) | 0 | -| BW_EXEC | defines the binary/executable for the Bitwarden CLI command | bw | -| BW_DATA_PATH | sets the path to the Bitwarden Cli data.json | "~/Library/Application Support/Bitwarden CLI/data.json"" | -| bw_keyword | defines the keyword which opens the Bitwarden Alfred Workflow | .bw | -| bwf_keyword | defines the keyword which opens the folder search of the Bitwarden Alfred Workflow | .bwf | -| bwauth_keyword | defines the keyword which opens the Bitwarden authentications of the Alfred Workflow | .bwauth | -| bwconf_keyword | defines the keyword which opens the Bitwarden configuration/settings of the Alfred Workflow | .bwconfig | -| EMAIL | the email which to use for the login via the Bitwarden CLI, will be read from the data.json of the Bitwarden CLI if present | "" | -| EMPTY_DETAIL_RESULTS | Show all information in the detail view, also if the content is empty | false | -| ICON_CACHE_ENABLED | Download icons for login items if a URL is set | true | -| ICON_CACHE_AGE | This defines how old the icon cache can get in minutes, if expired the Workflow will download icons again. If icons are missing the workflow will also try to download them unrelated to this timeout | 43200 (1 month) | -| AUTO_FETCH_ICON_CACHE_AGE | This defines how often the Workflow should check for an icon if is missing, it doesn't need to do it on every run hence this cache | 1440 (1 day) | -| MAX_RESULTS | The number of items to display maximal in the search view | 1000 | -| MODIFIER_1 | The first modifier key combination, possible options, which can be combined by comma separation, are "cmd,alt/opt,ctrl,shift,fn" | alt | -| MODIFIER_2 | The first modifier key combination, possible options, which can be combined by comma separation, are "cmd,alt/opt,ctrl,shift,fn" | shift | -| MODIFIER_3 | The first modifier key combination, possible options, which can be combined by comma separation, are "cmd,alt/opt,ctrl,shift,fn" | ctrl | -| MODIFIER_4 | The first modifier key combination, possible options, which can be combined by comma separation, are "cmd,alt/opt,ctrl,shift,fn" | cmd,opt | -| MODIFIER_1_ACTION | Action executed by the first modifier | username,code | -| MODIFIER_2_ACTION | Action executed by the second modifier | url | -| MODIFIER_3_ACTION | Action executed by the third modifier | totp | -| MODIFIER_4_ACTION | Action executed by the fourth modifier | more | -| NO_MODIFIER_ACTION | Action executed without modifier pressed | password,card | -| OUTPUT_FOLDER | The folder to which attachments should be saved when the action is triggered. Default is \$HOME/Downloads. "~" can be used as well. | "" | -| PATH | The PATH env variable which is used to search for executables (like the Bitwarden CLI configured with BW_EXEC, security to get and set keychain objects) | /usr/bin:/usr/local/bin:/usr/local/sbin:/usr/local/share/npm/bin:/usr/bin:/usr/sbin | -| REORDERING_DISABLED | If set to false the items which are often selected appear further up in the results. | true | -| SERVER_URL | Set the server url if you host your own Bitwarden instance - you can also set separate domains for api,webvault etc e.g. `--api http://localhost:4000 --identity http://localhost:33656` | https://bitwarden.com | -| TITLE_WITH_USER | If enabled the name of the login user item or the last 4 numbers of the card number will be appended (added) at the end of the name of the item | true | -| SYNC_CACHE_AGE | This defines how old the sync cache can get, if expired the Workflow will trigger a new sync with Bitwarden | 1440 (1 day) | -| CACHE_AGE | This defines how old the cached items can get, if expired the Workflow will trigger a new cache refresh with Bitwarden (this does not involve a sync with the Bitwarden server) | 1440 (1 day) | +| Name | Comment | Default Value | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| 2FA_ENABLED | enables or disables 2FA for login (can be set via .bwconfig ) | true | +| 2FA_NODE | sets the mode for the 2FA (can be set via .bwconfig ), 0 app, 1, email (not tested), 2 duo (not tested), 3 yubikey (not tested), 4 U2F (not tested) | 0 | +| AUTO_FETCH_ICON_CACHE_AGE | This defines how often the Workflow should check for an icon if is missing, it doesn't need to do it on every run hence this cache | 1440 (1 day) | +| BW_EXEC | defines the binary/executable for the Bitwarden CLI command | bw | +| BW_DATA_PATH | sets the path to the Bitwarden Cli data.json | "~/Library/Application Support/Bitwarden CLI/data.json"" | +| bw_keyword | defines the keyword which opens the Bitwarden Alfred Workflow | .bw | +| bwf_keyword | defines the keyword which opens the folder search of the Bitwarden Alfred Workflow | .bwf | +| bwauth_keyword | defines the keyword which opens the Bitwarden authentications of the Alfred Workflow | .bwauth | +| bwconf_keyword | defines the keyword which opens the Bitwarden configuration/settings of the Alfred Workflow | .bwconfig | +| CACHE_AGE | This defines how old the cached items can get, if expired the Workflow will trigger a new cache refresh with Bitwarden (this does not involve a sync with the Bitwarden server, values lower than 30 will be ignored and set to 30) | 0 (0 means disabled; unit is minutes) | +| DEBUG | If enabled print additional debug information, specially about for the decryption process | false | +| EMAIL | the email which to use for the login via the Bitwarden CLI, will be read from the data.json of the Bitwarden CLI if present | "" | +| EMPTY_DETAIL_RESULTS | Show all information in the detail view, also if the content is empty | false | +| ICON_CACHE_ENABLED | Download icons for login items if a URL is set | true | +| ICON_CACHE_AGE | This defines how old the icon cache can get in minutes, if expired the Workflow will download icons again. If icons are missing the workflow will also try to download them unrelated to this timeout | 43200 (1 month) | +| MAX_RESULTS | The number of items to display maximal in the search view | 1000 | +| MODIFIER_1 | The first modifier key combination, possible options, which can be combined by comma separation, are "cmd,alt/opt,ctrl,shift,fn" | alt | +| MODIFIER_2 | The first modifier key combination, possible options, which can be combined by comma separation, are "cmd,alt/opt,ctrl,shift,fn" | shift | +| MODIFIER_3 | The first modifier key combination, possible options, which can be combined by comma separation, are "cmd,alt/opt,ctrl,shift,fn" | ctrl | +| MODIFIER_4 | The first modifier key combination, possible options, which can be combined by comma separation, are "cmd,alt/opt,ctrl,shift,fn" | cmd,opt | +| MODIFIER_1_ACTION | Action executed by the first modifier | username,code | +| MODIFIER_2_ACTION | Action executed by the second modifier | url | +| MODIFIER_3_ACTION | Action executed by the third modifier | totp | +| MODIFIER_4_ACTION | Action executed by the fourth modifier | more | +| NO_MODIFIER_ACTION | Action executed without modifier pressed | password,card | +| OUTPUT_FOLDER | The folder to which attachments should be saved when the action is triggered. Default is \$HOME/Downloads. "~" can be used as well. | "" | +| PATH | The PATH env variable which is used to search for executables (like the Bitwarden CLI configured with BW_EXEC, security to get and set keychain objects) | /usr/bin:/usr/local/bin:/usr/local/sbin:/usr/local/share/npm/bin:/usr/bin:/usr/sbin | +| REORDERING_DISABLED | If set to false the items which are often selected appear further up in the results. | true | +| SERVER_URL | Set the server url if you host your own Bitwarden instance - you can also set separate domains for api,webvault etc e.g. `--api http://localhost:4000 --identity http://localhost:33656` | https://bitwarden.com | +| SYNC_CACHE_AGE | This defines how old the sync cache can get, if expired the Workflow will trigger a new sync with Bitwarden (values lower than 30 will be ignored and set to 30) | 0 (0 means disabled; unit is minutes) | +| TITLE_WITH_USER | If enabled the name of the login user item or the last 4 numbers of the card number will be appended (added) at the end of the name of the item | true | ## Modifier Actions Explained diff --git a/bitwarden.go b/bitwarden.go index 973921f..95a36c4 100644 --- a/bitwarden.go +++ b/bitwarden.go @@ -78,14 +78,6 @@ func runSync(force bool, last bool) { args = fmt.Sprintf("%s sync --session %s", conf.BwExec, token) } - // Clear the cache only if not getting --last sync date - if !last { - err := clearCache() - if err != nil { - log.Print("Error while deleting Caches ", err) - } - } - result, err := runCmd(args, message) if err != nil { wf.FatalError(err) @@ -265,6 +257,7 @@ func runGetItem() { encryptedSecret = value.String() } else { log.Print("Error, value for gjson not found.") + isDecryptSecretFromJsonFailed = true } } @@ -277,6 +270,7 @@ func runGetItem() { decryptedString, err = otpKey(decryptedString) if err != nil { log.Print("Error getting topt key, ", err) + isDecryptSecretFromJsonFailed = true } } receivedItem = decryptedString @@ -520,6 +514,12 @@ func runLogin() { } searchAlfred(conf.BwKeyword) fmt.Println("Logged In.") + + // reset sync-cache + err = wf.Cache.StoreJSON(CACHE_NAME, nil) + if err != nil { + fmt.Println("Error cleaning cache..") + } } // Logout from Bitwarden @@ -544,6 +544,12 @@ func runLogout() { wf.FatalError(err) } fmt.Println("Logged Out") + + // reset sync-cache + err = wf.Cache.StoreJSON(CACHE_NAME, nil) + if err != nil { + fmt.Println("Error cleaning cache..") + } } func runCache() { diff --git a/cli.go b/cli.go index 22200f4..c7f671d 100644 --- a/cli.go +++ b/cli.go @@ -541,10 +541,10 @@ func runSearch(folderSearch bool, itemId string) { } } - // Check the sync cache, if it expired or doesn't exist do a sync. + // Check the sync cache, if it expired. // don't sync if age is set to 0 // this cache is just a control to automatically trigger the sync, the data itself is stored in the data cache (CACHE_NAME and FOLDER_CACHE_NAME) - if (conf.SyncCacheAge != 0 && wf.Cache.Expired(SYNC_CACHE_NAME, conf.SyncMaxCacheAge)) || !wf.Cache.Exists(SYNC_CACHE_NAME) { + if conf.SyncCacheAge != 0 && wf.Cache.Expired(SYNC_CACHE_NAME, conf.SyncMaxCacheAge) { if !wf.IsRunning("sync") { cmd := exec.Command(os.Args[0], "-sync", "-force") log.Println("Sync cmd: ", cmd) @@ -565,7 +565,7 @@ func runSearch(folderSearch bool, itemId string) { // If the cache has expired, set Rerun (which tells Alfred to re-run the // workflow), and start the background update process if it isn't already // running. - if wf.Cache.Expired(CACHE_NAME, conf.MaxCacheAge) || wf.Cache.Expired(FOLDER_CACHE_NAME, conf.MaxCacheAge) { + if conf.CacheAge != 0 && (wf.Cache.Expired(CACHE_NAME, conf.MaxCacheAge) || wf.Cache.Expired(FOLDER_CACHE_NAME, conf.MaxCacheAge)) { wf.Rerun(0.3) if !wf.IsRunning("cache") { var wg sync.WaitGroup @@ -651,7 +651,7 @@ func runSearch(folderSearch bool, itemId string) { if len(items) == 0 && len(folders) == 0 { addRefreshCacheItem() - wf.WarnEmpty("No Secrets Found", "Try a different query or refresh the cache manually.") + wf.NewItem("No Secrets Found").Subtitle("Try a different query or refresh the cache or sync manually.").Icon(iconWarning).Valid(false) } if !folderSearch && itemId == "" { diff --git a/config.go b/config.go index c21f42f..4d0f568 100644 --- a/config.go +++ b/config.go @@ -85,6 +85,7 @@ type config struct { // BwDataPath default is set in loadBitwardenJSON() BwDataPath string `envconfig:"BW_DATA_PATH"` CacheAge int `default:"1440" split_words:"true"` + Debug bool `envconfig:"DEBUG" default:"false"` Email string EmptyDetailResults bool `default:"false" split_words:"true"` IconCacheAge int `default:"43200" split_words:"true"` @@ -138,7 +139,7 @@ func loadBitwardenJSON() error { return err } bwDataPath = fmt.Sprintf("%s/Library/Application Support/Bitwarden CLI/data.json", homedir) - log.Println("BW DataPath", bwDataPath) + debugLog(fmt.Sprintf("bwDataPath is: %s", bwDataPath)) } if err := loadDataFile(bwDataPath); err != nil { return err @@ -178,7 +179,11 @@ func loadConfig() { conf.OutputFolder = alfred.GetOutputFolder(wf, conf.OutputFolder) // Set a few cache timeout durations - cacheAgeDuration := time.Duration(conf.CacheAge) + setItemCacheAge := conf.CacheAge + if conf.CacheAge < 30 && conf.CacheAge != 0 { + setItemCacheAge = 30 + } + cacheAgeDuration := time.Duration(setItemCacheAge) conf.MaxCacheAge = cacheAgeDuration * time.Minute iconCacheAgeDuration := time.Duration(conf.IconCacheAge) @@ -187,7 +192,12 @@ func loadConfig() { autoFetchIconCacheAgeDuration := time.Duration(conf.AutoFetchIconCacheAge) conf.AutoFetchIconMaxCacheAge = autoFetchIconCacheAgeDuration * time.Minute - syncCacheAgeDuration := time.Duration(conf.SyncCacheAge) + // if SYNC_CACHE_AGE is lower than 30 but not 0 set to 30 + setSyncCacheAge := conf.SyncCacheAge + if conf.SyncCacheAge < 30 && conf.SyncCacheAge != 0 { + setSyncCacheAge = 30 + } + syncCacheAgeDuration := time.Duration(setSyncCacheAge) conf.SyncMaxCacheAge = syncCacheAgeDuration * time.Minute conf.BwauthKeyword = os.Getenv("bwauth_keyword") diff --git a/crypt.go b/crypt.go index 6862ecc..f187178 100644 --- a/crypt.go +++ b/crypt.go @@ -50,7 +50,7 @@ func Encrypt(message []byte) (string, bool) { } func Decrypt() ([]byte, error) { - log.Println("Decrypting data now.") + log.Println("Decrypting data.") encryptedHex, err := wf.Cache.Load(CACHE_NAME) if err != nil { log.Println(err) @@ -124,30 +124,47 @@ type CryptoKey struct { // TODO: split up into functions func MakeDecryptKeyFromSession(protectedKey string, sessionKey string) (CryptoKey, error) { + // the key which will be returned later, or empty in case of error + ck := CryptoKey{} + + debugLog("base64 decode protected key") pt, err := base64.StdEncoding.DecodeString(protectedKey) if err != nil { - log.Print("Error decoding protectedKey, ", err) + return ck, fmt.Errorf("error decoding protectedKey, %s", err) } // following every step from here: // https://github.com/attie/bitwarden-decrypt#protected-session-data + debugLog(fmt.Sprintf("protected Key length is: %d", len(pt))) + if len(pt) > 1 { + debugLog(fmt.Sprintf("protected Key encryption type is: %d", int(pt[0]))) + } + + // check length, return error if they key is probably to short so that we continue using the normal bw cli client + if len(pt) < 51 { + log.Print("protected key length is probably too short, returning with error. length is: ", len(pt)) + return ck, fmt.Errorf("protected key length is probably too short") + } encryptionType := pt[:1] encryptionTypeInt := int(encryptionType[0]) iv := pt[1:17] pkmac := pt[17:49] ct := pt[49:] - // and now here: // https://github.com/attie/bitwarden-decrypt#derive-source-key-from-protected-session-data + debugLog("base64 decode session key") ses, err := base64.StdEncoding.DecodeString(sessionKey) if err != nil { - log.Print("Error decoding sessionKey, ", err) + return ck, fmt.Errorf("error decoding sessionKey, %s", err) + } + debugLog(fmt.Sprintf("Session key length is: %d", len(ses))) + if len(ses) != 64 { + log.Print("session key length is too short, returning with error. length is: ", len(ses)) + return ck, fmt.Errorf("session key length is too short") } sesec := ses[:32] sesmac := ses[32:64] - // the key which will be returned later, or empty in case of error - ck := CryptoKey{} - + debugLog("comparing session mac with protected key") mac := hmac.New(sha256.New, sesmac) _, err = mac.Write(iv) if err != nil { @@ -160,10 +177,12 @@ func MakeDecryptKeyFromSession(protectedKey string, sessionKey string) (CryptoKe ms := mac.Sum(nil) if base64.StdEncoding.EncodeToString(ms) != base64.StdEncoding.EncodeToString(pkmac) { log.Printf("MAC doesn't match %s %s", base64.StdEncoding.EncodeToString(pkmac), base64.StdEncoding.EncodeToString(ms)) + return ck, fmt.Errorf("MACs don't match of protectedkey and session key") } - // and this one now: + // makeing the sourcekey // https://github.com/attie/bitwarden-decrypt#decrypt + debugLog("making the source key") cs := CipherString{ encryptedString: "", encryptionType: encryptionTypeInt, @@ -178,33 +197,41 @@ func MakeDecryptKeyFromSession(protectedKey string, sessionKey string) (CryptoKe MacKey: sesmac, EncryptionType: 2, } - sourceKey, err := cs.DecryptKey(ck, 2) + sourceKey, err := cs.DecryptKey(ck, ck.EncryptionType) if err != nil { - log.Print("Error decrypting key, ", err) + return ck, fmt.Errorf("error decrypting key, %s", err) } - // making now the intermediate keys: + // making the intermediate keys: // https://github.com/attie/bitwarden-decrypt#derive-intermediate-keys-from-source-key + debugLog("making intermediate keys") interKeys, err := MakeIntermediateKeys(sourceKey) if err != nil { - log.Print("Error making intermediate keys, ", err) + return ck, fmt.Errorf("error making intermediate keys, %s", err) } // finally decrypting the real users encryption key: // https://github.com/attie/bitwarden-decrypt#decrypt-the-users-final-keys + debugLog("decrypting final encryption keys") ekCs, err := NewCipherString(bwData.EncKey) if err != nil { - log.Print("Error making cipherstring from encKey, ", err) + return ck, fmt.Errorf("error making cipherstring from encKey, %s", err) } userDecryptKey, err := ekCs.DecryptKey(interKeys, ekCs.encryptionType) if err != nil { - log.Print("Error decrypting key, ", err) + return ck, fmt.Errorf("error decrypting key, %s", err) + } + + debugLog(fmt.Sprintf("bwData encKey length is: %d", len(bwData.EncKey))) + debugLog(fmt.Sprintf("bwData encKey encryption type is: %d", ekCs.encryptionType)) + debugLog(fmt.Sprintf("User decrypt key length is: %d", len(userDecryptKey.EncKey))) + if len(userDecryptKey.EncKey) != 32 { + log.Print("User decrypt key length is too short, returning with error. length is: ", len(userDecryptKey.EncKey)) + return ck, fmt.Errorf("user decrypt key length is too short") } - tmpKeyEnc := userDecryptKey[:32] - tmpKeyMac := userDecryptKey[32:64] userKey := CryptoKey{ - EncKey: tmpKeyEnc, - MacKey: tmpKeyMac, + EncKey: userDecryptKey.EncKey, + MacKey: userDecryptKey.MacKey, EncryptionType: 2, } return userKey, err diff --git a/crypt_util.go b/crypt_util.go index 1e33a47..07bc065 100644 --- a/crypt_util.go +++ b/crypt_util.go @@ -23,6 +23,26 @@ func DecryptString(s string, mk CryptoKey) (string, error) { return string(rv), err } +func NewCryptoKey(key []byte, encryptionType int) (CryptoKey, error) { + c := CryptoKey{EncryptionType: encryptionType} + + switch encryptionType { + case AesCbc256_B64: + c.EncKey = key + case AesCbc256_HmacSha256_B64: + c.EncKey = key[:32] + c.MacKey = key[32:] + default: + return c, fmt.Errorf("invalid encryption type: %d", encryptionType) + } + + if len(key) != (len(c.EncKey) + len(c.MacKey)) { + return c, fmt.Errorf("invalid key size: %d", len(key)) + } + + return c, nil +} + func DecryptValue(s string, mk CryptoKey) ([]byte, error) { if s == "" { return []byte(""), nil @@ -37,25 +57,26 @@ func DecryptValue(s string, mk CryptoKey) ([]byte, error) { return rv, err } -func (cs *CipherString) DecryptKey(key CryptoKey, encryptionType int) ([]byte, error) { - if encryptionType != 2 { - return nil, fmt.Errorf("encryption type not supported %d", encryptionType) - } +func (cs *CipherString) DecryptKey(key CryptoKey, encryptionType int) (CryptoKey, error) { kb, err := cs.Decrypt(key) - return kb, err + if err != nil { + return CryptoKey{}, err + } + k, err := NewCryptoKey(kb, encryptionType) + return k, err } -func MakeIntermediateKeys(sourceKey []byte) (CryptoKey, error) { +func MakeIntermediateKeys(sourceKey CryptoKey) (CryptoKey, error) { tmpKeyEnc := make([]byte, 32) tmpKeyMac := make([]byte, 32) var r io.Reader - r = hkdf.Expand(sha256.New, sourceKey, []byte("enc")) + r = hkdf.Expand(sha256.New, sourceKey.EncKey, []byte("enc")) _, err := r.Read(tmpKeyEnc) if err != nil { return CryptoKey{}, err } - r = hkdf.Expand(sha256.New, sourceKey, []byte("mac")) + r = hkdf.Expand(sha256.New, sourceKey.EncKey, []byte("mac")) _, err = r.Read(tmpKeyMac) if err != nil { return CryptoKey{}, err @@ -84,6 +105,7 @@ func NewCipherString(encryptedString string) (*CipherString, error) { return nil, errors.New("invalid key header") } + debugLog(fmt.Sprintf("cs.encryptionType %d", cs.encryptionType)) switch cs.encryptionType { case AesCbc256_B64: if len(encPieces) != 2 { diff --git a/items.go b/items.go index dab2bf4..bf022ac 100644 --- a/items.go +++ b/items.go @@ -528,7 +528,7 @@ func addNewModifierItem(item *aw.Item, modifier modifierActionRelation) { func addRefreshCacheItem() { wf.NewItem("Refresh Bitwardens Secret Cache"). - Subtitle("Fill the cache with cleaned Bitwarden secrets (the real secrets, are not kept in the cached)"). + Subtitle("Fill the cache with cleaned Bitwarden secrets (the real secrets are not kept in the cached)"). Valid(true). UID("cache"). Icon(ReloadIcon()). diff --git a/utils.go b/utils.go index c715903..2de87cf 100644 --- a/utils.go +++ b/utils.go @@ -64,7 +64,11 @@ func checkReturn(status cmd.Status, message string) ([]string, error) { log.Println("[DEBUG] Stderr: => ", line) } } - return []string{}, fmt.Errorf("Unexpected error. Exit code %d.", exitCode) + errMessage := "" + for _, line := range status.Stderr { + errMessage += fmt.Sprintf(" %s", line) + } + return []string{}, fmt.Errorf("Unexpected error. Exit code %d. Has the session key changed?\n[ERROR] %s", exitCode, errMessage) } } @@ -148,3 +152,9 @@ func clearCache() error { } return nil } + +func debugLog(message string) { + if conf.Debug { + log.Print("[DEBUG] ", message) + } +} diff --git a/workflow/info.plist b/workflow/info.plist index ba8ff6a..bd3b11e 100644 --- a/workflow/info.plist +++ b/workflow/info.plist @@ -1303,7 +1303,9 @@ Caching of the secret/item names (not the secret values itself) are cached so th BW_EXEC bw CACHE_AGE - 1440 + 0 + DEBUG + false EMAIL EMPTY_DETAIL_RESULTS @@ -1341,7 +1343,7 @@ Caching of the secret/item names (not the secret values itself) are cached so th SERVER_URL https://bitwarden.com SYNC_CACHE_AGE - 1440 + 0 TITLE_WITH_USER true bw_keyword