ruby-keychain 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +8 -0
- data/README +90 -0
- data/lib/keychain.rb +72 -0
- data/lib/keychain/error.rb +34 -0
- data/lib/keychain/item.rb +188 -0
- data/lib/keychain/keychain.rb +188 -0
- data/lib/keychain/protocols.rb +43 -0
- data/lib/keychain/scope.rb +131 -0
- data/lib/keychain/sec.rb +165 -0
- data/lib/keychain/version.rb +4 -0
- data/spec/keychain_item_spec.rb +67 -0
- data/spec/keychain_spec.rb +220 -0
- data/spec/spec_helper.rb +11 -0
- metadata +155 -0
data/LICENSE
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
The MIT License
|
2
|
+
Copyright (c) 2012 Frederick Cheung
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
5
|
+
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
7
|
+
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
A set of ruby bindings for the OS X keychain, written using ffi
|
2
|
+
|
3
|
+
Introduction
|
4
|
+
============
|
5
|
+
|
6
|
+
The keychain is OS X's secure credential storage mechanism. This library allows access to internet passwords (typically specified as a combination of host, protocol, account (optionally port)) and generic passwords (identified by a service and account).
|
7
|
+
|
8
|
+
|
9
|
+
Working with keychains
|
10
|
+
==================
|
11
|
+
|
12
|
+
Most operations will act on either the default keychain, or the default keychain search list. You can obtain specific keychains with
|
13
|
+
|
14
|
+
Keychain.default #the default keychain, usually /Users/<username>/Library/Keychains/<username>.keychain
|
15
|
+
Keychain.open(path) #opens a keychain file
|
16
|
+
Keychain.create(path, password) #creates a new keychain at the specified path
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
Searching for Keychain Items
|
21
|
+
=========
|
22
|
+
|
23
|
+
The top level constant `Keychain` as well as individual keychain objects have two methods `internet_passwords` and `generic_passwords` that return scope like objects. You can do
|
24
|
+
|
25
|
+
Keychain.internet_passwords.where(:server => 'example.com').all
|
26
|
+
|
27
|
+
to return Keychain::Item objects for that server
|
28
|
+
|
29
|
+
Keychain.internet_passwords.where(:server => 'example.com').first
|
30
|
+
|
31
|
+
to return the first Keychain::Item for that server or
|
32
|
+
|
33
|
+
Keychain.internet_passwords.where(:server => 'example.com').limit(4).all
|
34
|
+
|
35
|
+
to return up to 4 Keychain::Item for that server.
|
36
|
+
|
37
|
+
`generic_passwords` behaves similarly but searches the keychain for genereric passwords
|
38
|
+
|
39
|
+
You can restrict the search to a specific keychain with
|
40
|
+
|
41
|
+
some_keychain.internet_passwords.where(:server => 'example.com').all
|
42
|
+
|
43
|
+
returns matching `Keychain::Item` from the specified keychain.
|
44
|
+
|
45
|
+
or to an arbitrary list of keychains with
|
46
|
+
|
47
|
+
Keychain.internet_passwords.in(keychain_1, keychain2).all
|
48
|
+
|
49
|
+
|
50
|
+
Finding a Keychain::Item won't prompt the user for a password if the keychain is unlocked. Calling the password accessor method of the item may prompt the user for their password depending on the keychain item access settings.
|
51
|
+
|
52
|
+
If you call `where` multiple times, each successive invocation merges its conditions with the previous set of conditions
|
53
|
+
|
54
|
+
|
55
|
+
Creating keychain items
|
56
|
+
=========================
|
57
|
+
|
58
|
+
In the default keychain:
|
59
|
+
|
60
|
+
Keychain.internet_passwords.create(:server => 'example.com', :protocol => Keychain::Protocols::HTTP, :password => 'secret', :account => 'bob')
|
61
|
+
|
62
|
+
or
|
63
|
+
|
64
|
+
Keychain.generic_passwords.create(:service => 'AWS', :password => 'secret', :account => 'bob')
|
65
|
+
|
66
|
+
In a specific keychain
|
67
|
+
|
68
|
+
some_keychain.internet_passwords.create(...)
|
69
|
+
|
70
|
+
by default keychain items are only readable by the application that created them, however when running a ruby script the application is ruby: by default other ruby scripts will be able to read the items (if the keychain is unlocked).
|
71
|
+
|
72
|
+
Using keychain items
|
73
|
+
=====================
|
74
|
+
|
75
|
+
The `Keychain::Item` class has accessors for all its attributes, for the full list of attributes see `Sec::ATTR_MAP`
|
76
|
+
|
77
|
+
All strings returned are utf-8 encoded. Be careful not to set attribute values to strings with the ASCII_8BIT encoding as this will cause them to be treated as raw data rather than string. The exception to this is password data which the keychain api defines as being arbitrary binary data. When storing an actual password it is customary to use utf-8. The password data will always be returned as raw binary data
|
78
|
+
|
79
|
+
|
80
|
+
Error Handling
|
81
|
+
==============
|
82
|
+
|
83
|
+
Failed operations will result in `Keychain::Error` being raised. The original error code is available as the `code` attribute of the exception. When attempting to insert a duplicate item, `Keychain::DuplicateItemError` (a subclass of `Keychain::Error`) is raised instead
|
84
|
+
|
85
|
+
|
86
|
+
Compatibility
|
87
|
+
=============
|
88
|
+
Requires ruby 1.9 due to use of encoding related methods. Should work in MRI and jruby. Not compatible with rubinius due to rubinius' ffi implemenation
|
89
|
+
not supporting certain features
|
90
|
+
|
data/lib/keychain.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
require 'corefoundation'
|
3
|
+
require 'keychain/sec'
|
4
|
+
require 'keychain/keychain'
|
5
|
+
require 'keychain/error'
|
6
|
+
require 'keychain/item'
|
7
|
+
require 'keychain/scope'
|
8
|
+
require 'keychain/protocols'
|
9
|
+
# top level constant for this library
|
10
|
+
module Keychain
|
11
|
+
class << self
|
12
|
+
# creates a new keychain file and adds it to the keychain search path ( SecKeychainCreate )
|
13
|
+
#
|
14
|
+
# See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainCreate
|
15
|
+
# @param [String] path The path to the keychain file to create
|
16
|
+
# If it is not absolute it is interpreted relative to ~/Library/Keychains
|
17
|
+
# @param [String] password The password to use for the keychain
|
18
|
+
# @return [Keychain::Keychain] a keychain object representing the newly created keychain
|
19
|
+
|
20
|
+
def create(path, password)
|
21
|
+
password = password.encode(Encoding::UTF_8)
|
22
|
+
path = path.encode(Encoding::UTF_8)
|
23
|
+
|
24
|
+
out_buffer = FFI::MemoryPointer.new(:pointer)
|
25
|
+
status = Sec.SecKeychainCreate(path, password.bytesize, FFI::MemoryPointer.from_string(password), 0,
|
26
|
+
nil, out_buffer)
|
27
|
+
|
28
|
+
Sec.check_osstatus(status)
|
29
|
+
Keychain.new(out_buffer.read_pointer).release_on_gc
|
30
|
+
end
|
31
|
+
|
32
|
+
# Gets the default keychain object ( SecKeychainCopyDefault )
|
33
|
+
#
|
34
|
+
# See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainCopyDefault
|
35
|
+
# @return [Keychain::Keychain] a keychain object
|
36
|
+
def default
|
37
|
+
out_buffer = FFI::MemoryPointer.new(:pointer)
|
38
|
+
status = Sec.SecKeychainCopyDefault(out_buffer);
|
39
|
+
Sec.check_osstatus(status)
|
40
|
+
|
41
|
+
Keychain.new(out_buffer.read_pointer).release_on_gc
|
42
|
+
end
|
43
|
+
|
44
|
+
# Opens the keychain file at the specified path and adds it to the keychain search path ( SecKeychainOpen )
|
45
|
+
#
|
46
|
+
# Will succeed even if the file doesn't exists (however most operations on the keychain will then fail)
|
47
|
+
#
|
48
|
+
# See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainCopyDefault
|
49
|
+
# @param [String] path Path to the keychain file
|
50
|
+
# @return [Keychain::Keychain] a keychain object
|
51
|
+
def open(path)
|
52
|
+
out_buffer = FFI::MemoryPointer.new(:pointer)
|
53
|
+
status = Sec.SecKeychainOpen(path,out_buffer);
|
54
|
+
Sec.check_osstatus(status)
|
55
|
+
Keychain.new(out_buffer.read_pointer).release_on_gc
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns a scope for internet passwords contained in all keychains
|
59
|
+
#
|
60
|
+
# @return [Keychain::Scope] a new scope object
|
61
|
+
def internet_passwords
|
62
|
+
Scope.new(Sec::Classes::INTERNET)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns a scope for generic passwords in all keychains
|
66
|
+
#
|
67
|
+
# @return [Keychain::Scope] a new scope object
|
68
|
+
def generic_passwords
|
69
|
+
Scope.new(Sec::Classes::GENERIC)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
module Sec
|
3
|
+
attach_function 'SecCopyErrorMessageString', [:osstatus, :pointer], :pointer
|
4
|
+
end
|
5
|
+
|
6
|
+
# The base class of all keychain related errors
|
7
|
+
#
|
8
|
+
# The original error code is available as `code`
|
9
|
+
class Keychain::Error < StandardError
|
10
|
+
attr_accessor :code
|
11
|
+
def initialize(code)
|
12
|
+
self.code = code
|
13
|
+
description = Sec.SecCopyErrorMessageString(code, nil)
|
14
|
+
if description.null?
|
15
|
+
super("Sec Error #{code}")
|
16
|
+
else
|
17
|
+
description = CF::Base.typecast(description)
|
18
|
+
super("#{description.to_s} (#{code})")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Raised when saving or updating an item
|
24
|
+
# fails because an existing item is already in the keychain
|
25
|
+
class Keychain::DuplicateItemError < Keychain::Error; end
|
26
|
+
# Raised when an operation that requires a password fails,
|
27
|
+
# for example unlocking a keychain
|
28
|
+
class Keychain::AuthFailedError < Keychain::Error; end
|
29
|
+
# Raised when an action that requires user interaction
|
30
|
+
# (such as decrypting as password) is cancelled by the user
|
31
|
+
class Keychain::UserCancelledError < Keychain::Error; end
|
32
|
+
# Raised when an action fails because the underlying keychain
|
33
|
+
# does not exist
|
34
|
+
class Keychain::NoSuchKeychainError < Keychain::Error; end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
|
2
|
+
module Sec
|
3
|
+
attach_function 'SecKeychainItemDelete', [:pointer], :osstatus
|
4
|
+
attach_function 'SecItemAdd', [:pointer, :pointer], :osstatus
|
5
|
+
attach_function 'SecItemUpdate', [:pointer, :pointer], :osstatus
|
6
|
+
attach_function 'SecKeychainItemCopyKeychain', [:pointer, :pointer], :osstatus
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
# An individual item from the keychain. Individual accessors are generated for the items attributes
|
11
|
+
#
|
12
|
+
#
|
13
|
+
class Keychain::Item < Sec::Base
|
14
|
+
attr_accessor :attributes
|
15
|
+
register_type 'SecKeychainItem'
|
16
|
+
|
17
|
+
# returns a programmer friendly description of the item
|
18
|
+
# @return [String]
|
19
|
+
def inspect
|
20
|
+
"<SecKeychainItem 0x#{@ptr.address.to_s(16)} #{service ? "service: #{service}" : "server: #{server}"} account: #{account}>"
|
21
|
+
end
|
22
|
+
|
23
|
+
Sec::ATTR_MAP.values.each do |ruby_name|
|
24
|
+
unless method_defined?(ruby_name)
|
25
|
+
define_method ruby_name do
|
26
|
+
@attributes[ruby_name]
|
27
|
+
end
|
28
|
+
define_method ruby_name.to_s+'=' do |value|
|
29
|
+
@attributes[ruby_name] = value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Creates a new keychain item either from an FFI::Pointer or a hash of attributes
|
35
|
+
#
|
36
|
+
# @param [FFI::Pointer, Hash] attrs_or_pointer Either an FFI::Pointer to an existing
|
37
|
+
# SecKeychainItemRef to wrap or hash of attributes to create a new, unsaved Keychain::Item from
|
38
|
+
# see {Keychain::Scope#create}
|
39
|
+
#
|
40
|
+
def self.new(attrs_or_pointer)
|
41
|
+
if attrs_or_pointer.is_a? Hash
|
42
|
+
super(0).tap do |result|
|
43
|
+
attrs_or_pointer.each {|k,v| result.send("#{k}=", v)}
|
44
|
+
end
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @private
|
51
|
+
def initialize(*args)
|
52
|
+
super
|
53
|
+
@attributes = {}
|
54
|
+
end
|
55
|
+
|
56
|
+
# Removes the item from the associated keychain
|
57
|
+
#
|
58
|
+
def delete
|
59
|
+
status = Sec.SecKeychainItemDelete(self)
|
60
|
+
Sec.check_osstatus(status)
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
# Set a new password for the item
|
65
|
+
# @note The new password is not saved into the keychain until you call {Keychain::Item#save!}
|
66
|
+
# @param [String] value The new value for the password
|
67
|
+
# @return [String] The set value
|
68
|
+
def password=(value)
|
69
|
+
@unsaved_password = value
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the keychain the item is in
|
73
|
+
#
|
74
|
+
# @return [Keychain::Keychain]
|
75
|
+
def keychain
|
76
|
+
out = FFI::MemoryPointer.new :pointer
|
77
|
+
status = Sec.SecKeychainItemCopyKeychain(self,out)
|
78
|
+
Sec.check_osstatus(status)
|
79
|
+
CF::Base.new(out.read_pointer).release_on_gc
|
80
|
+
end
|
81
|
+
|
82
|
+
# Fetches the password data associated with the item. This may cause the user to be asked for access
|
83
|
+
# @return [String] The password data, an ASCII_8BIT encoded string
|
84
|
+
def password
|
85
|
+
return @unsaved_password if @unsaved_password
|
86
|
+
out_buffer = FFI::MemoryPointer.new(:pointer)
|
87
|
+
status = Sec.SecItemCopyMatching({Sec::Query::ITEM_LIST => CF::Array.immutable([self]),
|
88
|
+
Sec::Query::CLASS => klass,
|
89
|
+
Sec::Query::RETURN_DATA => true}.to_cf, out_buffer)
|
90
|
+
Sec.check_osstatus(status)
|
91
|
+
CF::Base.typecast(out_buffer.read_pointer).to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
# Attempts to update the keychain with any changes made to the item
|
95
|
+
# or saves a previously unpersisted item
|
96
|
+
# @param [optional, Hash] options extra options when saving the item
|
97
|
+
# @option options [Keychain::Keychain] :keychain when saving an unsaved item, they keychain to save it in
|
98
|
+
# @return [Keychain::Item] returns the item
|
99
|
+
def save!(options={})
|
100
|
+
if persisted?
|
101
|
+
cf_dict = update
|
102
|
+
else
|
103
|
+
cf_dict = create(options)
|
104
|
+
end
|
105
|
+
@unsaved_password = nil
|
106
|
+
update_self_from_dictionary(cf_dict)
|
107
|
+
cf_dict.release
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# @private
|
112
|
+
def self.from_dictionary_of_attributes(cf_dict)
|
113
|
+
new(0).tap {|item| item.send :update_self_from_dictionary, cf_dict}
|
114
|
+
end
|
115
|
+
|
116
|
+
# Whether the item has been persisted to the keychain
|
117
|
+
# @return [Boolean]
|
118
|
+
def persisted?
|
119
|
+
!@ptr.null?
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def create(options)
|
125
|
+
result = FFI::MemoryPointer.new :pointer
|
126
|
+
query = build_create_query(options)
|
127
|
+
query.merge!(build_new_attributes)
|
128
|
+
status = Sec.SecItemAdd(query, result);
|
129
|
+
Sec.check_osstatus(status)
|
130
|
+
cf_dict = CF::Base.typecast(result.read_pointer)
|
131
|
+
end
|
132
|
+
|
133
|
+
def update
|
134
|
+
status = Sec.SecItemUpdate({Sec::Query::ITEM_LIST => [self], Sec::INVERSE_ATTR_MAP[:klass] => klass}.to_cf, build_new_attributes);
|
135
|
+
Sec.check_osstatus(status)
|
136
|
+
|
137
|
+
result = FFI::MemoryPointer.new :pointer
|
138
|
+
query = build_refresh_query
|
139
|
+
status = Sec.SecItemCopyMatching(query, result);
|
140
|
+
Sec.check_osstatus(status)
|
141
|
+
cf_dict = CF::Base.typecast(result.read_pointer)
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
|
146
|
+
def update_self_from_dictionary(cf_dict)
|
147
|
+
if !persisted?
|
148
|
+
self.ptr = cf_dict[Sec::Value::REF].to_ptr
|
149
|
+
self.retain.release_on_gc
|
150
|
+
end
|
151
|
+
@attributes = cf_dict.inject({}) do |memo, (k,v)|
|
152
|
+
if ruby_name = Sec::ATTR_MAP[k]
|
153
|
+
memo[ruby_name] = v.to_ruby
|
154
|
+
end
|
155
|
+
memo
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def build_create_query options
|
160
|
+
query = CF::Dictionary.mutable
|
161
|
+
query[Sec::Value::DATA] = CF::Data.from_string(@unsaved_password) if @unsaved_password
|
162
|
+
query[Sec::Query::KEYCHAIN] = options[:keychain] if options[:keychain]
|
163
|
+
query[Sec::Query::RETURN_ATTRIBUTES] = CF::Boolean::TRUE
|
164
|
+
query[Sec::Query::RETURN_REF] = CF::Boolean::TRUE
|
165
|
+
query
|
166
|
+
end
|
167
|
+
|
168
|
+
def build_refresh_query
|
169
|
+
query = CF::Dictionary.mutable
|
170
|
+
query[Sec::Query::ITEM_LIST] = CF::Array.immutable([self])
|
171
|
+
query[Sec::Query::RETURN_ATTRIBUTES] = CF::Boolean::TRUE
|
172
|
+
query[Sec::Query::RETURN_REF] = CF::Boolean::TRUE
|
173
|
+
query[Sec::INVERSE_ATTR_MAP[:klass]] = klass.to_cf
|
174
|
+
query
|
175
|
+
end
|
176
|
+
|
177
|
+
def build_new_attributes
|
178
|
+
new_attributes = CF::Dictionary.mutable
|
179
|
+
@attributes.each do |k,v|
|
180
|
+
next if k == :created_at || k == :updated_at
|
181
|
+
next if k == :klass && persisted?
|
182
|
+
k = Sec::INVERSE_ATTR_MAP[k]
|
183
|
+
new_attributes[k] = v.to_cf
|
184
|
+
end
|
185
|
+
new_attributes[Sec::Value::DATA] = CF::Data.from_string(@unsaved_password) if @unsaved_password
|
186
|
+
new_attributes
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
|
2
|
+
module Sec
|
3
|
+
attach_function 'SecKeychainCopyDefault', [:pointer], :osstatus
|
4
|
+
attach_function 'SecKeychainDelete', [:keychainref], :osstatus
|
5
|
+
attach_function 'SecKeychainOpen', [:string, :pointer], :osstatus
|
6
|
+
attach_function 'SecKeychainGetPath', [:keychainref, :pointer, :pointer], :osstatus
|
7
|
+
|
8
|
+
attach_function 'SecKeychainCreate', [:string, :uint32, :pointer, :char, :pointer, :pointer], :osstatus
|
9
|
+
attach_function 'SecItemCopyMatching', [:pointer, :pointer], :osstatus
|
10
|
+
|
11
|
+
#@private
|
12
|
+
class KeychainSettings < FFI::Struct
|
13
|
+
layout :version, :uint32,
|
14
|
+
:lock_on_sleep, :uchar,
|
15
|
+
:use_lock_interval, :uchar, #apple ignores this
|
16
|
+
:lock_interval, :uint32
|
17
|
+
end
|
18
|
+
|
19
|
+
attach_function 'SecKeychainSetSettings', [:keychainref, KeychainSettings], :osstatus
|
20
|
+
attach_function 'SecKeychainCopySettings', [:keychainref, KeychainSettings], :osstatus
|
21
|
+
|
22
|
+
attach_function 'SecKeychainLock', [:keychainref], :osstatus
|
23
|
+
attach_function 'SecKeychainUnlock', [:keychainref, :uint32, :pointer, :uchar], :osstatus
|
24
|
+
|
25
|
+
attach_function 'SecKeychainGetStatus', [:keychainref, :pointer], :osstatus
|
26
|
+
|
27
|
+
enum :keychainStatus, [
|
28
|
+
:kSecUnlockStateStatus, 1,
|
29
|
+
:kSecReadPermStatus, 2,
|
30
|
+
:kSecWritePermStatus, 4,
|
31
|
+
]
|
32
|
+
end
|
33
|
+
|
34
|
+
module Keychain
|
35
|
+
# Wrapper class for individual keychains. Corresponds to a SecKeychainRef
|
36
|
+
#
|
37
|
+
class Keychain < Sec::Base
|
38
|
+
register_type 'SecKeychain'
|
39
|
+
|
40
|
+
# Returns whether the keychain will be locked if the machine goes to sleep
|
41
|
+
#
|
42
|
+
# @return [Boolean]
|
43
|
+
#
|
44
|
+
def lock_on_sleep?
|
45
|
+
get_settings[:lock_on_sleep] != 0
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the duration (in seconds) after which the keychain will be locked
|
49
|
+
#
|
50
|
+
# @return [Boolean]
|
51
|
+
#
|
52
|
+
def lock_interval
|
53
|
+
get_settings[:lock_interval]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set whether the keychain will be locked if the machine goes to sleep
|
57
|
+
#
|
58
|
+
# @param [Boolean] value
|
59
|
+
#
|
60
|
+
def lock_on_sleep= value
|
61
|
+
put_settings(get_settings.tap {|s| s[:lock_on_sleep] = value ? 1 : 0})
|
62
|
+
end
|
63
|
+
|
64
|
+
# Sets the duration (in seconds) after which the keychain will be locked
|
65
|
+
#
|
66
|
+
# @param [Integer] value dutarion in seconds
|
67
|
+
#
|
68
|
+
def lock_interval= value
|
69
|
+
put_settings(get_settings.tap {|s| s[:lock_interval] = value})
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns a scope for internet passwords contained in this keychain
|
73
|
+
#
|
74
|
+
# @return [Keychain::Scope] a new scope object
|
75
|
+
def internet_passwords
|
76
|
+
Scope.new(Sec::Classes::INTERNET, self)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns a scope for generic passwords contained in this keychain
|
80
|
+
#
|
81
|
+
# @return [Keychain::Scope] a new scope object
|
82
|
+
def generic_passwords
|
83
|
+
Scope.new(Sec::Classes::GENERIC, self)
|
84
|
+
end
|
85
|
+
|
86
|
+
# returns a description of the keychain
|
87
|
+
# @return [String]
|
88
|
+
def inspect
|
89
|
+
"<SecKeychain 0x#{@ptr.address.to_s(16)}: #{path}>"
|
90
|
+
end
|
91
|
+
|
92
|
+
# Removes the keychain from the search path and deletes the corresponding file (SecKeychainDelete)
|
93
|
+
#
|
94
|
+
# See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainDelete
|
95
|
+
# @return self
|
96
|
+
def delete
|
97
|
+
status = Sec.SecKeychainDelete(self)
|
98
|
+
Sec.check_osstatus(status)
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns the path at which the keychain is stored
|
103
|
+
#
|
104
|
+
# See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainGetPath
|
105
|
+
#
|
106
|
+
# @return [String] path to the keychain file
|
107
|
+
def path
|
108
|
+
out_buffer = FFI::MemoryPointer.new(:uchar, 2048)
|
109
|
+
io_size = FFI::MemoryPointer.new(:uint32)
|
110
|
+
io_size.put_uint32(0, out_buffer.size)
|
111
|
+
|
112
|
+
status = Sec.SecKeychainGetPath(self,io_size, out_buffer)
|
113
|
+
Sec.check_osstatus(status)
|
114
|
+
|
115
|
+
out_buffer.read_string(io_size.get_uint32(0)).force_encoding(Encoding::UTF_8)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Locks the keychain
|
119
|
+
#
|
120
|
+
def lock!
|
121
|
+
status = Sec.SecKeychainLock(self)
|
122
|
+
Sec.check_osstatus status
|
123
|
+
end
|
124
|
+
|
125
|
+
# Unlocks the keychain
|
126
|
+
#
|
127
|
+
# @param [optional, String] password the password to unlock the keychain with. If no password is supplied the keychain will prompt the user for a password
|
128
|
+
def unlock! password=nil
|
129
|
+
if password
|
130
|
+
password = password.encode(Encoding::UTF_8)
|
131
|
+
status = Sec.SecKeychainUnlock self, password.bytesize, password, 1
|
132
|
+
else
|
133
|
+
status = Sec.SecKeychainUnlock self, 0, nil, 0
|
134
|
+
end
|
135
|
+
Sec.check_osstatus status
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns whether the keychain is locked
|
139
|
+
# @return [Boolean]
|
140
|
+
def locked?
|
141
|
+
!status_flag?(:kSecUnlockStateStatus)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns whether the keychain is readable
|
145
|
+
# @return [Boolean]
|
146
|
+
def readable?
|
147
|
+
status_flag?(:kSecReadPermStatus)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns whether the keychain is writable
|
151
|
+
# @return [Boolean]
|
152
|
+
def writeable?
|
153
|
+
status_flag?(:kSecWritePermStatus)
|
154
|
+
end
|
155
|
+
|
156
|
+
def exists?
|
157
|
+
begin
|
158
|
+
readable?
|
159
|
+
true
|
160
|
+
rescue NoSuchKeychainError
|
161
|
+
false
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def status_flag? enum_name
|
168
|
+
out = FFI::MemoryPointer.new(:uint32)
|
169
|
+
status = Sec.SecKeychainGetStatus(self,out);
|
170
|
+
Sec.check_osstatus status
|
171
|
+
(out.get_uint32(0) & Sec.enum_value(enum_name)).nonzero?
|
172
|
+
end
|
173
|
+
|
174
|
+
def get_settings
|
175
|
+
settings = Sec::KeychainSettings.new
|
176
|
+
settings[:version] = 1
|
177
|
+
status = Sec.SecKeychainCopySettings(self, settings)
|
178
|
+
Sec.check_osstatus status
|
179
|
+
settings
|
180
|
+
end
|
181
|
+
|
182
|
+
def put_settings settings
|
183
|
+
status = Sec.SecKeychainSetSettings(self, settings)
|
184
|
+
Sec.check_osstatus status
|
185
|
+
settings
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|