the Chromium logo

The Chromium Projects

OS X Password Manager/Keychain Integration

Note: As of version 45, the password manager is no longer integrated with Keychain, since the interoperability goal discussed in the Background section is no longer possible. This document is here for historical purposes only.


Background

The Mac Keychain provides a shared, secure storage implementation that includes API specifically for storing various types of web passwords (web forms, HTTP auth, etc.). The platform expectation is that we should store our passwords there, providing interoperability with other browsers (with the exception of Firefox), rather than simply using it a private secure-storage mechanism.

Storage

Because the Keychain schema is limited, and is not extensible, we cannot use it as the sole storage mechanism—most notably we would lose the "action" field, without which we have dramatically different user experience for security reasons. This means we need our own (non-secure) storage for extra information. Because Keychain entries don't have a stable, unique identifier, and don't have a field we can use for adding an identifier for our own record, they must be paired by matching across multiple fields.

The fields that will be stored in each location, in PasswordForm terminology:

Keychain

*   scheme
    *   kSecAuthenticationTypeItemAttr (direct equivalents exist for
                each of the SCHEME_\* types)
*   signon_realm
    *   for SCHEME_HTML: kSecProtocolItemAttr + kSecServerItemAttr +
                kSecPortItemAttr
    *   for SCHEME_BASIC and SCHEME_DIGEST: as above, with the
                addition of kSecSecurityDomain
    *   for proxies: TBD (format is "proxy-host/auth-realm"; need to
                see what other browsers store, and how "origin" is handled
                on the Chromium side)
*   origin
    *   signon_realm components + kSecPathItemAttr
*   username_value
    *   kSecAccountItemAttr
*   password_value
    *   Keychain data
*   date_created
    *   kSecCreationDateItemAttr

Internal Database (using LoginDatabase)

  • scheme
  • signon_realm
  • origin
  • username_value
    • The above is duplicate information necessary to match our entries with Keychain entries. They should be sufficient to map uniquely to a Keychain entry, given Keychain's non-duplication enforcement.
  • action
    • This is the most important aspect of our metadata, since the UE changes if we don't have it (see discussion below).
  • submit_element
  • username_element
  • password_element
    • Used only for minor scoring changes.
  • ssl_valid
    • This gives us an extra security indicator for passwords we store; for passwords stored by other browsers we must infer it from kSecProtocolItemAttr, similar to how imported passwords are handled on other platforms.
  • preferred
  • blacklisted_by_user
  • date_created
    • Primarily for blacklisted_by_user here, which has no corresponding keychain entry

Note: In the PasswordStore implementations for other platforms, the PasswordStore doesn't need to understand anything about the format of signon_realm, so it can be free-form, which is not the case here. However, if we ever need to add a new format that cannot be expressed in Keychain terms, we can fall back to storing more metadata ourselves and using a generic application Keychain item for the password.

Saving, Updating, Retrieving, and Deleting

Saving is relatively straightforward, except for a wrinkle with collisions:

  1. Store the keychain entry
    • If this fails as a "duplicate item", attempt to set the password (since the other primary information must already match). If that succeeds—which is not guaranteed, since the user may deny us access to modify the item—count storage as a success.
  2. If (1) succeeds, store our metadata.

This means that it is possible for us to have multiple entries in our database (different only by the *_element fields) mapping to the same keychain entry.

Updating is essentially the same:

  1. Store/update the Keychain, exactly as with Saving
  2. If we successfully create or update a Keychain item, update our metadata (or if we had no metadata, store some).

Retrieving (single) is trickier:

  1. Find all the matching entries in our database, and convert them to PasswordForms.
  2. Find all the matching Keychain entries, and convert them to PassswordForms.
  3. Walk the database PasswordForm list, and for each item:
    • If it's a blacklist item, add it to the merged list; otherwise
    • try to find a matching entry from the Keychain list. If there is a match, merge the keychain PasswordForm into the database PasswordForm, then move the combined entry to the return list (and remember the keychain entry as having been used).
  4. Move everything left in the original keychain list that's not a blacklist item into the return list.
    • These will be treated like imported passwords in the UI due to the lack of an action field.
  5. Delete the database entry for anything left in the database list; if it's still there the user has deleted the keychain item outside of Chromium and it is now useless.

Retrieving (batch) differs from single retrieval in handling of Keychain entries (see Design Choices):

  1. Find all entries in our database, and convert them to PasswordForms.
  2. For each entry, find and merge in the corresponding Keychain entry.
    • If there isn't one, delete the database entry, since it is now useless.

Deleting (single) is slightly tricky due to potential sharing of an item by multiple database entries:

  1. Delete the database entry that we are trying to delete.
  2. Find the Keychain item corresponding to the entry to delete.
  3. If we created the Keychain item (see Design Choices below):
    1. Find all remaining database items that would use that Keychain item.
    2. If there aren't any, delete the Keychain item.

Deleting (batch) works much the same way (and is subject to some limitations; see Design Choices below):

  1. Delete everything in our metadata database from the given time range.
  2. Find everything that's left in the database.
  3. Find everything in the Keychain created by Chromium.
  4. Merge the lists from those two steps, and delete any Keychain entries that aren't used.

Issues/Limitations

Design Choices