diff --git a/build.gradle b/build.gradle index 913ba998..90da0318 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,6 @@ dependencies { implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion" implementation "com.hierynomus:asn-one:0.6.0" - implementation "net.i2p.crypto:eddsa:0.3.0" } license { @@ -182,8 +181,6 @@ jar { instruction "Import-Package", "!net.schmizz.*" instruction "Import-Package", "!com.hierynomus.sshj.*" instruction "Import-Package", "javax.crypto*" - instruction "Import-Package", "!net.i2p.crypto.eddsa.math" - instruction "Import-Package", "net.i2p*" instruction "Import-Package", "com.jcraft.jzlib*;version=\"[1.1,2)\";resolution:=optional" instruction "Import-Package", "org.slf4j*;version=\"[1.7,5)\"" instruction "Import-Package", "org.bouncycastle*;resolution:=optional" diff --git a/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java b/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java deleted file mode 100644 index f26b580c..00000000 --- a/src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 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. - */ -@SuppressWarnings("serial") -public class Ed25519PublicKey extends EdDSAPublicKey { - - public Ed25519PublicKey(EdDSAPublicKeySpec spec) { - super(spec); - - EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519"); - 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 index 5f460b33..01622606 100644 --- a/src/main/java/com/hierynomus/sshj/signature/SignatureEdDSA.java +++ b/src/main/java/com/hierynomus/sshj/signature/SignatureEdDSA.java @@ -15,14 +15,14 @@ */ package com.hierynomus.sshj.signature; -import net.i2p.crypto.eddsa.EdDSAEngine; import net.schmizz.sshj.common.KeyType; import net.schmizz.sshj.common.SSHRuntimeException; +import net.schmizz.sshj.common.SecurityUtils; import net.schmizz.sshj.signature.AbstractSignature; import net.schmizz.sshj.signature.Signature; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.SignatureException; public class SignatureEdDSA extends AbstractSignature { @@ -43,11 +43,11 @@ public class SignatureEdDSA extends AbstractSignature { super(getEngine(), KeyType.ED25519.toString()); } - private static EdDSAEngine getEngine() { + private static java.security.Signature getEngine() { try { - return new EdDSAEngine(MessageDigest.getInstance("SHA-512")); - } catch (NoSuchAlgorithmException e) { - throw new SSHRuntimeException(e); + return SecurityUtils.getSignature("Ed25519"); + } catch (NoSuchAlgorithmException | NoSuchProviderException e) { + throw new SSHRuntimeException("Ed25519 Signatures not supported", e); } } diff --git a/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java b/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java index 3fc30ba8..568649a3 100644 --- a/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java +++ b/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java @@ -21,9 +21,6 @@ import com.hierynomus.sshj.transport.cipher.BlockCiphers; import com.hierynomus.sshj.transport.cipher.ChachaPolyCiphers; import com.hierynomus.sshj.transport.cipher.GcmCiphers; import com.hierynomus.sshj.userauth.keyprovider.bcrypt.BCrypt; -import net.i2p.crypto.eddsa.EdDSAPrivateKey; -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; -import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; import net.schmizz.sshj.common.*; import net.schmizz.sshj.common.Buffer.PlainBuffer; import net.schmizz.sshj.transport.cipher.Cipher; @@ -351,8 +348,14 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider { keyBuffer.readUInt32(); // length of privatekey+publickey byte[] privKey = new byte[32]; keyBuffer.readRawBytes(privKey); // string privatekey - keyBuffer.readRawBytes(new byte[32]); // string publickey (again...) - kp = new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519")))); + + final byte[] pubKey = new byte[32]; + keyBuffer.readRawBytes(pubKey); // string publickey (again...) + + final PrivateKey edPrivateKey = Ed25519KeyFactory.getPrivateKey(privKey); + final PublicKey edPublicKey = Ed25519KeyFactory.getPublicKey(pubKey); + + kp = new KeyPair(edPublicKey, edPrivateKey); break; case RSA: final RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec = readRsaPrivateKeySpec(keyBuffer); diff --git a/src/main/java/net/schmizz/sshj/common/Ed25519KeyFactory.java b/src/main/java/net/schmizz/sshj/common/Ed25519KeyFactory.java new file mode 100644 index 00000000..5ca77130 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/common/Ed25519KeyFactory.java @@ -0,0 +1,90 @@ +/* + * 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.common; + +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.Objects; + +/** + * Factory for generating Edwards-curve 25519 Public and Private Keys + */ +public class Ed25519KeyFactory { + private static final int KEY_LENGTH = 32; + + private static final String KEY_ALGORITHM = "Ed25519"; + + private static final byte[] ED25519_PKCS8_PRIVATE_KEY_HEADER = Base64.getDecoder().decode("MC4CAQEwBQYDK2VwBCIEIA"); + + private static final byte[] ED25519_PKCS8_PUBLIC_KEY_HEADER = Base64.getDecoder().decode("MCowBQYDK2VwAyEA"); + + private static final int PRIVATE_KEY_ENCODED_LENGTH = 48; + + private static final int PUBLIC_KEY_ENCODED_LENGTH = 44; + + private Ed25519KeyFactory() { + + } + + /** + * Get Edwards-curve Private Key for private key binary + * + * @param privateKeyBinary Private Key byte array consisting of 32 bytes + * @return Edwards-curve 25519 Private Key + * @throws GeneralSecurityException Thrown on failure to generate Private Key + */ + public static PrivateKey getPrivateKey(final byte[] privateKeyBinary) throws GeneralSecurityException { + Objects.requireNonNull(privateKeyBinary, "Private Key byte array required"); + if (privateKeyBinary.length == KEY_LENGTH) { + final byte[] privateKeyEncoded = new byte[PRIVATE_KEY_ENCODED_LENGTH]; + System.arraycopy(ED25519_PKCS8_PRIVATE_KEY_HEADER, 0, privateKeyEncoded, 0, ED25519_PKCS8_PRIVATE_KEY_HEADER.length); + System.arraycopy(privateKeyBinary, 0, privateKeyEncoded, ED25519_PKCS8_PRIVATE_KEY_HEADER.length, KEY_LENGTH); + final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyEncoded); + + final KeyFactory keyFactory = SecurityUtils.getKeyFactory(KEY_ALGORITHM); + return keyFactory.generatePrivate(keySpec); + } else { + throw new IllegalArgumentException("Key length of 32 bytes required"); + } + } + + /** + * Get Edwards-curve Public Key for public key binary + * + * @param publicKeyBinary Public Key byte array consisting of 32 bytes + * @return Edwards-curve 25519 Public Key + * @throws GeneralSecurityException Thrown on failure to generate Public Key + */ + public static PublicKey getPublicKey(final byte[] publicKeyBinary) throws GeneralSecurityException { + Objects.requireNonNull(publicKeyBinary, "Public Key byte array required"); + if (publicKeyBinary.length == KEY_LENGTH) { + final byte[] publicKeyEncoded = new byte[PUBLIC_KEY_ENCODED_LENGTH]; + System.arraycopy(ED25519_PKCS8_PUBLIC_KEY_HEADER, 0, publicKeyEncoded, 0, ED25519_PKCS8_PUBLIC_KEY_HEADER.length); + System.arraycopy(publicKeyBinary, 0, publicKeyEncoded, ED25519_PKCS8_PUBLIC_KEY_HEADER.length, KEY_LENGTH); + final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyEncoded); + + final KeyFactory keyFactory = SecurityUtils.getKeyFactory(KEY_ALGORITHM); + return keyFactory.generatePublic(keySpec); + } else { + throw new IllegalArgumentException("Key length of 32 bytes required"); + } + } +} diff --git a/src/main/java/net/schmizz/sshj/common/KeyType.java b/src/main/java/net/schmizz/sshj/common/KeyType.java index b34ebbe4..79a3801e 100644 --- a/src/main/java/net/schmizz/sshj/common/KeyType.java +++ b/src/main/java/net/schmizz/sshj/common/KeyType.java @@ -16,13 +16,8 @@ package net.schmizz.sshj.common; import com.hierynomus.sshj.common.KeyAlgorithm; -import com.hierynomus.sshj.signature.Ed25519PublicKey; import com.hierynomus.sshj.signature.SignatureEdDSA; import com.hierynomus.sshj.userauth.certificate.Certificate; -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.Buffer.BufferException; import net.schmizz.sshj.signature.Signature; import net.schmizz.sshj.signature.SignatureDSA; @@ -178,20 +173,16 @@ public enum KeyType { public PublicKey readPubKeyFromBuffer(Buffer buf) throws GeneralSecurityException { try { final int keyLen = buf.readUInt32AsInt(); - final byte[] p = new byte[keyLen]; - buf.readRawBytes(p); + final byte[] publicKeyBinary = new byte[keyLen]; + buf.readRawBytes(publicKeyBinary); if (log.isDebugEnabled()) { log.debug(String.format("Key algo: %s, Key curve: 25519, Key Len: %s\np: %s", sType, keyLen, - Arrays.toString(p)) + Arrays.toString(publicKeyBinary)) ); } - - EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519"); - EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(p, ed25519); - return new Ed25519PublicKey(publicSpec); - + return Ed25519KeyFactory.getPublicKey(publicKeyBinary); } catch (Buffer.BufferException be) { throw new SSHRuntimeException(be); } @@ -199,13 +190,17 @@ public enum KeyType { @Override protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer buf) { - EdDSAPublicKey key = (EdDSAPublicKey) pk; - buf.putBytes(key.getAbyte()); + final byte[] encoded = pk.getEncoded(); + final int keyLength = 32; + final int headerLength = encoded.length - keyLength; + final byte[] encodedPublicKey = new byte[keyLength]; + System.arraycopy(encoded, headerLength, encodedPublicKey, 0, keyLength); + buf.putBytes(encodedPublicKey); } @Override protected boolean isMyType(Key key) { - return "EdDSA".equals(key.getAlgorithm()); + return "EdDSA".equals(key.getAlgorithm()) || "Ed25519".equals(key.getAlgorithm()); } }, diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java index feaa1c43..ef5133bc 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java @@ -16,12 +16,6 @@ package net.schmizz.sshj.userauth.keyprovider; import com.hierynomus.sshj.common.KeyAlgorithm; -import net.i2p.crypto.eddsa.EdDSAPrivateKey; -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.EdDSAPrivateKeySpec; -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; import net.schmizz.sshj.common.*; import net.schmizz.sshj.userauth.password.PasswordUtils; import org.bouncycastle.crypto.generators.Argon2BytesGenerator; @@ -165,10 +159,17 @@ public class PuTTYKeyFile extends BaseFileKeyProvider { } } if (KeyType.ED25519.equals(keyType)) { - EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519"); - EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(publicKeyReader.readBytes(), ed25519); - EdDSAPrivateKeySpec privateSpec = new EdDSAPrivateKeySpec(privateKeyReader.readBytes(), ed25519); - return new KeyPair(new EdDSAPublicKey(publicSpec), new EdDSAPrivateKey(privateSpec)); + try { + final byte[] publicKeyEncoded = publicKeyReader.readBytes(); + final PublicKey edPublicKey = Ed25519KeyFactory.getPublicKey(publicKeyEncoded); + + final byte[] privateKeyEncoded = privateKeyReader.readBytes(); + final PrivateKey edPrivateKey = Ed25519KeyFactory.getPrivateKey(privateKeyEncoded); + + return new KeyPair(edPublicKey, edPrivateKey); + } catch (final GeneralSecurityException e) { + throw new IOException("Reading Ed25519 Keys failed", e); + } } final ECDSACurve ecdsaCurve; switch (keyType) { diff --git a/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java b/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java index 3268e73c..b19beef7 100644 --- a/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java +++ b/src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java @@ -222,7 +222,7 @@ public class OpenSSHKeyFileTest { OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile(); keyFile.init(new File("src/test/resources/keytypes/test_ed25519")); PrivateKey aPrivate = keyFile.getPrivate(); - assertThat(aPrivate.getAlgorithm(), equalTo("EdDSA")); + assertThat(aPrivate.getAlgorithm(), equalTo("Ed25519")); } @Test @@ -343,7 +343,7 @@ public class OpenSSHKeyFileTest { WipeTrackingPasswordFinder pwf = new WipeTrackingPasswordFinder(password, withRetry); keyFile.init(new File(key), pwf); PrivateKey aPrivate = keyFile.getPrivate(); - assertThat(aPrivate.getAlgorithm(), equalTo("EdDSA")); + assertThat(aPrivate.getAlgorithm(), equalTo("Ed25519")); pwf.assertWiped(); }