org.xipki.security.p11.iaik.IaikP11Slot.java Source code

Java tutorial

Introduction

Here is the source code for org.xipki.security.p11.iaik.IaikP11Slot.java

Source

/*
 *
 * This file is part of the XiPKI project.
 * Copyright (c) 2014 - 2015 Lijun Liao
 * Author: 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.security.p11.iaik;

import iaik.pkcs.pkcs11.Mechanism;
import iaik.pkcs.pkcs11.Session;
import iaik.pkcs.pkcs11.SessionInfo;
import iaik.pkcs.pkcs11.Slot;
import iaik.pkcs.pkcs11.State;
import iaik.pkcs.pkcs11.Token;
import iaik.pkcs.pkcs11.TokenException;
import iaik.pkcs.pkcs11.objects.ByteArrayAttribute;
import iaik.pkcs.pkcs11.objects.Certificate.CertificateType;
import iaik.pkcs.pkcs11.objects.CharArrayAttribute;
import iaik.pkcs.pkcs11.objects.DSAPrivateKey;
import iaik.pkcs.pkcs11.objects.DSAPublicKey;
import iaik.pkcs.pkcs11.objects.ECDSAPrivateKey;
import iaik.pkcs.pkcs11.objects.ECDSAPublicKey;
import iaik.pkcs.pkcs11.objects.KeyPair;
import iaik.pkcs.pkcs11.objects.PrivateKey;
import iaik.pkcs.pkcs11.objects.PublicKey;
import iaik.pkcs.pkcs11.objects.RSAPrivateKey;
import iaik.pkcs.pkcs11.objects.RSAPublicKey;
import iaik.pkcs.pkcs11.objects.X509PublicKeyCertificate;
import iaik.pkcs.pkcs11.wrapper.PKCS11Constants;
import iaik.pkcs.pkcs11.wrapper.PKCS11Exception;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.TBSCertificate;
import org.bouncycastle.asn1.x509.Time;
import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X962NamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.SHA384Digest;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.generators.DSAParametersGenerator;
import org.bouncycastle.crypto.params.DSAParameterGenerationParameters;
import org.bouncycastle.crypto.params.DSAParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.common.CmpUtf8Pairs;
import org.xipki.common.ParamChecker;
import org.xipki.common.util.CollectionUtil;
import org.xipki.common.util.LogUtil;
import org.xipki.common.util.X509Util;
import org.xipki.security.api.PasswordResolverException;
import org.xipki.security.api.SecurityFactory;
import org.xipki.security.api.SignerException;
import org.xipki.security.api.p11.P11Identity;
import org.xipki.security.api.p11.P11KeyIdentifier;
import org.xipki.security.api.p11.P11KeypairGenerationResult;
import org.xipki.security.api.p11.P11SlotIdentifier;
import org.xipki.security.api.p11.P11WritableSlot;

/**
 * @author Lijun Liao
 */

public class IaikP11Slot implements P11WritableSlot {

    private static class PrivateKeyAndPKInfo {
        private final PrivateKey privateKey;
        private final SubjectPublicKeyInfo publicKeyInfo;

        public PrivateKeyAndPKInfo(final PrivateKey privateKey, final SubjectPublicKeyInfo publicKeyInfo)
                throws InvalidKeySpecException {
            super();
            this.privateKey = privateKey;
            this.publicKeyInfo = X509Util.toRfc3279Style(publicKeyInfo);
        }

        public PrivateKey getPrivateKey() {
            return privateKey;
        }

        public SubjectPublicKeyInfo getPublicKeyInfo() {
            return publicKeyInfo;
        }
    }

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

    public static final long YEAR = 365L * 24 * 60 * 60 * 1000; // milliseconds of one year

    private final static long DEFAULT_MAX_COUNT_SESSION = 20;
    private Slot slot;
    private int maxSessionCount;
    private List<char[]> password;

    private long timeOutWaitNewSession = 10000; // maximal wait for 10 second
    private AtomicLong countSessions = new AtomicLong(0);
    private BlockingQueue<Session> idleSessions = new LinkedBlockingDeque<>();

    private ConcurrentHashMap<String, PrivateKey> signingKeysById = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, PrivateKey> signingKeysByLabel = new ConcurrentHashMap<>();

    private final List<IaikP11Identity> identities = new LinkedList<>();

    private boolean writableSessionInUse = false;
    private Session writableSession;
    private final P11SlotIdentifier slotId;

    IaikP11Slot(final P11SlotIdentifier slotId, final Slot slot, final List<char[]> password)
            throws SignerException {
        ParamChecker.assertNotNull("slotId", slotId);
        ParamChecker.assertNotNull("slot", slot);

        this.slotId = slotId;
        this.slot = slot;
        this.password = password;

        Session session;
        try {
            session = openSession();
        } catch (TokenException e) {
            final String message = "openSession";
            if (LOG.isWarnEnabled()) {
                LOG.warn(LogUtil.buildExceptionLogFormat(message), e.getClass().getName(), e.getMessage());
            }
            LOG.debug(message, e);
            close();
            throw new SignerException(e.getMessage(), e);
        }

        try {
            firstLogin(session, password);
        } catch (TokenException e) {
            final String message = "firstLogin";
            if (LOG.isWarnEnabled()) {
                LOG.warn(LogUtil.buildExceptionLogFormat(message), e.getClass().getName(), e.getMessage());
            }
            LOG.debug(message, e);
            close();
            throw new SignerException(e.getMessage(), e);
        }

        long maxSessionCount2 = 1;
        try {
            maxSessionCount2 = this.slot.getToken().getTokenInfo().getMaxSessionCount();
        } catch (TokenException e) {
            final String message = "getToken";
            if (LOG.isWarnEnabled()) {
                LOG.warn(LogUtil.buildExceptionLogFormat(message), e.getClass().getName(), e.getMessage());
            }
            LOG.debug(message, e);
        }

        if (maxSessionCount2 == 0) {
            maxSessionCount2 = DEFAULT_MAX_COUNT_SESSION;
        } else {
            // 2 sessions as buffer, they may be used elsewhere.
            maxSessionCount2 = maxSessionCount2 < 3 ? 1 : maxSessionCount2 - 2;
        }

        this.maxSessionCount = (int) maxSessionCount2;

        LOG.info("maxSessionCount: {}", this.maxSessionCount);

        returnIdleSession(session);

        refresh();
    }

