From ca5fe6b5b8973b21a76cd41548af39451f90b124 Mon Sep 17 00:00:00 2001
From: Jack Magne <jmagne@dhcp-16-213.sjc.redhat.com>
Date: Thu, 24 Jul 2014 16:08:21 -0700
Subject: [PATCH] Implement Symmetric Key Changeover Feature

1. Created code to actually change the key over to a new key set.
2. Made calls to the code from the format and enrollment operations,
the Pin Reset processor will do so when completed.
3. Tested with real card. Was able to change to key set #2 and then back to stock

Ticket #878.
---
 .../server/tps/channel/SecureChannel.java          |  29 ++++
 .../org/dogtagpki/server/tps/engine/TPSEngine.java |  55 +++++++-
 .../server/tps/processor/TPSEnrollProcessor.java   |   6 +-
 .../server/tps/processor/TPSProcessor.java         | 148 ++++++++++++++++++---
 4 files changed, 214 insertions(+), 24 deletions(-)

diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/channel/SecureChannel.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/channel/SecureChannel.java
index 6d469ee..affdddd 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/channel/SecureChannel.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/channel/SecureChannel.java
@@ -33,6 +33,7 @@ import org.dogtagpki.tps.apdu.InstallAppletAPDU;
 import org.dogtagpki.tps.apdu.InstallLoadAPDU;
 import org.dogtagpki.tps.apdu.LifecycleAPDU;
 import org.dogtagpki.tps.apdu.LoadFileAPDU;
+import org.dogtagpki.tps.apdu.PutKeyAPDU;
 import org.dogtagpki.tps.apdu.ReadObjectAPDU;
 import org.dogtagpki.tps.apdu.SetIssuerInfoAPDU;
 import org.dogtagpki.tps.apdu.SetPinAPDU;
@@ -937,6 +938,7 @@ public class SecureChannel {
 
         computeAPDU(create);
 
+        @SuppressWarnings("unused")
         APDUResponse response = processor.handleAPDURequest(create);
 
         //If the pin already exists we may get an error here, but we go on.
@@ -967,4 +969,31 @@ public class SecureChannel {
 
     }
 
+    public void putKeys(byte curVersion, byte curIndex, TPSBuffer keySetData) throws TPSException, IOException {
+
+        CMS.debug("SecureChannel.putKeys: entering..");
+
+        if (keySetData == null) {
+            throw new TPSException("SecureChannel.putKeys: Invalid input data!", TPSStatus.STATUS_ERROR_KEY_CHANGE_OVER);
+        }
+
+        byte keyVersion = curVersion;
+
+        if (curVersion == 0xff) {
+            keyVersion = 0x0;
+        }
+
+        PutKeyAPDU putKey = new PutKeyAPDU(keyVersion, (byte) (0x80 | curIndex), keySetData);
+
+        computeAPDU(putKey);
+
+        APDUResponse response = processor.handleAPDURequest(putKey);
+
+        if (!response.checkResult()) {
+            throw new TPSException("SecureChannel.putKeys: failed to upgrade key set!",
+                    TPSStatus.STATUS_ERROR_KEY_CHANGE_OVER);
+        }
+
+    }
+
 }
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/engine/TPSEngine.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/engine/TPSEngine.java
index 0cb29e1..dda4d3f 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/engine/TPSEngine.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/engine/TPSEngine.java
@@ -18,6 +18,7 @@
 package org.dogtagpki.server.tps.engine;
 
 import org.dogtagpki.server.tps.cms.TKSComputeSessionKeyResponse;
+import org.dogtagpki.server.tps.cms.TKSCreateKeySetDataResponse;
 import org.dogtagpki.server.tps.cms.TKSRemoteRequestHandler;
 import org.dogtagpki.tps.main.TPSBuffer;
 import org.dogtagpki.tps.main.TPSException;
