Skip to content

Commit b47e6fa

Browse files
Add PKCS8 parsing for encrypted PEM ASN.1 Private Keys (#713)
- Added unit tests for encrypted PKCS8 RSA Private Key
1 parent f38fcbe commit b47e6fa

File tree

3 files changed

+102
-12
lines changed

3 files changed

+102
-12
lines changed

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

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@
2727
import org.bouncycastle.openssl.PEMKeyPair;
2828
import org.bouncycastle.openssl.PEMParser;
2929
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
30+
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
3031
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
32+
import org.bouncycastle.operator.InputDecryptorProvider;
33+
import org.bouncycastle.operator.OperatorCreationException;
34+
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
35+
import org.bouncycastle.pkcs.PKCSException;
3136
import org.slf4j.Logger;
3237
import org.slf4j.LoggerFactory;
3338

@@ -53,8 +58,6 @@ public String getName() {
5358

5459
protected final Logger log = LoggerFactory.getLogger(getClass());
5560

56-
protected char[] passphrase; // for blanking out
57-
5861
protected KeyPairConverter<PrivateKeyInfo> privateKeyInfoKeyPairConverter = new PrivateKeyInfoKeyPairConverter();
5962

6063
protected KeyPair readKeyPair()
@@ -74,22 +77,19 @@ protected KeyPair readKeyPair()
7477

7578
if (o instanceof PEMEncryptedKeyPair) {
7679
final PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) o;
77-
JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder();
78-
if (SecurityUtils.getSecurityProvider() != null) {
79-
decryptorBuilder.setProvider(SecurityUtils.getSecurityProvider());
80-
}
81-
try {
82-
passphrase = pwdf == null ? null : pwdf.reqPassword(resource);
83-
kp = pemConverter.getKeyPair(encryptedKeyPair.decryptKeyPair(decryptorBuilder.build(passphrase)));
84-
} finally {
85-
PasswordUtils.blankOut(passphrase);
86-
}
80+
final PEMKeyPair pemKeyPair = readEncryptedKeyPair(encryptedKeyPair);
81+
kp = pemConverter.getKeyPair(pemKeyPair);
8782
} else if (o instanceof PEMKeyPair) {
8883
kp = pemConverter.getKeyPair((PEMKeyPair) o);
8984
} else if (o instanceof PrivateKeyInfo) {
9085
final PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) o;
9186
final PEMKeyPair pemKeyPair = privateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo);
9287
kp = pemConverter.getKeyPair(pemKeyPair);
88+
} else if (o instanceof PKCS8EncryptedPrivateKeyInfo) {
89+
final PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = (PKCS8EncryptedPrivateKeyInfo) o;
90+
final PrivateKeyInfo privateKeyInfo = readEncryptedPrivateKeyInfo(encryptedPrivateKeyInfo);
91+
final PEMKeyPair pemKeyPair = privateKeyInfoKeyPairConverter.getKeyPair(privateKeyInfo);
92+
kp = pemConverter.getKeyPair(pemKeyPair);
9393
} else {
9494
log.warn("Unexpected PKCS8 PEM Object [{}]", o);
9595
}
@@ -114,4 +114,37 @@ protected KeyPair readKeyPair()
114114
public String toString() {
115115
return "PKCS8KeyFile{resource=" + resource + "}";
116116
}
117+
118+
private PEMKeyPair readEncryptedKeyPair(final PEMEncryptedKeyPair encryptedKeyPair) throws IOException {
119+
final JcePEMDecryptorProviderBuilder builder = new JcePEMDecryptorProviderBuilder();
120+
if (SecurityUtils.getSecurityProvider() != null) {
121+
builder.setProvider(SecurityUtils.getSecurityProvider());
122+
}
123+
char[] passphrase = null;
124+
try {
125+
passphrase = pwdf == null ? null : pwdf.reqPassword(resource);
126+
return encryptedKeyPair.decryptKeyPair(builder.build(passphrase));
127+
} finally {
128+
PasswordUtils.blankOut(passphrase);
129+
}
130+
}
131+
132+
private PrivateKeyInfo readEncryptedPrivateKeyInfo(final PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo) throws EncryptionException {
133+
final JceOpenSSLPKCS8DecryptorProviderBuilder builder = new JceOpenSSLPKCS8DecryptorProviderBuilder();
134+
if (SecurityUtils.getSecurityProvider() != null) {
135+
builder.setProvider(SecurityUtils.getSecurityProvider());
136+
}
137+
char[] passphrase = null;
138+
try {
139+
passphrase = pwdf == null ? null : pwdf.reqPassword(resource);
140+
final InputDecryptorProvider inputDecryptorProvider = builder.build(passphrase);
141+
return encryptedPrivateKeyInfo.decryptPrivateKeyInfo(inputDecryptorProvider);
142+
} catch (final OperatorCreationException e) {
143+
throw new EncryptionException("Loading Password for Encrypted Private Key Failed", e);
144+
} catch (final PKCSException e) {
145+
throw new EncryptionException("Reading Encrypted Private Key Failed", e);
146+
} finally {
147+
PasswordUtils.blankOut(passphrase);
148+
}
149+
}
117150
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@
1515
*/
1616
package net.schmizz.sshj.keyprovider;
1717