    public void refresh() throws SignerException {
        Set<IaikP11Identity> currentIdentifies = new HashSet<>();

        List<PrivateKey> signatureKeys = getAllPrivateObjects(Boolean.TRUE, null);
        for (PrivateKey signatureKey : signatureKeys) {
            byte[] keyId = signatureKey.getId().getByteArrayValue();
            if (keyId == null || keyId.length == 0) {
                continue;
            }

            try {
                X509PublicKeyCertificate certificateObject = getCertificateObject(keyId, null);

                X509Certificate signatureCert = null;
                java.security.PublicKey signaturePublicKey = null;

                if (certificateObject != null) {
                    byte[] encoded = certificateObject.getValue().getByteArrayValue();
                    try {
                        signatureCert = (X509Certificate) X509Util.parseCert(new ByteArrayInputStream(encoded));
                    } catch (Exception e) {
                        String keyIdStr = hex(keyId);
                        final String message = "could not parse certificate with id " + keyIdStr;
                        if (LOG.isWarnEnabled()) {
                            LOG.warn(LogUtil.buildExceptionLogFormat(message), e.getClass().getName(),
                                    e.getMessage());
                        }
                        LOG.debug(message, e);
                        continue;
                    }
                    signaturePublicKey = signatureCert.getPublicKey();
                } else {
                    signatureCert = null;
                    PublicKey publicKeyObject = getPublicKeyObject(Boolean.TRUE, null, keyId, null);
                    if (publicKeyObject == null) {
                        String msg = "neither certificate nor public key for signing is available";
                        LOG.info(msg);
                        continue;
                    }

                    signaturePublicKey = generatePublicKey(publicKeyObject);
                }

                Map<String, Set<X509Certificate>> allCerts = new HashMap<>();
                List<X509Certificate> certChain = new LinkedList<>();

                if (signatureCert != null) {
                    certChain.add(signatureCert);
                    while (true) {
                        X509Certificate context = certChain.get(certChain.size() - 1);
                        if (X509Util.isSelfSigned(context)) {
                            break;
                        }

                        String issuerSubject = signatureCert.getIssuerX500Principal().getName();
                        Set<X509Certificate> issuerCerts = allCerts.get(issuerSubject);
                        if (issuerCerts == null) {
                            issuerCerts = new HashSet<>();
                            X509PublicKeyCertificate[] certObjects = getCertificateObjects(
                                    signatureCert.getIssuerX500Principal());
                            if (certObjects != null && certObjects.length > 0) {
                                for (X509PublicKeyCertificate certObject : certObjects) {
                                    issuerCerts.add(X509Util.parseCert(certObject.getValue().getByteArrayValue()));
                                }
                            }

                            if (CollectionUtil.isNotEmpty(issuerCerts)) {
                                allCerts.put(issuerSubject, issuerCerts);
                            }
                        }

                        if (CollectionUtil.isEmpty(issuerCerts)) {
                            break;
                        }

                        // find the certificate
                        for (X509Certificate issuerCert : issuerCerts) {
                            try {
                                context.verify(issuerCert.getPublicKey());
                                certChain.add(issuerCert);
                            } catch (Exception e) {
                            }
                        }
                    }
                }

                P11KeyIdentifier tKeyId = new P11KeyIdentifier(signatureKey.getId().getByteArrayValue(),
                        new String(signatureKey.getLabel().getCharArrayValue()));

                IaikP11Identity identity = new IaikP11Identity(slotId, tKeyId,
                        certChain.toArray(new X509Certificate[0]), signaturePublicKey);
                currentIdentifies.add(identity);
            } catch (SignerException e) {
                String keyIdStr = hex(keyId);
                final String message = "SignerException while initializing key with key-id " + keyIdStr;
                if (LOG.isWarnEnabled()) {
                    LOG.warn(LogUtil.buildExceptionLogFormat(message), e.getClass().getName(), e.getMessage());
                }
                LOG.debug(message, e);
                continue;
            } catch (Throwable t) {
                String keyIdStr = hex(keyId);
                final String message = "unexpected exception while initializing key with key-id " + keyIdStr;
                if (LOG.isWarnEnabled()) {
                    LOG.warn(LogUtil.buildExceptionLogFormat(message), t.getClass().getName(), t.getMessage());
                }
                LOG.debug(message, t);
                continue;
            }
        }

        this.identities.clear();
        this.identities.addAll(currentIdentifies);
        currentIdentifies.clear();
    }

    public byte[] CKM_ECDSA(final byte[] hash, final P11KeyIdentifier keyId) throws SignerException {
        return CKM_SIGN(PKCS11Constants.CKM_ECDSA, hash, keyId);
    }

    public byte[] CKM_DSA(final byte[] hash, final P11KeyIdentifier keyId) throws SignerException {
        return CKM_SIGN(PKCS11Constants.CKM_DSA, hash, keyId);
    }

    public byte[] CKM_RSA_PKCS(final byte[] encodedDigestInfo, final P11KeyIdentifier keyId)
            throws SignerException {
        return CKM_SIGN(PKCS11Constants.CKM_RSA_PKCS, encodedDigestInfo, keyId);
    }

    public byte[] CKM_RSA_X509(final byte[] hash, final P11KeyIdentifier keyId) throws SignerException {
        return CKM_SIGN(PKCS11Constants.CKM_RSA_X_509, hash, keyId);
    }

    private byte[] CKM_SIGN(final long mech, final byte[] hash, final P11KeyIdentifier keyId)
            throws SignerException {
        PrivateKey signingKey;
        synchronized (keyId) {
            if (keyId.getKeyId() != null) {
                signingKey = signingKeysById.get(keyId.getKeyIdHex());
            } else {
                signingKey = signingKeysByLabel.get(keyId.getKeyLabel());
            }

            if (signingKey == null) {
                LOG.info("try to retieve private key " + keyId);
                String label = keyId.getKeyLabel();
                signingKey = getPrivateObject(Boolean.TRUE, null, keyId.getKeyId(),
                        (label == null) ? null : label.toCharArray());

                if (signingKey != null) {
                    LOG.info("found private key " + keyId);
                    cacheSigningKey(signingKey);
                } else {
                    LOG.warn("could not find private key " + keyId);
                }
                throw new SignerException("no key for signing is available");
            }
        }

        Session session = borrowIdleSession();
        if (session == null) {
            throw new SignerException("no idle session available");
        }

        try {
            Mechanism algorithmId = Mechanism.get(mech);

            if (LOG.isTraceEnabled()) {
                LOG.debug("sign with private key:\n{}", signingKey);
            }

            synchronized (session) {
                login(session);
                session.signInit(algorithmId, signingKey);
                byte[] signature = session.sign(hash);
                if (LOG.isDebugEnabled()) {
                    LOG.debug("signature:\n{}", Hex.toHexString(signature));
                }
                return signature;
            }
        } catch (TokenException e) {
            throw new SignerException(e.getMessage(), e);
        } finally {
            returnIdleSession(session);
        }
    }

    private Session openSession() throws TokenException {
        return openSession(false);
    }

    private Session openSession(final boolean rwSession) throws TokenException {
        Session session = slot.getToken().openSession(Token.SessionType.SERIAL_SESSION, rwSession, null, null);
        countSessions.incrementAndGet();
        return session;
    }

    private void closeSession(final Session session) throws TokenException {
        try {
            session.closeSession();
        } finally {
            countSessions.decrementAndGet();
        }
    }

    private synchronized Session borrowWritableSession() throws SignerException {
        if (writableSession == null) {
            try {
                writableSession = openSession(true);
            } catch (TokenException e) {
                throw new SignerException("could not open writable session", e);
            }
        }

        if (writableSessionInUse) {
            throw new SignerException("no idle writable session available");
        }

        writableSessionInUse = true;
        return writableSession;
    }

    private synchronized void returnWritableSession(final Session session) throws SignerException {
        if (session != writableSession) {
            throw new SignerException("the returned session does not belong to me");
        }
        this.writableSessionInUse = false;
    }

