mirror of
https://github.com/iterate-ch/cyberduck.git
synced 2026-05-26 19:10:49 +00:00
Load bundled opensc-pkcs11.so.
This commit is contained in:
@@ -128,5 +128,12 @@
|
||||
<version>${jna-version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>opensc</groupId>
|
||||
<artifactId>opensc-pkcs11</artifactId>
|
||||
<type>so</type>
|
||||
<version>0.27.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package ch.cyberduck.core.ssl;
|
||||
|
||||
/*
|
||||
* Copyright (c) 2002-2026 iterate GmbH. All rights reserved.
|
||||
* https://cyberduck.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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 General Public License for more details.
|
||||
*/
|
||||
|
||||
import ch.cyberduck.core.CertificateIdentityCallback;
|
||||
import ch.cyberduck.core.DisabledCertificateStore;
|
||||
import ch.cyberduck.core.Host;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.TestProtocol;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PKCS11CertificateStoreX509KeyManagerTest {
|
||||
|
||||
@Test
|
||||
public void testList() {
|
||||
final PKCS11CertificateStoreX509KeyManager manager = new PKCS11CertificateStoreX509KeyManager(CertificateIdentityCallback.noop,
|
||||
new Host(new TestProtocol()), new DisabledCertificateStore(), LoginCallback.noop, "opensc-pkcs11.so");
|
||||
assertTrue(manager.list().isEmpty());
|
||||
}
|
||||
}
|
||||
+68
-32
@@ -19,24 +19,32 @@ import ch.cyberduck.core.CertificateIdentityCallback;
|
||||
import ch.cyberduck.core.CertificateStore;
|
||||
import ch.cyberduck.core.Credentials;
|
||||
import ch.cyberduck.core.Host;
|
||||
import ch.cyberduck.core.LocalFactory;
|
||||
import ch.cyberduck.core.LocaleFactory;
|
||||
import ch.cyberduck.core.LoginCallback;
|
||||
import ch.cyberduck.core.LoginOptions;
|
||||
import ch.cyberduck.core.exception.LoginCanceledException;
|
||||
import ch.cyberduck.core.preferences.HostPreferencesFactory;
|
||||
import ch.cyberduck.core.preferences.PreferencesFactory;
|
||||
|
||||
import org.apache.commons.lang3.concurrent.ConcurrentException;
|
||||
import org.apache.commons.lang3.concurrent.LazyInitializer;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.security.auth.login.LoginException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
public class PKCS11CertificateStoreX509KeyManager extends CertificateStoreX509KeyManager {
|
||||
private static final Logger log = LogManager.getLogger(PKCS11CertificateStoreX509KeyManager.class);
|
||||
@@ -46,23 +54,39 @@ public class PKCS11CertificateStoreX509KeyManager extends CertificateStoreX509Ke
|
||||
this(prompt, bookmark, store, login, HostPreferencesFactory.get(bookmark).getProperty("connection.ssl.keystore.pkcs11.library"));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param library Native PKCS11 library name or path
|
||||
*/
|
||||
public PKCS11CertificateStoreX509KeyManager(final CertificateIdentityCallback prompt, final Host bookmark,
|
||||
final CertificateStore store, final LoginCallback login,
|
||||
final String libraryPath) {
|
||||
super(prompt, bookmark, store, buildKeyStore(libraryPath, bookmark, login));
|
||||
final String library) {
|
||||
super(prompt, bookmark, store, buildKeyStore(library, bookmark, login));
|
||||
}
|
||||
|
||||
private static LazyInitializer<KeyStore> buildKeyStore(final String libraryPath, final Host bookmark, final LoginCallback login) {
|
||||
private static LazyInitializer<KeyStore> buildKeyStore(final String library, final Host bookmark, final LoginCallback login) {
|
||||
return new LazyInitializer<KeyStore>() {
|
||||
@Override
|
||||
protected KeyStore initialize() throws ConcurrentException {
|
||||
try {
|
||||
log.info("Load PKCS11 store from library {}", libraryPath);
|
||||
log.info("Load PKCS11 store from library {}", library);
|
||||
// SunPKCS11 names the configured provider as "SunPKCS11-{name}"
|
||||
final String providerName = "SunPKCS11-Cyberduck";
|
||||
final String providerName = String.format("SunPKCS11-%s", PreferencesFactory.get().getProperty("application.name"));
|
||||
Provider provider = Security.getProvider(providerName);
|
||||
if(provider == null) {
|
||||
provider = configurePkcs11Provider(libraryPath);
|
||||
try {
|
||||
if(LocalFactory.get(library).exists()) {
|
||||
provider = load(library);
|
||||
}
|
||||
else {
|
||||
provider = load(LocalFactory.get(System.getProperty("java.library.path"), library).getAbsolute());
|
||||
}
|
||||
}
|
||||
catch(ReflectiveOperationException e) {
|
||||
log.error("Failed to load PKCS11 provider from {}: {}", library, ExceptionUtils.getRootCause(e).getMessage());
|
||||
throw new ConcurrentException(e);
|
||||
}
|
||||
log.debug("Loaded PKCS11 provider {}", provider.getName());
|
||||
// Register globally so JSSE can resolve RSASSA-PSS from this provider
|
||||
// during TLS 1.3 CertificateVerify — required for RSA keys on hardware tokens
|
||||
Security.addProvider(provider);
|
||||
@@ -72,60 +96,72 @@ public class PKCS11CertificateStoreX509KeyManager extends CertificateStoreX509Ke
|
||||
log.debug("Reusing existing PKCS11 provider {}", providerName);
|
||||
}
|
||||
final KeyStore store = KeyStore.getInstance("PKCS11", provider);
|
||||
char[] pin = null;
|
||||
while(true) {
|
||||
try {
|
||||
store.load(null, pin);
|
||||
break;
|
||||
}
|
||||
catch(IOException e) {
|
||||
if(e.getCause() instanceof LoginException) {
|
||||
// Token requires PIN or provided PIN was incorrect — prompt and retry
|
||||
log.debug("Token requires PIN: {}", e.getCause().getMessage());
|
||||
final Credentials credentials = login.prompt(bookmark,
|
||||
try {
|
||||
store.load(null, null);
|
||||
}
|
||||
catch(IOException e) {
|
||||
if(ExceptionUtils.getRootCause(e) instanceof LoginException) {
|
||||
// Token requires PIN or provided PIN was incorrect — prompt and retry
|
||||
log.debug("Token requires PIN: {}", e.getCause().getMessage());
|
||||
final Credentials credentials;
|
||||
try {
|
||||
credentials = login.prompt(bookmark,
|
||||
bookmark.getCredentials().getUsername(),
|
||||
LocaleFactory.localizedString("Provide additional login credentials", "Credentials"),
|
||||
LocaleFactory.localizedString("Enter PIN for PKCS11 token", "Credentials"),
|
||||
new LoginOptions().user(false).password(true).keychain(false).icon(bookmark.getProtocol().disk())
|
||||
);
|
||||
pin = credentials.getPassword().toCharArray();
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
catch(LoginCanceledException ex) {
|
||||
log.info("PIN prompt canceled for {}", library);
|
||||
throw new ConcurrentException(e);
|
||||
}
|
||||
// Retry with PIN entry
|
||||
store.load(null, credentials.getPassword().toCharArray());
|
||||
}
|
||||
else {
|
||||
log.error("Failed to initialize PKCS11 keystore from {}: {}", library, e.getMessage());
|
||||
throw new ConcurrentException(e);
|
||||
}
|
||||
}
|
||||
catch(CertificateException | NoSuchAlgorithmException e) {
|
||||
log.error("Failed to initialize PKCS11 keystore from {}: {}", library, e.getMessage());
|
||||
throw new ConcurrentException(e);
|
||||
}
|
||||
return store;
|
||||
}
|
||||
catch(LoginCanceledException e) {
|
||||
log.info("PIN prompt canceled for {}", libraryPath);
|
||||
throw new ConcurrentException(e);
|
||||
}
|
||||
catch(Exception e) {
|
||||
log.error("Failed to initialize PKCS11 keystore from {}: {}", libraryPath, e.getMessage());
|
||||
throw new ConcurrentException(e);
|
||||
catch(IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Provider configurePkcs11Provider(final String libraryPath) throws Exception {
|
||||
/**
|
||||
* Load and configure PKCS11 provider
|
||||
*
|
||||
* @param libraryPath Path to native library
|
||||
* @return Null when loading library fails
|
||||
*/
|
||||
private static Provider load(final String libraryPath) throws ReflectiveOperationException {
|
||||
final String config = String.format("--\nname=%s\nlibrary=%s\n",
|
||||
PreferencesFactory.get().getProperty("application.name"), libraryPath);
|
||||
// Java 9+: standard JCA Provider.configure(String) with inline config (prefix --)
|
||||
final Provider base = Security.getProvider("SunPKCS11");
|
||||
if(base != null) {
|
||||
try {
|
||||
final String config = "--\nname=Cyberduck\nlibrary=" + libraryPath + "\n";
|
||||
return (Provider) Provider.class.getMethod("configure", String.class).invoke(base, config);
|
||||
final Method configure = Provider.class.getMethod("configure", String.class);
|
||||
return (Provider) configure.invoke(base, config);
|
||||
}
|
||||
catch(NoSuchMethodException ignored) {
|
||||
// Java 8 does not have Provider.configure() — fall through
|
||||
log.warn("Fall through to reflection to load PKCS11 provider");
|
||||
}
|
||||
}
|
||||
// Java 8: sun.security.pkcs11.SunPKCS11(InputStream) — accessed via reflection so
|
||||
// the source compiles without a direct sun.* reference on Java 9+/21
|
||||
final String config = "name=Cyberduck\nlibrary=" + libraryPath + "\n";
|
||||
final Class<?> cls = Class.forName("sun.security.pkcs11.SunPKCS11");
|
||||
return (Provider) cls.getConstructor(java.io.InputStream.class)
|
||||
return (Provider) cls.getConstructor(InputStream.class)
|
||||
.newInstance(new ByteArrayInputStream(config.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -629,8 +629,8 @@ connection.ssl.keystore.provider=
|
||||
connection.ssl.keystore.pkcs11.library=/opt/homebrew/lib/opensc-pkcs11.so
|
||||
# OpenSC via Homebrew (Intel)
|
||||
#connection.ssl.keystore.pkcs11.library=/usr/local/lib/opensc-pkcs11.so
|
||||
# Override with PKCS11Provider directive in ~/.ssh/config
|
||||
#connection.ssl.keystore.pkcs11.library=/usr/lib/ssh-keychain.dylib
|
||||
# OpenSC Bundled
|
||||
#connection.ssl.keystore.pkcs11.library=opensc-pkcs11.so
|
||||
|
||||
# Transfer read buffer size
|
||||
connection.chunksize=32768
|
||||
|
||||
Reference in New Issue
Block a user