/*
*
* Copyright (c) 2013 - 2017 Lijun Liao
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
*
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the XiPKI software without
* disclosing the source code of your own applications.
*
* For more information, please contact Lijun Liao at this
* address: lijun.liao@gmail.com
*/
package org.xipki.commons.security.pkcs11.provider;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.common.util.LogUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.security.exception.P11TokenException;
import org.xipki.commons.security.exception.XiSecurityException;
import org.xipki.commons.security.pkcs11.P11CryptService;
import org.xipki.commons.security.pkcs11.P11CryptServiceFactory;
import org.xipki.commons.security.pkcs11.P11Identity;
import org.xipki.commons.security.pkcs11.P11Module;
import org.xipki.commons.security.pkcs11.P11ObjectIdentifier;
import org.xipki.commons.security.pkcs11.P11Slot;
import org.xipki.commons.security.pkcs11.P11SlotIdentifier;
/**
* construction of alias:
* <ul>
* <li><code><module name>#slotid-<slot id>#keyid-<key id></code></li>
* <li><code><module name>#slotid-<slot id>#keylabel-<key label></code></li>
* <li><code><module name>#slotindex-<slot index>#keyid-<key id></code></li>
* <li><code><module name>#slotindex-<slot index>#keylabel-<key label></code></li>
* </ul>
*
* @author Lijun Liao
* @since 2.0.0
*/
public class XipkiKeyStoreSpi extends KeyStoreSpi {
private static final Logger LOG = LoggerFactory.getLogger(XipkiKeyStoreSpi.class);
private static class MyEnumeration<E> implements Enumeration<E> {
private Iterator<E> iter;
MyEnumeration(final Iterator<E> iter) {
this.iter = iter;
}
@Override
public boolean hasMoreElements() {
return iter.hasNext();
}
@Override
public E nextElement() {
return iter.next();
}
} // class MyEnumeration
private static class KeyCertEntry {
private PrivateKey key;
private Certificate[] chain;
KeyCertEntry(final PrivateKey key, final Certificate[] chain) {
this.key = ParamUtil.requireNonNull("key", key);
this.chain = ParamUtil.requireNonNull("chain", chain);
if (chain.length < 1) {
throw new IllegalArgumentException("chain does not contain any certificate");
}
}
PrivateKey getKey() {
return key;
}
Certificate[] getCertificateChain() {
return Arrays.copyOf(chain, chain.length);
}
Certificate getCertificate() {
return chain[0];
}
} // class KeyCertEntry
private static P11CryptServiceFactory p11CryptServiceFactory;
private Date creationDate;
private Map<String, KeyCertEntry> keyCerts = new HashMap<>();
public static void setP11CryptServiceFactory(final P11CryptServiceFactory service) {
p11CryptServiceFactory = service;
}
@Override
public void engineLoad(final InputStream stream, final char[] password)
throws IOException, NoSuchAlgorithmException, CertificateException {
this.creationDate = new Date();
Set<String> moduleNames = p11CryptServiceFactory.getModuleNames();
for (String moduleName : moduleNames) {
try {
engineLoad(moduleName);
} catch (XiSecurityException | P11TokenException ex) {
LogUtil.error(LOG, ex, "could not load PKCS#11 module " + moduleName);
}
}
if (LOG.isErrorEnabled()) {
LOG.info("loaded key entries {}", keyCerts.keySet());
}
}
private void engineLoad(final String moduleName) throws P11TokenException, XiSecurityException {
P11CryptService p11Service = p11CryptServiceFactory.getP11CryptService(moduleName);
P11Module module = p11Service.getModule();
List<P11SlotIdentifier> slotIds = module.getSlotIdentifiers();
for (P11SlotIdentifier slotId: slotIds) {
P11Slot slot = module.getSlot(slotId);
Set<P11ObjectIdentifier> identityIds = slot.getIdentityIdentifiers();
for (P11ObjectIdentifier objId : identityIds) {
P11Identity identity = slot.getIdentity(objId);
X509Certificate[] chain = identity.getCertificateChain();
if (chain == null || chain.length == 0) {
continue;
}
P11PrivateKey key = new P11PrivateKey(p11Service, identity.getIdentityId());
KeyCertEntry keyCertEntry = new KeyCertEntry(key, chain);
keyCerts.put(moduleName + "#slotid-" + slotId.getId() + "#keyid-"
+ objId.getIdHex(), keyCertEntry);
keyCerts.put(moduleName + "#slotid-" + slotId.getId() + "#keylabel-"
+ objId.getLabel(), keyCertEntry);
keyCerts.put(moduleName + "#slotindex-" + slotId.getIndex() + "#keyid-"
+ objId.getIdHex(), keyCertEntry);
keyCerts.put(moduleName + "#slotindex-" + slotId.getIndex() + "#keylabel-"
+ objId.getLabel(), keyCertEntry);
}
}
} // method engineLoad
@Override
public void engineStore(final OutputStream stream, final char[] password)
throws IOException, NoSuchAlgorithmException, CertificateException {
}
@Override
public Key engineGetKey(final String alias, final char[] password)
throws NoSuchAlgorithmException, UnrecoverableKeyException {
if (!keyCerts.containsKey(alias)) {
return null;
}
return keyCerts.get(alias).getKey();
}
@Override
public Certificate[] engineGetCertificateChain(final String alias) {
if (!keyCerts.containsKey(alias)) {
return null;
}
return keyCerts.get(alias).getCertificateChain();
}
@Override
public Certificate engineGetCertificate(final String alias) {
if (!keyCerts.containsKey(alias)) {
return null;
}
return keyCerts.get(alias).getCertificate();
}
@Override
public Date engineGetCreationDate(final String alias) {
if (!keyCerts.containsKey(alias)) {
return null;
}
return creationDate;
}
@Override
public void engineSetKeyEntry(final String alias, final Key key, final char[] password,
final Certificate[] chain) throws KeyStoreException {
throw new KeyStoreException("keystore is read only");
}
@Override
public void engineSetKeyEntry(final String alias, final byte[] key, final Certificate[] chain)
throws KeyStoreException {
throw new KeyStoreException("keystore is read only");
}
@Override
public void engineSetCertificateEntry(final String alias, final Certificate cert)
throws KeyStoreException {
throw new KeyStoreException("keystore is read only");
}
@Override
public void engineDeleteEntry(final String alias) throws KeyStoreException {
throw new KeyStoreException("keystore is read only");
}
@Override
public Enumeration<String> engineAliases() {
return new MyEnumeration<>(keyCerts.keySet().iterator());
}
@Override
public boolean engineContainsAlias(final String alias) {
return keyCerts.containsKey(alias);
}
@Override
public int engineSize() {
return keyCerts.size();
}
@Override
public boolean engineIsKeyEntry(final String alias) {
if (!keyCerts.containsKey(alias)) {
return false;
}
return keyCerts.get(alias).key != null;
}
@Override
public boolean engineIsCertificateEntry(final String alias) {
if (!keyCerts.containsKey(alias)) {
return false;
}
return keyCerts.get(alias).key == null;
}
@Override
public String engineGetCertificateAlias(final Certificate cert) {
for (String alias : keyCerts.keySet()) {
if (keyCerts.get(alias).getCertificate().equals(cert)) {
return alias;
}
}
return null;
}
}