@Override
    public Boolean authenticate(final Host bookmark, final LoginCallback prompt, final CancelCallback cancel) throws BackgroundException {
        final Credentials credentials = bookmark.getCredentials();
        if(credentials.isPublicKeyAuthentication()) {
            if(log.isDebugEnabled()) {
                log.debug(String.format("Login using public key authentication with credentials %s", credentials));
            }
            final Local privKey = credentials.getIdentity();
            final Local pubKey;
            final FileKeyProvider provider;
            final AtomicBoolean canceled = new AtomicBoolean();
            try {
                final KeyFormat format = KeyProviderUtil.detectKeyFileFormat(
                        new InputStreamReader(privKey.getInputStream(), StandardCharsets.UTF_8), true);
                if(log.isInfoEnabled()) {
                    log.info(String.format("Reading private key %s with key format %s", privKey, format));
                }
                switch(format) {
                    case PKCS8:
                        provider = new PKCS8KeyFile.Factory().create();
                        pubKey = null;
                        break;
                    case OpenSSH: {
                        provider = new OpenSSHKeyFile.Factory().create();
                        final File f = OpenSSHKeyFileUtil.getPublicKeyFile(new File(privKey.getAbsolute()));
                        if(f != null) {
                            pubKey = LocalFactory.get(f.getAbsolutePath());
                        }
                        else {
                            pubKey = null;
                        }
                        break;
                    }
                    case OpenSSHv1: {
                        provider = new OpenSSHKeyV1KeyFile.Factory().create();
                        final File f = OpenSSHKeyFileUtil.getPublicKeyFile(new File(privKey.getAbsolute()));
                        if(f != null) {
                            pubKey = LocalFactory.get(f.getAbsolutePath());
                        }
                        else {
                            pubKey = null;
                        }
                        break;
                    }
                    case PuTTY:
                        provider = new PuTTYKeyFile.Factory().create();
                        pubKey = null;
                        break;
                    default:
                        throw new InteroperabilityException(String.format("Unknown key format for file %s", privKey.getName()));
                }
                provider.init(new InputStreamReader(privKey.getInputStream(), StandardCharsets.UTF_8),
                        pubKey != null ? new InputStreamReader(pubKey.getInputStream(), StandardCharsets.UTF_8) : null,
                        new PasswordFinder() {
                    @Override
                    public char[] reqPassword(Resource<?> resource) {
                        if(StringUtils.isEmpty(credentials.getIdentityPassphrase())) {
                            try {
                                // Use password prompt
                                final Credentials input = prompt.prompt(bookmark,
                                        LocaleFactory.localizedString("Private key password protected", "Credentials"),
                                        String.format("%s (%s)",
                                                LocaleFactory.localizedString("Enter the passphrase for the private key file", "Credentials"),
                                                privKey.getAbbreviatedPath()),
                                        new LoginOptions()
                                                .icon(bookmark.getProtocol().disk())
                                                .user(false).password(true)
                                );
                                credentials.setSaved(input.isSaved());
                                credentials.setIdentityPassphrase(input.getPassword());
                            }
                            catch(LoginCanceledException e) {
                                canceled.set(true);
                                // Return null if user cancels
                                return StringUtils.EMPTY.toCharArray();
                            }
                        }
                        return credentials.getIdentityPassphrase().toCharArray();
                    }

                    @Override
                    public boolean shouldRetry(Resource<?> resource) {
                        return false;
                    }
                });
                client.auth(credentials.getUsername(), new AuthPublickey(provider));
                return client.isAuthenticated();
            }
            catch(IOException e) {
                if(canceled.get()) {
                    throw new LoginCanceledException();
                }
                throw new SFTPExceptionMappingService().map(e);
            }
        }
        return false;
    }
