diff --git a/base/ocsp/src/main/java/com/netscape/cms/ocsp/CRLLdapValidator.java b/base/ocsp/src/main/java/com/netscape/cms/ocsp/CRLLdapValidator.java new file mode 100644 index 00000000000..c6112aaf984 --- /dev/null +++ b/base/ocsp/src/main/java/com/netscape/cms/ocsp/CRLLdapValidator.java @@ -0,0 +1,108 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2023 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.cms.ocsp; + +import java.io.IOException; +import java.security.cert.CertificateException; +import java.security.cert.X509CRLEntry; +import java.util.Arrays; +import java.util.Enumeration; + +import org.mozilla.jss.crypto.X509Certificate; +import org.mozilla.jss.netscape.security.x509.AuthorityKeyIdentifierExtension; +import org.mozilla.jss.netscape.security.x509.KeyIdentifier; +import org.mozilla.jss.netscape.security.x509.PKIXExtensions; +import org.mozilla.jss.netscape.security.x509.SubjectKeyIdentifierExtension; +import org.mozilla.jss.netscape.security.x509.X509CRLImpl; +import org.mozilla.jss.netscape.security.x509.X509CertImpl; +import org.mozilla.jss.ssl.SSLCertificateApprovalCallback; + +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.dbs.crldb.ICRLIssuingPointRecord; + +public class CRLLdapValidator implements SSLCertificateApprovalCallback { + + public static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CRLLdapValidator.class); + + private LDAPStore crlStore; + + + + public CRLLdapValidator(LDAPStore crlStore) { + super(); + this.crlStore = crlStore; + } + + + @Override + public boolean approve(X509Certificate certificate, ValidityStatus currentStatus) { + logger.info("CRLLdapValidator: validate of peer's certificate for the connection " + certificate.getSubjectDN()); + ICRLIssuingPointRecord pt = null; + try { + X509CertImpl peerCert = new X509CertImpl(certificate.getEncoded()); + Enumeration eCRL = crlStore.searchAllCRLIssuingPointRecord(-1); + AuthorityKeyIdentifierExtension peerAKIExt = (AuthorityKeyIdentifierExtension) peerCert.getExtension(PKIXExtensions.AuthorityKey_Id.toString()); + if(peerAKIExt == null) { + logger.error("CRLLdapValidator: the certificate has not Authority Key Identifier Extension. CRL verification cannot be done."); + return false; + } + while (eCRL.hasMoreElements() && pt == null) { + ICRLIssuingPointRecord tPt = eCRL.nextElement(); + logger.debug("CRLLdapValidator: CRL check issuer " + tPt.getId()); + X509CertImpl caCert = new X509CertImpl(tPt.getCACert()); + try { + SubjectKeyIdentifierExtension caAKIExt = (SubjectKeyIdentifierExtension) caCert.getExtension(PKIXExtensions.SubjectKey_Id.toString()); + if(caAKIExt == null) { + logger.error("CRLLdapValidator: signing certificate missing Subject Key Identifier. Skip CA " + caCert.getName()); + continue; + } + + KeyIdentifier caSKIId = (KeyIdentifier) caAKIExt.get(SubjectKeyIdentifierExtension.KEY_ID); + KeyIdentifier peerAKIId = (KeyIdentifier) peerAKIExt.get(AuthorityKeyIdentifierExtension.KEY_ID); + if(Arrays.equals(caSKIId.getIdentifier(), peerAKIId.getIdentifier())) { + pt = tPt; + } + } catch (IOException e) { + logger.error("CRLLdapValidator: problem extracting key from SKI/AKI"); + } + } + } catch (EBaseException | CertificateException e) { + logger.error("CRLLdapValidator: problem find CRL issuing point. " + e.getMessage(), e); + return false; + } + if (pt == null) { + logger.error("CRLLdapValidator: CRL issuing point not found for " + certificate.getIssuerDN()); + return false; + } + try { + X509CRLImpl crl = new X509CRLImpl(pt.getCRL()); + X509CRLEntry crlentry = crl.getRevokedCertificate(certificate.getSerialNumber()); + + if (crlentry == null) { + if (crlStore.isNotFoundGood()) { + return true; + } + } + } catch (Exception e) { + logger.error("CRLLdapValidator: crl check error. " + e.getMessage(), e); + } + logger.error("CRLLdapValidator: peer certificate not valid"); + return false; + } + +} diff --git a/base/ocsp/src/main/java/com/netscape/cms/ocsp/LDAPStore.java b/base/ocsp/src/main/java/com/netscape/cms/ocsp/LDAPStore.java index fb4e321877c..5485e888df5 100644 --- a/base/ocsp/src/main/java/com/netscape/cms/ocsp/LDAPStore.java +++ b/base/ocsp/src/main/java/com/netscape/cms/ocsp/LDAPStore.java @@ -17,7 +17,6 @@ // --- END COPYRIGHT BLOCK --- package com.netscape.cms.ocsp; -import java.lang.Integer; import java.math.BigInteger; import java.security.MessageDigest; import java.security.cert.X509CRL; @@ -83,6 +82,11 @@ public class LDAPStore implements IDefStore, IExtendedPluginInfo { private static final String PROP_HOST = "host"; private static final String PROP_PORT = "port"; + // This option enables the revocation verification of peer certificates using the CRL stored in the LDAP. + // Peer certificate of all the outcome connections from the OCSP subsystem are verified with the CRL. + // If also auths.revocationChecking.is set to true the peer certificate og all the income connections to the OCSP subsystem are verified with the CRL. + private static final String PROP_VALIDATE_CONNECTION_WITH_CRL = "validateConnCertWithCRL"; + private final static String PROP_NOT_FOUND_GOOD = "notFoundAsGood"; private final static String PROP_INCLUDE_NEXT_UPDATE = "includeNextUpdate"; @@ -94,6 +98,8 @@ public class LDAPStore implements IDefStore, IExtendedPluginInfo { private String mCACertAttr = null; protected Hashtable mReqCounts = new Hashtable<>(); private Hashtable mCRLs = new Hashtable<>(); + private boolean mValidateConnection = true; + /** * Constructs the default store. @@ -137,6 +143,7 @@ public void init(IConfigStore config, DBSubsystem dbSubsystem) throws EBaseExcep DEF_CA_CERT_ATTR); mByName = mConfig.getBoolean(PROP_BY_NAME, true); + mValidateConnection = mConfig.getBoolean(PROP_VALIDATE_CONNECTION_WITH_CRL, true); } /** @@ -238,6 +245,9 @@ public void startup() throws EBaseException { updater.start(); } + if(mValidateConnection) { + CMS.getCMSEngine().setApprovalCallback(new CRLLdapValidator(this)); + } } @Override @@ -490,6 +500,11 @@ public void setConfigParameters(NameValuePairs pairs) mConfig.put(key, pairs.get(key)); } } + + public boolean isCRLCheckAvailable() { + return mValidateConnection; + } + } class CRLUpdater extends Thread { diff --git a/base/ocsp/src/main/java/org/dogtagpki/server/ocsp/OCSPEngine.java b/base/ocsp/src/main/java/org/dogtagpki/server/ocsp/OCSPEngine.java index bba325e51ae..457e3bdd7e0 100644 --- a/base/ocsp/src/main/java/org/dogtagpki/server/ocsp/OCSPEngine.java +++ b/base/ocsp/src/main/java/org/dogtagpki/server/ocsp/OCSPEngine.java @@ -18,10 +18,28 @@ package org.dogtagpki.server.ocsp; +import java.io.IOException; +import java.security.cert.CertificateException; +import java.security.cert.X509CRLEntry; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Enumeration; + import javax.servlet.annotation.WebListener; +import org.mozilla.jss.netscape.security.x509.AuthorityKeyIdentifierExtension; +import org.mozilla.jss.netscape.security.x509.KeyIdentifier; +import org.mozilla.jss.netscape.security.x509.PKIXExtensions; +import org.mozilla.jss.netscape.security.x509.SubjectKeyIdentifierExtension; +import org.mozilla.jss.netscape.security.x509.X509CRLImpl; +import org.mozilla.jss.netscape.security.x509.X509CertImpl; +import org.mozilla.jss.ssl.SSLCertificateApprovalCallback.ValidityStatus; + +import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.ISubsystem; +import com.netscape.certsrv.dbs.crldb.ICRLIssuingPointRecord; +import com.netscape.cms.ocsp.LDAPStore; import com.netscape.cmscore.apps.CMS; import com.netscape.cmscore.apps.CMSEngine; import com.netscape.cmscore.apps.EngineConfig; @@ -63,5 +81,135 @@ public void initSubsystem(ISubsystem subsystem, IConfigStore subsystemConfig) th } super.initSubsystem(subsystem, subsystemConfig); + if (subsystem instanceof OCSPAuthority) { + subsystem.startup(); + } + } + + + protected void startupSubsystems() throws Exception { + + for (ISubsystem subsystem : subsystems.values()) { + logger.info("CMSEngine: Starting " + subsystem.getId() + " subsystem"); + if (!(subsystem instanceof OCSPAuthority)) + subsystem.startup(); + } + + // global admin servlet. (anywhere else more fit for this ?) } + @Override + protected void initSequence() throws Exception { + + initDebug(); + init(); + initPasswordStore(); + initSubsystemListeners(); + initSecurityProvider(); + initPluginRegistry(); + initLogSubsystem(); + initDatabase(); + initJssSubsystem(); + initDBSubsystem(); + initUGSubsystem(); + initOIDLoaderSubsystem(); + initX500NameSubsystem(); + // skip TP subsystem; + // problem in needing dbsubsystem in constructor. and it's not used. + initRequestSubsystem(); + + + startupSubsystems(); + + initAuthSubsystem(); + initAuthzSubsystem(); + initJobsScheduler(); + + configureAutoShutdown(); + configureServerCertNickname(); + configureExcludedLdapAttrs(); + + initSecurityDomain(); + } + + @Override + public boolean isRevoked(X509Certificate[] certificates) { + LDAPStore crlStore = null; + for (ISubsystem subsystem : subsystems.values()) { + if (subsystem instanceof OCSPAuthority) { + OCSPAuthority ocsp = (OCSPAuthority) subsystem; + if (ocsp.getDefaultStore() instanceof LDAPStore) { + crlStore = (LDAPStore) ocsp.getDefaultStore(); + } + break; + } + } + + if (crlStore == null || !crlStore.isCRLCheckAvailable()) { + return super.isRevoked(certificates); + } + + for (X509Certificate cert: certificates) { + if(!crlCertValid(crlStore, cert, null)) { + return true; + } + } + return false; + + } + + + private boolean crlCertValid(LDAPStore crlStore, X509Certificate certificate, ValidityStatus currentStatus) { + logger.info("OCSPEngine: validate of peer's certificate for the connection " + certificate.getSubjectX500Principal()); + ICRLIssuingPointRecord pt = null; + try { + X509CertImpl peerCert = new X509CertImpl(certificate.getEncoded()); + Enumeration eCRL = crlStore.searchAllCRLIssuingPointRecord(-1); + AuthorityKeyIdentifierExtension peerAKIExt = (AuthorityKeyIdentifierExtension) peerCert.getExtension(PKIXExtensions.AuthorityKey_Id.toString()); + if(peerAKIExt == null) { + logger.error("OCSPEngine: the certificate has not Authority Key Identifier Extension. CRL verification cannot be done."); + return false; + } + while (eCRL.hasMoreElements() && pt == null) { + ICRLIssuingPointRecord tPt = eCRL.nextElement(); + logger.debug("OCSPEngine: CRL check issuer " + tPt.getId()); + X509CertImpl caCert = new X509CertImpl(tPt.getCACert()); + + try { + SubjectKeyIdentifierExtension caSKIExt = (SubjectKeyIdentifierExtension) caCert.getExtension(PKIXExtensions.SubjectKey_Id.toString()); + if(caSKIExt == null) { + logger.error("OCSPEngine: signing certificate missing Subject Key Identifier. Skip CA " + caCert.getName()); + continue; + } + + KeyIdentifier caSKIId = (KeyIdentifier) caSKIExt.get(SubjectKeyIdentifierExtension.KEY_ID); + KeyIdentifier peerAKIId = (KeyIdentifier) peerAKIExt.get(AuthorityKeyIdentifierExtension.KEY_ID); + if(Arrays.equals(caSKIId.getIdentifier(), peerAKIId.getIdentifier())) { + pt = tPt; + } + } catch (IOException e) { + logger.error("OCSPEngine: problem extracting key from SKI/AKI"); + } + } + } catch (EBaseException | CertificateException e) { + logger.error("OCSPEngine: problem find CRL issuing point for " + certificate.getIssuerX500Principal().toString()); + return false; + } + if (pt == null) { + logger.error("OCSPEngine: CRL issuing point not found for " + certificate.getIssuerX500Principal().toString()); + return false; + } + try { + X509CRLImpl crl = new X509CRLImpl(pt.getCRL()); + X509CRLEntry crlentry = crl.getRevokedCertificate(certificate.getSerialNumber()); + + if (crlentry == null && crlStore.isNotFoundGood()) { + return true; + } + } catch (Exception e) { + logger.error("OCSPEngine: crl check error. " + e.getMessage()); + } + logger.info("OCSPEngine: peer certificate not valid"); + return false; + } + } diff --git a/base/server/cmsbundle/src/LogMessages.properties b/base/server/cmsbundle/src/LogMessages.properties index 12815638856..fe8eac2ccb5 100644 --- a/base/server/cmsbundle/src/LogMessages.properties +++ b/base/server/cmsbundle/src/LogMessages.properties @@ -68,7 +68,7 @@ CMSCORE_AUTH_AUTH_FAILED=Failed to authenticate as admin UID={0}. Error: {1} CMSCORE_AUTH_UID_NOT_FOUND=UID {0} is not a user in the internal usr/grp database. Error {1} CMSCORE_AUTH_MISSING_CERT=Agent authentication missing certificate credential. CMSCORE_AUTH_NO_CERT=No Client Certificate Found -CMSCORE_AUTH_REVOKED_CERT=Cannot authenticate agent. Agent certificate has been revoked. +CMSCORE_AUTH_REVOKED_CERT=Cannot authenticate user. Role user certificate has been revoked. CMSCORE_AUTH_AGENT_AUTH_FAILED=Cannot authenticate agent with certificate Serial 0x{0} Subject DN {1}. Error: {2} CMSCORE_AUTH_CANNOT_AGENT_AUTH=Cannot authenticate agent. LDAP Error: {0} CMSCORE_AUTH_AGENT_USER_NOT_FOUND=Cannot authenticate agent. Could not find a user for the agent cert. Check errors from UGSubsystem. diff --git a/base/server/src/main/java/com/netscape/cmscore/apps/CMSEngine.java b/base/server/src/main/java/com/netscape/cmscore/apps/CMSEngine.java index 7671828fa70..dcedec8db42 100644 --- a/base/server/src/main/java/com/netscape/cmscore/apps/CMSEngine.java +++ b/base/server/src/main/java/com/netscape/cmscore/apps/CMSEngine.java @@ -50,6 +50,7 @@ import org.mozilla.jss.crypto.SignatureAlgorithm; import org.mozilla.jss.netscape.security.util.Cert; import org.mozilla.jss.netscape.security.x509.X509CertImpl; +import org.mozilla.jss.ssl.SSLCertificateApprovalCallback; import com.netscape.certsrv.authentication.ISharedToken; import com.netscape.certsrv.base.EBaseException; @@ -151,6 +152,8 @@ public class CMSEngine implements ServletContextListener { protected LogSubsystem logSubsystem = LogSubsystem.getInstance(); protected JssSubsystem jssSubsystem = JssSubsystem.getInstance(); protected DBSubsystem dbSubsystem = new DBSubsystem(); + protected SSLCertificateApprovalCallback approvalCallback; + protected RequestRepository requestRepository; @@ -301,6 +304,14 @@ public void registerPendingListener(String name, IRequestListener listener) { pendingNotifier.registerListener(name, listener); } + public SSLCertificateApprovalCallback getApprovalCallback() { + return approvalCallback; + } + + public void setApprovalCallback(SSLCertificateApprovalCallback approvalCallback) { + this.approvalCallback = approvalCallback; + } + public void loadConfig(String path) throws Exception { ConfigStorage storage = new FileConfigStore(path); config = createConfig(storage); @@ -1102,6 +1113,28 @@ public void start() throws Exception { CMS.setCMSEngine(this); + initSequence(); + + // Register realm for this subsystem + ProxyRealm.registerRealm(id, new PKIRealm()); + + ready = true; + isStarted = true; + + mStartupTime = System.currentTimeMillis(); + + logger.info(name + " engine started"); + // Register TomcatJSS socket listener + TomcatJSS tomcatJss = TomcatJSS.getInstance(); + if(serverSocketListener == null) { + serverSocketListener = new PKIServerSocketListener(); + } + tomcatJss.addSocketListener(serverSocketListener); + + notifySubsystemStarted(); + } + + protected void initSequence() throws Exception { initDebug(); initPasswordStore(); initSubsystemListeners(); @@ -1131,24 +1164,6 @@ public void start() throws Exception { configureExcludedLdapAttrs(); initSecurityDomain(); - - // Register realm for this subsystem - ProxyRealm.registerRealm(id, new PKIRealm()); - - ready = true; - isStarted = true; - - mStartupTime = System.currentTimeMillis(); - - logger.info(name + " engine started"); - // Register TomcatJSS socket listener - TomcatJSS tomcatJss = TomcatJSS.getInstance(); - if(serverSocketListener == null) { - serverSocketListener = new PKIServerSocketListener(); - } - tomcatJss.addSocketListener(serverSocketListener); - - notifySubsystemStarted(); } public boolean isInRunningState() { diff --git a/base/server/src/main/java/com/netscape/cmscore/ldapconn/PKISocketFactory.java b/base/server/src/main/java/com/netscape/cmscore/ldapconn/PKISocketFactory.java index ccbebdc7c0c..b1c5b2db2f7 100644 --- a/base/server/src/main/java/com/netscape/cmscore/ldapconn/PKISocketFactory.java +++ b/base/server/src/main/java/com/netscape/cmscore/ldapconn/PKISocketFactory.java @@ -27,6 +27,7 @@ import java.util.logging.Logger; import org.dogtagpki.server.PKIClientSocketListener; +import org.mozilla.jss.ssl.SSLCertificateApprovalCallback; import org.mozilla.jss.ssl.SSLClientCertificateSelectionCallback; import org.mozilla.jss.ssl.SSLHandshakeCompletedEvent; import org.mozilla.jss.ssl.SSLHandshakeCompletedListener; @@ -149,9 +150,14 @@ public SSLSocket makeSSLSocket(String host, int port) throws UnknownHostExceptio */ SSLSocket s; + SSLCertificateApprovalCallback callback = null; + + if (CMS.getCMSEngine() != null) { + callback = CMS.getCMSEngine().getApprovalCallback(); + } if (mClientAuthCertNickname == null) { - s = new SSLSocket(host, port); + s = new SSLSocket(host, port, null, 0, callback, null); } else { // Let's create a selection callback in the case the client auth @@ -161,7 +167,7 @@ public SSLSocket makeSSLSocket(String host, int port) throws UnknownHostExceptio Socket js = new Socket(InetAddress.getByName(host), port); s = new SSLSocket(js, host, - null, + callback, new SSLClientCertificateSelectionCB(mClientAuthCertNickname)); }