>From 23f532da661f2528c47df67c8663a0f4f96401ea Mon Sep 17 00:00:00 2001
From: Christina Fu <cfu@redhat.com>
Date: Thu, 13 Apr 2017 16:53:58 -0700
Subject: [PATCH] Ticket #2614 CMC: id-cmc-popLinkWitnessV2 feature
 implementation This patch provides the feature for CMC on handling
 id-cmc-popLinkWitnessV2

---
 .../src/com/netscape/cmstools/CMCRequest.java      | 445 +++++++++++++++++++--
 .../src/com/netscape/cmstools/CRMFPopClient.java   |  10 +-
 .../src/com/netscape/cmstools/PKCS10Client.java    |  22 +-
 .../netscape/cms/profile/common/EnrollProfile.java | 416 ++++++++++++++-----
 .../cms/servlet/common/CMCOutputTemplate.java      |  12 +
 base/server/cmsbundle/src/UserMessages.properties  |   2 +
 6 files changed, 752 insertions(+), 155 deletions(-)

diff --git a/base/java-tools/src/com/netscape/cmstools/CMCRequest.java b/base/java-tools/src/com/netscape/cmstools/CMCRequest.java
index a2aca8a..004b81d 100644
--- a/base/java-tools/src/com/netscape/cmstools/CMCRequest.java
+++ b/base/java-tools/src/com/netscape/cmstools/CMCRequest.java
@@ -34,6 +34,7 @@ import java.security.NoSuchAlgorithmException;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.Random;
 import java.util.StringTokenizer;
 
 import org.mozilla.jss.CryptoManager;
@@ -53,10 +54,12 @@ import org.mozilla.jss.crypto.CryptoToken;
 import org.mozilla.jss.crypto.DigestAlgorithm;
 import org.mozilla.jss.crypto.ObjectNotFoundException;
 import org.mozilla.jss.crypto.PrivateKey;
+import org.mozilla.jss.crypto.Signature;
 import org.mozilla.jss.crypto.SignatureAlgorithm;
 import org.mozilla.jss.crypto.SymmetricKey;
 import org.mozilla.jss.crypto.X509Certificate;
 import org.mozilla.jss.pkcs10.CertificationRequest;
+import org.mozilla.jss.pkcs10.CertificationRequestInfo;
 import org.mozilla.jss.pkix.cmc.CMCCertId;
 import org.mozilla.jss.pkix.cmc.CMCStatusInfo;
 import org.mozilla.jss.pkix.cmc.DecryptedPOP;
@@ -68,6 +71,7 @@ import org.mozilla.jss.pkix.cmc.OtherInfo;
 import org.mozilla.jss.pkix.cmc.OtherMsg;
 import org.mozilla.jss.pkix.cmc.PKIData;
 import org.mozilla.jss.pkix.cmc.PendInfo;
+import org.mozilla.jss.pkix.cmc.PopLinkWitnessV2;
 import org.mozilla.jss.pkix.cmc.ResponseBody;
 import org.mozilla.jss.pkix.cmc.TaggedAttribute;
 import org.mozilla.jss.pkix.cmc.TaggedCertificationRequest;
@@ -85,7 +89,11 @@ import org.mozilla.jss.pkix.cms.SignerInfo;
 import org.mozilla.jss.pkix.crmf.CertReqMsg;
 import org.mozilla.jss.pkix.crmf.CertRequest;
 import org.mozilla.jss.pkix.crmf.CertTemplate;
+import org.mozilla.jss.pkix.crmf.POPOSigningKey;
+import org.mozilla.jss.pkix.crmf.ProofOfPossession;
+import org.mozilla.jss.pkix.primitive.AVA;
 import org.mozilla.jss.pkix.primitive.AlgorithmIdentifier;
+import org.mozilla.jss.pkix.primitive.Attribute;
 import org.mozilla.jss.pkix.primitive.Name;
 import org.mozilla.jss.pkix.primitive.SubjectPublicKeyInfo;
 import org.mozilla.jss.util.Password;
