[Pki-devel] [PATCH] 0113..0114 Lightweight CAs: renewal support

Fraser Tweedale ftweedal at redhat.com
Sat Jun 4 12:46:17 UTC 2016


On Sat, Jun 04, 2016 at 10:43:48PM +1000, Fraser Tweedale wrote:
> On Thu, Jun 02, 2016 at 10:28:00PM -0500, Endi Sukma Dewata wrote:
> > On 5/17/2016 12:26 AM, Fraser Tweedale wrote:
> > > Attached patches implement LWCA renewal support
> > > (https://fedorahosted.org/pki/ticket/2327).
> > > 
> > > It includes REST API
> > > 
> > >     POST /ca/rest/authorities/<id>/renew
> > > 
> > > But not implemented in CLI tool yet.  If we decide to make it a
> > > first-class CLI feature (cf certmonger, IPA, etc managing the
> > > renewal) then I'll file the ticket and implement it at that time.
> > > 
> > > Cheers,
> > > Fraser
> > 
> > Some comments:
> > 
> > 1. This is related to patch #111 too. Suppose an authority is
> > added/deleted/renewed in one replica while another replica is down, when the
> > second replica is brought back up will it know that it's missing the changes
> > and be able to update the NSSDB accordingly?
> > 
> > I'm thinking when the server is started there should be a process to
> > synchronize the NSSDB with the authorities in LDAP. Do we have something
> > like that already, or is this not an issue?
> > 
> Nice pickup - this will be an issue (I agree it can be addressed
> later; I'll create a ticket).
> 
> > 2. The locale object for the RenewalProcessor should be obtained from the
> > client, not from the server. See PKIService.getLocale(). In this case you
> > probably need to pass HttpServletRequest to the renewAuthority().
> > 
> > 3. The HttpServletRequest can be used to call processRenewal() as well.
> > 
> > I think #1 can be done separately later. The patches are ACKed assuming #2
> > and #3 are addressed.
> > 
> Updated patch attached.  I pass in the HttpServletRequest to
> processRenewal() and use the authToken from the principal if
> available (I also removed the method signature with the IAuthToken
> argument, which was added in the first patch).
> 
> If you're happy with the updated patch and I'm not around, would you
> kindly merge it on my behalf?
> 
> Thanks for your many reviews this week, Endi.
> Fraser

Now with patches!
-------------- next part --------------
From 7e59d3edfa9ae1e2888e5742618094e32ea9fed9 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Tue, 17 May 2016 12:44:03 +1000
Subject: [PATCH 113/114] Lightweight CAs: renew certs with same issuer

When renewing a certificate, propagate the Authority ID from the
original request to the new request, to ensure that the new
certificate is issued by the same issuer as the original.

Part of: https://fedorahosted.org/pki/ticket/2327
---
 .../cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java    | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java
index b22cc1ce4c28d89bc5380d1ab27d90775245abd5..8efa9162afd37cac8f6ba6dedd056ae36be5ce0e 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java
@@ -214,6 +214,9 @@ public class RenewalProcessor extends CertProcessor {
             String profileId = origReq.getExtDataInString("profileId");
             CMS.debug("RenewalSubmitter: renewal original profileId=" + profileId);
 
+            String aidString = origReq.getExtDataInString(
+                    IEnrollProfile.REQUEST_AUTHORITY_ID);
+
             Integer origSeqNum = origReq.getExtDataInInteger(IEnrollProfile.REQUEST_SEQ_NUM);
             IProfile profile = ps.getProfile(profileId);
             if (profile == null) {
@@ -226,6 +229,10 @@ public class RenewalProcessor extends CertProcessor {
             }
 
             IProfileContext ctx = profile.createContext();
+
+            if (aidString != null)
+                ctx.set(IEnrollProfile.REQUEST_AUTHORITY_ID, aidString);
+
             IProfileAuthenticator authenticator = renewProfile.getAuthenticator();
             IProfileAuthenticator origAuthenticator = profile.getAuthenticator();
 
-- 
2.5.5

-------------- next part --------------
From 722a682fc4675a2f5c6cca0ddec58db47120a991 Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Fri, 13 May 2016 09:00:44 +1000
Subject: [PATCH 114/114] Lightweight CAs: add method to renew certificate

Add the CertificateAuthority.renewAuthority() method that creates
and processes a renewal request for the lightweight CA's signing
cert.  The new certificate replaces the old certificate in the NSSDB
and the serial number is stored in the 'authoritySerial' attribute.

Clones observe when the 'authoritySerial' attribute has changed and
update the certificate in their NSSDB, too.

The renewal behaviour is available in the REST API as a POST to
/ca/rest/authorities/<id>/renew.

Fixes: https://fedorahosted.org/pki/ticket/2327
---
 .../src/com/netscape/ca/CertificateAuthority.java  | 120 ++++++++++++++++++++-
 .../dogtagpki/server/ca/rest/AuthorityService.java |  31 ++++++
 .../certsrv/authority/AuthorityResource.java       |   7 ++
 .../netscape/certsrv/ca/ICertificateAuthority.java |   6 ++
 .../cms/servlet/cert/RenewalProcessor.java         |   9 +-
 5 files changed, 168 insertions(+), 5 deletions(-)

diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java
index 93c5a9fb40a4b1e8f43080f31a04dcfd34f2470c..c9de9f9a58ce91a56043e21d47cd63020b20c085 100644
--- a/base/ca/src/com/netscape/ca/CertificateAuthority.java
+++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java
@@ -94,6 +94,7 @@ import com.netscape.certsrv.ca.ICertificateAuthority;
 import com.netscape.certsrv.ca.IssuerUnavailableException;
 import com.netscape.certsrv.cert.CertEnrollmentRequest;
 import com.netscape.certsrv.dbs.IDBSubsystem;
+import com.netscape.certsrv.dbs.certdb.CertId;
 import com.netscape.certsrv.dbs.certdb.ICertRecord;
 import com.netscape.certsrv.dbs.certdb.ICertificateRepository;
 import com.netscape.certsrv.dbs.crldb.ICRLRepository;
@@ -121,6 +122,7 @@ import com.netscape.certsrv.security.ISigningUnit;
 import com.netscape.certsrv.util.IStatsSubsystem;
 import com.netscape.cms.servlet.cert.CertEnrollmentRequestFactory;
 import com.netscape.cms.servlet.cert.EnrollmentProcessor;
+import com.netscape.cms.servlet.cert.RenewalProcessor;
 import com.netscape.cms.servlet.processors.CAProcessor;
 import com.netscape.cmscore.base.ArgBlock;
 import com.netscape.cmscore.dbs.CRLRepository;
@@ -210,6 +212,7 @@ public class CertificateAuthority
     protected CertificateAuthority hostCA = null;
     protected AuthorityID authorityID = null;
     protected AuthorityID authorityParentID = null;
+    protected BigInteger authoritySerial = null;
     protected String authorityDescription = null;
     protected Collection<String> authorityKeyHosts = null;
     protected boolean authorityEnabled = true;
@@ -346,6 +349,7 @@ public class CertificateAuthority
             X500Name dn,
             AuthorityID aid,
             AuthorityID parentAID,
+            BigInteger serial,
             String signingKeyNickname,
             Collection<String> authorityKeyHosts,
             String authorityDescription,
@@ -360,6 +364,7 @@ public class CertificateAuthority
 
         this.authorityID = aid;
         this.authorityParentID = parentAID;
+        this.authoritySerial = serial;
         this.authorityDescription = authorityDescription;
         this.authorityEnabled = authorityEnabled;
         mNickname = signingKeyNickname;
@@ -526,6 +531,8 @@ public class CertificateAuthority
                 }
             }
 
+            checkForNewerCert();
+
             mUseNonces = mConfig.getBoolean("enableNonces", true);
             mMaxNonces = mConfig.getInteger("maxNumberOfNonces", 100);
 
@@ -604,6 +611,49 @@ public class CertificateAuthority
         }
     }
 
