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 49f44cce..a58acb09 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PKCS8KeyFile.java @@ -27,7 +27,12 @@ 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,8 +58,6 @@ public String getName() { protected final Logger log = LoggerFactory.getLogger(getClass()); - protected char[] passphrase; // for blanking out - protected KeyPairConverter privateKeyInfoKeyPairConverter = new PrivateKeyInfoKeyPairConverter(); protected KeyPair readKeyPair() @@ -74,22 +77,19 @@ protected KeyPair readKeyPair() if (o instanceof PEMEncryptedKeyPair) { final PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) o; - JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder(); - if (SecurityUtils.getSecurityProvider() != null) { - decryptorBuilder.setProvider(SecurityUtils.getSecurityProvider()); - } - try { - passphrase = pwdf == null ? null : pwdf.reqPassword(resource); - kp = pemConverter.getKeyPair(encryptedKeyPair.decryptKeyPair(decryptorBuilder.build(passphrase))); - } finally { - PasswordUtils.blankOut(passphrase); - } + 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); } @@ -114,4 +114,37 @@ protected KeyPair readKeyPair() public String toString() { 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 PrivateKeyInfo readEncryptedPrivateKeyInfo(final PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo) throws EncryptionException { + final JceOpenSSLPKCS8DecryptorProviderBuilder builder = new JceOpenSSLPKCS8DecryptorProviderBuilder(); + if (SecurityUtils.getSecurityProvider() != null) { + builder.setProvider(SecurityUtils.getSecurityProvider()); + } + char[] passphrase = 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); + } finally { + PasswordUtils.blankOut(passphrase); + } + } } diff --git a/src/test/java/net/schmizz/sshj/keyprovider/PKCS8KeyFileTest.java b/src/test/java/net/schmizz/sshj/keyprovider/PKCS8KeyFileTest.java index 3557f5b5..c615c77a 100644 --- a/src/test/java/net/schmizz/sshj/keyprovider/PKCS8KeyFileTest.java +++ b/src/test/java/net/schmizz/sshj/keyprovider/PKCS8KeyFileTest.java @@ -15,13 +15,18 @@ */ package net.schmizz.sshj.keyprovider; +import com.hierynomus.sshj.common.KeyDecryptionFailedException; import net.schmizz.sshj.common.KeyType; import net.schmizz.sshj.common.SecurityUtils; import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider; import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile; +import net.schmizz.sshj.userauth.password.PasswordFinder; +import net.schmizz.sshj.userauth.password.PasswordUtils; import net.schmizz.sshj.util.KeyUtil; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import java.io.File; import java.io.IOException; @@ -35,6 +40,9 @@ public class PKCS8KeyFileTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + static final FileKeyProvider rsa = new PKCS8KeyFile(); static final String modulus = "a19f65e93926d9a2f5b52072db2c38c54e6cf0113d31fa92ff827b0f3bec609c45ea84264c88e64adba11ff093ed48ee0ed297757654b0884ab5a7e28b3c463bc9074b32837a2b69b61d914abf1d74ccd92b20fa44db3b31fb208c0dd44edaeb4ab097118e8ee374b6727b89ad6ce43f1b70c5a437ccebc36d2dad8ae973caad15cd89ae840fdae02cae42d241baef8fda8aa6bbaa54fd507a23338da6f06f61b34fb07d560e63fbce4a39c073e28573c2962cedb292b14b80d1b4e67b0465f2be0e38526232d0a7f88ce91a055fde082038a87ed91f3ef5ff971e30ea6cccf70d38498b186621c08f8fdceb8632992b480bf57fc218e91f2ca5936770fe9469"; @@ -70,6 +78,25 @@ public void testPkcs8Rsa() throws IOException { assertEquals("RSA", provider.getPrivate().getAlgorithm()); } + @Test + public void testPkcs8RsaEncrypted() throws IOException { + final PKCS8KeyFile provider = new PKCS8KeyFile(); + final PasswordFinder passwordFinder = PasswordUtils.createOneOff("passphrase".toCharArray()); + provider.init(getReader("pkcs8-rsa-2048-encrypted"), passwordFinder); + assertEquals("RSA", provider.getPublic().getAlgorithm()); + assertEquals("RSA", provider.getPrivate().getAlgorithm()); + } + + @Test + public void testPkcs8RsaEncryptedIncorrectPassword() throws IOException { + expectedException.expect(KeyDecryptionFailedException.class); + + final PKCS8KeyFile provider = new PKCS8KeyFile(); + final PasswordFinder passwordFinder = PasswordUtils.createOneOff(String.class.getSimpleName().toCharArray()); + provider.init(getReader("pkcs8-rsa-2048-encrypted"), passwordFinder); + provider.getPrivate(); + } + @Test public void testPkcs8Ecdsa() throws IOException { final PKCS8KeyFile provider = new PKCS8KeyFile(); diff --git a/src/test/resources/keyformats/pkcs8-rsa-2048-encrypted b/src/test/resources/keyformats/pkcs8-rsa-2048-encrypted new file mode 100644 index 00000000..6f4f7edf --- /dev/null +++ b/src/test/resources/keyformats/pkcs8-rsa-2048-encrypted @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIizi0oXD8HM0CAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBAvGR0fYj/JgxVzxslaeVRtBIIE +0IiXAnJ0YtAHia4QOziIghhJ+qdJXLvAssQ5gK6wPqMUWV8Zut+/7mb0mObI5qyb +afv4GmS5TyI5c+gYHzXGt++Lqp9JSdisdLEPsCFHYtRd6ujC1D2EXaFOpTRBFFuw +QCQa+qQFqEcDu31IFo7Obj86V2NIE8O8zWJvtTih5EloEaJmZK+lVjsXWRWRRFqh +ZNU7PanlADb6N+xXyn2VnQbiFouOCTvmCN8bzvpCg8wJMyfMJU4gJbJE2cVD0nYI +sz63ZO1I1ljhx7FKsTlnza6PsGbL3StN8eTlNUEL6SGiMllPXdKXXte5VqIJzoJ5 +a8OAC9Gmou6YRSttCGSaUCvKGCl0iEAe86vv1PiM873DNuer/IgUGXGBSk8uG5qm +7PTYs8kzvovcuMHUg1O2t5aKCCVah3o2jfY+koeQtBq01kP5jQ0VB8w0trKsT614 +BJhN7Es9AkNya/qvDoysmebc0NJQt8rDLkvumn2jlWCDVM56Xl4P/qknllGilSyf +wWClZzsd5Q8MflbjqCwiYp19ZK4IHW7Y6hxdBZWWFd5oWw47GwWPTLdgnsGtp0dW +TK3IamTLO5T2BK2NdctXZ9CCn8ReuRVBA2jTNp6PWrcVxhHD4uUr1FE++e2Q+cQM +iljqis/md4lmpb74lhCNbhxqiFoVSe3XzvVtNxVXNYqxb9PqRKxWXWtyYk7/jo2u +Q284skAMcPvuGkl9Ba5RTup7t4V/eQfPTHeY/rnKyTd+hlUb+Tc26EOIKwSCxSZy +q36h4JHVjM3BRQzNMpH/GyuW1+qUw9SFOcuqtTwOxDe5rUis0sFXQyyXc/3IZcYZ +HSQzdmzFXNXysVRox5+0AfuEAOj8bc4bn0Sr7UQrYKCtJHOnl01mT51pdRCZJV/j +fRYIbxJG0yqCxPoEh0sh3fwuiNkkHHTsaDZ8aXyXL1K0C+OzXlYkbc8i8iaW7UOc +ymPD7BdmLLSADH2nb3M+QORF5IJtQ+8j11B+App2V7ao35azGScCda9rDMZCPl/6 +4OwPtRWEOlUzZVq3Uid61w3Expc4zaZ4gyXG9q/UU4/TabPt4fVv6dMTb5tpOinG +CgBqBK5ihDOFUJxrFZ5OMy9SjJvecDslRrSxykPCHMx93iGfZlPmYfLfMa/AW3nr +uXVTtd6Pk4P7z4/LdsJqqlHVKqkaZI5nrCrUMQwSjhAOBhEkipyQWj+3OYgfheIi +FbNP0hdSvez5JCWin/aoc3Xe8oKM0i2liM7VcNKbKwPNULTmj7g08LUxjwT8pzPj +z1kjlKEJgEF/d+OQ6nhU2moKvIPyxzi/+FBfVG2H8Tm+57RlwRSsv2pe7XAn+0jW +xLSQFHgvZj3kcN9kT4o8A812kWgn3HV3ve2s/1sVKPvLrU8AjAHWW0pJ8fCGqAF+ +Q9cWPwtd2D3KJemdUUXPe2Vd/WbtfRmWzPtsVFdA4BRODgOIQNGNnjv6mkezpHrM +ixXKvSqDf1GuYNzLTNo91EmaAGgSA0T1ZMEesPXLhxxBCdw8Dyq90Uzp3Vzbiia6 +Sy5UKaoU73xI3HfPo5d77n5vdy9UoJ5Oje6oq4DXB3WstDK/WBeaju5eCezVBhSA +ksBiPIHfhkDdnTOzrCATAQBRO92VQV4b3yEXic3Q6FIH +-----END ENCRYPTED PRIVATE KEY-----