class Keychain::Item

@note Methods only need a user’s explicit authorization if they want the

password data, metadata does not need permission. In these cases, the OS
should present an alert asking to allow, deny, or always allow the script
to access. You need to be careful when using 'always allow' if you are
running this code from interactive ruby or the regular interpreter because
you could accidentally allow any future script to not require permission
to access any password in the keychain.

Represents an entry in the login keychain.

The big assumption that this class makes is that you only ever want to work with a single keychain item; whether it be searching for metadata, getting passwords, or adding a new entry.

In order to be secure, this class will NEVER cache a password; any time that you change a password, it will be written to the keychain immeadiately.

Attributes

attributes[RW]

@return [Hash]

Public Class Methods

new(attributes = {}) click to toggle source

You should initialize objects of this class with the attributes relevant to the item you wish to work with, but you can add or remove attributes via accessors as well. @param [Hash] attributes

# File lib/mr_keychain.rb, line 41
def initialize attributes = {}
  @attributes = { KSecClass => KSecClassInternetPassword }
  @attributes.merge! attributes
end

Public Instance Methods

[](key) click to toggle source

Direct access to the attributes hash of the keychain item.

# File lib/mr_keychain.rb, line 28
def [] key
  @attributes[key]
end
[]=(key, value) click to toggle source

Direct access to the attributes hash of the keychain item.

# File lib/mr_keychain.rb, line 33
def []= key, value
  @attributes[key] = value
end
exists?() click to toggle source

@note This method asks only for the metadata and doesn’t need authorization Returns true if there are any item matching the given attributes. @raise [KeychainException] for unexpected errors @return [true,false]

# File lib/mr_keychain.rb, line 50
def exists?
  result = Pointer.new :id
  search = @attributes.merge(
    KSecMatchLimit       => KSecMatchLimitOne,
    KSecReturnAttributes => true
  )

  case (error_code = SecItemCopyMatching(search, result))
  when ErrSecSuccess then
    true
  when ErrSecItemNotFound then
    false
  else
    message = SecCopyErrorMessageString(error_code, nil)
    raise KeychainException, "Error checking keychain item existence: #{message}"
  end
end
metadata() click to toggle source

Get all the metadata about a keychain item, they will be keyed according Apple’s documentation. @raise [KeychainException] @return [Hash]

# File lib/mr_keychain.rb, line 141
def metadata
  result = Pointer.new :id
  search = @attributes.merge(
    KSecMatchLimit       => KSecMatchLimitOne,
    KSecReturnAttributes => true
  )

  case (error_code = SecItemCopyMatching(search, result))
  when ErrSecSuccess then
    result[0]
  else
    message = SecCopyErrorMessageString(error_code, nil)
    raise KeychainException, "Error getting metadata: #{message}"
  end
end
metadata!() click to toggle source

Update attributes to include all the metadata from the keychain. @raise [KeychainException] @return [Hash]

# File lib/mr_keychain.rb, line 160
def metadata!
  @attributes = metadata
end
password() click to toggle source

@note We ask for an NSData object here in order to get the password. Returns the password for the first match found, raises an error if no keychain item is found.

Blank passwords should come back as an empty string, but that hasn’t been tested. @raise [KeychainException] @return [String] UTF8 encoded password string

# File lib/mr_keychain.rb, line 76
def password
  result = Pointer.new :id
  search = @attributes.merge(
    KSecMatchLimit => KSecMatchLimitOne,
    KSecReturnData => true
  )

  case (error_code = SecItemCopyMatching(search, result))
  when ErrSecSuccess then
    result[0].to_str
  else
    message = SecCopyErrorMessageString(error_code, nil)
    raise KeychainException, "Error getting password: #{message}"
  end
end
password=(new_password) click to toggle source

Updates the password associated with the keychain item. If the item does not exist in the keychain it will be added first. @raise [KeychainException] @param [String] new_password a UTF-8 encoded string @return [String] the saved password

# File lib/mr_keychain.rb, line 98
def password= new_password
  password_data = { KSecValueData => new_password.to_data }
  if exists?
    error_code = SecItemUpdate( @attributes, password_data )
  else
    error_code = SecItemAdd( @attributes.merge(password_data), nil )
  end

  case error_code
  when ErrSecSuccess then
    password
  else
    message = SecCopyErrorMessageString(error_code, nil)
    raise KeychainException, "Error updating password: #{message}"
  end
end
update!(new_attributes) click to toggle source

@todo This method does not really fit with the rest of the API. @note This method does not need authorization unless you are

updating the password.

Updates attributes of the item in the keychain. If the item does not exist yet then this method will raise an exception.

Use a value of nil to remove an attribute. @param [Hash] new_attributes the attributes that you want to update @return [Hash] attributes

# File lib/mr_keychain.rb, line 124
def update! new_attributes
  result = Pointer.new :id
  query  = @attributes.merge( KSecMatchLimit => KSecMatchLimitOne )

  case (error_code = SecItemUpdate(query, new_attributes))
  when ErrSecSuccess then
    metadata!
  else
    message = SecCopyErrorMessageString(error_code, nil)
    raise KeychainException, "Error updating keychain item: #{message}"
  end
end