Master Passphrase Support

Introduction

Like all popular web browsers, Mozilla Firefox allows you to optionally cache passwords used for site logins. Site credentials are cached on disk, and in plaintext by default. However, Firefox allows you to optionally configure a "Master Password". This password (or passphrase) is used to encrypt the on-disk cached site credentials, functioning effectively the same way that a keyring provider and associated passphrase would work. Firefox will challenge the user for the master password the first time it needs to consult its credentials cache, and will leave the cache "unlocked" for the duration of the application's lifetime. (Reference: http://luxsci.com/blog/master-password-encryption-in-firefox-and-thunderbird.html)

Subversion should be able to do something similar, allowing users to optionally employ a master passphrase which is used to encrypt and decrypt other sensitive information stored in its authentication credential cache(s). Long-lived Subversion GUI clients could query the user for his or her master passphrase the first time the local credential cache is consulted, and remember that passphrase for the lifetime of the application, just like Firefox does.

But what about the relatively short-lived command-client? Obviously, if naively implemented, a user would need to provide the master passphrase as often as they would their actual repository credentials if caching was not available at all. This would render the credential cache useful only insomuch as it reduces the potentially boundless amount of site credentials the user must memorize to a single item: the master password itself.

Fortunately, both the command-line client and GUI clients can benefit from existing integrations with encrypted stores on the various operating systems. On Windows, command-line clients and GUI clients alike needn't query for the master passphrase once that passphrase itself has been cached using Windows Cryptographic Services. Similar is true on Mac OS X using the Keychain. Essentially, any existing keystore integration which today can be used to store a bunch of passwords could instead be used to store just a single master passphrase.

Implementation Thoughts

High-level notions

Must Haves:

  • A pleasantly licensed encryption library which is available on all client platforms. We want to use AES-256 encryption/decryption in CBC mode (see below), and PBKDF2 for key generation. APR/APRUtil 1.4 should provide the required crypto algorithm and random data generation functionality – we'll look for the apr_random_standard_new() and apr_crypto_passphrase() functions at configure time.
  • A mechanism for telling Subversion to actually use a master passphrase. Probably a runtime configuration variable (use-master-passphrase=true, e.g.).
  • A way to acquire the master passphrase. Where the user has enabled third-party secure storage mechanisms (see EncryptedPasswordStorage), we can store the master passphrase (or a hash thereof) in those mechanisms, and retrieve it automatically from the same. Otherwise, we'll need to prompt the user for the passphrase.
  • Encrypted usernames and realmstrings, too. Those can be as sensitive as passwords in some contexts.
  • Tooling (API functions and binary support) to allow users to set/change their master passphrase without destroying their credential cache. Ideally, we treat the encryption store as a single atomic unit, discouraging users from trying to partially move encrypted bits from one machine's auth store to anothers. (On the other hands, we would explicitly like to support moving the whole auth store from one machine to another!) Most practically, this means keeping all the encrypted bits in a single file (flat file, SQLite DB, or otherwise), which can also help us ensure atomicity when we need to re-enrypt the store during a master password change event. To enable such changes, we'll provide a binary and subcommands (e.g., svnpass set-password).
  • Divorced storage for encrypted credentials. Because the runtime configuration area is per-user, and because the same user might be using different versions of Subversion for different projects, we cannot allow an encryption-aware client to clobber cached credentials which might still be used by an older client. We should, however, warn the user when encryption is enabled but old (non-encrypted) bits remain on disk.

Nice To Have:

  • A way to export encrypted credentials. The rough equivalent of Firefox's "Show Passwords" feature, this functionality should probably require the user to provide the master passphrase via prompt (to protect against snooping co-workers who commandeer your computer while you're on a bathroom break). This might manifest itself as svnpass show-passwords.

The logic, conversationally