    public Session borrowIdleSession() throws SignerException {
        if (countSessions.get() < maxSessionCount) {
            Session session = idleSessions.poll();
            if (session == null) {
                // create new session
                try {
                    session = openSession();
                } catch (TokenException e) {
                    LOG.error("openSession(), TokenException: {}", e.getMessage());
                    LOG.debug("openSession()", e);
                }
            }

            if (session != null)
                return session;
        }

        try {
            return idleSessions.poll(timeOutWaitNewSession, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
        }

        throw new SignerException("no idle session");
    }

    public void returnIdleSession(final Session session) {
        if (session == null)
            return;

        for (int i = 0; i < 3; i++) {
            try {
                idleSessions.put(session);
                return;
            } catch (InterruptedException e) {
            }
        }

        try {
            closeSession(session);
        } catch (TokenException e) {
            LOG.error("closeSession.{}: {}", e.getClass().getName(), e.getMessage());
            LOG.debug("closeSession", e);
        }
    }

    private void firstLogin(final Session session, final List<char[]> password) throws TokenException {
        boolean isProtectedAuthenticationPath = session.getToken().getTokenInfo().isProtectedAuthenticationPath();

        try {
            if (isProtectedAuthenticationPath || CollectionUtil.isEmpty(password)) {
                LOG.info("verify on PKCS11Module with PROTECTED_AUTHENTICATION_PATH");
                // some driver does not accept null PIN
                session.login(Session.UserType.USER, "".toCharArray());
                this.password = null;
            } else {
                LOG.info("verify on PKCS11Module with PIN");

                for (char[] singlePwd : password) {
                    session.login(Session.UserType.USER, singlePwd);
                }
                this.password = password;
            }
        } catch (PKCS11Exception p11e) {
            if (p11e.getErrorCode() != 0x100)// 0x100: user already logged in
            {
                throw p11e;
            }
        }
    }

    public void login() throws SignerException {
        Session session = borrowIdleSession();
        try {
            login(session);
        } finally {
            returnIdleSession(session);
        }
    }

    private void login(final Session session) throws SignerException {
        try {
            boolean isSessionLoggedIn = checkSessionLoggedIn(session);
            if (isSessionLoggedIn) {
                return;
            }
            boolean loginRequired = session.getToken().getTokenInfo().isLoginRequired();

            LOG.debug("loginRequired: {}", loginRequired);
            if (loginRequired == false) {
                return;
            }

            if (CollectionUtil.isEmpty(password)) {
                session.login(Session.UserType.USER, null);
            } else {
                for (char[] singlePwd : password) {
                    session.login(Session.UserType.USER, singlePwd);
                }
            }
        } catch (TokenException e) {
            throw new SignerException(e.getMessage(), e);
        }
    }

    private static boolean checkSessionLoggedIn(final Session session) throws SignerException {
        SessionInfo info;
        try {
            info = session.getSessionInfo();
        } catch (TokenException e) {
            throw new SignerException(e.getMessage(), e);
        }
        if (LOG.isTraceEnabled()) {
            LOG.debug("SessionInfo: {}", info);
        }

        State state = info.getState();
        long deviceError = info.getDeviceError();

        LOG.debug("to be verified PKCS11Module: state = {}, deviceError: {}", state, deviceError);

        boolean isRwSessionLoggedIn = state.equals(State.RW_USER_FUNCTIONS);
        boolean isRoSessionLoggedIn = state.equals(State.RO_USER_FUNCTIONS);

        boolean sessionSessionLoggedIn = ((isRoSessionLoggedIn || isRwSessionLoggedIn) && deviceError == 0);
        LOG.debug("sessionSessionLoggedIn: {}", sessionSessionLoggedIn);
        return sessionSessionLoggedIn;
    }

    public void close() {
        if (slot != null) {
            try {
                LOG.info("close all sessions on token: {}", slot.getSlotID());
                slot.getToken().closeAllSessions();
            } catch (Throwable t) {
                final String message = "error while slot.getToken().closeAllSessions()";
                if (LOG.isWarnEnabled()) {
                    LOG.warn(LogUtil.buildExceptionLogFormat(message), t.getClass().getName(), t.getMessage());
                }
                LOG.debug(message, t);
            }

            slot = null;
        }

        // clear the session pool
        idleSessions.clear();
        countSessions.lazySet(0);
    }

    public List<PrivateKey> getAllPrivateObjects(final Boolean forSigning, final Boolean forDecrypting)
            throws SignerException {
        Session session = borrowIdleSession();

        try {
            if (LOG.isTraceEnabled()) {
                String info = listPrivateKeyObjects(session, forSigning, forDecrypting);
                LOG.debug(info);
            }

            PrivateKey template = new PrivateKey();
            if (forSigning != null) {
                template.getSign().setBooleanValue(forSigning);
            }
            if (forDecrypting != null) {
                template.getDecrypt().setBooleanValue(forDecrypting);
            }

            List<iaik.pkcs.pkcs11.objects.Object> tmpObjects = getObjects(session, template);
            if (CollectionUtil.isEmpty(tmpObjects)) {
                return Collections.emptyList();
            }

            int n = tmpObjects.size();
            LOG.info("found {} private keys", n);

            List<PrivateKey> privateKeys = new ArrayList<>(n);
            for (iaik.pkcs.pkcs11.objects.Object tmpObject : tmpObjects) {
                PrivateKey privateKey = (PrivateKey) tmpObject;
                privateKeys.add(privateKey);
                cacheSigningKey(privateKey);
            }

            return privateKeys;
        } finally {
            returnIdleSession(session);
        }
    }

    public List<X509PublicKeyCertificate> getAllCertificateObjects() throws SignerException {
        Session session = borrowIdleSession();
        try {
            if (LOG.isTraceEnabled()) {
                String info = listCertificateObjects(session);
                LOG.debug(info);
            }
            X509PublicKeyCertificate template = new X509PublicKeyCertificate();
            List<iaik.pkcs.pkcs11.objects.Object> tmpObjects = getObjects(session, template);
            int n = tmpObjects.size();
            List<X509PublicKeyCertificate> certs = new ArrayList<>(n);
            for (iaik.pkcs.pkcs11.objects.Object tmpObject : tmpObjects) {
                X509PublicKeyCertificate cert = (X509PublicKeyCertificate) tmpObject;
                certs.add(cert);
            }
            return certs;
        } finally {
            returnIdleSession(session);
        }
    }

    private void cacheSigningKey(final PrivateKey privateKey) {
        Boolean b = privateKey.getSign().getBooleanValue();
        byte[] id = privateKey.getId().getByteArrayValue();
        char[] _label = privateKey.getLabel().getCharArrayValue();
        String label = (_label == null) ? null : new String(_label);

        if (b == null || b.booleanValue() == false) {
            LOG.warn("key {} is not for signing", new P11KeyIdentifier(id, label));
            return;
        }

        if (b != null && b.booleanValue()) {
            if (id != null) {
                signingKeysById.put(Hex.toHexString(id).toUpperCase(), privateKey);
            }
            if (label != null) {
                signingKeysByLabel.put(label, privateKey);
            }
        }
    }

    private PrivateKey getPrivateObject(final Boolean forSigning, final Boolean forDecrypting, final byte[] keyId,
            final char[] keyLabel) throws SignerException {
        Session session = borrowIdleSession();

        try {
            if (LOG.isTraceEnabled()) {
                String info = listPrivateKeyObjects(session, forSigning, forDecrypting);
                LOG.debug(info);
            }

            PrivateKey template = new PrivateKey();
            if (forSigning != null) {
                template.getSign().setBooleanValue(forSigning);
            }
            if (forDecrypting != null) {
                template.getDecrypt().setBooleanValue(forDecrypting);
            }
            if (keyId != null) {
                template.getId().setByteArrayValue(keyId);
            }
            if (keyLabel != null) {
                template.getLabel().setCharArrayValue(keyLabel);
            }

            List<iaik.pkcs.pkcs11.objects.Object> tmpObjects = getObjects(session, template);
            if (CollectionUtil.isEmpty(tmpObjects)) {
                return null;
            }

            int size = tmpObjects.size();
            if (size > 1) {
                LOG.warn("found {} private key identified by {}, use the first one", size,
                        getDescription(keyId, keyLabel));
            }
            return (PrivateKey) tmpObjects.get(0);
        } finally {
            returnIdleSession(session);
        }
    }

    private String listPrivateKeyObjects(final Session session, final Boolean forSigning,
            final Boolean forDecrypting) {
        try {
            StringBuilder msg = new StringBuilder();
            msg.append("available private keys: ");
            msg.append("forSigning: ").append(forSigning);
            msg.append(", forDecrypting: ").append(forDecrypting).append("\n");

            PrivateKey template = new PrivateKey();
            if (forSigning != null) {
                template.getSign().setBooleanValue(forSigning);
            }
            if (forDecrypting != null) {
                template.getDecrypt().setBooleanValue(forDecrypting);
            }
            List<iaik.pkcs.pkcs11.objects.Object> tmpObjects = getObjects(session, template);
            if (CollectionUtil.isEmpty(tmpObjects)) {
                msg.append(" empty");
            }
            for (int i = 0; i < tmpObjects.size(); i++) {
                PrivateKey privKey = (PrivateKey) tmpObjects.get(i);
                msg.append("------------------------PrivateKey ").append(i + 1)
                        .append("-------------------------\n");

                msg.append("\tid(hex): ");
                ByteArrayAttribute id = privKey.getId();
                byte[] bytes = null;
                if (id != null) {
                    bytes = id.getByteArrayValue();
                }
                msg.append(bytes == null ? "null" : Hex.toHexString(bytes)).append("\n");

                msg.append("\tlabel:   ");
                CharArrayAttribute label = privKey.getLabel();
                char[] chars = null;
                if (label != null) {
                    chars = label.getCharArrayValue();
                }
                msg.append(chars).append("\n");
            }
            return msg.toString();
        } catch (Throwable t) {
            return "Exception while calling listPrivateKeyObjects(): " + t.getMessage();
        }
    }

    public PublicKey getPublicKeyObject(final Boolean forSignature, final Boolean forCipher, final byte[] keyId,
            final char[] keyLabel) throws SignerException {
        Session session = borrowIdleSession();

        try {
            if (LOG.isTraceEnabled()) {
                String info = listPublicKeyObjects(session, forSignature, forCipher);
                LOG.debug(info);
            }

            iaik.pkcs.pkcs11.objects.PublicKey template = new iaik.pkcs.pkcs11.objects.PublicKey();
            if (keyId != null) {
                template.getId().setByteArrayValue(keyId);
            }
            if (keyLabel != null) {
                template.getLabel().setCharArrayValue(keyLabel);
            }

            if (forSignature != null) {
                template.getVerify().setBooleanValue(forSignature);
            }
            if (forCipher != null) {
                template.getEncrypt().setBooleanValue(forCipher);
            }

            List<iaik.pkcs.pkcs11.objects.Object> tmpObjects = getObjects(session, template);
            if (CollectionUtil.isEmpty(tmpObjects)) {
                return null;
            }

            int size = tmpObjects.size();
            if (size > 1) {
                LOG.warn("found {} public key identified by {}, use the first one", size,
                        getDescription(keyId, keyLabel));
            }

            iaik.pkcs.pkcs11.objects.PublicKey p11Key = (iaik.pkcs.pkcs11.objects.PublicKey) tmpObjects.get(0);
            return p11Key;
        } finally {
            returnIdleSession(session);
        }
    }

    private static List<iaik.pkcs.pkcs11.objects.Object> getObjects(final Session session,
            final iaik.pkcs.pkcs11.objects.Object template) throws SignerException {
        List<iaik.pkcs.pkcs11.objects.Object> objList = new LinkedList<>();

        try {
            session.findObjectsInit(template);

            while (true) {
                iaik.pkcs.pkcs11.objects.Object[] foundObjects = session.findObjects(1);
                if (foundObjects == null || foundObjects.length == 0) {
                    break;
                }

                for (iaik.pkcs.pkcs11.objects.Object object : foundObjects) {
                    if (LOG.isTraceEnabled()) {
                        LOG.debug("foundObject: {}", object);
                    }
                    objList.add(object);
                }
            }
        } catch (TokenException e) {
            throw new SignerException(e.getMessage(), e);
        } finally {
            try {
                session.findObjectsFinal();
            } catch (Exception e) {
            }
        }

        return objList;
    }

    private X509PublicKeyCertificate[] getCertificateObjects(final X500Principal subject) throws SignerException {
        Session session = borrowIdleSession();

        try {
            if (LOG.isTraceEnabled()) {
                String info = listCertificateObjects(session);
                LOG.debug(info);
            }

            X509PublicKeyCertificate template = new X509PublicKeyCertificate();
            template.getCertificateType().setLongValue(CertificateType.X_509_PUBLIC_KEY);
            template.getSubject().setByteArrayValue(subject.getEncoded());

            List<iaik.pkcs.pkcs11.objects.Object> tmpObjects = getObjects(session, template);
            int n = tmpObjects == null ? 0 : tmpObjects.size();
            if (n == 0) {
                LOG.warn("found no certificate with subject {}", X509Util.getRFC4519Name(subject));
                return null;
            }

            X509PublicKeyCertificate[] certs = new X509PublicKeyCertificate[n];
            for (int i = 0; i < n; i++) {
                certs[i] = (X509PublicKeyCertificate) tmpObjects.get(i);
            }
            return certs;
        } finally {
            returnIdleSession(session);
        }
    }

    private X509PublicKeyCertificate getCertificateObject(final byte[] keyId, final char[] keyLabel)
            throws SignerException {
        X509PublicKeyCertificate[] certs = getCertificateObjects(keyId, keyLabel);
        if (certs == null) {
            return null;
        }
        if (certs.length > 1) {
            LOG.warn("found {} public key identified by {}, use the first one", certs.length,
                    getDescription(keyId, keyLabel));
        }
        return certs[0];
    }

    private X509PublicKeyCertificate[] getCertificateObjects(final byte[] keyId, final char[] keyLabel)
            throws SignerException {
        Session session = borrowIdleSession();

        try {
            if (LOG.isTraceEnabled()) {
                String info = listCertificateObjects(session);
                LOG.debug(info);
            }

            X509PublicKeyCertificate template = new X509PublicKeyCertificate();
            if (keyId != null) {
                template.getId().setByteArrayValue(keyId);
            }
            if (keyLabel != null) {
                template.getLabel().setCharArrayValue(keyLabel);
            }

            List<iaik.pkcs.pkcs11.objects.Object> tmpObjects = getObjects(session, template);
            if (CollectionUtil.isEmpty(tmpObjects)) {
                LOG.info("found no certificate identified by {}", getDescription(keyId, keyLabel));
                return null;
            }

            int size = tmpObjects.size();
            X509PublicKeyCertificate[] certs = new X509PublicKeyCertificate[size];
            for (int i = 0; i < size; i++) {
                certs[i] = (X509PublicKeyCertificate) tmpObjects.get(i);
            }
            return certs;
        } finally {
            returnIdleSession(session);
        }
    }

    private String listCertificateObjects(final Session session) {
        try {
            StringBuilder msg = new StringBuilder();
            msg.append("available certificates: ");

            X509PublicKeyCertificate template = new X509PublicKeyCertificate();

            List<iaik.pkcs.pkcs11.objects.Object> tmpObjects = getObjects(session, template);
            if (CollectionUtil.isEmpty(tmpObjects)) {
                msg.append(" empty");
            }
            for (int i = 0; i < tmpObjects.size(); i++) {
                X509PublicKeyCertificate cert = (X509PublicKeyCertificate) tmpObjects.get(i);
                msg.append("------------------------Certificate ").append(i + 1)
                        .append("-------------------------\n");

                msg.append("\tid(hex): ");
                ByteArrayAttribute id = cert.getId();
                byte[] bytes = null;
                if (id != null) {
                    bytes = id.getByteArrayValue();
                }
                msg.append(bytes == null ? "null" : Hex.toHexString(bytes)).append("\n");

                msg.append("\tlabel:   ");
                CharArrayAttribute label = cert.getLabel();
                char[] chars = null;
                if (label != null) {
                    chars = label.getCharArrayValue();
                }
                msg.append(chars).append("\n");
            }
            return msg.toString();
        } catch (Throwable t) {
            return "Exception while calling listCertificateObjects(): " + t.getMessage();
        }
    }

    @Override
    public void updateCertificate(final P11KeyIdentifier keyIdentifier, final X509Certificate newCert,
            final Set<X509Certificate> caCerts, final SecurityFactory securityFactory) throws Exception {
        ParamChecker.assertNotNull("keyIdentifier", keyIdentifier);
        ParamChecker.assertNotNull("newCert", newCert);

        String keyLabel = keyIdentifier.getKeyLabel();
        char[] keyLabelChars = (keyLabel == null) ? null : keyLabel.toCharArray();

        PrivateKey privKey = getPrivateObject(null, null, keyIdentifier.getKeyId(), keyLabelChars);

        if (privKey == null) {
            throw new SignerException("could not find private key " + keyIdentifier);
        }

        byte[] keyId = privKey.getId().getByteArrayValue();
        X509PublicKeyCertificate[] existingCerts = getCertificateObjects(keyId, null);

        assertMatch(newCert, keyIdentifier, securityFactory);

        X509Certificate[] certChain = X509Util.buildCertPath(newCert, caCerts);

        Session session = borrowWritableSession();
        try {
            X509PublicKeyCertificate newCertTemp = createPkcs11Template(newCert, null, keyId,
                    privKey.getLabel().getCharArrayValue());
            // delete existing signer certificate objects
            if (existingCerts != null && existingCerts.length > 0) {
                for (X509PublicKeyCertificate existingCert : existingCerts) {
                    session.destroyObject(existingCert);
                }
                Thread.sleep(1000);
            }

            // create new signer certificate object
            session.createObject(newCertTemp);

            // craete CA certificate objects
            if (certChain.length > 1) {
                for (int i = 1; i < certChain.length; i++) {
                    X509Certificate caCert = certChain[i];
                    byte[] encodedCaCert = caCert.getEncoded();

                    boolean alreadyExists = false;
                    X509PublicKeyCertificate[] certObjs = getCertificateObjects(caCert.getSubjectX500Principal());
                    if (certObjs != null) {
                        for (X509PublicKeyCertificate certObj : certObjs) {
                            if (Arrays.equals(encodedCaCert, certObj.getValue().getByteArrayValue())) {
                                alreadyExists = true;
                                break;
                            }
                        }
                    }

                    if (alreadyExists) {
                        continue;
                    }

                    byte[] caCertKeyId = IaikP11Util.generateKeyID(session);
                    X509PublicKeyCertificate newCaCertTemp = createPkcs11Template(caCert, encodedCaCert,
                            caCertKeyId, null);
                    session.createObject(newCaCertTemp);
                }
            }
        } finally {
            returnWritableSession(session);
        }
    }

    @Override
    public boolean removeKeyAndCerts(final P11KeyIdentifier keyIdentifier) throws Exception {
        ParamChecker.assertNotNull("keyIdentifier", keyIdentifier);

        String keyLabel = keyIdentifier.getKeyLabel();
        char[] keyLabelChars = (keyLabel == null) ? null : keyLabel.toCharArray();

        PrivateKey privKey = getPrivateObject(null, null, keyIdentifier.getKeyId(), keyLabelChars);
        if (privKey == null) {
            return false;
        }

        StringBuilder msgBuilder = new StringBuilder();
        Session session = borrowWritableSession();
        try {
            try {
                session.destroyObject(privKey);
            } catch (TokenException e) {
                msgBuilder.append("could not delete private key, ");
            }

            PublicKey pubKey = getPublicKeyObject(null, null, privKey.getId().getByteArrayValue(), null);
            if (pubKey != null) {
                try {
                    session.destroyObject(pubKey);
                } catch (TokenException e) {
                    msgBuilder.append("could not delete public key, ");
                }
            }

            X509PublicKeyCertificate[] certs = getCertificateObjects(privKey.getId().getByteArrayValue(), null);
            if (certs != null && certs.length > 0) {
                for (int i = 0; i < certs.length; i++) {
                    try {
                        session.destroyObject(certs[i]);
                    } catch (TokenException e) {
                        msgBuilder.append("could not delete certificate at index ").append(i);
                        msgBuilder.append(", ");
                    }
                }
            }
        } finally {
            returnWritableSession(session);
        }

        int n = msgBuilder.length();
        if (n > 2) {
            throw new SignerException(msgBuilder.substring(0, n - 2));
        }

        return true;
    }

    @Override
    public void removeCerts(final P11KeyIdentifier keyIdentifier) throws Exception {
        ParamChecker.assertNotNull("keyIdentifier", keyIdentifier);

        String keyLabel = keyIdentifier.getKeyLabel();
        char[] keyLabelChars = (keyLabel == null) ? null : keyLabel.toCharArray();

        X509PublicKeyCertificate[] existingCerts = getCertificateObjects(keyIdentifier.getKeyId(), keyLabelChars);

        if (existingCerts == null || existingCerts.length == 0) {
            throw new SignerException("could not find certificates with id " + keyIdentifier);
        }

        Session session = borrowWritableSession();
        try {
            for (X509PublicKeyCertificate cert : existingCerts) {
                session.destroyObject(cert);
            }
        } finally {
            returnWritableSession(session);
        }
    }

    private String listPublicKeyObjects(final Session session, final Boolean forSignature,
            final Boolean forCipher) {
        try {
            StringBuilder msg = new StringBuilder();
            msg.append("available public keys: ");
            msg.append("forSignature: ").append(forSignature);
            msg.append(", forCipher: ").append(forCipher).append("\n");

            iaik.pkcs.pkcs11.objects.PublicKey template = new iaik.pkcs.pkcs11.objects.PublicKey();
            if (forSignature != null) {
                template.getVerify().setBooleanValue(forSignature);
            }
            if (forCipher != null) {
                template.getEncrypt().setBooleanValue(forCipher);
            }

            List<iaik.pkcs.pkcs11.objects.Object> tmpObjects = getObjects(session, template);
            if (CollectionUtil.isEmpty(tmpObjects)) {
                msg.append(" empty");
            }
            for (int i = 0; i < tmpObjects.size(); i++) {
                iaik.pkcs.pkcs11.objects.PublicKey pubKey = (iaik.pkcs.pkcs11.objects.PublicKey) tmpObjects.get(i);
                msg.append("------------------------Public Key ").append(i + 1)
                        .append("-------------------------\n");
                msg.append("\tid(hex): ");
                ByteArrayAttribute id = pubKey.getId();
                byte[] bytes = null;
                if (id != null) {
                    bytes = id.getByteArrayValue();
                }
                msg.append(bytes == null ? "null" : Hex.toHexString(bytes)).append("\n");

                msg.append("\tlabel:   ");
                CharArrayAttribute label = pubKey.getLabel();
                char[] chars = null;
                if (label != null) {
                    chars = label.getCharArrayValue();
                }
                msg.append(chars).append("\n");
            }
            return msg.toString();
        } catch (Throwable t) {
            return "Exception while calling listPublicKeyObjects(): " + t.getMessage();
        }
    }

    private static String getDescription(final byte[] keyId, final char[] keyLabel) {
        StringBuilder sb = new StringBuilder();
        sb.append("id ");
        sb.append(keyId == null ? "null" : Hex.toHexString(keyId));
        sb.append(" and label ");
        sb.append(keyLabel == null ? "null" : new String(keyLabel));
        return sb.toString();
    }

    private static X509PublicKeyCertificate createPkcs11Template(final X509Certificate cert, byte[] encodedCert,
            final byte[] keyId, char[] label) throws Exception {
        if (encodedCert == null) {
            encodedCert = cert.getEncoded();
        }

        if (label == null) {
            X500Name x500Name = X500Name.getInstance(cert.getSubjectX500Principal().getEncoded());
            label = X509Util.getCommonName(x500Name).toCharArray();
        }

        X509PublicKeyCertificate newCertTemp = new X509PublicKeyCertificate();
        newCertTemp.getId().setByteArrayValue(keyId);
        newCertTemp.getLabel().setCharArrayValue(label);
        newCertTemp.getToken().setBooleanValue(true);
        newCertTemp.getCertificateType().setLongValue(CertificateType.X_509_PUBLIC_KEY);

        newCertTemp.getSubject().setByteArrayValue(cert.getSubjectX500Principal().getEncoded());
        newCertTemp.getIssuer().setByteArrayValue(cert.getIssuerX500Principal().getEncoded());
        newCertTemp.getSerialNumber().setByteArrayValue(cert.getSerialNumber().toByteArray());
        newCertTemp.getValue().setByteArrayValue(encodedCert);
        return newCertTemp;
    }

    private void assertMatch(final X509Certificate cert, final P11KeyIdentifier keyId,
            final SecurityFactory securityFactory) throws SignerException, PasswordResolverException {
        CmpUtf8Pairs pairs = new CmpUtf8Pairs("slot-id", Long.toString(slot.getSlotID()));
        if (keyId.getKeyId() != null) {
            pairs.putUtf8Pair("key-id", Hex.toHexString(keyId.getKeyId()));
        }
        if (keyId.getKeyLabel() != null) {
            pairs.putUtf8Pair("key-label", keyId.getKeyLabel());
        }

        securityFactory.createSigner("PKCS11", pairs.getEncoded(), "SHA1", null, cert);
    }

    @Override
    public P11KeyIdentifier addCert(final X509Certificate cert) throws Exception {
        Session session = borrowWritableSession();
        try {
            byte[] encodedCert = cert.getEncoded();

            X509PublicKeyCertificate[] certObjs = getCertificateObjects(cert.getSubjectX500Principal());
            if (certObjs != null) {
                for (X509PublicKeyCertificate certObj : certObjs) {
                    if (Arrays.equals(encodedCert, certObj.getValue().getByteArrayValue())) {
                        P11KeyIdentifier p11KeyId = new P11KeyIdentifier(certObj.getId().getByteArrayValue(),
                                new String(certObj.getLabel().getCharArrayValue()));
                        throw new SignerException("given certificate already exists under " + p11KeyId);
                    }
                }
            }

            byte[] keyId = IaikP11Util.generateKeyID(session);
            X509PublicKeyCertificate newCaCertTemp = createPkcs11Template(cert, encodedCert, keyId, null);
            session.createObject(newCaCertTemp);
            P11KeyIdentifier p11KeyId = new P11KeyIdentifier(keyId,
                    new String(newCaCertTemp.getLabel().getCharArrayValue()));
            return p11KeyId;
        } finally {
            returnWritableSession(session);
        }
    }

    @Override
    public P11KeypairGenerationResult generateRSAKeypairAndCert(final int keySize, final BigInteger publicExponent,
            final String label, final String subject, final Integer keyUsage,
            final List<ASN1ObjectIdentifier> extendedKeyusage) throws Exception {
        ParamChecker.assertNotBlank("label", label);

        if (keySize < 1024) {
            throw new IllegalArgumentException("keysize not allowed: " + keySize);
        }

        if (keySize % 1024 != 0) {
            throw new IllegalArgumentException("key size is not multiple of 1024: " + keySize);
        }

        Session session = borrowWritableSession();
        try {
            if (IaikP11Util.labelExists(session, label)) {
                throw new IllegalArgumentException("label " + label + " exists, please specify another one");
            }

            byte[] id = IaikP11Util.generateKeyID(session);

            PrivateKeyAndPKInfo privateKeyAndPKInfo = generateRSAKeyPair(session, keySize, publicExponent, id,
                    label);

            AlgorithmIdentifier signatureAlgId = new AlgorithmIdentifier(
                    PKCSObjectIdentifiers.sha256WithRSAEncryption, DERNull.INSTANCE);

            X509CertificateHolder certificate = generateCertificate(session, id, label, subject, signatureAlgId,
                    privateKeyAndPKInfo, keyUsage, extendedKeyusage);
            return new P11KeypairGenerationResult(id, label, certificate);
        } finally {
            returnWritableSession(session);
        }
    }

    @Override
    public P11KeypairGenerationResult generateDSAKeypairAndCert(final int pLength, final int qLength,
            final String label, final String subject, final Integer keyUsage,
            final List<ASN1ObjectIdentifier> extendedKeyusage) throws Exception {
        ParamChecker.assertNotBlank("label", label);

        if (pLength < 1024) {
            throw new IllegalArgumentException("keysize not allowed: " + pLength);
        }

        if (pLength % 1024 != 0) {
            throw new IllegalArgumentException("key size is not multiple of 1024: " + pLength);
        }

        Session session = borrowWritableSession();
        try {
            if (IaikP11Util.labelExists(session, label)) {
                throw new IllegalArgumentException("label " + label + " exists, please specify another one");
            }

            byte[] id = IaikP11Util.generateKeyID(session);

            PrivateKeyAndPKInfo privateKeyAndPKInfo = generateDSAKeyPair(session, pLength, qLength, id, label);
            AlgorithmIdentifier signatureAlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.dsa_with_sha256);

            X509CertificateHolder certificate = generateCertificate(session, id, label, subject, signatureAlgId,
                    privateKeyAndPKInfo, keyUsage, extendedKeyusage);
            return new P11KeypairGenerationResult(id, label, certificate);
        } finally {
            returnWritableSession(session);
        }
    }

