diff --git a/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java b/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java index 6796309f0..a9f49bf25 100644 --- a/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java +++ b/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java @@ -23,6 +23,8 @@ import java.util.Arrays; +import static net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.CURVE_ED25519_SHA512; + /** * Our own extension of the EdDSAPublicKey that comes from ECC-25519, as that class does not implement equality. * The code uses the equality of the keys as an indicator whether they're the same during host key verification. @@ -32,7 +34,7 @@ public class Ed25519PublicKey extends EdDSAPublicKey { public Ed25519PublicKey(EdDSAPublicKeySpec spec) { super(spec); - EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("ed25519-sha-512"); + EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512); if (!spec.getParams().getCurve().equals(ed25519.getCurve())) { throw new SSHRuntimeException("Cannot create Ed25519 Public Key from wrong spec"); } diff --git a/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java b/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java new file mode 100644 index 000000000..1d28f1487 --- /dev/null +++ b/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java @@ -0,0 +1,160 @@ +/* + * Copyright (C)2009 - 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 com.hierynomus.sshj.userauth.keyprovider; + +import java.io.BufferedReader; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.PublicKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.i2p.crypto.eddsa.EdDSAPrivateKey; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; +import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; +import net.schmizz.sshj.common.*; +import net.schmizz.sshj.common.Buffer.PlainBuffer; +import net.schmizz.sshj.userauth.keyprovider.BaseFileKeyProvider; +import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; +import net.schmizz.sshj.userauth.keyprovider.KeyFormat; + +import static net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.CURVE_ED25519_SHA512; + +/** + * Reads a key file in the new OpenSSH format. + * The format is described in the following document: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key + */ +public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider { + private static final Logger logger = LoggerFactory.getLogger(OpenSSHKeyV1KeyFile.class); + private static final String BEGIN = "-----BEGIN "; + private static final String END = "-----END "; + private static final byte[] AUTH_MAGIC = "openssh-key-v1\0".getBytes(); + public static final String OPENSSH_PRIVATE_KEY = "OPENSSH PRIVATE KEY-----"; + + public static class Factory + implements net.schmizz.sshj.common.Factory.Named { + + @Override + public FileKeyProvider create() { + return new OpenSSHKeyV1KeyFile(); + } + + @Override + public String getName() { + return KeyFormat.OpenSSHv1.name(); + } + } + + @Override + protected KeyPair readKeyPair() throws IOException { + BufferedReader reader = new BufferedReader(resource.getReader()); + try { + if (!checkHeader(reader)) { + throw new IOException("This key is not in 'openssh-key-v1' format"); + } + + String keyFile = readKeyFile(reader); + byte[] decode = Base64.decode(keyFile); + PlainBuffer keyBuffer = new PlainBuffer(decode); + return readDecodedKeyPair(keyBuffer); + + } catch (GeneralSecurityException e) { + throw new SSHRuntimeException(e); + } finally { + IOUtils.closeQuietly(reader); + } + } + + private KeyPair readDecodedKeyPair(final PlainBuffer keyBuffer) throws IOException, GeneralSecurityException { + byte[] bytes = new byte[AUTH_MAGIC.length]; + keyBuffer.readRawBytes(bytes); // byte[] AUTH_MAGIC + if (!ByteArrayUtils.equals(bytes, 0, AUTH_MAGIC, 0, AUTH_MAGIC.length)) { + throw new IOException("This key does not contain the 'openssh-key-v1' format magic header"); + } + + String cipherName = keyBuffer.readString(); // string ciphername + String kdfName = keyBuffer.readString(); // string kdfname + String kdfOptions = keyBuffer.readString(); // string kdfoptions + + int nrKeys = keyBuffer.readUInt32AsInt(); // int number of keys N; Should be 1 + if (nrKeys != 1) { + throw new IOException("We don't support having more than 1 key in the file (yet)."); + } + PublicKey publicKey = readPublicKey(new PlainBuffer(keyBuffer.readBytes())); // string publickey1 + PlainBuffer privateKeyBuffer = new PlainBuffer(keyBuffer.readBytes()); // string (possibly) encrypted, padded list of private keys + if ("none".equals(cipherName)) { + logger.debug("Reading unencrypted keypair"); + return readUnencrypted(privateKeyBuffer, publicKey); + } else { + logger.info("Keypair is encrypted with: " + cipherName + ", " + kdfName + ", " + kdfOptions); + throw new IOException("Cannot read encrypted keypair with " + cipherName + " yet."); + } + } + + private PublicKey readPublicKey(final PlainBuffer plainBuffer) throws Buffer.BufferException, GeneralSecurityException { + return KeyType.fromString(plainBuffer.readString()).readPubKeyFromBuffer(plainBuffer); + } + + private String readKeyFile(final BufferedReader reader) throws IOException { + StringBuilder sb = new StringBuilder(); + String line = reader.readLine(); + while (!line.startsWith(END)) { + sb.append(line); + line = reader.readLine(); + } + return sb.toString(); + } + + private boolean checkHeader(final BufferedReader reader) throws IOException { + String line = reader.readLine(); + while (line != null && !line.startsWith(BEGIN)) { + line = reader.readLine(); + } + line = line.substring(BEGIN.length()); + return line.startsWith(OPENSSH_PRIVATE_KEY); + } + + private KeyPair readUnencrypted(final PlainBuffer keyBuffer, final PublicKey publicKey) throws IOException, GeneralSecurityException { + int privKeyListSize = keyBuffer.available(); + if (privKeyListSize % 8 != 0) { + throw new IOException("The private key section must be a multiple of the block size (8)"); + } + int checkInt1 = keyBuffer.readUInt32AsInt(); // uint32 checkint1 + int checkInt2 = keyBuffer.readUInt32AsInt(); // uint32 checkint2 + if (checkInt1 != checkInt2) { + throw new IOException("The checkInts differed, the key was not correctly decoded."); + } + // The private key section contains both the public key and the private key + String keyType = keyBuffer.readString(); // string keytype + logger.info("Read key type: {}", keyType); + + byte[] pubKey = keyBuffer.readBytes(); // string publickey (again...) + keyBuffer.readUInt32(); + byte[] privKey = new byte[32]; + keyBuffer.readRawBytes(privKey); // string privatekey + keyBuffer.readRawBytes(new byte[32]); // string publickey (again...) + String comment = keyBuffer.readString(); // string comment + byte[] padding = new byte[keyBuffer.available()]; + keyBuffer.readRawBytes(padding); // char[] padding + for (int i = 0; i < padding.length; i++) { + if ((int) padding[i] != i + 1) { + throw new IOException("Padding of key format contained wrong byte at position: " + i); + } + } + return new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName(CURVE_ED25519_SHA512)))); + } +} diff --git a/src/main/java/net/schmizz/sshj/DefaultConfig.java b/src/main/java/net/schmizz/sshj/DefaultConfig.java index 98625521f..9f3573bf2 100644 --- a/src/main/java/net/schmizz/sshj/DefaultConfig.java +++ b/src/main/java/net/schmizz/sshj/DefaultConfig.java @@ -33,6 +33,7 @@ import net.schmizz.sshj.transport.random.JCERandom; import net.schmizz.sshj.transport.random.SingletonRandomFactory; import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile; +import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile; import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile; import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile; import org.slf4j.Logger; @@ -113,7 +114,7 @@ protected void initRandomFactory(boolean bouncyCastleRegistered) { protected void initFileKeyProviderFactories(boolean bouncyCastleRegistered) { if (bouncyCastleRegistered) { - setFileKeyProviderFactories(new PKCS8KeyFile.Factory(), new OpenSSHKeyFile.Factory(), new PuTTYKeyFile.Factory()); + setFileKeyProviderFactories(new OpenSSHKeyV1KeyFile.Factory(), new PKCS8KeyFile.Factory(), new OpenSSHKeyFile.Factory(), new PuTTYKeyFile.Factory()); } } diff --git a/src/main/java/net/schmizz/sshj/SSHClient.java b/src/main/java/net/schmizz/sshj/SSHClient.java index 4c84cdde5..0a12698f3 100644 --- a/src/main/java/net/schmizz/sshj/SSHClient.java +++ b/src/main/java/net/schmizz/sshj/SSHClient.java @@ -316,7 +316,7 @@ public void authPassword(String username, PasswordFinder pfinder, PasswordUpdate public void authPublickey(String username) throws UserAuthException, TransportException { final String base = System.getProperty("user.home") + File.separator + ".ssh" + File.separator; - authPublickey(username, base + "id_rsa", base + "id_dsa"); + authPublickey(username, base + "id_rsa", base + "id_dsa", base + "id_ed25519", base + "id_ecdsa"); } /** @@ -524,8 +524,13 @@ public KeyProvider loadKeys(String location, char[] passphrase) } /** - * Creates a {@link KeyProvider} instance from given location on the file system. Currently only PKCS8 format - * private key files are supported (OpenSSH uses this format). + * Creates a {@link KeyProvider} instance from given location on the file system. Currently the following private key files are supported: + * *

* * @param location the location of the key file diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/BaseFileKeyProvider.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/BaseFileKeyProvider.java new file mode 100644 index 000000000..d209e0579 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/BaseFileKeyProvider.java @@ -0,0 +1,92 @@ +/* + * Copyright (C)2009 - 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.keyprovider; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; + +import net.schmizz.sshj.common.KeyType; +import net.schmizz.sshj.userauth.password.*; + +public abstract class BaseFileKeyProvider implements FileKeyProvider { + protected Resource resource; + protected PasswordFinder pwdf; + protected KeyPair kp; + + protected KeyType type; + + @Override + public void init(Reader location) { + assert location != null; + resource = new PrivateKeyReaderResource(location); + } + + @Override + public void init(Reader location, PasswordFinder pwdf) { + init(location); + this.pwdf = pwdf; + } + + @Override + public void init(File location) { + assert location != null; + resource = new PrivateKeyFileResource(location.getAbsoluteFile()); + } + + @Override + public void init(File location, PasswordFinder pwdf) { + init(location); + 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; + } + + @Override + public PrivateKey getPrivate() + throws IOException { + return kp != null ? kp.getPrivate() : (kp = readKeyPair()).getPrivate(); + } + + @Override + public PublicKey getPublic() + throws IOException { + return kp != null ? kp.getPublic() : (kp = readKeyPair()).getPublic(); + } + + @Override + public KeyType getType() + throws IOException { + return type != null ? type : (type = KeyType.fromKey(getPublic())); + } + + + protected abstract KeyPair readKeyPair() throws IOException; +} diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyFormat.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyFormat.java index 816b2bd89..6b3f5e2a4 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyFormat.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyFormat.java @@ -22,6 +22,7 @@ public enum KeyFormat { PKCS5, PKCS8, OpenSSH, + OpenSSHv1, PuTTY, Unknown } diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java index 41dc1d9ac..4df8aca20 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java @@ -18,6 +18,7 @@ import net.schmizz.sshj.common.IOUtils; import java.io.*; +import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile; public class KeyProviderUtil { @@ -88,10 +89,12 @@ private static String readHeader(Reader privateKey) throws IOException { private static KeyFormat keyFormatFromHeader(String header, boolean separatePubKey) { if (header.startsWith("-----BEGIN") && header.endsWith("PRIVATE KEY-----")) { - if (separatePubKey) { + if (separatePubKey && header.contains(OpenSSHKeyV1KeyFile.OPENSSH_PRIVATE_KEY)) { + return KeyFormat.OpenSSHv1; + } else if (separatePubKey) { // Can delay asking for password since have unencrypted pubkey return KeyFormat.OpenSSH; - } else if (header.indexOf("BEGIN PRIVATE KEY") != -1 || header.indexOf("BEGIN ENCRYPTED PRIVATE KEY") != -1) { + } else if (header.contains("BEGIN PRIVATE KEY") || header.contains("BEGIN ENCRYPTED PRIVATE KEY")) { return KeyFormat.PKCS8; } else { return KeyFormat.PKCS5; diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS5KeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS5KeyFile.java index ff14c3f07..66a28cd85 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS5KeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS5KeyFile.java @@ -15,28 +15,28 @@ */ package net.schmizz.sshj.userauth.keyprovider; -import net.schmizz.sshj.common.Base64; -import net.schmizz.sshj.common.IOUtils; -import net.schmizz.sshj.common.KeyType; -import net.schmizz.sshj.transport.cipher.*; -import net.schmizz.sshj.transport.digest.Digest; -import net.schmizz.sshj.transport.digest.MD5; -import net.schmizz.sshj.userauth.password.*; - -import javax.xml.bind.DatatypeConverter; -import java.io.*; +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.security.*; import java.security.spec.*; import java.util.Arrays; +import javax.xml.bind.DatatypeConverter; + +import net.schmizz.sshj.common.Base64; +import net.schmizz.sshj.common.IOUtils; +import net.schmizz.sshj.common.KeyType; +import net.schmizz.sshj.transport.cipher.*; +import net.schmizz.sshj.transport.digest.Digest; +import net.schmizz.sshj.transport.digest.MD5; /** * Represents a PKCS5-encoded key file. This is the format typically used by OpenSSH, OpenSSL, Amazon, etc. */ -public class PKCS5KeyFile - implements FileKeyProvider { +public class PKCS5KeyFile extends BaseFileKeyProvider { public static class Factory implements net.schmizz.sshj.common.Factory.Named { @@ -74,67 +74,8 @@ public static class DecryptException } } - protected PasswordFinder pwdf; - protected Resource resource; - protected KeyPair kp; - protected KeyType type; protected byte[] data; - @Override - public PrivateKey getPrivate() - throws IOException { - return kp != null ? kp.getPrivate() : (kp = readKeyPair()).getPrivate(); - } - - @Override - public PublicKey getPublic() - throws IOException { - return kp != null ? kp.getPublic() : (kp = readKeyPair()).getPublic(); - } - - @Override - public KeyType getType() - throws IOException { - return type != null ? type : (type = KeyType.fromKey(getPublic())); - } - - @Override - public void init(Reader location) { - assert location != null; - resource = new PrivateKeyReaderResource(location); - } - - @Override - public void init(Reader location, PasswordFinder pwdf) { - init(location); - this.pwdf = pwdf; - } - - @Override - public void init(File location) { - assert location != null; - resource = new PrivateKeyFileResource(location.getAbsoluteFile()); - } - - @Override - public void init(File location, PasswordFinder pwdf) { - init(location); - 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 KeyPair readKeyPair() throws IOException { diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java index b72e6ba71..26f500584 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java @@ -15,9 +15,8 @@ */ package net.schmizz.sshj.userauth.keyprovider; -import net.schmizz.sshj.common.IOUtils; -import net.schmizz.sshj.common.KeyType; -import net.schmizz.sshj.userauth.password.*; +import java.io.IOException; +import java.security.KeyPair; import org.bouncycastle.openssl.EncryptionException; import org.bouncycastle.openssl.PEMEncryptedKeyPair; import org.bouncycastle.openssl.PEMKeyPair; @@ -27,16 +26,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; -import java.io.Reader; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; +import net.schmizz.sshj.common.IOUtils; +import net.schmizz.sshj.userauth.password.PasswordUtils; -/** Represents a PKCS8-encoded key file. This is the format used by OpenSSH and OpenSSL. */ -public class PKCS8KeyFile - implements FileKeyProvider { +/** Represents a PKCS8-encoded key file. This is the format used by (old-style) OpenSSH and OpenSSL. */ +public class PKCS8KeyFile extends BaseFileKeyProvider { public static class Factory implements net.schmizz.sshj.common.Factory.Named { @@ -53,68 +47,9 @@ public String getName() { } protected final Logger log = LoggerFactory.getLogger(getClass()); - protected PasswordFinder pwdf; - protected Resource resource; - protected KeyPair kp; - - protected KeyType type; protected char[] passphrase; // for blanking out - @Override - public PrivateKey getPrivate() - throws IOException { - return kp != null ? kp.getPrivate() : (kp = readKeyPair()).getPrivate(); - } - - @Override - public PublicKey getPublic() - throws IOException { - return kp != null ? kp.getPublic() : (kp = readKeyPair()).getPublic(); - } - - @Override - public KeyType getType() - throws IOException { - return type != null ? type : (type = KeyType.fromKey(getPublic())); - } - - @Override - public void init(Reader location) { - assert location != null; - resource = new PrivateKeyReaderResource(location); - } - - @Override - public void init(Reader location, PasswordFinder pwdf) { - init(location); - this.pwdf = pwdf; - } - - @Override - public void init(File location) { - assert location != null; - resource = new PrivateKeyFileResource(location.getAbsoluteFile()); - } - - @Override - public void init(File location, PasswordFinder pwdf) { - init(location); - 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 KeyPair readKeyPair() throws IOException { diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java index 80728e83e..54d9f08b4 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java @@ -15,21 +15,21 @@ */ package net.schmizz.sshj.userauth.keyprovider; -import net.schmizz.sshj.common.Base64; -import net.schmizz.sshj.common.KeyType; -import net.schmizz.sshj.userauth.password.*; -import org.bouncycastle.util.encoders.Hex; - -import javax.crypto.Cipher; -import javax.crypto.Mac; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.math.BigInteger; import java.security.*; import java.security.spec.*; import java.util.HashMap; import java.util.Map; +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import org.bouncycastle.util.encoders.Hex; + +import net.schmizz.sshj.common.Base64; +import net.schmizz.sshj.common.KeyType; +import net.schmizz.sshj.userauth.password.PasswordUtils; /** *

Sample PuTTY file format

@@ -56,7 +56,7 @@ * * @version $Id:$ */ -public class PuTTYKeyFile implements FileKeyProvider { +public class PuTTYKeyFile extends BaseFileKeyProvider { public static class Factory implements net.schmizz.sshj.common.Factory.Named { @@ -75,56 +75,6 @@ public String getName() { private byte[] privateKey; private byte[] publicKey; - private KeyPair kp; - - protected PasswordFinder pwdf; - - protected Resource resource; - - @Override - public void init(Reader location) { - this.resource = new PrivateKeyReaderResource(location); - } - - public void init(Reader location, PasswordFinder pwdf) { - this.init(location); - this.pwdf = pwdf; - } - - @Override - public void init(File location) { - resource = new PrivateKeyFileResource(location.getAbsoluteFile()); - } - - @Override - public void init(File location, PasswordFinder pwdf) { - this.init(location); - this.pwdf = pwdf; - } - - @Override - public void init(String privateKey, String publicKey) { - resource = new PrivateKeyStringResource(privateKey); - } - - @Override - public void init(String privateKey, String publicKey, PasswordFinder pwdf) { - init(privateKey, publicKey); - this.pwdf = pwdf; - } - - @Override - public PrivateKey getPrivate() - throws IOException { - return kp != null ? kp.getPrivate() : (kp = this.readKeyPair()).getPrivate(); - } - - @Override - public PublicKey getPublic() - throws IOException { - return kp != null ? kp.getPublic() : (kp = this.readKeyPair()).getPublic(); - } - /** * Key type. Either "ssh-rsa" for RSA key, or "ssh-dss" for DSA key. */ @@ -150,7 +100,7 @@ public boolean isEncrypted() { protected KeyPair readKeyPair() throws IOException { this.parseKeyPair(); - if(KeyType.RSA.equals(this.getType())) { + if (KeyType.RSA.equals(this.getType())) { final KeyReader publicKeyReader = new KeyReader(publicKey); publicKeyReader.skip(); // skip this // public key exponent @@ -165,8 +115,7 @@ protected KeyPair readKeyPair() throws IOException { final KeyFactory factory; try { factory = KeyFactory.getInstance("RSA"); - } - catch(NoSuchAlgorithmException s) { + } catch (NoSuchAlgorithmException s) { throw new IOException(s.getMessage(), s); } try { @@ -174,12 +123,11 @@ protected KeyPair readKeyPair() throws IOException { factory.generatePublic(new RSAPublicKeySpec(n, e)), factory.generatePrivate(new RSAPrivateKeySpec(n, d)) ); - } - catch(InvalidKeySpecException i) { + } catch (InvalidKeySpecException i) { throw new IOException(i.getMessage(), i); } } - if(KeyType.DSA.equals(this.getType())) { + if (KeyType.DSA.equals(this.getType())) { final KeyReader publicKeyReader = new KeyReader(publicKey); publicKeyReader.skip(); // skip this BigInteger p = publicKeyReader.readInt(); @@ -194,8 +142,7 @@ protected KeyPair readKeyPair() throws IOException { final KeyFactory factory; try { factory = KeyFactory.getInstance("DSA"); - } - catch(NoSuchAlgorithmException s) { + } catch (NoSuchAlgorithmException s) { throw new IOException(s.getMessage(), s); } try { @@ -203,12 +150,10 @@ protected KeyPair readKeyPair() throws IOException { factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)), factory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g)) ); - } - catch(InvalidKeySpecException e) { + } catch (InvalidKeySpecException e) { throw new IOException(e.getMessage(), e); } - } - else { + } else { throw new IOException(String.format("Unknown key type %s", this.getType())); } } @@ -219,18 +164,16 @@ protected void parseKeyPair() throws IOException { try { String headerName = null; String line; - while((line = r.readLine()) != null) { + while ((line = r.readLine()) != null) { int idx = line.indexOf(": "); - if(idx > 0) { + if (idx > 0) { headerName = line.substring(0, idx); headers.put(headerName, line.substring(idx + 2)); - } - else { + } else { String s = payload.get(headerName); - if(s == null) { + if (s == null) { s = line; - } - else { + } else { // Append to previous line s += line; } @@ -238,29 +181,25 @@ protected void parseKeyPair() throws IOException { payload.put(headerName, s); } } - } - finally { + } finally { r.close(); } // Retrieve keys from payload publicKey = Base64.decode(payload.get("Public-Lines")); - if(this.isEncrypted()) { + if (this.isEncrypted()) { final char[] passphrase; - if(pwdf != null) { + if (pwdf != null) { passphrase = pwdf.reqPassword(resource); - } - else { + } else { passphrase = "".toCharArray(); } try { privateKey = this.decrypt(Base64.decode(payload.get("Private-Lines")), new String(passphrase)); this.verify(new String(passphrase)); - } - finally { + } finally { PasswordUtils.blankOut(passphrase); } - } - else { + } else { privateKey = Base64.decode(payload.get("Private-Lines")); } } @@ -292,8 +231,7 @@ private byte[] toKey(final String passphrase) throws IOException { System.arraycopy(key2, 0, r, 20, 12); return r; - } - catch(NoSuchAlgorithmException e) { + } catch (NoSuchAlgorithmException e) { throw new IOException(e.getMessage(), e); } } @@ -306,7 +244,7 @@ private void verify(final String passphrase) throws IOException { // The key to the MAC is itself a SHA-1 hash of: MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.update("putty-private-key-file-mac-key".getBytes()); - if(passphrase != null) { + if (passphrase != null) { digest.update(passphrase.getBytes()); } final byte[] key = digest.digest(); @@ -334,11 +272,10 @@ private void verify(final String passphrase) throws IOException { final String encoded = Hex.toHexString(mac.doFinal(out.toByteArray())); final String reference = headers.get("Private-MAC"); - if(!encoded.equals(reference)) { + if (!encoded.equals(reference)) { throw new IOException("Invalid passphrase"); } - } - catch(GeneralSecurityException e) { + } catch (GeneralSecurityException e) { throw new IOException(e.getMessage(), e); } } @@ -355,8 +292,7 @@ private byte[] decrypt(final byte[] key, final String passphrase) throws IOExcep cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(expanded, 0, 32, "AES"), new IvParameterSpec(new byte[16])); // initial vector=0 return cipher.doFinal(key); - } - catch(GeneralSecurityException e) { + } catch (GeneralSecurityException e) { throw new IOException(e.getMessage(), e); } } @@ -377,14 +313,14 @@ public KeyReader(byte[] key) { */ public void skip() throws IOException { final int read = di.readInt(); - if(read != di.skipBytes(read)) { + if (read != di.skipBytes(read)) { throw new IOException(String.format("Failed to skip %d bytes", read)); } } private byte[] read() throws IOException { int len = di.readInt(); - if(len <= 0 || len > 513) { + if (len <= 0 || len > 513) { throw new IOException(String.format("Invalid length %d", len)); } byte[] r = new byte[len]; diff --git a/src/test/java/com/hierynomus/sshj/IntegrationTest.java b/src/test/java/com/hierynomus/sshj/IntegrationTest.java index 52af7e7f9..b6f977680 100644 --- a/src/test/java/com/hierynomus/sshj/IntegrationTest.java +++ b/src/test/java/com/hierynomus/sshj/IntegrationTest.java @@ -15,14 +15,14 @@ */ package com.hierynomus.sshj; -import net.schmizz.sshj.DefaultConfig; -import net.schmizz.sshj.SSHClient; -import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts; +import java.io.File; +import java.io.IOException; import org.junit.Ignore; import org.junit.Test; -import java.io.File; -import java.io.IOException; +import net.schmizz.sshj.DefaultConfig; +import net.schmizz.sshj.SSHClient; +import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts; import static org.hamcrest.MatcherAssert.assertThat; @@ -32,8 +32,8 @@ public class IntegrationTest { public void shouldConnect() throws IOException { SSHClient sshClient = new SSHClient(new DefaultConfig()); sshClient.addHostKeyVerifier(new OpenSSHKnownHosts(new File("/Users/ajvanerp/.ssh/known_hosts"))); - sshClient.connect("172.16.37.129"); - sshClient.authPassword("jeroen", "jeroen"); + sshClient.connect("172.16.37.147"); + sshClient.authPublickey("jeroen"); assertThat("Is connected", sshClient.isAuthenticated()); } } diff --git a/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java b/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java index 478a3399c..d39443b75 100644 --- a/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java +++ b/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java @@ -18,6 +18,7 @@ import net.schmizz.sshj.common.KeyType; import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile; +import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile; import net.schmizz.sshj.userauth.password.PasswordFinder; import net.schmizz.sshj.userauth.password.PasswordUtils; import net.schmizz.sshj.userauth.password.Resource; @@ -30,11 +31,13 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; +import java.security.PrivateKey; import java.security.PublicKey; import java.util.Arrays; import java.util.Scanner; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -142,7 +145,7 @@ public void shouldHaveCorrectFingerprintForECDSA() throws IOException, GeneralSe @Test public void shouldHaveCorrectFingerprintForED25519() throws IOException { - OpenSSHKeyFile keyFile = new OpenSSHKeyFile(); + OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile(); keyFile.init(new File("src/test/resources/keytypes/test_ed25519")); String expected = "256 MD5:d3:5e:40:72:db:08:f1:6d:0c:d7:6d:35:0d:ba:7c:32 root@sshj (ED25519)\n"; PublicKey aPublic = keyFile.getPublic(); @@ -150,6 +153,14 @@ public void shouldHaveCorrectFingerprintForED25519() throws IOException { assertThat(expected, containsString(sshjFingerprintSshjKey)); } + @Test + public void shouldLoadED25519PrivateKey() throws IOException { + OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile(); + keyFile.init(new File("src/test/resources/keytypes/test_ed25519")); + PrivateKey aPrivate = keyFile.getPrivate(); + assertThat(aPrivate.getAlgorithm(), equalTo("EdDSA")); + } + @Before public void setup() throws UnsupportedEncodingException, GeneralSecurityException { @@ -171,4 +182,4 @@ private String readFile(String pathname) scanner.close(); } } -} \ No newline at end of file +}