Today, the authn subsystem walks a virtual table of "providers", each of which can answer questions about a certain type of credential (simple username/password, username only, client certificates, etc.) using a certain mechanism (prompting the user, looking into the authn disk cache, consulting an OS keyring, etc.). See “AN OVERVIEW” in subversion/libsvn_subr/auth.c (http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_subr/auth.c?view=markup#l38) for details. These next sections described how Subversion would do the things it does today with the master passphrase construct in place. This document assumes that the long-lived basic constructs of Subversion's authn subsystem are unchanged except where described herein.

User interface - working with the master passphrase

Subversion interactions with a master passphrase in use should only differ in very minor ways from those when no such passphrase is in use, and those differences limited to solely an additional prompt at essentially the same "place" that Subversion would otherwise prompt for authentication credentials or for permission to store credentials (in plaintext) today.

Setting/changing the master passphrase

Subversion will need to provide APIs and tooling (for example, an svnpass set-password command) for managing the master passphrase and encrypted credentials.

If the use-master-passphrase runtime configuration is enabled, svnpass set-password will challenge the user for the existing master passphrase (if any). If the challenge is met successfully, Subversion will prompt the user for a new passphrase (twice). Subversion will use the old passphrase to decrypt any cached, encrypted credentials, and then the new passphrase to re-crypt those credentials, atomically rebuilding the encrypted auth store using the re-encrypted bits.

It is critical that our choice of an encrypted auth store not leave unencrypted or encrypted-with-the-old-passphrase bits on disk. We must assume that the reason the password was changed was that it was compromised.

If the use-master-passphrase option is not set, svnpass set-password should error out, possibly warning about the presence of an apparently abandoned encrypted auth store on disk.

Consulting the authn cache

Subversion will continue to consult the auth cache at the same step in its operations as it does today. The difference will be that, when use-master-passphrase is enabled, it will consult the encrypted auth store first. To do so, Subversion will need to acquire the SHA-256 hash of the master passphrase (either from a secure store or by prompting the user), and will use that to decrypt the relevant bits of the encryped auth store. Failure to acquire a valid master passphrase hash (one which successfully decrypts the encrypted cached bits) will force Subversion to ignore encrypted auth store data. Subversion will cache a successfully acquired master passphrase hash in its auth_baton for future use by that Subversion process.

Updating the authn cache

When caching credentials, Subversion will check the runtime configuration to determine if encryption should be used. If not, no change in behavior is necessary. If, however, the user has indicated that a master passphrase should be used, Subversion will once again work to obtain the master passphrase hash (either from the auth_baton, or from a secure store, or by prompting the user). That passphrase hash will be used to encrypt the credentials when storing them on disk. If no valid master passphrase is obtained, the “save_credentials” callback of the file-based cache provider will fail and the next provider in the chain will be given the opportunity to save the credentials.

Encrypted Storage

Sensitive authentication credentials will be encrypted using AES-256 in CBC mode. This is a block-oriented, symmetric cipher (16 byte blocks), so some padding will be added/stripped to the plaintext password (a NUL character is fine). To prevent dictionary attacks against the encrypted passwords, the plaintext password will be prefixed with 4 bytes (32 bits) of random data before encryption. This should mean that reusing a password on multiple realms doesn't result in the same encrypted text.

The encryption algorithm requires a 16 byte crypt key (technically, it can also be 24 or 32 bytes), and a 16 byte initialization vector. The crypt key will be constructed as a derivation of the master password. So that we can avoid holding the users's master password in memory, it will be hashed (SHA-256) as soon as possible, and that hash will serve as the effective master password. This effective master password will be used derive the crypt key (see PBKDF2). PBKDF2 requires an 8-byte (64-bit) salt. The IV is a random 16 byte value. The salt and IV will be stored with the block of data encrypted by the master password. The master password (that is, the SHA-256 hash thereof) decrypts that data block, uses the salt to generate the (de)crypt key, then pairs it with the IV to decrypt the target password.

PREFIX_LEN=4

def encrypt(PLAINTEXT, MPHASH):
    PREFIX = generate_random(PREFIX_LEN)
    PREFIXED_PLAINTEXT_LEN = PREFIX_LEN + len(PLAINTEXT)
    if PREFIXED_PLAINTEXT_LEN % 16:
        PAD = NUL . generate_random(15 - (PREFIXED_PLAINTEXT_LEN % 16))
    else:
        PAD = ''
    SALT = generate_random(8)
    IV = generate_random(16)
    CIPHERTEXT = aes256cbc_encrypt(PREFIX . PLAINTEXT . PAD, IV, pbkdf2(MPHASH, SALT))
    return CIPHERTEXT, IV, SALT # these need to be stored

def decrypt(CIPHERTEXT, IV, SALT, MPHASH):
    return aes256cbc_decrypt(CIPHERTEXT, IV, pbkdf2(MPHASH, SALT))[PREFIX_LEN:]

To provide master passphrase validation (for the purposes of changing passwords, for example), we need to store a bit of comparison text. Using the SHA-256 hash of the known-good master passphrase, we'll encrypt a block of random data, storing the encrypted results along with a hash of the random data. Validating a provided passphrase, then, means using the SHA-256 of that passphrase to decrypt the stored encryption results, verifying that hash of the resulting decrypted data matches what we stored for the same previously.

def save_passphrase_checktext(MPHASH):
    STUFF = generate_random(32)
    IV = generate_random(16)
    SALT = generate_random(8)
    CHECKTEXT = sha256(STUFF)
    CIPHERTEXT = aes256cbc_encrypt(STUFF, IV, pbkdf2(MPHASH, SALT))
    save(CIPHERTEXT, IV, SALT, CHECKTEXT)

def verify_passphrase(MPHASH):
    CIPHERTEXT, IV, SALT, CHECKTEXT = load()
    STUFF = aes256cbc_decrypt(CIPHERTEXT, IV, pbkdf2(MPHASH, SALT))
    return sha256(STUFF) == CHECKTEXT

Note that the above algorithm is similar to how we actually store target passwords (substitute the password for STUFF with appropriate prefixing and padding).

SSH-Agent as a master password cache mechanism

Ivan Voras <ivoras{AT}gmail.com> provided the following suggestion (via private email to CMichaelPilato; posted here with permission):

From: Ivan Voras <ivoras{__AT__}gmail.com>
Date: Mon, 18 Jun 2012 16:06:54 +0200
Message-ID: <CAF-QHFVKV366BwDN7kK1YeiQY95Y83jB1xtP6Zx9U9=U+pTGrw@mail.gmail.com>
Subject: Subversion Master Passphrase Support
To: cmpilato@red-bean.com

Hello,

I have been searching for something in the Subversion wiki and
accidentally came across the
http://wiki.apache.org/subversion/MasterPassphrase page, which is
signed by you.

I think this is a very valuable feature but would like to suggest a
different approach: rather than reinventing the wheel and making your
own master-password maintenance mechanism, it would be better, more
convenient and even perhaps more reliable to use ssh-agent for this.

See:
 * http://en.wikipedia.org/wiki/Ssh-agent
 * http://www.freebsd.org/cgi/man.cgi?ssh-agent
 * http://ptspts.blogspot.com/2010/06/how-to-use-ssh-agent-programmatically.html
 * ... and others.

As the ssh-agent does PKI crypto by itself, I think the workflow would be:

0) User creates an ssh keypair protected by a master password. This
keypair is also used for normal ssh communication.
1) When master password is enabled in svn (by a user command, etc.),
svn needs to create a 256-bit random internal master password, make
ssh-agent encrypt it, then store the encrypted internal master
password wherever
2) To decrypt data, svn needs to ask ssh-agent to decrypt the internal
master password, then use it in whatever way necessary

