DualControl - evanx/vellumapp GitHub Wiki

public class DualControlGenSecKey {
    private int submissionCount;
    private String keyAlias;

Encryption is great for information security and all that. But the problem with encryption is... key management. An analogy often bandied about is that we lock our doors but leave the key in the lock. Or under the mat, but that's my personal favourite so ssh-ssh.

We hereby start the new 2013 "Dual Control" quadrilogy, part of the Enigma Posts.

PCI DSS

The "Payment Card Industry Data Security Standard" (PCI DSS) advocates common sense policies for building a secure network and protecting your data. Actually every enterprise should adopt PCI DSS because it's the only and best such thing we got. Although it focusses on credit card numbers (aka Primary Account Numbers, or PANs), it goes without saying that companies in other industries also have sensitive data that people might want to steal, governments even ;)

PCI DSS suggests encrypting our data-encryption key (DEK) in order to protect it. Great, we now have a "key-encryption key" (KEK) that requires even more protection ;)

PCI DSS mandates that manual key management requires "split knowledge and dual control" e.g. for key generation and loading. The intent is that no single person can extract the clear-text data.

The glaring problem is that sysadmins are a single person, with god-like access to all our data, and de facto custodian of the proverbial keys to the kindgom. Consequently sysadmins have root access ;)

Solution overview

We'll split the knowledge of the key password between two admins, so it's known to no single person. Clearly dual control by those two admins is then required to load the key.

We propose keeping at least three copies of the same key in our keystore, where each copy is password-protected by a different "split password" pairing. Then if one admin is on vacation or otherwise indisposed, that's OK because we only require two admins to load the key when we restart our app.

DualControlGenSecKey

Step 1 for any data security endeavour is to generate an encryption key, which preferrably not even root can pwn. Whereas keytool prompts for a password entered by a single admin, we introduce DualControlGenSecKey to handle multiple password submissions via SSL.

public class DualControlGenSecKey {
    private int submissionCount;
    private String keyAlias;
    private String keyStoreLocation;
    private String keyStoreType;
    private String keyAlg;
    private int keySize;
    private char[] keyStorePassword;
    private Map dualPasswordMap;
    private SSLContext sslContext;

    public static void main(String[] args) throws Exception {
        DualControlGenSecKey instance = new DualControlGenSecKey();
        try {
            instance.call(new VellumProperties(System.getProperties()), 
                    new SystemConsole());
        } catch (DualControlException e) {
            instance.console.writer().println(e.getMessage());
        } finally {
            instance.clear();
        }
    }
    ...
}

where our main() method passes System properties i.e. -D options, and the System console for entering the SSL keystore password.

    public void call(VellumProperties properties, MockableConsole console) 
            throws Exception {
        this.console = console;
        submissionCount = properties.getInt("dualcontrol.submissions", 3);
        keyStoreLocation = properties.getString("keystore");
        keyStorePassword = properties.getPassword("storepass", null);
        keyAlias = properties.getString("alias");
        if (keyStorePassword == null) {
            keyStorePassword = console.readPassword(
                    "Enter passphrase for keystore (%s): ", keyStoreLocation);
            if (keyStorePassword == null) {
                throw new Exception("No storepass from console");
            }
        }
        if (new File(keyStoreLocation).exists()) {
            throw new Exception(
                    "Keystore file already exists: " + keyStoreLocation);
        }
        sslContext = DualControlSSLContextFactory.createSSLContext(properties);
        String purpose = "new key " + keyAlias;
        KeyStore keyStore = createKeyStore(properties, new DualControlReader().
                readDualMap(purpose, submissionCount, sslContext));
        keyStore.store(new FileOutputStream(keyStoreLocation), keyStorePassword);
    }

where DualControlReader provides a map of aliases and passwords, composed from submissions via SSL.

We create a KeyStore as follows.

      
    public KeyStore createKeyStore(VellumProperties properties,
            Map dualPasswordMap) throws Exception {
        keyAlias = properties.getString("alias");
        keyStoreType = properties.getString("storetype");
        keyAlg = properties.getString("keyalg");
        keySize = properties.getInt("keysize");
        KeyGenerator keyGenerator = KeyGenerator.getInstance(keyAlg);
        keyGenerator.init(keySize);
        SecretKey secretKey = keyGenerator.generateKey();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);        
        setEntry(keyStore, secretKey, keyAlias, dualPasswordMap);
        return keyStore;
    }

    private static void setEntry(KeyStore keyStore, SecretKey secretKey,
            String keyAlias, Map dualPasswordMap) throws Exception {
        KeyStore.Entry entry = new KeyStore.SecretKeyEntry(secretKey);
        for (String dualAlias : dualPasswordMap.keySet()) {
            char[] dualPassword = dualPasswordMap.get(dualAlias);
            String alias = keyAlias + "-" + dualAlias;
            KeyStore.ProtectionParameter prot =
                    new KeyStore.PasswordProtection(dualPassword);
            keyStore.setEntry(alias, entry, prot);
        }
    }

