/*
*
* Copyright (c) 2013 - 2017 Lijun Liao
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
*
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the XiPKI software without
* disclosing the source code of your own applications.
*
* For more information, please contact Lijun Liao at this
* address: lijun.liao@gmail.com
*/
package org.xipki.pki.ocsp.server.impl.store.crl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.audit.AuditLevel;
import org.xipki.commons.audit.AuditStatus;
import org.xipki.commons.audit.PciAuditEvent;
import org.xipki.commons.common.util.DateUtil;
import org.xipki.commons.common.util.IoUtil;
import org.xipki.commons.common.util.LogUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.common.util.StringUtil;
import org.xipki.commons.datasource.DataSourceWrapper;
import org.xipki.commons.security.CertRevocationInfo;
import org.xipki.commons.security.CrlReason;
import org.xipki.commons.security.HashAlgoType;
import org.xipki.commons.security.util.X509Util;
import org.xipki.pki.ocsp.api.OcspStoreException;
import org.xipki.pki.ocsp.server.impl.store.db.DbCertStatusStore;
/**
* @author Lijun Liao
* @since 2.2.0
*/
public class CrlDbCertStatusStore extends DbCertStatusStore {
public static final String KEY_CA_REVOCATION_TIME = "ca.revocation.time";
public static final String KEY_CA_INVALIDITY_TIME = "ca.invalidity.time";
private class CrlUpdateService implements Runnable {
@Override
public void run() {
try {
initializeStore(datasource);
} catch (Throwable th) {
LogUtil.error(LOG, th, "error while calling initializeStore() for store " + name);
}
}
} // StoreUpdateService
private static final Logger LOG = LoggerFactory.getLogger(CrlDbCertStatusStore.class);
private final AtomicBoolean crlUpdateInProcess = new AtomicBoolean(false);
private X509Certificate caCert;
private X509Certificate issuerCert;
private String crlFilename;
private String crlUrl;
private String certsDirName;
private boolean useUpdateDatesFromCrl;
private boolean crlUpdated;
private boolean crlUpdateFailed;
@Override
public void init(final String conf, final DataSourceWrapper datasource,
final Set<HashAlgoType> certHashAlgos) throws OcspStoreException {
ParamUtil.requireNonBlank("conf", conf);
this.datasource = ParamUtil.requireNonNull("datasource", datasource);
StoreConf storeConf = new StoreConf(conf);
this.crlFilename = IoUtil.expandFilepath(storeConf.getCrlFile());
this.crlUrl = storeConf.getCrlUrl();
this.certsDirName = (storeConf.getCertsDir() == null) ? null
: IoUtil.expandFilepath(storeConf.getCertsDir());
this.caCert = parseCert(storeConf.getCaCertFile());
if (storeConf.getIssuerCertFile() != null) {
this.issuerCert = parseCert(storeConf.getIssuerCertFile());
} else {
this.issuerCert = null;
}
this.useUpdateDatesFromCrl = storeConf.isUseUpdateDatesFromCrl();
initializeStore(datasource);
super.init(conf, datasource, certHashAlgos);
}
@Override
protected List<Runnable> getScheduledServices() {
return Arrays.asList(new CrlUpdateService());
}
@Override
protected boolean isInitialized() {
return crlUpdated && super.isInitialized();
}
@Override
protected boolean isInitializationFailed() {
return crlUpdateFailed || super.isInitializationFailed();
}
private static X509Certificate parseCert(final String certFile) throws OcspStoreException {
try {
return X509Util.parseCert(certFile);
} catch (CertificateException | IOException ex) {
throw new OcspStoreException("could not parse X.509 certificate from file "
+ certFile + ": " + ex.getMessage(), ex);
}
}
private synchronized void initializeStore(DataSourceWrapper datasource) {
if (crlUpdateInProcess.get()) {
return;
}
crlUpdateInProcess.set(true);
Boolean updateCrlSuccessful = null;
File updateMeFile = new File(crlFilename + ".UPDATEME");
if (!updateMeFile.exists()) {
LOG.info("The CRL will not be updated. Create new file {} to force the update",
updateMeFile.getAbsolutePath());
crlUpdated = true;
crlUpdateFailed = false;
return;
}
try {
File fullCrlFile = new File(crlFilename);
if (!fullCrlFile.exists()) {
// file does not exist
LOG.warn("CRL File {} does not exist", crlFilename);
return;
}
auditPciEvent(AuditLevel.INFO, "UPDATE_CERTSTORE", "a newer CRL is available");
updateCrlSuccessful = false;
X509CRL crl = X509Util.parseCrl(crlFilename);
File revFile = new File(crlFilename + ".revocation");
CertRevocationInfo caRevInfo = null;
if (revFile.exists()) {
Properties props = new Properties();
FileInputStream is = new FileInputStream(revFile);
try {
props.load(is);
} finally {
is.close();
}
String str = props.getProperty(KEY_CA_REVOCATION_TIME);
if (StringUtil.isNotBlank(str)) {
Date revocationTime = DateUtil.parseUtcTimeyyyyMMddhhmmss(str);
Date invalidityTime = null;
str = props.getProperty(KEY_CA_INVALIDITY_TIME);
if (StringUtil.isNotBlank(str)) {
invalidityTime = DateUtil.parseUtcTimeyyyyMMddhhmmss(str);
}
caRevInfo = new CertRevocationInfo(CrlReason.UNSPECIFIED,
revocationTime, invalidityTime);
}
}
ImportCrl importCrl = new ImportCrl(datasource, useUpdateDatesFromCrl, crl, crlUrl,
caCert, issuerCert, caRevInfo, certsDirName);
updateCrlSuccessful = importCrl.importCrlToOcspDb();
crlUpdated = true;
if (updateCrlSuccessful) {
crlUpdateFailed = false;
LOG.info("updated CertStore {} successfully", name);
} else {
crlUpdateFailed = true;
LOG.error("updating CertStore {} failed", name);
}
} catch (Throwable th) {
LogUtil.error(LOG, th, "could not execute initializeStore()");
crlUpdateFailed = true;
crlUpdated = true;
} finally {
updateMeFile.delete();
crlUpdateInProcess.set(false);
if (updateCrlSuccessful != null) {
AuditLevel auditLevel = updateCrlSuccessful ? AuditLevel.INFO : AuditLevel.ERROR;
AuditStatus auditStatus = updateCrlSuccessful ? AuditStatus.SUCCESSFUL
: AuditStatus.FAILED;
auditPciEvent(auditLevel, "UPDATE_CRL", auditStatus.name());
}
}
} // method initializeStore
private void auditPciEvent(final AuditLevel auditLevel, final String eventType,
final String auditStatus) {
PciAuditEvent event = new PciAuditEvent(new Date());
event.setUserId("SYSTEM");
event.setEventType(eventType);
event.setAffectedResource("CRL-Updater");
event.setStatus(auditStatus);
event.setLevel(auditLevel);
getAuditService().logEvent(event);
}
}