From 29000c02bc9c8f27213130799266e064a501a17c Mon Sep 17 00:00:00 2001
From: Jack Magne <jmagne@localhost.localdomain>
Date: Tue, 22 Sep 2015 10:26:07 -0700
Subject: [PATCH] KRA: key archival/recovery via cli - should honor
 encryption/decryption flags.

Ticket # 1597

Currently, KRA allows sites to opt for doing encryption/decryption instead of wrapping/unwrapping for key archival and recovery.

The new cli code was later added without such support. We should honor the same flags when cli is called to do key archival and recovery.

This feature was due to a specific customer request. Here is what is now supported:

1. When the pki cli tool is used to recover a asymmetric private key, support is there to do so with encrypt / decrypt.
2. The passphrase and generic data facility already uses encrypt / decrypt so nothing here was needed. Calling it out since this will possibly be a customer issue.

3. While under the hood, it made sense to add this functionality to the Symmetric key archival and recovery operations.
4. All tests in DRMTest.java worked successfully when the kra was configured to support this feature and configured to not observe this feature.

What is missing:

We have since added a method to do a server side key generation of a asymmetric key pair in the kra and also archive it there at the same time. In order to do encrypt / decrypt in this case we need to extract the key contents out of a key object that is used to generate this key. It proved problematic to extract said key. This should be ok since the customer only needs to recover an asymmetric key in their test cases. We could look into doing this later if a pressing need arises.
---
 .../netscape/kra/SecurityDataRecoveryService.java  | 107 +++++++++++++++++----
 .../src/com/netscape/kra/SecurityDataService.java  |  61 ++++++++++--
 2 files changed, 138 insertions(+), 30 deletions(-)

diff --git a/base/kra/src/com/netscape/kra/SecurityDataRecoveryService.java b/base/kra/src/com/netscape/kra/SecurityDataRecoveryService.java
index b244967..f12222b 100644
--- a/base/kra/src/com/netscape/kra/SecurityDataRecoveryService.java
+++ b/base/kra/src/com/netscape/kra/SecurityDataRecoveryService.java
@@ -25,6 +25,7 @@ import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.spec.AlgorithmParameterSpec;
+import java.util.Arrays;
 import java.util.Hashtable;
 import java.util.Random;
 
@@ -57,6 +58,7 @@ import org.mozilla.jss.util.Password;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
+import com.netscape.certsrv.base.IConfigStore;
 import com.netscape.certsrv.dbs.keydb.IKeyRecord;
 import com.netscape.certsrv.dbs.keydb.IKeyRepository;
 import com.netscape.certsrv.key.KeyRequestResource;
