diff --git a/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java b/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java new file mode 100644 index 00000000..4b1c4191 --- /dev/null +++ b/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java @@ -0,0 +1,40 @@ +package com.hierynomus.sshj.signature; + +import net.i2p.crypto.eddsa.EdDSAPublicKey; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; +import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; +import net.schmizz.sshj.common.SSHRuntimeException; + +import java.util.Arrays; + +/** + * Our own extension of the EdDSAPublicKey that comes from ECC-25519, as that class does not implement equality. + * The code uses the equality of the keys as an indicator whether they're the same during host key verification. + */ +public class Ed25519PublicKey extends EdDSAPublicKey { + + public Ed25519PublicKey(EdDSAPublicKeySpec spec) { + super(spec); + + EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("ed25519-sha-512"); + if (!spec.getParams().getCurve().equals(ed25519.getCurve())) { + throw new SSHRuntimeException("Cannot create Ed25519 Public Key from wrong spec"); + } + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Ed25519PublicKey)) { + return false; + } + + Ed25519PublicKey otherKey = (Ed25519PublicKey) other; + return Arrays.equals(getAbyte(), otherKey.getAbyte()); + } + + @Override + public int hashCode() { + return getA().hashCode(); + } +} diff --git a/src/main/java/com/hierynomus/sshj/signature/SignatureEdDSA.java b/src/main/java/com/hierynomus/sshj/signature/SignatureEdDSA.java new file mode 100644 index 00000000..c16b6125 --- /dev/null +++ b/src/main/java/com/hierynomus/sshj/signature/SignatureEdDSA.java @@ -0,0 +1,94 @@ +package com.hierynomus.sshj.signature; + +import net.i2p.crypto.eddsa.EdDSAEngine; +import net.schmizz.sshj.common.Buffer; +import net.schmizz.sshj.common.KeyType; +import net.schmizz.sshj.common.SSHRuntimeException; +import net.schmizz.sshj.signature.Signature; + +import java.security.*; + +public class SignatureEdDSA implements Signature { + public static class Factory implements net.schmizz.sshj.common.Factory.Named { + + @Override + public String getName() { + return KeyType.ED25519.toString(); + } + + @Override + public Signature create() { + return new SignatureEdDSA(); + } + } + + final EdDSAEngine engine; + + protected SignatureEdDSA() { + try { + engine = new EdDSAEngine(MessageDigest.getInstance("SHA-512")); + } catch (NoSuchAlgorithmException e) { + throw new SSHRuntimeException(e); + } + } + + @Override + public void init(PublicKey pubkey, PrivateKey prvkey) { + try { + if (pubkey != null) { + engine.initVerify(pubkey); + } + + if (prvkey != null) { + engine.initSign(prvkey); + } + } catch (InvalidKeyException e) { + throw new SSHRuntimeException(e); + } + } + + @Override + public void update(byte[] H) { + update(H, 0, H.length); + } + + @Override + public void update(byte[] H, int off, int len) { + try { + engine.update(H, off, len); + } catch (SignatureException e) { + throw new SSHRuntimeException(e); + } + } + + @Override + public byte[] sign() { + try { + return engine.sign(); + } catch (SignatureException e) { + throw new SSHRuntimeException(e); + } + } + + @Override + public byte[] encode(byte[] signature) { + return signature; + } + + @Override + public boolean verify(byte[] sig) { + try { + Buffer.PlainBuffer plainBuffer = new Buffer.PlainBuffer(sig); + String algo = plainBuffer.readString(); + if (!"ssh-ed25519".equals(algo)) { + throw new SSHRuntimeException("Expected 'ssh-ed25519' key algorithm, but was: " + algo); + } + byte[] bytes = plainBuffer.readBytes(); + return engine.verify(bytes); + } catch (SignatureException e) { + throw new SSHRuntimeException(e); + } catch (Buffer.BufferException e) { + throw new SSHRuntimeException(e); + } + } +} diff --git a/src/main/java/net/schmizz/sshj/DefaultConfig.java b/src/main/java/net/schmizz/sshj/DefaultConfig.java index cffb7ae6..04f46f64 100644 --- a/src/main/java/net/schmizz/sshj/DefaultConfig.java +++ b/src/main/java/net/schmizz/sshj/DefaultConfig.java @@ -15,6 +15,7 @@ */ package net.schmizz.sshj; +import com.hierynomus.sshj.signature.SignatureEdDSA; import com.hierynomus.sshj.transport.cipher.BlockCiphers; import com.hierynomus.sshj.transport.cipher.StreamCiphers; import net.schmizz.keepalive.KeepAliveProvider; @@ -181,7 +182,7 @@ public class DefaultConfig } protected void initSignatureFactories() { - setSignatureFactories(new SignatureECDSA.Factory(), new SignatureRSA.Factory(), new SignatureDSA.Factory()); + setSignatureFactories(new SignatureECDSA.Factory(), new SignatureRSA.Factory(), new SignatureDSA.Factory(), new SignatureEdDSA.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 046d1ed9..a8a910ad 100644 --- a/src/main/java/net/schmizz/sshj/common/KeyType.java +++ b/src/main/java/net/schmizz/sshj/common/KeyType.java @@ -16,6 +16,13 @@ package net.schmizz.sshj.common; import com.hierynomus.sshj.secg.SecgUtils; +import com.hierynomus.sshj.signature.Ed25519PublicKey; +import net.i2p.crypto.eddsa.*; +import net.i2p.crypto.eddsa.math.GroupElement; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; +import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; +import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.jce.spec.ECParameterSpec; @@ -26,6 +33,7 @@ import org.slf4j.LoggerFactory; import java.math.BigInteger; import java.security.*; +import java.security.KeyFactory; import java.security.interfaces.*; import java.security.spec.DSAPublicKeySpec; import java.security.spec.RSAPublicKeySpec; @@ -163,18 +171,43 @@ public enum KeyType { 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; + ED25519("ssh-ed25519") { + private final Logger logger = LoggerFactory.getLogger(KeyType.class); + @Override + public PublicKey readPubKeyFromBuffer(String type, Buffer buf) throws GeneralSecurityException { + try { + final int keyLen = buf.readUInt32AsInt(); + final byte[] p = new byte[keyLen]; + buf.readRawBytes(p); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Key algo: %s, Key curve: 25519, Key Len: %s\np: %s", + type, + keyLen, + Arrays.toString(p)) + ); } + + EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("ed25519-sha-512"); + GroupElement point = ed25519.getCurve().createPoint(p, true); + EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(point, ed25519); + return new Ed25519PublicKey(publicSpec); + + } catch (Buffer.BufferException be) { + throw new SSHRuntimeException(be); } - final byte[] out = new byte[in.length - i]; - System.arraycopy(in, i, out, 0, out.length); - return out; + } + + @Override + public void putPubKeyIntoBuffer(PublicKey pk, Buffer buf) { + EdDSAPublicKey key = (EdDSAPublicKey) pk; + buf.putString(sType).putBytes(key.getAbyte()); + } + + @Override + protected boolean isMyType(Key key) { + return "EdDSA".equals(key.getAlgorithm()); } }, diff --git a/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java b/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java index 33d88d1c..1fd2cca1 100644 --- a/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java +++ b/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java @@ -133,13 +133,23 @@ public class OpenSSHKeyFileTest { @Test public void shouldHaveCorrectFingerprintForECDSA() throws IOException, GeneralSecurityException { OpenSSHKeyFile keyFile = new OpenSSHKeyFile(); - keyFile.init(new File("src/test/resources/test_ecdsa_nistp256")); + keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256")); String expected = "256 MD5:53:ae:db:ed:8f:2d:02:d4:d5:6c:24:bc:a4:66:88:79 root@itgcpkerberosstack-cbgateway-0-20151117031915 (ECDSA)\n"; PublicKey aPublic = keyFile.getPublic(); String sshjFingerprintSshjKey = net.schmizz.sshj.common.SecurityUtils.getFingerprint(aPublic); assertThat(expected, containsString(sshjFingerprintSshjKey)); } + @Test + public void shouldHaveCorrectFingerprintForED25519() throws IOException { + OpenSSHKeyFile keyFile = new OpenSSHKeyFile(); + keyFile.init(new File("src/test/resources/keytypes/test_ed25519")); + String expected = "256 MD5:d3:5e:40:72:db:08:f1:6d:0c:d7:6d:35:0d:ba:7c:32 root@sshj (ED25519)\n"; + PublicKey aPublic = keyFile.getPublic(); + String sshjFingerprintSshjKey = net.schmizz.sshj.common.SecurityUtils.getFingerprint(aPublic); + assertThat(expected, containsString(sshjFingerprintSshjKey)); + } + @Before public void setup() throws UnsupportedEncodingException, GeneralSecurityException { diff --git a/src/test/resources/test_ecdsa_nistp256 b/src/test/resources/keytypes/test_ecdsa_nistp256 similarity index 100% rename from src/test/resources/test_ecdsa_nistp256 rename to src/test/resources/keytypes/test_ecdsa_nistp256 diff --git a/src/test/resources/test_ecdsa_nistp256.pub b/src/test/resources/keytypes/test_ecdsa_nistp256.pub similarity index 100% rename from src/test/resources/test_ecdsa_nistp256.pub rename to src/test/resources/keytypes/test_ecdsa_nistp256.pub diff --git a/src/test/resources/keytypes/test_ed25519 b/src/test/resources/keytypes/test_ed25519 new file mode 100644 index 00000000..7e62b1f1 --- /dev/null +++ b/src/test/resources/keytypes/test_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAwHSYkZJATPMgvLHkxKAJ9j38Gyyq5HGoWdMcT6FiAiQAAAJDimgR84poE +fAAAAAtzc2gtZWQyNTUxOQAAACAwHSYkZJATPMgvLHkxKAJ9j38Gyyq5HGoWdMcT6FiAiQ +AAAECmsckQycWnfGQK6XtQpaMGODbAkMQOdJNK6XJSipB7dDAdJiRkkBM8yC8seTEoAn2P +fwbLKrkcahZ0xxPoWICJAAAACXJvb3RAc3NoagECAwQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/src/test/resources/keytypes/test_ed25519.pub b/src/test/resources/keytypes/test_ed25519.pub new file mode 100644 index 00000000..792bbc14 --- /dev/null +++ b/src/test/resources/keytypes/test_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDAdJiRkkBM8yC8seTEoAn2PfwbLKrkcahZ0xxPoWICJ root@sshj