From fd0054572a1769d0dfec2612bd6351a3a74c44c0 Mon Sep 17 00:00:00 2001
From: Endi Sukma Dewata <edewata@redhat.com>
Date: Tue, 8 Jan 2013 20:05:53 +0700
Subject: [PATCH] Added nonce validation for certificate revocation.

The certificate REST service has been modified to validate
nonce when revoking a certificate.

Ticket #213
---
 .../src/com/netscape/certsrv/cert/CertClient.java  |    4 +
 .../src/com/netscape/certsrv/cert/CertData.java    |   17 +++++
 .../com/netscape/certsrv/cert/CertResource.java    |    6 ++
 .../netscape/certsrv/cert/CertRevokeRequest.java   |   18 +++++
 .../com/netscape/cms/servlet/cert/CertService.java |   44 +++++++++++--
 .../com/netscape/cms/servlet/cert/DoRevoke.java    |   72 ++++++--------------
 .../cms/servlet/cert/RevocationProcessor.java      |   54 ++++++++++++++-
 .../netscape/cms/servlet/processors/Processor.java |    2 +
 .../com/netscape/cmstools/cert/CertHoldCLI.java    |    7 +-
 .../com/netscape/cmstools/cert/CertRevokeCLI.java  |    8 ++-
 10 files changed, 166 insertions(+), 66 deletions(-)

diff --git a/base/common/src/com/netscape/certsrv/cert/CertClient.java b/base/common/src/com/netscape/certsrv/cert/CertClient.java
index da60ed085f7de8e8ae79e56156c301d8362d7f49..d8fd346587006003cc486e2b52a437c03a34c585 100644
--- a/base/common/src/com/netscape/certsrv/cert/CertClient.java
+++ b/base/common/src/com/netscape/certsrv/cert/CertClient.java
@@ -52,6 +52,10 @@ public class CertClient extends PKIClient {
         return certClient.getCert(id);
     }
 
+    public CertData reviewCert(CertId id) {
+        return certClient.reviewCert(id);
+    }
+
     public CertDataInfos findCerts(CertSearchRequest data, Integer start, Integer size) {
         return certClient.searchCerts(data, start, size);
     }