@@ -91,6 +93,7 @@ public class SecurityDataRecoveryService implements IService {
             "LOGGING_SIGNED_AUDIT_SECURITY_DATA_RECOVERY_REQUEST_PROCESSED_5";
     public static final String ATTR_SERIALNO = "serialNumber";
     public static final String ATTR_KEY_RECORD = "keyRecord";
+    private static boolean allowEncDecrypt_recovery = false;
 
     public SecurityDataRecoveryService(IKeyRecoveryAuthority kra) {
         mKRA = kra;
@@ -124,6 +127,15 @@ public class SecurityDataRecoveryService implements IService {
         byte iv_default[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 };
         byte iv_in[] = null;
 
+        IConfigStore config = null;
+
+        try {
+            config = CMS.getConfigStore();
+            allowEncDecrypt_recovery = config.getBoolean("kra.allowEncDecrypt.recovery", false);
+        } catch (Exception e) {
+            throw new EBaseException(CMS.getUserMessage("CMS_BASE_CERT_ERROR", e.toString()));
+        }
+
         String requestor = request.getExtDataInString(IRequest.ATTR_REQUEST_OWNER);
         String auditSubjectID = requestor;
 
@@ -185,15 +197,24 @@ public class SecurityDataRecoveryService implements IService {
         PrivateKey privateKey = null;
 
         if (dataType.equals(KeyRequestResource.SYMMETRIC_KEY_TYPE)) {
-            symKey = recoverSymKey(keyRecord);
+            if (allowEncDecrypt_recovery == true) {
+                CMS.debug("Recover symmetric key by decrypting as per allowEncDecrypt_recovery: true.");
+                unwrappedSecData = recoverSecurityData(keyRecord);
+            } else {
+                symKey = recoverSymKey(keyRecord);
+            }
 
         } else if (dataType.equals(KeyRequestResource.PASS_PHRASE_TYPE)) {
             unwrappedSecData = recoverSecurityData(keyRecord);
-
         } else if (dataType.equals(KeyRequestResource.ASYMMETRIC_KEY_TYPE)) {
             try {
-                privateKey = mStorageUnit.unwrap(keyRecord.getPrivateKeyData(),
-                        X509Key.parsePublicKey(new DerValue(keyRecord.getPublicKeyData())));
+                if (allowEncDecrypt_recovery == true) {
+                    CMS.debug("Recover asymmetric key by decrypting as per allowEncDecrypt_recovery: true.");
+                    unwrappedSecData = recoverSecurityData(keyRecord);
+                } else {
+                    privateKey = mStorageUnit.unwrap(keyRecord.getPrivateKeyData(),
+                            X509Key.parsePublicKey(new DerValue(keyRecord.getPublicKeyData())));
+                }
 
             } catch (IOException e) {
                 throw new EBaseException("Cannot fetch the private key from the database.", e);
@@ -223,17 +244,27 @@ public class SecurityDataRecoveryService implements IService {
                 passStr = null;
 
                 if (dataType.equals(KeyRequestResource.SYMMETRIC_KEY_TYPE)) {
-                    CMS.debug("SecurityDataRecoveryService: wrap stored symmetric key with transport passphrase");
-                    pbeWrappedData = createEncryptedContentInfo(ct, symKey, null, null,
-                            pass);
-                } else if (dataType.equals(KeyRequestResource.PASS_PHRASE_TYPE)){
+                    CMS.debug("SecurityDataRecoveryService: wrap or encrypt stored symmetric key with transport passphrase");
+                    if (allowEncDecrypt_recovery == true) {
+                        CMS.debug("SecurityDataRecoveryServic: allowEncDecyypt_recovery: true, symmetric key:  create blob with unwrapped key.");
+                        pbeWrappedData = createEncryptedContentInfo(ct, null, unwrappedSecData, null, pass);
+                    } else {
+                        pbeWrappedData = createEncryptedContentInfo(ct, symKey, null, null,
+                                pass);
+                    }
+                } else if (dataType.equals(KeyRequestResource.PASS_PHRASE_TYPE)) {
                     CMS.debug("SecurityDataRecoveryService: encrypt stored passphrase with transport passphrase");
                     pbeWrappedData = createEncryptedContentInfo(ct, null, unwrappedSecData, null,
                             pass);
                 } else if (dataType.equals(KeyRequestResource.ASYMMETRIC_KEY_TYPE)) {
-                    CMS.debug("SecurityDataRecoveryService: wrap stored private key with transport passphrase");
-                    pbeWrappedData = createEncryptedContentInfo(ct, null, null, privateKey,
-                            pass);
+                    if (allowEncDecrypt_recovery == true) {
+                        CMS.debug("SecurityDataRecoveryService: allowEncDecyypt_recovery: true, asymmetric key:  create blob with unwrapped key.");
+                        pbeWrappedData = createEncryptedContentInfo(ct, null, unwrappedSecData, null, pass);
+                    } else {
+                        CMS.debug("SecurityDataRecoveryService: wrap stored private key with transport passphrase");
+                        pbeWrappedData = createEncryptedContentInfo(ct, null, null, privateKey,
+                                pass);
+                    }
                 }
 
                 params.put(IRequest.SECURITY_DATA_PASS_WRAPPED_DATA, pbeWrappedData);
@@ -257,12 +288,27 @@ public class SecurityDataRecoveryService implements IService {
             CMS.debug("SecurityDataRecoveryService: secure retrieved data with session key");
 
             if (dataType.equals(KeyRequestResource.SYMMETRIC_KEY_TYPE)) {
-                CMS.debug("SecurityDataRecoveryService: wrap stored symmetric key with session key");
+                CMS.debug("SecurityDataRecoveryService: wrap or encrypt stored symmetric key with session key");
                 try {
-                    unwrappedSess = mTransportUnit.unwrap_sym(wrappedSessKey, SymmetricKey.Usage.WRAP);
-                    KeyWrapper wrapper = ct.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD);
-                    wrapper.initWrap(unwrappedSess, new IVParameterSpec(iv));
-                    key_data = wrapper.wrap(symKey);
+                    if (allowEncDecrypt_recovery == true) {
+                        CMS.debug("SecurityDataRecoveryService: encrypt symmetric key with session key as per allowEncDecrypt_recovery: true.");
+                        unwrappedSess = mTransportUnit.unwrap_sym(wrappedSessKey, SymmetricKey.Usage.ENCRYPT);
+                        Cipher encryptor = ct.getCipherContext(EncryptionAlgorithm.DES3_CBC_PAD);
+                        if (encryptor != null) {
+                            encryptor.initEncrypt(unwrappedSess, new IVParameterSpec(iv));
+                            key_data = encryptor.doFinal(unwrappedSecData);
+                        } else {
+                            auditRecoveryRequestProcessed(auditSubjectID, ILogger.FAILURE, requestID,
+                                    serialno.toString(), "Failed to create cipher encrypting symmetric key");
+                            throw new IOException("Failed to create cipher encryping symmetric key");
+                        }
+
+                    } else {
+                        unwrappedSess = mTransportUnit.unwrap_sym(wrappedSessKey, SymmetricKey.Usage.WRAP);
+                        KeyWrapper wrapper = ct.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD);
+                        wrapper.initWrap(unwrappedSess, new IVParameterSpec(iv));
+                        key_data = wrapper.wrap(symKey);
+                    }
 
                 } catch (Exception e) {
                     auditRecoveryRequestProcessed(auditSubjectID, ILogger.FAILURE, requestID, serialno.toString(),
@@ -292,12 +338,27 @@ public class SecurityDataRecoveryService implements IService {
                 }
 
             } else if (dataType.equals(KeyRequestResource.ASYMMETRIC_KEY_TYPE)) {
-                CMS.debug("SecurityDataRecoveryService: wrap stored private key with session key");
+                CMS.debug("SecurityDataRecoveryService: wrap or encrypt stored private key with session key");
                 try {
-                    unwrappedSess = mTransportUnit.unwrap_sym(wrappedSessKey, SymmetricKey.Usage.WRAP);
-                    KeyWrapper wrapper = ct.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD);
-                    wrapper.initWrap(unwrappedSess, new IVParameterSpec(iv));
-                    key_data = wrapper.wrap(privateKey);
+                    if (allowEncDecrypt_recovery == true) {
+                        CMS.debug("SecurityDataRecoveryService: encrypt symmetric key with session key as per allowEncDecrypt_recovery: true.");
+                        unwrappedSess = mTransportUnit.unwrap_sym(wrappedSessKey, SymmetricKey.Usage.ENCRYPT);
+                        Cipher encryptor = ct.getCipherContext(EncryptionAlgorithm.DES3_CBC_PAD);
+                        if (encryptor != null) {
+                            encryptor.initEncrypt(unwrappedSess, new IVParameterSpec(iv));
+                            key_data = encryptor.doFinal(unwrappedSecData);
+                        } else {
+                            auditRecoveryRequestProcessed(auditSubjectID, ILogger.FAILURE, requestID,
+                                    serialno.toString(), "Failed to create cipher encrypting asymmetric key");
+                            throw new IOException("Failed to create cipher encrypting asymmetric key");
+                        }
+
+                    } else {
+                        unwrappedSess = mTransportUnit.unwrap_sym(wrappedSessKey, SymmetricKey.Usage.WRAP);
+                        KeyWrapper wrapper = ct.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD);
+                        wrapper.initWrap(unwrappedSess, new IVParameterSpec(iv));
+                        key_data = wrapper.wrap(privateKey);
+                    }
 
                 } catch (Exception e) {
                     auditRecoveryRequestProcessed(auditSubjectID, ILogger.FAILURE, requestID, serialno.toString(),
@@ -311,6 +372,10 @@ public class SecurityDataRecoveryService implements IService {
             params.put(IRequest.SECURITY_DATA_IV_STRING_OUT, ivStr);
         }
 
+        if(unwrappedSecData != null && unwrappedSecData.length > 0) {
+            Arrays.fill(unwrappedSecData, (byte)0);
+        }
+
         auditRecoveryRequestProcessed(auditSubjectID, ILogger.SUCCESS, requestID, serialno.toString(),
                 "None");
         request.setExtData(IRequest.RESULT, IRequest.RES_SUCCESS);
diff --git a/base/kra/src/com/netscape/kra/SecurityDataService.java b/base/kra/src/com/netscape/kra/SecurityDataService.java
index 25bb240..3a163e2 100644
--- a/base/kra/src/com/netscape/kra/SecurityDataService.java
+++ b/base/kra/src/com/netscape/kra/SecurityDataService.java
@@ -18,12 +18,14 @@
 package com.netscape.kra;
 
 import java.math.BigInteger;
+import java.util.Arrays;
 
 import org.dogtagpki.server.kra.rest.KeyRequestService;
 import org.mozilla.jss.crypto.SymmetricKey;
 
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.EBaseException;
+import com.netscape.certsrv.base.IConfigStore;
 import com.netscape.certsrv.dbs.keydb.IKeyRecord;
 import com.netscape.certsrv.dbs.keydb.IKeyRepository;
 import com.netscape.certsrv.key.KeyRequestResource;
@@ -53,6 +55,7 @@ public class SecurityDataService implements IService {
     private ITransportKeyUnit mTransportUnit = null;
     private IStorageKeyUnit mStorageUnit = null;
     private ILogger signedAuditLogger = CMS.getSignedAuditLogger();
+    private Boolean allowEncDecrypt_archival = false;
 
     private final static String LOGGING_SIGNED_AUDIT_SECURITY_DATA_ARCHIVAL_REQUEST_PROCESSED =
             "LOGGING_SIGNED_AUDIT_SECURITY_DATA_ARCHIVAL_REQUEST_PROCESSED_6";
@@ -100,6 +103,16 @@ public class SecurityDataService implements IService {
         CMS.debug("SecurityDataService.serviceRequest. Request id: " + id);
         CMS.debug("SecurityDataService.serviceRequest wrappedSecurityData: " + wrappedSecurityData);
 
+        IConfigStore config = null;
+
+        try {
+            config = CMS.getConfigStore();
+            allowEncDecrypt_archival = config.getBoolean("kra.allowEncDecrypt.archival", false);
+        } catch (Exception e) {
+            throw new EBaseException(CMS.getUserMessage("CMS_BASE_CERT_ERROR", e.toString()));
+        }
+
+
         String owner = request.getExtDataInString(IRequest.ATTR_REQUEST_OWNER);
         String auditSubjectID = owner;
 
@@ -146,16 +159,42 @@ public class SecurityDataService implements IService {
         byte[] securityData = null;
 
         String keyType = null;
+        byte [] tmp_unwrapped = null;
+        byte [] unwrapped = null;
         if (dataType.equals(KeyRequestResource.SYMMETRIC_KEY_TYPE)) {
             // Symmetric Key
             keyType = KeyRequestResource.SYMMETRIC_KEY_TYPE;
-            securitySymKey = mTransportUnit.unwrap_symmetric(
-                    wrappedSessionKey,
-                    algStr,
-                    sparams,
-                    secdata,
-                    KeyRequestService.SYMKEY_TYPES.get(algorithm),
-                    strength);
+
+            if (allowEncDecrypt_archival == true) {
+                tmp_unwrapped = mTransportUnit.decryptExternalPrivate(
+                        wrappedSessionKey,
+                        algStr,
+                        sparams,
+                        secdata);
+
+                if(tmp_unwrapped == null ) {
+                    throw new EBaseException("Can't decrypt symm key using allEncDecrypt_archival : true .");
+                }
+
+                /* making sure leading 0's are removed */
+                int first=0;
+                for (int j=0; (j< tmp_unwrapped.length) && (tmp_unwrapped[j]==0); j++) {
+                    first++;
+                }
+                unwrapped = Arrays.copyOfRange(tmp_unwrapped, first, tmp_unwrapped.length);
+                Arrays.fill(tmp_unwrapped, (byte)0);
+
+
+            } else {
+
+                securitySymKey = mTransportUnit.unwrap_symmetric(
+                        wrappedSessionKey,
+                        algStr,
+                        sparams,
+                        secdata,
+                        KeyRequestService.SYMKEY_TYPES.get(algorithm),
+                        strength);
+            }
 
         } else if (dataType.equals(KeyRequestResource.PASS_PHRASE_TYPE)) {
             keyType = KeyRequestResource.PASS_PHRASE_TYPE;
@@ -170,9 +209,13 @@ public class SecurityDataService implements IService {
         byte[] publicKey = null;
         byte privateSecurityData[] = null;
 
-        if (securitySymKey != null) {
+        if (securitySymKey != null && unwrapped == null) {
             privateSecurityData = mStorageUnit.wrap(securitySymKey);
-        } else if (securityData != null) {
+        } else if (unwrapped != null && allowEncDecrypt_archival == true) {
+            privateSecurityData = mStorageUnit.encryptInternalPrivate(unwrapped);
+            Arrays.fill(unwrapped, (byte)0);
+            CMS.debug("allowEncDecrypt_archival of symmetric key.");
+        }else if (securityData != null) {
             privateSecurityData = mStorageUnit.encryptInternalPrivate(securityData);
         } else { // We have no data.
             auditArchivalRequestProcessed(auditSubjectID, ILogger.FAILURE, request.getRequestId(),
-- 
2.1.0

