Skip to content

Commit

Permalink
Added support for ed25519 keys (Fixes #220)
Browse files Browse the repository at this point in the history
  • Loading branch information
hierynomus committed Nov 20, 2015
1 parent a73776a commit db75bad
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 11 deletions.
40 changes: 40 additions & 0 deletions src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
94 changes: 94 additions & 0 deletions src/main/java/com/hierynomus/sshj/signature/SignatureEdDSA.java
Original file line number Diff line number Diff line change
@@ -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<Signature> {

@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);
}
}
}
3 changes: 2 additions & 1 deletion src/main/java/net/schmizz/sshj/DefaultConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -181,7 +182,7 @@ protected void initCipherFactories() {
}

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() {
Expand Down
51 changes: 42 additions & 9 deletions src/main/java/net/schmizz/sshj/common/KeyType.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,6 +33,7 @@

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;
Expand Down Expand Up @@ -163,18 +171,43 @@ public void putPubKeyIntoBuffer(PublicKey pk, Buffer<?> buf) {
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());
}
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,23 @@ public void fromString()
@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 {
Expand Down
File renamed without changes.
File renamed without changes.
7 changes: 7 additions & 0 deletions src/test/resources/keytypes/test_ed25519
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAwHSYkZJATPMgvLHkxKAJ9j38Gyyq5HGoWdMcT6FiAiQAAAJDimgR84poE
fAAAAAtzc2gtZWQyNTUxOQAAACAwHSYkZJATPMgvLHkxKAJ9j38Gyyq5HGoWdMcT6FiAiQ
AAAECmsckQycWnfGQK6XtQpaMGODbAkMQOdJNK6XJSipB7dDAdJiRkkBM8yC8seTEoAn2P
fwbLKrkcahZ0xxPoWICJAAAACXJvb3RAc3NoagECAwQ=
-----END OPENSSH PRIVATE KEY-----
1 change: 1 addition & 0 deletions src/test/resources/keytypes/test_ed25519.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDAdJiRkkBM8yC8seTEoAn2PfwbLKrkcahZ0xxPoWICJ root@sshj

0 comments on commit db75bad

Please sign in to comment.