From 0f7355a277ed53a33d6e3946be98d0ae96624a5c Mon Sep 17 00:00:00 2001 From: Alexey Gromov Date: Tue, 6 May 2014 12:09:50 +0400 Subject: [PATCH 1/3] 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. *

*

*

* [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 2/3] 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 66f67db21b066533f3c4a8007ba16340a19fb09a Mon Sep 17 00:00:00 2001 From: xardazz Date: Wed, 7 May 2014 13:14:16 +0400 Subject: [PATCH 3/3] 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 +}