The benefits are that most professional users already have ssh keys
handled by ssh-agent, that ssh-agent already solves the problem of
protecting sensitive data and asking for the master password, and that
by using a random internal master password you guarantee better
security against brute-forcing the protected data.

The workflow I described should work - in theory - but I haven't tried
to implement it!

How Git stores passwords

https://confluence.atlassian.com/display/STASH/Permanently+authenticating+with+Git+repositories

Benefits

  • Centralization: Rather than spread repository credentials cross a variety of stores (on-disk, keystores, etc.), we return to a single, easy-to-manage storage solution: the on-disk store in ~/.subversion/auth/
  • Portability: ~/.subversion/auth/ is portable across computers, allowing users to transfer what could be hundreds of different sets of stored repository credentials to other machines with ease. So long as they employed the same master passphrase on those other machines, or did a one-time passphrase change, they would be able to make use of previously cached credentials.

Concerns

  • Is the Subversion codebase – and the authn subsystem specifically – capable of handling this sort of approach? (Research continues.)

The following IRC conversation occurred in irc.freenode.net ##crypto, and carries some concerns/questions about the planned approach raised by a user therein:

From irc.freenode.net ##crypto, Wed Jan 09 2012, around 11am EST:

<cmpilato> yfeldblum: here's the spec:
   http://wiki.apache.org/subversion/MasterPassphrase any additional eyes
   would be lovely!

