Skip to content

Commit bb7125f

Browse files
committed
Updated cli
1 parent 3aef1fa commit bb7125f

File tree

10 files changed

+221
-193
lines changed

10 files changed

+221
-193
lines changed

cmd/api/bitwarden.go

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ const (
3434
)
3535

3636
var (
37-
bwClient *bitwarden.Client
38-
bwPassword string
39-
bwConfigDir string
40-
bwForce bool
37+
bwClient *bitwarden.Client
38+
bwPassword string
39+
bwConfigDir string
40+
bwForce bool
41+
bwInteractive bool
4142
)
4243

4344
///////////////////////////////////////////////////////////////////////////////
@@ -49,6 +50,7 @@ func bwRegister(flags *Flags) {
4950
flags.String(bwName, "bitwarden-client-secret", "${BW_CLIENTSECRET}", "Client Secret")
5051
flags.String(bwName, "bitwarden-password", "${BW_PASSWORD}", "Master password")
5152
flags.Bool(bwName, "force", false, "Force login or sync to Bitwarden, even if existing token or data is valid")
53+
flags.Bool(bwName, "interactive", true, "Allow interactive prompts for password")
5254

5355
// Register commands
5456
flags.Register(Cmd{
@@ -82,6 +84,7 @@ func bwParse(flags *Flags, opts ...client.ClientOpt) error {
8284
return ErrBadParameter.With("Missing -bitwarden-client-id or -bitwarden-client-secret argument")
8385
}
8486
bwForce = flags.GetBool("force")
87+
bwInteractive = flags.GetBool("interactive")
8588
bwPassword = flags.GetString("bitwarden-password")
8689

8790
// Create the client
@@ -124,6 +127,16 @@ func bwFolders(w *tablewriter.Writer, _ []string) error {
124127
if bwForce {
125128
opts = append(opts, bitwarden.OptForce())
126129
}
130+
if bwPassword == "" && bwInteractive {
131+
if v, err := bwReadPasswordFromTerminal(); err != nil {
132+
return err
133+
} else {
134+
bwPassword = v
135+
}
136+
}
137+
if bwPassword != "" {
138+
opts = append(opts, bitwarden.OptPassword(bwPassword))
139+
}
127140
folders, err := bwClient.Folders(opts...)
128141
if err != nil {
129142
return err
@@ -132,7 +145,13 @@ func bwFolders(w *tablewriter.Writer, _ []string) error {
132145
// Decrypt the folders from the session
133146
var result []*schema.Folder
134147
for folder := folders.Next(); folder != nil; folder = folders.Next() {
135-
result = append(result, folders.Decrypt(folder))
148+
if folders.CanCrypt() {
149+
if folder, err := folders.Decrypt(folder); err == nil {
150+
result = append(result, folder)
151+
}
152+
} else {
153+
result = append(result, folder)
154+
}
136155
}
137156
return w.Write(result)
138157
}
@@ -142,6 +161,16 @@ func bwLogins(w *tablewriter.Writer, _ []string) error {
142161
if bwForce {
143162
opts = append(opts, bitwarden.OptForce())
144163
}
164+
if bwPassword == "" && bwInteractive {
165+
if v, err := bwReadPasswordFromTerminal(); err != nil {
166+
return err
167+
} else {
168+
bwPassword = v
169+
}
170+
}
171+
if bwPassword != "" {
172+
opts = append(opts, bitwarden.OptPassword(bwPassword))
173+
}
145174
ciphers, err := bwClient.Ciphers(opts...)
146175
if err != nil {
147176
return err
@@ -150,7 +179,13 @@ func bwLogins(w *tablewriter.Writer, _ []string) error {
150179
// Decrypt the ciphers from the session
151180
var result []*schema.Cipher
152181
for cipher := ciphers.Next(); cipher != nil; cipher = ciphers.Next() {
153-
result = append(result, ciphers.Decrypt(cipher))
182+
if ciphers.CanCrypt() {
183+
if cipher, err := ciphers.Decrypt(cipher); err == nil {
184+
result = append(result, cipher)
185+
}
186+
} else {
187+
result = append(result, cipher)
188+
}
154189
}
155190
return w.Write(result)
156191
}

pkg/bitwarden/filestorage/filestorage.go

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ import (
55
"path/filepath"
66

77
// Packages
8-
98
schema "github.com/mutablelogic/go-client/pkg/bitwarden/schema"
10-
11-
// Namespace imports
12-
. "github.com/djthorpe/go-errors"
139
)
1410

1511
////////////////////////////////////////////////////////////////////////////////
@@ -141,12 +137,7 @@ func (f *fileStorage) WriteCiphers(v schema.Ciphers) error {
141137
// PUBLIC METHODS - READ CIPHERS AND FOLDERS
142138

143139
// Read ciphers and return an iterator
144-
func (f *fileStorage) ReadCiphers(profile *schema.Profile) (schema.Iterator[*schema.Cipher], error) {
145-
// Profile is a required argument
146-
if profile == nil {
147-
return nil, ErrBadParameter.With("missing profile")
148-
}
149-
140+
func (f *fileStorage) ReadCiphers() (schema.Iterator[*schema.Cipher], error) {
150141
// Read the ciphers file
151142
ciphers := schema.Ciphers{}
152143
fileName := filepath.Join(f.cachePath, fileNameCiphers)
@@ -160,16 +151,11 @@ func (f *fileStorage) ReadCiphers(profile *schema.Profile) (schema.Iterator[*sch
160151
}
161152

162153
// Return an iterator
163-
return schema.NewIterator[*schema.Cipher](profile, ciphers), nil
154+
return schema.NewIterator[*schema.Cipher](ciphers), nil
164155
}
165156

166157
// Read folders and return an iterator
167-
func (f *fileStorage) ReadFolders(profile *schema.Profile) (schema.Iterator[*schema.Folder], error) {
168-
// Profile is a required argument
169-
if profile == nil {
170-
return nil, ErrBadParameter.With("missing profile")
171-
}
172-
158+
func (f *fileStorage) ReadFolders() (schema.Iterator[*schema.Folder], error) {
173159
// Read the folders file
174160
folders := schema.Folders{}
175161
fileName := filepath.Join(f.cachePath, fileNameFolders)
@@ -183,5 +169,5 @@ func (f *fileStorage) ReadFolders(profile *schema.Profile) (schema.Iterator[*sch
183169
}
184170

185171
// Return an iterator
186-
return schema.NewIterator[*schema.Folder](profile, folders), nil
172+
return schema.NewIterator[*schema.Folder](folders), nil
187173
}

pkg/bitwarden/schema/cipher.go

Lines changed: 21 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"io"
77
"time"
8+
9+
"github.com/mutablelogic/go-client/pkg/bitwarden/crypto"
810
)
911

1012
///////////////////////////////////////////////////////////////////////////////
@@ -23,7 +25,7 @@ type Cipher struct {
2325
RevisionDate time.Time `json:"revisionDate"`
2426
CollectionIds []string `json:"collectionIds,omitempty"`
2527
ViewPassword bool `json:"viewPassword"`
26-
Login *CipherLogin `json:"Login,omitempty"`
28+
Login *CipherLogin `json:"Login,omitempty,wrap"`
2729
// Card *CardData `json:"Card,omitempty"`
2830
// SecureNote *SecureNoteData `json:"SecureNote,omitempty"`
2931
// Identity *IdentityData `json:"Identity,omitempty"`
@@ -89,70 +91,29 @@ func (c *Ciphers) Write(w io.Writer) error {
8991
}
9092

9193
// Decrypt a cipher
92-
func (c Cipher) Decrypt(s *Session) (Crypter, error) {
94+
func (c Cipher) Decrypt(k *crypto.CryptoKey) (Crypter, error) {
9395
result := &c
94-
if value, err := s.DecryptStr(result.Name); err != nil {
96+
if value, err := k.DecryptStr(result.Name); err != nil {
9597
return nil, err
9698
} else {
9799
result.Name = value
98100
}
99-
if value, err := s.DecryptStr(result.Login.Username); err != nil {
100-
return nil, err
101-
} else {
102-
result.Login.Username = value
103-
}
104-
if value, err := s.DecryptStr(result.Login.URI); err != nil {
105-
return nil, err
106-
} else {
107-
result.Login.URI = value
101+
if result.Login != nil {
102+
if value, err := k.DecryptStr(result.Login.Username); err != nil {
103+
return nil, err
104+
} else {
105+
result.Login.Username = value
106+
}
107+
if value, err := k.DecryptStr(result.Login.Password); err != nil {
108+
return nil, err
109+
} else {
110+
result.Login.Password = value
111+
}
112+
if value, err := k.DecryptStr(result.Login.URI); err != nil {
113+
return nil, err
114+
} else {
115+
result.Login.URI = value
116+
}
108117
}
109118
return result, nil
110119
}
111-
112-
/*
113-
"collectionIds": [
114-
"86f7c94b-12a0-4eb2-bb0e-aedb007de863"
115-
],
116-
"folderId": null,
117-
"favorite": false,
118-
"edit": true,
119-
"": true,
120-
"id": "b5f097b5-b4a5-4a87-9b99-aedb007e6de0",
121-
"organizationId": "9e18928b-72ca-45c6-aa83-aedb007de85a",
122-
"type": 1,
123-
"data": {
124-
"uri": null,
125-
"uris": null,
126-
"username": "2.DAvbumAOG0xC6GqbJrhpnA==|HnOHH11CfVNKhlZ6O4qw2cu2auJ8Htny21fzce8K+Mk=|CEMiSK11mlcKUlQbYjDc0geZKX4Lf4wVd0HhvbvsXuY=",
127-
"password": "2.4abHZh9TmpDgSrw3KtdKeA==|Z5mniOuc5fafK+wMTv8gog==|2GDJ7tV8sz4cjqoUo/4wIQdgKay2QcEevwj7QqrK/XA=",
128-
"passwordRevisionDate": null,
129-
"totp": null,
130-
"autofillOnPageLoad": null,
131-
"name": "2.fkAxwCyKYwn06kULey5wnQ==|Xtwh+fpHZ0MEm0EAljGF5g==|89uI/dnpvGIfZDn8r3xqgNBCgezXLS73KK5fyupC6CQ=",
132-
"notes": null,
133-
"fields": null,
134-
"passwordHistory": null
135-
},
136-
"name": "2.fkAxwCyKYwn06kULey5wnQ==|Xtwh+fpHZ0MEm0EAljGF5g==|89uI/dnpvGIfZDn8r3xqgNBCgezXLS73KK5fyupC6CQ=",
137-
"notes": null,
138-
"login": {
139-
"uri": null,
140-
"uris": null,
141-
"username": "2.DAvbumAOG0xC6GqbJrhpnA==|HnOHH11CfVNKhlZ6O4qw2cu2auJ8Htny21fzce8K+Mk=|CEMiSK11mlcKUlQbYjDc0geZKX4Lf4wVd0HhvbvsXuY=",
142-
"password": "2.4abHZh9TmpDgSrw3KtdKeA==|Z5mniOuc5fafK+wMTv8gog==|2GDJ7tV8sz4cjqoUo/4wIQdgKay2QcEevwj7QqrK/XA=",
143-
"passwordRevisionDate": null,
144-
"totp": null,
145-
"autofillOnPageLoad": null
146-
},
147-
"card": null,
148-
"identity": null,
149-
"secureNote": null,
150-
"fields": null,
151-
"passwordHistory": null,
152-
"attachments": null,
153-
"organizationUseTotp": false,
154-
"revisionDate": "2022-07-23T07:40:18.88Z",
155-
"deletedDate": null,
156-
"reprompt": 0,
157-
"object": "cipherDetails"
158-
*/

pkg/bitwarden/schema/folder.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"encoding/json"
55
"io"
66
"time"
7+
8+
"github.com/mutablelogic/go-client/pkg/bitwarden/crypto"
79
)
810

911
///////////////////////////////////////////////////////////////////////////////
@@ -32,9 +34,9 @@ func (f *Folders) Write(w io.Writer) error {
3234
}
3335

3436
// Decrypt a folder
35-
func (f Folder) Decrypt(s *Session) (Crypter, error) {
37+
func (f Folder) Decrypt(k *crypto.CryptoKey) (Crypter, error) {
3638
result := &f
37-
if value, err := s.DecryptStr(result.Name); err != nil {
39+
if value, err := k.DecryptStr(result.Name); err != nil {
3840
return nil, err
3941
} else {
4042
result.Name = value

pkg/bitwarden/schema/iterator.go

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package schema
22

3-
import "fmt"
3+
import (
4+
// Packages
5+
crypto "github.com/mutablelogic/go-client/pkg/bitwarden/crypto"
6+
7+
// Namespace imports
8+
. "github.com/djthorpe/go-errors"
9+
)
410

511
/////////////////////////////////////////////////////////////////////////////////
612
// TYPES
713

814
type Iterable interface {
15+
Crypter
916
*Folder | *Cipher
1017
}
1118

@@ -14,20 +21,30 @@ type Iterator[T Iterable] interface {
1421
// Return next value or nil if there are no more values
1522
Next() T
1623

17-
// Decrypt the value
18-
Decrypt(T) T
24+
// Compute and return the encryption/decryption key from the profile,
25+
// password and KDF parameters
26+
CryptKey(*Profile, string, Kdf) (*crypto.CryptoKey, error)
27+
28+
// CanCrypt returns true if the iterator has a key
29+
// for encryption and decryption
30+
CanCrypt() bool
31+
32+
// Decrypt the value and return a copy of it
33+
Decrypt(T) (T, error)
1934
}
2035

2136
// Concrete iterator
2237
type iterator[T Iterable] struct {
23-
n int
24-
values []T
38+
n int
39+
values []T
40+
cryptKey *crypto.CryptoKey
2541
}
2642

2743
/////////////////////////////////////////////////////////////////////////////////
2844
// LIFECYCLE
2945

30-
func NewIterator[T Iterable](profile *Profile, values []T) Iterator[T] {
46+
// NewIterator returns a new iterator which can be used to iterate over values
47+
func NewIterator[T Iterable](values []T) Iterator[T] {
3148
iterator := new(iterator[T])
3249
iterator.values = values
3350
return iterator
@@ -36,6 +53,7 @@ func NewIterator[T Iterable](profile *Profile, values []T) Iterator[T] {
3653
/////////////////////////////////////////////////////////////////////////////////
3754
// PUBLIC METHODS
3855

56+
// Return the next value or nil if there are no more values
3957
func (i *iterator[T]) Next() T {
4058
var result T
4159
if i.n < len(i.values) {
@@ -45,7 +63,39 @@ func (i *iterator[T]) Next() T {
4563
return result
4664
}
4765

48-
func (i *iterator[T]) Decrypt(v T) T {
49-
fmt.Printf("TODO: Decrypt %T\n", v)
50-
return v
66+
// CanCrypt returns true if the iterator has a key for encryption and decryption
67+
func (i *iterator[T]) CanCrypt() bool {
68+
return i.cryptKey != nil
69+
}
70+
71+
// CryptKey computes and returns the encryption key. The key is cached by the iterator
72+
// for use in the Decrypt and Encrypt function calls
73+
func (i *iterator[T]) CryptKey(profile *Profile, passwd string, kdf Kdf) (*crypto.CryptoKey, error) {
74+
if profile == nil || passwd == "" || kdf.Iterations == 0 {
75+
return nil, ErrBadParameter.With("DecryptKey")
76+
}
77+
78+
key, err := profile.MakeKey(kdf, passwd)
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
// Cache the key
84+
i.cryptKey = key
85+
return key, nil
86+
}
87+
88+
// Decrypt T and return a new copy of T
89+
func (i *iterator[T]) Decrypt(v T) (T, error) {
90+
if v == nil {
91+
return nil, nil
92+
} else if i.cryptKey == nil {
93+
return nil, ErrNotAuthorized.With("No decryption key")
94+
} else if v, err := v.Decrypt(i.cryptKey); err != nil {
95+
return nil, err
96+
} else if v, ok := v.(T); !ok {
97+
panic("Unexpected type")
98+
} else {
99+
return v, nil
100+
}
51101
}

0 commit comments

Comments
 (0)