|
29 | 29 | import org.bouncycastle.asn1.ASN1ObjectIdentifier; |
30 | 30 | import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; |
31 | 31 | import org.bouncycastle.asn1.cms.ContentInfo; |
| 32 | +import org.bouncycastle.asn1.edec.EdECObjectIdentifiers; |
32 | 33 | import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; |
33 | 34 | import org.bouncycastle.asn1.x509.KeyPurposeId; |
34 | 35 | import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; |
|
42 | 43 | import org.bouncycastle.openssl.CertificateTrustBlock; |
43 | 44 | import org.bouncycastle.openssl.PEMDecryptorProvider; |
44 | 45 | import org.bouncycastle.openssl.PEMEncryptedKeyPair; |
| 46 | +import org.bouncycastle.openssl.PEMEncryptor; |
45 | 47 | import org.bouncycastle.openssl.PEMKeyPair; |
46 | 48 | import org.bouncycastle.openssl.PEMParser; |
47 | 49 | import org.bouncycastle.openssl.X509TrustedCertificateBlock; |
|
50 | 52 | import org.bouncycastle.openssl.jcajce.JcaPEMWriter; |
51 | 53 | import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; |
52 | 54 | import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; |
| 55 | +import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder; |
53 | 56 | import org.bouncycastle.operator.InputDecryptorProvider; |
54 | 57 | import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; |
| 58 | +import org.bouncycastle.util.Arrays; |
55 | 59 | import org.bouncycastle.util.Strings; |
| 60 | +import org.bouncycastle.util.encoders.Base64; |
| 61 | +import org.bouncycastle.util.encoders.Hex; |
56 | 62 | import org.bouncycastle.util.test.SimpleTest; |
57 | 63 |
|
58 | 64 | /** |
@@ -378,6 +384,97 @@ public void performTest() |
378 | 384 |
|
379 | 385 | doOpenSslGost2012Test(); |
380 | 386 | doParseAttrECKeyTest(); |
| 387 | + doLegacyEncryptedPkcs8PemTest(); |
| 388 | + doLegacyEncryptedPkcs8GenPemTest(); |
| 389 | + } |
| 390 | + |
| 391 | + private void doLegacyEncryptedPkcs8PemTest() |
| 392 | + throws Exception |
| 393 | + { |
| 394 | + char[] password = "Vjvyhfngz0MCUs$kwOF0".toCharArray(); |
| 395 | + |
| 396 | + String pem = "-----BEGIN PRIVATE KEY-----\n" |
| 397 | + + "Proc-Type: 4,ENCRYPTED\n" |
| 398 | + + "DEK-Info: AES-128-CBC,b619a06a16b7b7a6436579f06a14f45e\n" |
| 399 | + + "\n" |
| 400 | + + "QmysBFzoMkgvVTM39kvHjkKhcBjK6PVMZ6a/taF44ZXeOl3t5DUp4EWxyfs8htng\n" |
| 401 | + + "tjsKIb0yKJigIZGrCeHROQ==\n" |
| 402 | + + "-----END PRIVATE KEY-----\n"; |
| 403 | + |
| 404 | + PEMParser parser = new PEMParser(new StringReader(pem.toString())); |
| 405 | + Object o = parser.readObject(); |
| 406 | + |
| 407 | + if (!(o instanceof PEMEncryptedKeyPair)) |
| 408 | + { |
| 409 | + fail("expected PEMEncryptedKeyPair, got " + (o == null ? "null" : o.getClass().getName())); |
| 410 | + } |
| 411 | + |
| 412 | + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder() |
| 413 | + .setProvider("BC").build(password); |
| 414 | + PEMKeyPair pkp = ((PEMEncryptedKeyPair)o).decryptKeyPair(decProv); |
| 415 | + |
| 416 | + PrivateKeyInfo decoded = pkp.getPrivateKeyInfo(); |
| 417 | + if (decoded == null) |
| 418 | + { |
| 419 | + fail("decrypted PrivateKeyInfo was null"); |
| 420 | + } |
| 421 | + isEquals(EdECObjectIdentifiers.id_Ed25519, decoded.getPrivateKeyAlgorithm().getAlgorithm()); |
| 422 | + } |
| 423 | + |
| 424 | + private void doLegacyEncryptedPkcs8GenPemTest() |
| 425 | + throws Exception |
| 426 | + { |
| 427 | + // Reproduces github #1238: a PKCS#8 PrivateKeyInfo wrapped in OpenSSL legacy |
| 428 | + // encryption headers ("Proc-Type: 4,ENCRYPTED" / "DEK-Info") under a |
| 429 | + // "BEGIN PRIVATE KEY" label. Earlier releases tried to ASN.1-parse the |
| 430 | + // ciphertext and failed with "corrupted stream" before this could be |
| 431 | + // recognised as encrypted. |
| 432 | + char[] password = "wibble".toCharArray(); |
| 433 | + |
| 434 | + KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519", "BC"); |
| 435 | + KeyPair kp = kpg.generateKeyPair(); |
| 436 | + |
| 437 | + byte[] pkcs8 = kp.getPrivate().getEncoded(); |
| 438 | + |
| 439 | + PEMEncryptor encryptor = new JcePEMEncryptorBuilder("AES-128-CBC") |
| 440 | + .setProvider("BC").build(password); |
| 441 | + |
| 442 | + byte[] encrypted = encryptor.encrypt(pkcs8); |
| 443 | + String ivHex = Strings.fromByteArray(Hex.encode(encryptor.getIV())); |
| 444 | + String b64 = Strings.fromByteArray(Base64.encode(encrypted)); |
| 445 | + |
| 446 | + StringBuilder pem = new StringBuilder(); |
| 447 | + pem.append("-----BEGIN PRIVATE KEY-----\n"); |
| 448 | + pem.append("Proc-Type: 4,ENCRYPTED\n"); |
| 449 | + pem.append("DEK-Info: AES-128-CBC,").append(ivHex).append("\n"); |
| 450 | + pem.append("\n"); |
| 451 | + for (int i = 0; i < b64.length(); i += 64) |
| 452 | + { |
| 453 | + pem.append(b64, i, Math.min(i + 64, b64.length())).append("\n"); |
| 454 | + } |
| 455 | + pem.append("-----END PRIVATE KEY-----\n"); |
| 456 | + |
| 457 | + PEMParser parser = new PEMParser(new StringReader(pem.toString())); |
| 458 | + Object o = parser.readObject(); |
| 459 | + |
| 460 | + if (!(o instanceof PEMEncryptedKeyPair)) |
| 461 | + { |
| 462 | + fail("expected PEMEncryptedKeyPair, got " + (o == null ? "null" : o.getClass().getName())); |
| 463 | + } |
| 464 | + |
| 465 | + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder() |
| 466 | + .setProvider("BC").build(password); |
| 467 | + PEMKeyPair pkp = ((PEMEncryptedKeyPair)o).decryptKeyPair(decProv); |
| 468 | + |
| 469 | + PrivateKeyInfo decoded = pkp.getPrivateKeyInfo(); |
| 470 | + if (decoded == null) |
| 471 | + { |
| 472 | + fail("decrypted PrivateKeyInfo was null"); |
| 473 | + } |
| 474 | + if (!Arrays.areEqual(pkcs8, decoded.getEncoded())) |
| 475 | + { |
| 476 | + fail("decrypted PrivateKeyInfo did not round-trip"); |
| 477 | + } |
381 | 478 | } |
382 | 479 |
|
383 | 480 | private void checkTrustedCert(X509TrustedCertificateBlock trusted) |
|
0 commit comments