<yfeldblum> cmpilato, ah is this the subversion client, caching the
   user's username+password combinations to multiple subversion server
   endpoints?

<cmpilato> yfeldblum: yep.

<cmpilato> we currently store in plaintext or in an OS-provided
   keyring, but are considering offering our own "keyring" of sorts for
   uniformity and simplicity.

<cmpilato> (plaintext with restricted filesystem perms, i should say,
   but i realize that on a list such this that really just means we get
   hit with green tomatoes instead of red ones)

<yfeldblum> cmpilato, prefer either an authenticated encryption mode
   or to MAC all encrypted messages (passwords) and always verify the
   MACs first before attempting a decrypt (the MAC key should be
   different from the AES key)

<yfeldblum> cmpilato, re-encrypting is typically accomplished by
   re-encrypting the master key only, not decrypting all saved passwords
   and re-encrypting them

<yfeldblum> cmpilato, IANAE, but otherwise it seems rather similar to
   other decent schemes out there; but of course get a couple more
   opinions on that

<cmpilato> yfeldblum: Yes, that similarity is intentional.  We
   (Subversion devs) aren't trying to blaze new trails in the security
   space.

<cmpilato> yfeldblum: if i may return to a previous comment of yours:
   "re-encrypting is typically accomplished by re-encrypting the master
   key only, not decrypting all saved passwords and re-encrypting them"

<yfeldblum> cmpilato, well, that's how changing the master password is
   typically done

<yfeldblum> cmpilato, keep the same master key, but decrypt it with
   the old master password, then re-encrypt it with the new master
   password, and save it

<cmpilato> right.  did you spot something on the wiki page that
   indicated a different plan?

<yfeldblum> cmpilato, not really, just not too much on the issue
   overall

<yfeldblum> cmpilato, also use pkcs#5/pkcs#7 block cipher padding, not
   random padding, since the former is more interoperable

<yfeldblum> cmpilato, i prefer returning a FULL_CIPHERTEXT as
   IV . CIPHERTEXT; then a decrypt of the ciphertext starts with splitting
   off the first 16 bytes as the IV

<yfeldblum> cmpilato, since you will never need the IV apart from
   performing a decrypt, it makes sense just to embed it in the
   ciphertext string

<cmpilato> rather than return distinct variables?

<yfeldblum> cmpilato, right

<yfeldblum> cmpilato, why are you generating a prefix?

<yfeldblum> cmpilato, PREFIX = generate_random(PREFIX_LEN) <--- what's
   that for?

<cmpilato> "To prevent dictionary attacks against the encrypted
   passwords, the plaintext password will be prefixed with 4 bytes (32
   bits) of random data before encryption. This should mean that reusing
   a password on multiple realms doesn't result in the same encrypted
   text."

<yfeldblum> cmpilato, that's the whole purpose of the IV; since you're
   using CBC and have an IV, you don't need the prefix

<yfeldblum> cmpilato, the IV is effectively a prefix already

<cmpilato> yfeldblum: would you be opposed to my posting in the wiki a
   (trimmed) transcript of this IRC chat so that when I or others
   eventually get back to working on this feature, we can consider your
   input?

<yfeldblum> cmpilato, if you include me saying IANAE (I Am Not An
   Expert) and "Get A Second Opinion" in that

<cmpilato> of course!

<cmpilato> i'll even include you saying that i should include you
   saying that. ;-)

<yfeldblum> cmpilato, fantastic

Questions

  • Since we use PBKDF2, should we also be using its sibling PBES2? Or perhaps we're already doing just that, except for not calling it that?
  • How do we handle an accurately provided master passphrase that fails to decrypt some cached data (say, because the encrypted auth store is corrupt or has been manually tampered with)? Toss that data out? Leave it in place to continue failing indefinitely?

Compatibility

Users with existing on-disk cached credentials will be able to continue using those cached credentials. If the use-master-passphrase configuration bit is enabled, those credentials will be automatically copied into the encrypted auth store. However, we should warn any time we give the appearance of having stored passwords securely yet we find insecure copies thereof still on disk (preserved for use by older Subversion clients).

Additional Resources

  • No labels