From 0493625975c6aa03fd58eaba343e1acbd105fbe5 Mon Sep 17 00:00:00 2001 From: Marco Fargetta Date: Wed, 15 Nov 2023 20:07:35 +0100 Subject: [PATCH 1/4] Implement GET verb for /ca/v2/certs/ REST end-point This replicate the current behaviour imeplemented in /ca/rest/certs/. --- .../org/dogtagpki/server/ca/CAServlet.java | 19 ++ .../dogtagpki/server/ca/v2/CAInfoServlet.java | 9 + .../dogtagpki/server/ca/v2/CertServlet.java | 261 ++++++++++++++++++ .../netscape/certsrv/cert/CertDataInfos.java | 3 +- 4 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java diff --git a/base/ca/src/main/java/org/dogtagpki/server/ca/CAServlet.java b/base/ca/src/main/java/org/dogtagpki/server/ca/CAServlet.java index 6596b49c57e..f125dd67003 100644 --- a/base/ca/src/main/java/org/dogtagpki/server/ca/CAServlet.java +++ b/base/ca/src/main/java/org/dogtagpki/server/ca/CAServlet.java @@ -13,6 +13,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +/** + * @author Marco Fargetta + */ public class CAServlet extends HttpServlet { public static final long serialVersionUID = 1L; @@ -20,6 +23,9 @@ public class CAServlet extends HttpServlet { public void get(HttpServletRequest request, HttpServletResponse response) throws Exception { } + public void post(HttpServletRequest request, HttpServletResponse response) throws Exception { + } + @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { @@ -33,6 +39,19 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro } } + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + try { + post(request, response); + + } catch (ServletException | IOException e) { + throw e; + + } catch (Exception e) { + throw new ServletException(e); + } + } + public CAEngine getCAEngine() { ServletContext servletContext = getServletContext(); return (CAEngine) servletContext.getAttribute("engine"); diff --git a/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CAInfoServlet.java b/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CAInfoServlet.java index b4205c15737..548fbd78761 100644 --- a/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CAInfoServlet.java +++ b/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CAInfoServlet.java @@ -1,3 +1,8 @@ +// +// Copyright Red Hat, Inc. +// +// SPDX-License-Identifier: GPL-2.0-or-later +// package org.dogtagpki.server.ca.v2; import java.io.PrintWriter; @@ -13,11 +18,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * @author Marco Fargetta + */ @WebServlet("/v2/info") public class CAInfoServlet extends CAServlet { private static final long serialVersionUID = 1L; private static Logger logger = LoggerFactory.getLogger(CAInfoServlet.class); + @Override public void get(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpSession session = request.getSession(); logger.debug("CAInfoServlet.get(): session: " + session.getId()); diff --git a/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java b/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java new file mode 100644 index 00000000000..e7db60c0597 --- /dev/null +++ b/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java @@ -0,0 +1,261 @@ +// +// Copyright Red Hat, Inc. +// +// SPDX-License-Identifier: GPL-2.0-or-later +// +package org.dogtagpki.server.ca.v2; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.security.InvalidKeyException; +import java.security.Principal; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Date; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.dogtagpki.server.ca.CAEngine; +import org.dogtagpki.server.ca.CAServlet; +import org.dogtagpki.util.cert.CertUtil; +import org.mozilla.jss.netscape.security.pkcs.ContentInfo; +import org.mozilla.jss.netscape.security.pkcs.PKCS7; +import org.mozilla.jss.netscape.security.pkcs.SignerInfo; +import org.mozilla.jss.netscape.security.provider.RSAPublicKey; +import org.mozilla.jss.netscape.security.util.CertPrettyPrint; +import org.mozilla.jss.netscape.security.util.Utils; +import org.mozilla.jss.netscape.security.x509.AlgorithmId; +import org.mozilla.jss.netscape.security.x509.CRLExtensions; +import org.mozilla.jss.netscape.security.x509.CRLReasonExtension; +import org.mozilla.jss.netscape.security.x509.X509CertImpl; +import org.mozilla.jss.netscape.security.x509.X509ExtensionException; +import org.mozilla.jss.netscape.security.x509.X509Key; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.cert.CertData; +import com.netscape.certsrv.cert.CertDataInfo; +import com.netscape.certsrv.cert.CertDataInfos; +import com.netscape.certsrv.dbs.certdb.CertId; +import com.netscape.cmscore.dbs.CertRecord; +import com.netscape.cmscore.dbs.CertificateRepository; +import com.netscape.cmscore.dbs.RevocationInfo; +import com.netscape.cmsutil.ldap.LDAPUtil; + +/** + * @author Marco Fargetta + */ +@WebServlet("/v2/certs/*") +public class CertServlet extends CAServlet { + public static final int DEFAULT_MAXTIME = 0; + public static final int DEFAULT_MAXRESULTS = 20; + public final static int DEFAULT_SIZE = 20; + + private static final long serialVersionUID = 1L; + private static Logger logger = LoggerFactory.getLogger(CertServlet.class); + + @Override + public void get(HttpServletRequest request, HttpServletResponse response) throws Exception { + HttpSession session = request.getSession(); + logger.debug("CertServlet.get(): session: {}", session.getId()); + + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + if(request.getPathInfo() != null) { + CertId id = new CertId(request.getPathInfo().substring(1)); + CertData cert; + try { + cert = getCertData(id, request.getLocale()); + out.println(cert.toJSON()); + } catch (Exception e) { + response.sendError(HttpServletResponse.SC_NOT_FOUND, request.getRequestURI()); + } + return; + } + + int maxResults = request.getParameter("maxResults") == null ? + DEFAULT_MAXRESULTS : Integer.parseInt(request.getParameter("maxResults")); + int maxTime = request.getParameter("maxTime") == null ? + DEFAULT_MAXTIME : Integer.parseInt(request.getParameter("maxTime")); + int size = request.getParameter("size") == null ? + DEFAULT_SIZE : Integer.parseInt(request.getParameter("size")); + int start = request.getParameter("start") == null ? 0 : Integer.parseInt(request.getParameter("start")); + + CertDataInfos infos = listCerts(request.getParameter("status"), maxResults, maxTime, start, size); + out.println(infos.toJSON()); + } + + @Override + public void post(HttpServletRequest request, HttpServletResponse response) throws Exception { + HttpSession session = request.getSession(); + logger.debug("CertServlet.post(): session: {}", session.getId()); + + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + + if(request.getPathInfo() == null || !request.getPathInfo().equals("/search")) { + response.sendError(HttpServletResponse.SC_NOT_FOUND, request.getRequestURI()); + return; + } + + CertDataInfos infos = new CertDataInfos(); + out.println(infos.toString()); + } + + private CertData getCertData(CertId id, Locale loc) throws Exception { + CAEngine engine = getCAEngine(); + CertificateRepository repo = engine.getCertificateRepository(); + + //find the cert in question + CertRecord record = repo.readCertificateRecord(id.toBigInteger()); + X509CertImpl cert = record.getCertificate(); + + CertData certData = new CertData(); + certData.setSerialNumber(id); + + Principal issuerDN = cert.getIssuerName(); + if (issuerDN != null) certData.setIssuerDN(issuerDN.toString()); + + Principal subjectDN = cert.getSubjectName(); + if (subjectDN != null) certData.setSubjectDN(subjectDN.toString()); + + String base64 = CertUtil.toPEM(cert); + certData.setEncoded(base64); + + CertPrettyPrint print = new CertPrettyPrint(cert); + certData.setPrettyPrint(print.toString(loc)); + + X509Certificate[] certChain = engine.getCertChain(cert); + + PKCS7 pkcs7 = new PKCS7( + new AlgorithmId[0], + new ContentInfo(new byte[0]), + certChain, + new SignerInfo[0]); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + pkcs7.encodeSignedData(bos, false); + byte[] p7Bytes = bos.toByteArray(); + + String p7Str = Utils.base64encode(p7Bytes, true); + certData.setPkcs7CertChain(p7Str); + + Date notBefore = cert.getNotBefore(); + if (notBefore != null) certData.setNotBefore(notBefore.toString()); + + Date notAfter = cert.getNotAfter(); + if (notAfter != null) certData.setNotAfter(notAfter.toString()); + + certData.setRevokedOn(record.getRevokedOn()); + certData.setRevokedBy(record.getRevokedBy()); + + RevocationInfo revInfo = record.getRevocationInfo(); + if (revInfo != null) { + CRLExtensions revExts = revInfo.getCRLEntryExtensions(); + if (revExts != null) { + try { + CRLReasonExtension ext = (CRLReasonExtension) + revExts.get(CRLReasonExtension.NAME); + certData.setRevocationReason(ext.getReason().getCode()); + } catch (X509ExtensionException e) { + // nothing to do + } + } + } + + certData.setStatus(record.getStatus()); + + return certData; + } + + private CertDataInfos listCerts(String status, int maxResults, int maxTime, int start, int size) { + CAEngine engine = getCAEngine(); + CertificateRepository repo = engine.getCertificateRepository(); + + logger.info("Listing certificates"); + + String filter; + if (status == null) { + filter = "(certstatus=*)"; + + } else { + filter = "(certStatus=" + LDAPUtil.escapeFilter(status) + ")"; + } + logger.info("Search filter: " + filter); + + CertDataInfos infos = new CertDataInfos(); + try { + Enumeration e = repo.searchCertificates(filter, maxResults, maxTime); + if (e == null) { + throw new EBaseException("search results are null"); + } + + // store non-null results in a list + List results = new ArrayList<>(); + while (e.hasMoreElements()) { + CertRecord rec = e.nextElement(); + if (rec == null) continue; + results.add(createCertDataInfo(rec)); + } + + int total = results.size(); + logger.info("Search results: " + total); + infos.setTotal(total); + + // return entries in the requested page + for (int i = start; i < start + size && i < total ; i++) { + infos.addEntry(results.get(i)); + } + } catch (Exception e) { + logger.error("Unable to list certificates: " + e.getMessage(), e); + throw new PKIException("Unable to list certificates: " + e.getMessage(), e); + } + + return infos; + } + + private CertDataInfo createCertDataInfo(CertRecord record) throws EBaseException, InvalidKeyException { + CertDataInfo info = new CertDataInfo(); + + CertId id = new CertId(record.getSerialNumber()); + info.setID(id); + + X509Certificate cert = record.getCertificate(); + info.setIssuerDN(cert.getIssuerDN().toString()); + info.setSubjectDN(cert.getSubjectDN().toString()); + info.setStatus(record.getStatus()); + info.setVersion(cert.getVersion()); + info.setType(cert.getType()); + + PublicKey key = cert.getPublicKey(); + if (key instanceof X509Key) { + X509Key x509Key = (X509Key)key; + info.setKeyAlgorithmOID(x509Key.getAlgorithmId().getOID().toString()); + + if (x509Key.getAlgorithmId().toString().equalsIgnoreCase("RSA")) { + RSAPublicKey rsaKey = new RSAPublicKey(x509Key.getEncoded()); + info.setKeyLength(rsaKey.getKeySize()); + } + } + + info.setNotValidBefore(cert.getNotBefore()); + info.setNotValidAfter(cert.getNotAfter()); + + info.setIssuedOn(record.getCreateTime()); + info.setIssuedBy(record.getIssuedBy()); + + info.setRevokedOn(record.getRevokedOn()); + info.setRevokedBy(record.getRevokedBy()); + + return info; + } +} diff --git a/base/common/src/main/java/com/netscape/certsrv/cert/CertDataInfos.java b/base/common/src/main/java/com/netscape/certsrv/cert/CertDataInfos.java index dc0ca8b8668..e373d563c3d 100644 --- a/base/common/src/main/java/com/netscape/certsrv/cert/CertDataInfos.java +++ b/base/common/src/main/java/com/netscape/certsrv/cert/CertDataInfos.java @@ -38,10 +38,11 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.netscape.certsrv.base.DataCollection; +import com.netscape.certsrv.util.JSONSerializer; @JsonInclude(Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown=true) -public class CertDataInfos extends DataCollection { +public class CertDataInfos extends DataCollection implements JSONSerializer { public Element toDOM(Document document) { From 550276085c38090219ffbe562a882a4102751f86 Mon Sep 17 00:00:00 2001 From: Marco Fargetta Date: Thu, 7 Dec 2023 19:02:04 +0100 Subject: [PATCH 2/4] Implement v2 REST end-point for /ca/v2/certs Implement the REST operations: - GET of /ca/v2/certs - POST to /ca/v2/certs/search. These implement the behaviour of the current REST operations (see [1, 2]). The new version is not based on RESTeasy framework for the REST operation and the DB searches have replaced VLV with paged queries. The only differences with the previous implementation are: - the total value now show the number of returned item; - the number of returned entry is limited (this is hard-coded for now). 1. https://github.com/dogtagpki/pki/wiki/CA-List-Certificates-REST-API 2. https://github.com/dogtagpki/pki/wiki/CA-Search-Certificates-REST-API --- .../cmscore/dbs/CertificateRepository.java | 29 ++++ .../org/dogtagpki/server/ca/CAServlet.java | 2 + .../dogtagpki/server/ca/v2/CertServlet.java | 59 +++---- .../certsrv/cert/CertSearchRequest.java | 144 ++++++++++++++++++ .../com/netscape/cmscore/dbs/DBSSession.java | 44 +++++- .../com/netscape/cmscore/dbs/LDAPSession.java | 57 +++++++ 6 files changed, 307 insertions(+), 28 deletions(-) diff --git a/base/ca/src/main/java/com/netscape/cmscore/dbs/CertificateRepository.java b/base/ca/src/main/java/com/netscape/cmscore/dbs/CertificateRepository.java index 93a8b13def4..6721ea4fae2 100644 --- a/base/ca/src/main/java/com/netscape/cmscore/dbs/CertificateRepository.java +++ b/base/ca/src/main/java/com/netscape/cmscore/dbs/CertificateRepository.java @@ -19,10 +19,12 @@ import java.math.BigInteger; import java.security.SecureRandom; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; +import java.util.Iterator; import java.util.Vector; import org.mozilla.jss.netscape.security.x509.CertificateValidity; @@ -1112,6 +1114,33 @@ public Enumeration searchCertificates(String filter, int maxSize, } + /** + * Finds a list of certificate records that satisfies the filter. + * + * The filter should follow RFC1558 LDAP filter syntax. + * For example, + * + * {@Code (&(certRecordId=5)(x509Cert.notBefore=934398398))} + * + * @param filter search filter + * @param maxSize max size to return + * @return a list of certificates + * @exception EBaseException failed to search + */ + public Iterator searchCertificates(String filter, int timeLimit, int start, int size) + throws EBaseException { + + ArrayList records = new ArrayList<>(); + logger.debug("searchCertificates filter {filter}, start {start} and size {size}", filter, start, size); + try (DBSSession s = dbSubsystem.createSession()) { + DBSearchResults sr = s.pagedSearch(mBaseDN, filter, start, size); + while (sr.hasMoreElements()) { + records.add((CertRecord) sr.nextElement()); + } + } + return records.iterator(); + } + /** * Finds certificate records. diff --git a/base/ca/src/main/java/org/dogtagpki/server/ca/CAServlet.java b/base/ca/src/main/java/org/dogtagpki/server/ca/CAServlet.java index f125dd67003..19ee3e139e4 100644 --- a/base/ca/src/main/java/org/dogtagpki/server/ca/CAServlet.java +++ b/base/ca/src/main/java/org/dogtagpki/server/ca/CAServlet.java @@ -21,9 +21,11 @@ public class CAServlet extends HttpServlet { public void get(HttpServletRequest request, HttpServletResponse response) throws Exception { + response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } public void post(HttpServletRequest request, HttpServletResponse response) throws Exception { + response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } @Override diff --git a/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java b/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java index e7db60c0597..16bff247683 100644 --- a/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java +++ b/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java @@ -5,6 +5,7 @@ // package org.dogtagpki.server.ca.v2; +import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.security.InvalidKeyException; @@ -13,9 +14,10 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Date; -import java.util.Enumeration; +import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; @@ -45,11 +47,13 @@ import com.netscape.certsrv.cert.CertData; import com.netscape.certsrv.cert.CertDataInfo; import com.netscape.certsrv.cert.CertDataInfos; +import com.netscape.certsrv.cert.CertSearchRequest; import com.netscape.certsrv.dbs.certdb.CertId; +import com.netscape.certsrv.util.JSONSerializer; +import com.netscape.cms.servlet.cert.FilterBuilder; import com.netscape.cmscore.dbs.CertRecord; import com.netscape.cmscore.dbs.CertificateRepository; import com.netscape.cmscore.dbs.RevocationInfo; -import com.netscape.cmsutil.ldap.LDAPUtil; /** * @author Marco Fargetta @@ -57,7 +61,6 @@ @WebServlet("/v2/certs/*") public class CertServlet extends CAServlet { public static final int DEFAULT_MAXTIME = 0; - public static final int DEFAULT_MAXRESULTS = 20; public final static int DEFAULT_SIZE = 20; private static final long serialVersionUID = 1L; @@ -82,15 +85,14 @@ public void get(HttpServletRequest request, HttpServletResponse response) throws return; } - int maxResults = request.getParameter("maxResults") == null ? - DEFAULT_MAXRESULTS : Integer.parseInt(request.getParameter("maxResults")); int maxTime = request.getParameter("maxTime") == null ? DEFAULT_MAXTIME : Integer.parseInt(request.getParameter("maxTime")); int size = request.getParameter("size") == null ? DEFAULT_SIZE : Integer.parseInt(request.getParameter("size")); int start = request.getParameter("start") == null ? 0 : Integer.parseInt(request.getParameter("start")); - CertDataInfos infos = listCerts(request.getParameter("status"), maxResults, maxTime, start, size); + CertSearchRequest searchElems = CertSearchRequest.fromMap(request.getParameterMap()); + CertDataInfos infos = listCerts(searchElems, maxTime, start, size); out.println(infos.toJSON()); } @@ -99,16 +101,24 @@ public void post(HttpServletRequest request, HttpServletResponse response) throw HttpSession session = request.getSession(); logger.debug("CertServlet.post(): session: {}", session.getId()); - response.setContentType("application/json"); - PrintWriter out = response.getWriter(); - if(request.getPathInfo() == null || !request.getPathInfo().equals("/search")) { response.sendError(HttpServletResponse.SC_NOT_FOUND, request.getRequestURI()); return; } - CertDataInfos infos = new CertDataInfos(); - out.println(infos.toString()); + BufferedReader reader = request.getReader(); + String postMessage = reader.lines().collect(Collectors.joining()); + + CertSearchRequest requestFilter = JSONSerializer.fromJSON(postMessage, CertSearchRequest.class); + int size = request.getParameter("size") == null ? + DEFAULT_SIZE : Integer.parseInt(request.getParameter("size")); + int start = request.getParameter("start") == null ? 0 : Integer.parseInt(request.getParameter("start")); + + CertDataInfos infos = listCerts(requestFilter, start, size); + + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + out.println(infos.toJSON()); } private CertData getCertData(CertId id, Locale loc) throws Exception { @@ -177,32 +187,31 @@ private CertData getCertData(CertId id, Locale loc) throws Exception { return certData; } - private CertDataInfos listCerts(String status, int maxResults, int maxTime, int start, int size) { + private CertDataInfos listCerts(CertSearchRequest searchReq, int start, int size) { + return listCerts(searchReq, -1, start, size); + } + + private CertDataInfos listCerts(CertSearchRequest searchReq, int maxTime, int start, int size) { CAEngine engine = getCAEngine(); CertificateRepository repo = engine.getCertificateRepository(); logger.info("Listing certificates"); + FilterBuilder builder = new FilterBuilder(searchReq); + String filter = builder.buildFilter(); - String filter; - if (status == null) { - filter = "(certstatus=*)"; - - } else { - filter = "(certStatus=" + LDAPUtil.escapeFilter(status) + ")"; - } logger.info("Search filter: " + filter); CertDataInfos infos = new CertDataInfos(); try { - Enumeration e = repo.searchCertificates(filter, maxResults, maxTime); + Iterator e = repo.searchCertificates(filter, maxTime, start, size); if (e == null) { throw new EBaseException("search results are null"); } // store non-null results in a list List results = new ArrayList<>(); - while (e.hasMoreElements()) { - CertRecord rec = e.nextElement(); + while (e.hasNext()) { + CertRecord rec = e.next(); if (rec == null) continue; results.add(createCertDataInfo(rec)); } @@ -210,11 +219,7 @@ private CertDataInfos listCerts(String status, int maxResults, int maxTime, int int total = results.size(); logger.info("Search results: " + total); infos.setTotal(total); - - // return entries in the requested page - for (int i = start; i < start + size && i < total ; i++) { - infos.addEntry(results.get(i)); - } + infos.setEntries(results); } catch (Exception e) { logger.error("Unable to list certificates: " + e.getMessage(), e); throw new PKIException("Unable to list certificates: " + e.getMessage(), e); diff --git a/base/common/src/main/java/com/netscape/certsrv/cert/CertSearchRequest.java b/base/common/src/main/java/com/netscape/certsrv/cert/CertSearchRequest.java index 67da3c1b61d..1946df9a5b5 100644 --- a/base/common/src/main/java/com/netscape/certsrv/cert/CertSearchRequest.java +++ b/base/common/src/main/java/com/netscape/certsrv/cert/CertSearchRequest.java @@ -22,6 +22,7 @@ import java.io.StringReader; import java.io.StringWriter; +import java.util.Map; import java.util.Objects; import javax.ws.rs.core.MultivaluedMap; @@ -1104,4 +1105,147 @@ public static CertSearchRequest fromXML(String xml) throws Exception { Element rootElement = document.getDocumentElement(); return fromDOM(rootElement); } + + public static CertSearchRequest fromMap(Map elements) { + + CertSearchRequest request = new CertSearchRequest(); + + elements.forEach((key, values) -> { + switch (key) { + case "issuerDN": + request.setIssuerDN(values[0]); + break; + case "serialNumberRangeInUse": + request.setSerialNumberRangeInUse(Boolean.parseBoolean(values[0])); + break; + case "serialTo": + request.setSerialTo(values[0]); + break; + case "serialFrom": + request.setSerialFrom(values[0]); + break; + case "subjectInUse": + request.setSubjectInUse(Boolean.parseBoolean(values[0])); + break; + case "eMail": + request.setEmail(values[0]); + break; + case "commonName": + request.setCommonName(values[0]); + break; + case "userID": + request.setUserID(values[0]); + break; + case "orgUnit": + request.setOrgUnit(values[0]); + break; + case "org": + request.setOrg(values[0]); + break; + case "locality": + request.setLocality(values[0]); + break; + case "state": + request.setState(values[0]); + break; + case "country": + request.setCountry(values[0]); + break; + case "matchExactly": + request.setMatchExactly(Boolean.parseBoolean(values[0])); + break; + case "status": + request.setStatus(values[0]); + break; + case "revokedBy": + request.setRevokedBy(values[0]); + break; + case "revokedOnFrom": + request.setRevokedOnFrom(values[0]); + break; + case "revokedOnTo": + request.setRevokedOnTo(values[0]); + break; + case "revocationReason": + request.setRevocationReason(values[0]); + break; + case "issuedBy": + request.setIssuedBy(values[0]); + break; + case "issuedOnFrom": + request.setIssuedOnFrom(values[0]); + break; + case "issuedOnTo": + request.setIssuedOnTo(values[0]); + break; + case "validNotBeforeFrom": + request.setValidNotBeforeFrom(values[0]); + break; + case "validNotBeforeTo": + request.setValidNotBeforeTo(values[0]); + break; + case "validNotAfterFrom": + request.setValidNotAfterFrom(values[0]); + break; + case "validNotAfterTo": + request.setValidNotAfterTo(values[0]); + break; + case "validityOperation": + request.setValidityOperation(values[0]); + break; + case "validityCount": + request.setValidityCount(Integer.valueOf(values[0])); + break; + case "validityUnit": + request.setValidityUnit(Long.valueOf(values[0])); + break; + case "certTypeSubEmailCA": + request.setCertTypeSubEmailCA(values[0]); + break; + case "certTypeSubSSLCA": + request.setCertTypeSubSSLCA(values[0]); + break; + case "certTypeSecureEmail": + request.setCertTypeSecureEmail(values[0]); + break; + case "certTypeSSLClient": + request.setCertTypeSSLClient(values[0]); + break; + case "certTypeSSLServer": + request.setCertTypeSSLServer(values[0]); + break; + case "revokedByInUse": + request.setRevokedByInUse(Boolean.parseBoolean(values[0])); + break; + case "revokedOnInUse": + request.setRevokedOnInUse(Boolean.parseBoolean(values[0])); + break; + case "revocationReasonInUse": + request.setRevocationReasonInUse(Boolean.parseBoolean(values[0])); + break; + case "issuedByInUse": + request.setIssuedByInUse(Boolean.parseBoolean(values[0])); + break; + case "issuedOnInUse": + request.setIssuedOnInUse(Boolean.parseBoolean(values[0])); + break; + case "validNotBeforeInUse": + request.setValidNotBeforeInUse(Boolean.parseBoolean(values[0])); + break; + case "validNotAfterInUse": + request.setValidNotAfterInUse(Boolean.parseBoolean(values[0])); + break; + case "validityLengthInUse": + request.setValidityLengthInUse(Boolean.parseBoolean(values[0])); + break; + case "certTypeInUse": + request.setCertTypeInUse(Boolean.parseBoolean(values[0])); + break; + default: + } + }); + + return request; + } + } diff --git a/base/server/src/main/java/com/netscape/cmscore/dbs/DBSSession.java b/base/server/src/main/java/com/netscape/cmscore/dbs/DBSSession.java index d6ab1052443..69501234ffe 100644 --- a/base/server/src/main/java/com/netscape/cmscore/dbs/DBSSession.java +++ b/base/server/src/main/java/com/netscape/cmscore/dbs/DBSSession.java @@ -18,9 +18,9 @@ package com.netscape.cmscore.dbs; import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.dbs.DBVirtualList; import com.netscape.certsrv.dbs.EDBException; import com.netscape.certsrv.dbs.IDBObj; -import com.netscape.certsrv.dbs.DBVirtualList; import com.netscape.certsrv.dbs.ModificationSet; import netscape.ldap.LDAPSearchResults; @@ -211,6 +211,48 @@ public DBSearchResults search( return null; } + /** + * Retrieves a list of object that satifies the given + * filter. + * + * @param base starting point of the search + * @param filter search filter + * @param start index of the first element + * @param size max number of element in the page + * @return search results + * @exception EBaseException failed to search + */ + public DBSearchResults pagedSearch( + String base, + String filter, + int start, + int size + ) throws EBaseException { + return pagedSearch(base, filter, start, size, -1); + } + + /** + * Retrieves a list of object that satifies the given + * filter. + * + * @param base starting point of the search + * @param filter search filter + * @param start index of the first element + * @param size max number of element in the page + * @param timeLimit timeout limit + * @return search results + * @exception EBaseException failed to search + */ + public DBSearchResults pagedSearch( + String base, + String filter, + int start, + int size, + int timeLimit + ) throws EBaseException { + return null; + } + /** * Sets persistent search to retrieve modified * certificate records. diff --git a/base/server/src/main/java/com/netscape/cmscore/dbs/LDAPSession.java b/base/server/src/main/java/com/netscape/cmscore/dbs/LDAPSession.java index 3624bbb4202..fd5f9a36a68 100644 --- a/base/server/src/main/java/com/netscape/cmscore/dbs/LDAPSession.java +++ b/base/server/src/main/java/com/netscape/cmscore/dbs/LDAPSession.java @@ -32,6 +32,7 @@ import netscape.ldap.LDAPAttribute; import netscape.ldap.LDAPAttributeSet; import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPControl; import netscape.ldap.LDAPEntry; import netscape.ldap.LDAPException; import netscape.ldap.LDAPModification; @@ -40,6 +41,7 @@ import netscape.ldap.LDAPSearchResults; import netscape.ldap.LDAPSortKey; import netscape.ldap.LDAPv3; +import netscape.ldap.controls.LDAPPagedResultsControl; import netscape.ldap.controls.LDAPPersistSearchControl; import netscape.ldap.controls.LDAPSortControl; @@ -474,6 +476,61 @@ public DBSearchResults search(String base, String filter, } } + @Override + public DBSearchResults pagedSearch(String base, String filter, int start, int size, int timeLimit) + throws EBaseException { + try { + String ldapfilter = dbSubsystem.getRegistry().getFilter(filter); + logger.info("LDAPSession.pagedSearch(): Searching {} for {}", base, ldapfilter); + + LDAPSearchConstraints cons = new LDAPSearchConstraints(); + if (timeLimit > 0) { + cons.setServerTimeLimit(timeLimit); + } + LDAPPagedResultsControl pagecon; + LDAPSearchResults res; + int skipped = 0; + int pageSize = start > 0 ? Math.min(start, 500) : Math.min(size, 500); + byte[] cookie = null; + + pagecon = new LDAPPagedResultsControl(false, pageSize); + while (start > 0 && skipped < start) { + cons.setServerControls(pagecon); + res = mConn.search(base, + LDAPv3.SCOPE_ONE, ldapfilter, null, false, cons); + while(res.hasMoreElements()) + res.next(); + skipped += pageSize; + for (LDAPControl c: res.getResponseControls()){ + if(c instanceof LDAPPagedResultsControl resC){ + cookie = resC.getCookie(); + if(cookie!=null){ + pageSize = start - skipped > 0 ? Math.min((start - skipped) , 500) : Math.min(size, 500); + pagecon = new LDAPPagedResultsControl(false, pageSize, cookie); + } else { + res = new LDAPSearchResults(); + return new DBSearchResults(dbSubsystem.getRegistry(), + res); + } + } + } + } + cons.setServerControls(pagecon); + res = mConn.search(base, + LDAPv3.SCOPE_ONE, ldapfilter, null, false, cons); + return new DBSearchResults(dbSubsystem.getRegistry(), + res); + + } catch (LDAPException e) { + if (e.getLDAPResultCode() == LDAPException.UNAVAILABLE) + throw new EDBNotAvailException( + CMS.getUserMessage("CMS_DBS_INTERNAL_DIR_UNAVAILABLE")); + // XXX error handling, should not raise exception if + // entry not found + throw new EDBException("Unable to search LDAP record: " + e.getMessage(), e); + } + } + @Override public LDAPSearchResults persistentSearch(String base, String filter, String attrs[]) throws EBaseException { From 63ca1e8d26d345ee50d08f465da77c2de2f4f350 Mon Sep 17 00:00:00 2001 From: Marco Fargetta Date: Thu, 7 Dec 2023 20:04:46 +0100 Subject: [PATCH 3/4] Tidy up CertServlet --- .../dogtagpki/server/ca/v2/CertServlet.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java b/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java index 16bff247683..ee1d5c32f76 100644 --- a/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java +++ b/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java @@ -61,7 +61,7 @@ @WebServlet("/v2/certs/*") public class CertServlet extends CAServlet { public static final int DEFAULT_MAXTIME = 0; - public final static int DEFAULT_SIZE = 20; + public static final int DEFAULT_SIZE = 20; private static final long serialVersionUID = 1L; private static Logger logger = LoggerFactory.getLogger(CertServlet.class); @@ -126,8 +126,8 @@ private CertData getCertData(CertId id, Locale loc) throws Exception { CertificateRepository repo = engine.getCertificateRepository(); //find the cert in question - CertRecord record = repo.readCertificateRecord(id.toBigInteger()); - X509CertImpl cert = record.getCertificate(); + CertRecord certRecord = repo.readCertificateRecord(id.toBigInteger()); + X509CertImpl cert = certRecord.getCertificate(); CertData certData = new CertData(); certData.setSerialNumber(id); @@ -165,10 +165,10 @@ private CertData getCertData(CertId id, Locale loc) throws Exception { Date notAfter = cert.getNotAfter(); if (notAfter != null) certData.setNotAfter(notAfter.toString()); - certData.setRevokedOn(record.getRevokedOn()); - certData.setRevokedBy(record.getRevokedBy()); + certData.setRevokedOn(certRecord.getRevokedOn()); + certData.setRevokedBy(certRecord.getRevokedBy()); - RevocationInfo revInfo = record.getRevocationInfo(); + RevocationInfo revInfo = certRecord.getRevocationInfo(); if (revInfo != null) { CRLExtensions revExts = revInfo.getCRLEntryExtensions(); if (revExts != null) { @@ -182,7 +182,7 @@ private CertData getCertData(CertId id, Locale loc) throws Exception { } } - certData.setStatus(record.getStatus()); + certData.setStatus(certRecord.getStatus()); return certData; } @@ -228,16 +228,16 @@ private CertDataInfos listCerts(CertSearchRequest searchReq, int maxTime, int st return infos; } - private CertDataInfo createCertDataInfo(CertRecord record) throws EBaseException, InvalidKeyException { + private CertDataInfo createCertDataInfo(CertRecord certRecord) throws EBaseException, InvalidKeyException { CertDataInfo info = new CertDataInfo(); - CertId id = new CertId(record.getSerialNumber()); + CertId id = new CertId(certRecord.getSerialNumber()); info.setID(id); - X509Certificate cert = record.getCertificate(); + X509Certificate cert = certRecord.getCertificate(); info.setIssuerDN(cert.getIssuerDN().toString()); info.setSubjectDN(cert.getSubjectDN().toString()); - info.setStatus(record.getStatus()); + info.setStatus(certRecord.getStatus()); info.setVersion(cert.getVersion()); info.setType(cert.getType()); @@ -255,11 +255,11 @@ private CertDataInfo createCertDataInfo(CertRecord record) throws EBaseException info.setNotValidBefore(cert.getNotBefore()); info.setNotValidAfter(cert.getNotAfter()); - info.setIssuedOn(record.getCreateTime()); - info.setIssuedBy(record.getIssuedBy()); + info.setIssuedOn(certRecord.getCreateTime()); + info.setIssuedBy(certRecord.getIssuedBy()); - info.setRevokedOn(record.getRevokedOn()); - info.setRevokedBy(record.getRevokedBy()); + info.setRevokedOn(certRecord.getRevokedOn()); + info.setRevokedBy(certRecord.getRevokedBy()); return info; } From 477390f97e39cd415d4510af0f82e5bd41266174 Mon Sep 17 00:00:00 2001 From: Marco Fargetta Date: Mon, 11 Dec 2023 16:30:19 +0100 Subject: [PATCH 4/4] Fix Cert REST api v2 output format --- .../cmscore/dbs/CertificateRepository.java | 6 ++++-- .../dogtagpki/server/ca/v2/CertServlet.java | 21 ++++++++----------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/base/ca/src/main/java/com/netscape/cmscore/dbs/CertificateRepository.java b/base/ca/src/main/java/com/netscape/cmscore/dbs/CertificateRepository.java index 6721ea4fae2..538dc9095f7 100644 --- a/base/ca/src/main/java/com/netscape/cmscore/dbs/CertificateRepository.java +++ b/base/ca/src/main/java/com/netscape/cmscore/dbs/CertificateRepository.java @@ -1123,7 +1123,9 @@ public Enumeration searchCertificates(String filter, int maxSize, * {@Code (&(certRecordId=5)(x509Cert.notBefore=934398398))} * * @param filter search filter - * @param maxSize max size to return + * @param timeLimit timeout value + * @param start first entry to return from the list + * @param size max size to return * @return a list of certificates * @exception EBaseException failed to search */ @@ -1133,7 +1135,7 @@ public Iterator searchCertificates(String filter, int timeLimit, int ArrayList records = new ArrayList<>(); logger.debug("searchCertificates filter {filter}, start {start} and size {size}", filter, start, size); try (DBSSession s = dbSubsystem.createSession()) { - DBSearchResults sr = s.pagedSearch(mBaseDN, filter, start, size); + DBSearchResults sr = s.pagedSearch(mBaseDN, filter, start, size, timeLimit); while (sr.hasMoreElements()) { records.add((CertRecord) sr.nextElement()); } diff --git a/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java b/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java index ee1d5c32f76..54ff2ecf876 100644 --- a/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java +++ b/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java @@ -12,6 +12,7 @@ import java.security.Principal; import java.security.PublicKey; import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; @@ -26,12 +27,10 @@ import org.dogtagpki.server.ca.CAEngine; import org.dogtagpki.server.ca.CAServlet; -import org.dogtagpki.util.cert.CertUtil; import org.mozilla.jss.netscape.security.pkcs.ContentInfo; import org.mozilla.jss.netscape.security.pkcs.PKCS7; import org.mozilla.jss.netscape.security.pkcs.SignerInfo; import org.mozilla.jss.netscape.security.provider.RSAPublicKey; -import org.mozilla.jss.netscape.security.util.CertPrettyPrint; import org.mozilla.jss.netscape.security.util.Utils; import org.mozilla.jss.netscape.security.x509.AlgorithmId; import org.mozilla.jss.netscape.security.x509.CRLExtensions; @@ -138,11 +137,9 @@ private CertData getCertData(CertId id, Locale loc) throws Exception { Principal subjectDN = cert.getSubjectName(); if (subjectDN != null) certData.setSubjectDN(subjectDN.toString()); - String base64 = CertUtil.toPEM(cert); - certData.setEncoded(base64); + String base64 = Utils.base64encode(cert.getEncoded(), true); - CertPrettyPrint print = new CertPrettyPrint(cert); - certData.setPrettyPrint(print.toString(loc)); + certData.setEncoded(base64); X509Certificate[] certChain = engine.getCertChain(cert); @@ -159,11 +156,12 @@ private CertData getCertData(CertId id, Locale loc) throws Exception { String p7Str = Utils.base64encode(p7Bytes, true); certData.setPkcs7CertChain(p7Str); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); Date notBefore = cert.getNotBefore(); - if (notBefore != null) certData.setNotBefore(notBefore.toString()); + if (notBefore != null) certData.setNotBefore(sdf.format(notBefore)); Date notAfter = cert.getNotAfter(); - if (notAfter != null) certData.setNotAfter(notAfter.toString()); + if (notAfter != null) certData.setNotAfter(sdf.format(notAfter)); certData.setRevokedOn(certRecord.getRevokedOn()); certData.setRevokedBy(certRecord.getRevokedBy()); @@ -177,7 +175,7 @@ private CertData getCertData(CertId id, Locale loc) throws Exception { revExts.get(CRLReasonExtension.NAME); certData.setRevocationReason(ext.getReason().getCode()); } catch (X509ExtensionException e) { - // nothing to do + logger.debug("CRL extension error for certificate {}", id.toHexString()); } } } @@ -216,9 +214,8 @@ private CertDataInfos listCerts(CertSearchRequest searchReq, int maxTime, int st results.add(createCertDataInfo(rec)); } - int total = results.size(); - logger.info("Search results: " + total); - infos.setTotal(total); + infos.setTotal(results.size()); + logger.info("Search results: " + results.size()); infos.setEntries(results); } catch (Exception e) { logger.error("Unable to list certificates: " + e.getMessage(), e);