Implement Subject parsing in CertificateView.
This is meant to eventually replace QuicCertUtils::ExtractSubjectNameFromDERCert().
PiperOrigin-RevId: 339947896
Change-Id: I24e9955165976f5d8d3d1645c764b3ac32608def
diff --git a/quic/core/crypto/certificate_view.cc b/quic/core/crypto/certificate_view.cc
index 58cfe19..6042b9e 100644
--- a/quic/core/crypto/certificate_view.cc
+++ b/quic/core/crypto/certificate_view.cc
@@ -9,7 +9,9 @@
#include <memory>
#include <string>
+#include "absl/strings/escaping.h"
#include "absl/strings/match.h"
+#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "third_party/boringssl/src/include/openssl/base.h"
@@ -32,6 +34,7 @@
#include "net/third_party/quiche/src/common/platform/api/quiche_time_utils.h"
#include "net/third_party/quiche/src/common/quiche_data_reader.h"
+namespace quic {
namespace {
using ::quiche::QuicheTextUtils;
@@ -100,9 +103,78 @@
}
}
+std::string AttributeNameToString(const CBS& oid_cbs) {
+ absl::string_view oid = CbsToStringPiece(oid_cbs);
+
+ // We only handle OIDs of form 2.5.4.N, which have binary encoding of
+ // "55 04 0N".
+ if (oid.length() == 3 && absl::StartsWith(oid, "\x55\x04")) {
+ // clang-format off
+ switch (oid[2]) {
+ case '\x3': return "CN";
+ case '\x7': return "L";
+ case '\x8': return "ST";
+ case '\xa': return "O";
+ case '\xb': return "OU";
+ case '\x6': return "C";
+ }
+ // clang-format on
+ }
+
+ bssl::UniquePtr<char> oid_representation(CBS_asn1_oid_to_text(&oid_cbs));
+ if (oid_representation == nullptr) {
+ return quiche::QuicheStrCat("(", absl::BytesToHexString(oid), ")");
+ }
+ return std::string(oid_representation.get());
+}
+
} // namespace
-namespace quic {
+absl::optional<std::string> X509NameAttributeToString(CBS input) {
+ CBS name, value;
+ unsigned value_tag;
+ if (!CBS_get_asn1(&input, &name, CBS_ASN1_OBJECT) ||
+ !CBS_get_any_asn1(&input, &value, &value_tag) || CBS_len(&input) != 0) {
+ return absl::nullopt;
+ }
+ // Note that this does not process encoding of |input| in any way. This works
+ // fine for the most cases.
+ return quiche::QuicheStrCat(AttributeNameToString(name), "=",
+ absl::CHexEscape(CbsToStringPiece(value)));
+}
+
+namespace {
+
+template <unsigned inner_tag,
+ char separator,
+ absl::optional<std::string> (*parser)(CBS)>
+absl::optional<std::string> ParseAndJoin(CBS input) {
+ std::vector<std::string> pieces;
+ while (CBS_len(&input) != 0) {
+ CBS attribute;
+ if (!CBS_get_asn1(&input, &attribute, inner_tag)) {
+ return absl::nullopt;
+ }
+ absl::optional<std::string> formatted = parser(attribute);
+ if (!formatted.has_value()) {
+ return absl::nullopt;
+ }
+ pieces.push_back(*formatted);
+ }
+
+ return absl::StrJoin(pieces, std::string({separator}));
+}
+
+absl::optional<std::string> RelativeDistinguishedNameToString(CBS input) {
+ return ParseAndJoin<CBS_ASN1_SEQUENCE, '+', X509NameAttributeToString>(input);
+}
+
+absl::optional<std::string> DistinguishedNameToString(CBS input) {
+ return ParseAndJoin<CBS_ASN1_SET, ',', RelativeDistinguishedNameToString>(
+ input);
+}
+
+} // namespace
absl::optional<quic::QuicWallTime> ParseDerTime(unsigned tag,
absl::string_view payload) {
@@ -257,6 +329,8 @@
return nullptr;
}
+ result->subject_der_ = CbsToStringPiece(subject);
+
unsigned not_before_tag, not_after_tag;
CBS not_before, not_after;
if (!CBS_get_any_asn1(&validity, ¬_before, ¬_before_tag) ||
@@ -446,6 +520,11 @@
data.size());
}
+absl::optional<std::string> CertificateView::GetHumanReadableSubject() const {
+ CBS input = StringPieceToCbs(subject_der_);
+ return DistinguishedNameToString(input);
+}
+
std::unique_ptr<CertificatePrivateKey> CertificatePrivateKey::LoadFromDer(
absl::string_view private_key) {
std::unique_ptr<CertificatePrivateKey> result(new CertificatePrivateKey());
diff --git a/quic/core/crypto/certificate_view.h b/quic/core/crypto/certificate_view.h
index 8ec292b..f2e99ba 100644
--- a/quic/core/crypto/certificate_view.h
+++ b/quic/core/crypto/certificate_view.h
@@ -60,6 +60,10 @@
return subject_alt_name_ips_;
}
+ // Returns a human-readable representation of the Subject field. The format
+ // is similar to RFC 2253, but does not match it exactly.
+ absl::optional<std::string> GetHumanReadableSubject() const;
+
// |signature_algorithm| is a TLS signature algorithm ID.
bool VerifySignature(absl::string_view data,
absl::string_view signature,
@@ -70,6 +74,7 @@
QuicWallTime validity_start_ = QuicWallTime::Zero();
QuicWallTime validity_end_ = QuicWallTime::Zero();
+ absl::string_view subject_der_;
// Public key parsed from SPKI.
bssl::UniquePtr<EVP_PKEY> public_key_;
@@ -118,6 +123,9 @@
bssl::UniquePtr<EVP_PKEY> private_key_;
};
+// Parses a DER-encoded X.509 NameAttribute. Exposed primarily for testing.
+absl::optional<std::string> X509NameAttributeToString(CBS input);
+
// Parses a DER time based on the specified ASN.1 tag. Exposed primarily for
// testing.
QUIC_EXPORT_PRIVATE absl::optional<quic::QuicWallTime> ParseDerTime(
diff --git a/quic/core/crypto/certificate_view_der_fuzzer.cc b/quic/core/crypto/certificate_view_der_fuzzer.cc
index e2de1ba..e66f4f9 100644
--- a/quic/core/crypto/certificate_view_der_fuzzer.cc
+++ b/quic/core/crypto/certificate_view_der_fuzzer.cc
@@ -9,7 +9,11 @@
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
std::string input(reinterpret_cast<const char*>(data), size);
- quic::CertificateView::ParseSingleCertificate(input);
+ std::unique_ptr<quic::CertificateView> view =
+ quic::CertificateView::ParseSingleCertificate(input);
+ if (view != nullptr) {
+ view->GetHumanReadableSubject();
+ }
quic::CertificatePrivateKey::LoadFromDer(input);
return 0;
}
diff --git a/quic/core/crypto/certificate_view_test.cc b/quic/core/crypto/certificate_view_test.cc
index 7f8b60c..46e30f1 100644
--- a/quic/core/crypto/certificate_view_test.cc
+++ b/quic/core/crypto/certificate_view_test.cc
@@ -7,11 +7,13 @@
#include <memory>
#include <sstream>
+#include "absl/strings/escaping.h"
#include "absl/strings/string_view.h"
#include "third_party/boringssl/src/include/openssl/base.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
+#include "net/third_party/quiche/src/quic/core/crypto/boring_utils.h"
#include "net/third_party/quiche/src/quic/core/quic_time.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
@@ -56,6 +58,9 @@
const QuicWallTime validity_end = QuicWallTime::FromUNIXSeconds(
*quiche::QuicheUtcDateTimeToUnixSeconds(2020, 2, 2, 18, 13, 59));
EXPECT_EQ(view->validity_end(), validity_end);
+
+ EXPECT_EQ("C=US,ST=California,L=Mountain View,O=QUIC Server,CN=127.0.0.1",
+ view->GetHumanReadableSubject());
}
TEST(CertificateViewTest, ParseCertWithUnknownSanType) {
@@ -180,6 +185,28 @@
absl::nullopt);
}
+TEST(CertificateViewTest, NameAttribute) {
+ // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.112411 }
+ // UTF8String { "Test" }
+ std::string unknown_oid =
+ absl::HexStringToBytes("060b2a864886f712040186ee1b0c0454657374");
+ EXPECT_EQ("1.2.840.113554.4.1.112411=Test",
+ X509NameAttributeToString(StringPieceToCbs(unknown_oid)));
+
+ // OBJECT_IDENTIFIER { 2.5.4.3 }
+ // UTF8String { "Bell: \x07" }
+ std::string non_printable =
+ absl::HexStringToBytes("06035504030c0742656c6c3a2007");
+ EXPECT_EQ(R"(CN=Bell: \x07)",
+ X509NameAttributeToString(StringPieceToCbs(non_printable)));
+
+ // OBJECT_IDENTIFIER { "\x55\x80" }
+ // UTF8String { "Test" }
+ std::string invalid_oid = absl::HexStringToBytes("060255800c0454657374");
+ EXPECT_EQ("(5580)=Test",
+ X509NameAttributeToString(StringPieceToCbs(invalid_oid)));
+}
+
} // namespace
} // namespace test
} // namespace quic