1pass 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require '1pass'
4
+ require 'optparse'
5
+ require 'ostruct'
6
+ require 'highline/import'
7
+
8
+ options = OpenStruct.new
9
+
10
+ opts = OptionParser.new do |opts|
11
+ opts.banner = "Usage: 1pass [options]"
12
+
13
+ opts.on("-l", "--list", "List all keychain entries") do |l|
14
+ options.list = true
15
+ end
16
+
17
+ opts.on("-k", "--key [key-name]", "Get details for given key name") do |k|
18
+ options.key = k
19
+ end
20
+
21
+ opts.on("-f", "--field [field-name]", "Get value for given field. Key should also be specified with -k or --key") do |f|
22
+ options.field = f
23
+ end
24
+ end
25
+
26
+ opts.parse!(ARGV)
27
+
28
+ agile_keychain = AgileKeychain.new
29
+
30
+ if(options.list)
31
+ agile_keychain.list
32
+ elsif(options.key)
33
+ master_password = ask("Enter your master password: ") { |q| q.echo = "*" }
34
+ agile_keychain.load(master_password, options.key, options.field)
35
+ else
36
+ puts opts
37
+ end
@@ -0,0 +1,20 @@
1
+ require "1pass/version"
2
+ require "keychain"
3
+
4
+ class AgileKeychain
5
+ def initialize(path=nil)
6
+ path = path || "#{ENV["HOME"]}/Library/Application Support/1Password/1Password.agilekeychain"
7
+ @keychain = Keychain.new(path)
8
+ end
9
+
10
+ def list
11
+ @keychain.content.items.map {|i| puts i.name}
12
+ end
13
+
14
+ def load(master_password, key_name, field_name=nil)
15
+ @keychain.unlock(master_password)
16
+ key = @keychain.get(key_name)
17
+ return unless key
18
+ puts field_name ? key.find(field_name) : key.fields
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module OnePass
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,19 @@
1
+ class Content
2
+ attr_reader :items
3
+
4
+ def initialize(data)
5
+ @items = data.map {|item| ContentItem.new item}
6
+ end
7
+
8
+ def find(name)
9
+ @items.select {|i| i.name == name}.first
10
+ end
11
+ end
12
+
13
+ class ContentItem
14
+ attr_reader :uuid, :name, :type
15
+
16
+ def initialize(item)
17
+ @uuid, @type, @name, @url, @timestamp, @folder, @strength, @trashed = item
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ require 'openssl'
2
+
3
+ class Decrypt
4
+ def self.base64_decode(base64_encoded_string)
5
+ decoded_data = base64_encoded_string.unpack('m')[0]
6
+ salt = decoded_data[8..15]
7
+ data = decoded_data[16..decoded_data.length-1]
8
+ {salt: salt, data: data}
9
+ end
10
+
11
+ def self.aes_decrypt(data, key, iv)
12
+ decipher = OpenSSL::Cipher::AES.new(128, :CBC)
13
+ decipher.decrypt
14
+ decipher.key = key
15
+ decipher.iv = iv
16
+ begin
17
+ plain = decipher.update(data)
18
+ plain << decipher.final
19
+ rescue OpenSSL::Cipher::CipherError
20
+ nil
21
+ end
22
+ end
23
+
24
+ def self.derive_pbkdf2(password, salt, iterations)
25
+ key_and_iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, salt, iterations, 32)
26
+ {key: key_and_iv[0,16], iv: key_and_iv[16..-1]}
27
+ end
28
+
29
+ def self.derive_openssl(key, content_salt)
30
+ key = key[0,1024]
31
+ key_and_iv = ""
32
+ prev = ""
33
+
34
+ while key_and_iv.length < 32 do
35
+ prev = Digest::MD5.digest(prev + key + content_salt)
36
+ key_and_iv << prev
37
+ end
38
+
39
+ {key: key_and_iv[0,16], iv: key_and_iv[16..-1]}
40
+ end
41
+
42
+ def self.decrypt_pbkdf2(master_password, data, iterations)
43
+ encrypted = base64_decode(data)
44
+ key_and_iv = derive_pbkdf2(master_password, encrypted[:salt], iterations)
45
+ aes_decrypt(encrypted[:data], key_and_iv[:key], key_and_iv[:iv])
46
+ end
47
+
48
+ def self.decrypt_ssl(master_key, validation)
49
+ encrypted = base64_decode(validation)
50
+ key_and_iv = derive_openssl(master_key, encrypted[:salt])
51
+ aes_decrypt(encrypted[:data], key_and_iv[:key], key_and_iv[:iv])
52
+ end
53
+ end
@@ -0,0 +1,32 @@
1
+ require 'decrypt'
2
+
3
+ class EncryptionKey
4
+ attr_reader :items
5
+
6
+ def initialize(data)
7
+ @items = data['list'].map {|k| EncryptionKeyItem.new k}
8
+ end
9
+
10
+ def unlock(password)
11
+ @items.collect {|ek| ek.unlock password}.all?
12
+ end
13
+
14
+ def get(identifier)
15
+ @items.select {|ek| ek.identifier == identifier}.first
16
+ end
17
+ end
18
+
19
+ class EncryptionKeyItem
20
+ attr_reader :identifier, :decrypted_master_key
21
+
22
+ def initialize(hash_)
23
+ @identifier, @data, @validation, @iterations = hash_.values_at("identifier", "data", "validation", "iterations")
24
+ end
25
+
26
+ def unlock(password)
27
+ @decrypted_master_key = Decrypt.decrypt_pbkdf2(password, @data, @iterations)
28
+ return false unless @decrypted_master_key
29
+ validation_key = Decrypt.decrypt_ssl(@decrypted_master_key, @validation)
30
+ @decrypted_master_key == validation_key
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ class Key
2
+ attr_reader :key_id, :encrypted
3
+
4
+ def types
5
+ {"webforms.WebForm" => WebForm,
6
+ "passwords.Password" => Password}
7
+ end
8
+
9
+ def initialize(hash_)
10
+ @key_id, @encrypted, @type_name = hash_.values_at("keyID", "encrypted", "typeName")
11
+ end
12
+
13
+ def decrypt(encryption_key)
14
+ decrypted_master_key = encryption_key.get(@key_id).decrypted_master_key
15
+ return unless decrypted_master_key
16
+ decrypted_content = JSON.parse Decrypt.decrypt_ssl(decrypted_master_key, @encrypted)
17
+ types[@type_name].new decrypted_content
18
+ end
19
+ end
20
+
21
+ class WebForm
22
+ attr_reader :fields
23
+
24
+ def initialize(hash_)
25
+ @notes_plain, @fields = hash_.values_at("notesPlain", "fields")
26
+ end
27
+
28
+ def find(name)
29
+ field = @fields.select {|f| f["name"] == name || f["designation"] == name}.first
30
+ field["value"]
31
+ end
32
+ end
33
+
34
+ class Password
35
+ attr_reader :fields
36
+
37
+ def find(name)
38
+ @fields[name]
39
+ end
40
+
41
+ def initialize(hash_)
42
+ @fields = hash_
43
+ end
44
+ end
@@ -0,0 +1,39 @@
1
+ require 'json'
2
+ require 'encryption_key'
3
+ require 'key'
4
+ require 'content'
5
+
6
+ class Keychain
7
+ attr_reader :encryption_key, :content
8
+
9
+ def initialize(path)
10
+ @path = path
11
+ load_encryption_keys
12
+ load_contents
13
+ end
14
+
15
+ def unlock(password)
16
+ @unlocked = @encryption_key.unlock(password)
17
+ end
18
+
19
+ def get(name)
20
+ item = @content.find(name)
21
+ return unless item
22
+ key = Key.new load_file(item.uuid + ".1password")
23
+ key.decrypt(@encryption_key)
24
+ end
25
+
26
+ private
27
+ def load_encryption_keys
28
+ @encryption_key = EncryptionKey.new load_file("encryptionKeys.js")
29
+ end
30
+
31
+ def load_contents
32
+ @content = Content.new load_file("contents.js")
33
+ end
34
+
35
+ def load_file(file_name)
36
+ file = File.join(@path, "data", "default", file_name)
37
+ JSON.parse(IO.read(file))
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: 1pass
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Lokeshwaran
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Command line client for AgileBits 1Password
47
+ email:
48
+ - dlokesh@gmail.com
49
+ executables:
50
+ - 1pass
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/1pass.rb
55
+ - lib/1pass/version.rb
56
+ - lib/content.rb
57
+ - lib/decrypt.rb
58
+ - lib/encryption_key.rb
59
+ - lib/key.rb
60
+ - lib/keychain.rb
61
+ - bin/1pass
62
+ homepage: https://github.com/dlokesh/1pass
63
+ licenses:
64
+ - MIT
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ segments:
76
+ - 0
77
+ hash: -2624167293769510500
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ segments:
85
+ - 0
86
+ hash: -2624167293769510500
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.25
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: 1pass-0.1.0
93
+ test_files: []