@@ -161,6 +162,14 @@ public class TPSEngine {
             String connId,
             String tokenType) throws TPSException {
 
+        if (cuid == null || keyInfo == null || card_challenge == null || host_challenge == null
+                || card_cryptogram == null || connId == null || tokenType == null) {
+
+            throw new TPSException("TPSEngine.computeSessionKey: Invalid input data!",
+                    TPSStatus.STATUS_ERROR_SECURE_CHANNEL);
+
+        }
+
         CMS.debug("TPSEngine.computeSessionKey");
 
         TKSRemoteRequestHandler tks = null;
@@ -170,14 +179,14 @@ public class TPSEngine {
             tks = new TKSRemoteRequestHandler(connId);
             resp = tks.computeSessionKey(cuid, keyInfo, card_challenge, card_cryptogram, host_challenge, tokenType);
         } catch (EBaseException e) {
-            throw new TPSException("SecureChannel.computeSessionKey: Error computing session key!" + e,
+            throw new TPSException("TPSEngine.computeSessionKey: Error computing session key!" + e,
                     TPSStatus.STATUS_ERROR_SECURE_CHANNEL);
         }
 
         int status = resp.getStatus();
         if (status != 0) {
-            CMS.debug("SecureChannel.computeSessionKey: Non zero status result: " + status);
-            throw new TPSException("SecureChannel.computeSessionKey: invalid returned status: " + status);
+            CMS.debug("TPSEngine.computeSessionKey: Non zero status result: " + status);
+            throw new TPSException("TPSEngine.computeSessionKey: invalid returned status: " + status);
 
         }
 
@@ -185,6 +194,46 @@ public class TPSEngine {
 
     }
 
+    public TPSBuffer createKeySetData(TPSBuffer newMasterVersion, TPSBuffer oldVersion, TPSBuffer cuid, String connId)
+            throws TPSException {
+        CMS.debug("TPSEngine.createKeySetData. entering...");
+
+        if (newMasterVersion == null || oldVersion == null || cuid == null || connId == null) {
+            throw new TPSException("TPSEngine.createKeySetData: Invalid input data",
+                    TPSStatus.STATUS_ERROR_KEY_CHANGE_OVER);
+        }
+
+        TKSRemoteRequestHandler tks = null;
+
+        TKSCreateKeySetDataResponse resp = null;
+
+        try {
+            tks = new TKSRemoteRequestHandler(connId);
+            resp = tks.createKeySetData(newMasterVersion, oldVersion, cuid);
+        } catch (EBaseException e) {
+
+            throw new TPSException("TPSEngine.createKeySetData, failure to get key set data from TKS",
+                    TPSStatus.STATUS_ERROR_KEY_CHANGE_OVER);
+        }
+
+        int status = resp.getStatus();
+        if (status != 0) {
+            CMS.debug("TPSEngine.createKeySetData: Non zero status result: " + status);
+            throw new TPSException("TPSEngine.computeSessionKey: invalid returned status: " + status, TPSStatus.STATUS_ERROR_KEY_CHANGE_OVER);
+
+        }
+
+        TPSBuffer keySetData = resp.getKeySetData();
+
+        if(keySetData == null) {
+            CMS.debug("TPSEngine.createKeySetData: No valid key set data returned.");
+            throw new TPSException("TPSEngine.createKeySetData: No valid key set data returned.", TPSStatus.STATUS_ERROR_KEY_CHANGE_OVER);
+
+        }
+
+        return keySetData;
+    }
+
     public boolean isTokenPresent(String cuid) {
         boolean present = false;
 
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/processor/TPSEnrollProcessor.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/processor/TPSEnrollProcessor.java
index 300d78d..1b76c97 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/processor/TPSEnrollProcessor.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/processor/TPSEnrollProcessor.java
@@ -98,14 +98,12 @@ public class TPSEnrollProcessor extends TPSProcessor {
 
         CMS.debug("TPSEnrollProcessor.enroll: Finished updating applet if needed.");
 
-        //call stub for key changeover,will take more params when implemented.
+        //Check and upgrade keys if called for
 
         SecureChannel channel = checkAndUpgradeSymKeys();
-
         channel.externalAuthenticate();
 
-        //Call stub to reset pin, method here will be small and call into common pin reset functionality.
-        // Will be implemented during pin reset task.
+        //Reset the token's pin, create one if we don't have one already
 
         checkAndHandlePinReset(channel);
 
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/processor/TPSProcessor.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/processor/TPSProcessor.java
index 22fb573..bb2d1a9 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/processor/TPSProcessor.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/processor/TPSProcessor.java
@@ -974,7 +974,10 @@ public class TPSProcessor {
             statusUpdate(100, "PROGRESS_DONE");
         }
 
-        //ToDo:  Symmetric Key Changeover
+        // Upgrade Symm Keys if needed
+
+        SecureChannel channel = checkAndUpgradeSymKeys();
+        channel.externalAuthenticate();
 
         // ToDo: Update Token DB
         // ToDo:  Revoke certificates
@@ -1618,13 +1621,8 @@ public class TPSProcessor {
         AppletInfo result = null;
 
         CMS.debug("TPSProcessor.getAppletInfo, entering ...");
-        TPSBuffer aidBuf = getCardManagerAID();
-
-        APDUResponse select = selectApplet((byte) 0x04, (byte) 0x00, aidBuf);
 
-        if (!select.checkResult()) {
-            throw new TPSException("TPSProcessor.getAppletInfo: Can't selelect the card manager!");
-        }
+        selectCardManager();
 
         TPSBuffer cplc_data = getCplcData();
         CMS.debug("cplc_data: " + cplc_data.toString());
@@ -1636,11 +1634,7 @@ public class TPSProcessor {
          * Checks if the netkey has the required applet version.
          */
 
-        TPSBuffer netkeyAid = getNetkeyAID();
-
-        // We don't care if the above fails now. getStatus will determine outcome.
-
-        select = selectApplet((byte) 0x04, (byte) 0x00, netkeyAid);
+        selectCoolKeyApplet();
 
         TPSBuffer token_status = getStatus();
 
@@ -1686,6 +1680,17 @@ public class TPSProcessor {
         return result;
     }
 
+    protected void selectCardManager() throws TPSException, IOException {
+        CMS.debug("TPSProcessor.selectCardManager: entering..");
+        TPSBuffer aidBuf = getCardManagerAID();
+
+        APDUResponse select = selectApplet((byte) 0x04, (byte) 0x00, aidBuf);
+
+        if (!select.checkResult()) {
+            throw new TPSException("TPSProcessor.selectCardManager: Can't selelect the card manager applet!");
+        }
+    }
+
     protected boolean checkSymmetricKeysEnabled() throws TPSException {
         boolean result = true;
 
@@ -1703,27 +1708,136 @@ public class TPSProcessor {
         return result;
     }
 
+    protected int getSymmetricKeysRequiredVersion() throws TPSException {
+        int version = 0;
+        ;
+
+        IConfigStore configStore = CMS.getConfigStore();
+
+        String requiredVersionConfig = "op" + "." + currentTokenOperation + "." + selectedTokenType + "."
+                + "update.symmetricKeys.requiredVersion";
+
+        CMS.debug("TPSProcessor.getSymmetricKeysRequiredVersion: configValue: " + requiredVersionConfig);
+        try {
+            version = configStore.getInteger(requiredVersionConfig, 0x0);
+        } catch (EBaseException e) {
+            throw new TPSException("TPSProcessor.getSymmetricKeysRequired: Internal error getting config value.");
+        }
+
+        CMS.debug("TPSProcessor.getSymmetricKeysRequiredVersion: returning version: " + version);
+
+        return version;
+    }
+
     protected SecureChannel checkAndUpgradeSymKeys() throws TPSException, IOException {
 
+        /* If the key of the required version is
+          not found, create them.
+
+          This sends a InitializeUpdate request to the token.
+          We tell the token to use whatever it thinks is the
+          default key version (0). It will return the version
+          of the key it actually used later. (This is accessed
+          with GetKeyInfoData below)
+          [ Note: This is not explained very well in the manual
+            The token can have multiple sets of symmetric keys
+            Each set is given a version number, which I think is
+            better thought of as a SLOT. One key slot is populated
+            with a set of keys when the token is manufactured.
+            This is then designated as the default key set version.
+            Later, we will write a new key set with PutKey, and
+            set it to be the new default]
+        */
+
         SecureChannel channel = null;
 
+        int defKeyVersion = 0;
+        int defKeyIndex = getChannelDefKeyIndex();
+
         if (checkSymmetricKeysEnabled()) {
-            //To be implemented later
-            throw new TPSException("TPSProcessor.checkAndUpgradeSymKeys: Key changeover not yet implemented!",
-                    TPSStatus.STATUS_ERROR_MISCONFIGURATION);
+
+            CMS.debug("TPSProcessor.checkAndUpgradeSymKeys: Symm key upgrade enabled.");
+            int requiredVersion = getSymmetricKeysRequiredVersion();
+
+            // try to make a secure channel with the 'requiredVersion' keys
+            // If this fails, we know we will have to attempt an upgrade
+            // of the keys
+
+            boolean failed = false;
+            try {
+
+                channel = setupSecureChannel((byte) requiredVersion, (byte) defKeyIndex,
+                        SecurityLevel.SECURE_MSG_MAC_ENC,
+                        getTKSConnectorID());
+
+            } catch (TPSException e) {
+
+                CMS.debug("TPSProcessor.checkAndUpgradeSymKeys: failed to create secure channel with required version, we need to upgrade the keys.");
+                failed = true;
+            }
+
+            //If we failed we need to upgrade the keys
+            if (failed == true) {
+
+                selectCardManager();
+
+                channel = setupSecureChannel();
+
+                /* Assemble the Buffer with the version information
+                 The second byte is the key offset, which is always 1
+                */
+
+                byte[] nv = { (byte) requiredVersion, 0x01 };
+                TPSBuffer newVersion = new TPSBuffer(nv);
+
+                // GetKeyInfoData will return a buffer which is bytes 11,12 of
+                // the data structure on page 89 of Cyberflex Access Programmer's
+                // Guide
+                // Byte 0 is the key set version.
+                // Byte 1 is the index into that key set
+
+                String connId = getTKSConnectorID();
+                TPSBuffer curKeyInfo = channel.getKeyInfoData();
+                TPSEngine engine = getTPSEngine();
+
+                TPSBuffer keySetData = engine.createKeySetData(newVersion, curKeyInfo,
+                        channel.getKeyDiversificationData(), connId);
+
+                CMS.debug("TPSProcessor.checkAndUpgradeSymKeys: new keySetData from TKS: " + keySetData.toHexString());
+
+                byte curVersion = curKeyInfo.at(0);
+                byte curIndex = curKeyInfo.at(1);
+
+                channel.putKeys(curVersion, curIndex, keySetData);
+
+                String curVersionStr = curKeyInfo.toHexString();
+                String newVersionStr = newVersion.toHexString();
+
+                CMS.debug("TPSProcessor.checkAndUpgradeSymKeys: curVersionStr: " + curVersionStr + " newVersionStr: "
+                        + newVersionStr);
+
+                selectCoolKeyApplet();
+
+                channel = setupSecureChannel((byte) requiredVersion, (byte) defKeyIndex,
+                        SecurityLevel.SECURE_MSG_MAC_ENC,
+                        getTKSConnectorID());
+
+            } else {
+                CMS.debug("TPSProcessor.checkAndUpgradeSymeKeys: We are already at the desired key set, returning secure channel.");
+            }
 
         } else {
             //Create a standard secure channel with current key set.
             CMS.debug("TPSProcessor.checkAndUpgradeSymKeys: Key changeover disabled in the configuration.");
 
-            int defKeyVersion = getChannelDefKeyVersion();
-            int defKeyIndex = getChannelDefKeyIndex();
+            defKeyVersion = getChannelDefKeyVersion();
 
             channel = setupSecureChannel((byte) defKeyVersion, (byte) defKeyIndex, SecurityLevel.SECURE_MSG_MAC_ENC,
                     getTKSConnectorID());
 
         }
 
+        CMS.debug("TPSProcessor.checkAndUpdradeSymKeys: Leaving successfully....");
         return channel;
     }
 
-- 
1.8.3.1

