diff --git a/src/main/java/com/hierynomus/sshj/common/KeyDecryptionFailedException.java b/src/main/java/com/hierynomus/sshj/common/KeyDecryptionFailedException.java index 436d3218..eae5fc16 100644 --- a/src/main/java/com/hierynomus/sshj/common/KeyDecryptionFailedException.java +++ b/src/main/java/com/hierynomus/sshj/common/KeyDecryptionFailedException.java @@ -15,8 +15,6 @@ */ package com.hierynomus.sshj.common; -import org.bouncycastle.openssl.EncryptionException; - import java.io.IOException; /** @@ -32,7 +30,7 @@ public class KeyDecryptionFailedException extends IOException { super(MESSAGE); } - public KeyDecryptionFailedException(EncryptionException cause) { + public KeyDecryptionFailedException(IOException cause) { super(MESSAGE, cause); } 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 5e4e3260..3fc30ba8 100644 --- a/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java +++ b/src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java @@ -31,10 +31,6 @@ import net.schmizz.sshj.userauth.keyprovider.BaseFileKeyProvider; import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; import net.schmizz.sshj.userauth.keyprovider.KeyFormat; import net.schmizz.sshj.userauth.password.PasswordFinder; -import org.bouncycastle.asn1.nist.NISTNamedCurves; -import org.bouncycastle.asn1.x9.X9ECParameters; -import org.bouncycastle.jce.spec.ECNamedCurveSpec; -import org.bouncycastle.openssl.EncryptionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,7 +43,6 @@ import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.spec.ECPrivateKeySpec; import java.security.spec.RSAPrivateCrtKeySpec; import java.util.Arrays; import java.util.HashMap; @@ -55,7 +50,7 @@ import java.util.Map; /** * Reads a key file in the new OpenSSH format. - * The format is described in the following document: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key + * The format is described in the following document: Key Protocol */ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider { private static final String BEGIN = "-----BEGIN "; @@ -244,7 +239,7 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider { cipher.update(privateKey, 0, privateKeyLength); } catch (final SSHRuntimeException e) { final String message = String.format("OpenSSH Private Key decryption failed with cipher [%s]", cipherName); - throw new KeyDecryptionFailedException(new EncryptionException(message, e)); + throw new KeyDecryptionFailedException(new IOException(message, e)); } final PlainBuffer decryptedPrivateKey = new PlainBuffer(privateKeyLength); decryptedPrivateKey.putRawBytes(privateKey, 0, privateKeyLength); @@ -343,7 +338,7 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider { int checkInt1 = keyBuffer.readUInt32AsInt(); // uint32 checkint1 int checkInt2 = keyBuffer.readUInt32AsInt(); // uint32 checkint2 if (checkInt1 != checkInt2) { - throw new KeyDecryptionFailedException(new EncryptionException("OpenSSH Private Key integer comparison failed")); + throw new KeyDecryptionFailedException(new IOException("OpenSSH Private Key integer comparison failed")); } // The private key section contains both the public key and the private key String keyType = keyBuffer.readString(); // string keytype @@ -365,13 +360,13 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider { kp = new KeyPair(publicKey, privateKey); break; case ECDSA256: - kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-256")); + kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, ECDSACurve.SECP256R1)); break; case ECDSA384: - kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-384")); + kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, ECDSACurve.SECP384R1)); break; case ECDSA521: - kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-521")); + kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, ECDSACurve.SECP521R1)); break; default: @@ -388,13 +383,10 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider { return kp; } - private PrivateKey createECDSAPrivateKey(KeyType kt, PlainBuffer buffer, String name) throws GeneralSecurityException, Buffer.BufferException { + private PrivateKey createECDSAPrivateKey(KeyType kt, PlainBuffer buffer, ECDSACurve ecdsaCurve) throws GeneralSecurityException, Buffer.BufferException { kt.readPubKeyFromBuffer(buffer); // Public key - BigInteger s = new BigInteger(1, buffer.readBytes()); - X9ECParameters ecParams = NISTNamedCurves.getByName(name); - ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN()); - ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec); - return SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks); + final BigInteger s = new BigInteger(1, buffer.readBytes()); + return ECDSAKeyFactory.getPrivateKey(s, ecdsaCurve); } /** diff --git a/src/main/java/net/schmizz/sshj/common/ECDSACurve.java b/src/main/java/net/schmizz/sshj/common/ECDSACurve.java new file mode 100644 index 00000000..78cf25dc --- /dev/null +++ b/src/main/java/net/schmizz/sshj/common/ECDSACurve.java @@ -0,0 +1,45 @@ +/* + * 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; + +/** + * Enumeration of supported ECDSA Curves with corresponding algorithm parameter names + */ +public enum ECDSACurve { + /** NIST P-256 */ + SECP256R1("secp256r1"), + + /** NIST P-384 */ + SECP384R1("secp384r1"), + + /** NIST P-521 */ + SECP521R1("secp521r1"); + + private final String curveName; + + ECDSACurve(final String curveName) { + this.curveName = curveName; + } + + /** + * Get Curve Name for use with Java Cryptography Architecture components + * + * @return Curve Name + */ + public String getCurveName() { + return curveName; + } +} diff --git a/src/main/java/net/schmizz/sshj/common/ECDSAKeyFactory.java b/src/main/java/net/schmizz/sshj/common/ECDSAKeyFactory.java new file mode 100644 index 00000000..1546bc31 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/common/ECDSAKeyFactory.java @@ -0,0 +1,86 @@ +/* + * 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 com.hierynomus.sshj.common.KeyAlgorithm; + +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.util.Objects; + +/** + * Factory for generating Elliptic Curve Keys using Java Security components for NIST Curves + */ +public class ECDSAKeyFactory { + + private ECDSAKeyFactory() { + + } + + /** + * Get Elliptic Curve Private Key for private key value and Curve Name + * + * @param privateKeyInteger Private Key + * @param ecdsaCurve Elliptic Curve + * @return Elliptic Curve Private Key + * @throws GeneralSecurityException Thrown on failure to create parameter specification + */ + public static PrivateKey getPrivateKey(final BigInteger privateKeyInteger, final ECDSACurve ecdsaCurve) throws GeneralSecurityException { + Objects.requireNonNull(privateKeyInteger, "Private Key integer required"); + Objects.requireNonNull(ecdsaCurve, "Curve required"); + + final ECParameterSpec parameterSpec = getParameterSpec(ecdsaCurve); + final ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(privateKeyInteger, parameterSpec); + + final KeyFactory keyFactory = SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA); + return keyFactory.generatePrivate(privateKeySpec); + } + + /** + * Get Elliptic Curve Public Key for public key value and Curve Name + * + * @param point Public Key point + * @param ecdsaCurve Elliptic Curve + * @return Elliptic Curve Public Key + * @throws GeneralSecurityException Thrown on failure to create parameter specification + */ + public static PublicKey getPublicKey(final ECPoint point, final ECDSACurve ecdsaCurve) throws GeneralSecurityException { + Objects.requireNonNull(point, "Elliptic Curve Point required"); + Objects.requireNonNull(ecdsaCurve, "Curve required"); + + final ECParameterSpec parameterSpec = getParameterSpec(ecdsaCurve); + final ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(point, parameterSpec); + + final KeyFactory keyFactory = SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA); + return keyFactory.generatePublic(publicKeySpec); + } + + private static ECParameterSpec getParameterSpec(final ECDSACurve ecdsaCurve) throws GeneralSecurityException { + final ECGenParameterSpec genParameterSpec = new ECGenParameterSpec(ecdsaCurve.getCurveName()); + final AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance(KeyAlgorithm.EC_KEYSTORE); + algorithmParameters.init(genParameterSpec); + return algorithmParameters.getParameterSpec(ECParameterSpec.class); + } +} diff --git a/src/main/java/net/schmizz/sshj/common/ECDSAVariationsAdapter.java b/src/main/java/net/schmizz/sshj/common/ECDSAVariationsAdapter.java index 6636ea93..a996ce12 100644 --- a/src/main/java/net/schmizz/sshj/common/ECDSAVariationsAdapter.java +++ b/src/main/java/net/schmizz/sshj/common/ECDSAVariationsAdapter.java @@ -17,21 +17,16 @@ package net.schmizz.sshj.common; import com.hierynomus.sshj.common.KeyAlgorithm; import com.hierynomus.sshj.secg.SecgUtils; -import org.bouncycastle.asn1.nist.NISTNamedCurves; -import org.bouncycastle.asn1.x9.X9ECParameters; -import org.bouncycastle.jce.spec.ECNamedCurveSpec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.Key; -import java.security.KeyFactory; import java.security.PublicKey; import java.security.interfaces.ECKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -42,13 +37,13 @@ class ECDSAVariationsAdapter { private final static Logger log = LoggerFactory.getLogger(ECDSAVariationsAdapter.class); - public final static Map SUPPORTED_CURVES = new HashMap(); - public final static Map NIST_CURVES_NAMES = new HashMap(); + public final static Map SUPPORTED_CURVES = new HashMap<>(); + public final static Map NIST_CURVES = new HashMap<>(); static { - NIST_CURVES_NAMES.put("256", "p-256"); - NIST_CURVES_NAMES.put("384", "p-384"); - NIST_CURVES_NAMES.put("521", "p-521"); + NIST_CURVES.put("256", ECDSACurve.SECP256R1); + NIST_CURVES.put("384", ECDSACurve.SECP384R1); + NIST_CURVES.put("521", ECDSACurve.SECP521R1); SUPPORTED_CURVES.put("256", "nistp256"); SUPPORTED_CURVES.put("384", "nistp384"); @@ -72,21 +67,15 @@ class ECDSAVariationsAdapter { algorithm, curveName, keyLen, x04, Arrays.toString(x), Arrays.toString(y))); } - if (!SUPPORTED_CURVES.values().contains(curveName)) { + if (!SUPPORTED_CURVES.containsValue(curveName)) { throw new GeneralSecurityException(String.format("Unknown curve %s", curveName)); } - BigInteger bigX = new BigInteger(1, x); - BigInteger bigY = new BigInteger(1, y); - - String name = NIST_CURVES_NAMES.get(variation); - X9ECParameters ecParams = NISTNamedCurves.getByName(name); - ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN()); - ECPoint p = new ECPoint(bigX, bigY); - ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(p, ecCurveSpec); - - KeyFactory keyFactory = KeyFactory.getInstance(KeyAlgorithm.ECDSA); - return keyFactory.generatePublic(publicKeySpec); + final BigInteger bigX = new BigInteger(1, x); + final BigInteger bigY = new BigInteger(1, y); + final ECPoint point = new ECPoint(bigX, bigY); + final ECDSACurve ecdsaCurve = NIST_CURVES.get(variation); + return ECDSAKeyFactory.getPublicKey(point, ecdsaCurve); } catch (Exception ex) { throw new GeneralSecurityException(ex); } @@ -96,7 +85,7 @@ class ECDSAVariationsAdapter { final ECPublicKey ecdsa = (ECPublicKey) pk; byte[] encoded = SecgUtils.getEncoded(ecdsa.getW(), ecdsa.getParams().getCurve()); - buf.putString("nistp" + Integer.toString(fieldSizeFromKey(ecdsa))) + buf.putString("nistp" + (fieldSizeFromKey(ecdsa))) .putBytes(encoded); } 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 444c222a..e2486d66 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java @@ -24,11 +24,8 @@ 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.asn1.nist.NISTNamedCurves; -import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.generators.Argon2BytesGenerator; import org.bouncycastle.crypto.params.Argon2Parameters; -import org.bouncycastle.jce.spec.ECNamedCurveSpec; import org.bouncycastle.util.encoders.Hex; import javax.crypto.Cipher; @@ -173,29 +170,26 @@ public class PuTTYKeyFile extends BaseFileKeyProvider { EdDSAPrivateKeySpec privateSpec = new EdDSAPrivateKeySpec(privateKeyReader.readBytes(), ed25519); return new KeyPair(new EdDSAPublicKey(publicSpec), new EdDSAPrivateKey(privateSpec)); } - final String ecdsaCurve; + final ECDSACurve ecdsaCurve; switch (keyType) { case ECDSA256: - ecdsaCurve = "P-256"; + ecdsaCurve = ECDSACurve.SECP256R1; break; case ECDSA384: - ecdsaCurve = "P-384"; + ecdsaCurve = ECDSACurve.SECP384R1; break; case ECDSA521: - ecdsaCurve = "P-521"; + ecdsaCurve = ECDSACurve.SECP521R1; break; default: ecdsaCurve = null; break; } if (ecdsaCurve != null) { - BigInteger s = new BigInteger(1, privateKeyReader.readBytes()); - X9ECParameters ecParams = NISTNamedCurves.getByName(ecdsaCurve); - ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(ecdsaCurve, ecParams.getCurve(), ecParams.getG(), - ecParams.getN()); - ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec); + final BigInteger s = new BigInteger(1, privateKeyReader.readBytes()); + try { - PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks); + final PrivateKey privateKey = ECDSAKeyFactory.getPrivateKey(s, ecdsaCurve); return new KeyPair(keyType.readPubKeyFromBuffer(publicKeyReader), privateKey); } catch (GeneralSecurityException e) { throw new IOException(e.getMessage(), e); diff --git a/src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java b/src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java index 3b1d5218..1c582296 100644 --- a/src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java +++ b/src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java @@ -425,7 +425,6 @@ public class PuTTYKeyFileTest { PKCS8KeyFile referenceKey = new PKCS8KeyFile(); referenceKey.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256")); - assertEquals(key.getPrivate(), referenceKey.getPrivate()); assertEquals(key.getPublic(), referenceKey.getPublic()); }