// --- 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) 2007 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package com.netscape.cmscore.dbs;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import netscape.ldap.LDAPAttributeSet;
import netscape.ldap.LDAPEntry;
import netscape.ldap.LDAPSearchResults;
import netscape.security.x509.CertificateValidity;
import netscape.security.x509.RevokedCertImpl;
import netscape.security.x509.X500Name;
import netscape.security.x509.X509CertImpl;
import netscape.security.x509.X509CertInfo;
import com.netscape.certsrv.apps.CMS;
import com.netscape.certsrv.base.EBaseException;
import com.netscape.certsrv.base.MetaInfo;
import com.netscape.certsrv.base.SessionContext;
import com.netscape.certsrv.ca.ICRLIssuingPoint;
import com.netscape.certsrv.dbs.EDBException;
import com.netscape.certsrv.dbs.IDBSSession;
import com.netscape.certsrv.dbs.IDBSearchResults;
import com.netscape.certsrv.dbs.IDBSubsystem;
import com.netscape.certsrv.dbs.IDBVirtualList;
import com.netscape.certsrv.dbs.IElementProcessor;
import com.netscape.certsrv.dbs.Modification;
import com.netscape.certsrv.dbs.ModificationSet;
import com.netscape.certsrv.dbs.certdb.ICertRecord;
import com.netscape.certsrv.dbs.certdb.ICertRecordList;
import com.netscape.certsrv.dbs.certdb.ICertificateRepository;
import com.netscape.certsrv.dbs.certdb.IRevocationInfo;
import com.netscape.certsrv.dbs.repository.IRepository;
import com.netscape.certsrv.logging.ILogger;
/**
* A class represents a certificate repository. It
* stores all the issued certificate.
*
*
* @author thomask
* @author kanda
* @version $Revision$, $Date$
*/
public class CertificateRepository extends Repository
implements ICertificateRepository {
public final String CERT_X509ATTRIBUTE = "x509signedcert";
private IDBSubsystem mDBService;
private String mBaseDN;
private String mRequestBaseDN;
private boolean mConsistencyCheck = false;
@SuppressWarnings("unused")
private boolean mSkipIfInconsistent;
private Hashtable mCRLIssuingPoints = new Hashtable();
private int mTransitMaxRecords = 1000000;
private int mTransitRecordPageSize = 200;
CertStatusUpdateTask certStatusUpdateTask;
RetrieveModificationsTask retrieveModificationsTask;
IRepository requestRepository;
/**
* Constructs a certificate repository.
*/
public CertificateRepository(IDBSubsystem dbService, String certRepoBaseDN, int increment, String baseDN)
throws EDBException {
super(dbService, increment, baseDN);
mBaseDN = certRepoBaseDN;
mDBService = dbService;
}
public ICertRecord createCertRecord(BigInteger id, Certificate cert, MetaInfo meta) {
return new CertRecord(id, cert, meta);
}
public BigInteger getLastSerialNumberInRange(BigInteger serial_low_bound, BigInteger serial_upper_bound)
throws EBaseException {
CMS.debug("CertificateRepository: in getLastSerialNumberInRange: low "
+ serial_low_bound + " high " + serial_upper_bound);
if (serial_low_bound == null
|| serial_upper_bound == null || serial_low_bound.compareTo(serial_upper_bound) >= 0) {
return null;
}
String ldapfilter = "(" + "certstatus" + "=*" + ")";
String[] attrs = null;
ICertRecordList recList =
findCertRecordsInList(ldapfilter, attrs, serial_upper_bound.toString(10), "serialno", 5 * -1);
int size = recList.getSize();
CMS.debug("CertificateRepository:getLastSerialNumberInRange: recList size " + size);
if (size <= 0) {
CMS.debug("CertificateRepository:getLastSerialNumberInRange: index may be empty");
BigInteger ret = new BigInteger(serial_low_bound.toString(10));
ret = ret.add(new BigInteger("-1"));
CMS.debug("CertificateRepository:getLastCertRecordSerialNo: returning " + ret);
return ret;
}
int ltSize = recList.getSizeBeforeJumpTo();
CMS.debug("CertificateRepository:getLastSerialNumberInRange: ltSize " + ltSize);
CertRecord curRec = null;
int i;
Object obj = null;
for (i = 0; i < 5; i++) {
obj = recList.getCertRecord(i);
if (obj != null) {
curRec = (CertRecord) obj;
BigInteger serial = curRec.getSerialNumber();
CMS.debug("CertificateRepository:getLastCertRecordSerialNo: serialno " + serial);
if (((serial.compareTo(serial_low_bound) == 0) || (serial.compareTo(serial_low_bound) == 1)) &&
((serial.compareTo(serial_upper_bound) == 0) || (serial.compareTo(serial_upper_bound) == -1))) {
CMS.debug("getLastSerialNumberInRange returning: " + serial);
return serial;
}
} else {
CMS.debug("getLastSerialNumberInRange:found null from getCertRecord");
}
}
BigInteger ret = new BigInteger(serial_low_bound.toString(10));
ret = ret.add(new BigInteger("-1"));
CMS.debug("CertificateRepository:getLastCertRecordSerialNo: returning " + ret);
return ret;
}
/**
* Removes all objects with this repository.
*/
public void removeCertRecords(BigInteger beginS, BigInteger endS) throws EBaseException {
String filter = "(" + CertRecord.ATTR_CERT_STATUS + "=*" + ")";
ICertRecordList list = findCertRecordsInList(filter,
null, "serialno", 10);
int size = list.getSize();
Enumeration e = list.getCertRecords(0, size - 1);
while (e.hasMoreElements()) {
CertRecord rec = (CertRecord) e.nextElement();
BigInteger cur = rec.getSerialNumber();
BigInteger max = cur.max(beginS);
BigInteger min = cur;
if (endS != null)
min = cur.min(endS);
if (cur.equals(beginS) || cur.equals(endS) ||
(cur.equals(max) && cur.equals(min)))
deleteCertificateRecord(cur);
}
}
public void setConsistencyCheck(boolean ConsistencyCheck) {
mConsistencyCheck = ConsistencyCheck;
}
public void setSkipIfInConsistent(boolean SkipIfInconsistent) {
mSkipIfInconsistent = SkipIfInconsistent;
}
public void setTransitMaxRecords(int max) {
mTransitMaxRecords = max;
}
public void setTransitRecordPageSize(int size) {
mTransitRecordPageSize = size;
}
/**
* register CRL Issuing Point
*/
public void addCRLIssuingPoint(String id, ICRLIssuingPoint crlIssuingPoint) {
mCRLIssuingPoints.put(id, crlIssuingPoint);
}
/**
* interval value: (in seconds)
* 0 - disable
* >0 - enable
*/
public void setCertStatusUpdateInterval(IRepository requestRepository, int interval, boolean listenToCloneModifications) {
CMS.debug("In setCertStatusUpdateInterval " + interval);
this.requestRepository = requestRepository;
// stop running tasks
if (certStatusUpdateTask != null) {
certStatusUpdateTask.stop();
}
if (retrieveModificationsTask != null) {
retrieveModificationsTask.stop();
}
if (interval == 0) {
CMS.debug("In setCertStatusUpdateInterval interval = 0");
return;
}
CMS.debug("In setCertStatusUpdateInterval listenToCloneModifications=" + listenToCloneModifications);
if (listenToCloneModifications) {
CMS.debug("In setCertStatusUpdateInterval listening to modifications");
try {
retrieveModificationsTask = new RetrieveModificationsTask(this);
retrieveModificationsTask.start();
} catch (EBaseException e) {
retrieveModificationsTask = null;
e.printStackTrace();
}
}
CMS.debug("In setCertStatusUpdateInterval scheduling cert status update every " + interval + " seconds.");
certStatusUpdateTask = new CertStatusUpdateTask(this, interval);
certStatusUpdateTask.start();
}
/**
* This method blocks when another thread (such as the CRL Update) is running
*/
public synchronized void updateCertStatus() {
CMS.debug("In updateCertStatus()");
try {
CMS.getLogger().log(ILogger.EV_SYSTEM, ILogger.S_OTHER,
CMS.getLogMessage("CMSCORE_DBS_START_VALID_SEARCH"));
transitInvalidCertificates();
CMS.getLogger().log(ILogger.EV_SYSTEM, ILogger.S_OTHER,
CMS.getLogMessage("CMSCORE_DBS_FINISH_VALID_SEARCH"));
CMS.getLogger().log(ILogger.EV_SYSTEM, ILogger.S_OTHER,
CMS.getLogMessage("CMSCORE_DBS_START_EXPIRED_SEARCH"));
transitValidCertificates();
CMS.getLogger().log(ILogger.EV_SYSTEM, ILogger.S_OTHER,
CMS.getLogMessage("CMSCORE_DBS_FINISH_EXPIRED_SEARCH"));
CMS.getLogger().log(ILogger.EV_SYSTEM, ILogger.S_OTHER,
CMS.getLogMessage("CMSCORE_DBS_START_REVOKED_EXPIRED_SEARCH"));
transitRevokedExpiredCertificates();
CMS.getLogger().log(ILogger.EV_SYSTEM, ILogger.S_OTHER,
CMS.getLogMessage("CMSCORE_DBS_FINISH_REVOKED_EXPIRED_SEARCH"));
CMS.debug("Starting cert checkRanges");
checkRanges();
CMS.debug("cert checkRanges done");
CMS.debug("Starting request checkRanges");
requestRepository.checkRanges();
CMS.debug("request checkRanges done");
} catch (Exception e) {
CMS.debug("updateCertStatus done: " + e.toString());
}
}
public synchronized void processRevokedCerts(IElementProcessor p, String filter, int pageSize)
throws EBaseException {
CMS.debug("Starting processRevokedCerts (entered lock)");
ICertRecordList list = findCertRecordsInList(filter,
new String[] { ICertRecord.ATTR_ID, ICertRecord.ATTR_REVO_INFO, "objectclass" },
"serialno",
pageSize);
int totalSize = list.getSize();
list.processCertRecords(0, totalSize - 1, p);
CMS.debug("processRevokedCerts done");
}
/**
* Retrieves DN of this repository.
*/
public String getDN() {
return mBaseDN;
}
public void setRequestDN(String requestDN) {
mRequestBaseDN = requestDN;
}
public String getRequestDN() {
return mRequestBaseDN;
}
/**
* Retrieves backend database handle.
*/
public IDBSubsystem getDBSubsystem() {
return mDBService;
}
/**
* Adds a certificate record to the repository. Each certificate
* record contains four parts: certificate, meta-attributes,
* issue information and reovcation information.
*
*
* @param cert X.509 certificate
* @exception EBaseException failed to add new certificate to
* the repository
*/
public void addCertificateRecord(ICertRecord record)
throws EBaseException {
IDBSSession s = mDBService.createSession();
try {
String name = "cn" + "=" +
((CertRecord) record).getSerialNumber().toString() + "," + getDN();
SessionContext ctx = SessionContext.getContext();
String uid = (String) ctx.get(SessionContext.USER_ID);
if (uid == null) {
// XXX is this right?
record.set(CertRecord.ATTR_ISSUED_BY, "system");
/**
* System.out.println("XXX servlet should set USER_ID");
* throw new EBaseException(BaseResources.UNKNOWN_PRINCIPAL_1,
* "null");
**/
} else {
record.set(CertRecord.ATTR_ISSUED_BY, uid);
}
// Check validity of this certificate. If it is not invalid,
// mark it so. We will have a thread to transit the status
// from INVALID to VALID.
X509CertImpl x509cert = (X509CertImpl) record.get(
CertRecord.ATTR_X509CERT);
if (x509cert != null) {
Date now = CMS.getCurrentDate();
if (x509cert.getNotBefore().after(now)) {
// not yet valid
record.set(ICertRecord.ATTR_CERT_STATUS,
ICertRecord.STATUS_INVALID);
}
}
s.add(name, record);
} finally {
if (s != null)
s.close();
}
}
/**
* Used by the Clone Master (CLA) to add a revoked certificate
* record to the repository.
*
*
* @param record a CertRecord
* @exception EBaseException failed to add new certificate to
* the repository
*/
public void addRevokedCertRecord(CertRecord record)
throws EBaseException {
IDBSSession s = mDBService.createSession();
try {
String name = "cn" + "=" +
record.getSerialNumber().toString() + "," + getDN();
s.add(name, record);
} finally {
if (s != null)
s.close();
}
}
/**
* This transits a certificate status from VALID to EXPIRED
* if a certificate becomes expired.
*/
public void transitValidCertificates() throws EBaseException {
Date now = CMS.getCurrentDate();
ICertRecordList recList = getValidCertsByNotAfterDate(now, -1 * mTransitRecordPageSize);
int size = recList.getSize();
if (size <= 0) {
CMS.debug("index may be empty");
return;
}
int ltSize = recList.getSizeBeforeJumpTo();
ltSize = Math.min(ltSize, mTransitMaxRecords);
Vector cList = new Vector(ltSize);
CMS.debug("transidValidCertificates: list size: " + size);
CMS.debug("transitValidCertificates: ltSize " + ltSize);
CertRecord curRec = null;
int i;
ICertRecord obj = null;
for (i = 0; i < ltSize; i++) {
obj = recList.getCertRecord(i);
if (obj != null) {
curRec = (CertRecord) obj;
Date notAfter = curRec.getNotAfter();
//CMS.debug("notAfter " + notAfter.toString() + " now " + now.toString());
if (notAfter.after(now)) {
CMS.debug("Record does not qualify,notAfter " + notAfter.toString() + " date " + now.toString());
continue;
}
CMS.debug("transitValid: curRec: " + i + " " + curRec.toString());
if (mConsistencyCheck) {
cList.add(curRec);
} else {
cList.add(curRec.getSerialNumber());
}
} else {
CMS.debug("found null from getCertRecord");
}
}
transitCertList(cList, CertRecord.STATUS_EXPIRED);
}
/**
* This transits a certificate status from REVOKED to REVOKED_EXPIRED
* if an revoked certificate becomes expired.
*/
public void transitRevokedExpiredCertificates() throws EBaseException {
Date now = CMS.getCurrentDate();
ICertRecordList recList = getRevokedCertsByNotAfterDate(now, -1 * mTransitRecordPageSize);
int size = recList.getSize();
if (size <= 0) {
CMS.debug("index may be empty");
return;
}
int ltSize = recList.getSizeBeforeJumpTo();
Vector cList = new Vector(ltSize);
ltSize = Math.min(ltSize, mTransitMaxRecords);
CMS.debug("transitRevokedExpiredCertificates: list size: " + size);
CMS.debug("transitRevokedExpiredCertificates: ltSize " + ltSize);
CertRecord curRec = null;
int i;
Object obj = null;
for (i = 0; i < ltSize; i++) {
obj = recList.getCertRecord(i);
if (obj != null) {
curRec = (CertRecord) obj;
CMS.debug("transitRevokedExpired: curRec: " + i + " " + curRec.toString());
Date notAfter = curRec.getNotAfter();
// CMS.debug("notAfter " + notAfter.toString() + " now " + now.toString());
if (notAfter.after(now)) {
CMS.debug("Record does not qualify,notAfter " + notAfter.toString() + " date " + now.toString());
continue;
}
if (mConsistencyCheck) {
cList.add(curRec);
} else {
cList.add(curRec.getSerialNumber());
}
} else {
CMS.debug("found null record in getCertRecord");
}
}
transitCertList(cList, CertRecord.STATUS_REVOKED_EXPIRED);
}
/**
* This transits a certificate status from INVALID to VALID
* if a certificate becomes valid.
*/
public void transitInvalidCertificates() throws EBaseException {
Date now = CMS.getCurrentDate();
ICertRecordList recList = getInvalidCertsByNotBeforeDate(now, -1 * mTransitRecordPageSize);
int size = recList.getSize();
if (size <= 0) {
CMS.debug("index may be empty");
return;
}
int ltSize = recList.getSizeBeforeJumpTo();
ltSize = Math.min(ltSize, mTransitMaxRecords);
Vector cList = new Vector(ltSize);
CMS.debug("transidInValidCertificates: list size: " + size);
CMS.debug("transitInValidCertificates: ltSize " + ltSize);
CertRecord curRec = null;
int i;
Object obj = null;
for (i = 0; i < ltSize; i++) {
obj = recList.getCertRecord(i);
if (obj != null) {
curRec = (CertRecord) obj;
Date notBefore = curRec.getNotBefore();
//CMS.debug("notBefore " + notBefore.toString() + " now " + now.toString());
if (notBefore.after(now)) {
CMS.debug("Record does not qualify,notBefore " + notBefore.toString() + " date " + now.toString());
continue;
}
CMS.debug("transitInValid: curRec: " + i + " " + curRec.toString());
if (mConsistencyCheck) {
cList.add(curRec);
} else {
cList.add(curRec.getSerialNumber());
}
} else {
CMS.debug("found null from getCertRecord");
}
}
transitCertList(cList, CertRecord.STATUS_VALID);
}
private void transitCertList(Vector cList, String newCertStatus) throws EBaseException {
CertRecord cRec = null;
BigInteger serial = null;
int i;
CMS.debug("transitCertList " + newCertStatus);
for (i = 0; i < cList.size(); i++) {
if (mConsistencyCheck) {
cRec = (CertRecord) cList.elementAt(i);
if (cRec == null)
continue;
serial = cRec.getSerialNumber();
} else {
serial = (BigInteger) cList.elementAt(i);
}
updateStatus(serial, newCertStatus);
if (newCertStatus.equals(CertRecord.STATUS_REVOKED_EXPIRED)) {
// inform all CRLIssuingPoints about revoked and expired certificate
Enumeration eIPs = mCRLIssuingPoints.elements();
while (eIPs.hasMoreElements()) {
ICRLIssuingPoint ip = eIPs.nextElement();
if (ip != null) {
ip.addExpiredCert(serial);
}
}
}
CMS.debug("transitCertList number at: " + i + " = " + serial);
}
cList.removeAllElements();
}
/**
* Reads the certificate identified by the given serial no.
*/
public X509CertImpl getX509Certificate(BigInteger serialNo)
throws EBaseException {
ICertRecord cr = readCertificateRecord(serialNo);
return (cr.getCertificate());
}
/**
* Deletes certificate record.
*/
public void deleteCertificateRecord(BigInteger serialNo)
throws EBaseException {
IDBSSession s = mDBService.createSession();
try {
String name = "cn" + "=" +
serialNo.toString() + "," + getDN();
s.delete(name);
} finally {
if (s != null)
s.close();
}
}
/**
* Reads certificate from repository.
*/
public ICertRecord readCertificateRecord(BigInteger serialNo)
throws EBaseException {
IDBSSession s = mDBService.createSession();
CertRecord rec = null;
try {
String name = "cn" + "=" +
serialNo.toString() + "," + getDN();
rec = (CertRecord) s.read(name);
} finally {
if (s != null)
s.close();
}
return rec;
}
public synchronized void modifyCertificateRecord(BigInteger serialNo,
ModificationSet mods) throws EBaseException {
IDBSSession s = mDBService.createSession();
try {
String name = "cn" + "=" +
serialNo.toString() + "," + getDN();
mods.add(CertRecord.ATTR_MODIFY_TIME, Modification.MOD_REPLACE,
CMS.getCurrentDate());
s.modify(name, mods);
} finally {
if (s != null)
s.close();
}
}
/**
* Checks if the specified certificate is in the repository.
*/
public boolean containsCertificate(BigInteger serialNo)
throws EBaseException {
try {
ICertRecord cr = readCertificateRecord(serialNo);
if (cr != null)
return true;
} catch (EBaseException e) {
}
return false;
}
/**
* Marks certificate as revoked.
*/
public void markAsRevoked(BigInteger id, IRevocationInfo info)
throws EBaseException {
ModificationSet mods = new ModificationSet();
mods.add(CertRecord.ATTR_REVO_INFO, Modification.MOD_ADD, info);
SessionContext ctx = SessionContext.getContext();
String uid = (String) ctx.get(SessionContext.USER_ID);
if (uid == null) {
mods.add(CertRecord.ATTR_REVOKED_BY, Modification.MOD_ADD,
"system");
} else {
mods.add(CertRecord.ATTR_REVOKED_BY, Modification.MOD_ADD,
uid);
}
mods.add(CertRecord.ATTR_REVOKED_ON, Modification.MOD_ADD,
CMS.getCurrentDate());
mods.add(CertRecord.ATTR_CERT_STATUS, Modification.MOD_REPLACE,
CertRecord.STATUS_REVOKED);
modifyCertificateRecord(id, mods);
}
/**
* Unmarks revoked certificate.
*/
public void unmarkRevoked(BigInteger id, IRevocationInfo info,
Date revokedOn, String revokedBy)
throws EBaseException {
ModificationSet mods = new ModificationSet();
mods.add(CertRecord.ATTR_REVO_INFO, Modification.MOD_DELETE, info);
mods.add(CertRecord.ATTR_REVOKED_BY, Modification.MOD_DELETE, revokedBy);
mods.add(CertRecord.ATTR_REVOKED_ON, Modification.MOD_DELETE, revokedOn);
mods.add(CertRecord.ATTR_CERT_STATUS, Modification.MOD_REPLACE,
CertRecord.STATUS_VALID);
modifyCertificateRecord(id, mods);
}
/**
* Updates the certificiate record status to the specified.
*/
public void updateStatus(BigInteger id, String status)
throws EBaseException {
CMS.debug("updateStatus: " + id + " status " + status);
ModificationSet mods = new ModificationSet();
mods.add(CertRecord.ATTR_CERT_STATUS, Modification.MOD_REPLACE,
status);
modifyCertificateRecord(id, mods);
}
public Enumeration