mr_keychain 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,6 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
6
+ README.markdown
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm use macruby-nightly@keychain --create
2
+
data/.yardopts ADDED
@@ -0,0 +1,4 @@
1
+ --protected
2
+ --private
3
+ --no-cache
4
+ --markup markdown
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rspec", "~> 2.4.0"
5
+ gem "yard", "~> 0.6.0"
6
+ gem "bluecloth", "~> 2.0.0"
7
+ gem "bundler", "~> 1.0.0"
8
+ gem "jeweler", "~> 1.5.2"
9
+ gem "rcov", ">= 0"
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,32 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ bluecloth (2.0.9)
5
+ diff-lcs (1.1.2)
6
+ git (1.2.5)
7
+ jeweler (1.5.2)
8
+ bundler (~> 1.0.0)
9
+ git (>= 1.2.5)
10
+ rake
11
+ rake (0.8.7)
12
+ rcov (0.9.9)
13
+ rspec (2.4.0)
14
+ rspec-core (~> 2.4.0)
15
+ rspec-expectations (~> 2.4.0)
16
+ rspec-mocks (~> 2.4.0)
17
+ rspec-core (2.4.0)
18
+ rspec-expectations (2.4.0)
19
+ diff-lcs (~> 1.1.2)
20
+ rspec-mocks (2.4.0)
21
+ yard (0.6.4)
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ bluecloth (~> 2.0.0)
28
+ bundler (~> 1.0.0)
29
+ jeweler (~> 1.5.2)
30
+ rcov
31
+ rspec (~> 2.4.0)
32
+ yard (~> 0.6.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Mark Rada
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,67 @@
1
+ Keychain
2
+ ========
3
+
4
+ A simple class for working with the Mac OS X keychain.
5
+
6
+ Reference
7
+ =========
8
+
9
+ To learn more about using the Keychain on OS X, see Apple's [Keychain Services Programming Guide](http://developer.apple.com/library/ios/#documentation/Security/Conceptual/keychainServConcepts/01introduction/introduction.html) and the [Keychain Services Reference](http://developer.apple.com/library/mac/#documentation/Security/Reference/keychainservices/Reference/reference.html).
10
+
11
+ Tips
12
+ ====
13
+
14
+ * You need to be careful about what key-value pairs you have stored in an item's attributes, they can sometimes mess up searches or cause unexpected failures when saving/updating a keychain item.
15
+
16
+ Example Usage
17
+ =============
18
+
19
+ # get an item
20
+ item = Keychain::Item.new
21
+
22
+ # add some search criteria, you need at least one, options are listed
23
+ # in the keychain services reference 'Attribute Item Keys and Values'
24
+ # section (link above)
25
+ item.attributes.merge!({
26
+ KSecAttrProtocol => KSecAttrProtocolHTTPS,
27
+ KSecAttrServer => 'github.com'
28
+ })
29
+
30
+ # work with the entry if it exists
31
+ if item.exists?
32
+
33
+ # cache all the metadata and print the account name (user name)
34
+ puts item.metadata![KSecAttrAccount]
35
+
36
+ # print the password (needs authorization)
37
+ puts item.password
38
+
39
+ # change the password and check it (BE CAREFUL)
40
+ puts item.password = 'test'
41
+
42
+ # change the user name and save to the keychain
43
+ # note how you do not need authorization to change the user name
44
+ item.update!({ KSecAttrAccount => 'test' })
45
+ puts item.metadata[KSecAttrAccount]
46
+
47
+ else
48
+ puts 'No such item exists, maybe you need different criteria?'
49
+ end
50
+
51
+ Contributing to keychain
52
+ ========================
53
+
54
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
55
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
56
+ * Fork the project
57
+ * Start a feature/bugfix branch
58
+ * Commit and push until you are happy with your contribution
59
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
60
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
61
+
62
+ Copyright
63
+ =========
64
+
65
+ Copyright (c) 2011 Mark Rada. See LICENSE.txt for
66
+ further details.
67
+
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ gem.name = "mr_keychain"
15
+ gem.homepage = "http://github.com/ferrous26/keychain"
16
+ gem.license = "MIT"
17
+ gem.summary = %Q{Example code of how to use the Mac OS X keychain in MacRuby}
18
+ gem.description = %Q{Uses APIs new in Snow Leopard to create, read, and update keychain entries}
19
+ gem.email = "marada@uwaterloo.ca"
20
+ gem.authors = ["Mark Rada"]
21
+ end
22
+ Jeweler::RubygemsDotOrgTasks.new
23
+
24
+ require 'rspec/core'
25
+ require 'rspec/core/rake_task'
26
+ RSpec::Core::RakeTask.new(:spec) do |spec|
27
+ spec.pattern = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
31
+ spec.pattern = 'spec/**/*_spec.rb'
32
+ spec.rcov = true
33
+ end
34
+
35
+ task :default => :spec
36
+
37
+ require 'yard'
38
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,173 @@
1
+ framework 'Foundation'
2
+
3
+ # Classes, modules, methods, and constants relevant to working with the
4
+ # Mac OS X keychain.
5
+ module Keychain
6
+
7
+ # @note Methods only need a user's explicit authorization if they want the
8
+ # password data, metadata does not need permission. In these cases, the OS
9
+ # should present an alert asking to allow, deny, or always allow the script
10
+ # to access. You need to be careful when using 'always allow' if you are
11
+ # running this code from interactive ruby or the regular interpreter because
12
+ # you could accidentally allow any future script to not require permission
13
+ # to access any password in the keychain.
14
+ # Represents an entry in the login keychain.
15
+ #
16
+ # The big assumption that this class makes is that you only ever want
17
+ # to work with a single keychain item; whether it be searching for metadata,
18
+ # getting passwords, or adding a new entry.
19
+ #
20
+ # In order to be secure, this class will NEVER cache a password; any time
21
+ # that you change a password, it will be written to the keychain immeadiately.
22
+
23
+ class Item
24
+
25
+ # @return [Hash]
26
+ attr_accessor :attributes
27
+
28
+ # Direct access to the attributes hash of the keychain item.
29
+ def [] key
30
+ @attributes[key]
31
+ end
32
+
33
+ # Direct access to the attributes hash of the keychain item.
34
+ def []= key, value
35
+ @attributes[key] = value
36
+ end
37
+
38
+ # You should initialize objects of this class with the attributes relevant
39
+ # to the item you wish to work with, but you can add or remove attributes
40
+ # via accessors as well.
41
+ # @param [Hash] attributes
42
+ def initialize attributes = nil
43
+ @attributes = { KSecClass => KSecClassInternetPassword }
44
+ @attributes.merge! attributes if attributes
45
+ end
46
+
47
+ # @note This method asks only for the metadata and doesn't need authorization
48
+ # Returns true if there are any item matching the given attributes.
49
+ # @raise [KeychainException] for unexpected errors
50
+ # @return [true,false]
51
+ def exists?
52
+ result = Pointer.new :id
53
+ search = @attributes.merge({
54
+ KSecMatchLimit => KSecMatchLimitOne,
55
+ KSecReturnAttributes => true
56
+ })
57
+
58
+ case (error_code = SecItemCopyMatching(search, result))
59
+ when ErrSecSuccess then
60
+ true
61
+ when ErrSecItemNotFound then
62
+ false
63
+ else
64
+ message = SecCopyErrorMessageString(error_code, nil)
65
+ raise KeychainException, "Error checking keychain item existence: #{message}"
66
+ end
67
+ end
68
+
69
+ # @note We ask for an NSData object here in order to get the password.
70
+ # Returns the password for the first match found, raises an error if
71
+ # no keychain item is found.
72
+ #
73
+ # Blank passwords should come back as an empty string, but that hasn't
74
+ # been tested.
75
+ # @raise [KeychainException]
76
+ # @return [String] UTF8 encoded password string
77
+ def password
78
+ result = Pointer.new :id
79
+ search = @attributes.merge({
80
+ KSecMatchLimit => KSecMatchLimitOne,
81
+ KSecReturnData => true
82
+ })
83
+
84
+ case (error_code = SecItemCopyMatching(search, result))
85
+ when ErrSecSuccess then
86
+ NSString.alloc.initWithData result[0], encoding:NSUTF8StringEncoding
87
+ else
88
+ message = SecCopyErrorMessageString(error_code, nil)
89
+ raise KeychainException, "Error getting password: #{message}"
90
+ end
91
+ end
92
+
93
+
94
+ # Updates the password associated with the keychain item. If the item does
95
+ # not exist in the keychain it will be added first.
96
+ # @raise [KeychainException]
97
+ # @param [String] new_password a UTF-8 encoded string
98
+ # @return [String] the saved password
99
+ def password= new_password
100
+ password_data = {
101
+ KSecValueData => new_password.dataUsingEncoding(NSUTF8StringEncoding)
102
+ }
103
+ if exists?
104
+ error_code = SecItemUpdate( @attributes, password_data )
105
+ else
106
+ error_code = SecItemAdd( @attributes.merge password_data, nil )
107
+ end
108
+
109
+ case error_code
110
+ when ErrSecSuccess then
111
+ password
112
+ else
113
+ message = SecCopyErrorMessageString(error_code, nil)
114
+ raise KeychainException, "Error updating password: #{message}"
115
+ end
116
+ end
117
+
118
+ # @todo This method does not really fit with the rest of the API.
119
+ # @note This method does not need authorization unless you are
120
+ # updating the password.
121
+ # Updates attributes of the item in the keychain. If the item does not
122
+ # exist yet then this method will raise an exception.
123
+ #
124
+ # Use a value of nil to remove an attribute.
125
+ # @param [Hash] new_attributes the attributes that you want to update
126
+ # @return [Hash] attributes
127
+ def update! new_attributes
128
+ result = Pointer.new :id
129
+ query = @attributes.merge({ KSecMatchLimit => KSecMatchLimitOne })
130
+
131
+ case (error_code = SecItemUpdate(query, new_attributes))
132
+ when ErrSecSuccess then
133
+ metadata!
134
+ else
135
+ message = SecCopyErrorMessageString(error_code, nil)
136
+ raise KeychainException, "Error updating keychain item: #{message}"
137
+ end
138
+ end
139
+
140
+ # Get all the metadata about a keychain item, they will be keyed
141
+ # according Apple's documentation.
142
+ # @raise [KeychainException]
143
+ # @return [Hash]
144
+ def metadata
145
+ result = Pointer.new :id
146
+ search = @attributes.merge({
147
+ KSecMatchLimit => KSecMatchLimitOne,
148
+ KSecReturnAttributes => true
149
+ })
150
+
151
+ case (error_code = SecItemCopyMatching(search, result))
152
+ when ErrSecSuccess then
153
+ result[0]
154
+ else
155
+ message = SecCopyErrorMessageString(error_code, nil)
156
+ raise KeychainException, "Error getting metadata: #{message}"
157
+ end
158
+ end
159
+
160
+ # Update attributes to include all the metadata from the keychain.
161
+ # @raise [KeychainException]
162
+ # @return [Hash]
163
+ def metadata!
164
+ @attributes = metadata
165
+ end
166
+ end
167
+
168
+ # A trivial exception class that exists to help differentiate where
169
+ # exceptions are being raised.
170
+ class KeychainException < Exception
171
+ end
172
+
173
+ end
@@ -0,0 +1,227 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ # @todo get the spec tests to use mock data
4
+ describe 'Keychain' do
5
+
6
+ describe 'Item' do
7
+
8
+ before do
9
+ @item = Keychain::Item.new
10
+ end
11
+
12
+
13
+ describe 'attributes attribute' do
14
+ it 'is readable' do
15
+ @item.respond_to?(:attributes).should == true
16
+ end
17
+
18
+ it 'is writable' do
19
+ @item.respond_to?(:attributes=).should == true
20
+ end
21
+
22
+ it 'should be initialized with the class being set' do
23
+ @item.attributes[KSecClass].should_not == nil
24
+ end
25
+
26
+ it 'should be initialized to be of the interenet class' do
27
+ @item.attributes[KSecClass].should == KSecClassInternetPassword
28
+ end
29
+
30
+ it 'should allow the class to be overriden' do
31
+ @item = Keychain::Item.new({ KSecClass => 'different' })
32
+ @item.attributes[KSecClass].should == 'different'
33
+ end
34
+ end
35
+
36
+
37
+ describe '#exists?' do
38
+ it 'returns false if the item does not exist' do
39
+ @item.attributes.merge!({
40
+ KSecAttrProtocol => KSecAttrProtocolIRCS,
41
+ KSecAttrServer => 'github.com'
42
+ })
43
+ @item.exists?.should == false
44
+ end
45
+
46
+ it 'returns true if the item exists' do
47
+ @item.attributes.merge!({
48
+ KSecAttrProtocol => KSecAttrProtocolHTTPS,
49
+ KSecAttrServer => 'github.com'
50
+ })
51
+ @item.exists?.should == true
52
+ end
53
+
54
+ it 'raises an exception for unexpected error codes' do
55
+ @item.attributes[KSecClass] = 'made up class'
56
+ expect { @item.exists? }.to raise_exception(Keychain::KeychainException)
57
+ end
58
+ end
59
+
60
+
61
+ describe '#password' do
62
+ it 'should return a string with the password' do
63
+ @item.attributes.merge!({
64
+ KSecAttrProtocol => KSecAttrProtocolHTTPS,
65
+ KSecAttrServer => 'github.com'
66
+ })
67
+ @item.password.class.should == String
68
+ end
69
+
70
+ it 'should raise an exception if no password is found' do
71
+ @item.attributes.merge!({
72
+ KSecAttrProtocol => KSecAttrProtocolIRCS,
73
+ KSecAttrServer => 'github.com'
74
+ })
75
+ expect { @item.password }.to raise_exception(Keychain::KeychainException)
76
+ end
77
+ end
78
+
79
+
80
+ describe '#metadata' do
81
+ it 'should return a hash' do
82
+ @item.attributes.merge!({
83
+ KSecAttrProtocol => KSecAttrProtocolHTTPS,
84
+ KSecAttrServer => 'github.com'
85
+ })
86
+ @item.metadata.class.should == Hash
87
+ end
88
+
89
+ it 'should raise an exception if nothing is found' do
90
+ @item.attributes.merge!({
91
+ KSecAttrProtocol => KSecAttrProtocolIRCS,
92
+ KSecAttrServer => 'github.com'
93
+ })
94
+ expect { @item.metadata }.to raise_exception(Keychain::KeychainException)
95
+ end
96
+
97
+ # this assumes the keychain item has more metadata
98
+ it 'should not overwrite @attributes' do
99
+ @item.attributes.merge!({
100
+ KSecAttrProtocol => KSecAttrProtocolHTTPS,
101
+ KSecAttrServer => 'github.com'
102
+ })
103
+ metadata = @item.metadata
104
+ @item.attributes.should_not == metadata
105
+ end
106
+ end
107
+
108
+
109
+ describe '#metadata!' do
110
+ it 'should return a hash' do
111
+ @item.attributes.merge!({
112
+ KSecAttrProtocol => KSecAttrProtocolHTTPS,
113
+ KSecAttrServer => 'github.com'
114
+ })
115
+ @item.metadata.class.should == Hash
116
+ end
117
+
118
+ it 'should raise an exception if nothing is found' do
119
+ @item.attributes.merge!({
120
+ KSecAttrProtocol => KSecAttrProtocolIRCS,
121
+ KSecAttrServer => 'github.com'
122
+ })
123
+ expect { @item.metadata }.to raise_exception(Keychain::KeychainException)
124
+ end
125
+
126
+ # this assumes the keychain item has more metadata
127
+ it 'should overwrite @attributes' do
128
+ @item.attributes.merge!({
129
+ KSecAttrProtocol => KSecAttrProtocolHTTPS,
130
+ KSecAttrServer => 'github.com'
131
+ })
132
+ metadata = @item.metadata!
133
+ @item.attributes.should == metadata
134
+ end
135
+ end
136
+
137
+
138
+ describe '#[]' do
139
+ it 'should be equivalent to #attributes' do
140
+ @item.attributes[:test] = 'test'
141
+ @item[:test].should == 'test'
142
+ @item[KSecClass].should == @item.attributes[KSecClass]
143
+ end
144
+ end
145
+
146
+
147
+ describe '#[]=' do
148
+ it 'should be equivalent to #attributes=' do
149
+ @item[:test] = 'test'
150
+ @item.attributes[:test].should == 'test'
151
+ end
152
+ end
153
+
154
+
155
+ # describe '#password=' do
156
+ # before do
157
+ # @item.attributes.merge!({
158
+ # KSecAttrProtocol => KSecAttrProtocolHTTPS,
159
+ # KSecAttrServer => 'github.com'
160
+ # })
161
+ # end
162
+
163
+ # it 'should return the updated password' do
164
+ # (@item.password = 'new password').should == 'new password'
165
+ # end
166
+
167
+ # it 'should update the password in the keychain' do
168
+ # (@item.password = 'new password').should == @item.password
169
+ # end
170
+
171
+ # it 'should create entries if they do not exsit' do
172
+ # @item.attributes.merge!({
173
+ # KSecAttrAccount => 'test'
174
+ # })
175
+ # @item.password = 'another test'
176
+ # @item.exists?.should == true
177
+ # end
178
+
179
+ # after do
180
+ # NSLog('I created an entry in your keychain that you should clean up')
181
+ # end
182
+ # end
183
+
184
+
185
+ describe '#update!' do
186
+ it 'should update fields given in the persistent keychain' do
187
+ @item.attributes.merge!({
188
+ KSecAttrProtocol => KSecAttrProtocolHTTPS,
189
+ KSecAttrServer => 'github.com'
190
+ })
191
+ @item.update!({ KSecAttrComment => 'test' })
192
+ @item.metadata[KSecAttrComment].should == 'test'
193
+ end
194
+
195
+ it 'should raise an exception for non-existant items' do
196
+ @item.attributes.merge!({
197
+ KSecAttrProtocol => KSecAttrProtocolIRCS,
198
+ KSecAttrServer => 'github.com'
199
+ })
200
+ expect {
201
+ @item.update!({ KSecAttrComment => 'different test' })
202
+ }.to raise_exception(Keychain::KeychainException)
203
+ end
204
+
205
+ it 'should update @attributes' do
206
+ @item.attributes.merge!({
207
+ KSecAttrProtocol => KSecAttrProtocolHTTPS,
208
+ KSecAttrServer => 'github.com'
209
+ })
210
+ @item.update!({ KSecAttrComment => 'toast' })
211
+ @item.attributes[KSecAttrComment].should == 'toast'
212
+ end
213
+
214
+ it 'should return the metadata of the keychain item' do
215
+ @item.attributes.merge!({
216
+ KSecAttrProtocol => KSecAttrProtocolHTTPS,
217
+ KSecAttrServer => 'github.com'
218
+ })
219
+ @item.update!({
220
+ KSecAttrComment => 'bread'
221
+ })[KSecAttrComment].should == 'bread'
222
+ end
223
+ end
224
+
225
+ end
226
+
227
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'mr_keychain'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mr_keychain
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Mark Rada
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-05 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 4
30
+ - 0
31
+ version: 2.4.0
32
+ type: :development
33
+ prerelease: false
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: yard
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 0
44
+ - 6
45
+ - 0
46
+ version: 0.6.0
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: bluecloth
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 2
59
+ - 0
60
+ - 0
61
+ version: 2.0.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: bundler
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ~>
71
+ - !ruby/object:Gem::Version
72
+ segments:
73
+ - 1
74
+ - 0
75
+ - 0
76
+ version: 1.0.0
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *id004
80
+ - !ruby/object:Gem::Dependency
81
+ name: jeweler
82
+ requirement: &id005 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ segments:
88
+ - 1
89
+ - 5
90
+ - 2
91
+ version: 1.5.2
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: *id005
95
+ - !ruby/object:Gem::Dependency
96
+ name: rcov
97
+ requirement: &id006 !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ segments:
103
+ - 0
104
+ version: "0"
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: *id006
108
+ description: Uses APIs new in Snow Leopard to create, read, and update keychain entries
109
+ email: marada@uwaterloo.ca
110
+ executables: []
111
+
112
+ extensions: []
113
+
114
+ extra_rdoc_files:
115
+ - LICENSE.txt
116
+ - README.markdown
117
+ files:
118
+ - .document
119
+ - .rspec
120
+ - .rvmrc
121
+ - .yardopts
122
+ - Gemfile
123
+ - Gemfile.lock
124
+ - LICENSE.txt
125
+ - README.markdown
126
+ - Rakefile
127
+ - VERSION
128
+ - lib/mr_keychain.rb
129
+ - spec/keychain_spec.rb
130
+ - spec/spec_helper.rb
131
+ has_rdoc: true
132
+ homepage: http://github.com/ferrous26/keychain
133
+ licenses:
134
+ - MIT
135
+ post_install_message:
136
+ rdoc_options: []
137
+
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ hash: -1899776903697949187
146
+ segments:
147
+ - 0
148
+ version: "0"
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ none: false
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ segments:
155
+ - 0
156
+ version: "0"
157
+ requirements: []
158
+
159
+ rubyforge_project:
160
+ rubygems_version: 1.3.7
161
+ signing_key:
162
+ specification_version: 3
163
+ summary: Example code of how to use the Mac OS X keychain in MacRuby
164
+ test_files:
165
+ - spec/keychain_spec.rb
166
+ - spec/spec_helper.rb