    @Override
    public P11KeypairGenerationResult generateECDSAKeypairAndCert(final String curveNameOrOid, final String label,
            final String subject, final Integer keyUsage, final List<ASN1ObjectIdentifier> extendedKeyusage)
            throws Exception {
        ParamChecker.assertNotBlank("curveNameOrOid", curveNameOrOid);
        ParamChecker.assertNotBlank("label", label);

        ASN1ObjectIdentifier curveId = getCurveId(curveNameOrOid);
        if (curveId == null) {
            throw new IllegalArgumentException("unknown curve " + curveNameOrOid);
        }

        X9ECParameters ecParams = ECNamedCurveTable.getByOID(curveId);
        if (ecParams == null) {
            throw new IllegalArgumentException("unknown curve " + curveNameOrOid);
        }

        Session session = borrowWritableSession();
        try {
            if (IaikP11Util.labelExists(session, label)) {
                throw new IllegalArgumentException("label " + label + " exists, please specify another one");
            }

            byte[] id = IaikP11Util.generateKeyID(session);

            PrivateKeyAndPKInfo privateKeyAndPKInfo = generateECDSAKeyPair(session, curveId, ecParams, id, label);

            int keyBitLength = ecParams.getN().bitLength();

            ASN1ObjectIdentifier sigAlgOid;
            if (keyBitLength > 384) {
                sigAlgOid = X9ObjectIdentifiers.ecdsa_with_SHA512;
            } else if (keyBitLength > 256) {
                sigAlgOid = X9ObjectIdentifiers.ecdsa_with_SHA384;
            } else if (keyBitLength > 224) {
                sigAlgOid = X9ObjectIdentifiers.ecdsa_with_SHA256;
            } else if (keyBitLength > 160) {
                sigAlgOid = X9ObjectIdentifiers.ecdsa_with_SHA224;
            } else {
                sigAlgOid = X9ObjectIdentifiers.ecdsa_with_SHA1;
            }

            X509CertificateHolder certificate = generateCertificate(session, id, label, subject,
                    new AlgorithmIdentifier(sigAlgOid, DERNull.INSTANCE), privateKeyAndPKInfo, keyUsage,
                    extendedKeyusage);

            return new P11KeypairGenerationResult(id, label, certificate);
        } finally {
            returnWritableSession(session);
        }
    }

