mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 23:30:55 +03:00
Add support for public key authentication with keys as strings.
Currently, only keys as file locations are supported. This change adds support for keys as strings. Significant changes are: 1) Introduction of a new Resource type for keys as strings. 2) Initialization of a key provider with two strings (private and public keys) Leaving the public key null is equivalent to not having a .pub file. 3) Obtaining the reader for the resource is refactored into the resource itself to avoid requiring knowledge of the type outside the resource. The loadKeys and authPublickey convenience methods are not duplicated for the string based loading as we currently don't need them but they could be if desired (although method signature collisions will be a problem).
This commit is contained in:
@@ -50,6 +50,7 @@ import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyPairWrapper;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
|
||||
import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil;
|
||||
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
||||
import net.schmizz.sshj.userauth.method.AuthKeyboardInteractive;
|
||||
import net.schmizz.sshj.userauth.method.AuthMethod;
|
||||
import net.schmizz.sshj.userauth.method.AuthPassword;
|
||||
@@ -64,6 +65,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.net.SocketAddress;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
@@ -501,6 +504,33 @@ public class SSHClient
|
||||
return loadKeys(location, passphrase.toCharArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link KeyProvider} instance from passed strings. Currently only PKCS8 format
|
||||
* private key files are supported (OpenSSH uses this format).
|
||||
* <p/>
|
||||
*
|
||||
* @param privateKey the private key as a string
|
||||
* @param publicKey the public key as a string if it's not included with the private key
|
||||
* @param passwordFinder the {@link PasswordFinder} that can supply the passphrase for decryption (may be {@code
|
||||
* null} in case keyfile is not encrypted)
|
||||
*
|
||||
* @return the key provider ready for use in authentication
|
||||
*
|
||||
* @throws SSHException if there was no suitable key provider available for the file format; typically because
|
||||
* BouncyCastle is not in the classpath
|
||||
* @throws IOException if the key file format is not known, etc.
|
||||
*/
|
||||
public KeyProvider loadKeys(String privateKey, String publicKey, PasswordFinder passwordFinder)
|
||||
throws IOException {
|
||||
final FileKeyProvider.Format format = KeyProviderUtil.detectKeyFileFormat(privateKey, publicKey != null);
|
||||
final FileKeyProvider fkp = Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format
|
||||
.toString());
|
||||
if (fkp == null)
|
||||
throw new SSHException("No provider available for " + format + " key file");
|
||||
fkp.init(privateKey, publicKey, passwordFinder);
|
||||
return fkp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts loading the user's {@code known_hosts} file from the default locations, i.e. {@code ~/.ssh/known_hosts}
|
||||
* and {@code ~/.ssh/known_hosts2} on most platforms. Adds the resulting {@link OpenSSHKnownHosts} object as a host
|
||||
|
||||
@@ -33,4 +33,7 @@ public interface FileKeyProvider
|
||||
|
||||
void init(File location, PasswordFinder pwdf);
|
||||
|
||||
void init(String privateKey, String publicKey);
|
||||
|
||||
void init(String privateKey, String publicKey, PasswordFinder pwdf);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
public class KeyProviderUtil {
|
||||
|
||||
@@ -37,13 +39,50 @@ public class KeyProviderUtil {
|
||||
*/
|
||||
public static FileKeyProvider.Format detectKeyFileFormat(File location)
|
||||
throws IOException {
|
||||
BufferedReader br = new BufferedReader(new FileReader(location));
|
||||
return detectKeyFileFormat(new FileReader(location),
|
||||
new File(location + ".pub").exists());
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to detect how a key file is encoded.
|
||||
* <p/>
|
||||
* Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package.
|
||||
*
|
||||
* @param privateKey Private key stored in a string
|
||||
* @param separatePubKey Is the public key stored separately from the private key
|
||||
*
|
||||
* @return name of the key file format
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public static FileKeyProvider.Format detectKeyFileFormat(String privateKey,
|
||||
boolean separatePubKey)
|
||||
throws IOException {
|
||||
return detectKeyFileFormat(new StringReader(privateKey), separatePubKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to detect how a key file is encoded.
|
||||
* <p/>
|
||||
* Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package.
|
||||
*
|
||||
* @param privateKey Private key accessible through a {@code Reader}
|
||||
* @param separatePubKey Is the public key stored separately from the private key
|
||||
*
|
||||
* @return name of the key file format
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
private static FileKeyProvider.Format detectKeyFileFormat(Reader privateKey,
|
||||
boolean separatePubKey)
|
||||
throws IOException {
|
||||
BufferedReader br = new BufferedReader(privateKey);
|
||||
String firstLine = br.readLine();
|
||||
IOUtils.closeQuietly(br);
|
||||
if (firstLine == null)
|
||||
throw new IOException("Empty file");
|
||||
if (firstLine.startsWith("-----BEGIN") && firstLine.endsWith("PRIVATE KEY-----"))
|
||||
if (new File(location + ".pub").exists())
|
||||
if (separatePubKey)
|
||||
// Can delay asking for password since have unencrypted pubkey
|
||||
return FileKeyProvider.Format.OpenSSH;
|
||||
else
|
||||
@@ -54,5 +93,4 @@ public class KeyProviderUtil {
|
||||
*/
|
||||
return FileKeyProvider.Format.Unknown;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,8 +23,11 @@ import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.security.PublicKey;
|
||||
|
||||
|
||||
/**
|
||||
* Represents an OpenSSH identity that consists of a PKCS8-encoded private key file and an unencrypted public key file
|
||||
* of the same name with the {@code ".pub"} extension. This allows to delay requesting of the passphrase until the
|
||||
@@ -62,18 +65,7 @@ public class OpenSSHKeyFile
|
||||
final File f = new File(location + ".pub");
|
||||
if (f.exists())
|
||||
try {
|
||||
final BufferedReader br = new BufferedReader(new FileReader(f));
|
||||
try {
|
||||
final String keydata = br.readLine();
|
||||
if (keydata != null) {
|
||||
String[] parts = keydata.split(" ");
|
||||
assert parts.length >= 2;
|
||||
type = KeyType.fromString(parts[0]);
|
||||
pubKey = new Buffer.PlainBuffer(Base64.decode(parts[1])).readPublicKey();
|
||||
}
|
||||
} finally {
|
||||
br.close();
|
||||
}
|
||||
initPubKey(new FileReader(f));
|
||||
} catch (IOException e) {
|
||||
// let super provide both public & private key
|
||||
log.warn("Error reading public key file: {}", e.toString());
|
||||
@@ -81,4 +73,36 @@ public class OpenSSHKeyFile
|
||||
super.init(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(String privateKey, String publicKey) {
|
||||
if (publicKey != null) {
|
||||
initPubKey(new StringReader(publicKey));
|
||||
}
|
||||
super.init(privateKey, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and store the separate public key provided alongside the private key
|
||||
*
|
||||
* @param publicKey Public key accessible through a {@code Reader}
|
||||
*/
|
||||
private void initPubKey(Reader publicKey) {
|
||||
try {
|
||||
final BufferedReader br = new BufferedReader(publicKey);
|
||||
try {
|
||||
final String keydata = br.readLine();
|
||||
if (keydata != null) {
|
||||
String[] parts = keydata.split(" ");
|
||||
assert parts.length >= 2;
|
||||
type = KeyType.fromString(parts[0]);
|
||||
pubKey = new Buffer.PlainBuffer(Base64.decode(parts[1])).readPublicKey();
|
||||
}
|
||||
} finally {
|
||||
br.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// let super provide both public & private key
|
||||
log.warn("Error reading public key: {}", e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ import net.schmizz.sshj.common.KeyType;
|
||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||
import net.schmizz.sshj.userauth.password.PrivateKeyFileResource;
|
||||
import net.schmizz.sshj.userauth.password.PrivateKeyStringResource;
|
||||
import net.schmizz.sshj.userauth.password.Resource;
|
||||
|
||||
import org.bouncycastle.openssl.EncryptionException;
|
||||
import org.bouncycastle.openssl.PEMReader;
|
||||
import org.slf4j.Logger;
|
||||
@@ -29,6 +32,8 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
@@ -52,7 +57,8 @@ public class PKCS8KeyFile
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
protected PasswordFinder pwdf;
|
||||
protected PrivateKeyFileResource resource;
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Resource resource;
|
||||
protected KeyPair kp;
|
||||
|
||||
protected KeyType type;
|
||||
@@ -89,6 +95,19 @@ public class PKCS8KeyFile
|
||||
this.pwdf = pwdf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(String privateKey, String publicKey) {
|
||||
assert privateKey != null;
|
||||
assert publicKey == null;
|
||||
resource = new PrivateKeyStringResource(privateKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(String privateKey, String publicKey, PasswordFinder pwdf) {
|
||||
init(privateKey, publicKey);
|
||||
this.pwdf = pwdf;
|
||||
}
|
||||
|
||||
protected org.bouncycastle.openssl.PasswordFinder makeBouncyPasswordFinder() {
|
||||
if (pwdf == null)
|
||||
return null;
|
||||
@@ -111,7 +130,7 @@ public class PKCS8KeyFile
|
||||
for (; ;) {
|
||||
// while the PasswordFinder tells us we should retry
|
||||
try {
|
||||
r = new PEMReader(new InputStreamReader(new FileInputStream(resource.getDetail())), pFinder);
|
||||
r = new PEMReader(resource.getReader(), pFinder);
|
||||
o = r.readObject();
|
||||
} catch (EncryptionException e) {
|
||||
if (pwdf.shouldRetry(resource))
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package net.schmizz.sshj.userauth.password;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
public class AccountResource
|
||||
extends Resource<String> {
|
||||
|
||||
@@ -22,4 +26,8 @@ public class AccountResource
|
||||
super(user + "@" + host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader getReader() throws IOException {
|
||||
return new StringReader(getDetail());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
package net.schmizz.sshj.userauth.password;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
|
||||
public class PrivateKeyFileResource
|
||||
extends Resource<File> {
|
||||
@@ -24,4 +28,9 @@ public class PrivateKeyFileResource
|
||||
super(privateKeyFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader getReader()
|
||||
throws IOException {
|
||||
return new InputStreamReader(new FileInputStream(getDetail()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2010, 2011 sshj contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.schmizz.sshj.userauth.password;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
public class PrivateKeyStringResource extends Resource<String> {
|
||||
|
||||
public PrivateKeyStringResource(String string) {
|
||||
super(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader getReader() throws IOException {
|
||||
return new StringReader(getDetail());
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package net.schmizz.sshj.userauth.password;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
|
||||
/** A password-protected resource */
|
||||
public abstract class Resource<H> {
|
||||
|
||||
@@ -28,6 +31,9 @@ public abstract class Resource<H> {
|
||||
return detail;
|
||||
}
|
||||
|
||||
public abstract Reader getReader()
|
||||
throws IOException;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
|
||||
Reference in New Issue
Block a user