Sunday, 26 March 2017

Encrypted password storage for Subversion using an SSH agent

I use Subversion every working day, as it is used for source code control at Rapita Systems. I use it on many different computers - servers, workstations - whenever I am working with product source code, test cases and documents.

I was concerned to find SVN asking the following question:

 $ svn info https://xyzzy/svn/xyzzy/trunk
 Authentication realm:  https://xyzzy:443  xyzzy
 Password for 'jwhitham': ***
 
 -----------------------------------------------------------------------
 ATTENTION!  Your password for authentication realm:

   https://xyzzy:443  xyzzy
 
 can only be unencrypted when stored to disk!  You are advised to configure
 your system so that Subversion can store passwords encrypted, if
 possible.  See the documentation for details.
 
 You can avoid future appearances of this warning by setting the value
 of the 'store-plaintext-passwords' option to either 'yes' or 'no' in
 '/home/jwhitham/.subversion/servers'.
 -----------------------------------------------------------------------
 Store password unencrypted (yes/no)? 

As the message explains, SVN has no means to store its passwords securely on these systems. Like a "nag screen", this question is asked repeatedly until the user enters "yes".

I was disturbed by this state of affairs. I don't want to store passwords unencrypted, but nor do I want to enter a password every time I use SVN, because I enter hundreds of SVN commands each day. So I decided to try to resolve the problem. I found that SVN uses a plugin-like system for authentication. Many methods are available for secure password storage: there are Windows and MacOS-specific methods (which are always available on those platforms), and there are methods for use on Linux and other operating systems, such as gnome-keyring, kdewallet and GPG agent.

These provide "single sign on" authentication. When the user logs into a workstation, their username and password provide the means to encrypt and decrypt passwords for SVN.

Unencrypted storage is supposed to be a last-resort fallback. However, I found that the fallback was used all the time because at least one of the following always applies:

  • I'm not using a compatible desktop environment (e.g. GNOME or KDE) so I don't have gnome-keyring or kdewallet running.
  • Or the version of SVN isn't compiled with gnome-keyring, kdewallet or gpg-agent support.
  • Or the GNOME/KDE libraries are out of date and don't match the desktop environment on the PC.
  • Or I'm connected to a remote system using SSH.

What I really wanted was SSH authentication. I use SSH all the time. I have a set of SSH private keys which give me password-free access to all the machines I normally use. Each key is protected by a passphrase which I enter once per day - a "single sign on". (If you are an SSH user - or you're using WinSCP or PuTTY which use SSH internally - then like me, you should use an SSH authentication agent. There are setup instructions for Pageant (Windows) and OpenSSH.)

I wanted to use my SSH key for SVN as well as logging in.

SVN does support SSH authentication, but in order to use it, you must have a shell account on the SVN server. This isn't possible as a result of IT policy, and I would expect a similar situation at other companies. A shell account on the SVN server, combined with read-write access to the SVN repository, would give any user the ability to edit the repository files in any way - not just commit changes. It would be a bad idea to allow this. Therefore, at my workplace, access to SVN is via the HTTPS protocol with password authentication.

If SSH keys can be used to authenticate with a remote system, can they also be used to authenticate with a secure password store? The answer is yes, at least if the SSH key type is "RSA".

With an RSA key, the SSH agent capabilities can be used to encrypt and decrypt passwords. To encrypt a password, we first generate a block of random data. We then send this to the agent. The agent replies with an encrypted copy of the data. This is then used as the key for AES encryption of the password. We store the encrypted copy of the password, along with a copy of the random data. These can be used to decrypt the password provided that the same SSH key is loaded into the SSH authentication agent.

I made a "proof of concept" implementation in Python which convinced me that the approach was viable. I found it would be useful to make it into a command-line utility program, which (amongst other things) is capable of encrypting files using the technique. I also found it would need to be built into SVN. This meant I needed to rewrite it in C. I did so.

The result is safeu: the SSH Agent File Encryption Utility. It's on Github. safeu can be built standalone (it uses OpenSSL's encryption library, libcrypto) and there is a patch that allows it to be compiled into SVN where it acts as a password storage module ("libsvn_subr/safeu_auth.c").

Aside from the source code I am also distributing a precompiled copy of the program which comes with a patched copy of SVN 1.9.5. These programs are built for 64-bit Linux on x86 and should work on fairly old Linux distributions (back to CentOS 5, in fact). They only require some common system libraries e.g. libc and zlib. This makes them portable across all of the Linux systems I regularly use.

I have now been using this software for several months. In that time I have made a few enhancements, for example it can now encrypt passwords with multiple SSH keys, and error handling is more robust. I changed the encryption library from tomcrypt to libcrypto as the latter is already part of SVN (and more widely available). I am generally happy with it: it solves the problem of encrypted password storage. The first time I use it on a new machine, I must reenter my SVN password, but after that, I never enter the password again provided that my SSH key is available.

I also find the safeu program useful, as it can search for the socket used by an SSH authentication agent, allowing SSH to be re-enabled in shells running within "tmux" or "screen". If you disconnect from a tmux session, and then reconnect, the location of the SSH authentication socket changes but the environment variable SSH_AUTH_SOCK is not updated. A simple .bashrc alias can be used to find the correct settings again, restoring SSH functionality:

 ssh-fix ()
 {
    eval `safeu --search`
 }

My extension to SVN just provides another way to store encrypted passwords. Other methods already exist and may be better suited to your requirements. However, if you are already an SSH user, then like me, you may find it useful to protect your passwords using your SSH key rather than running additional software.