16
16
package okhttp3.tls
17
17
18
18
import okhttp3.internal.canParseAsIpAddress
19
+ import okio.Buffer
19
20
import okio.ByteString
21
+ import okio.ByteString.Companion.decodeBase64
20
22
import okio.ByteString.Companion.toByteString
21
23
import org.bouncycastle.asn1.ASN1Encodable
22
24
import org.bouncycastle.asn1.DERSequence
@@ -27,14 +29,20 @@ import org.bouncycastle.asn1.x509.X509Extensions
27
29
import org.bouncycastle.jce.provider.BouncyCastleProvider
28
30
import org.bouncycastle.x509.X509V3CertificateGenerator
29
31
import java.math.BigInteger
32
+ import java.security.GeneralSecurityException
33
+ import java.security.KeyFactory
30
34
import java.security.KeyPair
31
35
import java.security.KeyPairGenerator
32
36
import java.security.PrivateKey
33
37
import java.security.PublicKey
34
38
import java.security.SecureRandom
35
39
import java.security.Security
40
+ import java.security.cert.CertificateFactory
36
41
import java.security.cert.X509Certificate
42
+ import java.security.interfaces.ECPublicKey
37
43
import java.security.interfaces.RSAPrivateKey
44
+ import java.security.interfaces.RSAPublicKey
45
+ import java.security.spec.PKCS8EncodedKeySpec
38
46
import java.util.Date
39
47
import java.util.UUID
40
48
import java.util.concurrent.TimeUnit
@@ -353,7 +361,7 @@ class HeldCertificate(
353
361
BigInteger .ONE
354
362
}
355
363
val signatureAlgorithm = if (signedByKeyPair.private is RSAPrivateKey ) {
356
- " SHA256WithRSAEncryption "
364
+ " SHA256WithRSA "
357
365
} else {
358
366
" SHA256withECDSA"
359
367
}
@@ -419,4 +427,103 @@ class HeldCertificate(
419
427
}
420
428
}
421
429
}
430
+
431
+ companion object {
432
+ private val PEM_REGEX = Regex (""" -----BEGIN ([!-,.-~ ]*)-----([^-]*)-----END \1-----""" )
433
+
434
+ /* *
435
+ * Decodes a multiline string that contains both a [certificate][certificatePem] and a
436
+ * [private key][privateKeyPkcs8Pem], both [PEM-encoded][rfc_7468]. A typical input string looks
437
+ * like this:
438
+ *
439
+ * ```
440
+ * -----BEGIN CERTIFICATE-----
441
+ * MIIBYTCCAQegAwIBAgIBKjAKBggqhkjOPQQDAjApMRQwEgYDVQQLEwtlbmdpbmVl
442
+ * cmluZzERMA8GA1UEAxMIY2FzaC5hcHAwHhcNNzAwMTAxMDAwMDA1WhcNNzAwMTAx
443
+ * MDAwMDEwWjApMRQwEgYDVQQLEwtlbmdpbmVlcmluZzERMA8GA1UEAxMIY2FzaC5h
444
+ * cHAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASda8ChkQXxGELnrV/oBnIAx3dD
445
+ * ocUOJfdz4pOJTP6dVQB9U3UBiW5uSX/MoOD0LL5zG3bVyL3Y6pDwKuYvfLNhoyAw
446
+ * HjAcBgNVHREBAf8EEjAQhwQBAQEBgghjYXNoLmFwcDAKBggqhkjOPQQDAgNIADBF
447
+ * AiAyHHg1N6YDDQiY920+cnI5XSZwEGhAtb9PYWO8bLmkcQIhAI2CfEZf3V/obmdT
448
+ * yyaoEufLKVXhrTQhRfodTeigi4RX
449
+ * -----END CERTIFICATE-----
450
+ * -----BEGIN PRIVATE KEY-----
451
+ * MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCA7ODT0xhGSNn4ESj6J
452
+ * lu/GJQZoU9lDrCPeUcQ28tzOWw==
453
+ * -----END PRIVATE KEY-----
454
+ * ```
455
+ *
456
+ * The string should contain exactly one certificate and one private key in [PKCS #8][rfc_5208]
457
+ * format. It should not contain any other PEM-encoded blocks, but it may contain other text
458
+ * which will be ignored.
459
+ *
460
+ * Encode a held certificate into this format by concatenating the results of
461
+ * [certificatePem()][certificatePem] and [privateKeyPkcs8Pem()][privateKeyPkcs8Pem].
462
+ *
463
+ * [rfc_7468]: https://tools.ietf.org/html/rfc7468
464
+ * [rfc_5208]: https://tools.ietf.org/html/rfc5208
465
+ */
466
+ @JvmStatic
467
+ fun decode (certificateAndPrivateKeyPem : String ): HeldCertificate {
468
+ var certificatePem: String? = null
469
+ var pkcs8Base64: String? = null
470
+ for (match in PEM_REGEX .findAll(certificateAndPrivateKeyPem)) {
471
+ when (val label = match.groups[1 ]!! .value) {
472
+ " CERTIFICATE" -> {
473
+ require(certificatePem == null ) { " string includes multiple certificates" }
474
+ certificatePem = match.groups[0 ]!! .value // Keep --BEGIN-- and --END-- for certificates.
475
+ }
476
+ " PRIVATE KEY" -> {
477
+ require(pkcs8Base64 == null ) { " string includes multiple private keys" }
478
+ pkcs8Base64 = match.groups[2 ]!! .value // Include the contents only for PKCS8.
479
+ }
480
+ else -> {
481
+ throw IllegalArgumentException (" unexpected type: $label " )
482
+ }
483
+ }
484
+ }
485
+ require(certificatePem != null ) { " string does not include a certificate" }
486
+ require(pkcs8Base64 != null ) { " string does not include a private key" }
487
+
488
+ return decode(certificatePem, pkcs8Base64)
489
+ }
490
+
491
+ private fun decode (certificatePem : String , pkcs8Base64Text : String ): HeldCertificate {
492
+ val certificate = try {
493
+ decodePem(certificatePem)
494
+ } catch (e: GeneralSecurityException ) {
495
+ throw IllegalArgumentException (" failed to decode certificate" , e)
496
+ }
497
+
498
+ val privateKey = try {
499
+ val pkcs8Bytes = pkcs8Base64Text.decodeBase64()
500
+ ? : throw IllegalArgumentException (" failed to decode private key" )
501
+
502
+ // The private key doesn't tell us its type but it's okay because the certificate knows!
503
+ val keyType = when (certificate.publicKey) {
504
+ is ECPublicKey -> " EC"
505
+ is RSAPublicKey -> " RSA"
506
+ else -> throw IllegalArgumentException (" unexpected key type: ${certificate.publicKey} " )
507
+ }
508
+
509
+ decodePkcs8(pkcs8Bytes, keyType)
510
+ } catch (e: GeneralSecurityException ) {
511
+ throw IllegalArgumentException (" failed to decode private key" , e)
512
+ }
513
+
514
+ val keyPair = KeyPair (certificate.publicKey, privateKey)
515
+ return HeldCertificate (keyPair, certificate)
516
+ }
517
+
518
+ private fun decodePem (pem : String ): X509Certificate {
519
+ val certificates = CertificateFactory .getInstance(" X.509" )
520
+ .generateCertificates(Buffer ().writeUtf8(pem).inputStream())
521
+ return certificates.iterator().next() as X509Certificate
522
+ }
523
+
524
+ private fun decodePkcs8 (data : ByteString , keyAlgorithm : String ): PrivateKey {
525
+ val keyFactory = KeyFactory .getInstance(keyAlgorithm)
526
+ return keyFactory.generatePrivate(PKCS8EncodedKeySpec (data.toByteArray()))
527
+ }
528
+ }
422
529
}
0 commit comments