+    private void checkForNewerCert() throws EBaseException {
+        if (authoritySerial == null)
+            return;
+        if (authoritySerial.equals(mCaCert.getSerialNumber()))
+            return;
+
+        // The authoritySerial recorded in LDAP differs from the
+        // certificate in NSSDB.  Import the newer cert.
+        //
+        // Note that the new serial number need not be greater,
+        // e.g. if random serial numbers are enabled.
+        //
+        CMS.debug(
+            "Updating certificate in NSSDB; new serial number: "
+            + authoritySerial);
+        try {
+            X509Certificate oldCert = mCaX509Cert;
+            CryptoManager manager = CryptoManager.getInstance();
+
+            // add new cert
+            X509CertImpl newCert = mCertRepot.getX509Certificate(authoritySerial);
+            manager.importUserCACertPackage(newCert.getEncoded(), mNickname);
+
+            // delete old cert
+            manager.getInternalKeyStorageToken().getCryptoStore()
+                .deleteCert(oldCert);
+
+            // reinit signing unit
+            initSigUnit(false);
+        } catch (CertificateException e) {
+            throw new ECAException("Failed to update certificate", e);
+        } catch (CryptoManager.NotInitializedException e) {
+            throw new ECAException("CryptoManager not initialized", e);
+        } catch (CryptoManager.NicknameConflictException e) {
+            throw new ECAException("Failed to update certificate; nickname conflict", e);
+        } catch (CryptoManager.UserCertConflictException e) {
+            throw new ECAException("Failed to update certificate; user cert conflict", e);
+        } catch (TokenException | NoSuchItemOnTokenException e) {
+            // really shouldn't happen
+            throw new ECAException("Failed to update certificate", e);
+        }
+    }
+
     private String authorityBaseDN() {
         return "ou=authorities,ou=" + getId()
             + "," + getDBSubsystem().getBaseDN();
@@ -2580,6 +2630,8 @@ public class CertificateAuthority
 
         addAuthorityEntry(aid, ldapEntry);
 
+        X509CertImpl cert = null;
+
         try {
             // Generate signing key
             CryptoManager cryptoManager = CryptoManager.getInstance();
@@ -2628,7 +2680,7 @@ public class CertificateAuthority
                 throw new EBaseException("createSubCA: certificate request did not complete; status: " + requestStatus);
 
             // Add certificate to nssdb
-            X509CertImpl cert = request.getExtDataInCert(IEnrollProfile.REQUEST_ISSUED_CERT);
+            cert = request.getExtDataInCert(IEnrollProfile.REQUEST_ISSUED_CERT);
             cryptoManager.importCertPackage(cert.getEncoded(), nickname);
         } catch (Exception e) {
             // something went wrong; delete just-added entry
@@ -2644,11 +2696,65 @@ public class CertificateAuthority
             throw new ECAException("Error creating lightweight CA certificate: " + e);
         }
 
-        return new CertificateAuthority(
+        CertificateAuthority ca = new CertificateAuthority(
             hostCA, subjectX500Name,
-            aid, this.authorityID,
+            aid, this.authorityID, cert.getSerialNumber(),
             nickname, Collections.singleton(thisClone),
             description, true);
+
+        // Update authority record with serial of issued cert
+        LDAPModificationSet mods = new LDAPModificationSet();
+        mods.add(
+            LDAPModification.REPLACE,
+            new LDAPAttribute("authoritySerial", cert.getSerialNumber().toString()));
+        ca.modifyAuthorityEntry(mods);
+
+        return ca;
+    }
+
+    /**
+     * Renew certificate of this CA.
+     */
+    public void renewAuthority(HttpServletRequest httpReq)
+            throws EBaseException {
+        if (
+            authorityParentID != null
+            && !authorityParentID.equals(authorityID)
+        ) {
+            ICertificateAuthority issuer = getCA(authorityParentID);
+            issuer.ensureReady();
+        }
+
+        IProfileSubsystem ps = (IProfileSubsystem)
+            CMS.getSubsystem(IProfileSubsystem.ID);
+        IProfile profile = ps.getProfile("caManualRenewal");
+        CertEnrollmentRequest req = CertEnrollmentRequestFactory.create(
+            new ArgBlock(), profile, httpReq.getLocale());
+        req.setSerialNum(new CertId(mCaCert.getSerialNumber()));
+        RenewalProcessor processor =
+            new RenewalProcessor("renewAuthority", httpReq.getLocale());
+        Map<String, Object> resultMap =
+            processor.processRenewal(req, httpReq, null);
+        IRequest requests[] = (IRequest[]) resultMap.get(CAProcessor.ARG_REQUESTS);
+        IRequest request = requests[0];
+        Integer result = request.getExtDataInInteger(IRequest.RESULT);
+        if (result != null && !result.equals(IRequest.RES_SUCCESS))
+            throw new EBaseException("renewAuthority: certificate renewal submission resulted in error: " + result);
+        RequestStatus requestStatus = request.getRequestStatus();
+        if (requestStatus != RequestStatus.COMPLETE)
+            throw new EBaseException("renewAuthority: certificate renewal did not complete; status: " + requestStatus);
+        X509CertImpl cert = request.getExtDataInCert(IEnrollProfile.REQUEST_ISSUED_CERT);
+        authoritySerial = cert.getSerialNumber();
+
+        // Update authority record with serial of issued cert
+        LDAPModificationSet mods = new LDAPModificationSet();
+        mods.add(
+            LDAPModification.REPLACE,
+            new LDAPAttribute("authoritySerial", authoritySerial.toString()));
+        modifyAuthorityEntry(mods);
+
+        // update cert in NSSDB
+        checkForNewerCert();
     }
 
     /**
@@ -3041,6 +3147,7 @@ public class CertificateAuthority
         LDAPAttribute dnAttr = entry.getAttribute("authorityDN");
         LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID");
         LDAPAttribute parentDNAttr = entry.getAttribute("authorityParentDN");
+        LDAPAttribute serialAttr = entry.getAttribute("authoritySerial");
 
         if (aidAttr == null || nickAttr == null || dnAttr == null) {
             CMS.debug("Malformed authority object; required attribute(s) missing: " + entry.getDN());
@@ -3115,6 +3222,10 @@ public class CertificateAuthority
             parentAID = new AuthorityID((String)
                 parentAIDAttr.getStringValues().nextElement());
 
+        BigInteger serial = null;
+        if (serialAttr != null)
+            serial = new BigInteger(serialAttr.getStringValueArray()[0]);
+
         boolean enabled = true;
         LDAPAttribute enabledAttr = entry.getAttribute("authorityEnabled");
         if (enabledAttr != null) {
@@ -3125,7 +3236,8 @@ public class CertificateAuthority
 
         try {
             CertificateAuthority ca = new CertificateAuthority(
-                hostCA, dn, aid, parentAID, keyNick, keyHosts, desc, enabled);
+                hostCA, dn, aid, parentAID, serial,
+                keyNick, keyHosts, desc, enabled);
             caMap.put(aid, ca);
             entryUSNs.put(aid, newEntryUSN);
             nsUniqueIds.put(aid, nsUniqueId);
diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java
index 199ebef1a30c0cb946731ba448320f33611b3605..0993b5c0d8a831f942720f4e1acf67f59da58fda 100644
--- a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java
@@ -282,6 +282,37 @@ public class AuthorityService extends PKIService implements AuthorityResource {
     }
 
     @Override
+    public Response renewCA(String aidString) {
+        AuthorityID aid = null;
+        try {
+            aid = new AuthorityID(aidString);
+        } catch (IllegalArgumentException e) {
+            throw new BadRequestException("Bad AuthorityID: " + aidString);
+        }
+
+        ICertificateAuthority ca = hostCA.getCA(aid);
+        if (ca == null)
+            throw new ResourceNotFoundException("CA \"" + aidString + "\" not found");
+
+        Map<String, String> auditParams = new LinkedHashMap<>();
+
+        try {
+            ca.renewAuthority(servletRequest);
+            audit(ILogger.SUCCESS, OpDef.OP_MODIFY, aidString, null);
+            return createNoContentResponse();
+        } catch (CADisabledException e) {
+            auditParams.put("exception", e.toString());
+            audit(ILogger.FAILURE, OpDef.OP_MODIFY, aidString, auditParams);
+            throw new ConflictingOperationException(e.toString());
+        } catch (EBaseException e) {
+            CMS.debug(e);
+            auditParams.put("exception", e.toString());
+            audit(ILogger.FAILURE, OpDef.OP_MODIFY, aidString, auditParams);
+            throw new PKIException("Error renewing authority: " + e.toString());
+        }
+    }
+
+    @Override
     public Response deleteCA(String aidString) {
         AuthorityID aid = null;
         try {
diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java
index c6dc696247122b5f07802696c38c2f3517341106..0f8b70ade12c2b4174d5b9880a9c1bbeb35664cf 100644
--- a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java
+++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java
@@ -94,6 +94,13 @@ public interface AuthorityResource {
     @ACLMapping("authorities.modify")
     public Response disableCA(@PathParam("id") String caIDString);
 
+    @POST
+    @Path("{id}/renew")
+    @ClientResponseType(entityType=AuthorityData.class)
+    @AuthMethodMapping("authorities")
+    @ACLMapping("authorities.modify")
+    public Response renewCA(@PathParam("id") String caIDString);
+
     @DELETE
     @Path("{id}")
     @ClientResponseType(entityType=Void.class)
diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java
index dd0d1b0851f03b3df8b69f7595a3524e1f9bd9ba..308bfba126cf56d4cccae59a9a1550e34b926f08 100644
--- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java
+++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java
@@ -598,6 +598,12 @@ public interface ICertificateAuthority extends ISubsystem {
         throws EBaseException;
 
     /**
+     * Renew certificate of CA.
+     */
+    public void renewAuthority(HttpServletRequest httpReq)
+        throws EBaseException;
+
+    /**
      * Delete this lightweight CA.
      */
     public void deleteAuthority()
diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java
index 8efa9162afd37cac8f6ba6dedd056ae36be5ce0e..206d23a5d7898af2e7e93f98080dfa8b009d07ef 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/cert/RenewalProcessor.java
@@ -19,6 +19,7 @@ package com.netscape.cms.servlet.cert;
 
 import java.math.BigInteger;
 import java.security.cert.X509Certificate;
+import java.security.Principal;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.Enumeration;
@@ -51,6 +52,7 @@ import com.netscape.certsrv.registry.IPluginInfo;
 import com.netscape.certsrv.registry.IPluginRegistry;
 import com.netscape.certsrv.request.IRequest;
 import com.netscape.cms.profile.input.SerialNumRenewInput;
+import com.netscape.cms.realm.PKIPrincipal;
 import com.netscape.cms.servlet.common.AuthCredentials;
 import com.netscape.cms.servlet.common.CMSTemplate;
 import com.netscape.cms.servlet.profile.SSLClientCertProvider;
@@ -265,7 +267,12 @@ public class RenewalProcessor extends CertProcessor {
                 context.put("origSubjectDN", origSubjectDN);
 
             // before creating the request, authenticate the request
-            IAuthToken authToken = authenticate(request, origReq, authenticator, context, true, credentials);
+            IAuthToken authToken = null;
+            Principal principal = request.getUserPrincipal();
+            if (principal instanceof PKIPrincipal)
+                authToken = ((PKIPrincipal) principal).getAuthToken();
+            if (authToken == null)
+                authToken = authenticate(request, origReq, authenticator, context, true, credentials);
 
             // authentication success, now authorize
             authorize(profileId, renewProfile, authToken);
-- 
2.5.5



More information about the Pki-devel mailing list