18+
import com.hierynomus.sshj.common.KeyDecryptionFailedException;
1819
import net.schmizz.sshj.common.KeyType;
1920
import net.schmizz.sshj.common.SecurityUtils;
2021
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
2122
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
23+
import net.schmizz.sshj.userauth.password.PasswordFinder;
24+
import net.schmizz.sshj.userauth.password.PasswordUtils;
2225
import net.schmizz.sshj.util.KeyUtil;
2326
import org.junit.Before;
27+
import org.junit.Rule;
2428
import org.junit.Test;
29+
import org.junit.rules.ExpectedException;
2530

2631
import java.io.File;
2732
import java.io.IOException;
@@ -35,6 +40,9 @@
3540

3641
public class PKCS8KeyFileTest {
3742

43+
@Rule
44+
public ExpectedException expectedException = ExpectedException.none();
45+
3846
static final FileKeyProvider rsa = new PKCS8KeyFile();
3947

4048
static final String modulus = "a19f65e93926d9a2f5b52072db2c38c54e6cf0113d31fa92ff827b0f3bec609c45ea84264c88e64adba11ff093ed48ee0ed297757654b0884ab5a7e28b3c463bc9074b32837a2b69b61d914abf1d74ccd92b20fa44db3b31fb208c0dd44edaeb4ab097118e8ee374b6727b89ad6ce43f1b70c5a437ccebc36d2dad8ae973caad15cd89ae840fdae02cae42d241baef8fda8aa6bbaa54fd507a23338da6f06f61b34fb07d560e63fbce4a39c073e28573c2962cedb292b14b80d1b4e67b0465f2be0e38526232d0a7f88ce91a055fde082038a87ed91f3ef5ff971e30ea6cccf70d38498b186621c08f8fdceb8632992b480bf57fc218e91f2ca5936770fe9469";
@@ -70,6 +78,25 @@ public void testPkcs8Rsa() throws IOException {
7078
assertEquals("RSA", provider.getPrivate().getAlgorithm());
7179
}
7280

81+
@Test
82+
public void testPkcs8RsaEncrypted() throws IOException {
83+
final PKCS8KeyFile provider = new PKCS8KeyFile();
84+
final PasswordFinder passwordFinder = PasswordUtils.createOneOff("passphrase".toCharArray());
85+
provider.init(getReader("pkcs8-rsa-2048-encrypted"), passwordFinder);
86+
assertEquals("RSA", provider.getPublic().getAlgorithm());
87+
assertEquals("RSA", provider.getPrivate().getAlgorithm());
88+
}
89+
90+
@Test
91+
public void testPkcs8RsaEncryptedIncorrectPassword() throws IOException {
92+
expectedException.expect(KeyDecryptionFailedException.class);
93+
94+
final PKCS8KeyFile provider = new PKCS8KeyFile();
95+
final PasswordFinder passwordFinder = PasswordUtils.createOneOff(String.class.getSimpleName().toCharArray());
96+
provider.init(getReader("pkcs8-rsa-2048-encrypted"), passwordFinder);
97+
provider.getPrivate();
98+
}
99+
73100
@Test
74101
public void testPkcs8Ecdsa() throws IOException {
75102
final PKCS8KeyFile provider = new PKCS8KeyFile();
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-----BEGIN ENCRYPTED PRIVATE KEY-----
2+
MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIizi0oXD8HM0CAggA
3+
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBAvGR0fYj/JgxVzxslaeVRtBIIE
4+
0IiXAnJ0YtAHia4QOziIghhJ+qdJXLvAssQ5gK6wPqMUWV8Zut+/7mb0mObI5qyb
5+
afv4GmS5TyI5c+gYHzXGt++Lqp9JSdisdLEPsCFHYtRd6ujC1D2EXaFOpTRBFFuw
6+
QCQa+qQFqEcDu31IFo7Obj86V2NIE8O8zWJvtTih5EloEaJmZK+lVjsXWRWRRFqh
7+
ZNU7PanlADb6N+xXyn2VnQbiFouOCTvmCN8bzvpCg8wJMyfMJU4gJbJE2cVD0nYI
8+
sz63ZO1I1ljhx7FKsTlnza6PsGbL3StN8eTlNUEL6SGiMllPXdKXXte5VqIJzoJ5
9+
a8OAC9Gmou6YRSttCGSaUCvKGCl0iEAe86vv1PiM873DNuer/IgUGXGBSk8uG5qm
10+
7PTYs8kzvovcuMHUg1O2t5aKCCVah3o2jfY+koeQtBq01kP5jQ0VB8w0trKsT614
11+
BJhN7Es9AkNya/qvDoysmebc0NJQt8rDLkvumn2jlWCDVM56Xl4P/qknllGilSyf
12+
wWClZzsd5Q8MflbjqCwiYp19ZK4IHW7Y6hxdBZWWFd5oWw47GwWPTLdgnsGtp0dW
13+
TK3IamTLO5T2BK2NdctXZ9CCn8ReuRVBA2jTNp6PWrcVxhHD4uUr1FE++e2Q+cQM
14+
iljqis/md4lmpb74lhCNbhxqiFoVSe3XzvVtNxVXNYqxb9PqRKxWXWtyYk7/jo2u
15+
Q284skAMcPvuGkl9Ba5RTup7t4V/eQfPTHeY/rnKyTd+hlUb+Tc26EOIKwSCxSZy
16+
q36h4JHVjM3BRQzNMpH/GyuW1+qUw9SFOcuqtTwOxDe5rUis0sFXQyyXc/3IZcYZ
17+
HSQzdmzFXNXysVRox5+0AfuEAOj8bc4bn0Sr7UQrYKCtJHOnl01mT51pdRCZJV/j
18+
fRYIbxJG0yqCxPoEh0sh3fwuiNkkHHTsaDZ8aXyXL1K0C+OzXlYkbc8i8iaW7UOc
19+
ymPD7BdmLLSADH2nb3M+QORF5IJtQ+8j11B+App2V7ao35azGScCda9rDMZCPl/6
20+
4OwPtRWEOlUzZVq3Uid61w3Expc4zaZ4gyXG9q/UU4/TabPt4fVv6dMTb5tpOinG
21+
CgBqBK5ihDOFUJxrFZ5OMy9SjJvecDslRrSxykPCHMx93iGfZlPmYfLfMa/AW3nr
22+
uXVTtd6Pk4P7z4/LdsJqqlHVKqkaZI5nrCrUMQwSjhAOBhEkipyQWj+3OYgfheIi
23+
FbNP0hdSvez5JCWin/aoc3Xe8oKM0i2liM7VcNKbKwPNULTmj7g08LUxjwT8pzPj
24+
z1kjlKEJgEF/d+OQ6nhU2moKvIPyxzi/+FBfVG2H8Tm+57RlwRSsv2pe7XAn+0jW
25+
xLSQFHgvZj3kcN9kT4o8A812kWgn3HV3ve2s/1sVKPvLrU8AjAHWW0pJ8fCGqAF+
26+
Q9cWPwtd2D3KJemdUUXPe2Vd/WbtfRmWzPtsVFdA4BRODgOIQNGNnjv6mkezpHrM
27+
ixXKvSqDf1GuYNzLTNo91EmaAGgSA0T1ZMEesPXLhxxBCdw8Dyq90Uzp3Vzbiia6
28+
Sy5UKaoU73xI3HfPo5d77n5vdy9UoJ5Oje6oq4DXB3WstDK/WBeaju5eCezVBhSA
29+
ksBiPIHfhkDdnTOzrCATAQBRO92VQV4b3yEXic3Q6FIH
30+
-----END ENCRYPTED PRIVATE KEY-----

0 commit comments

Comments
 (0)