Skip to content

Commit

Permalink
Modify init order for OCSP subsystem
Browse files Browse the repository at this point in the history
The init order for OCSP is modified to allow CRL retrieval before
creating connection with DS or other services. Secure`connections will be
verified against the CRL.

Solve RHCS-4262

Add callback for CRL validation at application level

Add new field in CMS for a callback validation of certificate
instantiated by PKISocketFactory.

This is useful for OCSP where the OCSP protocol cannot be enabled and
the verification is done on CRLs.

Solve RHCS-4262

Make crl check for connection optional

Add a new parameter to enable the crl check for OCSP connection when
acting as client. The new parameter is
`ocsp.store.ldapStore.checkSubsystemConnection` and its default value is
`false`. When set to `true` connection certificate are verified using
the crl stored in the LDAP.

Add crl check for OCSP acting as server

When OCSP is acting as server certificate can be verified using CRL
internally stored.

To verify the certificates the `LDAPStore` has to be enabled with the
variable `ocsp.store.ldapStore.checkSubsystemConnection` and the
variable `auths.revocationChecking.enabled` both set to true.

Solve RHCS-4262

Move callback reference from CMS to CMSEngine

Socket callback moved to CMSEngine to avoid dependencies on global
variables.

OCSP default CRL check and CA cert validation

The parameter `ocsp.store.ldapStore.checkSubsystemConnection` default
value has been modified to `true` so when LDAPStore is used certificates
are verified against the CRL.

Additionally, during the certificate verification the certificate signer
is verified with the CA certificate providing the CRL to be sure it is
the real issuer.

Rename checkSubsystemConnection to validateConnCertWithCRL

The option `ocsp.store.ldapStore.validateConnCertWithCRL` enables the
revocation verification of peer certificates using the CRL stored in the LDAP
shared with the CA.

When it is set to `true` (default value), the peer certificate of all the outcome connections from the OCSP subsystem are verified with the CRL.

If the option `auths.revocationChecking.enabled` is also set to `true` the peer certificate ot all the income connections to the OCSP subsystem are verified with the CRL.

Use AKI/SKI to match peer certificate with CA CRL

Identification of CRL issuing point done by matching Authority Key
Identifier with Subject Key Identifier instead of DN matching.

This should make more reliable the check because not affected of
encoding or format changes in the DN.

Add comment for the option ocsp.store.ldapStore.validateConnCertWithCRL

Modify local variable names

Update log message for revoked certificate

Modify the callback location

Due to refactoring the engine object is not accessible using static
reference from outside the declaring package. Therefore the callback
reference have been stored globally in the `CMSEngine` class

Improve OCSP exception handling

Add stack trace for error logs when they are generated from internal
error

Move the callback to PKISocketFactory and fix startup

Moving the callback to `PKISocketFactory` there is no need to have store
it in a static variable. However, only OCSPEngine instances have a valid
value so no other instances are used.

The startup order has been fixed.
  • Loading branch information
fmarco76 committed Aug 17, 2023
1 parent c7ea684 commit 8b24b48
Show file tree
Hide file tree
Showing 7 changed files with 343 additions and 18 deletions.
108 changes: 108 additions & 0 deletions base/ocsp/src/main/java/com/netscape/cms/ocsp/CRLLdapValidator.java
Original file line number Diff line number Diff line change
@@ -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.cmscore.dbs.CRLIssuingPointRecord;

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());
CRLIssuingPointRecord pt = null;
try {
X509CertImpl peerCert = new X509CertImpl(certificate.getEncoded());
Enumeration<CRLIssuingPointRecord> 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) {
CRLIssuingPointRecord 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: " + e.getMessage(), e);
}
}
} 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;
}

}
17 changes: 17 additions & 0 deletions base/ocsp/src/main/java/com/netscape/cms/ocsp/LDAPStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.Locale;
import java.util.Vector;

import org.dogtagpki.server.ocsp.OCSPEngine;
import org.mozilla.jss.asn1.GeneralizedTime;
import org.mozilla.jss.asn1.INTEGER;
import org.mozilla.jss.netscape.security.x509.RevokedCertificate;
Expand Down Expand Up @@ -82,6 +83,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";
Expand All @@ -93,6 +99,8 @@ public class LDAPStore implements IDefStore, IExtendedPluginInfo {
private String mCACertAttr = null;
protected Hashtable<String, Long> mReqCounts = new Hashtable<>();
private Hashtable<X509CertImpl, X509CRLImpl> mCRLs = new Hashtable<>();
private boolean mValidateConnection = true;


/**
* Constructs the default store.
Expand Down Expand Up @@ -140,6 +148,7 @@ public void init(ConfigStore config, DBSubsystem dbSubsystem) throws EBaseExcept
DEF_CA_CERT_ATTR);
mByName = mConfig.getBoolean(PROP_BY_NAME, true);

mValidateConnection = mConfig.getBoolean(PROP_VALIDATE_CONNECTION_WITH_CRL, true);
}

/**
Expand Down Expand Up @@ -277,6 +286,9 @@ public void startup() throws EBaseException {

updater.start();
}
if(mValidateConnection && OCSPEngine.getInstance() != null) {
OCSPEngine.getInstance().setApprovalCallback(new CRLLdapValidator(this));
}
}

@Override
Expand Down Expand Up @@ -531,6 +543,11 @@ public void setConfigParameters(NameValuePairs pairs)
mConfig.put(key, pairs.get(key));
}
}

public boolean isCRLCheckAvailable() {
return mValidateConnection;
}

}

class CRLUpdater extends Thread {
Expand Down
173 changes: 173 additions & 0 deletions base/ocsp/src/main/java/org/dogtagpki/server/ocsp/OCSPEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,33 @@

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 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.Subsystem;
import com.netscape.cms.ocsp.LDAPStore;
import com.netscape.cmscore.apps.CMSEngine;
import com.netscape.cmscore.apps.DatabaseConfig;
import com.netscape.cmscore.base.ConfigStorage;
import com.netscape.cmscore.base.ConfigStore;
import com.netscape.cmscore.dbs.CRLIssuingPointRecord;
import com.netscape.cmscore.dbs.DBSubsystem;
import com.netscape.cmscore.ldapconn.LDAPConfig;
import com.netscape.cmscore.ldapconn.PKISocketConfig;
import com.netscape.cmsutil.password.PasswordStore;
import com.netscape.ocsp.OCSPAuthority;

public class OCSPEngine extends CMSEngine {
Expand Down Expand Up @@ -51,6 +74,13 @@ public OCSPAuthority getOCSP() {
return (OCSPAuthority) getSubsystem(OCSPAuthority.ID);
}

@Override
public void initDBSubsystem() throws Exception {

dbSubsystem = new DBSubsystem();
dbSubsystem.setCMSEngine(this);
dbSubsystem.setEngineConfig(config);
}
@Override
public void initSubsystem(Subsystem subsystem, ConfigStore subsystemConfig) throws Exception {

Expand All @@ -60,5 +90,148 @@ public void initSubsystem(Subsystem subsystem, ConfigStore subsystemConfig) thro
}

super.initSubsystem(subsystem, subsystemConfig);
if (subsystem instanceof OCSPAuthority) {
subsystem.startup();
}
}


protected void startupSubsystems() throws Exception {

for (Subsystem subsystem : subsystems.values()) {
logger.info("CMSEngine: Starting " + subsystem.getId() + " subsystem");
if (!(subsystem instanceof OCSPAuthority)) {
DatabaseConfig dbConfig = config.getDatabaseConfig();
LDAPConfig ldapConfig = dbConfig.getLDAPConfig();
PKISocketConfig socketConfig = config.getSocketConfig();
PasswordStore passwordStore = getPasswordStore();
dbSubsystem.init(dbConfig, ldapConfig, socketConfig, passwordStore);
subsystem.startup();
}
}

// global admin servlet. (anywhere else more fit for this ?)
}
@Override
protected void initSequence() throws Exception {


initDebug();
initAuditor();
initDBSubsystem();
init();
initPasswordStore();
initSubsystemListeners();
initSecurityProvider();
initPluginRegistry();
initLogSubsystem();

initClientSocketListener();
initServerSocketListener();

testLDAPConnections();
initDatabase();

initJssSubsystem();
initUGSubsystem();
initOIDLoaderSubsystem();
initX500NameSubsystem();
// skip TP subsystem;
// problem in needing dbsubsystem in constructor. and it's not used.
initRequestSubsystem();


startupSubsystems();

initAuthSubsystem();
initAuthzSubsystem();
initCMSGateway();
initJobsScheduler();

configureAutoShutdown();
configureServerCertNickname();

initSecurityDomain();
}

@Override
public boolean isRevoked(X509Certificate[] certificates) {
LDAPStore crlStore = null;
for (Subsystem 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());
CRLIssuingPointRecord pt = null;
try {
X509CertImpl peerCert = new X509CertImpl(certificate.getEncoded());
Enumeration<CRLIssuingPointRecord> 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) {
CRLIssuingPointRecord 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: " + e.getMessage(), e);
}
}
} 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(), e);
}
logger.info("OCSPEngine: peer certificate not valid");
return false;
}
}
Loading

0 comments on commit 8b24b48

Please sign in to comment.