>From 6d6a56b3a079b8e6f56d098d169e52a46da68509 Mon Sep 17 00:00:00 2001
From: Christina Fu <cfu@redhat.com>
Date: Tue, 2 Dec 2014 14:38:08 -0800
Subject: [PATCH] Ticket #864 865 866 (part 1 symkey, common) NIST SP800-108
 KDF

---
 .../cms/selftests/tks/TKSKnownSessionKey.java      |  59 +-
 .../cms/servlet/csadmin/ConfigurationUtils.java    |   3 +
 .../com/netscape/cms/servlet/tks/TokenServlet.java | 665 +++++++++++++++++----
 base/server/cmsbundle/src/LogMessages.properties   |  80 +++
 base/symkey/src/com/netscape/symkey/CMakeLists.txt |   2 +
 .../symkey/src/com/netscape/symkey/EncryptData.cpp | 140 ++++-
 .../src/com/netscape/symkey/NistSP800_108KDF.cpp   | 470 +++++++++++++++
 .../src/com/netscape/symkey/NistSP800_108KDF.h     | 115 ++++
 base/symkey/src/com/netscape/symkey/SessionKey.cpp | 580 ++++++++++++++++--
 .../symkey/src/com/netscape/symkey/SessionKey.java |  25 +-
 base/symkey/src/com/netscape/symkey/SymKey.cpp     | 424 +++++++++++--
 base/tks/shared/conf/CS.cfg.in                     |  10 +-
 12 files changed, 2345 insertions(+), 228 deletions(-)
 create mode 100644 base/symkey/src/com/netscape/symkey/NistSP800_108KDF.cpp
 create mode 100644 base/symkey/src/com/netscape/symkey/NistSP800_108KDF.h

diff --git a/base/server/cms/src/com/netscape/cms/selftests/tks/TKSKnownSessionKey.java b/base/server/cms/src/com/netscape/cms/selftests/tks/TKSKnownSessionKey.java
index 06a6398..d8a2619 100644
--- a/base/server/cms/src/com/netscape/cms/selftests/tks/TKSKnownSessionKey.java
+++ b/base/server/cms/src/com/netscape/cms/selftests/tks/TKSKnownSessionKey.java
@@ -67,6 +67,9 @@ public class TKSKnownSessionKey
     private byte[] mCUID = null;
     private byte[] mMacKey = null;
     private byte[] mSessionKey = null;
