Sync pki to chromium 5934e28579cddeae3d9ae5b5974f8aae31f200dd

This brings in pem_unittest.cc which belongs in here now with
the pem stuff.

Change-Id: I04bb16692d31e2aa9de2c146785684c05543b1a7
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/63645
Commit-Queue: Bob Beck <bbe@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
Auto-Submit: Bob Beck <bbe@google.com>
diff --git a/pki/fillins/path_service.h b/pki/fillins/path_service.h
index c004f9c..ccc5014 100644
--- a/pki/fillins/path_service.h
+++ b/pki/fillins/path_service.h
@@ -27,7 +27,7 @@
 };
 
 enum PathKey {
-  DIR_SOURCE_ROOT = 0,
+  BSSL_TEST_DATA_ROOT = 0,
 };
 
 class PathService {
diff --git a/pki/import_spec.json b/pki/import_spec.json
index 198af1b..9420713 100644
--- a/pki/import_spec.json
+++ b/pki/import_spec.json
@@ -161,8 +161,8 @@
      "replace": "fillins::CollapseWhitespaceASCII"},
     {"match": "base::FilePath",
      "replace": "fillins::FilePath"},
-    {"match": "base::DIR_SOURCE_ROOT",
-     "replace": "fillins::DIR_SOURCE_ROOT"},
+    {"match": "base::DIR_SRC_TEST_DATA_ROOT",
+     "replace": "fillins::BSSL_TEST_DATA_ROOT"},
     {"match": "base::NetToHost16\\(",
      "replace": "ntohs("},
     {"match": "base::NetToHost32\\(",
@@ -330,6 +330,7 @@
     "net/cert/ocsp_verify_result.cc",
     "net/cert/pem.cc",
     "net/cert/pem.h",
+    "net/cert/pem_unittest.cc",
     "net/der/encode_values.cc",
     "net/der/encode_values.h",
     "net/der/encode_values_unittest.cc",
diff --git a/pki/pem_unittest.cc b/pki/pem_unittest.cc
new file mode 100644
index 0000000..12e05ec
--- /dev/null
+++ b/pki/pem_unittest.cc
@@ -0,0 +1,205 @@
+// Copyright 2010 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "pem.h"
+
+#include <gtest/gtest.h>
+
+namespace bssl {
+
+TEST(PEMTokenizerTest, BasicParsing) {
+  const char data[] =
+      "-----BEGIN EXPECTED-BLOCK-----\n"
+      "TWF0Y2hlc0FjY2VwdGVkQmxvY2tUeXBl\n"
+      "-----END EXPECTED-BLOCK-----\n";
+  std::string_view string_piece(data);
+  std::vector<std::string> accepted_types;
+  accepted_types.push_back("EXPECTED-BLOCK");
+
+  PEMTokenizer tokenizer(string_piece, accepted_types);
+  EXPECT_TRUE(tokenizer.GetNext());
+
+  EXPECT_EQ("EXPECTED-BLOCK", tokenizer.block_type());
+  EXPECT_EQ("MatchesAcceptedBlockType", tokenizer.data());
+
+  EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, CarriageReturnLineFeeds) {
+  const char data[] =
+      "-----BEGIN EXPECTED-BLOCK-----\r\n"
+      "TWF0Y2hlc0FjY2VwdGVkQmxvY2tUeXBl\r\n"
+      "-----END EXPECTED-BLOCK-----\r\n";
+  std::string_view string_piece(data);
+  std::vector<std::string> accepted_types;
+  accepted_types.push_back("EXPECTED-BLOCK");
+
+  PEMTokenizer tokenizer(string_piece, accepted_types);
+  EXPECT_TRUE(tokenizer.GetNext());
+
+  EXPECT_EQ("EXPECTED-BLOCK", tokenizer.block_type());
+  EXPECT_EQ("MatchesAcceptedBlockType", tokenizer.data());
+
+  EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, NoAcceptedBlockTypes) {
+  const char data[] =
+      "-----BEGIN UNEXPECTED-BLOCK-----\n"
+      "SWdub3Jlc1JlamVjdGVkQmxvY2tUeXBl\n"
+      "-----END UNEXPECTED-BLOCK-----\n";
+  std::string_view string_piece(data);
+  std::vector<std::string> accepted_types;
+  accepted_types.push_back("EXPECTED-BLOCK");
+
+  PEMTokenizer tokenizer(string_piece, accepted_types);
+  EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, MultipleAcceptedBlockTypes) {
+  const char data[] =
+      "-----BEGIN BLOCK-ONE-----\n"
+      "RW5jb2RlZERhdGFPbmU=\n"
+      "-----END BLOCK-ONE-----\n"
+      "-----BEGIN BLOCK-TWO-----\n"
+      "RW5jb2RlZERhdGFUd28=\n"
+      "-----END BLOCK-TWO-----\n";
+  std::string_view string_piece(data);
+  std::vector<std::string> accepted_types;
+  accepted_types.push_back("BLOCK-ONE");
+  accepted_types.push_back("BLOCK-TWO");
+
+  PEMTokenizer tokenizer(string_piece, accepted_types);
+  EXPECT_TRUE(tokenizer.GetNext());
+
+  EXPECT_EQ("BLOCK-ONE", tokenizer.block_type());
+  EXPECT_EQ("EncodedDataOne", tokenizer.data());
+
+  EXPECT_TRUE(tokenizer.GetNext());
+
+  EXPECT_EQ("BLOCK-TWO", tokenizer.block_type());
+  EXPECT_EQ("EncodedDataTwo", tokenizer.data());
+
+  EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, MissingFooter) {
+  const char data[] =
+      "-----BEGIN MISSING-FOOTER-----\n"
+      "RW5jb2RlZERhdGFPbmU=\n"
+      "-----END MISSING-FOOTER-----\n"
+      "-----BEGIN MISSING-FOOTER-----\n"
+      "RW5jb2RlZERhdGFUd28=\n";
+  std::string_view string_piece(data);
+  std::vector<std::string> accepted_types;
+  accepted_types.push_back("MISSING-FOOTER");
+
+  PEMTokenizer tokenizer(string_piece, accepted_types);
+  EXPECT_TRUE(tokenizer.GetNext());
+
+  EXPECT_EQ("MISSING-FOOTER", tokenizer.block_type());
+  EXPECT_EQ("EncodedDataOne", tokenizer.data());
+
+  EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, NestedEncoding) {
+  const char data[] =
+      "-----BEGIN BLOCK-ONE-----\n"
+      "RW5jb2RlZERhdGFPbmU=\n"
+      "-----BEGIN BLOCK-TWO-----\n"
+      "RW5jb2RlZERhdGFUd28=\n"
+      "-----END BLOCK-TWO-----\n"
+      "-----END BLOCK-ONE-----\n"
+      "-----BEGIN BLOCK-ONE-----\n"
+      "RW5jb2RlZERhdGFUaHJlZQ==\n"
+      "-----END BLOCK-ONE-----\n";
+  std::string_view string_piece(data);
+  std::vector<std::string> accepted_types;
+  accepted_types.push_back("BLOCK-ONE");
+
+  PEMTokenizer tokenizer(string_piece, accepted_types);
+  EXPECT_TRUE(tokenizer.GetNext());
+
+  EXPECT_EQ("BLOCK-ONE", tokenizer.block_type());
+  EXPECT_EQ("EncodedDataThree", tokenizer.data());
+
+  EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, EmptyAcceptedTypes) {
+  const char data[] =
+      "-----BEGIN BLOCK-ONE-----\n"
+      "RW5jb2RlZERhdGFPbmU=\n"
+      "-----END BLOCK-ONE-----\n";
+  std::string_view string_piece(data);
+  std::vector<std::string> accepted_types;
+
+  PEMTokenizer tokenizer(string_piece, accepted_types);
+  EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, BlockWithHeader) {
+  const char data[] =
+      "-----BEGIN BLOCK-ONE-----\n"
+      "Header-One: Data data data\n"
+      "Header-Two: \n"
+      "  continuation\n"
+      "Header-Three: Mix-And,Match\n"
+      "\n"
+      "RW5jb2RlZERhdGFPbmU=\n"
+      "-----END BLOCK-ONE-----\n"
+      "-----BEGIN BLOCK-ONE-----\n"
+      "RW5jb2RlZERhdGFUd28=\n"
+      "-----END BLOCK-ONE-----\n";
+  std::string_view string_piece(data);
+  std::vector<std::string> accepted_types;
+  accepted_types.push_back("BLOCK-ONE");
+
+  PEMTokenizer tokenizer(string_piece, accepted_types);
+  EXPECT_TRUE(tokenizer.GetNext());
+
+  EXPECT_EQ("BLOCK-ONE", tokenizer.block_type());
+  EXPECT_EQ("EncodedDataTwo", tokenizer.data());
+
+  EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMEncodeTest, Basic) {
+  EXPECT_EQ(
+      "-----BEGIN BLOCK-ONE-----\n"
+      "RW5jb2RlZERhdGFPbmU=\n"
+      "-----END BLOCK-ONE-----\n",
+      PEMEncode("EncodedDataOne", "BLOCK-ONE"));
+  EXPECT_EQ(
+      "-----BEGIN BLOCK-TWO-----\n"
+      "RW5jb2RlZERhdGFUd28=\n"
+      "-----END BLOCK-TWO-----\n",
+      PEMEncode("EncodedDataTwo", "BLOCK-TWO"));
+}
+
+TEST(PEMEncodeTest, Empty) {
+  EXPECT_EQ(
+      "-----BEGIN EMPTY-----\n"
+      "-----END EMPTY-----\n",
+      PEMEncode("", "EMPTY"));
+}
+
+TEST(PEMEncodeTest, Wrapping) {
+  EXPECT_EQ(
+      "-----BEGIN SINGLE LINE-----\n"
+      "MTIzNDU2Nzg5MGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktM\n"
+      "-----END SINGLE LINE-----\n",
+      PEMEncode("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL",
+                "SINGLE LINE"));
+
+  EXPECT_EQ(
+      "-----BEGIN WRAPPED LINE-----\n"
+      "MTIzNDU2Nzg5MGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktM\nTQ==\n"
+      "-----END WRAPPED LINE-----\n",
+      PEMEncode("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLM",
+                "WRAPPED LINE"));
+}
+
+}  // namespace net
diff --git a/pki/test_helpers.cc b/pki/test_helpers.cc
index 36f0ffb..8b4e033 100644
--- a/pki/test_helpers.cc
+++ b/pki/test_helpers.cc
@@ -415,7 +415,7 @@
 std::string ReadTestFileToString(const std::string& file_path_ascii) {
   // Compute the full path, relative to the src/ directory.
   fillins::FilePath src_root;
-  bssl::fillins::PathService::Get(fillins::DIR_SOURCE_ROOT, &src_root);
+  bssl::fillins::PathService::Get(fillins::BSSL_TEST_DATA_ROOT, &src_root);
   fillins::FilePath filepath = src_root.AppendASCII(file_path_ascii);
 
   // Read the full contents of the file.
diff --git a/pki/testdata/ssl/certificates/cronet-quic-chain.pem b/pki/testdata/ssl/certificates/cronet-quic-chain.pem
new file mode 100644
index 0000000..d40da79
--- /dev/null
+++ b/pki/testdata/ssl/certificates/cronet-quic-chain.pem
@@ -0,0 +1,159 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=Test Intermediate CA
+        Validity
+            Not Before: Jun  1 11:27:00 2023 GMT
+            Not After : May 29 11:27:00 2033 GMT
+        Subject: CN=localhost
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:f2:5e:81:dd:8a:91:07:17:5e:dc:89:e0:56:36:
+                    2d:5f:80:01:13:a6:64:48:04:79:3c:5e:08:23:75:
+                    bd:12:f9:d4:00:fb:47:7b:25:11:21:12:0b:05:25:
+                    6e:9e:04:b9:70:dc:5d:be:c3:59:a9:55:ff:3c:d4:
+                    95:b0:77:39:d0:31:0b:b4:c9:a4:c9:53:30:21:e8:
+                    58:f0:70:1c:fd:26:07:24:0d:ae:c9:0e:73:99:3e:
+                    e8:cb:cb:54:90:ae:49:90:90:89:c1:d3:82:23:d7:
+                    b5:dd:2b:0a:77:d0:10:c7:6f:71:b9:1c:d4:0b:39:
+                    f7:8d:ca:46:94:d4:cb:93:35:28:9d:36:90:cf:a8:
+                    ac:e5:70:e8:66:16:7b:5a:8b:9f:c4:e8:d0:e2:de:
+                    87:7b:6f:fa:0d:81:90:e2:13:34:a5:bb:58:72:1d:
+                    f1:51:1c:9d:4c:55:50:e5:98:21:bf:a9:b3:8b:fc:
+                    e8:74:30:32:f8:81:6c:b0:b0:f4:0b:16:94:d8:af:
+                    18:aa:03:09:a4:77:2f:ed:d9:d9:dd:b3:84:1f:a6:
+                    59:f7:ba:a7:16:b2:38:e2:f4:c7:bc:16:e6:98:46:
+                    82:d1:ce:f6:45:b9:0c:b9:f1:98:9d:92:36:33:cc:
+                    3f:08:80:02:25:9a:a7:8b:ca:2e:ab:8e:7f:44:5e:
+                    be:9b
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:FALSE
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication
+            X509v3 Subject Alternative Name: 
+                DNS:localhost
+            X509v3 Subject Key Identifier: 
+                F8:F0:38:9F:09:35:40:22:F7:E3:30:D8:FC:E5:A8:5C:1F:8C:D1:8C
+            X509v3 Authority Key Identifier: 
+                19:A2:E3:C8:BC:6D:CA:84:54:09:B3:67:4D:3E:60:CF:C4:78:38:95
+    Signature Algorithm: sha256WithRSAEncryption
+    Signature Value:
+        43:58:ea:e3:50:da:d4:50:9a:3e:d7:0a:82:c2:1c:ca:f8:05:
+        4d:4a:96:bf:6e:ab:0a:0a:ae:d3:4e:c4:ce:73:73:ec:c5:45:
+        d2:9b:9f:4c:cd:32:f9:27:a7:6f:59:bf:66:e9:78:b5:6b:10:
+        6a:e8:44:51:dc:f7:31:68:36:a8:d3:59:bc:90:94:48:d5:f5:
+        30:67:7e:bf:22:58:ca:0c:0a:79:6d:94:d6:9f:70:68:e4:24:
+        68:49:16:ea:d7:f9:1b:35:dc:93:49:56:d1:c1:1d:31:a5:c4:
+        34:c7:88:e8:20:0f:de:ea:b5:0a:6d:da:10:df:5a:db:ca:e5:
+        7d:1c:99:eb:f5:26:bf:cd:dd:67:31:8b:15:63:ea:90:68:fa:
+        83:81:48:e2:b9:94:02:c2:61:74:71:06:fe:e8:97:e0:f0:d2:
+        da:79:06:14:a2:44:94:93:32:f6:00:7d:28:ef:21:89:2e:2b:
+        69:45:cb:ae:a6:64:46:ae:db:36:d2:a3:fe:ed:8c:65:c0:c9:
+        51:42:73:df:21:2e:2d:51:35:c3:e8:54:76:5b:4e:0f:39:9d:
+        9f:f4:6e:9f:af:8c:9e:de:60:b2:e7:5d:5c:61:12:f3:4b:4b:
+        2e:08:ec:cc:20:90:00:36:f3:c4:14:b3:43:a2:f9:19:33:e5:
+        c3:01:56:9b
+-----BEGIN CERTIFICATE-----
+MIIDNTCCAh2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRUZXN0
+IEludGVybWVkaWF0ZSBDQTAeFw0yMzA2MDExMTI3MDBaFw0zMzA1MjkxMTI3MDBa
+MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAPJegd2KkQcXXtyJ4FY2LV+AAROmZEgEeTxeCCN1vRL51AD7R3slESES
+CwUlbp4EuXDcXb7DWalV/zzUlbB3OdAxC7TJpMlTMCHoWPBwHP0mByQNrskOc5k+
+6MvLVJCuSZCQicHTgiPXtd0rCnfQEMdvcbkc1As5943KRpTUy5M1KJ02kM+orOVw
+6GYWe1qLn8To0OLeh3tv+g2BkOITNKW7WHId8VEcnUxVUOWYIb+ps4v86HQwMviB
+bLCw9AsWlNivGKoDCaR3L+3Z2d2zhB+mWfe6pxayOOL0x7wW5phGgtHO9kW5DLnx
+mJ2SNjPMPwiAAiWap4vKLquOf0RevpsCAwEAAaOBhjCBgzAMBgNVHRMBAf8EAjAA
+MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAUBgNVHREEDTALgglsb2Nh
+bGhvc3QwHQYDVR0OBBYEFPjwOJ8JNUAi9+Mw2PzlqFwfjNGMMB8GA1UdIwQYMBaA
+FBmi48i8bcqEVAmzZ00+YM/EeDiVMA0GCSqGSIb3DQEBCwUAA4IBAQBDWOrjUNrU
+UJo+1wqCwhzK+AVNSpa/bqsKCq7TTsTOc3PsxUXSm59MzTL5J6dvWb9m6Xi1axBq
+6ERR3PcxaDao01m8kJRI1fUwZ36/IljKDAp5bZTWn3Bo5CRoSRbq1/kbNdyTSVbR
+wR0xpcQ0x4joIA/e6rUKbdoQ31rbyuV9HJnr9Sa/zd1nMYsVY+qQaPqDgUjiuZQC
+wmF0cQb+6Jfg8NLaeQYUokSUkzL2AH0o7yGJLitpRcuupmRGrts20qP+7YxlwMlR
+QnPfIS4tUTXD6FR2W04POZ2f9G6fr4ye3mCy511cYRLzS0suCOzMIJAANvPEFLND
+ovkZM+XDAVab
+-----END CERTIFICATE-----
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=Test Root CA
+        Validity
+            Not Before: Jun  1 11:27:00 2023 GMT
+            Not After : May 29 11:27:00 2033 GMT
+        Subject: CN=Test Intermediate CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b7:e9:bb:fb:dd:c9:fc:be:5c:a7:8a:ed:d0:c6:
+                    f8:89:33:d8:a7:81:db:85:43:dd:ba:46:7e:2a:44:
+                    76:58:81:f9:1d:e7:02:1c:11:8e:13:2c:51:c9:36:
+                    4b:a9:8f:d2:a7:d0:2d:0a:6e:16:33:08:a5:37:6e:
+                    75:56:12:70:6d:51:0e:83:a6:4c:91:b9:74:50:73:
+                    c6:3a:bf:82:0d:8a:f2:df:9c:dd:a4:ea:42:3e:eb:
+                    04:4a:22:44:d0:1b:2a:0d:d6:18:f6:b1:95:73:8f:
+                    f8:e5:c4:56:ff:5c:32:6a:5a:ec:42:93:79:18:a9:
+                    09:87:ff:40:d3:90:58:5b:9c:fa:5b:d5:46:b3:17:
+                    7c:e2:6f:9c:26:d1:18:18:0d:5a:3e:7f:7f:70:ca:
+                    ee:f5:e3:b7:8c:9c:b7:9d:72:ad:cb:1b:42:3f:21:
+                    98:29:0f:9e:83:db:e0:54:4c:24:54:fc:84:b8:53:
+                    92:ed:26:d6:4a:f6:10:c5:7e:1c:5c:dc:3b:b2:eb:
+                    90:4c:a6:93:91:75:9c:b3:91:95:d2:6d:7c:97:3f:
+                    69:fc:16:86:9e:c9:30:f2:85:64:a1:88:7b:c0:d3:
+                    28:67:c1:e6:51:84:00:5b:ed:12:e5:5a:8f:4a:14:
+                    22:39:0f:1b:76:5f:3f:87:91:55:d9:39:91:50:41:
+                    98:2d
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                19:A2:E3:C8:BC:6D:CA:84:54:09:B3:67:4D:3E:60:CF:C4:78:38:95
+            X509v3 Authority Key Identifier: 
+                55:D5:CD:87:69:64:E7:5F:FA:84:C9:B3:D1:9F:8E:A4:87:BC:F9:FD
+    Signature Algorithm: sha256WithRSAEncryption
+    Signature Value:
+        95:78:6d:c2:94:1d:1d:55:7f:91:01:57:a8:d6:9c:28:c2:13:
+        26:b7:25:67:f3:ea:28:be:99:e8:fb:9b:12:f1:90:86:a8:c9:
+        b3:db:14:10:d8:cf:67:75:cc:f9:5e:e8:a0:f8:0e:dc:fe:de:
+        78:13:63:b3:66:a5:a6:0e:2b:9b:8c:54:13:f0:14:de:26:55:
+        38:73:cf:6a:37:10:dd:3c:07:5a:c3:6f:cd:c4:96:ee:bf:8c:
+        35:bf:c6:9c:97:21:98:e0:5e:94:61:0d:3d:5c:f0:83:ce:29:
+        c0:2b:bf:b9:87:b8:c2:d1:b1:60:72:9a:ff:e6:8b:6b:89:e7:
+        db:2d:56:d8:a6:08:9c:6c:48:23:a4:da:d5:d7:17:b0:ef:4e:
+        ea:c2:df:ce:aa:dc:d7:12:4b:93:88:e5:d3:81:b7:8c:98:03:
+        51:2c:11:6b:23:07:5c:60:07:df:93:30:53:d6:6d:a8:cc:dd:
+        ad:00:4b:e8:80:1b:71:1f:6c:51:32:01:f9:f5:f2:91:8e:b3:
+        d4:13:26:b0:e3:6c:fa:64:b0:94:af:e7:12:b5:b9:7a:4c:20:
+        67:6e:bb:e7:c5:73:97:39:fe:1e:c9:0e:17:33:45:5b:90:f0:
+        da:6d:a8:49:cf:29:de:0a:cb:ac:0c:d0:59:0a:a5:d1:c8:0e:
+        19:96:2d:63
+-----BEGIN CERTIFICATE-----
+MIIDFDCCAfygAwIBAgIBATANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxUZXN0
+IFJvb3QgQ0EwHhcNMjMwNjAxMTEyNzAwWhcNMzMwNTI5MTEyNzAwWjAfMR0wGwYD
+VQQDDBRUZXN0IEludGVybWVkaWF0ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBALfpu/vdyfy+XKeK7dDG+Ikz2KeB24VD3bpGfipEdliB+R3nAhwR
+jhMsUck2S6mP0qfQLQpuFjMIpTdudVYScG1RDoOmTJG5dFBzxjq/gg2K8t+c3aTq
+Qj7rBEoiRNAbKg3WGPaxlXOP+OXEVv9cMmpa7EKTeRipCYf/QNOQWFuc+lvVRrMX
+fOJvnCbRGBgNWj5/f3DK7vXjt4yct51yrcsbQj8hmCkPnoPb4FRMJFT8hLhTku0m
+1kr2EMV+HFzcO7LrkEymk5F1nLORldJtfJc/afwWhp7JMPKFZKGIe8DTKGfB5lGE
+AFvtEuVaj0oUIjkPG3ZfP4eRVdk5kVBBmC0CAwEAAaNjMGEwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBmi48i8bcqEVAmzZ00+YM/E
+eDiVMB8GA1UdIwQYMBaAFFXVzYdpZOdf+oTJs9GfjqSHvPn9MA0GCSqGSIb3DQEB
+CwUAA4IBAQCVeG3ClB0dVX+RAVeo1pwowhMmtyVn8+oovpno+5sS8ZCGqMmz2xQQ
+2M9ndcz5Xuig+A7c/t54E2OzZqWmDiubjFQT8BTeJlU4c89qNxDdPAdaw2/NxJbu
+v4w1v8aclyGY4F6UYQ09XPCDzinAK7+5h7jC0bFgcpr/5otriefbLVbYpgicbEgj
+pNrV1xew707qwt/OqtzXEkuTiOXTgbeMmANRLBFrIwdcYAffkzBT1m2ozN2tAEvo
+gBtxH2xRMgH59fKRjrPUEyaw42z6ZLCUr+cStbl6TCBnbrvnxXOXOf4eyQ4XM0Vb
+kPDabahJzyneCsusDNBZCqXRyA4Zli1j
+-----END CERTIFICATE-----
diff --git a/pki/testdata/ssl/certificates/cronet-quic-leaf-cert.key b/pki/testdata/ssl/certificates/cronet-quic-leaf-cert.key
new file mode 100644
index 0000000..78e562b
--- /dev/null
+++ b/pki/testdata/ssl/certificates/cronet-quic-leaf-cert.key
Binary files differ
diff --git a/pki/testdata/ssl/certificates/cronet-quic-leaf-cert.key.pkcs8.pem b/pki/testdata/ssl/certificates/cronet-quic-leaf-cert.key.pkcs8.pem
new file mode 100644
index 0000000..a0c66c8
--- /dev/null
+++ b/pki/testdata/ssl/certificates/cronet-quic-leaf-cert.key.pkcs8.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDyXoHdipEHF17c
+ieBWNi1fgAETpmRIBHk8Xggjdb0S+dQA+0d7JREhEgsFJW6eBLlw3F2+w1mpVf88
+1JWwdznQMQu0yaTJUzAh6FjwcBz9JgckDa7JDnOZPujLy1SQrkmQkInB04Ij17Xd
+Kwp30BDHb3G5HNQLOfeNykaU1MuTNSidNpDPqKzlcOhmFntai5/E6NDi3od7b/oN
+gZDiEzSlu1hyHfFRHJ1MVVDlmCG/qbOL/Oh0MDL4gWywsPQLFpTYrxiqAwmkdy/t
+2dnds4Qfpln3uqcWsjji9Me8FuaYRoLRzvZFuQy58ZidkjYzzD8IgAIlmqeLyi6r
+jn9EXr6bAgMBAAECggEAYptnu6I+KgPsoM5pmrm1ERcrmPqH7zL+vjxFlGO5FqjV
+A8c4CFaG1w0XtbLKg/jJF61YaJ3i8z2dN/sHocGIIHdr21R0ukIhoZH96Wi7qpTX
+r+fvNjauemk5XnytvaVjIbFkkprCZmHDSKXTvvZEdVeWjak4bg7Z9HKY78F5x7w8
+x4aChtcWo6bsB8N2oQxsGYHew9v7KKNUiuqXMnvBSSnH/GOCwsYzlLfFCjhOdOrT
+7/J3VhtcP/7zMCARUosteOyjK8Mr8mt9KNNRjs1b0A1JU6yPdqsmkjEaYeeW/7K8
+SkxEzXwBJ9iBKPpVsAix25JCBxBpfDg7GYu0XGpcyQKBgQD5GMU8E6T5HOtB053C
+FR9ooMPLtKzCCW52nGZnXn4KfMTppGyl6uKmxj5adUVIOkzWwadGBSUDmj3BEW6F
+Tch/TbaPS2Mcsn7Vs9v9ZgzB4zJ2R0x0LJkkSncGy36uAQkdn/w7DyNru9Y7AelI
+fS2VM4SQDgNE2YVmOhbz9vcfeQKBgQD5FgHymkUogvr9P90JluNNPSfnG4NWa+Fn
+zq/oH+2X6VNcX1n69olmP4v4IirCKMMGWKS+xBplBVFExiMJj8RNhrAMDTzDUsip
+rrtsJHo/KsL63xuEcLlcDb0Z7YvolOs9CtZQ/RNUmQeD5wE/dDR8RaGbWtGxWjli
+I/MBkDFlswKBgQDaWQsy4SlgMChMGFoV8XwJs7pNSr+QisHoeKoO/DZRrRLSlw2h
+1qX8eJPZqSgk6u3F5hRhfUr3wHFXVpdhhFA2vwFC1Fs5oYEqhDFXCugt4KCwK0lq
+IFnRBPEW5LPThRbfgAiSIG/1FL7yFHfP0eJuAa22tIHLDT0w4cSS762pSQKBgQDC
+vWgManyDbdQZR2Li6msPqi6WSzZlzLZP2JJC4yN4z1zzLUFGKpXFsQ0XlG7lmcTQ
+I2G6G8ufC4c8Q8SE5zfMfO8KnRbShtiHgSrbI7HHzQDf7qqu4wqoY71ilgv6bON1
+uBFTBGmmjHYJtAOPvqSCPFZbBahIBogyqHSG1lsobQKBgQDTHrF6X0VDkR8V6rZg
+fhxFcOf9zkZEKOFP2aWQ0s3s5N/QEeaIiWLCJu8yLxCe15WzaHSl0iR/piwplRhC
+nqPufl86Yn6ztL1nI7Y9O/sCc0HBzAAntLW/L+ZCeqm28BWHCuMGySc/fUVUV04w
+lUGD7/W2H+/yr/TZOqXZY1ZVHw==
+-----END PRIVATE KEY-----
diff --git a/pki/testdata/ssl/certificates/cronet-quic-root.pem b/pki/testdata/ssl/certificates/cronet-quic-root.pem
new file mode 100644
index 0000000..1d22b86
--- /dev/null
+++ b/pki/testdata/ssl/certificates/cronet-quic-root.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/jCCAeagAwIBAgIUXOi6XoxnMUjJg4jeOwRhsdqEqEQwDQYJKoZIhvcNAQEL
+BQAwFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTIzMDYwMTExMjcwMFoXDTMz
+MDUyOTExMjcwMFowFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl9xCMPMIvfmJWz25AG/VtgWbqNs67HXQbXWf
+pDF2wjQpHVOYbfl7Zgly5O+5es1aUbJaGyZ9G6xuYSXKFnnYLoP7M86O05fQQBAj
+K+IE5nO6136ksCAfxCFTFfn4vhPvK8Vba5rqox4WeIXYKvHYSoiHz0ELrnFOHcyN
+Innyze7bLtkMCA1ShHpmvDCR+U3Uj6JwOfoirn29jjU/48/ORha7dcJYtYXk2eGo
+RJfrtIx20tXAaKaGnXOCGYbEVXTeQkQPqKFVzqP7+KYS/Y8eNFV35ugpLNES+44T
+bQ2QruTZdrNRjJkEoyiB/E53a0OUltB/R7Z0L0xstnKfsAf3OwIDAQABo0IwQDAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUVdXNh2lk
+51/6hMmz0Z+OpIe8+f0wDQYJKoZIhvcNAQELBQADggEBADNg7G8n6DUrQ5doXzm9
+kOp5siX6iPs0zFReXKhIT1Gef63l3tb7AdPedF03aj9XkUt0shhNOGG5SK2k5KBQ
+MJc9muYRCAyo2xMr3rFUQdI5B51SCy5HeAMralgTHXN0Hv+TH04YfRrACVmr+5ke
+pH3bF1gYaT+Zy5/pHJnV5lcwS6/H44g9XXWIopjWCwbfzKxIuWofqL4fiToPSIYu
+MCUI4bKZipcJT5O6rdz/S9lbgYVjOJ4HAoT2icNQqNMMfULKevmF8SdJzfNd35yn
+tAKTROhIE2aQRVCclrjo/T3eyjWGGoJlGmxKbeCf/rXzcn1BRtk/UzLnbUFFlg5l
+axw=
+-----END CERTIFICATE-----
diff --git a/sources.cmake b/sources.cmake
index 83073a6..1e90a60 100644
--- a/sources.cmake
+++ b/sources.cmake
@@ -410,6 +410,7 @@
   pki/path_builder_pkits_unittest.cc
   pki/path_builder_unittest.cc
   pki/path_builder_verify_certificate_chain_unittest.cc
+  pki/pem_unittest.cc
   pki/signature_algorithm_unittest.cc
   pki/simple_path_builder_delegate_unittest.cc
   pki/string_util_unittest.cc