where for each duo we programmatically create a KeyStore entry containing the same key, but protected by a different password, known to no single person.

We redesigned this class from the ground up to be readily testable, so let's test it :)

    @Test
    public void genKeyTest() throws Exception {
        dualPasswordMap.put("brent-evanx", "bbbb+eeee".toCharArray());
        dualPasswordMap.put("brent-henty", "bbbb+hhhh".toCharArray());
        dualPasswordMap.put("evanx-henty", "eeee+hhhh".toCharArray());
        properties.put("alias", "dek2013");
        properties.put("storetype", "JCEKS");
        properties.put("keyalg", "AES");
        properties.put("keysize", "256");
        DualControlGenSecKey instance = new DualControlGenSecKey();
        KeyStore keyStore = instance.createKeyStore(properties, dualPasswordMap);
        assertEquals(3, Collections.list(keyStore.aliases()).size());
        assertEquals("dek2013-brent-evanx", Lists.asSortedSet(keyStore.aliases()).first());
        SecretKey key = getSecretKey(keyStore, "dek2013-brent-evanx", "bbbb+eeee".toCharArray());
        assertEquals("AES", key.getAlgorithm());
        assertTrue(Arrays.equals(key.getEncoded(), getSecretKey(keyStore, 
                "dek2013-brent-henty", "bbbb+hhhh".toCharArray()).getEncoded()));
    }

Incidently, we refactored DualControlGenSecKey to return a KeyStore for inspection, especially for the unit test above.

One might argue that we should not write code per se - rather we should write tests with accompanying code. This naturally requires public methods specially engineered for our tests.

Usage demo

Let's run DualControlGenSecKey from the command-line.

  java -Ddualcontrol.submissions=3 -Ddualcontrol.minPasswordLength=8 \
     -Dkeystore=$keystore -Dstoretype=JCEKS -Dstorepass=$storepass \
     -Dalias=dek2013 -Dkeyalg=AES -Dkeysize=256 \
     dualcontrol.DualControlGenSecKey

where we use a JCEKS-type keystore for our symmetric secret key, generated as 256bit AES, and aliased as "dek2013."

For example, three admins submit their passwords via SSL sockets where their client cert's CN identifies them as evanx, henty and brent.

INFO [DualControlReader] readDualMap submissionCount: 3
INFO [DualControlReader] readDualMap purpose: new key dek2013
INFO [DualControlReader] readSubmissions SSL port 4444
INFO [DualControlReader] Received evanx KGV3SY3APDPPX6EI3BCXU7DW7BOI6EKM
INFO [DualControlReader] Received brent SLZP3GMHTMGCIZVLQZEK7NR4JEBSG6OB
INFO [DualControlReader] Received henty 6QLBNC26WGS34AILXPBSP2V65B3N5CYF
INFO [DualControlReader] readDualMap dualAlias: brent-evanx
INFO [DualControlReader] readDualMap dualAlias: brent-henty
INFO [DualControlReader] readDualMap dualAlias: evanx-henty
INFO [DualControlGenSecKey] alias dek2013-brent-evanx
INFO [DualControlGenSecKey] alias dek2013-brent-henty
INFO [DualControlGenSecKey] alias dek2013-evanx-henty

We check that DualControlGenSecKey creates secret key entries under the following "dual aliases."

$ keytool -keystore $keystore -storetype JCEKS -storepass $storepass -list | grep Entry
dek2013-brent-henty, 18 Aug 2013, SecretKeyEntry, 
dek2013-evanx-henty, 18 Aug 2013, SecretKeyEntry, 
dek2013-brent-evanx, 18 Aug 2013, SecretKeyEntry,

Actually these three keys are one and the same! However each copy has a different key password, which is actually a combination of a pair of personal passwords, which we call a "split password."

@evanxsummers

⚠️ **GitHub.com Fallback** ⚠️