@Test
    public void testAuthenticateKeyNoPassword() throws Exception {
        final Local key = new Local(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString());
        try {
            session.getHost().getCredentials().setIdentity(key);
            new DefaultLocalTouchFeature().touch(key);
            IOUtils.copy(new StringReader("-----BEGIN RSA PRIVATE KEY-----\n" +
                "MIIEpAIBAAKCAQEA0oUHL9fx0o91DHadQq0npjbWpOx61EmHsDBenBcR6U7oI3xg\n" +
                "SE5/YqYp1nkFlIpBeffIplxxpVqxVLazzQqt73vkCdCsILFsFGfs+2LPlh0nL5Bx\n" +
                "hKKCbxbHZKk9IqYXYQbMjGEWexenLPaUQiJWZO4lZRYU2WyLnIxDyULarWE81umv\n" +
                "VXNy+yBIs5j58WWMPpEpDv8+OK0CnXLF5+MUS+3afUvhNyheC52FgoEx7hDEhFb2\n" +
                "W0/cURGoFsq0zih7Xbu0z6c5DaQxfJgLR6tKGPkKdYcW19oQcz4/wzqlyrXp2h0U\n" +
                "LFfFkyJ2WVkmcC75FPB0FQdxWLsEfHVwj5rGWQIDAQABAoIBABaBPhlxPlrCF0Mz\n" +
                "0u/zzs9IAX40AZV8HtNI+HQ7SyDj3JEelLRmH9wN418YJO6SMRm20W5nZ4bDwT1L\n" +
                "IsGCqMF3PxOFiUEddm5gBceB8qsbhrrg+CGztbnmd35dUTnJA6rNrUQ8hMi36Jk3\n" +
                "6vV+8jlr8/dwet4banlbzUtQqEvsbWT32/IiIiflWzDGEpiftDWQ2hoGV/siEKCu\n" +
                "tbNH9S2QJ8/GkCNfFLax6wImy7nxuHtzhjM2OnApXZHxrots5HTNqEv6gETM+1nz\n" +
                "N2nNa19CnxKKVVkZIeg8yQp395QmSTmVZKbzBwLLkyX00EZH1VD+lySkIwOoYOo1\n" +
                "K+qlDwECgYEA/BJ1vba0bndxsqhAmrNSIGc7gRc5FzijyfhcHV7/rpuwfJu/8zV5\n" +
                "KuB0QmqVRCdF/DTKAJb82sf5K4Y4rwOHC8/FUE63UEXzTEHs1LYVugjOT1Jrv9dP\n" +
                "xYHEwL/r9lGZ8ZeC7qRBik4Bj1O5klZuRg+864gsMjcBJ+gZDOBL+EkCgYEA1czP\n" +
                "sv2Z9JTyXWcDxKcCEiAVbkaJybIhfh3qEL9YdHfYnKjSmfd7T1ALKfOI6Npl19WB\n" +
                "pTSiBLyf10hSR2CJ6INvTNbG+a/k7Eit8L1m5QfWBNUm4/6fYE5C28dq9zvMxg8O\n" +
                "2NuhFdwEjKONFK6KRO5nV51TZy/s/zK4+jyb/ZECgYEAzqrD5fs6C6fajEPuU+fZ\n" +
                "2b9JxhkoMaW6hAkgXORadOoo1MFNqzWYVlMYfqusHeilNJaDV+BzB10VKV4ucy9N\n" +
                "NZ3vhFxFZ4cyZr9WUT/IqHI31EkbFnqNaL1wdyfjalyU/sLeEpKRObejB8P5TcWX\n" +
                "O9IDboiC09jNFc7xWuE66CECgYEAgFq6YtnvgK+fJcX/tCMTkjAizZaZiDZh9yYc\n" +
                "bLuDbkC5Eawk63xxUjdazG/BxRHAzKgIclc74Z4gVA2+H83sXWPzAO18fVBFHjmv\n" +
                "+3Sp/lwK99HDzGMD6bxyVSHOrOkA0fMeVcDdX5M8Q/a4by8uIZ4C+s2f0bYrETax\n" +
                "aMHUYJECgYAtpNPOcW8gou019Ys+RaRc0c0k8R5WAgxP0lqEujn+OS/PIdjdWOdD\n" +
                "ZDuIcmUpyqcqqD/+wfgS/HBu011Mi9NSzcw6mogsXFerk28usMUfKBnibyLgtfiM\n" +
                "jo5i4PI+2gDrScmRkBSmm86ewCo1J7Iq1Vk/0FL/oiEA2et7J8vIYA==\n" +
                "-----END RSA PRIVATE KEY-----\n"), key.getOutputStream(false), StandardCharsets.UTF_8);
            // Reconnect
            session.disconnect();
            session.open(new DisabledProxyFinder(), new DisabledHostKeyCallback(), new DisabledLoginCallback(), new DisabledCancelCallback());
            assertTrue(new SFTPPublicKeyAuthentication(session.getClient()).authenticate(session.getHost(), new DisabledLoginCallback() {
                @Override
                public Credentials prompt(final Host bookmark, String username, String title, String reason, LoginOptions options) throws LoginCanceledException {
                    fail();
                    throw new LoginCanceledException();
                }
            }, new DisabledCancelCallback()));

        }
        finally {
            key.delete();
        }
    }