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 {