Skip to content

Commit e86ce8b

Browse files
authored
crypto: add X509Certificate inspection getters
Expose version, subjectName, issuerName, extensions, tbs, and signature without requiring callers to parse display strings or certificate DER. Names include complete DER and grouped, ordered RDN attributes. Extensions retain their order, duplicate and unknown OIDs, criticality, and raw values. Expose the exact signed TBSCertificate bytes and the signature octets, algorithm OID, and DER parameters. Return fresh objects, arrays, and Buffers from the new getters, leaving existing getter caching unchanged. Report inspection failures as ERR_CRYPTO_INVALID_X509_CERTIFICATE. Fixes: #48730 Assisted-by: Codex Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #67082 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Tim Perry <pimterry@gmail.com>
1 parent d36e9ba commit e86ce8b

13 files changed

Lines changed: 1206 additions & 0 deletions

File tree

‎deps/ncrypto/ncrypto.cc‎

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ using BignumCtxPointer = DeleteFnPtr<BN_CTX, BN_CTX_free>;
4646
using BignumGenCallbackPointer = DeleteFnPtr<BN_GENCB, BN_GENCB_free>;
4747
using NetscapeSPKIPointer = DeleteFnPtr<NETSCAPE_SPKI, NETSCAPE_SPKI_free>;
4848

49+
struct ASN1SequenceDeleter final {
50+
void operator()(ASN1_SEQUENCE_ANY* sequence) const {
51+
sk_ASN1_TYPE_pop_free(sequence, ASN1_TYPE_free);
52+
}
53+
};
54+
55+
using ASN1SequencePointer =
56+
std::unique_ptr<ASN1_SEQUENCE_ANY, ASN1SequenceDeleter>;
57+
4958
#if NCRYPTO_USE_OPENSSL_PROVIDER
5059
using X509PubKeyPointer = DeleteFnPtr<X509_PUBKEY, X509_PUBKEY_free>;
5160
// OSSL_STORE_close() returns int, so it needs a void-returning adapter to be
@@ -1606,6 +1615,164 @@ bool X509View::isCA() const {
16061615
return X509_check_ca(const_cast<X509*>(cert_)) == 1;
16071616
}
16081617

