yakg 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ .bundle
2
+ .DS_Store
3
+ vendor/bundle
4
+ default.pem
5
+ *.swo
6
+ resources_dump.json
7
+ hosts.txt
8
+ out
9
+ coverage
10
+ todo
11
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+ gem "corefoundation", :git => "https://github.com/tmaher/corefoundation.git"
data/Gemfile.lock ADDED
@@ -0,0 +1,43 @@
1
+ GIT
2
+ remote: https://github.com/tmaher/corefoundation.git
3
+ revision: 73a97c6e7d268d21d17f671c94e98185153c5e3f
4
+ specs:
5
+ corefoundation (0.1.4)
6
+ ffi
7
+
8
+ PATH
9
+ remote: .
10
+ specs:
11
+ yakg (0.0.1)
12
+ corefoundation
13
+ ffi
14
+
15
+ GEM
16
+ remote: https://rubygems.org/
17
+ specs:
18
+ diff-lcs (1.1.3)
19
+ excon (0.16.10)
20
+ ffi (1.2.0)
21
+ highline (1.6.15)
22
+ rake (10.0.2)
23
+ rspec (2.12.0)
24
+ rspec-core (~> 2.12.0)
25
+ rspec-expectations (~> 2.12.0)
26
+ rspec-mocks (~> 2.12.0)
27
+ rspec-core (2.12.0)
28
+ rspec-expectations (2.12.0)
29
+ diff-lcs (~> 1.1.3)
30
+ rspec-mocks (2.12.0)
31
+ woof_util (0.0.16)
32
+ excon (~> 0.16.10)
33
+ highline (~> 1.6.15)
34
+
35
+ PLATFORMS
36
+ ruby
37
+
38
+ DEPENDENCIES
39
+ corefoundation!
40
+ rake
41
+ rspec
42
+ woof_util
43
+ yakg!
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/setup"
2
+ require "rspec/core/rake_task"
3
+ require "woof_util/gem_rake_tasks"
4
+
5
+ RSpec::Core::RakeTask.new "spec"
6
+ WoofUtil::GemRakeTasks.create_tasks
7
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,220 @@
1
+ # https://developer.apple.com/library/mac/#documentation/security/Reference/keychainservices/Reference/reference.html
2
+ require "ffi"
3
+ require "corefoundation"
4
+
5
+ class Yakg
6
+ module Backend
7
+
8
+ def self.framework name
9
+ "/System/Library/Frameworks/#{name}.framework/#{name}"
10
+ end
11
+
12
+ class KeychainError < Exception
13
+ end
14
+
15
+ extend FFI::Library
16
+ NULL = FFI::Pointer::NULL
17
+ ffi_lib(FFI::Library::LIBC,
18
+ framework(:CoreFoundation),
19
+ framework(:Security))
20
+
21
+ attach_function :malloc, [:size_t], :pointer
22
+ attach_function :free, [:pointer], :void
23
+ attach_function :CFRelease, [:pointer], :void
24
+
25
+ attach_function(:SecKeychainItemFreeContent,
26
+ [:pointer, :pointer], :uint32)
27
+
28
+ attach_function(:SecKeychainFindGenericPassword,
29
+ [:pointer, :uint32, :string, :uint32, :string,
30
+ :pointer, :pointer, :pointer],
31
+ :uint32)
32
+
33
+ attach_function(:SecKeychainAddGenericPassword,
34
+ [:pointer, :uint32, :string, :uint32, :string,
35
+ :uint32, :pointer, :pointer],
36
+ :uint32)
37
+
38
+ attach_function(:SecKeychainItemDelete, [:pointer], :uint32)
39
+ attach_function(:SecCopyErrorMessageString, [:uint32, :pointer],
40
+ :pointer)
41
+
42
+ attach_function(:SecKeychainItemModifyContent,
43
+ [:pointer, :pointer, :uint32, :string], :uint32)
44
+
45
+ attach_function(:SecKeychainSearchCreateFromAttributes,
46
+ [:pointer, :uint32, :pointer, :pointer], :uint32)
47
+ attach_function(:SecKeychainSearchCopyNext,
48
+ [:pointer, :pointer], :uint32)
49
+ attach_function(:SecKeychainItemCopyAttributesAndData,
50
+ [:pointer, :pointer, :pointer, :pointer,
51
+ :pointer, :pointer], :uint32)
52
+ attach_function(:SecKeychainItemFreeAttributesAndData,
53
+ [:pointer, :pointer], :uint32)
54
+
55
+ def self.error_message code
56
+ CF::String.new(SecCopyErrorMessageString(code, NULL)).to_s
57
+ end
58
+
59
+ def self.raise_error? code
60
+ raise KeychainError.new(error_message code) if code != 0
61
+ code
62
+ end
63
+
64
+ def self.delete acct, svc
65
+ pw_length = FFI::MemoryPointer.new :uint32
66
+ pw_val = FFI::MemoryPointer.new :pointer
67
+ item_ref = FFI::MemoryPointer.new :pointer
68
+ raise_error? SecKeychainFindGenericPassword(NULL, svc.length, svc,
69
+ acct.length, acct,
70
+ pw_length, pw_val,
71
+ item_ref)
72
+ raise_error? SecKeychainItemFreeContent(NULL, pw_val.read_pointer)
73
+ raise_error? SecKeychainItemDelete(item_ref.read_pointer)
74
+ CFRelease item_ref.read_pointer
75
+ true
76
+ end
77
+
78
+ def self.get acct, svc
79
+ pw_length = FFI::MemoryPointer.new :uint32
80
+ pw_val = FFI::MemoryPointer.new :pointer
81
+ retval = SecKeychainFindGenericPassword(NULL, svc.length, svc,
82
+ acct.length, acct,
83
+ pw_length, pw_val, NULL)
84
+ return nil unless 0 == retval
85
+ pw = pw_val.read_pointer.read_string(pw_length.read_int)
86
+ retval = SecKeychainItemFreeContent(NULL, pw_val.read_pointer)
87
+ return nil unless 0 == retval
88
+
89
+ pw
90
+ end
91
+
92
+ def self.set acct, value, svc
93
+ pw_length = FFI::MemoryPointer.new :uint32
94
+ pw_val = FFI::MemoryPointer.new :pointer
95
+ item_ref = FFI::MemoryPointer.new :pointer
96
+ retval = SecKeychainFindGenericPassword(NULL, svc.length, svc,
97
+ acct.length, acct,
98
+ pw_length, pw_val, item_ref)
99
+ if retval != 0
100
+ raise_error? SecKeychainAddGenericPassword(NULL, svc.length, svc,
101
+ acct.length, acct,
102
+ value.length, value, NULL)
103
+ else
104
+ raise_error? SecKeychainItemFreeContent(NULL, pw_val.read_pointer)
105
+ raise_error? SecKeychainItemModifyContent(item_ref.read_pointer, NULL,
106
+ value.length, value)
107
+ CFRelease item_ref.read_pointer
108
+ end
109
+ true
110
+ end
111
+
112
+ class SecKeychainAttributeList < FFI::Struct
113
+ layout(:count, :uint32,
114
+ :attr, :pointer)
115
+ end
116
+
117
+ def self.s2i s
118
+ s.unpack("N")[0].to_i
119
+ end
120
+
121
+ enum :SecItemAttr, [:kSecCreatiofnDateItemAttr, s2i('cdat'),
122
+ :kSecModDateItemAttr, s2i('mdat'),
123
+ :kSecDescriptionItemAttr, s2i('desc'),
124
+ :kSecCommentItemAttr, s2i('icmt'),
125
+ :kSecCreatorItemAttr, s2i('crtr'),
126
+ :kSecTypeItemAttr, s2i('type'),
127
+ :kSecScriptCodeItemAttr, s2i('scrp'),
128
+ :kSecLabelItemAttr, s2i('labl'),
129
+ :kSecInvisibleItemAttr, s2i('invi'),
130
+ :kSecNegativeItemAttr, s2i('nega'),
131
+ :kSecCustomIconItemAttr, s2i('cusi'),
132
+ :kSecAccountItemAttr, s2i('acct'),
133
+ :kSecServiceItemAttr, s2i('svce'),
134
+ :kSecGenericItemAttr, s2i('gena'),
135
+ :kSecSecurityDomainItemAttr, s2i('sdmn'),
136
+ :kSecServerItemAttr, s2i('srvr'),
137
+ :kSecAuthenticationTypeItemAttr, s2i('atyp'),
138
+ :kSecPortItemAttr, s2i('port'),
139
+ :kSecPathItemAttr, s2i('path'),
140
+ :kSecVolumeItemAttr, s2i('vlme'),
141
+ :kSecAddressItemAttr, s2i('addr'),
142
+ :kSecSignatureItemAttr, s2i('ssig'),
143
+ :kSecProtocolItemAttr, s2i('ptcl'),
144
+ :kSecCertificateType, s2i('ctyp'),
145
+ :kSecCertificateEncoding, s2i('cenc'),
146
+ :kSecCrlType, s2i('crtp'),
147
+ :kSecCrlEncoding, s2i('crnc'),
148
+ :kSecAlias, s2i('alis')
149
+ ]
150
+
151
+ class SecKeychainAttribute < FFI::Struct
152
+ layout(:tag, :SecItemAttr,
153
+ :length, :uint32,
154
+ :data, :pointer)
155
+ end
156
+
157
+ class SecKeychainAttributeInfo < FFI::Struct
158
+ layout(:count, :uint32,
159
+ :tag, :pointer,
160
+ :format, :pointer)
161
+ end
162
+
163
+
164
+ def self.list svc
165
+ search_ref = FFI::MemoryPointer.new :pointer
166
+ found_item = FFI::MemoryPointer.new :pointer
167
+ found_attr_list = FFI::MemoryPointer.new :pointer
168
+ svc_attr = SecKeychainAttribute.new
169
+
170
+ acct_info = SecKeychainAttributeInfo.new
171
+ acct_info[:count] = 1
172
+ acct_info[:tag] = FFI::MemoryPointer.new :uint32
173
+ acct_info[:tag].write_array_of_int [s2i("acct")]
174
+ acct_info[:format] = FFI::MemoryPointer.new :uint32
175
+ acct_info[:format].write_array_of_int [0]
176
+
177
+ svc_attr[:tag] = s2i "svce"
178
+ svc_attr[:length] = svc.length
179
+ svc_attr[:data] = FFI::MemoryPointer.from_string(svc)
180
+
181
+ search_attr_list = SecKeychainAttributeList.new
182
+ search_attr_list[:count] = 1
183
+ search_attr_list[:attr] = svc_attr.pointer
184
+
185
+ raise_error? SecKeychainSearchCreateFromAttributes(NULL,
186
+ s2i("genp"),
187
+ search_attr_list,
188
+ search_ref)
189
+
190
+ acct_names = []
191
+ while true
192
+ retval = SecKeychainSearchCopyNext(search_ref.read_pointer,
193
+ found_item)
194
+ # the magic "no more items" number
195
+ break if retval == 4294941996
196
+ raise_error? retval
197
+
198
+ raise_error? SecKeychainItemCopyAttributesAndData(found_item.read_pointer,
199
+ acct_info,
200
+ NULL,
201
+ found_attr_list,
202
+ NULL, NULL)
203
+ f = SecKeychainAttributeList.new found_attr_list.read_pointer
204
+ next if f[:count] != 1
205
+
206
+ a = SecKeychainAttribute.new f[:attr]
207
+ next if a[:tag] != :kSecAccountItemAttr
208
+
209
+ acct_names.push a[:data].get_string(0, a[:length])
210
+ end
211
+
212
+ raise_error? SecKeychainItemFreeAttributesAndData(found_attr_list.read_pointer,
213
+ NULL)
214
+ CFRelease search_ref.read_pointer
215
+
216
+ acct_names
217
+ end
218
+
219
+ end
220
+ end
data/lib/yakg.rb ADDED
@@ -0,0 +1,24 @@
1
+ require "yakg/backend"
2
+
3
+ class Yakg
4
+ @@DEFAULT_SERVICE_NAME = "ruby-yakg-gem"
5
+ def self.DEFAULT_SERVICE_NAME= x; @@DEFAULT_SERVICE_NAME = x; end
6
+ def self.DEFAULT_SERVICE_NAME ; @@DEFAULT_SERVICE_NAME; end
7
+
8
+ def self.set acct, value, svc=@@DEFAULT_SERVICE_NAME
9
+ Backend.set acct, value, svc
10
+ end
11
+
12
+ def self.get acct, svc=@@DEFAULT_SERVICE_NAME
13
+ Backend.get acct, svc
14
+ end
15
+
16
+ def self.unset acct, svc=@@DEFAULT_SERVICE_NAME
17
+ Backend.delete acct, svc
18
+ end
19
+
20
+ def self.list svc=@@DEFAULT_SERVICE_NAME
21
+ Backend.list svc
22
+ end
23
+
24
+ end
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ $: << File.dirname(__FILE__) + '/../lib'
5
+
6
+ require 'yakg'
7
+ Yakg.DEFAULT_SERVICE_NAME = "yakg-rspec"
8
+ require 'securerandom'
9
+
10
+ RSpec.configure do |config|
11
+ config.color_enabled = true
12
+
13
+ # Use color not only in STDOUT but also in pagers and files
14
+ config.tty = true
15
+
16
+ # Use the specified formatter
17
+ #config.formatter = :documentation # :progress, :html, :textmate
18
+ end
data/spec/yakg_spec.rb ADDED
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yakg do
4
+ pw_name = SecureRandom.hex 4
5
+ pw_value = SecureRandom.hex 4
6
+ new_pw_name = SecureRandom.hex 4
7
+
8
+ it "pre-nukes everything" do
9
+ Yakg.list.each {|x| Yakg.unset x }
10
+ Yakg.list.should eq []
11
+ end
12
+
13
+ it "confirms nonexistent keys are nil" do
14
+ Yakg.get(pw_name).should be_nil
15
+ end
16
+
17
+ it "can create a key" do
18
+ Yakg.set(pw_name, pw_value).should be_true
19
+ Yakg.get(pw_name).should eq pw_value
20
+ end
21
+
22
+ it "can list keys" do
23
+ Yakg.set(new_pw_name, SecureRandom.hex(4))
24
+ Yakg.list.should eq [pw_name, new_pw_name]
25
+ end
26
+
27
+ it "can update keys" do
28
+ new_pw_value = SecureRandom.hex 4
29
+ Yakg.set(pw_name, new_pw_value).should be_true
30
+ Yakg.get(pw_name).should eq new_pw_value
31
+ end
32
+
33
+ it "can delete a key" do
34
+ Yakg.unset(pw_name).should be_true
35
+ Yakg.get(pw_name).should be_nil
36
+ end
37
+
38
+ end
data/test/cribsheet.md ADDED
@@ -0,0 +1,11 @@
1
+ set
2
+ `security add-generic-password -a keyname -s service_name -w password`
3
+
4
+ get
5
+ `security find-generic-password -a somekey -s service_name -w`
6
+
7
+ delete
8
+ `security delete-generic-password -a somekey -s service_name`
9
+
10
+ update
11
+ delete and add, or call add with `-U`
data/test/mytest.rb ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "bundler/setup"
5
+ require "lib/yakg"
6
+
7
+ Yakg.DEFAULT_SERVICE_NAME = "yakg-rspec"
8
+ Yakg.list.each {|x| Yakg.unset x }
9
+
10
+ exit 0
data/yakg.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ require 'find'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'yakg'
5
+ s.version = File.read("VERSION").chomp
6
+ s.date = '2012-11-25'
7
+ s.summary = 'Yet Another Keyring Gem'
8
+ s.description = "Use Ruby's DL mechanism to access the MacOS Keychain"
9
+ s.authors = ["Tom Maher"]
10
+ s.email = "tmaher@tursom.org"
11
+ s.files = `git ls-files`.split("\n")
12
+ s.homepage = "https://github.com/tmaher/yakg"
13
+ s.add_dependency "ffi"
14
+ s.add_dependency "corefoundation"
15
+ s.add_development_dependency "woof_util"
16
+ s.add_development_dependency "rake"
17
+ s.add_development_dependency "rspec"
18
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yakg
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Tom Maher
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-11-25 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ version_requirements: &id001 !ruby/object:Gem::Requirement
22
+ none: false
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ hash: 3
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ prerelease: false
31
+ type: :runtime
32
+ name: ffi
33
+ requirement: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ version_requirements: &id002 !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ hash: 3
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ prerelease: false
45
+ type: :runtime
46
+ name: corefoundation
47
+ requirement: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ version_requirements: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ hash: 3
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ prerelease: false
59
+ type: :development
60
+ name: woof_util
61
+ requirement: *id003
62
+ - !ruby/object:Gem::Dependency
63
+ version_requirements: &id004 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ hash: 3
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ prerelease: false
73
+ type: :development
74
+ name: rake
75
+ requirement: *id004
76
+ - !ruby/object:Gem::Dependency
77
+ version_requirements: &id005 !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 3
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ prerelease: false
87
+ type: :development
88
+ name: rspec
89
+ requirement: *id005
90
+ description: Use Ruby's DL mechanism to access the MacOS Keychain
91
+ email: tmaher@tursom.org
92
+ executables: []
93
+
94
+ extensions: []
95
+
96
+ extra_rdoc_files: []
97
+
98
+ files:
99
+ - .gitignore
100
+ - Gemfile
101
+ - Gemfile.lock
102
+ - Rakefile
103
+ - VERSION
104
+ - lib/yakg.rb
105
+ - lib/yakg/backend.rb
106
+ - spec/spec_helper.rb
107
+ - spec/yakg_spec.rb
108
+ - test/cribsheet.md
109
+ - test/mytest.rb
110
+ - yakg.gemspec
111
+ homepage: https://github.com/tmaher/yakg
112
+ licenses: []
113
+
114
+ post_install_message:
115
+ rdoc_options: []
116
+
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ hash: 3
125
+ segments:
126
+ - 0
127
+ version: "0"
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ hash: 3
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ requirements: []
138
+
139
+ rubyforge_project:
140
+ rubygems_version: 1.8.24
141
+ signing_key:
142
+ specification_version: 3
143
+ summary: Yet Another Keyring Gem
144
+ test_files: []
145
+