mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 23:30:55 +03:00
Merge pull request #16 from cloudera/forUpstream
Adding support for public key authentication from strings
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)
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Scanner;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -113,6 +114,19 @@ public class OpenSSHKeyFileTest {
|
||||
assertEquals(KeyUtil.newDSAPrivateKey(x, p, q, g), dsa.getPrivate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromString()
|
||||
throws IOException, GeneralSecurityException {
|
||||
FileKeyProvider dsa = new OpenSSHKeyFile();
|
||||
String privateKey = readFile("src/test/resources/id_dsa");
|
||||
String publicKey = readFile("src/test/resources/id_dsa.pub");
|
||||
dsa.init(privateKey, publicKey,
|
||||
PasswordUtils.createOneOff(correctPassphrase));
|
||||
assertEquals(dsa.getType(), KeyType.DSA);
|
||||
assertEquals(KeyUtil.newDSAPublicKey(y, p, q, g), dsa.getPublic());
|
||||
assertEquals(KeyUtil.newDSAPrivateKey(x, p, q, g), dsa.getPrivate());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
@@ -120,4 +134,19 @@ public class OpenSSHKeyFileTest {
|
||||
throw new AssertionError("bouncy castle needed");
|
||||
}
|
||||
|
||||
private String readFile(String pathname)
|
||||
throws IOException {
|
||||
|
||||
StringBuilder fileContents = new StringBuilder();
|
||||
Scanner scanner = new Scanner(new File(pathname));
|
||||
String lineSeparator = System.getProperty("line.separator");
|
||||
try {
|
||||
while(scanner.hasNextLine()) {
|
||||
fileContents.append(scanner.nextLine() + lineSeparator);
|
||||
}
|
||||
return fileContents.toString();
|
||||
} finally {
|
||||
scanner.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user