From fdb891b842f243f88eaa6a41b0953b675488fa3f Mon Sep 17 00:00:00 2001 From: David Kocher Date: Mon, 5 May 2014 13:11:27 +0200 Subject: [PATCH 01/28] Add hmac-sha2-256 and hmac-sha2-512. --- .../java/net/schmizz/sshj/DefaultConfig.java | 4 +- .../sshj/transport/mac/HMACSHA2256.java | 60 +++++++++++++++++++ .../sshj/transport/mac/HMACSHA2512.java | 60 +++++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/schmizz/sshj/transport/mac/HMACSHA2256.java create mode 100644 src/main/java/net/schmizz/sshj/transport/mac/HMACSHA2512.java diff --git a/src/main/java/net/schmizz/sshj/DefaultConfig.java b/src/main/java/net/schmizz/sshj/DefaultConfig.java index d788bd8f..ca446afb 100644 --- a/src/main/java/net/schmizz/sshj/DefaultConfig.java +++ b/src/main/java/net/schmizz/sshj/DefaultConfig.java @@ -56,6 +56,8 @@ import net.schmizz.sshj.transport.mac.HMACMD5; import net.schmizz.sshj.transport.mac.HMACMD596; import net.schmizz.sshj.transport.mac.HMACSHA1; import net.schmizz.sshj.transport.mac.HMACSHA196; +import net.schmizz.sshj.transport.mac.HMACSHA2256; +import net.schmizz.sshj.transport.mac.HMACSHA2512; import net.schmizz.sshj.transport.random.BouncyCastleRandom; import net.schmizz.sshj.transport.random.JCERandom; import net.schmizz.sshj.transport.random.SingletonRandomFactory; @@ -167,7 +169,7 @@ public class DefaultConfig protected void initMACFactories() { setMACFactories(new HMACSHA1.Factory(), new HMACSHA196.Factory(), new HMACMD5.Factory(), - new HMACMD596.Factory()); + new HMACMD596.Factory(), new HMACSHA2256.Factory(), new HMACSHA2512.Factory()); } protected void initCompressionFactories() { diff --git a/src/main/java/net/schmizz/sshj/transport/mac/HMACSHA2256.java b/src/main/java/net/schmizz/sshj/transport/mac/HMACSHA2256.java new file mode 100644 index 00000000..6482c6e4 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/transport/mac/HMACSHA2256.java @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2012 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. + * + * This file may incorporate work covered by the following copyright and + * permission notice: + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.transport.mac; + +/** HMAC-SHA1 MAC */ +public class HMACSHA2256 + extends BaseMAC { + + /** Named factory for the HMAC-SHA1 MAC */ + public static class Factory + implements net.schmizz.sshj.common.Factory.Named { + + @Override + public MAC create() { + return new HMACSHA2256(); + } + + @Override + public String getName() { + return "hmac-sha2-256"; + } + } + + public HMACSHA2256() { + super("HmacSHA256", 20, 20); + } +} diff --git a/src/main/java/net/schmizz/sshj/transport/mac/HMACSHA2512.java b/src/main/java/net/schmizz/sshj/transport/mac/HMACSHA2512.java new file mode 100644 index 00000000..e0bcd75f --- /dev/null +++ b/src/main/java/net/schmizz/sshj/transport/mac/HMACSHA2512.java @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2012 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. + * + * This file may incorporate work covered by the following copyright and + * permission notice: + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.transport.mac; + +/** HMAC-SHA1 MAC */ +public class HMACSHA2512 + extends BaseMAC { + + /** Named factory for the HMAC-SHA1 MAC */ + public static class Factory + implements net.schmizz.sshj.common.Factory.Named { + + @Override + public MAC create() { + return new HMACSHA2512(); + } + + @Override + public String getName() { + return "hmac-sha2-512"; + } + } + + public HMACSHA2512() { + super("HmacSHA512", 20, 20); + } +} From df6019accc41b92d014542a0fb8f3fa878319aa5 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Mon, 5 May 2014 13:11:54 +0200 Subject: [PATCH 02/28] Fix type of fileOffset to long. --- src/main/java/net/schmizz/sshj/sftp/RemoteFile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java b/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java index 2efe656a..588fd081 100644 --- a/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java +++ b/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java @@ -175,7 +175,7 @@ public class RemoteFile this(0); } - public RemoteFileInputStream(int fileOffset) { + public RemoteFileInputStream(long fileOffset) { this.fileOffset = fileOffset; } From 1f992c3fae417528eac6ac090d72f22074432766 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Mon, 5 May 2014 13:12:10 +0200 Subject: [PATCH 03/28] Ignore user auth banner in transport. --- src/main/java/net/schmizz/sshj/transport/TransportImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/net/schmizz/sshj/transport/TransportImpl.java b/src/main/java/net/schmizz/sshj/transport/TransportImpl.java index f700dded..22821397 100644 --- a/src/main/java/net/schmizz/sshj/transport/TransportImpl.java +++ b/src/main/java/net/schmizz/sshj/transport/TransportImpl.java @@ -499,6 +499,10 @@ public final class TransportImpl gotServiceAccept(); break; } + case USERAUTH_BANNER: { + log.debug("Received USERAUTH_BANNER"); + break; + } default: sendUnimplemented(); } From 466ff99e1cb86cbc01a4d7eef61dfb70790ff0d2 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Mon, 5 May 2014 13:12:48 +0200 Subject: [PATCH 04/28] Update BC dependency to 1.50. --- pom.xml | 6 +-- .../userauth/keyprovider/PKCS8KeyFile.java | 38 ++++++++++++++----- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index c6bc3de0..9230248e 100644 --- a/pom.xml +++ b/pom.xml @@ -49,18 +49,18 @@ org.bouncycastle bcpkix-jdk15on - 1.49 + 1.50 org.bouncycastle bcprov-jdk15on - 1.49 + 1.50 provided com.jcraft jzlib - 1.1.2 + 1.1.3 provided 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 3ed6c96e..34e0bb82 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java @@ -15,6 +15,12 @@ */ package net.schmizz.sshj.userauth.keyprovider; +import java.io.File; +import java.io.IOException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; + import net.schmizz.sshj.common.IOUtils; import net.schmizz.sshj.common.KeyType; import net.schmizz.sshj.userauth.password.PasswordFinder; @@ -22,17 +28,17 @@ 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.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; - /** Represents a PKCS8-encoded key file. This is the format used by OpenSSH and OpenSSL. */ public class PKCS8KeyFile implements FileKeyProvider { @@ -119,14 +125,26 @@ public class PKCS8KeyFile throws IOException { KeyPair kp = null; org.bouncycastle.openssl.PasswordFinder pFinder = makeBouncyPasswordFinder(); - PEMReader r = null; + PEMParser r = null; Object o = null; try { for (; ; ) { // while the PasswordFinder tells us we should retry try { - r = new PEMReader(resource.getReader(), pFinder); + r = new PEMParser(resource.getReader()); o = r.readObject(); + + JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); + pemConverter.setProvider("BC"); + if (pFinder != null && o instanceof PEMEncryptedKeyPair) { + JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder(); + PEMDecryptorProvider pemDecryptor = decryptorBuilder.build(pFinder.getPassword()); + o = pemConverter.getKeyPair(((PEMEncryptedKeyPair) o).decryptKeyPair(pemDecryptor)); + } + if (o instanceof PEMKeyPair) { + o = pemConverter.getKeyPair((PEMKeyPair) o); + } + } catch (EncryptionException e) { if (pwdf.shouldRetry(resource)) continue; @@ -154,4 +172,4 @@ public class PKCS8KeyFile public String toString() { return "PKCS8KeyFile{resource=" + resource + "}"; } -} +} \ No newline at end of file From 0f7355a277ed53a33d6e3946be98d0ae96624a5c Mon Sep 17 00:00:00 2001 From: Alexey Gromov Date: Tue, 6 May 2014 12:09:50 +0400 Subject: [PATCH 05/28] add ecdsa --- .../java/net/schmizz/sshj/DefaultConfig.java | 29 ++-- .../java/net/schmizz/sshj/common/KeyType.java | 106 ++++++++++++- .../sshj/signature/SignatureECDSA.java | 143 ++++++++++++++++++ 3 files changed, 256 insertions(+), 22 deletions(-) create mode 100644 src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java diff --git a/src/main/java/net/schmizz/sshj/DefaultConfig.java b/src/main/java/net/schmizz/sshj/DefaultConfig.java index d788bd8f..b40d60b5 100644 --- a/src/main/java/net/schmizz/sshj/DefaultConfig.java +++ b/src/main/java/net/schmizz/sshj/DefaultConfig.java @@ -39,6 +39,7 @@ package net.schmizz.sshj; import net.schmizz.sshj.common.Factory; import net.schmizz.sshj.common.SecurityUtils; import net.schmizz.sshj.signature.SignatureDSA; +import net.schmizz.sshj.signature.SignatureECDSA; import net.schmizz.sshj.signature.SignatureRSA; import net.schmizz.sshj.transport.cipher.AES128CBC; import net.schmizz.sshj.transport.cipher.AES128CTR; @@ -70,22 +71,22 @@ import java.util.LinkedList; import java.util.List; /** - * A {@link Config} that is initialized as follows. Items marked with an asterisk are added to the config only if + * A {@link net.schmizz.sshj.Config} that is initialized as follows. Items marked with an asterisk are added to the config only if * BouncyCastle is in the classpath. *

*

    - *
  • {@link ConfigImpl#setKeyExchangeFactories Key exchange}: {@link DHG14}*, {@link DHG1}
  • - *
  • {@link ConfigImpl#setCipherFactories Ciphers} [1]: {@link AES128CTR}, {@link AES192CTR}, {@link AES256CTR}, + *
  • {@link net.schmizz.sshj.ConfigImpl#setKeyExchangeFactories Key exchange}: {@link net.schmizz.sshj.transport.kex.DHG14}*, {@link net.schmizz.sshj.transport.kex.DHG1}
  • + *
  • {@link net.schmizz.sshj.ConfigImpl#setCipherFactories Ciphers} [1]: {@link net.schmizz.sshj.transport.cipher.AES128CTR}, {@link net.schmizz.sshj.transport.cipher.AES192CTR}, {@link net.schmizz.sshj.transport.cipher.AES256CTR}, * {@link - * AES128CBC}, {@link AES192CBC}, {@link AES256CBC}, {@link AES192CBC}, {@link TripleDESCBC}, {@link BlowfishCBC}
  • - *
  • {@link ConfigImpl#setMACFactories MAC}: {@link HMACSHA1}, {@link HMACSHA196}, {@link HMACMD5}, {@link - * HMACMD596}
  • - *
  • {@link ConfigImpl#setCompressionFactories Compression}: {@link NoneCompression}
  • - *
  • {@link ConfigImpl#setSignatureFactories Signature}: {@link SignatureRSA}, {@link SignatureDSA}
  • - *
  • {@link ConfigImpl#setRandomFactory PRNG}: {@link BouncyCastleRandom}* or {@link JCERandom}
  • - *
  • {@link ConfigImpl#setFileKeyProviderFactories Key file support}: {@link PKCS8KeyFile}*, {@link - * OpenSSHKeyFile}*
  • - *
  • {@link ConfigImpl#setVersion Client version}: {@code "NET_3_0"}
  • + * net.schmizz.sshj.transport.cipher.AES128CBC}, {@link net.schmizz.sshj.transport.cipher.AES192CBC}, {@link net.schmizz.sshj.transport.cipher.AES256CBC}, {@link net.schmizz.sshj.transport.cipher.AES192CBC}, {@link net.schmizz.sshj.transport.cipher.TripleDESCBC}, {@link net.schmizz.sshj.transport.cipher.BlowfishCBC} + *
  • {@link net.schmizz.sshj.ConfigImpl#setMACFactories MAC}: {@link net.schmizz.sshj.transport.mac.HMACSHA1}, {@link net.schmizz.sshj.transport.mac.HMACSHA196}, {@link net.schmizz.sshj.transport.mac.HMACMD5}, {@link + * net.schmizz.sshj.transport.mac.HMACMD596}
  • + *
  • {@link net.schmizz.sshj.ConfigImpl#setCompressionFactories Compression}: {@link net.schmizz.sshj.transport.compression.NoneCompression}
  • + *
  • {@link net.schmizz.sshj.ConfigImpl#setSignatureFactories Signature}: {@link net.schmizz.sshj.signature.SignatureRSA}, {@link net.schmizz.sshj.signature.SignatureDSA}
  • + *
  • {@link net.schmizz.sshj.ConfigImpl#setRandomFactory PRNG}: {@link net.schmizz.sshj.transport.random.BouncyCastleRandom}* or {@link net.schmizz.sshj.transport.random.JCERandom}
  • + *
  • {@link net.schmizz.sshj.ConfigImpl#setFileKeyProviderFactories Key file support}: {@link net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile}*, {@link + * net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile}*
  • + *
  • {@link net.schmizz.sshj.ConfigImpl#setVersion Client version}: {@code "NET_3_0"}
  • *
*

* [1] It is worth noting that Sun's JRE does not have the unlimited cryptography extension enabled by default. This @@ -96,7 +97,7 @@ public class DefaultConfig private final Logger log = LoggerFactory.getLogger(getClass()); - private static final String VERSION = "SSHJ_0_9_1"; + private static final String VERSION = "SSHJ_0_9_0"; public DefaultConfig() { setVersion(VERSION); @@ -162,7 +163,7 @@ public class DefaultConfig } protected void initSignatureFactories() { - setSignatureFactories(new SignatureRSA.Factory(), new SignatureDSA.Factory()); + setSignatureFactories(new SignatureECDSA.Factory(), new SignatureRSA.Factory(), new SignatureDSA.Factory()); } protected void initMACFactories() { diff --git a/src/main/java/net/schmizz/sshj/common/KeyType.java b/src/main/java/net/schmizz/sshj/common/KeyType.java index 80509f33..9df2cec0 100644 --- a/src/main/java/net/schmizz/sshj/common/KeyType.java +++ b/src/main/java/net/schmizz/sshj/common/KeyType.java @@ -15,21 +15,25 @@ */ package net.schmizz.sshj.common; +import bidstreet.core.thunder.common.StringHelper; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.math.ec.ECPoint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.Key; -import java.security.KeyFactory; -import java.security.PublicKey; -import java.security.interfaces.DSAPrivateKey; -import java.security.interfaces.DSAPublicKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; +import java.security.*; +import java.security.interfaces.*; import java.security.spec.DSAPublicKeySpec; import java.security.spec.RSAPublicKeySpec; /** Type of key e.g. rsa, dsa */ public enum KeyType { + /** SSH identifier for RSA keys */ RSA("ssh-rsa") { @Override @@ -96,6 +100,90 @@ public enum KeyType { }, + /** SSH identifier for ECDSA keys */ + ECDSA("ecdsa-sha2-nistp256") { + private final Logger LOG = LoggerFactory.getLogger(getClass()); + @Override + public PublicKey readPubKeyFromBuffer(String type, Buffer buf) + throws GeneralSecurityException { + try { + // final String algo = buf.readString(); it has been already read + final String curveName = buf.readString(); + final int keyLen = buf.readUInt32AsInt(); + final byte x04 = buf.readByte(); // it must be 0x04, but don't think we need that check + final byte[] x = new byte[(keyLen - 1) / 2]; + final byte[] y = new byte[(keyLen - 1) / 2]; + buf.readRawBytes(x); + buf.readRawBytes(y); + LOG.debug(String.format("Key algo: %s, Key curve: %s, Key Len: %s, 0x04: %s\nx: %s\ny: %s", + type, + curveName, + keyLen, + x04, + StringHelper.printBytes(x), + StringHelper.printBytes(y)) + ); + + if (!NISTP_CURVE.equals(curveName)) { + throw new GeneralSecurityException("Unknown curve name"); + } + + BigInteger bigX = new BigInteger(1, x); + BigInteger bigY = new BigInteger(1, y); + + X9ECParameters ecParams = NISTNamedCurves.getByName("p-256"); + ECPoint pPublicPoint = ecParams.getCurve().createPoint(bigX, bigY, false); + ECParameterSpec spec = new ECParameterSpec(ecParams.getCurve(), + ecParams.getG(), ecParams.getN()); + ECPublicKeySpec publicSpec = new ECPublicKeySpec(pPublicPoint, spec); + + KeyFactory keyFactory = KeyFactory.getInstance("ECDSA"); + + PublicKey pubKey = keyFactory.generatePublic(publicSpec); + return pubKey; + } catch (Exception ex) { + throw new GeneralSecurityException(ex); + } + } + + + @Override + public void putPubKeyIntoBuffer(PublicKey pk, Buffer buf) { + final ECPublicKey ecdsa = (ECPublicKey) pk; + final java.security.spec.ECPoint point = ecdsa.getW(); + final byte[] x = trimStartingZeros(point.getAffineX().toByteArray()); + final byte[] y = trimStartingZeros(point.getAffineY().toByteArray()); + + buf.putString(sType) + .putString(NISTP_CURVE) + .putUInt32(1 + x.length + y.length) + .putRawBytes(new byte[] { (byte) 0x04 }) + .putRawBytes(x) + .putRawBytes(y) + .compact() + ; + } + + @Override + protected boolean isMyType(Key key) { + return ("ECDSA".equals(key.getAlgorithm())); + } + + private byte[] trimStartingZeros(byte[] in) { + + int i = 0; + for (; i < in.length; i++) { + if (in[i] != 0) { + break; + } + } + final byte[] out = new byte[in.length - i]; + System.arraycopy(in, i, out, 0, out.length); + return out; + } + + }, + /** Unrecognized */ UNKNOWN("unknown") { @Override @@ -117,6 +205,8 @@ public enum KeyType { }; + private static final String NISTP_CURVE = "nistp256"; + protected final String sType; private KeyType(String type) { diff --git a/src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java b/src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java new file mode 100644 index 00000000..65596d9e --- /dev/null +++ b/src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java @@ -0,0 +1,143 @@ +/* + * Copyright 2010-2012 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. + * + * This file may incorporate work covered by the following copyright and + * permission notice: + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.signature; + +import net.schmizz.sshj.common.Buffer; +import net.schmizz.sshj.common.KeyType; +import net.schmizz.sshj.common.SSHRuntimeException; + +import java.security.SignatureException; + +/** ECDSA {@link Signature} */ +public class SignatureECDSA + extends AbstractSignature { + + /** A named factory for ECDSA signature */ + public static class Factory + implements net.schmizz.sshj.common.Factory.Named { + + @Override + public Signature create() { + return new SignatureECDSA(); + } + + @Override + public String getName() { + return KeyType.ECDSA.toString(); + } + + } + + public SignatureECDSA() { + super("SHA256withECDSA"); + } + + @Override + public byte[] sign() { + throw new UnsupportedOperationException("No implementation for sign!"); + } + + @Override + public boolean verify(byte[] sig) { + + byte[] r = null; + byte[] s = null; + + + try { + Buffer sigbuf = new Buffer.PlainBuffer(sig); + final String algo = new String(sigbuf.readBytes()); + if (!"ecdsa-sha2-nistp256".equals(algo)) { + throw new SSHRuntimeException(String.format("Signature :: ecdsa-sha2-nistp256 expected, got %s", algo)); + } + final int rsLen = sigbuf.readUInt32AsInt(); + if (!(sigbuf.available() == rsLen)) { + throw new SSHRuntimeException("Invalid key length"); + } + r = sigbuf.readBytes(); + s = sigbuf.readBytes(); + } catch (Exception e) { + throw new SSHRuntimeException(e); + } + + int rLen = r.length; + int sLen = s.length; + + /* We can't have the high bit set, so add an extra zero at the beginning if so. */ + if ((r[0] & 0x80) != 0) { + rLen++; + } + if ((s[0] & 0x80) != 0) { + sLen++; + } + + /* Calculate total output length */ + int length = 6 + rLen + sLen; + byte[] asn1 = new byte[length]; + + /* ASN.1 SEQUENCE tag */ + asn1[0] = (byte) 0x30; + + /* Size of SEQUENCE */ + asn1[1] = (byte) (4 + rLen + sLen); + + /* ASN.1 INTEGER tag */ + asn1[2] = (byte) 0x02; + + /* "r" INTEGER length */ + asn1[3] = (byte) rLen; + + /* Copy in the "r" INTEGER */ + System.arraycopy(r, 0, asn1, 4, rLen); + + /* ASN.1 INTEGER tag */ + asn1[rLen + 4] = (byte) 0x02; + + /* "s" INTEGER length */ + asn1[rLen + 5] = (byte) sLen; + + /* Copy in the "s" INTEGER */ + System.arraycopy(s, 0, asn1, (6 + rLen), sLen); + + + try { + return signature.verify(asn1); + } catch (SignatureException e) { + throw new SSHRuntimeException(e); + } + } + +} From b5796f5e7473910bef251b1527cdc3c5cfcf865f Mon Sep 17 00:00:00 2001 From: Alexey Gromov Date: Tue, 6 May 2014 12:13:11 +0400 Subject: [PATCH 06/28] fix version --- src/main/java/net/schmizz/sshj/DefaultConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/schmizz/sshj/DefaultConfig.java b/src/main/java/net/schmizz/sshj/DefaultConfig.java index b40d60b5..39dec71e 100644 --- a/src/main/java/net/schmizz/sshj/DefaultConfig.java +++ b/src/main/java/net/schmizz/sshj/DefaultConfig.java @@ -97,7 +97,7 @@ public class DefaultConfig private final Logger log = LoggerFactory.getLogger(getClass()); - private static final String VERSION = "SSHJ_0_9_0"; + private static final String VERSION = "SSHJ_0_9_2"; public DefaultConfig() { setVersion(VERSION); From 06e421e752a03c7871b44642800227387b168564 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Tue, 6 May 2014 15:41:35 +0200 Subject: [PATCH 07/28] Extract formats. Add PuTTY to enum. --- .../userauth/keyprovider/FileKeyProvider.java | 6 -- .../sshj/userauth/keyprovider/KeyFormat.java | 11 ++++ .../userauth/keyprovider/KeyProviderUtil.java | 57 ++++++++++--------- 3 files changed, 42 insertions(+), 32 deletions(-) create mode 100644 src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyFormat.java diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/FileKeyProvider.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/FileKeyProvider.java index 3c120f49..8f4183c2 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/FileKeyProvider.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/FileKeyProvider.java @@ -23,12 +23,6 @@ import java.io.File; public interface FileKeyProvider extends KeyProvider { - enum Format { - PKCS8, - OpenSSH, - Unknown - } - void init(File location); void init(File location, PasswordFinder pwdf); diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyFormat.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyFormat.java new file mode 100644 index 00000000..2860b616 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyFormat.java @@ -0,0 +1,11 @@ +package net.schmizz.sshj.userauth.keyprovider; + +/** +* @version $Id:$ +*/ +public enum KeyFormat { + PKCS8, + OpenSSH, + 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 6ec33527..5b0f26a6 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/KeyProviderUtil.java @@ -15,8 +15,6 @@ */ package net.schmizz.sshj.userauth.keyprovider; -import net.schmizz.sshj.common.IOUtils; - import java.io.BufferedReader; import java.io.File; import java.io.FileReader; @@ -24,6 +22,8 @@ import java.io.IOException; import java.io.Reader; import java.io.StringReader; +import net.schmizz.sshj.common.IOUtils; + public class KeyProviderUtil { /** @@ -32,15 +32,13 @@ public class KeyProviderUtil { * Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package. * * @param location - * * @return name of the key file format - * * @throws java.io.IOException */ - public static FileKeyProvider.Format detectKeyFileFormat(File location) + public static KeyFormat detectKeyFileFormat(File location) throws IOException { return detectKeyFileFormat(new FileReader(location), - new File(location + ".pub").exists()); + new File(location + ".pub").exists()); } /** @@ -50,12 +48,10 @@ public class KeyProviderUtil { * * @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, + public static KeyFormat detectKeyFileFormat(String privateKey, boolean separatePubKey) throws IOException { return detectKeyFileFormat(new StringReader(privateKey), separatePubKey); @@ -68,29 +64,38 @@ public class KeyProviderUtil { * * @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) + public static KeyFormat detectKeyFileFormat(Reader privateKey, + boolean separatePubKey) throws IOException { BufferedReader br = new BufferedReader(privateKey); - String firstLine = br.readLine(); - IOUtils.closeQuietly(br); - if (firstLine == null) + final String firstLine; + try { + firstLine = br.readLine(); + } + finally { + IOUtils.closeQuietly(br); + } + if(firstLine == null) { throw new IOException("Empty file"); - if (firstLine.startsWith("-----BEGIN") && firstLine.endsWith("PRIVATE KEY-----")) - if (separatePubKey) - // Can delay asking for password since have unencrypted pubkey - return FileKeyProvider.Format.OpenSSH; + } + if(firstLine.startsWith("-----BEGIN") && firstLine.endsWith("PRIVATE KEY-----")) { + if(separatePubKey) + // Can delay asking for password since have unencrypted pubkey + { + return KeyFormat.OpenSSH; + } else - // More general - return FileKeyProvider.Format.PKCS8; - /* - * TODO: Tectia, PuTTY (.ppk) ... - */ - return FileKeyProvider.Format.Unknown; + // More general + { + return KeyFormat.PKCS8; + } + } + if(firstLine.startsWith("PuTTY-User-Key-File-")) { + return KeyFormat.PuTTY; + } + return KeyFormat.Unknown; } } From 60d54fa5de904c58403df218f9debc9ded917d9c Mon Sep 17 00:00:00 2001 From: David Kocher Date: Tue, 6 May 2014 21:21:23 +0200 Subject: [PATCH 08/28] Addendum --- src/main/java/net/schmizz/sshj/SSHClient.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/SSHClient.java b/src/main/java/net/schmizz/sshj/SSHClient.java index 980dd57e..b6e5626c 100644 --- a/src/main/java/net/schmizz/sshj/SSHClient.java +++ b/src/main/java/net/schmizz/sshj/SSHClient.java @@ -46,6 +46,7 @@ import net.schmizz.sshj.userauth.UserAuth; import net.schmizz.sshj.userauth.UserAuthException; import net.schmizz.sshj.userauth.UserAuthImpl; import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; +import net.schmizz.sshj.userauth.keyprovider.KeyFormat; import net.schmizz.sshj.userauth.keyprovider.KeyPairWrapper; import net.schmizz.sshj.userauth.keyprovider.KeyProvider; import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil; @@ -485,7 +486,7 @@ public class SSHClient public KeyProvider loadKeys(String location, PasswordFinder passwordFinder) throws IOException { final File loc = new File(location); - final FileKeyProvider.Format format = KeyProviderUtil.detectKeyFileFormat(loc); + final KeyFormat format = KeyProviderUtil.detectKeyFileFormat(loc); final FileKeyProvider fkp = Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format.toString()); if (fkp == null) @@ -529,7 +530,7 @@ public class SSHClient */ public KeyProvider loadKeys(String privateKey, String publicKey, PasswordFinder passwordFinder) throws IOException { - final FileKeyProvider.Format format = KeyProviderUtil.detectKeyFileFormat(privateKey, publicKey != null); + final KeyFormat format = KeyProviderUtil.detectKeyFileFormat(privateKey, publicKey != null); final FileKeyProvider fkp = Factory.Named.Util.create(trans.getConfig().getFileKeyProviderFactories(), format.toString()); if (fkp == null) From 896b0ea2881cdf24e53552caa0864479a8d36331 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Tue, 6 May 2014 21:22:12 +0200 Subject: [PATCH 09/28] Add provider with reader resource. --- .../userauth/keyprovider/OpenSSHKeyFile.java | 4 +-- .../userauth/keyprovider/PKCS8KeyFile.java | 20 +++++++++-- .../keyprovider/ReaderKeyProvider.java | 15 +++++++++ .../password/PrivateKeyReaderResource.java | 33 +++++++++++++++++++ 4 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 src/main/java/net/schmizz/sshj/userauth/keyprovider/ReaderKeyProvider.java create mode 100644 src/main/java/net/schmizz/sshj/userauth/password/PrivateKeyReaderResource.java diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/OpenSSHKeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/OpenSSHKeyFile.java index 5e0b615a..19d2c733 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/OpenSSHKeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/OpenSSHKeyFile.java @@ -39,10 +39,10 @@ public class OpenSSHKeyFile extends PKCS8KeyFile { public static class Factory - implements net.schmizz.sshj.common.Factory.Named { + implements net.schmizz.sshj.common.Factory.Named { @Override - public FileKeyProvider create() { + public KeyProvider create() { return new OpenSSHKeyFile(); } 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 34e0bb82..ce7d8833 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java @@ -17,6 +17,7 @@ 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; @@ -26,6 +27,7 @@ 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.PrivateKeyReaderResource; import net.schmizz.sshj.userauth.password.PrivateKeyStringResource; import net.schmizz.sshj.userauth.password.Resource; @@ -41,13 +43,13 @@ import org.slf4j.LoggerFactory; /** Represents a PKCS8-encoded key file. This is the format used by OpenSSH and OpenSSL. */ public class PKCS8KeyFile - implements FileKeyProvider { + implements FileKeyProvider, ReaderKeyProvider { public static class Factory - implements net.schmizz.sshj.common.Factory.Named { + implements net.schmizz.sshj.common.Factory.Named { @Override - public FileKeyProvider create() { + public KeyProvider create() { return new PKCS8KeyFile(); } @@ -84,6 +86,18 @@ public class PKCS8KeyFile 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; diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/ReaderKeyProvider.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/ReaderKeyProvider.java new file mode 100644 index 00000000..abf4f8f5 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/ReaderKeyProvider.java @@ -0,0 +1,15 @@ +package net.schmizz.sshj.userauth.keyprovider; + +import java.io.Reader; + +import net.schmizz.sshj.userauth.password.PasswordFinder; + +/** + * @version $Id:$ + */ +public interface ReaderKeyProvider extends KeyProvider { + + void init(Reader location); + + void init(Reader location, PasswordFinder pwdf); +} diff --git a/src/main/java/net/schmizz/sshj/userauth/password/PrivateKeyReaderResource.java b/src/main/java/net/schmizz/sshj/userauth/password/PrivateKeyReaderResource.java new file mode 100644 index 00000000..90eb3336 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/userauth/password/PrivateKeyReaderResource.java @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2012 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; + +public class PrivateKeyReaderResource + extends Resource { + + public PrivateKeyReaderResource(Reader privateKeyFile) { + super(privateKeyFile); + } + + @Override + public Reader getReader() + throws IOException { + return getDetail(); + } +} From 97535bbcae11593d786ca9214f9c864fadf29255 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Wed, 7 May 2014 10:37:14 +0200 Subject: [PATCH 10/28] Merge interfaces. --- .../userauth/keyprovider/FileKeyProvider.java | 5 +++++ .../sshj/userauth/keyprovider/OpenSSHKeyFile.java | 4 ++-- .../sshj/userauth/keyprovider/PKCS8KeyFile.java | 6 +++--- .../userauth/keyprovider/ReaderKeyProvider.java | 15 --------------- 4 files changed, 10 insertions(+), 20 deletions(-) delete mode 100644 src/main/java/net/schmizz/sshj/userauth/keyprovider/ReaderKeyProvider.java diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/FileKeyProvider.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/FileKeyProvider.java index 8f4183c2..ad8a7739 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/FileKeyProvider.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/FileKeyProvider.java @@ -18,6 +18,7 @@ package net.schmizz.sshj.userauth.keyprovider; import net.schmizz.sshj.userauth.password.PasswordFinder; import java.io.File; +import java.io.Reader; /** A file key provider is initialized with a location of */ public interface FileKeyProvider @@ -27,6 +28,10 @@ public interface FileKeyProvider void init(File location, PasswordFinder pwdf); + void init(Reader location); + + void init(Reader location, PasswordFinder pwdf); + void init(String privateKey, String publicKey); void init(String privateKey, String publicKey, PasswordFinder pwdf); diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/OpenSSHKeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/OpenSSHKeyFile.java index 19d2c733..5e0b615a 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/OpenSSHKeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/OpenSSHKeyFile.java @@ -39,10 +39,10 @@ public class OpenSSHKeyFile extends PKCS8KeyFile { public static class Factory - implements net.schmizz.sshj.common.Factory.Named { + implements net.schmizz.sshj.common.Factory.Named { @Override - public KeyProvider create() { + public FileKeyProvider create() { return new OpenSSHKeyFile(); } 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 ce7d8833..e2f64210 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java @@ -43,13 +43,13 @@ import org.slf4j.LoggerFactory; /** Represents a PKCS8-encoded key file. This is the format used by OpenSSH and OpenSSL. */ public class PKCS8KeyFile - implements FileKeyProvider, ReaderKeyProvider { + implements FileKeyProvider { public static class Factory - implements net.schmizz.sshj.common.Factory.Named { + implements net.schmizz.sshj.common.Factory.Named { @Override - public KeyProvider create() { + public FileKeyProvider create() { return new PKCS8KeyFile(); } diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/ReaderKeyProvider.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/ReaderKeyProvider.java deleted file mode 100644 index abf4f8f5..00000000 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/ReaderKeyProvider.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.schmizz.sshj.userauth.keyprovider; - -import java.io.Reader; - -import net.schmizz.sshj.userauth.password.PasswordFinder; - -/** - * @version $Id:$ - */ -public interface ReaderKeyProvider extends KeyProvider { - - void init(Reader location); - - void init(Reader location, PasswordFinder pwdf); -} From 3356f533d0ccc77741212f1dc31bd2db7ce8e009 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Wed, 7 May 2014 10:42:27 +0200 Subject: [PATCH 11/28] Add dependencyManagement. --- pom.xml | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index 9230248e..15e9fa60 100644 --- a/pom.xml +++ b/pom.xml @@ -40,39 +40,65 @@ 7 + + + + org.bouncycastle + bcprov-jdk15on + 1.50 + + + org.bouncycastle + bcpkix-jdk15on + 1.50 + + + com.jcraft + jzlib + 1.1.3 + + + ch.qos.logback + logback-core + 1.1.2 + + + ch.qos.logback + logback-classic + 1.1.2 + + + + org.slf4j slf4j-api - 1.7.5 + 1.7.7 org.bouncycastle bcpkix-jdk15on - 1.50 org.bouncycastle bcprov-jdk15on - 1.50 provided com.jcraft jzlib - 1.1.3 provided org.apache.sshd sshd-core - 0.8.0 + 0.11.0 test ch.qos.logback logback-core - 1.0.13 test @@ -84,7 +110,6 @@ ch.qos.logback logback-classic - 1.0.13 test @@ -126,7 +151,7 @@ org.apache.maven.plugins maven-release-plugin - 2.4.1 + 2.5 forked-path @@ -134,7 +159,7 @@ org.apache.maven.plugins maven-source-plugin - 2.1.2 + 2.2.1 attach-sources @@ -202,22 +227,18 @@ org.bouncycastle bcprov-jdk15on - 1.49 com.jcraft jzlib - 1.0.7 ch.qos.logback logback-core - 1.0.13 ch.qos.logback logback-classic - 1.0.13 @@ -251,7 +272,5 @@ - - From 66f67db21b066533f3c4a8007ba16340a19fb09a Mon Sep 17 00:00:00 2001 From: xardazz Date: Wed, 7 May 2014 13:14:16 +0400 Subject: [PATCH 12/28] Update KeyType.java remove my common lib --- src/main/java/net/schmizz/sshj/common/KeyType.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/common/KeyType.java b/src/main/java/net/schmizz/sshj/common/KeyType.java index 9df2cec0..00f47fc0 100644 --- a/src/main/java/net/schmizz/sshj/common/KeyType.java +++ b/src/main/java/net/schmizz/sshj/common/KeyType.java @@ -15,7 +15,6 @@ */ package net.schmizz.sshj.common; -import bidstreet.core.thunder.common.StringHelper; import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.jce.spec.ECParameterSpec; @@ -120,8 +119,8 @@ public enum KeyType { curveName, keyLen, x04, - StringHelper.printBytes(x), - StringHelper.printBytes(y)) + x, + y) ); if (!NISTP_CURVE.equals(curveName)) { @@ -239,4 +238,4 @@ public enum KeyType { return sType; } -} \ No newline at end of file +} From 587684c6a8c0c490d5e67e21aed47a7ffdc41629 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Wed, 7 May 2014 17:01:45 +0200 Subject: [PATCH 13/28] Fix null pointer. --- .../sshj/transport/verification/OpenSSHKnownHosts.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java index ca011473..daf1269a 100644 --- a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java +++ b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java @@ -376,11 +376,6 @@ public class OpenSSHKnownHosts return saltyBytes; } - @Override - public String getLine() { - return null; - } - @Override protected String getHostPart() { return hashedHost; From 131e85c4d0d1bee87ed0e6a2ab254bfd0d33927d Mon Sep 17 00:00:00 2001 From: David Kocher Date: Wed, 7 May 2014 17:02:18 +0200 Subject: [PATCH 14/28] Add write method to append single entry. --- .../verification/OpenSSHKnownHosts.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java index daf1269a..aaeb5aa4 100644 --- a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java +++ b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java @@ -28,9 +28,11 @@ import org.slf4j.LoggerFactory; import java.io.BufferedOutputStream; import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; import java.math.BigInteger; import java.security.KeyFactory; @@ -129,6 +131,22 @@ public class OpenSSHKnownHosts } } + /** + * Append a single entry + */ + public void write(HostEntry entry) + throws IOException { + final BufferedWriter writer = new BufferedWriter(new FileWriter(khFile, true)); + try { + writer.write(entry.getLine()); + writer.newLine(); + writer.flush(); + } + finally { + IOUtils.closeQuietly(writer); + } + } + public static File detectSSHDir() { final File sshDir = new File(System.getProperty("user.home"), ".ssh"); return sshDir.exists() ? sshDir : null; From 63424657da167099e232779c22a34d4e3d5b0d26 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Thu, 8 May 2014 16:17:43 +0200 Subject: [PATCH 15/28] Check index length. --- .../sshj/transport/verification/OpenSSHKnownHosts.java | 5 ++++- .../sshj/transport/verification/OpenSSHKnownHostsTest.java | 5 +++++ src/test/resources/known_hosts.invalid | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/known_hosts.invalid diff --git a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java index aaeb5aa4..1ade6d86 100644 --- a/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java +++ b/src/main/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHosts.java @@ -200,7 +200,10 @@ public class OpenSSHKnownHosts if (marker != null) { i++; } - + if(split.length < 3) { + LOG.error("Error reading entry `{}`", line); + return null; + } final String hostnames = split[i++]; final String sType = split[i++]; diff --git a/src/test/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHostsTest.java b/src/test/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHostsTest.java index 02e9f585..8edfaee7 100644 --- a/src/test/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHostsTest.java +++ b/src/test/java/net/schmizz/sshj/transport/verification/OpenSSHKnownHostsTest.java @@ -80,4 +80,9 @@ public class OpenSSHKnownHostsTest { assertTrue(kh.verify("69.163.155.180", 22, key)); } + @Test + public void testVerifyIndexError() throws Exception { + final OpenSSHKnownHosts v = new OpenSSHKnownHosts(new File("src/test/resources/known_hosts.invalid")); + assertTrue(v.entries().isEmpty()); + } } diff --git a/src/test/resources/known_hosts.invalid b/src/test/resources/known_hosts.invalid new file mode 100644 index 00000000..edd846e6 --- /dev/null +++ b/src/test/resources/known_hosts.invalid @@ -0,0 +1 @@ +M36Lo+Ik5ukNugvvoNFlpnyiHMmtKxt3FpyEfYuryXjNqMNWHn/ARVnpUIl5jRLTB7WBzyLYMG7X5nuoFL9zYqKGtHxChbDunxMVbspw5WXI9VN+qxcLwmITmpEvI9ApyS/Ox2ZyN7zw== From 93f1543af869e32c1f5091f6b40b5c46c98c2499 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Fri, 9 May 2014 10:37:53 +0200 Subject: [PATCH 16/28] Add PuTTY key file implementation. --- .../java/net/schmizz/sshj/DefaultConfig.java | 4 +- .../userauth/keyprovider/PuTTYKeyFile.java | 411 ++++++++++++++++++ .../sshj/keyprovider/PuTTYKeyFileTest.java | 221 ++++++++++ 3 files changed, 635 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java create mode 100644 src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java diff --git a/src/main/java/net/schmizz/sshj/DefaultConfig.java b/src/main/java/net/schmizz/sshj/DefaultConfig.java index ca446afb..9b950da9 100644 --- a/src/main/java/net/schmizz/sshj/DefaultConfig.java +++ b/src/main/java/net/schmizz/sshj/DefaultConfig.java @@ -63,6 +63,8 @@ import net.schmizz.sshj.transport.random.JCERandom; import net.schmizz.sshj.transport.random.SingletonRandomFactory; import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile; import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile; +import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -126,7 +128,7 @@ public class DefaultConfig protected void initFileKeyProviderFactories(boolean bouncyCastleRegistered) { if (bouncyCastleRegistered) { - setFileKeyProviderFactories(new PKCS8KeyFile.Factory(), new OpenSSHKeyFile.Factory()); + setFileKeyProviderFactories(new PKCS8KeyFile.Factory(), new OpenSSHKeyFile.Factory(), new PuTTYKeyFile.Factory()); } } diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java new file mode 100644 index 00000000..c0f2c3ea --- /dev/null +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java @@ -0,0 +1,411 @@ +package net.schmizz.sshj.userauth.keyprovider; + +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.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPrivateKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.util.HashMap; +import java.util.Map; + +import net.schmizz.sshj.common.Base64; +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.PrivateKeyReaderResource; +import net.schmizz.sshj.userauth.password.PrivateKeyStringResource; +import net.schmizz.sshj.userauth.password.Resource; + +/** + *

Sample PuTTY file format

+ *
+ * PuTTY-User-Key-File-2: ssh-rsa
+ * Encryption: none
+ * Comment: rsa-key-20080514
+ * Public-Lines: 4
+ * AAAAB3NzaC1yc2EAAAABJQAAAIEAiPVUpONjGeVrwgRPOqy3Ym6kF/f8bltnmjA2
+ * BMdAtaOpiD8A2ooqtLS5zWYuc0xkW0ogoKvORN+RF4JI+uNUlkxWxnzJM9JLpnvA
+ * HrMoVFaQ0cgDMIHtE1Ob1cGAhlNInPCRnGNJpBNcJ/OJye3yt7WqHP4SPCCLb6nL
+ * nmBUrLM=
+ * Private-Lines: 8
+ * AAAAgGtYgJzpktzyFjBIkSAmgeVdozVhgKmF6WsDMUID9HKwtU8cn83h6h7ug8qA
+ * hUWcvVxO201/vViTjWVz9ALph3uMnpJiuQaaNYIGztGJBRsBwmQW9738pUXcsUXZ
+ * 79KJP01oHn6Wkrgk26DIOsz04QOBI6C8RumBO4+F1WdfueM9AAAAQQDmA4hcK8Bx
+ * nVtEpcF310mKD3nsbJqARdw5NV9kCxPnEsmy7Sy1L4Ob/nTIrynbc3MA9HQVJkUz
+ * 7V0va5Pjm/T7AAAAQQCYbnG0UEekwk0LG1Hkxh1OrKMxCw2KWMN8ac3L0LVBg/Tk
+ * 8EnB2oT45GGeJaw7KzdoOMFZz0iXLsVLNUjNn2mpAAAAQQCN6SEfWqiNzyc/w5n/
+ * lFVDHExfVUJp0wXv+kzZzylnw4fs00lC3k4PZDSsb+jYCMesnfJjhDgkUA0XPyo8
+ * Emdk
+ * Private-MAC: 50c45751d18d74c00fca395deb7b7695e3ed6f77
+ * 
+ * + * @version $Id:$ + */ +public class PuTTYKeyFile implements FileKeyProvider { + + public static class Factory + implements net.schmizz.sshj.common.Factory.Named { + + @Override + public FileKeyProvider create() { + return new PuTTYKeyFile(); + } + + @Override + public String getName() { + return "PuTTY"; + } + } + + 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. + */ + @Override + public KeyType getType() throws IOException { + return KeyType.fromString(headers.get("PuTTY-User-Key-File-2")); + } + + public boolean isEncrypted() { + // Currently the only supported encryption types are "aes256-cbc" and "none". + return "aes256-cbc".equals(headers.get("Encryption")); + } + + private Map payload + = new HashMap(); + + /** + * For each line that looks like "Xyz: vvv", it will be stored in this map. + */ + private final Map headers + = new HashMap(); + + + protected KeyPair readKeyPair() throws IOException { + this.parseKeyPair(); + if(KeyType.RSA.equals(this.getType())) { + final KeyReader publicKeyReader = new KeyReader(publicKey); + publicKeyReader.skip(); // skip this + // public key exponent + BigInteger e = publicKeyReader.readInt(); + // modulus + BigInteger n = publicKeyReader.readInt(); + + final KeyReader privateKeyReader = new KeyReader(privateKey); + // private key exponent + BigInteger d = privateKeyReader.readInt(); + + final KeyFactory factory; + try { + factory = KeyFactory.getInstance("RSA"); + } + catch(NoSuchAlgorithmException s) { + throw new IOException(s.getMessage(), s); + } + try { + return new KeyPair( + factory.generatePublic(new RSAPublicKeySpec(n, e)), + factory.generatePrivate(new RSAPrivateKeySpec(n, d)) + ); + } + catch(InvalidKeySpecException i) { + throw new IOException(i.getMessage(), i); + } + } + if(KeyType.DSA.equals(this.getType())) { + final KeyReader publicKeyReader = new KeyReader(publicKey); + publicKeyReader.skip(); // skip this + BigInteger p = publicKeyReader.readInt(); + BigInteger q = publicKeyReader.readInt(); + BigInteger g = publicKeyReader.readInt(); + BigInteger y = publicKeyReader.readInt(); + + final KeyReader privateKeyReader = new KeyReader(privateKey); + // Private exponent from the private key + BigInteger x = privateKeyReader.readInt(); + + final KeyFactory factory; + try { + factory = KeyFactory.getInstance("DSA"); + } + catch(NoSuchAlgorithmException s) { + throw new IOException(s.getMessage(), s); + } + try { + return new KeyPair( + factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)), + factory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g)) + ); + } + catch(InvalidKeySpecException e) { + throw new IOException(e.getMessage(), e); + } + } + else { + throw new IOException(String.format("Unknown key type %s", this.getType())); + } + } + + protected void parseKeyPair() throws IOException { + BufferedReader r = new BufferedReader(resource.getReader()); + // Parse the text into headers and payloads + try { + String headerName = null; + String line; + while((line = r.readLine()) != null) { + int idx = line.indexOf(": "); + if(idx > 0) { + headerName = line.substring(0, idx); + headers.put(headerName, line.substring(idx + 2)); + } + else { + String s = payload.get(headerName); + if(s == null) { + s = line; + } + else { + // Append to previous line + s += line; + } + // Save payload + payload.put(headerName, s); + } + } + } + finally { + r.close(); + } + // Retrieve keys from payload + publicKey = Base64.decode(payload.get("Public-Lines")); + if(this.isEncrypted()) { + final char[] passphrase; + if(pwdf != null) { + passphrase = pwdf.reqPassword(resource); + } + else { + passphrase = "".toCharArray(); + } + try { + privateKey = this.decrypt(Base64.decode(payload.get("Private-Lines")), new String(passphrase)); + this.verify(new String(passphrase)); + } + finally { + PasswordUtils.blankOut(passphrase); + } + } + else { + privateKey = Base64.decode(payload.get("Private-Lines")); + } + } + + /** + * Converts a passphrase into a key, by following the convention that PuTTY uses. + *

+ *

+ * This is used to decrypt the private key when it's encrypted. + */ + private byte[] toKey(final String passphrase) throws IOException { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + + // The encryption key is derived from the passphrase by means of a succession of SHA-1 hashes. + + // Sequence number 0 + digest.update(new byte[]{0, 0, 0, 0}); + digest.update(passphrase.getBytes()); + byte[] key1 = digest.digest(); + + // Sequence number 1 + digest.update(new byte[]{0, 0, 0, 1}); + digest.update(passphrase.getBytes()); + byte[] key2 = digest.digest(); + + byte[] r = new byte[32]; + System.arraycopy(key1, 0, r, 0, 20); + System.arraycopy(key2, 0, r, 20, 12); + + return r; + } + catch(NoSuchAlgorithmException e) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * Verify the MAC. + */ + private void verify(final String passphrase) throws IOException { + try { + // 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) { + digest.update(passphrase.getBytes()); + } + final byte[] key = digest.digest(); + + final Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(new SecretKeySpec(key, 0, 20, mac.getAlgorithm())); + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final DataOutputStream data = new DataOutputStream(out); + // name of algorithm + data.writeInt(this.getType().toString().length()); + data.writeBytes(this.getType().toString()); + + data.writeInt(headers.get("Encryption").length()); + data.writeBytes(headers.get("Encryption")); + + data.writeInt(headers.get("Comment").length()); + data.writeBytes(headers.get("Comment")); + + data.writeInt(publicKey.length); + data.write(publicKey); + + data.writeInt(privateKey.length); + data.write(privateKey); + + final String encoded = Hex.toHexString(mac.doFinal(out.toByteArray())); + final String reference = headers.get("Private-MAC"); + if(!encoded.equals(reference)) { + throw new IOException("Invalid passphrase"); + } + } + catch(GeneralSecurityException e) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * Decrypt private key + * + * @param passphrase To decrypt + */ + private byte[] decrypt(final byte[] key, final String passphrase) throws IOException { + try { + final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + final byte[] expanded = this.toKey(passphrase); + 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) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * Parses the putty key bit vector, which is an encoded sequence + * of {@link java.math.BigInteger}s. + */ + private final static class KeyReader { + private final DataInput di; + + public KeyReader(byte[] key) { + this.di = new DataInputStream(new ByteArrayInputStream(key)); + } + + /** + * Skips an integer without reading it. + */ + public void skip() throws IOException { + final int read = di.readInt(); + 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) { + throw new IOException(String.format("Invalid length %d", len)); + } + byte[] r = new byte[len]; + di.readFully(r); + return r; + } + + /** + * Reads the next integer. + */ + public BigInteger readInt() throws IOException { + return new BigInteger(read()); + } + } +} \ No newline at end of file diff --git a/src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java b/src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java new file mode 100644 index 00000000..11ce6152 --- /dev/null +++ b/src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java @@ -0,0 +1,221 @@ +package net.schmizz.sshj.keyprovider; + +import org.junit.Test; + +import java.io.IOException; +import java.io.StringReader; + +import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile; +import net.schmizz.sshj.userauth.password.PasswordFinder; +import net.schmizz.sshj.userauth.password.Resource; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public class PuTTYKeyFileTest { + + final static String ppk2048 = "PuTTY-User-Key-File-2: ssh-rsa\n" + + "Encryption: none\n" + + "Comment: \n" + + "Public-Lines: 6\n" + + "AAAAB3NzaC1yc2EAAAADAQABAAABAQC0ITaAE49ievGiREUNxjccle9zJEZkNdkE\n" + + "2Nnkl0zxlGVwShwRIjtarM0uKiUQAFD1OhkdSA/1FhZKRumIUWD+2Fkj23EEvox0\n" + + "bTZyPzvQJERchdpvZKJyfxZgnPL6ygY6UQj8oNBhxnuwm9lL11cSJWS+4qJigT7g\n" + + "59eEhDyBaZ/HoijtDGvfPmJlhdNcq0MlC6ALy7XXpOd4RS2oo8m/TRpLFvoSnAQ/\n" + + "sr1wv9u7sTZh5C5RAjPN8LWBu7GuodqZ8PhlyT3oT+qbjpA/2e18vQta1ELROBKk\n" + + "qsLnsN4fjH69eYSZ8h5R07tfvQxTKRwCWKziqMjP4dF9Lz+gVcy1\n" + + "Private-Lines: 14\n" + + "AAABAQCqLWasAc7JH5YB07XZmZafrxeWFINcUXNCnQzeZgMPiT98osd5eHnS5MbE\n" + + "ApUZVPMne0gW3eoVhlRwwCYJ37hfjE5LDhrsfIl9xWBW916u+lSLhPolm1HOEjs1\n" + + "85GrVgokNkLjSZsVhMt+wv68JCnivuk7XipEHg8ltGNskvIG4AjW97uBqewyvyeG\n" + + "wsPYyBtiifRxJQ1th5hlLPh+jOBsyz91uC+ZeEW2pil2ftI7XrbKVbA95SRh4W8R\n" + + "tNBjqUpI+M2mJQ7nwh2gxd/GdNxKyvTUyCAfJo+DzAG+XRGW1Px7ibFw38jiT+CP\n" + + "tKTjCZRFMPUvmoH3MR1hzjqjqpuBAAAAgQDj4/2h35V/aAEYvQfkwF4k6rWOY15+\n" + + "gEV+gbfjWlYGxkH6U0AvMQv3c6EAvJNQsKip3/fqOHgdd37CcGVW+NQTucHlxz8K\n" + + "e4cYs0Dy8g4gcNhy2M99MOy9TuMsC0/mrTQUP0Vewwo7FASWF23sbhZsBM//BC8W\n" + + "m3LM843RwCbvXQAAAIEAylkY1TU721y22mVA2C+o6ADs55ZtMGJqjI0DjOiWCgxt\n" + + "j6pXRmJQ05hFZy4pO4AOYMZ5IW7MdqmCu2+GVytA6PxA6C+OGYF0Eh1YJbIh/Qrv\n" + + "07NMrYQVwQY2+FAJpLAwWJAjlrRRlANgGBHkbppf8RuQFB/euToHCZ6R6goJdTkA\n" + + "AACAOn0n+B0Lums/2FFmBRak2niTRONt6GMWhtK4e42MnKN3VxMGshAB4SdDAQOY\n" + + "4Qk66RYmniuhaC3sLwxtXsEKoVnMp9EXVoTPEd+BQCVBOJzZDVtAaejO9bqrvRL8\n" + + "kmknsX54RBXxrOVvNTofHLiRojncZnRSrM3BR+Xjo0b0+mE=\n" + + "Private-MAC: c9c10df8a1e3546eedcc08608efd5338de5df723\n"; + + final static String ppk4096 = "PuTTY-User-Key-File-2: ssh-rsa\n" + + "Encryption: none\n" + + "Comment: \n" + + "Public-Lines: 12\n" + + "AAAAB3NzaC1yc2EAAAADAQABAAACAQDMlwE5YNobWP8R47Ms41hnQnATKfJblTxW\n" + + "k/6nf+5IOknCNFBMQUOnToCmvcVRPzepr3nRFGm/gvo5SjsKdE4b0b9eT7xOGAYM\n" + + "9y18qO3flt6hARasK8NoivbT8Cm1f0Zj02eLBaiFpFYZOuBZdpluKiYH0wHuSPeq\n" + + "K3Q3/arsnQj1C0X+h5f4Nm0IYIHRkNsnvZrJf+MlHtcwS+BPXpAK9tkICcP1MJ2x\n" + + "UvTKh+TgWQJQ8EUq0OUkTBUBdmG+J6O+sdB0V6r06IpcXZUNed02F+bzP/DVUE1b\n" + + "mJZTx0ynZhKyP6NeXlwuZ3fUZhiwwqMRvCQuq1p8/itG9Vz+eY652KIIrCoVsyH1\n" + + "gIIRert3ADX5UySMdPcgBoDWYlfyj/fS+dR2o1lIwQXLcl9uL8ZELteSq/sLmapA\n" + + "YJbIis3r9aJnNSVaQSKn8p20tCWNnazAcSK0RTN2h5r0/r7WfvXafFIMt6VZUxPn\n" + + "dpFCJtxhgCrszosy87eL92NCoZvdQMOddpV3op04CDccZy0LEAt7o9dXoNNaeYlx\n" + + "czaUTV/JcuAnk9G0u3xUpTh0AOQauuxn8Dv6yyVLvXNJANp89zAhUukEwdhqOvXD\n" + + "U5qLLgY0Kf3v+ySj6HUWNBoms6ijF1txT3RDmJdCiVfuZ1nic9tsyp0A77S1oEEQ\n" + + "QD7Rgmi4rQ==\n" + + "Private-Lines: 28\n" + + "AAACABi+/xfonhkGt7t7NjXsvcmnoJTA0x6+u1ChkADEmZbE7hz+ZOQEVOGMvkTs\n" + + "2UwNgHcW0X43oN7YQdniH6gRD02QHjyTGmy7vSeeUjMs37DWt9Dzp8FlfbpMbLSP\n" + + "7QuV/HagoHqRUaPwj7V3iKFplf9cO8Ngg3BGBSbhIKqRFTaPfADfvzSdRAVy19dW\n" + + "jP1DLy7sYSeUP25C/7ZIxzXycyvQVcoCHGCw47IKHa/NpiJ4wa32kfcu0ziDt1q4\n" + + "7fOpKcYsDdG0tOnwoqOvchLyNY6Qb4/moQO8Nc8pcq1pgt0QnJxQ1Dra4P1/6F+Z\n" + + "hc0DjePcROgcM9LAj42Cqh/hpiCfCiLJiDts+HhQppgA4fOMy5d/wrG0nuqKfhIv\n" + + "BsX9nJDj4eHU4eNBAoraUfNLDIq0GHYDcm+jlhqO1SHxymjIhDqS6Cz/FWf07L1Q\n" + + "5DQ/+xHysVHCcavQk4jA7JwbZrRWo5qyKrdLoWRPFUX5w5ocLnmj0Zx8VOl6a+8M\n" + + "Q+ehLSZXFoCbao3nES/oEkKH0RFNQsDMJb0uiKQv4b/+6bywtYIFc0eqvEqd1GSF\n" + + "x3exCdHNhLRycaCgGSh+IdPCRrMj0N7/9pGZmbjfcZ7uKlFwqETVmy1H67NTXUCW\n" + + "NukVfsqTRewpqjFFeaxW5GEYwEeA34MbIChfdw4/KRr5XDhFAAABAQD3c9w0rWAQ\n" + + "rjVF1WTeD89Mf+Fnf7NvRaHAaD1EJxfimgqCD5juCIa5WSplzEBpPSG/rpl3HYVz\n" + + "CZ98rdJSS/bmJieojefvjlz1nuuPlApg2ctCfEZYOFnNP6yt0w88GLp3aMTfIsTf\n" + + "Z893GZnMFzMMLItLcZBTSmQLiqpyU6lWE+Tr4QOcQeCF8XqHGrLWbwACuKocmCW/\n" + + "4nI+6gZ4SfucLXKwFgcuhSaXo0XM6HiSgZHb5wEyjS2Boad6vX8t4YdjZbCcnGVm\n" + + "9TEm0/ow41Cl44SJUU6pLlo4UnSmR7aLmTK4iEG3fIMdEmmy4VX3MJ8fqXuVJwLE\n" + + "RLzqEjCgIcQzAAABAQDTqB/A3CyJfeHYFO7Et6edAOklejxqRW4UuuOu55v7FOj5\n" + + "X/yW72rWbndcci+mDXQvDL6P9EG3vF1twPS0konHqVxqj6Jlp1AtUWND2FzVTypY\n" + + "0X7z4Mif5V0p5bS5Qx6/pBg37XXbisSANSDxFVdH0/OSTYXi4EKmh0LjU5Ls0zIw\n" + + "MB6TYetuR1hEcCxuVESnOMUgjXMsoIwGR/jeKynle45UwTqUv/oWRQvFeIi5wlwn\n" + + "82GtUzLxhAo/BbXc3ODWjIGfKSxBJdsn0ZEXtPAk4CTqxM3VF4s3aOFAhHBDSyOv\n" + + "nHvWXwVRwmhtyXKEkTfAO6K4ptcS57LTNT8ta6+fAAABAQC9dPiPexqC35vWtWQd\n" + + "Zvm8DVCVscd7IPDn952FUsf2svoQ9MWodpD1craKGadSRsFCTVeYyHzS3Sg8HwKC\n" + + "NNoanAxpY4IqEPfuaLZZuKQsj3PsVj5rXdSEbmwCR7EhI9oDUDNcSLufR5A5DMpz\n" + + "wY4EJmg8uC2nO/O9Rzr516pIfDGsNwsdSKGWLlhgRzJxWl7M+cJjJfRlf6XruhLI\n" + + "WDDIq/jMHb5cLNjXdWTt3jyRQkm3HI6r5C3vc4mdInBm3tNUE+KKBtChegpgDgqg\n" + + "hZ41/hnd1e+3on3tvrE7arM3t4IHt7grwS/i1vdukV8ilYkTYHMG/Ls+6pUr+Swy\n" + + "z15x\n" + + "Private-MAC: a11331fa8b59cfb2be1c8e9f67ead34ac848d514\n"; + + final static String ppk1024_passphrase = "PuTTY-User-Key-File-2: ssh-rsa\n" + + "Encryption: aes256-cbc\n" + + "Comment: rsa-key-20121215\n" + + "Public-Lines: 4\n" + + "AAAAB3NzaC1yc2EAAAABJQAAAIB7KdUyuvGb2ne9G9YDAjaYvX/Mq6Q6ppGjbEQo\n" + + "bac66VUazxVpZsnAWikcdYAU7odkyt3jg7Nn1NgQS1a5mpXk/j77Ss5C9W4rymrU\n" + + "p32cmbgB/KIV80DnOyZyOtDWDPM0M0RRXqQvAO6TsnmsNSnBa8puMLHqCtrhvvJD\n" + + "KU+XEw==\n" + + "Private-Lines: 8\n" + + "4YMkPgLQJ9hOI1L1HsdOUnYi57tDy5h9DoPTHD55fhEYsn53h4WaHpxuZH8dTpbC\n" + + "5TcV3vYTfhh+aFBY0p/FI8L1hKfABLRxhkqkkc7xMmOGlA6HejAc8oTA3VArgSeG\n" + + "tRBuQRmBAC1Edtek/U+s8HzI2whzTw8tZoUUnT6844oc4tyCpWJUy5T8l+O3/03s\n" + + "SceJ98DN2k+L358VY8AXgPxP6NJvHvIlwmIo+PtcMWsyZegfSHEnoXN2GN4N0ul6\n" + + "298RzA9R+I3GSKKxsxUvWfOVibLq0dDM3+CTwcbmo4qvyM2xrRRLhObB2rVW07gL\n" + + "7+FZpHxf44QoQQ8mVkDJNaT1faF+h/8tCp2j1Cj5yEPHMOHGTVMyaz7gqhoMw5RX\n" + + "sfSP4ZaCGinLbouPrZN9Ue3ytwdEpmqU2MelmcZdcH6kWbLCqpWBswsxPfuhFdNt\n" + + "oYhmT2+0DKBuBVCAM4qRdA==\n" + + "Private-MAC: 40ccc8b9a7291ec64e5be0c99badbc8a012bf220\n"; + + final static String ppkdsa_passphrase = "PuTTY-User-Key-File-2: ssh-dss\n" + + "Encryption: aes256-cbc\n" + + "Comment: dsa-key-20140507\n" + + "Public-Lines: 10\n" + + "AAAAB3NzaC1kc3MAAACBAN6eo/Yh8ih26sKRAHAta/UqKesrXRS83GN7YqAxQzsP\n" + + "2tJ00UzOqZCdBoHIXLXC07QRJ9SkXOMnILw/KuaZ3paJ6ym92FzKi3BRfpzujIdo\n" + + "qBAEGSGOWbz2oYPDDSi0bsL84P4O8WD7ZxKhgTb4JAxlVJiW20vPfZA8Ft6xKJyd\n" + + "AAAAFQD1pnKWpSyHzi6RcVPn16FwmGIgmwAAAIEAiFPw87HVijatNOBeuxoU5PHH\n" + + "80kMl0TtxoI7rhB8fKO9bu7wLcT79h6xYS4Np6nHv9ajWwwVSLh8NjKgMbCXCz2j\n" + + "qD4ajvnusS7yz7TbTumeaGqFXEEzqzG4Xe6KXkv7kd7Yg+Dnw29zucgeAvPfuJFW\n" + + "Gtr4CWPoHSBgpTeyemEAAACBAJYvGi5gIMJQQUhIErKbtZ64V2L0zZtYkzlms03R\n" + + "cTBFN9++xV8zUvTPAAM8imsoxZ/5JNtNjJCAD+Ghrzyav24gxYG9v/YXtd2WsYa5\n" + + "0E/5wxcPor82SAqU2fd3IEQ5y9KHamXBuX/5KFDOTMC6cnGsutFkeo5rXQ0fI55C\n" + + "VSTq\n" + + "Private-Lines: 1\n" + + "nLEIBzB8WEaMMgDz5qwlqq7eBxLPIIi9uHyjMf7NOsQ=\n" + + "Private-MAC: b200c6d801fc8dd8f84a14afc3b94d9f9bb2df90\n"; + + @Test + public void test2048() throws Exception { + PuTTYKeyFile key = new PuTTYKeyFile(); + key.init(new StringReader(ppk2048)); + assertNotNull(key.getPrivate()); + assertNotNull(key.getPublic()); + } + + @Test + public void test4096() throws Exception { + PuTTYKeyFile key = new PuTTYKeyFile(); + key.init(new StringReader(ppk4096)); + assertNotNull(key.getPrivate()); + assertNotNull(key.getPublic()); + } + + @Test + public void testCorrectPassphraseRsa() throws Exception { + PuTTYKeyFile key = new PuTTYKeyFile(); + key.init(new StringReader(ppk1024_passphrase), new PasswordFinder() { + @Override + public char[] reqPassword(Resource resource) { + // correct passphrase + return "123456".toCharArray(); + } + + @Override + public boolean shouldRetry(Resource resource) { + return false; + } + }); + // Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size + assertNotNull(key.getPrivate()); + assertNotNull(key.getPublic()); + } + + @Test(expected = IOException.class) + public void testWrongPassphraseRsa() throws Exception { + PuTTYKeyFile key = new PuTTYKeyFile(); + key.init(new StringReader(ppk1024_passphrase), new PasswordFinder() { + @Override + public char[] reqPassword(Resource resource) { + // wrong passphrase + return "egfsdgdfgsdfsdfasfs523534dgdsgdfa".toCharArray(); + } + + @Override + public boolean shouldRetry(Resource resource) { + return false; + } + }); + assertNotNull(key.getPublic()); + assertNull(key.getPrivate()); + } + + @Test + public void testCorrectPassphraseDsa() throws Exception { + PuTTYKeyFile key = new PuTTYKeyFile(); + key.init(new StringReader(ppkdsa_passphrase), new PasswordFinder() { + @Override + public char[] reqPassword(Resource resource) { + // correct passphrase + return "secret".toCharArray(); + } + + @Override + public boolean shouldRetry(Resource resource) { + return false; + } + }); + // Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size + assertNotNull(key.getPrivate()); + assertNotNull(key.getPublic()); + } + + @Test(expected = IOException.class) + public void testWrongPassphraseDsa() throws Exception { + PuTTYKeyFile key = new PuTTYKeyFile(); + key.init(new StringReader(ppkdsa_passphrase), new PasswordFinder() { + @Override + public char[] reqPassword(Resource resource) { + // wrong passphrase + return "egfsdgdfgsdfsdfasfs523534dgdsgdfa".toCharArray(); + } + + @Override + public boolean shouldRetry(Resource resource) { + return false; + } + }); + assertNotNull(key.getPublic()); + assertNull(key.getPrivate()); + } +} From f354fd66615ddf70401e8a7191a15ffc4dd11f45 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Fri, 9 May 2014 13:43:52 +0200 Subject: [PATCH 17/28] Implement read ahead to speed up transfer rates for downloads by a magnitude. --- .../net/schmizz/sshj/sftp/RemoteFile.java | 96 ++++++++++++++----- 1 file changed, 73 insertions(+), 23 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java b/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java index 588fd081..e77157f3 100644 --- a/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java +++ b/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java @@ -15,9 +15,6 @@ */ package net.schmizz.sshj.sftp; -import net.schmizz.concurrent.Promise; -import net.schmizz.sshj.sftp.Response.StatusCode; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -25,6 +22,10 @@ import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.TimeUnit; +import net.schmizz.concurrent.Promise; +import net.schmizz.sshj.common.Buffer; +import net.schmizz.sshj.sftp.Response.StatusCode; + public class RemoteFile extends RemoteResource { @@ -52,10 +53,17 @@ public class RemoteFile public int read(long fileOffset, byte[] to, int offset, int len) throws IOException { - final Response res = requester.request( - newRequest(PacketType.READ).putUInt64(fileOffset).putUInt32(len) - ).retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS); - switch (res.getType()) { + final Response res = this.asyncRead(fileOffset, len).retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS); + return this.checkReadResponse(res, to, offset); + } + + protected Promise asyncRead(long fileOffset, int len) + throws IOException { + return requester.request(newRequest(PacketType.READ).putUInt64(fileOffset).putUInt32(len)); + } + + protected int checkReadResponse(Response res, byte[] to, int offset) throws Buffer.BufferException, SFTPException { + switch(res.getType()) { case DATA: int recvLen = res.readUInt32AsInt(); System.arraycopy(res.array(), res.rpos(), to, offset, recvLen); @@ -72,19 +80,19 @@ public class RemoteFile public void write(long fileOffset, byte[] data, int off, int len) throws IOException { - checkResponse(asyncWrite(fileOffset, data, off, len)); + checkWriteResponse(asyncWrite(fileOffset, data, off, len)); } protected Promise asyncWrite(long fileOffset, byte[] data, int off, int len) throws IOException { return requester.request(newRequest(PacketType.WRITE) - .putUInt64(fileOffset) - .putUInt32(len - off) - .putRawBytes(data, off, len) + .putUInt64(fileOffset) + .putUInt32(len - off) + .putRawBytes(data, off, len) ); } - private void checkResponse(Promise responsePromise) + private void checkWriteResponse(Promise responsePromise) throws SFTPException { responsePromise.retrieve(requester.getTimeoutMs(), TimeUnit.MILLISECONDS).ensureStatusPacketIsOK(); } @@ -139,8 +147,8 @@ public class RemoteFile @Override public void write(byte[] buf, int off, int len) throws IOException { - if (unconfirmedWrites.size() > maxUnconfirmedWrites) { - checkResponse(unconfirmedWrites.remove()); + if(unconfirmedWrites.size() > maxUnconfirmedWrites) { + checkWriteResponse(unconfirmedWrites.remove()); } unconfirmedWrites.add(RemoteFile.this.asyncWrite(fileOffset, buf, off, len)); fileOffset += len; @@ -149,8 +157,8 @@ public class RemoteFile @Override public void flush() throws IOException { - while (!unconfirmedWrites.isEmpty()) { - checkResponse(unconfirmedWrites.remove()); + while(!unconfirmedWrites.isEmpty()) { + checkWriteResponse(unconfirmedWrites.remove()); } } @@ -167,6 +175,11 @@ public class RemoteFile private final byte[] b = new byte[1]; + private final int maxUnconfirmedReads; + private final Queue> unconfirmedReads; + + private boolean eof; + private long fileOffset; private long markPos; private long readLimit; @@ -176,7 +189,13 @@ public class RemoteFile } public RemoteFileInputStream(long fileOffset) { + this(fileOffset, 0); + } + + public RemoteFileInputStream(long fileOffset, int maxUnconfirmedReads) { this.fileOffset = fileOffset; + this.maxUnconfirmedReads = maxUnconfirmedReads; + this.unconfirmedReads = new LinkedList>(); } @Override @@ -187,7 +206,7 @@ public class RemoteFile @Override public void mark(int readLimit) { this.readLimit = readLimit; - markPos = fileOffset; + this.markPos = fileOffset; } @Override @@ -211,14 +230,45 @@ public class RemoteFile @Override public int read(byte[] into, int off, int len) throws IOException { - int read = RemoteFile.this.read(fileOffset, into, off, len); - if (read != -1) { - fileOffset += read; - if (markPos != 0 && read > readLimit) // Invalidate mark position - markPos = 0; + while(!eof && unconfirmedReads.size() <= maxUnconfirmedReads) { + // Send read requests as long as there is no EOF and we have not reached the maximum parallelism + unconfirmedReads.add(asyncRead(fileOffset, len)); + fileOffset += len; } - return read; + if(unconfirmedReads.isEmpty()) { + // Attempted to read while status was already received + return -1; + } + // Retrieve first in + final Response res = unconfirmedReads.remove().retrieve( + requester.getTimeoutMs(), TimeUnit.MILLISECONDS); + final int recvLen = checkReadResponse(res, into, off); + if(markPos != 0 && recvLen > readLimit) // Invalidate mark position + { + markPos = 0; + } + if(-1 == recvLen) { + eof = true; + } + return recvLen; } + @Override + public void close() throws IOException { + while(!unconfirmedReads.isEmpty()) { + final Response res = unconfirmedReads.remove().retrieve( + requester.getTimeoutMs(), TimeUnit.MILLISECONDS); + switch(res.getType()) { + case STATUS: + res.ensureStatusIs(StatusCode.EOF); + break; + case DATA: + log.warn("Pending data packet from read response discarded"); + continue; + default: + throw new SFTPException("Unexpected packet: " + res.getType()); + } + } + } } } From 5c540b6889b2be31382ef387bcf94df320c1f846 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Wed, 14 May 2014 11:03:46 +0200 Subject: [PATCH 18/28] Interrupt packet reader thread on close. --- src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java b/src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java index fdf22418..b6fffd5c 100644 --- a/src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java +++ b/src/main/java/net/schmizz/sshj/sftp/SFTPEngine.java @@ -245,6 +245,7 @@ public class SFTPEngine public void close() throws IOException { sub.close(); + reader.interrupt(); } protected FileAttributes stat(PacketType pt, String path) From 08d0e59b6b1c18e5fd6bb1dead0cb0df9127dc14 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Wed, 14 May 2014 11:06:33 +0200 Subject: [PATCH 19/28] Logging --- .../java/net/schmizz/sshj/common/KeyType.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/common/KeyType.java b/src/main/java/net/schmizz/sshj/common/KeyType.java index 00f47fc0..be4f8522 100644 --- a/src/main/java/net/schmizz/sshj/common/KeyType.java +++ b/src/main/java/net/schmizz/sshj/common/KeyType.java @@ -28,6 +28,7 @@ import java.security.*; import java.security.interfaces.*; import java.security.spec.DSAPublicKeySpec; import java.security.spec.RSAPublicKeySpec; +import java.util.Arrays; /** Type of key e.g. rsa, dsa */ public enum KeyType { @@ -101,7 +102,8 @@ public enum KeyType { /** SSH identifier for ECDSA keys */ ECDSA("ecdsa-sha2-nistp256") { - private final Logger LOG = LoggerFactory.getLogger(getClass()); + private final Logger log = LoggerFactory.getLogger(getClass()); + @Override public PublicKey readPubKeyFromBuffer(String type, Buffer buf) throws GeneralSecurityException { @@ -114,17 +116,19 @@ public enum KeyType { final byte[] y = new byte[(keyLen - 1) / 2]; buf.readRawBytes(x); buf.readRawBytes(y); - LOG.debug(String.format("Key algo: %s, Key curve: %s, Key Len: %s, 0x04: %s\nx: %s\ny: %s", - type, - curveName, - keyLen, - x04, - x, - y) - ); + if(log.isDebugEnabled()) { + log.debug(String.format("Key algo: %s, Key curve: %s, Key Len: %s, 0x04: %s\nx: %s\ny: %s", + type, + curveName, + keyLen, + x04, + Arrays.toString(x), + Arrays.toString(y)) + ); + } if (!NISTP_CURVE.equals(curveName)) { - throw new GeneralSecurityException("Unknown curve name"); + throw new GeneralSecurityException(String.format("Unknown curve %s", curveName)); } BigInteger bigX = new BigInteger(1, x); @@ -137,9 +141,7 @@ public enum KeyType { ECPublicKeySpec publicSpec = new ECPublicKeySpec(pPublicPoint, spec); KeyFactory keyFactory = KeyFactory.getInstance("ECDSA"); - - PublicKey pubKey = keyFactory.generatePublic(publicSpec); - return pubKey; + return keyFactory.generatePublic(publicSpec); } catch (Exception ex) { throw new GeneralSecurityException(ex); } @@ -159,7 +161,6 @@ public enum KeyType { .putRawBytes(new byte[] { (byte) 0x04 }) .putRawBytes(x) .putRawBytes(y) - .compact() ; } @@ -180,7 +181,6 @@ public enum KeyType { System.arraycopy(in, i, out, 0, out.length); return out; } - }, /** Unrecognized */ @@ -200,7 +200,6 @@ public enum KeyType { protected boolean isMyType(Key key) { return false; } - }; From 77f5d7fdb8ff6ed95da125bc1bd1ecab6db039a2 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Wed, 14 May 2014 12:33:46 +0200 Subject: [PATCH 20/28] Extract encode method for signature. Implement signing for ECDSA. --- .../sshj/signature/AbstractSignature.java | 9 +++++ .../net/schmizz/sshj/signature/Signature.java | 7 ++++ .../schmizz/sshj/signature/SignatureDSA.java | 27 ++++----------- .../sshj/signature/SignatureECDSA.java | 34 +++++++++++++------ .../schmizz/sshj/signature/SignatureRSA.java | 12 +++---- .../sshj/userauth/method/KeyedAuthMethod.java | 16 ++++----- 6 files changed, 59 insertions(+), 46 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/signature/AbstractSignature.java b/src/main/java/net/schmizz/sshj/signature/AbstractSignature.java index 0c73315d..b18ef841 100644 --- a/src/main/java/net/schmizz/sshj/signature/AbstractSignature.java +++ b/src/main/java/net/schmizz/sshj/signature/AbstractSignature.java @@ -81,6 +81,15 @@ public abstract class AbstractSignature } } + @Override + public byte[] sign() { + try { + return signature.sign(); + } catch (SignatureException e) { + throw new SSHRuntimeException(e); + } + } + protected byte[] extractSig(byte[] sig) { if (sig[0] == 0 && sig[1] == 0 && sig[2] == 0) { int i = 0; diff --git a/src/main/java/net/schmizz/sshj/signature/Signature.java b/src/main/java/net/schmizz/sshj/signature/Signature.java index a58f2768..bebbad8d 100644 --- a/src/main/java/net/schmizz/sshj/signature/Signature.java +++ b/src/main/java/net/schmizz/sshj/signature/Signature.java @@ -74,6 +74,13 @@ public interface Signature { */ byte[] sign(); + /** + * Encode the signature as blog + * @param signature the signature to encode + * @return Encoded signature + */ + byte[] encode(byte[] signature); + /** * Verify against the given signature. * diff --git a/src/main/java/net/schmizz/sshj/signature/SignatureDSA.java b/src/main/java/net/schmizz/sshj/signature/SignatureDSA.java index ce1d50b8..45a1aa28 100644 --- a/src/main/java/net/schmizz/sshj/signature/SignatureDSA.java +++ b/src/main/java/net/schmizz/sshj/signature/SignatureDSA.java @@ -35,11 +35,11 @@ */ package net.schmizz.sshj.signature; +import java.security.SignatureException; + import net.schmizz.sshj.common.KeyType; import net.schmizz.sshj.common.SSHRuntimeException; -import java.security.SignatureException; - /** DSA {@link Signature} */ public class SignatureDSA extends AbstractSignature { @@ -65,14 +65,7 @@ public class SignatureDSA } @Override - public byte[] sign() { - byte[] sig; - try { - sig = signature.sign(); - } catch (SignatureException e) { - throw new SSHRuntimeException(e); - } - + public byte[] encode(byte[] sig) { // sig is in ASN.1 // SEQUENCE::={ r INTEGER, s INTEGER } @@ -90,17 +83,11 @@ public class SignatureDSA // result must be 40 bytes, but length of r and s may not be 20 bytes - System.arraycopy(r, - r.length > 20 ? 1 : 0, - result, - r.length > 20 ? 0 : 20 - r.length, - r.length > 20 ? 20 : r.length); + int r_copylen = (r.length < 20) ? r.length : 20; + int s_copylen = (s.length < 20) ? s.length : 20; - System.arraycopy(s, - s.length > 20 ? 1 : 0, - result, - s.length > 20 ? 20 : 40 - s.length, - s.length > 20 ? 20 : s.length); + System.arraycopy(r, r.length - r_copylen, result, 20 - r_copylen, r_copylen); + System.arraycopy(s, s.length - s_copylen, result, 40 - s_copylen, s_copylen); return result; } diff --git a/src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java b/src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java index 65596d9e..469ba14c 100644 --- a/src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java +++ b/src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java @@ -35,12 +35,13 @@ */ package net.schmizz.sshj.signature; +import java.math.BigInteger; +import java.security.SignatureException; + import net.schmizz.sshj.common.Buffer; import net.schmizz.sshj.common.KeyType; import net.schmizz.sshj.common.SSHRuntimeException; -import java.security.SignatureException; - /** ECDSA {@link Signature} */ public class SignatureECDSA extends AbstractSignature { @@ -66,17 +67,31 @@ public class SignatureECDSA } @Override - public byte[] sign() { - throw new UnsupportedOperationException("No implementation for sign!"); + public byte[] encode(byte[] sig) { + int rIndex = 3; + int rLen = sig[rIndex++] & 0xff; + byte[] r = new byte[rLen]; + System.arraycopy(sig, rIndex, r, 0, r.length); + + int sIndex = rIndex + rLen + 1; + int sLen = sig[sIndex++] & 0xff; + byte[] s = new byte[sLen]; + System.arraycopy(sig, sIndex, s, 0, s.length); + + System.arraycopy(sig, 4, r, 0, rLen); + System.arraycopy(sig, 6 + rLen, s, 0, sLen); + + Buffer buf = new Buffer.PlainBuffer(); + buf.putMPInt(new BigInteger(r)); + buf.putMPInt(new BigInteger(s)); + + return buf.getCompactData(); } @Override public boolean verify(byte[] sig) { - - byte[] r = null; - byte[] s = null; - - + byte[] r; + byte[] s; try { Buffer sigbuf = new Buffer.PlainBuffer(sig); final String algo = new String(sigbuf.readBytes()); @@ -139,5 +154,4 @@ public class SignatureECDSA throw new SSHRuntimeException(e); } } - } diff --git a/src/main/java/net/schmizz/sshj/signature/SignatureRSA.java b/src/main/java/net/schmizz/sshj/signature/SignatureRSA.java index a64e741b..753de0ab 100644 --- a/src/main/java/net/schmizz/sshj/signature/SignatureRSA.java +++ b/src/main/java/net/schmizz/sshj/signature/SignatureRSA.java @@ -35,11 +35,11 @@ */ package net.schmizz.sshj.signature; +import java.security.SignatureException; + import net.schmizz.sshj.common.KeyType; import net.schmizz.sshj.common.SSHRuntimeException; -import java.security.SignatureException; - /** RSA {@link Signature} */ public class SignatureRSA extends AbstractSignature { @@ -65,12 +65,8 @@ public class SignatureRSA } @Override - public byte[] sign() { - try { - return signature.sign(); - } catch (SignatureException e) { - throw new SSHRuntimeException(e); - } + public byte[] encode(byte[] signature) { + return signature; } @Override diff --git a/src/main/java/net/schmizz/sshj/userauth/method/KeyedAuthMethod.java b/src/main/java/net/schmizz/sshj/userauth/method/KeyedAuthMethod.java index 6bbcbfc4..c92f6db9 100644 --- a/src/main/java/net/schmizz/sshj/userauth/method/KeyedAuthMethod.java +++ b/src/main/java/net/schmizz/sshj/userauth/method/KeyedAuthMethod.java @@ -62,16 +62,16 @@ public abstract class KeyedAuthMethod } final String kt = KeyType.fromKey(key).toString(); - Signature sigger = Factory.Named.Util.create(params.getTransport().getConfig().getSignatureFactories(), kt); - if (sigger == null) + Signature signature = Factory.Named.Util.create(params.getTransport().getConfig().getSignatureFactories(), kt); + if (signature == null) throw new UserAuthException("Could not create signature instance for " + kt + " key"); - sigger.init(null, key); - sigger.update(new Buffer.PlainBuffer() - .putString(params.getTransport().getSessionID()) - .putBuffer(reqBuf) // & rest of the data for sig - .getCompactData()); - reqBuf.putSignature(kt, sigger.sign()); + signature.init(null, key); + signature.update(new Buffer.PlainBuffer() + .putString(params.getTransport().getSessionID()) + .putBuffer(reqBuf) // & rest of the data for sig + .getCompactData()); + reqBuf.putSignature(kt, signature.encode(signature.sign())); return reqBuf; } From f34667521d4c82fbfc2588c74972ad4d6d0e6e45 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Wed, 14 May 2014 23:31:07 +0200 Subject: [PATCH 21/28] Close remote handles when closing stream. --- src/main/java/net/schmizz/sshj/sftp/RemoteFile.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java b/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java index e77157f3..ef5c19cd 100644 --- a/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java +++ b/src/main/java/net/schmizz/sshj/sftp/RemoteFile.java @@ -166,6 +166,8 @@ public class RemoteFile public void close() throws IOException { flush(); + // Close handle + RemoteFile.this.close(); } } @@ -269,6 +271,8 @@ public class RemoteFile throw new SFTPException("Unexpected packet: " + res.getType()); } } + // Close handle + RemoteFile.this.close(); } } } From a8d2ea20281fe6ab2c2c1c7d82b98e4f43f83614 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Fri, 16 May 2014 15:30:04 +0200 Subject: [PATCH 22/28] Add disconnect message from server. --- .../java/net/schmizz/sshj/transport/DisconnectListener.java | 2 +- src/main/java/net/schmizz/sshj/transport/TransportImpl.java | 6 +++--- src/test/java/net/schmizz/sshj/transport/Disconnection.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/transport/DisconnectListener.java b/src/main/java/net/schmizz/sshj/transport/DisconnectListener.java index 09013761..f2a4c299 100644 --- a/src/main/java/net/schmizz/sshj/transport/DisconnectListener.java +++ b/src/main/java/net/schmizz/sshj/transport/DisconnectListener.java @@ -19,6 +19,6 @@ import net.schmizz.sshj.common.DisconnectReason; public interface DisconnectListener { - void notifyDisconnect(DisconnectReason reason); + void notifyDisconnect(DisconnectReason reason, String message); } diff --git a/src/main/java/net/schmizz/sshj/transport/TransportImpl.java b/src/main/java/net/schmizz/sshj/transport/TransportImpl.java index 22821397..75a0ed24 100644 --- a/src/main/java/net/schmizz/sshj/transport/TransportImpl.java +++ b/src/main/java/net/schmizz/sshj/transport/TransportImpl.java @@ -89,7 +89,7 @@ public final class TransportImpl private final DisconnectListener nullDisconnectListener = new DisconnectListener() { @Override - public void notifyDisconnect(DisconnectReason reason) { + public void notifyDisconnect(DisconnectReason reason, String message) { log.info("Disconnected - {}", reason); } }; @@ -383,7 +383,7 @@ public final class TransportImpl close.lock(); try { if (isRunning()) { - disconnectListener.notifyDisconnect(reason); + disconnectListener.notifyDisconnect(reason, message); getService().notifyError(new TransportException(reason, "Disconnected")); sendDisconnect(reason, message); finishOff(); @@ -576,7 +576,7 @@ public final class TransportImpl final SSHException causeOfDeath = SSHException.chainer.chain(ex); - disconnectListener.notifyDisconnect(causeOfDeath.getDisconnectReason()); + disconnectListener.notifyDisconnect(causeOfDeath.getDisconnectReason(), causeOfDeath.getMessage()); ErrorDeliveryUtil.alertEvents(causeOfDeath, close, serviceAccept); kexer.notifyError(causeOfDeath); diff --git a/src/test/java/net/schmizz/sshj/transport/Disconnection.java b/src/test/java/net/schmizz/sshj/transport/Disconnection.java index 1b5586e5..d0efa6ce 100644 --- a/src/test/java/net/schmizz/sshj/transport/Disconnection.java +++ b/src/test/java/net/schmizz/sshj/transport/Disconnection.java @@ -42,7 +42,7 @@ public class Disconnection { fixture.getClient().getTransport().setDisconnectListener(new DisconnectListener() { @Override - public void notifyDisconnect(DisconnectReason reason) { + public void notifyDisconnect(DisconnectReason reason, String message) { notified = true; } }); From 92973381958cad20acb39ec8c068243524a31975 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Fri, 16 May 2014 15:33:24 +0200 Subject: [PATCH 23/28] Use plain server message. --- src/main/java/net/schmizz/sshj/transport/TransportImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/schmizz/sshj/transport/TransportImpl.java b/src/main/java/net/schmizz/sshj/transport/TransportImpl.java index 75a0ed24..7c964839 100644 --- a/src/main/java/net/schmizz/sshj/transport/TransportImpl.java +++ b/src/main/java/net/schmizz/sshj/transport/TransportImpl.java @@ -525,7 +525,7 @@ public final class TransportImpl final DisconnectReason code = DisconnectReason.fromInt(buf.readUInt32AsInt()); final String message = buf.readString(); log.info("Received SSH_MSG_DISCONNECT (reason={}, msg={})", code, message); - throw new TransportException(code, "Disconnected; server said: " + message); + throw new TransportException(code, message); } catch (Buffer.BufferException be) { throw new TransportException(be); } From f2ebbe288f964f26fb7e41cbfae017e58a9a6221 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Fri, 16 May 2014 22:21:00 +0200 Subject: [PATCH 24/28] Ignore socket timeout in read which occurs if we have set the timeout to > 0. We should continue reading from the stream unless the reader is interrupted. Note that with the default timeout set to 0, the reader thread will never return. --- .../java/net/schmizz/sshj/transport/Reader.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/transport/Reader.java b/src/main/java/net/schmizz/sshj/transport/Reader.java index 35e31712..8ba98d07 100644 --- a/src/main/java/net/schmizz/sshj/transport/Reader.java +++ b/src/main/java/net/schmizz/sshj/transport/Reader.java @@ -40,6 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; +import java.net.SocketTimeoutException; public final class Reader extends Thread { @@ -65,13 +66,21 @@ public final class Reader int needed = 1; while (!isInterrupted()) { - int read = inp.read(recvbuf, 0, needed); + int read; + try { + read = inp.read(recvbuf, 0, needed); + } + catch(SocketTimeoutException e) { + if (isInterrupted()) { + throw e; + } + continue; + } if (read == -1) throw new TransportException("Broken transport; encountered EOF"); else needed = decoder.received(recvbuf, read); } - } catch (Exception e) { if (isInterrupted()) { // We are meant to shut up and draw to a close if interrupted From bdc541c9590edbd448931ee5469bbe3fe0ef15bb Mon Sep 17 00:00:00 2001 From: David Kocher Date: Tue, 3 Jun 2014 11:54:38 +0200 Subject: [PATCH 25/28] Format client identification. --- src/main/java/net/schmizz/sshj/transport/TransportImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/schmizz/sshj/transport/TransportImpl.java b/src/main/java/net/schmizz/sshj/transport/TransportImpl.java index 7c964839..40527a73 100644 --- a/src/main/java/net/schmizz/sshj/transport/TransportImpl.java +++ b/src/main/java/net/schmizz/sshj/transport/TransportImpl.java @@ -139,7 +139,7 @@ public final class TransportImpl this.encoder = new Encoder(config.getRandomFactory().create(), writeLock); this.decoder = new Decoder(this); this.kexer = new KeyExchanger(this); - clientID = "SSH-2.0-" + config.getVersion(); + this.clientID = String.format("SSH-2.0-%s", config.getVersion()); } @Override From 01be48508dbc5f9a23eef5cfafb59ea109a1138e Mon Sep 17 00:00:00 2001 From: David Kocher Date: Tue, 3 Jun 2014 11:55:44 +0200 Subject: [PATCH 26/28] Throw SSHException for packet length exceeding max size. --- src/main/java/net/schmizz/sshj/sftp/PacketReader.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/schmizz/sshj/sftp/PacketReader.java b/src/main/java/net/schmizz/sshj/sftp/PacketReader.java index 30e55edf..0cd18674 100644 --- a/src/main/java/net/schmizz/sshj/sftp/PacketReader.java +++ b/src/main/java/net/schmizz/sshj/sftp/PacketReader.java @@ -16,6 +16,8 @@ package net.schmizz.sshj.sftp; import net.schmizz.concurrent.Promise; +import net.schmizz.sshj.common.SSHException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,7 +64,7 @@ public class PacketReader | lenBuf[3] & 0x000000ffL); if (len > SFTPPacket.MAX_SIZE) { - throw new IllegalStateException("Invalid packet: indicated length "+len+" too large"); + throw new SSHException(String.format("Indicated packet length %d too large", len)); } return (int) len; From 9886facf42ef130511bdc9e58b8ded422a3c3927 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Wed, 18 Jun 2014 10:08:07 +0200 Subject: [PATCH 27/28] Fix test. --- src/test/java/net/schmizz/sshj/sftp/PacketReaderTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/net/schmizz/sshj/sftp/PacketReaderTest.java b/src/test/java/net/schmizz/sshj/sftp/PacketReaderTest.java index b638c795..5a380be5 100644 --- a/src/test/java/net/schmizz/sshj/sftp/PacketReaderTest.java +++ b/src/test/java/net/schmizz/sshj/sftp/PacketReaderTest.java @@ -9,6 +9,7 @@ import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.util.Arrays; +import net.schmizz.sshj.common.SSHException; import net.schmizz.sshj.connection.channel.direct.Session.Subsystem; import org.junit.Before; @@ -57,7 +58,7 @@ public class PacketReaderTest { try { reader.readPacket(); fail("Should have failed to read packet of size " + Integer.MAX_VALUE); - } catch (IllegalStateException e) { + } catch (SSHException e) { e.printStackTrace(); // success; indicated packet size was too large } From d6c22fef5502521911063080b16d82d91d80534c Mon Sep 17 00:00:00 2001 From: David Kocher Date: Wed, 18 Jun 2014 10:16:22 +0200 Subject: [PATCH 28/28] ADd clirr-maven-plugin --- pom.xml | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 15e9fa60..0ddc63c5 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 @@ -10,21 +11,20 @@ sshj SSHv2 library for Java - http://github.com/shikhar/sshj + http://github.com/dkocher/sshj 2009 github - http://github.com/shikhar/sshj/issues + http://github.com/dkocher/sshj/issues - scm:git:git://github.com/shikhar/sshj.git - scm:git:git@github.com:shikhar/sshj.git - http://github.com/shikhar/sshj - HEAD - + scm:git:git://github.com/dkocher/sshj.git + scm:git:git@github.com:dkocher/sshj.git + http://github.com/dkocher/sshj + @@ -128,6 +128,13 @@ shikhar@schmizz.net http://schmizz.net + + iterate + David Kocher + dkocher@iterate.ch + iterate GmbH + https://iterate.ch + @@ -204,6 +211,11 @@ + + org.codehaus.mojo + clirr-maven-plugin + 2.6.1 + @@ -273,4 +285,13 @@ + + + + org.codehaus.mojo + clirr-maven-plugin + 2.6.1 + + +