diff --git a/src/main/java/com/hierynomus/sshj/common/KeyDecryptionFailedException.java b/src/main/java/com/hierynomus/sshj/common/KeyDecryptionFailedException.java index eae5fc16..a8bc8cff 100644 --- a/src/main/java/com/hierynomus/sshj/common/KeyDecryptionFailedException.java +++ b/src/main/java/com/hierynomus/sshj/common/KeyDecryptionFailedException.java @@ -26,8 +26,12 @@ public class KeyDecryptionFailedException extends IOException { public static final String MESSAGE = "Decryption of the key failed. A supplied passphrase may be incorrect."; - public KeyDecryptionFailedException() { - super(MESSAGE); + public KeyDecryptionFailedException(final String message) { + super(message); + } + + public KeyDecryptionFailedException(final String message, final Throwable cause) { + super(message, cause); } public KeyDecryptionFailedException(IOException cause) { diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/EncryptedPEMKeyReader.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/EncryptedPEMKeyReader.java new file mode 100644 index 00000000..20333e10 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/EncryptedPEMKeyReader.java @@ -0,0 +1,149 @@ +/* + * 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.userauth.keyprovider; + +import com.hierynomus.sshj.common.KeyDecryptionFailedException; +import net.schmizz.sshj.userauth.password.PasswordFinder; +import net.schmizz.sshj.userauth.password.PasswordUtils; +import net.schmizz.sshj.userauth.password.Resource; +import org.bouncycastle.openssl.PEMDecryptor; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMException; +import org.bouncycastle.openssl.bc.BcPEMDecryptorProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.util.encoders.Hex; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * PEM Key Reader implementation supporting historical password-based encryption from OpenSSL EVP_BytesToKey + */ +class EncryptedPEMKeyReader extends StandardPEMKeyReader { + private static final String PROC_TYPE_ENCRYPTED_HEADER = "Proc-Type: 4,ENCRYPTED"; + + private static final Pattern DEK_INFO_PATTERN = Pattern.compile("^DEK-Info: ([A-Z0-9\\-]+),([A-F0-9]{16,32})$"); + + private static final int DEK_INFO_ALGORITHM_GROUP = 1; + + private static final int DEK_INFO_IV_GROUP = 2; + + private final PasswordFinder passwordFinder; + + private final Resource resource; + + EncryptedPEMKeyReader(final PasswordFinder passwordFinder, final Resource resource) { + this.passwordFinder = Objects.requireNonNull(passwordFinder, "Password Finder required"); + this.resource = Objects.requireNonNull(resource, "Resource required"); + } + + @Override + public PEMKey readPemKey(final BufferedReader bufferedReader) throws IOException { + final PEMKey pemKey = super.readPemKey(bufferedReader); + final List headers = pemKey.getHeaders(); + + final PEMKey processedPemKey; + if (isEncrypted(headers)) { + processedPemKey = readEncryptedPemKey(pemKey); + } else { + processedPemKey = pemKey; + } + + return processedPemKey; + } + + private boolean isEncrypted(final List headers) { + return headers.contains(PROC_TYPE_ENCRYPTED_HEADER); + } + + private PEMKey readEncryptedPemKey(final PEMKey pemKey) throws IOException { + final List headers = pemKey.getHeaders(); + final DataEncryptionKeyInfo dataEncryptionKeyInfo = getDataEncryptionKeyInfo(headers); + final byte[] pemKeyBody = pemKey.getBody(); + + byte[] decryptedPemKeyBody = null; + char[] password = passwordFinder.reqPassword(resource); + while (password != null) { + try { + decryptedPemKeyBody = getDecryptedPemKeyBody(password, pemKeyBody, dataEncryptionKeyInfo); + break; + } catch (final KeyDecryptionFailedException e) { + if (passwordFinder.shouldRetry(resource)) { + password = passwordFinder.reqPassword(resource); + } else { + throw e; + } + } + } + + if (decryptedPemKeyBody == null) { + throw new KeyDecryptionFailedException("PEM Key password-based decryption failed"); + } + + return new PEMKey(pemKey.getPemKeyType(), headers, decryptedPemKeyBody); + } + + private byte[] getDecryptedPemKeyBody(final char[] password, final byte[] pemKeyBody, final DataEncryptionKeyInfo dataEncryptionKeyInfo) throws IOException { + final String algorithm = dataEncryptionKeyInfo.algorithm; + try { + final PEMDecryptorProvider pemDecryptorProvider = new BcPEMDecryptorProvider(password); + final PEMDecryptor pemDecryptor = pemDecryptorProvider.get(algorithm); + final byte[] initializationVector = dataEncryptionKeyInfo.initializationVector; + return pemDecryptor.decrypt(pemKeyBody, initializationVector); + } catch (final OperatorCreationException e) { + throw new IOException(String.format("PEM decryption support not found for algorithm [%s]", algorithm), e); + } catch (final PEMException e) { + throw new KeyDecryptionFailedException(String.format("PEM Key decryption failed for algorithm [%s]", algorithm), e); + } finally { + PasswordUtils.blankOut(password); + } + } + + private DataEncryptionKeyInfo getDataEncryptionKeyInfo(final List headers) throws IOException { + DataEncryptionKeyInfo dataEncryptionKeyInfo = null; + + for (final String header : headers) { + final Matcher matcher = DEK_INFO_PATTERN.matcher(header); + if (matcher.matches()) { + final String algorithm = matcher.group(DEK_INFO_ALGORITHM_GROUP); + final String initializationVectorGroup = matcher.group(DEK_INFO_IV_GROUP); + final byte[] initializationVector = Hex.decode(initializationVectorGroup); + dataEncryptionKeyInfo = new DataEncryptionKeyInfo(algorithm, initializationVector); + } + } + + if (dataEncryptionKeyInfo == null) { + throw new IOException("Data Encryption Key Information header [DEK-Info] not found"); + } + + return dataEncryptionKeyInfo; + } + + private static class DataEncryptionKeyInfo { + private final String algorithm; + + private final byte[] initializationVector; + + private DataEncryptionKeyInfo(final String algorithm, final byte[] initializationVector) { + this.algorithm = algorithm; + this.initializationVector = initializationVector; + } + } +} diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PEMKey.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PEMKey.java new file mode 100644 index 00000000..0ba51baa --- /dev/null +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PEMKey.java @@ -0,0 +1,75 @@ +/* + * 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.userauth.keyprovider; + +import java.util.List; +import java.util.Objects; + +/** + * PEM Key container with identified Key Type and decoded body + */ +public class PEMKey { + private final PEMKeyType pemKeyType; + + private final List headers; + + private final byte[] body; + + PEMKey(final PEMKeyType pemKeyType, final List headers, final byte[] body) { + this.pemKeyType = Objects.requireNonNull(pemKeyType, "PEM Key Type required"); + this.headers = Objects.requireNonNull(headers, "Headers required"); + this.body = Objects.requireNonNull(body, "Body required"); + } + + PEMKeyType getPemKeyType() { + return pemKeyType; + } + + List getHeaders() { + return headers; + } + + byte[] getBody() { + return body.clone(); + } + + public enum PEMKeyType { + /** RFC 3279 Section 2.3.2 */ + DSA("-----BEGIN DSA PRIVATE KEY-----"), + + /** RFC 5915 Section 3 */ + EC("-----BEGIN EC PRIVATE KEY-----"), + + /** RFC 8017 Appendix 1.2 */ + RSA("-----BEGIN RSA PRIVATE KEY-----"), + + /** RFC 5208 Section 5 */ + PKCS8("-----BEGIN PRIVATE KEY-----"), + + /** RFC 5208 Section 6 */ + PKCS8_ENCRYPTED("-----BEGIN ENCRYPTED PRIVATE KEY-----"); + + private final String header; + + PEMKeyType(final String header) { + this.header = header; + } + + String getHeader() { + return header; + } + } +} diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/KeyPairConverter.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PEMKeyReader.java similarity index 59% rename from src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/KeyPairConverter.java rename to src/main/java/net/schmizz/sshj/userauth/keyprovider/PEMKeyReader.java index dec183ee..1c8c31e3 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/KeyPairConverter.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PEMKeyReader.java @@ -13,23 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package net.schmizz.sshj.userauth.keyprovider.pkcs; - -import org.bouncycastle.openssl.PEMKeyPair; +package net.schmizz.sshj.userauth.keyprovider; +import java.io.BufferedReader; import java.io.IOException; /** - * Converter from typed object to PEM Key Pair - * @param Object Type + * Abstraction for parsing and returning PEM Keys */ -public interface KeyPairConverter { +interface PEMKeyReader { /** - * Get PEM Key Pair from typed object + * Read PEM Key from buffered reader * - * @param object Typed Object - * @return PEM Key Pair - * @throws IOException Thrown on conversion failures + * @param bufferedReader Buffered Reader containing lines from resource reader + * @return PEM Key + * @throws IOException Thrown on failure to read PEM Key from resources */ - PEMKeyPair getKeyPair(T object) throws IOException; + PEMKey readPemKey(BufferedReader bufferedReader) throws IOException; } diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java index 300e4aa1..0322b68d 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java @@ -15,37 +15,66 @@ */ package net.schmizz.sshj.userauth.keyprovider; +import com.hierynomus.asn1.ASN1InputStream; +import com.hierynomus.asn1.encodingrules.der.DERDecoder; +import com.hierynomus.asn1.types.ASN1Tag; +import com.hierynomus.asn1.types.constructed.ASN1Sequence; +import com.hierynomus.asn1.types.constructed.ASN1TaggedObject; +import com.hierynomus.asn1.types.primitive.ASN1Integer; +import com.hierynomus.asn1.types.primitive.ASN1ObjectIdentifier; +import com.hierynomus.asn1.types.string.ASN1BitString; +import com.hierynomus.asn1.types.string.ASN1OctetString; +import com.hierynomus.sshj.common.KeyAlgorithm; import com.hierynomus.sshj.common.KeyDecryptionFailedException; -import net.schmizz.sshj.common.IOUtils; +import net.schmizz.sshj.common.ECDSACurve; +import net.schmizz.sshj.common.ECDSAKeyFactory; import net.schmizz.sshj.common.SecurityUtils; -import net.schmizz.sshj.userauth.keyprovider.pkcs.KeyPairConverter; -import net.schmizz.sshj.userauth.keyprovider.pkcs.PrivateKeyInfoKeyPairConverter; import net.schmizz.sshj.userauth.password.PasswordUtils; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.openssl.EncryptionException; -import org.bouncycastle.openssl.PEMEncryptedKeyPair; -import org.bouncycastle.openssl.PEMKeyPair; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; -import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; -import org.bouncycastle.operator.InputDecryptorProvider; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; -import org.bouncycastle.pkcs.PKCSException; +import net.schmizz.sshj.userauth.keyprovider.PEMKey.PEMKeyType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.crypto.Cipher; +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.io.BufferedReader; import java.io.IOException; +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECField; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.EllipticCurve; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPublicKeySpec; /** * Key File implementation supporting PEM-encoded PKCS8 and PKCS1 formats with or without password-based encryption */ public class PKCS8KeyFile extends BaseFileKeyProvider { + /** Bouncy Castle class for detecting support of historical OpenSSL password-based decryption */ + private static final String BOUNCY_CASTLE_CLASS = "org.bouncycastle.openssl.PEMDecryptor"; - public static class Factory - implements net.schmizz.sshj.common.Factory.Named { + private static final boolean HISTORICAL_DECRYPTION_SUPPORTED = isHistoricalDecryptionSupported(); + + protected final Logger log = LoggerFactory.getLogger(getClass()); + + public static class Factory implements net.schmizz.sshj.common.Factory.Named { @Override public FileKeyProvider create() { @@ -58,58 +87,47 @@ public class PKCS8KeyFile extends BaseFileKeyProvider { } } - protected final Logger log = LoggerFactory.getLogger(getClass()); + @Override + protected KeyPair readKeyPair() throws IOException { + final PEMKeyReader pemKeyReader; - protected KeyPairConverter privateKeyInfoKeyPairConverter = new PrivateKeyInfoKeyPairConverter(); - - protected KeyPair readKeyPair() - throws IOException { - KeyPair kp = null; - - for (PEMParser r = null; ; ) { - // while the PasswordFinder tells us we should retry - try { - r = new PEMParser(resource.getReader()); - final Object o = r.readObject(); - - final JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); - if (SecurityUtils.getSecurityProvider() != null) { - pemConverter.setProvider(SecurityUtils.getSecurityProvider()); - } - - if (o instanceof PEMEncryptedKeyPair) { - final PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) o; - final PEMKeyPair pemKeyPair = readEncryptedKeyPair(encryptedKeyPair); - kp = pemConverter.getKeyPair(pemKeyPair); - } else if (o instanceof PEMKeyPair) { - kp = pemConverter.getKeyPair((PEMKeyPair) o); - } else if (o instanceof PrivateKeyInfo) { - final PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) o; - final PEMKeyPair pemKeyPair = privateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo); - kp = pemConverter.getKeyPair(pemKeyPair); - } else if (o instanceof PKCS8EncryptedPrivateKeyInfo) { - final PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = (PKCS8EncryptedPrivateKeyInfo) o; - final PrivateKeyInfo privateKeyInfo = readEncryptedPrivateKeyInfo(encryptedPrivateKeyInfo); - final PEMKeyPair pemKeyPair = privateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo); - kp = pemConverter.getKeyPair(pemKeyPair); - } else { - log.warn("Unexpected PKCS8 PEM Object [{}]", o); - } - - } catch (EncryptionException e) { - if (pwdf != null && pwdf.shouldRetry(resource)) - continue; - else - throw new KeyDecryptionFailedException(e); - } finally { - IOUtils.closeQuietly(r); + if (HISTORICAL_DECRYPTION_SUPPORTED) { + if (pwdf == null) { + pemKeyReader = new StandardPEMKeyReader(); + } else { + pemKeyReader = new EncryptedPEMKeyReader(pwdf, resource); } - break; + } else { + pemKeyReader = new StandardPEMKeyReader(); } - if (kp == null) - throw new IOException("Could not read key pair from: " + resource); - return kp; + try (BufferedReader bufferedReader = new BufferedReader(resource.getReader())) { + final PEMKey pemKey = pemKeyReader.readPemKey(bufferedReader); + return readKeyPair(pemKey); + } + } + + private KeyPair readKeyPair(final PEMKey pemKey) throws IOException { + final KeyPair keyPair; + + final PEMKeyType pemKeyType = pemKey.getPemKeyType(); + final byte[] pemKeyBody = pemKey.getBody(); + + if (PEMKeyType.DSA == pemKeyType) { + keyPair = readDsaKeyPair(pemKeyBody); + } else if (PEMKeyType.EC == pemKeyType) { + keyPair = readEcKeyPair(pemKeyBody); + } else if (PEMKeyType.PKCS8 == pemKeyType) { + keyPair = getPkcs8KeyPair(pemKeyBody); + } else if (PEMKeyType.PKCS8_ENCRYPTED == pemKeyType) { + keyPair = readEncryptedPkcs8KeyPair(pemKeyBody); + } else if (PEMKeyType.RSA == pemKeyType) { + keyPair = readRsaKeyPair(pemKeyBody); + } else { + throw new IOException(String.format("PEM Key Type [%s] not supported", pemKeyType)); + } + + return keyPair; } @Override @@ -117,36 +135,304 @@ public class PKCS8KeyFile extends BaseFileKeyProvider { return "PKCS8KeyFile{resource=" + resource + "}"; } - private PEMKeyPair readEncryptedKeyPair(final PEMEncryptedKeyPair encryptedKeyPair) throws IOException { - final JcePEMDecryptorProviderBuilder builder = new JcePEMDecryptorProviderBuilder(); - if (SecurityUtils.getSecurityProvider() != null) { - builder.setProvider(SecurityUtils.getSecurityProvider()); - } - char[] passphrase = null; - try { - passphrase = pwdf == null ? null : pwdf.reqPassword(resource); - return encryptedKeyPair.decryptKeyPair(builder.build(passphrase)); - } finally { - PasswordUtils.blankOut(passphrase); + private KeyPair readDsaKeyPair(final byte[] pemKeyBody) throws IOException { + try (ASN1InputStream inputStream = new ASN1InputStream(new DERDecoder(), pemKeyBody)) { + final ASN1Sequence sequence = inputStream.readObject(); + + final BigInteger p = getBigInteger(sequence, 1); + final BigInteger q = getBigInteger(sequence, 2); + final BigInteger g = getBigInteger(sequence, 3); + + final BigInteger y = getBigInteger(sequence, 4); + final BigInteger x = getBigInteger(sequence, 5); + + final DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g); + final DSAPublicKeySpec publicKeySpec = new DSAPublicKeySpec(y, p, q, g); + + final KeyFactory keyFactory = SecurityUtils.getKeyFactory(KeyAlgorithm.DSA); + final PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + final PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + return new KeyPair(publicKey, privateKey); + } catch (final Exception e) { + throw new IOException("PEM Key [DSA] processing failed", e); } } - private PrivateKeyInfo readEncryptedPrivateKeyInfo(final PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo) throws EncryptionException { - final JceOpenSSLPKCS8DecryptorProviderBuilder builder = new JceOpenSSLPKCS8DecryptorProviderBuilder(); - if (SecurityUtils.getSecurityProvider() != null) { - builder.setProvider(SecurityUtils.getSecurityProvider()); + private KeyPair readRsaKeyPair(final byte[] pemKeyBody) throws IOException { + try (ASN1InputStream inputStream = new ASN1InputStream(new DERDecoder(), pemKeyBody)) { + final ASN1Sequence sequence = inputStream.readObject(); + final BigInteger modulus = getBigInteger(sequence, 1); + final BigInteger publicExponent = getBigInteger(sequence, 2); + final BigInteger privateExponent = getBigInteger(sequence, 3); + final BigInteger prime1 = getBigInteger(sequence, 4); + final BigInteger prime2 = getBigInteger(sequence, 5); + final BigInteger exponent1 = getBigInteger(sequence, 6); + final BigInteger exponent2 = getBigInteger(sequence, 7); + final BigInteger coefficient = getBigInteger(sequence, 8); + + final RSAPrivateCrtKeySpec privateKeySpec = new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, prime1, prime2, exponent1, exponent2, coefficient); + final KeyFactory keyFactory = SecurityUtils.getKeyFactory(KeyAlgorithm.RSA); + final PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + + final RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, publicExponent); + final PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + return new KeyPair(publicKey, privateKey); + } catch (final Exception e) { + throw new IOException("PEM Key [RSA] processing failed", e); } - char[] passphrase = null; + } + + private KeyPair readEcKeyPair(final byte[] pemKeyBody) throws IOException { + try (ASN1InputStream inputStream = new ASN1InputStream(new DERDecoder(), pemKeyBody)) { + final ASN1Sequence sequence = inputStream.readObject(); + + final ASN1TaggedObject taggedObjectParameters = (ASN1TaggedObject) sequence.get(2); + final ASN1ObjectIdentifier objectIdentifier = (ASN1ObjectIdentifier) taggedObjectParameters.getObject(); + final String objectId = objectIdentifier.getValue(); + final ECNamedCurveObjectIdentifier ecNamedCurveObjectIdentifier = getEcNamedCurve(objectId); + + final ASN1OctetString privateKeyOctetString = (ASN1OctetString) sequence.get(1); + final BigInteger privateKeyInteger = new BigInteger(1, privateKeyOctetString.getValue()); + final ECPrivateKey privateKey = (ECPrivateKey) ECDSAKeyFactory.getPrivateKey(privateKeyInteger, ecNamedCurveObjectIdentifier.ecdsaCurve); + final ECParameterSpec ecParameterSpec = privateKey.getParams(); + + final ASN1TaggedObject taggedBitString = (ASN1TaggedObject) sequence.get(3); + final ASN1BitString publicKeyBitString = (ASN1BitString) taggedBitString.getObject(); + final byte[] bitString = publicKeyBitString.getValueBytes(); + final PublicKey publicKey = getEcPublicKey(bitString, ecParameterSpec); + return new KeyPair(publicKey, privateKey); + } catch (final Exception e) { + throw new IOException("PEM Key [EC] processing failed", e); + } + } + + private ECNamedCurveObjectIdentifier getEcNamedCurve(final String objectId) { + ECNamedCurveObjectIdentifier objectIdentifierFound = null; + + for (final ECNamedCurveObjectIdentifier objectIdentifier : ECNamedCurveObjectIdentifier.values()) { + if (objectIdentifier.objectId.equals(objectId)) { + objectIdentifierFound = objectIdentifier; + } + } + + if (objectIdentifierFound == null) { + throw new IllegalArgumentException(String.format("ECDSA Key Algorithm [%s] not supported", objectId)); + } + + return objectIdentifierFound; + } + + private KeyPair readEncryptedPkcs8KeyPair(final byte[] pemKeyBody) throws IOException { + if (pwdf == null) { + throw new KeyDecryptionFailedException("Password not provided for encrypted PKCS8 key"); + } + + KeyPair keyPair = null; try { - passphrase = pwdf == null ? null : pwdf.reqPassword(resource); - final InputDecryptorProvider inputDecryptorProvider = builder.build(passphrase); - return encryptedPrivateKeyInfo.decryptPrivateKeyInfo(inputDecryptorProvider); - } catch (final OperatorCreationException e) { - throw new EncryptionException("Loading Password for Encrypted Private Key Failed", e); - } catch (final PKCSException e) { - throw new EncryptionException("Reading Encrypted Private Key Failed", e); + char[] password = pwdf.reqPassword(resource); + while (password != null) { + try { + final PKCS8EncodedKeySpec encodedKeySpec = getPkcs8DecryptedKeySpec(password, pemKeyBody); + keyPair = getPkcs8KeyPair(encodedKeySpec.getEncoded()); + break; + } catch (final KeyDecryptionFailedException e) { + if (pwdf.shouldRetry(resource)) { + password = pwdf.reqPassword(resource); + } else { + throw e; + } + } + } + } catch (final GeneralSecurityException e) { + throw new IOException("PEM Key [PKCS8] processing failed", e); + } + + if (keyPair == null) { + throw new KeyDecryptionFailedException("PEM Key [PKCS8] decryption failed"); + } + + return keyPair; + } + + private PKCS8EncodedKeySpec getPkcs8DecryptedKeySpec(final char[] password, final byte[] encoded) throws IOException, GeneralSecurityException { + try { + final EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(encoded); + final AlgorithmParameters algorithmParameters = encryptedPrivateKeyInfo.getAlgParameters(); + final String secretKeyAlgorithm = algorithmParameters.toString(); + final SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(secretKeyAlgorithm); + final PBEKeySpec secretKeySpec = new PBEKeySpec(password); + final SecretKey secretKey = secretKeyFactory.generateSecret(secretKeySpec); + final Cipher cipher = Cipher.getInstance(secretKeyAlgorithm); + cipher.init(Cipher.DECRYPT_MODE, secretKey, algorithmParameters); + + try { + return encryptedPrivateKeyInfo.getKeySpec(cipher); + } catch (final GeneralSecurityException e) { + throw new KeyDecryptionFailedException(String.format("PKCS8 Key Decryption failed for algorithm [%s]", secretKeyAlgorithm), e); + } } finally { - PasswordUtils.blankOut(passphrase); + PasswordUtils.blankOut(password); + } + } + + private KeyPair getPkcs8KeyPair(final byte[] encoded) throws IOException { + try (ASN1InputStream inputStream = new ASN1InputStream(new DERDecoder(), encoded)) { + final ASN1Sequence sequence = inputStream.readObject(); + + final ASN1Sequence privateKeyAlgorithmSequence = (ASN1Sequence) sequence.get(1); + final ASN1ObjectIdentifier privateKeyAlgorithm = (ASN1ObjectIdentifier) privateKeyAlgorithmSequence.get(0); + final String privateKeyAlgorithmObjectId = privateKeyAlgorithm.getValue(); + final KeyAlgorithmObjectIdentifier keyAlgorithmObjectIdentifier = getKeyAlgorithmObjectIdentifier(privateKeyAlgorithmObjectId); + + return getPkcs8KeyPair(keyAlgorithmObjectIdentifier, encoded); + } catch (final Exception e) { + throw new IOException("PEM Key [PKCS8] processing failed", e); + } + } + + private KeyPair getPkcs8KeyPair(final KeyAlgorithmObjectIdentifier objectIdentifier, final byte[] privateKeyInfo) throws GeneralSecurityException { + final PublicKey publicKey; + + final PrivateKey privateKey = getPkcs8PrivateKey(objectIdentifier, privateKeyInfo); + + if (privateKey instanceof RSAPrivateCrtKey) { + final RSAPrivateCrtKey rsaPrivateKey = (RSAPrivateCrtKey) privateKey; + final BigInteger modulus = rsaPrivateKey.getModulus(); + final BigInteger publicExponent = rsaPrivateKey.getPublicExponent(); + final RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, publicExponent); + final KeyFactory keyFactory = SecurityUtils.getKeyFactory(privateKey.getAlgorithm()); + publicKey = keyFactory.generatePublic(publicKeySpec); + } else if (privateKey instanceof DSAPrivateKey) { + final DSAPrivateKey dsaPrivateKey = (DSAPrivateKey) privateKey; + final DSAParams dsaParams = dsaPrivateKey.getParams(); + final BigInteger p = dsaParams.getP(); + final BigInteger g = dsaParams.getG(); + final BigInteger q = dsaParams.getQ(); + final BigInteger x = dsaPrivateKey.getX(); + final BigInteger y = g.modPow(x, p); + final DSAPublicKeySpec publicKeySpec = new DSAPublicKeySpec(y, p, q, g); + final KeyFactory keyFactory = SecurityUtils.getKeyFactory(privateKey.getAlgorithm()); + publicKey = keyFactory.generatePublic(publicKeySpec); + } else if (privateKey instanceof ECPrivateKey) { + final ECPrivateKey ecPrivateKey = (ECPrivateKey) privateKey; + final ECParameterSpec ecParameterSpec = ecPrivateKey.getParams(); + + // Read ECDSA Public Key from ASN.1 + try (ASN1InputStream inputStream = new ASN1InputStream(new DERDecoder(), privateKeyInfo)) { + final ASN1Sequence sequence = inputStream.readObject(); + final ASN1OctetString keyOctetString = (ASN1OctetString) sequence.get(2); + final byte[] keyBytes = keyOctetString.getValue(); + try (ASN1InputStream keyInputStream = new ASN1InputStream(new DERDecoder(), keyBytes)) { + final ASN1Sequence keySequence = keyInputStream.readObject(); + final ASN1TaggedObject taggedObject = (ASN1TaggedObject) keySequence.get(2); + final ASN1BitString publicKeyBitString = taggedObject.getObject(ASN1Tag.BIT_STRING); + final byte[] bitString = publicKeyBitString.getValueBytes(); + + publicKey = getEcPublicKey(bitString, ecParameterSpec); + } + } catch (final IOException e) { + throw new GeneralSecurityException("ECDSA Private Key Info parsing failed", e); + } + } else { + throw new GeneralSecurityException(String.format("PEM Key [PKCS8] algorithm [%s] Key Pair derivation not supported", privateKey.getAlgorithm())); + } + + return new KeyPair(publicKey, privateKey); + } + + private PrivateKey getPkcs8PrivateKey(final KeyAlgorithmObjectIdentifier objectIdentifier, final byte[] privateKeyInfo) throws GeneralSecurityException { + final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyInfo); + final KeyFactory keyFactory = SecurityUtils.getKeyFactory(objectIdentifier.name()); + return keyFactory.generatePrivate(keySpec); + } + + private PublicKey getEcPublicKey(final byte[] bitString, final ECParameterSpec ecParameterSpec) throws GeneralSecurityException { + final EllipticCurve ellipticCurve = ecParameterSpec.getCurve(); + final ECField ecField = ellipticCurve.getField(); + final int fieldSize = (ecField.getFieldSize() + 7) / 8; + final int publicKeyPointSize = fieldSize * 2; + + final byte[] x = new byte[fieldSize]; + final byte[] y = new byte[fieldSize]; + + final int pointOffset = bitString.length - publicKeyPointSize; + + System.arraycopy(bitString, pointOffset, x, 0, x.length); + System.arraycopy(bitString, pointOffset + y.length, y, 0, y.length); + + final BigInteger pointX = new BigInteger(1, x); + final BigInteger pointY = new BigInteger(1, y); + final ECPoint point = new ECPoint(pointX, pointY); + final ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(point, ecParameterSpec); + + final KeyFactory keyFactory = SecurityUtils.getKeyFactory(KeyAlgorithm.EC_KEYSTORE); + return keyFactory.generatePublic(publicKeySpec); + } + + private KeyAlgorithmObjectIdentifier getKeyAlgorithmObjectIdentifier(final String objectId) { + KeyAlgorithmObjectIdentifier keyAlgorithmObjectIdentifier = null; + + for (final KeyAlgorithmObjectIdentifier objectIdentifier : KeyAlgorithmObjectIdentifier.values()) { + if (objectIdentifier.getObjectId().equals(objectId)) { + keyAlgorithmObjectIdentifier = objectIdentifier; + } + } + + if (keyAlgorithmObjectIdentifier == null) { + throw new IllegalArgumentException(String.format("PKCS8 Private Key Algorithm [%s] not supported", objectId)); + } + + return keyAlgorithmObjectIdentifier; + } + + private BigInteger getBigInteger(final ASN1Sequence sequence, final int index) { + final ASN1Integer integer = (ASN1Integer) sequence.get(index); + return integer.getValue(); + } + + private static boolean isHistoricalDecryptionSupported() { + try { + // Support requires Bouncy Castle library for OpenSSL password-based decryption + Class.forName(BOUNCY_CASTLE_CLASS); + return true; + } catch (final Exception e) { + return false; + } + } + + private enum ECNamedCurveObjectIdentifier { + SECP256R1("1.2.840.10045.3.1.7", ECDSACurve.SECP256R1), + + SECP384R1("1.3.132.0.34", ECDSACurve.SECP384R1), + + SECP521R1("1.3.132.0.35", ECDSACurve.SECP521R1); + + private final String objectId; + + private final ECDSACurve ecdsaCurve; + + ECNamedCurveObjectIdentifier(final String objectId, final ECDSACurve ecdsaCurve) { + this.objectId = objectId; + this.ecdsaCurve = ecdsaCurve; + } + } + + private enum KeyAlgorithmObjectIdentifier { + DSA("1.2.840.10040.4.1"), + + EC("1.2.840.10045.2.1"), + + RSA("1.2.840.113549.1.1.1"); + + private final String objectId; + + KeyAlgorithmObjectIdentifier(final String objectId) { + this.objectId = objectId; + } + + String getObjectId() { + return objectId; } } } diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/StandardPEMKeyReader.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/StandardPEMKeyReader.java new file mode 100644 index 00000000..077ad903 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/StandardPEMKeyReader.java @@ -0,0 +1,113 @@ +/* + * 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.userauth.keyprovider; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Objects; + +/** + * Standard implementation of PEM Key Reader supporting Base64 decoding without decryption + */ +class StandardPEMKeyReader implements PEMKeyReader { + private static final String HEADER_DELIMITER = "-----BEGIN"; + + private static final String FOOTER_DELIMITER = "-----END"; + + private static final char PEM_HEADER_DELIMITER = ':'; + + private static final int CHARACTER_NOT_FOUND = -1; + + private static final String HEADER_NOT_FOUND = "header not found"; + + private static final Base64.Decoder bodyDecoder = Base64.getDecoder(); + + /** + * Read PEM Key from Buffered Reader + * + * @param bufferedReader Buffered Reader containing lines from resource reader + * @return PEM Key + * @throws IOException Thrown on failure to read or decode PEM Key + */ + @Override + public PEMKey readPemKey(final BufferedReader bufferedReader) throws IOException { + Objects.requireNonNull(bufferedReader, "Reader required"); + final PEMKey.PEMKeyType pemKeyType = findPemKeyType(bufferedReader); + return readPemKeyBody(pemKeyType, bufferedReader); + } + + private PEMKey.PEMKeyType findPemKeyType(final BufferedReader bufferedReader) throws IOException { + PEMKey.PEMKeyType pemKeyTypeFound = null; + + String header = HEADER_NOT_FOUND; + String line = bufferedReader.readLine(); + readLoop: while (line != null) { + if (line.startsWith(HEADER_DELIMITER)) { + header = line; + for (final PEMKey.PEMKeyType pemKeyType : PEMKey.PEMKeyType.values()) { + if (pemKeyType.getHeader().equals(line)) { + pemKeyTypeFound = pemKeyType; + break readLoop; + } + } + } + + line = bufferedReader.readLine(); + } + + if (pemKeyTypeFound == null) { + throw new IOException(String.format("Supported PEM Key Type not found for header [%s]", header)); + } + + return pemKeyTypeFound; + } + + private PEMKey readPemKeyBody(final PEMKey.PEMKeyType pemKeyType, final BufferedReader bufferedReader) throws IOException { + final StringBuilder builder = new StringBuilder(); + + final List headers = new ArrayList<>(); + + String line = bufferedReader.readLine(); + while (line != null) { + if (line.startsWith(FOOTER_DELIMITER)) { + break; + } + + if (line.indexOf(PEM_HEADER_DELIMITER) > CHARACTER_NOT_FOUND) { + headers.add(line); + } else if (!line.isEmpty()) { + builder.append(line); + } + + line = bufferedReader.readLine(); + } + + final String pemKeyBody = builder.toString(); + final byte[] pemKeyBodyDecoded = getPemKeyBodyDecoded(pemKeyBody); + return new PEMKey(pemKeyType, headers, pemKeyBodyDecoded); + } + + private byte[] getPemKeyBodyDecoded(final String pemKeyBodyEncoded) throws IOException { + try { + return bodyDecoder.decode(pemKeyBodyEncoded); + } catch (final IllegalArgumentException e) { + throw new IOException("Base64 decoding of PEM Key failed", e); + } + } +} diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/DSAPrivateKeyInfoKeyPairConverter.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/DSAPrivateKeyInfoKeyPairConverter.java deleted file mode 100644 index 03b36bc1..00000000 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/DSAPrivateKeyInfoKeyPairConverter.java +++ /dev/null @@ -1,90 +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 net.schmizz.sshj.userauth.keyprovider.pkcs; - -import org.bouncycastle.asn1.ASN1Integer; -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.ASN1Sequence; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.asn1.x509.AlgorithmIdentifier; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; -import org.bouncycastle.crypto.params.DSAParameters; -import org.bouncycastle.openssl.PEMKeyPair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.Objects; - -/** - * Key Pair Converter from DSA Private Key Information to PEM Key Pair - */ -class DSAPrivateKeyInfoKeyPairConverter implements KeyPairConverter { - private static final Logger logger = LoggerFactory.getLogger(DSAPrivateKeyInfoKeyPairConverter.class); - - private static final int P_INDEX = 0; - - private static final int Q_INDEX = 1; - - private static final int G_INDEX = 2; - - /** - * Get PEM Key Pair calculating DSA Public Key from DSA Private Key Information - * - * @param privateKeyInfo DSA Private Key Information - * @return PEM Key Pair - * @throws IOException Thrown on Public Key parsing failures - */ - @Override - public PEMKeyPair getKeyPair(final PrivateKeyInfo privateKeyInfo) throws IOException { - Objects.requireNonNull(privateKeyInfo, "Private Key Info required"); - final AlgorithmIdentifier algorithmIdentifier = privateKeyInfo.getPrivateKeyAlgorithm(); - final ASN1ObjectIdentifier algorithm = algorithmIdentifier.getAlgorithm(); - if (X9ObjectIdentifiers.id_dsa.equals(algorithm)) { - logger.debug("DSA Algorithm Found [{}]", algorithm); - } else { - throw new IllegalArgumentException(String.format("DSA Algorithm OID required [%s]", algorithm)); - } - final ASN1Integer encodedPublicKey = getEncodedPublicKey(privateKeyInfo); - final SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo(algorithmIdentifier, encodedPublicKey); - return new PEMKeyPair(subjectPublicKeyInfo, privateKeyInfo); - } - - /** - * Get ASN.1 Encoded Public Key calculated according to RFC 6979 Section 2.2 - * - * @param privateKeyInfo DSA Private Key Information - * @return ASN.1 Encoded DSA Public Key - * @throws IOException Thrown on failures parsing private key - */ - private ASN1Integer getEncodedPublicKey(final PrivateKeyInfo privateKeyInfo) throws IOException { - final ASN1Integer privateKey = ASN1Integer.getInstance(privateKeyInfo.parsePrivateKey()); - final AlgorithmIdentifier algorithmIdentifier = privateKeyInfo.getPrivateKeyAlgorithm(); - final DSAParameters dsaParameters = getDsaParameters(algorithmIdentifier); - final BigInteger publicKey = dsaParameters.getG().modPow(privateKey.getValue(), dsaParameters.getP()); - return new ASN1Integer(publicKey); - } - - private DSAParameters getDsaParameters(final AlgorithmIdentifier algorithmIdentifier) { - final ASN1Sequence sequence = ASN1Sequence.getInstance(algorithmIdentifier.getParameters()); - final ASN1Integer p = ASN1Integer.getInstance(sequence.getObjectAt(P_INDEX)); - final ASN1Integer q = ASN1Integer.getInstance(sequence.getObjectAt(Q_INDEX)); - final ASN1Integer g = ASN1Integer.getInstance(sequence.getObjectAt(G_INDEX)); - return new DSAParameters(p.getValue(), q.getValue(), g.getValue()); - } -} diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/ECDSAPrivateKeyInfoKeyPairConverter.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/ECDSAPrivateKeyInfoKeyPairConverter.java deleted file mode 100644 index 4d7cfd48..00000000 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/ECDSAPrivateKeyInfoKeyPairConverter.java +++ /dev/null @@ -1,90 +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 net.schmizz.sshj.userauth.keyprovider.pkcs; - -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.asn1.sec.ECPrivateKey; -import org.bouncycastle.asn1.x509.AlgorithmIdentifier; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.asn1.x9.X9ECParameters; -import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; -import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; -import org.bouncycastle.math.ec.ECMultiplier; -import org.bouncycastle.math.ec.ECPoint; -import org.bouncycastle.math.ec.FixedPointCombMultiplier; -import org.bouncycastle.openssl.PEMKeyPair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.Objects; - -/** - * Key Pair Converter from ECDSA Private Key Information to PEM Key Pair - */ -class ECDSAPrivateKeyInfoKeyPairConverter implements KeyPairConverter { - private static final Logger logger = LoggerFactory.getLogger(ECDSAPrivateKeyInfoKeyPairConverter.class); - - private static final boolean POINT_COMPRESSED = false; - - /** - * Get PEM Key Pair calculating ECDSA Public Key from ECDSA Private Key Information - * - * @param privateKeyInfo ECDSA Private Key Information - * @return PEM Key Pair - * @throws IOException Thrown on Public Key parsing failures - */ - @Override - public PEMKeyPair getKeyPair(final PrivateKeyInfo privateKeyInfo) throws IOException { - Objects.requireNonNull(privateKeyInfo, "Private Key Info required"); - final AlgorithmIdentifier algorithmIdentifier = privateKeyInfo.getPrivateKeyAlgorithm(); - final ASN1ObjectIdentifier algorithm = algorithmIdentifier.getAlgorithm(); - if (X9ObjectIdentifiers.id_ecPublicKey.equals(algorithm)) { - logger.debug("ECDSA Algorithm Found [{}]", algorithm); - } else { - throw new IllegalArgumentException(String.format("ECDSA Algorithm OID required [%s]", algorithm)); - } - final byte[] encodedPublicKey = getEncodedPublicKey(privateKeyInfo); - final SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo(algorithmIdentifier, encodedPublicKey); - return new PEMKeyPair(subjectPublicKeyInfo, privateKeyInfo); - } - - /** - * Get Encoded Elliptic Curve Public Key calculated according to RFC 6979 Section 2.2 - * - * @param privateKeyInfo ECDSA Private Key Information - * @return Encoded Elliptic Curve Public Key - * @throws IOException Thrown on failures parsing private key - */ - private byte[] getEncodedPublicKey(final PrivateKeyInfo privateKeyInfo) throws IOException { - final X9ECParameters parameters = getParameters(privateKeyInfo.getPrivateKeyAlgorithm()); - final ECPrivateKey ecPrivateKey = ECPrivateKey.getInstance(privateKeyInfo.parsePrivateKey()); - final ECPoint publicKey = getPublicKey(parameters, ecPrivateKey.getKey()); - return publicKey.getEncoded(POINT_COMPRESSED); - } - - private X9ECParameters getParameters(final AlgorithmIdentifier algorithmIdentifier) { - final ASN1ObjectIdentifier encodedParameters = ASN1ObjectIdentifier.getInstance(algorithmIdentifier.getParameters()); - return ECUtil.getNamedCurveByOid(encodedParameters); - } - - private ECPoint getPublicKey(final X9ECParameters parameters, final BigInteger privateKey) { - final ECMultiplier multiplier = new FixedPointCombMultiplier(); - return multiplier.multiply(parameters.getG(), privateKey); - } -} diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/PrivateKeyInfoKeyPairConverter.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/PrivateKeyInfoKeyPairConverter.java deleted file mode 100644 index 04527607..00000000 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/PrivateKeyInfoKeyPairConverter.java +++ /dev/null @@ -1,61 +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 net.schmizz.sshj.userauth.keyprovider.pkcs; - -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.asn1.x509.AlgorithmIdentifier; -import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; -import org.bouncycastle.openssl.PEMKeyPair; - -import java.io.IOException; -import java.util.Objects; - -/** - * Key Pair Converter for Private Key Information using known Algorithm Object Identifiers - */ -public class PrivateKeyInfoKeyPairConverter implements KeyPairConverter { - private DSAPrivateKeyInfoKeyPairConverter dsaPrivateKeyInfoKeyPairConverter = new DSAPrivateKeyInfoKeyPairConverter(); - - private ECDSAPrivateKeyInfoKeyPairConverter ecdsaPrivateKeyInfoKeyPairConverter = new ECDSAPrivateKeyInfoKeyPairConverter(); - - private RSAPrivateKeyInfoKeyPairConverter rsaPrivateKeyInfoKeyPairConverter = new RSAPrivateKeyInfoKeyPairConverter(); - - /** - * Get PEM Key Pair delegating to configured converters based on Algorithm Object Identifier - * - * @param privateKeyInfo Private Key Information - * @return PEM Key Pair - * @throws IOException Thrown on conversion failures - */ - @Override - public PEMKeyPair getKeyPair(final PrivateKeyInfo privateKeyInfo) throws IOException { - Objects.requireNonNull(privateKeyInfo, "Private Key Info required"); - final AlgorithmIdentifier algorithmIdentifier = privateKeyInfo.getPrivateKeyAlgorithm(); - final ASN1ObjectIdentifier algorithm = algorithmIdentifier.getAlgorithm(); - - if (PKCSObjectIdentifiers.rsaEncryption.equals(algorithm)) { - return rsaPrivateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo); - } else if (X9ObjectIdentifiers.id_ecPublicKey.equals(algorithm)) { - return ecdsaPrivateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo); - } else if (X9ObjectIdentifiers.id_dsa.equals(algorithm)) { - return dsaPrivateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo); - } else { - throw new IllegalArgumentException(String.format("Unsupported Algorithm [%s]", algorithm)); - } - } -} diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/RSAPrivateKeyInfoKeyPairConverter.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/RSAPrivateKeyInfoKeyPairConverter.java deleted file mode 100644 index 01b77cae..00000000 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/pkcs/RSAPrivateKeyInfoKeyPairConverter.java +++ /dev/null @@ -1,65 +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 net.schmizz.sshj.userauth.keyprovider.pkcs; - -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.asn1.pkcs.RSAPrivateKey; -import org.bouncycastle.asn1.pkcs.RSAPublicKey; -import org.bouncycastle.asn1.x509.AlgorithmIdentifier; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.openssl.PEMKeyPair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Objects; - -/** - * Key Pair Converter from RSA Private Key Information to PEM Key Pair - */ -class RSAPrivateKeyInfoKeyPairConverter implements KeyPairConverter { - private static final Logger logger = LoggerFactory.getLogger(RSAPrivateKeyInfoKeyPairConverter.class); - - /** - * Get PEM Key Pair parsing RSA Public Key attributes from RSA Private Key Information - * - * @param privateKeyInfo RSA Private Key Information - * @return PEM Key Pair - * @throws IOException Thrown on Public Key parsing failures - */ - @Override - public PEMKeyPair getKeyPair(final PrivateKeyInfo privateKeyInfo) throws IOException { - Objects.requireNonNull(privateKeyInfo, "Private Key Info required"); - final AlgorithmIdentifier algorithmIdentifier = privateKeyInfo.getPrivateKeyAlgorithm(); - final ASN1ObjectIdentifier algorithm = algorithmIdentifier.getAlgorithm(); - if (PKCSObjectIdentifiers.rsaEncryption.equals(algorithm)) { - logger.debug("RSA Algorithm Found [{}]", algorithm); - } else { - throw new IllegalArgumentException(String.format("RSA Algorithm OID required [%s]", algorithm)); - } - - final RSAPublicKey rsaPublicKey = getRsaPublicKey(privateKeyInfo); - final SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo(algorithmIdentifier, rsaPublicKey); - return new PEMKeyPair(subjectPublicKeyInfo, privateKeyInfo); - } - - private RSAPublicKey getRsaPublicKey(final PrivateKeyInfo privateKeyInfo) throws IOException { - final RSAPrivateKey rsaPrivateKey = RSAPrivateKey.getInstance(privateKeyInfo.parsePrivateKey()); - return new RSAPublicKey(rsaPrivateKey.getModulus(), rsaPrivateKey.getPublicExponent()); - } -} diff --git a/src/test/java/net/schmizz/sshj/keyprovider/PKCS8KeyFileTest.java b/src/test/java/net/schmizz/sshj/keyprovider/PKCS8KeyFileTest.java index 48a03de1..95f2dac0 100644 --- a/src/test/java/net/schmizz/sshj/keyprovider/PKCS8KeyFileTest.java +++ b/src/test/java/net/schmizz/sshj/keyprovider/PKCS8KeyFileTest.java @@ -126,8 +126,8 @@ public class PKCS8KeyFileTest { public void testPkcs8Ecdsa() throws IOException { final PKCS8KeyFile provider = new PKCS8KeyFile(); provider.init(getFile("pkcs8-ecdsa")); - assertEquals(KeyAlgorithm.ECDSA, provider.getPublic().getAlgorithm()); - assertEquals(KeyAlgorithm.ECDSA, provider.getPrivate().getAlgorithm()); + assertEquals(KeyAlgorithm.EC_KEYSTORE, provider.getPublic().getAlgorithm()); + assertEquals(KeyAlgorithm.EC_KEYSTORE, provider.getPrivate().getAlgorithm()); } @Test