1618+
namespace {
1619+
bool CopyStringBytes(const ASN1_STRING* value,
1620+
std::vector<unsigned char>* output) {
1621+
if (value == nullptr) return false;
1622+
const int length = ASN1_STRING_length(value);
1623+
const unsigned char* data = ASN1_STRING_get0_data(value);
1624+
if (length < 0 || (length > 0 && data == nullptr)) return false;
1625+
if (length == 0) {
1626+
output->clear();
1627+
} else {
1628+
output->assign(data, data + static_cast<size_t>(length));
1629+
}
1630+
return true;
1631+
}
1632+
1633+
bool CanConvertNameValueToUtf8(const ASN1_STRING* value) {
1634+
if (value == nullptr) return false;
1635+
switch (ASN1_STRING_type(value)) {
1636+
case V_ASN1_UTF8STRING:
1637+
case V_ASN1_NUMERICSTRING:
1638+
case V_ASN1_PRINTABLESTRING:
1639+
case V_ASN1_T61STRING:
1640+
case V_ASN1_IA5STRING:
1641+
case V_ASN1_UTCTIME:
1642+
case V_ASN1_GENERALIZEDTIME:
1643+
case V_ASN1_ISO64STRING:
1644+
case V_ASN1_UNIVERSALSTRING:
1645+
case V_ASN1_BMPSTRING:
1646+
return true;
1647+
default:
1648+
return false;
1649+
}
1650+
}
1651+
1652+
std::optional<size_t> GetEncodedRdnCount(const X509_NAME* name) {
1653+
const unsigned char* der = nullptr;
1654+
size_t der_length = 0;
1655+
if (X509_NAME_get0_der(name, &der, &der_length) != 1 || der == nullptr ||
1656+
der_length > static_cast<size_t>(LONG_MAX)) {
1657+
return std::nullopt;
1658+
}
1659+
1660+
const unsigned char* cursor = der;
1661+
ASN1SequencePointer rdns(d2i_ASN1_SEQUENCE_ANY(
1662+
nullptr, &cursor, static_cast<long>(der_length))); // NOLINT(runtime/int)
1663+
if (!rdns || cursor != der + der_length) return std::nullopt;
1664+
1665+
const OPENSSL_SIZE_T count = sk_ASN1_TYPE_num(rdns.get());
1666+
for (OPENSSL_SIZE_T i = 0; i < count; i++) {
1667+
const ASN1_TYPE* rdn = sk_ASN1_TYPE_value(rdns.get(), i);
1668+
if (rdn == nullptr || ASN1_TYPE_get(rdn) != V_ASN1_SET) {
1669+
return std::nullopt;
1670+
}
1671+
}
1672+
return static_cast<size_t>(count);
1673+
}
1674+
1675+
std::optional<X509NameInspection> InspectName(const X509_NAME* name) {
1676+
if (name == nullptr) return std::nullopt;
1677+
const int count = X509_NAME_entry_count(name);
1678+
if (count < 0) return std::nullopt;
1679+
const auto encoded_rdn_count = GetEncodedRdnCount(name);
1680+
if (!encoded_rdn_count.has_value()) return std::nullopt;
1681+
1682+
X509NameInspection output;
1683+
output.reserve(static_cast<size_t>(count));
1684+
int previous_set = -1;
1685+
for (int i = 0; i < count; i++) {
1686+
const X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, i);
1687+
if (entry == nullptr) return std::nullopt;
1688+
1689+
const ASN1_OBJECT* object = X509_NAME_ENTRY_get_object(entry);
1690+
const ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry);
1691+
auto oid = ObjectToText(object, true);
1692+
const int set = X509_NAME_ENTRY_set(entry);
1693+
if (!oid.has_value() || value == nullptr || set < 0 ||
1694+
(set != previous_set &&
1695+
(previous_set == INT_MAX || set != previous_set + 1))) {
1696+
return std::nullopt;
1697+
}
1698+
previous_set = set;
1699+
1700+
X509NameAttribute attribute{
1701+
.set = set,
1702+
.oid = std::move(oid.value()),
1703+
};
1704+
if (CanConvertNameValueToUtf8(value)) {
1705+
unsigned char* data = nullptr;
1706+
const int length = ASN1_STRING_to_UTF8(&data, value);
1707+
DataPointer owned(data, length > 0 ? static_cast<size_t>(length) : 0);
1708+
if (length < 0 || (length > 0 && !owned)) return std::nullopt;
1709+
attribute.value =
1710+
length == 0 ? std::string()
1711+
: std::string(reinterpret_cast<const char*>(owned.get()),
1712+
static_cast<size_t>(length));
1713+
}
1714+
output.push_back(std::move(attribute));
1715+
}
1716+
const size_t inspected_rdn_count =
1717+
previous_set < 0 ? 0 : static_cast<size_t>(previous_set) + 1;
1718+
if (inspected_rdn_count != encoded_rdn_count.value()) return std::nullopt;
1719+
return output;
1720+
}
1721+
1722+
std::optional<std::vector<X509RawExtension>> InspectExtensions(
1723+
const X509* certificate) {
1724+
if (certificate == nullptr) return std::nullopt;
1725+
const int count = X509_get_ext_count(certificate);
1726+
if (count < 0) return std::nullopt;
1727+
1728+
std::vector<X509RawExtension> output;
1729+
output.reserve(static_cast<size_t>(count));
1730+
for (int i = 0; i < count; i++) {
1731+
const X509_EXTENSION* extension = X509_get_ext(certificate, i);
1732+
if (extension == nullptr) return std::nullopt;
1733+
1734+
auto* mutable_extension = const_cast<X509_EXTENSION*>(extension);
1735+
auto oid = ObjectToText(X509_EXTENSION_get_object(mutable_extension), true);
1736+
const ASN1_OCTET_STRING* value = X509_EXTENSION_get_data(mutable_extension);
1737+
if (!oid.has_value() || value == nullptr) return std::nullopt;
1738+
1739+
X509RawExtension inspected{
1740+
.oid = std::move(oid.value()),
1741+
.critical = X509_EXTENSION_get_critical(mutable_extension) != 0,
1742+
};
1743+
if (!CopyStringBytes(value, &inspected.value)) return std::nullopt;
1744+
output.push_back(std::move(inspected));
1745+
}
1746+
return output;
1747+
}
1748+
} // namespace
1749+
1750+
std::optional<int64_t> X509View::getVersion() const {
1751+
MarkPopErrorOnReturn mark_pop_error_on_return;
1752+
if (cert_ == nullptr) return std::nullopt;
1753+
const long version = X509_get_version(cert_); // NOLINT(runtime/int)
1754+
if (version < 0 || version > 2) return std::nullopt;
1755+
return static_cast<int64_t>(version) + 1;
1756+
}
1757+
1758+
std::optional<X509NameInspection> X509View::inspectSubjectName() const {
1759+
MarkPopErrorOnReturn mark_pop_error_on_return;
1760+
if (cert_ == nullptr) return std::nullopt;
1761+
return InspectName(X509_get_subject_name(cert_));
1762+
}
1763+
1764+
std::optional<X509NameInspection> X509View::inspectIssuerName() const {
1765+
MarkPopErrorOnReturn mark_pop_error_on_return;
1766+
if (cert_ == nullptr) return std::nullopt;
1767+
return InspectName(X509_get_issuer_name(cert_));
1768+
}
1769+
1770+
std::optional<std::vector<X509RawExtension>> X509View::inspectExtensions()
1771+
const {
1772+
MarkPopErrorOnReturn mark_pop_error_on_return;
1773+
return InspectExtensions(cert_);
1774+
}
1775+
16091776
bool X509View::isIssuedBy(const X509View& issuer) const {
16101777
MarkPopErrorOnReturn mark_pop_error_on_return;
16111778
if (cert_ == nullptr || issuer.cert_ == nullptr) return false;

‎deps/ncrypto/ncrypto.h‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@
6161

6262
#define NCRYPTO_USE_LEGACY_KEY_TYPES NCRYPTO_USE_BORINGSSL
6363

64+
// OpenSSL 4 makes ASN1_STRING opaque and provides a checked BIT STRING length
65+
// accessor. Earlier OpenSSL releases and pinned BoringSSL expose the decoded
66+
// unused-bit count through ASN1_STRING::flags.
67+
#if !NCRYPTO_USE_BORINGSSL && OPENSSL_VERSION_PREREQ(4, 0)
68+
#define NCRYPTO_HAS_ASN1_BIT_STRING_GET_LENGTH 1
69+
#else
70+
#define NCRYPTO_HAS_ASN1_BIT_STRING_GET_LENGTH 0
71+
#endif
72+
6473
#if NCRYPTO_USE_OPENSSL_PROVIDER
6574
#include <openssl/core_names.h>
6675
#include <openssl/encoder.h>
@@ -1648,6 +1657,20 @@ class X509Name final {
16481657
int total_;
16491658
};
16501659

1660+
struct X509NameAttribute final {
1661+
int set = 0;
1662+
std::string oid;
1663+
std::optional<std::string> value;
1664+
};
1665+
1666+
using X509NameInspection = std::vector<X509NameAttribute>;
1667+
1668+
struct X509RawExtension final {
1669+
std::string oid;
1670+
bool critical = false;
1671+
std::vector<unsigned char> value;
1672+
};
1673+
16511674
class X509View final {
16521675
public:
16531676
static X509View From(const SSLPointer& ssl);
@@ -1685,6 +1708,11 @@ class X509View final {
16851708
Result<EVPKeyPointer, int> getPublicKey() const;
16861709
StackOfASN1 getKeyUsage() const;
16871710

1711+
std::optional<int64_t> getVersion() const;
1712+
std::optional<X509NameInspection> inspectSubjectName() const;
1713+
std::optional<X509NameInspection> inspectIssuerName() const;
1714+
std::optional<std::vector<X509RawExtension>> inspectExtensions() const;
1715+
16881716
bool isCA() const;
16891717
bool isIssuedBy(const X509View& other) const;
16901718
bool checkPrivateKey(const EVPKeyPointer& pkey) const;

0 commit comments

Comments
 (0)