Java tutorial
/* * Copyright (c) 2008-2011, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo 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 Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.common.security.certstore.dao; import java.io.IOException; import java.math.BigInteger; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.CertSelector; import java.security.cert.CertStoreException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateParsingException; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.LinkedList; import java.util.List; import javax.security.auth.x500.X500Principal; import mitm.common.hibernate.DatabaseCriterion; import mitm.common.hibernate.GenericHibernateDAO; import mitm.common.hibernate.HibernateDatabaseCriterion; import mitm.common.hibernate.SessionAdapter; import mitm.common.hibernate.DatabaseCriterion.Restriction; import mitm.common.locale.DefaultLocale; import mitm.common.security.NoSuchProviderRuntimeException; import mitm.common.security.certificate.X500PrincipalInspector; import mitm.common.security.certificate.X509CertSelectorInspector; import mitm.common.security.certificate.X509CertificateInspector; import mitm.common.security.certstore.Expired; import mitm.common.security.certstore.Match; import mitm.common.security.certstore.MissingKeyAlias; import mitm.common.security.certstore.hibernate.X509CertStoreEntryHibernate; import mitm.common.util.BigIntegerUtils; import mitm.common.util.CloseableIterator; import mitm.common.util.CloseableIteratorException; import mitm.common.util.HexUtils; import org.hibernate.Criteria; import org.hibernate.Query; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Order; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Property; import org.hibernate.criterion.Restrictions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class X509CertStoreDAOHibernate extends GenericHibernateDAO<X509CertStoreEntryHibernate, Long> implements X509CertStoreDAO { private final static Logger logger = LoggerFactory.getLogger(X509CertStoreDAOHibernate.class); /* * The table columns */ private enum Field { ISSUER, ISSUER_FRIENDLY, SERIAL_NUMBER, SUBJECT, SUBJECT_FRIENDLY, SUBJECT_KEY_ID, KEY_ALIAS, THUMBPRINT, NOT_BEFORE, NOT_AFTER, CREATION_DATE, STORE_NAME }; private final String storeName; private final String entityName; public X509CertStoreDAOHibernate(SessionAdapter session, String storeName, String entityName) { super(session); this.storeName = storeName; this.entityName = entityName; } public X509CertStoreDAOHibernate(SessionAdapter session, String storeName) { this(session, storeName, X509CertStoreEntryHibernate.ENTITY_NAME); } private Query createByEmailQuery(String baseQuery, String email, Match match, Expired expired, MissingKeyAlias missingKeyAlias, Date date) { if (email != null) { baseQuery = baseQuery + " join c.email e where e"; baseQuery = baseQuery + (match == Match.EXACT ? " = :email" : " like :email"); } else { /* if email is null we want to get all the entries without an email address */ baseQuery = baseQuery + " left join c.email e where e is null"; } if (expired == Expired.NOT_ALLOWED) { baseQuery = baseQuery + " and :time between notBefore and notAfter"; } if (missingKeyAlias == MissingKeyAlias.NOT_ALLOWED) { baseQuery = baseQuery + " and keyAlias is not null"; } baseQuery = baseQuery + " and storeName = :storeName"; Query query = createQuery(baseQuery); if (email != null) { email = email.toLowerCase().trim(); query.setString("email", email); } if (expired == Expired.NOT_ALLOWED) { query.setTimestamp("time", date); } query.setString(getColumnName(Field.STORE_NAME), storeName); return query; } @Override public CloseableIterator<X509CertStoreEntryHibernate> getByEmail(String email, Match match, Expired expired, MissingKeyAlias missingKeyAlias, Date date, Integer firstResult, Integer maxResults) { /* * we need to get distinct results using the distinct keyword. This is not supported by Criteria * AFAIK so we will need to use HQL */ String baseQuery = "select distinct c from " + entityName + " c"; Query query = createByEmailQuery(baseQuery, email, match, expired, missingKeyAlias, date); if (firstResult != null) { query.setFirstResult(firstResult); } if (maxResults != null) { query.setMaxResults(maxResults); } ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY); return new X509CertStoreEntryIterator(results); } /** * returns the number of records that getByEmail would return */ @Override public long getByEmailCount(String email, Match match, Expired expired, MissingKeyAlias missingKeyAlias, Date date) { /* * we need to get distinct results using the distinct keyword. This is not supported by Criteria * AFAIK so we will need to use HQL */ String baseQuery = "select count (distinct c) from " + entityName + " c"; Query query = createByEmailQuery(baseQuery, email, match, expired, missingKeyAlias, date); return (Long) query.uniqueResult(); } /** * Searches the subject friendly name using ILIKE. */ @Override public CloseableIterator<X509CertStoreEntryHibernate> searchBySubject(String subject, Expired expired, MissingKeyAlias missingKeyAlias, Date date, Integer firstResult, Integer maxResults) { return searchField(Field.SUBJECT_FRIENDLY, subject, expired, missingKeyAlias, date, firstResult, maxResults); } @Override public int getSearchBySubjectCount(String subject, Expired expired, MissingKeyAlias missingKeyAlias, Date date) { return getSearchFieldCount(Field.SUBJECT_FRIENDLY, subject, expired, missingKeyAlias, date); } /** * Searches the issuer friendly name using ILIKE. */ @Override public CloseableIterator<X509CertStoreEntryHibernate> searchByIssuer(String issuer, Expired expired, MissingKeyAlias missingKeyAlias, Date date, Integer firstResult, Integer maxResults) { return searchField(Field.ISSUER_FRIENDLY, issuer, expired, missingKeyAlias, date, firstResult, maxResults); } @Override public int getSearchByIssuerCount(String issuer, Expired expired, MissingKeyAlias missingKeyAlias, Date date) { return getSearchFieldCount(Field.ISSUER_FRIENDLY, issuer, expired, missingKeyAlias, date); } /** * Helper function that searches the field using ILIKE. */ private CloseableIterator<X509CertStoreEntryHibernate> searchField(Field field, String search, Expired expired, MissingKeyAlias missingKeyAlias, Date date, Integer firstResult, Integer maxResults) { Collection<DatabaseCriterion> criterion = new LinkedList<DatabaseCriterion>(); criterion.add(createCriterion(field, search, Restriction.ILIKE)); if (expired == Expired.NOT_ALLOWED) { criterion.add(createCriterion(Field.NOT_BEFORE, date, Restriction.LT)); criterion.add(createCriterion(Field.NOT_AFTER, date, Restriction.GT)); } if (missingKeyAlias == MissingKeyAlias.NOT_ALLOWED) { criterion.add(createCriterion(Field.KEY_ALIAS, Restriction.NOT_NULL)); } ScrollableResults results = findByCriterionScrollable(criterion, firstResult, maxResults); return new X509CertStoreEntryIterator(results); } /** * Helper function that returns the number of records that searchField would return. */ private int getSearchFieldCount(Field field, String search, Expired expired, MissingKeyAlias missingKeyAlias, Date date) { Criteria criteria = createCriteria(entityName); criteria.setProjection(Projections.rowCount()); criteria.add(Restrictions.eq(getColumnName(Field.STORE_NAME), storeName)); criteria.add(Restrictions.ilike(getColumnName(field), search)); if (expired == Expired.NOT_ALLOWED) { criteria.add(Restrictions.lt(getColumnName(Field.NOT_BEFORE), date)); criteria.add(Restrictions.gt(getColumnName(Field.NOT_AFTER), date)); } if (missingKeyAlias == MissingKeyAlias.NOT_ALLOWED) { criteria.add(Restrictions.isNotNull(getColumnName(Field.KEY_ALIAS))); } return (Integer) criteria.uniqueResult(); } @Override public X509CertStoreEntryHibernate getByThumbprint(String thumbprint) { if (thumbprint != null) { thumbprint = thumbprint.toUpperCase(DefaultLocale.getDefaultLocale()); } return findByCriterionUniqueResult(createCriterion(Field.THUMBPRINT, thumbprint, Restriction.EQ)); } @Override public X509CertStoreEntryHibernate getByCertificate(X509Certificate certificate) throws CertStoreException { String thumbprint; try { thumbprint = (certificate != null ? X509CertificateInspector.getThumbprint(certificate) : null); } catch (CertificateEncodingException e) { throw new CertStoreException(e); } catch (NoSuchAlgorithmException e) { throw new CertStoreException(e); } catch (NoSuchProviderException e) { throw new NoSuchProviderRuntimeException(e); } X509CertStoreEntryHibernate entry = getByThumbprint(thumbprint); return entry; } @Override public X509CertStoreEntryHibernate getLatest() { DetachedCriteria maxDateCriteria = createDetachedCriteria(entityName); maxDateCriteria.setProjection(Projections.max(getColumnName(Field.CREATION_DATE))); maxDateCriteria.add(Restrictions.eq(getColumnName(Field.STORE_NAME), storeName)); Criteria criteria = createCriteria(entityName); criteria.add(Property.forName(getColumnName(Field.CREATION_DATE)).eq(maxDateCriteria)); criteria.add(Restrictions.eq(getColumnName(Field.STORE_NAME), storeName)); return (X509CertStoreEntryHibernate) criteria.uniqueResult(); } @Override public int getRowCount() { Criteria criteria = createCriteria(entityName); criteria.setProjection(Projections.rowCount()); criteria.add(Restrictions.eq(getColumnName(Field.STORE_NAME), storeName)); return (Integer) criteria.uniqueResult(); } @Override public int getRowCount(Expired expired, MissingKeyAlias missingKeyAlias, Date date) { Criteria criteria = createCriteria(entityName); criteria.setProjection(Projections.rowCount()); criteria.add(Restrictions.eq(getColumnName(Field.STORE_NAME), storeName)); if (expired == Expired.NOT_ALLOWED) { criteria.add(Restrictions.lt(getColumnName(Field.NOT_BEFORE), date)); criteria.add(Restrictions.gt(getColumnName(Field.NOT_AFTER), date)); } if (missingKeyAlias == MissingKeyAlias.NOT_ALLOWED) { criteria.add(Restrictions.isNotNull(getColumnName(Field.KEY_ALIAS))); } return (Integer) criteria.uniqueResult(); } @Override public X509CertStoreEntryHibernate addCertificate(X509Certificate certificate, String keyAlias) throws CertStoreException { Date creationDate = new Date(); X509CertStoreEntryHibernate entry; try { entry = new X509CertStoreEntryHibernate(certificate, keyAlias, storeName, creationDate); } catch (CertificateParsingException e) { throw new CertStoreException(e); } catch (IOException e) { throw new CertStoreException(e); } makePersistent(entry); return entry; } @Override public void removeCertificate(X509Certificate certificate) throws CertStoreException { X509CertStoreEntryHibernate entry = getByCertificate(certificate); if (entry != null) { this.delete(entry); } } @Override public void removeAllEntries() throws CertStoreException { /* * I tried using HQL and delete but that did not work because of a constraint violation. * Using delete seems not to cascade to the certificate_email table. */ CloseableIterator<X509CertStoreEntryHibernate> iterator = getCertStoreIterator(null, MissingKeyAlias.ALLOWED, null, null); try { try { while (iterator.hasNext()) { X509CertStoreEntryHibernate entry = iterator.next(); /* evict the entry to save memory */ this.evict(entry); this.delete(entry); } } finally { /* need to close the iterator */ iterator.close(); } } catch (CloseableIteratorException e) { throw new CertStoreException(e); } } @Override public Collection<X509Certificate> getCertificates(CertSelector certSelector, MissingKeyAlias missingKeyAlias, Integer firstResult, Integer maxResults) throws CertStoreException { Collection<X509Certificate> foundCertificates = new LinkedList<X509Certificate>(); CloseableIterator<X509Certificate> iterator = getCertificateIterator(certSelector, missingKeyAlias, firstResult, maxResults); try { try { while (iterator.hasNext()) { X509Certificate certificate = iterator.next(); if (certificate != null && (certSelector == null || certSelector.match(certificate))) { foundCertificates.add(certificate); } } } finally { /* need to close the iterator */ iterator.close(); } } catch (CloseableIteratorException e) { throw new CertStoreException(e); } return foundCertificates; } @Override public CloseableIterator<X509Certificate> getCertificateIterator(CertSelector certSelector, MissingKeyAlias missingKeyAlias, Integer firstResult, Integer maxResults) throws CertStoreException { ScrollableResults results = getPreFilteredEntriesScrollable(certSelector, missingKeyAlias, firstResult, maxResults); return new X509CertStoreCertificateIterator(results, certSelector); } @Override public CloseableIterator<X509CertStoreEntryHibernate> getCertStoreIterator(CertSelector certSelector, MissingKeyAlias missingKeyAlias, Integer firstResult, Integer maxResults) throws CertStoreException { ScrollableResults results = getPreFilteredEntriesScrollable(certSelector, missingKeyAlias, firstResult, maxResults); return new X509CertStoreEntryIterator(results, certSelector); } private static String getColumnName(Field field) { switch (field) { case ISSUER: return "certificate.issuer"; case ISSUER_FRIENDLY: return "certificate.issuerFriendly"; case SERIAL_NUMBER: return "certificate.serial"; case SUBJECT: return "certificate.subject"; case SUBJECT_FRIENDLY: return "certificate.subjectFriendly"; case SUBJECT_KEY_ID: return "certificate.subjectKeyIdentifier"; case THUMBPRINT: return "certificate.thumbprint"; case NOT_BEFORE: return "certificate.notBefore"; case NOT_AFTER: return "certificate.notAfter"; case KEY_ALIAS: return "keyAlias"; case CREATION_DATE: return "creationDate"; case STORE_NAME: return "storeName"; default: throw new IllegalArgumentException("Unknown field: " + field); } } private DatabaseCriterion createCriterion(Field field, Object value, Restriction restriction) { return new HibernateDatabaseCriterion(getColumnName(field), value, restriction); } private DatabaseCriterion createCriterion(Field field, Restriction restriction) { return createCriterion(field, null, restriction); } @SuppressWarnings("unchecked") private List<X509CertStoreEntryHibernate> findByCriterion(Collection<DatabaseCriterion> criterions) { Criteria criteria = createCriteria(criterions); return criteria.list(); } private ScrollableResults findByCriterionScrollable(Collection<DatabaseCriterion> criterions, Integer firstResult, Integer maxResults) { Criteria criteria = createCriteria(criterions); if (firstResult != null) { criteria.setFirstResult(firstResult); } if (maxResults != null) { criteria.setMaxResults(maxResults); } /* * Sort on creation date */ criteria.addOrder(Order.asc(getColumnName(Field.CREATION_DATE))); return criteria.scroll(ScrollMode.FORWARD_ONLY); } private X509CertStoreEntryHibernate findByCriterionUniqueResult(Collection<DatabaseCriterion> criterions) { List<X509CertStoreEntryHibernate> entries = findByCriterion(criterions); X509CertStoreEntryHibernate entry = null; if (entries != null && entries.size() > 0) { entry = entries.get(0); } return entry; } private X509CertStoreEntryHibernate findByCriterionUniqueResult(DatabaseCriterion... criterions) { return findByCriterionUniqueResult(Arrays.asList(criterions)); } private Criteria createCriteria(Collection<DatabaseCriterion> criterions) { Criteria criteria = createCriteria(entityName); for (DatabaseCriterion criterion : criterions) { if (!(criterion instanceof HibernateDatabaseCriterion)) { throw new IllegalArgumentException("criterion must be a HibernateDatabaseCriterion."); } criteria.add(((HibernateDatabaseCriterion) criterion).getCriterion()); } /* make sure we only get results for just one store */ criteria.add(Restrictions.eq(getColumnName(Field.STORE_NAME), storeName)); return criteria; } /* * Creates a collection of DatabaseCriterions used for pre-filtering based on the * CertSelector. This speeds up the search process because the database will * filter all relevant entries using database queries. */ private Collection<DatabaseCriterion> createCriterion(X509CertSelector certSelector) throws CertStoreException, IOException { X500Principal issuer = certSelector.getIssuer(); X500Principal subject = certSelector.getSubject(); BigInteger serialNumber = certSelector.getSerialNumber(); /* CertSelector store the subjectKeyIdentifier DER encoded */ byte[] subjectKeyIdentifier = X509CertSelectorInspector.getSubjectKeyIdentifier(certSelector); /* * if a certificate is specified we need to get the properties from the * certificate */ if (certSelector.getCertificate() != null) { X509Certificate certificate = certSelector.getCertificate(); if (issuer == null) { issuer = certificate.getIssuerX500Principal(); } if (subject == null) { subject = certificate.getSubjectX500Principal(); } if (serialNumber == null) { serialNumber = certificate.getSerialNumber(); } if (subjectKeyIdentifier == null) { subjectKeyIdentifier = X509CertificateInspector.getSubjectKeyIdentifier(certificate); } } Collection<DatabaseCriterion> criterion = new LinkedList<DatabaseCriterion>(); if (issuer != null) { criterion.add( createCriterion(Field.ISSUER, X500PrincipalInspector.getCanonical(issuer), Restriction.EQ)); } if (subject != null) { criterion.add( createCriterion(Field.SUBJECT, X500PrincipalInspector.getCanonical(subject), Restriction.EQ)); } if (serialNumber != null) { criterion.add( createCriterion(Field.SERIAL_NUMBER, BigIntegerUtils.hexEncode(serialNumber), Restriction.EQ)); } if (subjectKeyIdentifier != null) { criterion.add(createCriterion(Field.SUBJECT_KEY_ID, HexUtils.hexEncode(subjectKeyIdentifier), Restriction.EQ)); } if (certSelector.getCertificateValid() != null) { criterion.add(createCriterion(Field.NOT_BEFORE, certSelector.getCertificateValid(), Restriction.LT)); criterion.add(createCriterion(Field.NOT_AFTER, certSelector.getCertificateValid(), Restriction.GT)); } return criterion; } /* * Return a pre filtered collection of X509CertStoreEntry object. The filtering is done using * database queries. */ private ScrollableResults getPreFilteredEntriesScrollable(CertSelector certSelector, MissingKeyAlias missingKeyAlias, Integer firstResult, Integer maxResults) throws CertStoreException { Collection<DatabaseCriterion> criterion = new LinkedList<DatabaseCriterion>(); /* * If the selector is a X509CertSelector we can do some pre-filtering to * speed the search process */ if (certSelector instanceof X509CertSelector) { try { criterion = createCriterion((X509CertSelector) certSelector); } catch (IOException e) { throw new CertStoreException(e); } } if (missingKeyAlias == MissingKeyAlias.NOT_ALLOWED) { criterion.add(createCriterion(Field.KEY_ALIAS, Restriction.NOT_NULL)); } if (criterion.size() == 0) { logger.debug("No optimization possible so step through all certificates."); } return findByCriterionScrollable(criterion, firstResult, maxResults); } }