diff --git a/base/common/src/com/netscape/certsrv/cert/CertData.java b/base/common/src/com/netscape/certsrv/cert/CertData.java
index 58f7fcd2c91b3115b71db294f3508687faba193f..1c75d8f7c5ed21ad783c18c1c73f16c880f91c83 100644
--- a/base/common/src/com/netscape/certsrv/cert/CertData.java
+++ b/base/common/src/com/netscape/certsrv/cert/CertData.java
@@ -63,6 +63,7 @@ public class CertData {
     String notBefore;
     String notAfter;
     String status;
+    Long nonce;
 
     Link link;
 
@@ -148,6 +149,15 @@ public class CertData {
         this.status = status;
     }
 
+    @XmlElement(name="Nonce")
+    public Long getNonce() {
+        return nonce;
+    }
+
+    public void setNonce(Long nonce) {
+        this.nonce = nonce;
+    }
+
     @XmlElement(name="Link")
     public Link getLink() {
         return link;
@@ -163,6 +173,7 @@ public class CertData {
         int result = 1;
         result = prime * result + ((encoded == null) ? 0 : encoded.hashCode());
         result = prime * result + ((issuerDN == null) ? 0 : issuerDN.hashCode());
+        result = prime * result + ((nonce == null) ? 0 : nonce.hashCode());
         result = prime * result + ((notAfter == null) ? 0 : notAfter.hashCode());
         result = prime * result + ((notBefore == null) ? 0 : notBefore.hashCode());
         result = prime * result + ((pkcs7CertChain == null) ? 0 : pkcs7CertChain.hashCode());
@@ -192,6 +203,11 @@ public class CertData {
                 return false;
         } else if (!issuerDN.equals(other.issuerDN))
             return false;
+        if (nonce == null) {
+            if (other.nonce != null)
+                return false;
+        } else if (!nonce.equals(other.nonce))
+            return false;
         if (notAfter == null) {
             if (other.notAfter != null)
                 return false;
@@ -273,6 +289,7 @@ public class CertData {
         before.setIssuerDN("CN=Test User,UID=testuser,O=EXAMPLE-COM");
         before.setSubjectDN("CN=Test User,UID=testuser,O=EXAMPLE-COM");
         before.setEncoded(sw.toString());
+        before.setNonce(12345l);
 
         String string = before.toString();
         System.out.println(string);
diff --git a/base/common/src/com/netscape/certsrv/cert/CertResource.java b/base/common/src/com/netscape/certsrv/cert/CertResource.java
index 53e06ca6ebdf48ce2b71395648555ef4b7df78e5..17395032d335f4a8f74a5cb2a64b886f5f7492f3 100644
--- a/base/common/src/com/netscape/certsrv/cert/CertResource.java
+++ b/base/common/src/com/netscape/certsrv/cert/CertResource.java
@@ -41,6 +41,12 @@ public interface CertResource {
     @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
     public CertData getCert(@PathParam("id") CertId id);
 
+    @GET
+    @Path("agent/certs/{id}")
+    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
+    @ACLMapping("agent.certs")
+    public CertData reviewCert(@PathParam("id") CertId id);
+
     @POST
     @Path("agent/certs/{id}/revoke-ca")
     @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
diff --git a/base/common/src/com/netscape/certsrv/cert/CertRevokeRequest.java b/base/common/src/com/netscape/certsrv/cert/CertRevokeRequest.java
index 7b86286cedafe224eca05135b3b3080305e49f0d..0fe0c4696e6104506a34989d1a21b07280e4ed9c 100644
--- a/base/common/src/com/netscape/certsrv/cert/CertRevokeRequest.java
+++ b/base/common/src/com/netscape/certsrv/cert/CertRevokeRequest.java
@@ -63,6 +63,7 @@ public class CertRevokeRequest {
     Date invalidityDate;
     String comments;
     String encoded;
+    Long nonce;
 
 
     @XmlElement(name="RequestID")
@@ -118,6 +119,16 @@ public class CertRevokeRequest {
         this.encoded = encoded;
     }
 
+    @XmlElement(name="Nonce")
+    @FormParam("nonce")
+    public Long getNonce() {
+        return nonce;
+    }
+
+    public void setNonce(Long nonce) {
+        this.nonce = nonce;
+    }
+
     @Override
     public int hashCode() {
         final int prime = 31;
@@ -125,6 +136,7 @@ public class CertRevokeRequest {
         result = prime * result + ((comments == null) ? 0 : comments.hashCode());
         result = prime * result + ((encoded == null) ? 0 : encoded.hashCode());
         result = prime * result + ((invalidityDate == null) ? 0 : invalidityDate.hashCode());
+        result = prime * result + ((nonce == null) ? 0 : nonce.hashCode());
         result = prime * result + ((reason == null) ? 0 : reason.hashCode());
         result = prime * result + ((requestID == null) ? 0 : requestID.hashCode());
         return result;
@@ -154,6 +166,11 @@ public class CertRevokeRequest {
                 return false;
         } else if (!invalidityDate.equals(other.invalidityDate))
             return false;
+        if (nonce == null) {
+            if (other.nonce != null)
+                return false;
+        } else if (!nonce.equals(other.nonce))
+            return false;
         if (reason == null) {
             if (other.reason != null)
                 return false;
@@ -194,6 +211,7 @@ public class CertRevokeRequest {
         before.setInvalidityDate(new Date());
         before.setComments("test");
         before.setEncoded("test");
+        before.setNonce(12345l);
 
         String string = before.toString();
         System.out.println(string);
diff --git a/base/common/src/com/netscape/cms/servlet/cert/CertService.java b/base/common/src/com/netscape/cms/servlet/cert/CertService.java
index 9b7b9d45e13d0b8ca093222295526e5dc9d77854..e049710cb6e4d194d2cab1917f2258d8c37ebb35 100644
--- a/base/common/src/com/netscape/cms/servlet/cert/CertService.java
+++ b/base/common/src/com/netscape/cms/servlet/cert/CertService.java
@@ -29,6 +29,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.List;
+import java.util.Random;
 
 import netscape.security.pkcs.ContentInfo;
 import netscape.security.pkcs.PKCS7;
@@ -43,6 +44,7 @@ import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.BadRequestException;
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.ICertPrettyPrint;
+import com.netscape.certsrv.base.Nonces;
 import com.netscape.certsrv.base.PKIException;
 import com.netscape.certsrv.base.UnauthorizedException;
 import com.netscape.certsrv.ca.ICertificateAuthority;
@@ -65,6 +67,7 @@ import com.netscape.certsrv.logging.ILogger;
 import com.netscape.certsrv.request.IRequest;
 import com.netscape.cms.servlet.base.PKIService;
 import com.netscape.cms.servlet.processors.Processor;
+import com.netscape.cmscore.realm.PKIPrincipal;
 import com.netscape.cmsutil.ldap.LDAPUtil;
 import com.netscape.cmsutil.util.Utils;
 
@@ -76,11 +79,17 @@ public class CertService extends PKIService implements CertResource {
 
     ICertificateAuthority authority;
     ICertificateRepository repo;
+    Random random;
+    Nonces nonces;
 
     public final static int DEFAULT_SIZE = 20;
 
     public CertService() {
         authority = (ICertificateAuthority) CMS.getSubsystem("ca");
+        if (authority.noncesEnabled()) {
+            random = new Random();
+            nonces = authority.getNonces();
+        }
         repo = authority.getCertificateRepository();
     }
 
@@ -92,6 +101,15 @@ public class CertService extends PKIService implements CertResource {
 
     @Override
     public CertData getCert(CertId id) {
+        return getCert(id, false);
+    }
+
+    @Override
+    public CertData reviewCert(CertId id) {
+        return getCert(id, true);
+    }
+
+    public CertData getCert(CertId id, boolean generateNonce) {
         validateRequest(id);
 
         CertRetrievalRequest data = new CertRetrievalRequest();
@@ -100,13 +118,13 @@ public class CertService extends PKIService implements CertResource {
         CertData certData = null;
 
         try {
-            certData = getCert(data);
+            certData = getCert(data, generateNonce);
         } catch (EDBRecordNotFoundException e) {
             throw new CertNotFoundException(id);
         } catch (EBaseException e) {
-            throw new PKIException("Problem returning certificate: " + id);
+            throw new PKIException(e.getMessage(), e);
         } catch (CertificateEncodingException e) {
-            throw new PKIException("Problem encoding certificate searched for: " + id);
+            throw new PKIException(e.getMessage(), e);
         }
 
         return certData;
@@ -177,12 +195,20 @@ public class CertService extends PKIService implements CertResource {
                 }
             }
 
+            processor.validateNonce(clientCert, request.getNonce());
+
             // Find target cert record if different from client cert.
             ICertRecord targetRecord = id.equals(clientSerialNumber) ? clientRecord : processor.getCertificateRecord(id);
             X509CertImpl targetCert = targetRecord.getCertificate();
 
             processor.createCRLExtension();
-            processor.validateCertificateToRevoke(clientSubjectDN, targetRecord, caCert);
+
+            PKIPrincipal principal = (PKIPrincipal)servletRequest.getUserPrincipal();
+            // TODO: do not hard-code role name
+            String subjectDN = principal.hasRole("Certificate Manager Agents") ?
+                    null : clientSubjectDN;
+
+            processor.validateCertificateToRevoke(subjectDN, targetRecord, caCert);
             processor.addCertificateToRevoke(targetCert);
             processor.createRevocationRequest();
 
@@ -410,7 +436,7 @@ public class CertService extends PKIService implements CertResource {
         return ret;
     }
 
-    public CertData getCert(CertRetrievalRequest data) throws EBaseException, CertificateEncodingException {
+    public CertData getCert(CertRetrievalRequest data, boolean generateNonce) throws EBaseException, CertificateEncodingException {
         CertId certId = data.getCertId();
 
         //find the cert in question
@@ -444,6 +470,14 @@ public class CertService extends PKIService implements CertResource {
 
         certData.setStatus(record.getStatus());
 
+        if (generateNonce && nonces != null) {
+            long n = random.nextLong();
+            long m = nonces.addNonce(n, Processor.getSSLClientCertificate(servletRequest));
+            if (n + m != 0) {
+                certData.setNonce(m);
+            }
+        }
+
         URI uri = uriInfo.getBaseUriBuilder().path(CertResource.class).path("{id}").build(certId.toHexString());
         certData.setLink(new Link("self", uri));
 
diff --git a/base/common/src/com/netscape/cms/servlet/cert/DoRevoke.java b/base/common/src/com/netscape/cms/servlet/cert/DoRevoke.java
index f45947e8c7548da7186f2d1906de4c69af105780..5d33bf2bcf5b32598086d1867e84f59dcfc1ce31 100644
--- a/base/common/src/com/netscape/cms/servlet/cert/DoRevoke.java
+++ b/base/common/src/com/netscape/cms/servlet/cert/DoRevoke.java
@@ -47,6 +47,7 @@ import com.netscape.certsrv.authority.ICertAuthority;
 import com.netscape.certsrv.authorization.AuthzToken;
 import com.netscape.certsrv.authorization.EAuthzAccessDenied;
 import com.netscape.certsrv.base.EBaseException;
+import com.netscape.certsrv.base.ForbiddenException;
 import com.netscape.certsrv.base.IArgBlock;
 import com.netscape.certsrv.base.Nonces;
 import com.netscape.certsrv.base.PKIException;
@@ -62,10 +63,8 @@ import com.netscape.certsrv.ra.IRegistrationAuthority;
 import com.netscape.certsrv.request.IRequest;
 import com.netscape.certsrv.request.RequestId;
 import com.netscape.certsrv.request.RequestStatus;
-import com.netscape.certsrv.usrgrp.Certificates;
 import com.netscape.certsrv.usrgrp.ICertUserLocator;
 import com.netscape.certsrv.usrgrp.IUGSubsystem;
-import com.netscape.certsrv.usrgrp.IUser;
 import com.netscape.cms.servlet.base.CMSServlet;
 import com.netscape.cms.servlet.common.CMSRequest;
 import com.netscape.cms.servlet.common.CMSTemplate;
@@ -207,47 +206,6 @@ public class DoRevoke extends CMSServlet {
             }
             revokeAll = req.getParameter("revokeAll");
 
-            if (mNonces != null) {
-                boolean nonceVerified = false;
-                boolean skipNonceVerification = false;
-
-                X509Certificate cert2 = getSSLClientCertificate(req);
-                if (cert2 != null) {
-                    X509Certificate certChain[] = new X509Certificate[1];
-                    certChain[0] = cert2;
-                    IUser user = null;
-                    try {
-                        user = mUL.locateUser(new Certificates(certChain));
-                    } catch (Exception e) {
-                        CMS.debug("DoRevoke:  Failed to map certificate '" +
-                                   cert2.getSubjectDN().getName() + "' to user.");
-                    }
-                    if (mUG.isMemberOf(user, "Subsystem Group")) {
-                        skipNonceVerification = true;
-                    }
-                }
-
-                String nonceStr = req.getParameter("nonce");
-                if (nonceStr != null) {
-                    long nonce = Long.parseLong(nonceStr.trim());
-                    X509Certificate cert1 = mNonces.getCertificate(nonce);
-                    if (cert1 == null) {
-                        CMS.debug("DoRevoke:  Unknown nonce");
-                    } else if (cert1 != null && cert2 != null && cert1.equals(cert2)) {
-                        nonceVerified = true;
-                        mNonces.removeNonce(nonce);
-                    }
-                } else {
-                    CMS.debug("DoRevoke:  Missing nonce");
-                }
-                CMS.debug("DoRevoke:  nonceVerified=" + nonceVerified);
-                CMS.debug("DoRevoke:  skipNonceVerification=" + skipNonceVerification);
-                if ((!nonceVerified) && (!skipNonceVerification)) {
-                    cmsReq.setStatus(CMSRequest.UNAUTHORIZED);
-                    return;
-                }
-            }
-
             String comments = req.getParameter(IRequest.REQUESTOR_COMMENTS);
             String eeSubjectDN = null;
             String eeSerialNumber = null;
@@ -279,11 +237,13 @@ public class DoRevoke extends CMSServlet {
 
             if (mAuthMgr != null && mAuthMgr.equals(IAuthSubsystem.CERTUSERDB_AUTHMGR_ID)) {
                 if (authToken != null) {
+                    // Request is from agent.
 
                     String serialNumber = req.getParameter("serialNumber");
                     getSSLClientCertificate(req); // throw exception on error
 
                     if (serialNumber != null) {
+                        // Agent has null subject DN.
                         eeSerialNumber = serialNumber;
                     }
 
@@ -294,7 +254,7 @@ public class DoRevoke extends CMSServlet {
                             " authenticated by " + authMgr;
                 }
             } else {
-                // request is fromUser.
+                // Request is from user.
                 initiative = AuditFormat.FROMUSER;
 
                 String serialNumber = req.getParameter("serialNumber");
@@ -302,7 +262,8 @@ public class DoRevoke extends CMSServlet {
 
                 if (serialNumber == null || sslCert == null ||
                         !(serialNumber.equals(sslCert.getSerialNumber().toString(16)))) {
-                    authorized = false;
+                    throw new ForbiddenException("Invalid serial number.");
+
                 } else {
                     eeSubjectDN = sslCert.getSubjectDN().toString();
                     eeSerialNumber = sslCert.getSerialNumber().toString();
@@ -310,19 +271,21 @@ public class DoRevoke extends CMSServlet {
 
             }
 
-            if (authorized) {
-                BigInteger serialNumber = parseSerialNumber(eeSerialNumber);
+            BigInteger serialNumber = parseSerialNumber(eeSerialNumber);
 
-                process(argSet, header, reason, invalidityDate, initiative,
-                        req, resp, verifiedRecordCount, revokeAll,
-                        totalRecordCount, serialNumber, eeSubjectDN,
-                        comments, locale[0]);
-            }
+            process(argSet, header, reason, invalidityDate, initiative,
+                    req, resp, verifiedRecordCount, revokeAll,
+                    totalRecordCount, serialNumber, eeSubjectDN,
+                    comments, locale[0]);
 
         } catch (NumberFormatException e) {
             log(ILogger.LL_FAILURE,
                     CMS.getLogMessage("BASE_INVALID_NUMBER_FORMAT"));
             error = new EBaseException(CMS.getLogMessage("BASE_INVALID_NUMBER_FORMAT"));
+
+        } catch (ForbiddenException e) {
+            authorized = false;
+
         } catch (EBaseException e) {
             error = e;
         }
@@ -442,6 +405,11 @@ public class DoRevoke extends CMSServlet {
             processor.setAuthority((ICertificateAuthority)mAuthority);
         }
 
+        X509Certificate clientCert = getSSLClientCertificate(req);
+        String requestedNonce = req.getParameter("nonce");
+        Long nonce = requestedNonce == null ? null : new Long(requestedNonce.trim());
+        processor.validateNonce(clientCert, nonce);
+
         try {
             processor.createCRLExtension();
 
diff --git a/base/common/src/com/netscape/cms/servlet/cert/RevocationProcessor.java b/base/common/src/com/netscape/cms/servlet/cert/RevocationProcessor.java
index 36d00459b9377866ba85c25416433c7cab29618b..34131488749534b5597fb79acd2fac2a6ff7d2d5 100644
--- a/base/common/src/com/netscape/cms/servlet/cert/RevocationProcessor.java
+++ b/base/common/src/com/netscape/cms/servlet/cert/RevocationProcessor.java
@@ -38,6 +38,7 @@ import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.base.BadRequestException;
 import com.netscape.certsrv.base.EBaseException;
 import com.netscape.certsrv.base.EPropertyNotFound;
+import com.netscape.certsrv.base.ForbiddenException;
 import com.netscape.certsrv.base.UnauthorizedException;
 import com.netscape.certsrv.ca.ICertificateAuthority;
 import com.netscape.certsrv.dbs.certdb.CertId;
@@ -50,6 +51,8 @@ import com.netscape.certsrv.request.IRequest;
 import com.netscape.certsrv.request.IRequestQueue;
 import com.netscape.certsrv.request.RequestId;
 import com.netscape.certsrv.request.RequestStatus;
+import com.netscape.certsrv.usrgrp.Certificates;
+import com.netscape.certsrv.usrgrp.IUser;
 
 /**
  * @author Endi S. Dewata
@@ -185,14 +188,59 @@ public class RevocationProcessor extends CertProcessor {
         return request;
     }
 
-    public void validateCertificateToRevoke(String clientSubjectDN, ICertRecord targetRecord, boolean revokingCACert) {
+    public void validateNonce(X509Certificate clientCert, Long nonce) {
+
+        if (nonces != null) {
+            boolean nonceVerified = false;
+            boolean skipNonceVerification = false;
+
+            if (clientCert != null) {
+                X509Certificate certChain[] = new X509Certificate[1];
+                certChain[0] = clientCert;
+                IUser user = null;
+                try {
+                    user = ul.locateUser(new Certificates(certChain));
+                } catch (Exception e) {
+                    CMS.debug("RevocationProcessor:  Failed to map certificate '" +
+                            clientCert.getSubjectDN().getName() + "' to user.");
+                }
+                if (ug.isMemberOf(user, "Subsystem Group")) {
+                    skipNonceVerification = true;
+                }
+            }
+
+            if (nonce != null) {
+                X509Certificate storedCert = nonces.getCertificate(nonce);
+                if (storedCert == null) {
+                    CMS.debug("RevocationProcessor:  Unknown nonce");
+
+                } else if (clientCert != null && storedCert.equals(clientCert)) {
+                    nonceVerified = true;
+                    nonces.removeNonce(nonce);
+                }
+            } else {
+                CMS.debug("RevocationProcessor:  Missing nonce");
+            }
+
+            CMS.debug("RevocationProcessor:  nonceVerified=" + nonceVerified);
+            CMS.debug("RevocationProcessor:  skipNonceVerification=" + skipNonceVerification);
+            if ((!nonceVerified) && (!skipNonceVerification)) {
+                throw new ForbiddenException("Invalid nonce.");
+            }
+        }
+
+    }
+
+    public void validateCertificateToRevoke(String subjectDN, ICertRecord targetRecord, boolean revokingCACert) {
 
         X509CertImpl targetCert = targetRecord.getCertificate();
         BigInteger targetSerialNumber = targetCert.getSerialNumber();
         Principal targetSubjectDN = targetCert.getSubjectDN();
 
-        // Verify client cert's subject DN matches the target cert's subject DN.
-        if (clientSubjectDN != null && !clientSubjectDN.equals(targetSubjectDN.toString())) {
+        // Verify the subject DN matches the target cert's subject DN.
+        // Agent has null subject DN so he can revoke any certificate.
+        // Other users can only revoke their own certificate.
+        if (subjectDN != null && !subjectDN.equals(targetSubjectDN.toString())) {
             throw new UnauthorizedException(
                     "Certificate 0x" + targetSerialNumber.toString(16) + " belongs to different subject.");
         }
diff --git a/base/common/src/com/netscape/cms/servlet/processors/Processor.java b/base/common/src/com/netscape/cms/servlet/processors/Processor.java
index fdbc85dd5de3389b1584aa35623d7e88a6d34783..423d1bed539e2447769f9acfa3a3aef8c093e4eb 100644
--- a/base/common/src/com/netscape/cms/servlet/processors/Processor.java
+++ b/base/common/src/com/netscape/cms/servlet/processors/Processor.java
@@ -59,6 +59,7 @@ import com.netscape.certsrv.profile.IProfileSubsystem;
 import com.netscape.certsrv.request.IRequest;
 import com.netscape.certsrv.request.IRequestQueue;
 import com.netscape.certsrv.request.RequestId;
+import com.netscape.certsrv.usrgrp.ICertUserLocator;
 import com.netscape.certsrv.usrgrp.IGroup;
 import com.netscape.certsrv.usrgrp.IUGSubsystem;
 import com.netscape.certsrv.util.IStatsSubsystem;
@@ -144,6 +145,7 @@ public class Processor {
     protected ICertificateAuthority authority = (ICertificateAuthority) CMS.getSubsystem("ca");
     protected IAuthzSubsystem authz = (IAuthzSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_AUTHZ);
     protected IUGSubsystem ug = (IUGSubsystem) CMS.getSubsystem(CMS.SUBSYSTEM_UG);
+    protected ICertUserLocator ul = ug.getCertUserLocator();
     protected IRequestQueue queue;
     protected IProfileSubsystem ps;
     protected ICertificateRepository certdb;
diff --git a/base/java-tools/src/com/netscape/cmstools/cert/CertHoldCLI.java b/base/java-tools/src/com/netscape/cmstools/cert/CertHoldCLI.java
index b5c604a86f21f9ef88b975698a797ee53f59cc51..a112d3616cfe70f612171dfb0a6576630c3df4b2 100644
--- a/base/java-tools/src/com/netscape/cmstools/cert/CertHoldCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/cert/CertHoldCLI.java
@@ -77,14 +77,14 @@ public class CertHoldCLI extends CLI {
         }
 
         CertId certID = new CertId(cmdArgs[0]);
+        CertData certData = parent.client.reviewCert(certID);
 
         if (!cmd.hasOption("force")) {
 
-            CertData certData = parent.client.getCert(certID);
-
             System.out.println("Placing certificate on-hold:");
 
             CertCLI.printCertData(certData, false, false);
+            if (verbose) System.out.println("  Nonce: " + certData.getNonce());
 
             System.out.print("Are you sure (Y/N)? ");
             System.out.flush();
@@ -99,6 +99,7 @@ public class CertHoldCLI extends CLI {
         CertRevokeRequest request = new CertRevokeRequest();
         request.setReason(RevocationReason.CERTIFICATE_HOLD);
         request.setComments(cmd.getOptionValue("comments"));
+        request.setNonce(certData.getNonce());
 
         CertRequestInfo certRequestInfo = parent.client.revokeCert(certID, request);
 
@@ -108,7 +109,7 @@ public class CertHoldCLI extends CLI {
 
         if (certRequestInfo.getRequestStatus() == RequestStatus.COMPLETE) {
             MainCLI.printMessage("Placed certificate \"" + certID.toHexString() + "\" on-hold");
-            CertData certData = parent.client.getCert(certID);
+            certData = parent.client.getCert(certID);
             CertCLI.printCertData(certData, false, false);
 
         } else {
diff --git a/base/java-tools/src/com/netscape/cmstools/cert/CertRevokeCLI.java b/base/java-tools/src/com/netscape/cmstools/cert/CertRevokeCLI.java
index d8a4d5295cb723e0916253e96dcf06cd305661db..1a867d079b843e80acf018316d887b426363dea9 100644
--- a/base/java-tools/src/com/netscape/cmstools/cert/CertRevokeCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/cert/CertRevokeCLI.java
@@ -105,10 +105,10 @@ public class CertRevokeCLI extends CLI {
             return;
         }
 
+        CertData certData = parent.client.reviewCert(certID);
+
         if (!cmd.hasOption("force")) {
 
-            CertData certData = parent.client.getCert(certID);
-
             if (reason == RevocationReason.CERTIFICATE_HOLD) {
                 System.out.println("Placing certificate on-hold:");
             } else if (reason == RevocationReason.REMOVE_FROM_CRL) {
@@ -118,6 +118,7 @@ public class CertRevokeCLI extends CLI {
             }
 
             CertCLI.printCertData(certData, false, false);
+            if (verbose) System.out.println("  Nonce: " + certData.getNonce());
 
             System.out.print("Are you sure (Y/N)? ");
             System.out.flush();
@@ -132,6 +133,7 @@ public class CertRevokeCLI extends CLI {
         CertRevokeRequest request = new CertRevokeRequest();
         request.setReason(reason);
         request.setComments(cmd.getOptionValue("comments"));
+        request.setNonce(certData.getNonce());
 
         CertRequestInfo certRequestInfo;
 
@@ -154,7 +156,7 @@ public class CertRevokeCLI extends CLI {
                 MainCLI.printMessage("Revoked certificate \"" + certID.toHexString() + "\"");
             }
 
-            CertData certData = parent.client.getCert(certID);
+            certData = parent.client.getCert(certID);
             CertCLI.printCertData(certData, false, false);
 
         } else {
-- 
1.7.7.1.msysgit.0

