AMCRLStore.java revision e6c3a72a023407f5d1fface64356e1cc81f1af31
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* https://opensso.dev.java.net/public/CDDLv1.0.html or
* opensso/legal/CDDLv1.0.txt
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at opensso/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* $Id: AMCRLStore.java,v 1.7 2009/01/28 05:35:12 ww203982 Exp $
*
* Portions Copyrighted 2013-2016 ForgeRock AS.
*/
package com.sun.identity.security.cert;
import com.forgerock.opendj.ldap.controls.TransactionIdControl;
import com.iplanet.security.x509.CertUtils;
import com.iplanet.security.x509.IssuingDistributionPointExtension;
import com.sun.identity.common.HttpURLConnectionManager;
import com.sun.identity.shared.encode.URLEncDec;
import sun.security.x509.CRLDistributionPointsExtension;
import sun.security.x509.DistributionPoint;
import sun.security.x509.GeneralNames;
import sun.security.x509.PKIXExtensions;
import sun.security.x509.X509CertImpl;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.StringTokenizer;
import javax.security.auth.x500.X500Principal;
import org.apache.commons.lang.ArrayUtils;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.openam.audit.context.AuditRequestContext;
import org.forgerock.openam.ldap.LDAPRequests;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPUrl;
import org.forgerock.opendj.ldap.LdapException;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.SSLContextBuilder;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldif.ConnectionEntryReader;
import org.forgerock.util.Options;
/**
* The class is used to manage crl store in LDAP server
* This class does get crl and update crl with CRLDistribution
* PointsExtension in client certificate or IssuingDistribution
* PointExtension in CRL. This class should be used
* in order to manage CRL store in LDAP
* <pre>
* id-ce-cRLDistributionPoints OBJECT IDENTIFIER ::= { id-ce 31 }
*
* RLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
*
* DistributionPoint ::= SEQUENCE {
* distributionPoint [0] DistributionPointName OPTIONAL,
* reasons [1] ReasonFlags OPTIONAL,
* cRLIssuer [2] GeneralNames OPTIONAL }
*
* DistributionPointName ::= CHOICE {
* fullName [0] GeneralNames,
* nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
*
* ReasonFlags ::= BIT STRING {
* unused (0),
* keyCompromise (1),
* cACompromise (2),
* affiliationChanged (3),
* superseded (4),
* cessationOfOperation (5),
* certificateHold (6),
* privilegeWithdrawn (7),
* aACompromise (8) }
* </pre>
*/
public class AMCRLStore extends AMCertStore {
public static final String CERTIFICATE_REVOCATION_LIST = "certificaterevocationlist";
public static final String CERTIFICATE_REVOCATION_LIST_BINARY = CERTIFICATE_REVOCATION_LIST + ";binary";
// In memory CRL cache
private static Hashtable<String, X509CRL> cachedcrls = new Hashtable<String, X509CRL>();
private String mCrlAttrName = null;
/**
* Class AMCRLStore is special cased CRL store for LDAP.
* A AMCRLStore instance has to have all the information for ldap
* and all the access information for CRLDistributionPointExtension and
* CRLIssuingDistributionPoint Extension
*
* @param param
*/
public AMCRLStore(AMLDAPCertStoreParameters param) {
super(param);
}
/**
* Checks certificate and returns corresponding stored CRL in ldap store
*
* @param certificate
*/
public X509CRL getCRL(X509Certificate certificate) throws IOException {
SearchResultEntry crlEntry = null;
X509CRL crl = null;
if (storeParam.isDoCRLCaching()) {
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getCRL: Trying to get CRL from cache");
}
crl = getCRLFromCache(certificate);
}
try (Connection ldc = getConnection()) {
if (ldc == null) {
return null;
}
if (crl == null) {
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getCRL: crl is null");
}
if (mCrlAttrName == null) {
crlEntry = getLdapEntry(ldc, CERTIFICATE_REVOCATION_LIST, CERTIFICATE_REVOCATION_LIST_BINARY);
} else {
crlEntry = getLdapEntry(ldc, mCrlAttrName);
}
crl = getCRLFromEntry(crlEntry);
}
if (storeParam.isDoUpdateCRLs() && needCRLUpdate(crl)) {
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getCRL: need CRL update");
}
X509CRL tmpcrl = null;
IssuingDistributionPointExtension crlIDPExt = null;
try {
if (crl != null) {
crlIDPExt = getCRLIDPExt(crl);
}
} catch (Exception e) {
debug.message("AMCRLStore.getCRL: crlIDPExt is null");
}
CRLDistributionPointsExtension crlDPExt = null;
try {
crlDPExt = getCRLDPExt(certificate);
} catch (Exception e) {
debug.message("AMCRLStore.getCRL: crlDPExt is null");
}
if ((tmpcrl == null) && (crlIDPExt != null)) {
tmpcrl = getUpdateCRLFromCrlIDP(crlIDPExt);
}
if ((tmpcrl == null) && (crlDPExt != null)) {
tmpcrl = getUpdateCRLFromCrlDP(crlDPExt);
}
if (tmpcrl != null) {
if (crlEntry == null) {
crlEntry = getLdapEntry(ldc);
}
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getCRL: new crl = " + tmpcrl);
}
if (crlEntry != null) {
updateCRL(ldc, crlEntry.getName().toString(),
tmpcrl.getEncoded());
}
}
crl = tmpcrl;
}
if (storeParam.isDoCRLCaching()) {
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getCRL: Updating CRL cache");
}
updateCRLCache(certificate, crl);
}
} catch (Exception e) {
debug.error("AMCRLStore.getCRL: Error in getting CRL : ", e);
}
return crl;
}
/**
* Checks certificate and returns corresponding stored CRL in cached CRL store.
*
* @param certificate
* @return Cached CRL information about the certificate.
*/
public X509CRL getCRLFromCache(X509Certificate certificate) {
return cachedcrls.get(CertUtils.getIssuerName(certificate));
}
/**
* Checks certificate and update CRL in cached CRL store.
*
* @param certificate
* @param crl
*/
public void updateCRLCache(X509Certificate certificate, X509CRL crl) {
String issuer = CertUtils.getIssuerName(certificate);
if (crl == null) {
cachedcrls.remove(issuer);
} else {
cachedcrls.put(issuer, crl);
}
}
private X509CRL getCRLFromEntry(SearchResultEntry entry)
throws Exception {
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getCRLFromEntry:");
}
if (entry == null) {
return null;
}
Attribute crlAttribute = null;
X509CRL crl = null;
try {
/*
* Retrieve the certificate revocation list if available.
*/
if (mCrlAttrName == null) {
crlAttribute = entry.getAttribute(CERTIFICATE_REVOCATION_LIST);
if (crlAttribute == null) {
crlAttribute = entry.getAttribute(CERTIFICATE_REVOCATION_LIST_BINARY);
if (crlAttribute == null) {
debug.error("No CRL Cache is configured");
return null;
}
}
mCrlAttrName = crlAttribute.getAttributeDescriptionAsString();
} else {
crlAttribute = entry.getAttribute(mCrlAttrName);
}
if (crlAttribute.size() > 1) {
debug.error("More than one CRL entries are configured");
return null;
}
} catch (Exception e) {
debug.error("Error in getting Cached CRL");
return null;
}
try {
byte[] bytes = crlAttribute.firstValue().toByteArray();
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getCRLFromEntry: crl size = " +
bytes.length);
}
cf = CertificateFactory.getInstance("X.509");
crl = (X509CRL) cf.generateCRL(new ByteArrayInputStream(bytes));
} catch (Exception e) {
debug.error("Certificate: CertRevoked = ", e);
}
return crl;
}
/**
* It checks whether the certificate has CRLDistributionPointsExtension
* or not. If there is, it returns the extension.
*
* @param certificate
*/
private CRLDistributionPointsExtension getCRLDPExt(X509Certificate certificate) {
CRLDistributionPointsExtension dpExt = null;
try {
X509CertImpl certImpl = new X509CertImpl(certificate.getEncoded());
dpExt = certImpl.getCRLDistributionPointsExtension();
} catch (Exception e) {
debug.error("Error finding CRL distribution Point configured: ", e);
}
return dpExt;
}
/**
* It checks whether the crl has IssuingDistributionPointExtension
* or not. If there is, it returns the extension.
*
* @param crl
*/
private IssuingDistributionPointExtension getCRLIDPExt(X509CRL crl) {
IssuingDistributionPointExtension idpExt = null;
if (crl == null) {
return null;
}
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getCRLIDPExt: crl = " + crl);
}
try {
byte[] ext =
crl.getExtensionValue(
PKIXExtensions.IssuingDistributionPoint_Id.toString());
if (ext != null) {
idpExt = new IssuingDistributionPointExtension(ext);
}
} catch (Exception e) {
debug.error("Error finding CRL distribution Point configured: ", e);
}
return idpExt;
}
/**
* It updates CRL under the dn in the directory server.
* It retrieves CRL distribution points from the parameter
* CRLDistributionPointsExtension dpExt.
*
* @param dpExt
*/
private synchronized X509CRL
getUpdateCRLFromCrlDP(CRLDistributionPointsExtension dpExt) {
// Get CRL Distribution points
if (dpExt == null) {
return null;
}
List dps = null;
try {
dps = (List) dpExt.get(CRLDistributionPointsExtension.POINTS);
} catch (IOException ioex) {
if (debug.warningEnabled()) {
debug.warning("AMCRLStore.getUpdateCRLFromCrlDP: ", ioex);
}
}
if (dps == null || dps.isEmpty()) {
return null;
}
for (Object dp1 : dps) {
DistributionPoint dp = (DistributionPoint) dp1;
GeneralNames gName = dp.getFullName();
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getUpdateCRLFromCrlDP: DP = " +
gName);
}
byte[] Crls = getCRLsFromGeneralNames(gName);
if (Crls != null && Crls.length > 0) {
try {
return (X509CRL) cf.generateCRL(
new ByteArrayInputStream(Crls));
} catch (Exception ex) {
if (debug.warningEnabled()) {
debug.warning("AMCRLStore.getUpdateCRLFromCrlDP: " +
"Error in generating X509CRL", ex);
}
}
}
}
return null;
}
/**
* It updates CRL under the dn in the directory server.
* It retrieves CRL distribution points from the parameter
* CRLDistributionPointsExtension dpExt.
*
* @param idpExt
*/
private synchronized X509CRL getUpdateCRLFromCrlIDP(IssuingDistributionPointExtension idpExt) {
GeneralNames gName = idpExt.getFullName();
if (gName == null) {
return null;
}
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getUpdateCRLFromCrlIDP: gName = " + gName);
}
byte[] Crls = getCRLsFromGeneralNames(gName);
X509CRL crl = null;
if (Crls != null) {
try {
crl = (X509CRL) cf.generateCRL(new ByteArrayInputStream(Crls));
} catch (Exception e) {
debug.error("Error in generating X509CRL" + e.toString());
}
}
return crl;
}
private byte[] getCRLsFromGeneralNames(GeneralNames gName) {
byte[] Crls = null;
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getCRLsFromGeneralNames: gNames.size = " +
gName.size());
}
int idx = 0;
do {
String uri = gName.get(idx++).toString().trim();
String protocol = uri.toLowerCase();
int proto_pos;
if ((proto_pos = protocol.indexOf("http")) == -1) {
if ((proto_pos = protocol.indexOf("https")) == -1) {
if ((proto_pos = protocol.indexOf("ldap")) == -1) {
if ((proto_pos = protocol.indexOf("ldaps")) == -1) {
continue;
}
}
}
}
uri = uri.substring(proto_pos, uri.length());
if (debug.messageEnabled()) {
debug.message("DP Name : " + uri);
}
Crls = getCRLByURI(uri);
} while ((Crls != null) && (idx < gName.size()));
return Crls;
}
/**
* It replaces attribute value under the DN.
* It is used to replace old CRL with new one.
*
* @param ldc
* @param dn
* @param crls
*/
private void updateCRL(Connection ldc, String dn, byte[] crls) {
try {
ModifyRequest modifyRequest = LDAPRequests.newModifyRequest(dn)
.addModification(ModificationType.REPLACE, mCrlAttrName, crls);
ldc.modify(modifyRequest);
} catch (LdapException e) {
debug.error("Error updating CRL Cache : ", e);
}
}
/**
* It is checking uri's protocol.
* Protocol has to be http(s) or ldap.
* Based on checked protocol, it gets new CRL by invking
* getCRLByLdapURI() or getCRLByHttpURI()
*
* @param uri
*/
private byte[] getCRLByURI(String uri) {
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getCRLByURI : uri = " + uri);
}
if (uri == null) {
return null;
}
String protocol = uri.trim().toLowerCase();
if (protocol.startsWith("http") || protocol.startsWith("https")) {
return getCRLByHttpURI(uri);
} else if (protocol.startsWith("ldap")
|| protocol.startsWith("ldaps")) {
return getCRLByLdapURI(uri);
}
return null;
}
/**
* It gets the new CRL from ldap server.
* If it is ldap URI, the URI has to be a dn that can be accessed
* with ldap anonymous bind.
* (example : ldap://server:port/uid=ca,o=company.com)
* This dn entry has to have CRL in attribute certificaterevocationlist
* or certificaterevocationlist;binary.
*
* @param uri
*/
private byte[] getCRLByLdapURI(String uri) {
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getCRLByLdapURI: uri = " + uri);
}
LDAPUrl url;
LDAPConnectionFactory factory;
byte[] crl = null;
try {
url = LDAPUrl.valueOf(uri);
} catch (LocalizedIllegalArgumentException e) {
debug.error("AMCRLStore.getCRLByLdapURI(): Could not parse uri: {}", uri, e);
return null;
}
debug.message("AMCRLStore.getCRLByLdapURI: url.dn = {}", url.getName());
// Check ldap over SSL
if (url.isSecure()) {
try {
factory = new LDAPConnectionFactory(url.getHost(), url.getPort(),
Options.defaultOptions().set(LDAPConnectionFactory.SSL_CONTEXT,
new SSLContextBuilder().getSSLContext()));
} catch (GeneralSecurityException e) {
debug.error("AMCRLStore.getCRLByLdapURI: Error getting SSL Context", e);
return null;
}
} else { // non-ssl
factory = new LDAPConnectionFactory(url.getHost(), url.getPort());
}
try (Connection ldc = factory.getConnection()) {
ConnectionEntryReader results = ldc.search(url.asSearchRequest()
.addControl(TransactionIdControl.newControl(
AuditRequestContext.createSubTransactionIdValue())));
if (!results.hasNext()) {
debug.error("verifyCertificate - No CRL distribution Point configured");
return null;
}
if (results.isReference()) {
debug.warning("Getting CRL but got LDAP reference: {}", results.readReference());
return null;
}
SearchResultEntry entry = results.readEntry();
/*
* Retrieve the certificate revocation list if available.
*/
Attribute crlAttribute = entry.getAttribute(CERTIFICATE_REVOCATION_LIST);
if (crlAttribute == null) {
crlAttribute = entry.getAttribute(CERTIFICATE_REVOCATION_LIST_BINARY);
if (crlAttribute == null) {
debug.error("verifyCertificate - No CRL distribution Point configured");
return null;
}
}
crl = crlAttribute.firstValue().toByteArray();
} catch (Exception e) {
debug.error("getCRLByLdapURI : Error in getting CRL", e);
}
return crl;
}
private byte[] getCRLByHttpURI(String url) {
String argString = ""; //default
StringBuffer params = null;
HttpURLConnection con = null;
byte[] crl = null;
String uriParamsCRL = storeParam.getURIParams();
try {
if (uriParamsCRL != null) {
params = new StringBuffer();
StringTokenizer st1 = new StringTokenizer(uriParamsCRL, ",");
while (st1.hasMoreTokens()) {
String token = st1.nextToken();
StringTokenizer st2 = new StringTokenizer(token, "=");
if (st2.countTokens() == 2) {
String param = st2.nextToken();
String value = st2.nextToken();
params.append(URLEncDec.encode(param) + "=" + URLEncDec.encode(value));
} else {
continue;
}
if (st1.hasMoreTokens()) {
params.append("&");
}
}
}
URL uri = new URL(url);
con = HttpURLConnectionManager.getConnection(uri);
// Prepare for both input and output
con.setDoInput(true);
// Turn off Caching
con.setUseCaches(false);
if (params != null) {
byte[] paramsBytes = params.toString().trim().getBytes("UTF-8");
if (paramsBytes.length > 0) {
con.setDoOutput(true);
con.setRequestProperty("Content-Length",
Integer.toString(paramsBytes.length));
// Write the arguments as post data
BufferedOutputStream out =
new BufferedOutputStream(con.getOutputStream());
out.write(paramsBytes, 0, paramsBytes.length);
out.flush();
out.close();
}
}
// Input ...
InputStream in = con.getInputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len;
byte[] buf = new byte[1024];
while ((len = in.read(buf, 0, buf.length)) != -1) {
bos.write(buf, 0, len);
}
crl = bos.toByteArray();
if (debug.messageEnabled()) {
debug.message("AMCRLStore.getCRLByHttpURI: crl.length = " +
crl.length);
}
} catch (Exception e) {
debug.error("getCRLByHttpURI : Error in getting CRL", e);
}
return crl;
}
// It returns NextCRLUpdate for current cached CRL
// It gets CRL from crlAttribue member variable
private boolean needCRLUpdate(X509CRL crl) {
Date nextCRLUpdate = null;
if (crl == null) {
return true;
}
// Check CRLNextUpdate in CRL
nextCRLUpdate = crl.getNextUpdate();
if (debug.messageEnabled()) {
debug.message("AMCRLStore.needCRLUpdate: nextCrlUpdate = " +
nextCRLUpdate);
}
return ((nextCRLUpdate != null) && nextCRLUpdate.before(new Date()));
}
/**
* It gets the new CRL from ldap server.
* If it is ldap URI, the URI has to be a dn that can be accessed
* with ldap anonymous bind.
* (example : ldap://server:port/uid=ca,o=company.com)
* This dn entry has to have CRL in attribute certificaterevocationlist
* or certificaterevocationlist;binary.
* <p/>
* if attrNames does only contain one value the ldap search filter will be
* (attrName=Value_of_the_corresponding_Attribute_from_SubjectDN)
* e.g. SubjectDN of issuer cert 'C=BE, CN=Citizen CA, serialNumber=201007'
* attrNames is 'CN', search filter used will be (CN=Citizen CA)
* <p/>
* if attrNames does contain serveral values the ldap search filter value will be
* a comma separated list of name attribute values, the search attribute will be 'cn'
* (cn="attrNames[0]=Value_of_the_corresponding_Attribute_from_SubjectDN,
* attrNames[1]=Value_of_the_corresponding_Attribute_from_SubjectDN")
* <p/>
* e.g. SubjectDN of issuer cert 'C=BE, CN=Citizen CA, serialNumber=201007'
* attrNames is {"CN","serialNumber"}, search filter used will be
* (cn=CN=Citizen CA,serialNumber=201007)
* <p/>
* The order of the values of attrNames matter as they must match the value of the
* 'cn' attribute of a crlDistributionPoint entry in the directory server
*
* @param ldapParam
* @param cert
* @param attrNames, attributes names from the subjectDN of the issuer cert
*/
public static X509CRL getCRL(AMLDAPCertStoreParameters ldapParam, X509Certificate cert, String... attrNames) {
X509CRL crl = null;
try {
if (!ArrayUtils.isEmpty(attrNames)) {
X500Principal issuerPrincipal = cert.getIssuerX500Principal();
String searchFilter;
if (attrNames.length < 2) {
/*
* Get the CN of the input certificate
*/
String attrValue = null;
// Retrieve attribute value of the attribute name
attrValue = CertUtils.getAttributeValue(issuerPrincipal, attrNames[0]);
if (null == attrValue) {
return crl;
}
searchFilter = setSearchFilter(attrNames[0], attrValue);
} else {
String searchFilterValue = buildSearchFilterValue(attrNames, issuerPrincipal);
if (searchFilterValue.isEmpty()) {
return crl;
}
searchFilter = setSearchFilter("cn", searchFilterValue);
}
if (debug.messageEnabled()) {
debug.message("AMCRLStore:getCRL using searchFilter " + searchFilter);
}
/*
* Lookup the certificate in the LDAP certificate directory
*/
ldapParam.setSearchFilter(searchFilter);
AMCRLStore store = new AMCRLStore(ldapParam);
crl = store.getCRL(cert);
}
} catch (Exception e) {
debug.error("AMCRLStore:getCRL ", e);
}
return crl;
}
private static String buildSearchFilterValue(String[] attrNames, X500Principal principal) {
StringBuilder searchFilterBuilder = new StringBuilder();
for (int i = 0; i < attrNames.length; i++) {
String attrName = attrNames[i];
String attrValue = CertUtils.getAttributeValue(principal, attrName);
if (null != attrValue) {
searchFilterBuilder.append(attrName);
searchFilterBuilder.append("=");
searchFilterBuilder.append(attrValue);
if (i < attrNames.length - 1) {
searchFilterBuilder.append(",");
}
}
}
return searchFilterBuilder.toString();
}
}