Skip to content

Commit ef80945

Browse files
committed
Correct parsing for openssl -topk8, relates to github #400.
1 parent 1d406d7 commit ef80945

3 files changed

Lines changed: 73 additions & 9 deletions

File tree

docs/releasenotes.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ <h3>2.1.2 Defects Fixed</h3>
4646
<li>X509CertificateImpl.hasUnsupportedCriticalExtension() reported a critical extendedKeyUsage (id-ce 37) extension as unsupported, so loading such a certificate through the BC CertificateFactory returned true where the JDK provider returned false. RFC 5280 sec. 4.2.1.12 explicitly permits extendedKeyUsage to be marked critical, and the BC X509Certificate implementation fully recognises it (getExtendedKeyUsage()); the OID has been added to the skip list in hasUnsupportedCriticalExtension() so the BC and JDK providers now agree (issue #1796).</li>
4747
<li>The BC X.509 CertificateFactory (Provider "BC", "X.509"/"X509") previously recognised only the RFC 2315 PKCS#7 SignedData ContentType OID (1.2.840.113549.1.7.2) when extracting embedded certificates and CRLs from a SignedData wrapper, so generateCertificate{s} / generateCRL{s} returned nothing (or threw "sequence wrong size for a certificate") for a GM/T 0010-2012 SM2 SignedData wrapper (ContentType 1.2.156.10197.6.1.4.2.2). Both SignedData ASN.1 structures are identical apart from the outer ContentType OID, so the factory now treats the SM2 OID equivalently and walks the embedded certificate / CRL sets the same way. New ASN.1 constants for the full GM/T 0010 SM2 content-type arc (1.2.156.10197.6.1.4.2.{1..6}) have been added to GMObjectIdentifiers (issue #1355).</li>
4848
<li>ESTService.getCSRAttributes treated a server response of 204 No Content or 404 Not Found as "no attributes available" and returned null, but did not drain the response body before letting the surrounding finally block close it. When the server attached a body to the 404 (e.g. a JSON error message), ESTResponse's underlying LimitedInputStream then threw "Stream closed before limit fully read" on close and the caller saw an opaque IOException-wrapping ESTException instead of the 404 status they could act on. The 204 / 404 branches now drain the body via Streams.drain before returning, so the close path completes cleanly and the call returns a null CSRAttributesResponse as documented (issue #781).</li>
49+
<li>JceOpenSSLPKCS8DecryptorProviderBuilder cast the PBES2 key-derivation-function parameters blind to PBKDF2Params, so an EncryptedPrivateKeyInfo whose KDF inside PBES2 was scrypt (RFC 7914, e.g. anything produced by "openssl pkcs8 -topk8 -scrypt") failed to decrypt with "DLSequence cannot be cast to PBKDF2Params". The builder now dispatches on the KDF algorithm OID: id-PBKDF2 takes the existing PBKDF2 path, id-scrypt parses the parameters as ScryptParams and derives the key via SCrypt.generate (the password is encoded as UTF-8 to match OpenSSL's raw-bytes treatment). PBKDF2-based PBES2, PKCS#5 PBES1 and PKCS#12 PBE paths are unchanged (issue #400).</li>
4950
</ul>
5051
<h3>2.2.3 Additional Features and Functionality</h3>
5152
<ul>

pkix/src/main/java/org/bouncycastle/openssl/jcajce/JceOpenSSLPKCS8DecryptorProviderBuilder.java

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88

99
import javax.crypto.Cipher;
1010
import javax.crypto.SecretKey;
11+
import javax.crypto.spec.SecretKeySpec;
1112

13+
import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
14+
import org.bouncycastle.asn1.misc.ScryptParams;
1215
import org.bouncycastle.asn1.pkcs.EncryptionScheme;
1316
import org.bouncycastle.asn1.pkcs.KeyDerivationFunc;
1417
import org.bouncycastle.asn1.pkcs.PBEParameter;
@@ -17,6 +20,7 @@
1720
import org.bouncycastle.asn1.pkcs.PKCS12PBEParams;
1821
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
1922
import org.bouncycastle.crypto.CharToByteConverter;
23+
import org.bouncycastle.crypto.generators.SCrypt;
2024
import org.bouncycastle.jcajce.PBKDF1KeyWithParameters;
2125
import org.bouncycastle.jcajce.PKCS12KeyWithParameters;
2226
import org.bouncycastle.jcajce.io.CipherInputStream;
@@ -73,24 +77,43 @@ public InputDecryptor get(final AlgorithmIdentifier algorithm)
7377
PBES2Parameters params = PBES2Parameters.getInstance(algorithm.getParameters());
7478
KeyDerivationFunc func = params.getKeyDerivationFunc();
7579
EncryptionScheme scheme = params.getEncryptionScheme();
76-
PBKDF2Params defParams = (PBKDF2Params)func.getParameters();
77-
78-
int iterationCount = defParams.getIterationCount().intValue();
79-
byte[] salt = defParams.getSalt();
8080

8181
String oid = scheme.getAlgorithm().getId();
82-
8382
SecretKey key;
8483

85-
if (PEMUtilities.isHmacSHA1(defParams.getPrf()))
84+
if (MiscObjectIdentifiers.id_scrypt.equals(func.getAlgorithm()))
8685
{
87-
key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(helper, oid, password, salt, iterationCount);
86+
// RFC 7914 / RFC 8018 scrypt KDF inside PBES2.
87+
// OpenSSL 1.1+ "openssl pkcs8 -topk8 -scrypt" produces this form;
88+
// the caller-supplied char[] password is fed as UTF-8 bytes,
89+
// matching OpenSSL's raw-bytes treatment (github #400).
90+
ScryptParams scrypt = ScryptParams.getInstance(func.getParameters());
91+
int keySizeBits = PEMUtilities.getKeySize(oid);
92+
byte[] derived = SCrypt.generate(Strings.toUTF8ByteArray(password),
93+
scrypt.getSalt(),
94+
scrypt.getCostParameter().intValue(),
95+
scrypt.getBlockSize().intValue(),
96+
scrypt.getParallelizationParameter().intValue(),
97+
(keySizeBits + 7) / 8);
98+
key = new SecretKeySpec(derived, PEMUtilities.getAlgorithmName(oid));
8899
}
89100
else
90101
{
91-
key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(helper, oid, password, salt, iterationCount, defParams.getPrf());
102+
PBKDF2Params defParams = (PBKDF2Params)func.getParameters();
103+
104+
int iterationCount = defParams.getIterationCount().intValue();
105+
byte[] salt = defParams.getSalt();
106+
107+
if (PEMUtilities.isHmacSHA1(defParams.getPrf()))
108+
{
109+
key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(helper, oid, password, salt, iterationCount);
110+
}
111+
else
112+
{
113+
key = PEMUtilities.generateSecretKeyForPKCS5Scheme2(helper, oid, password, salt, iterationCount, defParams.getPrf());
114+
}
92115
}
93-
116+
94117
cipher = helper.createCipher(PEMUtilities.getCipherName(scheme.getAlgorithm()));
95118
AlgorithmParameters algParams = helper.createAlgorithmParameters(oid);
96119

pkix/src/test/java/org/bouncycastle/openssl/test/AllTests.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,46 @@ public void testVectors()
182182
TestCase.assertEquals(key, rdKey);
183183
}
184184

185+
/**
186+
* github #400: OpenSSL 1.1+ "openssl pkcs8 -topk8 -scrypt" emits a PBES2
187+
* EncryptedPrivateKeyInfo whose key-derivation function is scrypt
188+
* (RFC 7914) rather than PBKDF2. JceOpenSSLPKCS8DecryptorProviderBuilder
189+
* previously cast the KDF parameters blind to PBKDF2Params and threw
190+
* "DLSequence cannot be cast to PBKDF2Params". The builder now recognises
191+
* id-scrypt inside PBES2 and derives the key via SCrypt.generate.
192+
*
193+
* Fixture from RFC 7914 sec. 7.2 (password "Rabbit").
194+
*/
195+
public void testScryptOpenSSLDecryptorIssue400()
196+
throws Exception
197+
{
198+
if (Security.getProvider("BC") == null)
199+
{
200+
Security.addProvider(new BouncyCastleProvider());
201+
}
202+
203+
byte[] pkcs8Scrypt = Base64.decode(
204+
"MIHiME0GCSqGSIb3DQEFDTBAMB8GCSsGAQQB2kcECzASBAVNb3VzZQIDEAAAAgEI" +
205+
"AgEBMB0GCWCGSAFlAwQBKgQQyYmguHMsOwzGMPoyObk/JgSBkJb47EWd5iAqJlyy" +
206+
"+ni5ftd6gZgOPaLQClL7mEZc2KQay0VhjZm/7MbBUNbqOAXNM6OGebXxVp6sHUAL" +
207+
"iBGY/Dls7B1TsWeGObE0sS1MXEpuREuloZjcsNVcNXWPlLdZtkSH6uwWzR0PyG/Z" +
208+
"+ZXfNodZtd/voKlvLOw5B3opGIFaLkbtLZQwMiGtl42AS89lZg==");
209+
210+
byte[] expected = Base64.decode(
211+
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg4RaNK5CuHY3CXr9f" +
212+
"/CdVgOhEurMohrQmWbbLZK4ZInyhRANCAARs2WMV6UMlLjLaoc0Dsdnj4Vlffc9T" +
213+
"t48lJU0RiCzXc280Vg/H5fm1xAP1B7UnIVcBqgDHDcfqWm1h/xSeCHXS");
214+
215+
PKCS8EncryptedPrivateKeyInfo info = new PKCS8EncryptedPrivateKeyInfo(pkcs8Scrypt);
216+
217+
PrivateKeyInfo pkInfo = info.decryptPrivateKeyInfo(
218+
new JceOpenSSLPKCS8DecryptorProviderBuilder()
219+
.setProvider("BC")
220+
.build("Rabbit".toCharArray()));
221+
222+
assertTrue(org.bouncycastle.util.Arrays.areEqual(expected, pkInfo.getEncoded()));
223+
}
224+
185225
public void testPKCS8PlainNew()
186226
throws Exception
187227
{

0 commit comments

Comments
 (0)