org.signserver.server.cryptotokens.CryptoTokenHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.signserver.server.cryptotokens.CryptoTokenHelper.java

Source

/*************************************************************************
 *                                                                       *
 *  SignServer: The OpenSource Automated Signing Server                  *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.signserver.server.cryptotokens;

import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.DSAKey;
import java.security.interfaces.ECKey;
import java.security.interfaces.RSAKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.crypto.SecretKey;
import javax.security.auth.x500.X500Principal;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.PredicateUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.ECKeyUtil;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.bouncycastle.util.encoders.Hex;
import org.cesecore.certificates.util.AlgorithmTools;
import org.cesecore.keys.token.p11.Pkcs11SlotLabelType;
import org.cesecore.util.QueryParameterException;
import org.cesecore.util.query.Elem;
import org.cesecore.util.query.QueryCriteria;
import org.cesecore.util.query.clauses.Order;
import org.cesecore.util.query.elems.LogicOperator;
import org.cesecore.util.query.elems.Operation;
import org.cesecore.util.query.elems.Term;
import org.ejbca.util.Base64;
import org.ejbca.util.CertTools;
import org.signserver.common.Base64SignerCertReqData;
import org.signserver.common.CryptoTokenOfflineException;
import org.signserver.common.ICertReqData;
import org.signserver.common.ISignerCertReqInfo;
import org.signserver.common.KeyTestResult;
import org.signserver.common.PKCS10CertReqInfo;
import org.signserver.common.QueryException;
import org.signserver.common.SignServerException;
import org.signserver.server.KeyUsageCounterHash;

/**
 * Helper methods used by the CryptoTokens.
 *
 * @version $Id: CryptoTokenHelper.java 6147 2015-07-17 11:14:51Z netmackan $
 */
public class CryptoTokenHelper {

    private static final Logger LOG = Logger.getLogger(CryptoTokenHelper.class);

    public static final String PROPERTY_NEXTCERTSIGNKEY = "NEXTCERTSIGNKEY";
    public static final String PROPERTY_ATTRIBUTESFILE = "ATTRIBUTESFILE";
    public static final String PROPERTY_ATTRIBUTES = "ATTRIBUTES";
    public static final String PROPERTY_SLOTLISTINDEX = "SLOTLISTINDEX";
    public static final String PROPERTY_SLOT = "SLOT";
    public static final String PROPERTY_SHAREDLIBRARY = "SHAREDLIBRARY";
    public static final String PROPERTY_SHAREDLIBRARYNAME = "SHAREDLIBRARYNAME";
    public static final String PROPERTY_PIN = "PIN";
    public static final String PROPERTY_DEFAULTKEY = "DEFAULTKEY";
    public static final String PROPERTY_AUTHCODE = "AUTHCODE";
    public static final String PROPERTY_SLOTLABELTYPE = "SLOTLABELTYPE";
    public static final String PROPERTY_SLOTLABELVALUE = "SLOTLABELVALUE";

    public static final String PROPERTY_KEYGENERATIONLIMIT = "KEYGENERATIONLIMIT";

    public enum TokenEntryFields {
        /** Key alias of entry. */
        alias,

        /**
         * Type of entry.
         * @see TokenEntry#TYPE_PRIVATEKEY_ENTRY
         * @see TokenEntry#TYPE_SECRETKEY_ENTRY
         * @see TokenEntry#TYPE_TRUSTED_ENTRY
         */
        type,

    }

    /** DN part used to mark dummy certificates by SignServer. */
    private static final String SUBJECT_DUMMY = "L=_SignServer_DUMMY_CERT_";
    private static final String CESECORE_SUBJECT_DUMMY_CN = "CN=some guy";
    private static final String CESECORE_SUBJECT_DUMMY_L = "L=around";
    private static final String CESECORE_SUBJECT_DUMMY_C = "C=US";

