Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ca certs api v2 #4635

Merged
merged 4 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1112,6 +1114,35 @@ public Enumeration<CertRecord> 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 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
*/
public Iterator<CertRecord> searchCertificates(String filter, int timeLimit, int start, int size)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timeLimit doesn't seem to be used. Should we keep it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is implemented in the pagedSeearch. I will add to the call.

throws EBaseException {

ArrayList<CertRecord> 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, timeLimit);
while (sr.hasMoreElements()) {
records.add((CertRecord) sr.nextElement());
}
}
return records.iterator();
}


/**
* Finds certificate records.
Expand Down
21 changes: 21 additions & 0 deletions base/ca/src/main/java/org/dogtagpki/server/ca/CAServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @author Marco Fargetta <[email protected]>
*/
public class CAServlet extends HttpServlet {
public static final long serialVersionUID = 1L;


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
Expand All @@ -33,6 +41,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");
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,11 +18,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author Marco Fargetta <[email protected]>
*/
@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());
Expand Down
263 changes: 263 additions & 0 deletions base/ca/src/main/java/org/dogtagpki/server/ca/v2/CertServlet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
//
// Copyright Red Hat, Inc.
//
// SPDX-License-Identifier: GPL-2.0-or-later
//
package org.dogtagpki.server.ca.v2;

import java.io.BufferedReader;
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.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
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;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.dogtagpki.server.ca.CAEngine;
import org.dogtagpki.server.ca.CAServlet;
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.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.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;

/**
* @author Marco Fargetta <[email protected]>
*/
@WebServlet("/v2/certs/*")
public class CertServlet extends CAServlet {
public static final int DEFAULT_MAXTIME = 0;
public static final 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 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"));

CertSearchRequest searchElems = CertSearchRequest.fromMap(request.getParameterMap());
CertDataInfos infos = listCerts(searchElems, 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());

if(request.getPathInfo() == null || !request.getPathInfo().equals("/search")) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, request.getRequestURI());
return;
}

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 {
CAEngine engine = getCAEngine();
CertificateRepository repo = engine.getCertificateRepository();

//find the cert in question
CertRecord certRecord = repo.readCertificateRecord(id.toBigInteger());
X509CertImpl cert = certRecord.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 = Utils.base64encode(cert.getEncoded(), true);

certData.setEncoded(base64);

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if we should skip this field since not all clients needs to get the PKCS #7 cert chain, so maybe it can be provided by a different API.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this field when the new API is created in a separate PR.


SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
Date notBefore = cert.getNotBefore();
if (notBefore != null) certData.setNotBefore(sdf.format(notBefore));

Date notAfter = cert.getNotAfter();
if (notAfter != null) certData.setNotAfter(sdf.format(notAfter));

certData.setRevokedOn(certRecord.getRevokedOn());
certData.setRevokedBy(certRecord.getRevokedBy());

RevocationInfo revInfo = certRecord.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) {
logger.debug("CRL extension error for certificate {}", id.toHexString());
}
}
}

certData.setStatus(certRecord.getStatus());

return certData;
}

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();

logger.info("Search filter: " + filter);

CertDataInfos infos = new CertDataInfos();
try {
Iterator<CertRecord> 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<CertDataInfo> results = new ArrayList<>();
while (e.hasNext()) {
CertRecord rec = e.next();
if (rec == null) continue;
results.add(createCertDataInfo(rec));
}

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);
throw new PKIException("Unable to list certificates: " + e.getMessage(), e);
}

return infos;
}

private CertDataInfo createCertDataInfo(CertRecord certRecord) throws EBaseException, InvalidKeyException {
CertDataInfo info = new CertDataInfo();

CertId id = new CertId(certRecord.getSerialNumber());
info.setID(id);

X509Certificate cert = certRecord.getCertificate();
info.setIssuerDN(cert.getIssuerDN().toString());
info.setSubjectDN(cert.getSubjectDN().toString());
info.setStatus(certRecord.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(certRecord.getCreateTime());
info.setIssuedBy(certRecord.getIssuedBy());

info.setRevokedOn(certRecord.getRevokedOn());
info.setRevokedBy(certRecord.getRevokedBy());

return info;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<CertDataInfo> {
public class CertDataInfos extends DataCollection<CertDataInfo> implements JSONSerializer {

public Element toDOM(Document document) {

Expand Down
Loading
Loading