diff --git a/src/main/java/net/schmizz/sshj/common/KeyType.java b/src/main/java/net/schmizz/sshj/common/KeyType.java index 9d2ea94d..c93d3ea7 100644 --- a/src/main/java/net/schmizz/sshj/common/KeyType.java +++ b/src/main/java/net/schmizz/sshj/common/KeyType.java @@ -119,8 +119,8 @@ public enum KeyType { }, - /** SSH identifier for ECDSA keys */ - ECDSA("ecdsa-sha2-nistp256") { + /** SSH identifier for ECDSA-256 keys */ + ECDSA256("ecdsa-sha2-nistp256") { private final Logger log = LoggerFactory.getLogger(getClass()); @Override @@ -149,7 +149,7 @@ public enum KeyType { ); } - if (!NISTP_CURVE.equals(curveName)) { + if (!NISTP_CURVE_256.equals(curveName)) { throw new GeneralSecurityException(String.format("Unknown curve %s", curveName)); } @@ -175,16 +175,82 @@ public enum KeyType { final ECPublicKey ecdsa = (ECPublicKey) pk; byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve()); - buf.putString(NISTP_CURVE) + buf.putString(NISTP_CURVE_256) .putBytes(encoded); } @Override protected boolean isMyType(Key key) { - return ("ECDSA".equals(key.getAlgorithm())); + return ("ECDSA".equals(key.getAlgorithm()) && ((ECPublicKey) key).getParams().getCurve().getField().getFieldSize() == 256); } }, + /** SSH identifier for ECDSA-384 keys */ + ECDSA384("ecdsa-sha2-nistp384") { + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Override + public PublicKey readPubKeyFromBuffer(Buffer buf) + throws GeneralSecurityException { + if (!SecurityUtils.isBouncyCastleRegistered()) { + throw new GeneralSecurityException("BouncyCastle is required to read a key of type " + sType); + } + 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); + if(log.isDebugEnabled()) { + log.debug(String.format("Key algo: %s, Key curve: %s, Key Len: %s, 0x04: %s\nx: %s\ny: %s", + sType, + curveName, + keyLen, + x04, + Arrays.toString(x), + Arrays.toString(y)) + ); + } + + if (!NISTP_CURVE_384.equals(curveName)) { + throw new GeneralSecurityException(String.format("Unknown curve %s", curveName)); + } + + BigInteger bigX = new BigInteger(1, x); + BigInteger bigY = new BigInteger(1, y); + + X9ECParameters ecParams = NISTNamedCurves.getByName("p-384"); + ECPoint pPublicPoint = ecParams.getCurve().createPoint(bigX, bigY); + ECParameterSpec spec = new ECParameterSpec(ecParams.getCurve(), + ecParams.getG(), ecParams.getN()); + ECPublicKeySpec publicSpec = new ECPublicKeySpec(pPublicPoint, spec); + + KeyFactory keyFactory = KeyFactory.getInstance("ECDSA"); + return keyFactory.generatePublic(publicSpec); + } catch (Exception ex) { + throw new GeneralSecurityException(ex); + } + } + + + @Override + protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer buf) { + final ECPublicKey ecdsa = (ECPublicKey) pk; + byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve()); + + buf.putString(NISTP_CURVE_384) + .putBytes(encoded); + } + + @Override + protected boolean isMyType(Key key) { + return ("ECDSA".equals(key.getAlgorithm()) && ((ECPublicKey) key).getParams().getCurve().getField().getFieldSize() == 384); + } + }, + ED25519("ssh-ed25519") { private final Logger log = LoggerFactory.getLogger(KeyType.class); @Override @@ -285,7 +351,8 @@ public enum KeyType { }; - private static final String NISTP_CURVE = "nistp256"; + private static final String NISTP_CURVE_256 = "nistp256"; + private static final String NISTP_CURVE_384 = "nistp384"; protected final String sType; diff --git a/src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java b/src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java index 93e228a6..32273285 100644 --- a/src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java +++ b/src/main/java/net/schmizz/sshj/signature/SignatureECDSA.java @@ -37,7 +37,7 @@ public class SignatureECDSA @Override public String getName() { - return KeyType.ECDSA.toString(); + return KeyType.ECDSA256.toString(); } } diff --git a/src/main/java/net/schmizz/sshj/signature/SignatureECDSA384.java b/src/main/java/net/schmizz/sshj/signature/SignatureECDSA384.java new file mode 100644 index 00000000..33fcdc54 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/signature/SignatureECDSA384.java @@ -0,0 +1,137 @@ +/* + * Copyright (C)2009 - SSHJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.schmizz.sshj.signature; + +import net.schmizz.sshj.common.Buffer; +import net.schmizz.sshj.common.KeyType; +import net.schmizz.sshj.common.SSHRuntimeException; + +import java.math.BigInteger; +import java.security.SignatureException; + +/** ECDSA {@link Signature} */ +public class SignatureECDSA384 + 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 SignatureECDSA384(); + } + + @Override + public String getName() { + return KeyType.ECDSA384.toString(); + } + + } + + public SignatureECDSA384() { + super("SHA384withECDSA"); + } + + @Override + 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; + byte[] s; + try { + Buffer sigbuf = new Buffer.PlainBuffer(sig); + final String algo = new String(sigbuf.readBytes()); + if (!"ecdsa-sha2-nistp384".equals(algo)) { + throw new SSHRuntimeException(String.format("Signature :: ecdsa-sha2-nistp384 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); + } + } +}