diff --git a/src/main/java/net/schmizz/sshj/DefaultConfig.java b/src/main/java/net/schmizz/sshj/DefaultConfig.java
index 9b950da9..ccf9bd13 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;
@@ -74,22 +75,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
@@ -100,7 +101,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_2";
public DefaultConfig() {
setVersion(VERSION);
@@ -166,7 +167,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..00f47fc0 100644
--- a/src/main/java/net/schmizz/sshj/common/KeyType.java
+++ b/src/main/java/net/schmizz/sshj/common/KeyType.java
@@ -15,21 +15,24 @@
*/
package net.schmizz.sshj.common;
+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 +99,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,
+ x,
+ 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 +204,8 @@ public enum KeyType {
};
+ private static final String NISTP_CURVE = "nistp256";
+
protected final String sType;
private KeyType(String type) {
@@ -149,4 +238,4 @@ public enum KeyType {
return sType;
}
-}
\ No newline at end of file
+}
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);
+ }
+ }
+
+}