    private PrivateKeyAndPKInfo generateDSAKeyPair(final Session session, final int pLength, final int qLength,
            final byte[] id, final String label) throws Exception {
        DSAParametersGenerator paramGen = new DSAParametersGenerator(new SHA512Digest());
        DSAParameterGenerationParameters genParams = new DSAParameterGenerationParameters(pLength, qLength, 80,
                new SecureRandom());
        paramGen.init(genParams);
        DSAParameters dsaParams = paramGen.generateParameters();

        DSAPrivateKey privateKey = new DSAPrivateKey();
        DSAPublicKey publicKey = new DSAPublicKey();

        setKeyAttributes(id, label, PKCS11Constants.CKK_DSA, privateKey, publicKey);

        publicKey.getPrime().setByteArrayValue(dsaParams.getP().toByteArray());
        publicKey.getSubprime().setByteArrayValue(dsaParams.getQ().toByteArray());
        publicKey.getBase().setByteArrayValue(dsaParams.getG().toByteArray());

        KeyPair kp = session.generateKeyPair(Mechanism.get(PKCS11Constants.CKM_DSA_KEY_PAIR_GEN), publicKey,
                privateKey);

        publicKey = (DSAPublicKey) kp.getPublicKey();
        BigInteger value = new BigInteger(1, publicKey.getValue().getByteArrayValue());

        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(dsaParams.getP()));
        v.add(new ASN1Integer(dsaParams.getQ()));
        v.add(new ASN1Integer(dsaParams.getG()));
        ASN1Sequence dssParams = new DERSequence(v);