@@ -148,6 +156,37 @@ public class CMCRequest {
     }
 
     /**
+     * getSigningAlgFromPrivate
+     *
+     */
+    static SignatureAlgorithm getSigningAlgFromPrivate (java.security.PrivateKey privKey) {
+        String method = "getSigningAlgFromPrivate: ";
+        System.out.println(method + "begins.");
+            SignatureAlgorithm signAlg = null;
+        /*
+            org.mozilla.jss.crypto.PrivateKey.Type signingKeyType =
+                    ((org.mozilla.jss.crypto.PrivateKey) privKey)
+                    .getType();
+        */
+        // TODO: allow more options later
+        String signingKeyType = privKey.getAlgorithm();
+        System.out.println(method + "found signingKeyType=" + signingKeyType);
+        if (signingKeyType.equalsIgnoreCase("RSA")) {
+            signAlg = SignatureAlgorithm.RSASignatureWithSHA256Digest;
+        } else if (signingKeyType.equalsIgnoreCase("EC")) {
+            signAlg = SignatureAlgorithm.ECSignatureWithSHA256Digest;
+        } else {
+            System.out.println(method + "Algorithm not supported:" +
+                    signingKeyType);
+            return null;
+        }
+        System.out.println(method + "using SignatureAlgorithm: " +
+                signAlg.toString());
+
+        return signAlg;
+    }
+
+    /**
      * signData signs the request PKIData
      *
      * @param signerCert the certificate of the authorized signer of the CMC revocation request.
@@ -190,17 +229,9 @@ public class CMCRequest {
 
             EncapsulatedContentInfo ci = new EncapsulatedContentInfo(OBJECT_IDENTIFIER.id_cct_PKIData, pkidata);
             DigestAlgorithm digestAlg = null;
-            SignatureAlgorithm signAlg = null;
-            org.mozilla.jss.crypto.PrivateKey.Type signingKeyType = ((org.mozilla.jss.crypto.PrivateKey) privKey)
-                    .getType();
-            if (signingKeyType.equals(org.mozilla.jss.crypto.PrivateKey.Type.RSA)) {
-                signAlg = SignatureAlgorithm.RSASignatureWithSHA256Digest;
-            } else if (signingKeyType.equals(org.mozilla.jss.crypto.PrivateKey.Type.EC)) {
-                signAlg = SignatureAlgorithm.ECSignatureWithSHA256Digest;
-            } else {
-                System.out.println("Algorithm not supported");
+            SignatureAlgorithm signAlg = getSigningAlgFromPrivate(privKey);
+            if (signAlg == null)
                 return null;
-            }
 
             MessageDigest SHADigest = null;
 
@@ -292,9 +323,13 @@ public class CMCRequest {
             String transactionMgtId,
             String identificationEnable, String identification,
             String identityProofEnable, String identityProofSharedSecret,
-            String identityProofV2Enable, String witnessSharedSecret,
+            String witnessSharedSecret,
+            String identityProofV2Enable,
             String identityProofV2hashAlg, String identityProofV2macAlg,
-            SEQUENCE controlSeq, SEQUENCE otherMsgSeq, int bpid) {
+            String popLinkWitnessV2Enable,
+            String popLinkWitnessV2keyGenAlg, String popLinkWitnessV2macAlg,
+            SEQUENCE controlSeq, SEQUENCE otherMsgSeq, int bpid,
+            CryptoToken token, PrivateKey privk) {
 
         String method = "createPKIData: ";
 
@@ -305,6 +340,26 @@ public class CMCRequest {
             TaggedRequest trq = null;
             PKCS10 pkcs = null;
             CertReqMsg certReqMsg = null;
+            CertReqMsg new_certReqMsg = null;
+            CertRequest new_certreq = null;
+
+            PopLinkWitnessV2 popLinkWitnessV2Control = null;
+            if (popLinkWitnessV2Enable.equals("true")) {
+                popLinkWitnessV2Control =
+                        createPopLinkWitnessV2Attr(
+                                bpid,
+                                controlSeq,
+                                witnessSharedSecret,
+                                popLinkWitnessV2keyGenAlg,
+                                popLinkWitnessV2macAlg,
+                                (identificationEnable.equals("true")) ?
+                                        identification : null);
+                if (popLinkWitnessV2Control == null) {
+                    System.out.println(method +
+                            "createPopLinkWitnessV2Attr returned null...exit");
+                    System.exit(1);
+                }
+            }
 
             // create CMC req
             SEQUENCE reqSequence = new SEQUENCE();
@@ -325,9 +380,63 @@ public class CMCRequest {
                             System.exit(1);
                         }
                         certReqMsg = (CertReqMsg) crmfMsgs.elementAt(0);
-                        trq = new TaggedRequest(TaggedRequest.CRMF, null,
-                                certReqMsg);
+
+                        if (popLinkWitnessV2Enable.equals("true")) {
+                            System.out.println(method +
+                                    "popLinkWitnessV2 enabled. reconstructing crmf");
+                            //crmf reconstruction to include PopLinkWitnessV2 control
+                            CertRequest certReq = certReqMsg.getCertReq();
+                            INTEGER certReqId = certReq.getCertReqId();
+                            CertTemplate certTemplate = certReq.getCertTemplate();
+                            SEQUENCE controls = certReq.getControls();
+                            controls.addElement(new AVA(OBJECT_IDENTIFIER.id_cmc_popLinkWitnessV2,
+                                    popLinkWitnessV2Control));
+                            new_certreq = new CertRequest(certReqId, certTemplate, controls);
+
+                            // recalculate signing POP, if it had one
+                            ProofOfPossession new_pop = null;
+                            if (certReqMsg.hasPop()) {
+                                if (privk == null) {
+                                    System.out.println(method +
+                                            "privateKey not found; can't regenerate new POP");
+                                    System.exit(1);
+                                }
+                                if (token == null) {
+                                    System.out.println(method +
+                                            "token not found; can't regenerate new POP");
+                                    System.exit(1);
+                                }
+                                new_pop = createNewPOP(
+                                        certReqMsg,
+                                        new_certreq,
+                                        token,
+                                        privk);
+                            } else { // !hasPop
+                                System.out.println(method +
+                                        "old certReqMsg has no pop, so will the new certReqMsg");
+                            }
+
+                            new_certReqMsg = new CertReqMsg(new_certreq, new_pop, null);
+                            SEQUENCE seq = new SEQUENCE();
+                            seq.addElement(new_certReqMsg);
+
+                            byte[] encodedNewCrmfMessage = ASN1Util.encode(seq);
+                            String b64String = Utils.base64encode(encodedNewCrmfMessage);
+                            System.out.println(method + "new CRMF b64encode completes.");
+                            System.out.println(CryptoUtil.CERTREQ_BEGIN_HEADING);
+                            System.out.println(b64String);
+                            System.out.println(CryptoUtil.CERTREQ_END_HEADING);
+                            System.out.println("");
+
+                            trq = new TaggedRequest(TaggedRequest.CRMF, null,
+                                    new_certReqMsg);
+
+                        } else { // !popLinkWitnessV2Enable
+                            trq = new TaggedRequest(TaggedRequest.CRMF, null,
+                                    certReqMsg);
+                        }
                     } else if (format.equals("pkcs10")) {
+                        System.out.println(method + " format: pkcs10");
                         try {
                             pkcs = new PKCS10(decodedBytes, true);
                         } catch (Exception e2) {
@@ -338,9 +447,82 @@ public class CMCRequest {
                                 pkcs.toByteArray());
                         CertificationRequest cr = (CertificationRequest) CertificationRequest.getTemplate()
                                 .decode(crInputStream);
-                        TaggedCertificationRequest tcr = new TaggedCertificationRequest(
-                                new INTEGER(bpid++), cr);
-                        trq = new TaggedRequest(TaggedRequest.PKCS10, tcr, null);
+                        if (popLinkWitnessV2Enable.equals("true")) {
+                            System.out.println(method +
+                                    "popLinkWitnessV2 enabled. reconstructing pkcs#10");
+                            //pkcs#10 reconstruction to include PopLinkWitnessV2 control
+
+                            CertificationRequestInfo certReqInfo = cr.getInfo();
+
+                            INTEGER version = certReqInfo.getVersion();
+                            Name subject = certReqInfo.getSubject();
+                            SubjectPublicKeyInfo spkInfo = certReqInfo.getSubjectPublicKeyInfo();
+                            /*
+                            AlgorithmIdentifier alg = spkInfo.getAlgorithmIdentifier();
+                            SignatureAlgorithm signAlg = SignatureAlgorithm.fromOID(alg.getOID());
+                            if (signAlg == SignatureAlgorithm.RSASignatureWithSHA256Digest) {
+                                System.out.println(method +
+                                        "signAlg == SignatureAlgorithm.RSASignatureWithSHA256Digest");
+                            } else {
+                                System.out.println(method +
+                                        "signAlg == " + signAlg.toString());
+                            }
+                            */
+
+                            Attribute attr = new Attribute(
+                                    OBJECT_IDENTIFIER.id_cmc_popLinkWitnessV2,
+                                    popLinkWitnessV2Control);
+                            SET attrs = certReqInfo.getAttributes();
+                            if (attrs == null) {
+                                attrs = new SET();
+                            }
+                            attrs.addElement(attr);
+                            System.out.println(method +
+                                    " new pkcs#10 Attribute created for id_cmc_popLinkWitnessV2.");
+
+                            SignatureAlgorithm signAlg = getSigningAlgFromPrivate(privk);
+                            if (signAlg == null) {
+                                System.out.println(method +
+                                        "signAlg not found");
+                                System.exit(1);
+                            }
+                            CertificationRequestInfo new_certReqInfo = new CertificationRequestInfo(
+                                    version,
+                                    subject,
+                                    spkInfo,
+                                    attrs);
+                            System.out.println(method +
+                                    " new pkcs#10 CertificationRequestInfo created.");
+
+                            CertificationRequest new_certRequest = new CertificationRequest(
+                                    new_certReqInfo,
+                                    privk,
+                                    signAlg);
+                            System.out.println(method +
+                                    "new pkcs#10 CertificationRequest created.");
+
+                            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+                            new_certRequest.encode(bos);
+                            byte[] bb = bos.toByteArray();
+
+                            System.out.println(method + "calling Utils.b64encode.");
+                            String b64String = Utils.base64encode(bb);
+                            System.out.println(method + "new PKCS#10 b64encode completes.");
+                            System.out.println(CryptoUtil.CERTREQ_BEGIN_HEADING);
+                            System.out.println(b64String);
+                            System.out.println(CryptoUtil.CERTREQ_END_HEADING);
+                            System.out.println("");
+
+                            TaggedCertificationRequest tcr = new TaggedCertificationRequest(
+                                    new INTEGER(bpid++), new_certRequest);
+                            trq = new TaggedRequest(TaggedRequest.PKCS10, tcr, null);
+
+                        } else { // !popLinkWitnessV2Enable
+
+                            TaggedCertificationRequest tcr = new TaggedCertificationRequest(
+                                    new INTEGER(bpid++), cr);
+                            trq = new TaggedRequest(TaggedRequest.PKCS10, tcr, null);
+                        }
                     } else {
                         System.out.println(method + " Unrecognized request format: " + format);
                         System.exit(1);
@@ -348,7 +530,7 @@ public class CMCRequest {
                     reqSequence.addElement(trq);
                 }
             } catch (Exception e) {
-                System.out.println(method + " Exception:" + e.toString());
+                System.out.println(method + " Exception:" + e);
                 System.exit(1);
             }
 
@@ -380,6 +562,63 @@ public class CMCRequest {
         return pkidata;
     }
 
