From fab992249f71d1e8698adc86a493210f067a9241 Mon Sep 17 00:00:00 2001 From: Marco Fargetta Date: Tue, 14 Nov 2023 11:35:56 +0100 Subject: [PATCH] Implement CA Info REST v2 The code of CAInfoService.java has moved to CAEngine and used for the current API and the new v2 API. Introduced a base servlet for all CA APIs and the servlet for the Info end-point. No code changes performed. --- .github/workflows/kra-oaep-test.yml | 4 +- .../org/dogtagpki/server/ca/CAEngine.java | 176 ++++++++++++++++++ .../org/dogtagpki/server/ca/CAServlet.java | 40 ++++ .../dogtagpki/server/ca/v2/CAInfoServlet.java | 33 ++++ .../dogtagpki/server/rest/CAInfoService.java | 162 +--------------- .../org/dogtagpki/common/CAInfoClient.java | 2 +- 6 files changed, 253 insertions(+), 164 deletions(-) create mode 100644 base/ca/src/main/java/org/dogtagpki/server/ca/CAServlet.java create mode 100644 base/ca/src/main/java/org/dogtagpki/server/ca/v2/CAInfoServlet.java diff --git a/.github/workflows/kra-oaep-test.yml b/.github/workflows/kra-oaep-test.yml index 7738369f3e5..75e7ce81532 100644 --- a/.github/workflows/kra-oaep-test.yml +++ b/.github/workflows/kra-oaep-test.yml @@ -109,8 +109,8 @@ jobs: - name: Verify CAInfo run: | - docker exec pki curl -k https://pki.example.com:8443/ca/rest/info > info - echo -n '{"ArchivalMechanism":"keywrap","EncryptionAlgorithm":"AES/CBC/PKCS5Padding","KeyWrapAlgorithm":"AES KeyWrap/Padding","RsaPublicKeyWrapAlgorithm":"RSA_OAEP","CaRsaPublicKeyWrapAlgorithm":"RSA_OAEP","Attributes":{"Attribute":[]}}' > expectedInfo + docker exec pki curl -k https://pki.example.com:8443/ca/v2/info | python -m json.tool > info + echo -n '{"ArchivalMechanism":"keywrap","EncryptionAlgorithm":"AES/CBC/PKCS5Padding","KeyWrapAlgorithm":"AES KeyWrap/Padding","RsaPublicKeyWrapAlgorithm":"RSA_OAEP","CaRsaPublicKeyWrapAlgorithm":"RSA_OAEP","Attributes":{"Attribute":[]}}' | python -m json.tool > expectedInfo diff expectedInfo info - name: Verify KRAInfo diff --git a/base/ca/src/main/java/org/dogtagpki/server/ca/CAEngine.java b/base/ca/src/main/java/org/dogtagpki/server/ca/CAEngine.java index 76878b1484c..f94f1a60c89 100644 --- a/base/ca/src/main/java/org/dogtagpki/server/ca/CAEngine.java +++ b/base/ca/src/main/java/org/dogtagpki/server/ca/CAEngine.java @@ -37,6 +37,9 @@ import javax.servlet.http.HttpSession; import org.apache.commons.lang3.StringUtils; +import org.dogtagpki.common.CAInfo; +import org.dogtagpki.common.KRAInfo; +import org.dogtagpki.common.KRAInfoClient; import org.dogtagpki.legacy.ca.CAPolicy; import org.dogtagpki.legacy.ca.CAPolicyConfig; import org.dogtagpki.server.authentication.AuthToken; @@ -44,6 +47,8 @@ import org.dogtagpki.util.cert.CertUtil; import org.mozilla.jss.CryptoManager; import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.EncryptionAlgorithm; +import org.mozilla.jss.crypto.KeyWrapAlgorithm; import org.mozilla.jss.crypto.PrivateKey; import org.mozilla.jss.netscape.security.pkcs.PKCS10; import org.mozilla.jss.netscape.security.x509.CertificateChain; @@ -70,14 +75,19 @@ import com.netscape.certsrv.ca.CATypeException; import com.netscape.certsrv.ca.ECAException; import com.netscape.certsrv.ca.IssuerUnavailableException; +import com.netscape.certsrv.client.ClientConfig; +import com.netscape.certsrv.client.PKIClient; +import com.netscape.certsrv.connector.ConnectorConfig; import com.netscape.certsrv.connector.ConnectorsConfig; import com.netscape.certsrv.dbs.certdb.CertId; import com.netscape.certsrv.ldap.ELdapException; import com.netscape.certsrv.profile.EProfileException; import com.netscape.certsrv.publish.CRLPublisher; import com.netscape.certsrv.request.RequestListener; +import com.netscape.certsrv.system.KRAConnectorInfo; import com.netscape.cms.authentication.CAAuthSubsystem; import com.netscape.cms.request.RequestScheduler; +import com.netscape.cms.servlet.admin.KRAConnectorProcessor; import com.netscape.cmscore.apps.CMS; import com.netscape.cmscore.apps.CMSEngine; import com.netscape.cmscore.authentication.VerifiedCert; @@ -164,6 +174,17 @@ public class CAEngine extends CMSEngine { protected AuthorityMonitor authorityMonitor; protected boolean enableAuthorityMonitor = true; + // is the current KRA-related info authoritative? + private static boolean kraInfoAuthoritative = false; + + // KRA-related fields (the initial values are only used if we + // did not yet receive authoritative info from KRA) + private static String archivalMechanism = CAInfo.KEYWRAP_MECHANISM; + private static String encryptAlgorithm; + private static String keyWrapAlgorithm; + private static String rsaPublicKeyWrapAlgorithm; + private static String caRsaPublicKeyWrapAlgorithm; + public CAEngine() { super("CA"); instance = this; @@ -1817,6 +1838,161 @@ public PKCS10 parsePKCS10(Locale locale, String certreq) throws Exception { } } + /** + * This method returns CA info, including KRA-related values the CA + * clients may need to know (e.g. for generating a CRMF cert request + * that will cause keys to be archived in KRA). + * + * The KRA-related info is read from the KRAInfoService, which is + * queried according to the KRA Connector configuration. After + * the KRAInfoService has been successfully contacted, the recorded + * KRA-related settings are regarded as authoritative. + * + * The KRA is contacted ONLY if the current info is NOT + * authoritative, otherwise the currently recorded values are used. + * This means that any change to relevant KRA configuration (which + * should occur seldom if ever) necessitates restart of the CA + * subsystem. + * + * If this is unsuccessful (e.g. if the KRA is down or the + * connector is misconfigured) we use the default values, which + * may be incorrect. + * + * @author Ade Lee + * @author M Fargetta + */ + public CAInfo getInfo(Locale loc) throws Exception { + CAInfo info = new CAInfo(); + addKRAInfo(info, loc); + info.setCaRsaPublicKeyWrapAlgorithm(caRsaPublicKeyWrapAlgorithm); + return info; + } + + /** + * Add KRA fields if KRA is configured, querying the KRA + * if necessary. + * + * Apart from reading 'headers', this method doesn't access + * any instance data. + */ + private void addKRAInfo(CAInfo info, Locale loc) throws Exception { + + KRAConnectorInfo connInfo = null; + + try { + KRAConnectorProcessor processor = new KRAConnectorProcessor(loc); + processor.setCMSEngine(this); + processor.init(); + + connInfo = processor.getConnectorInfo(); + } catch (Throwable e) { + // connInfo remains as null + } + boolean kraEnabled = + connInfo != null + && "true".equalsIgnoreCase(connInfo.getEnable()); + + if (kraEnabled) { + if (!kraInfoAuthoritative) { + // KRA is enabled but we are yet to successfully + // query the KRA-related info. Do it now. + queryKRAInfo(connInfo); + } + + info.setArchivalMechanism(archivalMechanism); + info.setEncryptAlgorithm(encryptAlgorithm); + info.setKeyWrapAlgorithm(keyWrapAlgorithm); + info.setRsaPublicKeyWrapAlgorithm(rsaPublicKeyWrapAlgorithm); + } + } + + private static void queryKRAInfo(KRAConnectorInfo connInfo) throws Exception { + CAEngine engine = CAEngine.getInstance(); + CAEngineConfig cs = engine.getConfig(); + + try (PKIClient client = createPKIClient(connInfo)) { + + KRAInfoClient kraInfoClient = new KRAInfoClient(client, "kra"); + KRAInfo kraInfo = kraInfoClient.getInfo(); + + archivalMechanism = kraInfo.getArchivalMechanism(); + encryptAlgorithm = kraInfo.getEncryptAlgorithm(); + keyWrapAlgorithm = kraInfo.getWrapAlgorithm(); + rsaPublicKeyWrapAlgorithm = kraInfo.getRsaPublicKeyWrapAlgorithm(); + caRsaPublicKeyWrapAlgorithm = getCaRsaPublicKeyWrapAlgorithm(); + + // mark info as authoritative + kraInfoAuthoritative = true; + } catch (PKIException e) { + if (e.getCode() == 404) { + // The KRAInfoResource was added in 10.4, + // so we are talking to a pre-10.4 KRA + + encryptAlgorithm = EncryptionAlgorithm.DES3_CBC_PAD.toString(); + keyWrapAlgorithm = KeyWrapAlgorithm.DES3_CBC_PAD.toString(); + + // pre-10.4 KRA does not advertise the archival + // mechanism; look for the old knob in CA's config + // or fall back to the default + boolean encrypt_archival; + try { + encrypt_archival = cs.getBoolean( + "kra.allowEncDecrypt.archival", false); + } catch (EBaseException e1) { + encrypt_archival = false; + } + archivalMechanism = encrypt_archival ? CAInfo.ENCRYPT_MECHANISM : CAInfo.KEYWRAP_MECHANISM; + + // mark info as authoritative + kraInfoAuthoritative = true; + } else { + logger.warn("Failed to retrieve archive wrapping information from the CA: " + e.getMessage(), e); + } + } catch (Throwable e) { + logger.warn("Failed to retrieve archive wrapping information from the CA: " + e.getMessage(), e); + } + } + + /** + * Construct PKIClient given KRAConnectorInfo + */ + private static PKIClient createPKIClient(KRAConnectorInfo connInfo) throws Exception { + + CAEngine engine = CAEngine.getInstance(); + CAEngineConfig cs = engine.getConfig(); + CAConfig caConfig = cs.getCAConfig(); + ConnectorsConfig connectorsConfig = caConfig.getConnectorsConfig(); + ConnectorConfig kraConnectorConfig = connectorsConfig.getConnectorConfig("KRA"); + + ClientConfig config = new ClientConfig(); + int port = Integer.parseInt(connInfo.getPort()); + config.setServerURL("https", connInfo.getHost(), port); + config.setNSSDatabase(CMS.getInstanceDir() + "/alias"); + + // Use client cert specified in KRA connector + String nickname = kraConnectorConfig.getString("nickName", null); + if (nickname == null) { + // Use subsystem cert as client cert + nickname = cs.getString("ca.subsystem.nickname"); + + String tokenname = cs.getString("ca.subsystem.tokenname", ""); + if (!CryptoUtil.isInternalToken(tokenname)) nickname = tokenname + ":" + nickname; + } + config.setCertNickname(nickname); + + return new PKIClient(config); + } + + private static String getCaRsaPublicKeyWrapAlgorithm() throws EBaseException { + + CAEngine engine = CAEngine.getInstance(); + CAEngineConfig cs = engine.getConfig(); + + boolean useOAEP = cs.getUseOAEPKeyWrap(); + + return useOAEP ? "RSA_OAEP" : "RSA"; + } + @Override public void shutdownDatabase() { 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 new file mode 100644 index 00000000000..6596b49c57e --- /dev/null +++ b/base/ca/src/main/java/org/dogtagpki/server/ca/CAServlet.java @@ -0,0 +1,40 @@ +// +// Copyright Red Hat, Inc. +// +// SPDX-License-Identifier: GPL-2.0-or-later +// +package org.dogtagpki.server.ca; + +import java.io.IOException; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class CAServlet extends HttpServlet { + public static final long serialVersionUID = 1L; + + + public void get(HttpServletRequest request, HttpServletResponse response) throws Exception { + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + try { + get(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 new file mode 100644 index 00000000000..b4205c15737 --- /dev/null +++ b/base/ca/src/main/java/org/dogtagpki/server/ca/v2/CAInfoServlet.java @@ -0,0 +1,33 @@ +package org.dogtagpki.server.ca.v2; + +import java.io.PrintWriter; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.dogtagpki.common.CAInfo; +import org.dogtagpki.server.ca.CAEngine; +import org.dogtagpki.server.ca.CAServlet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@WebServlet("/v2/info") +public class CAInfoServlet extends CAServlet { + private static final long serialVersionUID = 1L; + private static Logger logger = LoggerFactory.getLogger(CAInfoServlet.class); + + public void get(HttpServletRequest request, HttpServletResponse response) throws Exception { + HttpSession session = request.getSession(); + logger.debug("CAInfoServlet.get(): session: " + session.getId()); + + CAEngine engine = getCAEngine(); + CAInfo info = engine.getInfo(request.getLocale()); + + response.setContentType("application/json"); + + PrintWriter out = response.getWriter(); + out.println(info.toJSON()); + } +} diff --git a/base/ca/src/main/java/org/dogtagpki/server/rest/CAInfoService.java b/base/ca/src/main/java/org/dogtagpki/server/rest/CAInfoService.java index c9b59cfb223..60e02b443b3 100644 --- a/base/ca/src/main/java/org/dogtagpki/server/rest/CAInfoService.java +++ b/base/ca/src/main/java/org/dogtagpki/server/rest/CAInfoService.java @@ -21,29 +21,12 @@ import javax.servlet.http.HttpSession; import javax.ws.rs.core.Response; -import org.dogtagpki.common.CAInfo; import org.dogtagpki.common.CAInfoResource; -import org.dogtagpki.common.KRAInfo; -import org.dogtagpki.common.KRAInfoClient; -import org.dogtagpki.server.ca.CAConfig; import org.dogtagpki.server.ca.CAEngine; -import org.dogtagpki.server.ca.CAEngineConfig; -import org.mozilla.jss.crypto.EncryptionAlgorithm; -import org.mozilla.jss.crypto.KeyWrapAlgorithm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.netscape.certsrv.base.EBaseException; -import com.netscape.certsrv.base.PKIException; -import com.netscape.certsrv.client.ClientConfig; -import com.netscape.certsrv.client.PKIClient; -import com.netscape.certsrv.connector.ConnectorConfig; -import com.netscape.certsrv.connector.ConnectorsConfig; -import com.netscape.certsrv.system.KRAConnectorInfo; -import com.netscape.cms.servlet.admin.KRAConnectorProcessor; import com.netscape.cms.servlet.base.PKIService; -import com.netscape.cmscore.apps.CMS; -import com.netscape.cmsutil.crypto.CryptoUtil; /** * @author Ade Lee @@ -71,16 +54,6 @@ public class CAInfoService extends PKIService implements CAInfoResource { private static Logger logger = LoggerFactory.getLogger(CAInfoService.class); - // is the current KRA-related info authoritative? - private static boolean kraInfoAuthoritative = false; - - // KRA-related fields (the initial values are only used if we - // did not yet receive authoritative info from KRA) - private static String archivalMechanism = CAInfo.KEYWRAP_MECHANISM; - private static String encryptAlgorithm; - private static String keyWrapAlgorithm; - private static String rsaPublicKeyWrapAlgorithm; - private static String caRsaPublicKeyWrapAlgorithm; @Override public Response getInfo() throws Exception { @@ -88,140 +61,7 @@ public Response getInfo() throws Exception { HttpSession session = servletRequest.getSession(); logger.debug("CAInfoService.getInfo(): session: " + session.getId()); - CAInfo info = new CAInfo(); - - addKRAInfo(info); - info.setCaRsaPublicKeyWrapAlgorithm(caRsaPublicKeyWrapAlgorithm); - - return createOKResponse(info); - } - - /** - * Add KRA fields if KRA is configured, querying the KRA - * if necessary. - * - * Apart from reading 'headers', this method doesn't access - * any instance data. - */ - private void addKRAInfo(CAInfo info) throws Exception { - CAEngine engine = (CAEngine) getCMSEngine(); - KRAConnectorInfo connInfo = null; - - try { - KRAConnectorProcessor processor = new KRAConnectorProcessor(getLocale(headers)); - processor.setCMSEngine(engine); - processor.init(); - - connInfo = processor.getConnectorInfo(); - } catch (Throwable e) { - // connInfo remains as null - } - boolean kraEnabled = - connInfo != null - && "true".equalsIgnoreCase(connInfo.getEnable()); - - if (kraEnabled) { - if (!kraInfoAuthoritative) { - // KRA is enabled but we are yet to successfully - // query the KRA-related info. Do it now. - queryKRAInfo(connInfo); - } - - info.setArchivalMechanism(archivalMechanism); - info.setEncryptAlgorithm(encryptAlgorithm); - info.setKeyWrapAlgorithm(keyWrapAlgorithm); - info.setRsaPublicKeyWrapAlgorithm(rsaPublicKeyWrapAlgorithm); - } - } - - private static void queryKRAInfo(KRAConnectorInfo connInfo) throws Exception { - - CAEngine engine = CAEngine.getInstance(); - CAEngineConfig cs = engine.getConfig(); - - try (PKIClient client = createPKIClient(connInfo)) { - - KRAInfoClient kraInfoClient = new KRAInfoClient(client, "kra"); - KRAInfo kraInfo = kraInfoClient.getInfo(); - - archivalMechanism = kraInfo.getArchivalMechanism(); - encryptAlgorithm = kraInfo.getEncryptAlgorithm(); - keyWrapAlgorithm = kraInfo.getWrapAlgorithm(); - rsaPublicKeyWrapAlgorithm = kraInfo.getRsaPublicKeyWrapAlgorithm(); - caRsaPublicKeyWrapAlgorithm = getCaRsaPublicKeyWrapAlgorithm(); - - // mark info as authoritative - kraInfoAuthoritative = true; - } catch (PKIException e) { - if (e.getCode() == 404) { - // The KRAInfoResource was added in 10.4, - // so we are talking to a pre-10.4 KRA - - encryptAlgorithm = EncryptionAlgorithm.DES3_CBC_PAD.toString(); - keyWrapAlgorithm = KeyWrapAlgorithm.DES3_CBC_PAD.toString(); - - // pre-10.4 KRA does not advertise the archival - // mechanism; look for the old knob in CA's config - // or fall back to the default - boolean encrypt_archival; - try { - encrypt_archival = cs.getBoolean( - "kra.allowEncDecrypt.archival", false); - } catch (EBaseException e1) { - encrypt_archival = false; - } - archivalMechanism = encrypt_archival ? CAInfo.ENCRYPT_MECHANISM : CAInfo.KEYWRAP_MECHANISM; - - // mark info as authoritative - kraInfoAuthoritative = true; - } else { - logger.warn("Failed to retrieve archive wrapping information from the CA: " + e.getMessage(), e); - } - } catch (Throwable e) { - logger.warn("Failed to retrieve archive wrapping information from the CA: " + e.getMessage(), e); - } + return createOKResponse(engine.getInfo(getLocale(headers))); } - - /** - * Construct PKIClient given KRAConnectorInfo - */ - private static PKIClient createPKIClient(KRAConnectorInfo connInfo) throws Exception { - - CAEngine engine = CAEngine.getInstance(); - CAEngineConfig cs = engine.getConfig(); - CAConfig caConfig = cs.getCAConfig(); - ConnectorsConfig connectorsConfig = caConfig.getConnectorsConfig(); - ConnectorConfig kraConnectorConfig = connectorsConfig.getConnectorConfig("KRA"); - - ClientConfig config = new ClientConfig(); - int port = Integer.parseInt(connInfo.getPort()); - config.setServerURL("https", connInfo.getHost(), port); - config.setNSSDatabase(CMS.getInstanceDir() + "/alias"); - - // Use client cert specified in KRA connector - String nickname = kraConnectorConfig.getString("nickName", null); - if (nickname == null) { - // Use subsystem cert as client cert - nickname = cs.getString("ca.subsystem.nickname"); - - String tokenname = cs.getString("ca.subsystem.tokenname", ""); - if (!CryptoUtil.isInternalToken(tokenname)) nickname = tokenname + ":" + nickname; - } - config.setCertNickname(nickname); - - return new PKIClient(config); - } - - private static String getCaRsaPublicKeyWrapAlgorithm() throws EBaseException { - - CAEngine engine = CAEngine.getInstance(); - CAEngineConfig cs = engine.getConfig(); - - boolean useOAEP = cs.getUseOAEPKeyWrap(); - - return useOAEP ? "RSA_OAEP" : "RSA"; - } - - } diff --git a/base/common/src/main/java/org/dogtagpki/common/CAInfoClient.java b/base/common/src/main/java/org/dogtagpki/common/CAInfoClient.java index 6eba50f5ba7..fbf485887a6 100644 --- a/base/common/src/main/java/org/dogtagpki/common/CAInfoClient.java +++ b/base/common/src/main/java/org/dogtagpki/common/CAInfoClient.java @@ -27,7 +27,7 @@ public class CAInfoClient extends Client { public CAInfoClient(PKIClient client, String subsystem) throws Exception { - super(client, subsystem, "info"); + super(client, subsystem, "v2", "info"); } public CAInfo getInfo() throws Exception {