        SubjectPublicKeyInfo pkInfo = new SubjectPublicKeyInfo(
                new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, dssParams), new ASN1Integer(value));

        return new PrivateKeyAndPKInfo((DSAPrivateKey) kp.getPrivateKey(), pkInfo);
    }

    private X509CertificateHolder generateCertificate(final Session session, final byte[] id, final String label,
            final String subject, final AlgorithmIdentifier signatureAlgId,
            final PrivateKeyAndPKInfo privateKeyAndPkInfo, Integer keyUsage,
            List<ASN1ObjectIdentifier> extendedKeyUsage) throws Exception {
        BigInteger serialNumber = BigInteger.ONE;
        Date startDate = new Date();
        Date endDate = new Date(startDate.getTime() + 20 * YEAR);

        X500Name x500Name_subject = new X500Name(subject);
        x500Name_subject = X509Util.sortX509Name(x500Name_subject);

        V3TBSCertificateGenerator tbsGen = new V3TBSCertificateGenerator();
        tbsGen.setSerialNumber(new ASN1Integer(serialNumber));
        tbsGen.setSignature(signatureAlgId);
        tbsGen.setIssuer(x500Name_subject);
        tbsGen.setStartDate(new Time(startDate));
        tbsGen.setEndDate(new Time(endDate));
        tbsGen.setSubject(x500Name_subject);
        tbsGen.setSubjectPublicKeyInfo(privateKeyAndPkInfo.getPublicKeyInfo());

        List<Extension> extensions = new ArrayList<>(2);
        if (keyUsage == null) {
            keyUsage = KeyUsage.keyCertSign | KeyUsage.cRLSign | KeyUsage.digitalSignature
                    | KeyUsage.keyEncipherment;
        }
        extensions.add(new Extension(Extension.keyUsage, true, new DEROctetString(new KeyUsage(keyUsage))));

        if (CollectionUtil.isNotEmpty(extendedKeyUsage)) {
            KeyPurposeId[] kps = new KeyPurposeId[extendedKeyUsage.size()];

            int i = 0;
            for (ASN1ObjectIdentifier oid : extendedKeyUsage) {
                kps[i++] = KeyPurposeId.getInstance(oid);
            }

            extensions.add(new Extension(Extension.extendedKeyUsage, false,
                    new DEROctetString(new ExtendedKeyUsage(kps))));
        }

        Extensions paramX509Extensions = new Extensions(extensions.toArray(new Extension[0]));
        tbsGen.setExtensions(paramX509Extensions);

        TBSCertificate tbsCertificate = tbsGen.generateTBSCertificate();
        byte[] encodedTbsCertificate = tbsCertificate.getEncoded();
        byte[] signature = null;
        Digest digest = null;
        Mechanism sigMechanism = null;

        ASN1ObjectIdentifier sigAlgID = signatureAlgId.getAlgorithm();

        if (sigAlgID.equals(PKCSObjectIdentifiers.sha256WithRSAEncryption)) {
            sigMechanism = Mechanism.get(PKCS11Constants.CKM_SHA256_RSA_PKCS);
            session.signInit(sigMechanism, privateKeyAndPkInfo.getPrivateKey());
            signature = session.sign(encodedTbsCertificate);
        } else if (sigAlgID.equals(NISTObjectIdentifiers.dsa_with_sha256)) {
            digest = new SHA256Digest();
            byte[] digestValue = new byte[digest.getDigestSize()];
            digest.update(encodedTbsCertificate, 0, encodedTbsCertificate.length);
            digest.doFinal(digestValue, 0);

            session.signInit(Mechanism.get(PKCS11Constants.CKM_DSA), privateKeyAndPkInfo.getPrivateKey());
            byte[] rawSignature = session.sign(digestValue);
            signature = convertToX962Signature(rawSignature);
        } else {
            if (sigAlgID.equals(X9ObjectIdentifiers.ecdsa_with_SHA1)) {
                digest = new SHA1Digest();
            } else if (sigAlgID.equals(X9ObjectIdentifiers.ecdsa_with_SHA256)) {
                digest = new SHA256Digest();
            } else if (sigAlgID.equals(X9ObjectIdentifiers.ecdsa_with_SHA384)) {
                digest = new SHA384Digest();
            } else if (sigAlgID.equals(X9ObjectIdentifiers.ecdsa_with_SHA512)) {
                digest = new SHA512Digest();
            } else {
                System.err.println("unknown algorithm ID: " + sigAlgID.getId());
                return null;
            }

            byte[] digestValue = new byte[digest.getDigestSize()];
            digest.update(encodedTbsCertificate, 0, encodedTbsCertificate.length);
            digest.doFinal(digestValue, 0);

            session.signInit(Mechanism.get(PKCS11Constants.CKM_ECDSA), privateKeyAndPkInfo.getPrivateKey());
            byte[] rawSignature = session.sign(digestValue);
            signature = convertToX962Signature(rawSignature);
        }

        // build DER certificate
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(tbsCertificate);
        v.add(signatureAlgId);
        v.add(new DERBitString(signature));
        DERSequence cert = new DERSequence(v);

        // build and store PKCS#11 certificate object
        X509PublicKeyCertificate certTemp = new X509PublicKeyCertificate();
        certTemp.getToken().setBooleanValue(true);
        certTemp.getId().setByteArrayValue(id);
        certTemp.getLabel().setCharArrayValue(label.toCharArray());
        certTemp.getSubject().setByteArrayValue(x500Name_subject.getEncoded());
        certTemp.getIssuer().setByteArrayValue(x500Name_subject.getEncoded());
        certTemp.getSerialNumber().setByteArrayValue(serialNumber.toByteArray());
        certTemp.getValue().setByteArrayValue(cert.getEncoded());
        session.createObject(certTemp);

        return new X509CertificateHolder(Certificate.getInstance(cert));
    }

    private static byte[] convertToX962Signature(final byte[] signature) throws IOException {
        int n = signature.length / 2;
        byte[] x = Arrays.copyOfRange(signature, 0, n);
        byte[] y = Arrays.copyOfRange(signature, n, 2 * n);

        ASN1EncodableVector sigder = new ASN1EncodableVector();
        sigder.add(new ASN1Integer(new BigInteger(1, x)));
        sigder.add(new ASN1Integer(new BigInteger(1, y)));

        return new DERSequence(sigder).getEncoded();
    }

    private static void setKeyAttributes(byte[] id, String label, long keyType, PrivateKey privateKey,
            PublicKey publicKey) {
        if (privateKey != null) {
            privateKey.getId().setByteArrayValue(id);
            privateKey.getToken().setBooleanValue(true);
            privateKey.getLabel().setCharArrayValue(label.toCharArray());
            privateKey.getKeyType().setLongValue(keyType);
            privateKey.getSign().setBooleanValue(true);
            privateKey.getPrivate().setBooleanValue(true);
            privateKey.getSensitive().setBooleanValue(true);
        }

        if (publicKey != null) {
            publicKey.getId().setByteArrayValue(id);
            publicKey.getToken().setBooleanValue(true);
            publicKey.getLabel().setCharArrayValue(label.toCharArray());
            publicKey.getKeyType().setLongValue(keyType);
            publicKey.getVerify().setBooleanValue(true);
            publicKey.getModifiable().setBooleanValue(Boolean.TRUE);
        }
    }

    private PrivateKeyAndPKInfo generateRSAKeyPair(final Session session, final int keySize,
            BigInteger publicExponent, final byte[] id, final String label) throws Exception {
        if (publicExponent == null) {
            publicExponent = BigInteger.valueOf(65537);
        }

        RSAPrivateKey privateKey = new RSAPrivateKey();
        RSAPublicKey publicKey = new RSAPublicKey();

        setKeyAttributes(id, label, PKCS11Constants.CKK_RSA, privateKey, publicKey);

        publicKey.getModulusBits().setLongValue((long) keySize);
        publicKey.getPublicExponent().setByteArrayValue(publicExponent.toByteArray());

        KeyPair kp = session.generateKeyPair(Mechanism.get(PKCS11Constants.CKM_RSA_PKCS_KEY_PAIR_GEN), publicKey,
                privateKey);

        publicKey = (RSAPublicKey) kp.getPublicKey();

        BigInteger modulus = new BigInteger(1, publicKey.getModulus().getByteArrayValue());
        publicExponent = new BigInteger(1, publicKey.getPublicExponent().getByteArrayValue());
        RSAKeyParameters keyParams = new RSAKeyParameters(false, modulus, publicExponent);
        SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(keyParams);

        return new PrivateKeyAndPKInfo((RSAPrivateKey) kp.getPrivateKey(), pkInfo);
    }

    private static ASN1ObjectIdentifier getCurveId(final String curveNameOrOid) {
        ASN1ObjectIdentifier curveId;

        try {
            curveId = new ASN1ObjectIdentifier(curveNameOrOid);
            return curveId;
        } catch (Exception e) {
        }

        curveId = X962NamedCurves.getOID(curveNameOrOid);

        if (curveId == null) {
            curveId = SECNamedCurves.getOID(curveNameOrOid);
        }

        if (curveId == null) {
            curveId = TeleTrusTNamedCurves.getOID(curveNameOrOid);
        }

        if (curveId == null) {
            curveId = NISTNamedCurves.getOID(curveNameOrOid);
        }

        return curveId;
    }

    private PrivateKeyAndPKInfo generateECDSAKeyPair(final Session session, final ASN1ObjectIdentifier curveId,
            final X9ECParameters ecParams, final byte[] id, final String label) throws Exception {
        KeyPair kp = null;

        try {
            kp = generateNamedECDSAKeyPair(session, curveId, id, label);
        } catch (TokenException e) {
            kp = generateSpecifiedECDSAKeyPair(session, curveId, ecParams, id, label);
        }

        ECDSAPublicKey publicKey = (ECDSAPublicKey) kp.getPublicKey();

        // build subjectPKInfo object
        byte[] pubPoint = publicKey.getEcPoint().getByteArrayValue();
        DEROctetString os = (DEROctetString) DEROctetString.fromByteArray(pubPoint);

        AlgorithmIdentifier keyAlgID = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, curveId);
        SubjectPublicKeyInfo pkInfo = new SubjectPublicKeyInfo(keyAlgID, os.getOctets());
        return new PrivateKeyAndPKInfo((ECDSAPrivateKey) kp.getPrivateKey(), pkInfo);
    }

    private KeyPair generateNamedECDSAKeyPair(final Session session, final ASN1ObjectIdentifier curveId,
            final byte[] id, final String label) throws TokenException, IOException {
        ECDSAPrivateKey privateKeyTemplate = new ECDSAPrivateKey();
        ECDSAPublicKey publicKeyTemplate = new ECDSAPublicKey();
        setKeyAttributes(id, label, PKCS11Constants.CKK_ECDSA, privateKeyTemplate, publicKeyTemplate);

        byte[] ecdsaParamsBytes = curveId.getEncoded();
        publicKeyTemplate.getEcdsaParams().setByteArrayValue(ecdsaParamsBytes);

        return session.generateKeyPair(Mechanism.get(PKCS11Constants.CKM_EC_KEY_PAIR_GEN), publicKeyTemplate,
                privateKeyTemplate);
    }

    private KeyPair generateSpecifiedECDSAKeyPair(final Session session, final ASN1ObjectIdentifier curveId,
            final X9ECParameters ecParams, final byte[] id, String label) throws TokenException, IOException {
        ECDSAPrivateKey privateKeyTemplate = new ECDSAPrivateKey();
        ECDSAPublicKey publicKeyTemplate = new ECDSAPublicKey();
        setKeyAttributes(id, label, PKCS11Constants.CKK_ECDSA, privateKeyTemplate, publicKeyTemplate);

        byte[] ecdsaParamsBytes = ecParams.getEncoded();
        publicKeyTemplate.getEcdsaParams().setByteArrayValue(ecdsaParamsBytes);

        return session.generateKeyPair(Mechanism.get(PKCS11Constants.CKM_EC_KEY_PAIR_GEN), publicKeyTemplate,
                privateKeyTemplate);
    }

    private static String hex(final byte[] bytes) {
        return Hex.toHexString(bytes).toUpperCase();
    }

    private static java.security.PublicKey generatePublicKey(final PublicKey p11Key) throws SignerException {
        if (p11Key instanceof RSAPublicKey) {
            RSAPublicKey rsaP11Key = (RSAPublicKey) p11Key;
            byte[] expBytes = rsaP11Key.getPublicExponent().getByteArrayValue();
            BigInteger exp = new BigInteger(1, expBytes);

            byte[] modBytes = rsaP11Key.getModulus().getByteArrayValue();
            BigInteger mod = new BigInteger(1, modBytes);

            if (LOG.isDebugEnabled()) {
                LOG.debug("modulus:\n {}", Hex.toHexString(modBytes));
            }
            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(mod, exp);
            try {
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                return keyFactory.generatePublic(keySpec);
            } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
                throw new SignerException(e.getMessage(), e);
            }
        } else if (p11Key instanceof DSAPublicKey) {
            DSAPublicKey dsaP11Key = (DSAPublicKey) p11Key;

            BigInteger prime = new BigInteger(1, dsaP11Key.getPrime().getByteArrayValue()); // p
            BigInteger subPrime = new BigInteger(1, dsaP11Key.getSubprime().getByteArrayValue()); // q
            BigInteger base = new BigInteger(1, dsaP11Key.getBase().getByteArrayValue()); // g
            BigInteger value = new BigInteger(1, dsaP11Key.getValue().getByteArrayValue()); // y

            DSAPublicKeySpec keySpec = new DSAPublicKeySpec(value, prime, subPrime, base);
            try {
                KeyFactory keyFactory = KeyFactory.getInstance("DSA");
                return keyFactory.generatePublic(keySpec);
            } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
                throw new SignerException(e.getMessage(), e);
            }
        } else if (p11Key instanceof ECDSAPublicKey) {
            // FIXME: implement me
            return null;
        } else {
            throw new SignerException("unknown public key class " + p11Key.getClass().getName());
        }
    }

    @Override
    public List<? extends P11Identity> getP11Identities() {
        return Collections.unmodifiableList(identities);
    }

    @Override
    public X509Certificate exportCert(final P11KeyIdentifier keyIdentifier) throws Exception {
        String keyLabel = keyIdentifier.getKeyLabel();
        char[] keyLabelChars = (keyLabel == null) ? null : keyLabel.toCharArray();

        PrivateKey privKey = getPrivateObject(null, null, keyIdentifier.getKeyId(), keyLabelChars);
        if (privKey == null) {
            return null;
        }

        X509PublicKeyCertificate cert = getCertificateObject(privKey.getId().getByteArrayValue(), null);
        return X509Util.parseCert(cert.getValue().getByteArrayValue());
    }

}