Skip to content

Commit cea67fe

Browse files
Removed eddsa library in favor of standard Java Security classes (#993)
- Bouncy Castle provides Ed25519 support using standard Java Security classes - Removed net.i2p.crypto:eddsa:0.3.0 dependency - Removed Ed25519PublicKey extension of EdDSAPublicKey class from eddsa library - Added Ed25519KeyFactory for generating Java PublicKey and PrivateKey objects from raw encoded key byte arrays - Refactored key parsing to use Ed25519KeyFactory - Refactored SignatureEdDSA to use Java Signature class with Ed25519 Co-authored-by: Jeroen van Erp <[email protected]>
1 parent b4bc696 commit cea67fe

File tree

8 files changed

+128
-98
lines changed

8 files changed

+128
-98
lines changed

build.gradle

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ dependencies {
5454
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
5555
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
5656
implementation "com.hierynomus:asn-one:0.6.0"
57-
implementation "net.i2p.crypto:eddsa:0.3.0"
5857
}
5958

6059
license {
@@ -182,8 +181,6 @@ jar {
182181
instruction "Import-Package", "!net.schmizz.*"
183182
instruction "Import-Package", "!com.hierynomus.sshj.*"
184183
instruction "Import-Package", "javax.crypto*"
185-
instruction "Import-Package", "!net.i2p.crypto.eddsa.math"
186-
instruction "Import-Package", "net.i2p*"
187184
instruction "Import-Package", "com.jcraft.jzlib*;version=\"[1.1,2)\";resolution:=optional"
188185
instruction "Import-Package", "org.slf4j*;version=\"[1.7,5)\""
189186
instruction "Import-Package", "org.bouncycastle*;resolution:=optional"

src/main/java/com/hierynomus/sshj/signature/Ed25519PublicKey.java

Lines changed: 0 additions & 56 deletions
This file was deleted.

src/main/java/com/hierynomus/sshj/signature/SignatureEdDSA.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
*/
1616
package com.hierynomus.sshj.signature;
1717

18-
import net.i2p.crypto.eddsa.EdDSAEngine;
1918
import net.schmizz.sshj.common.KeyType;
2019
import net.schmizz.sshj.common.SSHRuntimeException;
20+
import net.schmizz.sshj.common.SecurityUtils;
2121
import net.schmizz.sshj.signature.AbstractSignature;
2222
import net.schmizz.sshj.signature.Signature;
2323

24-
import java.security.MessageDigest;
2524
import java.security.NoSuchAlgorithmException;
25+
import java.security.NoSuchProviderException;
2626
import java.security.SignatureException;
2727

2828
public class SignatureEdDSA extends AbstractSignature {
@@ -43,11 +43,11 @@ public Signature create() {
4343
super(getEngine(), KeyType.ED25519.toString());
4444
}
4545

46-
private static EdDSAEngine getEngine() {
46+
private static java.security.Signature getEngine() {
4747
try {
48-
return new EdDSAEngine(MessageDigest.getInstance("SHA-512"));
49-
} catch (NoSuchAlgorithmException e) {
50-
throw new SSHRuntimeException(e);
48+
return SecurityUtils.getSignature("Ed25519");
49+
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
50+
throw new SSHRuntimeException("Ed25519 Signatures not supported", e);
5151
}
5252
}
5353

src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@
2121
import com.hierynomus.sshj.transport.cipher.ChachaPolyCiphers;
2222
import com.hierynomus.sshj.transport.cipher.GcmCiphers;
2323
import com.hierynomus.sshj.userauth.keyprovider.bcrypt.BCrypt;
24-
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
25-
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
26-
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
2724
import net.schmizz.sshj.common.*;
2825
import net.schmizz.sshj.common.Buffer.PlainBuffer;
2926
import net.schmizz.sshj.transport.cipher.Cipher;
@@ -351,8 +348,14 @@ private KeyPair readUnencrypted(final PlainBuffer keyBuffer, final PublicKey pub
351348
keyBuffer.readUInt32(); // length of privatekey+publickey
352349
byte[] privKey = new byte[32];
353350
keyBuffer.readRawBytes(privKey); // string privatekey
354-
keyBuffer.readRawBytes(new byte[32]); // string publickey (again...)
355-
kp = new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519"))));
351+
352+
final byte[] pubKey = new byte[32];
353+
keyBuffer.readRawBytes(pubKey); // string publickey (again...)
354+
355+
final PrivateKey edPrivateKey = Ed25519KeyFactory.getPrivateKey(privKey);
356+
final PublicKey edPublicKey = Ed25519KeyFactory.getPublicKey(pubKey);
357+
358+
kp = new KeyPair(edPublicKey, edPrivateKey);
356359
break;
357360
case RSA:
358361
final RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec = readRsaPrivateKeySpec(keyBuffer);
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (C)2009 - SSHJ Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package net.schmizz.sshj.common;
17+
18+
import java.security.GeneralSecurityException;
19+
import java.security.KeyFactory;
20+
import java.security.PrivateKey;
21+
import java.security.PublicKey;
22+
import java.security.spec.PKCS8EncodedKeySpec;
23+
import java.security.spec.X509EncodedKeySpec;
24+
import java.util.Base64;
25+
import java.util.Objects;
26+
27+
/**
28+
* Factory for generating Edwards-curve 25519 Public and Private Keys
29+
*/
30+
public class Ed25519KeyFactory {
31+
private static final int KEY_LENGTH = 32;
32+
33+
private static final String KEY_ALGORITHM = "Ed25519";
34+
35+
private static final byte[] ED25519_PKCS8_PRIVATE_KEY_HEADER = Base64.getDecoder().decode("MC4CAQEwBQYDK2VwBCIEIA");
36+
37+
private static final byte[] ED25519_PKCS8_PUBLIC_KEY_HEADER = Base64.getDecoder().decode("MCowBQYDK2VwAyEA");
38+
39+
private static final int PRIVATE_KEY_ENCODED_LENGTH = 48;
40+
41+
private static final int PUBLIC_KEY_ENCODED_LENGTH = 44;
42+
43+
private Ed25519KeyFactory() {
44+
45+
}
46+
47+
/**
48+
* Get Edwards-curve Private Key for private key binary
49+
*
50+
* @param privateKeyBinary Private Key byte array consisting of 32 bytes
51+
* @return Edwards-curve 25519 Private Key
52+
* @throws GeneralSecurityException Thrown on failure to generate Private Key
53+
*/
54+
public static PrivateKey getPrivateKey(final byte[] privateKeyBinary) throws GeneralSecurityException {
55+
Objects.requireNonNull(privateKeyBinary, "Private Key byte array required");
56+
if (privateKeyBinary.length == KEY_LENGTH) {
57+
final byte[] privateKeyEncoded = new byte[PRIVATE_KEY_ENCODED_LENGTH];
58+
System.arraycopy(ED25519_PKCS8_PRIVATE_KEY_HEADER, 0, privateKeyEncoded, 0, ED25519_PKCS8_PRIVATE_KEY_HEADER.length);
59+
System.arraycopy(privateKeyBinary, 0, privateKeyEncoded, ED25519_PKCS8_PRIVATE_KEY_HEADER.length, KEY_LENGTH);
60+
final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyEncoded);
61+
62+
final KeyFactory keyFactory = SecurityUtils.getKeyFactory(KEY_ALGORITHM);
63+
return keyFactory.generatePrivate(keySpec);
64+
} else {
65+
throw new IllegalArgumentException("Key length of 32 bytes required");
66+
}
67+
}
68+
69+
/**
70+
* Get Edwards-curve Public Key for public key binary
71+
*
72+
* @param publicKeyBinary Public Key byte array consisting of 32 bytes
73+
* @return Edwards-curve 25519 Public Key
74+
* @throws GeneralSecurityException Thrown on failure to generate Public Key
75+
*/
76+
public static PublicKey getPublicKey(final byte[] publicKeyBinary) throws GeneralSecurityException {
77+
Objects.requireNonNull(publicKeyBinary, "Public Key byte array required");
78+
if (publicKeyBinary.length == KEY_LENGTH) {
79+
final byte[] publicKeyEncoded = new byte[PUBLIC_KEY_ENCODED_LENGTH];
80+
System.arraycopy(ED25519_PKCS8_PUBLIC_KEY_HEADER, 0, publicKeyEncoded, 0, ED25519_PKCS8_PUBLIC_KEY_HEADER.length);
81+
System.arraycopy(publicKeyBinary, 0, publicKeyEncoded, ED25519_PKCS8_PUBLIC_KEY_HEADER.length, KEY_LENGTH);
82+
final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyEncoded);
83+
84+
final KeyFactory keyFactory = SecurityUtils.getKeyFactory(KEY_ALGORITHM);
85+
return keyFactory.generatePublic(keySpec);
86+
} else {
87+
throw new IllegalArgumentException("Key length of 32 bytes required");
88+
}
89+
}
90+
}

src/main/java/net/schmizz/sshj/common/KeyType.java

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,8 @@
1616
package net.schmizz.sshj.common;
1717

1818
import com.hierynomus.sshj.common.KeyAlgorithm;
19-
import com.hierynomus.sshj.signature.Ed25519PublicKey;
2019
import com.hierynomus.sshj.signature.SignatureEdDSA;
2120
import com.hierynomus.sshj.userauth.certificate.Certificate;
22-
import net.i2p.crypto.eddsa.EdDSAPublicKey;
23-
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
24-
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
25-
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
2621
import net.schmizz.sshj.common.Buffer.BufferException;
2722
import net.schmizz.sshj.signature.Signature;
2823
import net.schmizz.sshj.signature.SignatureDSA;
@@ -178,34 +173,34 @@ protected boolean isMyType(Key key) {
178173
public PublicKey readPubKeyFromBuffer(Buffer<?> buf) throws GeneralSecurityException {
179174
try {
180175
final int keyLen = buf.readUInt32AsInt();
181-
final byte[] p = new byte[keyLen];
182-
buf.readRawBytes(p);
176+
final byte[] publicKeyBinary = new byte[keyLen];
177+
buf.readRawBytes(publicKeyBinary);
183178
if (log.isDebugEnabled()) {
184179
log.debug(String.format("Key algo: %s, Key curve: 25519, Key Len: %s\np: %s",
185180
sType,
186181
keyLen,
187-
Arrays.toString(p))
182+
Arrays.toString(publicKeyBinary))
188183
);
189184
}
190-
191-
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
192-
EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(p, ed25519);
193-
return new Ed25519PublicKey(publicSpec);
194-
185+
return Ed25519KeyFactory.getPublicKey(publicKeyBinary);
195186
} catch (Buffer.BufferException be) {
196187
throw new SSHRuntimeException(be);
197188
}
198189
}
199190

200191
@Override
201192
protected void writePubKeyContentsIntoBuffer(PublicKey pk, Buffer<?> buf) {
202-
EdDSAPublicKey key = (EdDSAPublicKey) pk;
203-
buf.putBytes(key.getAbyte());
193+
final byte[] encoded = pk.getEncoded();
194+
final int keyLength = 32;
195+
final int headerLength = encoded.length - keyLength;
196+
final byte[] encodedPublicKey = new byte[keyLength];
197+
System.arraycopy(encoded, headerLength, encodedPublicKey, 0, keyLength);
198+
buf.putBytes(encodedPublicKey);
204199
}
205200

206201
@Override
207202
protected boolean isMyType(Key key) {
208-
return "EdDSA".equals(key.getAlgorithm());
203+
return "EdDSA".equals(key.getAlgorithm()) || "Ed25519".equals(key.getAlgorithm());
209204
}
210205
},
211206

src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,6 @@
1616
package net.schmizz.sshj.userauth.keyprovider;
1717

1818
import com.hierynomus.sshj.common.KeyAlgorithm;
19-
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
20-
import net.i2p.crypto.eddsa.EdDSAPublicKey;
21-
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
22-
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
23-
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
24-
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
2519
import net.schmizz.sshj.common.*;
2620
import net.schmizz.sshj.userauth.password.PasswordUtils;
2721
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
@@ -165,10 +159,17 @@ protected KeyPair readKeyPair() throws IOException {
165159
}
166160
}
167161
if (KeyType.ED25519.equals(keyType)) {
168-
EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName("Ed25519");
169-
EdDSAPublicKeySpec publicSpec = new EdDSAPublicKeySpec(publicKeyReader.readBytes(), ed25519);
170-
EdDSAPrivateKeySpec privateSpec = new EdDSAPrivateKeySpec(privateKeyReader.readBytes(), ed25519);
171-
return new KeyPair(new EdDSAPublicKey(publicSpec), new EdDSAPrivateKey(privateSpec));
162+
try {
163+
final byte[] publicKeyEncoded = publicKeyReader.readBytes();
164+
final PublicKey edPublicKey = Ed25519KeyFactory.getPublicKey(publicKeyEncoded);
165+
166+
final byte[] privateKeyEncoded = privateKeyReader.readBytes();
167+
final PrivateKey edPrivateKey = Ed25519KeyFactory.getPrivateKey(privateKeyEncoded);
168+
169+
return new KeyPair(edPublicKey, edPrivateKey);
170+
} catch (final GeneralSecurityException e) {
171+
throw new IOException("Reading Ed25519 Keys failed", e);
172+
}
172173
}
173174
final ECDSACurve ecdsaCurve;
174175
switch (keyType) {

src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ public void shouldLoadED25519PrivateKey() throws IOException {
222222
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
223223
keyFile.init(new File("src/test/resources/keytypes/test_ed25519"));
224224
PrivateKey aPrivate = keyFile.getPrivate();
225-
assertThat(aPrivate.getAlgorithm(), equalTo("EdDSA"));
225+
assertThat(aPrivate.getAlgorithm(), equalTo("Ed25519"));
226226
}
227227

228228
@Test
@@ -343,7 +343,7 @@ private void checkOpenSSHKeyV1(String key, final String password, boolean withRe
343343
WipeTrackingPasswordFinder pwf = new WipeTrackingPasswordFinder(password, withRetry);
344344
keyFile.init(new File(key), pwf);
345345
PrivateKey aPrivate = keyFile.getPrivate();
346-
assertThat(aPrivate.getAlgorithm(), equalTo("EdDSA"));
346+
assertThat(aPrivate.getAlgorithm(), equalTo("Ed25519"));
347347
pwf.assertWiped();
348348
}
349349

0 commit comments

Comments
 (0)