module Sec enum :SecExternalItemType, [:kSecItemTypeUnknown , :kSecItemTypePrivateKey, :kSecItemTypePublicKey, :kSecItemTypeSessionKey, :kSecItemTypeCertificate, :kSecItemTypeAggregate] attach_function 'SecAccessCreate', [:pointer, :pointer, :pointer], :osstatus attach_function 'SecTrustedApplicationCreateFromPath', [:string, :pointer], :osstatus attach_function 'SecKeychainCopyDefault', [:pointer], :osstatus attach_function 'SecKeychainDelete', [:keychainref], :osstatus attach_function 'SecKeychainOpen', [:string, :pointer], :osstatus attach_function 'SecKeychainGetPath', [:keychainref, :pointer, :pointer], :osstatus attach_function 'SecKeychainCreate', [:string, :uint32, :pointer, :char, :pointer, :pointer], :osstatus attach_function 'SecItemCopyMatching', [:pointer, :pointer], :osstatus attach_function 'SecKeychainSetSearchList', [:pointer], :osstatus attach_function 'SecKeychainCopySearchList', [:pointer], :osstatus #@private class KeychainSettings < FFI::Struct layout :version, :uint32, :lock_on_sleep, :uchar, :use_lock_interval, :uchar, #apple ignores this :lock_interval, :uint32 end attach_function 'SecKeychainSetSettings', [:keychainref, KeychainSettings], :osstatus attach_function 'SecKeychainCopySettings', [:keychainref, KeychainSettings], :osstatus attach_function 'SecKeychainLock', [:keychainref], :osstatus attach_function 'SecKeychainUnlock', [:keychainref, :uint32, :pointer, :uchar], :osstatus attach_function 'SecKeychainGetStatus', [:keychainref, :pointer], :osstatus attach_function 'SecKeychainSetUserInteractionAllowed', [:uchar], :osstatus attach_function 'SecKeychainGetUserInteractionAllowed', [:pointer], :osstatus enum :keychainStatus, [ :kSecUnlockStateStatus, 1, :kSecReadPermStatus, 2, :kSecWritePermStatus, 4, ] end module Keychain # Wrapper class for individual keychains. Corresponds to a SecKeychainRef # class Keychain < Sec::Base register_type 'SecKeychain' # Add the keychain to the default searchlist # # def add_to_search_list list = FFI::MemoryPointer.new(:pointer) status = Sec.SecKeychainCopySearchList(list) Sec.check_osstatus(status) ruby_list = CF::Base.typecast(list.read_pointer).release_on_gc.to_ruby ruby_list << self unless ruby_list.include?(self) status = Sec.SecKeychainSetSearchList(CF::Array.immutable(ruby_list)) Sec.check_osstatus(status) self end # Returns whether the keychain will be locked if the machine goes to sleep # # @return [Boolean] # def lock_on_sleep? get_settings[:lock_on_sleep] != 0 end # Returns the duration (in seconds) after which the keychain will be locked # # @return [Boolean] # def lock_interval get_settings[:lock_interval] end # Set whether the keychain will be locked if the machine goes to sleep # # @param [Boolean] value # def lock_on_sleep= value put_settings(get_settings.tap {|s| s[:lock_on_sleep] = value ? 1 : 0}) end # Sets the duration (in seconds) after which the keychain will be locked # # @param [Integer] value dutarion in seconds # def lock_interval= value put_settings(get_settings.tap {|s| s[:lock_interval] = value}) end # Returns a scope for internet passwords contained in this keychain # # @return [Keychain::Scope] a new scope object def internet_passwords Scope.new(Sec::Classes::INTERNET, self) end # Returns a scope for generic passwords contained in this keychain # # @return [Keychain::Scope] a new scope object def generic_passwords Scope.new(Sec::Classes::GENERIC, self) end # Imports item from string or file to this keychain # # @param [IO, String] input IO object or String with raw data to import # @param [Array ] app_list List of applications which will be # permitted to access imported items # @return [Array ] List of imported keychain objects, # each of which may be a SecCertificate, SecKey, or SecIdentity instance def import(input, app_list=[]) input = input.read if input.is_a? IO # Create array of TrustedApplication objects trusted_apps = get_trusted_apps(app_list) # Create an Access object access_buffer = FFI::MemoryPointer.new(:pointer) status = Sec.SecAccessCreate(path.to_cf, trusted_apps, access_buffer) Sec.check_osstatus status access = CF::Base.typecast(access_buffer.read_pointer) key_params = Sec::SecItemImportExportKeyParameters.new key_params[:accessRef] = access # Import item to the keychain cf_data = CF::Data.from_string(input).release_on_gc cf_array = FFI::MemoryPointer.new(:pointer) status = Sec.SecItemImport(cf_data, nil, :kSecFormatUnknown, :kSecItemTypeUnknown, :kSecItemPemArmour, key_params, self, cf_array) access.release Sec.check_osstatus status item_array = CF::Base.typecast(cf_array.read_pointer).release_on_gc item_array.to_ruby end # returns a description of the keychain # @return [String] def inspect "" end # Removes the keychain from the search path and deletes the corresponding file (SecKeychainDelete) # # See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainDelete # @return self def delete status = Sec.SecKeychainDelete(self) Sec.check_osstatus(status) self end # Returns the path at which the keychain is stored # # See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainGetPath # # @return [String] path to the keychain file def path out_buffer = FFI::MemoryPointer.new(:uchar, 2048) io_size = FFI::MemoryPointer.new(:uint32) io_size.put_uint32(0, out_buffer.size) status = Sec.SecKeychainGetPath(self,io_size, out_buffer) Sec.check_osstatus(status) out_buffer.read_string(io_size.get_uint32(0)).force_encoding(Encoding::UTF_8) end # Locks the keychain # def lock! status = Sec.SecKeychainLock(self) Sec.check_osstatus status end # Unlocks the keychain # # @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 def unlock! password=nil if password password = password.encode(Encoding::UTF_8) status = Sec.SecKeychainUnlock self, password.bytesize, password, 1 else status = Sec.SecKeychainUnlock self, 0, nil, 0 end Sec.check_osstatus status end # Returns whether the keychain is locked # @return [Boolean] def locked? !status_flag?(:kSecUnlockStateStatus) end # Returns whether the keychain is readable # @return [Boolean] def readable? status_flag?(:kSecReadPermStatus) end # Returns whether the keychain is writable # @return [Boolean] def writeable? status_flag?(:kSecWritePermStatus) end def exists? begin readable? true rescue NoSuchKeychainError false end end private def status_flag? enum_name out = FFI::MemoryPointer.new(:uint32) status = Sec.SecKeychainGetStatus(self,out); Sec.check_osstatus status (out.get_uint32(0) & Sec.enum_value(enum_name)).nonzero? end def get_settings settings = Sec::KeychainSettings.new settings[:version] = 1 status = Sec.SecKeychainCopySettings(self, settings) Sec.check_osstatus status settings end def get_trusted_apps apps trusted_app_array = apps.map do |path| trusted_app_buffer = FFI::MemoryPointer.new(:pointer) status = Sec.SecTrustedApplicationCreateFromPath( path.encode(Encoding::UTF_8), trusted_app_buffer) Sec.check_osstatus(status) CF::Base.typecast(trusted_app_buffer.read_pointer).release_on_gc end trusted_app_array.to_cf end def put_settings settings status = Sec.SecKeychainSetSettings(self, settings) Sec.check_osstatus status settings end end end