yakg 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+