    /** A workaround for the feature in SignServer 2.0 that property keys are 
     * always converted to upper case. The EJBCA CA Tokens usually use mixed case properties
     */
    public static Properties fixP11Properties(final Properties props) {
        String prop = props.getProperty(PROPERTY_AUTHCODE);
        if (prop != null) {
            props.setProperty("authCode", prop);
        }
        prop = props.getProperty(PROPERTY_DEFAULTKEY);
        if (prop != null) {
            props.setProperty("defaultKey", prop);
        }
        prop = props.getProperty(PROPERTY_PIN);
        if (prop != null) {
            props.setProperty("pin", prop);
        }
        prop = props.getProperty(PROPERTY_SHAREDLIBRARY);
        if (prop != null) {
            props.setProperty("sharedLibrary", prop);
        }
        prop = props.getProperty(PROPERTY_SHAREDLIBRARYNAME);
        if (prop != null) {
            props.setProperty("sharedLibraryName", prop);
        }
        prop = props.getProperty(PROPERTY_SLOTLABELVALUE);
        if (prop != null) {
            props.setProperty(org.cesecore.keys.token.PKCS11CryptoToken.SLOT_LABEL_VALUE, prop);
        }
        prop = props.getProperty(PROPERTY_SLOT);
        if (prop != null) {
            props.setProperty("slot", prop);
            props.setProperty(PROPERTY_SLOTLABELTYPE, Pkcs11SlotLabelType.SLOT_NUMBER.getKey());
            props.setProperty(org.cesecore.keys.token.PKCS11CryptoToken.SLOT_LABEL_VALUE, prop);
            props.setProperty(PROPERTY_SLOTLABELVALUE, prop);
        }
        prop = props.getProperty(PROPERTY_SLOTLISTINDEX);
        if (prop != null) {
            props.setProperty("slotListIndex", prop);
            props.setProperty(PROPERTY_SLOTLABELTYPE, Pkcs11SlotLabelType.SLOT_INDEX.getKey());
            props.setProperty(org.cesecore.keys.token.PKCS11CryptoToken.SLOT_LABEL_VALUE, prop);
            props.setProperty(PROPERTY_SLOTLABELVALUE, prop);
        }
        prop = props.getProperty(PROPERTY_ATTRIBUTESFILE);
        if (prop != null) {
            props.setProperty("attributesFile", prop);
        }
        prop = props.getProperty(PROPERTY_NEXTCERTSIGNKEY);
        if (prop != null) {
            props.setProperty("nextCertSignKey", prop);
        }
        prop = props.getProperty(PROPERTY_SLOTLABELTYPE);
        if (prop != null) {
            props.setProperty(org.cesecore.keys.token.PKCS11CryptoToken.SLOT_LABEL_TYPE, prop);
        }
        return props;
    }

    /**
     * Remove a key with the specified alias from the keystore.
     * @param keyStore to remove from
     * @param alias of key to remove
     * @return true if the key alias was removed
     * @throws CryptoTokenOfflineException if the keystore was null
     * @throws KeyStoreException for keystore related errors
     * @throws SignServerException if the keystore did not contain a key with the specified alias
     */
    public static boolean removeKey(final KeyStore keyStore, final String alias)
            throws CryptoTokenOfflineException, KeyStoreException, SignServerException {
        if (keyStore == null) {
            throw new CryptoTokenOfflineException("Token offline");
        }
        if (!keyStore.containsAlias(alias)) {
            throw new SignServerException("No such alias in token: " + alias);
        }
        keyStore.deleteEntry(alias);
        return !keyStore.containsAlias(alias);
    }