+    /**
+     * createNewPOP
+     * called in case of PopLinkwitnessV2 when pop exists, thus
+     * requiring recalculation due to changes in CertRequest controls
+     *
+     * @param old_certReqMsg,
+     * @param new_certReqMsg,
+     * @param token,
+     * @param privKey
+     *
+     * @author cfu
+     */
+    static ProofOfPossession createNewPOP(
+            CertReqMsg old_certReqMsg,
+            CertRequest new_certReq,
+            CryptoToken token,
+            PrivateKey privKey) {
+        String method = "createNewPOP: ";
+
+        System.out.println(method + "begins");
+        if (old_certReqMsg == null ||
+                new_certReq == null ||
+                token == null ||
+                privKey == null) {
+            System.out.println(method + "method params cannot be null.");
+            System.exit(1);
+        }
+        ProofOfPossession old_pop = old_certReqMsg.getPop();
+        if (old_pop == null) {
+            System.out.println(method + "no pop in old_certReqMsg.");
+            System.exit(1);
+        }
+
+        POPOSigningKey PopOfsignKey = old_pop.getSignature();
+        AlgorithmIdentifier algId = PopOfsignKey.getAlgorithmIdentifier();
+
+        byte[] signature = null;
+        try {
+            SignatureAlgorithm signAlg = SignatureAlgorithm.fromOID(algId.getOID());
+            Signature signer = token.getSignatureContext(signAlg);
+            signer.initSign(privKey);
+            ByteArrayOutputStream bo = new ByteArrayOutputStream();
+            new_certReq.encode(bo);
+            signer.update(bo.toByteArray());
+            signature = signer.sign();
+        } catch (Exception e) {
+            System.out.println(method + e);
+            System.exit(1);
+        }
+
+        System.out.println(method + "about to create POPOSigningKey");
+        POPOSigningKey newPopOfSigningKey = new POPOSigningKey(null, algId, new BIT_STRING(signature, 0));
+
+        System.out.println(method + "creating and returning newPopOfSigningKey");
+        return ProofOfPossession.createSignature(newPopOfSigningKey);
+    }
+
     static void printUsage() {
         System.out.println("");
         System.out.println("Usage: CMCRequest <configuration file>");
@@ -516,13 +755,29 @@ public class CMCRequest {
         System.out.println("identityProofV2.hashAlg=SHA-256");
         System.out.println("identityProofV2.macAlg=SHA-256-HMAC");
         System.out.println("");
+        System.out.println("#witness.sharedSecret works with identityProofV2 and popLinkWitnessV2");
         System.out.println("#witness.sharedSecret: Shared Secret");
         System.out.println("witness.sharedSecret=testing");
         System.out.println("");
-        System.out.println("#identification works with identityProofV2");
+        System.out.println("#identification works with identityProofV2 and popLinkWitnessV2");
         System.out.println("identification.enable=false");
         System.out.println("identification=testuser");
         System.out.println("");
+        System.out.println("#popLinkWitnessV2.enable:  if true, then the underlying request will contain");
+        System.out.println("#this control or attribute. Otherwise, false.");
+        System.out.println("#Supported keyGenAlg are:");
+        System.out.println("# SHA-256, SHA-384, and SHA-512");
+        System.out.println("#Supported macAlg are:");
+        System.out.println("# SHA-256-HMAC, SHA-384-HMAC, and SHA-512-HMAC");
+        System.out.println("popLinkWitnessV2.enable=false");
+        System.out.println("popLinkWitnessV2.keyGenAlg=SHA-256");
+        System.out.println("popLinkWitnessV2.macAlg=SHA-256-HMAC");
+        System.out.println("");
+        System.out.println("");
+        System.out.println("###############################");
+        System.out.println("Note: The following controls are outdated and replaced by newer");
+        System.out.println("      controls above.  They remain untouched, but also untested.");
+        System.out.println("###############################");
         System.out.println("#identityProof.enable: if true, then the request will contain");
         System.out.println("#this control. Otherwise, false.");
         System.out.println("#Note that this control is updated by identityProofV2 above");
@@ -879,7 +1134,7 @@ public class CMCRequest {
             System.out.println("");
             seq.addElement(getCertControl);
         } catch (Exception e) {
-            System.out.println("Error in creating get certificate control. Check the parameters.");
+            System.out.println("Error in creating get certificate control. Check the parameters." + e);
             System.exit(1);
         }
 
@@ -1023,6 +1278,111 @@ public class CMCRequest {
         return bpid;
     }
 
+    /**
+     * createPopLinkWitnessV2Attr generates witness v2
+     *
+     * @param
+     * @return PopLinkWitnessV2
+     *
+     * @author cfu
+     */
+    private static PopLinkWitnessV2 createPopLinkWitnessV2Attr(
+            int bpid, SEQUENCE controlSeq,
+            String sharedSecret,
+            String keyGenAlgString,
+            String macAlgString,
+            String ident) {
+
+        String method = "createPopLinkWitnessV2Attr: ";
+        byte[] key = null;
+        byte[] finalDigest = null;
+
+        // (1) generate a random byte-string R of 512 bits
+        Random random = new Random();
+        byte[] random_R = new byte[64];
+        random.nextBytes(random_R);
+
+        // default to SHA256 if not specified
+        if (keyGenAlgString == null) {
+            keyGenAlgString = "SHA-256";
+        }
+        if (macAlgString == null) {
+            macAlgString = "SHA-256-HMAC";
+        }
+        System.out.println(method + "keyGenAlg=" + keyGenAlgString +
+                "; macAlg=" + macAlgString);
+
+        String toBeDigested = sharedSecret;
+        if (ident != null) {
+            toBeDigested = sharedSecret + ident;
+        }
+
+        // (2) compute key from sharedSecret + identity
+        try {
+            MessageDigest hash = MessageDigest.getInstance(keyGenAlgString);
+            key = hash.digest(toBeDigested.getBytes());
+        } catch (NoSuchAlgorithmException ex) {
+            System.out.println(method + "No such algorithm!");
+            return null;
+        }
+
+        MessageDigest mac;
+        // (3) compute MAC over R from (1) using key from (2)
+        try {
+            mac = MessageDigest.getInstance(
+                    CryptoUtil.getHMACtoMessageDigestName(macAlgString));
+            HMACDigest hmacDigest = new HMACDigest(mac, key);
+            hmacDigest.update(random_R);
+            finalDigest = hmacDigest.digest();
+        } catch (NoSuchAlgorithmException ex) {
+            System.out.println(method + "No such algorithm!");
+            return null;
+        }
+
+        // (4) encode R as the value of a POP Link Random control
+        TaggedAttribute idPOPLinkRandom =
+                new TaggedAttribute(new INTEGER(bpid++),
+                OBJECT_IDENTIFIER.id_cmc_idPOPLinkRandom,
+                new OCTET_STRING(random_R));
+        controlSeq.addElement(idPOPLinkRandom);
+        System.out.println(method +
+                "Successfully created id_cmc_idPOPLinkRandom control. bpid = "
+                + (bpid - 1));
+
+        AlgorithmIdentifier keyGenAlg;
+        try {
+            keyGenAlg = new AlgorithmIdentifier(
+                    CryptoUtil.getHashAlgorithmOID(keyGenAlgString));
+        } catch (NoSuchAlgorithmException ex) {
+            System.out.println(method + "No such hashing algorithm:" + keyGenAlgString);
+            return null;
+        }
+        AlgorithmIdentifier macAlg;
+        try {
+            macAlg = new AlgorithmIdentifier(
+                    CryptoUtil.getHMACAlgorithmOID(macAlgString));
+        } catch (NoSuchAlgorithmException ex) {
+            System.out.println(method + "No such HMAC algorithm:" + macAlgString);
+            return null;
+        }
+
+        // (5) put MAC value from (3) in PopLinkWitnessV2
+        PopLinkWitnessV2 popLinkWitnessV2 =
+                new PopLinkWitnessV2(keyGenAlg, macAlg,
+                        new OCTET_STRING(finalDigest));
+        /*
+         * for CRMF, needs to go into CRMF controls field of the CertRequest structure.
+         * for PKCS#10, needs to go into the aributes field of CertificationRequestInfo structure
+         *   - return the PopLinkWitnessV2 for such surgical procedure
+         */
+        System.out.println(method + "Successfully created PopLinkWitnessV2 control.");
+
+        System.out.println(method + "returning...");
+        System.out.println("");
+
+        return popLinkWitnessV2;
+    }
+
     private static int addPopLinkWitnessAttr(int bpid, SEQUENCE controlSeq) {
         byte[] seed =
         { 0x10, 0x53, 0x42, 0x24, 0x1a, 0x2a, 0x35, 0x3c,
@@ -1309,7 +1669,8 @@ public class CMCRequest {
         String dbdir = null, nickname = null;
         String tokenName = null;
         String ifilename = null, ofilename = null, password = null, format = null;
-        String decryptedPopEnable = "false", encryptedPopResponseFile=null, privKeyId = null, decryptedPopRequestFile= null;
+        String privKeyId = null;
+        String decryptedPopEnable = "false", encryptedPopResponseFile=null, decryptedPopRequestFile= null;
         String confirmCertEnable = "false", confirmCertIssuer = null, confirmCertSerial = null;
         String getCertEnable = "false", getCertIssuer = null, getCertSerial = null;
         String dataReturnEnable = "false", dataReturnData = null;
@@ -1321,7 +1682,9 @@ public class CMCRequest {
         String revRequestInvalidityDatePresent = "false";
         String identificationEnable = "false", identification = null;
         String identityProofEnable = "false", identityProofSharedSecret = null;
-        String identityProofV2Enable = "false", witnessSharedSecret = null, identityProofV2hashAlg = "SHA256", identityProofV2macAlg = "SHA256";
+        String identityProofV2Enable = "false", identityProofV2hashAlg = "SHA256", identityProofV2macAlg = "SHA256";
+        String witnessSharedSecret = null; //shared by identityProofV2 and popLinkWitnessV2
+        String popLinkWitnessV2Enable = "false", popLinkWitnessV2keyGenAlg = "SHA256", popLinkWitnessV2macAlg = "SHA256";
         String popLinkWitnessEnable = "false";
         String bodyPartIDs = null, lraPopWitnessEnable = "false";
 
@@ -1378,6 +1741,8 @@ public class CMCRequest {
                         ofilename = val;
                     } else if (name.equals("input")) {
                         ifilename = val;
+                    } else if (name.equals("numRequests")) {
+                        numRequests = val;
                     } else if (name.equals("decryptedPop.enable")) {
                         decryptedPopEnable = val;
                     } else if (name.equals("encryptedPopResponseFile")) {
@@ -1430,14 +1795,21 @@ public class CMCRequest {
                         identificationEnable = val;
                     } else if (name.equals("identification")) {
                         identification = val;
-                    } else if (name.equals("identityProofV2.enable")) {
-                        identityProofV2Enable = val;
                     } else if (name.equals("witness.sharedSecret")) {
                         witnessSharedSecret = val;
+                    } else if (name.equals("identityProofV2.enable")) {
+                        identityProofV2Enable = val;
                     } else if (name.equals("identityProofV2.hashAlg")) {
                         identityProofV2hashAlg = val;
                     } else if (name.equals("identityProofV2.macAlg")) {
                         identityProofV2macAlg = val;
+                    } else if (name.equals("popLinkWitnessV2.enable")) {
+                        popLinkWitnessV2Enable = val;
+                    } else if (name.equals("popLinkWitnessV2.keyGenAlg")) {
+                        popLinkWitnessV2keyGenAlg = val;
+                    } else if (name.equals("popLinkWitnessV2.macAlg")) {
+                        popLinkWitnessV2macAlg = val;
+                    /* the following are outdated */
                     } else if (name.equals("identityProof.enable")) {
                         identityProofEnable = val;
                     } else if (name.equals("identityProof.sharedSecret")) {
@@ -1448,8 +1820,6 @@ public class CMCRequest {
                         lraPopWitnessEnable = val;
                     } else if (name.equals("LraPopWitness.bodyPartIDs")) {
                         bodyPartIDs = val;
-                    } else if (name.equals("numRequests")) {
-                        numRequests = val;
                     }
                 }
             }
@@ -1518,13 +1888,14 @@ public class CMCRequest {
             //cfu
             ContentInfo cmcblob = null;
             PKIData pkidata = null;
-            if (decryptedPopEnable.equalsIgnoreCase("true")) {
-                PrivateKey privk = null;
+            PrivateKey privk = null;
+            if (decryptedPopEnable.equalsIgnoreCase("true") ||
+                    popLinkWitnessV2Enable.equalsIgnoreCase("true")) {
                 if (privKeyId == null) {
-                    System.out.println("ecryptedPop.enable = true, but privKeyId not specified.");
+                    System.out.println("ecryptedPop.enable or popLinkWitnessV2 true, but privKeyId not specified.");
                     printUsage();
                 } else {
-                    System.out.println("got privKeyId: " + privKeyId);
+                    System.out.println("got request privKeyId: " + privKeyId);
 
                     byte[] keyIDb = CryptoUtil.string2byte(privKeyId);
 
@@ -1538,7 +1909,9 @@ public class CMCRequest {
                         System.exit(1);
                     }
                 }
+            }
 
+            if (decryptedPopEnable.equalsIgnoreCase("true")) {
                 if (encryptedPopResponseFile == null) {
                     System.out.println("ecryptedPop.enable = true, but encryptedPopResponseFile is not specified.");
                     printUsage();
@@ -1688,7 +2061,9 @@ public class CMCRequest {
                 if (senderNonceEnable.equalsIgnoreCase("true"))
                     bpid = addSenderNonceAttr(bpid, controlSeq, senderNonce);
 
-                if (popLinkWitnessEnable.equalsIgnoreCase("true"))
+                //popLinkWitnessV2 takes precedence
+                if (!popLinkWitnessV2Enable.equalsIgnoreCase("true") &
+                        popLinkWitnessEnable.equalsIgnoreCase("true"))
                     bpid = addPopLinkWitnessAttr(bpid, controlSeq);
 
                 SEQUENCE otherMsgSeq = new SEQUENCE();
@@ -1711,9 +2086,13 @@ public class CMCRequest {
                         format, transactionMgtEnable, transactionMgtId,
                         identificationEnable, identification,
                         identityProofEnable, identityProofSharedSecret,
-                        identityProofV2Enable, witnessSharedSecret,
+                        witnessSharedSecret,
+                        identityProofV2Enable,
                         identityProofV2hashAlg, identityProofV2macAlg,
-                        controlSeq, otherMsgSeq, bpid);
+                        popLinkWitnessV2Enable,
+                        popLinkWitnessV2keyGenAlg, popLinkWitnessV2macAlg,
+                        controlSeq, otherMsgSeq, bpid,
+                        token, privk);
 
                 if (pkidata == null) {
                     System.out.println("pkidata null after createPKIData(). Exiting with error");
diff --git a/base/java-tools/src/com/netscape/cmstools/CRMFPopClient.java b/base/java-tools/src/com/netscape/cmstools/CRMFPopClient.java
index 901528c..e8271b0 100644
--- a/base/java-tools/src/com/netscape/cmstools/CRMFPopClient.java
+++ b/base/java-tools/src/com/netscape/cmstools/CRMFPopClient.java
@@ -577,8 +577,10 @@ public class CRMFPopClient {
         SEQUENCE seq = new SEQUENCE();
         seq.addElement(new AVA(new OBJECT_IDENTIFIER("1.3.6.1.5.5.7.5.1.4"), opts));
 
+        /*
         OCTET_STRING ostr = createIDPOPLinkWitness();
         seq.addElement(new AVA(OBJECT_IDENTIFIER.id_cmc_idPOPLinkWitness, ostr));
+        */
 
         return new CertRequest(new INTEGER(1), certTemplate, seq);
     }
@@ -657,10 +659,10 @@ public class CRMFPopClient {
 
         Signature signer;
         if (algorithm.equals("rsa")) {
-            signer =  token.getSignatureContext(SignatureAlgorithm.RSASignatureWithMD5Digest);
+            signer =  token.getSignatureContext(SignatureAlgorithm.RSASignatureWithSHA256Digest);
 
         } else if (algorithm.equals("ec")) {
-            signer =  token.getSignatureContext(SignatureAlgorithm.ECSignatureWithSHA1Digest);
+            signer =  token.getSignatureContext(SignatureAlgorithm.ECSignatureWithSHA256Digest);
 
         } else {
             throw new Exception("Unknown algorithm: " + algorithm);
@@ -675,10 +677,10 @@ public class CRMFPopClient {
 
         AlgorithmIdentifier algorithmID;
         if (algorithm.equals("rsa")) {
-            algorithmID = new AlgorithmIdentifier(SignatureAlgorithm.RSASignatureWithMD5Digest.toOID(), null);
+            algorithmID = new AlgorithmIdentifier(SignatureAlgorithm.RSASignatureWithSHA256Digest.toOID(), null);
 
         } else if (algorithm.equals("ec")) {
-            algorithmID = new AlgorithmIdentifier(SignatureAlgorithm.ECSignatureWithSHA1Digest.toOID(), null);
+            algorithmID = new AlgorithmIdentifier(SignatureAlgorithm.ECSignatureWithSHA256Digest.toOID(), null);
 
         } else {
             throw new Exception("Unknown algorithm: " + algorithm);
diff --git a/base/java-tools/src/com/netscape/cmstools/PKCS10Client.java b/base/java-tools/src/com/netscape/cmstools/PKCS10Client.java
index 57f8792..fd1d087 100644
--- a/base/java-tools/src/com/netscape/cmstools/PKCS10Client.java
+++ b/base/java-tools/src/com/netscape/cmstools/PKCS10Client.java
@@ -22,14 +22,12 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.security.KeyPair;
-import java.security.MessageDigest;
 import java.security.PublicKey;
 
 import org.mozilla.jss.CryptoManager;
 import org.mozilla.jss.asn1.BMPString;
 import org.mozilla.jss.asn1.INTEGER;
 import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;
-import org.mozilla.jss.asn1.OCTET_STRING;
 import org.mozilla.jss.asn1.PrintableString;
 import org.mozilla.jss.asn1.SET;
 import org.mozilla.jss.asn1.TeletexString;
@@ -38,17 +36,16 @@ import org.mozilla.jss.asn1.UniversalString;
 import org.mozilla.jss.crypto.CryptoToken;
 import org.mozilla.jss.crypto.KeyPairAlgorithm;
 import org.mozilla.jss.crypto.KeyPairGenerator;
+import org.mozilla.jss.crypto.PrivateKey;
 import org.mozilla.jss.crypto.SignatureAlgorithm;
 import org.mozilla.jss.pkcs10.CertificationRequest;
 import org.mozilla.jss.pkcs10.CertificationRequestInfo;
 import org.mozilla.jss.pkix.primitive.AVA;
-import org.mozilla.jss.pkix.primitive.Attribute;
 import org.mozilla.jss.pkix.primitive.Name;
 import org.mozilla.jss.pkix.primitive.SubjectPublicKeyInfo;
 import org.mozilla.jss.util.Password;
 
 import com.netscape.cmsutil.crypto.CryptoUtil;
-import com.netscape.cmsutil.util.HMACDigest;
 import com.netscape.cmsutil.util.Utils;
 
 import netscape.security.pkcs.PKCS10;
@@ -248,6 +245,8 @@ public class PKCS10Client {
 
             System.out.println("PKCS10Client: key pair generated."); //key pair generated");
 
+            /*** leave out this test code; cmc can add popLinkwitnessV2;
+
             // Add idPOPLinkWitness control
             String secretValue = "testing";
             byte[] key1 = null;
@@ -255,7 +254,7 @@ public class PKCS10Client {
             MessageDigest SHA1Digest = MessageDigest.getInstance("SHA1");
             key1 = SHA1Digest.digest(secretValue.getBytes());
 
-            /* seed */
+            // seed
             byte[] b =
             { 0x10, 0x53, 0x42, 0x24, 0x1a, 0x2a, 0x35, 0x3c,
                 0x7a, 0x52, 0x54, 0x56, 0x71, 0x65, 0x66, 0x4c,
@@ -272,9 +271,10 @@ public class PKCS10Client {
 
             OCTET_STRING ostr = new OCTET_STRING(finalDigest);
             Attribute attr = new Attribute(OBJECT_IDENTIFIER.id_cmc_idPOPLinkWitness, ostr);
+            ***/
 
             SET attributes = new SET();
-            attributes.addElement(attr);
+            //attributes.addElement(attr);
             Name n = getJssName(enable_encoding, subjectName);
             SubjectPublicKeyInfo subjectPub = new SubjectPublicKeyInfo(pair.getPublic());
             System.out.println("PKCS10Client: pair.getPublic() called.");
@@ -286,7 +286,7 @@ public class PKCS10Client {
             if (alg.equals("rsa")) {
                 CertificationRequest certRequest = null;
                 certRequest = new CertificationRequest(certReqInfo,
-                pair.getPrivate(), SignatureAlgorithm.RSASignatureWithMD5Digest);
+                pair.getPrivate(), SignatureAlgorithm.RSASignatureWithSHA256Digest);
                 System.out.println("PKCS10Client: CertificationRequest created.");
 
                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
@@ -323,6 +323,14 @@ public class PKCS10Client {
                 b64E = CryptoUtil.base64Encode(certReqb);
             }
 
+            // print out keyid to be used in cmc popLinkWitnessV2
+            PrivateKey privateKey = (PrivateKey) pair.getPrivate();
+            @SuppressWarnings("deprecation")
+            byte id[] = privateKey.getUniqueID();
+            String kid = CryptoUtil.byte2string(id);
+            System.out.println("Keypair private key id: " + kid);
+            System.out.println("");
+
             System.out.println(RFC7468_HEADER);
             System.out.println(b64E);
             System.out.println(RFC7468_TRAILER);
diff --git a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java
index f4a59d2..b8053c1 100644
--- a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java
+++ b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java
@@ -55,6 +55,7 @@ import org.mozilla.jss.pkix.cmc.IdentityProofV2;
 import org.mozilla.jss.pkix.cmc.LraPopWitness;
 import org.mozilla.jss.pkix.cmc.OtherMsg;
 import org.mozilla.jss.pkix.cmc.PKIData;
+import org.mozilla.jss.pkix.cmc.PopLinkWitnessV2;
 import org.mozilla.jss.pkix.cmc.TaggedAttribute;
 import org.mozilla.jss.pkix.cmc.TaggedCertificationRequest;
 import org.mozilla.jss.pkix.cmc.TaggedRequest;
@@ -64,6 +65,7 @@ import org.mozilla.jss.pkix.crmf.CertTemplate;
 import org.mozilla.jss.pkix.crmf.PKIArchiveOptions;
 import org.mozilla.jss.pkix.crmf.ProofOfPossession;
 import org.mozilla.jss.pkix.primitive.AVA;
+import org.mozilla.jss.pkix.primitive.AlgorithmIdentifier;
 import org.mozilla.jss.pkix.primitive.Attribute;
 import org.mozilla.jss.pkix.primitive.Name;
 import org.mozilla.jss.pkix.primitive.SubjectPublicKeyInfo;
@@ -73,7 +75,6 @@ import com.netscape.certsrv.authentication.IAuthToken;
 import com.netscape.certsrv.authentication.ISharedToken;
 import com.netscape.certsrv.authority.IAuthority;
 import com.netscape.certsrv.base.EBaseException;
-import com.netscape.certsrv.base.EPropertyNotFound;
 import com.netscape.certsrv.base.SessionContext;
 import com.netscape.certsrv.ca.ICertificateAuthority;
 import com.netscape.certsrv.logging.ILogger;
@@ -147,6 +148,9 @@ public abstract class EnrollProfile extends BasicProfile
      */
     public IRequest[] createRequests(IProfileContext ctx, Locale locale)
             throws EProfileException {
+        String method = "EnrollProfile: createRequests";
+        CMS.debug(method + "begins");
+
         // determine how many requests should be created
         String cert_request_type = ctx.get(CTX_CERT_REQUEST_TYPE);
         String cert_request = ctx.get(CTX_CERT_REQUEST);
@@ -155,7 +159,7 @@ public abstract class EnrollProfile extends BasicProfile
 
         /* cert_request_type can be null for the case of CMC */
         if (cert_request_type == null) {
-            CMS.debug("EnrollProfile: request type is null");
+            CMS.debug(method + " request type is null");
         }
 
         int num_requests = 1; // default to 1 request
@@ -178,10 +182,14 @@ public abstract class EnrollProfile extends BasicProfile
              */
             // catch for invalid request
             cmc_msgs = parseCMC(locale, cert_request);
-            if (cmc_msgs == null)
+            if (cmc_msgs == null) {
+                CMS.debug(method + "parseCMC returns cmc_msgs null");
                 return null;
-            else
+            } else {
                 num_requests = cmc_msgs.length;
+                CMS.debug(method + "parseCMC returns cmc_msgs num_requests=" +
+                        num_requests);
+            }
         }
 
         // only 1 request for renewal
@@ -360,7 +368,6 @@ public abstract class EnrollProfile extends BasicProfile
             throw new EBaseException(method + msg);
         }
         byte[] req_key_data = req.getExtDataInByteArray(IEnrollProfile.REQUEST_KEY);
-        netscape.security.x509.CertificateX509Key pubKey = null;
         if (req_key_data != null) {
             CMS.debug(method + "found user public key in request");
 
@@ -557,6 +564,7 @@ public abstract class EnrollProfile extends BasicProfile
             int numcontrols = controlSeq.size();
             SEQUENCE reqSeq = pkiData.getReqSequence();
             byte randomSeed[] = null;
+            UTF8String ident_s = null;
             SessionContext context = SessionContext.getContext();
             if (!context.containsKey("numOfControls")) {
                 if (numcontrols > 0) {
@@ -592,6 +600,7 @@ public abstract class EnrollProfile extends BasicProfile
                             id_cmc_identityProof = true;
                             attr = attributes[i];
                         } else if (oid.equals(OBJECT_IDENTIFIER.id_cmc_idPOPLinkRandom)) {
+                            CMS.debug(method + "id_cmc_idPOPLinkRandom true");
                             id_cmc_idPOPLinkRandom = true;
                             vals = attributes[i].getValues();
                         } else {
@@ -625,23 +634,31 @@ public abstract class EnrollProfile extends BasicProfile
                         return null;
                     }
 
-                    UTF8String ident_s = null;
                     if (id_cmc_identification) {
                         if (ident == null) {
                             msg = "id_cmc_identification contains null attribute value";
                             CMS.debug(method + msg);
                             SEQUENCE bpids = getRequestBpids(reqSeq);
                             context.put("identification", bpids);
-                            return null;
+
+                            msg = " id_cmc_identification attribute value not found in";
+                            CMS.debug(method + msg);
+                            throw new EProfileException(
+                                    CMS.getUserMessage(locale, "CMS_PROFILE_INVALID_REQUEST") +
+                                            msg);
                         }
                         ident_s = (UTF8String) (ASN1Util.decode(UTF8String.getTemplate(),
                                 ASN1Util.encode(ident.elementAt(0))));
                         if (ident_s == null) {
-                            msg = "id_cmc_identification contains invalid content";
+                            msg = " id_cmc_identification contains invalid content";
                             CMS.debug(method + msg);
                             SEQUENCE bpids = getRequestBpids(reqSeq);
                             context.put("identification", bpids);
-                            return null;
+
+                            CMS.debug(method + msg);
+                            throw new EProfileException(
+                                    CMS.getUserMessage(locale, "CMS_PROFILE_INVALID_REQUEST") +
+                                            msg);
                         }
                     }
 
@@ -650,7 +667,8 @@ public abstract class EnrollProfile extends BasicProfile
                         if (!id_cmc_identification) {
                             SEQUENCE bpids = getRequestBpids(reqSeq);
                             context.put("identification", bpids);
-                            msg = "id_cmc_identityProofV2 must be accompanied by id_cmc_identification in this server";
+                            context.put("identityProofV2", bpids);
+                            msg = "id_cmc_identityProofV2 missing id_cmc_identification";
                             CMS.debug(method + msg);
                             throw new EProfileException(
                                     CMS.getUserMessage(locale, "CMS_PROFILE_INVALID_REQUEST") +
@@ -662,7 +680,11 @@ public abstract class EnrollProfile extends BasicProfile
                         if (!valid) {
                             SEQUENCE bpids = getRequestBpids(reqSeq);
                             context.put("identityProofV2", bpids);
-                            return null;
+
+                            msg = " in verifyIdentityProofV2";
+                            CMS.debug(method + msg);
+                            throw new EProfileException(CMS.getUserMessage(locale,
+                                    "CMS_POI_VERIFICATION_ERROR")+ msg);
                         }
                     } else if (id_cmc_identityProof && (attr != null)) {
                         boolean valid = verifyIdentityProof(attr,
@@ -670,14 +692,20 @@ public abstract class EnrollProfile extends BasicProfile
                         if (!valid) {
                             SEQUENCE bpids = getRequestBpids(reqSeq);
                             context.put("identityProof", bpids);
-                            return null;
+
+                            msg = " in verifyIdentityProof";
+                            CMS.debug(method + msg);
+                            throw new EProfileException(CMS.getUserMessage(locale,
+                                    "CMS_POI_VERIFICATION_ERROR")+ msg);
                         }
                     }
 
                     if (id_cmc_idPOPLinkRandom && vals != null) {
-                        OCTET_STRING ostr = (OCTET_STRING) (ASN1Util.decode(OCTET_STRING.getTemplate(),
+                        OCTET_STRING ostr =
+                                (OCTET_STRING) (ASN1Util.decode(OCTET_STRING.getTemplate(),
                                 ASN1Util.encode(vals.elementAt(0))));
                         randomSeed = ostr.toByteArray();
+                        CMS.debug(method + "got randomSeed");
                     }
                 } // numcontrols > 0
             }
@@ -695,19 +723,55 @@ public abstract class EnrollProfile extends BasicProfile
 
             int nummsgs = reqSeq.size();
             if (nummsgs > 0) {
+
                 msgs = new TaggedRequest[reqSeq.size()];
                 SEQUENCE bpids = new SEQUENCE();
+
+                /* TODO: add this in CS.cfg later: cmc.popLinkWitnessRequired=true
+                // enforce popLinkWitness (or V2)
+                boolean popLinkWitnessRequired = true;
+                try {
+                    String configName = "cmc.popLinkWitnessRequired";
+                    CMS.debug(method + "getting :" + configName);
+                    popLinkWitnessRequired = CMS.getConfigStore().getBoolean(configName, true);
+                    CMS.debug(method + "cmc.popLinkWitnessRequired is " + popLinkWitnessRequired);
+                } catch (Exception e) {
+                    // unlikely to get here
+                    msg = method + " Failed to retrieve cmc.popLinkWitnessRequired";
+                    CMS.debug(msg);
+                    throw new EProfileException(method + msg);
+                }
+*/
+
                 boolean valid = true;
                 for (int i = 0; i < nummsgs; i++) {
                     msgs[i] = (TaggedRequest) reqSeq.elementAt(i);
-                    if (!context.containsKey("POPLinkWitness")) {
+                    if (!context.containsKey("POPLinkWitnessV2") &&
+                            !context.containsKey("POPLinkWitness")) {
                         if (randomSeed != null) {
-                            valid = verifyPOPLinkWitness(randomSeed, msgs[i], bpids);
-                            if (!valid || bpids.size() > 0) {
-                                context.put("POPLinkWitness", bpids);
-                                return null;
+                            // verifyPOPLinkWitness() will determine if this is
+                            // POPLinkWitnessV2 or POPLinkWitness
+                            // If failure, context is set in verifyPOPLinkWitness
+                            valid = verifyPOPLinkWitness(ident_s, randomSeed, msgs[i], bpids, context);
+                            if (valid == false) {
+                                if (context.containsKey("POPLinkWitnessV2"))
+                                    msg = " in POPLinkWitnessV2";
+                                else if (context.containsKey("POPLinkWitness"))
+                                    msg = " in POPLinkWitness";
+                                else
+                                    msg = " unspecified failure from verifyPOPLinkWitness";
+
+                                CMS.debug(method + msg);
+                                throw new EProfileException(CMS.getUserMessage(locale,
+                                        "MS_POP_LINK_WITNESS_VERIFICATION_ERROR")+ msg);
                             }
-                        }
+                        /* TODO: for next cmc ticket, eliminate the extra trip of parseCMC if possible, or figure a way out to bypass this on 2nd trip
+                        } else if (popLinkWitnessRequired == true) {
+                            //popLinkWitnessRequired == true, must have randomSeed
+                            CMS.debug(method + "popLinkWitness(V2) required; no randomSeed found");
+                            context.put("POPLinkWitnessV2", bpids);
+                            return null;*/
+                        } //randomSeed != null
                     }
                 }
             } else
@@ -715,8 +779,10 @@ public abstract class EnrollProfile extends BasicProfile
 
             CMS.debug(method + "ends");
             return msgs;
+        } catch (EProfileException e) {
+            throw new EProfileException(e);
         } catch (Exception e) {
-            CMS.debug(method + "Unable to parse CMC request: " + e);
+            CMS.debug(method + e);
             throw new EProfileException(
                     CMS.getUserMessage(locale, "CMS_PROFILE_INVALID_REQUEST"), e);
         }
@@ -782,9 +848,9 @@ public abstract class EnrollProfile extends BasicProfile
         }
 
         byte[] cmc_msg = req.getExtDataInByteArray(IEnrollProfile.CTX_CERT_REQUEST);
-        if (pop_sysPubEncreyptedSession == null) {
+        if (cmc_msg == null) {
             msg = method +
-                    "pop_sysPubEncreyptedSession not found in request:" +
+                    "cmc_msg not found in request:" +
                     reqId.toString();
             CMS.debug(msg);
             return null;
@@ -861,43 +927,125 @@ public abstract class EnrollProfile extends BasicProfile
         return reqId;
     }
 
-    private boolean verifyPOPLinkWitness(byte[] randomSeed, TaggedRequest req,
-            SEQUENCE bpids) {
-        ISharedToken tokenClass = null;
-        boolean sharedSecretFound = true;
-        String name = null;
+    /**
+     * getPopLinkWitnessV2control
+     *
+     * @author cfu
+     */
+    protected PopLinkWitnessV2 getPopLinkWitnessV2control(ASN1Value value) {
+        String method = "EnrollProfile: getPopLinkWitnessV2control: ";
+
+        ByteArrayInputStream bis = new ByteArrayInputStream(
+                ASN1Util.encode(value));
+        PopLinkWitnessV2 popLinkWitnessV2 = null;
+
         try {
-            name = CMS.getConfigStore().getString("cmc.sharedSecret.class");
-        } catch (EPropertyNotFound e) {
-            CMS.debug("EnrollProfile: Failed to find the token class in the configuration file.");
-            sharedSecretFound = false;
-        } catch (EBaseException e) {
-            CMS.debug("EnrollProfile: Failed to find the token class in the configuration file.");
-            sharedSecretFound = false;
+            popLinkWitnessV2 = (PopLinkWitnessV2) (new PopLinkWitnessV2.Template()).decode(bis);
+        } catch (Exception e) {
+            CMS.debug(method + e);
+        }
+        return popLinkWitnessV2;
+    }
+
+    /**
+     * verifyPopLinkWitnessV2
+     *
+     * @author cfu
+     */
+    protected boolean verifyPopLinkWitnessV2(
+            PopLinkWitnessV2 popLinkWitnessV2,
+            byte[] randomSeed,
+            String sharedSecret,
+            String ident_string) {
+        String method = "EnrollProfile: verifyPopLinkWitnessV2: ";
+
+        if ((popLinkWitnessV2 == null) ||
+                (randomSeed == null) ||
+                (sharedSecret == null)) {
+            CMS.debug(method + " method parameters cannot be null");
+            return false;
+        }
+        AlgorithmIdentifier keyGenAlg = popLinkWitnessV2.getKeyGenAlgorithm();
+        AlgorithmIdentifier macAlg = popLinkWitnessV2.getMacAlgorithm();
+        OCTET_STRING witness = popLinkWitnessV2.getWitness();
+        if (keyGenAlg == null) {
+            CMS.debug(method + " keyGenAlg reurned by popLinkWitnessV2.getWitness is null");
+            return false;
+        }
+        if (macAlg == null) {
+            CMS.debug(method + " macAlg reurned by popLinkWitnessV2.getWitness is null");
+            return false;
+        }
+        if (witness == null) {
+            CMS.debug(method + " witness reurned by popLinkWitnessV2.getWitness is null");
+            return false;
         }
 
         try {
-            tokenClass = (ISharedToken) Class.forName(name).newInstance();
-        } catch (ClassNotFoundException e) {
-            CMS.debug("EnrollProfile: Failed to find class name: " + name);
-            sharedSecretFound = false;
-        } catch (InstantiationException e) {
-            CMS.debug("EnrollProfile: Failed to instantiate class: " + name);
-            sharedSecretFound = false;
-        } catch (IllegalAccessException e) {
-            CMS.debug("EnrollProfile: Illegal access: " + name);
+            DigestAlgorithm keyGenAlgID = DigestAlgorithm.fromOID(keyGenAlg.getOID());
+            MessageDigest keyGenMDAlg = MessageDigest.getInstance(keyGenAlgID.toString());
+
+            HMACAlgorithm macAlgID = HMACAlgorithm.fromOID(macAlg.getOID());
+            MessageDigest macMDAlg = MessageDigest
+                    .getInstance(CryptoUtil.getHMACtoMessageDigestName(macAlgID.toString()));
+
+            byte[] witness_bytes = witness.toByteArray();
+            return verifyDigest(
+                    (ident_string != null) ? (sharedSecret + ident_string).getBytes() : sharedSecret.getBytes(),
+                    randomSeed,
+                    witness_bytes,
+                    keyGenMDAlg, macMDAlg);
+        } catch (NoSuchAlgorithmException e) {
+            CMS.debug(method + e);
+            return false;
+        } catch (Exception e) {
+            CMS.debug(method + e);
+            return false;
+        }
+    }
+
+    /*
+     * verifyPOPLinkWitness now handles POPLinkWitnessV2;
+     */
+    private boolean verifyPOPLinkWitness(
+            UTF8String ident, byte[] randomSeed, TaggedRequest req,
+            SEQUENCE bpids, SessionContext context) {
+        String method = "EnrollProfile: verifyPOPLinkWitness: ";
+        CMS.debug(method + "begins.");
+
+        String ident_string = null;
+        if (ident != null) {
+            ident_string = ident.toString();
+        }
+
+        boolean sharedSecretFound = true;
+        String configName = "cmc.sharedSecret.class";
+        String sharedSecret = null;
+        ISharedToken tokenClass = getSharedTokenClass(configName);
+        if (tokenClass == null) {
+            CMS.debug(method + " Failed to retrieve shared secret plugin class");
             sharedSecretFound = false;
+        } else {
+            if (ident_string != null) {
+                sharedSecret = tokenClass.getSharedToken(ident_string);
+            } else {
+                sharedSecret = tokenClass.getSharedToken(mCMCData);
+            }
+            if (sharedSecret == null)
+                sharedSecretFound = false;
         }
 
         INTEGER reqId = null;
         byte[] bv = null;
-        String sharedSecret = null;
-        if (tokenClass != null)
-            sharedSecret = tokenClass.getSharedToken(mCMCData);
+
         if (req.getType().equals(TaggedRequest.PKCS10)) {
+            String methodPos = method + "PKCS10: ";
+            CMS.debug(methodPos + "begins");
+
             TaggedCertificationRequest tcr = req.getTcr();
             if (!sharedSecretFound) {
                 bpids.addElement(tcr.getBodyPartID());
+                context.put("POPLinkWitness", bpids);
                 return false;
             } else {
                 CertificationRequest creq = tcr.getCertificationRequest();
@@ -905,13 +1053,42 @@ public abstract class EnrollProfile extends BasicProfile
                 SET attrs = cinfo.getAttributes();
                 for (int j = 0; j < attrs.size(); j++) {
                     Attribute pkcs10Attr = (Attribute) attrs.elementAt(j);
-                    if (pkcs10Attr.getType().equals(OBJECT_IDENTIFIER.id_cmc_idPOPLinkWitness)) {
+                    if (pkcs10Attr.getType().equals(OBJECT_IDENTIFIER.id_cmc_popLinkWitnessV2)) {
+                        CMS.debug(methodPos + "found id_cmc_popLinkWitnessV2");
+                        if (ident_string == null) {
+                            bpids.addElement(reqId);
+                            context.put("identification", bpids);
+                            context.put("POPLinkWitnessV2", bpids);
+                            String msg = "id_cmc_popLinkWitnessV2 must be accompanied by id_cmc_identification in this server";
+                            CMS.debug(methodPos + msg);
+                            return false;
+                        }
+
+                        SET witnessVal = pkcs10Attr.getValues();
+                        if (witnessVal.size() > 0) {
+                            try {
+                                PopLinkWitnessV2 popLinkWitnessV2 = getPopLinkWitnessV2control(witnessVal.elementAt(0));
+                                boolean valid = verifyPopLinkWitnessV2(popLinkWitnessV2,
+                                        randomSeed,
+                                        sharedSecret,
+                                        ident_string);
+                                if (!valid) {
+                                    bpids.addElement(reqId);
+                                    context.put("POPLinkWitnessV2", bpids);
+                                    return valid;
+                                }
+                                return true;
+                            } catch (Exception ex) {
+                                CMS.debug(methodPos + ex);
+                                return false;
+                            }
+                        }
+                    } else if (pkcs10Attr.getType().equals(OBJECT_IDENTIFIER.id_cmc_idPOPLinkWitness)) {
                         SET witnessVal = pkcs10Attr.getValues();
                         if (witnessVal.size() > 0) {
                             try {
-                                OCTET_STRING str =
-                                        (OCTET_STRING) (ASN1Util.decode(OCTET_STRING.getTemplate(),
-                                                ASN1Util.encode(witnessVal.elementAt(0))));
+                                OCTET_STRING str = (OCTET_STRING) (ASN1Util.decode(OCTET_STRING.getTemplate(),
+                                        ASN1Util.encode(witnessVal.elementAt(0))));
                                 bv = str.toByteArray();
                                 return verifyDigest(sharedSecret.getBytes(),
                                         randomSeed, bv);
@@ -925,27 +1102,55 @@ public abstract class EnrollProfile extends BasicProfile
                 return false;
             }
         } else if (req.getType().equals(TaggedRequest.CRMF)) {
+            String methodPos = method + "CRMF: ";
+            CMS.debug(methodPos + "begins");
+
             CertReqMsg crm = req.getCrm();
             CertRequest certReq = crm.getCertReq();
             reqId = certReq.getCertReqId();
             if (!sharedSecretFound) {
                 bpids.addElement(reqId);
+                context.put("POPLinkWitness", bpids);
                 return false;
             } else {
                 for (int i = 0; i < certReq.numControls(); i++) {
                     AVA ava = certReq.controlAt(i);
 
-                    if (ava.getOID().equals(OBJECT_IDENTIFIER.id_cmc_idPOPLinkWitness)) {
+                    if (ava.getOID().equals(OBJECT_IDENTIFIER.id_cmc_popLinkWitnessV2)) {
+                        CMS.debug(methodPos + "found id_cmc_popLinkWitnessV2");
+                        if (ident_string == null) {
+                            bpids.addElement(reqId);
+                            context.put("identification", bpids);
+                            context.put("POPLinkWitnessV2", bpids);
+                            String msg = "id_cmc_popLinkWitnessV2 must be accompanied by id_cmc_identification in this server";
+                            CMS.debug(methodPos + msg);
+                            return false;
+                        }
+
+                        ASN1Value value = ava.getValue();
+                        PopLinkWitnessV2 popLinkWitnessV2 = getPopLinkWitnessV2control(value);
+
+                        boolean valid = verifyPopLinkWitnessV2(popLinkWitnessV2,
+                                randomSeed,
+                                sharedSecret,
+                                ident_string);
+                        if (!valid) {
+                            bpids.addElement(reqId);
+                            context.put("POPLinkWitnessV2", bpids);
+                            return valid;
+                        }
+                    } else if (ava.getOID().equals(OBJECT_IDENTIFIER.id_cmc_idPOPLinkWitness)) {
+                        CMS.debug(methodPos + "found id_cmc_idPOPLinkWitness");
                         ASN1Value value = ava.getValue();
                         ByteArrayInputStream bis = new ByteArrayInputStream(
                                 ASN1Util.encode(value));
                         OCTET_STRING ostr = null;
                         try {
-                            ostr = (OCTET_STRING)
-                                    (new OCTET_STRING.Template()).decode(bis);
+                            ostr = (OCTET_STRING) (new OCTET_STRING.Template()).decode(bis);
                             bv = ostr.toByteArray();
                         } catch (Exception e) {
                             bpids.addElement(reqId);
+                            context.put("POPLinkWitness", bpids);
                             return false;
                         }
 
@@ -953,6 +1158,7 @@ public abstract class EnrollProfile extends BasicProfile
                                 randomSeed, bv);
                         if (!valid) {
                             bpids.addElement(reqId);
+                            context.put("POPLinkWitness", bpids);
                             return valid;
                         }
                     }
@@ -1006,10 +1212,7 @@ public abstract class EnrollProfile extends BasicProfile
         byte[] finalDigest = null;
         HMACDigest hmacDigest = new HMACDigest(macAlg, key);
         hmacDigest.update(text);
-        if (hmacDigest == null) {
-            CMS.debug(method + " hmacDigest null after hmacDigest.update");
-            return false;
-        }
+
         finalDigest = hmacDigest.digest();
 
         if (finalDigest.length != bv.length) {
@@ -1045,6 +1248,40 @@ public abstract class EnrollProfile extends BasicProfile
         return bpids;
     }
 
+
+    ISharedToken getSharedTokenClass(String configName) {
+        String method = "EnrollProfile: getSharedTokenClass: ";
+        ISharedToken tokenClass = null;
+
+        String name = null;
+        try {
+            CMS.debug(method + "getting :" + configName);
+            name = CMS.getConfigStore().getString(configName);
+            CMS.debug(method + "Shared Secret plugin class name retrieved:" +
+                    name);
+        } catch (Exception e) {
+            CMS.debug(method + " Failed to retrieve shared secret plugin class name");
+            return null;
+        }
+
+        try {
+            tokenClass = (ISharedToken) Class.forName(name).newInstance();
+            CMS.debug(method + "Shared Secret plugin class retrieved");
+        } catch (ClassNotFoundException e) {
+            CMS.debug(method + " Failed to find class name: " + name);
+            return null;
+        } catch (InstantiationException e) {
+            CMS.debug("EnrollProfile: Failed to instantiate class: " + name);
+            return null;
+        } catch (IllegalAccessException e) {
+            CMS.debug(method + " Illegal access: " + name);
+            return null;
+        }
+
+        return tokenClass;
+    }
+
+
     /**
      * verifyIdentityProofV2 handles IdentityProofV2 as defined by RFC5272
      *
@@ -1074,32 +1311,9 @@ public abstract class EnrollProfile extends BasicProfile
             return false;
         }
 
-        String name = null;
-        try {
-            String configName = "cmc.sharedSecret.class";
-            CMS.debug(method + "getting :" + configName);
-            name = CMS.getConfigStore().getString(configName);
-            CMS.debug(method + "Shared Secret plugin class name retrieved:" +
-                    name);
-        } catch (Exception e) {
-            CMS.debug(method + " Failed to retrieve shared secret plugin class name");
-            return false;
-        }
+        String configName = "cmc.sharedSecret.class";
+        ISharedToken tokenClass = getSharedTokenClass(configName);
 
-        ISharedToken tokenClass = null;
-        try {
-            tokenClass = (ISharedToken) Class.forName(name).newInstance();
-            CMS.debug(method + "Shared Secret plugin class retrieved");
-        } catch (ClassNotFoundException e) {
-            CMS.debug(method + " Failed to find class name: " + name);
-            return false;
-        } catch (InstantiationException e) {
-            CMS.debug("EnrollProfile: Failed to instantiate class: " + name);
-            return false;
-        } catch (IllegalAccessException e) {
-            CMS.debug(method + " Illegal access: " + name);
-            return false;
-        }
         if (tokenClass == null) {
             CMS.debug(method + " Failed to retrieve shared secret plugin class");
             return false;
@@ -1120,19 +1334,13 @@ public abstract class EnrollProfile extends BasicProfile
         try {
             IdentityProofV2 idV2val = (IdentityProofV2) (ASN1Util.decode(IdentityProofV2.getTemplate(),
                     ASN1Util.encode(vals.elementAt(0))));
-            /**
-             * TODO: cfu:
-             * phase2: getting configurable allowable hashing and mac algorithms
-             */
 
             DigestAlgorithm hashAlgID = DigestAlgorithm.fromOID(idV2val.getHashAlgID().getOID());
             MessageDigest hashAlg = MessageDigest.getInstance(hashAlgID.toString());
-            // TODO: check against CA allowed algs later
 
             HMACAlgorithm macAlgId = HMACAlgorithm.fromOID(idV2val.getMacAlgId().getOID());
             MessageDigest macAlg = MessageDigest
                     .getInstance(CryptoUtil.getHMACtoMessageDigestName(macAlgId.toString()));
-            // TODO: check against CA allowed algs later
 
             OCTET_STRING witness = idV2val.getWitness();
             if (witness == null) {
@@ -1155,32 +1363,18 @@ public abstract class EnrollProfile extends BasicProfile
     } // verifyIdentityProofV2
 
     private boolean verifyIdentityProof(TaggedAttribute attr, SEQUENCE reqSeq) {
+        String method = "verifyIdentityProof: ";
+
         SET vals = attr.getValues();
         if (vals.size() < 1)
             return false;
-        String name = null;
-        try {
-            name = CMS.getConfigStore().getString("cmc.sharedSecret.class");
-        } catch (EPropertyNotFound e) {
-        } catch (EBaseException e) {
-        }
 
-        if (name == null)
+        String configName = "cmc.sharedSecret.class";
+            ISharedToken tokenClass = getSharedTokenClass(configName);
+        if (tokenClass == null) {
+            CMS.debug(method + " Failed to retrieve shared secret plugin class");
             return false;
-        else {
-            ISharedToken tokenClass = null;
-            try {
-                tokenClass = (ISharedToken) Class.forName(name).newInstance();
-            } catch (ClassNotFoundException e) {
-                CMS.debug("EnrollProfile: Failed to find class name: " + name);
-                return false;
-            } catch (InstantiationException e) {
-                CMS.debug("EnrollProfile: Failed to instantiate class: " + name);
-                return false;
-            } catch (IllegalAccessException e) {
-                CMS.debug("EnrollProfile: Illegal access: " + name);
-                return false;
-            }
+        }
 
             String token = tokenClass.getSharedToken(mCMCData);
             OCTET_STRING ostr = null;
@@ -1188,20 +1382,20 @@ public abstract class EnrollProfile extends BasicProfile
                 ostr = (OCTET_STRING) (ASN1Util.decode(OCTET_STRING.getTemplate(),
                         ASN1Util.encode(vals.elementAt(0))));
             } catch (InvalidBERException e) {
-                CMS.debug("EnrollProfile: Failed to decode the byte value.");
+                CMS.debug(method + "Failed to decode the byte value.");
                 return false;
             }
             byte[] b = ostr.toByteArray();
             byte[] text = ASN1Util.encode(reqSeq);
 
             return verifyDigest(token.getBytes(), text, b);
-        }
     }
 
     public void fillTaggedRequest(Locale locale, TaggedRequest tagreq, X509CertInfo info,
             IRequest req)
             throws EProfileException {
         String method = "EnrollProfile: fillTaggedRequest: ";
+        CMS.debug(method + "begins");
         TaggedRequest.Type type = tagreq.getType();
         if (type == null) {
             CMS.debug(method + "TaggedRequest type == null");
diff --git a/base/server/cms/src/com/netscape/cms/servlet/common/CMCOutputTemplate.java b/base/server/cms/src/com/netscape/cms/servlet/common/CMCOutputTemplate.java
index ac690f2..c130a1e 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/common/CMCOutputTemplate.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/common/CMCOutputTemplate.java
@@ -268,6 +268,18 @@ public class CMCOutputTemplate {
                 controlSeq.addElement(tagattr);
             }
 
+            SEQUENCE POPLinkWitnessV2Bpids = (SEQUENCE) context.get("POPLinkWitnessV2");
+            if (POPLinkWitnessV2Bpids != null && POPLinkWitnessV2Bpids.size() > 0) {
+                OtherInfo otherInfo = new OtherInfo(OtherInfo.FAIL,
+                        new INTEGER(OtherInfo.BAD_REQUEST), null);
+                cmcStatusInfo = new CMCStatusInfo(CMCStatusInfo.FAILED,
+                        POPLinkWitnessV2Bpids, (String) null, otherInfo);
+                tagattr = new TaggedAttribute(
+                        new INTEGER(bpid++),
+                        OBJECT_IDENTIFIER.id_cmc_cMCStatusInfo, cmcStatusInfo);
+                controlSeq.addElement(tagattr);
+            }
+
             SEQUENCE POPLinkWitnessBpids = (SEQUENCE) context.get("POPLinkWitness");
             if (POPLinkWitnessBpids != null && POPLinkWitnessBpids.size() > 0) {
                 OtherInfo otherInfo = new OtherInfo(OtherInfo.FAIL,
diff --git a/base/server/cmsbundle/src/UserMessages.properties b/base/server/cmsbundle/src/UserMessages.properties
index bc7f8cf..bf96f90 100644
--- a/base/server/cmsbundle/src/UserMessages.properties
+++ b/base/server/cmsbundle/src/UserMessages.properties
@@ -306,6 +306,8 @@ CMS_ADMIN_SRVLT_CERT_VALIDATE_FAILED=Imported cert has not been verified to be v
 # ProfileSubmitServlet
 #######################################################
 CMS_POP_VERIFICATION_ERROR=Proof-of-Possession Verification Failed
+CMS_POI_VERIFICATION_ERROR=Proof-of-Identification Verification Failed
+CMS_POP_LINK_WITNESS_VERIFICATION_ERROR=POP Link Witness Verification Failed
 CMS_AUTHENTICATION_AGENT_NAME=Agent Authentication
 CMS_AUTHENTICATION_AGENT_TEXT=This plugin authenticates agents using a certificate.
 CMS_AUTHENTICATION_SSL_CLIENT_NAME=SSL Client Authentication
-- 
2.7.4

