From 1538c32d65152d1792e6d2404f2018c9db29ab19 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Sat, 4 Apr 2015 17:17:58 -0700 Subject: Add Ed25519 host key support --- .../src/main/java/com/trilead/ssh2/KnownHosts.java | 25 +++- .../trilead/ssh2/auth/AuthenticationManager.java | 36 ++++++ .../com/trilead/ssh2/crypto/key/Ed25519Key.java | 45 +++++++ .../trilead/ssh2/crypto/key/Ed25519PrivateKey.java | 35 ++++++ .../trilead/ssh2/crypto/key/Ed25519PublicKey.java | 35 ++++++ .../trilead/ssh2/signature/ECDSASHA2Verify.java | 13 ++ .../com/trilead/ssh2/signature/Ed25519Verify.java | 140 +++++++++++++++++++++ .../com/trilead/ssh2/transport/KexManager.java | 12 ++ 8 files changed, 338 insertions(+), 3 deletions(-) create mode 100644 sshlib/src/main/java/com/trilead/ssh2/crypto/key/Ed25519Key.java create mode 100644 sshlib/src/main/java/com/trilead/ssh2/crypto/key/Ed25519PrivateKey.java create mode 100644 sshlib/src/main/java/com/trilead/ssh2/crypto/key/Ed25519PublicKey.java create mode 100644 sshlib/src/main/java/com/trilead/ssh2/signature/Ed25519Verify.java (limited to 'sshlib/src/main/java') diff --git a/sshlib/src/main/java/com/trilead/ssh2/KnownHosts.java b/sshlib/src/main/java/com/trilead/ssh2/KnownHosts.java index 4e75de1..1a5f0a8 100644 --- a/sshlib/src/main/java/com/trilead/ssh2/KnownHosts.java +++ b/sshlib/src/main/java/com/trilead/ssh2/KnownHosts.java @@ -28,8 +28,10 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import com.trilead.ssh2.crypto.Base64; +import com.trilead.ssh2.crypto.key.Ed25519PublicKey; import com.trilead.ssh2.signature.DSASHA1Verify; import com.trilead.ssh2.signature.ECDSASHA2Verify; +import com.trilead.ssh2.signature.Ed25519Verify; import com.trilead.ssh2.signature.RSASHA1Verify; @@ -121,12 +123,22 @@ public class KnownHosts { ECPublicKey epk = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey); - synchronized (publicKeys) { + synchronized (publicKeys) + { publicKeys.add(new KnownHostsEntry(hostnames, epk)); } } + else if (Ed25519Verify.ED25519_ID.equals(serverHostKeyAlgorithm)) + { + Ed25519PublicKey edpk = Ed25519Verify.decodeSSHEd25519PublicKey(serverHostKey); + + synchronized (publicKeys) + { + publicKeys.add(new KnownHostsEntry(hostnames, edpk)); + } + } else - throw new IOException("Unknwon host key type (" + serverHostKeyAlgorithm + ")"); + throw new IOException("Unknown host key type (" + serverHostKeyAlgorithm + ")"); } /** @@ -604,6 +616,10 @@ public class KnownHosts { remoteKey = ECDSASHA2Verify.decodeSSHECDSAPublicKey(serverHostKey); } + else if (Ed25519Verify.ED25519_ID.equals(serverHostKeyAlgorithm)) + { + remoteKey = Ed25519Verify.decodeSSHEd25519PublicKey(serverHostKey); + } else throw new IllegalArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm); @@ -719,7 +735,10 @@ public class KnownHosts throw new IllegalArgumentException("Unknown hash type " + type); } - if (keyType.startsWith("ecdsa-sha2-")) + if (Ed25519Verify.ED25519_ID.equals(keyType)) + { + } + else if (keyType.startsWith("ecdsa-sha2-")) { } else if ("ssh-rsa".equals(keyType)) diff --git a/sshlib/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java b/sshlib/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java index e551495..dfafcbd 100644 --- a/sshlib/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java +++ b/sshlib/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java @@ -15,6 +15,8 @@ import java.util.Vector; import com.trilead.ssh2.InteractiveCallback; import com.trilead.ssh2.crypto.PEMDecoder; +import com.trilead.ssh2.crypto.key.Ed25519PrivateKey; +import com.trilead.ssh2.crypto.key.Ed25519PublicKey; import com.trilead.ssh2.packets.PacketServiceAccept; import com.trilead.ssh2.packets.PacketServiceRequest; import com.trilead.ssh2.packets.PacketUserauthBanner; @@ -29,6 +31,7 @@ import com.trilead.ssh2.packets.Packets; import com.trilead.ssh2.packets.TypesWriter; import com.trilead.ssh2.signature.DSASHA1Verify; import com.trilead.ssh2.signature.ECDSASHA2Verify; +import com.trilead.ssh2.signature.Ed25519Verify; import com.trilead.ssh2.signature.RSASHA1Verify; import com.trilead.ssh2.transport.MessageHandler; import com.trilead.ssh2.transport.TransportManager; @@ -276,6 +279,39 @@ public class AuthenticationManager implements MessageHandler tm.sendMessage(ua.getPayload()); } + else if (key instanceof Ed25519PrivateKey) + { + Ed25519PrivateKey pk = (Ed25519PrivateKey) key; + + final String algo = Ed25519Verify.ED25519_ID; + + byte[] pk_enc = Ed25519Verify.encodeSSHEd25519PublicKey((Ed25519PublicKey) pair.getPublic()); + + TypesWriter tw = new TypesWriter(); + { + byte[] H = tm.getSessionIdentifier(); + + tw.writeString(H, 0, H.length); + tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST); + tw.writeString(user); + tw.writeString("ssh-connection"); + tw.writeString("publickey"); + tw.writeBoolean(true); + tw.writeString(algo); + tw.writeString(pk_enc, 0, pk_enc.length); + } + + byte[] msg = tw.getBytes(); + + byte[] ds = Ed25519Verify.generateSignature(msg, pk); + + byte[] ed_sig_enc = Ed25519Verify.encodeSSHEd25519Signature(ds); + + PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user, + algo, pk_enc, ed_sig_enc); + + tm.sendMessage(ua.getPayload()); + } else { throw new IOException("Unknown private key type returned by the PEM decoder."); diff --git a/sshlib/src/main/java/com/trilead/ssh2/crypto/key/Ed25519Key.java b/sshlib/src/main/java/com/trilead/ssh2/crypto/key/Ed25519Key.java new file mode 100644 index 0000000..5c9e549 --- /dev/null +++ b/sshlib/src/main/java/com/trilead/ssh2/crypto/key/Ed25519Key.java @@ -0,0 +1,45 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2015 Kenny Root + * + * 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 com.trilead.ssh2.crypto.key; + +import java.security.Key; + +/** + * Java representation of a native Ed25519 key. + * + * @author Kenny Root + */ +public class Ed25519Key implements Key { + private final byte[] keyBytes; + + protected Ed25519Key(byte[] keyBytes) { + this.keyBytes = keyBytes; + } + + public String getAlgorithm() { + return "Ed25519"; + } + + public String getFormat() { + return "RAW"; + } + + public byte[] getEncoded() { + return keyBytes; + } +} diff --git a/sshlib/src/main/java/com/trilead/ssh2/crypto/key/Ed25519PrivateKey.java b/sshlib/src/main/java/com/trilead/ssh2/crypto/key/Ed25519PrivateKey.java new file mode 100644 index 0000000..05ab4ee --- /dev/null +++ b/sshlib/src/main/java/com/trilead/ssh2/crypto/key/Ed25519PrivateKey.java @@ -0,0 +1,35 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2015 Kenny Root + * + * 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 com.trilead.ssh2.crypto.key; + +import java.security.PrivateKey; + +/** + * Java representation of a native Ed25519 key. + * + * @author Kenny Root + */ +public class Ed25519PrivateKey extends Ed25519Key implements PrivateKey { + private Ed25519PrivateKey(byte[] keyBytes) { + super(keyBytes); + } + + public static Ed25519PrivateKey getInstance(byte[] keyBytes) { + return new Ed25519PrivateKey(keyBytes); + } +} diff --git a/sshlib/src/main/java/com/trilead/ssh2/crypto/key/Ed25519PublicKey.java b/sshlib/src/main/java/com/trilead/ssh2/crypto/key/Ed25519PublicKey.java new file mode 100644 index 0000000..a4e2092 --- /dev/null +++ b/sshlib/src/main/java/com/trilead/ssh2/crypto/key/Ed25519PublicKey.java @@ -0,0 +1,35 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2015 Kenny Root, Jeffrey Sharkey + * + * 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 com.trilead.ssh2.crypto.key; + +import java.security.PublicKey; + +/** + * Java representation of a native Ed25519 key. + * + * @author Kenny Root + */ +public class Ed25519PublicKey extends Ed25519Key implements PublicKey { + private Ed25519PublicKey(byte[] keyBytes) { + super(keyBytes); + } + + public static Ed25519PublicKey getInstance(byte[] keyBytes) { + return new Ed25519PublicKey(keyBytes); + } +} diff --git a/sshlib/src/main/java/com/trilead/ssh2/signature/ECDSASHA2Verify.java b/sshlib/src/main/java/com/trilead/ssh2/signature/ECDSASHA2Verify.java index 89cacfb..638a7d3 100644 --- a/sshlib/src/main/java/com/trilead/ssh2/signature/ECDSASHA2Verify.java +++ b/sshlib/src/main/java/com/trilead/ssh2/signature/ECDSASHA2Verify.java @@ -1,5 +1,18 @@ /** + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2014 Kenny Root * + * 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 com.trilead.ssh2.signature; diff --git a/sshlib/src/main/java/com/trilead/ssh2/signature/Ed25519Verify.java b/sshlib/src/main/java/com/trilead/ssh2/signature/Ed25519Verify.java new file mode 100644 index 0000000..e6dd036 --- /dev/null +++ b/sshlib/src/main/java/com/trilead/ssh2/signature/Ed25519Verify.java @@ -0,0 +1,140 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2015 Kenny Root + * + * 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 com.trilead.ssh2.signature; + +import com.trilead.ssh2.crypto.key.Ed25519PrivateKey; +import com.trilead.ssh2.crypto.key.Ed25519PublicKey; +import com.trilead.ssh2.log.Logger; +import com.trilead.ssh2.packets.TypesReader; +import com.trilead.ssh2.packets.TypesWriter; +import net.i2p.crypto.eddsa.EdDSAEngine; +import net.i2p.crypto.eddsa.EdDSAPrivateKey; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; +import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; +import net.vrallev.java.ecc.Ecc25519Helper; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; + +/** + * @author Kenny Root + */ +public class Ed25519Verify { + private static final Logger log = Logger.getLogger(Ed25519Verify.class); + + /** Identifies this as an Ed25519 key in the protocol. */ + public static final String ED25519_ID = "ssh-ed25519"; + + private static final int ED25519_PK_SIZE_BYTES = 32; + private static final int ED25519_SIG_SIZE_BYTES = 64; + + public static byte[] encodeSSHEd25519PublicKey(Ed25519PublicKey key) { + TypesWriter tw = new TypesWriter(); + + tw.writeString(ED25519_ID); + tw.writeBytes(key.getEncoded()); + + return tw.getBytes(); + } + + public static Ed25519PublicKey decodeSSHEd25519PublicKey(byte[] key) throws IOException { + TypesReader tr = new TypesReader(key); + + String key_format = tr.readString(); + if (key_format.equals(ED25519_ID) == false) { + throw new IOException("This is not an Ed25519 key"); + } + + byte[] keyBytes = tr.readByteString(); + + if (tr.remain() != 0) { + throw new IOException("Padding in Ed25519 public key! " + tr.remain() + " bytes left."); + } + + if (keyBytes.length != ED25519_PK_SIZE_BYTES) { + throw new IOException("Ed25519 was not of correct length: " + keyBytes.length + " vs " + ED25519_PK_SIZE_BYTES); + } + + return Ed25519PublicKey.getInstance(keyBytes); + } + + public static byte[] generateSignature(byte[] msg, Ed25519PrivateKey privateKey) throws IOException { + byte[] privateKeyBytes = privateKey.getEncoded(); + + EdDSANamedCurveSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512); + EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(privateKeyBytes, spec); + + try { + EdDSAEngine engine = new EdDSAEngine(MessageDigest.getInstance("SHA-512")); + engine.initSign(new EdDSAPrivateKey(privKeySpec)); + engine.update(msg); + return engine.sign(); + } catch (NoSuchAlgorithmException e) { + throw new IOException(e); + } catch (SignatureException e) { + throw new IOException(e); + } catch (InvalidKeyException e) { + throw new IOException(e); + } + } + + public static boolean verifySignature(byte[] msg, byte[] sig, Ed25519PublicKey publicKey) throws IOException { + byte[] publicKeyBytes = publicKey.getEncoded(); + if (publicKeyBytes.length != ED25519_PK_SIZE_BYTES) { + throw new IOException("Invalid Ed25519 key length " + publicKeyBytes.length); + } + Ecc25519Helper helper = new Ecc25519Helper(); + return helper.isValidSignature(msg, sig, publicKeyBytes); + } + + public static byte[] encodeSSHEd25519Signature(byte[] sig) { + TypesWriter tw = new TypesWriter(); + + tw.writeString(ED25519_ID); + tw.writeBytes(sig); + + return tw.getBytes(); + } + + public static byte[] decodeSSHEd25519Signature(byte[] sig) throws IOException { + byte[] rsArray; + + TypesReader tr = new TypesReader(sig); + + String sig_format = tr.readString(); + if (sig_format.equals(ED25519_ID) == false) { + throw new IOException("Peer sent wrong signature format"); + } + + rsArray = tr.readByteString(); + + if (tr.remain() != 0) { + throw new IOException("Padding in Ed25519 signature!"); + } + + if (rsArray.length > ED25519_SIG_SIZE_BYTES) { + throw new IOException("Ed25519 signature was " + rsArray.length + " bytes (" + ED25519_PK_SIZE_BYTES + " expected)"); + } + + return rsArray; + } +} diff --git a/sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java b/sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java index 741268b..ab6d0b6 100644 --- a/sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java +++ b/sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java @@ -25,6 +25,7 @@ import com.trilead.ssh2.crypto.dh.Curve25519Exchange; import com.trilead.ssh2.crypto.dh.DhGroupExchange; import com.trilead.ssh2.crypto.dh.GenericDhExchange; import com.trilead.ssh2.crypto.digest.MAC; +import com.trilead.ssh2.crypto.key.Ed25519PublicKey; import com.trilead.ssh2.log.Logger; import com.trilead.ssh2.packets.PacketKexDHInit; import com.trilead.ssh2.packets.PacketKexDHReply; @@ -38,6 +39,7 @@ import com.trilead.ssh2.packets.PacketNewKeys; import com.trilead.ssh2.packets.Packets; import com.trilead.ssh2.signature.DSASHA1Verify; import com.trilead.ssh2.signature.ECDSASHA2Verify; +import com.trilead.ssh2.signature.Ed25519Verify; import com.trilead.ssh2.signature.RSASHA1Verify; @@ -65,6 +67,7 @@ public class KexManager private static final Set HOSTKEY_ALGS = new LinkedHashSet(); static { + HOSTKEY_ALGS.add(Ed25519Verify.ED25519_ID); if (supportsEc) { HOSTKEY_ALGS.add("ecdsa-sha2-nistp256"); HOSTKEY_ALGS.add("ecdsa-sha2-nistp384"); @@ -380,6 +383,15 @@ public class KexManager private boolean verifySignature(byte[] sig, byte[] hostkey) throws IOException { + if (kxs.np.server_host_key_algo.equals(Ed25519Verify.ED25519_ID)) { + byte[] eds = Ed25519Verify.decodeSSHEd25519Signature(sig); + Ed25519PublicKey edpk = Ed25519Verify.decodeSSHEd25519PublicKey(hostkey); + + log.log(50, "Verifying ed25519 signature"); + + return Ed25519Verify.verifySignature(kxs.H, eds, edpk); + + } if (kxs.np.server_host_key_algo.startsWith("ecdsa-sha2-")) { byte[] rs = ECDSASHA2Verify.decodeSSHECDSASignature(sig); -- cgit v1.2.3