summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mcrute@gmail.com>2016-11-26 14:43:59 -0800
committerMike Crute <mcrute@gmail.com>2016-11-26 14:43:59 -0800
commitb96e751c8944c1b63bf78033c76892c72abad432 (patch)
treee0bda7ce52d88be32294acb0e8d55ce481984fe4
downloadgo-1password-b96e751c8944c1b63bf78033c76892c72abad432.tar.bz2
go-1password-b96e751c8944c1b63bf78033c76892c72abad432.tar.xz
go-1password-b96e751c8944c1b63bf78033c76892c72abad432.zip
Initial import
-rw-r--r--main.go446
1 files changed, 446 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..88cca34
--- /dev/null
+++ b/main.go
@@ -0,0 +1,446 @@
1package main
2
3import (
4 "bytes"
5 "crypto/aes"
6 "crypto/cipher"
7 "crypto/hmac"
8 "crypto/sha256"
9 "crypto/sha512"
10 "encoding/base64"
11 "encoding/binary"
12 "encoding/json"
13 "errors"
14 "fmt"
15 "io/ioutil"
16 "os/user"
17 "path"
18 "strings"
19 "syscall"
20 "time"
21
22 "golang.org/x/crypto/pbkdf2"
23 "golang.org/x/crypto/ssh/terminal"
24)
25
26var Categories = map[string]string{
27 "Login": "001",
28 "Credit Card": "002",
29 "Secure Note": "003",
30 "Identity": "004",
31 "Password": "005",
32 "Tombstone": "099",
33 "Software License": "100",
34 "Bank Account": "101",
35 "Database": "102",
36 "Driver License": "103",
37 "Outdoor License": "104",
38 "Membership": "105",
39 "Passport": "106",
40 "Rewards": "107",
41 "SSN": "108",
42 "Router": "109",
43 "Server": "110",
44 "Email": "111",
45}
46
47var CategoriesRev = map[string]string{
48 "001": "Login",
49 "002": "Credit Card",
50 "003": "Secure Note",
51 "004": "Identity",
52 "005": "Password",
53 "099": "Tombstone",
54 "100": "Software License",
55 "101": "Bank Account",
56 "102": "Database",
57 "103": "Driver License",
58 "104": "Outdoor License",
59 "105": "Membership",
60 "106": "Passport",
61 "107": "Rewards",
62 "108": "SSN",
63 "109": "Router",
64 "110": "Server",
65 "111": "Email",
66}
67
68type Key struct {
69 Encryption []byte
70 Mac []byte
71}
72
73func NewKey(combined []byte) *Key {
74 if len(combined) != 64 {
75 panic("Invalid key size")
76 }
77
78 return &Key{combined[:32], combined[32:64]}
79}
80
81func NewKeyPBKDF2(pass []byte, p *OPProfile) *Key {
82 return NewKey(pbkdf2.Key(pass, p.Salt, p.Iterations, 64, sha512.New))
83}
84
85type OPBandItem struct {
86 Profile *OPProfile `json:"-"`
87 UUID string `json:"uuid"`
88 Category string `json:"category"`
89 CategoryName string `json:"-"`
90 Folder string `json:"folder"`
91 Trashed bool `json:"trashed"`
92 Favorite int `json:"fave"`
93 Data []byte `json:"d"`
94 HMAC []byte `json:"hmac"`
95 Key []byte `json:"k"`
96 Overview []byte `json:"o"`
97 Created time.Time `json:"created"`
98 TransactionTime time.Time `json:"tx"`
99 Updated time.Time `json:"updated"`
100}
101
102func (i *OPBandItem) UnmarshalJSON(data []byte) error {
103 var err error
104 type LocalItem OPBandItem
105
106 ti := &struct {
107 Data string `json:"d"`
108 HMAC string `json:"hmac"`
109 Key string `json:"k"`
110 Overview string `json:"o"`
111 Created int64 `json:"created"`
112 Updated int64 `json:"updated"`
113 TransactionTime int64 `json:"tx"`
114 *LocalItem
115 }{
116 LocalItem: (*LocalItem)(i),
117 }
118
119 if err = json.Unmarshal(data, &ti); err != nil {
120 return err
121 }
122
123 i.Data, err = base64.StdEncoding.DecodeString(ti.Data)
124 if err != nil {
125 return err
126 }
127
128 i.HMAC, err = base64.StdEncoding.DecodeString(ti.HMAC)
129 if err != nil {
130 return err
131 }
132
133 i.Key, err = base64.StdEncoding.DecodeString(ti.Key)
134 if err != nil {
135 return err
136 }
137
138 i.Overview, err = base64.StdEncoding.DecodeString(ti.Overview)
139 if err != nil {
140 return err
141 }
142
143 i.CategoryName = CategoriesRev[i.Category]
144 i.Created = time.Unix(ti.Created, 0)
145 i.Updated = time.Unix(ti.Updated, 0)
146 i.TransactionTime = time.Unix(ti.TransactionTime, 0)
147
148 return nil
149}
150
151func (i *OPBandItem) DecryptKey(key *Key) (*Key, error) {
152 data := i.Key
153
154 mac := hmac.New(sha256.New, key.Mac)
155 mac.Write(data[:len(data)-32])
156 expectedMac := mac.Sum(nil)
157
158 if !hmac.Equal(data[len(data)-32:len(data)], expectedMac) {
159 return nil, errors.New("HMAC does not match")
160 }
161
162 iv := data[:16]
163 ct := data[16 : len(data)-32]
164
165 block, err := aes.NewCipher(key.Encryption)
166 if err != nil {
167 return nil, err
168 }
169
170 if len(ct)%aes.BlockSize != 0 {
171 return nil, errors.New("Ciphertext is not multiple of block size")
172 }
173
174 mode := cipher.NewCBCDecrypter(block, iv)
175 mode.CryptBlocks(ct, ct)
176
177 return NewKey(ct), nil
178}
179
180func (i *OPBandItem) DecryptData() ([]byte, error) {
181 k, err := i.DecryptKey(i.Profile.masterKey)
182 if err != nil {
183 return nil, err
184 }
185
186 d, err := ParseOpdata01(i.Data, k)
187 if err != nil {
188 return nil, err
189 }
190
191 return d, nil
192}
193
194func (i *OPBandItem) DecryptOverview() ([]byte, error) {
195 o, err := ParseOpdata01(i.Overview, i.Profile.overviewKey)
196 if err != nil {
197 return nil, err
198 }
199
200 return o, nil
201}
202
203type OPProfile struct {
204 Path string `json:"-"`
205 UUID string `json:"uuid"`
206 ProfileName string `json:"profileName"`
207 Iterations int `json:"iterations"`
208 Salt []byte `json:"salt"`
209 MasterKey []byte `json:"masterKey"`
210 OverviewKey []byte `json:"overviewKey"`
211 CreatedAt time.Time `json:"createdAt"`
212 UpdatedAt time.Time `json:"updatedAt"`
213 LastUpdatedBy string `json:"lastUpdatedBy"`
214
215 masterKey *Key
216 overviewKey *Key
217 Items map[string]*OPBandItem `json:"-"`
218}
219
220func (p *OPProfile) UnmarshalJSON(data []byte) error {
221 var err error
222 type LocalProfile OPProfile
223
224 tp := &struct {
225 CreatedAt int64 `json:"createdAt"`
226 UpdatedAt int64 `json:"updatedAt"`
227 Salt string `json:"salt"`
228 MasterKey string `json:"masterKey"`
229 OverviewKey string `json:"overviewKey"`
230 *LocalProfile
231 }{
232 LocalProfile: (*LocalProfile)(p),
233 }
234
235 if err = json.Unmarshal(data, &tp); err != nil {
236 return err
237 }
238
239 p.Salt, err = base64.StdEncoding.DecodeString(tp.Salt)
240 if err != nil {
241 return err
242 }
243
244 p.MasterKey, err = base64.StdEncoding.DecodeString(tp.MasterKey)
245 if err != nil {
246 return err
247 }
248
249 p.OverviewKey, err = base64.StdEncoding.DecodeString(tp.OverviewKey)
250 if err != nil {
251 return err
252 }
253
254 p.CreatedAt = time.Unix(tp.CreatedAt, 0)
255 p.UpdatedAt = time.Unix(tp.UpdatedAt, 0)
256
257 return nil
258}
259
260func (p *OPProfile) Unlock(masterpass []byte) error {
261 var err error
262 derived := NewKeyPBKDF2(masterpass, p)
263
264 p.masterKey, err = DeriveKey(p.MasterKey, derived)
265 if err != nil {
266 return err
267 }
268
269 p.overviewKey, err = DeriveKey(p.OverviewKey, derived)
270 if err != nil {
271 return err
272 }
273
274 return nil
275}
276
277func (p *OPProfile) LoadAllBands() {
278 for i := 0; i < 16; i++ {
279 _ = p.LoadBand(fmt.Sprintf("%X", i))
280 }
281}
282
283func (p *OPProfile) LoadBand(band string) error {
284 c, err := ioutil.ReadFile(path.Join(p.Path, "default", fmt.Sprintf("band_%s.js", band)))
285 if err != nil {
286 return err
287 }
288
289 // JSON surrounded by "ld({...json...});"
290 b := make(map[string]*OPBandItem)
291 if err = json.Unmarshal(c[3:len(c)-2], &b); err != nil {
292 return err
293 }
294
295 for k, v := range b {
296 v.Profile = p
297 o, err := v.DecryptOverview()
298 if err == nil {
299 v.Overview = o
300 }
301 p.Items[k] = v
302 }
303
304 return nil
305}
306
307func LoadProfile(vaultpath string, masterpass []byte) (*OPProfile, error) {
308 p, err := NewProfile(vaultpath)
309 if err != nil {
310 return nil, err
311 }
312
313 err = p.Unlock(masterpass)
314 if err != nil {
315 return nil, err
316 }
317
318 // Wipe master password buffer
319 for i, _ := range masterpass {
320 masterpass[i] = 0
321 }
322
323 p.LoadAllBands()
324
325 return p, nil
326}
327
328func NewProfile(vaultpath string) (*OPProfile, error) {
329 c, err := ioutil.ReadFile(path.Join(vaultpath, "default", "profile.js"))
330 if err != nil {
331 return nil, err
332 }
333
334 cut := bytes.Index(c, []byte("{"))
335 if cut == -1 {
336 return nil, errors.New("Profile not a valid JSON document")
337 }
338
339 // JSON surrounded by "var profile={...json...};"
340 p := &OPProfile{
341 Path: vaultpath,
342 Items: make(map[string]*OPBandItem),
343 }
344 if err = json.Unmarshal(c[cut:len(c)-1], &p); err != nil {
345 return nil, err
346 }
347
348 return p, nil
349}
350
351func ParseOpdata01(data []byte, key *Key) ([]byte, error) {
352 // Validate data header
353 if !bytes.Equal(data[:8], []byte("opdata01")) {
354 return nil, errors.New("opdata01 header mismatch")
355 }
356
357 // Validate HMAC before we continue
358 mac := hmac.New(sha256.New, key.Mac)
359 mac.Write(data[:len(data)-32])
360 expectedMac := mac.Sum(nil)
361
362 if !hmac.Equal(data[len(data)-32:len(data)], expectedMac) {
363 return nil, errors.New("HMAC does not match")
364 }
365
366 iv := data[16:32]
367 ct := data[32 : len(data)-32]
368 plaintextLen := binary.LittleEndian.Uint64(data[8:16])
369
370 block, err := aes.NewCipher(key.Encryption)
371 if err != nil {
372 return nil, err
373 }
374
375 if len(ct)%aes.BlockSize != 0 {
376 return nil, errors.New("Ciphertext is not multiple of block size")
377 }
378
379 mode := cipher.NewCBCDecrypter(block, iv)
380 mode.CryptBlocks(ct, ct)
381
382 // Copy so we can free the buffer with IV/padding prefix
383 out := make([]byte, plaintextLen)
384 copy(out, ct[len(ct)-int(plaintextLen):len(ct)])
385
386 return out, nil
387}
388
389func DeriveKey(data []byte, dkey *Key) (*Key, error) {
390 raw, err := ParseOpdata01(data, dkey)
391 if err != nil {
392 return nil, err
393 }
394
395 h := sha512.New()
396 h.Write(raw)
397
398 return NewKey(h.Sum(nil)), nil
399}
400
401func main() {
402 datapath := "~/Dropbox/1Password/1Password.opvault"
403 if strings.HasPrefix(datapath, "~/") {
404 u, _ := user.Current()
405 datapath = u.HomeDir + datapath[1:len(datapath)]
406 }
407
408 fmt.Print("Enter Password: ")
409 password, err := terminal.ReadPassword(int(syscall.Stdin))
410 if err != nil {
411 fmt.Println("Unable to read password")
412 return
413 }
414 fmt.Println("")
415
416 p, err := LoadProfile(datapath, password)
417 if err != nil {
418 fmt.Println(err)
419 return
420 }
421
422 // Login - CD05161569D347ADB401DE06D30A0A89
423 // Note - 7FF3565B434B47CF8906869BDCAD28C3
424 // Password - 7059A882C5F84DDCBD2EAD9EFFAA2B58
425 // Router - 6009533D5A3B483A93FC7A843C39EDED
426 // Server - 7BDDE92045834A5386D56576DAEDDE54
427 // Credit - 6B9AFF656D264EEF8A887F79D243AE0D
428 // SW License - 7529DADA9453426BAF8B23A931834B2A
429 // Database - 102
430 // Email - 111
431
432 item, ok := p.Items[""]
433 if !ok {
434 fmt.Println("UUID not found in profile")
435 return
436 }
437
438 itemD, err := item.DecryptData()
439 if err != nil {
440 fmt.Println("Error decoding item data")
441 fmt.Println(err)
442 return
443 }
444
445 fmt.Printf("[\"%s\", %s, %s]", item.CategoryName, item.Overview, itemD)
446}