/** * DSS - Digital Signature Services * Copyright (C) 2015 European Commission, provided under the CEF programme * * This file is part of the "DSS - Digital Signature Services" project. * * This library 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 (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package eu.europa.esig.dss.token; import java.lang.reflect.Field; import java.security.KeyStore; import java.security.KeyStore.PrivateKeyEntry; import java.security.KeyStore.ProtectionParameter; import java.security.KeyStoreSpi; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.europa.esig.dss.DSSException; /** * Class holding all MS CAPI API access logic. * */ public class MSCAPISignatureToken extends AbstractSignatureTokenConnection { private static final Logger LOG = LoggerFactory.getLogger(MSCAPISignatureToken.class); private static class CallbackPasswordProtection extends KeyStore.PasswordProtection { PasswordInputCallback passwordCallback; public CallbackPasswordProtection(PasswordInputCallback callback) { super(null); this.passwordCallback = callback; } @Override public synchronized char[] getPassword() { if (passwordCallback == null) { throw new RuntimeException("MSCAPI: No callback provided for entering the PIN/password"); } char[] password = passwordCallback.getPassword(); return password; } } @Override public void close() { } /** * This method is a workaround for scenarios when multiple entries have the same alias. Since the alias is the only * "official" * way of retrieving an entry, only the first entry with a given alias is accessible. * See: * https://joinup.ec.europa.eu/software/sd-dss/issue/problem-possible-keystore-aliases-collision-when-using-mscapi * * @param keyStore * the key store to fix */ private static void _fixAliases(KeyStore keyStore) { Field field; KeyStoreSpi keyStoreVeritable; try { field = keyStore.getClass().getDeclaredField("keyStoreSpi"); field.setAccessible(true); keyStoreVeritable = (KeyStoreSpi) field.get(keyStore); if ("sun.security.mscapi.KeyStore$MY".equals(keyStoreVeritable.getClass().getName())) { field = keyStoreVeritable.getClass().getEnclosingClass().getDeclaredField("entries"); field.setAccessible(true); Object entriesObject = field.get(keyStoreVeritable); if (entriesObject instanceof Map) { // Old issue fixed in JDK 7u121 and JDK8 // More info : // https://bugs.openjdk.java.net/browse/JDK-6483657 // http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/0901dc70ae2b return; } else if (entriesObject instanceof Collection<?>) { Collection<?> entries = (Collection<?>) entriesObject; String alias, hashCode; X509Certificate[] certificates; for (Object entry : entries) { field = entry.getClass().getDeclaredField("certChain"); field.setAccessible(true); certificates = (X509Certificate[]) field.get(entry); hashCode = Integer.toString(certificates[0].hashCode()); field = entry.getClass().getDeclaredField("alias"); field.setAccessible(true); alias = (String) field.get(entry); if (!alias.equals(hashCode)) { field.set(entry, alias.concat(" - ").concat(hashCode)); } } } else { LOG.warn("Unsupported entries type : " + entriesObject.getClass().getName()); } } } catch (Exception exception) { LOG.error(exception.getMessage(), exception); } } @Override public List<DSSPrivateKeyEntry> getKeys() throws DSSException { List<DSSPrivateKeyEntry> list = new ArrayList<DSSPrivateKeyEntry>(); try { ProtectionParameter protectionParameter = new CallbackPasswordProtection(new PrefilledPasswordCallback("nimp".toCharArray())); KeyStore keyStore = KeyStore.getInstance("Windows-MY"); keyStore.load(null, null); _fixAliases(keyStore); Enumeration<String> aliases = keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); if (keyStore.isKeyEntry(alias)) { PrivateKeyEntry entry = (PrivateKeyEntry) keyStore.getEntry(alias, protectionParameter); list.add(new KSPrivateKeyEntry(alias, entry)); } } } catch (Exception e) { throw new DSSException(e); } return list; } }