    /**
     * Performs test signatures for the specified keys or for all if "all" specified.
     * @param keyStore Loaded keystore to read keys from
     * @param alias Alias of key to test or "all" to test all
     * @param authCode Key password (if used, ie for JKS only)
     * @param signatureProvider Provider for creating the signature
     * @return The results for each key found
     * @throws CryptoTokenOfflineException In case the key could not be used
     */
    public static Collection<KeyTestResult> testKey(KeyStore keyStore, String alias, char[] authCode,
            String signatureProvider) throws CryptoTokenOfflineException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("testKey for alias: " + alias);
        }

        final Collection<KeyTestResult> result = new LinkedList<KeyTestResult>();

        try {
            final Enumeration<String> e = keyStore.aliases();
            while (e.hasMoreElements()) {
                final String keyAlias = e.nextElement();
                if (alias.equalsIgnoreCase(ICryptoToken.ALL_KEYS) || alias.equals(keyAlias)) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("checking keyAlias: " + keyAlias);
                    }

                    if (keyStore.isKeyEntry(keyAlias)) {
                        String status;
                        String publicKeyHash = null;
                        boolean success = false;
                        try {
                            final PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, authCode);
                            final Certificate entryCert = keyStore.getCertificate(keyAlias);
                            if (entryCert != null) {
                                final PublicKey publicKey = entryCert.getPublicKey();
                                publicKeyHash = createKeyHash(publicKey);
                                testSignAndVerify(privateKey, publicKey, signatureProvider);
                                success = true;
                                status = "";
                            } else {
                                status = "Not testing keys with alias " + keyAlias + ". No certificate exists.";
                            }
                        } catch (ClassCastException ce) {
                            status = "Not testing keys with alias " + keyAlias + ". Not a private key.";
                        } catch (InvalidKeyException ex) {
                            LOG.error("Error testing key: " + keyAlias, ex);
                            status = ex.getMessage();
                        } catch (KeyStoreException ex) {
                            LOG.error("Error testing key: " + keyAlias, ex);
                            status = ex.getMessage();
                        } catch (NoSuchAlgorithmException ex) {
                            LOG.error("Error testing key: " + keyAlias, ex);
                            status = ex.getMessage();
                        } catch (NoSuchProviderException ex) {
                            LOG.error("Error testing key: " + keyAlias, ex);
                            status = ex.getMessage();
                        } catch (SignatureException ex) {
                            LOG.error("Error testing key: " + keyAlias, ex);
                            status = ex.getMessage();
                        } catch (UnrecoverableKeyException ex) {
                            LOG.error("Error testing key: " + keyAlias, ex);
                            status = ex.getMessage();
                        }
                        result.add(new KeyTestResult(keyAlias, success, status, publicKeyHash));
                    }
                }
            }
        } catch (KeyStoreException ex) {
            throw new CryptoTokenOfflineException(ex);
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("<testKey");
        }
        return result;
    }

    /**
     * Creates a test signature and verifies it.
     *
     * @param privateKey Private key to sign with
     * @param publicKey Public key to verify with
     * @param signatureProvider Name of provider to sign with
     * @throws NoSuchAlgorithmException In case the key or signature algorithm is unknown
     * @throws NoSuchProviderException In case the supplied provider name is unknown or BC is not installed
     * @throws InvalidKeyException If signature verification failed or the key was invalid
     * @throws SignatureException If the signature could not be made or verified correctly
     */
    public static void testSignAndVerify(PrivateKey privateKey, PublicKey publicKey, String signatureProvider)
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {
        final byte input[] = "Lillan gick pa vagen ut, motte dar en katt...".getBytes();
        final String sigAlg = suggestSigAlg(publicKey);
        if (sigAlg == null) {
            throw new NoSuchAlgorithmException("Unknown key algorithm: " + publicKey.getAlgorithm());
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug("Testing keys with algorithm: " + publicKey.getAlgorithm());
            LOG.debug("testSigAlg: " + sigAlg);
            LOG.debug("provider: " + signatureProvider);
            LOG.trace("privateKey: " + privateKey);
            LOG.trace("privateKey class: " + privateKey.getClass().getName());
            LOG.trace("publicKey: " + publicKey);
            LOG.trace("publicKey class: " + publicKey.getClass().getName());
        }
        final Signature signSignature = Signature.getInstance(sigAlg, signatureProvider);
        signSignature.initSign(privateKey);
        signSignature.update(input);
        byte[] signBA = signSignature.sign();
        if (LOG.isTraceEnabled()) {
            LOG.trace("Created signature of size: " + signBA.length);
            LOG.trace("Created signature: " + new String(Hex.encode(signBA)));
        }

        final Signature verifySignature = Signature.getInstance(sigAlg, "BC");
        verifySignature.initVerify(publicKey);
        verifySignature.update(input);
        if (!verifySignature.verify(signBA)) {
            throw new InvalidKeyException("Test signature inconsistent");
        }
    }

    /**
     * Generate a certificate signing request (PKCS#10).
     * @param info A PKCS10CertReqInfo
     * @param privateKey Private key for signing the request
     * @param signatureProvider Name of provider to sign with
     * @param publicKey Public key to include in the request
     * @param explicitEccParameters True if the EC domain parameters should be included (ie. not a named curve)
     * @return the certificate request data
     */
    public static ICertReqData genCertificateRequest(ISignerCertReqInfo info, final PrivateKey privateKey,
            final String signatureProvider, PublicKey publicKey, final boolean explicitEccParameters)
            throws IllegalArgumentException {
        LOG.debug(">genCertificateRequest");
        final Base64SignerCertReqData retval;
        if (info instanceof PKCS10CertReqInfo) {
            PKCS10CertReqInfo reqInfo = (PKCS10CertReqInfo) info;
            PKCS10CertificationRequest pkcs10;

            if (LOG.isDebugEnabled()) {
                LOG.debug("signatureAlgorithm: " + reqInfo.getSignatureAlgorithm());
                LOG.debug("subjectDN: " + reqInfo.getSubjectDN());
                LOG.debug("explicitEccParameters: " + explicitEccParameters);
            }

            try {
                // Handle ECDSA key with explicit parameters
                if (explicitEccParameters && publicKey.getAlgorithm().contains("EC")) {
                    publicKey = ECKeyUtil.publicToExplicitParameters(publicKey, "BC");
                }

                if (LOG.isDebugEnabled()) {
                    LOG.debug("Public key SHA1: " + createKeyHash(publicKey));
                    LOG.debug("Public key SHA256: " + KeyUsageCounterHash.create(publicKey));
                }

                // Generate request
                final JcaPKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(
                        new X500Name(CertTools.stringToBCDNString(reqInfo.getSubjectDN())), publicKey);
                final ContentSigner contentSigner = new JcaContentSignerBuilder(reqInfo.getSignatureAlgorithm())
                        .setProvider(signatureProvider).build(privateKey);
                pkcs10 = builder.build(contentSigner);
                retval = new Base64SignerCertReqData(Base64.encode(pkcs10.getEncoded()));
            } catch (IOException e) {
                throw new IllegalArgumentException("Certificate request error: " + e.getMessage(), e);
            } catch (OperatorCreationException e) {
                throw new IllegalArgumentException("Certificate request error: " + e.getMessage(), e);
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalArgumentException("Certificate request error: " + e.getMessage(), e);
            } catch (NoSuchProviderException e) {
                throw new IllegalArgumentException("Certificate request error: " + e.getMessage(), e);
            }
            LOG.debug("<genCertificateRequest");
            return retval;
        } else {
            throw new IllegalArgumentException(
                    "Unsupported certificate request info type: " + info.getClass().getName());
        }
    }

    /**
     * Creates a SHA-1 hash for the public key.
     * @param key to create hash for
     * @return Hex encoded hash
     */
    public static String createKeyHash(PublicKey key) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
            final String res = new String(Hex.encode(md.digest(key.getEncoded())));
            return res;
        } catch (NoSuchProviderException ex) {
            final String message = "No such provider trying to hash public key";
            LOG.error(message, ex);
            throw new RuntimeException(message, ex);
        } catch (NoSuchAlgorithmException ex) {
            final String message = "No such algorithm trying to hash public key";
            LOG.error(message, ex);
            throw new RuntimeException(message, ex);
        }
    }

    /**
     * Suggests a signature algorithm base on public key type.
     * @param key public key
     * @return One signature algorithm that should work with the key type
     */
    public static String suggestSigAlg(PublicKey key) {
        final String alg;
        if (key instanceof ECKey) {
            alg = "SHA1withECDSA";
        } else if (key instanceof RSAKey) {
            alg = "SHA1withRSA";
        } else if (key instanceof DSAKey) {
            alg = "SHA1withDSA";
        } else {
            alg = null;
        }
        return alg;
    }

    /**
     * Checks if the certificate looks like one of those dummy certificates
     * generated by either SignServer or CESeCore.
     * @param certificate to check
     * @return True if the certificate looks like a dummy certificate
     */
    public static boolean isDummyCertificate(Certificate certificate) {
        final boolean result;
        if (certificate instanceof X509Certificate) {
            result = isDummyCertificateDN(((X509Certificate) certificate).getSubjectX500Principal().getName());
        } else {
            result = false;
        }
        return result;
    }

    /**
     * Checks if the DN looks like one from a dummy certificate
     * generated by either SignServer or CESeCore.
     * @param dn to check
     * @return True if the certificate looks like a dummy certificate DN
     */
    public static boolean isDummyCertificateDN(final String dn) {
        return dn.contains(CryptoTokenHelper.SUBJECT_DUMMY) || (dn.contains(CESECORE_SUBJECT_DUMMY_CN)
                && dn.contains(CESECORE_SUBJECT_DUMMY_L) && dn.contains(CESECORE_SUBJECT_DUMMY_C));
    }

    /**
     * Creates a self-signed dummy certificate that can be used as place holder
     * in a keystore and is recognized as a dummy certificate by a call to
     * isDummyCertificate().
     * @param commonName to use as CN
     * @param sigAlgName signature algorithm for the certificate
     * @param keyPair with public key to include and private key to sign with 
     * @param provider to use for signing
     * @return the new certificate
     * @throws OperatorCreationException
     * @throws CertificateException 
     * @see #isDummyCertificate(java.security.cert.Certificate)
     */
    public static X509Certificate createDummyCertificate(String commonName, String sigAlgName, KeyPair keyPair,
            String provider) throws OperatorCreationException, CertificateException {
        return getSelfCertificate("CN=" + commonName + ", " + CryptoTokenHelper.SUBJECT_DUMMY + ", C=SE",
                (long) 30 * 24 * 60 * 60 * 365, sigAlgName, keyPair, provider);
    }

    private static X509Certificate getSelfCertificate(String myname, long validity, String sigAlg, KeyPair keyPair,
            String provider) throws OperatorCreationException, CertificateException {
        final long currentTime = new Date().getTime();
        final Date firstDate = new Date(currentTime - 24 * 60 * 60 * 1000);
        final Date lastDate = new Date(currentTime + validity * 1000);

        // Add all mandatory attributes
        if (LOG.isDebugEnabled()) {
            LOG.debug("keystore signing algorithm " + sigAlg);
        }

        final PublicKey publicKey = keyPair.getPublic();
        if (publicKey == null) {
            throw new IllegalArgumentException("Public key is null");
        }

        X509v3CertificateBuilder cg = new JcaX509v3CertificateBuilder(new X500Principal(myname),
                BigInteger.valueOf(firstDate.getTime()), firstDate, lastDate, new X500Principal(myname), publicKey);
        final JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(sigAlg);
        contentSignerBuilder.setProvider(provider);

        final ContentSigner contentSigner = contentSignerBuilder.build(keyPair.getPrivate());

        return new JcaX509CertificateConverter().getCertificate(cg.build(contentSigner));
    }

    public static TokenSearchResults searchTokenEntries(final KeyStore keyStore, final int startIndex,
            final int max, final QueryCriteria qc, final boolean includeData)
            throws CryptoTokenOfflineException, QueryException {
        final TokenSearchResults result;
        try {
            final ArrayList<TokenEntry> tokenEntries = new ArrayList<TokenEntry>();
            final Enumeration<String> e = keyStore.aliases(); // We assume the order is the same for every call unless entries has been added or removed

            final long maxIndex = (long) startIndex + max;
            for (int i = 0; i < maxIndex && e.hasMoreElements();) {
                final String keyAlias = e.nextElement();

                final String type;
                if (keyStore.entryInstanceOf(keyAlias, KeyStore.PrivateKeyEntry.class)) {
                    type = TokenEntry.TYPE_PRIVATEKEY_ENTRY;
                } else if (keyStore.entryInstanceOf(keyAlias, KeyStore.SecretKeyEntry.class)) {
                    type = TokenEntry.TYPE_SECRETKEY_ENTRY;
                } else if (keyStore.entryInstanceOf(keyAlias, KeyStore.TrustedCertificateEntry.class)) {
                    type = TokenEntry.TYPE_TRUSTED_ENTRY;
                } else {
                    type = null;
                }

                TokenEntry entry = new TokenEntry(keyAlias, type);

                if (shouldBeIncluded(entry, qc)) {
                    if (i < startIndex) {
                        i++;
                        continue;
                    }

                    if (LOG.isDebugEnabled()) {
                        LOG.debug("checking keyAlias: " + keyAlias);
                    }

                    // Add additional data
                    if (includeData) {
                        Map<String, String> info = new HashMap<String, String>();
                        try {
                            Date creationDate = keyStore.getCreationDate(keyAlias);
                            entry.setCreationDate(creationDate);
                        } catch (ProviderException ex) {
                        } // NOPMD: We ignore if it is not supported

                        if (TokenEntry.TYPE_PRIVATEKEY_ENTRY.equals(type)) {
                            final Certificate[] chain = keyStore.getCertificateChain(keyAlias);
                            if (chain.length > 0) {
                                info.put(INFO_KEY_ALGORITHM,
                                        AlgorithmTools.getKeyAlgorithm(chain[0].getPublicKey()));
                                info.put(INFO_KEY_SPECIFICATION,
                                        AlgorithmTools.getKeySpecification(chain[0].getPublicKey()));
                            }
                            try {
                                entry.setParsedChain(chain);
                            } catch (CertificateEncodingException ex) {
                                info.put("Error", ex.getMessage());
                                LOG.error("Certificate could not be encoded for alias: " + keyAlias, ex);
                            }
                        } else if (TokenEntry.TYPE_TRUSTED_ENTRY.equals(type)) {
                            Certificate certificate = keyStore.getCertificate(keyAlias);
                            try {
                                entry.setParsedTrustedCertificate(certificate);
                            } catch (CertificateEncodingException ex) {
                                info.put("Error", ex.getMessage());
                                LOG.error("Certificate could not be encoded for alias: " + keyAlias, ex);
                            }
                        } else if (TokenEntry.TYPE_SECRETKEY_ENTRY.equals(type)) {
                            try {
                                KeyStore.Entry entry1 = keyStore.getEntry(keyAlias, null);
                                SecretKey secretKey = ((KeyStore.SecretKeyEntry) entry1).getSecretKey();

                                info.put(INFO_KEY_ALGORITHM, secretKey.getAlgorithm());
                                //info.put(INFO_KEY_SPECIFICATION, AlgorithmTools.getKeySpecification(chain[0].getPublicKey())); // TODO: Key specification support for secret keys
                            } catch (NoSuchAlgorithmException ex) {
                                info.put("Error", ex.getMessage());
                                LOG.error("Unable to get secret key for alias: " + keyAlias, ex);
                            } catch (UnrecoverableEntryException ex) {
                                info.put("Error", ex.getMessage());
                                LOG.error("Unable to get secret key for alias: " + keyAlias, ex);
                            }
                        }
                        entry.setInfo(info);
                    }
                    tokenEntries.add(entry);

                    // Increase index
                    i++;
                }
            }
            result = new TokenSearchResults(tokenEntries, e.hasMoreElements());
        } catch (KeyStoreException ex) {
            throw new CryptoTokenOfflineException(ex);
        }
        return result;
    }

    public static final String INFO_KEY_SPECIFICATION = "Key specification";
    public static final String INFO_KEY_ALGORITHM = "Key algorithm";

    private static boolean shouldBeIncluded(TokenEntry tokenEntry, QueryCriteria qc) throws QueryException {
        final List<Elem> terms = new ArrayList<Elem>();

        CollectionUtils.selectRejected(qc.getElements(), PredicateUtils.instanceofPredicate(Order.class), terms);
        if (terms.isEmpty()) {
            return true;
        }
        return generate(tokenEntry, terms.iterator().next());
    }

    private static boolean generate(TokenEntry tokenEntry, final Elem elem) throws QueryException {
        if (elem instanceof Operation) {
            return generateRestriction(tokenEntry, (Operation) elem);
        } else if (elem instanceof Term) {
            return matches(tokenEntry, (Term) elem);
        } else {
            throw new QueryParameterException("No matched restriction");
        }
    }

    private static boolean generateRestriction(TokenEntry tokenEntry, final Operation op) throws QueryException {
        boolean left = matches(tokenEntry, op.getTerm());
        final Elem elem = op.getElement();

        if (op.getOperator() == LogicOperator.OR && left) {
            return true;
        } else if (op.getOperator() == LogicOperator.AND && !left) {
            return false;
        } else if (elem == null) {
            return left;
        } else {
            return generate(tokenEntry, elem);
        }
    }

    private static boolean matches(TokenEntry entry, Term term) throws QueryException {
        final boolean result;

        final Object actualValue;
        switch (TokenEntryFields.valueOf(term.getName())) {
        case alias: {
            actualValue = entry.getAlias();
            break;
        }
        default: {
            throw new QueryException("Unsupported token entry field in query terms: " + term.getName());
        }
        }
        switch (term.getOperator()) {
        case EQ: {
            result = term.getValue().equals(actualValue);
            break;
        }
        case NEQ: {
            result = !term.getValue().equals(actualValue);
            break;
        }
        case NULL: {
            result = term.getValue() == null;
            break;
        }
        case NOTNULL: {
            result = term.getValue() != null;
            break;
        }
        case LIKE: {
            if (term.getValue() instanceof String && actualValue != null) {
                final String value = (String) term.getValue();
                // TODO: At the moment we only support '%' and only in beginning and/or end of value
                final boolean wildcardInBeginning = value.startsWith("%");
                final boolean wildcardAtEnd = value.endsWith("%");

                if (!wildcardInBeginning && !wildcardAtEnd) {
                    result = value.equals(actualValue);
                } else {
                    final String content = value.substring(wildcardInBeginning ? 1 : 0,
                            wildcardAtEnd ? value.length() - 1 : value.length());
                    if (wildcardInBeginning && wildcardAtEnd) {
                        result = actualValue.toString().contains(content);
                    } else if (wildcardInBeginning) {
                        result = actualValue.toString().endsWith(content);
                    } else {
                        result = actualValue.toString().startsWith(content);
                    }
                }
            } else {
                result = false;
            }
            break;
        }
        default: {
            throw new QueryException("Operator not yet supported in query terms: " + term.getOperator().name());
        }
        }

        return result;
    }

    /**
     * Checks that the supplied certificate has a public key matching the
     * exiting one in the keystore.
     * @param keyStore to get the current public key from
     * @param alias of the entry to check
     * @param newCertificate to compare with the current one
     * @throws KeyStoreException if the keystore has not been initialized
     * @throws CryptoTokenOfflineException in case the keys does not match
     */
    public static void ensureNewPublicKeyMatchesOld(KeyStore keyStore, String alias, Certificate newCertificate)
            throws KeyStoreException, CryptoTokenOfflineException {
        Certificate oldCert = keyStore.getCertificate(alias);
        if (!oldCert.getPublicKey().equals(newCertificate.getPublicKey())) {
            throw new CryptoTokenOfflineException("New certificate public key does not match current one");
        }
    }

}