+    private byte mNistSP800_108KdfOnKeyVersion = 0;       // AC: KDF SPEC CHANGE
+    private boolean mNistSP800_108KdfUseCuidAsKdd = false;  // AC: KDF SPEC CHANGE
+    private byte[] mKDD = null;                   // AC: KDF SPEC CHANGE
 
     /**
      * Initializes this subsystem with the configuration store
@@ -101,6 +104,46 @@ public class TKSKnownSessionKey
         mMacKey = getConfigByteArray("macKey", 16);
         mUseSoftToken = getConfigString("useSoftToken");
 
+        // AC: KDF SPEC CHANGE 
+        // read CUID for the KDD field
+        mKDD = getConfigByteArray("CUID", 10);
+        //
+        //
+        // read self-test configuration item for nistSP800-108KdfOnKeyVersion
+        //
+        // read setting as string
+        String nistSP800_108KdfOnKeyVersion_str = getConfigString("nistSP800-108KdfOnKeyVersion");
+        short nistSP800_108KdfOnKeyVersion_short;
+        // convert setting value (in ASCII-hex) to short
+        try{
+                nistSP800_108KdfOnKeyVersion_short = Short.parseShort(nistSP800_108KdfOnKeyVersion_str,16);
+                if ((nistSP800_108KdfOnKeyVersion_short < 0) || (nistSP800_108KdfOnKeyVersion_short > (short)0x00FF)){
+                        throw new Exception("Out of range.");
+                }
+        }catch(Throwable t){
+                mSelfTestSubsystem.log (mSelfTestSubsystem.getSelfTestLogger(),
+                    CMS.getLogMessage("SELFTESTS_MISSING_VALUES",
+                    getSelfTestName(), mPrefix + ".nistSP800-108KdfOnKeyVersion"));
+                throw new EMissingSelfTestException("nistSP800-108KdfOnKeyVersion");
+        }
+        // convert to byte (anything higher than 0x7F is represented as negative)
+        mNistSP800_108KdfOnKeyVersion = (byte)nistSP800_108KdfOnKeyVersion_short;
+        //
+        //
+        // read self-test configuration item for nistSP800-108KdfUseCuidAsKdd
+        //
+        // read setting as string
+        String nistSP800_108KdfUseCuidAsKdd_str = getConfigString("nistSP800-108KdfUseCuidAsKdd");
+        // convert setting value to boolean
+        try{
+                mNistSP800_108KdfUseCuidAsKdd = Boolean.parseBoolean(nistSP800_108KdfUseCuidAsKdd_str);
+        }catch(Throwable t){
+                mSelfTestSubsystem.log (mSelfTestSubsystem.getSelfTestLogger(),
+                    CMS.getLogMessage("SELFTESTS_MISSING_VALUES",
+                    getSelfTestName(), mPrefix + ".nistSP800-108KdfUseCuidAsKdd"));
+                throw new EMissingSelfTestException("nistSP800-108KdfUseCuidAsKdd");
+        }
+        
         String defKeySetMacKey = null;
         tks = CMS.getSubsystem(mTksSubId);
         if (tks != null) {
@@ -132,7 +175,12 @@ public class TKSKnownSessionKey
             if (mSessionKey == null) {
                 mSessionKey = SessionKey.ComputeSessionKey(mToken, mKeyName,
                                                             mCardChallenge, mHostChallenge,
-                                                            mKeyInfo, mCUID, mMacKey, mUseSoftToken, null, null);
+                                                            mKeyInfo, 
+                                                            mNistSP800_108KdfOnKeyVersion,   // AC: KDF SPEC CHANGE - pass in configuration self-test value
+                                                            mNistSP800_108KdfUseCuidAsKdd,   // AC: KDF SPEC CHANGE - pass in configuration self-test value
+                                                            mCUID,
+                                                            mKDD,                            // AC: KDF SPEC CHANGE - pass in KDD
+                                                            mMacKey, mUseSoftToken, null, null);
                 if (mSessionKey == null || mSessionKey.length != 16) {
                     mSelfTestSubsystem.log(mSelfTestSubsystem.getSelfTestLogger(),
                                             CMS.getLogMessage("SELFTESTS_MISSING_VALUES",
@@ -314,8 +362,13 @@ public class TKSKnownSessionKey
         String keySet = "defKeySet";
 
         byte[] sessionKey = SessionKey.ComputeSessionKey(
-                mToken, mKeyName, mCardChallenge, mHostChallenge, mKeyInfo,
-                mCUID, mMacKey, mUseSoftToken, keySet, sharedSecretName);
+                mToken, mKeyName, mCardChallenge, mHostChallenge,
+                mKeyInfo,
+                mNistSP800_108KdfOnKeyVersion,   // AC: KDF SPEC CHANGE - pass in configuration self-test value
+                mNistSP800_108KdfUseCuidAsKdd,   // AC: KDF SPEC CHANGE - pass in configuration self-test value
+                mCUID,
+                mKDD,                            // AC: KDF SPEC CHANGE - pass in KDD
+                mMacKey, mUseSoftToken, keySet, null);
 
         // Now we just see if we can successfully generate a session key.
         // For FIPS compliance, the routine now returns a wrapped key, which can't be extracted and compared.
diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
index f443238..a7eec4e 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/ConfigurationUtils.java
@@ -273,6 +273,8 @@ public class ConfigurationUtils {
             HttpResponse httpresponse = httpclient.send(httprequest);
 
             c = httpresponse.getContent();
+            //cfu
+            
         } catch (ConnectException e) {
             CMS.debug("getHttpResponse: " + e.toString());
             throw new IOException("The server you tried to contact is not running.");
@@ -4023,6 +4025,7 @@ public class ConfigurationUtils {
             CMS.debug("registerUser: response is empty or null.");
             throw new IOException("The server " + targetURI + "is not available");
         } else {
+            CMS.debug("registerUser: response: " + response);
             ByteArrayInputStream bis = new ByteArrayInputStream(response.getBytes());
             XMLObject parser = new XMLObject(bis);
 
diff --git a/base/server/cms/src/com/netscape/cms/servlet/tks/TokenServlet.java b/base/server/cms/src/com/netscape/cms/servlet/tks/TokenServlet.java
index 9b0c2bc..2cca81b 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/tks/TokenServlet.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/tks/TokenServlet.java
@@ -72,25 +72,25 @@ public class TokenServlet extends CMSServlet {
     IPrettyPrintFormat pp = CMS.getPrettyPrintFormat(":");
 
     private final static String LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST =
-            "LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_3";
+            "LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_4"; // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID.
 
     private final static String LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS =
-            "LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS_8";
+            "LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS_13"; // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID.  Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd.
 
     private final static String LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE =
-            "LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE_9";
+            "LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE_14"; // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID.  Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd.
 
     private final static String LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST =
-            "LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_5";
+            "LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_6"; // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID.
 
     private final static String LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS =
-            "LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS_6";
+            "LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS_12"; // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID.  Also added TKSKeyset, OldKeyInfo_KeyVersion, NewKeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd.
 
     private final static String LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE =
-            "LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE_7";
+            "LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE_13"; // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID.  Also added TKSKeyset, OldKeyInfo_KeyVersion, NewKeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd.
 
     private final static String LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST =
-            "LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_4";
+            "LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_5"; // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID.
 
     private final static String LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_SUCCESS =
             "LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_SUCCESS_7";
@@ -216,9 +216,110 @@ public class TokenServlet extends CMSServlet {
 
     }
 
+    // AC: KDF SPEC CHANGE - read new setting value from config file 
+    // (This value allows configuration of which master keys use the NIST SP800-108 KDF and which use the original KDF for backwards compatibility)
+    // CAREFUL:  Result returned may be negative due to java's lack of unsigned types.
+    //           Negative values need to be treated as higher key numbers than positive key numbers.
+    private static byte read_setting_nistSP800_108KdfOnKeyVersion(String keySet) throws Exception{
+        String nistSP800_108KdfOnKeyVersion_map = "tks." + keySet + ".nistSP800-108KdfOnKeyVersion";
+        // KDF phase1: default to 00
+        String nistSP800_108KdfOnKeyVersion_value =
+            CMS.getConfigStore().getString(nistSP800_108KdfOnKeyVersion_map, "00" /*null*/);
+        short nistSP800_108KdfOnKeyVersion_short = 0;
+        // if value does not exist in file
+        if (nistSP800_108KdfOnKeyVersion_value == null){
+            // throw
+            //  (we want admins to pay attention to this configuration item rather than guessing for them)
+            throw new Exception("Required configuration value \"" + nistSP800_108KdfOnKeyVersion_map + "\" missing from configuration file.");
+        }
+        // convert setting value (in ASCII-hex) to short
+        try{
+            nistSP800_108KdfOnKeyVersion_short = Short.parseShort(nistSP800_108KdfOnKeyVersion_value, 16);
+            if ((nistSP800_108KdfOnKeyVersion_short < 0) || (nistSP800_108KdfOnKeyVersion_short > (short)0x00FF)){
+                throw new Exception("Out of range.");
+            }
+        } catch(Throwable t){
+            throw new Exception("Configuration value \"" + nistSP800_108KdfOnKeyVersion_map + "\" is in incorrect format. " + 
+                                "Correct format is \"" + nistSP800_108KdfOnKeyVersion_map + "=xx\" where xx is key version specified in ASCII-HEX format.", t);
+        }
+        // convert to byte (anything higher than 0x7F is represented as a negative)
+        byte nistSP800_108KdfOnKeyVersion_byte = (byte)nistSP800_108KdfOnKeyVersion_short;
+        return nistSP800_108KdfOnKeyVersion_byte;
+    }
+    
+    // AC: KDF SPEC CHANGE - read new setting value from config file 
+    // (This value allows configuration of the NIST SP800-108 KDF:
+    //   If "true" we use the CUID parameter within the NIST SP800-108 KDF.
+    //   If "false" we use the KDD parameter within the NIST SP800-108 KDF.
+    private static boolean read_setting_nistSP800_108KdfUseCuidAsKdd(String keySet) throws Exception{
+        String setting_map = "tks." + keySet + ".nistSP800-108KdfUseCuidAsKdd";
+        // KDF phase1: default to "false"
+        String setting_str =
+            CMS.getConfigStore().getString(setting_map, "false" /*null*/);
+        boolean setting_boolean = false;
+        // if value does not exist in file
+        if (setting_str == null){
+            // throw
+            //  (we want admins to pay attention to this configuration item rather than guessing for them)
+            throw new Exception("Required configuration value \"" + setting_map + "\" missing from configuration file.");
+        }
+        // convert setting value to boolean
+        try{
+            setting_boolean = Boolean.parseBoolean(setting_str);
+        }catch(Throwable t){
+            throw new Exception("Configuration value \"" + setting_map + "\" is in incorrect format.  Should be either \"true\" or \"false\".", t);
+        }
+        return setting_boolean;
+    }
+    
+    // AC: KDF SPEC CHANGE - Audit logging helper functions.
+    // Converts a byte array to an ASCII-hex string.
+    //   We implemented this ourselves rather than using this.pp.toHexArray() because 
+    //   the team preferred CUID and KDD strings to be without ":" separators every byte. 
+    final char[] bytesToHex_hexArray = "0123456789ABCDEF".toCharArray();
+    private String bytesToHex(byte[] bytes){
+        char[] hexChars = new char[bytes.length * 2];
+        for (int i = 0; i < bytes.length; i++){
+            int thisChar = bytes[i] & 0x000000FF;
+            hexChars[i * 2] = bytesToHex_hexArray[thisChar >>> 4];    // div 16
+            hexChars[i*2 + 1] = bytesToHex_hexArray[thisChar & 0x0F];
+        }
+        return new String(hexChars);
+    }
+    
+    // AC: KDF SPEC CHANGE - Audit logging helper functions.
+    // Safely converts a keyInfo byte array to a Key version hex string in the format: 0xa
+    // Since key version is always the first byte, this function returns the unsigned hex string representation of parameter[0]. 
+    //   Returns "null" if parameter is null.
+    //   Returns "invalid" if parameter.length < 1
+    private String log_string_from_keyInfo(byte[] xkeyInfo){
+        return (xkeyInfo == null) ? "null" : (xkeyInfo.length < 1 ? "invalid" : "0x" + Integer.toHexString((int)(xkeyInfo[0]) & 0x000000FF) );
+    }
+    
+    // AC: KDF SPEC CHANGE - Audit logging helper functions.
+    // Safely converts a byte array containing specialDecoded information to an ASCII-hex string.
+    // Parameters:
+    //   specialDecoded - byte array containing data.  May be null.
+    // Returns:
+    //   if specialDecoded is blank, returns "null"
+    //   if specialDecoded != null, returns <ASCII-HEX string representation of specialDecoded>
+    private String log_string_from_specialDecoded_byte_array(byte[] specialDecoded){
+        if (specialDecoded == null){
+            return "null";
+        }else{
+            return bytesToHex(specialDecoded);
+        }
+    }
+    
+
     private void processComputeSessionKey(HttpServletRequest req,
             HttpServletResponse resp) throws EBaseException {
-        byte[] card_challenge, host_challenge, keyInfo, xCUID, CUID, session_key;
+        byte[] card_challenge ,host_challenge,keyInfo, xCUID, session_key, xKDD;  // AC: KDF SPEC CHANGE: removed duplicative 'CUID' variable and added xKDD
+
+        // AC: KDF SPEC CHANGE - new config file values (needed for symkey)
+        byte nistSP800_108KdfOnKeyVersion = (byte)0xff;
+        boolean nistSP800_108KdfUseCuidAsKdd = false;
+
         byte[] card_crypto, host_cryptogram, input_card_crypto;
         byte[] xcard_challenge, xhost_challenge;
         byte[] enc_session_key, xkeyInfo;
@@ -228,6 +329,15 @@ public class TokenServlet extends CMSServlet {
         String transportKeyName = "";
 
         String rCUID = req.getParameter(IRemoteRequest.TOKEN_CUID);
+
+        // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
+        String rKDD = req.getParameter("KDD");
+        if ((rKDD == null) || (rKDD.length() == 0)) {
+            // KDF phase1: default to rCUID if not present
+            CMS.debug("TokenServlet: KDD not supplied, set to CUID before TPS change");
+            rKDD = rCUID;
+        }
+
         String keySet = req.getParameter(IRemoteRequest.TOKEN_KEYSET);
         if (keySet == null || keySet.equals("")) {
             keySet = "defKeySet";
@@ -243,6 +353,10 @@ public class TokenServlet extends CMSServlet {
         IConfigStore sconfig = CMS.getConfigStore();
         boolean isCryptoValidate = true;
         boolean missingParam = false;
+        
+        // AC: KDF SPEC CHANGE - flag for if there is an error reading our new setting
+        Exception missingSetting_exception = null;
+        
         session_key = null;
         card_crypto = null;
         host_cryptogram = null;
@@ -257,9 +371,11 @@ public class TokenServlet extends CMSServlet {
                     (String) sContext.get(SessionContext.USER_ID);
         }
 
+        // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID
         auditMessage = CMS.getLogMessage(
                          LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST,
                         rCUID,
+                        rKDD,            // AC: KDF SPEC CHANGE - Log both CUID and KDD.
                         ILogger.SUCCESS,
                         agentId);
 
@@ -299,6 +415,13 @@ public class TokenServlet extends CMSServlet {
             badParams += " CUID,";
             missingParam = true;
         }
+        
+        // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
+        if ((rKDD == null) || (rKDD.length() == 0)) {
+            CMS.debug("TokenServlet: ComputeSessionKey(): missing request parameter: KDD");
+            badParams += " KDD,";
+            missingParam = true;
+        }
 
         if ((rcard_challenge == null) || (rcard_challenge.equals(""))) {
             badParams += " card_challenge,";
@@ -322,6 +445,11 @@ public class TokenServlet extends CMSServlet {
         String keyNickName = null;
         boolean sameCardCrypto = true;
 
+        // AC: KDF SPEC CHANGE 
+        xCUID = null;       // avoid errors about non-initialization
+        xKDD = null;        // avoid errors about non-initialization
+        xkeyInfo = null;    // avoid errors about non-initialization
+        
         if (!missingParam) {
 
             xCUID = com.netscape.cmsutil.util.Utils.SpecialDecode(rCUID);
@@ -330,6 +458,15 @@ public class TokenServlet extends CMSServlet {
                 CMS.debug("TokenServlet: Invalid CUID length");
                 missingParam = true;
             }
+            
+            // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
+            xKDD = com.netscape.cmsutil.util.Utils.SpecialDecode(rKDD);
+            if (xKDD == null || xKDD.length != 10) {
+                        badParams += " KDD length,";
+                        CMS.debug("TokenServlet: Invalid KDD length");
+                        missingParam = true;
+            }
+            
             xkeyInfo = com.netscape.cmsutil.util.Utils.SpecialDecode(rKeyInfo);
             if (xkeyInfo == null || xkeyInfo.length != 2) {
                 badParams += " KeyInfo length,";
@@ -353,7 +490,9 @@ public class TokenServlet extends CMSServlet {
 
         }
 
-        CUID = null;
+        // AC: KDF SPEC CHANGE - Remove duplicative variable.
+        // CUID = null;  
+        
         if (!missingParam) {
             card_challenge =
                     com.netscape.cmsutil.util.Utils.SpecialDecode(rcard_challenge);
@@ -361,7 +500,33 @@ public class TokenServlet extends CMSServlet {
             host_challenge = com.netscape.cmsutil.util.Utils.SpecialDecode(rhost_challenge);
             keyInfo = com.netscape.cmsutil.util.Utils.SpecialDecode(rKeyInfo);
 
-            CUID = com.netscape.cmsutil.util.Utils.SpecialDecode(rCUID);
+            // CUID = com.netscape.cmsutil.util.Utils.SpecialDecode(rCUID);
+            // AC: KDF SPEC CHANGE: Removed duplicative variable/processing.
+
+            // AC: KDF SPEC CHANGE - read new config file values (needed for symkey)
+            try{
+                nistSP800_108KdfOnKeyVersion = TokenServlet.read_setting_nistSP800_108KdfOnKeyVersion(keySet);
+                nistSP800_108KdfUseCuidAsKdd = TokenServlet.read_setting_nistSP800_108KdfUseCuidAsKdd(keySet);
+
+                // log settings read in to debug log along with xkeyInfo
+                CMS.debug("TokenServlet: ComputeSessionKey():  xkeyInfo[0] = 0x"
+                         + Integer.toHexString((int)(xkeyInfo[0]) & 0x0000000FF)
+                         + ",  xkeyInfo[1] = 0x"
+                         + Integer.toHexString((int)(xkeyInfo[1]) & 0x0000000FF)
+                         );
+                CMS.debug("TokenServlet: ComputeSessionKey():  Nist SP800-108 KDF will be used for key versions >= 0x"
+                         + Integer.toHexString((int)(nistSP800_108KdfOnKeyVersion) & 0x0000000FF)
+                         );
+                if (nistSP800_108KdfUseCuidAsKdd == true){
+                    CMS.debug("TokenServlet: ComputeSessionKey():  Nist SP800-108 KDF (if used) will use CUID instead of KDD.");
+                }else{
+                    CMS.debug("TokenServlet: ComputeSessionKey():  Nist SP800-108 KDF (if used) will use KDD.");
+                }
+            // conform to the set-an-error-flag mentality
+            }catch(Exception e){
+                missingSetting_exception = e;
+                CMS.debug("TokenServlet: ComputeSessionKey():  Exception reading Nist SP800-108 KDF config values: " + e.toString());
+            }
 
             String keyInfoMap = "tks." + keySet + ".mk_mappings." + rKeyInfo; //#xx#xx
             String mappingValue = CMS.getConfigStore().getString(keyInfoMap, null);
@@ -377,7 +542,9 @@ public class TokenServlet extends CMSServlet {
                     keyNickName = st.nextToken();
             }
 
-            if (selectedToken != null && keyNickName != null) {
+            if (selectedToken != null && keyNickName != null
+                    // AC: KDF SPEC CHANGE - check for error flag
+                    && missingSetting_exception == null) {
 
                 try {
 
@@ -388,7 +555,12 @@ public class TokenServlet extends CMSServlet {
                             + selectedToken + " keyNickName=" + keyNickName);
                     session_key = SessionKey.ComputeSessionKey(
                             selectedToken, keyNickName, card_challenge,
-                            host_challenge, keyInfo, CUID, macKeyArray, useSoftToken_s, keySet, transportKeyName);
+                             host_challenge,keyInfo,
+                             nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                             nistSP800_108KdfUseCuidAsKdd,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                             xCUID,                           // AC: KDF SPEC CHANGE - removed duplicative 'CUID' variable and replaced with 'xCUID'
+                             xKDD,                            // AC: KDF SPEC CHANGE - pass in KDD so symkey can make decision about which value (KDD,CUID) to use
+                             macKeyArray, useSoftToken_s, keySet, transportKeyName );
 
                     if (session_key == null) {
                         CMS.debug("TokenServlet:Tried ComputeSessionKey, got NULL ");
@@ -401,7 +573,12 @@ public class TokenServlet extends CMSServlet {
                                     + keySet + ".auth_key"));
                     enc_session_key = SessionKey.ComputeEncSessionKey(
                             selectedToken, keyNickName, card_challenge,
-                            host_challenge, keyInfo, CUID, encKeyArray, useSoftToken_s, keySet);
+                      host_challenge,keyInfo,
+                      nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                      nistSP800_108KdfUseCuidAsKdd,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                      xCUID,                           // AC: KDF SPEC CHANGE - removed duplicative 'CUID' variable and replaced with 'xCUID'
+                      xKDD,                            // AC: KDF SPEC CHANGE - pass in KDD so symkey can make decision about which value (KDD,CUID) to use
+                      encKeyArray, useSoftToken_s, keySet);
 
                     if (enc_session_key == null) {
                         CMS.debug("TokenServlet:Tried ComputeEncSessionKey, got NULL ");
@@ -426,7 +603,12 @@ public class TokenServlet extends CMSServlet {
 
                         kek_key = SessionKey.ComputeKekKey(
                                 selectedToken, keyNickName, card_challenge,
-                                host_challenge, keyInfo, CUID, kekKeyArray, useSoftToken_s, keySet);
+                             host_challenge,keyInfo,
+                             nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                             nistSP800_108KdfUseCuidAsKdd,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                             xCUID,                           // AC: KDF SPEC CHANGE - removed duplicative 'CUID' variable and replaced with 'xCUID'
+                             xKDD,                            // AC: KDF SPEC CHANGE - pass in KDD so symkey can make decision about which value (KDD,CUID) to use
+                             kekKeyArray, useSoftToken_s,keySet);
 
                         CMS.debug("TokenServlet: called ComputeKekKey");
 
@@ -462,10 +644,19 @@ public class TokenServlet extends CMSServlet {
                             CMS.debug("TokenServlet: key encryption key generated on " + selectedToken);
                             desKey = SessionKey.GenerateSymkey(selectedToken);
                         }
-                        if (desKey != null)
-                            CMS.debug("TokenServlet: key encryption key generated for " + rCUID);
-                        else {
-                            CMS.debug("TokenServlet: key encryption key generation failed for " + rCUID);
+                        if (desKey != null) {
+                            // AC: KDF SPEC CHANGE - Output using CUID and KDD
+                            CMS.debug("TokenServlet: key encryption key generated for CUID=" +
+                                trim(pp.toHexString(xCUID)) +
+                                ", KDD=" +
+                                trim(pp.toHexString(xKDD)));
+                        } else {
+                            // AC: KDF SPEC CHANGE - Output using CUID and KDD
+                            CMS.debug("TokenServlet: key encryption key generation failed for CUID=" +
+                                trim(pp.toHexString(xCUID)) +
+                                ", KDD=" +
+                                trim(pp.toHexString(xKDD)));
+
                             throw new Exception("can't generate key encryption key");
                         }
 
@@ -538,7 +729,12 @@ public class TokenServlet extends CMSServlet {
                                     + keySet + ".auth_key"));
                     host_cryptogram = SessionKey.ComputeCryptogram(
                             selectedToken, keyNickName, card_challenge,
-                            host_challenge, keyInfo, CUID, 0, authKeyArray, useSoftToken_s, keySet);
+                      host_challenge,keyInfo,
+                      nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                      nistSP800_108KdfUseCuidAsKdd,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                      xCUID,                           // AC: KDF SPEC CHANGE - removed duplicative 'CUID' variable and replaced with 'xCUID'
+                      xKDD,                            // AC: KDF SPEC CHANGE - pass in KDD so symkey can make decision about which value (KDD,CUID) to use
+                      0, authKeyArray, useSoftToken_s, keySet);
 
                     if (host_cryptogram == null) {
                         CMS.debug("TokenServlet:Tried ComputeCryptogram, got NULL ");
@@ -547,7 +743,13 @@ public class TokenServlet extends CMSServlet {
                     }
                     card_crypto = SessionKey.ComputeCryptogram(
                             selectedToken, keyNickName, card_challenge,
-                            host_challenge, keyInfo, CUID, 1, authKeyArray, useSoftToken_s, keySet);
+                      host_challenge,keyInfo,
+                      nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                      nistSP800_108KdfUseCuidAsKdd,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                      xCUID,                           // AC: KDF SPEC CHANGE - removed duplicative 'CUID' variable and replaced with 'xCUID'
+                      xKDD,                            // AC: KDF SPEC CHANGE - pass in KDD so symkey can make decision about which value (KDD,CUID) to use
+                      1, authKeyArray, useSoftToken_s, keySet);
+
 
                     if (card_crypto == null) {
                         CMS.debug("TokenServlet:Tried ComputeCryptogram, got NULL ");
@@ -575,10 +777,13 @@ public class TokenServlet extends CMSServlet {
                         }
                     }
 
+                    // AC: KDF SPEC CHANGE - print both KDD and CUID
                     CMS.getLogger().log(ILogger.EV_AUDIT,
                             ILogger.S_TKS,
                             ILogger.LL_INFO, "processComputeSessionKey for CUID=" +
-                                    trim(pp.toHexString(CUID)));
+                            trim(pp.toHexString(xCUID)) +
+                            ", KDD=" +
+                            trim(pp.toHexString(xKDD)));
                 } catch (Exception e) {
                     CMS.debug(e);
                     CMS.debug("TokenServlet Computing Session Key: " + e.toString());
@@ -625,18 +830,35 @@ public class TokenServlet extends CMSServlet {
             cryptogram =
                     com.netscape.cmsutil.util.Utils.SpecialEncode(host_cryptogram);
         } else {
-            status = "2";
+            // AC: Bugfix: Don't override status's value if an error was already flagged
+            if (status.equals("0") == true){
+                status = "2";
+            }
         }
 
         if (selectedToken == null || keyNickName == null) {
-            status = "4";
+            // AC: Bugfix: Don't override status's value if an error was already flagged
+            if (status.equals("0") == true){
+                status = "4";
+            }
         }
 
         if (!sameCardCrypto) {
-            status = "3";
+            // AC: Bugfix: Don't override status's value if an error was already flagged
+            if (status.equals("0") == true){
+                // AC: Bugfix: Don't mis-represent host cryptogram mismatch errors as TPS parameter issues
+                status = "5";
+            }
         }
 
+        // AC: KDF SPEC CHANGE - check for settings file issue (flag)
+        if (missingSetting_exception != null){
+            // AC: Intentionally override previous errors if config file settings were missing.
+            status = "6";
+        }
+        
         if (missingParam) {
+            // AC: Intentionally override previous errors if parameters were missing.
             status = "3";
         }
 
@@ -650,10 +872,20 @@ public class TokenServlet extends CMSServlet {
                 errorMsg = "Problem creating host_cryptogram.";
             }
 
+            // AC: Bugfix: Don't mis-represent card cryptogram mismatch errors as TPS parameter issues
+            if (status.equals("5")) {
+               errorMsg = "Card cryptogram mismatch. Token likely has incorrect keys.";
+            }
+
             if (status.equals("4")) {
                 errorMsg = "Problem obtaining token information.";
             }
 
+            // AC: KDF SPEC CHANGE - handle missing configuration item
+            if (status.equals("6")) {
+                errorMsg = "Problem reading required configuration value.";
+            }
+
             if (status.equals("3")) {
                 if (badParams.endsWith(",")) {
                     badParams = badParams.substring(0, badParams.length() - 1);
@@ -706,31 +938,45 @@ public class TokenServlet extends CMSServlet {
         }
 
         if (status.equals("0")) {
-
-            auditMessage = CMS.getLogMessage(
-                         LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS,
-                        rCUID,
-                        ILogger.SUCCESS,
-                        status,
-                        agentId,
-                        isCryptoValidate ? "true" : "false",
-                        serversideKeygen ? "true" : "false",
-                        selectedToken,
-                        keyNickName);
-
+            // AC: KDF SPEC CHANGE - Log both CUID and KDD.
+            //                       Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
+            //                       Finally, log CUID and KDD in ASCII-HEX format, as long as special-decoded version is available.
+            String[] logParams = {  log_string_from_specialDecoded_byte_array(xCUID),                                   // CUID_decoded
+                                    log_string_from_specialDecoded_byte_array(xKDD),                                    // KDD_decoded
+                                    ILogger.SUCCESS,                                                                    // Outcome
+                                    status,                                                                             // status
+                                    agentId,                                                                            // AgentID
+                                    isCryptoValidate? "true":"false",                                                   // IsCryptoValidate
+                                    serversideKeygen? "true":"false",                                                   // IsServerSideKeygen
+                                    selectedToken,                                                                      // SelectedToken
+                                    keyNickName,                                                                        // KeyNickName
+                                    keySet,                                                                             // TKSKeyset
+                                    log_string_from_keyInfo(xkeyInfo),                                                  // KeyInfo_KeyVersion
+                                    "0x" + Integer.toHexString((int)nistSP800_108KdfOnKeyVersion & 0x000000FF),         // NistSP800_108KdfOnKeyVersion
+                                    Boolean.toString(nistSP800_108KdfUseCuidAsKdd)                                      // NistSP800_108KdfUseCuidAsKdd
+                                 };
+            auditMessage = CMS.getLogMessage(LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS, logParams);
         } else {
+            // AC: KDF SPEC CHANGE - Log both CUID and KDD
+            //                       Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
+            //                       Finally, log CUID and KDD in ASCII-HEX format, as long as special-decoded version is available.
+            String[] logParams = {  log_string_from_specialDecoded_byte_array(xCUID),                                   // CUID_decoded
+                                    log_string_from_specialDecoded_byte_array(xKDD),                                    // KDD_decoded
+                                    ILogger.FAILURE,                                                                    // Outcome
+                                    status,                                                                             // status
+                                    agentId,                                                                            // AgentID
+                                    isCryptoValidate? "true":"false",                                                   // IsCryptoValidate
+                                    serversideKeygen? "true":"false",                                                   // IsServerSideKeygen
+                                    selectedToken,                                                                      // SelectedToken
+                                    keyNickName,                                                                        // KeyNickName
+                                    keySet,                                                                             // TKSKeyset
+                                    log_string_from_keyInfo(xkeyInfo),                                                  // KeyInfo_KeyVersion
+                                    "0x" + Integer.toHexString((int)nistSP800_108KdfOnKeyVersion & 0x000000FF),         // NistSP800_108KdfOnKeyVersion
+                                    Boolean.toString(nistSP800_108KdfUseCuidAsKdd),                                     // NistSP800_108KdfUseCuidAsKdd
+                                    errorMsg                                                                            // Error
+                                 };
+            auditMessage = CMS.getLogMessage(LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE, logParams);
 
-            auditMessage = CMS.getLogMessage(
-                         LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE,
-                        rCUID,
-                        ILogger.FAILURE,
-                        status,
-                        agentId,
-                        isCryptoValidate ? "true" : "false",
-                        serversideKeygen ? "true" : "false",
-                        selectedToken,
-                        keyNickName,
-                        errorMsg);
         }
 
         audit(auditMessage);
@@ -768,8 +1014,22 @@ public class TokenServlet extends CMSServlet {
 
     private void processDiversifyKey(HttpServletRequest req,
             HttpServletResponse resp) throws EBaseException {
-        byte[] KeySetData, CUID, xCUID;
-        byte[] xkeyInfo, xnewkeyInfo;
+        byte[] KeySetData,KeysValues,xCUID,xKDD;  // AC: KDF SPEC CHANGE: removed duplicative 'CUID' variable and added xKDD
+
+        // AC: BUGFIX:  Record the actual parameters to DiversifyKey in the audit log.
+        String oldKeyNickName = null;
+        String newKeyNickName = null;
+
+        // AC: KDF SPEC CHANGE - new config file values (needed for symkey)
+        byte nistSP800_108KdfOnKeyVersion = (byte)0xff;
+        boolean nistSP800_108KdfUseCuidAsKdd = false;
+
+        // AC: BUGFIX for key versions higher than 09:  We need to initialize these variables in order for the compiler not to complain when we pass them to DiversifyKey.
+        byte[] xkeyInfo = null,xnewkeyInfo = null;
+
+        // AC: KDF SPEC CHANGE - flag for if there is an error reading our new setting
+        Exception missingSetting_exception = null;
+
         boolean missingParam = false;
         String errorMsg = "";
         String badParams = "";
@@ -779,6 +1039,15 @@ public class TokenServlet extends CMSServlet {
         String newMasterKeyName = req.getParameter(IRemoteRequest.TOKEN_NEW_KEYINFO);
         String oldMasterKeyName = req.getParameter(IRemoteRequest.TOKEN_KEYINFO);
         String rCUID = req.getParameter(IRemoteRequest.TOKEN_CUID);
+
+        // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
+        String rKDD = req.getParameter("KDD");
+        if ((rKDD == null) || (rKDD.length() == 0)) {
+            // temporarily make it friendly before TPS change
+            CMS.debug("TokenServlet: KDD not supplied, set to CUID before TPS change");
+            rKDD = rCUID;
+        }
+
         String auditMessage = "";
 
         String keySet = req.getParameter(IRemoteRequest.TOKEN_KEYSET);
@@ -795,9 +1064,11 @@ public class TokenServlet extends CMSServlet {
                     (String) sContext.get(SessionContext.USER_ID);
         }
 
+        // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID
         auditMessage = CMS.getLogMessage(
                         LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST,
                         rCUID,
+                        rKDD,            // AC: KDF SPEC CHANGE - Log both CUID and KDD.
                         ILogger.SUCCESS,
                         agentId,
                         oldMasterKeyName,
@@ -810,6 +1081,14 @@ public class TokenServlet extends CMSServlet {
             CMS.debug("TokenServlet: processDiversifyKey(): missing request parameter: CUID");
             missingParam = true;
         }
+        
+        // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
+        if ((rKDD == null) || (rKDD.length() == 0)) {
+            CMS.debug("TokenServlet: processDiversifyKey(): missing request parameter: KDD");
+            badParams += " KDD,";
+            missingParam = true;
+        }
+        
         if ((rnewKeyInfo == null) || (rnewKeyInfo.equals(""))) {
             badParams += " newKeyInfo,";
             CMS.debug("TokenServlet: processDiversifyKey(): missing request parameter: newKeyInfo");
@@ -821,6 +1100,12 @@ public class TokenServlet extends CMSServlet {
             missingParam = true;
         }
 
+        // AC: KDF SPEC CHANGE 
+        xCUID = null;       // avoid errors about non-initialization
+        xKDD = null;        // avoid errors about non-initialization
+        xkeyInfo = null;    // avoid errors about non-initialization
+        xnewkeyInfo = null; // avoid errors about non-initialization
+
         if (!missingParam) {
             xkeyInfo = com.netscape.cmsutil.util.Utils.SpecialDecode(oldMasterKeyName);
             if (xkeyInfo == null || xkeyInfo.length != 2) {
@@ -847,9 +1132,46 @@ public class TokenServlet extends CMSServlet {
                 CMS.debug("TokenServlet: Invalid CUID length");
                 missingParam = true;
             }
+
+            // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
+            xKDD = com.netscape.cmsutil.util.Utils.SpecialDecode(rKDD);
+            if (xKDD == null || xKDD.length != 10) {
+                badParams += " KDD length,";
+                CMS.debug("TokenServlet: Invalid KDD length");
+                missingParam = true;
+            }
         }
         if (!missingParam) {
-            CUID = com.netscape.cmsutil.util.Utils.SpecialDecode(rCUID);
+          // CUID = com.netscape.cmsutil.util.Utils.SpecialDecode(rCUID); // AC: KDF SPEC CHANGE: Removed duplicative variable/processing.
+
+          // AC: KDF SPEC CHANGE - read new config file values (needed for symkey)
+          try{
+              nistSP800_108KdfOnKeyVersion = TokenServlet.read_setting_nistSP800_108KdfOnKeyVersion(keySet);
+              nistSP800_108KdfUseCuidAsKdd = TokenServlet.read_setting_nistSP800_108KdfUseCuidAsKdd(keySet);
+
+              // log settings read in to debug log along with xkeyInfo and xnewkeyInfo
+              CMS.debug("TokenServlet: processDiversifyKey():  xkeyInfo[0] (old) = 0x"
+                        + Integer.toHexString((int)(xkeyInfo[0]) & 0x0000000FF)
+                        + ",  xkeyInfo[1] (old) = 0x"
+                        + Integer.toHexString((int)(xkeyInfo[1]) & 0x0000000FF)
+                        + ",  xnewkeyInfo[0] = 0x"
+                        + Integer.toHexString((int)(xnewkeyInfo[0]) & 0x000000FF)
+                        + ",  xnewkeyInfo[1] = 0x"
+                        + Integer.toHexString((int)(xnewkeyInfo[1]) & 0x000000FF)
+                       );
+              CMS.debug("TokenServlet: processDiversifyKey():  Nist SP800-108 KDF will be used for key versions >= 0x"
+                        + Integer.toHexString((int)(nistSP800_108KdfOnKeyVersion) & 0x0000000FF)
+                       );
+              if (nistSP800_108KdfUseCuidAsKdd == true){
+                  CMS.debug("TokenServlet: processDiversifyKey():  Nist SP800-108 KDF (if used) will use CUID instead of KDD.");
+              }else{
+                  CMS.debug("TokenServlet: processDiversifyKey():  Nist SP800-108 KDF (if used) will use KDD.");
+              }
+          // conform to the set-an-error-flag mentality
+          }catch(Exception e){
+              missingSetting_exception = e;
+              CMS.debug("TokenServlet: processDiversifyKey():  Exception reading Nist SP800-108 KDF config values: " + e.toString());
+          }
 
             if (mKeyNickName != null)
                 oldMasterKeyName = mKeyNickName;
@@ -859,7 +1181,6 @@ public class TokenServlet extends CMSServlet {
             String oldKeyInfoMap = "tks." + keySet + ".mk_mappings." + req.getParameter(IRemoteRequest.TOKEN_KEYINFO); //#xx#xx
             String oldMappingValue = CMS.getConfigStore().getString(oldKeyInfoMap, null);
             String oldSelectedToken = null;
-            String oldKeyNickName = null;
             if (oldMappingValue == null) {
                 oldSelectedToken = CMS.getConfigStore().getString("tks.defaultSlot", "internal");
                 oldKeyNickName = req.getParameter(IRemoteRequest.TOKEN_KEYINFO);
@@ -872,7 +1193,6 @@ public class TokenServlet extends CMSServlet {
             String newKeyInfoMap = "tks.mk_mappings." + rnewKeyInfo; //#xx#xx
             String newMappingValue = CMS.getConfigStore().getString(newKeyInfoMap, null);
             String newSelectedToken = null;
-            String newKeyNickName = null;
             if (newMappingValue == null) {
                 newSelectedToken = CMS.getConfigStore().getString("tks.defaultSlot", "internal");
                 newKeyNickName = rnewKeyInfo;
@@ -888,10 +1208,20 @@ public class TokenServlet extends CMSServlet {
                     newKeyNickName);
 
             byte kekKeyArray[] =
-                    com.netscape.cmsutil.util.Utils.SpecialDecode(sconfig.getString("tks." + keySet + ".kek_key"));
+                com.netscape.cmsutil.util.Utils.SpecialDecode(sconfig.getString("tks." + keySet + ".kek_key"));
+
+          // AC: KDF SPEC CHANGE - check for error reading settings
+          if (missingSetting_exception == null){
             KeySetData = SessionKey.DiversifyKey(oldSelectedToken,
                      newSelectedToken, oldKeyNickName,
-                    newKeyNickName, rnewKeyInfo, CUID, kekKeyArray, useSoftToken_s, keySet);
+                     newKeyNickName,
+                     xkeyInfo,                        // AC: KDF SPEC CHANGE - pass in old key info so symkey can make decision about which KDF version to use
+                     xnewkeyInfo,                     // AC: BUGFIX for key versions higher than 09:  We need to specialDecode keyInfo parameters before sending them into symkey!  This means the parameters must be byte[]
+                     nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                     nistSP800_108KdfUseCuidAsKdd,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                     xCUID,                           // AC: KDF SPEC CHANGE - removed duplicative 'CUID' variable and replaced with 'xCUID'
+                     xKDD,                            // AC: KDF SPEC CHANGE - pass in KDD so symkey can make decision about which value (KDD,CUID) to use
+                     kekKeyArray, useSoftToken_s, keySet);
 
             if (KeySetData == null || KeySetData.length <= 1) {
                 CMS.getLogger().log(ILogger.EV_AUDIT,
@@ -901,11 +1231,18 @@ public class TokenServlet extends CMSServlet {
 
             CMS.getLogger().log(ILogger.EV_AUDIT,
                     ILogger.S_TKS,
-                    ILogger.LL_INFO, "process DiversifyKey for CUID =" + trim(pp.toHexString(CUID))
+                  ILogger.LL_INFO,
+                  "process DiversifyKey for CUID=" +
+                     trim(pp.toHexString(xCUID)) +  // AC: KDF SPEC CHANGE:  Log both CUID and KDD
+                  ", KDD=" +
+                     trim(pp.toHexString(xKDD))
                             + ";from oldMasterKeyName=" + oldSelectedToken + ":" + oldKeyNickName
                             + ";to newMasterKeyName=" + newSelectedToken + ":" + newKeyNickName);
 
             resp.setContentType("text/html");
+
+          } // AC: KDF SPEC CHANGE - endif no error reading settings from settings file
+
         } // ! missingParam
 
         //CMS.debug("TokenServlet:processDiversifyKey " +outputString);
@@ -918,6 +1255,11 @@ public class TokenServlet extends CMSServlet {
             value = IRemoteRequest.RESPONSE_STATUS+"=0&" + IRemoteRequest.TKS_RESPONSE_KeySetData+"=" +
                      com.netscape.cmsutil.util.Utils.SpecialEncode(KeySetData);
             CMS.debug("TokenServlet:process DiversifyKey.encode " + value);
+        // AC: KDF SPEC CHANGE - check for settings file issue (flag)
+        } else if (missingSetting_exception != null){
+            status = "6";
+            errorMsg = "Problem reading required configuration value.";
+            value = "status=" + status;
         } else if (missingParam) {
             status = "3";
             if (badParams.endsWith(",")) {
@@ -945,26 +1287,48 @@ public class TokenServlet extends CMSServlet {
 
         if (status.equals("0")) {
 
-            auditMessage = CMS.getLogMessage(
-                         LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS,
-                        rCUID,
-                        ILogger.SUCCESS,
-                        status,
-                        agentId,
-                        oldMasterKeyName,
-                        newMasterKeyName);
-
+                // AC: KDF SPEC CHANGE - Log both CUID and KDD
+                //                       Also added TKSKeyset, OldKeyInfo_KeyVersion, NewKeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
+                //                       Finally, log CUID and KDD in ASCII-HEX format, as long as special-decoded version is available.
+                String[] logParams = {  log_string_from_specialDecoded_byte_array(xCUID),                               // CUID_decoded
+                                        log_string_from_specialDecoded_byte_array(xKDD),                                // KDD_decoded
+                                        ILogger.SUCCESS,                                                                // Outcome
+                                        status,                                                                         // status
+                                        agentId,                                                                        // AgentID
+
+                                        // AC: BUGFIX:  Record the actual parameters to DiversifyKey in the audit log.
+                                        oldKeyNickName,                                                                 // oldMasterKeyName
+                                        newKeyNickName,                                                                 // newMasterKeyName
+
+                                        keySet,                                                                         // TKSKeyset
+                                        log_string_from_keyInfo(xkeyInfo),                                              // OldKeyInfo_KeyVersion
+                                        log_string_from_keyInfo(xnewkeyInfo),                                           // NewKeyInfo_KeyVersion
+                                        "0x" + Integer.toHexString((int)nistSP800_108KdfOnKeyVersion & 0x000000FF),     // NistSP800_108KdfOnKeyVersion
+                                        Boolean.toString(nistSP800_108KdfUseCuidAsKdd)                                  // NistSP800_108KdfUseCuidAsKdd
+                                     };
+                auditMessage = CMS.getLogMessage(LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS, logParams);
         } else {
-
-            auditMessage = CMS.getLogMessage(
-                         LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE,
-                        rCUID,
-                        ILogger.FAILURE,
-                        status,
-                        agentId,
-                        oldMasterKeyName,
-                        newMasterKeyName,
-                        errorMsg);
+                // AC: KDF SPEC CHANGE - Log both CUID and KDD
+                //                       Also added TKSKeyset, OldKeyInfo_KeyVersion, NewKeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
+                //                       Finally, log CUID and KDD in ASCII-HEX format, as long as special-decoded version is available.
+                String[] logParams = {  log_string_from_specialDecoded_byte_array(xCUID),                               // CUID_decoded
+                                        log_string_from_specialDecoded_byte_array(xKDD),                                // KDD_decoded
+                                        ILogger.FAILURE,                                                                // Outcome
+                                        status,                                                                         // status
+                                        agentId,                                                                        // AgentID
+
+                                        // AC: BUGFIX:  Record the actual parameters to DiversifyKey in the audit log.
+                                        oldKeyNickName,                                                                 // oldMasterKeyName
+                                        newKeyNickName,                                                                 // newMasterKeyName
+
+                                        keySet,                                                                         // TKSKeyset
+                                        log_string_from_keyInfo(xkeyInfo),                                              // OldKeyInfo_KeyVersion
+                                        log_string_from_keyInfo(xnewkeyInfo),                                           // NewKeyInfo_KeyVersion
+                                        "0x" + Integer.toHexString((int)nistSP800_108KdfOnKeyVersion & 0x000000FF),     // NistSP800_108KdfOnKeyVersion
+                                        Boolean.toString(nistSP800_108KdfUseCuidAsKdd),                                 // NistSP800_108KdfUseCuidAsKdd
+                                        errorMsg                                                                        // Error
+                                     };
+                auditMessage = CMS.getLogMessage(LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE, logParams);
         }
 
         audit(auditMessage);
@@ -972,7 +1336,15 @@ public class TokenServlet extends CMSServlet {
 
     private void processEncryptData(HttpServletRequest req,
             HttpServletResponse resp) throws EBaseException {
-        byte[] keyInfo, CUID, xCUID, encryptedData, xkeyInfo;
+        byte[] keyInfo, xCUID, encryptedData, xkeyInfo, xKDD;  // AC: KDF SPEC CHANGE: removed duplicative 'CUID' variable and added xKDD
+
+        // AC: KDF SPEC CHANGE - new config file values (needed for symkey)
+        byte nistSP800_108KdfOnKeyVersion = (byte)0xff;
+        boolean nistSP800_108KdfUseCuidAsKdd = false;
+
+        // AC: KDF SPEC CHANGE - flag for if there is an error reading our new setting
+        Exception missingSetting_exception = null;
+
         boolean missingParam = false;
         byte[] data = null;
         boolean isRandom = true; // randomly generate the data to be encrypted
@@ -984,6 +1356,15 @@ public class TokenServlet extends CMSServlet {
         String rdata = req.getParameter(IRemoteRequest.TOKEN_DATA);
         String rKeyInfo = req.getParameter(IRemoteRequest.TOKEN_KEYINFO);
         String rCUID = req.getParameter(IRemoteRequest.TOKEN_CUID);
+
+        // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
+        String rKDD = req.getParameter("KDD");
+        if ((rKDD == null) || (rKDD.length() == 0)) {
+            // temporarily make it friendly before TPS change
+            CMS.debug("TokenServlet: KDD not supplied, set to CUID before TPS change");
+            rKDD = rCUID;
+        }
+
         String keySet = req.getParameter(IRemoteRequest.TOKEN_KEYSET);
         if (keySet == null || keySet.equals("")) {
             keySet = "defKeySet";
@@ -1008,9 +1389,11 @@ public class TokenServlet extends CMSServlet {
             isRandom = true;
         }
 
+        // AC: KDF SPEC CHANGE:  Need to log both KDD and CUID
         String auditMessage = CMS.getLogMessage(
                        LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST,
                        rCUID,
+                       rKDD,            // AC: KDF SPEC CHANGE - Log both CUID and KDD.
                        ILogger.SUCCESS,
                        agentId,
                        s_isRandom);
@@ -1044,12 +1427,24 @@ public class TokenServlet extends CMSServlet {
             missingParam = true;
         }
 
+        // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
+        if ((rKDD == null) || (rKDD.length() == 0)) {
+            CMS.debug("TokenServlet: processDiversifyKey(): missing request parameter: KDD");
+            badParams += " KDD,";
+            missingParam = true;
+        }
+
         if ((rKeyInfo == null) || (rKeyInfo.equals(""))) {
             badParams += " KeyInfo,";
             CMS.debug("TokenServlet: processEncryptData(): missing request parameter: key info");
             missingParam = true;
         }
 
+        // AC: KDF SPEC CHANGE
+        xCUID = null;       // avoid errors about non-initialization
+        xKDD = null;        // avoid errors about non-initialization
+        xkeyInfo = null;    // avoid errors about non-initialization
+
         if (!missingParam) {
             xCUID = com.netscape.cmsutil.util.Utils.SpecialDecode(rCUID);
             if (xCUID == null || xCUID.length != 10) {
@@ -1057,6 +1452,15 @@ public class TokenServlet extends CMSServlet {
                 CMS.debug("TokenServlet: Invalid CUID length");
                 missingParam = true;
             }
+
+            // AC: KDF SPEC CHANGE - read new KDD parameter from TPS
+            xKDD = com.netscape.cmsutil.util.Utils.SpecialDecode(rKDD);
+            if (xKDD == null || xKDD.length != 10) {
+                badParams += " KDD length,";
+                CMS.debug("TokenServlet: Invalid KDD length");
+                missingParam = true;
+            }
+
             xkeyInfo = com.netscape.cmsutil.util.Utils.SpecialDecode(rKeyInfo);
             if (xkeyInfo == null || xkeyInfo.length != 2) {
                 badParams += " KeyInfo length,";
@@ -1072,10 +1476,35 @@ public class TokenServlet extends CMSServlet {
         String selectedToken = null;
         String keyNickName = null;
         if (!missingParam) {
+
+            // AC: KDF SPEC CHANGE - read new config file values (needed for symkey
+            try{
+                nistSP800_108KdfOnKeyVersion = TokenServlet.read_setting_nistSP800_108KdfOnKeyVersion(keySet);
+                nistSP800_108KdfUseCuidAsKdd = TokenServlet.read_setting_nistSP800_108KdfUseCuidAsKdd(keySet);
+
+                // log settings read in to debug log along with xkeyInfo
+                CMS.debug("TokenServlet: processEncryptData():  xkeyInfo[0] = 0x"
+                          + Integer.toHexString((int)(xkeyInfo[0]) & 0x0000000FF)
+                          + ",  xkeyInfo[1] = 0x"
+                          + Integer.toHexString((int)(xkeyInfo[1]) & 0x0000000FF)
+                         );
+                CMS.debug("TokenServlet: processEncryptData():  Nist SP800-108 KDF will be used for key versions >= 0x"
+                          + Integer.toHexString((int)(nistSP800_108KdfOnKeyVersion) & 0x0000000FF)
+                         );
+                if (nistSP800_108KdfUseCuidAsKdd == true){
+                    CMS.debug("TokenServlet: processEncryptData():  Nist SP800-108 KDF (if used) will use CUID instead of KDD.");
+                }else{
+                    CMS.debug("TokenServlet: processEncryptData():  Nist SP800-108 KDF (if used) will use KDD.");
+                }
+            // conform to the set-an-error-flag mentality
+            }catch(Exception e){
+                missingSetting_exception = e;
+                CMS.debug("TokenServlet: processEncryptData():  Exception reading Nist SP800-108 KDF config values: " + e.toString());
+            }
+
             if (!isRandom)
                 data = com.netscape.cmsutil.util.Utils.SpecialDecode(rdata);
             keyInfo = com.netscape.cmsutil.util.Utils.SpecialDecode(rKeyInfo);
-            CUID = com.netscape.cmsutil.util.Utils.SpecialDecode(rCUID);
 
             String keyInfoMap = "tks." + keySet + ".mk_mappings." + rKeyInfo;
             String mappingValue = CMS.getConfigStore().getString(keyInfoMap, null);
@@ -1090,12 +1519,30 @@ public class TokenServlet extends CMSServlet {
 
             byte kekKeyArray[] =
                     com.netscape.cmsutil.util.Utils.SpecialDecode(sconfig.getString("tks." + keySet + ".kek_key"));
+          // AC: KDF SPEC CHANGE - check for error reading settings
+          if (missingSetting_exception == null){
+
             encryptedData = SessionKey.EncryptData(
-                       selectedToken, keyNickName, data, keyInfo, CUID, kekKeyArray, useSoftToken_s, keySet);
+                       selectedToken,keyNickName,data,keyInfo,
+                       nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                       nistSP800_108KdfUseCuidAsKdd,    // AC: KDF SPEC CHANGE - pass in configuration file value
+                       xCUID,                           // AC: KDF SPEC CHANGE - removed duplicative 'CUID' variable and replaced with 'xCUID'
+                       xKDD,                            // AC: KDF SPEC CHANGE - pass in KDD so symkey can make decision about which value (KDD,CUID) to use
+                       kekKeyArray, useSoftToken_s, keySet);
+
+          // AC: KDF SPEC CHANGE - Log both CUID and KDD
+
 
             CMS.getLogger().log(ILogger.EV_AUDIT,
                      ILogger.S_TKS,
-                     ILogger.LL_INFO, "process EncryptData for CUID =" + trim(pp.toHexString(CUID)));
+                     ILogger.LL_INFO,"process EncryptData for CUID="+
+                       trim(pp.toHexString(xCUID)) +
+                       ", KDD=" +
+                       trim(pp.toHexString(xKDD)));
+
+          } // AC: KDF SPEC CHANGE - endif no error reading settings from settings file
+
+
         } // !missingParam
 
         resp.setContentType("text/html");
@@ -1109,6 +1556,11 @@ public class TokenServlet extends CMSServlet {
                          com.netscape.cmsutil.util.Utils.SpecialEncode(data) +
                     "&"+IRemoteRequest.TKS_RESPONSE_EncryptedData+"=" +
                          com.netscape.cmsutil.util.Utils.SpecialEncode(encryptedData);
+        // AC: KDF SPEC CHANGE - check for settings file issue (flag)
+        } else if (missingSetting_exception != null){
+            status = "6";
+            errorMsg = "Problem reading required configuration value.";
+            value = "status=" + status;
         } else if (missingParam) {
             if (badParams.endsWith(",")) {
                 badParams = badParams.substring(0, badParams.length() - 1);
@@ -1137,29 +1589,42 @@ public class TokenServlet extends CMSServlet {
         }
 
         if (status.equals("0")) {
-
-            auditMessage = CMS.getLogMessage(
-                         LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_SUCCESS,
-                        rCUID,
-                        ILogger.SUCCESS,
-                        status,
-                        agentId,
-                        s_isRandom,
-                        selectedToken,
-                        keyNickName);
-
+                // AC: KDF SPEC CHANGE - Log both CUID and KDD
+                //                       Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
+                //                       Finally, log CUID and KDD in ASCII-HEX format, as long as special-decoded version is available.
+                String[] logParams = {  log_string_from_specialDecoded_byte_array(xCUID),                               // CUID_decoded
+                                        log_string_from_specialDecoded_byte_array(xKDD),                                // KDD_decoded
+                                        ILogger.SUCCESS,                                                                // Outcome
+                                        status,                                                                         // status
+                                        agentId,                                                                        // AgentID
+                                        s_isRandom,                                                                     // isRandom
+                                        selectedToken,                                                                  // SelectedToken
+                                        keyNickName,                                                                    // KeyNickName
+                                        keySet,                                                                         // TKSKeyset
+                                        log_string_from_keyInfo(xkeyInfo),                                              // KeyInfo_KeyVersion
+                                        "0x" + Integer.toHexString((int)nistSP800_108KdfOnKeyVersion & 0x000000FF),     // NistSP800_108KdfOnKeyVersion
+                                        Boolean.toString(nistSP800_108KdfUseCuidAsKdd)                                  // NistSP800_108KdfUseCuidAsKdd
+                                     };
+                auditMessage = CMS.getLogMessage(LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_SUCCESS, logParams);
         } else {
-
-            auditMessage = CMS.getLogMessage(
-                         LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_FAILURE,
-                        rCUID,
-                        ILogger.FAILURE,
-                        status,
-                        agentId,
-                        s_isRandom,
-                        selectedToken,
-                        keyNickName,
-                        errorMsg);
+                // AC: KDF SPEC CHANGE - Log both CUID and KDD
+                //                       Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
+                //                       Finally, log CUID and KDD in ASCII-HEX format, as long as special-decoded version is available.
+                String[] logParams = {  log_string_from_specialDecoded_byte_array(xCUID),                               // CUID_decoded
+                                        log_string_from_specialDecoded_byte_array(xKDD),                                // KDD_decoded
+                                        ILogger.FAILURE,                                                                // Outcome
+                                        status,                                                                         // status
+                                        agentId,                                                                        // AgentID
+                                        s_isRandom,                                                                     // isRandom
+                                        selectedToken,                                                                  // SelectedToken
+                                        keyNickName,                                                                    // KeyNickName
+                                        keySet,                                                                         // TKSKeyset
+                                        log_string_from_keyInfo(xkeyInfo),                                              // KeyInfo_KeyVersion
+                                        "0x" + Integer.toHexString((int)nistSP800_108KdfOnKeyVersion & 0x000000FF),     // NistSP800_108KdfOnKeyVersion
+                                        Boolean.toString(nistSP800_108KdfUseCuidAsKdd),                                 // NistSP800_108KdfUseCuidAsKdd
+                                        errorMsg                                                                        // Error
+                                     };
+                auditMessage = CMS.getLogMessage(LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_FAILURE, logParams);
         }
 
         audit(auditMessage);
diff --git a/base/server/cmsbundle/src/LogMessages.properties b/base/server/cmsbundle/src/LogMessages.properties
index dfa23c1..ef3872c 100644
--- a/base/server/cmsbundle/src/LogMessages.properties
+++ b/base/server/cmsbundle/src/LogMessages.properties
@@ -2263,6 +2263,13 @@ LOGGING_SIGNED_AUDIT_COMPUTE_RANDOM_DATA_REQUEST_PROCESSED_FAILURE_4=<type=COMPU
 # SubjectID must be the CUID of the token establishing the secure channel
 # AgentID must be the trusted agent id used to make the request
 LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_3=<type=COMPUTE_SESSION_KEY_REQUEST>:[AuditEvent=COMPUTE_SESSION_KEY_REQUEST][SubjectID={0}][Outcome={1}][AgentID={2}] TKS Compute session key request
+## AC: KDF SPEC CHANGE - Need to log both the KDD and CUID, not just the 
+##   CUID.  Renamed to "CUID_encoded" and "KDD_encoded" to reflect fact that
+##   encoded parameters are being logged.
+# CUID_encoded must be the special-encoded CUID of the token establishing the secure channel
+# KDD_encoded must be the special-encoded KDD of the token establishing the secure channel
+LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_4=<type=COMPUTE_SESSION_KEY_REQUEST>:[AuditEvent=COMPUTE_SESSION_KEY_REQUEST][CUID_encoded={0}][KDD_encoded={1}][Outcome={2}][AgentID={3}] TKS Compute session key request
+
 #
 #
 # LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS
@@ -2277,6 +2284,19 @@ LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_3=<type=COMPUTE_SESSION_KEY_REQ
 # KeyNickName is the number keyset ex: #01#01
 #
 LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS_8=<type=COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS>:[AuditEvent=COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS][SubjectID={0}][Outcome={1}][status={2}][AgentID={3}][IsCryptoValidate={4}][IsServerSideKeygen={5}][SelectedToken={6}][KeyNickName={7}] TKS Compute session key request processed successfully
+## AC: KDF SPEC CHANGE - Need to log both the KDD and CUID, not just the
+##   CUID.  Renamed to "CUID_decoded" and "KDD_decoded" to reflect fact
+##   that decoded parameters are now logged.
+##       Also added TKSKeyset, KeyInfo_KeyVersion,
+##            NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
+# CUID_decoded must be the ASCII-HEX representation of the CUID of the token establishing the secure channel
+# KDD_decoded must be the ASCII-HEX representation of the KDD of the token establishing the secure channel
+# TKSKeyset is the name of the TKS keyset being used for this request.
+# KeyInfo_KeyVersion is the key version number requested in hex.
+# NistSP800_108KdfOnKeyVersion lists the value of the corresponding setting in hex.
+# NistSP800_108KdfUseCuidAsKdd lists the value of the corresponding setting in hex.
+LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS_13=<type=COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS>:[AuditEvent=COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS][CUID_decoded={0}][KDD_decoded={1}][Outcome={2}][status={3}][AgentID={4}][IsCryptoValidate={5}][IsServerSideKeygen={6}][SelectedToken={7}][KeyNickName={8}][TKSKeyset={9}][KeyInfo_KeyVersion={10}][NistSP800_108KdfOnKeyVersion={11}][NistSP800_108KdfUseCuidAsKdd={12}] TKS Compute session key request processed successfully
+
 #
 #
 # LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE
@@ -2293,6 +2313,16 @@ LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_SUCCESS_8=<type=COMPU
 # Error gives the error message
 LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE_9=<type=COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE>:[AuditEvent=COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE][SubjectID={0}][Outcome={1}][status={2}][AgentID={3}][IsCryptoValidate={4}][IsServerSideKeygen={5}][SelectedToken={7}][KeyNickName={7}][Error={8}] TKS Compute session key request failed 
 #
+## AC: KDF SPEC CHANGE - Need to log both the KDD and CUID, not just the CUID.  Renamed to "CUID_decoded" and "KDD_decoded" to reflect fact that decoded parameters are now logged.
+##                       Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
+# CUID_decoded must be the ASCII-HEX representation of the CUID of the token establishing the secure channel
+# KDD_decoded must be the ASCII-HEX representation of the KDD of the token establishing the secure channel
+# TKSKeyset is the name of the TKS keyset being used for this request.
+# KeyInfo_KeyVersion is the key version number requested in hex.
+# NistSP800_108KdfOnKeyVersion lists the value of the corresponding setting in hex.
+# NistSP800_108KdfUseCuidAsKdd lists the value of the corresponding setting in hex
+LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE_14=<type=COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE>:[AuditEvent=COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE][CUID_decoded={0}][KDD_decoded={1}][Outcome={2}][status={3}][AgentID={4}][IsCryptoValidate={5}][IsServerSideKeygen={6}][SelectedToken={7}][KeyNickName={8}][TKSKeyset={9}][KeyInfo_KeyVersion={10}][NistSP800_108KdfOnKeyVersion={11}][NistSP800_108KdfUseCuidAsKdd={12}][Error={13}] TKS Compute session key request failed
+
 
 # LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST
 # - request for TPS to TKS to do key change over
@@ -2303,6 +2333,11 @@ LOGGING_SIGNED_AUDIT_COMPUTE_SESSION_KEY_REQUEST_PROCESSED_FAILURE_9=<type=COMPU
 # newMasterKeyName is the new master key name
 LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_5=<type=DIVERSIFY_KEY_REQUEST>:[AuditEvent=DIVERSIFY_KEY_REQUEST][SubjectID={0}][Outcome={1}][AgentID={2}][oldMasterKeyName={3}][newMasterKeyName={4}] TKS Key Change Over request
 #
+## AC: KDF SPEC CHANGE - Need to log both the KDD and CUID, not just the CUID.  Renamed to "CUID_encoded" and "KDD_encoded" to reflect fact that encoded parameters are being logged.
+# CUID_encoded must be the special-encoded CUID of the token establishing the secure channel
+# KDD_encoded must be the special-encoded KDD of the token establishing the secure channel
+LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_6=<type=DIVERSIFY_KEY_REQUEST>:[AuditEvent=DIVERSIFY_KEY_REQUEST][CUID_encoded={0}][KDD_encoded={1}][Outcome={2}][AgentID={3}][oldMasterKeyName={4}][newMasterKeyName={5}] TKS Key Change Over request
+
 ###########################
 # LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS
 # - request for TPS to TKS to do key change over request processed
@@ -2314,6 +2349,17 @@ LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_5=<type=DIVERSIFY_KEY_REQUEST>:[Audit
 # newMasterKeyName is the new master key name
 LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS_6=<type=DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS>:[AuditEvent=DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS][SubjectID={0}][Outcome={1}][status={2}][AgentID={3}][oldMasterKeyName={4}][newMasterKeyName={5}] TKS Key Change Over request processed successfully
 #
+## AC: KDF SPEC CHANGE - Need to log both the KDD and CUID, not just the CUID.  Renamed to "CUID_decoded" and "KDD_decoded" to reflect fact that decoded parameters are now logged.
+##                       Also added TKSKeyset, OldKeyInfo_KeyVersion, NewKeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
+# CUID_decoded must be the ASCII-HEX representation of the CUID of the token establishing the secure channel
+# KDD_decoded must be the ASCII-HEX representation of the KDD of the token establishing the secure channel
+# TKSKeyset is the name of the TKS keyset being used for this request.
+# OldKeyInfo_KeyVersion is the old key version number in hex.
+# NewKeyInfo_KeyVersion is the new key version number in hex.
+# NistSP800_108KdfOnKeyVersion lists the value of the corresponding setting in hex.
+# NistSP800_108KdfUseCuidAsKdd lists the value of the corresponding setting in hex.
+LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS_12=<type=DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS>:[AuditEvent=DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS][CUID_decoded={0}][KDD_decoded={1}][Outcome={2}][status={3}][AgentID={4}][oldMasterKeyName={5}][newMasterKeyName={6}][TKSKeyset={7}][OldKeyInfo_KeyVersion={8}][NewKeyInfo_KeyVersion={9}][NistSP800_108KdfOnKeyVersion={10}][NistSP800_108KdfUseCuidAsKdd={11}] TKS Key Change Over request processed successfully
+
 #
 ###########################
 # LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE
@@ -2327,6 +2373,16 @@ LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_SUCCESS_6=<type=DIVERSIFY_K
 # Error gives the error message
 LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE_7=<type=DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE>:[AuditEvent=DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE][SubjectID={0}][Outcome={1}][status={2}][AgentID={3}][oldMasterKeyName={4}][newMasterKeyName={5}][Error={6}] TKS Key Change Over request failed 
 #
+## AC: KDF SPEC CHANGE - Need to log both the KDD and CUID, not just the CUID.  Renamed to "CUID_decoded" and "KDD_decoded" to reflect fact that decoded parameters are now logged.
+##                       Also added TKSKeyset, OldKeyInfo_KeyVersion, NewKeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
+# CUID_decoded must be the ASCII-HEX representation of the CUID of the token establishing the secure channel
+# KDD_decoded must be the ASCII-HEX representation of the KDD of the token establishing the secure channel
+# TKSKeyset is the name of the TKS keyset being used for this request.
+# OldKeyInfo_KeyVersion is the old key version number in hex.
+# NewKeyInfo_KeyVersion is the new key version number in hex.
+# NistSP800_108KdfOnKeyVersion lists the value of the corresponding setting in hex.
+# NistSP800_108KdfUseCuidAsKdd lists the value of the corresponding setting in hex
+LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE_13=<type=DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE>:[AuditEvent=DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE][CUID_decoded={0}][KDD_decoded={1}][Outcome={2}][status={3}][AgentID={4}][oldMasterKeyName={5}][newMasterKeyName={6}][TKSKeyset={7}][OldKeyInfo_KeyVersion={8}][NewKeyInfo_KeyVersion={9}][NistSP800_108KdfOnKeyVersion={10}][NistSP800_108KdfUseCuidAsKdd={11}][Error={12}] TKS Key Change Over request failed
 
 # LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST
 # - request from TPS to TKS to encrypt data 
@@ -2337,6 +2393,11 @@ LOGGING_SIGNED_AUDIT_DIVERSIFY_KEY_REQUEST_PROCESSED_FAILURE_7=<type=DIVERSIFY_K
 # isRandom tells if the data is randomly generated on TKS
 LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_4=<type=ENCRYPT_DATA_REQUEST>:[AuditEvent=ENCRYPT_DATA_REQUEST][SubjectID={0}][status={1}][AgentID={2}][isRandom={3}] TKS encrypt data request
 #
+## AC: KDF SPEC CHANGE - Need to log both the KDD and CUID, not just the CUID.  Renamed to "CUID_encoded" and "KDD_encoded" to reflect fact that encoded parameters are being logged.
+# CUID_encoded must be the special-encoded CUID of the token establishing the secure channel
+# KDD_encoded must be the special-encoded KDD of the token establishing the secure channel
+LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_5=<type=ENCRYPT_DATA_REQUEST>:[AuditEvent=ENCRYPT_DATA_REQUEST][CUID_encoded={0}][KDD_encoded={1}][status={2}][AgentID={3}][isRandom={4}] TKS encrypt data request
+
 #
 # LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_SUCCESS
 # - request from TPS to TKS to encrypt data 
@@ -2350,6 +2411,16 @@ LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_4=<type=ENCRYPT_DATA_REQUEST>:[AuditEv
 # KeyNickName is the numeric keyset ex: #01#01
 LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_SUCCESS_7=<type=ENCRYPT_DATA_REQUEST_PROCESSED_SUCCESS>:[AuditEvent=ENCRYPT_DATA_REQUEST_PROCESSED_SUCCESS][SubjectID={0}][Outcome={1}][status={2}][AgentID={3}][isRandom={4}][SelectedToken={5}][KeyNickName={6}] TKS encrypt data request processed successfully
 #
+## AC: KDF SPEC CHANGE - Need to log both the KDD and CUID, not just the CUID.  Renamed to "CUID_decoded" and "KDD_decoded" to reflect fact that decoded parameters are now logged.
+##                       Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
+# CUID_decoded must be the ASCII-HEX representation of the CUID of the token establishing the secure channel
+# KDD_decoded must be the ASCII-HEX representation of the KDD of the token establishing the secure channel
+# TKSKeyset is the name of the TKS keyset being used for this request.
+# KeyInfo_KeyVersion is the key version number requested in hex.
+# NistSP800_108KdfOnKeyVersion lists the value of the corresponding setting in hex.
+# NistSP800_108KdfUseCuidAsKdd lists the value of the corresponding setting in hex.
+LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_SUCCESS_12=<type=ENCRYPT_DATA_REQUEST_PROCESSED_SUCCESS>:[AuditEvent=ENCRYPT_DATA_REQUEST_PROCESSED_SUCCESS][CUID_decoded={0}][KDD_decoded={1}][Outcome={2}][status={3}][AgentID={4}][isRandom={5}][SelectedToken={6}][KeyNickName={7}][TKSKeyset={8}][KeyInfo_KeyVersion={9}][NistSP800_108KdfOnKeyVersion={10}][NistSP800_108KdfUseCuidAsKdd={11}] TKS encrypt data request processed successfully
+
 #
 # LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_FAILURE
 # - request from TPS to TKS to encrypt data 
@@ -2364,6 +2435,15 @@ LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_SUCCESS_7=<type=ENCRYPT_DATA
 # Error gives the error message
 LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_FAILURE_8=<type=ENCRYPT_DATA_REQUEST_PROCESSED_FAILURE>:[AuditEvent=ENCRYPT_DATA_REQUEST_PROCESSED_FAILURE][SubjectID={0}][Outcome={1}][status={2}][AgentID={3}][isRandom={4}][SelectedToken={5}][KeyNickName={6}][Error={7}] TKS encrypt data request failed 
 #
+## AC: KDF SPEC CHANGE - Need to log both the KDD and CUID, not just the CUID.  Renamed to "CUID_decoded" and "KDD_decoded" to reflect fact that decoded parameters are now logged.
+##                       Also added TKSKeyset, KeyInfo_KeyVersion, NistSP800_108KdfOnKeyVersion, NistSP800_108KdfUseCuidAsKdd
+# CUID_decoded must be the ASCII-HEX representation of the CUID of the token establishing the secure channel
+# KDD_decoded must be the ASCII-HEX representation of the KDD of the token establishing the secure channel
+# TKSKeyset is the name of the TKS keyset being used for this request.
+# KeyInfo_KeyVersion is the key version number requested in hex.
+# NistSP800_108KdfOnKeyVersion lists the value of the corresponding setting in hex.
+# NistSP800_108KdfUseCuidAsKdd lists the value of the corresponding setting in hex.
+LOGGING_SIGNED_AUDIT_ENCRYPT_DATA_REQUEST_PROCESSED_FAILURE_13=<type=ENCRYPT_DATA_REQUEST_PROCESSED_FAILURE>:[AuditEvent=ENCRYPT_DATA_REQUEST_PROCESSED_FAILURE][CUID_decoded={0}][KDD_decoded={1}][Outcome={2}][status={3}][AgentID={4}][isRandom={5}][SelectedToken={6}][KeyNickName={7}][TKSKeyset={8}][KeyInfo_KeyVersion={9}][NistSP800_108KdfOnKeyVersion={10}][NistSP800_108KdfUseCuidAsKdd={11}][Error={12}] TKS encrypt data request failed
 #
 #
 # LOGGING_SIGNED_AUDIT_SECURITY_DOMAIN_UPDATE
diff --git a/base/symkey/src/com/netscape/symkey/CMakeLists.txt b/base/symkey/src/com/netscape/symkey/CMakeLists.txt
index 84c8e08..63c3190 100644
--- a/base/symkey/src/com/netscape/symkey/CMakeLists.txt
+++ b/base/symkey/src/com/netscape/symkey/CMakeLists.txt
@@ -25,6 +25,7 @@ set(SYMKEY_LINK_LIBRARIES
 
 set(symkey_library_HDRS
     SessionKey.h
+    NistSP800_108KDF.h
 )
 
 set(symkey_library_SRCS
@@ -32,6 +33,7 @@ set(symkey_library_SRCS
     EncryptData.cpp
     SessionKey.cpp
     SymKey.cpp
+    NistSP800_108KDF.cpp
 )
 
 include_directories(${SYMKEY_PRIVATE_INCLUDE_DIRS})
diff --git a/base/symkey/src/com/netscape/symkey/EncryptData.cpp b/base/symkey/src/com/netscape/symkey/EncryptData.cpp
index ccb817f..3963b50 100644
--- a/base/symkey/src/com/netscape/symkey/EncryptData.cpp
+++ b/base/symkey/src/com/netscape/symkey/EncryptData.cpp
@@ -37,6 +37,10 @@ extern "C"
 #include <stdlib.h>
 #include "Buffer.h"
 #include "SymKey.h"
+
+// AC: KDF SPEC CHANGE: Include headers for NIST SP800-108 KDF functions.
+#include "NistSP800_108KDF.h"
+
 #define DES2_WORKAROUND
 
 PRFileDesc *d = NULL;
@@ -66,17 +70,24 @@ void GetKeyName(jbyte *keyVersion, char *keyname)
     sprintf(keyname+index+4,"%.2d", keyVersion[1]);
 }
 
-
+// AC: KDF SPEC CHANGE: function signature change - added jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, and jbyteArray KDD
 extern "C"  JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_EncryptData
-(JNIEnv *, jclass, jstring, jstring, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jstring, jstring);
+(JNIEnv *, jclass, jstring, jstring, jbyteArray, jbyteArray, jbyte, jboolean, jbyteArray, jbyteArray, jbyteArray, jstring, jstring);
 
+// AC: KDF SPEC CHANGE: function signature change - added jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, and jbyteArray KDD
 extern "C" JNIEXPORT jbyteArray JNICALL
-Java_com_netscape_symkey_SessionKey_EncryptData(JNIEnv * env, jclass this2, jstring j_tokenName, jstring j_keyName, jbyteArray  j_in, jbyteArray keyInfo, jbyteArray CUID, jbyteArray kekKeyArray, jstring useSoftToken_s,jstring keySet)
+Java_com_netscape_symkey_SessionKey_EncryptData(JNIEnv * env, jclass this2, jstring j_tokenName, jstring j_keyName, jbyteArray  j_in, jbyteArray keyInfo, jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, jbyteArray CUID, jbyteArray KDD, jbyteArray kekKeyArray, jstring useSoftToken_s,jstring keySet)
 {
     jbyte * kek_key =  NULL;
 
     PK11SymKey *masterKey = NULL;
-    PK11SymKey *kekKey =  NULL;
+
+    // AC: KDF SPEC CHANGE:  For the NIST SP800-108 KDF, we build all 3 keys despite only using one of them (Kek) in this function.
+    //                       We do this because our NIST SP800-108 KDF outputs the data for all three keys simultaneously.
+    // KDF output keys
+    PK11SymKey* macKey = NULL;
+    PK11SymKey* encKey = NULL;
+    PK11SymKey* kekKey = NULL;
 
     Buffer out = Buffer(KEYLENGTH, (BYTE)0);
     BYTE kekData[KEYLENGTH];
@@ -86,7 +97,13 @@ Java_com_netscape_symkey_SessionKey_EncryptData(JNIEnv * env, jclass this2, jstr
 
     jbyte *cc = NULL;
     int cc_len = 0;
-    jbyte * cuidValue = NULL;
+
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    //                       Also added "len" variable for CUID (for sanity check).
+    jbyte* cuidValue = NULL;
+    jsize cuidValue_len = -1;
+    jbyte* kddValue = NULL;
+    jsize kddValue_len = -1;
 
     if( kekKeyArray != NULL) {
         kek_key = (jbyte*)(env)->GetByteArrayElements(kekKeyArray, NULL);
@@ -122,13 +139,30 @@ Java_com_netscape_symkey_SessionKey_EncryptData(JNIEnv * env, jclass this2, jstr
         goto done;
     }
 
-    if( CUID != NULL) {
-        cuidValue = (jbyte*)(env)->GetByteArrayElements( CUID, NULL);
-    }
 
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    //                       Also added "len" variable for CUID (for sanity check).
+    if ( CUID != NULL ) {
+        cuidValue =  (jbyte*)(env)->GetByteArrayElements( CUID, NULL);
+        cuidValue_len = env->GetArrayLength(CUID);
+    }
     if( cuidValue == NULL) {
         goto done;
     }
+    if ( cuidValue_len <= 0){  // check that CUID is at least 1 byte in length
+        goto done;
+    }
+    if ( KDD != NULL ){
+        kddValue = env->GetByteArrayElements(KDD, NULL);
+        kddValue_len = env->GetArrayLength(KDD);
+    }
+    if ( kddValue == NULL ){
+        goto done;
+    }
+    if ( kddValue_len != static_cast<jsize>(NistSP800_108KDF::KDD_SIZE_BYTES) ){   // check that KDD is expected size
+        goto done;
+    }
+
 
     if( j_in != NULL) {
         cc = (jbyte*)(env)->GetByteArrayElements( j_in, NULL);
@@ -139,7 +173,8 @@ Java_com_netscape_symkey_SessionKey_EncryptData(JNIEnv * env, jclass this2, jstr
         goto done;
     }
 
-    GetDiversificationData(cuidValue,kekData,kek);
+    // AC: KDF SPEC CHANGE: Moved this call down. (We don't necessarily need it anymore depending on the KDF we're going to use.)
+    //GetDiversificationData(cuidValue,kekData,kek);
 
     PR_fprintf(PR_STDOUT,"In SessionKey: EncryptData! \n");
 
@@ -181,12 +216,75 @@ Java_com_netscape_symkey_SessionKey_EncryptData(JNIEnv * env, jclass this2, jstr
         {
             masterKey = ReturnSymKey( slot,keyname);
 
-            /* We need to use internal so that the key
-             * can be exported  by using PK11_GetKeyData()
-             */
             if (masterKey != NULL)
             {
-                kekKey = ComputeCardKeyOnToken(masterKey,kekData);
+
+
+                // ---------------------------------
+                // AC KDF SPEC CHANGE: Determine which KDF to use.
+                //
+                // Convert to unsigned types
+                BYTE nistSP800_108KdfOnKeyVersion_byte = static_cast<BYTE>(nistSP800_108KdfOnKeyVersion);
+                BYTE requestedKeyVersion_byte = static_cast<BYTE>(keyVersion[0]);
+                // if requested key version meets setting value, use NIST SP800-108 KDF
+                if (NistSP800_108KDF::useNistSP800_108KDF(nistSP800_108KdfOnKeyVersion_byte, requestedKeyVersion_byte) == true){
+
+                    PR_fprintf(PR_STDOUT,"EncryptData NistSP800_108KDF code: Using NIST SP800-108 KDF.\n");
+
+                    // react to "UseCUIDAsKDD" setting value
+                    jbyte* context_jbyte = NULL;
+                    jsize context_len_jsize = 0;
+                    if (nistSP800_108KdfUseCuidAsKdd == JNI_TRUE){
+                        context_jbyte = cuidValue;
+                        context_len_jsize = cuidValue_len;
+                    }else{
+                        context_jbyte = kddValue;
+                        context_len_jsize = kddValue_len;
+                    }
+
+                    // Converting this way is safe since jbyte is guaranteed to be 8 bits
+                    // Of course, this assumes that "char" is 8 bits (not guaranteed, but typical),
+                    //            but it looks like this assumption is also made in GetDiversificationData
+                    const BYTE* const context = reinterpret_cast<const BYTE*>(context_jbyte);
+
+                    // Convert jsize to size_t
+                    const size_t context_len = static_cast<size_t>(context_len_jsize);
+                    if (context_len > 0x000000FF){  // sanity check (CUID should never be larger than 255 bytes)
+                        PR_fprintf(PR_STDERR, "EncryptData NistSP800_108KDF code: Error; context_len larger than 255 bytes.\n");
+                        goto done;
+                    }
+
+                    // call NIST SP800-108 KDF routine
+                    try{
+                        NistSP800_108KDF::ComputeCardKeys(masterKey, context, context_len, &encKey, &macKey, &kekKey);
+                    }catch(std::runtime_error& ex){
+                        PR_fprintf(PR_STDERR, "EncryptData NistSP800_108KDF code: Exception invoking NistSP800_108KDF::ComputeCardKeys: ");
+                        PR_fprintf(PR_STDERR, "%s\n", ex.what() == NULL ? "null" : ex.what());
+                        goto done;
+                    }catch(...){
+                        PR_fprintf(PR_STDERR, "EncryptData NistSP800_108KDF code: Unknown exception invoking NistSP800_108KDF::ComputeCardKeys.\n");
+                        goto done;
+                    }
+
+                // if not a key version where we use the NIST SP800-108 KDF, use the original KDF
+                }else{
+
+                    PR_fprintf(PR_STDOUT,"EncryptData NistSP800_108KDF code: Using original KDF.\n");
+
+                    // AC: KDF SPEC CHANGE: Moved this call down from the original location.
+                    //                      (We don't always need to call it anymore; it depends on the KDF we're going to use.)
+                    //
+                    // Note the change from "cuidValue" to "kddValue".
+                    //   This change is necessary due to the semantics change in the parameters passed between TPS and TKS.
+                    GetDiversificationData(kddValue,kekData,kek);
+
+                    // AC: Derives the Kek key for the token.
+                    kekKey = ComputeCardKeyOnToken(masterKey,kekData);
+
+                } // endif use original KDF
+                // ---------------------------------
+
+
                 if (kekKey != NULL)
                 {
                     Buffer input = Buffer((BYTE*)cc, cc_len);
@@ -213,6 +311,16 @@ done:
        internal = NULL;
     }
 
+    // AC: KDF SPEC CHANGE:  For the NIST SP800-108 KDF, we build all 3 keys despite only using one of them (Kek) in this function.
+    //                       We do this because our NIST SP800-108 KDF outputs the data for all three keys simultaneously.
+    if( macKey ) {
+        PK11_FreeSymKey(macKey);
+        macKey = NULL;
+    }
+    if ( encKey ) {
+        PK11_FreeSymKey(encKey);
+        encKey = NULL;
+    }
     if ( kekKey != NULL) {
         PK11_FreeSymKey( kekKey);
         kekKey = NULL;
@@ -246,5 +354,11 @@ done:
         env->ReleaseByteArrayElements(CUID, cuidValue, JNI_ABORT);
     }
 
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    if ( kddValue != NULL){
+        env->ReleaseByteArrayElements(KDD, kddValue, JNI_ABORT);
+        kddValue = NULL;
+    }
+
     return handleBA;
 }
diff --git a/base/symkey/src/com/netscape/symkey/NistSP800_108KDF.cpp b/base/symkey/src/com/netscape/symkey/NistSP800_108KDF.cpp
new file mode 100644
index 0000000..9f89dd3
--- /dev/null
+++ b/base/symkey/src/com/netscape/symkey/NistSP800_108KDF.cpp
@@ -0,0 +1,470 @@
+/*
+ * NistSP800_108KDF.cpp - Implements the new Key Diversification Function (KDF) as required
+ *                        by the latest Department of Defense SIPRnet token interface
+ *                        specification.  The functions in this file are internally called
+ *                        by other functions in the Symkey library.  We have made patches
+ *                        to these other Symkey functions to trigger this new KDF routine
+ *                        at the appropriate times.
+ *
+ *                        Also provides a utility function for adding DES key parity.
+ */
+
+//*******************************************************************************
+
+#include "NistSP800_108KDF.h"
+
+//*******************************************************************************
+
+#include <cstring>    // memset()
+#include <sstream>    // std::ostringstream
+
+#ifdef NISTSP800_108_KDF_DEBUG
+#include <iostream>
+#endif
+
+#include "pk11pub.h"
+
+//*******************************************************************************
+
+namespace NistSP800_108KDF{
+
+//*******************************************************************************
+// Generates three PK11SymKey objects using the KDF_CM_SHA256HMAC_L384() function for key data.
+// After calling KDF_CM_SHA256HMAC_L384, the function splits up the output, sets DES parity,
+//   and imports the keys into the token.
+//
+// Careful:  This function currently generates the key data **IN RAM** using calls to NSS sha256.
+//           The key data is then "unwrapped" (imported) to the NSS token and then erased from RAM.
+//           (This means that a malicious actor on the box could steal the key data.)
+//
+// Note: Returned key material from the KDF is converted into keys according to the following:
+//   * Bytes 0  - 15 : enc/auth key
+//   * Bytes 16 - 31 : mac key
+//   * Bytes 32 - 47 : kek key
+//   We chose this order to conform with the key order used by the PUT KEY command.
+//
+//*******************************************************************************
+void ComputeCardKeys(  PK11SymKey* masterKey,               // Key Derivation Key
+                       const BYTE* context,                 // unique data passed to the kdf (kdd)
+                       const size_t context_length,         // length of context
+                       PK11SymKey** encKey,                 // output parameter: generated enc/auth key
+                       PK11SymKey** macKey,                 // output parameter: generated mac key
+                       PK11SymKey** kekKey)                 // output parameter: generated kek key
+{
+    // sanity check output parameters
+    if (*encKey != NULL){
+        throw std::runtime_error("Output parameter \"encKey\" wasn't initialized to NULL. Overwriting may result in a memory leak.");
+    }
+    if (*macKey != NULL){
+        throw std::runtime_error("Output parameter \"macKey\" wasn't initialized to NULL. Overwriting may result in a memory leak.");
+    }
+    if (*kekKey != NULL){
+        throw std::runtime_error("Output parameter \"kekKey\" wasn't initialized to NULL. Overwriting may result in a memory leak.");
+    }
+
+    // allocate space for KDF output
+    BYTE kdf_output[KDF_OUTPUT_SIZE_BYTES];
+
+    try{
+        // generate 384 bits of key data from the master key
+        KDF_CM_SHA256HMAC_L384(masterKey, context, context_length, KDF_LABEL, kdf_output, KDF_OUTPUT_SIZE_BYTES);
+    }catch(std::runtime_error& ex){
+        std::ostringstream msg;
+        msg << "Exception invoking NistSP800_108KDF::KDF_CM_SHA256HMAC_L384: ";
+        if (ex.what() == NULL){
+            msg << "NULL";
+        }else{
+            msg << ex.what();
+        }
+        throw std::runtime_error(msg.str());
+    }catch(...){
+        throw std::runtime_error("Unknown exception invoking NistSP800_108KDF::KDF_CM_SHA256HMAC_L384.");
+    }
+
+    try{
+        // get slot from master key
+        //   (we need the slot to be able to generate our temp key and unwrap our generated bytes
+        PK11SlotInfo* slot = PK11_GetSlotFromKey(masterKey);
+        if (slot == NULL){
+            throw std::runtime_error("Failed to get slot from masterKey.");
+        }
+        try{
+            // generate a temp key to import the key data with
+            PK11SymKey* tmpKey = PK11_TokenKeyGenWithFlags(slot,               // slot handle
+                                                           CKM_DES3_KEY_GEN,   // mechanism type
+                                                           NULL,               // pointer to params (SECItem structure)
+                                                           0,                  // keySize (per documentation in pk11skey.c, must be 0 for fixed key length algorithms)
+                                                           0,                  // pointer to keyid (SECItem structure)
+                                                           CKF_WRAP | CKF_UNWRAP | CKF_ENCRYPT | CKF_DECRYPT, // opFlags
+                                                           PK11_ATTR_PRIVATE | PK11_ATTR_UNEXTRACTABLE | PK11_ATTR_SENSITIVE, // attrFlags (AC: this is my "best guess" as to what flags should be set)
+                                                           NULL);              // pointer to wincx (AC: also my "best guess" - per pkix_sample_modules.h line 265, this should always be NULL on non-Windows)
+            if (tmpKey == NULL) {
+                throw std::runtime_error("Unable to create temp key (for use with importing the key data).");
+            }
+            try{
+
+                // set parity on each of the 3 generated **16 byte** keys
+                set_des_parity(kdf_output + (0 * KEY_DATA_SIZE_BYTES), KEY_DATA_SIZE_BYTES);
+                set_des_parity(kdf_output + (1 * KEY_DATA_SIZE_BYTES), KEY_DATA_SIZE_BYTES);
+                set_des_parity(kdf_output + (2 * KEY_DATA_SIZE_BYTES), KEY_DATA_SIZE_BYTES);
+
+                try{
+                    // copy byte array information into 2-key 3DES PK11 keys on token
+                    *encKey = Copy2Key3DESKeyDataToToken(slot, tmpKey, kdf_output + (0 * KEY_DATA_SIZE_BYTES), KEY_DATA_SIZE_BYTES);
+                    *macKey = Copy2Key3DESKeyDataToToken(slot, tmpKey, kdf_output + (1 * KEY_DATA_SIZE_BYTES), KEY_DATA_SIZE_BYTES);
+                    *kekKey = Copy2Key3DESKeyDataToToken(slot, tmpKey, kdf_output + (2 * KEY_DATA_SIZE_BYTES), KEY_DATA_SIZE_BYTES);
+                }catch(...){
+                    // free any keys we created before rethrowing
+                    if (*encKey != NULL){
+                        PK11_FreeSymKey(*encKey);
+                        *encKey = NULL;
+                    }
+                    if (*macKey != NULL){
+                        PK11_FreeSymKey(*macKey);
+                        *macKey = NULL;
+                    }
+                    if (*kekKey != NULL){
+                        PK11_FreeSymKey(*kekKey);
+                        *kekKey = NULL;
+                    }
+
+                    throw;
+                }
+
+                // clean up
+                PK11_FreeSymKey(tmpKey);
+                tmpKey = NULL;
+            }catch(...){
+                // clean up
+                PK11_FreeSymKey(tmpKey);
+                tmpKey = NULL;
+
+                throw;
+            }
+            // clean up
+            PK11_FreeSlot(slot);
+            slot = NULL;
+        }catch(...){
+            // clean up
+            PK11_FreeSlot(slot);
+            slot = NULL;
+
+            throw;
+        }
+
+        // erase key data from RAM
+        memset(kdf_output, 0, KDF_OUTPUT_SIZE_BYTES);
+    }catch(...){
+        // erase key data from RAM before rethrowing
+        memset(kdf_output, 0, KDF_OUTPUT_SIZE_BYTES);
+
+        throw;
+    }
+}
+
+// uses the specified temporary key to encrypt and then unwrap (decrypt) the specified binary data onto the specified token
+// this has the net effect of copying the raw key data to the token
+PK11SymKey* Copy2Key3DESKeyDataToToken( PK11SlotInfo* slot,      // slot to unwrap key onto
+                                        PK11SymKey* tmpKey,      // temporary key to use (must already be on the slot)
+                                        const BYTE* const data,  // pointer to array containing the key data to encrypt and then unwrap (decrypt) on the token
+                                        const size_t data_len)   // length of data in above array
+{
+
+    // ensure expected input data size
+    if (data_len != KEY_DATA_SIZE_BYTES){
+        throw std::runtime_error("Invalid data length value (should be 16) (Copy2Key3DESKeyDataToToken).");
+    }
+
+    // create encryption context
+    SECItem noParams = { siBuffer, NULL, 0 };
+    PK11Context* context = PK11_CreateContextBySymKey(CKM_DES3_ECB,   // mechanism type
+                                                      CKA_ENCRYPT,    // operation type
+                                                      tmpKey,         // symKey to operate on
+                                                      &noParams);     // pointer to param (SECItem structure)
+    if (context == NULL) {
+        throw std::runtime_error("Unable to create context (Copy2Key3DESKeyDataToToken).");
+    }
+    try{
+        BYTE encryptedData[KEY_DATA_SIZE_BYTES + 8];
+        BYTE unencryptedData[KEY_DATA_SIZE_BYTES + 8];
+
+        // copy the key data to a new (larger) buffer
+        memcpy(unencryptedData, data, KEY_DATA_SIZE_BYTES);
+
+        // copy first DES key (of the two) into the end of the buffer
+        //  (key1-key2-key1)
+        memcpy(unencryptedData + KEY_DATA_SIZE_BYTES, data, 8);
+
+        try{
+
+            // encrypt key data with the temp key
+            int encryptedData_result_len = -1;
+            SECStatus result = PK11_CipherOp( context,                      // [in] pointer to PK11Context object
+                                              encryptedData,                // [out] pointer to output buffer for encrypted data
+                                              &encryptedData_result_len,    // [out] pointer to output buffer length
+                                              KEY_DATA_SIZE_BYTES + 8,      // [in] size of output buffer
+                                              unencryptedData,              // [in] pointer to input buffer for unencrypted data
+                                              KEY_DATA_SIZE_BYTES + 8);     // [in] size of input buffer
+            if (result != SECSuccess){
+                throw std::runtime_error("Unable to encrypt plaintext key data with temporary key (Copy2Key3DESKeyDataToToken).");
+            }
+            if (encryptedData_result_len != KEY_DATA_SIZE_BYTES + 8){
+                throw std::runtime_error("Invalid output encrypting plaintext key data with temporary key (Copy2Key3DESKeyDataToToken).");
+            }
+
+            // now "unwrap" the encrypted key data onto the token with the temporary key
+            SECItem wrappeditem;
+            wrappeditem.type = siBuffer;
+            wrappeditem.data = encryptedData;
+            wrappeditem.len = encryptedData_result_len;
+            noParams.type = siBuffer;
+            noParams.data = NULL;
+            noParams.len = 0;
+            PK11SymKey* const resultingKey = PK11_UnwrapSymKeyWithFlags(tmpKey,                      // pointer to wrappingKey (PK11SymKey)
+                                                                        CKM_DES3_ECB,                // wrapType (CK_MECHANISM_TYPE)
+                                                                        &noParams,                   // pointer to param (SECItem struct)
+                                                                        &wrappeditem,                // pointer to wrappedKey data (SECItem struct)
+                                                                        CKM_DES3_KEY_GEN,            // target (CK_MECHANISM_TYPE)
+                                                                        CKA_DECRYPT,                 // operation (CK_ATTRIBUTE_TYPE)
+                                                                        KEY_DATA_SIZE_BYTES + 8,     // keySize (int)
+                                                                        CKF_SIGN | CKF_WRAP | CKF_UNWRAP | CKF_ENCRYPT | CKF_DECRYPT); // flags (CK_FLAGS)
+            if (resultingKey == NULL){
+                throw std::runtime_error("Unable to unwrap key onto token (Copy2Key3DESKeyDataToToken).");
+            }
+
+            // zeroize unencrypted key data before returning
+            memset(unencryptedData, 0, KEY_DATA_SIZE_BYTES + 8);
+
+            // clean up
+            PK11_DestroyContext(context, PR_TRUE);
+
+            return resultingKey;
+        }catch(...){
+            // zeroize unencrypted key data before rethrowing
+            memset(unencryptedData, 0, KEY_DATA_SIZE_BYTES + 8);
+
+            throw;
+        }
+
+    }catch(...){
+        // clean up
+        PK11_DestroyContext(context, PR_TRUE);
+
+        throw;
+    }
+}
+
+//*******************************************************************************
+// Key Derivation Function in Counter Mode using PRF = SHA256HMAC (NIST SP 800-108)
+//   Calculates 384 bits of diversified output from the provided master key (K_I)
+//*******************************************************************************
+void KDF_CM_SHA256HMAC_L384(  PK11SymKey* K_I,                     // Key Derivation Key
+                              const BYTE* context,                 // unique data passed to the kdf (kdd)
+                              const size_t context_length,         // length of context
+                              const BYTE label,                    // one BYTE label parameter
+                              BYTE* const output,                  // output is a L-bit array of BYTEs
+                              const size_t output_length)          // output length must be at least 48 bytes
+{
+    //unsigned int h_bits = SHA256_LENGTH * 8;      // SHA256_HMAC output size = 256 bits
+    //unsigned int h_bytes = SHA256_LENGTH;         // SHA256_HMAC output size = 32 bytes
+    //const unsigned int r_bits = 8;                // The counter will be representable in 8 bits
+    //unsigned int n = L / h_bits;                  // Number of iterations of the PRF
+    //unsigned int L_BYTE_array_length = (int)ceil(L/256.0);
+
+    const BYTE n = 2;                               // ceil(384 / (SHA256LENGTH * 8)) == 2
+    const size_t L_BYTE_array_length = 2;           // 384 = 0x0180 hex; 2 byte long representation
+
+    // sanity check that output buffer is large enough to contain 384 bits
+    if (output_length < KDF_OUTPUT_SIZE_BYTES){
+        throw std::runtime_error("Array \"output\" must be at least 48 bytes in size.");
+    }
+
+    // calculate size of temporary buffer 
+    size_t HMAC_DATA_INPUT_SIZE = context_length + 3 + L_BYTE_array_length; // Don't change without reviewing code below.
+    // prevent integer overflow
+    if (HMAC_DATA_INPUT_SIZE < context_length){
+        throw std::runtime_error("Input parameter \"context_length\" too large.");
+    }
+    BYTE* hmac_data_input = new BYTE[HMAC_DATA_INPUT_SIZE];                 // Hash Input = context + 5 BYTES
+    
+    BYTE K[n * SHA256_LENGTH];                                              // BYTE K[n * h_bytes]; - Buffer to store PRF output
+    try{
+        const BYTE L_BYTE_array[L_BYTE_array_length] = {0x01, 0x80};        // Array to store L in BYTES
+
+        /* Establish HMAC Input */
+        memset(hmac_data_input, 0, HMAC_DATA_INPUT_SIZE);
+        hmac_data_input[1] = label;
+        hmac_data_input[2] = 0x00;
+        memcpy(&hmac_data_input[3], context, context_length);
+        memcpy(&hmac_data_input[context_length+3], L_BYTE_array, 2);
+
+        for(BYTE i = 1; i <= n; i++){
+            // hmac_data_input = i || label || 0x00 || context || L
+            hmac_data_input[0] = i;
+
+#ifdef NISTSP800_108_KDF_DEBUG
+            std::cout << "hmac_data_input:\n";
+            print_BYTE_array(hmac_data_input, HMAC_DATA_INPUT_SIZE); // 5 bytes added to context
+#endif
+
+            SHA256HMAC(K_I, hmac_data_input, HMAC_DATA_INPUT_SIZE, &K[(i - 1) * SHA256_LENGTH]);
+        }
+        
+        // clean up
+        delete[] hmac_data_input;
+        hmac_data_input = NULL;
+    
+    // upon exception, clean up before rethrowing
+    }catch(...){
+        // clean up
+        delete[] hmac_data_input;
+        hmac_data_input = NULL;
+
+        throw;
+    }
+
+#ifdef NISTSP800_108_KDF_DEBUG
+    std::cout << "KDF Output (untrimmed):\n";
+    print_BYTE_array(K, n * SHA256_LENGTH);
+#endif
+
+    // copy result to output buffer, trimming it to 384 bits
+    memcpy(output, K, KDF_OUTPUT_SIZE_BYTES);
+
+    // clear K before returning
+    memset(K, 0, n * SHA256_LENGTH);
+}
+
+//*******************************************************************************
+
+void SHA256HMAC(     PK11SymKey* key,                  // HMAC Secret Key (K_I)
+                     const BYTE* input,                // HMAC Input (i||04||00||context||0180)
+                     const size_t input_length,        // Input Length
+                     BYTE* const output)               // Output Buffer (32 BYTES written)
+{
+    unsigned int len = 32;
+    PK11Context *context = 0;
+    SECStatus s;
+    SECItem noParams;
+    noParams.type = siBuffer;
+    noParams.data = 0;
+    noParams.len = 0;
+
+    context = PK11_CreateContextBySymKey(CKM_SHA256_HMAC, CKA_SIGN, key, &noParams);
+    if (!context) {
+        throw std::runtime_error("CreateContextBySymKey failed");
+    }
+    try{
+
+        s = PK11_DigestBegin(context);
+        if (s != SECSuccess) {
+            throw std::runtime_error("DigestBegin failed");
+        }
+
+        s = PK11_DigestOp(context, input, input_length);
+        if (s != SECSuccess) {
+            throw std::runtime_error("DigestOp failed");
+        }
+
+        s = PK11_DigestFinal(context, output, &len, 32);
+        if (s != SECSuccess) {
+            throw std::runtime_error("DigestFinal failed");
+        }
+
+/* Debug Output */
+#ifdef NISTSP800_108_KDF_DEBUG
+        std::cout << "********************SHA256HMAC_NSS********************\n";
+        std::cout << "\nInput Data:\n";
+        print_BYTE_array(input, input_length);
+        std::cout << "\nSHA256HMAC_NSS output:\n";
+        print_BYTE_array(output, SHA256_LENGTH);
+#endif
+
+        PK11_DestroyContext(context, PR_TRUE);
+    }catch(...){
+        PK11_DestroyContext(context, PR_TRUE);
+        throw;
+    }
+}
+
+//*******************************************************************************
+//    DES Parity Functions
+//*******************************************************************************
+
+/* DES KEY Parity conversion table. Takes each byte >> 1 as an index, returns
+ * that byte with the proper parity bit set*/
+const unsigned char parityTable[256] =
+  {
+  /* Even...0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e */
+  /* E */0x01, 0x02, 0x04, 0x07, 0x08, 0x0b, 0x0d, 0x0e,
+  /* Odd....0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e */
+  /* O */0x10, 0x13, 0x15, 0x16, 0x19, 0x1a, 0x1c, 0x1f,
+  /* Odd....0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e */
+  /* O */0x20, 0x23, 0x25, 0x26, 0x29, 0x2a, 0x2c, 0x2f,
+  /* Even...0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e */
+  /* E */0x31, 0x32, 0x34, 0x37, 0x38, 0x3b, 0x3d, 0x3e,
+  /* Odd....0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e */
+  /* O */0x40, 0x43, 0x45, 0x46, 0x49, 0x4a, 0x4c, 0x4f,
+  /* Even...0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e */
+  /* E */0x51, 0x52, 0x54, 0x57, 0x58, 0x5b, 0x5d, 0x5e,
+  /* Even...0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e */
+  /* E */0x61, 0x62, 0x64, 0x67, 0x68, 0x6b, 0x6d, 0x6e,
+  /* Odd....0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e */
+  /* O */0x70, 0x73, 0x75, 0x76, 0x79, 0x7a, 0x7c, 0x7f,
+  /* Odd....0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e */
+  /* O */0x80, 0x83, 0x85, 0x86, 0x89, 0x8a, 0x8c, 0x8f,
+  /* Even...0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e */
+  /* E */0x91, 0x92, 0x94, 0x97, 0x98, 0x9b, 0x9d, 0x9e,
+  /* Even...0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae */
+  /* E */0xa1, 0xa2, 0xa4, 0xa7, 0xa8, 0xab, 0xad, 0xae,
+  /* Odd....0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe */
+  /* O */0xb0, 0xb3, 0xb5, 0xb6, 0xb9, 0xba, 0xbc, 0xbf,
+  /* Even...0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce */
+  /* E */0xc1, 0xc2, 0xc4, 0xc7, 0xc8, 0xcb, 0xcd, 0xce,
+  /* Odd....0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde */
+  /* O */0xd0, 0xd3, 0xd5, 0xd6, 0xd9, 0xda, 0xdc, 0xdf,
+  /* Odd....0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee */
+  /* O */0xe0, 0xe3, 0xe5, 0xe6, 0xe9, 0xea, 0xec, 0xef,
+  /* Even...0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe */
+  /* E */0xf1, 0xf2, 0xf4, 0xf7, 0xf8, 0xfb, 0xfd, 0xfe, };
+
+void set_des_parity(BYTE* const key, const size_t length)
+{
+    if(length != 2*8){
+        throw std::runtime_error("set_des_parity failed: wrong key size");
+    }
+
+    for (size_t i=0; i < length; i++)
+    {
+        key[i] = parityTable[key[i]>>1];
+    }
+}
+
+//*******************************************************************************
+//   BYTE Array Management Functions
+//*******************************************************************************
+#ifdef NISTSP800_108_KDF_DEBUG
+void print_BYTE_array(const BYTE *array2, const size_t length)
+{
+    for (size_t i = 0; i < length; i++){
+        printf("%02x ", array2[i]);
+        if((i+1)%16 == 0)
+            printf("\n");
+    }
+    std::cout << std::endl;
+}
+#endif
+
+//*******************************************************************************
+// NistSP800_108KDF Decision-Making Functions
+//*******************************************************************************
+// Returns true if the new KDF should be used, otherwise false.
+bool useNistSP800_108KDF(BYTE nistSP800_108KDFonKeyVersion, BYTE requestedKeyVersion){
+    return (requestedKeyVersion >= nistSP800_108KDFonKeyVersion);
+}
+
+//*******************************************************************************
+
+} // end namespace NistSP800_108KDF
+
+//*******************************************************************************
diff --git a/base/symkey/src/com/netscape/symkey/NistSP800_108KDF.h b/base/symkey/src/com/netscape/symkey/NistSP800_108KDF.h
new file mode 100644
index 0000000..f26edd5
--- /dev/null
+++ b/base/symkey/src/com/netscape/symkey/NistSP800_108KDF.h
@@ -0,0 +1,115 @@
+/*
+ * NistSP800_108KDF.H  -  Implements the new Key Diversification Function (KDF) as required
+ *                        by the latest Department of Defense SIPRnet token interface
+ *                        specification.  The functions in this file are internally called
+ *                        by other functions in the Symkey library.  We have made patches
+ *                        to these other Symkey functions to trigger this new KDF routine
+ *                        at the appropriate times.
+ *
+ *                        Also provides a utility function for adding DES key parity.
+ */
+
+#ifndef NISTSP800_108KDF_H_
+#define NISTSP800_108KDF_H_
+
+//*******************************************************************************
+//   Defines
+//*******************************************************************************
+// Debug Flag - Enabling this includes <iostream> and results in the  NIST SP800-108
+//              KDF code printing out lots of stuff (including key material!) to stdout.
+//#define NISTSP800_108_KDF_DEBUG 1
+
+//*******************************************************************************
+//   Includes
+//*******************************************************************************
+#include <cstddef>   // typedef size_t
+#include <stdexcept> // std::runtime_error
+
+#include "pk11pub.h"
+
+#include "Base.h"    // typedef BYTE
+
+//*******************************************************************************
+
+namespace NistSP800_108KDF{
+
+//*******************************************************************************
+//   Constants
+//*******************************************************************************
+
+// might already be defined by NSS
+#ifndef SHA256_LENGTH
+#define SHA256_LENGTH 32
+#endif
+
+// AC: don't change any of these constants without validating the code that uses them
+const size_t KDF_OUTPUT_SIZE_BITS = 384;
+const size_t KDF_OUTPUT_SIZE_BYTES = KDF_OUTPUT_SIZE_BITS / 8;
+const size_t KEY_DATA_SIZE_BYTES = KDF_OUTPUT_SIZE_BYTES / 3;
+
+const size_t KDD_SIZE_BYTES = 10;   // expected KDD field length in bytes
+
+const BYTE KDF_LABEL = 0x04; // arbitrary input to crypto routine (see documentation)
+
+//*******************************************************************************
+//   Function Headers
+//*******************************************************************************
+
+// Generates three PK11SymKey objects using the KDF_CM_SHA256HMAC_L384() function for key data.
+// After calling KDF_CM_SHA256HMAC_L384, the function splits up the output, sets DES parity,
+//   and imports the keys into the token.
+//
+// Careful:  This function currently generates the key data **IN RAM** using calls to NSS sha256.
+//           The key data is then "unwrapped" (imported) to the NSS token and then erased from RAM.
+//           (This means that a malicious actor on the box could steal the key data.)
+//
+// Note: Returned key material from the KDF is converted into keys according to the following:
+//   * Bytes 0  - 15 : enc/auth key
+//   * Bytes 16 - 31 : mac key
+//   * Bytes 32 - 47 : kek key
+//   We chose this order to conform with the key order used by the PUT KEY command.
+void ComputeCardKeys(  PK11SymKey* masterKey,               // Key Derivation Key
+                       const BYTE* context,                 // unique data passed to the kdf (kdd)
+                       const size_t context_length,         // length of context
+                       PK11SymKey** encKey,                 // output parameter: generated enc/auth key
+                       PK11SymKey** macKey,                 // output parameter: generated mac key
+                       PK11SymKey** kekKey);                // output parameter: generated kek key
+
+// uses the specified temporary key to encrypt and then unwrap (decrypt) the specified binary data onto the specified token
+// this has the net effect of copying the raw key data to the token
+PK11SymKey* Copy2Key3DESKeyDataToToken( PK11SlotInfo* slot,      // slot to unwrap key onto
+                                        PK11SymKey* tmpKey,      // temporary key to use (must already be on the slot)
+                                        const BYTE* const data,  // pointer to array containing the key data to encrypt and then unwrap (decrypt) on the token
+                                        const size_t data_len);  // length of data in above array
+
+// calculates 384 bits of diversified output from the provided master key (K_I)
+void KDF_CM_SHA256HMAC_L384(  PK11SymKey* K_I,                     // Key Derivation Key
+                              const BYTE* context,                 // unique data passed to the kdf (kdd)
+                              const size_t context_length,         // length of context
+                              const BYTE label,                    // one BYTE label parameter
+                              BYTE* const output,                  // output is a L-bit array of BYTEs
+                              const size_t output_length);         // output length must be at least 48 bytes
+
+void SHA256HMAC(     PK11SymKey* key,                     // HMAC Secret Key (K_I)
+                     const BYTE* input,                   // HMAC Input (i||04||00||context||0180)
+                     const size_t input_length,           // Input Length
+                     BYTE* const output);                 // Output Buffer (32 BYTES written)
+
+/* DES KEY Parity conversion table. Takes each byte >> 1 as an index, returns
+ * that byte with the proper parity bit set*/
+void set_des_parity(BYTE* const key, const size_t length);
+
+#ifdef NISTSP800_108_KDF_DEBUG
+void print_BYTE_array(const BYTE *array2, const size_t len);
+#endif
+
+// Returns true if the new KDF should be used, otherwise false.
+bool useNistSP800_108KDF(BYTE nistSP800_108KDFonKeyVersion, BYTE requestedKeyVersion);
+
+//*******************************************************************************
+
+} // end namespace NistSP800_108KDF
+
+//*******************************************************************************
+
+#endif /* NISTSP800_108KDF_H_ */
diff --git a/base/symkey/src/com/netscape/symkey/SessionKey.cpp b/base/symkey/src/com/netscape/symkey/SessionKey.cpp
index 9f3a353..2c14673 100644
--- a/base/symkey/src/com/netscape/symkey/SessionKey.cpp
+++ b/base/symkey/src/com/netscape/symkey/SessionKey.cpp
@@ -51,6 +51,10 @@ extern "C"
 #include "Buffer.h"
 #include "SymKey.h"
 
+// AC: KDF SPEC CHANGE: Include headers for NIST SP800-108 KDF functions.
+#include "NistSP800_108KDF.h"
+
+
 #define STEAL_JSS
 #ifdef STEAL_JSS
 // stealing code from JSS to handle DRM support
@@ -573,13 +577,15 @@ extern "C"
  * Method:    ComputeSessionKey
  * Signature: ([B[B[B[B)[B
  */
+// AC: KDF SPEC CHANGE: function signature change - added jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, and jbyteArray KDD
     JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_ComputeSessionKey
-        (JNIEnv *, jclass, jstring, jstring, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jstring, jstring, jstring);
+        (JNIEnv *, jclass, jstring, jstring, jbyteArray, jbyteArray, jbyteArray, jbyte, jboolean, jbyteArray, jbyteArray, jbyteArray, jstring, jstring, jstring);
 #ifdef __cplusplus
 }
 #endif
 #define KEYLENGTH 16
-extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_ComputeSessionKey(JNIEnv * env, jclass this2, jstring tokenName, jstring keyName, jbyteArray card_challenge, jbyteArray host_challenge, jbyteArray keyInfo, jbyteArray CUID, jbyteArray macKeyArray, jstring useSoftToken_s, jstring keySet, jstring sharedSecretKeyName)
+// AC: KDF SPEC CHANGE: function signature change - added jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, and jbyteArray KDD
+extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_ComputeSessionKey(JNIEnv * env, jclass this2, jstring tokenName, jstring keyName, jbyteArray card_challenge, jbyteArray host_challenge, jbyteArray keyInfo, jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, jbyteArray CUID, jbyteArray KDD, jbyteArray macKeyArray, jstring useSoftToken_s, jstring keySet, jstring sharedSecretKeyName)
 {
     /* hardcore permanent mac key */
     jbyte *mac_key = NULL;
@@ -608,8 +614,13 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
 
     PK11SymKey *macSymKey = NULL;
     PK11SymKey *symkey16 = NULL;
-    PK11SymKey *macKey = NULL;
 
+    // AC: KDF SPEC CHANGE:  For the NIST SP800-108 KDF, we build all 3 keys despite only using one of them (Mac) in this function.
+    //                       We do this because our NIST SP800-108 KDF outputs the data for all three keys simultaneously.
+    // KDF output keys
+    PK11SymKey* macKey = NULL;
+    PK11SymKey* encKey = NULL;
+    PK11SymKey* kekKey = NULL;
 
     BYTE macData[KEYLENGTH];
     char keyname[KEYNAMELENGTH];
@@ -625,7 +636,12 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
     jbyteArray handleBA=NULL;
     jbyte *handleBytes=NULL;
 
-    jbyte *    cuidValue = NULL;
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    //                       Also added "len" variable for CUID (for sanity check).
+    jbyte* cuidValue = NULL;
+    jsize cuidValue_len = -1;
+    jbyte* kddValue = NULL;
+    jsize kddValue_len = -1;
 
     jbyte *cc = NULL;
     int cc_len = 0;
@@ -693,13 +709,30 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
         goto done;
     }
 
+
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    //                       Also added "len" variable for CUID (for sanity check).
     if ( CUID != NULL ) {
         cuidValue =  (jbyte*)(env)->GetByteArrayElements( CUID, NULL);
+        cuidValue_len = env->GetArrayLength(CUID);
     }
-
     if( cuidValue == NULL) {
         goto done;
     }
+    if ( cuidValue_len <= 0){  // check that CUID is at least 1 byte in length
+        goto done;
+    }
+    if ( KDD != NULL ){
+        kddValue = env->GetByteArrayElements(KDD, NULL);
+        kddValue_len = env->GetArrayLength(KDD);
+    }
+    if ( kddValue == NULL ){
+        goto done;
+    }
+    if ( kddValue_len != static_cast<jsize>(NistSP800_108KDF::KDD_SIZE_BYTES) ){   // check that KDD is expected size
+        goto done;
+    }
+
 
     /* copy card and host challenge into input buffer */
     for (i = 0; i < 8; i++)
@@ -711,7 +744,8 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
         input[8+i] = hc[i];
     }
 
-    GetDiversificationData(cuidValue,macData,mac);//keytype is mac
+    // AC: KDF SPEC CHANGE: Moved this call down. (We don't necessarily need it anymore depending on the KDF we're going to use.)
+    //GetDiversificationData(cuidValue,macData,mac);//keytype is mac
 
     if(tokenName)
     {
@@ -753,21 +787,90 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
             goto done;
         }
 
-        macKey =ComputeCardKeyOnToken(masterKey,macData);
+
+        // ---------------------------------
+        // AC KDF SPEC CHANGE: Determine which KDF to use.
+        //
+        // Convert to unsigned types
+        BYTE nistSP800_108KdfOnKeyVersion_byte = static_cast<BYTE>(nistSP800_108KdfOnKeyVersion);
+        BYTE requestedKeyVersion_byte = static_cast<BYTE>(keyVersion[0]);
+        // if requested key version meets setting value, use NIST SP800-108 KDF
+        if (NistSP800_108KDF::useNistSP800_108KDF(nistSP800_108KdfOnKeyVersion_byte, requestedKeyVersion_byte) == true){
+
+            PR_fprintf(PR_STDOUT,"ComputeSessionKey NistSP800_108KDF code: Using NIST SP800-108 KDF.\n");
+
+            // react to "UseCUIDAsKDD" setting value
+            jbyte* context_jbyte = NULL;
+            jsize context_len_jsize = 0;
+            if (nistSP800_108KdfUseCuidAsKdd == JNI_TRUE){
+                context_jbyte = cuidValue;
+                context_len_jsize = cuidValue_len;
+            }else{
+                context_jbyte = kddValue;
+                context_len_jsize = kddValue_len;
+            }
+
+            // Converting this way is safe since jbyte is guaranteed to be 8 bits
+            // Of course, this assumes that "char" is 8 bits (not guaranteed, but typical),
+            //            but it looks like this assumption is also made in GetDiversificationData
+            const BYTE* const context = reinterpret_cast<const BYTE*>(context_jbyte);
+
+            // Convert jsize to size_t
+            const size_t context_len = static_cast<size_t>(context_len_jsize);
+            if (context_len > 0x000000FF){  // sanity check (CUID should never be larger than 255 bytes)
+                PR_fprintf(PR_STDERR, "ComputeSessionKey NistSP800_108KDF code: Error; context_len larger than 255 bytes.\n");
+                goto done;
+            }
+
+            // call NIST SP800-108 KDF routine
+            try{
+                NistSP800_108KDF::ComputeCardKeys(masterKey, context, context_len, &encKey, &macKey, &kekKey);
+            }catch(std::runtime_error& ex){
+                PR_fprintf(PR_STDERR, "ComputeSessionKey NistSP800_108KDF code: Exception invoking NistSP800_108KDF::ComputeCardKeys: ");
+                PR_fprintf(PR_STDERR, "%s\n", ex.what() == NULL ? "null" : ex.what());
+                goto done;
+            }catch(...){
+                PR_fprintf(PR_STDERR, "ComputeSessionKey NistSP800_108KDF code: Unknown exception invoking NistSP800_108KDF::ComputeCardKeys.\n");
+                goto done;
+            }
+
+        // if not a key version where we use the NIST SP800-108 KDF, use the original KDF
+        }else{
+
+            PR_fprintf(PR_STDOUT,"ComputeSessionKey NistSP800_108KDF code: Using original KDF.\n");
+
+            // AC: KDF SPEC CHANGE: Moved this call down from the original location.
+            //                      (We don't always need to call it anymore; it depends on the KDF we're going to use.)
+            //
+            // Note the change from "cuidValue" to "kddValue".
+            //   This change is necessary due to the semantics change in the parameters passed between TPS and TKS.
+            GetDiversificationData(kddValue,macData,mac);//keytype is mac
+
+            // AC: Derives the mac key for the token.
+            macKey =ComputeCardKeyOnToken(masterKey,macData);
+
+        } // endif use original KDF
+        // ---------------------------------
+
+
         if(macKey == NULL)
         {
             goto done;
         }
-         
+
+        // AC: This computes the GP session key using the card-specific MAC key we previously derived.
         symkey = DeriveKey(macKey, Buffer((BYTE*)hc, hc_len), Buffer((BYTE*)cc, cc_len));
 
-        if(symkey == NULL)
-        {
-            goto done;
-        }
     }
-    //Now wrap the key for the trip back to TPS with shared secret transport key
 
+    // AC: Moved this check out of the else block so we catch NULL keys in the developer key case
+    //     (The call already exists outside the "else" block for ComputeEncSessionKey and ComputeKekKey.)
+    if(symkey == NULL)
+    {
+        goto done;
+    }
+
+    //Now wrap the key for the trip back to TPS with shared secret transport key
     symkey16 = NULL;
      transportKey = ReturnSymKey( internal, GetSharedSecretKeyName(NULL));
     if ( transportKey == NULL ) {
@@ -829,10 +932,20 @@ done:
         masterKey = NULL;
     }
            
+    // AC: KDF SPEC CHANGE:  For the NIST SP800-108 KDF, we build all 3 keys despite only using one of them (Mac) in this function.
+    //                       We do this because our NIST SP800-108 KDF outputs the data for all three keys simultaneously.
     if( macKey ) {
         PK11_FreeSymKey( macKey);
         macKey = NULL;
     }
+    if ( encKey ) {
+        PK11_FreeSymKey(encKey);
+        encKey = NULL;
+    }
+    if ( kekKey ) {
+        PK11_FreeSymKey(kekKey);
+        kekKey = NULL;
+    }
 
     if( macSymKey ) {
         PK11_FreeSymKey( macSymKey );
@@ -849,7 +962,9 @@ done:
         sharedSecretKeyNameChars = NULL;
     }
 
-    if ( handleBA != NULL) {
+    // AC BUGFIX:  Check the value of handleBytes (not handleBA) before freeing handleBytes!
+    //if ( handleBA != NULL) {
+    if ( handleBytes != NULL) {
         (env)->ReleaseByteArrayElements( handleBA, handleBytes, 0);
     }
 
@@ -869,11 +984,22 @@ done:
         (env)->ReleaseByteArrayElements(CUID, cuidValue, JNI_ABORT);
     }
 
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    if ( kddValue != NULL){
+        env->ReleaseByteArrayElements(KDD, kddValue, JNI_ABORT);
+        kddValue = NULL;
+    }
+
     if( mac_key != NULL) {
         (env)->ReleaseByteArrayElements(macKeyArray, mac_key, JNI_ABORT);
     }
 
-    return handleBA;
+    // AC: BUGFIX: Don't return a java array with uninitialized or zero'd data.
+    if (wrapStatus != SECFailure ){
+        return handleBA;
+    }else{
+        return NULL;
+    }
 }
 
 
@@ -886,13 +1012,15 @@ extern "C"
  * Method:    ComputeEncSessionKey
  * Signature: ([B[B[B[B)[B
  */
+// AC: KDF SPEC CHANGE: function signature change - added jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, and jbyteArray KDD
     JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_ComputeEncSessionKey
-        (JNIEnv *, jclass, jstring, jstring, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jstring, jstring);
+        (JNIEnv *, jclass, jstring, jstring, jbyteArray, jbyteArray, jbyteArray, jbyte, jboolean, jbyteArray, jbyteArray, jbyteArray, jstring, jstring);
 #ifdef __cplusplus
 }
 #endif
 #define KEYLENGTH 16
-extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_ComputeEncSessionKey(JNIEnv * env, jclass this2, jstring tokenName, jstring keyName, jbyteArray card_challenge, jbyteArray host_challenge, jbyteArray keyInfo, jbyteArray CUID, jbyteArray encKeyArray, jstring useSoftToken_s, jstring keySet)
+// AC: KDF SPEC CHANGE: function signature change - added jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, and jbyteArray KDD
+extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_ComputeEncSessionKey(JNIEnv * env, jclass this2, jstring tokenName, jstring keyName, jbyteArray card_challenge, jbyteArray host_challenge, jbyteArray keyInfo, jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, jbyteArray CUID, jbyteArray KDD, jbyteArray encKeyArray, jstring useSoftToken_s, jstring keySet)
 {
     /* hardcoded permanent enc key */
     jbyte *enc_key = NULL;
@@ -919,9 +1047,15 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
     PK11SymKey *masterKey  = NULL;
 
     PK11SymKey *encSymKey  = NULL;
-    PK11SymKey *encKey     = NULL;
     PK11SymKey *symkey16   = NULL;
 
+    // AC: KDF SPEC CHANGE:  For the NIST SP800-108 KDF, we build all 3 keys despite only using one of them (Enc) in this function.
+    //                       We do this because our NIST SP800-108 KDF outputs the data for all three keys simultaneously.
+    // KDF output keys
+    PK11SymKey* macKey = NULL;
+    PK11SymKey* encKey = NULL;
+    PK11SymKey* kekKey = NULL;
+
     BYTE encData[KEYLENGTH];
     char keyname[KEYNAMELENGTH];
 
@@ -934,7 +1068,12 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
     jbyteArray handleBA=NULL;
     jbyte *handleBytes=NULL;
 
-    jbyte *    cuidValue = NULL;
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    //                       Also added "len" variable for CUID (for sanity check).
+    jbyte* cuidValue = NULL;
+    jsize cuidValue_len = -1;
+    jbyte* kddValue = NULL;
+    jsize kddValue_len = -1;
 
     jbyte *cc = NULL;
     int cc_len = 0;
@@ -989,13 +1128,30 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
         goto done;
     }
 
-    if( CUID != NULL) {
-        cuidValue = (jbyte*)(env)->GetByteArrayElements( CUID, NULL);
-    }
 
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    //                       Also added "len" variable for CUID (for sanity check).
+    if ( CUID != NULL ) {
+        cuidValue =  (jbyte*)(env)->GetByteArrayElements( CUID, NULL);
+        cuidValue_len = env->GetArrayLength(CUID);
+    }
     if( cuidValue == NULL) {
         goto done;
     }
+    if ( cuidValue_len <= 0){  // check that CUID is at least 1 byte in length
+        goto done;
+    }
+    if ( KDD != NULL ){
+        kddValue = env->GetByteArrayElements(KDD, NULL);
+        kddValue_len = env->GetArrayLength(KDD);
+    }
+    if ( kddValue == NULL ){
+        goto done;
+    }
+    if ( kddValue_len != static_cast<jsize>(NistSP800_108KDF::KDD_SIZE_BYTES) ){   // check that KDD is expected size
+        goto done;
+    }
+
 
     /* copy card and host challenge into input buffer */
     for (i = 0; i < 8; i++)
@@ -1007,7 +1163,8 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
         input[8+i] = hc[i];
     }
 
-    GetDiversificationData(cuidValue,encData,enc);
+    // AC: KDF SPEC CHANGE: Moved this call down. (We don't necessarily need it anymore depending on the KDF we're going to use.)
+    //GetDiversificationData(cuidValue,encData,enc);
 
     if(tokenName)
     {
@@ -1044,17 +1201,81 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
     {
         masterKey = ReturnSymKey( slot,keyname);
 
-        /* We need to use internal so that the key
-         * can be exported  by using PK11_GetKeyData()
-         */
         if(masterKey == NULL) {
             goto done;
         }
 
-        encKey =ComputeCardKeyOnToken(masterKey,encData);
+
+        // ---------------------------------
+        // AC KDF SPEC CHANGE: Determine which KDF to use.
+        //
+        // Convert to unsigned types
+        BYTE nistSP800_108KdfOnKeyVersion_byte = static_cast<BYTE>(nistSP800_108KdfOnKeyVersion);
+        BYTE requestedKeyVersion_byte = static_cast<BYTE>(keyVersion[0]);
+        // if requested key version meets setting value, use NIST SP800-108 KDF
+        if (NistSP800_108KDF::useNistSP800_108KDF(nistSP800_108KdfOnKeyVersion_byte, requestedKeyVersion_byte) == true){
+
+            PR_fprintf(PR_STDOUT,"ComputeEncSessionKey NistSP800_108KDF code: Using NIST SP800-108 KDF.\n");
+
+            // react to "UseCUIDAsKDD" setting value
+            jbyte* context_jbyte = NULL;
+            jsize context_len_jsize = 0;
+            if (nistSP800_108KdfUseCuidAsKdd == JNI_TRUE){
+                context_jbyte = cuidValue;
+                context_len_jsize = cuidValue_len;
+            }else{
+                context_jbyte = kddValue;
+                context_len_jsize = kddValue_len;
+            }
+
+            // Converting this way is safe since jbyte is guaranteed to be 8 bits
+            // Of course, this assumes that "char" is 8 bits (not guaranteed, but typical),
+            //            but it looks like this assumption is also made in GetDiversificationData
+            const BYTE* const context = reinterpret_cast<const BYTE*>(context_jbyte);
+
+            // Convert jsize to size_t
+            const size_t context_len = static_cast<size_t>(context_len_jsize);
+            if (context_len > 0x000000FF){  // sanity check (CUID should never be larger than 255 bytes)
+                PR_fprintf(PR_STDERR, "ComputeEncSessionKey NistSP800_108KDF code: Error; context_len larger than 255 bytes.\n");
+                goto done;
+            }
+
+            // call NIST SP800-108 KDF routine
+            try{
+                NistSP800_108KDF::ComputeCardKeys(masterKey, context, context_len, &encKey, &macKey, &kekKey);
+            }catch(std::runtime_error& ex){
+                PR_fprintf(PR_STDERR, "ComputeEncSessionKey NistSP800_108KDF code: Exception invoking NistSP800_108KDF::ComputeCardKeys: ");
+                PR_fprintf(PR_STDERR, "%s\n", ex.what() == NULL ? "null" : ex.what());
+                goto done;
+            }catch(...){
+                PR_fprintf(PR_STDERR, "ComputeEncSessionKey NistSP800_108KDF code: Unknown exception invoking NistSP800_108KDF::ComputeCardKeys.\n");
+                goto done;
+            }
+
+        // if not a key version where we use the NIST SP800-108 KDF, use the original KDF
+        }else{
+
+            PR_fprintf(PR_STDOUT,"ComputeEncSessionKey NistSP800_108KDF code: Using original KDF.\n");
+
+            // AC: KDF SPEC CHANGE: Moved this call down from the original location.
+            //                      (We don't always need to call it anymore; it depends on the KDF we're going to use.)
+            //
+            // Note the change from "cuidValue" to "kddValue".
+            //   This change is necessary due to the semantics change in the parameters passed between TPS and TKS.
+            GetDiversificationData(kddValue,encData,enc);
+
+            // AC: Derives the enc key for the token.
+            encKey =ComputeCardKeyOnToken(masterKey,encData);
+
+        } // endif use original KDF
+        // ---------------------------------
+
+
         if(encKey == NULL) {
             goto done;
         }
+
+        // AC: This computes the GP session key using the card-specific ENC key we previously derived.
         symkey = DeriveKey(encKey, Buffer((BYTE*)hc, hc_len), Buffer((BYTE*)cc, cc_len));
     }
 
@@ -1127,9 +1348,19 @@ done:
         encSymKey = NULL;
     }
    
-    if( encKey) {
-       PK11_FreeSymKey( encKey);
-       encKey = NULL;
+    // AC: KDF SPEC CHANGE:  For the NIST SP800-108 KDF, we build all 3 keys despite only using one of them (Enc) in this function.
+    //                       We do this because our NIST SP800-108 KDF outputs the data for all three keys simultaneously.
+    if( macKey ) {
+        PK11_FreeSymKey(macKey);
+        macKey = NULL;
+    }
+    if ( encKey) {
+        PK11_FreeSymKey( encKey);
+        encKey = NULL;
+    }
+    if ( kekKey ) {
+        PK11_FreeSymKey(kekKey);
+        kekKey = NULL;
     }
 
     if( keySetStringChars ) {
@@ -1156,11 +1387,22 @@ done:
         (env)->ReleaseByteArrayElements(CUID, cuidValue, JNI_ABORT);
     }
 
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    if ( kddValue != NULL){
+        env->ReleaseByteArrayElements(KDD, kddValue, JNI_ABORT);
+        kddValue = NULL;
+    }
+
     if( enc_key != NULL) {
         (env)->ReleaseByteArrayElements(encKeyArray, enc_key, JNI_ABORT);
     }
 
-    return handleBA;
+    // AC: BUGFIX: Don't return a java array with uninitialized or zero'd data.
+    if (wrapStatus != SECFailure ){
+        return handleBA;
+    }else{
+        return NULL;
+    }
 }
 
 #ifdef __cplusplus
@@ -1172,14 +1414,16 @@ extern "C"
  * Method:    ComputeKekKey
  * Signature: ([B[B[B[B)[B
  */
+// AC: KDF SPEC CHANGE: function signature change - added jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, and jbyteArray KDD
     JNIEXPORT jobject JNICALL Java_com_netscape_symkey_SessionKey_ComputeKekKey
-        (JNIEnv *, jclass, jstring, jstring, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jstring, jstring);
+        (JNIEnv *, jclass, jstring, jstring, jbyteArray, jbyteArray, jbyteArray, jbyte, jboolean, jbyteArray, jbyteArray, jbyteArray, jstring, jstring);
 #ifdef __cplusplus
 }
 #endif
 #define KEYLENGTH 16
 
-extern "C" JNIEXPORT jobject JNICALL Java_com_netscape_symkey_SessionKey_ComputeKekKey(JNIEnv * env, jclass this2, jstring tokenName, jstring keyName, jbyteArray card_challenge, jbyteArray host_challenge, jbyteArray keyInfo, jbyteArray CUID, jbyteArray kekKeyArray, jstring useSoftToken_s, jstring keySet)
+// AC: KDF SPEC CHANGE: function signature change - added jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, and jbyteArray KDD
+extern "C" JNIEXPORT jobject JNICALL Java_com_netscape_symkey_SessionKey_ComputeKekKey(JNIEnv * env, jclass this2, jstring tokenName, jstring keyName, jbyteArray card_challenge, jbyteArray host_challenge, jbyteArray keyInfo, jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, jbyteArray CUID, jbyteArray KDD, jbyteArray kekKeyArray, jstring useSoftToken_s, jstring keySet)
 {
     /* hardcoded permanent kek key */
     jbyte *kek_key = NULL;
@@ -1210,13 +1454,25 @@ extern "C" JNIEXPORT jobject JNICALL Java_com_netscape_symkey_SessionKey_Compute
     jbyte *hc = NULL;
     jbyte *    keyVersion = NULL;
     int keyVersion_len = 0;
-    jbyte *    cuidValue = NULL;
+
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    //                       Also added "len" variable for CUID (for sanity check).
+    jbyte* cuidValue = NULL;
+    jsize cuidValue_len = -1;
+    jbyte* kddValue = NULL;
+    jsize kddValue_len = -1;
 
     char *keyNameChars=NULL;
     char *tokenNameChars = NULL;
     PK11SlotInfo *slot = NULL;
 
-    PK11SymKey *kekKey = NULL;
+    // AC: KDF SPEC CHANGE:  For the NIST SP800-108 KDF, we build all 3 keys despite only using one of them (KEK) in this function.
+    //                       We do this because our NIST SP800-108 KDF outputs the data for all three keys simultaneously.
+    // KDF output keys
+    PK11SymKey* macKey = NULL;
+    PK11SymKey* encKey = NULL;
+    PK11SymKey* kekKey = NULL;
+
     PK11SymKey *masterKey = NULL;
 
     BYTE kekData[KEYLENGTH];
@@ -1249,13 +1505,30 @@ extern "C" JNIEXPORT jobject JNICALL Java_com_netscape_symkey_SessionKey_Compute
         goto done;
     }
 
-    if( CUID != NULL) {
-        cuidValue = (jbyte*)(env)->GetByteArrayElements( CUID, NULL);
-    }
 
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    //                       Also added "len" variable for CUID (for sanity check).
+    if ( CUID != NULL ) {
+        cuidValue =  (jbyte*)(env)->GetByteArrayElements( CUID, NULL);
+        cuidValue_len = env->GetArrayLength(CUID);
+    }
     if( cuidValue == NULL) {
         goto done;
     }
+    if ( cuidValue_len <= 0){  // check that CUID is at least 1 byte in length
+        goto done;
+    }
+    if ( KDD != NULL ){
+        kddValue = env->GetByteArrayElements(KDD, NULL);
+        kddValue_len = env->GetArrayLength(KDD);
+    }
+    if ( kddValue == NULL ){
+        goto done;
+    }
+    if ( kddValue_len != static_cast<jsize>(NistSP800_108KDF::KDD_SIZE_BYTES) ){   // check that KDD is expected size
+        goto done;
+    }
+
 
     /* copy card and host challenge into input buffer */
     for (i = 0; i < 8; i++)
@@ -1267,7 +1540,8 @@ extern "C" JNIEXPORT jobject JNICALL Java_com_netscape_symkey_SessionKey_Compute
         input[8+i] = hc[i];
     }
 
-    GetDiversificationData(cuidValue,kekData,kek);//keytype is kek
+    // AC: KDF SPEC CHANGE: Moved this call down. (We don't necessarily need it anymore depending on the KDF we're going to use.)
+    //GetDiversificationData(cuidValue,kekData,kek);//keytype is kek
 
     if (tokenName)
     {
@@ -1301,7 +1575,71 @@ extern "C" JNIEXPORT jobject JNICALL Java_com_netscape_symkey_SessionKey_Compute
             goto done;
         }
 
-        kekKey =ComputeCardKeyOnToken(masterKey,kekData);
+
+        // ---------------------------------
+        // AC KDF SPEC CHANGE: Determine which KDF to use.
+        //
+        // Convert to unsigned types
+        BYTE nistSP800_108KdfOnKeyVersion_byte = static_cast<BYTE>(nistSP800_108KdfOnKeyVersion);
+        BYTE requestedKeyVersion_byte = static_cast<BYTE>(keyVersion[0]);
+        // if requested key version meets setting value, use NIST SP800-108 KDF
+        if (NistSP800_108KDF::useNistSP800_108KDF(nistSP800_108KdfOnKeyVersion_byte, requestedKeyVersion_byte) == true){
+
+            PR_fprintf(PR_STDOUT,"ComputeKekKey NistSP800_108KDF code: Using NIST SP800-108 KDF.\n");
+
+            // react to "UseCUIDAsKDD" setting value
+            jbyte* context_jbyte = NULL;
+            jsize context_len_jsize = 0;
+            if (nistSP800_108KdfUseCuidAsKdd == JNI_TRUE){
+                context_jbyte = cuidValue;
+                context_len_jsize = cuidValue_len;
+            }else{
+                context_jbyte = kddValue;
+                context_len_jsize = kddValue_len;
+            }
+
+            // Converting this way is safe since jbyte is guaranteed to be 8 bits
+            // Of course, this assumes that "char" is 8 bits (not guaranteed, but typical),
+            //            but it looks like this assumption is also made in GetDiversificationData
+            const BYTE* const context = reinterpret_cast<const BYTE*>(context_jbyte);
+
+            // Convert jsize to size_t
+            const size_t context_len = static_cast<size_t>(context_len_jsize);
+            if (context_len > 0x000000FF){  // sanity check (CUID should never be larger than 255 bytes)
+                PR_fprintf(PR_STDERR, "ComputeKekKey NistSP800_108KDF code: Error; context_len larger than 255 bytes.\n");
+                goto done;
+            }
+
+            // call NIST SP800-108 KDF routine
+            try{
+                NistSP800_108KDF::ComputeCardKeys(masterKey, context, context_len, &encKey, &macKey, &kekKey);
+            }catch(std::runtime_error& ex){
+                PR_fprintf(PR_STDERR, "ComputeKekKey NistSP800_108KDF code: Exception invoking NistSP800_108KDF::ComputeCardKeys: ");
+                PR_fprintf(PR_STDERR, "%s\n", ex.what() == NULL ? "null" : ex.what());
+                goto done;
+            }catch(...){
+                PR_fprintf(PR_STDERR, "ComputeKekKey NistSP800_108KDF code: Unknown exception invoking NistSP800_108KDF::ComputeCardKeys.\n");
+                goto done;
+            }
+
+        // if not a key version where we use the NIST SP800-108 KDF, use the original KDF
+        }else{
+
+            PR_fprintf(PR_STDOUT,"ComputeKekKey NistSP800_108KDF code: Using original KDF.\n");
+
+            // AC: KDF SPEC CHANGE: Moved this call down from the original location.
+            //                      (We don't always need to call it anymore; it depends on the KDF we're going to use.)
+            //
+            // Note the change from "cuidValue" to "kddValue".
+            //   This change is necessary due to the semantics change in the parameters passed between TPS and TKS.
+            GetDiversificationData(kddValue,kekData,kek);//keytype is kek
+
+            // AC: Derives the mac key for the token.
+            kekKey =ComputeCardKeyOnToken(masterKey,kekData);
+
+        } // endif use original KDF
+        // ---------------------------------
+
 
     }
 
@@ -1323,6 +1661,16 @@ done:
         masterKey = NULL;
     }
 
+    // AC: KDF SPEC CHANGE:  For the NIST SP800-108 KDF, we build all 3 keys despite only using one of them (Kek) in this function.
+    //                       We do this because our NIST SP800-108 KDF outputs the data for all three keys simultaneously.
+    if( macKey ) {
+        PK11_FreeSymKey(macKey);
+        macKey = NULL;
+    }
+    if ( encKey ) {
+        PK11_FreeSymKey(encKey);
+        encKey = NULL;
+    }
     if(kekKey) {
         PK11_FreeSymKey( kekKey);
         kekKey = NULL;
@@ -1349,6 +1697,12 @@ done:
         (env)->ReleaseByteArrayElements(CUID, cuidValue, JNI_ABORT);
     }
 
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    if ( kddValue != NULL){
+        env->ReleaseByteArrayElements(KDD, kddValue, JNI_ABORT);
+        kddValue = NULL;
+    }
+
     return keyObj;
 }
 
@@ -1498,13 +1852,15 @@ extern "C"
  * Method:    ComputeCryptogram
  * Signature: ([B[B[B[B)[B
  */
+// AC: KDF SPEC CHANGE: function signature change - added jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, and jbyteArray KDD
     JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_ComputeCryptogram
-        (JNIEnv *, jclass, jstring, jstring, jbyteArray, jbyteArray, jbyteArray, jbyteArray, int, jbyteArray, jstring, jstring);
+        (JNIEnv *, jclass, jstring, jstring, jbyteArray, jbyteArray, jbyteArray, jbyte, jboolean, jbyteArray, jbyteArray, int, jbyteArray, jstring, jstring);
 #ifdef __cplusplus
 }
 #endif
 #define KEYLENGTH 16
-extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_ComputeCryptogram(JNIEnv * env, jclass this2, jstring tokenName, jstring keyName, jbyteArray card_challenge, jbyteArray host_challenge, jbyteArray keyInfo, jbyteArray CUID, int type, jbyteArray authKeyArray, jstring useSoftToken_s, jstring keySet)
+// AC: KDF SPEC CHANGE: function signature change - added jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, and jbyteArray KDD
+extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_ComputeCryptogram(JNIEnv * env, jclass this2, jstring tokenName, jstring keyName, jbyteArray card_challenge, jbyteArray host_challenge, jbyteArray keyInfo, jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, jbyteArray CUID, jbyteArray KDD, int type, jbyteArray authKeyArray, jstring useSoftToken_s, jstring keySet)
 {
 /* hardcore permanent mac key */
     jbyte *auth_key = NULL;
@@ -1542,7 +1898,13 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
     int hc_len = 0;
     jbyte *    keyVersion = NULL;
     int keyVersion_len = 0;
-    jbyte *    cuidValue = NULL;
+
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    //                       Also added "len" variable for CUID (for sanity check).
+    jbyte* cuidValue = NULL;
+    jsize cuidValue_len = -1;
+    jbyte* kddValue = NULL;
+    jsize kddValue_len = -1;
 
     char *tokenNameChars = NULL;
     char *keyNameChars=NULL;
@@ -1551,13 +1913,22 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
     jbyte * session_key = NULL;
     PK11SymKey *symkey     = NULL;
     PK11SymKey *masterKey  = NULL;
-    PK11SymKey *authKey    = NULL;
     PK11SymKey *authSymKey = NULL;
 
+    // AC: KDF SPEC CHANGE:  For the NIST SP800-108 KDF, we build all 3 keys despite only using one of them (Enc/Auth) in this function.
+    //                       We do this because our NIST SP800-108 KDF outputs the data for all three keys simultaneously.
+    // KDF output keys
+    PK11SymKey* macKey = NULL;
+    PK11SymKey* authKey = NULL;
+    PK11SymKey* kekKey = NULL;
+
     BYTE authData[KEYLENGTH];
     char keyname[KEYNAMELENGTH];
     Buffer input_x = Buffer(KEYLENGTH);
 
+    // AC: BUGFIX: Don't return a java array with uninitialized or zero'd data.
+    bool error_computing_result = true;
+
     if( card_challenge != NULL ) {
         cc = (jbyte*)(env)->GetByteArrayElements( card_challenge, NULL);
         cc_len =  (env)->GetArrayLength(card_challenge);
@@ -1587,13 +1958,30 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
         goto done;
     }
 
-    if( CUID != NULL) {
-        cuidValue = (jbyte*)(env)->GetByteArrayElements( CUID, NULL);
-    }
 
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    //                       Also added "len" variable for CUID (for sanity check).
+    if ( CUID != NULL ) {
+        cuidValue =  (jbyte*)(env)->GetByteArrayElements( CUID, NULL);
+        cuidValue_len = env->GetArrayLength(CUID);
+    }
     if( cuidValue == NULL) {
         goto done;
     }
+    if ( cuidValue_len <= 0){  // check that CUID is at least 1 byte in length
+        goto done;
+    }
+    if ( KDD != NULL ){
+        kddValue = env->GetByteArrayElements(KDD, NULL);
+        kddValue_len = env->GetArrayLength(KDD);
+    }
+    if ( kddValue == NULL ){
+        goto done;
+    }
+    if ( kddValue_len != static_cast<jsize>(NistSP800_108KDF::KDD_SIZE_BYTES) ){   // check that KDD is expected size
+        goto done;
+    }
+
 
     if (type == 0)                                // compute host cryptogram
     {
@@ -1621,7 +2009,8 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
 
     input_x.replace(0, (BYTE*) input, KEYLENGTH); 
 
-    GetDiversificationData(cuidValue,authData,enc);
+    // AC: KDF SPEC CHANGE: Moved this call down. (We don't necessarily need it anymore depending on the KDF we're going to use.)
+    //GetDiversificationData(cuidValue,authData,enc);
 
     if (tokenName)
     {
@@ -1660,12 +2049,78 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
             goto done;
         }
 
-        authKey = ComputeCardKeyOnToken(masterKey,authData);
+
+        // ---------------------------------
+        // AC KDF SPEC CHANGE: Determine which KDF to use.
+        //
+        // Convert to unsigned types
+        BYTE nistSP800_108KdfOnKeyVersion_byte = static_cast<BYTE>(nistSP800_108KdfOnKeyVersion);
+        BYTE requestedKeyVersion_byte = static_cast<BYTE>(keyVersion[0]);
+        // if requested key version meets setting value, use NIST SP800-108 KDF
+        if (NistSP800_108KDF::useNistSP800_108KDF(nistSP800_108KdfOnKeyVersion_byte, requestedKeyVersion_byte) == true){
+
+            PR_fprintf(PR_STDOUT,"ComputeCryptogram NistSP800_108KDF code: Using NIST SP800-108 KDF.\n");
+
+            // react to "UseCUIDAsKDD" setting value
+            jbyte* context_jbyte = NULL;
+            jsize context_len_jsize = 0;
+            if (nistSP800_108KdfUseCuidAsKdd == JNI_TRUE){
+                context_jbyte = cuidValue;
+                context_len_jsize = cuidValue_len;
+            }else{
+                context_jbyte = kddValue;
+                context_len_jsize = kddValue_len;
+            }
+
+            // Converting this way is safe since jbyte is guaranteed to be 8 bits
+            // Of course, this assumes that "char" is 8 bits (not guaranteed, but typical),
+            //            but it looks like this assumption is also made in GetDiversificationData
+            const BYTE* const context = reinterpret_cast<const BYTE*>(context_jbyte);
+
+            // Convert jsize to size_t
+            const size_t context_len = static_cast<size_t>(context_len_jsize);
+            if (context_len > 0x000000FF){  // sanity check (CUID should never be larger than 255 bytes)
+                PR_fprintf(PR_STDERR, "ComputeCryptogram NistSP800_108KDF code: Error; context_len larger than 255 bytes.\n");
+                goto done;
+            }
+
+            // call NIST SP800-108 KDF routine
+            try{
+                NistSP800_108KDF::ComputeCardKeys(masterKey, context, context_len, &authKey, &macKey, &kekKey);
+            }catch(std::runtime_error& ex){
+                PR_fprintf(PR_STDERR, "ComputeCryptogram NistSP800_108KDF code: Exception invoking NistSP800_108KDF::ComputeCardKeys: ");
+                PR_fprintf(PR_STDERR, "%s\n", ex.what() == NULL ? "null" : ex.what());
+                goto done;
+            }catch(...){
+                PR_fprintf(PR_STDERR, "ComputeCryptogram NistSP800_108KDF code: Unknown exception invoking NistSP800_108KDF::ComputeCardKeys.\n");
+                goto done;
+            }
+
+        // if not a key version where we use the NIST SP800-108 KDF, use the original KDF
+        }else{
+
+            PR_fprintf(PR_STDOUT,"ComputeCryptogram NistSP800_108KDF code: Using original KDF.\n");
+
+            // AC: KDF SPEC CHANGE: Moved this call down from the original location.
+            //                      (We don't always need to call it anymore; it depends on the KDF we're going to use.)
+            //
+            // Note the change from "cuidValue" to "kddValue".
+            //   This change is necessary due to the semantics change in the parameters passed between TPS and TKS.
+            GetDiversificationData(kddValue,authData,enc);
+
+            // AC: Derives the mac key for the token.
+            authKey = ComputeCardKeyOnToken(masterKey,authData);
+
+        } // endif use original KDF
+        // ---------------------------------
+
+
         if (authKey == NULL)
         {
             goto done;
         }
 
+        // AC: This computes the GP session key using the card-specific ENC key we previously derived.
         symkey = DeriveKey(authKey,
             Buffer((BYTE*)hc, hc_len), Buffer((BYTE*)cc, cc_len));
 
@@ -1678,6 +2133,10 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Comp
     handleBytes = (env)->GetByteArrayElements(handleBA, NULL);
     if( handleBytes ) {
         memcpy(handleBytes, session_key, EIGHT_BYTES);
+
+        // AC: BUGFIX: Don't return a java array with uninitialized or zero'd data.
+        // Set flag that we've successfully copied.
+        error_computing_result = false;
     }
 
 done:
@@ -1697,10 +2156,20 @@ done:
         authSymKey = NULL;
     }
  
+    // AC: KDF SPEC CHANGE:  For the NIST SP800-108 KDF, we build all 3 keys despite only using one of them (Enc/Auth) in this function.
+    //                       We do this because our NIST SP800-108 KDF outputs the data for all three keys simultaneously.
+    if( macKey ) {
+        PK11_FreeSymKey(macKey);
+        macKey = NULL;
+    }
     if( authKey) {
         PK11_FreeSymKey( authKey);
         authKey = NULL;
     }
+    if ( kekKey ) {
+        PK11_FreeSymKey(kekKey);
+        kekKey = NULL;
+    }
 
     if( masterKey) {
         PK11_FreeSymKey( masterKey);
@@ -1732,7 +2201,18 @@ done:
         (env)->ReleaseByteArrayElements(CUID, cuidValue, JNI_ABORT);
     }
 
-    return handleBA;
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    if ( kddValue != NULL){
+        env->ReleaseByteArrayElements(KDD, kddValue, JNI_ABORT);
+        kddValue = NULL;
+    }
+
+    // AC: BUGFIX: Don't return a java array with uninitialized or zero'd data.
+    if (error_computing_result == false){
+        return handleBA;
+    }else{
+        return NULL;
+    }
 }
 
 
diff --git a/base/symkey/src/com/netscape/symkey/SessionKey.java b/base/symkey/src/com/netscape/symkey/SessionKey.java
index 56782aa..d44cc54 100644
--- a/base/symkey/src/com/netscape/symkey/SessionKey.java
+++ b/base/symkey/src/com/netscape/symkey/SessionKey.java
@@ -79,7 +79,10 @@ public class SessionKey {
             byte[] card_challenge,
             byte[] host_challenge,
             byte[] keyInfo,
+            byte nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE
+            boolean nistSP800_108KdfUseCuidAsKdd, // AC: KDF SPEC CHANGE
             byte[] CUID,
+            byte[] KDD,                           // AC: KDF SPEC CHANGE
             byte[] macKeyArray,
             String useSoftToken,
             String keySet,
@@ -90,11 +93,15 @@ public class SessionKey {
             byte[] card_challenge,
             byte[] host_challenge,
             byte[] keyInfo,
+            byte nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE
+            boolean nistSP800_108KdfUseCuidAsKdd, // AC: KDF SPEC CHANGE
             byte[] CUID,
+            byte[] KDD,                           // AC: KDF SPEC CHANGE
             byte[] encKeyArray,
             String useSoftToken,
             String keySet);
 
+    /* AC: KDF SPEC CHANGE; unused method with no JNI implementation
     public static native PK11SymKey ComputeKekSessionKey(String tokenName,
             String keyName,
             byte[] card_challenge,
@@ -104,13 +111,17 @@ public class SessionKey {
             byte[] kekKeyArray,
             String useSoftToken,
             String keySet);
+    */
 
     public static native PK11SymKey ComputeKekKey(String tokenName,
             String keyName,
             byte[] card_challenge,
             byte[] host_challenge,
             byte[] keyInfo,
+            byte nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE
+            boolean nistSP800_108KdfUseCuidAsKdd, // AC: KDF SPEC CHANGE
             byte[] CUID,
+            byte[] KDD,                           // AC: KDF SPEC CHANGE
             byte[] kekKeyArray,
             String useSoftToken, String keySet);
 
@@ -130,7 +141,10 @@ public class SessionKey {
             byte[] card_challenge,
             byte[] host_challenge,
             byte[] keyInfo,
+            byte nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE
+            boolean nistSP800_108KdfUseCuidAsKdd, // AC: KDF SPEC CHANGE
             byte[] CUID,
+            byte[] KDD,                           // AC: KDF SPEC CHANGE
             int type,
             byte[] authKeyArray,
             String useSoftToken, String keySet);
@@ -139,7 +153,10 @@ public class SessionKey {
             String keyName,
             byte[] in,
             byte[] keyInfo,
+            byte nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE
+            boolean nistSP800_108KdfUseCuidAsKdd, // AC: KDF SPEC CHANGE
             byte[] CUID,
+            byte[] KDD,                           // AC: KDF SPEC CHANGE
             byte[] kekKeyArray,
             String useSoftToken, String keySet);
 
@@ -147,8 +164,14 @@ public class SessionKey {
             String newTokenName,
             String oldMasterKeyName,
             String newMasterKeyName,
-            String keyInfo,
+            byte[] oldKeyInfo,          // AC: KDF SPEC CHANGE
+          // AC: BUGFIX for key versions higher than 09:  We need to specialDecode keyInfo parameters before sending them into symkey!  This means the parameters must be jbyteArray's
+          //     -- Changed parameter "jstring keyInfo" to "jbyteArray newKeyInfo"
+            byte[] newKeyInfo,          
+            byte nistSP800_108KdfOnKeyVersion,    // AC: KDF SPEC CHANGE
+            boolean nistSP800_108KdfUseCuidAsKdd, // AC: KDF SPEC CHANGE
             byte[] CUIDValue,
+            byte[] KDD,                           // AC: KDF SPEC CHANGE
             byte[] kekKeyArray,
             String useSoftToken, String keySet);
 
diff --git a/base/symkey/src/com/netscape/symkey/SymKey.cpp b/base/symkey/src/com/netscape/symkey/SymKey.cpp
index 7581566..6e206db 100644
--- a/base/symkey/src/com/netscape/symkey/SymKey.cpp
+++ b/base/symkey/src/com/netscape/symkey/SymKey.cpp
@@ -69,6 +69,9 @@ extern "C"
 #include "Buffer.h"
 #include "SymKey.h"
 
+// AC: KDF SPEC CHANGE: Include headers for NIST SP800-108 KDF functions.
+#include "NistSP800_108KDF.h"
+
 typedef unsigned char BYTE;
 
 typedef struct
@@ -152,15 +155,17 @@ PK11SymKey * ReturnSymKey( PK11SlotInfo *slot, char *keyname)
     firstSymKey = PK11_ListFixedKeysInSlot( slot , NULL, ( void *) &pwdata );
     /* scan through the symmetric key list for a key matching our nickname */
     sk = firstSymKey;
-    while( sk != NULL )
+    // AC: Stop iteration if we've found the key
+    while(( sk != NULL ) && (foundSymKey == NULL))
     {
         /* get the nickname of this symkey */
         name = PK11_GetSymKeyNickname( sk );
 
         /* if the name matches, make a 'copy' of it */
-        if ( name != NULL && !strcmp( keyname, name ))
+        // AC BUGFIX: Don't leak key name string memory if name isn't equal to keyname
+        if ( name != NULL )
         {
-            if (foundSymKey == NULL)
+            if ((foundSymKey == NULL) && (strcmp( keyname, name ) == 0))
             {
                 foundSymKey = PK11_ReferenceSymKey(sk);
             }
@@ -659,6 +664,8 @@ PRStatus EncryptData(const Buffer &kek_key,PK11SymKey *cardKey, Buffer &input, B
 #ifdef DES2_WORKAROUND
     unsigned char masterKeyData[DES3_LENGTH];
 #else
+// AC: Prevent broken code from compiling.
+#error "This code will not work unless DES2_WORKAROUND is defined!!!  (memcpy below writes beyond array bounds)"
     unsigned char masterKeyData[KEYLENGTH];
 #endif
     unsigned char result[EIGHT_BYTES];
@@ -987,20 +994,22 @@ void GetDiversificationData(jbyte *cuidValue,BYTE *KDC,keyType keytype)
 
 }
 
-static int getMasterKeyVersion(char *newMasterKeyNameChars)
-{
-    if( newMasterKeyNameChars == NULL || 
-        strlen( newMasterKeyNameChars) < 3) {
-        return 0;
-    }
-
-    char masterKeyVersionNumber[3];
-    masterKeyVersionNumber[0]=newMasterKeyNameChars[1];
-    masterKeyVersionNumber[1]=newMasterKeyNameChars[2];
-    masterKeyVersionNumber[2]=0;
-    int newMasterKeyVesion = atoi(masterKeyVersionNumber);
-    return newMasterKeyVesion;
-}
+// AC: BUGFIX for key versions higher than 09:  We need to specialDecode keyInfo parameters before sending them into symkey!
+// (atoi doesn't do the same thing as specialDecode does; since we're decoding on the Java side, this function is unnecessary)
+//static int getMasterKeyVersion(char *newMasterKeyNameChars)
+//{
+//    if( newMasterKeyNameChars == NULL ||
+//        strlen( newMasterKeyNameChars) < 3) {
+//        return 0;
+//    }
+//
+//    char masterKeyVersionNumber[3];
+//    masterKeyVersionNumber[0]=newMasterKeyNameChars[1];
+//    masterKeyVersionNumber[1]=newMasterKeyNameChars[2];
+//    masterKeyVersionNumber[2]=0;
+//    int newMasterKeyVesion = atoi(masterKeyVersionNumber);
+//    return newMasterKeyVesion;
+//}
 
 char *GetSharedSecretKeyName(char *newKeyName) {
     if ( newKeyName && strlen( newKeyName ) > 0 ) {
@@ -1030,10 +1039,16 @@ void getFullName(char * fullMasterKeyName, char * masterKeyNameChars )
  * Method:    DiversifyKey
  * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)[B
  */
+// AC: KDF SPEC CHANGE: function signature change - added jstring oldKeyInfo, jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, and jbyteArray KDD
+// AC: BUGFIX for key versions higher than 09:  We need to specialDecode keyInfo parameters before sending them into symkey!  This means the parameters must be jbyteArray's
+//     -- Changed parameter "jstring keyInfo" to "jbyteArray newKeyInfo"
 extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_DiversifyKey
-(JNIEnv *, jclass, jstring, jstring, jstring, jstring, jstring, jbyteArray, jbyteArray, jstring, jstring);
+(JNIEnv *, jclass, jstring, jstring, jstring, jstring, jbyteArray, jbyteArray, jbyte, jboolean, jbyteArray, jbyteArray, jbyteArray, jstring, jstring);
 
-extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_DiversifyKey( JNIEnv * env, jclass this2, jstring tokenName,jstring newTokenName, jstring oldMasterKeyName, jstring newMasterKeyName, jstring keyInfo, jbyteArray CUIDValue, jbyteArray kekKeyArray, jstring useSoftToken_s, jstring keySet)
+// AC: KDF SPEC CHANGE: function signature change - added jstring oldKeyInfo, jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, and jbyteArray KDD
+// AC: BUGFIX for key versions higher than 09:  We need to specialDecode keyInfo parameters before sending them into symkey!  This means the parameters must be jbyteArray's
+//     -- Changed parameter "jstring keyInfo" to "jbyteArray newKeyInfo"
+extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_DiversifyKey( JNIEnv * env, jclass this2, jstring tokenName,jstring newTokenName, jstring oldMasterKeyName, jstring newMasterKeyName, jbyteArray oldKeyInfo, jbyteArray newKeyInfo, jbyte nistSP800_108KdfOnKeyVersion, jboolean nistSP800_108KdfUseCuidAsKdd, jbyteArray CUIDValue, jbyteArray KDD, jbyteArray kekKeyArray, jstring useSoftToken_s, jstring keySet)
 {
     PK11SymKey *encKey = NULL;
     PK11SymKey *macKey = NULL;
@@ -1047,7 +1062,13 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Dive
     char fullMasterKeyName[KEYNAMELENGTH];
     char fullNewMasterKeyName[KEYNAMELENGTH];
     PRBool specified_key_is_present = PR_TRUE;
-    PK11SymKey *old_kek_sym_key = NULL;
+
+    // AC: KDF SPEC CHANGE:  For the NIST SP800-108 KDF being used for old key version, we build all 3 old keys despite only using one of them (Kek) in this function.
+    //                       We do this because our NIST SP800-108 KDF outputs the data for all three keys simultaneously.
+    // KDF output keys
+    PK11SymKey* old_mac_sym_key = NULL;
+    PK11SymKey* old_enc_sym_key = NULL;
+    PK11SymKey* old_kek_sym_key = NULL;
 
     char *keySetStringChars =  NULL;
     if ( keySet != NULL ) {
@@ -1062,7 +1083,19 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Dive
 
     jbyteArray handleBA=NULL;
     jbyte *handleBytes=NULL;
-    int newMasterKeyVesion = 1;
+
+
+    // AC: BUGFIX for key versions higher than 09
+    // No longer need this variable (it's misspelled anyway) and it's the wrong type.
+    // int newMasterKeyVesion = 1;
+
+    // AC: BUGFIX for key versions higher than 09
+    // New variables used for JNI retrieval.
+    jbyte* oldKeyInfo_jbyteptr = NULL;
+    jbyte* newKeyInfo_jbyteptr = NULL;
+    jsize oldKeyInfo_jbyteptr_len = -1;
+    jsize newKeyInfo_jbyteptr_len = -1;
+
 
     /* find slot */
     char *tokenNameChars = NULL;
@@ -1075,7 +1108,19 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Dive
     char * newTokenNameChars = NULL;
     char *keyInfoChars = NULL;
 
-    jbyte * cuidValue =  NULL;
+    // AC: KDF SPEC CHANGE:  Need to retrieve old key info from JNI.
+    char* oldKeyInfoChars = NULL;
+
+    // AC: KDF SPEC CHANGE:  Convert new setting value to BYTE (unsigned).
+    BYTE nistSP800_108KdfOnKeyVersion_byte = static_cast<BYTE>(nistSP800_108KdfOnKeyVersion);
+
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    //                       Also added "len" variable for CUID (for sanity check).
+    jbyte* cuidValue = NULL;
+    jsize cuidValue_len = -1;
+    jbyte* kddValue = NULL;
+    jsize kddValue_len = -1;
+
     jbyte * old_kek_key = NULL;
 
     PK11SymKey * masterKey = NULL;
@@ -1085,13 +1130,37 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Dive
     BYTE KDCmac[KEYLENGTH];
     BYTE KDCkek[KEYLENGTH];
 
-    if( CUIDValue != NULL) {
-        cuidValue = (jbyte*)(env)->GetByteArrayElements( CUIDValue, NULL);
-    }
+    // AC: BUGFIX for key versions higher than 09:  New code to retrieve oldKeyInfo and newKeyInfo byte arrays from JNI.
+    BYTE oldKeyVersion;
+    BYTE newKeyVersion;
+
+    // AC: BUGFIX: Don't return a java array with uninitialized or zero'd data.
+    bool error_computing_result = true;
 
+
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    //                       Also added "len" variable for CUID (for sanity check).
+    if ( CUIDValue != NULL ) {
+        cuidValue =  (jbyte*)(env)->GetByteArrayElements( CUIDValue, NULL);
+        cuidValue_len = env->GetArrayLength(CUIDValue);
+    }
     if( cuidValue == NULL) {
        goto done;
     }
+    if ( cuidValue_len <= 0){  // check that CUID is at least 1 byte in length
+        goto done;
+    }
+    if ( KDD != NULL ){
+        kddValue = env->GetByteArrayElements(KDD, NULL);
+        kddValue_len = env->GetArrayLength(KDD);
+    }
+    if ( kddValue == NULL ){
+        goto done;
+    }
+    if ( kddValue_len != static_cast<jsize>(NistSP800_108KDF::KDD_SIZE_BYTES) ){   // check that KDD is expected size
+        goto done;
+    }
+
 
     if( kekKeyArray != NULL) {
         old_kek_key = (jbyte*)(env)->GetByteArrayElements(kekKeyArray, NULL);
@@ -1103,9 +1172,12 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Dive
 
     PR_fprintf(PR_STDOUT,"In SessionKey.DiversifyKey! \n");
 
-    GetDiversificationData(cuidValue,KDCenc,enc);
-    GetDiversificationData(cuidValue,KDCmac,mac);
-    GetDiversificationData(cuidValue,KDCkek,kek);
+    // AC: KDF SPEC CHANGE:
+    // Changed from "cuidValue" to "kddValue".
+    //   This change is necessary due to the semantics change in the parameters passed between TPS and TKS.
+    GetDiversificationData(kddValue,KDCenc,enc);
+    GetDiversificationData(kddValue,KDCmac,mac);
+    GetDiversificationData(kddValue,KDCkek,kek);
 
     if(tokenName)
     {
@@ -1142,20 +1214,56 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Dive
         (env)->ReleaseStringUTFChars(newMasterKeyName, (const char *)newMasterKeyNameChars);
     }
 
-    /* packing return */
-    if( keyInfo != NULL) {
-         keyInfoChars = (char *)(env)->GetStringUTFChars(keyInfo, NULL);
-    }
 
-    newMasterKeyVesion = getMasterKeyVersion(keyInfoChars);
 
-    if(keyInfoChars)
-    {
-        (env)->ReleaseStringUTFChars(keyInfo, (const char *)keyInfoChars);
+    // AC: BUGFIX for key versions higher than 09:  Since "jstring keyInfo" is now passed in as "jbyteArray newKeyInfo", we no longer need this code.
+    //
+    ///* packing return */
+    //if( keyInfo != NULL) {
+    //     keyInfoChars = (char *)(env)->GetStringUTFChars(keyInfo, NULL);
+    //}
+    //
+    //newMasterKeyVesion = getMasterKeyVersion(keyInfoChars);
+    //
+    //if(keyInfoChars)
+    //{
+    //    (env)->ReleaseStringUTFChars(keyInfo, (const char *)keyInfoChars);
+    //}
+    //
+    ///* NEW MASTER KEY VERSION */
+    //newMasterKeyBuffer = Buffer((unsigned int) 1,  (BYTE)newMasterKeyVesion);
+
+
+
+    // AC: BUGFIX for key versions higher than 09:  New code to retrieve oldKeyInfo and newKeyInfo byte arrays from JNI.
+    if (oldKeyInfo != NULL){
+        oldKeyInfo_jbyteptr =  env->GetByteArrayElements(oldKeyInfo, NULL);
+        oldKeyInfo_jbyteptr_len = env->GetArrayLength(oldKeyInfo);
+    }
+    if(oldKeyInfo_jbyteptr == NULL){
+        goto done;
+    }
+    if (oldKeyInfo_jbyteptr_len != 2){
+        goto done;
     }
+    if (newKeyInfo != NULL){
+        newKeyInfo_jbyteptr =  env->GetByteArrayElements(newKeyInfo, NULL);
+        newKeyInfo_jbyteptr_len = env->GetArrayLength(newKeyInfo);
+    }
+    if(newKeyInfo_jbyteptr == NULL){
+        goto done;
+    }
+    if (newKeyInfo_jbyteptr_len != 2){
+        goto done;
+    }
+    // now get the key versions from the byte arrays we got from JNI
+    oldKeyVersion = oldKeyInfo_jbyteptr[0];
+    newKeyVersion = newKeyInfo_jbyteptr[0];
+    // for compatibility with old code: wrap newKeyVersion inside Buffer object
+    newMasterKeyBuffer = Buffer((unsigned int) 1,  newKeyVersion);
+
+
 
-    /* NEW MASTER KEY VERSION */
-    newMasterKeyBuffer = Buffer((unsigned int) 1,  (BYTE)newMasterKeyVesion);
     if(oldMasterKeyName)
     {
         oldMasterKeyNameChars = (char *)(env)->GetStringUTFChars(oldMasterKeyName, NULL);
@@ -1169,24 +1277,108 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Dive
     if(strcmp( oldMasterKeyNameChars, "#01#01") == 0 || strcmp( oldMasterKeyNameChars, "#FF#01") == 0)
     {
         old_kek_key_buff    =   Buffer((BYTE*)old_kek_key, KEYLENGTH);
-    }else if(strcmp( oldMasterKeyNameChars, "#00#00") == 0)
-    {
-        /* print Debug message - do not create real keysetdata */
-        old_kek_key_buff    =       Buffer((BYTE*)"#00#00", 6);
-        output              =       Buffer((BYTE*)old_kek_key, KEYLENGTH);
+
+
+    // AC: BUGFIX: Remove garbage code.
+    //             I believe that this code is a no-op as long as the system is working correctly
+    //             (with the developer keyset data populated in the config file & copied to HSM).
+    //
+    //             Notes:
+    //               "old_kek_key_buff" appears to only be used if unable to read/load the developer keys into HSM.
+    //               "old_kek_key_buff" is populated with incorrect data (not appropriate key-length)
+    //               "output" is overwritten when "CreateKeySetDataWithSymKeys" is called
+    //
+    //             As a result, only when there is some failure (i.e. we execute a "goto" and skip assignment
+    //               to "output") do we return a keyset data that is 16 bytes in length (the default KEK).
+    //               This is unlikely to work and even if it does, is a horrible idea as the caller has no way
+    //               of knowing that we've now essentially inserted a "backdoor" on the token.  So, instead of
+    //               this, we treat #00#00 just like any other "normal" case.
+    //
+    //}else if(strcmp( oldMasterKeyNameChars, "#00#00") == 0)
+    //{
+    //    /* print Debug message - do not create real keysetdata */
+    //    old_kek_key_buff    =       Buffer((BYTE*)"#00#00", 6);
+    //    output              =       Buffer((BYTE*)old_kek_key, KEYLENGTH);
+
+
     }
     else
     {
         oldMasterKey =     ReturnSymKey(slot,fullMasterKeyName);
-        old_kek_sym_key = ComputeCardKeyOnToken(oldMasterKey,KDCkek);
-        if (oldMasterKey) {
-            PK11_FreeSymKey( oldMasterKey );
-            oldMasterKey = NULL;
+
+
+        // AC: BUGFIX: Check for nonexistent master key instead of (potentially) crashing.
+        if (oldMasterKey == NULL){
+            goto done;
         }
+
+
+        // ---------------------------------
+        // AC KDF SPEC CHANGE: Determine which KDF to use.
+        //
+        // if old key version meets setting value, use NIST SP800-108 KDF for deriving old keys
+        if (NistSP800_108KDF::useNistSP800_108KDF(nistSP800_108KdfOnKeyVersion_byte, oldKeyVersion) == true){
+
+            PR_fprintf(PR_STDOUT,"DiversifyKey old key NistSP800_108KDF code: Using NIST SP800-108 KDF for old keyset.\n");
+
+            // react to "UseCUIDAsKDD" setting value
+            jbyte* context_jbyte = NULL;
+            jsize context_len_jsize = 0;
+            if (nistSP800_108KdfUseCuidAsKdd == JNI_TRUE){
+                context_jbyte = cuidValue;
+                context_len_jsize = cuidValue_len;
+            }else{
+                context_jbyte = kddValue;
+                context_len_jsize = kddValue_len;
+            }
+
+            // Converting this way is safe since jbyte is guaranteed to be 8 bits
+            // Of course, this assumes that "char" is 8 bits (not guaranteed, but typical),
+            //            but it looks like this assumption is also made in GetDiversificationData
+            const BYTE* const context = reinterpret_cast<const BYTE*>(context_jbyte);
+
+            // Convert jsize to size_t
+            const size_t context_len = static_cast<size_t>(context_len_jsize);
+            if (context_len > 0x000000FF){  // sanity check (CUID should never be larger than 255 bytes)
+                PR_fprintf(PR_STDERR, "DiversifyKey old key NistSP800_108KDF code: Error; context_len larger than 255 bytes.\n");
+                goto done;
+            }
+
+            // call NIST SP800-108 KDF routine
+            try{
+                NistSP800_108KDF::ComputeCardKeys(oldMasterKey, context, context_len, &old_enc_sym_key, &old_mac_sym_key, &old_kek_sym_key);
+            }catch(std::runtime_error& ex){
+                PR_fprintf(PR_STDERR, "DiversifyKey old key NistSP800_108KDF code: Exception invoking NistSP800_108KDF::ComputeCardKeys: ");
+                PR_fprintf(PR_STDERR, "%s\n", ex.what() == NULL ? "null" : ex.what());
+                goto done;
+            }catch(...){
+                PR_fprintf(PR_STDERR, "DiversifyKey old key NistSP800_108KDF code: Unknown exception invoking NistSP800_108KDF::ComputeCardKeys.\n");
+                goto done;
+            }
+
+        // if not a key version where we use the NIST SP800-108 KDF, use the original KDF
+        }else{
+
+            PR_fprintf(PR_STDOUT,"DiversifyKey old key NistSP800_108KDF code: Using original KDF for old keyset.\n");
+
+            // AC: Derives the kek key for the token.
+            old_kek_sym_key = ComputeCardKeyOnToken(oldMasterKey,KDCkek);
+
+        } // endif use original KDF
+        // ---------------------------------
+
+
+        // AC KDF SPEC CHANGE: Moved this code down so we don't skip it during "goto done".
+        //if (oldMasterKey) {
+        //    PK11_FreeSymKey( oldMasterKey );
+        //    oldMasterKey = NULL;
+        //}
     }
-    if(oldMasterKeyNameChars) {
-        (env)->ReleaseStringUTFChars(oldMasterKeyName, (const char *)oldMasterKeyNameChars);
-    }
+
+    // AC KDF SPEC CHANGE: Moved this code down so we don't skip it during "goto done".
+    //if(oldMasterKeyNameChars) {
+    //    (env)->ReleaseStringUTFChars(oldMasterKeyName, (const char *)oldMasterKeyNameChars);
+    //}
 
     /* special case #01#01 */
     if (fullNewMasterKeyName != NULL && strcmp(fullNewMasterKeyName, "#01#01") == 0)
@@ -1213,10 +1405,65 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Dive
 
     } else {
         PR_fprintf(PR_STDOUT,"DiversifyKey: Compute card key on token case ! \n");
-        /* compute card key */
-        encKey = ComputeCardKeyOnSoftToken(masterKey, KDCenc);
-        macKey = ComputeCardKeyOnSoftToken(masterKey, KDCmac);
-        kekKey = ComputeCardKeyOnSoftToken(masterKey, KDCkek);
+
+
+        // ---------------------------------
+        // AC KDF SPEC CHANGE: Determine which KDF to use.
+        //
+        // if old key version meets setting value, use NIST SP800-108 KDF for deriving new keys
+        if (NistSP800_108KDF::useNistSP800_108KDF(nistSP800_108KdfOnKeyVersion_byte, newKeyVersion) == true){
+
+            PR_fprintf(PR_STDOUT,"DiversifyKey new key NistSP800_108KDF code: Using NIST SP800-108 KDF for new keyset.\n");
+
+            // react to "UseCUIDAsKDD" setting value
+            jbyte* context_jbyte = NULL;
+            jsize context_len_jsize = 0;
+            if (nistSP800_108KdfUseCuidAsKdd == JNI_TRUE){
+                context_jbyte = cuidValue;
+                context_len_jsize = cuidValue_len;
+            }else{
+                context_jbyte = kddValue;
+                context_len_jsize = kddValue_len;
+            }
+
+            // Converting this way is safe since jbyte is guaranteed to be 8 bits
+            // Of course, this assumes that "char" is 8 bits (not guaranteed, but typical),
+            //            but it looks like this assumption is also made in GetDiversificationData
+            const BYTE* const context = reinterpret_cast<const BYTE*>(context_jbyte);
+
+            // Convert jsize to size_t
+            const size_t context_len = static_cast<size_t>(context_len_jsize);
+            if (context_len > 0x000000FF){  // sanity check (CUID should never be larger than 255 bytes)
+                PR_fprintf(PR_STDERR, "DiversifyKey new key NistSP800_108KDF code: Error; context_len larger than 255 bytes.\n");
+                goto done;
+            }
+
+            // call NIST SP800-108 KDF routine
+            try{
+                NistSP800_108KDF::ComputeCardKeys(masterKey, context, context_len, &encKey, &macKey, &kekKey);
+            }catch(std::runtime_error& ex){
+                PR_fprintf(PR_STDERR, "DiversifyKey new key NistSP800_108KDF code: Exception invoking NistSP800_108KDF::ComputeCardKeys: ");
+                PR_fprintf(PR_STDERR, "%s\n", ex.what() == NULL ? "null" : ex.what());
+                goto done;
+            }catch(...){
+                PR_fprintf(PR_STDERR, "DiversifyKey new key NistSP800_108KDF code: Unknown exception invoking NistSP800_108KDF::ComputeCardKeys.\n");
+                goto done;
+            }
+
+        // if not a key version where we use the NIST SP800-108 KDF, use the original KDF
+        }else{
+
+            PR_fprintf(PR_STDOUT,"DiversifyKey new key NistSP800_108KDF code: Using original KDF for new keyset.\n");
+
+            // AC: Derives the kek key for the token.
+            /* compute card key */
+            encKey = ComputeCardKeyOnSoftToken(masterKey, KDCenc);
+            macKey = ComputeCardKeyOnSoftToken(masterKey, KDCmac);
+            kekKey = ComputeCardKeyOnSoftToken(masterKey, KDCkek);
+
+        } // endif use original KDF
+        // ---------------------------------
+
 
         /* Fixes Bugscape Bug #55855: TKS crashes if specified key
          * is not present -- for each portion of the key, check if
@@ -1257,6 +1504,17 @@ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_netscape_symkey_SessionKey_Dive
     }
 
 done:
+
+    // AC: BUGFIX for key versions higher than 09:  Release oldKeyInfo and newKeyInfo JNI byte arrays.
+    if ( oldKeyInfo_jbyteptr != NULL){
+        env->ReleaseByteArrayElements(oldKeyInfo, oldKeyInfo_jbyteptr, JNI_ABORT);
+        oldKeyInfo_jbyteptr = NULL;
+    }
+    if ( newKeyInfo_jbyteptr != NULL){
+        env->ReleaseByteArrayElements(newKeyInfo, newKeyInfo_jbyteptr, JNI_ABORT);
+        newKeyInfo_jbyteptr = NULL;
+    }
+
     if (masterKey != NULL) {
         PK11_FreeSymKey( masterKey);
         masterKey = NULL;
@@ -1277,6 +1535,32 @@ done:
         kekKey = NULL;
     }
 
+    // AC: KDF SPEC CHANGE:  For the NIST SP800-108 KDF being used for old key version, we build all 3 old keys despite only using one of them (Kek) in this function.
+    //                       We do this because our NIST SP800-108 KDF outputs the data for all three keys simultaneously.
+    // AC: BUGFIX: Note that there was previously no PK11_FreeSymKey(old_kek_sym_key) call.  This most likely resulted in a memory / keyhandle leak.
+    if( old_mac_sym_key ) {
+        PK11_FreeSymKey(old_mac_sym_key);
+        old_mac_sym_key = NULL;
+    }
+    if ( old_enc_sym_key ) {
+        PK11_FreeSymKey(old_enc_sym_key);
+        old_enc_sym_key = NULL;
+    }
+    if ( old_kek_sym_key ) {
+        PK11_FreeSymKey(old_kek_sym_key);
+        old_kek_sym_key = NULL;
+    }
+
+    // AC KDF SPEC CHANGE: Moved this code down so we don't skip it during "goto done".
+    if (oldMasterKey) {
+        PK11_FreeSymKey( oldMasterKey );
+        oldMasterKey = NULL;
+    }
+    if(oldMasterKeyNameChars) {
+        (env)->ReleaseStringUTFChars(oldMasterKeyName, (const char *)oldMasterKeyNameChars);
+        oldMasterKeyNameChars = NULL;
+    }
+
     if( keySetStringChars ) {
         (env)->ReleaseStringUTFChars(keySet, (const char *)keySetStringChars);
         keySetStringChars = NULL;
@@ -1286,10 +1570,21 @@ done:
     {
         if(output.size()>0)
             handleBA = (env)->NewByteArray( output.size());
-        else
-            handleBA = (env)->NewByteArray(1);
-        handleBytes = (env)->GetByteArrayElements(handleBA, NULL);
-        memcpy(handleBytes, (BYTE*)output,output.size());
+
+        // AC: Bugfix: Return NULL if no output is present.
+        //else
+        //    handleBA = (env)->NewByteArray(1);
+
+        // AC: Bugfix: Don't crash if we couldn't allocate array.
+        if (handleBA != NULL){
+            handleBytes = (env)->GetByteArrayElements(handleBA, NULL);
+
+            // AC: BUGFIX: Don't return a java array with uninitialized or zero'd data.
+            if (handleBytes != NULL){
+                memcpy(handleBytes, (BYTE*)output,output.size());
+                error_computing_result = false;
+            }
+        }
 
         if( handleBytes != NULL) {
             (env)->ReleaseByteArrayElements( handleBA, handleBytes, 0);
@@ -1300,6 +1595,12 @@ done:
         (env)->ReleaseByteArrayElements(CUIDValue, cuidValue, JNI_ABORT);
     }
 
+    // AC: KDF SPEC CHANGE:  Need to retrieve KDD as well as CUID from JNI.
+    if ( kddValue != NULL){
+        env->ReleaseByteArrayElements(KDD, kddValue, JNI_ABORT);
+        kddValue = NULL;
+    }
+
     if( kekKeyArray != NULL) {
         (env)->ReleaseByteArrayElements(kekKeyArray, old_kek_key, JNI_ABORT);
     }
@@ -1319,7 +1620,12 @@ done:
         internal = NULL;
     }
 
-    return handleBA;
+    // AC: BUGFIX: Don't return a java array with uninitialized or zero'd data.
+    if (error_computing_result == false){
+        return handleBA;
+    }else{
+        return NULL;
+    }
 }
 
 PK11SymKey *CreateUnWrappedSymKeyOnToken( PK11SlotInfo *slot, PK11SymKey * unWrappingKey, BYTE *keyToBeUnWrapped, int sizeOfKeyToBeUnWrapped, PRBool isPerm)
diff --git a/base/tks/shared/conf/CS.cfg.in b/base/tks/shared/conf/CS.cfg.in
index bd2858d..63bf1ea 100644
--- a/base/tks/shared/conf/CS.cfg.in
+++ b/base/tks/shared/conf/CS.cfg.in
@@ -295,13 +295,15 @@ selftests.container.logger.maxFileSize=2000
 selftests.container.logger.register=false
 selftests.container.logger.rolloverInterval=2592000
 selftests.container.logger.type=transaction
-selftests.container.order.onDemand=TKSKnownSessionKey:critical, SystemCertsVerification:critical
-selftests.container.order.startup=TKSKnownSessionKey:critical, SystemCertsVerification:critical
+selftests.container.order.onDemand=SystemCertsVerification:critical
+selftests.container.order.startup=SystemCertsVerification:critical
 selftests.plugin.TKSKnownSessionKey.CUID=#a0#01#92#03#04#05#06#07#08#c9
 selftests.plugin.TKSKnownSessionKey.TksSubId=tks
 selftests.plugin.TKSKnownSessionKey.cardChallenge=#bd#6d#19#85#6e#54#0f#cd
 selftests.plugin.TKSKnownSessionKey.hostChallenge=#77#57#62#e4#5e#23#66#7d
 selftests.plugin.TKSKnownSessionKey.keyName=#01#01
+selftests.plugin.TKSKnownSessionKey.nistSP800-108KdfOnKeyVersion=FF
+selftests.plugin.TKSKnownSessionKey.nistSP800-108KdfUseCuidAsKdd=false
 selftests.plugin.TKSKnownSessionKey.macKey=#40#41#42#43#44#45#46#47#48#49#4a#4b#4c#4d#4e#4f
 selftests.plugin.TKSKnownSessionKey.sessionKey=#d1#be#b8#26#dc#56#20#25#8c#93#e7#de#f0#ab#4f#5b
 selftests.plugin.TKSKnownSessionKey.token=Internal Key Storage Token
@@ -339,6 +341,8 @@ tks.defKeySet._004=##
 tks.defKeySet.auth_key=#40#41#42#43#44#45#46#47#48#49#4a#4b#4c#4d#4e#4f
 tks.defKeySet.mac_key=#40#41#42#43#44#45#46#47#48#49#4a#4b#4c#4d#4e#4f
 tks.defKeySet.kek_key=#40#41#42#43#44#45#46#47#48#49#4a#4b#4c#4d#4e#4f
+tks.defKeySet.nistSP800-108KdfOnKeyVersion=00
+tks.defKeySet.nistSP800-108KdfUseCuidAsKdd=false
 tks.jForte._000=##
 tks.jForte._001=## SAFLink's jForte default key set:
 tks.jForte._002=##
@@ -347,6 +351,8 @@ tks.jForte._004=##
 tks.jForte.auth_key=#30#31#32#33#34#35#36#37#38#39#3a#3b#3c#3d#3e#3f
 tks.jForte.mac_key=#40#41#42#43#44#45#46#47#48#49#4a#4b#4c#4d#4e#4f
 tks.jForte.kek_key=#50#51#52#53#54#55#56#57#58#59#5a#5b#5c#5d#5e#5f
+tks.jForte.nistSP800-108KdfOnKeyVersion=00
+tks.jForte.nistSP800-108KdfUseCuidAsKdd=false
 multiroles._000=##
 multiroles._001=## multiroles
 multiroles._002=##
-- 
1.8.4.2

