From 76653987e46306eb93b8230ad2bf9ebe5ec98388 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Wed, 16 Jul 2025 04:54:05 -0400 Subject: [PATCH 01/14] Add CertificateParametersBuilder as a replacement for X509_Cert_Options --- src/cli/perf_x509.cpp | 23 ++- src/lib/utils/types.h | 2 +- src/lib/x509/info.txt | 3 +- src/lib/x509/pkix_enums.h | 2 + src/lib/x509/x509_builder.cpp | 288 ++++++++++++++++++++++++++++++++ src/lib/x509/x509_builder.h | 184 ++++++++++++++++++++ src/lib/x509/x509opt.cpp | 71 ++++++++ src/lib/x509/x509self.cpp | 136 ++------------- src/lib/x509/x509self.h | 7 + src/tests/test_x509_rpki.cpp | 305 +++++++++++++--------------------- 10 files changed, 703 insertions(+), 318 deletions(-) create mode 100644 src/lib/x509/x509_builder.cpp create mode 100644 src/lib/x509/x509_builder.h diff --git a/src/cli/perf_x509.cpp b/src/cli/perf_x509.cpp index 92429892e12..cd7c975f035 100644 --- a/src/cli/perf_x509.cpp +++ b/src/cli/perf_x509.cpp @@ -16,9 +16,9 @@ #include #include #include + #include #include #include - #include #endif namespace Botan_CLI { @@ -40,14 +40,23 @@ class PerfTest_ASN1_Parsing final : public PerfTest { } static CA create_ca(Botan::RandomNumberGenerator& rng) { - auto root_cert_options = Botan::X509_Cert_Options("Benchmark Root/DE/RS/CS"); - root_cert_options.dns = "unobtainium.example.com"; - root_cert_options.email = "idont@exist.com"; - root_cert_options.is_CA = true; - auto root_key = create_private_key(rng); BOTAN_ASSERT_NONNULL(root_key); - auto root_cert = Botan::X509::create_self_signed_cert(root_cert_options, *root_key, get_hash_function(), rng); + + Botan::CertificateParametersBuilder root_cert_params; + root_cert_params.add_common_name("Benchmark Root") + .add_country("DE") + .add_organization("RS") + .add_organizational_unit("CS") + .add_dns("unobtainium.example.com") + .add_email("idont@exist.com") + .set_as_ca_certificate(); + + const auto not_before = std::chrono::system_clock::now(); + const auto not_after = not_before + std::chrono::seconds(86400); + + auto root_cert = + root_cert_params.into_self_signed_cert(not_before, not_after, *root_key, rng, get_hash_function()); auto ca = Botan::X509_CA(root_cert, *root_key, get_hash_function(), rng); return CA{ diff --git a/src/lib/utils/types.h b/src/lib/utils/types.h index 47756252a65..2c0b8d02124 100644 --- a/src/lib/utils/types.h +++ b/src/lib/utils/types.h @@ -86,7 +86,7 @@ namespace Botan { * TLS::Client, TLS::Server, TLS::Policy, TLS::Protocol_Version, TLS::Callbacks, TLS::Ciphersuite, * TLS::Session, TLS::Session_Summary, TLS::Session_Manager, Credentials_Manager *
X.509
-* X509_Certificate, X509_CRL, X509_CA, Certificate_Extension, PKCS10_Request, X509_Cert_Options, +* X509_Certificate, X509_CRL, X509_CA, Certificate_Extension, PKCS10_Request, CertificateParametersBuilder, * Certificate_Store, Certificate_Store_In_SQL, Certificate_Store_In_SQLite *
eXtendable Output Functions
* @ref Ascon_XOF128 "Ascon-XOF128", @ref SHAKE_XOF "SHAKE" diff --git a/src/lib/x509/info.txt b/src/lib/x509/info.txt index 831783bedee..dd05c67a99f 100644 --- a/src/lib/x509/info.txt +++ b/src/lib/x509/info.txt @@ -1,6 +1,6 @@ X509_CERTIFICATES -> 20201106 -X509 -> 20201106 +X509 -> 20250716 OCSP -> 20201106 @@ -22,6 +22,7 @@ ocsp.h pkcs10.h pkix_enums.h pkix_types.h +x509_builder.h x509_ca.h x509_crl.h x509_ext.h diff --git a/src/lib/x509/pkix_enums.h b/src/lib/x509/pkix_enums.h index 3cdfb27e764..4bc7dd863e0 100644 --- a/src/lib/x509/pkix_enums.h +++ b/src/lib/x509/pkix_enums.h @@ -167,6 +167,8 @@ class BOTAN_PUBLIC_API(3, 0) Key_Constraints final { void operator|=(Key_Constraints::Bits other) { m_value |= other; } + void operator|=(Key_Constraints other) { m_value |= other.m_value; } + // Return true if all bits in mask are set bool includes(Key_Constraints::Bits other) const { return (m_value & other) == other; } diff --git a/src/lib/x509/x509_builder.cpp b/src/lib/x509/x509_builder.cpp new file mode 100644 index 00000000000..d4f5bc30b2e --- /dev/null +++ b/src/lib/x509/x509_builder.cpp @@ -0,0 +1,288 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include + +namespace Botan { + +class CertificateParametersBuilder::State final { + public: + const X509_DN& subject_dn() const { return m_subject_dn; } + + Extensions finalize_extensions(const Public_Key& key) const { + auto extensions = m_extensions; + + extensions.replace(setup_alt_name(extensions)); + + extensions.add_new( + std::make_unique(m_is_ca_request, m_path_limit.value_or(0)), true); + + const Key_Constraints usage = this->usage(); + + if(!usage.empty()) { + if(!usage.compatible_with(key)) { + throw Invalid_Argument("The requested key usage is incompatible with the algorithm"); + } + + extensions.add_new(std::make_unique(usage), true); + } + + if(!m_ext_usage.empty()) { + extensions.add_new(std::make_unique(m_ext_usage)); + } + + return extensions; + } + + Key_Constraints usage() const { + if(m_is_ca_request) { + return Key_Constraints::ca_constraints(); + } else { + return m_usage; + } + } + + void add_common_name(std::string_view cn) { add_subject_dn("common_name", "X520.CommonName", cn); } + + void add_country(std::string_view country) { add_subject_dn("country", "X520.Country", country); } + + void add_state(std::string_view state) { add_subject_dn("state", "X520.State", state); } + + void add_locality(std::string_view locality) { add_subject_dn("locality", "X520.Locality", locality); } + + void add_serial_number(std::string_view sn) { add_subject_dn("serial_number", "X520.SerialNumber", sn); } + + void add_organization(std::string_view org) { add_subject_dn("organization", "X520.Organization", org); } + + void add_organizational_unit(std::string_view org_unit) { + add_subject_dn("organizational_unit", "X520.OrganizationalUnit", org_unit); + } + + void add_extension(std::unique_ptr extn, bool is_critical) { + if(!m_extensions.add_new(std::move(extn), is_critical)) { + throw Invalid_Argument("CertificateParametersBuilder::add_extension: cannot add same extension twice"); + } + } + + void add_email(std::string_view email) { m_email.emplace_back(email); } + + void add_dns(std::string_view dns) { m_dns.emplace_back(dns); } + + void add_uri(std::string_view uri) { m_uri.emplace_back(uri); } + + void add_xmpp(std::string_view xmpp) { m_xmpp.emplace_back(xmpp); } + + void add_ipv4(uint32_t ipv4) { m_ipv4.push_back(ipv4); } + + void add_allowed_usage(Key_Constraints usage) { m_usage |= usage; } + + void add_allowed_extended_usage(const OID& usage) { m_ext_usage.push_back(usage); } + + void set_as_ca_certificate(std::optional path_limit) { + if(m_is_ca_request) { + throw Invalid_State("CertificateParametersBuilder::set_as_ca_certificate cannot be called twice"); + } else { + m_is_ca_request = true; + m_path_limit = path_limit; + } + } + + private: + void add_subject_dn(std::string_view fn_suffix, std::string_view attr, std::string_view value) { + const auto oid = OID::from_string(attr); + const size_t ub = X509_DN::lookup_ub(oid); + + if(value.empty()) { + throw Invalid_Argument(fmt("CertificateParametersBuilder::add_{}: empty name is prohibited", fn_suffix)); + } + + if(value.size() > ub) { + throw Invalid_Argument( + fmt("CertificateParametersBuilder::add_{}: name exceeds maximum allowed length ({}) for this type", + fn_suffix, + ub)); + } + m_subject_dn.add_attribute(oid, value); + } + + std::unique_ptr setup_alt_name(const Extensions& extensions) const { + AlternativeName subject_alt; + + /* + If the extension was already created in extensions we need to merge the + values provided with the extension value + */ + if(const auto* ext = extensions.get_extension_object_as()) { + subject_alt = ext->get_alt_name(); + } + + for(const auto& dns : m_dns) { + subject_alt.add_dns(dns); + } + for(const auto& uri : m_uri) { + subject_alt.add_uri(uri); + } + for(const auto& email : m_email) { + subject_alt.add_email(email); + } + for(const auto& xmpp : m_xmpp) { + subject_alt.add_other_name(OID::from_string("PKIX.XMPPAddr"), ASN1_String(xmpp, ASN1_Type::Utf8String)); + } + for(const uint32_t ipv4 : m_ipv4) { + subject_alt.add_ipv4_address(ipv4); + } + + return std::make_unique(subject_alt); + } + + X509_DN m_subject_dn; + Extensions m_extensions; + Key_Constraints m_usage; + std::vector m_email; + std::vector m_dns; + std::vector m_uri; + std::vector m_xmpp; + std::vector m_ipv4; + std::vector m_ext_usage; + bool m_is_ca_request = false; + std::optional m_path_limit; +}; + +CertificateParametersBuilder::CertificateParametersBuilder() : + m_state(std::make_unique()) {} + +CertificateParametersBuilder::CertificateParametersBuilder(CertificateParametersBuilder&& other) noexcept = default; + +CertificateParametersBuilder::~CertificateParametersBuilder() = default; + +X509_Certificate CertificateParametersBuilder::into_self_signed_cert(std::chrono::system_clock::time_point not_before, + std::chrono::system_clock::time_point not_after, + const Private_Key& key, + RandomNumberGenerator& rng, + std::optional hash_fn, + std::optional padding) const { + auto signer_p = X509_Object::choose_sig_format(key, rng, hash_fn.value_or(""), padding.value_or("")); + auto& signer = *signer_p; + + const AlgorithmIdentifier sig_algo = signer.algorithm_identifier(); + BOTAN_ASSERT_NOMSG(sig_algo.oid().has_value()); + + Extensions extensions = m_state->finalize_extensions(key); + + const std::vector pub_key = key.subject_public_key(); + auto skid = std::make_unique(pub_key, signer.hash_function()); + + extensions.add_new(std::make_unique(skid->get_key_id())); + extensions.add_new(std::move(skid)); + + const auto& subject_dn = m_state->subject_dn(); + + return X509_CA::make_cert( + signer, rng, sig_algo, pub_key, X509_Time(not_before), X509_Time(not_after), subject_dn, subject_dn, extensions); +} + +PKCS10_Request CertificateParametersBuilder::into_pkcs10_request( + const Private_Key& key, + RandomNumberGenerator& rng, + std::optional hash_fn, + std::optional padding, + std::optional challenge_password) const { + const auto& subject_dn = m_state->subject_dn(); + + const auto extensions = m_state->finalize_extensions(key); + + return PKCS10_Request::create( + key, subject_dn, extensions, hash_fn.value_or(""), rng, padding.value_or(""), challenge_password.value_or("")); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_common_name(std::string_view cn) { + m_state->add_common_name(cn); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_country(std::string_view country) { + m_state->add_country(country); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_organization(std::string_view org) { + m_state->add_organization(org); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_organizational_unit(std::string_view org_unit) { + m_state->add_organizational_unit(org_unit); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_locality(std::string_view locality) { + m_state->add_locality(locality); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_state(std::string_view state) { + m_state->add_state(state); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_serial_number(std::string_view serial) { + m_state->add_serial_number(serial); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_email(std::string_view email) { + m_state->add_email(email); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_uri(std::string_view uri) { + m_state->add_uri(uri); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_dns(std::string_view dns) { + m_state->add_dns(dns); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_ipv4(uint32_t ipv4) { + m_state->add_ipv4(ipv4); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_xmpp(std::string_view xmpp) { + m_state->add_xmpp(xmpp); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_allowed_usage(Key_Constraints kc) { + m_state->add_allowed_usage(kc); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_allowed_extended_usage(const OID& usage) { + m_state->add_allowed_extended_usage(usage); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::add_extension(std::unique_ptr extn, + bool is_critical) { + m_state->add_extension(std::move(extn), is_critical); + return (*this); +} + +CertificateParametersBuilder& CertificateParametersBuilder::set_as_ca_certificate(std::optional path_limit) { + m_state->set_as_ca_certificate(path_limit); + return (*this); +} + +} // namespace Botan diff --git a/src/lib/x509/x509_builder.h b/src/lib/x509/x509_builder.h new file mode 100644 index 00000000000..a8c7b178480 --- /dev/null +++ b/src/lib/x509/x509_builder.h @@ -0,0 +1,184 @@ +/* +* (C) 2025 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_CERT_PARAM_BUILDER_H_ +#define BOTAN_X509_CERT_PARAM_BUILDER_H_ + +#include +#include +#include +#include +#include +#include +#include + +namespace Botan { + +class Certificate_Extension; +class Private_Key; +class RandomNumberGenerator; +class OID; + +/** +* Certificate Metadata Builder +* +* Allows incrementally forming metadata included in a certificate or PKCS10 request. +* +* The various add_ methods each return a reference to this, allowing for convenient method chaining. +*/ +class BOTAN_PUBLIC_API(3, 9) CertificateParametersBuilder final { + public: + CertificateParametersBuilder(); + + CertificateParametersBuilder(const CertificateParametersBuilder& other) = delete; + CertificateParametersBuilder& operator=(const CertificateParametersBuilder& other) = delete; + + CertificateParametersBuilder(CertificateParametersBuilder&& other) noexcept; + CertificateParametersBuilder& operator=(CertificateParametersBuilder&& other) = delete; + ~CertificateParametersBuilder(); + + /** + * Create a self-signed X.509 certificate from these parameters. + * + * @param not_before the initial validity time of the generated certificate + * @param not_after the final validity time of the generated certificate + * @param key the private key + * @param rng the rng to use + * @param hash_fn the hash function to use; if unset a reasonable default is used + * @param padding optional padding scheme (for specifying RSA-PSS vs RSA-PKCS1, + * otherwise not necessary) + * + * @return newly created self-signed certificate + */ + X509_Certificate into_self_signed_cert(std::chrono::system_clock::time_point not_before, + std::chrono::system_clock::time_point not_after, + const Private_Key& key, + RandomNumberGenerator& rng, + std::optional hash_fn = {}, + std::optional padding = {}) const; + + /** + * Create a PKCS10 request + * + * @param key the private key + * @param rng the rng to use + * @param hash_fn the hash function to use; if unset a reasonable default is used + * @param padding optional padding scheme (for specifying RSA-PSS vs RSA-PKCS1, + * otherwise not necessary) + * @param challenge_password an optional challenge password included in the PKCS10 request + * (originally intended as a preshared key to allow authenticating a + * later revocation request, but now obsolete in most contexts) + * + * @return newly created self-signed certificate + */ + PKCS10_Request into_pkcs10_request(const Private_Key& key, + RandomNumberGenerator& rng, + std::optional hash_fn = {}, + std::optional padding = {}, + std::optional challenge_password = {}) const; + + /** + * Add an additional common name to the certificate metadata + */ + CertificateParametersBuilder& add_common_name(std::string_view cn); + + /** + * Add an additional country name to the certificate metadata + */ + CertificateParametersBuilder& add_country(std::string_view country); + + /** + * Add an additional organization name to the certificate metadata + */ + CertificateParametersBuilder& add_organization(std::string_view org); + + /** + * Add an additional organization unit name to the certificate metadata + */ + CertificateParametersBuilder& add_organizational_unit(std::string_view org_unit); + + /** + * Add an additional locality name to the certificate metadata + */ + CertificateParametersBuilder& add_locality(std::string_view locality); + + /** + * Add an additional state name to the certificate metadata + */ + CertificateParametersBuilder& add_state(std::string_view state); + + /** + * Add an additional serial number to the certificate metadata + * + * Note this is the X.520 serial number included in the DN, and has nothing + * to do with the serial number of the issued certificate. + */ + CertificateParametersBuilder& add_serial_number(std::string_view serial); + + /** + * Add an additional email address to the certificate metadata + */ + CertificateParametersBuilder& add_email(std::string_view email); + + /** + * Add an additional URI to the certificate metadata + */ + CertificateParametersBuilder& add_uri(std::string_view uri); + + /** + * Add an additional DNS name to the certificate metadata + */ + CertificateParametersBuilder& add_dns(std::string_view dns); + + /** + * Add an additional IPv4 address to the certificate metadata + */ + CertificateParametersBuilder& add_ipv4(uint32_t ipv4); + + /** + * Add a XMPP name to the certificate metadata + */ + CertificateParametersBuilder& add_xmpp(std::string_view xmpp); + + /** + * Add another allowed usage to the KeyUsage extension + * + * This is cummulative; all usages indicated are ORed together + */ + CertificateParametersBuilder& add_allowed_usage(Key_Constraints kc); + + /** + * Add another allowed usage to the ExtendedKeyUsage extension + */ + CertificateParametersBuilder& add_allowed_extended_usage(const OID& usage); + + /** + * Add an arbitrary certificate extension + * + * The extension + */ + CertificateParametersBuilder& add_extension(std::unique_ptr extn, + bool is_critical = false); + + /** + * Set the parameters as being for a CA certificate + * + * If the path limit is specified then the BasicConstraints extension will include + * that value as the maximum chain length issuable by the resulting cert. + * + * It is not allowed to call set_as_ca_certificate more than once; further calls will + * result in an Invalid_State exception. + */ + CertificateParametersBuilder& set_as_ca_certificate(std::optional path_limit = {}); + + private: + class State; + std::unique_ptr m_state; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/x509/x509opt.cpp b/src/lib/x509/x509opt.cpp index f045c5cfd22..f17a89f2090 100644 --- a/src/lib/x509/x509opt.cpp +++ b/src/lib/x509/x509opt.cpp @@ -7,6 +7,7 @@ #include +#include #include #include @@ -92,4 +93,74 @@ X509_Cert_Options::X509_Cert_Options(std::string_view initial_opts, uint32_t exp } } +CertificateParametersBuilder X509_Cert_Options::into_builder() const { + auto builder = CertificateParametersBuilder(); + if(!this->common_name.empty()) { + builder.add_common_name(this->common_name); + } + if(!this->country.empty()) { + builder.add_country(this->country); + } + if(!this->organization.empty()) { + builder.add_organization(this->organization); + } + if(!this->org_unit.empty()) { + builder.add_organizational_unit(this->org_unit); + } + for(const auto& ou : this->more_org_units) { + if(!ou.empty()) { + builder.add_organizational_unit(ou); + } + } + if(!this->locality.empty()) { + builder.add_locality(this->locality); + } + if(!this->state.empty()) { + builder.add_state(this->state); + } + if(!this->serial_number.empty()) { + builder.add_serial_number(this->serial_number); + } + if(!this->email.empty()) { + builder.add_email(this->email); + } + if(!this->uri.empty()) { + builder.add_uri(this->uri); + } + if(!this->ip.empty()) { + if(auto ipv4 = string_to_ipv4(this->ip)) { + builder.add_ipv4(*ipv4); + } else { + throw Invalid_Argument(fmt("Invalid IPv4 address '{}'", this->ip)); + } + } + + if(!this->dns.empty()) { + builder.add_dns(this->dns); + } + for(const auto& nm : this->more_dns) { + if(!nm.empty()) { + builder.add_dns(nm); + } + } + if(!this->xmpp.empty()) { + builder.add_xmpp(this->xmpp); + } + if(this->is_CA) { + builder.set_as_ca_certificate(this->path_limit); + } + if(!this->constraints.empty()) { + builder.add_allowed_usage(this->constraints); + } + for(const OID& usage : this->ex_constraints) { + builder.add_allowed_extended_usage(usage); + } + + for(auto& [extn, is_critical] : this->extensions.extensions()) { + builder.add_extension(std::move(extn), is_critical); + } + + return builder; +} + } // namespace Botan diff --git a/src/lib/x509/x509self.cpp b/src/lib/x509/x509self.cpp index fe66501fda3..7533a3c15d1 100644 --- a/src/lib/x509/x509self.cpp +++ b/src/lib/x509/x509self.cpp @@ -7,76 +7,7 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Botan { - -namespace { - -/* -* Load information from the X509_Cert_Options -*/ -X509_DN load_dn_info(const X509_Cert_Options& opts) { - X509_DN subject_dn; - - subject_dn.add_attribute("X520.CommonName", opts.common_name); - subject_dn.add_attribute("X520.Country", opts.country); - subject_dn.add_attribute("X520.State", opts.state); - subject_dn.add_attribute("X520.Locality", opts.locality); - subject_dn.add_attribute("X520.Organization", opts.organization); - subject_dn.add_attribute("X520.OrganizationalUnit", opts.org_unit); - subject_dn.add_attribute("X520.SerialNumber", opts.serial_number); - - for(const auto& extra_ou : opts.more_org_units) { - subject_dn.add_attribute("X520.OrganizationalUnit", extra_ou); - } - - return subject_dn; -} - -auto create_alt_name_ext(const X509_Cert_Options& opts, const Extensions& extensions) { - AlternativeName subject_alt; - - /* - If the extension was already created in opts.extension we need to - merge the values provided in opts with the values set in the extension. - */ - if(const auto* ext = extensions.get_extension_object_as()) { - subject_alt = ext->get_alt_name(); - } - - subject_alt.add_dns(opts.dns); - for(const auto& nm : opts.more_dns) { - subject_alt.add_dns(nm); - } - subject_alt.add_uri(opts.uri); - subject_alt.add_email(opts.email); - if(!opts.ip.empty()) { - if(auto ipv4 = string_to_ipv4(opts.ip)) { - subject_alt.add_ipv4_address(*ipv4); - } else { - throw Invalid_Argument(fmt("Invalid IPv4 address '{}'", opts.ip)); - } - } - - if(!opts.xmpp.empty()) { - subject_alt.add_other_name(OID::from_string("PKIX.XMPPAddr"), ASN1_String(opts.xmpp, ASN1_Type::Utf8String)); - } - - return std::make_unique(subject_alt); -} - -} // namespace - -namespace X509 { +namespace Botan::X509 { /* * Create a new self-signed X.509 certificate @@ -85,37 +16,13 @@ X509_Certificate create_self_signed_cert(const X509_Cert_Options& opts, const Private_Key& key, std::string_view hash_fn, RandomNumberGenerator& rng) { - const std::vector pub_key = X509::BER_encode(key); - auto signer = X509_Object::choose_sig_format(key, rng, hash_fn, opts.padding_scheme); - const AlgorithmIdentifier sig_algo = signer->algorithm_identifier(); - BOTAN_ASSERT_NOMSG(sig_algo.oid().has_value()); + auto not_before = opts.start.to_std_timepoint(); + auto not_after = opts.end.to_std_timepoint(); - const auto subject_dn = load_dn_info(opts); + const std::optional padding = + (opts.padding_scheme.empty()) ? std::nullopt : std::optional(opts.padding_scheme); - Extensions extensions = opts.extensions; - - const auto constraints = opts.is_CA ? Key_Constraints::ca_constraints() : opts.constraints; - - if(!constraints.compatible_with(key)) { - throw Invalid_Argument("The requested key constraints are incompatible with the algorithm"); - } - - extensions.add_new(std::make_unique(opts.is_CA, opts.path_limit), true); - - if(!constraints.empty()) { - extensions.add_new(std::make_unique(constraints), true); - } - - auto skid = std::make_unique(pub_key, signer->hash_function()); - - extensions.add_new(std::make_unique(skid->get_key_id())); - extensions.add_new(std::move(skid)); - - extensions.replace(create_alt_name_ext(opts, extensions)); - - extensions.add_new(std::make_unique(opts.ex_constraints)); - - return X509_CA::make_cert(*signer, rng, sig_algo, pub_key, opts.start, opts.end, subject_dn, subject_dn, extensions); + return opts.into_builder().into_self_signed_cert(not_before, not_after, key, rng, hash_fn, padding); } /* @@ -125,31 +32,14 @@ PKCS10_Request create_cert_req(const X509_Cert_Options& opts, const Private_Key& key, std::string_view hash_fn, RandomNumberGenerator& rng) { - const auto subject_dn = load_dn_info(opts); - - const auto constraints = opts.is_CA ? Key_Constraints::ca_constraints() : opts.constraints; - - if(!constraints.compatible_with(key)) { - throw Invalid_Argument("The requested key constraints are incompatible with the algorithm"); - } - - Extensions extensions = opts.extensions; + const std::optional challenge_password = + (opts.challenge.empty()) ? std::nullopt : std::optional(opts.challenge); + const std::optional padding = + (opts.padding_scheme.empty()) ? std::nullopt : std::optional(opts.padding_scheme); - extensions.add_new(std::make_unique(opts.is_CA, opts.path_limit)); + // opts.start and opts.end are ignored here - if(!constraints.empty()) { - extensions.add_new(std::make_unique(constraints)); - } - - extensions.replace(create_alt_name_ext(opts, extensions)); - - if(!opts.ex_constraints.empty()) { - extensions.add_new(std::make_unique(opts.ex_constraints)); - } - - return PKCS10_Request::create(key, subject_dn, extensions, hash_fn, rng, opts.padding_scheme, opts.challenge); + return opts.into_builder().into_pkcs10_request(key, rng, hash_fn, padding, challenge_password); } -} // namespace X509 - -} // namespace Botan +} // namespace Botan::X509 diff --git a/src/lib/x509/x509self.h b/src/lib/x509/x509self.h index 4362f9e1b09..d9332014348 100644 --- a/src/lib/x509/x509self.h +++ b/src/lib/x509/x509self.h @@ -10,13 +10,18 @@ #include #include +#include #include +#include +#include namespace Botan { class RandomNumberGenerator; class Private_Key; +// Older interface for creating PKCS10 requests and self-signed certificates follows + /** * Options for X.509 certificates. */ @@ -177,6 +182,8 @@ class BOTAN_PUBLIC_API(2, 0) X509_Cert_Options final { */ void add_ex_constraint(std::string_view name); + CertificateParametersBuilder into_builder() const; + /** * Construct a new options object * @param opts define the common name of this object. An example for this diff --git a/src/tests/test_x509_rpki.cpp b/src/tests/test_x509_rpki.cpp index 23954ecc9e8..b4d2adfd97d 100644 --- a/src/tests/test_x509_rpki.cpp +++ b/src/tests/test_x509_rpki.cpp @@ -11,10 +11,10 @@ #include #include #include + #include #include #include #include - #include #include #endif @@ -57,99 +57,96 @@ std::unique_ptr generate_key(const std::string& algo, Botan: } else if(algo == "Ed25519") { params = ""; } else if(algo == "RSA") { - params = "1536"; + params = "1024"; } return Botan::create_private_key(algo, rng, params); } -Botan::X509_Cert_Options ca_opts(const std::string& sig_padding = "") { - Botan::X509_Cert_Options opts("Test CA/US/Botan Project/Testing"); +Botan::CertificateParametersBuilder ca_params() { + Botan::CertificateParametersBuilder builder; - opts.uri = "https://botan.randombit.net"; - opts.dns = "botan.randombit.net"; - opts.email = "testing@randombit.net"; - opts.set_padding_scheme(sig_padding); + builder.add_common_name("RPKI Test CA") + .add_country("US") + .add_organization("Botan") + .add_dns("botan.randombit.net") + .set_as_ca_certificate(1); - opts.CA_key(1); - - return opts; + return builder; } -Botan::X509_Cert_Options req_opts(const std::string& algo, const std::string& sig_padding = "") { - Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing"); - - opts.uri = "https://botan.randombit.net"; - opts.dns = "botan.randombit.net"; - opts.email = "testing@randombit.net"; - opts.set_padding_scheme(sig_padding); - - opts.not_before("160101200000Z"); - opts.not_after("300101200000Z"); - - opts.challenge = "zoom"; +Botan::CertificateParametersBuilder user_params() { + Botan::CertificateParametersBuilder builder; - if(algo == "RSA") { - opts.constraints = Botan::Key_Constraints::KeyEncipherment; - } else if(algo == "DSA" || algo == "ECDSA" || algo == "ECGDSA" || algo == "ECKCDSA") { - opts.constraints = Botan::Key_Constraints::DigitalSignature; - } + builder.add_common_name("RPKI Test User") + .add_country("US") + .add_organization("Botan") + .add_allowed_usage(Botan::Key_Constraints::DigitalSignature); - return opts; + return builder; } -std::tuple get_sig_algo_padding() { +std::pair get_sig_algo() { #if defined(BOTAN_HAS_ECDSA) const std::string sig_algo{"ECDSA"}; - const std::string padding_method; const std::string hash_fn{"SHA-256"}; #elif defined(BOTAN_HAS_ED25519) const std::string sig_algo{"Ed25519"}; - const std::string padding_method; const std::string hash_fn{"SHA-512"}; #elif defined(BOTAN_HAS_RSA) const std::string sig_algo{"RSA"}; - const std::string padding_method{"PKCS1v15(SHA-256)"}; const std::string hash_fn{"SHA-256"}; #endif - return std::make_tuple(sig_algo, padding_method, hash_fn); + return std::make_pair(sig_algo, hash_fn); } -Botan::X509_Certificate make_self_signed(std::unique_ptr& rng, - const Botan::X509_Cert_Options& opts = std::move(ca_opts())) { - auto [sig_algo, padding_method, hash_fn] = get_sig_algo_padding(); - auto key = generate_key(sig_algo, *rng); - const auto cert = Botan::X509::create_self_signed_cert(opts, *key, hash_fn, *rng); +Botan::X509_Certificate make_self_signed(Botan::RandomNumberGenerator& rng, + const Botan::CertificateParametersBuilder& params) { + auto [sig_algo, hash_fn] = get_sig_algo(); + auto key = generate_key(sig_algo, rng); + + const auto now = std::chrono::system_clock::now(); + const auto not_before = now - std::chrono::seconds(180); + const auto not_after = now + std::chrono::seconds(86400); - return cert; + return params.into_self_signed_cert(not_before, not_after, *key, rng, hash_fn); } -CA_Creation_Result make_ca(std::unique_ptr& rng, - const Botan::X509_Cert_Options& opts = std::move(ca_opts())) { - auto [sig_algo, padding_method, hash_fn] = get_sig_algo_padding(); - auto ca_key = generate_key(sig_algo, *rng); - const auto ca_cert = Botan::X509::create_self_signed_cert(opts, *ca_key, hash_fn, *rng); - Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, *rng); - auto sub_key = generate_key(sig_algo, *rng); +CA_Creation_Result make_ca(Botan::RandomNumberGenerator& rng, const Botan::CertificateParametersBuilder& params) { + auto [sig_algo, hash_fn] = get_sig_algo(); + auto ca_key = generate_key(sig_algo, rng); + + const auto now = std::chrono::system_clock::now(); + const auto not_before = now - std::chrono::seconds(180); + const auto not_after = now + std::chrono::seconds(86400); + + const auto ca_cert = params.into_self_signed_cert(not_before, not_after, *ca_key, rng, hash_fn); + + Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, rng); + auto sub_key = generate_key(sig_algo, rng); return CA_Creation_Result{ca_cert, std::move(ca), std::move(sub_key), sig_algo, hash_fn}; } -std::pair make_and_sign_ca( - std::unique_ptr ext, - Botan::X509_CA& parent_ca, - std::unique_ptr& rng) { - auto [sig_algo, padding_method, hash_fn] = get_sig_algo_padding(); +CA_Creation_Result make_ca(Botan::RandomNumberGenerator& rng) { + return make_ca(rng, ca_params()); +} + +std::pair make_and_sign_ca(std::unique_ptr ext, + Botan::X509_CA& parent_ca, + Botan::RandomNumberGenerator& rng) { + auto [sig_algo, hash_fn] = get_sig_algo(); + + auto params = ca_params(); + params.add_extension(std::move(ext)); - Botan::X509_Cert_Options opts = ca_opts(); - opts.extensions.add(std::move(ext)); + std::unique_ptr key = generate_key(sig_algo, rng); - std::unique_ptr key = generate_key(sig_algo, *rng); + Botan::PKCS10_Request req = params.into_pkcs10_request(*key, rng, hash_fn); - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *key, hash_fn, *rng); - Botan::X509_Certificate cert = parent_ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); - Botan::X509_CA ca(cert, *key, hash_fn, padding_method, *rng); + Botan::X509_Certificate cert = parent_ca.sign_request(req, rng, from_date(-1, 01, 01), from_date(2, 01, 01)); + Botan::X509_CA ca(cert, *key, hash_fn, rng); return std::make_pair(std::move(cert), std::move(ca)); } @@ -394,10 +391,7 @@ Test::Result test_x509_ip_addr_blocks_rfc3779_example() { blocks_1->add_address({10, 3, 0, 0}, {10, 3, 255, 255}, 1); blocks_1->inherit(); - Botan::X509_Cert_Options opts_1 = ca_opts(); - opts_1.extensions.add(std::move(blocks_1)); - - auto cert_1 = make_self_signed(rng, opts_1); + auto cert_1 = make_self_signed(*rng, ca_params().add_extension(std::move(blocks_1))); auto bits_1 = cert_1.v3_extensions().get_extension_bits(IPAddressBlocks::static_oid()); @@ -441,10 +435,7 @@ Test::Result test_x509_ip_addr_blocks_rfc3779_example() { blocks_2->add_address({172, 16, 0, 0}, {172, 31, 255, 255}, 1); blocks_2->inherit(2); - Botan::X509_Cert_Options opts_2 = ca_opts(); - opts_2.extensions.add(std::move(blocks_2)); - - auto cert_2 = make_self_signed(rng, opts_2); + auto cert_2 = make_self_signed(*rng, ca_params().add_extension(std::move(blocks_2))); auto bits_2 = cert_2.v3_extensions().get_extension_bits(IPAddressBlocks::static_oid()); @@ -508,10 +499,7 @@ Test::Result test_x509_ip_addr_blocks_encode_builder() { blocks->add_address({123, 123, 2, 1}); - Botan::X509_Cert_Options opts = ca_opts(); - opts.extensions.add(std::move(blocks)); - - auto cert = make_self_signed(rng, opts); + auto cert = make_self_signed(*rng, ca_params().add_extension(std::move(blocks))); auto bits = cert.v3_extensions().get_extension_bits(IPAddressBlocks::static_oid()); // hand validated with https://lapo.it/asn1js/ @@ -545,7 +533,7 @@ Test::Result test_x509_ip_addr_blocks_extension_encode_ctor() { auto rng = Test::new_rng(__func__); - auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); + auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(*rng); for(size_t i = 0; i < 64; i++) { bool push_ipv4_ranges = bit_set<0>(i); @@ -555,8 +543,6 @@ Test::Result test_x509_ip_addr_blocks_extension_encode_ctor() { bool push_ipv4_family = bit_set<4>(i); bool push_ipv6_family = bit_set<5>(i); - Botan::X509_Cert_Options opts = req_opts(sig_algo); - std::vector a = {123, 123, 2, 1}; auto ipv4_1 = IPAddressBlocks::IPAddress(a); a = {255, 255, 255, 255}; @@ -641,9 +627,7 @@ Test::Result test_x509_ip_addr_blocks_extension_encode_ctor() { std::unique_ptr blocks = std::make_unique(addr_blocks); - opts.extensions.add(std::move(blocks)); - - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); + auto req = user_params().add_extension(std::move(blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* ip_blocks = cert.v3_extensions().get_extension_object_as(); @@ -722,7 +706,7 @@ Test::Result test_x509_ip_addr_blocks_extension_encode_edge_cases_ctor() { std::vector edge_values = {0, 2, 4, 8, 16, 32, 64, 128, 1, 3, 7, 15, 31, 63, 127, 255, 12, 46, 123, 160, 234}; - auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); + auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(*rng); for(size_t i = 0; i < edge_values.size(); i++) { for(size_t j = 0; j < 4; j++) { @@ -736,8 +720,6 @@ Test::Result test_x509_ip_addr_blocks_extension_encode_edge_cases_ctor() { break; } - Botan::X509_Cert_Options opts = req_opts(sig_algo); - std::vector min_bytes(16, 0x00); std::vector max_bytes(16, 0xFF); @@ -765,9 +747,7 @@ Test::Result test_x509_ip_addr_blocks_extension_encode_edge_cases_ctor() { std::unique_ptr blocks = std::make_unique(addr_blocks); - opts.extensions.add(std::move(blocks)); - - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); + auto req = user_params().add_extension(std::move(blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* ip_blocks = cert.v3_extensions().get_extension_object_as(); @@ -797,8 +777,7 @@ Test::Result test_x509_ip_addr_blocks_range_merge() { auto rng = Test::new_rng(__func__); - auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); - Botan::X509_Cert_Options opts = req_opts(sig_algo); + auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(*rng); std::vector>> addresses = { {{11, 0, 0, 0}, {{11, 0, 0, 0}}}, @@ -828,9 +807,7 @@ Test::Result test_x509_ip_addr_blocks_range_merge() { std::unique_ptr blocks = std::make_unique(addr_blocks); - opts.extensions.add(std::move(blocks)); - - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); + auto req = user_params().add_extension(std::move(blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* ip_blocks = cert.v3_extensions().get_extension_object_as(); @@ -860,8 +837,7 @@ Test::Result test_x509_ip_addr_blocks_family_merge() { auto rng = Test::new_rng(__func__); - auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); - Botan::X509_Cert_Options opts = req_opts(sig_algo); + auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(*rng); std::vector addr_blocks; @@ -916,9 +892,7 @@ Test::Result test_x509_ip_addr_blocks_family_merge() { std::unique_ptr blocks = std::make_unique(addr_blocks); - opts.extensions.add(std::move(blocks)); - - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); + auto req = user_params().add_extension(std::move(blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); const auto* ip_blocks = cert.v3_extensions().get_extension_object_as(); @@ -1084,12 +1058,9 @@ Test::Result test_x509_ip_addr_blocks_path_validation_success_builder() { {0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x0D, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); - Botan::X509_Cert_Options root_opts = ca_opts(); - root_opts.extensions.add(std::move(root_blocks)); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = + make_ca(*rng, ca_params().add_extension(std::move(root_blocks))); + auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, *rng); Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root_cert); @@ -1115,11 +1086,11 @@ Test::Result test_x509_ip_addr_blocks_path_validation_success_builder() { dyn_blocks->inherit(); } - auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng); + auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, *rng); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); - Botan::X509_Certificate sub_cert = - dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); + const auto sub_req = user_params().add_extension(sub_blocks->copy()).into_pkcs10_request(*sub_key, *rng, hash_fn); + + const auto sub_cert = dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); const Botan::Path_Validation_Restrictions restrictions(false, 80); std::vector certs = {sub_cert, dyn_cert, inherit_cert}; @@ -1249,12 +1220,9 @@ Test::Result test_x509_ip_addr_blocks_path_validation_success_ctor() { auto sub_addr_blocks = {sub_ipv4_family, sub_ipv6_family}; std::unique_ptr sub_blocks = std::make_unique(sub_addr_blocks); - Botan::X509_Cert_Options root_opts = ca_opts(); - root_opts.extensions.add(std::move(root_blocks)); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = + make_ca(*rng, ca_params().add_extension(std::move(root_blocks))); + auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, *rng); Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root_cert); @@ -1274,11 +1242,10 @@ Test::Result test_x509_ip_addr_blocks_path_validation_success_ctor() { auto dyn_addr_blocks = {dyn_ipv4_family, dyn_ipv6_family}; std::unique_ptr dyn_blocks = std::make_unique(dyn_addr_blocks); - auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng); + auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, *rng); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); - Botan::X509_Certificate sub_cert = - dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); + const auto sub_req = user_params().add_extension(sub_blocks->copy()).into_pkcs10_request(*sub_key, *rng, hash_fn); + const auto sub_cert = dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); const Botan::Path_Validation_Restrictions restrictions(false, 80); std::vector certs = {sub_cert, dyn_cert, inherit_cert}; @@ -1315,11 +1282,11 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure_builder() { root_blocks->inherit(42); } - Botan::X509_Cert_Options root_opts = ca_opts(); + auto root_params = ca_params(); if(!nullptr_extensions) { - root_opts.extensions.add(std::move(root_blocks)); + root_params.add_extension(std::move(root_blocks)); } - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(*rng, root_params); // Issuer Cert std::unique_ptr iss_blocks = std::make_unique(); @@ -1333,7 +1300,7 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure_builder() { iss_blocks->inherit(42); } - auto [iss_cert, iss_ca] = make_and_sign_ca(std::move(iss_blocks), root_ca, rng); + auto [iss_cert, iss_ca] = make_and_sign_ca(std::move(iss_blocks), root_ca, *rng); // Subject cert std::unique_ptr sub_blocks = std::make_unique(); @@ -1354,10 +1321,7 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure_builder() { sub_blocks->inherit(safi); } - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + auto sub_req = user_params().add_extension(std::move(sub_blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = iss_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); @@ -1405,11 +1369,11 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure_ctor() { auto root_addr_blocks = {root_family}; std::unique_ptr root_blocks = std::make_unique(root_addr_blocks); - Botan::X509_Cert_Options root_opts = ca_opts(); + auto root_params = ca_params(); if(!nullptr_extensions) { - root_opts.extensions.add(std::move(root_blocks)); + root_params.add_extension(std::move(root_blocks)); } - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(*rng, root_params); // Issuer Cert a = {122, 0, 0, 255}; @@ -1428,7 +1392,7 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure_ctor() { auto iss_family = IPAddressBlocks::IPAddressFamily(iss_choice, 42); auto iss_addr_blocks = {iss_family}; std::unique_ptr iss_blocks = std::make_unique(iss_addr_blocks); - auto [iss_cert, iss_ca] = make_and_sign_ca(std::move(iss_blocks), root_ca, rng); + auto [iss_cert, iss_ca] = make_and_sign_ca(std::move(iss_blocks), root_ca, *rng); // Subject cert if(too_small_subrange) { @@ -1457,10 +1421,7 @@ Test::Result test_x509_ip_addr_blocks_path_validation_failure_ctor() { auto sub_addr_blocks = {sub_family}; std::unique_ptr sub_blocks = std::make_unique(sub_addr_blocks); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + auto sub_req = user_params().add_extension(std::move(sub_blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = iss_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); @@ -1492,10 +1453,7 @@ Test::Result test_x509_as_blocks_rfc3779_example() { blocks->add_asnum(5001); blocks->inherit_rdi(); - Botan::X509_Cert_Options opts = ca_opts(); - opts.extensions.add(std::move(blocks)); - - auto cert = make_self_signed(rng, opts); + auto cert = make_self_signed(*rng, ca_params().add_extension(std::move(blocks))); auto bits = cert.v3_extensions().get_extension_bits(ASBlocks::static_oid()); result.test_eq( @@ -1529,10 +1487,7 @@ Test::Result test_x509_as_blocks_encode_builder() { // this overwrites the previous two blocks->restrict_asnum(); - Botan::X509_Cert_Options opts = ca_opts(); - opts.extensions.add(std::move(blocks)); - - auto cert = make_self_signed(rng, opts); + auto cert = make_self_signed(*rng, ca_params().add_extension(std::move(blocks))); auto bits = cert.v3_extensions().get_extension_bits(ASBlocks::static_oid()); result.test_eq("extension is encoded as specified", bits, "3011A0023000A10B300930070201090202012D"); @@ -1549,7 +1504,7 @@ Test::Result test_x509_as_blocks_extension_encode_ctor() { auto rng = Test::new_rng(__func__); - auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); + auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(*rng); for(size_t i = 0; i < 16; i++) { bool push_asnum = bit_set<0>(i); @@ -1590,10 +1545,7 @@ Test::Result test_x509_as_blocks_extension_encode_ctor() { std::unique_ptr blocks = std::make_unique(ident); - Botan::X509_Cert_Options opts = req_opts(sig_algo); - opts.extensions.add(std::move(blocks)); - - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); + auto req = user_params().add_extension(std::move(blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { @@ -1648,8 +1600,7 @@ Test::Result test_x509_as_blocks_range_merge() { auto rng = Test::new_rng(__func__); - auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(rng); - Botan::X509_Cert_Options opts = req_opts(sig_algo); + auto [ca_cert, ca, sub_key, sig_algo, hash_fn] = make_ca(*rng); std::vector> ranges = { {2005, 37005}, @@ -1673,9 +1624,7 @@ Test::Result test_x509_as_blocks_range_merge() { std::unique_ptr blocks = std::make_unique(ident); - opts.extensions.add(std::move(blocks)); - - Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *sub_key, hash_fn, *rng); + const auto req = user_params().add_extension(std::move(blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); { const auto* as_blocks = cert.v3_extensions().get_extension_object_as(); @@ -1741,12 +1690,9 @@ Test::Result test_x509_as_blocks_path_validation_success_builder() { sub_blocks->add_rdi(1567); sub_blocks->add_rdi(33100, 40000); - Botan::X509_Cert_Options root_opts = ca_opts(); - root_opts.extensions.add(std::move(root_blocks)); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = + make_ca(*rng, ca_params().add_extension(std::move(root_blocks))); + auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, *rng); Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root_cert); @@ -1771,9 +1717,9 @@ Test::Result test_x509_as_blocks_path_validation_success_builder() { dyn_blocks->inherit_rdi(); } - auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng); + auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, *rng); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + const auto sub_req = user_params().add_extension(sub_blocks->copy()).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); @@ -1880,12 +1826,9 @@ Test::Result test_x509_as_blocks_path_validation_success_ctor() { ASBlocks::ASIdentifiers sub_ident = ASBlocks::ASIdentifiers(sub_asnum, sub_rdi); std::unique_ptr sub_blocks = std::make_unique(sub_ident); - Botan::X509_Cert_Options root_opts = ca_opts(); - root_opts.extensions.add(std::move(root_blocks)); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = + make_ca(*rng, ca_params().add_extension(std::move(root_blocks))); + auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, *rng); Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root_cert); @@ -1901,9 +1844,9 @@ Test::Result test_x509_as_blocks_path_validation_success_ctor() { ASBlocks::ASIdentifiers dyn_ident = ASBlocks::ASIdentifiers(dyn_asnum, dyn_rdi); std::unique_ptr dyn_blocks = std::make_unique(dyn_ident); - auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng); + auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, *rng); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + const auto sub_req = user_params().add_extension(sub_blocks->copy()).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); @@ -1938,11 +1881,8 @@ Test::Result test_x509_as_blocks_path_validation_extension_not_present_builder() sub_blocks->add_rdi(33100, 40000); // create a root ca that does not have any extension - Botan::X509_Cert_Options root_opts = ca_opts(); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(*rng, ca_params()); + auto sub_req = user_params().add_extension(std::move(sub_blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = root_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); Botan::Certificate_Store_In_Memory trusted; @@ -1996,11 +1936,8 @@ Test::Result test_x509_as_blocks_path_validation_extension_not_present_ctor() { std::unique_ptr sub_blocks = std::make_unique(sub_ident); // create a root ca that does not have any extension - Botan::X509_Cert_Options root_opts = ca_opts(); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(*rng, ca_params()); + const auto sub_req = user_params().add_extension(std::move(sub_blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = root_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); Botan::Certificate_Store_In_Memory trusted; @@ -2193,14 +2130,12 @@ Test::Result test_x509_as_blocks_path_validation_failure_builder() { sub_blocks->inherit_rdi(); } - Botan::X509_Cert_Options root_opts = ca_opts(); - root_opts.extensions.add(std::move(root_blocks)); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - auto [issu_cert, issu_ca] = make_and_sign_ca(std::move(issu_blocks), root_ca, rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = + make_ca(*rng, ca_params().add_extension(std::move(root_blocks))); + auto [issu_cert, issu_ca] = make_and_sign_ca(std::move(issu_blocks), root_ca, *rng); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + const auto sub_req = + user_params().add_extension(std::move(sub_blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = issu_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); @@ -2388,14 +2323,12 @@ Test::Result test_x509_as_blocks_path_validation_failure_ctor() { ASBlocks::ASIdentifiers sub_ident = ASBlocks::ASIdentifiers(sub_asnum, sub_rdi); std::unique_ptr sub_blocks = std::make_unique(sub_ident); - Botan::X509_Cert_Options root_opts = ca_opts(); - root_opts.extensions.add(std::move(root_blocks)); - auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts); - auto [issu_cert, issu_ca] = make_and_sign_ca(std::move(issu_blocks), root_ca, rng); + auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = + make_ca(*rng, ca_params().add_extension(std::move(root_blocks))); + auto [issu_cert, issu_ca] = make_and_sign_ca(std::move(issu_blocks), root_ca, *rng); - Botan::X509_Cert_Options sub_opts = req_opts(sig_algo); - sub_opts.extensions.add(std::move(sub_blocks)); - Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng); + const auto sub_req = + user_params().add_extension(std::move(sub_blocks)).into_pkcs10_request(*sub_key, *rng, hash_fn); Botan::X509_Certificate sub_cert = issu_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); From ae842f7746e7f2d1e0abad378cd2b730fe41dad0 Mon Sep 17 00:00:00 2001 From: Brassinolide <99178854+Brassinolide@users.noreply.github.com> Date: Fri, 22 Aug 2025 18:50:34 +0800 Subject: [PATCH 02/14] Extend Certificate_Store interface to support searching by issuer DN and serial number --- .gitignore | 1 + src/lib/x509/certstor.cpp | 11 ++++++ src/lib/x509/certstor.h | 13 +++++++ .../certstor_flatfile/certstor_flatfile.cpp | 36 ++++++++++++++----- .../certstor_flatfile/certstor_flatfile.h | 4 +++ src/lib/x509/certstor_sql/certstor_sql.cpp | 5 +++ src/lib/x509/certstor_sql/certstor_sql.h | 3 ++ .../x509/certstor_system/certstor_system.cpp | 5 +++ .../x509/certstor_system/certstor_system.h | 3 ++ .../certstor_system_macos/certstor_macos.cpp | 24 +++++++++++++ .../certstor_system_macos/certstor_macos.h | 3 ++ .../certstor_windows.cpp | 20 +++++++++++ .../certstor_windows.h | 3 ++ src/tests/test_certstor.cpp | 36 ++++++++++++++----- src/tests/test_certstor_flatfile.cpp | 23 ++++++++++++ src/tests/test_certstor_system.cpp | 22 ++++++++++++ src/tests/test_certstor_utils.cpp | 5 +++ src/tests/test_certstor_utils.h | 1 + 18 files changed, 200 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 61d01f96326..5e70f6452a1 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ vgcore.* /*.sublime-project /*.sublime-workspace /.editorconfig +/.vscode # Archive files *.tgz diff --git a/src/lib/x509/certstor.cpp b/src/lib/x509/certstor.cpp index 377cc52d83c..5ad5076ddeb 100644 --- a/src/lib/x509/certstor.cpp +++ b/src/lib/x509/certstor.cpp @@ -129,6 +129,17 @@ std::optional Certificate_Store_In_Memory::find_cert_by_raw_su return std::nullopt; } +std::optional Certificate_Store_In_Memory::find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const { + for(const auto& cert : m_certs) { + if(cert.issuer_dn() == issuer_dn && std::ranges::equal(cert.serial_number(), serial_number)) { + return cert; + } + } + + return std::nullopt; +} + void Certificate_Store_In_Memory::add_crl(const X509_CRL& crl) { const X509_DN& crl_issuer = crl.issuer_dn(); diff --git a/src/lib/x509/certstor.h b/src/lib/x509/certstor.h index f802fdfb003..03bbc715aef 100644 --- a/src/lib/x509/certstor.h +++ b/src/lib/x509/certstor.h @@ -56,6 +56,16 @@ class BOTAN_PUBLIC_API(2, 0) Certificate_Store /* NOLINT(*-special-member-functi virtual std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const = 0; + /** + * Find a certificate by searching for one with a matching issuer DN and + * serial number. Used for CMS or PKCS#7. + * @param issuer_dn the distinguished name of the issuer + * @param serial_number the certificate's serial number + * @return a matching certificate or nullopt otherwise + */ + virtual std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const = 0; + /** * Finds a CRL for the given certificate * @param subject the subject certificate @@ -134,6 +144,9 @@ class BOTAN_PUBLIC_API(2, 0) Certificate_Store_In_Memory final : public Certific std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const override; + std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const override; + /** * Finds a CRL for the given certificate */ diff --git a/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp b/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp index 06ff290b3fe..f1c891407e3 100644 --- a/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp +++ b/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp @@ -56,6 +56,7 @@ Flatfile_Certificate_Store::Flatfile_Certificate_Store(std::string_view file, bo m_dn_to_cert[cert.subject_dn()].push_back(cert); m_pubkey_sha1_to_cert.emplace(cert.subject_public_key_bitstring_sha1(), cert); m_subject_dn_sha256_to_cert.emplace(cert.raw_subject_dn_sha256(), cert); + m_issuer_dn_to_cert[cert.issuer_dn()].push_back(cert); } else if(!ignore_non_ca) { throw Invalid_Argument("Flatfile_Certificate_Store received non CA cert " + cert.subject_dn().to_string()); } @@ -72,17 +73,17 @@ std::vector Flatfile_Certificate_Store::all_subjects() const { std::vector Flatfile_Certificate_Store::find_all_certs(const X509_DN& subject_dn, const std::vector& key_id) const { - std::vector found_certs; - try { - const auto certs = m_dn_to_cert.at(subject_dn); + if(!m_dn_to_cert.contains(subject_dn)) { + return {}; + } - for(const auto& cert : certs) { - if(key_id.empty() || key_id == cert.subject_key_id()) { - found_certs.push_back(cert); - } + const auto& certs = m_dn_to_cert.at(subject_dn); + + std::vector found_certs; + for(const auto& cert : certs) { + if(key_id.empty() || key_id == cert.subject_key_id()) { + found_certs.push_back(cert); } - } catch(const std::out_of_range&) { - return {}; } return found_certs; @@ -118,6 +119,23 @@ std::optional Flatfile_Certificate_Store::find_cert_by_raw_sub return std::nullopt; } +std::optional Flatfile_Certificate_Store::find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const { + if(!m_issuer_dn_to_cert.contains(issuer_dn)) { + return std::nullopt; + } + + const auto& certs = m_issuer_dn_to_cert.at(issuer_dn); + + for(const auto& cert : certs) { + if(std::ranges::equal(cert.serial_number(), serial_number)) { + return cert; + } + } + + return std::nullopt; +} + std::optional Flatfile_Certificate_Store::find_crl_for(const X509_Certificate& subject) const { BOTAN_UNUSED(subject); return {}; diff --git a/src/lib/x509/certstor_flatfile/certstor_flatfile.h b/src/lib/x509/certstor_flatfile/certstor_flatfile.h index f61380da2d3..8e8bce4857a 100644 --- a/src/lib/x509/certstor_flatfile/certstor_flatfile.h +++ b/src/lib/x509/certstor_flatfile/certstor_flatfile.h @@ -60,6 +60,9 @@ class BOTAN_PUBLIC_API(2, 11) Flatfile_Certificate_Store final : public Certific std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const override; + std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const override; + /** * Fetching CRLs is not supported by this certificate store. This will * always return an empty list. @@ -71,6 +74,7 @@ class BOTAN_PUBLIC_API(2, 11) Flatfile_Certificate_Store final : public Certific std::map> m_dn_to_cert; std::map, std::optional> m_pubkey_sha1_to_cert; std::map, std::optional> m_subject_dn_sha256_to_cert; + std::map> m_issuer_dn_to_cert; }; } // namespace Botan diff --git a/src/lib/x509/certstor_sql/certstor_sql.cpp b/src/lib/x509/certstor_sql/certstor_sql.cpp index 0adb0396c78..94266d85663 100644 --- a/src/lib/x509/certstor_sql/certstor_sql.cpp +++ b/src/lib/x509/certstor_sql/certstor_sql.cpp @@ -107,6 +107,11 @@ std::optional Certificate_Store_In_SQL::find_cert_by_raw_subje throw Not_Implemented("Certificate_Store_In_SQL::find_cert_by_raw_subject_dn_sha256"); } +std::optional Certificate_Store_In_SQL::find_cert_by_issuer_dn_and_serial_number( + const X509_DN& /*issuer_dn*/, std::span /*serial_number*/) const { + throw Not_Implemented("Certificate_Store_In_SQL::find_cert_by_issuer_dn_and_serial_number"); +} + std::optional Certificate_Store_In_SQL::find_crl_for(const X509_Certificate& subject) const { auto all_crls = generate_crls(); diff --git a/src/lib/x509/certstor_sql/certstor_sql.h b/src/lib/x509/certstor_sql/certstor_sql.h index 0ef0d45a980..47a53b3668b 100644 --- a/src/lib/x509/certstor_sql/certstor_sql.h +++ b/src/lib/x509/certstor_sql/certstor_sql.h @@ -53,6 +53,9 @@ class BOTAN_PUBLIC_API(2, 0) Certificate_Store_In_SQL : public Certificate_Store std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const override; + std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const override; + /** * Returns all subject DNs known to the store instance. */ diff --git a/src/lib/x509/certstor_system/certstor_system.cpp b/src/lib/x509/certstor_system/certstor_system.cpp index 28a83f6da49..77769da5462 100644 --- a/src/lib/x509/certstor_system/certstor_system.cpp +++ b/src/lib/x509/certstor_system/certstor_system.cpp @@ -52,6 +52,11 @@ std::optional System_Certificate_Store::find_cert_by_raw_subje return m_system_store->find_cert_by_raw_subject_dn_sha256(subject_hash); } +std::optional System_Certificate_Store::find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const { + return m_system_store->find_cert_by_issuer_dn_and_serial_number(issuer_dn, serial_number); +} + std::optional System_Certificate_Store::find_crl_for(const X509_Certificate& subject) const { return m_system_store->find_crl_for(subject); } diff --git a/src/lib/x509/certstor_system/certstor_system.h b/src/lib/x509/certstor_system/certstor_system.h index 83412711cf3..a9f0d968b0c 100644 --- a/src/lib/x509/certstor_system/certstor_system.h +++ b/src/lib/x509/certstor_system/certstor_system.h @@ -26,6 +26,9 @@ class BOTAN_PUBLIC_API(2, 11) System_Certificate_Store final : public Certificat std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const override; + std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const override; + std::optional find_crl_for(const X509_Certificate& subject) const override; std::vector all_subjects() const override; diff --git a/src/lib/x509/certstor_system_macos/certstor_macos.cpp b/src/lib/x509/certstor_system_macos/certstor_macos.cpp index e5893e905af..8adb58854a0 100644 --- a/src/lib/x509/certstor_system_macos/certstor_macos.cpp +++ b/src/lib/x509/certstor_system_macos/certstor_macos.cpp @@ -392,6 +392,30 @@ std::optional Certificate_Store_MacOS::find_cert_by_raw_subjec throw Not_Implemented("Certificate_Store_MacOS::find_cert_by_raw_subject_dn_sha256"); } +std::optional Certificate_Store_MacOS::find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const { + Certificate_Store_MacOS_Impl::Query query; + /* + Directly using kSecAttrSerialNumber can't find the certificate + Maybe macOS has a special encoding for the serial number + + query.addParameter(kSecAttrSerialNumber, serial_number); + */ + query.addParameter(kSecAttrIssuer, normalizeAndSerialize(issuer_dn)); + + /* + This is a temporary solution + Use only the issuer DN to find all certificates and filters the serial number, but may affect performance + */ + for(const auto& cert : m_impl->findAll(std::move(query))) { + if(std::ranges::equal(cert.serial_number(), serial_number)) { + return cert; + } + } + + return std::nullopt; +} + std::optional Certificate_Store_MacOS::find_crl_for(const X509_Certificate& subject) const { BOTAN_UNUSED(subject); return {}; diff --git a/src/lib/x509/certstor_system_macos/certstor_macos.h b/src/lib/x509/certstor_system_macos/certstor_macos.h index d24f4c394d0..0706e3ec040 100644 --- a/src/lib/x509/certstor_system_macos/certstor_macos.h +++ b/src/lib/x509/certstor_system_macos/certstor_macos.h @@ -63,6 +63,9 @@ class BOTAN_PUBLIC_API(2, 10) Certificate_Store_MacOS final : public Certificate std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const override; + std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const override; + /** * Fetching CRLs is not supported by the keychain on macOS. This will * always return an empty list. diff --git a/src/lib/x509/certstor_system_windows/certstor_windows.cpp b/src/lib/x509/certstor_system_windows/certstor_windows.cpp index 8528733f77c..41ad3d1542a 100644 --- a/src/lib/x509/certstor_system_windows/certstor_windows.cpp +++ b/src/lib/x509/certstor_system_windows/certstor_windows.cpp @@ -219,6 +219,26 @@ std::optional Certificate_Store_Windows::find_cert_by_raw_subj throw Not_Implemented("Certificate_Store_Windows::find_cert_by_raw_subject_dn_sha256"); } +std::optional Certificate_Store_Windows::find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const { + std::vector dn_data = issuer_dn.BER_encode(); + + _CRYPTOAPI_BLOB blob; + blob.cbData = static_cast(dn_data.size()); + blob.pbData = reinterpret_cast(dn_data.data()); + + auto filter = [&](const std::vector& certs, const X509_Certificate& cert) { + return !already_contains_certificate(certs, cert) && std::ranges::equal(cert.serial_number(), serial_number); + }; + + const auto certs = search_cert_stores(blob, CERT_FIND_ISSUER_NAME, filter, true); + if(certs.empty()) { + return std::nullopt; + } + + return certs.front(); +} + std::optional Certificate_Store_Windows::find_crl_for(const X509_Certificate& subject) const { // TODO: this could be implemented by using the CertFindCRLInStore function BOTAN_UNUSED(subject); diff --git a/src/lib/x509/certstor_system_windows/certstor_windows.h b/src/lib/x509/certstor_system_windows/certstor_windows.h index cdb836ed7a6..5e9766b4703 100644 --- a/src/lib/x509/certstor_system_windows/certstor_windows.h +++ b/src/lib/x509/certstor_system_windows/certstor_windows.h @@ -59,6 +59,9 @@ class BOTAN_PUBLIC_API(2, 11) Certificate_Store_Windows final : public Certifica std::optional find_cert_by_raw_subject_dn_sha256( const std::vector& subject_hash) const override; + std::optional find_cert_by_issuer_dn_and_serial_number( + const X509_DN& issuer_dn, std::span serial_number) const override; + /** * Not Yet Implemented * @return nullptr; diff --git a/src/tests/test_certstor.cpp b/src/tests/test_certstor.cpp index bd5beb6e0ad..546b93818dc 100644 --- a/src/tests/test_certstor.cpp +++ b/src/tests/test_certstor.cpp @@ -267,8 +267,8 @@ Test::Result test_certstor_sqlite3_find_all_certs_test(const std::vector& certsandkeys) { - Test::Result result("Certificate Store - Find by subject hash"); +Test::Result test_certstor_all_finders(const std::vector& certsandkeys) { + Test::Result result("Certificate Store - Test all finders"); try { Botan::Certificate_Store_In_Memory store; @@ -279,15 +279,33 @@ Test::Result test_certstor_find_hash_subject(const std::vectorraw_subject_dn_sha256()); } - result.test_eq("Got wrong certificate", hash, found->raw_subject_dn_sha256()); + // find by issuer dn and serial number + { + const auto& issuer_dn = cert.issuer_dn(); + const auto& serial_number = cert.serial_number(); + + const auto found = store.find_cert_by_issuer_dn_and_serial_number(issuer_dn, serial_number); + if(!found) { + result.test_failure("Can't retrieve certificate " + cert.fingerprint("SHA-1")); + return result; + } + + result.test_eq("Got wrong certificate", serial_number, found->serial_number()); + } } const auto found = store.find_cert_by_raw_subject_dn_sha256(std::vector(32, 0)); @@ -363,7 +381,7 @@ class Certstor_Tests final : public Test { std::vector results; - results.push_back(test_certstor_find_hash_subject(certsandkeys)); + results.push_back(test_certstor_all_finders(certsandkeys)); results.push_back(test_certstor_load_allcert()); #if defined(BOTAN_HAS_CERTSTOR_SQLITE3) results.push_back(test_certstor_sqlite3_insert_find_remove_test(certsandkeys)); diff --git a/src/tests/test_certstor_flatfile.cpp b/src/tests/test_certstor_flatfile.cpp index f5499834447..cc1294066a0 100644 --- a/src/tests/test_certstor_flatfile.cpp +++ b/src/tests/test_certstor_flatfile.cpp @@ -228,6 +228,28 @@ Test::Result certstore_contains_user_certificate() { return result; } +Test::Result find_cert_by_issuer_dn_and_serial_number() { + Test::Result result("Flatfile Certificate Store - Find Certificate by issuer DN and serial number"); + + try { + result.start_timer(); + Botan::Flatfile_Certificate_Store certstore(get_valid_ca_bundle_path()); + auto cert = certstore.find_cert_by_issuer_dn_and_serial_number(get_dn(), get_serial_number()); + result.end_timer(); + + if(result.test_not_nullopt("found certificate", cert)) { + auto cns = cert->subject_dn().get_attribute("CN"); + result.test_int_eq("exactly one CN", cns.size(), 1); + result.test_eq("CN", cns.front(), get_subject_cn()); + result.test_eq("serial number", cert->serial_number(), get_serial_number()); + } + } catch(std::exception& e) { + result.test_failure(e.what()); + } + + return result; +} + class Certstor_Flatfile_Tests final : public Test { public: std::vector run() override { @@ -242,6 +264,7 @@ class Certstor_Flatfile_Tests final : public Test { results.push_back(find_all_subjects()); results.push_back(no_certificate_matches()); results.push_back(certstore_contains_user_certificate()); + results.push_back(find_cert_by_issuer_dn_and_serial_number()); return results; } diff --git a/src/tests/test_certstor_system.cpp b/src/tests/test_certstor_system.cpp index e7c9ed3825f..14a29d0b480 100644 --- a/src/tests/test_certstor_system.cpp +++ b/src/tests/test_certstor_system.cpp @@ -227,6 +227,27 @@ Test::Result find_all_subjects(Botan::Certificate_Store& certstore) { return result; } +Test::Result find_cert_by_issuer_dn_and_serial_number(Botan::Certificate_Store& certstore) { + Test::Result result("System Certificate Store - Find Certificate by issuer DN and serial number"); + + try { + result.start_timer(); + auto cert = certstore.find_cert_by_issuer_dn_and_serial_number(get_dn(), get_serial_number()); + result.end_timer(); + + if(result.test_not_nullopt("found certificate", cert)) { + auto cns = cert->subject_dn().get_attribute("CN"); + result.test_is_eq("exactly one CN", cns.size(), size_t(1)); + result.test_eq("CN", cns.front(), get_subject_cn()); + result.test_eq("serial number", cert->serial_number(), get_serial_number()); + } + } catch(std::exception& e) { + result.test_failure(e.what()); + } + + return result; +} + Test::Result no_certificate_matches(Botan::Certificate_Store& certstore) { Test::Result result("System Certificate Store - can deal with no matches (regression test)"); @@ -312,6 +333,7 @@ class Certstor_System_Tests final : public Test { results.push_back(find_all_subjects(*system)); results.push_back(no_certificate_matches(*system)); results.push_back(find_cert_by_utf8_subject_dn(*system)); + results.push_back(find_cert_by_issuer_dn_and_serial_number(*system)); #if defined(BOTAN_HAS_CERTSTOR_MACOS) results.push_back(certificate_matching_with_dn_normalization(*system)); #endif diff --git a/src/tests/test_certstor_utils.cpp b/src/tests/test_certstor_utils.cpp index dbd52acb297..18daf9ede11 100644 --- a/src/tests/test_certstor_utils.cpp +++ b/src/tests/test_certstor_utils.cpp @@ -88,6 +88,11 @@ std::string get_subject_cn() { return "ISRG Root X1"; } +std::vector get_serial_number() { + // serial number of "ISRG Root X1" + return Botan::hex_decode("8210CFB0D240E3594463E0BB63828B00"); +} + std::vector get_pubkey_sha1_of_cert_with_different_key_id() { // see https://github.com/randombit/botan/issues/2779 for details // diff --git a/src/tests/test_certstor_utils.h b/src/tests/test_certstor_utils.h index 4c87f44cc2b..b03cd9aed93 100644 --- a/src/tests/test_certstor_utils.h +++ b/src/tests/test_certstor_utils.h @@ -28,6 +28,7 @@ std::vector> get_utf8_dn_alternatives(); std::vector get_key_id(); std::string get_subject_cn(); +std::vector get_serial_number(); std::vector get_pubkey_sha1_of_cert_with_different_key_id(); Botan::X509_DN get_dn_of_cert_with_different_key_id(); From f2f428ac1bfa708b9e7a8b2ee2305340691c56ed Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:27:26 +0200 Subject: [PATCH 03/14] Add type hints to botan3.py --- src/python/botan3.py | 540 ++++++++++++++++++++++--------------------- 1 file changed, 274 insertions(+), 266 deletions(-) diff --git a/src/python/botan3.py b/src/python/botan3.py index fa71882abcb..f0abce3f334 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -7,6 +7,7 @@ (C) 2015,2017,2018,2019,2023 Jack Lloyd (C) 2015 Uri Blumenthal (extensions and patches) (C) 2024 Amos Treiber, René Meusel - Rohde & Schwarz Cybersecurity +(C) 2025 Dominik Schricker Botan is released under the Simplified BSD License (see license.txt) @@ -16,9 +17,10 @@ It uses botan's ffi module, which exposes a C API. """ +from __future__ import annotations from ctypes import CDLL, CFUNCTYPE, POINTER, byref, create_string_buffer, \ - c_void_p, c_size_t, c_uint8, c_uint32, c_uint64, c_int, c_uint, c_char, c_char_p, addressof -from typing import Callable + c_void_p, c_size_t, c_uint8, c_uint32, c_uint64, c_int, c_uint, c_char, c_char_p, addressof, Array +from typing import Callable, Any, Union, List from sys import platform from time import strptime, mktime, time as system_time @@ -52,7 +54,7 @@ def __init__(self, message, rc=0): super().__init__(formatted_msg) - def error_code(self): + def error_code(self) -> int: return self.__rc # @@ -578,12 +580,12 @@ def ffi_api(fn, args, allowed_errors=None): # # Internal utilities # -def _call_fn_returning_sz(fn): +def _call_fn_returning_sz(fn) -> int: sz = c_size_t(0) fn(byref(sz)) return int(sz.value) -def _call_fn_returning_vec(guess, fn): +def _call_fn_returning_vec(guess, fn) -> bytes: buf = create_string_buffer(guess) buf_len = c_size_t(len(buf)) @@ -595,7 +597,7 @@ def _call_fn_returning_vec(guess, fn): assert buf_len.value <= len(buf) return buf.raw[0:int(buf_len.value)] -def _call_fn_returning_vec_pair(guess1, guess2, fn): +def _call_fn_returning_vec_pair(guess1, guess2, fn) -> tuple[bytes, bytes]: buf1 = create_string_buffer(guess1) buf1_len = c_size_t(len(buf1)) @@ -626,7 +628,7 @@ def _view_bin_fn(_ctx, buf_val, buf_len): _view_bin_fn.output = buf_val[0:buf_len] return 0 -def _call_fn_viewing_vec(fn): +def _call_fn_viewing_vec(fn) -> bytes: fn(None, _view_bin_fn) result = _view_bin_fn.output _view_bin_fn.output = None @@ -637,22 +639,22 @@ def _view_str_fn(_ctx, str_val, _str_len): _view_str_fn.output = str_val return 0 -def _call_fn_viewing_str(fn): +def _call_fn_viewing_str(fn) -> str: fn(None, _view_str_fn) result = _view_str_fn.output.decode('utf8') _view_str_fn.output = None return result -def _ctype_str(s): +def _ctype_str(s: str) -> bytes: if s is None: return None assert isinstance(s, str) return s.encode('utf-8') -def _ctype_to_str(s): +def _ctype_to_str(s: bytes) -> str: return s.decode('utf-8') -def _ctype_bits(s): +def _ctype_bits(s: str | bytes) -> bytes: if isinstance(s, bytes): return s elif isinstance(s, str): @@ -666,28 +668,34 @@ def _ctype_bufout(buf): def _hex_encode(buf): return hexlify(buf).decode('ascii') + +# +# Internal types +# +_MPIArg = Union[str, "MPI", Any, None] + # # Versioning # -def version_major(): +def version_major() -> int: return int(_DLL.botan_version_major()) -def version_minor(): +def version_minor() -> int: return int(_DLL.botan_version_minor()) -def version_patch(): +def version_patch() -> int: return int(_DLL.botan_version_patch()) -def ffi_api_version(): +def ffi_api_version() -> int: return int(_DLL.botan_ffi_api_version()) -def version_string(): +def version_string() -> int: return _DLL.botan_version_string().decode('ascii') # # Utilities # -def const_time_compare(x, y): +def const_time_compare(x: str | bytes, y: str | bytes) -> bool: xbits = _ctype_bits(x) ybits = _ctype_bits(y) len_x = len(xbits) @@ -735,7 +743,7 @@ def supports_botan_crypto_backend() -> bool: rc = _DLL.botan_tpm2_supports_crypto_backend() return rc == 1 - def enable_botan_crypto_backend(self, rng): + def enable_botan_crypto_backend(self, rng: RandomNumberGenerator): """Enables the Botan-based crypto backend. The passed rng MUST NOT be dependent on the TPM.""" # By keeping a reference to the passed-in RNG object, we make sure @@ -781,7 +789,7 @@ def __init__(self, ctx: TPM2Context): # class RandomNumberGenerator: # Can also use type "system" - def __init__(self, rng_type='system', **kwargs): + def __init__(self, rng_type: str = 'system', **kwargs): """Constructs a RandomNumberGenerator of type rng_type Available RNG types are:: @@ -813,17 +821,17 @@ def __del__(self): def handle_(self): return self.__obj - def reseed(self, bits=256): + def reseed(self, bits: int = 256): _DLL.botan_rng_reseed(self.__obj, bits) - def reseed_from_rng(self, source_rng, bits=256): + def reseed_from_rng(self, source_rng: RandomNumberGenerator, bits: int = 256): _DLL.botan_rng_reseed_from_rng(self.__obj, source_rng.handle_(), bits) - def add_entropy(self, seed): + def add_entropy(self, seed: str | bytes): seedbits = _ctype_bits(seed) _DLL.botan_rng_add_entropy(self.__obj, seedbits, len(seedbits)) - def get(self, length): + def get(self, length: int) -> bytes: out = create_string_buffer(length) l = c_size_t(length) _DLL.botan_rng_get(self.__obj, out, l) @@ -833,7 +841,7 @@ def get(self, length): # Block cipher # class BlockCipher: - def __init__(self, algo): + def __init__(self, algo: str | c_void_p): if isinstance(algo, c_void_p): self.__obj = algo @@ -856,10 +864,10 @@ def __init__(self, algo): def __del__(self): _DLL.botan_block_cipher_destroy(self.__obj) - def set_key(self, key): + def set_key(self, key: bytes): _DLL.botan_block_cipher_set_key(self.__obj, key, len(key)) - def encrypt(self, pt): + def encrypt(self, pt: bytes) -> Array[c_char]: if len(pt) % self.block_size() != 0: raise Exception("Invalid input must be multiple of block size") @@ -868,7 +876,7 @@ def encrypt(self, pt): _DLL.botan_block_cipher_encrypt_blocks(self.__obj, pt, output, blocks) return output - def decrypt(self, ct): + def decrypt(self, ct: bytes) -> Array[c_char]: if len(ct) % self.block_size() != 0: raise Exception("Invalid input must be multiple of block size") @@ -877,22 +885,22 @@ def decrypt(self, ct): _DLL.botan_block_cipher_decrypt_blocks(self.__obj, ct, output, blocks) return output - def algo_name(self): + def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_block_cipher_name(self.__obj, b, bl)) def clear(self): _DLL.botan_block_cipher_clear(self.__obj) - def block_size(self): + def block_size(self) -> int: return self.__block_size - def minimum_keylength(self): + def minimum_keylength(self) -> int: return self.__min_keylen - def maximum_keylength(self): + def maximum_keylength(self) -> int: return self.__max_keylen - def keylength_modulo(self): + def keylength_modulo(self) -> int: return self.__mod_keylen @@ -900,7 +908,7 @@ def keylength_modulo(self): # Hash function # class HashFunction: - def __init__(self, algo): + def __init__(self, algo: str | c_void_p): if isinstance(algo, c_void_p): self.__obj = algo @@ -915,28 +923,28 @@ def __init__(self, algo): def __del__(self): _DLL.botan_hash_destroy(self.__obj) - def copy_state(self): + def copy_state(self) -> HashFunction: copy = c_void_p(0) _DLL.botan_hash_copy_state(byref(copy), self.__obj) return HashFunction(copy) - def algo_name(self): + def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_hash_name(self.__obj, b, bl)) def clear(self): _DLL.botan_hash_clear(self.__obj) - def output_length(self): + def output_length(self) -> int: return self.__output_length - def block_size(self): + def block_size(self) -> int: return self.__block_size - def update(self, x): + def update(self, x: str | bytes): bits = _ctype_bits(x) _DLL.botan_hash_update(self.__obj, bits, len(bits)) - def final(self): + def final(self) -> bytes: out = create_string_buffer(self.output_length()) _DLL.botan_hash_final(self.__obj, out) return _ctype_bufout(out) @@ -945,7 +953,7 @@ def final(self): # Message authentication codes # class MsgAuthCode: - def __init__(self, algo): + def __init__(self, algo: str): flags = c_uint32(0) # always zero in this API version self.__obj = c_void_p(0) _DLL.botan_mac_init(byref(self.__obj), _ctype_str(algo), flags) @@ -969,38 +977,38 @@ def __del__(self): def clear(self): _DLL.botan_mac_clear(self.__obj) - def algo_name(self): + def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_mac_name(self.__obj, b, bl)) - def output_length(self): + def output_length(self) -> int: return self.__output_length - def minimum_keylength(self): + def minimum_keylength(self) -> int: return self.__min_keylen - def maximum_keylength(self): + def maximum_keylength(self) -> int: return self.__max_keylen - def keylength_modulo(self): + def keylength_modulo(self) -> int: return self.__mod_keylen - def set_key(self, key): + def set_key(self, key: bytes): _DLL.botan_mac_set_key(self.__obj, key, len(key)) - def set_nonce(self, nonce): + def set_nonce(self, nonce: bytes): _DLL.botan_mac_set_nonce(self.__obj, nonce, len(nonce)) - def update(self, x): + def update(self, x: str | bytes): bits = _ctype_bits(x) _DLL.botan_mac_update(self.__obj, bits, len(bits)) - def final(self): + def final(self) -> bytes: out = create_string_buffer(self.output_length()) _DLL.botan_mac_final(self.__obj, out) return _ctype_bufout(out) class SymmetricCipher: - def __init__(self, algo, encrypt=True): + def __init__(self, algo: str, encrypt: bool = True): flags = 0 if encrypt else 1 self.__obj = c_void_p(0) _DLL.botan_cipher_init(byref(self.__obj), _ctype_str(algo), flags) @@ -1010,50 +1018,50 @@ def __init__(self, algo, encrypt=True): def __del__(self): _DLL.botan_cipher_destroy(self.__obj) - def algo_name(self): + def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_cipher_name(self.__obj, b, bl)) - def default_nonce_length(self): + def default_nonce_length(self) -> int: l = c_size_t(0) _DLL.botan_cipher_get_default_nonce_length(self.__obj, byref(l)) return l.value - def update_granularity(self): + def update_granularity(self) -> int: l = c_size_t(0) _DLL.botan_cipher_get_update_granularity(self.__obj, byref(l)) return l.value - def ideal_update_granularity(self): + def ideal_update_granularity(self) -> int: l = c_size_t(0) _DLL.botan_cipher_get_ideal_update_granularity(self.__obj, byref(l)) return l.value - def key_length(self): + def key_length(self) -> int: kmin = c_size_t(0) kmax = c_size_t(0) _DLL.botan_cipher_query_keylen(self.__obj, byref(kmin), byref(kmax)) return kmin.value, kmax.value - def minimum_keylength(self): + def minimum_keylength(self) -> int: l = c_size_t(0) _DLL.botan_cipher_get_keyspec(self.__obj, byref(l), None, None) return l.value - def maximum_keylength(self): + def maximum_keylength(self) -> int: l = c_size_t(0) _DLL.botan_cipher_get_keyspec(self.__obj, None, byref(l), None) return l.value - def tag_length(self): + def tag_length(self) -> int: l = c_size_t(0) _DLL.botan_cipher_get_tag_length(self.__obj, byref(l)) return l.value - def is_authenticated(self): + def is_authenticated(self) -> bool: rc = _DLL.botan_cipher_is_authenticated(self.__obj) return rc == 1 - def valid_nonce_length(self, nonce_len): + def valid_nonce_length(self, nonce_len) -> bool: rc = _DLL.botan_cipher_valid_nonce_length(self.__obj, nonce_len) return rc == 1 @@ -1063,16 +1071,16 @@ def reset(self): def clear(self): _DLL.botan_cipher_clear(self.__obj) - def set_key(self, key): + def set_key(self, key: bytes): _DLL.botan_cipher_set_key(self.__obj, key, len(key)) - def set_assoc_data(self, ad): + def set_assoc_data(self, ad: bytes): _DLL.botan_cipher_set_associated_data(self.__obj, ad, len(ad)) - def start(self, nonce): + def start(self, nonce: bytes): _DLL.botan_cipher_start(self.__obj, nonce, len(nonce)) - def _update(self, txt, final): + def _update(self, txt: str | bytes, final: bool): inp = txt if txt else '' bits = _ctype_bits(inp) @@ -1100,13 +1108,13 @@ def _update(self, txt, final): assert inp_consumed.value == inp_sz.value return out.raw[0:int(out_written.value)] - def update(self, txt): + def update(self, txt: str | bytes): return self._update(txt, False) - def finish(self, txt=None): + def finish(self, txt: str | bytes | None = None): return self._update(txt, True) -def bcrypt(passwd, rng_obj, work_factor=10): +def bcrypt(passwd: str, rng_obj: RandomNumberGenerator, work_factor=10): """ Bcrypt password hashing """ @@ -1120,14 +1128,14 @@ def bcrypt(passwd, rng_obj, work_factor=10): b = b[:-1] return _ctype_to_str(b) -def check_bcrypt(passwd, passwd_hash): +def check_bcrypt(passwd: str, passwd_hash: str): rc = _DLL.botan_bcrypt_is_valid(_ctype_str(passwd), _ctype_str(passwd_hash)) return rc == 0 # # PBKDF # -def pbkdf(algo, password, out_len, iterations=100000, salt=None): +def pbkdf(algo: str, password: str, out_len: int, iterations: int = 100000, salt: bytes | None = None) -> tuple[bytes, int, bytes]: if salt is None: salt = RandomNumberGenerator().get(12) @@ -1139,7 +1147,7 @@ def pbkdf(algo, password, out_len, iterations=100000, salt=None): salt, len(salt)) return (salt, iterations, out_buf.raw) -def pbkdf_timed(algo, password, out_len, ms_to_run=300, salt=None): +def pbkdf_timed(algo: str, password: str, out_len: int, ms_to_run: int = 300, salt: bytes | None = None) -> tuple[bytes, int, bytes]: if salt is None: salt = RandomNumberGenerator().get(12) @@ -1156,7 +1164,7 @@ def pbkdf_timed(algo, password, out_len, ms_to_run=300, salt=None): # # Scrypt # -def scrypt(out_len, password, salt, n=1024, r=8, p=8): +def scrypt(out_len: int, password: str, salt: str | bytes, n: int = 1024, r: int = 8, p: int = 8) -> bytes: out_buf = create_string_buffer(out_len) passbits = _ctype_str(password) saltbits = _ctype_bits(salt) @@ -1177,7 +1185,7 @@ def scrypt(out_len, password, salt, n=1024, r=8, p=8): # p specifies the parallelism # # returns an output of out_len bytes -def argon2(variant, out_len, password, salt, m=256, t=1, p=1): +def argon2(variant: str, out_len: int, password: str, salt: str | bytes, m: int = 256, t: int = 1, p: int = 1) -> bytes: out_buf = create_string_buffer(out_len) passbits = _ctype_str(password) saltbits = _ctype_bits(salt) @@ -1192,7 +1200,7 @@ def argon2(variant, out_len, password, salt, m=256, t=1, p=1): # # KDF # -def kdf(algo, secret, out_len, salt, label): +def kdf(algo: str, secret: bytes, out_len: int, salt: bytes, label: bytes) -> bytes: out_buf = create_string_buffer(out_len) out_sz = c_size_t(out_len) _DLL.botan_kdf(_ctype_str(algo), out_buf, out_sz, @@ -1205,20 +1213,20 @@ def kdf(algo, secret, out_len, salt, label): # Public key # class PublicKey: # pylint: disable=invalid-name - def __init__(self, obj=None): + def __init__(self, obj: c_void_p | None = None): if not obj: obj = c_void_p(0) self.__obj = obj @classmethod - def load(cls, val): + def load(cls, val: str | bytes) -> PublicKey: pub = PublicKey() bits = _ctype_bits(val) _DLL.botan_pubkey_load(byref(pub.handle_()), bits, len(bits)) return pub @classmethod - def load_rsa(cls, n, e): + def load_rsa(cls, n: _MPIArg, e: _MPIArg) -> PublicKey: pub = PublicKey() n = MPI(n) e = MPI(e) @@ -1226,7 +1234,7 @@ def load_rsa(cls, n, e): return pub @classmethod - def load_dsa(cls, p, q, g, y): + def load_dsa(cls, p: _MPIArg, q: _MPIArg, g: _MPIArg, y: _MPIArg) -> PublicKey: pub = PublicKey() p = MPI(p) q = MPI(q) @@ -1236,7 +1244,7 @@ def load_dsa(cls, p, q, g, y): return pub @classmethod - def load_dh(cls, p, g, y): + def load_dh(cls, p: _MPIArg, g: _MPIArg, y: _MPIArg) -> PublicKey: pub = PublicKey() p = MPI(p) g = MPI(g) @@ -1245,7 +1253,7 @@ def load_dh(cls, p, g, y): return pub @classmethod - def load_elgamal(cls, p, q, g, y): + def load_elgamal(cls, p: _MPIArg, q: _MPIArg, g: _MPIArg, y: _MPIArg) -> PublicKey: pub = PublicKey() p = MPI(p) q = MPI(q) @@ -1255,7 +1263,7 @@ def load_elgamal(cls, p, q, g, y): return pub @classmethod - def load_ecdsa(cls, curve, pub_x, pub_y): + def load_ecdsa(cls, curve: str, pub_x: _MPIArg, pub_y: _MPIArg) -> PublicKey: pub = PublicKey() pub_x = MPI(pub_x) pub_y = MPI(pub_y) @@ -1263,13 +1271,13 @@ def load_ecdsa(cls, curve, pub_x, pub_y): return pub @classmethod - def load_ecdsa_sec1(cls, curve, sec1_encoding): + def load_ecdsa_sec1(cls, curve: str, sec1_encoding: str | bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_ecdsa_sec1(byref(pub.handle_()), _ctype_bits(sec1_encoding), len(sec1_encoding), _ctype_str(curve)) return pub @classmethod - def load_ecdh(cls, curve, pub_x, pub_y): + def load_ecdh(cls, curve: str, pub_x: _MPIArg, pub_y: _MPIArg) -> PublicKey: pub = PublicKey() pub_x = MPI(pub_x) pub_y = MPI(pub_y) @@ -1277,13 +1285,13 @@ def load_ecdh(cls, curve, pub_x, pub_y): return pub @classmethod - def load_ecdh_sec1(cls, curve, sec1_encoding): + def load_ecdh_sec1(cls, curve: str, sec1_encoding: str | bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_ecdh_sec1(byref(pub.handle_()), _ctype_bits(sec1_encoding), len(sec1_encoding), _ctype_str(curve)) return pub @classmethod - def load_sm2(cls, curve, pub_x, pub_y): + def load_sm2(cls, curve: str, pub_x: _MPIArg, pub_y: _MPIArg) -> PublicKey: pub = PublicKey() pub_x = MPI(pub_x) pub_y = MPI(pub_y) @@ -1291,43 +1299,43 @@ def load_sm2(cls, curve, pub_x, pub_y): return pub @classmethod - def load_sm2_sec1(cls, curve, sec1_encoding): + def load_sm2_sec1(cls, curve: str, sec1_encoding: str | bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_sm2_sec1(byref(pub.handle_()), _ctype_bits(sec1_encoding), len(sec1_encoding), _ctype_str(curve)) return pub @classmethod - def load_kyber(cls, key): + def load_kyber(cls, key: bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_kyber(byref(pub.handle_()), key, len(key)) return pub @classmethod - def load_ml_kem(cls, mlkem_mode, key): + def load_ml_kem(cls, mlkem_mode: str, key: bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_ml_kem(byref(pub.handle_()), key, len(key), _ctype_str(mlkem_mode)) return pub @classmethod - def load_ml_dsa(cls, mldsa_mode, key): + def load_ml_dsa(cls, mldsa_mode: str, key: bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_ml_dsa(byref(pub.handle_()), key, len(key), _ctype_str(mldsa_mode)) return pub @classmethod - def load_slh_dsa(cls, slhdsa_mode, key): + def load_slh_dsa(cls, slhdsa_mode: str, key: bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_slh_dsa(byref(pub.handle_()), key, len(key), _ctype_str(slhdsa_mode)) return pub @classmethod - def load_frodokem(cls, frodo_mode, key): + def load_frodokem(cls, frodo_mode: str, key: bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_frodokem(byref(pub.handle_()), key, len(key), _ctype_str(frodo_mode)) return pub @classmethod - def load_classic_mceliece(cls, cmce_mode, key): + def load_classic_mceliece(cls, cmce_mode: str, key: bytes) -> PublicKey: pub = PublicKey() _DLL.botan_pubkey_load_classic_mceliece(byref(pub.handle_()), key, len(key), _ctype_str(cmce_mode)) return pub @@ -1338,39 +1346,39 @@ def __del__(self): def handle_(self): return self.__obj - def check_key(self, rng_obj, strong=True): + def check_key(self, rng_obj: RandomNumberGenerator, strong: bool = True) -> bool: flags = 1 if strong else 0 rc = _DLL.botan_pubkey_check_key(self.__obj, rng_obj.handle_(), flags) return rc == 0 - def estimated_strength(self): + def estimated_strength(self) -> int: r = c_size_t(0) _DLL.botan_pubkey_estimated_strength(self.__obj, byref(r)) return r.value - def algo_name(self): + def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_pubkey_algo_name(self.__obj, b, bl)) - def export(self, pem=False): + def export(self, pem: bool = False) -> str | bytes: if pem: return self.to_pem() else: return self.to_der() - def to_der(self): + def to_der(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_pubkey_view_der(self.__obj, vc, vfn)) - def to_pem(self): + def to_pem(self) -> str: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_pubkey_view_pem(self.__obj, vc, vfn)) - def to_raw(self): + def to_raw(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_pubkey_view_raw(self.__obj, vc, vfn)) - def view_kyber_raw_key(self): + def view_kyber_raw_key(self) -> bytes: """Deprecated: use to_raw() instead""" return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_pubkey_view_kyber_raw_key(self.__obj, vc, vfn)) - def fingerprint(self, hash_algorithm='SHA-256'): + def fingerprint(self, hash_algorithm: str = 'SHA-256') -> str: n = HashFunction(hash_algorithm).output_length() buf = create_string_buffer(n) buf_len = c_size_t(n) @@ -1378,30 +1386,30 @@ def fingerprint(self, hash_algorithm='SHA-256'): _DLL.botan_pubkey_fingerprint(self.__obj, _ctype_str(hash_algorithm), buf, byref(buf_len)) return _hex_encode(buf[0:int(buf_len.value)]) - def get_field(self, field_name): + def get_field(self, field_name: str) -> int: v = MPI() _DLL.botan_pubkey_get_field(v.handle_(), self.__obj, _ctype_str(field_name)) return int(v) - def object_identifier(self): + def object_identifier(self) -> OID: oid = OID() _DLL.botan_pubkey_oid(byref(oid.handle_()), self.__obj) return oid - def get_public_point(self): + def get_public_point(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_pubkey_view_ec_public_point(self.__obj, vc, vfn)) # # Private Key # class PrivateKey: - def __init__(self, obj=None): + def __init__(self, obj: c_void_p | None = None): if not obj: obj = c_void_p(0) self.__obj = obj @classmethod - def load(cls, val, passphrase=""): + def load(cls, val: str | bytes, passphrase: str = "") -> PrivateKey: priv = PrivateKey() rng_obj = c_void_p(0) # unused in recent versions bits = _ctype_bits(val) @@ -1409,7 +1417,7 @@ def load(cls, val, passphrase=""): return priv @classmethod - def create(cls, algo, params, rng_obj): + def create(cls, algo: str, params: str | int | tuple[int, int], rng_obj: RandomNumberGenerator) -> PrivateKey: if algo == 'rsa': algo = 'RSA' params = "%d" % (params) @@ -1433,13 +1441,13 @@ def create(cls, algo, params, rng_obj): return priv @classmethod - def create_ec(cls, algo, ec_group, rng_obj): + def create_ec(cls, algo: str, ec_group: ECGroup, rng_obj: RandomNumberGenerator) -> PrivateKey: obj = c_void_p(0) _DLL.botan_ec_privkey_create(byref(obj), _ctype_str(algo), ec_group.handle_(), rng_obj.handle_()) return PrivateKey(obj) @classmethod - def load_rsa(cls, p, q, e): + def load_rsa(cls, p: _MPIArg, q: _MPIArg, e: _MPIArg) -> PrivateKey: priv = PrivateKey() p = MPI(p) q = MPI(q) @@ -1448,7 +1456,7 @@ def load_rsa(cls, p, q, e): return priv @classmethod - def load_dsa(cls, p, q, g, x): + def load_dsa(cls, p: _MPIArg, q: _MPIArg, g: _MPIArg, x: _MPIArg) -> PrivateKey: priv = PrivateKey() p = MPI(p) q = MPI(q) @@ -1458,7 +1466,7 @@ def load_dsa(cls, p, q, g, x): return priv @classmethod - def load_dh(cls, p, g, x): + def load_dh(cls, p: _MPIArg, g: _MPIArg, x: _MPIArg) -> PrivateKey: priv = PrivateKey() p = MPI(p) g = MPI(g) @@ -1467,7 +1475,7 @@ def load_dh(cls, p, g, x): return priv @classmethod - def load_elgamal(cls, p, q, g, x): + def load_elgamal(cls, p: _MPIArg, q: _MPIArg, g: _MPIArg, x: _MPIArg) -> PrivateKey: priv = PrivateKey() p = MPI(p) q = MPI(q) @@ -1477,34 +1485,34 @@ def load_elgamal(cls, p, q, g, x): return priv @classmethod - def load_ecdsa(cls, curve, x): + def load_ecdsa(cls, curve: str, x: _MPIArg) -> PrivateKey: priv = PrivateKey() x = MPI(x) _DLL.botan_privkey_load_ecdsa(byref(priv.handle_()), x.handle_(), _ctype_str(curve)) return priv @classmethod - def load_ecdh(cls, curve, x): + def load_ecdh(cls, curve: str, x: _MPIArg) -> PrivateKey: priv = PrivateKey() x = MPI(x) _DLL.botan_privkey_load_ecdh(byref(priv.handle_()), x.handle_(), _ctype_str(curve)) return priv @classmethod - def load_sm2(cls, curve, x): + def load_sm2(cls, curve: str, x: _MPIArg) -> PrivateKey: priv = PrivateKey() x = MPI(x) _DLL.botan_privkey_load_sm2(byref(priv.handle_()), x.handle_(), _ctype_str(curve)) return priv @classmethod - def load_kyber(cls, key): + def load_kyber(cls, key: bytes) -> PrivateKey: priv = PrivateKey() _DLL.botan_privkey_load_kyber(byref(priv.handle_()), key, len(key)) return priv @classmethod - def load_ml_kem(cls, mlkem_mode, key): + def load_ml_kem(cls, mlkem_mode: str, key: bytes) -> PrivateKey: priv = PrivateKey() _DLL.botan_privkey_load_ml_kem(byref(priv.handle_()), key, len(key), _ctype_str(mlkem_mode)) return priv @@ -1516,19 +1524,19 @@ def load_ml_dsa(cls, mldsa_mode, key): return priv @classmethod - def load_slh_dsa(cls, slh_dsa, key): + def load_slh_dsa(cls, slh_dsa: str, key: bytes) -> PrivateKey: priv = PrivateKey() _DLL.botan_privkey_load_slh_dsa(byref(priv.handle_()), key, len(key), _ctype_str(slh_dsa)) return priv @classmethod - def load_frodokem(cls, frodo_mode, key): + def load_frodokem(cls, frodo_mode: str, key: bytes) -> PrivateKey: priv = PrivateKey() _DLL.botan_privkey_load_frodokem(byref(priv.handle_()), key, len(key), _ctype_str(frodo_mode)) return priv @classmethod - def load_classic_mceliece(cls, cmce_mode, key): + def load_classic_mceliece(cls, cmce_mode: str, key: bytes) -> PrivateKey: priv = PrivateKey() _DLL.botan_privkey_load_classic_mceliece(byref(priv.handle_()), key, len(key), _ctype_str(cmce_mode)) return priv @@ -1539,39 +1547,39 @@ def __del__(self): def handle_(self): return self.__obj - def check_key(self, rng_obj, strong=True): + def check_key(self, rng_obj: RandomNumberGenerator, strong: bool = True) -> bool: flags = 1 if strong else 0 rc = _DLL.botan_privkey_check_key(self.__obj, rng_obj.handle_(), flags) return rc == 0 - def algo_name(self): + def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_privkey_algo_name(self.__obj, b, bl)) - def get_public_key(self): + def get_public_key(self) -> PublicKey: pub = PublicKey() _DLL.botan_privkey_export_pubkey(byref(pub.handle_()), self.__obj) return pub - def to_der(self): + def to_der(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_privkey_view_der(self.__obj, vc, vfn)) - def to_pem(self): + def to_pem(self) -> str: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_privkey_view_pem(self.__obj, vc, vfn)) - def to_raw(self): + def to_raw(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_privkey_view_raw(self.__obj, vc, vfn)) - def view_kyber_raw_key(self): + def view_kyber_raw_key(self) -> bytes: """Deprecated: use to_raw() instead""" return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_privkey_view_kyber_raw_key(self.__obj, vc, vfn)) - def export(self, pem=False): + def export(self, pem: bool = False) -> str | bytes: if pem: return self.to_pem() else: return self.to_der() - def export_encrypted(self, passphrase, rng, pem=False, msec=300, cipher=None, pbkdf=None): # pylint: disable=redefined-outer-name + def export_encrypted(self, passphrase: str, rng: RandomNumberGenerator, pem: bool = False, msec: int = 300, cipher: str = None, pbkdf: str = None): # pylint: disable=redefined-outer-name if pem: return _call_fn_viewing_str( lambda vc, vfn: _DLL.botan_privkey_view_encrypted_pem_timed( @@ -1583,30 +1591,30 @@ def export_encrypted(self, passphrase, rng, pem=False, msec=300, cipher=None, pb self.__obj, rng.handle_(), _ctype_str(passphrase), _ctype_str(cipher), _ctype_str(pbkdf), c_size_t(msec), vc, vfn)) - def get_field(self, field_name): + def get_field(self, field_name: str) -> int: v = MPI() _DLL.botan_privkey_get_field(v.handle_(), self.__obj, _ctype_str(field_name)) return int(v) - def object_identifier(self): + def object_identifier(self) -> OID: oid = OID() _DLL.botan_privkey_oid(byref(oid.handle_()), self.__obj) return oid - def stateful_operation(self): + def stateful_operation(self) -> str: r = c_int(0) _DLL.botan_privkey_stateful_operation(self.__obj, byref(r)) if r.value == 0: return False return True - def remaining_operations(self): + def remaining_operations(self) -> int: r = c_uint64(0) _DLL.botan_privkey_remaining_operations(self.__obj, byref(r)) return r.value class PKEncrypt: - def __init__(self, key, padding): + def __init__(self, key: PublicKey, padding: str): self.__obj = c_void_p(0) flags = c_uint32(0) # always zero in this ABI _DLL.botan_pk_op_encrypt_create(byref(self.__obj), key.handle_(), _ctype_str(padding), flags) @@ -1614,7 +1622,7 @@ def __init__(self, key, padding): def __del__(self): _DLL.botan_pk_op_encrypt_destroy(self.__obj) - def encrypt(self, msg, rng_obj): + def encrypt(self, msg: bytes, rng_obj: RandomNumberGenerator) -> bytes: outbuf_sz = c_size_t(0) _DLL.botan_pk_op_encrypt_output_length(self.__obj, len(msg), byref(outbuf_sz)) outbuf = create_string_buffer(outbuf_sz.value) @@ -1623,7 +1631,7 @@ def encrypt(self, msg, rng_obj): class PKDecrypt: - def __init__(self, key, padding): + def __init__(self, key: PrivateKey, padding: str): self.__obj = c_void_p(0) flags = c_uint32(0) # always zero in this ABI _DLL.botan_pk_op_decrypt_create(byref(self.__obj), key.handle_(), _ctype_str(padding), flags) @@ -1631,7 +1639,7 @@ def __init__(self, key, padding): def __del__(self): _DLL.botan_pk_op_decrypt_destroy(self.__obj) - def decrypt(self, msg): + def decrypt(self, msg: bytes) -> bytes: outbuf_sz = c_size_t(0) _DLL.botan_pk_op_decrypt_output_length(self.__obj, len(msg), byref(outbuf_sz)) outbuf = create_string_buffer(outbuf_sz.value) @@ -1640,7 +1648,7 @@ def decrypt(self, msg): return outbuf.raw[0:int(outbuf_sz.value)] class PKSign: # pylint: disable=invalid-name - def __init__(self, key, padding, der=False): + def __init__(self, key: PrivateKey, padding: str, der: bool = False): self.__obj = c_void_p(0) flags = c_uint32(1) if der else c_uint32(0) _DLL.botan_pk_op_sign_create(byref(self.__obj), key.handle_(), _ctype_str(padding), flags) @@ -1648,10 +1656,10 @@ def __init__(self, key, padding, der=False): def __del__(self): _DLL.botan_pk_op_sign_destroy(self.__obj) - def update(self, msg): + def update(self, msg: str | bytes): _DLL.botan_pk_op_sign_update(self.__obj, _ctype_bits(msg), len(msg)) - def finish(self, rng_obj): + def finish(self, rng_obj: RandomNumberGenerator) -> bytes: outbuf_sz = c_size_t(0) _DLL.botan_pk_op_sign_output_length(self.__obj, byref(outbuf_sz)) outbuf = create_string_buffer(outbuf_sz.value) @@ -1659,7 +1667,7 @@ def finish(self, rng_obj): return outbuf.raw[0:int(outbuf_sz.value)] class PKVerify: - def __init__(self, key, padding, der=False): + def __init__(self, key: PublicKey, padding: str, der: bool = False): self.__obj = c_void_p(0) flags = c_uint32(1) if der else c_uint32(0) _DLL.botan_pk_op_verify_create(byref(self.__obj), key.handle_(), _ctype_str(padding), flags) @@ -1667,11 +1675,11 @@ def __init__(self, key, padding, der=False): def __del__(self): _DLL.botan_pk_op_verify_destroy(self.__obj) - def update(self, msg): + def update(self, msg: str | bytes): bits = _ctype_bits(msg) _DLL.botan_pk_op_verify_update(self.__obj, bits, len(bits)) - def check_signature(self, signature): + def check_signature(self, signature: str | bytes) -> bool: bits = _ctype_bits(signature) rc = _DLL.botan_pk_op_verify_finish(self.__obj, bits, len(bits)) if rc == 0: @@ -1679,7 +1687,7 @@ def check_signature(self, signature): return False class PKKeyAgreement: - def __init__(self, key, kdf_name): + def __init__(self, key: PrivateKey, kdf_name: str): self.__obj = c_void_p(0) flags = c_uint32(0) # always zero in this ABI _DLL.botan_pk_op_key_agreement_create(byref(self.__obj), key.handle_(), _ctype_str(kdf_name), flags) @@ -1690,15 +1698,15 @@ def __init__(self, key, kdf_name): def __del__(self): _DLL.botan_pk_op_key_agreement_destroy(self.__obj) - def public_value(self): + def public_value(self) -> bytes: return self.m_public_value - def underlying_output_length(self): + def underlying_output_length(self) -> int: out_len = c_size_t(0) _DLL.botan_pk_op_key_agreement_size(self.__obj, byref(out_len)) return out_len.value - def agree(self, other, key_len, salt): + def agree(self, other: bytes, key_len: int, salt: bytes) -> bytes: if key_len == 0: key_len = self.underlying_output_length() return _call_fn_returning_vec(key_len, lambda b, bl: @@ -1707,22 +1715,22 @@ def agree(self, other, key_len, salt): salt, len(salt))) class KemEncrypt: - def __init__(self, key, params): + def __init__(self, key: PublicKey, params: str): self.__obj = c_void_p(0) _DLL.botan_pk_op_kem_encrypt_create(byref(self.__obj), key.handle_(), _ctype_str(params)) def __del__(self): _DLL.botan_pk_op_kem_encrypt_destroy(self.__obj) - def shared_key_length(self, desired_key_len): + def shared_key_length(self, desired_key_len: int) -> int: return _call_fn_returning_sz( lambda l: _DLL.botan_pk_op_kem_encrypt_shared_key_length(self.__obj, desired_key_len, l)) - def encapsulated_key_length(self): + def encapsulated_key_length(self) -> int: return _call_fn_returning_sz( lambda l: _DLL.botan_pk_op_kem_encrypt_encapsulated_key_length(self.__obj, l)) - def create_shared_key(self, rng, salt, desired_key_len): + def create_shared_key(self, rng: RandomNumberGenerator, salt: bytes, desired_key_len: int) -> tuple[bytes, bytes]: shared_key_len = self.shared_key_length(desired_key_len) shared_key_buf = create_string_buffer(shared_key_len) @@ -1747,18 +1755,18 @@ def create_shared_key(self, rng, salt, desired_key_len): return (shared_key, encapsulated_key) class KemDecrypt: - def __init__(self, key, params): + def __init__(self, key: PrivateKey, params: str): self.__obj = c_void_p(0) _DLL.botan_pk_op_kem_decrypt_create(byref(self.__obj), key.handle_(), _ctype_str(params)) def __del__(self): _DLL.botan_pk_op_kem_decrypt_destroy(self.__obj) - def shared_key_length(self, desired_key_len): + def shared_key_length(self, desired_key_len: int) -> int: return _call_fn_returning_sz( lambda l: _DLL.botan_pk_op_kem_decrypt_shared_key_length(self.__obj, desired_key_len, l)) - def decrypt_shared_key(self, salt, desired_key_len, encapsulated_key): + def decrypt_shared_key(self, salt: bytes, desired_key_len: int, encapsulated_key: bytes) -> bytes: shared_key_len = self.shared_key_length(desired_key_len) return _call_fn_returning_vec( @@ -1795,14 +1803,14 @@ def _load_buf_or_file(filename, buf, file_fn, buf_fn): # X.509 certificates # class X509Cert: # pylint: disable=invalid-name - def __init__(self, filename=None, buf=None): + def __init__(self, filename: str | None = None, buf: bytes | None = None): self.__obj = c_void_p(0) self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_cert_load_file, _DLL.botan_x509_cert_load) def __del__(self): _DLL.botan_x509_cert_destroy(self.__obj) - def time_starts(self): + def time_starts(self) -> str: starts = _call_fn_returning_str( 16, lambda b, bl: _DLL.botan_x509_cert_get_time_starts(self.__obj, b, bl)) if len(starts) == 13: @@ -1816,7 +1824,7 @@ def time_starts(self): return datetime.fromtimestamp(mktime(struct_time)) - def time_expires(self): + def time_expires(self) -> str: expires = _call_fn_returning_str( 16, lambda b, bl: _DLL.botan_x509_cert_get_time_expires(self.__obj, b, bl)) if len(expires) == 13: @@ -1830,59 +1838,59 @@ def time_expires(self): return datetime.fromtimestamp(mktime(struct_time)) - def to_string(self): + def to_string(self) -> str: return _call_fn_viewing_str( lambda vc, vfn: _DLL.botan_x509_cert_view_as_string(self.__obj, vc, vfn)) - def fingerprint(self, hash_algo='SHA-256'): + def fingerprint(self, hash_algo: str = 'SHA-256') -> str: n = HashFunction(hash_algo).output_length() * 3 return _call_fn_returning_str( n, lambda b, bl: _DLL.botan_x509_cert_get_fingerprint(self.__obj, _ctype_str(hash_algo), b, bl)) - def serial_number(self): + def serial_number(self) -> bytes: return _call_fn_returning_vec( 32, lambda b, bl: _DLL.botan_x509_cert_get_serial_number(self.__obj, b, bl)) - def authority_key_id(self): + def authority_key_id(self) -> bytes: return _call_fn_returning_vec( 32, lambda b, bl: _DLL.botan_x509_cert_get_authority_key_id(self.__obj, b, bl)) - def subject_key_id(self): + def subject_key_id(self) -> bytes: return _call_fn_returning_vec( 32, lambda b, bl: _DLL.botan_x509_cert_get_subject_key_id(self.__obj, b, bl)) - def subject_public_key_bits(self): + def subject_public_key_bits(self) -> bytes: return _call_fn_viewing_vec( lambda vc, vfn: _DLL.botan_x509_cert_view_public_key_bits(self.__obj, vc, vfn)) - def subject_public_key(self): + def subject_public_key(self) -> PublicKey: pub = c_void_p(0) _DLL.botan_x509_cert_get_public_key(self.__obj, byref(pub)) return PublicKey(pub) - def subject_dn(self, key, index): + def subject_dn(self, key: str, index: int) -> str: return _call_fn_returning_str( 0, lambda b, bl: _DLL.botan_x509_cert_get_subject_dn(self.__obj, _ctype_str(key), index, b, bl)) - def issuer_dn(self, key, index): + def issuer_dn(self, key: str, index: int) -> str: return _call_fn_returning_str( 0, lambda b, bl: _DLL.botan_x509_cert_get_issuer_dn(self.__obj, _ctype_str(key), index, b, bl)) - def hostname_match(self, hostname): + def hostname_match(self, hostname: str) -> bool: rc = _DLL.botan_x509_cert_hostname_match(self.__obj, _ctype_str(hostname)) return rc == 0 - def not_before(self): + def not_before(self) -> int: time = c_uint64(0) _DLL.botan_x509_cert_not_before(self.__obj, byref(time)) return time.value - def not_after(self): + def not_after(self) -> int: time = c_uint64(0) _DLL.botan_x509_cert_not_after(self.__obj, byref(time)) return time.value - def allowed_usage(self, usage_list): + def allowed_usage(self, usage_list: List[str]) -> bool: usage_values = {"NO_CONSTRAINTS": 0, "DIGITAL_SIGNATURE": 32768, "NON_REPUDIATION": 16384, @@ -1906,13 +1914,13 @@ def handle_(self): return self.__obj def verify(self, - intermediates=None, - trusted=None, - trusted_path=None, - required_strength=0, - hostname=None, - reference_time=0, - crls=None): + intermediates: List[X509Cert] | None = None, + trusted: List[X509Cert] | None = None, + trusted_path: str | None = None, + required_strength: int = 0, + hostname: str = None, + reference_time: int = 0, + crls: List[X509CRL] | None = None) -> int: if intermediates is not None: c_intermediates = len(intermediates) * c_void_p @@ -1962,10 +1970,10 @@ def verify(self, return error_code.value @classmethod - def validation_status(cls, error_code): + def validation_status(cls, error_code: int) -> str: return _ctype_to_str(_DLL.botan_x509_cert_validation_status(c_int(error_code))) - def is_revoked(self, crl): + def is_revoked(self, crl: X509CRL) -> bool: rc = _DLL.botan_x509_is_revoked(crl.handle_(), self.__obj) return rc == 0 @@ -1974,7 +1982,7 @@ def is_revoked(self, crl): # X.509 Certificate revocation lists # class X509CRL: - def __init__(self, filename=None, buf=None): + def __init__(self, filename: str | None = None, buf: bytes | None = None): self.__obj = c_void_p(0) self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_crl_load_file, _DLL.botan_x509_crl_load) @@ -1987,7 +1995,7 @@ def handle_(self): class MPI: - def __init__(self, initial_value=None, radix=None): + def __init__(self, initial_value: _MPIArg = None, radix: int = None): self.__obj = c_void_p(0) _DLL.botan_mp_init(byref(self.__obj)) @@ -2005,13 +2013,13 @@ def __init__(self, initial_value=None, radix=None): _DLL.botan_mp_set_from_str(self.__obj, _ctype_str(str(initial_value))) @classmethod - def random(cls, rng_obj, bits): + def random(cls, rng_obj: RandomNumberGenerator, bits: int) -> MPI: bn = MPI() _DLL.botan_mp_rand_bits(bn.handle_(), rng_obj.handle_(), c_size_t(bits)) return bn @classmethod - def random_range(cls, rng_obj, lower, upper): + def random_range(cls, rng_obj: RandomNumberGenerator, lower: MPI, upper: MPI): bn = MPI() _DLL.botan_mp_rand_range(bn.handle_(), rng_obj.handle_(), lower.handle_(), upper.handle_()) return bn @@ -2039,7 +2047,7 @@ def __repr__(self): out = out.raw[0:int(out_len.value - 1)] return _ctype_to_str(out) - def to_bytes(self): + def to_bytes(self) -> Array[c_char]: byte_count = self.byte_count() out_len = c_size_t(byte_count) out = create_string_buffer(out_len.value) @@ -2047,28 +2055,28 @@ def to_bytes(self): assert out_len.value == byte_count return out - def is_negative(self): + def is_negative(self) -> bool: rc = _DLL.botan_mp_is_negative(self.__obj) return rc == 1 - def is_positive(self): + def is_positive(self) -> bool: rc = _DLL.botan_mp_is_positive(self.__obj) return rc == 1 - def is_zero(self): + def is_zero(self) -> bool: rc = _DLL.botan_mp_is_zero(self.__obj) return rc == 1 - def is_odd(self): + def is_odd(self) -> bool: return self.get_bit(0) == 1 - def is_even(self): + def is_even(self) -> bool: return self.get_bit(0) == 0 def flip_sign(self): _DLL.botan_mp_flip_sign(self.__obj) - def cmp(self, other): + def cmp(self, other: MPI) -> int: r = c_int(0) _DLL.botan_mp_cmp(byref(r), self.__obj, other.handle_()) return r.value @@ -2076,130 +2084,130 @@ def cmp(self, other): def __hash__(self): return hash(self.to_bytes()) - def __eq__(self, other): + def __eq__(self, other: MPI): return self.cmp(other) == 0 - def __ne__(self, other): + def __ne__(self, other: MPI): return self.cmp(other) != 0 - def __lt__(self, other): + def __lt__(self, other: MPI): return self.cmp(other) < 0 - def __le__(self, other): + def __le__(self, other: MPI): return self.cmp(other) <= 0 - def __gt__(self, other): + def __gt__(self, other: MPI): return self.cmp(other) > 0 - def __ge__(self, other): + def __ge__(self, other: MPI): return self.cmp(other) >= 0 - def __add__(self, other): + def __add__(self, other: MPI): r = MPI() _DLL.botan_mp_add(r.handle_(), self.__obj, other.handle_()) return r - def __iadd__(self, other): + def __iadd__(self, other: MPI): _DLL.botan_mp_add(self.__obj, self.__obj, other.handle_()) return self - def __sub__(self, other): + def __sub__(self, other: MPI): r = MPI() _DLL.botan_mp_sub(r.handle_(), self.__obj, other.handle_()) return r - def __isub__(self, other): + def __isub__(self, other: MPI): _DLL.botan_mp_sub(self.__obj, self.__obj, other.handle_()) return self - def __mul__(self, other): + def __mul__(self, other: MPI): r = MPI() _DLL.botan_mp_mul(r.handle_(), self.__obj, other.handle_()) return r - def __imul__(self, other): + def __imul__(self, other: MPI): _DLL.botan_mp_mul(self.__obj, self.__obj, other.handle_()) return self - def __divmod__(self, other): + def __divmod__(self, other: MPI): d = MPI() q = MPI() _DLL.botan_mp_div(d.handle_(), q.handle_(), self.__obj, other.handle_()) return (d, q) - def __mod__(self, other): + def __mod__(self, other: MPI): d = MPI() q = MPI() _DLL.botan_mp_div(d.handle_(), q.handle_(), self.__obj, other.handle_()) return q - def __lshift__(self, shift): + def __lshift__(self, shift: int): shift = c_size_t(shift) r = MPI() _DLL.botan_mp_lshift(r.handle_(), self.__obj, shift) return r - def __ilshift__(self, shift): + def __ilshift__(self, shift: int): shift = c_size_t(shift) _DLL.botan_mp_lshift(self.__obj, self.__obj, shift) return self - def __rshift__(self, shift): + def __rshift__(self, shift: int): shift = c_size_t(shift) r = MPI() _DLL.botan_mp_rshift(r.handle_(), self.__obj, shift) return r - def __irshift__(self, shift): + def __irshift__(self, shift: int): shift = c_size_t(shift) _DLL.botan_mp_rshift(self.__obj, self.__obj, shift) return self - def mod_mul(self, other, modulus): + def mod_mul(self, other: MPI, modulus: MPI) -> MPI: r = MPI() _DLL.botan_mp_mod_mul(r.handle_(), self.__obj, other.handle_(), modulus.handle_()) return r - def gcd(self, other): + def gcd(self, other: MPI) -> MPI: r = MPI() _DLL.botan_mp_gcd(r.handle_(), self.__obj, other.handle_()) return r - def pow_mod(self, exponent, modulus): + def pow_mod(self, exponent: MPI, modulus: MPI) -> MPI: r = MPI() _DLL.botan_mp_powmod(r.handle_(), self.__obj, exponent.handle_(), modulus.handle_()) return r - def is_prime(self, rng_obj, prob=128): + def is_prime(self, rng_obj: RandomNumberGenerator, prob: int = 128) -> bool: return _DLL.botan_mp_is_prime(self.__obj, rng_obj.handle_(), c_size_t(prob)) == 1 - def inverse_mod(self, modulus): + def inverse_mod(self, modulus: MPI) -> MPI: r = MPI() _DLL.botan_mp_mod_inverse(r.handle_(), self.__obj, modulus.handle_()) return r - def bit_count(self): + def bit_count(self) -> int: b = c_size_t(0) _DLL.botan_mp_num_bits(self.__obj, byref(b)) return b.value - def byte_count(self): + def byte_count(self) -> int: b = c_size_t(0) _DLL.botan_mp_num_bytes(self.__obj, byref(b)) return b.value - def get_bit(self, bit): + def get_bit(self, bit: int) -> bool: return _DLL.botan_mp_get_bit(self.__obj, c_size_t(bit)) == 1 - def clear_bit(self, bit): + def clear_bit(self, bit: int): _DLL.botan_mp_clear_bit(self.__obj, c_size_t(bit)) - def set_bit(self, bit): + def set_bit(self, bit: int): _DLL.botan_mp_set_bit(self.__obj, c_size_t(bit)) class OID: - def __init__(self, obj=None): + def __init__(self, obj: c_void_p | None = None): if not obj: obj = c_void_p(0) self.__obj = obj @@ -2211,46 +2219,46 @@ def handle_(self): return self.__obj @classmethod - def from_string(cls, value): + def from_string(cls, value: str) -> OID: oid = OID() _DLL.botan_oid_from_string(byref(oid.handle_()), _ctype_str(value)) return oid - def to_string(self): + def to_string(self) -> str: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_oid_view_string(self.__obj, vc, vfn)) - def to_name(self): + def to_name(self) -> str: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_oid_view_name(self.__obj, vc, vfn)) - def register(self, name): + def register(self, name: str): _DLL.botan_oid_register(self.__obj, _ctype_str(name)) - def cmp(self, other): + def cmp(self, other: OID) -> int: r = c_int(0) _DLL.botan_oid_cmp(byref(r), self.__obj, other.handle_()) return r.value - def __eq__(self, other): + def __eq__(self, other: OID): return self.cmp(other) == 0 - def __ne__(self, other): + def __ne__(self, other: OID): return self.cmp(other) != 0 - def __lt__(self, other): + def __lt__(self, other: OID): return self.cmp(other) < 0 - def __le__(self, other): + def __le__(self, other: OID): return self.cmp(other) <= 0 - def __gt__(self, other): + def __gt__(self, other: OID): return self.cmp(other) > 0 - def __ge__(self, other): + def __ge__(self, other: OID): return self.cmp(other) >= 0 class ECGroup: - def __init__(self, obj=None): + def __init__(self, obj: c_void_p | None = None): if not obj: obj = c_void_p(0) self.__obj = obj @@ -2262,7 +2270,7 @@ def __del__(self): _DLL.botan_ec_group_destroy(self.__obj) @classmethod - def supports_application_specific_group(cls): + def supports_application_specific_group(cls) -> bool: r = c_int(0) _DLL.botan_ec_group_supports_application_specific_group(byref(r)) if r.value == 0: @@ -2270,7 +2278,7 @@ def supports_application_specific_group(cls): return True @classmethod - def supports_named_group(cls, name): + def supports_named_group(cls, name: str) -> bool: r = c_int(0) _DLL.botan_ec_group_supports_named_group(_ctype_str(name), byref(r)) if r.value == 0: @@ -2278,7 +2286,7 @@ def supports_named_group(cls, name): return True @classmethod - def from_params(cls, oid, p, a, b, base_x, base_y, order): + def from_params(cls, oid: OID, p: MPI, a: MPI, b: MPI, base_x: MPI, base_y: MPI, order: MPI) -> ECGroup: ec_group = ECGroup() _DLL.botan_ec_group_from_params( byref(ec_group.handle_()), @@ -2293,81 +2301,81 @@ def from_params(cls, oid, p, a, b, base_x, base_y, order): return ec_group @classmethod - def from_ber(cls, ber): + def from_ber(cls, ber: bytes) -> ECGroup: ec_group = ECGroup() _DLL.botan_ec_group_from_ber(byref(ec_group.handle_()), ber, len(ber)) return ec_group @classmethod - def from_pem(cls, pem): + def from_pem(cls, pem: str) -> ECGroup: ec_group = ECGroup() _DLL.botan_ec_group_from_pem(byref(ec_group.handle_()), _ctype_str(pem)) return ec_group @classmethod - def from_oid(cls, oid): + def from_oid(cls, oid: OID) -> ECGroup: ec_group = ECGroup() _DLL.botan_ec_group_from_oid(byref(ec_group.handle_()), oid.handle_()) return ec_group @classmethod - def from_name(cls, name): + def from_name(cls, name: str) -> ECGroup: ec_group = ECGroup() _DLL.botan_ec_group_from_name(byref(ec_group.handle_()), _ctype_str(name)) return ec_group - def to_der(self): + def to_der(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_ec_group_view_der(self.__obj, vc, vfn)) - def to_pem(self): + def to_pem(self) -> bytes: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_ec_group_view_pem(self.__obj, vc, vfn)) - def get_curve_oid(self): + def get_curve_oid(self) -> OID: oid = OID() _DLL.botan_ec_group_get_curve_oid(byref(oid.handle_()), self.__obj) return oid - def get_p(self): + def get_p(self) -> MPI: p = MPI() _DLL.botan_ec_group_get_p(byref(p.handle_()), self.__obj) return p - def get_a(self): + def get_a(self) -> MPI: a = MPI() _DLL.botan_ec_group_get_a(byref(a.handle_()), self.__obj) return a - def get_b(self): + def get_b(self) -> MPI: b = MPI() _DLL.botan_ec_group_get_b(byref(b.handle_()), self.__obj) return b - def get_g_x(self): + def get_g_x(self) -> MPI: g_x = MPI() _DLL.botan_ec_group_get_g_x(byref(g_x.handle_()), self.__obj) return g_x - def get_g_y(self): + def get_g_y(self) -> MPI: g_y = MPI() _DLL.botan_ec_group_get_g_y(byref(g_y.handle_()), self.__obj) return g_y - def get_order(self): + def get_order(self) -> MPI: order = MPI() _DLL.botan_ec_group_get_order(byref(order.handle_()), self.__obj) return order - def __eq__(self, other): + def __eq__(self, other: ECGroup): rc = _DLL.botan_ec_group_equal(self.__obj, other.handle_()) return rc == 1 - def __ne__(self, other): + def __ne__(self, other: ECGroup): return not self == other class FormatPreservingEncryptionFE1: - def __init__(self, modulus, key, rounds=5, compat_mode=False): + def __init__(self, modulus: MPI, key: bytes, rounds: int = 5, compat_mode: bool = False): flags = c_uint32(1 if compat_mode else 0) self.__obj = c_void_p(0) _DLL.botan_fpe_fe1_init(byref(self.__obj), modulus.handle_(), key, len(key), c_size_t(rounds), flags) @@ -2375,32 +2383,32 @@ def __init__(self, modulus, key, rounds=5, compat_mode=False): def __del__(self): _DLL.botan_fpe_destroy(self.__obj) - def encrypt(self, msg, tweak): + def encrypt(self, msg: _MPIArg, tweak: str | bytes) -> MPI: r = MPI(msg) bits = _ctype_bits(tweak) _DLL.botan_fpe_encrypt(self.__obj, r.handle_(), bits, len(bits)) return r - def decrypt(self, msg, tweak): + def decrypt(self, msg: _MPIArg, tweak: str | bytes) -> MPI: r = MPI(msg) bits = _ctype_bits(tweak) _DLL.botan_fpe_decrypt(self.__obj, r.handle_(), bits, len(bits)) return r class HOTP: - def __init__(self, key, digest="SHA-1", digits=6): + def __init__(self, key: bytes, digest: str = "SHA-1", digits: int = 6): self.__obj = c_void_p(0) _DLL.botan_hotp_init(byref(self.__obj), key, len(key), _ctype_str(digest), digits) def __del__(self): _DLL.botan_hotp_destroy(self.__obj) - def generate(self, counter): + def generate(self, counter: int) -> int: code = c_uint32(0) _DLL.botan_hotp_generate(self.__obj, byref(code), counter) return code.value - def check(self, code, counter, resync_range=0): + def check(self, code: int, counter: int, resync_range: int = 0) -> tuple[bool, int]: next_ctr = c_uint64(0) rc = _DLL.botan_hotp_check(self.__obj, byref(next_ctr), code, counter, resync_range) if rc == 0: @@ -2409,21 +2417,21 @@ def check(self, code, counter, resync_range=0): return (False, counter) class TOTP: - def __init__(self, key, digest="SHA-1", digits=6, timestep=30): + def __init__(self, key: bytes, digest: str = "SHA-1", digits: int = 6, timestep: int = 30): self.__obj = c_void_p(0) _DLL.botan_totp_init(byref(self.__obj), key, len(key), _ctype_str(digest), digits, timestep) def __del__(self): _DLL.botan_totp_destroy(self.__obj) - def generate(self, timestamp=None): + def generate(self, timestamp: int | None = None) -> int: if timestamp is None: timestamp = int(system_time()) code = c_uint32(0) _DLL.botan_totp_generate(self.__obj, byref(code), timestamp) return code.value - def check(self, code, timestamp=None, acceptable_drift=0): + def check(self, code: int, timestamp: int | None = None, acceptable_drift: int = 0) -> bool: if timestamp is None: timestamp = int(system_time()) rc = _DLL.botan_totp_check(self.__obj, code, timestamp, acceptable_drift) @@ -2431,7 +2439,7 @@ def check(self, code, timestamp=None, acceptable_drift=0): return True return False -def nist_key_wrap(kek, key, cipher=None): +def nist_key_wrap(kek: bytes, key: bytes, cipher: str | None = None) -> bytes: cipher_algo = "AES-%d" % (8*len(kek)) if cipher is None else cipher padding = 0 output = create_string_buffer(len(key) + 8) @@ -2442,7 +2450,7 @@ def nist_key_wrap(kek, key, cipher=None): output, byref(out_len)) return output[0:int(out_len.value)] -def nist_key_unwrap(kek, wrapped, cipher=None): +def nist_key_unwrap(kek: bytes, wrapped: bytes, cipher: str | None = None) -> bytes: cipher_algo = "AES-%d" % (8*len(kek)) if cipher is None else cipher padding = 0 output = create_string_buffer(len(wrapped)) @@ -2456,7 +2464,7 @@ def nist_key_unwrap(kek, wrapped, cipher=None): class Srp6ServerSession: __obj = c_void_p(0) - def __init__(self, group): + def __init__(self, group: str): _DLL.botan_srp6_server_session_init(byref(self.__obj)) self.__group = group self.__group_size = _call_fn_returning_sz( @@ -2465,7 +2473,7 @@ def __init__(self, group): def __del__(self): _DLL.botan_srp6_server_session_destroy(self.__obj) - def step1(self, verifier, hsh, rng): + def step1(self, verifier: bytes, hsh: str, rng: RandomNumberGenerator) -> bytes: return _call_fn_returning_vec(self.__group_size, lambda b, bl: _DLL.botan_srp6_server_session_step1(self.__obj, @@ -2475,13 +2483,13 @@ def step1(self, verifier, hsh, rng): rng.handle_(), b, bl)) - def step2(self, a): + def step2(self, a: bytes): return _call_fn_returning_vec(self.__group_size, lambda k, kl: _DLL.botan_srp6_server_session_step2(self.__obj, a, len(a), k, kl)) -def srp6_generate_verifier(identifier, password, salt, group, hsh): +def srp6_generate_verifier(identifier: str, password: str, salt: bytes, group: str, hsh: str) -> bytes: sz = _call_fn_returning_sz(lambda l: _DLL.botan_srp6_group_size(_ctype_str(group), l)) return _call_fn_returning_vec(sz, lambda v, vl: @@ -2492,7 +2500,7 @@ def srp6_generate_verifier(identifier, password, salt, group, hsh): _ctype_str(hsh), v, vl)) -def srp6_client_agree(username, password, group, hsh, salt, b, rng): +def srp6_client_agree(username: str, password: str, group: str, hsh: str, salt: bytes, b: bytes, rng: RandomNumberGenerator) -> tuple[bytes, bytes]: sz = _call_fn_returning_sz(lambda l: _DLL.botan_srp6_group_size(_ctype_str(group), l)) return _call_fn_returning_vec_pair(sz, sz, lambda a, al, k, kl: @@ -2506,7 +2514,7 @@ def srp6_client_agree(username, password, group, hsh, salt, b, rng): a, al, k, kl)) -def zfec_encode(k, n, input_bytes): +def zfec_encode(k: int, n: int, input_bytes: bytes) -> List[bytes]: """ ZFEC-encode an input message according to the given parameters @@ -2539,7 +2547,7 @@ def zfec_encode(k, n, input_bytes): return [output.raw for output in outputs] -def zfec_decode(k, n, indexes, inputs): +def zfec_decode(k: int, n: int, indexes: List[int], inputs: List[bytes]) -> List[bytes]: """ ZFEC decode From 00f1aed35199cfb3ae471cf93b2394648552c468 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Wed, 3 Sep 2025 11:18:51 -0400 Subject: [PATCH 04/14] Fix various warnings from the ruff linter --- configure.py | 8 +-- src/configs/sphinx/conf.py | 2 +- src/python/botan3.py | 55 +++++++++---------- src/scripts/bench.py | 38 ++++++------- src/scripts/ci/gh_clang_tidy_fixes_in_pr.py | 6 +- src/scripts/ci_report_sizes.py | 2 +- .../dev_tools/analyze_timing_results.py | 2 +- src/scripts/dev_tools/file_size_check.py | 6 +- src/scripts/dev_tools/gen_dilithium_kat.py | 2 +- src/scripts/dev_tools/gen_ec_groups.py | 2 +- src/scripts/dev_tools/gen_frodo_kat.py | 5 +- src/scripts/dev_tools/gen_kyber_kat.py | 5 +- src/scripts/dev_tools/gen_mp_monty.py | 6 +- src/scripts/dev_tools/gen_pqc_dsa_kats.py | 2 +- src/scripts/run_limbo_tests.py | 2 +- src/scripts/run_tls_attacker.py | 6 +- src/scripts/run_tls_fuzzer.py | 2 +- src/scripts/test_cli.py | 4 +- src/scripts/test_fuzzers.py | 4 +- src/scripts/test_python.py | 14 ++--- src/scripts/tls_scanner/tls_scanner.py | 4 +- 21 files changed, 87 insertions(+), 90 deletions(-) diff --git a/configure.py b/configure.py index 10bc5822669..799e36dbca7 100755 --- a/configure.py +++ b/configure.py @@ -41,8 +41,8 @@ class InternalError(Exception): pass -def flatten(l): - return sum(l, []) +def flatten(lst): + return sum(lst, []) def normalize_source_path(source): """ @@ -1586,7 +1586,7 @@ def cc_compile_flags(self, options): def _so_link_search(osname, debug_info): so_link_typ = [osname, 'default'] if debug_info: - so_link_typ = [l + '-debug' for l in so_link_typ] + so_link_typ + so_link_typ = [link + '-debug' for link in so_link_typ] + so_link_typ return so_link_typ def so_link_command_for(self, osname, options): @@ -3408,7 +3408,7 @@ def run_compiler_preproc(options, ccinfo, source_file, default_return, extra_fla cc_output = run_compiler(options, ccinfo, default_return, ccinfo.preproc_flags.split(' ') + extra_flags + [source_file]) def cleanup_output(output): - return ('\n'.join([l for l in output.splitlines() if l.startswith('#') is False])).strip() + return ('\n'.join([line for line in output.splitlines() if not line.startswith('#')])).strip() return cleanup_output(cc_output) diff --git a/src/configs/sphinx/conf.py b/src/configs/sphinx/conf.py index c27c89ab993..91953753d5d 100644 --- a/src/configs/sphinx/conf.py +++ b/src/configs/sphinx/conf.py @@ -111,7 +111,7 @@ def parse_version_file(version_path): 'source_branch': 'master', 'source_directory': 'doc/', } -except ImportError as e: +except ImportError: print("Could not import furo theme; falling back to agago") html_theme = 'agogo' html_theme_path = [] diff --git a/src/python/botan3.py b/src/python/botan3.py index f0abce3f334..3ce29a4bff1 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -833,8 +833,7 @@ def add_entropy(self, seed: str | bytes): def get(self, length: int) -> bytes: out = create_string_buffer(length) - l = c_size_t(length) - _DLL.botan_rng_get(self.__obj, out, l) + _DLL.botan_rng_get(self.__obj, out, c_size_t(length)) return _ctype_bufout(out) # @@ -917,8 +916,8 @@ def __init__(self, algo: str | c_void_p): self.__obj = c_void_p(0) _DLL.botan_hash_init(byref(self.__obj), _ctype_str(algo), flags) - self.__output_length = _call_fn_returning_sz(lambda l: _DLL.botan_hash_output_length(self.__obj, l)) - self.__block_size = _call_fn_returning_sz(lambda l: _DLL.botan_hash_block_size(self.__obj, l)) + self.__output_length = _call_fn_returning_sz(lambda length: _DLL.botan_hash_output_length(self.__obj, length)) + self.__block_size = _call_fn_returning_sz(lambda length: _DLL.botan_hash_block_size(self.__obj, length)) def __del__(self): _DLL.botan_hash_destroy(self.__obj) @@ -1022,19 +1021,19 @@ def algo_name(self) -> str: return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_cipher_name(self.__obj, b, bl)) def default_nonce_length(self) -> int: - l = c_size_t(0) - _DLL.botan_cipher_get_default_nonce_length(self.__obj, byref(l)) - return l.value + length = c_size_t(0) + _DLL.botan_cipher_get_default_nonce_length(self.__obj, byref(length)) + return length.value def update_granularity(self) -> int: - l = c_size_t(0) - _DLL.botan_cipher_get_update_granularity(self.__obj, byref(l)) - return l.value + length = c_size_t(0) + _DLL.botan_cipher_get_update_granularity(self.__obj, byref(length)) + return length.value def ideal_update_granularity(self) -> int: - l = c_size_t(0) - _DLL.botan_cipher_get_ideal_update_granularity(self.__obj, byref(l)) - return l.value + length = c_size_t(0) + _DLL.botan_cipher_get_ideal_update_granularity(self.__obj, byref(length)) + return length.value def key_length(self) -> int: kmin = c_size_t(0) @@ -1043,19 +1042,19 @@ def key_length(self) -> int: return kmin.value, kmax.value def minimum_keylength(self) -> int: - l = c_size_t(0) - _DLL.botan_cipher_get_keyspec(self.__obj, byref(l), None, None) - return l.value + length = c_size_t(0) + _DLL.botan_cipher_get_keyspec(self.__obj, byref(length), None, None) + return length.value def maximum_keylength(self) -> int: - l = c_size_t(0) - _DLL.botan_cipher_get_keyspec(self.__obj, None, byref(l), None) - return l.value + length = c_size_t(0) + _DLL.botan_cipher_get_keyspec(self.__obj, None, byref(length), None) + return length.value def tag_length(self) -> int: - l = c_size_t(0) - _DLL.botan_cipher_get_tag_length(self.__obj, byref(l)) - return l.value + length = c_size_t(0) + _DLL.botan_cipher_get_tag_length(self.__obj, byref(length)) + return length.value def is_authenticated(self) -> bool: rc = _DLL.botan_cipher_is_authenticated(self.__obj) @@ -1724,11 +1723,11 @@ def __del__(self): def shared_key_length(self, desired_key_len: int) -> int: return _call_fn_returning_sz( - lambda l: _DLL.botan_pk_op_kem_encrypt_shared_key_length(self.__obj, desired_key_len, l)) + lambda len: _DLL.botan_pk_op_kem_encrypt_shared_key_length(self.__obj, desired_key_len, len)) def encapsulated_key_length(self) -> int: return _call_fn_returning_sz( - lambda l: _DLL.botan_pk_op_kem_encrypt_encapsulated_key_length(self.__obj, l)) + lambda len: _DLL.botan_pk_op_kem_encrypt_encapsulated_key_length(self.__obj, len)) def create_shared_key(self, rng: RandomNumberGenerator, salt: bytes, desired_key_len: int) -> tuple[bytes, bytes]: shared_key_len = self.shared_key_length(desired_key_len) @@ -1764,7 +1763,7 @@ def __del__(self): def shared_key_length(self, desired_key_len: int) -> int: return _call_fn_returning_sz( - lambda l: _DLL.botan_pk_op_kem_decrypt_shared_key_length(self.__obj, desired_key_len, l)) + lambda len: _DLL.botan_pk_op_kem_decrypt_shared_key_length(self.__obj, desired_key_len, len)) def decrypt_shared_key(self, salt: bytes, desired_key_len: int, encapsulated_key: bytes) -> bytes: shared_key_len = self.shared_key_length(desired_key_len) @@ -2468,7 +2467,7 @@ def __init__(self, group: str): _DLL.botan_srp6_server_session_init(byref(self.__obj)) self.__group = group self.__group_size = _call_fn_returning_sz( - lambda l: _DLL.botan_srp6_group_size(_ctype_str(group), l)) + lambda len: _DLL.botan_srp6_group_size(_ctype_str(group), len)) def __del__(self): _DLL.botan_srp6_server_session_destroy(self.__obj) @@ -2490,7 +2489,7 @@ def step2(self, a: bytes): k, kl)) def srp6_generate_verifier(identifier: str, password: str, salt: bytes, group: str, hsh: str) -> bytes: - sz = _call_fn_returning_sz(lambda l: _DLL.botan_srp6_group_size(_ctype_str(group), l)) + sz = _call_fn_returning_sz(lambda len: _DLL.botan_srp6_group_size(_ctype_str(group), len)) return _call_fn_returning_vec(sz, lambda v, vl: _DLL.botan_srp6_generate_verifier(_ctype_str(identifier), @@ -2501,7 +2500,7 @@ def srp6_generate_verifier(identifier: str, password: str, salt: bytes, group: s v, vl)) def srp6_client_agree(username: str, password: str, group: str, hsh: str, salt: bytes, b: bytes, rng: RandomNumberGenerator) -> tuple[bytes, bytes]: - sz = _call_fn_returning_sz(lambda l: _DLL.botan_srp6_group_size(_ctype_str(group), l)) + sz = _call_fn_returning_sz(lambda len: _DLL.botan_srp6_group_size(_ctype_str(group), len)) return _call_fn_returning_vec_pair(sz, sz, lambda a, al, k, kl: _DLL.botan_srp6_client_agree(_ctype_str(username), diff --git a/src/scripts/bench.py b/src/scripts/bench.py index 250f0ec83be..c461b6a8244 100755 --- a/src/scripts/bench.py +++ b/src/scripts/bench.py @@ -120,18 +120,18 @@ def run_openssl_bench(openssl, algo): result = {} - for l in output.splitlines(): - if ignored.match(l): + for line in output.splitlines(): + if ignored.match(line): continue if not result: - match = buf_header.match(l) + match = buf_header.match(line) if match is None: - logging.error("Unexpected output from OpenSSL %s", l) + logging.error("Unexpected output from OpenSSL %s", line) result = {'algo': algo, 'buf_size': int(match.group(3))} else: - match = res_header.match(l) + match = res_header.match(line) result['bytes'] = int(match.group(1)) * result['buf_size'] result['runtime'] = float(match.group(2)) @@ -145,18 +145,18 @@ def run_openssl_bench(openssl, algo): result = {} - for l in output.splitlines(): - if ignored.match(l): + for line in output.splitlines(): + if ignored.match(line): continue - if match := signature_ops.match(l): + if match := signature_ops.match(line): results.append({ 'algo': algo, 'key_size': int(match.group(3)), 'op': 'sign', 'ops': int(match.group(2)), 'runtime': float(match.group(4))}) - elif match := verify_ops.match(l): + elif match := verify_ops.match(line): results.append({ 'algo': algo, 'key_size': int(match.group(3)), @@ -165,7 +165,7 @@ def run_openssl_bench(openssl, algo): 'runtime': float(match.group(4)) }) else: - logging.error("Unexpected output from OpenSSL %s", l) + logging.error("Unexpected output from OpenSSL %s", line) elif algo in KEY_AGREEMENT_EVP_MAP: res_header = re.compile(r'\+(R7|R9|R12|R14):([0-9]+):([0-9]+):([0-9]+\.[0-9]+)$') @@ -173,18 +173,18 @@ def run_openssl_bench(openssl, algo): result = {} - for l in output.splitlines(): - if ignored.match(l): + for line in output.splitlines(): + if ignored.match(line): continue - if match := res_header.match(l): + if match := res_header.match(line): results.append({ 'algo': algo, 'key_size': int(match.group(3)), 'ops': int(match.group(2)), 'runtime': float(match.group(4))}) else: - logging.error("Unexpected output from OpenSSL %s", l) + logging.error("Unexpected output from OpenSSL %s", line) return results @@ -236,13 +236,13 @@ def run_botan_key_agreement_bench(botan, runtime, algo): output = json.loads(output) results = [] - for l in output: - if l['op'] == 'key agreements': + for res in output: + if res['op'] == 'key agreements': results.append({ 'algo': algo, - 'key_size': int(re.search(r'[A-Z]+-[a-z]*([0-9]+).*', l['algo']).group(1)), - 'ops': l['events'], - 'runtime': l['nanos'] / 1000 / 1000 / 1000, + 'key_size': int(re.search(r'[A-Z]+-[a-z]*([0-9]+).*', res['algo']).group(1)), + 'ops': res['events'], + 'runtime': res['nanos'] / 1000 / 1000 / 1000, }) return results diff --git a/src/scripts/ci/gh_clang_tidy_fixes_in_pr.py b/src/scripts/ci/gh_clang_tidy_fixes_in_pr.py index edb851445af..ad756546dc9 100755 --- a/src/scripts/ci/gh_clang_tidy_fixes_in_pr.py +++ b/src/scripts/ci/gh_clang_tidy_fixes_in_pr.py @@ -48,11 +48,11 @@ def __map_file_offset(self, offset : int) -> tuple[int, int]: with open(self.file, encoding="utf-8") as srcfile: readoffset = 0 lineoffset = 0 - for l in srcfile.readlines(): - readoffset += len(l) + for line in srcfile.readlines(): + readoffset += len(line) lineoffset += 1 if readoffset >= offset: - coloffset = offset - readoffset + len(l) + coloffset = offset - readoffset + len(line) return (lineoffset, coloffset) raise RuntimeError(f"FileOffset {offset} out of range for {self.file}") diff --git a/src/scripts/ci_report_sizes.py b/src/scripts/ci_report_sizes.py index 7665a84d3b5..54e85f4e5e8 100755 --- a/src/scripts/ci_report_sizes.py +++ b/src/scripts/ci_report_sizes.py @@ -22,7 +22,7 @@ def format_size(bytes): return "%d bytes" % (bytes) def report_size(fsname): - if os.access(fsname, os.R_OK) == False: + if not os.access(fsname, os.R_OK): print("ERROR: Could not find %s" % (fsname)) return bytes = os.stat(fsname).st_size diff --git a/src/scripts/dev_tools/analyze_timing_results.py b/src/scripts/dev_tools/analyze_timing_results.py index 690928dcfff..ef3b9972578 100755 --- a/src/scripts/dev_tools/analyze_timing_results.py +++ b/src/scripts/dev_tools/analyze_timing_results.py @@ -41,7 +41,7 @@ def main(args = None): if match is None: print("Failed to match on '%s'" % (line)) - cnt = int(match.group(1)) + #cnt = int(match.group(1)) id = int(match.group(2)) time = int(match.group(3)) diff --git a/src/scripts/dev_tools/file_size_check.py b/src/scripts/dev_tools/file_size_check.py index b9678172389..50ada693489 100755 --- a/src/scripts/dev_tools/file_size_check.py +++ b/src/scripts/dev_tools/file_size_check.py @@ -7,11 +7,11 @@ def lines_in(f): lines = 0 - for l in f.decode('utf8').splitlines(): - if l == '': + for line in f.decode('utf8').splitlines(): + if line == '': continue - if l.startswith('#'): + if line.startswith('#'): continue lines += 1 return lines diff --git a/src/scripts/dev_tools/gen_dilithium_kat.py b/src/scripts/dev_tools/gen_dilithium_kat.py index 177e5cc054e..6c4e830ca77 100755 --- a/src/scripts/dev_tools/gen_dilithium_kat.py +++ b/src/scripts/dev_tools/gen_dilithium_kat.py @@ -41,7 +41,7 @@ def read_kats(self): while True: key, val = self.next_value() - if key == None: + if key is None: return # eof if key not in ['count', 'seed', 'mlen', 'msg', 'pk', 'sk', 'smlen', 'sm']: diff --git a/src/scripts/dev_tools/gen_ec_groups.py b/src/scripts/dev_tools/gen_ec_groups.py index 196a115cd61..c0873be0cb7 100755 --- a/src/scripts/dev_tools/gen_ec_groups.py +++ b/src/scripts/dev_tools/gen_ec_groups.py @@ -115,7 +115,7 @@ class OmitFirstLine: def __init__(self): self.first_line = True - def __call__(self, l): + def __call__(self, line): r = not self.first_line self.first_line = False return r diff --git a/src/scripts/dev_tools/gen_frodo_kat.py b/src/scripts/dev_tools/gen_frodo_kat.py index a891f67dbfc..861aebedda1 100644 --- a/src/scripts/dev_tools/gen_frodo_kat.py +++ b/src/scripts/dev_tools/gen_frodo_kat.py @@ -44,7 +44,7 @@ def read_kats(self): while True: key, val = self.next_value() - if key == None: + if key is None: return # eof if key not in ['count', 'seed', 'pk', 'sk', 'ct', 'ss']: @@ -66,8 +66,7 @@ def shake_256_16(v): return h.hexdigest(16) def compress_kat(kat): - first = kat['count'] == 0 - del kat['count'] + del kat['count'] # not needed # rename keys kat['Seed'] = kat.pop('seed') diff --git a/src/scripts/dev_tools/gen_kyber_kat.py b/src/scripts/dev_tools/gen_kyber_kat.py index a05a92f6c72..eed0592ec40 100755 --- a/src/scripts/dev_tools/gen_kyber_kat.py +++ b/src/scripts/dev_tools/gen_kyber_kat.py @@ -57,7 +57,7 @@ def read_kats(self): while True: key, val = self.next_value() - if key == None: + if key is None: return # eof if key in ['msg']: @@ -88,8 +88,7 @@ def sha256_16(v): return h.hexdigest()[:32] def compress_kat(kat, mode): - first = kat['count'] == 0 - del kat['count'] + del kat['count'] # Not needed hash_fn = sha256_16 if '90s' in mode else shake_256_16 diff --git a/src/scripts/dev_tools/gen_mp_monty.py b/src/scripts/dev_tools/gen_mp_monty.py index 601dc81c72c..75c0745ad1f 100755 --- a/src/scripts/dev_tools/gen_mp_monty.py +++ b/src/scripts/dev_tools/gen_mp_monty.py @@ -15,9 +15,9 @@ def monty_redc_code(n, p_dash1=False): fn_name = "bigint_monty_redc_pdash1" if p_dash1 else "bigint_monty_redc" if p_dash1: - hdr = "void bigint_monty_redc_pdash1_%d(word r[%d], const word z[%d], const word p[%d], word ws[%d]) {\n" % (n, n, 2*n, n, n) + hdr = "void %s_%d(word r[%d], const word z[%d], const word p[%d], word ws[%d]) {\n" % (fn_name, n, n, 2*n, n, n) else: - hdr = "void bigint_monty_redc_%d(word r[%d], const word z[%d], const word p[%d], word p_dash, word ws[%d]) {\n" % (n, n, 2*n, n, n) + hdr = "void %s_%d(word r[%d], const word z[%d], const word p[%d], word p_dash, word ws[%d]) {\n" % (fn_name, n, n, 2*n, n, n) ftr = "\n}\n" @@ -52,7 +52,7 @@ def monty_redc_code(n, p_dash1=False): lines.append("bigint_monty_maybe_sub<%d>(r, w1, ws, p);" % (n)) - return hdr + "\n".join([" %s" % (l) for l in lines]) + ftr + return hdr + "\n".join([" %s" % (line) for line in lines]) + ftr def main(args = None): if args is None: diff --git a/src/scripts/dev_tools/gen_pqc_dsa_kats.py b/src/scripts/dev_tools/gen_pqc_dsa_kats.py index d9d24c52e28..498e8789106 100644 --- a/src/scripts/dev_tools/gen_pqc_dsa_kats.py +++ b/src/scripts/dev_tools/gen_pqc_dsa_kats.py @@ -112,7 +112,7 @@ def main(args = None): hash_fn = sha3_256 def mldsa_sign_internal(m, sk, rnd): # For some reason the interfaces vary between FIPS 204 and FIPS 205... - if rnd == None: + if rnd is None: rnd = bytes([0]*32) return alg.sign_internal(sk, m, rnd) diff --git a/src/scripts/run_limbo_tests.py b/src/scripts/run_limbo_tests.py index 276b5943a6e..bb29034e0bf 100755 --- a/src/scripts/run_limbo_tests.py +++ b/src/scripts/run_limbo_tests.py @@ -224,7 +224,7 @@ def main(args = None): validation_time = int(parser.parse(test['validation_time']).timestamp()) hostname = None - if test['expected_peer_name'] != None: + if test['expected_peer_name'] is not None: if test['expected_peer_name']['kind'] in ['DNS', 'IP']: hostname = test['expected_peer_name']['value'] else: diff --git a/src/scripts/run_tls_attacker.py b/src/scripts/run_tls_attacker.py index c2f226ed4ec..4d69749f7cc 100755 --- a/src/scripts/run_tls_attacker.py +++ b/src/scripts/run_tls_attacker.py @@ -54,11 +54,11 @@ def main(args=None): print("Unknown --type %s" % (options.test_type)) return 1 - if os.access(cli_exe, os.X_OK) != True: + if not os.access(cli_exe, os.X_OK): print("Unable to find CLI tool at %s" % (cli_exe)) return 1 - if os.access(src_dir, os.X_OK) != True: + if not os.access(src_dir, os.X_OK): print("Unable to find src dir at %s" % (src_dir)) return 1 @@ -72,7 +72,7 @@ def main(args=None): tls_attacker_testsuites = os.path.join(tls_attacker_dir, 'resources/testsuite') tls_fuzzer_workflows = os.path.join(tls_attacker_dir, 'resources/fuzzing/workflows') - if os.access(tls_attacker_jar, os.R_OK) != True: + if not os.access(tls_attacker_jar, os.R_OK): print("Unable to find TLS-Attacker jar at %s" % (tls_attacker_jar)) return 1 diff --git a/src/scripts/run_tls_fuzzer.py b/src/scripts/run_tls_fuzzer.py index d5221e09178..a5ca0535a52 100755 --- a/src/scripts/run_tls_fuzzer.py +++ b/src/scripts/run_tls_fuzzer.py @@ -86,7 +86,7 @@ def main(args = None): if script in results: continue - if proc.poll() != None: + if proc.poll() is not None: rv = proc.returncode results[script] = rv if rv == 0: diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py index d7770cf84c7..91f61c85be4 100755 --- a/src/scripts/test_cli.py +++ b/src/scripts/test_cli.py @@ -922,8 +922,8 @@ def cli_dl_group_info_tests(_tmp_dir): if len(lines) != 2: logging.error('Unexpected output from dl_group_info') - for l in lines: - if not dl_output.match(l): + for line in lines: + if not dl_output.match(line): logging.error('Unexpected output from dl_group_info') diff --git a/src/scripts/test_fuzzers.py b/src/scripts/test_fuzzers.py index c245d643950..b1f32bec487 100755 --- a/src/scripts/test_fuzzers.py +++ b/src/scripts/test_fuzzers.py @@ -120,7 +120,7 @@ def main(args=None): for fuzzer in sorted(list(fuzzers_with_corpus)): fuzzer_bin = os.path.join(fuzzer_dir, fuzzer) corpus_subdir = os.path.join(corpus_dir, fuzzer) - corpus_files = [os.path.join(corpus_subdir, l) for l in sorted(list(os.listdir(corpus_subdir)))] + corpus_files = [os.path.join(corpus_subdir, fsname) for fsname in sorted(list(os.listdir(corpus_subdir)))] # We have to do this hack because multiprocessing's Pool.map doesn't support # passing any initial arguments, just the single iterable @@ -170,7 +170,7 @@ def main(args=None): else: corpus_subdir = random_corpus_dir - corpus_files = [os.path.join(corpus_subdir, l) for l in sorted(list(os.listdir(corpus_subdir)))] + corpus_files = [os.path.join(corpus_subdir, fsname) for fsname in sorted(list(os.listdir(corpus_subdir)))] if fuzzer in slow_fuzzers: corpus_files = corpus_files[:random_corpus_size_for_slow_fuzzers] diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 52ebdd96e7d..02c8b184cfe 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -918,16 +918,16 @@ def test_mpi(self): def test_mpi_random(self): rng = botan.RandomNumberGenerator() - u = botan.MPI.random(rng, 512) - self.assertEqual(u.bit_count(), 512) + upper = botan.MPI.random(rng, 512) + self.assertEqual(upper.bit_count(), 512) - l = u >> 32 - self.assertEqual(l.bit_count(), 512-32) + lower = upper >> 32 + self.assertEqual(lower.bit_count(), 512-32) for _i in range(10): - x = botan.MPI.random_range(rng, l, u) - self.assertLess(x, u) - self.assertGreater(x, l) + x = botan.MPI.random_range(rng, lower, upper) + self.assertLess(x, upper) + self.assertGreater(x, lower) def test_fpe(self): diff --git a/src/scripts/tls_scanner/tls_scanner.py b/src/scripts/tls_scanner/tls_scanner.py index e7e89fda9a1..1004ffeec28 100755 --- a/src/scripts/tls_scanner/tls_scanner.py +++ b/src/scripts/tls_scanner/tls_scanner.py @@ -45,12 +45,12 @@ def scanner(args = None): for i in range(timeout): scanners[url].poll() - if scanners[url].returncode != None: + if scanners[url].returncode is not None: break #print("Waiting %d more seconds for %s" % (timeout-i, url)) time.sleep(1) - if scanners[url].returncode != None: + if scanners[url].returncode is not None: output = scanners[url].stdout.read() + scanners[url].stderr.read() report[url] = format_report(output.decode("utf-8")) From f1a976aa25c6e5e462e405666edb4c97f0e5ed39 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Wed, 3 Sep 2025 11:20:23 -0400 Subject: [PATCH 05/14] Silence a few warnings from ruff that are difficult to fix --- src/configs/sphinx/conf.py | 2 +- src/scripts/dev_tools/gen_os_features.py | 2 +- src/scripts/dev_tools/show_dependencies.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/configs/sphinx/conf.py b/src/configs/sphinx/conf.py index 91953753d5d..30455619f1d 100644 --- a/src/configs/sphinx/conf.py +++ b/src/configs/sphinx/conf.py @@ -101,7 +101,7 @@ def parse_version_file(version_path): try: # On Arch this is python-sphinx-furo - import furo + import furo # noqa: F401 html_theme = "furo" # Add a small edit button to each document to allow visitors to easily diff --git a/src/scripts/dev_tools/gen_os_features.py b/src/scripts/dev_tools/gen_os_features.py index 625f759c7ab..7a447b06429 100755 --- a/src/scripts/dev_tools/gen_os_features.py +++ b/src/scripts/dev_tools/gen_os_features.py @@ -22,7 +22,7 @@ # locale sys.path.append(botan_root) -from configure import OsInfo +from configure import OsInfo # noqa: E402 parser = argparse.ArgumentParser(description="") parser.add_argument('--verbose', dest='verbose', action='store_const', diff --git a/src/scripts/dev_tools/show_dependencies.py b/src/scripts/dev_tools/show_dependencies.py index a8f0164b40f..28d3ed60ec9 100755 --- a/src/scripts/dev_tools/show_dependencies.py +++ b/src/scripts/dev_tools/show_dependencies.py @@ -25,7 +25,7 @@ # locale sys.path.append(botan_root) -from configure import ModuleInfo +from configure import ModuleInfo # noqa: E402 parser = argparse.ArgumentParser(description= 'Show Botan module dependencies. ' From c30b29731fb5733b2432eda0c37ce378c8f2b23b Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Wed, 3 Sep 2025 16:06:56 -0400 Subject: [PATCH 06/14] Fix some typing errors in botan3.py --- src/python/botan3.py | 137 +++++++++++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 51 deletions(-) diff --git a/src/python/botan3.py b/src/python/botan3.py index 3ce29a4bff1..27602d4cd60 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -645,7 +645,7 @@ def _call_fn_viewing_str(fn) -> str: _view_str_fn.output = None return result -def _ctype_str(s: str) -> bytes: +def _ctype_str(s: str | None) -> bytes | None: if s is None: return None assert isinstance(s, str) @@ -724,7 +724,7 @@ def handle_(self): class TPM2Context(TPM2Object): """TPM 2.0 Context object""" - def __init__(self, tcti_name_maybe_with_conf: str = None, tcti_conf: str = None): + def __init__(self, tcti_name_maybe_with_conf: str | None = None, tcti_conf: str | None = None): """Construct a TPM2Context object with optional TCTI name and configuration.""" obj = c_void_p(0) @@ -1035,7 +1035,7 @@ def ideal_update_granularity(self) -> int: _DLL.botan_cipher_get_ideal_update_granularity(self.__obj, byref(length)) return length.value - def key_length(self) -> int: + def key_length(self) -> tuple[int, int]: kmin = c_size_t(0) kmax = c_size_t(0) _DLL.botan_cipher_query_keylen(self.__obj, byref(kmin), byref(kmax)) @@ -1079,7 +1079,7 @@ def set_assoc_data(self, ad: bytes): def start(self, nonce: bytes): _DLL.botan_cipher_start(self.__obj, nonce, len(nonce)) - def _update(self, txt: str | bytes, final: bool): + def _update(self, txt: str | bytes | None, final: bool): inp = txt if txt else '' bits = _ctype_bits(inp) @@ -1165,7 +1165,7 @@ def pbkdf_timed(algo: str, password: str, out_len: int, ms_to_run: int = 300, sa # def scrypt(out_len: int, password: str, salt: str | bytes, n: int = 1024, r: int = 8, p: int = 8) -> bytes: out_buf = create_string_buffer(out_len) - passbits = _ctype_str(password) + passbits = _ctype_bits(password) saltbits = _ctype_bits(salt) _DLL.botan_pwdhash(_ctype_str("Scrypt"), n, r, p, @@ -1186,7 +1186,7 @@ def scrypt(out_len: int, password: str, salt: str | bytes, n: int = 1024, r: int # returns an output of out_len bytes def argon2(variant: str, out_len: int, password: str, salt: str | bytes, m: int = 256, t: int = 1, p: int = 1) -> bytes: out_buf = create_string_buffer(out_len) - passbits = _ctype_str(password) + passbits = _ctype_bits(password) saltbits = _ctype_bits(salt) _DLL.botan_pwdhash(_ctype_str(variant), m, t, p, @@ -1432,6 +1432,7 @@ def create(cls, algo: str, params: str | int | tuple[int, int], rng_obj: RandomN else: algo = 'ECDH' elif algo in ['mce', 'mceliece']: + # TODO(Botan4) remove this case algo = 'McEliece' params = "%d,%d" % (params[0], params[1]) @@ -1578,7 +1579,7 @@ def export(self, pem: bool = False) -> str | bytes: else: return self.to_der() - def export_encrypted(self, passphrase: str, rng: RandomNumberGenerator, pem: bool = False, msec: int = 300, cipher: str = None, pbkdf: str = None): # pylint: disable=redefined-outer-name + def export_encrypted(self, passphrase: str, rng: RandomNumberGenerator, pem: bool = False, msec: int = 300, cipher: str | None = None, pbkdf: str | None = None): # pylint: disable=redefined-outer-name if pem: return _call_fn_viewing_str( lambda vc, vfn: _DLL.botan_privkey_view_encrypted_pem_timed( @@ -1600,7 +1601,7 @@ def object_identifier(self) -> OID: _DLL.botan_privkey_oid(byref(oid.handle_()), self.__obj) return oid - def stateful_operation(self) -> str: + def stateful_operation(self) -> bool: r = c_int(0) _DLL.botan_privkey_stateful_operation(self.__obj, byref(r)) if r.value == 0: @@ -1809,7 +1810,7 @@ def __init__(self, filename: str | None = None, buf: bytes | None = None): def __del__(self): _DLL.botan_x509_cert_destroy(self.__obj) - def time_starts(self) -> str: + def time_starts(self) -> datetime: starts = _call_fn_returning_str( 16, lambda b, bl: _DLL.botan_x509_cert_get_time_starts(self.__obj, b, bl)) if len(starts) == 13: @@ -1823,7 +1824,7 @@ def time_starts(self) -> str: return datetime.fromtimestamp(mktime(struct_time)) - def time_expires(self) -> str: + def time_expires(self) -> datetime: expires = _call_fn_returning_str( 16, lambda b, bl: _DLL.botan_x509_cert_get_time_expires(self.__obj, b, bl)) if len(expires) == 13: @@ -1917,7 +1918,7 @@ def verify(self, trusted: List[X509Cert] | None = None, trusted_path: str | None = None, required_strength: int = 0, - hostname: str = None, + hostname: str | None = None, reference_time: int = 0, crls: List[X509CRL] | None = None) -> int: @@ -1994,7 +1995,7 @@ def handle_(self): class MPI: - def __init__(self, initial_value: _MPIArg = None, radix: int = None): + def __init__(self, initial_value: _MPIArg = None, radix: int | None = None): self.__obj = c_void_p(0) _DLL.botan_mp_init(byref(self.__obj)) @@ -2083,23 +2084,41 @@ def cmp(self, other: MPI) -> int: def __hash__(self): return hash(self.to_bytes()) - def __eq__(self, other: MPI): - return self.cmp(other) == 0 + def __eq__(self, other: MPI | object) -> bool: + if isinstance(other, MPI): + return self.cmp(other) == 0 + else: + return False - def __ne__(self, other: MPI): - return self.cmp(other) != 0 + def __ne__(self, other: MPI | object) -> bool: + if isinstance(other, MPI): + return self.cmp(other) != 0 + else: + return False - def __lt__(self, other: MPI): - return self.cmp(other) < 0 + def __lt__(self, other: MPI | object) -> bool: + if isinstance(other, MPI): + return self.cmp(other) < 0 + else: + return False - def __le__(self, other: MPI): - return self.cmp(other) <= 0 + def __le__(self, other: MPI | object) -> bool: + if isinstance(other, MPI): + return self.cmp(other) <= 0 + else: + return False - def __gt__(self, other: MPI): - return self.cmp(other) > 0 + def __gt__(self, other: MPI | object) -> bool: + if isinstance(other, MPI): + return self.cmp(other) > 0 + else: + return False - def __ge__(self, other: MPI): - return self.cmp(other) >= 0 + def __ge__(self, other: MPI | object) -> bool: + if isinstance(other, MPI): + return self.cmp(other) >= 0 + else: + return False def __add__(self, other: MPI): r = MPI() @@ -2141,25 +2160,21 @@ def __mod__(self, other: MPI): return q def __lshift__(self, shift: int): - shift = c_size_t(shift) r = MPI() - _DLL.botan_mp_lshift(r.handle_(), self.__obj, shift) + _DLL.botan_mp_lshift(r.handle_(), self.__obj, c_size_t(shift)) return r def __ilshift__(self, shift: int): - shift = c_size_t(shift) - _DLL.botan_mp_lshift(self.__obj, self.__obj, shift) + _DLL.botan_mp_lshift(self.__obj, self.__obj, c_size_t(shift)) return self def __rshift__(self, shift: int): - shift = c_size_t(shift) r = MPI() - _DLL.botan_mp_rshift(r.handle_(), self.__obj, shift) + _DLL.botan_mp_rshift(r.handle_(), self.__obj, c_size_t(shift)) return r def __irshift__(self, shift: int): - shift = c_size_t(shift) - _DLL.botan_mp_rshift(self.__obj, self.__obj, shift) + _DLL.botan_mp_rshift(self.__obj, self.__obj, c_size_t(shift)) return self def mod_mul(self, other: MPI, modulus: MPI) -> MPI: @@ -2237,23 +2252,41 @@ def cmp(self, other: OID) -> int: _DLL.botan_oid_cmp(byref(r), self.__obj, other.handle_()) return r.value - def __eq__(self, other: OID): - return self.cmp(other) == 0 + def __eq__(self, other: OID | object) -> bool: + if isinstance(other, OID): + return self.cmp(other) == 0 + else: + return False - def __ne__(self, other: OID): - return self.cmp(other) != 0 + def __ne__(self, other: OID | object) -> bool: + if isinstance(other, OID): + return self.cmp(other) != 0 + else: + return False - def __lt__(self, other: OID): - return self.cmp(other) < 0 + def __lt__(self, other: OID | object) -> bool: + if isinstance(other, OID): + return self.cmp(other) < 0 + else: + return False - def __le__(self, other: OID): - return self.cmp(other) <= 0 + def __le__(self, other: OID | object) -> bool: + if isinstance(other, OID): + return self.cmp(other) <= 0 + else: + return False - def __gt__(self, other: OID): - return self.cmp(other) > 0 + def __gt__(self, other: OID | object) -> bool: + if isinstance(other, OID): + return self.cmp(other) > 0 + else: + return False - def __ge__(self, other: OID): - return self.cmp(other) >= 0 + def __ge__(self, other: OID | object) -> bool: + if isinstance(other, OID): + return self.cmp(other) >= 0 + else: + return False class ECGroup: @@ -2326,7 +2359,7 @@ def from_name(cls, name: str) -> ECGroup: def to_der(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_ec_group_view_der(self.__obj, vc, vfn)) - def to_pem(self) -> bytes: + def to_pem(self) -> str: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_ec_group_view_pem(self.__obj, vc, vfn)) def get_curve_oid(self) -> OID: @@ -2364,11 +2397,13 @@ def get_order(self) -> MPI: _DLL.botan_ec_group_get_order(byref(order.handle_()), self.__obj) return order - def __eq__(self, other: ECGroup): - rc = _DLL.botan_ec_group_equal(self.__obj, other.handle_()) - return rc == 1 + def __eq__(self, other: ECGroup | object) -> bool: + if isinstance(other, ECGroup): + return _DLL.botan_ec_group_equal(self.__obj, other.handle_()) == 1 + else: + return False - def __ne__(self, other: ECGroup): + def __ne__(self, other: ECGroup | object) -> bool: return not self == other @@ -2447,7 +2482,7 @@ def nist_key_wrap(kek: bytes, key: bytes, cipher: str | None = None) -> bytes: key, len(key), kek, len(kek), output, byref(out_len)) - return output[0:int(out_len.value)] + return bytes(output[0:int(out_len.value)]) def nist_key_unwrap(kek: bytes, wrapped: bytes, cipher: str | None = None) -> bytes: cipher_algo = "AES-%d" % (8*len(kek)) if cipher is None else cipher @@ -2458,7 +2493,7 @@ def nist_key_unwrap(kek: bytes, wrapped: bytes, cipher: str | None = None) -> by wrapped, len(wrapped), kek, len(kek), output, byref(out_len)) - return output[0:int(out_len.value)] + return bytes(output[0:int(out_len.value)]) class Srp6ServerSession: __obj = c_void_p(0) From 2905258cd2e3e491d0d7cfcef7dd116b561f3c5f Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Fri, 9 May 2025 23:34:45 +0200 Subject: [PATCH 07/14] basic layout --- src/lib/ffi/ffi.h | 101 +++++++++ src/lib/ffi/ffi_cert.cpp | 408 +++++++++++++++++++++++++++++++++++-- src/lib/ffi/ffi_cert.h | 36 ++++ src/lib/ffi/info.txt | 1 + src/python/botan3.py | 235 ++++++++++++++++++++- src/scripts/test_python.py | 31 +++ src/tests/test_ffi.cpp | 66 ++++++ 7 files changed, 856 insertions(+), 22 deletions(-) create mode 100644 src/lib/ffi/ffi_cert.h diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index ff3792961bb..bbd6443da2c 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -2140,6 +2140,12 @@ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_serial_number(botan_x509_cert_t c BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_authority_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_subject_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); +BOTAN_FFI_EXPORT(3, 9) int botan_x509_get_basic_constraints(botan_x509_cert_t cert, int* is_ca, size_t* limit); +BOTAN_FFI_EXPORT(3, 9) int botan_x509_get_key_constraints(botan_x509_cert_t cert, uint32_t* usage); +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); +BOTAN_FFI_EXPORT(3, 9) int botan_x509_is_self_signed(botan_x509_cert_t cert, int* out); + BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_public_key_bits(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); BOTAN_FFI_EXPORT(3, 0) @@ -2162,6 +2168,8 @@ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_to_string(botan_x509_cert_t cert, cha BOTAN_FFI_EXPORT(3, 0) int botan_x509_cert_view_as_string(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_view_pem(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); + /* Must match values of Key_Constraints in key_constraints.h */ enum botan_x509_cert_key_constraints /* NOLINT(*-enum-size) */ { NO_CONSTRAINTS = 0, @@ -2210,6 +2218,99 @@ int botan_x509_cert_verify(int* validation_result, */ BOTAN_FFI_EXPORT(2, 8) const char* botan_x509_cert_validation_status(int code); +typedef struct botan_x509_cert_opts_struct* botan_x509_cert_opts_t; +typedef struct botan_x509_ca_struct* botan_x509_ca_t; +typedef struct botan_x509_pkcs10_req_struct* botan_x509_pkcs10_req_t; +typedef struct botan_x509_time_struct* botan_x509_time_t; + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_destroy(botan_x509_cert_opts_t opts); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_ca_destroy(botan_x509_ca_t ca); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_time_destroy(botan_x509_time_t time); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* opts, uint32_t* expire_time); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_common_name(botan_x509_cert_opts_t opts, const char* name); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_country(botan_x509_cert_opts_t opts, const char* country); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_organization(botan_x509_cert_opts_t opts, const char* organization); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_org_unit(botan_x509_cert_opts_t opts, const char* org_unit); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_locality(botan_x509_cert_opts_t opts, const char* locality); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_state(botan_x509_cert_opts_t opts, const char* state); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_serial_number(botan_x509_cert_opts_t opts, const char* serial_number); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_email(botan_x509_cert_opts_t opts, const char* email); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_uri(botan_x509_cert_opts_t opts, const char* uri); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_ip(botan_x509_cert_opts_t opts, const char* ip); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_dns(botan_x509_cert_opts_t opts, const char* dns); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_xmpp(botan_x509_cert_opts_t opts, const char* xmpp); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_challenge(botan_x509_cert_opts_t opts, const char* challenge); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_opts_more_org_units(botan_x509_cert_opts_t opts, const char** more_org_units, size_t cnt); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_opts_more_dns(botan_x509_cert_opts_t opts, const char** more_dns, size_t cnt); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_ca_key(botan_x509_cert_opts_t opts, size_t limit); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_set_padding_scheme(botan_x509_cert_opts_t opts, const char* scheme); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_not_before(botan_x509_cert_opts_t opts, botan_x509_time_t not_before); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_not_after(botan_x509_cert_opts_t opts, botan_x509_time_t not_after); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_add_constraints(botan_x509_cert_opts_t opts, uint32_t usage); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_add_ex_constraint(botan_x509_cert_opts_t opts, botan_asn1_oid_t oid); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, + botan_privkey_t key, + botan_x509_cert_opts_t opts, + const char* hash_fn, + const char* sig_padding, + botan_rng_t rng); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_create_ca(botan_x509_ca_t* ca_obj, + botan_x509_cert_t ca_cert, + botan_privkey_t key, + const char* hash_fn, + const char* sig_padding, + botan_rng_t rng); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, + botan_x509_cert_opts_t opts, + botan_privkey_t key, + const char* hash_fn, + botan_rng_t rng); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_sign_req(botan_x509_cert_t* cert_obj, + botan_x509_ca_t ca, + botan_x509_pkcs10_req_t req, + botan_rng_t rng, + botan_x509_time_t not_before, + botan_x509_time_t not_after); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_create_time(botan_x509_time_t* time_obj, uint64_t time_since_epoch); + /* * X.509 CRL **************************/ diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index 55756ca9ba6..40ec37e033c 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -1,32 +1,23 @@ /* * (C) 2015,2017,2018 Jack Lloyd +* (C) 2025 Dominik Schricker * * Botan is released under the Simplified BSD License (see license.txt) */ #include +#include +#include #include +#include #include #include -#if defined(BOTAN_HAS_X509_CERTIFICATES) - #include - #include - #include - #include -#endif - extern "C" { using namespace Botan_FFI; -#if defined(BOTAN_HAS_X509_CERTIFICATES) - -BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_struct, Botan::X509_Certificate, 0x8F628937); - -#endif - int botan_x509_cert_load_file(botan_x509_cert_t* cert_obj, const char* cert_path) { if(cert_obj == nullptr || cert_path == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; @@ -134,7 +125,12 @@ int botan_x509_cert_get_subject_dn( } int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) return copy_view_str(reinterpret_cast(out), out_len, botan_x509_cert_view_as_string, cert); +#else + BOTAN_UNUSED(cert, out, out_len) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif } int botan_x509_cert_view_as_string(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) { @@ -146,6 +142,15 @@ int botan_x509_cert_view_as_string(botan_x509_cert_t cert, botan_view_ctx ctx, b #endif } +int botan_x509_cert_view_pem(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) { return invoke_view_callback(view, ctx, c.PEM_encode()); }); +#else + BOTAN_UNUSED(cert, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_allowed_usage(botan_x509_cert_t cert, unsigned int key_usage) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { @@ -161,6 +166,73 @@ int botan_x509_cert_allowed_usage(botan_x509_cert_t cert, unsigned int key_usage #endif } +int botan_x509_get_basic_constraints(botan_x509_cert_t cert, int* is_ca, size_t* limit) { + if(!is_ca || !limit) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { + if(c.is_CA_cert()) { + *is_ca = 1; + *limit = c.path_limit(); + } else { + *is_ca = 0; + *limit = 0; + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_get_key_constraints(botan_x509_cert_t cert, uint32_t* usage) { + if(!usage) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { + *usage = c.constraints().value(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, + [=](const auto& c) -> int { return invoke_view_callback(view, ctx, c.ocsp_responder()); }); +#else + BOTAN_UNUSED(cert, ctx, view) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_is_self_signed(botan_x509_cert_t cert, int* out) { + if(!out) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) { + if(c.is_self_signed()) { + *out = 1; + } else { + *out = 0; + } + }); +#else + BOTAN_UNUSED(cert, out) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_destroy(botan_x509_cert_t cert) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_CHECKED_DELETE(cert); @@ -355,12 +427,6 @@ const char* botan_x509_cert_validation_status(int code) { #endif } -#if defined(BOTAN_HAS_X509_CERTIFICATES) - -BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910); - -#endif - int botan_x509_crl_load_file(botan_x509_crl_t* crl_obj, const char* crl_path) { if(crl_obj == nullptr || crl_path == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; @@ -491,4 +557,308 @@ int botan_x509_cert_verify_with_crl(int* result_code, return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } + +int botan_x509_cert_opts_destroy(botan_x509_cert_opts_t opts) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(opts); +#else + BOTAN_UNUSED(opts); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ca_destroy(botan_x509_ca_t ca) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(ca); +#else + BOTAN_UNUSED(ca); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(req); +#else + BOTAN_UNUSED(req); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_time_destroy(botan_x509_time_t time) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(time); +#else + BOTAN_UNUSED(time); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* opts, uint32_t* expire_time) { + if(!opts_obj || !opts) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + std::unique_ptr co; + if(expire_time) { + co = std::make_unique(opts, *expire_time); + } else { + co = std::make_unique(opts); + } + + *opts_obj = new botan_x509_cert_opts_struct(std::move(co)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(expire_time); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) + #define X509_GET_CERT_OPTS_STRING(FIELD_NAME) \ + int botan_x509_cert_opts_##FIELD_NAME(botan_x509_cert_opts_t opts, const char* value) { \ + if(!value) { \ + return BOTAN_FFI_ERROR_NULL_POINTER; \ + } \ + return ffi_guard_thunk(__func__, [=]() -> int { \ + safe_get(opts).FIELD_NAME = value; \ + return BOTAN_FFI_SUCCESS; \ + }); \ + } +#else + // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) + #define X509_GET_CERT_OPTS_STRING(FIELD_NAME) \ + int botan_x509_cert_opts_##FIELD_NAME(botan_x509_cert_opts_t opts, const char* value) { \ + if(!value) { \ + return BOTAN_FFI_ERROR_NULL_POINTER; \ + } \ + BOTAN_UNUSED(opts); \ + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; \ + } +#endif + +X509_GET_CERT_OPTS_STRING(common_name) +X509_GET_CERT_OPTS_STRING(country) +X509_GET_CERT_OPTS_STRING(organization) +X509_GET_CERT_OPTS_STRING(org_unit) +X509_GET_CERT_OPTS_STRING(locality) +X509_GET_CERT_OPTS_STRING(state) +X509_GET_CERT_OPTS_STRING(serial_number) +X509_GET_CERT_OPTS_STRING(email) +X509_GET_CERT_OPTS_STRING(uri) +X509_GET_CERT_OPTS_STRING(ip) +X509_GET_CERT_OPTS_STRING(dns) +X509_GET_CERT_OPTS_STRING(xmpp) +X509_GET_CERT_OPTS_STRING(challenge) + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) + #define X509_GET_CERT_OPTS_VEC(FIELD_NAME) \ + int botan_x509_cert_opts_##FIELD_NAME(botan_x509_cert_opts_t opts, const char** value, size_t cnt) { \ + if(!value) { \ + return BOTAN_FFI_ERROR_NULL_POINTER; \ + } \ + return ffi_guard_thunk(__func__, [=]() -> int { \ + std::vector val; \ + for(size_t i = 0; i < cnt; i++) { \ + if(!value[i]) { \ + return BOTAN_FFI_ERROR_NULL_POINTER; \ + } \ + val.push_back(value[i]); \ + } \ + safe_get(opts).FIELD_NAME = val; \ + return BOTAN_FFI_SUCCESS; \ + }); \ + } +#else + // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) + #define X509_GET_CERT_OPTS_VEC(FIELD_NAME) \ + int botan_x509_cert_opts_##FIELD_NAME(botan_x509_cert_opts_t opts, const char** value, size_t cnt) { \ + if(!value) { \ + return BOTAN_FFI_ERROR_NULL_POINTER; \ + } \ + BOTAN_UNUSED(opts, cnt); \ + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; \ + } +#endif + +X509_GET_CERT_OPTS_VEC(more_org_units) +X509_GET_CERT_OPTS_VEC(more_dns) + +int botan_x509_cert_opts_ca_key(botan_x509_cert_opts_t opts, size_t limit) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(opts, [=](auto& o) { o.CA_key(limit); }); +#else + BOTAN_UNUSED(opts, limit); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_opts_set_padding_scheme(botan_x509_cert_opts_t opts, const char* scheme) { + if(!scheme) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(opts).set_padding_scheme(scheme); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(opts); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_opts_not_before(botan_x509_cert_opts_t opts, botan_x509_time_t not_before) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(opts).start = safe_get(not_before); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(opts, not_before); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_opts_not_after(botan_x509_cert_opts_t opts, botan_x509_time_t not_after) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(opts).end = safe_get(not_after); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(opts.not_after); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_opts_add_constraints(botan_x509_cert_opts_t opts, uint32_t usage) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(opts, [=](auto& o) { o.add_constraints(Botan::Key_Constraints(usage)); }); +#else + BOTAN_UNUSED(opts, usage); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_opts_add_ex_constraint(botan_x509_cert_opts_t opts, botan_asn1_oid_t oid) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(opts).add_ex_constraint(safe_get(oid)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(opts, oid); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, + botan_privkey_t key, + botan_x509_cert_opts_t opts, + const char* hash_fn, + const char* sig_padding, + botan_rng_t rng) { + if(!cert_obj || !hash_fn || !sig_padding) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto ca_cert = std::make_unique( + Botan::X509::create_self_signed_cert(safe_get(opts), safe_get(key), hash_fn, safe_get(rng))); + *cert_obj = new botan_x509_cert_struct(std::move(ca_cert)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(key, opts, rng); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_create_ca(botan_x509_ca_t* ca_obj, + botan_x509_cert_t ca_cert, + botan_privkey_t key, + const char* hash_fn, + const char* sig_padding, + botan_rng_t rng) { + if(!ca_obj || !hash_fn || !sig_padding) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto ca = std::make_unique(safe_get(ca_cert), safe_get(key), hash_fn, sig_padding, safe_get(rng)); + *ca_obj = new botan_x509_ca_struct(std::move(ca)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ca_cert, key, rng); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, + botan_x509_cert_opts_t opts, + botan_privkey_t key, + const char* hash_fn, + botan_rng_t rng) { + if(!req_obj || !hash_fn) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto req = std::make_unique( + Botan::X509::create_cert_req(safe_get(opts), safe_get(key), hash_fn, safe_get(rng))); + *req_obj = new botan_x509_pkcs10_req_struct(std::move(req)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(opts, key, rng); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_sign_req(botan_x509_cert_t* cert_obj, + botan_x509_ca_t ca, + botan_x509_pkcs10_req_t req, + botan_rng_t rng, + botan_x509_time_t not_before, + botan_x509_time_t not_after) { + if(!cert_obj) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto cert = std::make_unique(safe_get(ca).sign_request( + safe_get(req), safe_get(rng), safe_get(not_before), safe_get(not_after))); + *cert_obj = new botan_x509_cert_struct(std::move(cert)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ca, req, rng, not_before, not_after); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_create_time(botan_x509_time_t* time_obj, uint64_t time_since_epoch) { + if(!time_obj) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto tp = std::chrono::system_clock::time_point(std::chrono::seconds(time_since_epoch)); + auto time = std::make_unique(tp); + *time_obj = new botan_x509_time_struct(std::move(time)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(time_since_epoch); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} } diff --git a/src/lib/ffi/ffi_cert.h b/src/lib/ffi/ffi_cert.h new file mode 100644 index 00000000000..0d15c3fa089 --- /dev/null +++ b/src/lib/ffi/ffi_cert.h @@ -0,0 +1,36 @@ +/* +* (C) 2025 Jack Lloyd +* (C) 2025 Dominik Schricker +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_FFI_CERT_H_ +#define BOTAN_FFI_CERT_H_ + +#include + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + #include + #include + #include + #include + #include + #include + #include +#endif + +extern "C" { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + +BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_struct, Botan::X509_Certificate, 0x8F628937); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_opts_struct, Botan::X509_Cert_Options, 0x92597C7D); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_ca_struct, Botan::X509_CA, 0x8BE2A8F1); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_pkcs10_req_struct, Botan::PKCS10_Request, 0x87F0690A); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_time_struct, Botan::X509_Time, 0x739396FA); + +#endif +} + +#endif diff --git a/src/lib/ffi/info.txt b/src/lib/ffi/info.txt index a10f8727187..85ad393b38a 100644 --- a/src/lib/ffi/info.txt +++ b/src/lib/ffi/info.txt @@ -8,6 +8,7 @@ brief -> "C API for Botan's functionality" +ffi_cert.h ffi_ec.h ffi_mp.h ffi_oid.h diff --git a/src/python/botan3.py b/src/python/botan3.py index 27602d4cd60..f44b980670d 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -490,17 +490,59 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_cert_get_serial_number, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_get_authority_key_id, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_get_subject_key_id, [c_void_p, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_get_basic_constraints, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_get_key_constraints, [c_void_p, POINTER(c_uint32)]) + ffi_api(dll.botan_x509_get_ocsp_responder, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) + ffi_api(dll.botan_x509_is_self_signed, [c_void_p, POINTER(c_int)]) + ffi_api(dll.botan_x509_cert_get_public_key_bits, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_view_public_key_bits, [c_void_p, c_void_p, VIEW_BIN_CALLBACK]) ffi_api(dll.botan_x509_cert_get_public_key, [c_void_p, c_void_p]) ffi_api(dll.botan_x509_cert_get_issuer_dn, [c_void_p, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_get_subject_dn, [c_void_p, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_cert_to_string, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_view_as_string, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) + ffi_api(dll.botan_x509_cert_view_pem, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) ffi_api(dll.botan_x509_cert_allowed_usage, [c_void_p, c_uint]) - ffi_api(dll.botan_x509_cert_hostname_match, [c_void_p, c_char_p], [-1]) + ffi_api(dll.botan_x509_cert_hostname_match, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_cert_verify, [POINTER(c_int), c_void_p, c_void_p, c_size_t, c_void_p, c_size_t, c_char_p, c_size_t, c_char_p, c_uint64]) + ffi_api(dll.botan_x509_cert_opts_destroy, [c_void_p]) + ffi_api(dll.botan_x509_ca_destroy, [c_void_p]) + ffi_api(dll.botan_x509_pkcs10_req_destroy, [c_void_p]) + ffi_api(dll.botan_x509_time_destroy, [c_void_p]) + ffi_api(dll.botan_x509_create_cert_opts, [c_void_p, c_char_p, POINTER(c_uint32)]) + ffi_api(dll.botan_x509_cert_opts_common_name, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_country, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_organization, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_org_unit, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_locality, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_state, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_serial_number, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_email, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_uri, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_ip, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_dns, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_xmpp, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_challenge, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_more_org_units, [c_void_p, POINTER(c_char_p), c_size_t]) + ffi_api(dll.botan_x509_cert_opts_more_dns, [c_void_p, POINTER(c_char_p), c_size_t]) + ffi_api(dll.botan_x509_cert_opts_ca_key, [c_void_p, c_size_t]) + ffi_api(dll.botan_x509_cert_opts_set_padding_scheme, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_not_before, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_opts_not_after, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_opts_add_constraints, [c_void_p, c_uint32]) + ffi_api(dll.botan_x509_cert_opts_add_ex_constraint, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_create_self_signed_cert, + [c_void_p, c_void_p, c_void_p, c_char_p, c_char_p, c_void_p]) + ffi_api(dll.botan_x509_create_ca, + [c_void_p, c_void_p, c_void_p, c_char_p, c_char_p, c_void_p]) + ffi_api(dll.botan_x509_create_pkcs10_req, + [c_void_p, c_void_p, c_void_p, c_char_p, c_void_p]) + ffi_api(dll.botan_x509_sign_req, + [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p]) + ffi_api(dll.botan_x509_create_time, [c_void_p, c_uint64]) dll.botan_x509_cert_validation_status.argtypes = [c_int] dll.botan_x509_cert_validation_status.restype = c_char_p @@ -1802,14 +1844,144 @@ def _load_buf_or_file(filename, buf, file_fn, buf_fn): # # X.509 certificates # + +class X509Time: + def __init__(self, time_since_epoch): + self.__obj = c_void_p(0) + _DLL.botan_x509_create_time(byref(self.__obj), c_uint64(time_since_epoch)) + + def __del__(self): + _DLL.botan_x509_time_destroy(self.__obj) + + def handle_(self): + return self.__obj + + +class PKCS10Req: + def __init__(self): + self.__obj = c_void_p(0) + + def __del__(self): + _DLL.botan_x509_pkcs10_req_destroy(self.__obj) + + def handle_(self): + return self.__obj + + +class X509Opts: + def __init__(self, opts, expire_time=None): + self.__obj = c_void_p(0) + _DLL.botan_x509_create_cert_opts(byref(self.__obj), _ctype_str(opts), c_uint32(expire_time) if expire_time else None) + + def __del__(self): + _DLL.botan_x509_cert_opts_destroy(self.__obj) + + def handle_(self): + return self.__obj + + def set_common_name(self, name): + _DLL.botan_x509_cert_opts_common_name(self.__obj, _ctype_str(name)) + + def set_country(self, country): + _DLL.botan_x509_cert_opts_country(self.__obj, _ctype_str(country)) + + def set_organization(self, organization): + _DLL.botan_x509_cert_opts_organization(self.__obj, _ctype_str(organization)) + + def set_org_unit(self, org_unit): + _DLL.botan_x509_cert_opts_org_unit(self.__obj, _ctype_str(org_unit)) + + def set_locality(self, locality): + _DLL.botan_x509_cert_opts_locality(self.__obj, _ctype_str(locality)) + + def set_state(self, state): + _DLL.botan_x509_cert_opts_state(self.__obj, _ctype_str(state)) + + def set_serial_number(self, serial_number): + _DLL.botan_x509_cert_opts_serial_number(self.__obj, _ctype_str(serial_number)) + + def set_email(self, email): + _DLL.botan_x509_cert_opts_email(self.__obj, _ctype_str(email)) + + def set_uri(self, uri): + _DLL.botan_x509_cert_opts_uri(self.__obj, _ctype_str(uri)) + + def set_ip(self, ip): + _DLL.botan_x509_cert_opts_ip(self.__obj, _ctype_str(ip)) + + def set_dns(self, dns): + _DLL.botan_x509_cert_opts_dns(self.__obj, _ctype_str(dns)) + + def set_xmpp(self, xmpp): + _DLL.botan_x509_cert_opts_xmpp(self.__obj, _ctype_str(xmpp)) + + def set_challenge(self, challenge): + _DLL.botan_x509_cert_opts_challenge(self.__obj, _ctype_str(challenge)) + + def set_more_org_units(self, more_org_units): + cnt = len(more_org_units) + _DLL.botan_x509_cert_opts_more_org_units(self.__obj, (c_char_p * cnt)(*(_ctype_str(x) for x in more_org_units)), c_size_t(cnt)) + + def set_more_dns(self, more_dns): + cnt = len(more_dns) + _DLL.botan_x509_cert_opts_more_dns(self.__obj, (c_char_p * cnt)(*(_ctype_str(x) for x in more_dns)), c_size_t(cnt)) + + def set_ca_key(self, limit): + _DLL.botan_x509_cert_opts_ca_key(self.__obj, c_size_t(limit)) + + def set_padding_scheme(self, scheme): + _DLL.botan_x509_cert_opts_set_padding_scheme(self.__obj, _ctype_str(scheme)) + + def set_not_before(self, not_before): + _DLL.botan_x509_cert_opts_not_before(self.__obj, not_before.handle_()) + + def set_not_after(self, not_after): + _DLL.botan_x509_cert_opts_not_after(self.__obj, not_after.handle_()) + + def set_constraints(self, usage_list): + # TODO this sucks, where enum + usage_values = {"NO_CONSTRAINTS": 0, + "DIGITAL_SIGNATURE": 32768, + "NON_REPUDIATION": 16384, + "KEY_ENCIPHERMENT": 8192, + "DATA_ENCIPHERMENT": 4096, + "KEY_AGREEMENT": 2048, + "KEY_CERT_SIGN": 1024, + "CRL_SIGN": 512, + "ENCIPHER_ONLY": 256, + "DECIPHER_ONLY": 128} + usage = 0 + for u in usage_list: + if u not in usage_values: + pass + usage += usage_values[u] + _DLL.botan_x509_cert_opts_add_constraints(self.__obj, c_uint32(usage)) + + def add_ex_constraints(self, oid): + _DLL.botan_x509_cert_opts_add_ex_constraint(self.__obj, oid.handle_()) + + def create_req(self, key, hash_fn, rng): + req = PKCS10Req() + _DLL.botan_x509_create_pkcs10_req(byref(req.handle_()), self.__obj, key.handle_(), _ctype_str(hash_fn), rng.handle_()) + return req + + class X509Cert: # pylint: disable=invalid-name def __init__(self, filename: str | None = None, buf: bytes | None = None): - self.__obj = c_void_p(0) - self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_cert_load_file, _DLL.botan_x509_cert_load) + if not filename and not buf: + self.__obj = c_void_p(0) + else: + self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_cert_load_file, _DLL.botan_x509_cert_load) def __del__(self): _DLL.botan_x509_cert_destroy(self.__obj) + @classmethod + def create_self_signed(cls, key, opts, hash_fn, sig_padding, rng): + cert = X509Cert() + _DLL.botan_x509_create_self_signed_cert(byref(cert.handle_()), key.handle_(), opts.handle_(), _ctype_str(hash_fn), _ctype_str(sig_padding), rng.handle_()) + return cert + def time_starts(self) -> datetime: starts = _call_fn_returning_str( 16, lambda b, bl: _DLL.botan_x509_cert_get_time_starts(self.__obj, b, bl)) @@ -1842,6 +2014,10 @@ def to_string(self) -> str: return _call_fn_viewing_str( lambda vc, vfn: _DLL.botan_x509_cert_view_as_string(self.__obj, vc, vfn)) + def to_pem(self): + return _call_fn_viewing_str( + lambda vc, vfn: _DLL.botan_x509_cert_view_pem(self.__obj, vc, vfn)) + def fingerprint(self, hash_algo: str = 'SHA-256') -> str: n = HashFunction(hash_algo).output_length() * 3 return _call_fn_returning_str( @@ -1910,6 +2086,45 @@ def allowed_usage(self, usage_list: List[str]) -> bool: rc = _DLL.botan_x509_cert_allowed_usage(self.__obj, c_uint(usage)) return rc == 0 + def key_constraints(self): + usage_values = {"NO_CONSTRAINTS": 0, + "DIGITAL_SIGNATURE": 32768, + "NON_REPUDIATION": 16384, + "KEY_ENCIPHERMENT": 8192, + "DATA_ENCIPHERMENT": 4096, + "KEY_AGREEMENT": 2048, + "KEY_CERT_SIGN": 1024, + "CRL_SIGN": 512, + "ENCIPHER_ONLY": 256, + "DECIPHER_ONLY": 128} + usage = c_uint32(0) + _DLL.botan_x509_get_key_constraints(self.__obj, byref(usage)) + if usage.value == 0: + return ["NO_CONSTRAINTS"] + else: + return [name for name, bit in usage_values.items() if bit != 0 and usage.value & bit] + + def is_ca(self): + is_ca = c_int(0) + limit = c_size_t(0) + _DLL.botan_x509_get_basic_constraints(self.__obj, byref(is_ca), byref(limit)) + if is_ca.value == 0: + return (False, 0) + else: + return (True, limit.value) + + def ocsp_responder(self): + return _call_fn_viewing_str( + lambda vc, vfn: _DLL.botan_x509_get_ocsp_responder(self.__obj, vc, vfn)) + + def is_self_signed(self): + self_signed = c_int(0) + _DLL.botan_x509_is_self_signed(self.__obj, byref(self_signed)) + if self_signed.value == 0: + return False + else: + return True + def handle_(self): return self.__obj @@ -1978,6 +2193,20 @@ def is_revoked(self, crl: X509CRL) -> bool: return rc == 0 +class X509Ca: + def __init__(self, cert, key, rng, hash_fn, sig_padding=""): + self.__obj = c_void_p(0) + _DLL.botan_x509_create_ca(byref(self.__obj), cert.handle_(), key.handle_(), _ctype_str(hash_fn), _ctype_str(sig_padding), rng.handle_()) + + def __del__(self): + _DLL.botan_x509_ca_destroy(self.__obj) + + def sign(self, req, rng, not_before, not_after): + cert = X509Cert() + _DLL.botan_x509_sign_req(byref(cert.handle_()), self.__obj, req.handle_(), rng.handle_(), not_before.handle_(), not_after.handle_()) + return cert + + # # X.509 Certificate revocation lists # diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 02c8b184cfe..5ba81193b3a 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -12,6 +12,7 @@ import platform import argparse import sys +import time from itertools import permutations # Starting with Python 3.8 DLL search locations are more restricted on Windows. @@ -820,6 +821,36 @@ def test_certs(self): self.assertFalse(int04_1.is_revoked(rootcrl)) self.assertTrue(end21.is_revoked(int21crl)) + def test_cert_creation(self): + hash_fn = "SHA-256" + padding_method = "EMSA3(SHA-256)" + + rng = botan.RandomNumberGenerator() + ca_key = botan.PrivateKey.create("RSA", "4096", rng) + ca_opts = botan.X509Opts("Test CA/US/Botan Project/Testing") + ca_opts.set_ca_key(1) + ca_opts.set_constraints(["DIGITAL_SIGNATURE"]) + ca_cert = botan.X509Cert.create_self_signed(ca_key, ca_opts, hash_fn, padding_method, rng) + constraints = ca_cert.key_constraints() + self.assertEqual(len(constraints), 3) + for item in ["DIGITAL_SIGNATURE", "KEY_CERT_SIGN", "CRL_SIGN"]: + self.assertTrue(item in constraints) + + self.assertTrue(ca_cert.is_self_signed()) + self.assertEqual(ca_cert.key_constraints(), ["DIGITAL_SIGNATURE", "KEY_CERT_SIGN", "CRL_SIGN"]) + ca = botan.X509Ca(ca_cert, ca_key, rng, hash_fn) + + cert_key = botan.PrivateKey.create("RSA", "4096", rng) + req_opts = botan.X509Opts("Test CA/US/Botan Project/Testing") + req_opts.set_uri("https://botan.randombit.net") + req_opts.set_more_dns(["botan.randombit.net", "randombit.net"]) + req = req_opts.create_req(cert_key, hash_fn, rng) + + cert = ca.sign(req, rng, botan.X509Time(int(time.time())), botan.X509Time(int(time.time()) + 60)) + self.assertFalse(cert.is_self_signed()) + self.assertEqual(cert.key_constraints(), ["NO_CONSTRAINTS"]) + + self.assertEqual(cert.verify(None, [ca_cert]), 0) def test_mpi(self): z = botan.MPI() diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index a19d8204e87..3c0a34c7d63 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -793,6 +793,71 @@ class FFI_Cert_Validation_Test final : public FFI_Test { } }; +class FFI_Cert_Creation_Test final : public FFI_Test { + public: + std::string name() const override { return "FFI Cert Creation"; } + + void ffi_test(Test::Result& result, botan_rng_t rng) override { + const std::string hash_fn{"SHA-256"}; + const std::string padding_method{"EMSA3(SHA-256)"}; + + botan_privkey_t ca_key; + botan_privkey_t cert_key; + + if(TEST_FFI_INIT(botan_privkey_create_rsa, (&ca_key, rng, 4096))) { + TEST_FFI_OK(botan_privkey_create_rsa, (&cert_key, rng, 4096)); + + uint64_t now = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) + .count(); + + botan_x509_time_t not_before; + botan_x509_time_t not_after; + + if(TEST_FFI_INIT(botan_x509_create_time, (¬_before, now))) { + TEST_FFI_OK(botan_x509_create_time, (¬_after, now + 60 * 60)); + + botan_x509_cert_opts_t ca_opts; + TEST_FFI_OK(botan_x509_create_cert_opts, (&ca_opts, "Test CA/US/Botan Project/Testing", nullptr)); + TEST_FFI_OK(botan_x509_cert_opts_ca_key, (ca_opts, 1)); + TEST_FFI_OK(botan_x509_cert_opts_uri, (ca_opts, "https://botan.randombit.net")); + const char* dns_names[] = {"botan.randombit.net", "randombit.net"}; + TEST_FFI_OK(botan_x509_cert_opts_more_dns, (ca_opts, dns_names, 2)); + + botan_x509_cert_t ca_cert; + TEST_FFI_OK(botan_x509_create_self_signed_cert, (&ca_cert, ca_key, ca_opts, hash_fn.c_str(), "", rng)); + + botan_x509_ca_t ca; + TEST_FFI_OK(botan_x509_create_ca, (&ca, ca_cert, ca_key, hash_fn.c_str(), padding_method.c_str(), rng)); + + botan_x509_cert_opts_t req_opts; + TEST_FFI_OK(botan_x509_create_cert_opts, (&req_opts, "Test CA/US/Botan Project/Testing", nullptr)); + + botan_x509_pkcs10_req_t req; + TEST_FFI_OK(botan_x509_create_pkcs10_req, (&req, req_opts, cert_key, hash_fn.c_str(), rng)); + + botan_x509_cert_t cert; + TEST_FFI_OK(botan_x509_sign_req, (&cert, ca, req, rng, not_before, not_after)); + + int rc; + TEST_FFI_RC(0, botan_x509_cert_verify, (&rc, cert, nullptr, 0, &ca_cert, 1, nullptr, 0, nullptr, 0)); + + TEST_FFI_OK(botan_x509_time_destroy, (not_before)); + TEST_FFI_OK(botan_x509_time_destroy, (not_after)); + TEST_FFI_OK(botan_x509_cert_opts_destroy, (ca_opts)); + TEST_FFI_OK(botan_x509_cert_opts_destroy, (req_opts)); + TEST_FFI_OK(botan_x509_ca_destroy, (ca)); + TEST_FFI_OK(botan_x509_pkcs10_req_destroy, (req)); + TEST_FFI_OK(botan_x509_cert_destroy, (ca_cert)); + TEST_FFI_OK(botan_x509_cert_destroy, (cert)); + } + + TEST_FFI_OK(botan_privkey_destroy, (ca_key)); + TEST_FFI_OK(botan_privkey_destroy, (cert_key)); + } + } +}; + class FFI_ECDSA_Certificate_Test final : public FFI_Test { public: std::string name() const override { return "FFI ECDSA cert"; } @@ -4606,6 +4671,7 @@ BOTAN_REGISTER_TEST("ffi", "ffi_rsa_cert", FFI_RSA_Cert_Test); BOTAN_REGISTER_TEST("ffi", "ffi_zfec", FFI_ZFEC_Test); BOTAN_REGISTER_TEST("ffi", "ffi_crl", FFI_CRL_Test); BOTAN_REGISTER_TEST("ffi", "ffi_cert_validation", FFI_Cert_Validation_Test); +BOTAN_REGISTER_TEST("ffi", "ffi_cert_creation", FFI_Cert_Creation_Test); BOTAN_REGISTER_TEST("ffi", "ffi_ecdsa_certificate", FFI_ECDSA_Certificate_Test); BOTAN_REGISTER_TEST("ffi", "ffi_pkcs_hashid", FFI_PKCS_Hashid_Test); BOTAN_REGISTER_TEST("ffi", "ffi_cbc_cipher", FFI_CBC_Cipher_Test); From 2ae6510240239795be0a5691aac2464c1aac0fb5 Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Mon, 23 Jun 2025 18:35:06 +0200 Subject: [PATCH 08/14] rpki stuff --- doc/api_ref/ffi.rst | 4 + src/lib/ffi/ffi.h | 83 ++++++ src/lib/ffi/ffi_cert.cpp | 56 +++- src/lib/ffi/ffi_x509_rpki.cpp | 543 ++++++++++++++++++++++++++++++++++ src/lib/ffi/ffi_x509_rpki.h | 26 ++ src/lib/ffi/info.txt | 1 + src/python/botan3.py | 219 ++++++++++++++ src/scripts/test_python.py | 99 ++++++- src/tests/test_ffi.cpp | 189 ++++++++++++ 9 files changed, 1202 insertions(+), 18 deletions(-) create mode 100644 src/lib/ffi/ffi_x509_rpki.cpp create mode 100644 src/lib/ffi/ffi_x509_rpki.h diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index cf512d8f923..4ade83fed0f 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -103,6 +103,10 @@ The following enum values are defined in the FFI header: While decrypting in an AEAD mode, the tag failed to verify. +.. cpp:enumerator:: BOTAN_FFI_ERROR_NO_VALUE = -3 + + The requested value was not available or does not exist. + .. cpp:enumerator:: BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE = -10 Functions which write a variable amount of space return this if the indicated diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index bbd6443da2c..f06cd6e2a71 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -2219,12 +2219,18 @@ int botan_x509_cert_verify(int* validation_result, BOTAN_FFI_EXPORT(2, 8) const char* botan_x509_cert_validation_status(int code); typedef struct botan_x509_cert_opts_struct* botan_x509_cert_opts_t; +typedef struct botan_x509_ext_as_blocks_struct* botan_x509_ext_as_blocks_t; +typedef struct botan_x509_ext_ip_addr_blocks_struct* botan_x509_ext_ip_addr_blocks_t; typedef struct botan_x509_ca_struct* botan_x509_ca_t; typedef struct botan_x509_pkcs10_req_struct* botan_x509_pkcs10_req_t; typedef struct botan_x509_time_struct* botan_x509_time_t; BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_destroy(botan_x509_cert_opts_t opts); +BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks); + BOTAN_FFI_EXPORT(3, 9) int botan_x509_ca_destroy(botan_x509_ca_t ca); BOTAN_FFI_EXPORT(3, 9) int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req); @@ -2278,6 +2284,83 @@ BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_add_constraints(botan_x509_cert_ BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_add_ex_constraint(botan_x509_cert_opts_t opts, botan_asn1_oid_t oid); +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_opts_add_ext_ip_addr_blocks(botan_x509_cert_opts_t opts, + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_opts_add_ext_as_blocks(botan_x509_cert_opts_t opts, botan_x509_ext_as_blocks_t as_blocks); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_create_ip_addr_blocks(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_create_ip_addr_blocks_from_cert(botan_x509_cert_t cert, + botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_ip_addr_blocks_add_ip_addr( + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, const uint8_t* min, const uint8_t* max, int ipv6, uint8_t* safi); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_ip_addr_blocks_restrict(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_ip_addr_blocks_inherit(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_ip_addr_blocks_get_counts(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + size_t* v4_count, + size_t* v6_count); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int ipv6, + size_t i, + int* has_safi, + uint8_t* safi, + int* present, + int* count); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int ipv6, + size_t i, + size_t entry, + uint8_t min_out[], + uint8_t max_out[], + size_t* out_len); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_create_as_blocks(botan_x509_ext_as_blocks_t* as_blocks); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_create_as_blocks_from_cert(botan_x509_cert_t cert, botan_x509_ext_as_blocks_t* as_blocks); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_as_blocks_add_asnum(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_restrict_asnum(botan_x509_ext_as_blocks_t as_blocks); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_inherit_asnum(botan_x509_ext_as_blocks_t as_blocks); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_as_blocks_add_rdi(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_restrict_rdi(botan_x509_ext_as_blocks_t as_blocks); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_inherit_rdi(botan_x509_ext_as_blocks_t as_blocks); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_as_blocks_get_asnum(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_as_blocks_get_asnum_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_as_blocks_get_rdi(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_ext_as_blocks_get_rdi_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max); + BOTAN_FFI_EXPORT(3, 9) int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, botan_privkey_t key, diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index 40ec37e033c..196e10c3385 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include extern "C" { @@ -167,7 +168,7 @@ int botan_x509_cert_allowed_usage(botan_x509_cert_t cert, unsigned int key_usage } int botan_x509_get_basic_constraints(botan_x509_cert_t cert, int* is_ca, size_t* limit) { - if(!is_ca || !limit) { + if(is_ca == nullptr || limit == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -189,7 +190,7 @@ int botan_x509_get_basic_constraints(botan_x509_cert_t cert, int* is_ca, size_t* } int botan_x509_get_key_constraints(botan_x509_cert_t cert, uint32_t* usage) { - if(!usage) { + if(usage == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -215,7 +216,7 @@ int botan_x509_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, bo } int botan_x509_is_self_signed(botan_x509_cert_t cert, int* out) { - if(!out) { + if(out == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -595,7 +596,7 @@ int botan_x509_time_destroy(botan_x509_time_t time) { } int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* opts, uint32_t* expire_time) { - if(!opts_obj || !opts) { + if(opts_obj == nullptr || opts == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -621,7 +622,7 @@ int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* op // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define X509_GET_CERT_OPTS_STRING(FIELD_NAME) \ int botan_x509_cert_opts_##FIELD_NAME(botan_x509_cert_opts_t opts, const char* value) { \ - if(!value) { \ + if(value == nullptr) { \ return BOTAN_FFI_ERROR_NULL_POINTER; \ } \ return ffi_guard_thunk(__func__, [=]() -> int { \ @@ -633,7 +634,7 @@ int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* op // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define X509_GET_CERT_OPTS_STRING(FIELD_NAME) \ int botan_x509_cert_opts_##FIELD_NAME(botan_x509_cert_opts_t opts, const char* value) { \ - if(!value) { \ + if(value == nullptr) { \ return BOTAN_FFI_ERROR_NULL_POINTER; \ } \ BOTAN_UNUSED(opts); \ @@ -659,13 +660,13 @@ X509_GET_CERT_OPTS_STRING(challenge) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define X509_GET_CERT_OPTS_VEC(FIELD_NAME) \ int botan_x509_cert_opts_##FIELD_NAME(botan_x509_cert_opts_t opts, const char** value, size_t cnt) { \ - if(!value) { \ + if(value == nullptr) { \ return BOTAN_FFI_ERROR_NULL_POINTER; \ } \ return ffi_guard_thunk(__func__, [=]() -> int { \ std::vector val; \ for(size_t i = 0; i < cnt; i++) { \ - if(!value[i]) { \ + if(value[i] == nullptr) { \ return BOTAN_FFI_ERROR_NULL_POINTER; \ } \ val.push_back(value[i]); \ @@ -678,7 +679,7 @@ X509_GET_CERT_OPTS_STRING(challenge) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define X509_GET_CERT_OPTS_VEC(FIELD_NAME) \ int botan_x509_cert_opts_##FIELD_NAME(botan_x509_cert_opts_t opts, const char** value, size_t cnt) { \ - if(!value) { \ + if(value == nullptr) { \ return BOTAN_FFI_ERROR_NULL_POINTER; \ } \ BOTAN_UNUSED(opts, cnt); \ @@ -699,7 +700,7 @@ int botan_x509_cert_opts_ca_key(botan_x509_cert_opts_t opts, size_t limit) { } int botan_x509_cert_opts_set_padding_scheme(botan_x509_cert_opts_t opts, const char* scheme) { - if(!scheme) { + if(scheme == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -759,13 +760,38 @@ int botan_x509_cert_opts_add_ex_constraint(botan_x509_cert_opts_t opts, botan_as #endif } +int botan_x509_cert_opts_add_ext_ip_addr_blocks(botan_x509_cert_opts_t opts, + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(opts).extensions.add(safe_get(ip_addr_blocks).copy()); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(opts, as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_opts_add_ext_as_blocks(botan_x509_cert_opts_t opts, botan_x509_ext_as_blocks_t as_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(opts).extensions.add(safe_get(as_blocks).copy()); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(opts, as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, botan_privkey_t key, botan_x509_cert_opts_t opts, const char* hash_fn, const char* sig_padding, botan_rng_t rng) { - if(!cert_obj || !hash_fn || !sig_padding) { + if(cert_obj == nullptr || hash_fn == nullptr || sig_padding == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) @@ -787,7 +813,7 @@ int botan_x509_create_ca(botan_x509_ca_t* ca_obj, const char* hash_fn, const char* sig_padding, botan_rng_t rng) { - if(!ca_obj || !hash_fn || !sig_padding) { + if(ca_obj == nullptr || hash_fn == nullptr || sig_padding == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) @@ -807,7 +833,7 @@ int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, botan_privkey_t key, const char* hash_fn, botan_rng_t rng) { - if(!req_obj || !hash_fn) { + if(req_obj == nullptr || hash_fn == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) @@ -829,7 +855,7 @@ int botan_x509_sign_req(botan_x509_cert_t* cert_obj, botan_rng_t rng, botan_x509_time_t not_before, botan_x509_time_t not_after) { - if(!cert_obj) { + if(cert_obj == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) @@ -846,7 +872,7 @@ int botan_x509_sign_req(botan_x509_cert_t* cert_obj, } int botan_x509_create_time(botan_x509_time_t* time_obj, uint64_t time_since_epoch) { - if(!time_obj) { + if(time_obj == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) diff --git a/src/lib/ffi/ffi_x509_rpki.cpp b/src/lib/ffi/ffi_x509_rpki.cpp new file mode 100644 index 00000000000..6556e5adaa1 --- /dev/null +++ b/src/lib/ffi/ffi_x509_rpki.cpp @@ -0,0 +1,543 @@ +/* +* (C) 2025 Jack Lloyd +* (C) 2025 Dominik Schricker +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include + +namespace { +template +void ip_addr_blocks_ext_add_address(Botan::Cert_Extension::IPAddressBlocks& ext, + const uint8_t* min, + const uint8_t* max, + uint8_t* safi) { + const size_t version_octets = static_cast(V); + + std::optional safi_ = safi == nullptr ? std::nullopt : std::optional(*safi); + std::array min_; + std::array max_; + std::copy(min, min + version_octets, min_.begin()); + std::copy(max, max + version_octets, max_.begin()); + ext.add_address(min_, max_, safi_); +} + +template +int ip_addr_blocks_get_family(const Botan::Cert_Extension::IPAddressBlocks::IPAddressFamily& family, + int* present, + int* count) { + if(!std::holds_alternative>(family.addr_choice())) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + auto v4 = std::get>(family.addr_choice()); + + if(!v4.ranges().has_value()) { + *present = 0; + } else { + *present = 1; + *count = v4.ranges().value().size(); + } + return BOTAN_FFI_SUCCESS; +} + +template +int ip_addr_blocks_get_address(const Botan::Cert_Extension::IPAddressBlocks::IPAddressFamily::AddrChoice& choice, + size_t entry, + uint8_t min_out[], + uint8_t max_out[], + size_t* out_len) { + if(!std::holds_alternative>(choice)) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + auto v4 = std::get>(choice); + + if(!v4.ranges().has_value() || entry >= v4.ranges().value().size()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + auto entry_ = v4.ranges().value().at(entry); + + auto ret = Botan_FFI::write_vec_output(min_out, out_len, entry_.min().value()); + if(ret != BOTAN_FFI_SUCCESS) { + return ret; + } + return Botan_FFI::write_vec_output(max_out, out_len, entry_.max().value()); +} + +} // namespace + +extern "C" { + +using namespace Botan_FFI; + +int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(as_blocks); +#else + BOTAN_UNUSED(opts); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(ip_addr_blocks); +#else + BOTAN_UNUSED(opts); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_create_ip_addr_blocks(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) { + if(ip_addr_blocks == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + auto ext = std::make_unique(); + *ip_addr_blocks = new botan_x509_ext_ip_addr_blocks_struct(std::move(ext)); + return BOTAN_FFI_SUCCESS; + }); +#else + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_create_ip_addr_blocks_from_cert(botan_x509_cert_t cert, + botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) { + if(ip_addr_blocks == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto ext = safe_get(cert).v3_extensions().get_extension_object_as(); + + if(ext == nullptr) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + *ip_addr_blocks = + new botan_x509_ext_ip_addr_blocks_struct(std::unique_ptr( + dynamic_cast(ext->copy().release()))); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_add_ip_addr( + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, const uint8_t* min, const uint8_t* max, int ipv6, uint8_t* safi) { + if(min == nullptr || max == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + if(ipv6 != 0 && ipv6 != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + auto& ext = safe_get(ip_addr_blocks); + + if(ipv6 == 0) { + ip_addr_blocks_ext_add_address(ext, min, max, safi); + } else { + ip_addr_blocks_ext_add_address(ext, min, max, safi); + } + + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ip_addr_blocks, addr_length); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_restrict(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi) { +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + if(ipv6 != 0 && ipv6 != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + auto& ext = safe_get(ip_addr_blocks); + std::optional safi_ = safi == nullptr ? std::nullopt : std::optional(*safi); + + if(ipv6 == 0) { + ext.restrict(safi_); + } else { + ext.restrict(safi_); + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ip_addr_blocks, ipv6, safi); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_inherit(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi) { +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + if(ipv6 != 0 && ipv6 != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + auto& ext = safe_get(ip_addr_blocks); + std::optional safi_ = safi == nullptr ? std::nullopt : std::optional(*safi); + + if(ipv6 == 0) { + ext.inherit(safi_); + } else { + ext.inherit(safi_); + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ip_addr_blocks, ipv6, safi); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_get_counts(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + size_t* v4_count, + size_t* v6_count) { + if(v4_count == nullptr || v6_count == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + const auto& ext = safe_get(ip_addr_blocks); + size_t v4 = 0; + size_t v6 = 0; + + for(const auto& entry : ext.addr_blocks()) { + if(entry.afi() == 1) { + v4++; + } else { + v6++; + } + } + *v4_count = v4; + *v6_count = v6; + + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ip_addr_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int ipv6, + size_t i, + int* has_safi, + uint8_t* safi, + int* present, + int* count) { + if(has_safi == nullptr || safi == nullptr || present == nullptr || count == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + if(ipv6 != 0 && ipv6 != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + const auto& addr_blocks = safe_get(ip_addr_blocks).addr_blocks(); + + if(i >= addr_blocks.size()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + const auto& family = addr_blocks.at(i); + if(family.safi().has_value()) { + *has_safi = 1; + *safi = family.safi().value(); + } else { + *has_safi = 0; + } + + if(ipv6 == 0) { + return ip_addr_blocks_get_family( + family, present, count); + } else { + return ip_addr_blocks_get_family( + family, present, count); + } + }); +#else + BOTAN_UNUSED(ip_addr_blocks, i, ipv6); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int ipv6, + size_t i, + size_t entry, + uint8_t min_out[], + uint8_t max_out[], + size_t* out_len) { + if(out_len == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + if(ipv6 != 0 && ipv6 != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + const auto& addr_blocks = safe_get(ip_addr_blocks).addr_blocks(); + + if(i >= addr_blocks.size()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + const auto& choice = addr_blocks.at(i).addr_choice(); + + if(ipv6 == 0) { + return ip_addr_blocks_get_address( + choice, entry, min_out, max_out, out_len); + } else { + return ip_addr_blocks_get_address( + choice, entry, min_out, max_out, out_len); + } + }); +#else + BOTAN_UNUSED(ip_addr_blocks, ipv6, i, entry, min_out, max_out); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_create_as_blocks(botan_x509_ext_as_blocks_t* as_blocks) { + if(as_blocks == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined BOTAN_HAS_X509_CERTIFICATES + return ffi_guard_thunk(__func__, [=]() -> int { + auto ext = std::make_unique(); + *as_blocks = new botan_x509_ext_as_blocks_struct(std::move(ext)); + return BOTAN_FFI_SUCCESS; + }); +#else + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_create_as_blocks_from_cert(botan_x509_cert_t cert, botan_x509_ext_as_blocks_t* as_blocks) { + if(as_blocks == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto ext = safe_get(cert).v3_extensions().get_extension_object_as(); + + if(ext == nullptr) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + *as_blocks = new botan_x509_ext_as_blocks_struct(std::unique_ptr( + dynamic_cast(ext->copy().release()))); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_add_asnum(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(as_blocks).add_asnum(min, max); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks, min, max); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_restrict_asnum(botan_x509_ext_as_blocks_t as_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(as_blocks).restrict_asnum(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_inherit_asnum(botan_x509_ext_as_blocks_t as_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(as_blocks).inherit_asnum(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_add_rdi(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(as_blocks).add_rdi(min, max); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks, min, max); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_restrict_rdi(botan_x509_ext_as_blocks_t as_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(as_blocks).restrict_rdi(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_inherit_rdi(botan_x509_ext_as_blocks_t as_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + safe_get(as_blocks).inherit_rdi(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_get_asnum(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count) { + if(present == nullptr || count == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + const auto& asnum = safe_get(as_blocks).as_identifiers().asnum(); + + if(!asnum.has_value()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const auto& ranges = asnum.value().ranges(); + + if(!ranges.has_value()) { + *present = 0; + return BOTAN_FFI_SUCCESS; + } + + *present = 1; + *count = ranges.value().size(); + + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_get_asnum_at(botan_x509_ext_as_blocks_t as_blocks, + size_t i, + uint32_t* min, + uint32_t* max) { + if(min == nullptr || max == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + const auto& asnum = safe_get(as_blocks).as_identifiers().asnum(); + + if(!asnum.has_value() || !asnum.value().ranges().has_value()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const auto& range = asnum.value().ranges().value(); + if(i >= range.size()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + *min = range.at(i).min(); + *max = range.at(i).max(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks, i); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_get_rdi(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count) { + if(present == nullptr || count == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + const auto& rdi = safe_get(as_blocks).as_identifiers().rdi(); + + if(!rdi.has_value()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const auto& ranges = rdi.value().ranges(); + + if(!ranges.has_value()) { + *present = 0; + return BOTAN_FFI_SUCCESS; + } + + *present = 1; + *count = ranges.value().size(); + + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_ext_as_blocks_get_rdi_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max) { + if(min == nullptr || max == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + const auto& rdi = safe_get(as_blocks).as_identifiers().rdi(); + + if(!rdi.has_value() || !rdi.value().ranges().has_value()) { + return BOTAN_FFI_ERROR_NO_VALUE; + } + + const auto& range = rdi.value().ranges().value(); + if(i >= range.size()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + + *min = range.at(i).min(); + *max = range.at(i).max(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(as_blocks, i); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} +} diff --git a/src/lib/ffi/ffi_x509_rpki.h b/src/lib/ffi/ffi_x509_rpki.h new file mode 100644 index 00000000000..a5e61497999 --- /dev/null +++ b/src/lib/ffi/ffi_x509_rpki.h @@ -0,0 +1,26 @@ +/* +* (C) 2025 Jack Lloyd +* (C) 2025 Dominik Schricker +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_FFI_X509_RPKI_H_ +#define BOTAN_FFI_X509_RPKI_H_ + +#include + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + #include +#endif + +extern "C" { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + +BOTAN_FFI_DECLARE_STRUCT(botan_x509_ext_as_blocks_struct, Botan::Cert_Extension::ASBlocks, 0xA56348EC); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_ext_ip_addr_blocks_struct, Botan::Cert_Extension::IPAddressBlocks, 0xB489828F); + +#endif +} + +#endif diff --git a/src/lib/ffi/info.txt b/src/lib/ffi/info.txt index 85ad393b38a..fb08b29c6e6 100644 --- a/src/lib/ffi/info.txt +++ b/src/lib/ffi/info.txt @@ -15,6 +15,7 @@ ffi_oid.h ffi_pkey.h ffi_rng.h ffi_util.h +ffi_x509_rpki.h diff --git a/src/python/botan3.py b/src/python/botan3.py index f44b980670d..d8c279ab43d 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -509,6 +509,8 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_cert_verify, [POINTER(c_int), c_void_p, c_void_p, c_size_t, c_void_p, c_size_t, c_char_p, c_size_t, c_char_p, c_uint64]) ffi_api(dll.botan_x509_cert_opts_destroy, [c_void_p]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_destroy, [c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_destroy, [c_void_p]) ffi_api(dll.botan_x509_ca_destroy, [c_void_p]) ffi_api(dll.botan_x509_pkcs10_req_destroy, [c_void_p]) ffi_api(dll.botan_x509_time_destroy, [c_void_p]) @@ -534,6 +536,31 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_cert_opts_not_after, [c_void_p, c_void_p]) ffi_api(dll.botan_x509_cert_opts_add_constraints, [c_void_p, c_uint32]) ffi_api(dll.botan_x509_cert_opts_add_ex_constraint, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_opts_add_ext_ip_addr_blocks, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_opts_add_ext_as_blocks, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_ext_create_ip_addr_blocks, [c_void_p]) + ffi_api(dll.botan_x509_ext_create_ip_addr_blocks_from_cert, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_add_ip_addr, + [c_void_p, c_char_p, c_char_p, c_int, POINTER(c_uint8)]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_restrict, [c_void_p, c_int, POINTER(c_uint8)]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_inherit, [c_void_p, c_int, POINTER(c_uint8)]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_counts, [c_void_p, POINTER(c_size_t), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_family, + [c_void_p, c_int, c_size_t, POINTER(c_int), POINTER(c_uint8), POINTER(c_int), POINTER(c_int)]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_address, + [c_void_p, c_int, c_size_t, c_size_t, c_char_p, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_ext_create_as_blocks, [c_void_p]) + ffi_api(dll.botan_x509_ext_create_as_blocks_from_cert, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_add_asnum, [c_void_p, c_uint32, c_uint32]) + ffi_api(dll.botan_x509_ext_as_blocks_restrict_asnum, [c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_inherit_asnum, [c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_add_rdi, [c_void_p, c_uint32, c_uint32]) + ffi_api(dll.botan_x509_ext_as_blocks_restrict_rdi, [c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_inherit_rdi, [c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_get_asnum, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_ext_as_blocks_get_asnum_at, [c_void_p, c_size_t, POINTER(c_uint32), POINTER(c_uint32)]) + ffi_api(dll.botan_x509_ext_as_blocks_get_rdi, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_ext_as_blocks_get_rdi_at, [c_void_p, c_size_t, POINTER(c_uint32), POINTER(c_uint32)]) ffi_api(dll.botan_x509_create_self_signed_cert, [c_void_p, c_void_p, c_void_p, c_char_p, c_char_p, c_void_p]) ffi_api(dll.botan_x509_create_ca, @@ -1965,6 +1992,192 @@ def create_req(self, key, hash_fn, rng): _DLL.botan_x509_create_pkcs10_req(byref(req.handle_()), self.__obj, key.handle_(), _ctype_str(hash_fn), rng.handle_()) return req + def add_ext_ip_addr_blocks(self, ip_addr_blocks): + _DLL.botan_x509_cert_opts_add_ext_ip_addr_blocks(self.__obj, ip_addr_blocks.handle_()) + + def add_ext_as_blocks(self, as_blocks): + _DLL.botan_x509_cert_opts_add_ext_as_blocks(self.__obj, as_blocks.handle_()) + + +class X509ExtIPAddrBlocks: + def __init__(self, cert=None): + self.__obj = c_void_p(0) + self.__writable = cert is None + if cert: + _DLL.botan_x509_ext_create_ip_addr_blocks_from_cert(cert.handle_(), byref(self.__obj)) + else: + _DLL.botan_x509_ext_create_ip_addr_blocks(byref(self.__obj)) + + def __del__(self): + _DLL.botan_x509_ext_ip_addr_blocks_destroy(self.__obj) + + def handle_(self): + return self.__obj + + def add_ip(self, ip, safi=None): + self.add_ip_range(ip, ip, safi) + + def add_ip_range(self, min, max, safi=None): + if not self.__writable: + raise BotanException("Extension is read-only") + min_len = len(min) + if (min_len != 4 and min_len != 16) or len(max) != min_len: + raise BotanException("Address must be 4 or 16 bytes long") + + ipv6 = 1 if min_len == 16 else 0 + safi = byref(c_uint8(safi)) if safi is not None else None + _DLL.botan_x509_ext_ip_addr_blocks_add_ip_addr(self.__obj, bytes(min), bytes(max), c_int(ipv6), safi) + + def restrict(self, ipv6, safi=None): + if not self.__writable: + raise BotanException("Extension is read-only") + + ipv6 = 1 if ipv6 else 0 + safi = byref(c_uint8(safi)) if safi is not None else None + _DLL.botan_x509_ext_ip_addr_blocks_restrict(self.__obj, c_int(ipv6), safi) + + def inherit(self, ipv6, safi=None): + if not self.__writable: + raise BotanException("Extension is read-only") + + ipv6 = 1 if ipv6 else 0 + safi = byref(c_uint8(safi)) if safi is not None else None + _DLL.botan_x509_ext_ip_addr_blocks_inherit(self.__obj, c_int(ipv6), safi) + + def addresses(self): + v4 = [] + v6 = [] + + v4_count = c_size_t(0) + v6_count = c_size_t(0) + + _DLL.botan_x509_ext_ip_addr_blocks_get_counts(self.__obj, byref(v4_count), byref(v6_count)) + + v4_count = v4_count.value + v6_count = v6_count.value + + for (ipv6, start, stop) in ((0, 0, v4_count), (1, v4_count, v4_count + v6_count)): + for i in range(start, stop): + size = 16 if ipv6 else 4 + + has_safi = c_int(0) + safi = c_uint8(0) + present = c_int(0) + count = c_int(0) + _DLL.botan_x509_ext_ip_addr_blocks_get_family(self.__obj, c_int(ipv6), c_size_t(i), byref(has_safi), byref(safi), byref(present), byref(count)) + ranges = None + if present.value == 1: + ranges = [] + for entry in range(count.value): + min_, max_ = _call_fn_returning_vec_pair( + size, size, lambda mi, _, ma, l: _DLL.botan_x509_ext_ip_addr_blocks_get_address( + self.__obj, + c_int(ipv6), + c_size_t(i), + c_size_t(entry), + mi, + ma, + l)) + ranges.append((list(min_), list(max_))) + + safi = safi.value if has_safi.value == 1 else None + ranges = tuple(ranges) if ranges is not None else None + if ipv6 == 0: + v4.append((safi, ranges)) + else: + v6.append((safi, ranges)) + + return { + 4: v4, + 6: v6 + } + + +class X509ExtASBlocks: + def __init__(self, cert=None): + self.__obj = c_void_p(0) + self.__writable = cert is None + if cert: + _DLL.botan_x509_ext_create_as_blocks_from_cert(cert.handle_(), byref(self.__obj)) + else: + _DLL.botan_x509_ext_create_as_blocks(byref(self.__obj)) + + def __del__(self): + _DLL.botan_x509_ext_as_blocks_destroy(self.__obj) + + def handle_(self): + return self.__obj + + def add_asnum(self, asnum): + self.add_asnum_range(asnum, asnum) + + def add_asnum_range(self, min, max): + if not self.__writable: + raise BotanException("Extension is read-only") + _DLL.botan_x509_ext_as_blocks_add_asnum(self.__obj, c_uint32(min), c_uint32(max)) + + def restrict_asnum(self): + if not self.__writable: + raise BotanException("Extension is read-only") + _DLL.botan_x509_ext_as_blocks_restrict_asnum(self.__obj) + + def inherit_asnum(self): + if not self.__writable: + raise BotanException("Extension is read-only") + _DLL.botan_x509_ext_as_blocks_inherit_asnum(self.__obj) + + def add_rdi(self, rdi): + self.add_rdi_range(rdi, rdi) + + def add_rdi_range(self, min, max): + if not self.__writable: + raise BotanException("Extension is read-only") + _DLL.botan_x509_ext_as_blocks_add_rdi(self.__obj, c_uint32(min), c_uint32(max)) + + def restrict_rdi(self): + if not self.__writable: + raise BotanException("Extension is read-only") + _DLL.botan_x509_ext_as_blocks_restrict_rdi(self.__obj) + + def inherit_rdi(self): + if not self.__writable: + raise BotanException("Extension is read-only") + _DLL.botan_x509_ext_as_blocks_inherit_rdi(self.__obj) + + def asnum(self): + present = c_int(0) + count = c_size_t(0) + _DLL.botan_x509_ext_as_blocks_get_asnum(self.__obj, byref(present), byref(count)) + + # asnum is 'inherit' + if present.value == 0: + return None + + asnums = [] + for i in range(count.value): + min_ = c_uint32(0) + max_ = c_uint32(0) + _DLL.botan_x509_ext_as_blocks_get_asnum_at(self.__obj, c_size_t(i), byref(min_), byref(max_)) + asnums.append((min_.value, max_.value)) + return asnums + + def rdi(self): + present = c_int(0) + count = c_size_t(0) + _DLL.botan_x509_ext_as_blocks_get_rdi(self.__obj, byref(present), byref(count)) + + # rdi is 'inherit' + if present.value == 0: + return None + + rdis = [] + for i in range(count.value): + min_ = c_uint32(0) + max_ = c_uint32(0) + _DLL.botan_x509_ext_as_blocks_get_rdi_at(self.__obj, c_size_t(i), byref(min_), byref(max_)) + rdis.append((min_.value, max_.value)) + return rdis + class X509Cert: # pylint: disable=invalid-name def __init__(self, filename: str | None = None, buf: bytes | None = None): @@ -2125,6 +2338,12 @@ def is_self_signed(self): else: return True + def ext_ip_addr_blocks(self): + return X509ExtIPAddrBlocks(self) + + def ext_as_blocks(self): + return X509ExtASBlocks(self) + def handle_(self): return self.__obj diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 5ba81193b3a..95fb78eea7d 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -823,10 +823,11 @@ def test_certs(self): def test_cert_creation(self): hash_fn = "SHA-256" - padding_method = "EMSA3(SHA-256)" + padding_method = "EMSA1(SHA-256)" + group = "secp256r1" rng = botan.RandomNumberGenerator() - ca_key = botan.PrivateKey.create("RSA", "4096", rng) + ca_key = botan.PrivateKey.create("ECDSA", group, rng) ca_opts = botan.X509Opts("Test CA/US/Botan Project/Testing") ca_opts.set_ca_key(1) ca_opts.set_constraints(["DIGITAL_SIGNATURE"]) @@ -840,7 +841,7 @@ def test_cert_creation(self): self.assertEqual(ca_cert.key_constraints(), ["DIGITAL_SIGNATURE", "KEY_CERT_SIGN", "CRL_SIGN"]) ca = botan.X509Ca(ca_cert, ca_key, rng, hash_fn) - cert_key = botan.PrivateKey.create("RSA", "4096", rng) + cert_key = botan.PrivateKey.create("ECDSA", group, rng) req_opts = botan.X509Opts("Test CA/US/Botan Project/Testing") req_opts.set_uri("https://botan.randombit.net") req_opts.set_more_dns(["botan.randombit.net", "randombit.net"]) @@ -852,6 +853,98 @@ def test_cert_creation(self): self.assertEqual(cert.verify(None, [ca_cert]), 0) + def test_x509_rpki(self): + hash_fn = "SHA-256" + padding_method = "EMSA1(SHA-256)" + group = "secp256r1" + + rng = botan.RandomNumberGenerator() + ca_key = botan.PrivateKey.create("ECDSA", group, rng) + ca_opts = botan.X509Opts("Test CA/US/Botan Project/Testing") + ca_opts.set_ca_key(1) + ca_opts.set_constraints(["DIGITAL_SIGNATURE"]) + + ca_ip_addr_blocks = botan.X509ExtIPAddrBlocks() + + ca_ip_addr_blocks.add_ip([192, 168, 2, 1]) + ca_ip_addr_blocks.add_ip_range([10, 0, 0, 1], [10, 0, 255, 255]) + ca_ip_addr_blocks.restrict(False, 42) + + ca_ip_addr_blocks.add_ip([0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]) + ca_ip_addr_blocks.restrict(True, 234) + + ca_as_blocks = botan.X509ExtASBlocks() + ca_as_blocks.add_asnum(30) + ca_as_blocks.add_asnum_range(3000, 4999) + ca_as_blocks.restrict_rdi() + + ca_opts.add_ext_ip_addr_blocks(ca_ip_addr_blocks) + ca_opts.add_ext_as_blocks(ca_as_blocks) + + ca_cert = botan.X509Cert.create_self_signed(ca_key, ca_opts, hash_fn, padding_method, rng) + + ca_ip_addr_blocks = ca_cert.ext_ip_addr_blocks() + self.assertEqual(ca_ip_addr_blocks.addresses(), { + 4: [ + (None, (([10, 0, 0, 1], [10, 0, 255, 255]), ([192, 168, 2, 1], [192, 168, 2, 1]))), + (42, ()) + ], + 6: [ + (None, (( + [0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + [0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01] + ), )), + (234, ()) + ] + }) + + ca_as_blocks = ca_cert.ext_as_blocks() + self.assertEqual(ca_as_blocks.asnum(), [(30, 30), (3000, 4999)]) + self.assertEqual(ca_as_blocks.rdi(), []) + + with self.assertRaisesRegex(botan.BotanException, r".*read-only.*"): + ca_as_blocks.add_asnum(999) + + ca = botan.X509Ca(ca_cert, ca_key, rng, hash_fn) + + cert_key = botan.PrivateKey.create("ECDSA", group, rng) + req_opts = botan.X509Opts("Test CA/US/Botan Project/Testing") + + req_ip_addr_blocks = botan.X509ExtIPAddrBlocks() + req_ip_addr_blocks.add_ip([192, 168, 2, 1]) + req_ip_addr_blocks.add_ip_range([10, 0, 5, 5], [10, 0, 7, 7]) + req_ip_addr_blocks.restrict(False, 42) + req_ip_addr_blocks.inherit(True) + req_ip_addr_blocks.inherit(True, 234) + + req_as_blocks = botan.X509ExtASBlocks() + req_as_blocks.add_asnum_range(3100, 4000) + req_as_blocks.inherit_rdi() + + req_opts.add_ext_ip_addr_blocks(req_ip_addr_blocks) + req_opts.add_ext_as_blocks(req_as_blocks) + req = req_opts.create_req(cert_key, hash_fn, rng) + + cert = ca.sign(req, rng, botan.X509Time(int(time.time())), botan.X509Time(int(time.time()) + 60)) + + req_ip_addr_blocks = cert.ext_ip_addr_blocks() + self.assertEqual(req_ip_addr_blocks.addresses(), { + 4: [ + (None, (([10, 0, 5, 5], [10, 0, 7, 7]), ([192, 168, 2, 1], [192, 168, 2, 1]))), + (42, ()) + ], + 6: [ + (None, None), + (234, None) + ] + }) + + req_as_blocks = cert.ext_as_blocks() + self.assertEqual(req_as_blocks.asnum(), [(3100, 4000)]) + self.assertEqual(req_as_blocks.rdi(), None) + + self.assertEqual(cert.verify(None, [ca_cert]), 0) + def test_mpi(self): z = botan.MPI() self.assertEqual(z.bit_count(), 0) diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index 3c0a34c7d63..42af00a915e 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -858,6 +858,194 @@ class FFI_Cert_Creation_Test final : public FFI_Test { } }; +class FFI_X509_RPKI_Test final : public FFI_Test { + public: + std::string name() const override { return "FFI X509 RPKI"; } + + void ffi_test(Test::Result& result, botan_rng_t rng) override { + const std::string hash_fn{"SHA-256"}; + const std::string padding_method{"EMSA3(SHA-256)"}; + + botan_privkey_t ca_key; + botan_privkey_t cert_key; + + if(TEST_FFI_INIT(botan_privkey_create_rsa, (&ca_key, rng, 4096))) { + TEST_FFI_OK(botan_privkey_create_rsa, (&cert_key, rng, 4096)); + + uint64_t now = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) + .count(); + + botan_x509_time_t not_before; + botan_x509_time_t not_after; + + if(TEST_FFI_INIT(botan_x509_create_time, (¬_before, now))) { + TEST_FFI_OK(botan_x509_create_time, (¬_after, now + 60 * 60)); + + botan_x509_cert_opts_t ca_opts; + TEST_FFI_OK(botan_x509_create_cert_opts, (&ca_opts, "Test CA/US/Botan Project/Testing", nullptr)); + TEST_FFI_OK(botan_x509_cert_opts_ca_key, (ca_opts, 1)); + + botan_x509_ext_ip_addr_blocks_t ca_ip_addr_blocks; + TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks, (&ca_ip_addr_blocks)); + std::vector ip_addr = {192, 168, 2, 1}; + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_add_ip_addr, + (ca_ip_addr_blocks, ip_addr.data(), ip_addr.data(), 0, nullptr)); + + std::vector min = {10, 0, 0, 1}; + std::vector max = {10, 255, 255, 255}; + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_add_ip_addr, + (ca_ip_addr_blocks, min.data(), max.data(), 0, nullptr)); + + std::vector ipv6_addr = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + uint8_t ipv6_safi = 1; + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_add_ip_addr, + (ca_ip_addr_blocks, ipv6_addr.data(), ipv6_addr.data(), 1, &ipv6_safi)); + + ipv6_safi = 2; + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_restrict, (ca_ip_addr_blocks, 1, &ipv6_safi)); + + size_t v4_count; + size_t v6_count; + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_counts, (ca_ip_addr_blocks, &v4_count, &v6_count)); + + result.test_eq("ext has v4 addresses", v4_count, 1); + result.test_eq("ext has v6 addresses", v6_count, 2); + + int has_safi; + uint8_t safi; + int present; + int count; + + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, + botan_x509_ext_ip_addr_blocks_get_family, + (ca_ip_addr_blocks, 0, 1, &has_safi, &safi, &present, &count)); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_family, + (ca_ip_addr_blocks, 0, 0, &has_safi, &safi, &present, &count)); + + result.confirm("index 0 does not have a safi", has_safi == 0); + result.confirm("index 0 has a value", present == 1); + result.confirm("index 0 has entries", count == 2); + + std::vector min_out(4); + std::vector max_out(4); + size_t out_len = 4; + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_address, + (ca_ip_addr_blocks, 0, 0, 0, min_out.data(), max_out.data(), &out_len)); + + result.confirm("data has correct size", out_len == 4); + result.test_eq("index 0 entry 0 has correct min", min_out, min); + result.test_eq("index 0 entry 0 has correct min", max_out, max); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_address, + (ca_ip_addr_blocks, 0, 0, 1, min_out.data(), max_out.data(), &out_len)); + + result.test_eq("index 0 entry 1 has correct min", min_out, ip_addr); + result.test_eq("index 0 entry 1 has correct min", max_out, ip_addr); + + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, + botan_x509_ext_ip_addr_blocks_get_address, + (ca_ip_addr_blocks, 0, 0, 2, min_out.data(), max_out.data(), &out_len)); + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, + botan_x509_ext_ip_addr_blocks_get_address, + (ca_ip_addr_blocks, 0, 1, 0, min_out.data(), max_out.data(), &out_len)); + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, + botan_x509_ext_ip_addr_blocks_get_address, + (ca_ip_addr_blocks, 1, 0, 0, min_out.data(), max_out.data(), &out_len)); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_family, + (ca_ip_addr_blocks, 1, 1, &has_safi, &safi, &present, &count)); + + result.confirm("index 1 does have a safi", has_safi == 1); + result.confirm("index 1 safi is correct", safi == 1); + result.confirm("index 1 has a value", present == 1); + result.confirm("index 1 has entries", count == 1); + + min_out.resize(16); + max_out.resize(16); + out_len = 16; + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_address, + (ca_ip_addr_blocks, 1, 1, 0, min_out.data(), max_out.data(), &out_len)); + + result.confirm("data has correct size", out_len == 16); + result.test_eq("index 1 entry 0 has correct min", min_out, ipv6_addr); + result.test_eq("index 1 entry 0 has correct min", max_out, ipv6_addr); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_family, + (ca_ip_addr_blocks, 1, 2, &has_safi, &safi, &present, &count)); + + result.confirm("index 1 does have a safi", has_safi == 1); + result.confirm("index 1 safi is correct", safi == 2); + result.confirm("index 1 has a value", present == 1); + result.confirm("index 1 has entries", count == 0); + + TEST_FFI_OK(botan_x509_cert_opts_add_ext_ip_addr_blocks, (ca_opts, ca_ip_addr_blocks)); + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (ca_ip_addr_blocks)); + + botan_x509_cert_t ca_cert; + TEST_FFI_OK(botan_x509_create_self_signed_cert, (&ca_cert, ca_key, ca_opts, hash_fn.c_str(), "", rng)); + + botan_x509_ca_t ca; + TEST_FFI_OK(botan_x509_create_ca, (&ca, ca_cert, ca_key, hash_fn.c_str(), padding_method.c_str(), rng)); + + botan_x509_cert_opts_t req_opts; + TEST_FFI_OK(botan_x509_create_cert_opts, (&req_opts, "Test CA/US/Botan Project/Testing", nullptr)); + + botan_x509_ext_ip_addr_blocks_t req_ip_addr_blocks; + TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks, (&req_ip_addr_blocks)); + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_inherit, (req_ip_addr_blocks, 0, nullptr)); + + TEST_FFI_OK(botan_x509_cert_opts_add_ext_ip_addr_blocks, (req_opts, req_ip_addr_blocks)); + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (req_ip_addr_blocks)); + + botan_x509_pkcs10_req_t req; + TEST_FFI_OK(botan_x509_create_pkcs10_req, (&req, req_opts, cert_key, hash_fn.c_str(), rng)); + + botan_x509_cert_t cert; + TEST_FFI_OK(botan_x509_sign_req, (&cert, ca, req, rng, not_before, not_after)); + + botan_x509_ext_ip_addr_blocks_t req_ip_addr_blocks_from_cert; + TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks_from_cert, (cert, &req_ip_addr_blocks_from_cert)); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_counts, + (req_ip_addr_blocks_from_cert, &v4_count, &v6_count)); + + result.test_eq("ext has v4 addresses", v4_count, 1); + result.test_eq("ext has no v6 addresses", v6_count, 0); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_family, + (req_ip_addr_blocks_from_cert, 0, 0, &has_safi, &safi, &present, &count)); + + result.confirm("index 0 does not have a safi", has_safi == 0); + result.confirm("index 0 has a value", present == 0); + + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (req_ip_addr_blocks_from_cert)); + + int rc; + TEST_FFI_RC(0, botan_x509_cert_verify, (&rc, cert, nullptr, 0, &ca_cert, 1, nullptr, 0, nullptr, 0)); + + TEST_FFI_OK(botan_x509_time_destroy, (not_before)); + TEST_FFI_OK(botan_x509_time_destroy, (not_after)); + TEST_FFI_OK(botan_x509_cert_opts_destroy, (ca_opts)); + TEST_FFI_OK(botan_x509_cert_opts_destroy, (req_opts)); + TEST_FFI_OK(botan_x509_ca_destroy, (ca)); + TEST_FFI_OK(botan_x509_pkcs10_req_destroy, (req)); + TEST_FFI_OK(botan_x509_cert_destroy, (ca_cert)); + TEST_FFI_OK(botan_x509_cert_destroy, (cert)); + } + + TEST_FFI_OK(botan_privkey_destroy, (ca_key)); + TEST_FFI_OK(botan_privkey_destroy, (cert_key)); + } + } +}; + class FFI_ECDSA_Certificate_Test final : public FFI_Test { public: std::string name() const override { return "FFI ECDSA cert"; } @@ -4672,6 +4860,7 @@ BOTAN_REGISTER_TEST("ffi", "ffi_zfec", FFI_ZFEC_Test); BOTAN_REGISTER_TEST("ffi", "ffi_crl", FFI_CRL_Test); BOTAN_REGISTER_TEST("ffi", "ffi_cert_validation", FFI_Cert_Validation_Test); BOTAN_REGISTER_TEST("ffi", "ffi_cert_creation", FFI_Cert_Creation_Test); +BOTAN_REGISTER_TEST("ffi", "ffi_x509_rpki", FFI_X509_RPKI_Test); BOTAN_REGISTER_TEST("ffi", "ffi_ecdsa_certificate", FFI_ECDSA_Certificate_Test); BOTAN_REGISTER_TEST("ffi", "ffi_pkcs_hashid", FFI_PKCS_Hashid_Test); BOTAN_REGISTER_TEST("ffi", "ffi_cbc_cipher", FFI_CBC_Cipher_Test); From 66f5bae0e92bbfa158da88036765dffae653d13e Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:28:26 +0200 Subject: [PATCH 09/14] documentation --- doc/api_ref/ffi.rst | 270 ++++++++++++++++++++++++++++++++++ doc/api_ref/python.rst | 183 +++++++++++++++++++++++ src/lib/ffi/ffi.h | 30 ++-- src/lib/ffi/ffi_cert.cpp | 62 ++++---- src/lib/ffi/ffi_cert.h | 2 +- src/lib/ffi/ffi_x509_rpki.cpp | 113 +++++++++----- src/lib/ffi/ffi_x509_rpki.h | 28 +++- src/python/botan3.py | 75 ++++------ src/scripts/test_python.py | 27 ++-- src/tests/test_ffi.cpp | 105 ++++++++++++- 10 files changed, 745 insertions(+), 150 deletions(-) diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index 4ade83fed0f..ddf0da8892a 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -1671,6 +1671,23 @@ X.509 Certificates Return the subject key ID set in the certificate, which may be empty. +.. cpp:function::int botan_x509_get_basic_constraints(botan_x509_cert_t cert, int* is_ca, size_t* limit) + + Checks whether the certificate is a CA certificate and sets ``is_ca`` to 1 if it is, 0 otherwise. + If it is a CA certificate, ``limit`` is set to the path limit, otherwise 0. + +.. cpp:function::int botan_x509_get_key_constraints(botan_x509_cert_t cert, uint32_t* usage) + + Returns the key usage constraints. + +.. cpp:function::int botan_x509_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) + + Returns the OCSP responder. + +.. cpp:function::int botan_x509_is_self_signed(botan_x509_cert_t cert, int* out) + + Checks whether the certificate is self signed and sets ``out`` to 1 if it is, 0 otherwise. + .. cpp:function:: int botan_x509_cert_get_public_key_bits(botan_x509_cert_t cert, \ uint8_t out[], size_t* out_len) @@ -1706,6 +1723,10 @@ X.509 Certificates View the certificate as a free-form string. +.. cpp:function::int botan_x509_cert_view_pem(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) + + View the certificate as a PEM string. + .. cpp:enum:: botan_x509_cert_key_constraints Certificate key usage constraints. Allowed values: `NO_CONSTRAINTS`, @@ -1771,6 +1792,255 @@ X.509 Certificates Return a (statically allocated) string associated with the verification result, or NULL if the code is not known. +.. cpp:type:: opaque* botan_x509_cert_opts_t + + An opaque data type for X.509 certificate options. Don't mess with it. + +.. cpp:type:: opaque* botan_x509_time_t + + An opaque data type for an X.509 time. Don't mess with it. + +.. cpp:type:: opaque* botan_x509_ext_as_blocks_t + + An opaque data type for an X.509 AS Blocks extension (RFC 3779). Don't mess with it. + +.. cpp:type:: opaque* botan_x509_ext_ip_addr_blocks_t + + An opaque data type for an X.509 IP Address Blocks extension (RFC 3779). Don't mess with it. + +.. cpp:type:: opaque* botan_x509_ca_t + + An opaque data type for an X.509 CA. Don't mess with it. + +.. cpp:type:: opaque* botan_x509_pkcs10_req_t + + An opaque data type for a PKCS #10 certificate request. Don't mess with it. + +.. cpp:function::int botan_x509_cert_opts_destroy(botan_x509_cert_opts_t opts) + + Destroy the options object. + +.. cpp:function::int botan_x509_time_destroy(botan_x509_time_t time) + + Destroy the time object. + +.. cpp:function::int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) + + Destroy the IP Address Blocks object. + +.. cpp:function::int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks) + + Destroy the AS Blocks object. + +.. cpp:function::int botan_x509_ca_destroy(botan_x509_ca_t ca) + + Destroy the CA object. + +.. cpp:function::int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req) + + Destroy the PKCS #10 certificate request object. + +.. cpp:function::int int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* opts, uint32_t* expire_time) + + Creates a new options object. ``opts`` defines the common name (e.g. `common_name/country/organization/organizational_unit`), ``expire_time`` if given + is the expiration time from current clock in seconds. + +.. cpp:function::int botan_x509_cert_opts_common_name(botan_x509_cert_opts_t opts, const char* name) + + Set the common name for the object. + +.. cpp:function::int botan_x509_cert_opts_country(botan_x509_cert_opts_t opts, const char* country) + + Set the country for the objects. + +.. cpp:function::int botan_x509_cert_opts_organization(botan_x509_cert_opts_t opts, const char* organization) + +.. cpp:function::int botan_x509_cert_opts_org_unit(botan_x509_cert_opts_t opts, const char* org_unit) + +.. cpp:function::int botan_x509_cert_opts_locality(botan_x509_cert_opts_t opts, const char* locality) + +.. cpp:function::int botan_x509_cert_opts_state(botan_x509_cert_opts_t opts, const char* state) + +.. cpp:function::int botan_x509_cert_opts_serial_number(botan_x509_cert_opts_t opts, const char* serial_number) + +.. cpp:function::int botan_x509_cert_opts_email(botan_x509_cert_opts_t opts, const char* email) + +.. cpp:function::int botan_x509_cert_opts_uri(botan_x509_cert_opts_t opts, const char* uri) + +.. cpp:function::int botan_x509_cert_opts_ip(botan_x509_cert_opts_t opts, const char* ip) + +.. cpp:function::int botan_x509_cert_opts_dns(botan_x509_cert_opts_t opts, const char* dns) + +.. cpp:function::int botan_x509_cert_opts_xmpp(botan_x509_cert_opts_t opts, const char* xmpp) + +.. cpp:function::int botan_x509_cert_opts_challenge(botan_x509_cert_opts_t opts, const char* challenge) + +.. cpp:function::int int botan_x509_cert_opts_more_org_units(botan_x509_cert_opts_t opts, const char** more_org_units, size_t cnt) + +.. cpp:function::int int botan_x509_cert_opts_more_dns(botan_x509_cert_opts_t opts, const char** more_dns, size_t cnt) + +.. cpp:function::int botan_x509_cert_opts_ca_key(botan_x509_cert_opts_t opts, size_t limit) + + Mark the certificate for CA usage. + +.. cpp:function::int botan_x509_cert_opts_padding_scheme(botan_x509_cert_opts_t opts, const char* scheme) + +.. cpp:function::int botan_x509_cert_opts_not_before(botan_x509_cert_opts_t opts, botan_x509_time_t not_before) + +.. cpp:function::int botan_x509_cert_opts_not_after(botan_x509_cert_opts_t opts, botan_x509_time_t not_after) + +.. cpp:function::int botan_x509_cert_opts_constraints(botan_x509_cert_opts_t opts, uint32_t usage) + +.. cpp:function::int botan_x509_cert_opts_ex_constraint(botan_x509_cert_opts_t opts, botan_asn1_oid_t oid) + +.. cpp:function::int botan_x509_create_time(botan_x509_time_t* time_obj, uint64_t time_since_epoch) + + Create a new time object. + +.. cpp:function::int int botan_x509_cert_opts_ext_ip_addr_blocks(botan_x509_cert_opts_t opts, \ + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) + +.. cpp:function::int int botan_x509_cert_opts_ext_as_blocks(botan_x509_cert_opts_t opts, botan_x509_ext_as_blocks_t as_blocks) + +.. cpp:function::int botan_x509_ext_create_ip_addr_blocks(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) + + Create a new IP Address Blocks object. + +.. cpp:function::int int botan_x509_ext_create_ip_addr_blocks_from_cert(botan_x509_cert_t cert, \ + botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) + + Get an IP Address Blocks object from a certificate. Cannot be mutated. + +.. cpp:function::int int botan_x509_ext_ip_addr_blocks_add_ip_addr( + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, const uint8_t* min, const uint8_t* max, int ipv6, uint8_t* safi) + + Add a new IP Address to the extension. Set ``ipv6`` to 0 if the address is v4, 1 if it is v6. + ``safi`` may be NULL. + +.. cpp:function::int int botan_x509_ext_ip_addr_blocks_restrict(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi) + + Make the extension contain no allowed IP addresses for the specified IP version. + Set ``ipv6`` to 0 for v4, 1 for v6. ``safi`` may be NULL. + +.. cpp:function::int int botan_x509_ext_ip_addr_blocks_inherit(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi) + + Mark the specified IP version as "inherit". Set ``ipv6`` to 0 for v4, 1 for v6. ``safi`` may be NULL. + +.. cpp:function::int int botan_x509_ext_ip_addr_blocks_get_counts(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, \ + size_t* v4_count, \ + size_t* v6_count) + + Retrieve the counts of v4/v6 entries in the extension. + v4 entries always precede v6 entries. + +.. cpp:function::int int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int ipv6, \ + size_t i, \ + int* has_safi, \ + uint8_t* safi, \ + int* present, \ + size_t* count) + + Retrieve information about an entry in the extension. + Set ``ipv6`` to 0 to indicate the index is v4, 1 to indicate it is v6. + Set ``i`` to the index you want to access. + You can get these values from :cpp:func:`botan_x509_ext_ip_addr_blocks_get_counts`. + ``has_afi`` will be set to 1 if the entry has a SAFI, 0 otherwise. + If a SAFI is present, ``safi`` will be set to its value, otherwise it will not be written to. + ``present`` will be set to one to indicate a value, 0 otherwise (inherit). + ``count`` will be set to the number of address pairs present for the entry if present. + +.. cpp:function::int int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int ipv6, \ + size_t i, \ + size_t entry, \ + uint8_t min_out[], \ + uint8_t max_out[], \ + size_t* out_len) + + Retrieve a single address pair for an entry in the extension. + Retrieve information about an entry in the extension. + Set ``ipv6`` to 0 to indicate the index is v4, 1 to indicate it is v6. + Set ``i`` to the index you want to access. + Set ``entry`` to the index of the address pair you want to access. + ``ipv6`` and ``i`` are retrieved from :cpp:func:`botan_x509_ext_ip_addr_blocks_get_counts`, ``entry`` is the inferred from the ``count`` parameter of :cpp:func:`botan_x509_ext_ip_addr_blocks_get_family`. + ``min_out`` and ``max_out`` will be set to the minimum and maximum of the IP range. + You must provide 4 / 16 bytes of buffer space for each for IP v4 / v6 respectively. + +.. cpp:function::int botan_x509_ext_create_as_blocks(botan_x509_ext_as_blocks_t* as_blocks) + + Create a new AS Blocks object. + +.. cpp:function::int int botan_x509_ext_create_as_blocks_from_cert(botan_x509_cert_t cert, botan_x509_ext_as_blocks_t* as_blocks) + + Get an AS Blocks object from a certificate. Cannot be mutated. + +.. cpp:function::int int botan_x509_ext_as_blocks_add_asnum(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) + + Add an asnum to the extension. + +.. cpp:function::int botan_x509_ext_as_blocks_restrict_asnum(botan_x509_ext_as_blocks_t as_blocks) + + Make the extension contain no allowed asnum's + +.. cpp:function::int botan_x509_ext_as_blocks_inherit_asnum(botan_x509_ext_as_blocks_t as_blocks) + + Mark the asnum entry as "inherit". + +.. cpp:function::int int botan_x509_ext_as_blocks_add_rdi(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) + +.. cpp:function::int botan_x509_ext_as_blocks_restrict_rdi(botan_x509_ext_as_blocks_t as_blocks) + +.. cpp:function::int botan_x509_ext_as_blocks_inherit_rdi(botan_x509_ext_as_blocks_t as_blocks) + +.. cpp:function::int int botan_x509_ext_as_blocks_get_asnum(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count) + + If the extension has an asnum entry, ``present`` will be set to 1, otherwise 0 (inherit). + If an entry is present ``count`` will be set to the number of elements. + +.. cpp:function::int int botan_x509_ext_as_blocks_get_asnum_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max) + + Retrieve information on a single asnum entry. + +.. cpp:function::int int botan_x509_ext_as_blocks_get_rdi(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count) + +.. cpp:function::int int botan_x509_ext_as_blocks_get_rdi_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max) + +.. cpp:function::int int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, \ + botan_privkey_t key, \ + botan_x509_cert_opts_t opts, \ + const char* hash_fn, \ + const char* sig_padding, \ + botan_rng_t rng) + + Create a new self-signed X.509 certificate. + +.. cpp:function::int int botan_x509_create_ca(botan_x509_ca_t* ca_obj, \ + botan_x509_cert_t ca_cert, \ + botan_privkey_t key, \ + const char* hash_fn, \ + const char* sig_padding, \ + botan_rng_t rng) + + Create a CA object capable of signing other certificates. + +.. cpp:function::int int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, \ + botan_x509_cert_opts_t opts, \ + botan_privkey_t key, \ + const char* hash_fn, \ + botan_rng_t rng) + + Create a PCKS #10 certificate request. + +.. cpp:function::int int botan_x509_sign_req(botan_x509_cert_t* cert_obj, \ + botan_x509_ca_t ca, \ + botan_x509_pkcs10_req_t req, \ + botan_rng_t rng, \ + botan_x509_time_t not_before, \ + botan_x509_time_t not_after) + + Sign a PKCS #10 certificate request + X.509 Certificate Revocation Lists ---------------------------------------- diff --git a/doc/api_ref/python.rst b/doc/api_ref/python.rst index f2e633df7dc..dfd70677738 100644 --- a/doc/api_ref/python.rst +++ b/doc/api_ref/python.rst @@ -715,11 +715,155 @@ HOTP in. If the code did verify and resync_range was zero, then the next counter will always be counter+1. +X509Time +----------------------------------------- +.. versionadded:: 3.9.0 + +.. py:class:: X509Time(time_since_epoch) + +PKCS10Req +----------------------------------------- +.. versionadded:: 3.9.0 + +.. py:class:: PKCS10Req() + +X509Opts +----------------------------------------- +.. versionadded:: 3.9.0 + +.. py:class:: X509Opts(opts, expire_time=None) + + .. py:method:: set_common_name(name) + + Set the common name for the certificate. + + .. py:method:: set_country(country) + + .. py:method:: set_organization(organization) + + .. py:method:: set_org_unit(org_unit) + + .. py:method:: set_locality(locality) + + .. py:method:: set_state(state) + + .. py:method:: set_serial_number(serial_number) + + .. py:method:: set_email(email) + + .. py:method:: set_uri(uri) + + .. py:method:: set_ip(ip) + + .. py:method:: set_dns(dns) + + .. py:method:: set_xmpp(xmpp) + + .. py:method:: set_challenge(challenge) + + .. py:method:: set_more_org_units(more_org_units) + + ``more_org_units`` is expected to be a of type ``list[string]`` + + .. py:method:: set_more_dns(more_dns) + + ``more_dns`` is expected to be a of type ``list[string]`` + + .. py:method:: set_ca_key(limit) + + .. py:method:: set_padding_scheme(scheme) + + .. py:method:: set_not_before(not_before) + + .. py:method:: set_not_after(not_after) + + .. py:method:: set_constraints(usage_list) + + .. py:method:: add_ex_constraints(oid) + + .. py:method:: create_req(key, hash_fn, rng) + + Create a PKCS #10 certificate request that can later be signed. + + .. py:method:: add_ext_ip_addr_blocks(ip_addr_blocks) + + .. py:method:: add_ext_as_blocks(as_blocks) + +X509ExtIPAddrBlocks +----------------------------------------- + +.. versionadded:: 3.9.0 + +.. py:class:: X509ExtIPAddrBlocks(cert=None) + + .. py:method:: add_ip(ip, safi=None) + + Add a single IP address to the extension. ``ip`` is expected to be a ``list[int]`` + of length 4/16 for IPv4/IPv6. + + .. py:method:: add_ip_range(min_, max_, safi=None) + + Add an IP address range to the extension. + + .. py:method:: restrict(ipv6, safi=None) + + Make the extension contain no allowed IP addresses for the given SAFI (if any). + Set ``ipv6`` to True to indicate IPv6, False for IPv4. + + .. py:method:: inherit(ipv6, safi=None) + + Mark the specified IP version and SAFI (if any) as "inherit". + + .. py:method:: addresses() + + Get the IP addresses registered in the extension. + +X509ExtASBlocks +----------------------------------------- + +.. versionadded:: 3.9.0 + +.. py:class:: X509ExtASBlocks(cert=None) + + .. py:method:: add_asnum(asnum): + + Add a single asnum to the extension. + + .. py:method:: add_asnum_range(min_, max_) + + Add an asnum range to the extension. + + .. py:method:: restrict_asnum() + + Make the extension contain no allowed asnum's. + + .. py:method:: inherit_asnum() + + Mark the asnum entry as "inherit". + + .. py:method:: add_rdi(rdi): + + .. py:method:: add_rdi_range(min_, max_) + + .. py:method:: restrict_rdi() + + .. py:method:: inherit_rdi() + + .. py:method:: asnum() + + Get the asnum(s) registered in the extension. + + .. py:method:: rdi() + X509Cert ----------------------------------------- .. py:class:: X509Cert(filename=None, buf=None) + .. py:classmethod:: create_self_signed(key, opts, hash_fn, sig_padding, rng) + + Create a self-signed certificate from the given certificate options. + .. py:method:: time_starts() Return the time the certificate becomes valid, as a string in form @@ -736,6 +880,10 @@ X509Cert Format the certificate as a free-form string. + .. py:method:: to_pem() + + Format the certificate as a PEM string. + .. py:method:: fingerprint(hash_algo='SHA-256') Return a fingerprint for the certificate, which is basically just a hash @@ -792,6 +940,30 @@ X509Cert Also return True if the certificate doesn't have this extension. Example usage constraints are: ``"DIGITAL_SIGNATURE"``, ``"KEY_CERT_SIGN"``, ``"CRL_SIGN"``. + .. py:method:: key_constraints() + + Return a list of all the key constraints listed in the certificate. + + .. py:method:: is_ca() + + Return (True, limit) if the certificate is marked for CA usage, else (False, 0) + + .. py:method:: ocsp_responder() + + Return the OCSP responder. + + .. py:method:: is_self_signed() + + Return True if the certificate was self-signed. + + .. py:method:: ext_ip_addr_blocks() + + Return the certificate's IP Address Blocks extension. + + .. py:method:: ext_as_blocks() + + Return the certificate's AS Blocks extension. + .. py:method:: verify(intermediates=None, \ trusted=None, \ trusted_path=None, \ @@ -830,6 +1002,17 @@ X509Cert Check if the certificate (``self``) is revoked on the given ``crl``. +X509Ca +----------------------------------------- + +.. versionadded:: 3.9.0 + +.. py:class:: X509Ca(cert, key, rng, has_fn, sig_padding="") + + .. py:method:: sign(req, rng, not_before, not_after) + + Sign a PKCS #10 certificate request. + X509CRL ----------------------------------------- diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index f06cd6e2a71..98e5cc7199a 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -2219,14 +2219,16 @@ int botan_x509_cert_verify(int* validation_result, BOTAN_FFI_EXPORT(2, 8) const char* botan_x509_cert_validation_status(int code); typedef struct botan_x509_cert_opts_struct* botan_x509_cert_opts_t; +typedef struct botan_x509_time_struct* botan_x509_time_t; typedef struct botan_x509_ext_as_blocks_struct* botan_x509_ext_as_blocks_t; typedef struct botan_x509_ext_ip_addr_blocks_struct* botan_x509_ext_ip_addr_blocks_t; typedef struct botan_x509_ca_struct* botan_x509_ca_t; typedef struct botan_x509_pkcs10_req_struct* botan_x509_pkcs10_req_t; -typedef struct botan_x509_time_struct* botan_x509_time_t; BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_destroy(botan_x509_cert_opts_t opts); +BOTAN_FFI_EXPORT(3, 9) int botan_x509_time_destroy(botan_x509_time_t time); + BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks); @@ -2235,8 +2237,6 @@ BOTAN_FFI_EXPORT(3, 9) int botan_x509_ca_destroy(botan_x509_ca_t ca); BOTAN_FFI_EXPORT(3, 9) int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_time_destroy(botan_x509_time_t time); - BOTAN_FFI_EXPORT(3, 9) int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* opts, uint32_t* expire_time); @@ -2274,28 +2274,30 @@ int botan_x509_cert_opts_more_dns(botan_x509_cert_opts_t opts, const char** more BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_ca_key(botan_x509_cert_opts_t opts, size_t limit); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_set_padding_scheme(botan_x509_cert_opts_t opts, const char* scheme); +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_padding_scheme(botan_x509_cert_opts_t opts, const char* scheme); BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_not_before(botan_x509_cert_opts_t opts, botan_x509_time_t not_before); BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_not_after(botan_x509_cert_opts_t opts, botan_x509_time_t not_after); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_add_constraints(botan_x509_cert_opts_t opts, uint32_t usage); +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_constraints(botan_x509_cert_opts_t opts, uint32_t usage); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_add_ex_constraint(botan_x509_cert_opts_t opts, botan_asn1_oid_t oid); +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_ex_constraint(botan_x509_cert_opts_t opts, botan_asn1_oid_t oid); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_opts_add_ext_ip_addr_blocks(botan_x509_cert_opts_t opts, - botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); +int botan_x509_cert_opts_ext_ip_addr_blocks(botan_x509_cert_opts_t opts, + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_opts_add_ext_as_blocks(botan_x509_cert_opts_t opts, botan_x509_ext_as_blocks_t as_blocks); +int botan_x509_cert_opts_ext_as_blocks(botan_x509_cert_opts_t opts, botan_x509_ext_as_blocks_t as_blocks); + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_create_time(botan_x509_time_t* time_obj, uint64_t time_since_epoch); BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_create_ip_addr_blocks(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_ext_create_ip_addr_blocks_from_cert(botan_x509_cert_t cert, - botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks); +int botan_x509_ext_create_ip_addr_blocks_from_cert(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks, + botan_x509_cert_t cert); BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_ip_addr_blocks_add_ip_addr( @@ -2319,7 +2321,7 @@ int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_ext_ip_addr_blocks_t ip_ int* has_safi, uint8_t* safi, int* present, - int* count); + size_t* count); BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, @@ -2333,7 +2335,7 @@ int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_create_as_blocks(botan_x509_ext_as_blocks_t* as_blocks); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_ext_create_as_blocks_from_cert(botan_x509_cert_t cert, botan_x509_ext_as_blocks_t* as_blocks); +int botan_x509_ext_create_as_blocks_from_cert(botan_x509_ext_as_blocks_t* as_blocks, botan_x509_cert_t cert); BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_add_asnum(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max); @@ -2392,8 +2394,6 @@ int botan_x509_sign_req(botan_x509_cert_t* cert_obj, botan_x509_time_t not_before, botan_x509_time_t not_after); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_create_time(botan_x509_time_t* time_obj, uint64_t time_since_epoch); - /* * X.509 CRL **************************/ diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index 196e10c3385..e6370d815bb 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -227,6 +227,7 @@ int botan_x509_is_self_signed(botan_x509_cert_t cert, int* out) { } else { *out = 0; } + return BOTAN_FFI_SUCCESS; }); #else BOTAN_UNUSED(cert, out) @@ -608,9 +609,7 @@ int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* op } else { co = std::make_unique(opts); } - - *opts_obj = new botan_x509_cert_opts_struct(std::move(co)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(opts_obj, std::move(co)); }); #else BOTAN_UNUSED(expire_time); @@ -699,7 +698,7 @@ int botan_x509_cert_opts_ca_key(botan_x509_cert_opts_t opts, size_t limit) { #endif } -int botan_x509_cert_opts_set_padding_scheme(botan_x509_cert_opts_t opts, const char* scheme) { +int botan_x509_cert_opts_padding_scheme(botan_x509_cert_opts_t opts, const char* scheme) { if(scheme == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -739,7 +738,7 @@ int botan_x509_cert_opts_not_after(botan_x509_cert_opts_t opts, botan_x509_time_ #endif } -int botan_x509_cert_opts_add_constraints(botan_x509_cert_opts_t opts, uint32_t usage) { +int botan_x509_cert_opts_constraints(botan_x509_cert_opts_t opts, uint32_t usage) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(opts, [=](auto& o) { o.add_constraints(Botan::Key_Constraints(usage)); }); #else @@ -748,7 +747,7 @@ int botan_x509_cert_opts_add_constraints(botan_x509_cert_opts_t opts, uint32_t u #endif } -int botan_x509_cert_opts_add_ex_constraint(botan_x509_cert_opts_t opts, botan_asn1_oid_t oid) { +int botan_x509_cert_opts_ex_constraint(botan_x509_cert_opts_t opts, botan_asn1_oid_t oid) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { safe_get(opts).add_ex_constraint(safe_get(oid)); @@ -760,8 +759,24 @@ int botan_x509_cert_opts_add_ex_constraint(botan_x509_cert_opts_t opts, botan_as #endif } -int botan_x509_cert_opts_add_ext_ip_addr_blocks(botan_x509_cert_opts_t opts, - botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) { +int botan_x509_create_time(botan_x509_time_t* time_obj, uint64_t time_since_epoch) { + if(time_obj == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto tp = std::chrono::system_clock::time_point(std::chrono::seconds(time_since_epoch)); + auto time = std::make_unique(tp); + return ffi_new_object(time_obj, std::move(time)); + }); +#else + BOTAN_UNUSED(time_since_epoch); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_opts_ext_ip_addr_blocks(botan_x509_cert_opts_t opts, + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { safe_get(opts).extensions.add(safe_get(ip_addr_blocks).copy()); @@ -773,7 +788,7 @@ int botan_x509_cert_opts_add_ext_ip_addr_blocks(botan_x509_cert_opts_t opts, #endif } -int botan_x509_cert_opts_add_ext_as_blocks(botan_x509_cert_opts_t opts, botan_x509_ext_as_blocks_t as_blocks) { +int botan_x509_cert_opts_ext_as_blocks(botan_x509_cert_opts_t opts, botan_x509_ext_as_blocks_t as_blocks) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { safe_get(opts).extensions.add(safe_get(as_blocks).copy()); @@ -798,8 +813,7 @@ int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, return ffi_guard_thunk(__func__, [=]() -> int { auto ca_cert = std::make_unique( Botan::X509::create_self_signed_cert(safe_get(opts), safe_get(key), hash_fn, safe_get(rng))); - *cert_obj = new botan_x509_cert_struct(std::move(ca_cert)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(cert_obj, std::move(ca_cert)); }); #else BOTAN_UNUSED(key, opts, rng); @@ -819,8 +833,7 @@ int botan_x509_create_ca(botan_x509_ca_t* ca_obj, #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { auto ca = std::make_unique(safe_get(ca_cert), safe_get(key), hash_fn, sig_padding, safe_get(rng)); - *ca_obj = new botan_x509_ca_struct(std::move(ca)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(ca_obj, std::move(ca)); }); #else BOTAN_UNUSED(ca_cert, key, rng); @@ -840,8 +853,7 @@ int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, return ffi_guard_thunk(__func__, [=]() -> int { auto req = std::make_unique( Botan::X509::create_cert_req(safe_get(opts), safe_get(key), hash_fn, safe_get(rng))); - *req_obj = new botan_x509_pkcs10_req_struct(std::move(req)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(req_obj, std::move(req)); }); #else BOTAN_UNUSED(opts, key, rng); @@ -862,29 +874,11 @@ int botan_x509_sign_req(botan_x509_cert_t* cert_obj, return ffi_guard_thunk(__func__, [=]() -> int { auto cert = std::make_unique(safe_get(ca).sign_request( safe_get(req), safe_get(rng), safe_get(not_before), safe_get(not_after))); - *cert_obj = new botan_x509_cert_struct(std::move(cert)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(cert_obj, std::move(cert)); }); #else BOTAN_UNUSED(ca, req, rng, not_before, not_after); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } - -int botan_x509_create_time(botan_x509_time_t* time_obj, uint64_t time_since_epoch) { - if(time_obj == nullptr) { - return BOTAN_FFI_ERROR_NULL_POINTER; - } -#if defined(BOTAN_HAS_X509_CERTIFICATES) - return ffi_guard_thunk(__func__, [=]() -> int { - auto tp = std::chrono::system_clock::time_point(std::chrono::seconds(time_since_epoch)); - auto time = std::make_unique(tp); - *time_obj = new botan_x509_time_struct(std::move(time)); - return BOTAN_FFI_SUCCESS; - }); -#else - BOTAN_UNUSED(time_since_epoch); - return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; -#endif -} } diff --git a/src/lib/ffi/ffi_cert.h b/src/lib/ffi/ffi_cert.h index 0d15c3fa089..e333a3daf82 100644 --- a/src/lib/ffi/ffi_cert.h +++ b/src/lib/ffi/ffi_cert.h @@ -26,9 +26,9 @@ extern "C" { BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_struct, Botan::X509_Certificate, 0x8F628937); BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910); BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_opts_struct, Botan::X509_Cert_Options, 0x92597C7D); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_time_struct, Botan::X509_Time, 0x739396FA); BOTAN_FFI_DECLARE_STRUCT(botan_x509_ca_struct, Botan::X509_CA, 0x8BE2A8F1); BOTAN_FFI_DECLARE_STRUCT(botan_x509_pkcs10_req_struct, Botan::PKCS10_Request, 0x87F0690A); -BOTAN_FFI_DECLARE_STRUCT(botan_x509_time_struct, Botan::X509_Time, 0x739396FA); #endif } diff --git a/src/lib/ffi/ffi_x509_rpki.cpp b/src/lib/ffi/ffi_x509_rpki.cpp index 6556e5adaa1..802df7e24cb 100644 --- a/src/lib/ffi/ffi_x509_rpki.cpp +++ b/src/lib/ffi/ffi_x509_rpki.cpp @@ -17,21 +17,20 @@ template void ip_addr_blocks_ext_add_address(Botan::Cert_Extension::IPAddressBlocks& ext, const uint8_t* min, const uint8_t* max, - uint8_t* safi) { + std::optional safi) { const size_t version_octets = static_cast(V); - std::optional safi_ = safi == nullptr ? std::nullopt : std::optional(*safi); - std::array min_; - std::array max_; + std::array min_{}; + std::array max_{}; std::copy(min, min + version_octets, min_.begin()); std::copy(max, max + version_octets, max_.begin()); - ext.add_address(min_, max_, safi_); + ext.add_address(min_, max_, safi); } template int ip_addr_blocks_get_family(const Botan::Cert_Extension::IPAddressBlocks::IPAddressFamily& family, int* present, - int* count) { + size_t* count) { if(!std::holds_alternative>(family.addr_choice())) { return BOTAN_FFI_ERROR_BAD_PARAMETER; } @@ -101,31 +100,30 @@ int botan_x509_ext_create_ip_addr_blocks(botan_x509_ext_ip_addr_blocks_t* ip_add #if defined BOTAN_HAS_X509_CERTIFICATES return ffi_guard_thunk(__func__, [=]() -> int { auto ext = std::make_unique(); - *ip_addr_blocks = new botan_x509_ext_ip_addr_blocks_struct(std::move(ext)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(ip_addr_blocks, std::move(ext), true); }); #else return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_ext_create_ip_addr_blocks_from_cert(botan_x509_cert_t cert, - botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) { +int botan_x509_ext_create_ip_addr_blocks_from_cert(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks, + botan_x509_cert_t cert) { if(ip_addr_blocks == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - auto ext = safe_get(cert).v3_extensions().get_extension_object_as(); + const auto* const ext = + safe_get(cert).v3_extensions().get_extension_object_as(); if(ext == nullptr) { return BOTAN_FFI_ERROR_NO_VALUE; } - - *ip_addr_blocks = - new botan_x509_ext_ip_addr_blocks_struct(std::unique_ptr( - dynamic_cast(ext->copy().release()))); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(ip_addr_blocks, + std::unique_ptr( + dynamic_cast(ext->copy().release())), + false); }); #else BOTAN_UNUSED(cert); @@ -145,11 +143,19 @@ int botan_x509_ext_ip_addr_blocks_add_ip_addr( } auto& ext = safe_get(ip_addr_blocks); + if(!ip_addr_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + + std::optional safi_; + if(safi != nullptr) { + safi_ = *safi; + } if(ipv6 == 0) { - ip_addr_blocks_ext_add_address(ext, min, max, safi); + ip_addr_blocks_ext_add_address(ext, min, max, safi_); } else { - ip_addr_blocks_ext_add_address(ext, min, max, safi); + ip_addr_blocks_ext_add_address(ext, min, max, safi_); } return BOTAN_FFI_SUCCESS; @@ -168,7 +174,14 @@ int botan_x509_ext_ip_addr_blocks_restrict(botan_x509_ext_ip_addr_blocks_t ip_ad } auto& ext = safe_get(ip_addr_blocks); - std::optional safi_ = safi == nullptr ? std::nullopt : std::optional(*safi); + if(!ip_addr_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + + std::optional safi_; + if(safi != nullptr) { + safi_ = *safi; + } if(ipv6 == 0) { ext.restrict(safi_); @@ -191,7 +204,14 @@ int botan_x509_ext_ip_addr_blocks_inherit(botan_x509_ext_ip_addr_blocks_t ip_add } auto& ext = safe_get(ip_addr_blocks); - std::optional safi_ = safi == nullptr ? std::nullopt : std::optional(*safi); + if(!ip_addr_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + + std::optional safi_; + if(safi != nullptr) { + safi_ = *safi; + } if(ipv6 == 0) { ext.inherit(safi_); @@ -242,7 +262,7 @@ int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_ext_ip_addr_blocks_t ip_ int* has_safi, uint8_t* safi, int* present, - int* count) { + size_t* count) { if(has_safi == nullptr || safi == nullptr || present == nullptr || count == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -322,29 +342,28 @@ int botan_x509_ext_create_as_blocks(botan_x509_ext_as_blocks_t* as_blocks) { #if defined BOTAN_HAS_X509_CERTIFICATES return ffi_guard_thunk(__func__, [=]() -> int { auto ext = std::make_unique(); - *as_blocks = new botan_x509_ext_as_blocks_struct(std::move(ext)); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(as_blocks, std::move(ext), true); }); #else return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_ext_create_as_blocks_from_cert(botan_x509_cert_t cert, botan_x509_ext_as_blocks_t* as_blocks) { +int botan_x509_ext_create_as_blocks_from_cert(botan_x509_ext_as_blocks_t* as_blocks, botan_x509_cert_t cert) { if(as_blocks == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - auto ext = safe_get(cert).v3_extensions().get_extension_object_as(); + const auto* const ext = safe_get(cert).v3_extensions().get_extension_object_as(); if(ext == nullptr) { return BOTAN_FFI_ERROR_NO_VALUE; } - - *as_blocks = new botan_x509_ext_as_blocks_struct(std::unique_ptr( - dynamic_cast(ext->copy().release()))); - return BOTAN_FFI_SUCCESS; + return ffi_new_object(as_blocks, + std::unique_ptr( + dynamic_cast(ext->copy().release())), + false); }); #else BOTAN_UNUSED(cert); @@ -355,7 +374,11 @@ int botan_x509_ext_create_as_blocks_from_cert(botan_x509_cert_t cert, botan_x509 int botan_x509_ext_as_blocks_add_asnum(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(as_blocks).add_asnum(min, max); + auto& ext = safe_get(as_blocks); + if(!as_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + ext.add_asnum(min, max); return BOTAN_FFI_SUCCESS; }); #else @@ -367,7 +390,11 @@ int botan_x509_ext_as_blocks_add_asnum(botan_x509_ext_as_blocks_t as_blocks, uin int botan_x509_ext_as_blocks_restrict_asnum(botan_x509_ext_as_blocks_t as_blocks) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(as_blocks).restrict_asnum(); + auto& ext = safe_get(as_blocks); + if(!as_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + ext.restrict_asnum(); return BOTAN_FFI_SUCCESS; }); #else @@ -379,7 +406,11 @@ int botan_x509_ext_as_blocks_restrict_asnum(botan_x509_ext_as_blocks_t as_blocks int botan_x509_ext_as_blocks_inherit_asnum(botan_x509_ext_as_blocks_t as_blocks) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(as_blocks).inherit_asnum(); + auto& ext = safe_get(as_blocks); + if(!as_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + ext.inherit_asnum(); return BOTAN_FFI_SUCCESS; }); #else @@ -391,7 +422,11 @@ int botan_x509_ext_as_blocks_inherit_asnum(botan_x509_ext_as_blocks_t as_blocks) int botan_x509_ext_as_blocks_add_rdi(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(as_blocks).add_rdi(min, max); + auto& ext = safe_get(as_blocks); + if(!as_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + ext.add_rdi(min, max); return BOTAN_FFI_SUCCESS; }); #else @@ -403,7 +438,11 @@ int botan_x509_ext_as_blocks_add_rdi(botan_x509_ext_as_blocks_t as_blocks, uint3 int botan_x509_ext_as_blocks_restrict_rdi(botan_x509_ext_as_blocks_t as_blocks) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(as_blocks).restrict_rdi(); + auto& ext = safe_get(as_blocks); + if(!as_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + ext.restrict_rdi(); return BOTAN_FFI_SUCCESS; }); #else @@ -415,7 +454,11 @@ int botan_x509_ext_as_blocks_restrict_rdi(botan_x509_ext_as_blocks_t as_blocks) int botan_x509_ext_as_blocks_inherit_rdi(botan_x509_ext_as_blocks_t as_blocks) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(as_blocks).inherit_rdi(); + auto& ext = safe_get(as_blocks); + if(!as_blocks->writable()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + ext.inherit_rdi(); return BOTAN_FFI_SUCCESS; }); #else diff --git a/src/lib/ffi/ffi_x509_rpki.h b/src/lib/ffi/ffi_x509_rpki.h index a5e61497999..af80a171b3c 100644 --- a/src/lib/ffi/ffi_x509_rpki.h +++ b/src/lib/ffi/ffi_x509_rpki.h @@ -15,10 +15,34 @@ #endif extern "C" { + +using namespace Botan_FFI; + #if defined(BOTAN_HAS_X509_CERTIFICATES) -BOTAN_FFI_DECLARE_STRUCT(botan_x509_ext_as_blocks_struct, Botan::Cert_Extension::ASBlocks, 0xA56348EC); -BOTAN_FFI_DECLARE_STRUCT(botan_x509_ext_ip_addr_blocks_struct, Botan::Cert_Extension::IPAddressBlocks, 0xB489828F); +struct botan_x509_ext_as_blocks_struct final : public botan_struct { + public: + explicit botan_x509_ext_as_blocks_struct(std::unique_ptr obj, bool writable) : + botan_struct(std::move(obj)), m_writable(writable) {} + + bool writable() const { return m_writable; } + + private: + bool m_writable; +}; + +struct botan_x509_ext_ip_addr_blocks_struct final + : public botan_struct { + public: + explicit botan_x509_ext_ip_addr_blocks_struct(std::unique_ptr obj, + bool writable) : + botan_struct(std::move(obj)), m_writable(writable) {} + + bool writable() const { return m_writable; } + + private: + bool m_writable; +}; #endif } diff --git a/src/python/botan3.py b/src/python/botan3.py index d8c279ab43d..1063eb11342 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -531,13 +531,14 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_cert_opts_more_org_units, [c_void_p, POINTER(c_char_p), c_size_t]) ffi_api(dll.botan_x509_cert_opts_more_dns, [c_void_p, POINTER(c_char_p), c_size_t]) ffi_api(dll.botan_x509_cert_opts_ca_key, [c_void_p, c_size_t]) - ffi_api(dll.botan_x509_cert_opts_set_padding_scheme, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_opts_padding_scheme, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_cert_opts_not_before, [c_void_p, c_void_p]) ffi_api(dll.botan_x509_cert_opts_not_after, [c_void_p, c_void_p]) - ffi_api(dll.botan_x509_cert_opts_add_constraints, [c_void_p, c_uint32]) - ffi_api(dll.botan_x509_cert_opts_add_ex_constraint, [c_void_p, c_void_p]) - ffi_api(dll.botan_x509_cert_opts_add_ext_ip_addr_blocks, [c_void_p, c_void_p]) - ffi_api(dll.botan_x509_cert_opts_add_ext_as_blocks, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_opts_constraints, [c_void_p, c_uint32]) + ffi_api(dll.botan_x509_cert_opts_ex_constraint, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_opts_ext_ip_addr_blocks, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_opts_ext_as_blocks, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_create_time, [c_void_p, c_uint64]) ffi_api(dll.botan_x509_ext_create_ip_addr_blocks, [c_void_p]) ffi_api(dll.botan_x509_ext_create_ip_addr_blocks_from_cert, [c_void_p, c_void_p]) ffi_api(dll.botan_x509_ext_ip_addr_blocks_add_ip_addr, @@ -546,7 +547,7 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_ext_ip_addr_blocks_inherit, [c_void_p, c_int, POINTER(c_uint8)]) ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_counts, [c_void_p, POINTER(c_size_t), POINTER(c_size_t)]) ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_family, - [c_void_p, c_int, c_size_t, POINTER(c_int), POINTER(c_uint8), POINTER(c_int), POINTER(c_int)]) + [c_void_p, c_int, c_size_t, POINTER(c_int), POINTER(c_uint8), POINTER(c_int), POINTER(c_size_t)]) ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_address, [c_void_p, c_int, c_size_t, c_size_t, c_char_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_ext_create_as_blocks, [c_void_p]) @@ -569,7 +570,6 @@ def ffi_api(fn, args, allowed_errors=None): [c_void_p, c_void_p, c_void_p, c_char_p, c_void_p]) ffi_api(dll.botan_x509_sign_req, [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p]) - ffi_api(dll.botan_x509_create_time, [c_void_p, c_uint64]) dll.botan_x509_cert_validation_status.argtypes = [c_int] dll.botan_x509_cert_validation_status.restype = c_char_p @@ -1957,7 +1957,7 @@ def set_ca_key(self, limit): _DLL.botan_x509_cert_opts_ca_key(self.__obj, c_size_t(limit)) def set_padding_scheme(self, scheme): - _DLL.botan_x509_cert_opts_set_padding_scheme(self.__obj, _ctype_str(scheme)) + _DLL.botan_x509_cert_opts_padding_scheme(self.__obj, _ctype_str(scheme)) def set_not_before(self, not_before): _DLL.botan_x509_cert_opts_not_before(self.__obj, not_before.handle_()) @@ -1982,10 +1982,10 @@ def set_constraints(self, usage_list): if u not in usage_values: pass usage += usage_values[u] - _DLL.botan_x509_cert_opts_add_constraints(self.__obj, c_uint32(usage)) + _DLL.botan_x509_cert_opts_constraints(self.__obj, c_uint32(usage)) def add_ex_constraints(self, oid): - _DLL.botan_x509_cert_opts_add_ex_constraint(self.__obj, oid.handle_()) + _DLL.botan_x509_cert_opts_ex_constraint(self.__obj, oid.handle_()) def create_req(self, key, hash_fn, rng): req = PKCS10Req() @@ -1993,18 +1993,17 @@ def create_req(self, key, hash_fn, rng): return req def add_ext_ip_addr_blocks(self, ip_addr_blocks): - _DLL.botan_x509_cert_opts_add_ext_ip_addr_blocks(self.__obj, ip_addr_blocks.handle_()) + _DLL.botan_x509_cert_opts_ext_ip_addr_blocks(self.__obj, ip_addr_blocks.handle_()) def add_ext_as_blocks(self, as_blocks): - _DLL.botan_x509_cert_opts_add_ext_as_blocks(self.__obj, as_blocks.handle_()) + _DLL.botan_x509_cert_opts_ext_as_blocks(self.__obj, as_blocks.handle_()) class X509ExtIPAddrBlocks: def __init__(self, cert=None): self.__obj = c_void_p(0) - self.__writable = cert is None if cert: - _DLL.botan_x509_ext_create_ip_addr_blocks_from_cert(cert.handle_(), byref(self.__obj)) + _DLL.botan_x509_ext_create_ip_addr_blocks_from_cert(byref(self.__obj), cert.handle_()) else: _DLL.botan_x509_ext_create_ip_addr_blocks(byref(self.__obj)) @@ -2017,29 +2016,21 @@ def handle_(self): def add_ip(self, ip, safi=None): self.add_ip_range(ip, ip, safi) - def add_ip_range(self, min, max, safi=None): - if not self.__writable: - raise BotanException("Extension is read-only") - min_len = len(min) - if (min_len != 4 and min_len != 16) or len(max) != min_len: + def add_ip_range(self, min_, max_, safi=None): + min_len = len(min_) + if min_len not in (4, 16) or len(max_) != min_len: raise BotanException("Address must be 4 or 16 bytes long") ipv6 = 1 if min_len == 16 else 0 safi = byref(c_uint8(safi)) if safi is not None else None - _DLL.botan_x509_ext_ip_addr_blocks_add_ip_addr(self.__obj, bytes(min), bytes(max), c_int(ipv6), safi) + _DLL.botan_x509_ext_ip_addr_blocks_add_ip_addr(self.__obj, bytes(min_), bytes(max_), c_int(ipv6), safi) def restrict(self, ipv6, safi=None): - if not self.__writable: - raise BotanException("Extension is read-only") - ipv6 = 1 if ipv6 else 0 safi = byref(c_uint8(safi)) if safi is not None else None _DLL.botan_x509_ext_ip_addr_blocks_restrict(self.__obj, c_int(ipv6), safi) def inherit(self, ipv6, safi=None): - if not self.__writable: - raise BotanException("Extension is read-only") - ipv6 = 1 if ipv6 else 0 safi = byref(c_uint8(safi)) if safi is not None else None _DLL.botan_x509_ext_ip_addr_blocks_inherit(self.__obj, c_int(ipv6), safi) @@ -2063,14 +2054,14 @@ def addresses(self): has_safi = c_int(0) safi = c_uint8(0) present = c_int(0) - count = c_int(0) + count = c_size_t(0) _DLL.botan_x509_ext_ip_addr_blocks_get_family(self.__obj, c_int(ipv6), c_size_t(i), byref(has_safi), byref(safi), byref(present), byref(count)) ranges = None if present.value == 1: ranges = [] for entry in range(count.value): min_, max_ = _call_fn_returning_vec_pair( - size, size, lambda mi, _, ma, l: _DLL.botan_x509_ext_ip_addr_blocks_get_address( + size, size, lambda mi, _, ma, l, ipv6=ipv6, i=i, entry=entry: _DLL.botan_x509_ext_ip_addr_blocks_get_address( self.__obj, c_int(ipv6), c_size_t(i), @@ -2087,18 +2078,14 @@ def addresses(self): else: v6.append((safi, ranges)) - return { - 4: v4, - 6: v6 - } + return (v4, v6) class X509ExtASBlocks: def __init__(self, cert=None): self.__obj = c_void_p(0) - self.__writable = cert is None if cert: - _DLL.botan_x509_ext_create_as_blocks_from_cert(cert.handle_(), byref(self.__obj)) + _DLL.botan_x509_ext_create_as_blocks_from_cert(byref(self.__obj), cert.handle_()) else: _DLL.botan_x509_ext_create_as_blocks(byref(self.__obj)) @@ -2111,37 +2098,25 @@ def handle_(self): def add_asnum(self, asnum): self.add_asnum_range(asnum, asnum) - def add_asnum_range(self, min, max): - if not self.__writable: - raise BotanException("Extension is read-only") - _DLL.botan_x509_ext_as_blocks_add_asnum(self.__obj, c_uint32(min), c_uint32(max)) + def add_asnum_range(self, min_, max_): + _DLL.botan_x509_ext_as_blocks_add_asnum(self.__obj, c_uint32(min_), c_uint32(max_)) def restrict_asnum(self): - if not self.__writable: - raise BotanException("Extension is read-only") _DLL.botan_x509_ext_as_blocks_restrict_asnum(self.__obj) def inherit_asnum(self): - if not self.__writable: - raise BotanException("Extension is read-only") _DLL.botan_x509_ext_as_blocks_inherit_asnum(self.__obj) def add_rdi(self, rdi): self.add_rdi_range(rdi, rdi) - def add_rdi_range(self, min, max): - if not self.__writable: - raise BotanException("Extension is read-only") - _DLL.botan_x509_ext_as_blocks_add_rdi(self.__obj, c_uint32(min), c_uint32(max)) + def add_rdi_range(self, min_, max_): + _DLL.botan_x509_ext_as_blocks_add_rdi(self.__obj, c_uint32(min_), c_uint32(max_)) def restrict_rdi(self): - if not self.__writable: - raise BotanException("Extension is read-only") _DLL.botan_x509_ext_as_blocks_restrict_rdi(self.__obj) def inherit_rdi(self): - if not self.__writable: - raise BotanException("Extension is read-only") _DLL.botan_x509_ext_as_blocks_inherit_rdi(self.__obj) def asnum(self): diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 95fb78eea7d..9e92466c5af 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -851,6 +851,12 @@ def test_cert_creation(self): self.assertFalse(cert.is_self_signed()) self.assertEqual(cert.key_constraints(), ["NO_CONSTRAINTS"]) + with self.assertRaisesRegex(botan.BotanException, r".*No value available.*"): + _ = cert.ext_ip_addr_blocks() + + with self.assertRaisesRegex(botan.BotanException, r".*No value available.*"): + _ = cert.ext_as_blocks() + self.assertEqual(cert.verify(None, [ca_cert]), 0) def test_x509_rpki(self): @@ -884,25 +890,28 @@ def test_x509_rpki(self): ca_cert = botan.X509Cert.create_self_signed(ca_key, ca_opts, hash_fn, padding_method, rng) ca_ip_addr_blocks = ca_cert.ext_ip_addr_blocks() - self.assertEqual(ca_ip_addr_blocks.addresses(), { - 4: [ + self.assertEqual(ca_ip_addr_blocks.addresses(), ( + [ (None, (([10, 0, 0, 1], [10, 0, 255, 255]), ([192, 168, 2, 1], [192, 168, 2, 1]))), (42, ()) ], - 6: [ + [ (None, (( [0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], [0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01] ), )), (234, ()) ] - }) + )) + + with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): + ca_ip_addr_blocks.add_ip([123, 0, 0, 1]) ca_as_blocks = ca_cert.ext_as_blocks() self.assertEqual(ca_as_blocks.asnum(), [(30, 30), (3000, 4999)]) self.assertEqual(ca_as_blocks.rdi(), []) - with self.assertRaisesRegex(botan.BotanException, r".*read-only.*"): + with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): ca_as_blocks.add_asnum(999) ca = botan.X509Ca(ca_cert, ca_key, rng, hash_fn) @@ -928,16 +937,16 @@ def test_x509_rpki(self): cert = ca.sign(req, rng, botan.X509Time(int(time.time())), botan.X509Time(int(time.time()) + 60)) req_ip_addr_blocks = cert.ext_ip_addr_blocks() - self.assertEqual(req_ip_addr_blocks.addresses(), { - 4: [ + self.assertEqual(req_ip_addr_blocks.addresses(), ( + [ (None, (([10, 0, 5, 5], [10, 0, 7, 7]), ([192, 168, 2, 1], [192, 168, 2, 1]))), (42, ()) ], - 6: [ + [ (None, None), (234, None) ] - }) + )) req_as_blocks = cert.ext_as_blocks() self.assertEqual(req_as_blocks.asnum(), [(3100, 4000)]) diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index 42af00a915e..b5c0b6a5f79 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -918,7 +918,7 @@ class FFI_X509_RPKI_Test final : public FFI_Test { int has_safi; uint8_t safi; int present; - int count; + size_t count; TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, botan_x509_ext_ip_addr_blocks_get_family, @@ -985,9 +985,43 @@ class FFI_X509_RPKI_Test final : public FFI_Test { result.confirm("index 1 has a value", present == 1); result.confirm("index 1 has entries", count == 0); - TEST_FFI_OK(botan_x509_cert_opts_add_ext_ip_addr_blocks, (ca_opts, ca_ip_addr_blocks)); + TEST_FFI_OK(botan_x509_cert_opts_ext_ip_addr_blocks, (ca_opts, ca_ip_addr_blocks)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (ca_ip_addr_blocks)); + botan_x509_ext_as_blocks_t ca_as_blocks; + TEST_FFI_OK(botan_x509_ext_create_as_blocks, (&ca_as_blocks)); + + TEST_FFI_OK(botan_x509_ext_as_blocks_add_asnum, (ca_as_blocks, 0, 3000)); + TEST_FFI_OK(botan_x509_ext_as_blocks_add_asnum, (ca_as_blocks, 4000, 5000)); + TEST_FFI_OK(botan_x509_ext_as_blocks_restrict_rdi, (ca_as_blocks)); + + TEST_FFI_OK(botan_x509_ext_as_blocks_get_asnum, (ca_as_blocks, &present, &count)); + result.confirm("ext has asnum", present == 1); + result.confirm("ext has 2 asnums", count == 2); + + uint32_t min_out_ = 0; + uint32_t max_out_ = 0; + + TEST_FFI_OK(botan_x509_ext_as_blocks_get_asnum_at, (ca_as_blocks, 0, &min_out_, &max_out_)); + result.confirm("asnum 0 min is correct", min_out_ == 0); + result.confirm("asnum 0 max is correct", max_out_ == 3000); + TEST_FFI_OK(botan_x509_ext_as_blocks_get_asnum_at, (ca_as_blocks, 1, &min_out_, &max_out_)); + result.confirm("asnum 1 min is correct", min_out_ == 4000); + result.confirm("asnum 1 max is correct", max_out_ == 5000); + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, + botan_x509_ext_as_blocks_get_asnum_at, + (ca_as_blocks, 2, &min_out_, &max_out_)); + + TEST_FFI_OK(botan_x509_ext_as_blocks_get_rdi, (ca_as_blocks, &present, &count)); + result.confirm("ext has rdi", present == 1); + result.confirm("ext has 0 rdis", count == 0); + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, + botan_x509_ext_as_blocks_get_rdi_at, + (ca_as_blocks, 0, &min_out_, &max_out_)); + + TEST_FFI_OK(botan_x509_cert_opts_ext_as_blocks, (ca_opts, ca_as_blocks)); + TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (ca_as_blocks)); + botan_x509_cert_t ca_cert; TEST_FFI_OK(botan_x509_create_self_signed_cert, (&ca_cert, ca_key, ca_opts, hash_fn.c_str(), "", rng)); @@ -1001,9 +1035,16 @@ class FFI_X509_RPKI_Test final : public FFI_Test { TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks, (&req_ip_addr_blocks)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_inherit, (req_ip_addr_blocks, 0, nullptr)); - TEST_FFI_OK(botan_x509_cert_opts_add_ext_ip_addr_blocks, (req_opts, req_ip_addr_blocks)); + TEST_FFI_OK(botan_x509_cert_opts_ext_ip_addr_blocks, (req_opts, req_ip_addr_blocks)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (req_ip_addr_blocks)); + botan_x509_ext_as_blocks_t req_as_blocks; + TEST_FFI_OK(botan_x509_ext_create_as_blocks, (&req_as_blocks)); + TEST_FFI_OK(botan_x509_ext_as_blocks_inherit_asnum, (req_as_blocks)); + + TEST_FFI_OK(botan_x509_cert_opts_ext_as_blocks, (req_opts, req_as_blocks)); + TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (req_as_blocks)); + botan_x509_pkcs10_req_t req; TEST_FFI_OK(botan_x509_create_pkcs10_req, (&req, req_opts, cert_key, hash_fn.c_str(), rng)); @@ -1011,7 +1052,7 @@ class FFI_X509_RPKI_Test final : public FFI_Test { TEST_FFI_OK(botan_x509_sign_req, (&cert, ca, req, rng, not_before, not_after)); botan_x509_ext_ip_addr_blocks_t req_ip_addr_blocks_from_cert; - TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks_from_cert, (cert, &req_ip_addr_blocks_from_cert)); + TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks_from_cert, (&req_ip_addr_blocks_from_cert, cert)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_counts, (req_ip_addr_blocks_from_cert, &v4_count, &v6_count)); @@ -1025,8 +1066,64 @@ class FFI_X509_RPKI_Test final : public FFI_Test { result.confirm("index 0 does not have a safi", has_safi == 0); result.confirm("index 0 has a value", present == 0); + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_ip_addr_blocks_add_ip_addr, + (req_ip_addr_blocks_from_cert, min.data(), min.data(), 0, &safi)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_ip_addr_blocks_restrict, + (req_ip_addr_blocks_from_cert, 0, &safi)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_ip_addr_blocks_inherit, + (req_ip_addr_blocks_from_cert, 0, &safi)); + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (req_ip_addr_blocks_from_cert)); + botan_x509_ext_as_blocks_t req_as_blocks_from_cert; + TEST_FFI_OK(botan_x509_ext_create_as_blocks_from_cert, (&req_as_blocks_from_cert, cert)); + + TEST_FFI_OK(botan_x509_ext_as_blocks_get_asnum, (req_as_blocks_from_cert, &present, &count)); + result.confirm("ext inherits asnum", present == 0); + + TEST_FFI_RC(BOTAN_FFI_ERROR_NO_VALUE, + botan_x509_ext_as_blocks_get_asnum_at, + (req_as_blocks_from_cert, 0, &min_out_, &max_out_)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_NO_VALUE, + botan_x509_ext_as_blocks_get_rdi, + (req_as_blocks_from_cert, &present, &count)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_NO_VALUE, + botan_x509_ext_as_blocks_get_rdi_at, + (req_as_blocks_from_cert, 0, &min_out_, &max_out_)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_as_blocks_add_asnum, + (req_as_blocks_from_cert, 0, 1)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_as_blocks_restrict_asnum, + (req_as_blocks_from_cert)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_as_blocks_inherit_asnum, + (req_as_blocks_from_cert)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_as_blocks_add_rdi, + (req_as_blocks_from_cert, 0, 1)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_as_blocks_restrict_rdi, + (req_as_blocks_from_cert)); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_ext_as_blocks_inherit_rdi, + (req_as_blocks_from_cert)); + + TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (req_as_blocks_from_cert)); + int rc; TEST_FFI_RC(0, botan_x509_cert_verify, (&rc, cert, nullptr, 0, &ca_cert, 1, nullptr, 0, nullptr, 0)); From 1605373a3215fa091374b90e6f6a1b1f9d7c9adf Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Tue, 15 Jul 2025 21:54:23 +0200 Subject: [PATCH 10/14] use a builder style api instead --- doc/api_ref/ffi.rst | 238 ++++++++++++------------- doc/api_ref/python.rst | 91 ++++------ src/lib/ffi/ffi.h | 188 ++++++++++---------- src/lib/ffi/ffi_cert.cpp | 315 ++++++++++++++++------------------ src/lib/ffi/ffi_cert.h | 6 +- src/lib/ffi/ffi_x509_rpki.cpp | 18 +- src/lib/ffi/ffi_x509_rpki.h | 12 +- src/python/botan3.py | 237 ++++++++++++------------- src/scripts/test_python.py | 80 +++++---- src/tests/test_ffi.cpp | 93 +++++----- 10 files changed, 597 insertions(+), 681 deletions(-) diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index ddf0da8892a..7b2165ff562 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -1792,148 +1792,50 @@ X.509 Certificates Return a (statically allocated) string associated with the verification result, or NULL if the code is not known. -.. cpp:type:: opaque* botan_x509_cert_opts_t - - An opaque data type for X.509 certificate options. Don't mess with it. - -.. cpp:type:: opaque* botan_x509_time_t - - An opaque data type for an X.509 time. Don't mess with it. - -.. cpp:type:: opaque* botan_x509_ext_as_blocks_t - - An opaque data type for an X.509 AS Blocks extension (RFC 3779). Don't mess with it. .. cpp:type:: opaque* botan_x509_ext_ip_addr_blocks_t An opaque data type for an X.509 IP Address Blocks extension (RFC 3779). Don't mess with it. -.. cpp:type:: opaque* botan_x509_ca_t - - An opaque data type for an X.509 CA. Don't mess with it. - -.. cpp:type:: opaque* botan_x509_pkcs10_req_t - - An opaque data type for a PKCS #10 certificate request. Don't mess with it. - -.. cpp:function::int botan_x509_cert_opts_destroy(botan_x509_cert_opts_t opts) - - Destroy the options object. - -.. cpp:function::int botan_x509_time_destroy(botan_x509_time_t time) - - Destroy the time object. - .. cpp:function::int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) Destroy the IP Address Blocks object. -.. cpp:function::int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks) - - Destroy the AS Blocks object. - -.. cpp:function::int botan_x509_ca_destroy(botan_x509_ca_t ca) - - Destroy the CA object. - -.. cpp:function::int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req) - - Destroy the PKCS #10 certificate request object. - -.. cpp:function::int int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* opts, uint32_t* expire_time) - - Creates a new options object. ``opts`` defines the common name (e.g. `common_name/country/organization/organizational_unit`), ``expire_time`` if given - is the expiration time from current clock in seconds. - -.. cpp:function::int botan_x509_cert_opts_common_name(botan_x509_cert_opts_t opts, const char* name) - - Set the common name for the object. - -.. cpp:function::int botan_x509_cert_opts_country(botan_x509_cert_opts_t opts, const char* country) - - Set the country for the objects. - -.. cpp:function::int botan_x509_cert_opts_organization(botan_x509_cert_opts_t opts, const char* organization) - -.. cpp:function::int botan_x509_cert_opts_org_unit(botan_x509_cert_opts_t opts, const char* org_unit) - -.. cpp:function::int botan_x509_cert_opts_locality(botan_x509_cert_opts_t opts, const char* locality) - -.. cpp:function::int botan_x509_cert_opts_state(botan_x509_cert_opts_t opts, const char* state) - -.. cpp:function::int botan_x509_cert_opts_serial_number(botan_x509_cert_opts_t opts, const char* serial_number) - -.. cpp:function::int botan_x509_cert_opts_email(botan_x509_cert_opts_t opts, const char* email) - -.. cpp:function::int botan_x509_cert_opts_uri(botan_x509_cert_opts_t opts, const char* uri) - -.. cpp:function::int botan_x509_cert_opts_ip(botan_x509_cert_opts_t opts, const char* ip) - -.. cpp:function::int botan_x509_cert_opts_dns(botan_x509_cert_opts_t opts, const char* dns) - -.. cpp:function::int botan_x509_cert_opts_xmpp(botan_x509_cert_opts_t opts, const char* xmpp) - -.. cpp:function::int botan_x509_cert_opts_challenge(botan_x509_cert_opts_t opts, const char* challenge) - -.. cpp:function::int int botan_x509_cert_opts_more_org_units(botan_x509_cert_opts_t opts, const char** more_org_units, size_t cnt) - -.. cpp:function::int int botan_x509_cert_opts_more_dns(botan_x509_cert_opts_t opts, const char** more_dns, size_t cnt) - -.. cpp:function::int botan_x509_cert_opts_ca_key(botan_x509_cert_opts_t opts, size_t limit) - - Mark the certificate for CA usage. - -.. cpp:function::int botan_x509_cert_opts_padding_scheme(botan_x509_cert_opts_t opts, const char* scheme) - -.. cpp:function::int botan_x509_cert_opts_not_before(botan_x509_cert_opts_t opts, botan_x509_time_t not_before) - -.. cpp:function::int botan_x509_cert_opts_not_after(botan_x509_cert_opts_t opts, botan_x509_time_t not_after) - -.. cpp:function::int botan_x509_cert_opts_constraints(botan_x509_cert_opts_t opts, uint32_t usage) - -.. cpp:function::int botan_x509_cert_opts_ex_constraint(botan_x509_cert_opts_t opts, botan_asn1_oid_t oid) - -.. cpp:function::int botan_x509_create_time(botan_x509_time_t* time_obj, uint64_t time_since_epoch) - - Create a new time object. - -.. cpp:function::int int botan_x509_cert_opts_ext_ip_addr_blocks(botan_x509_cert_opts_t opts, \ - botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) - -.. cpp:function::int int botan_x509_cert_opts_ext_as_blocks(botan_x509_cert_opts_t opts, botan_x509_ext_as_blocks_t as_blocks) - .. cpp:function::int botan_x509_ext_create_ip_addr_blocks(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) Create a new IP Address Blocks object. -.. cpp:function::int int botan_x509_ext_create_ip_addr_blocks_from_cert(botan_x509_cert_t cert, \ +.. cpp:function::int botan_x509_ext_create_ip_addr_blocks_from_cert(botan_x509_cert_t cert, \ botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) Get an IP Address Blocks object from a certificate. Cannot be mutated. -.. cpp:function::int int botan_x509_ext_ip_addr_blocks_add_ip_addr( - botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, const uint8_t* min, const uint8_t* max, int ipv6, uint8_t* safi) +.. cpp:function::int botan_x509_ext_ip_addr_blocks_add_ip_addr(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, \ + const uint8_t* min, \ + const uint8_t* max, \ + int ipv6, \ + uint8_t* safi) Add a new IP Address to the extension. Set ``ipv6`` to 0 if the address is v4, 1 if it is v6. ``safi`` may be NULL. -.. cpp:function::int int botan_x509_ext_ip_addr_blocks_restrict(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi) +.. cpp:function::int botan_x509_ext_ip_addr_blocks_restrict(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi) Make the extension contain no allowed IP addresses for the specified IP version. Set ``ipv6`` to 0 for v4, 1 for v6. ``safi`` may be NULL. -.. cpp:function::int int botan_x509_ext_ip_addr_blocks_inherit(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi) +.. cpp:function::int botan_x509_ext_ip_addr_blocks_inherit(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi) Mark the specified IP version as "inherit". Set ``ipv6`` to 0 for v4, 1 for v6. ``safi`` may be NULL. -.. cpp:function::int int botan_x509_ext_ip_addr_blocks_get_counts(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, \ +.. cpp:function::int botan_x509_ext_ip_addr_blocks_get_counts(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, \ size_t* v4_count, \ size_t* v6_count) Retrieve the counts of v4/v6 entries in the extension. v4 entries always precede v6 entries. -.. cpp:function::int int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, +.. cpp:function::int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, \ size_t i, \ int* has_safi, \ @@ -1950,7 +1852,7 @@ X.509 Certificates ``present`` will be set to one to indicate a value, 0 otherwise (inherit). ``count`` will be set to the number of address pairs present for the entry if present. -.. cpp:function::int int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, +.. cpp:function::int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, \ size_t i, \ size_t entry, \ @@ -1967,15 +1869,23 @@ X.509 Certificates ``min_out`` and ``max_out`` will be set to the minimum and maximum of the IP range. You must provide 4 / 16 bytes of buffer space for each for IP v4 / v6 respectively. +.. cpp:type:: opaque* botan_x509_ext_as_blocks_t + + An opaque data type for an X.509 AS Blocks extension (RFC 3779). Don't mess with it. + +.. cpp:function::int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks) + + Destroy the AS Blocks object. + .. cpp:function::int botan_x509_ext_create_as_blocks(botan_x509_ext_as_blocks_t* as_blocks) Create a new AS Blocks object. -.. cpp:function::int int botan_x509_ext_create_as_blocks_from_cert(botan_x509_cert_t cert, botan_x509_ext_as_blocks_t* as_blocks) +.. cpp:function::int botan_x509_ext_create_as_blocks_from_cert(botan_x509_cert_t cert, botan_x509_ext_as_blocks_t* as_blocks) Get an AS Blocks object from a certificate. Cannot be mutated. -.. cpp:function::int int botan_x509_ext_as_blocks_add_asnum(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) +.. cpp:function::int botan_x509_ext_as_blocks_add_asnum(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) Add an asnum to the extension. @@ -1987,44 +1897,103 @@ X.509 Certificates Mark the asnum entry as "inherit". -.. cpp:function::int int botan_x509_ext_as_blocks_add_rdi(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) +.. cpp:function::int botan_x509_ext_as_blocks_add_rdi(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max) .. cpp:function::int botan_x509_ext_as_blocks_restrict_rdi(botan_x509_ext_as_blocks_t as_blocks) .. cpp:function::int botan_x509_ext_as_blocks_inherit_rdi(botan_x509_ext_as_blocks_t as_blocks) -.. cpp:function::int int botan_x509_ext_as_blocks_get_asnum(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count) +.. cpp:function::int botan_x509_ext_as_blocks_get_asnum(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count) If the extension has an asnum entry, ``present`` will be set to 1, otherwise 0 (inherit). If an entry is present ``count`` will be set to the number of elements. -.. cpp:function::int int botan_x509_ext_as_blocks_get_asnum_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max) +.. cpp:function::int botan_x509_ext_as_blocks_get_asnum_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max) Retrieve information on a single asnum entry. -.. cpp:function::int int botan_x509_ext_as_blocks_get_rdi(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count) +.. cpp:function::int botan_x509_ext_as_blocks_get_rdi(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count) + +.. cpp:function::int botan_x509_ext_as_blocks_get_rdi_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max) + +.. cpp:type:: opaque* botan_x509_cert_params_builder_t + +.. cpp:function::int botan_x509_cert_opts_destroy(botan_x509_cert_opts_t opts) + + Destroy the options object. + +.. cpp:function::int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* builder_obj, \ + const char* opts, \ + uint32_t* expire_time); + + Create a new certificate builder object. ``opts`` defines the common name (e.g. `common_name/country/organization/organizational_unit`). + ``expire_time`` if given is the expiration time from current clock in seconds. + +.. cpp:function::int botan_x509_cert_params_builder_add_common_name(botan_x509_cert_params_builder_t builder, const char* name); + +.. cpp:function::int botan_x509_cert_params_builder_add_country(botan_x509_cert_params_builder_t builder, const char* country); + +.. cpp:function::int botan_x509_cert_params_builder_add_organization(botan_x509_cert_params_builder_t builder, const char* organization); + +.. cpp:function::int botan_x509_cert_params_builder_add_org_unit(botan_x509_cert_params_builder_t builder, const char* org_unit); + +.. cpp:function::int botan_x509_cert_params_builder_add_locality(botan_x509_cert_params_builder_t builder, const char* locality); + +.. cpp:function::int botan_x509_cert_params_builder_add_state(botan_x509_cert_params_builder_t builder, const char* state); + +.. cpp:function::int botan_x509_cert_params_builder_add_serial_number(botan_x509_cert_params_builder_t builder, const char* serial_number); -.. cpp:function::int int botan_x509_ext_as_blocks_get_rdi_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max) +.. cpp:function::int botan_x509_cert_params_builder_add_email(botan_x509_cert_params_builder_t builder, const char* email); -.. cpp:function::int int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, \ +.. cpp:function::int botan_x509_cert_params_builder_add_uri(botan_x509_cert_params_builder_t builder, const char* uri); + +.. cpp:function::int botan_x509_cert_params_builder_add_ip(botan_x509_cert_params_builder_t builder, const char* ip); + +.. cpp:function::int botan_x509_cert_params_builder_add_dns(botan_x509_cert_params_builder_t builder, const char* dns); + +.. cpp:function::int botan_x509_cert_params_builder_add_xmpp(botan_x509_cert_params_builder_t builder, const char* xmpp); + +.. cpp:function::int botan_x509_cert_params_builder_add_challenge(botan_x509_cert_params_builder_t builder, const char* challenge); + +.. cpp:function::int botan_x509_cert_params_builder_mark_as_ca_key(botan_x509_cert_params_builder_t builder, size_t limit); + + Mark the certificate for CA usage. + +.. cpp:function::int botan_x509_cert_params_builder_add_not_before(botan_x509_cert_params_builder_t builder, uint64_t time_since_epoch); + + ``time_since_epoch`` is expected to be in seconds. + +.. cpp:function::int botan_x509_cert_params_builder_add_not_after(botan_x509_cert_params_builder_t builder, uint64_t time_since_epoch); + +.. cpp:function::int botan_x509_cert_params_builder_add_constraints(botan_x509_cert_params_builder_t builder, uint32_t usage); + +.. cpp:function::int botan_x509_cert_params_builder_add_ex_constraint(botan_x509_cert_params_builder_t builder, botan_asn1_oid_t oid); + +.. cpp:function::int botan_x509_cert_params_builder_add_ext_ip_addr_blocks(botan_x509_cert_params_builder_t builder, \ + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); + +.. cpp:function::int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_builder_t builder, \ + botan_x509_ext_as_blocks_t as_blocks); + +.. cpp:function::int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, \ botan_privkey_t key, \ botan_x509_cert_opts_t opts, \ const char* hash_fn, \ - const char* sig_padding, \ + const char* padding, \ botan_rng_t rng) + Create a new self-signed X.509 certificate. -.. cpp:function::int int botan_x509_create_ca(botan_x509_ca_t* ca_obj, \ - botan_x509_cert_t ca_cert, \ - botan_privkey_t key, \ - const char* hash_fn, \ - const char* sig_padding, \ - botan_rng_t rng) +.. cpp:type:: opaque* botan_x509_pkcs10_req_t - Create a CA object capable of signing other certificates. + An opaque data type for a PKCS #10 certificate request. Don't mess with it. -.. cpp:function::int int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, \ +.. cpp:function::int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req) + + Destroy the PKCS #10 certificate request object. + +.. cpp:function::int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, \ botan_x509_cert_opts_t opts, \ botan_privkey_t key, \ const char* hash_fn, \ @@ -2032,14 +2001,19 @@ X.509 Certificates Create a PCKS #10 certificate request. -.. cpp:function::int int botan_x509_sign_req(botan_x509_cert_t* cert_obj, \ - botan_x509_ca_t ca, \ - botan_x509_pkcs10_req_t req, \ +.. cpp:function::int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view) + +.. cpp:function::int botan_x509_sign_req(botan_x509_cert_t* subject_cert, \ + botan_x509_pkcs10_req_t subject_req, \ + botan_x509_cert_t issuing_cert, \ + botan_privkey_t issuing_key, \ botan_rng_t rng, \ - botan_x509_time_t not_before, \ - botan_x509_time_t not_after) + uint64_t not_before, \ + uint64_t not_after, \ + const char* hash_fn, \ + const char* padding) - Sign a PKCS #10 certificate request + Sign a PKCS #10 certificate request. ``not_before`` and ``not_after`` are expected to be the time since the UNIX epoch, in seconds. X.509 Certificate Revocation Lists ---------------------------------------- diff --git a/doc/api_ref/python.rst b/doc/api_ref/python.rst index dfd70677738..e3e1e21ac5a 100644 --- a/doc/api_ref/python.rst +++ b/doc/api_ref/python.rst @@ -715,80 +715,58 @@ HOTP in. If the code did verify and resync_range was zero, then the next counter will always be counter+1. -X509Time +X509CertificateBuilder ----------------------------------------- .. versionadded:: 3.9.0 -.. py:class:: X509Time(time_since_epoch) +.. py:class:: X509CertificateBuilder(opts, expire_time=None) -PKCS10Req ------------------------------------------ -.. versionadded:: 3.9.0 - -.. py:class:: PKCS10Req() - -X509Opts ------------------------------------------ -.. versionadded:: 3.9.0 - -.. py:class:: X509Opts(opts, expire_time=None) - - .. py:method:: set_common_name(name) + .. py:method:: add_common_name(name) - Set the common name for the certificate. + .. py:method:: add_country(country) - .. py:method:: set_country(country) + .. py:method:: add_organization(organization) - .. py:method:: set_organization(organization) + .. py:method:: add_org_unit(org_unit) - .. py:method:: set_org_unit(org_unit) + .. py:method:: add_locality(locality) - .. py:method:: set_locality(locality) + .. py:method:: add_state(state) - .. py:method:: set_state(state) + .. py:method:: add_serial_number(serial_number) - .. py:method:: set_serial_number(serial_number) + .. py:method:: add_email(email) - .. py:method:: set_email(email) + .. py:method:: add_uri(uri) - .. py:method:: set_uri(uri) + .. py:method:: add_ip(ip) - .. py:method:: set_ip(ip) + .. py:method:: add_dns(dns) - .. py:method:: set_dns(dns) + .. py:method:: add_xmpp(xmpp) - .. py:method:: set_xmpp(xmpp) + .. py:method:: add_challenge(challenge) - .. py:method:: set_challenge(challenge) + .. py:method:: mark_as_ca_key(limit) - .. py:method:: set_more_org_units(more_org_units) + .. py:method:: add_not_before(time_since_epoch) - ``more_org_units`` is expected to be a of type ``list[string]`` + ``time_since_epoch`` is expected to be in seconds. - .. py:method:: set_more_dns(more_dns) + .. py:method:: add_not_after(time_since_epoch) - ``more_dns`` is expected to be a of type ``list[string]`` + .. py:method:: add_constraints(usage_list) - .. py:method:: set_ca_key(limit) - - .. py:method:: set_padding_scheme(scheme) - - .. py:method:: set_not_before(not_before) - - .. py:method:: set_not_after(not_after) + .. py:method:: add_ex_constraints(oid) - .. py:method:: set_constraints(usage_list) + .. py:method:: add_ext_ip_addr_blocks(ip_addr_blocks) - .. py:method:: add_ex_constraints(oid) + .. py:method:: add_ext_as_blocks(as_blocks) .. py:method:: create_req(key, hash_fn, rng) Create a PKCS #10 certificate request that can later be signed. - .. py:method:: add_ext_ip_addr_blocks(ip_addr_blocks) - - .. py:method:: add_ext_as_blocks(as_blocks) - X509ExtIPAddrBlocks ----------------------------------------- @@ -855,6 +833,18 @@ X509ExtASBlocks .. py:method:: rdi() +PKCS10Req +----------------------------------------- +.. versionadded:: 3.9.0 + +.. py:class:: PKCS10Req() + + .. py:method:: sign(issuing_cert, issuing_key, rng, not_before, not_after, hash_fn, padding) + + ``not_before`` and ``not_after`` are expected to be the time since the UNIX epoch, in seconds. + + .. py:method:: to_pem() + X509Cert ----------------------------------------- @@ -1002,17 +992,6 @@ X509Cert Check if the certificate (``self``) is revoked on the given ``crl``. -X509Ca ------------------------------------------ - -.. versionadded:: 3.9.0 - -.. py:class:: X509Ca(cert, key, rng, has_fn, sig_padding="") - - .. py:method:: sign(req, rng, not_before, not_after) - - Sign a PKCS #10 certificate request. - X509CRL ----------------------------------------- diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 98e5cc7199a..72c7a7c3551 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -2218,81 +2218,13 @@ int botan_x509_cert_verify(int* validation_result, */ BOTAN_FFI_EXPORT(2, 8) const char* botan_x509_cert_validation_status(int code); -typedef struct botan_x509_cert_opts_struct* botan_x509_cert_opts_t; -typedef struct botan_x509_time_struct* botan_x509_time_t; -typedef struct botan_x509_ext_as_blocks_struct* botan_x509_ext_as_blocks_t; +/* +* X.509 extensions +*/ typedef struct botan_x509_ext_ip_addr_blocks_struct* botan_x509_ext_ip_addr_blocks_t; -typedef struct botan_x509_ca_struct* botan_x509_ca_t; -typedef struct botan_x509_pkcs10_req_struct* botan_x509_pkcs10_req_t; - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_destroy(botan_x509_cert_opts_t opts); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_time_destroy(botan_x509_time_t time); BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_ca_destroy(botan_x509_ca_t ca); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req); - -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* opts, uint32_t* expire_time); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_common_name(botan_x509_cert_opts_t opts, const char* name); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_country(botan_x509_cert_opts_t opts, const char* country); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_organization(botan_x509_cert_opts_t opts, const char* organization); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_org_unit(botan_x509_cert_opts_t opts, const char* org_unit); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_locality(botan_x509_cert_opts_t opts, const char* locality); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_state(botan_x509_cert_opts_t opts, const char* state); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_serial_number(botan_x509_cert_opts_t opts, const char* serial_number); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_email(botan_x509_cert_opts_t opts, const char* email); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_uri(botan_x509_cert_opts_t opts, const char* uri); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_ip(botan_x509_cert_opts_t opts, const char* ip); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_dns(botan_x509_cert_opts_t opts, const char* dns); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_xmpp(botan_x509_cert_opts_t opts, const char* xmpp); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_challenge(botan_x509_cert_opts_t opts, const char* challenge); - -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_opts_more_org_units(botan_x509_cert_opts_t opts, const char** more_org_units, size_t cnt); - -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_opts_more_dns(botan_x509_cert_opts_t opts, const char** more_dns, size_t cnt); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_ca_key(botan_x509_cert_opts_t opts, size_t limit); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_padding_scheme(botan_x509_cert_opts_t opts, const char* scheme); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_not_before(botan_x509_cert_opts_t opts, botan_x509_time_t not_before); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_not_after(botan_x509_cert_opts_t opts, botan_x509_time_t not_after); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_constraints(botan_x509_cert_opts_t opts, uint32_t usage); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_opts_ex_constraint(botan_x509_cert_opts_t opts, botan_asn1_oid_t oid); - -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_opts_ext_ip_addr_blocks(botan_x509_cert_opts_t opts, - botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); - -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_opts_ext_as_blocks(botan_x509_cert_opts_t opts, botan_x509_ext_as_blocks_t as_blocks); - -BOTAN_FFI_EXPORT(3, 9) int botan_x509_create_time(botan_x509_time_t* time_obj, uint64_t time_since_epoch); - BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_create_ip_addr_blocks(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks); BOTAN_FFI_EXPORT(3, 9) @@ -2332,6 +2264,10 @@ int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip uint8_t max_out[], size_t* out_len); +typedef struct botan_x509_ext_as_blocks_struct* botan_x509_ext_as_blocks_t; + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks); + BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_create_as_blocks(botan_x509_ext_as_blocks_t* as_blocks); BOTAN_FFI_EXPORT(3, 9) @@ -2363,36 +2299,116 @@ int botan_x509_ext_as_blocks_get_rdi(botan_x509_ext_as_blocks_t as_blocks, int* BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_get_rdi_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max); +/* +* X.509 cert param builder +*/ +typedef struct botan_x509_cert_params_builder_struct* botan_x509_cert_params_builder_t; + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_params_builder_destroy(botan_x509_cert_params_builder_t builder); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* builder_obj, + const char* opts, + uint32_t* expire_time); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_common_name(botan_x509_cert_params_builder_t builder, const char* name); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_country(botan_x509_cert_params_builder_t builder, const char* country); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_organization(botan_x509_cert_params_builder_t builder, const char* organization); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_org_unit(botan_x509_cert_params_builder_t builder, const char* org_unit); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_locality(botan_x509_cert_params_builder_t builder, const char* locality); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_state(botan_x509_cert_params_builder_t builder, const char* state); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_serial_number(botan_x509_cert_params_builder_t builder, + const char* serial_number); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_email(botan_x509_cert_params_builder_t builder, const char* email); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_uri(botan_x509_cert_params_builder_t builder, const char* uri); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_ip(botan_x509_cert_params_builder_t builder, const char* ip); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_dns(botan_x509_cert_params_builder_t builder, const char* dns); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_xmpp(botan_x509_cert_params_builder_t builder, const char* xmpp); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_challenge(botan_x509_cert_params_builder_t builder, const char* challenge); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_mark_as_ca_key(botan_x509_cert_params_builder_t builder, size_t limit); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_not_before(botan_x509_cert_params_builder_t builder, uint64_t time_since_epoch); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_not_after(botan_x509_cert_params_builder_t builder, uint64_t time_since_epoch); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_constraints(botan_x509_cert_params_builder_t builder, uint32_t usage); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_ex_constraint(botan_x509_cert_params_builder_t builder, botan_asn1_oid_t oid); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_ext_ip_addr_blocks(botan_x509_cert_params_builder_t builder, + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_builder_t builder, + botan_x509_ext_as_blocks_t as_blocks); + +/* +* X.509 cert creation +*/ BOTAN_FFI_EXPORT(3, 9) int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, botan_privkey_t key, - botan_x509_cert_opts_t opts, + botan_x509_cert_params_builder_t builder, const char* hash_fn, - const char* sig_padding, + const char* padding, botan_rng_t rng); -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_create_ca(botan_x509_ca_t* ca_obj, - botan_x509_cert_t ca_cert, - botan_privkey_t key, - const char* hash_fn, - const char* sig_padding, - botan_rng_t rng); +typedef struct botan_x509_pkcs10_req_struct* botan_x509_pkcs10_req_t; + +BOTAN_FFI_EXPORT(3, 9) int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req); BOTAN_FFI_EXPORT(3, 9) int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, - botan_x509_cert_opts_t opts, + botan_x509_cert_params_builder_t builder, botan_privkey_t key, const char* hash_fn, botan_rng_t rng); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_sign_req(botan_x509_cert_t* cert_obj, - botan_x509_ca_t ca, - botan_x509_pkcs10_req_t req, +int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view); + +BOTAN_FFI_EXPORT(3, 9) +int botan_x509_sign_req(botan_x509_cert_t* subject_cert, + botan_x509_pkcs10_req_t subject_req, + botan_x509_cert_t issuing_cert, + botan_privkey_t issuing_key, botan_rng_t rng, - botan_x509_time_t not_before, - botan_x509_time_t not_after); + uint64_t not_before, + uint64_t not_after, + const char* hash_fn, + const char* padding); /* * X.509 CRL diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index e6370d815bb..628db63b00a 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -15,6 +15,13 @@ #include #include +namespace { +Botan::X509_Time time_from_timestamp(uint64_t time_since_epoch) { + auto tp = std::chrono::system_clock::time_point(std::chrono::seconds(time_since_epoch)); + return Botan::X509_Time(tp); +} +} // namespace + extern "C" { using namespace Botan_FFI; @@ -560,44 +567,19 @@ int botan_x509_cert_verify_with_crl(int* result_code, #endif } -int botan_x509_cert_opts_destroy(botan_x509_cert_opts_t opts) { -#if defined(BOTAN_HAS_X509_CERTIFICATES) - return BOTAN_FFI_CHECKED_DELETE(opts); -#else - BOTAN_UNUSED(opts); - return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; -#endif -} - -int botan_x509_ca_destroy(botan_x509_ca_t ca) { -#if defined(BOTAN_HAS_X509_CERTIFICATES) - return BOTAN_FFI_CHECKED_DELETE(ca); -#else - BOTAN_UNUSED(ca); - return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; -#endif -} - -int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req) { -#if defined(BOTAN_HAS_X509_CERTIFICATES) - return BOTAN_FFI_CHECKED_DELETE(req); -#else - BOTAN_UNUSED(req); - return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; -#endif -} - -int botan_x509_time_destroy(botan_x509_time_t time) { +int botan_x509_cert_params_builder_destroy(botan_x509_cert_params_builder_t builder) { #if defined(BOTAN_HAS_X509_CERTIFICATES) - return BOTAN_FFI_CHECKED_DELETE(time); + return BOTAN_FFI_CHECKED_DELETE(builder); #else - BOTAN_UNUSED(time); + BOTAN_UNUSED(builder); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* opts, uint32_t* expire_time) { - if(opts_obj == nullptr || opts == nullptr) { +int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* builder_obj, + const char* opts, + uint32_t* expire_time) { + if(builder_obj == nullptr || opts == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -609,7 +591,7 @@ int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* op } else { co = std::make_unique(opts); } - return ffi_new_object(opts_obj, std::move(co)); + return ffi_new_object(builder_obj, std::move(co)); }); #else BOTAN_UNUSED(expire_time); @@ -619,190 +601,185 @@ int botan_x509_create_cert_opts(botan_x509_cert_opts_t* opts_obj, const char* op #if defined(BOTAN_HAS_X509_CERTIFICATES) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) - #define X509_GET_CERT_OPTS_STRING(FIELD_NAME) \ - int botan_x509_cert_opts_##FIELD_NAME(botan_x509_cert_opts_t opts, const char* value) { \ - if(value == nullptr) { \ - return BOTAN_FFI_ERROR_NULL_POINTER; \ - } \ - return ffi_guard_thunk(__func__, [=]() -> int { \ - safe_get(opts).FIELD_NAME = value; \ - return BOTAN_FFI_SUCCESS; \ - }); \ + #define X509_GET_CERT_PARAMS_BUILDER_STRING(FIELD_NAME) \ + int botan_x509_cert_params_builder_add_##FIELD_NAME(botan_x509_cert_params_builder_t builder, \ + const char* value) { \ + if(value == nullptr) { \ + return BOTAN_FFI_ERROR_NULL_POINTER; \ + } \ + return ffi_guard_thunk(__func__, [=]() -> int { \ + auto& builder_ = safe_get(builder); \ + if(!builder_.FIELD_NAME.empty()) { \ + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; \ + } \ + builder_.FIELD_NAME = value; \ + return BOTAN_FFI_SUCCESS; \ + }); \ } #else // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) - #define X509_GET_CERT_OPTS_STRING(FIELD_NAME) \ - int botan_x509_cert_opts_##FIELD_NAME(botan_x509_cert_opts_t opts, const char* value) { \ - if(value == nullptr) { \ - return BOTAN_FFI_ERROR_NULL_POINTER; \ - } \ - BOTAN_UNUSED(opts); \ - return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; \ + #define X509_GET_CERT_PARAMS_BUILDER_STRING(FIELD_NAME) \ + int botan_x509_cert_params_builder_add_##FIELD_NAME(botan_x509_cert_params_builder_t builder, \ + const char* value) { \ + if(value == nullptr) { \ + return BOTAN_FFI_ERROR_NULL_POINTER; \ + } \ + BOTAN_UNUSED(builder); \ + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; \ } #endif -X509_GET_CERT_OPTS_STRING(common_name) -X509_GET_CERT_OPTS_STRING(country) -X509_GET_CERT_OPTS_STRING(organization) -X509_GET_CERT_OPTS_STRING(org_unit) -X509_GET_CERT_OPTS_STRING(locality) -X509_GET_CERT_OPTS_STRING(state) -X509_GET_CERT_OPTS_STRING(serial_number) -X509_GET_CERT_OPTS_STRING(email) -X509_GET_CERT_OPTS_STRING(uri) -X509_GET_CERT_OPTS_STRING(ip) -X509_GET_CERT_OPTS_STRING(dns) -X509_GET_CERT_OPTS_STRING(xmpp) -X509_GET_CERT_OPTS_STRING(challenge) - +X509_GET_CERT_PARAMS_BUILDER_STRING(common_name) +X509_GET_CERT_PARAMS_BUILDER_STRING(country) +X509_GET_CERT_PARAMS_BUILDER_STRING(organization) +X509_GET_CERT_PARAMS_BUILDER_STRING(locality) +X509_GET_CERT_PARAMS_BUILDER_STRING(state) +X509_GET_CERT_PARAMS_BUILDER_STRING(serial_number) +X509_GET_CERT_PARAMS_BUILDER_STRING(email) +X509_GET_CERT_PARAMS_BUILDER_STRING(uri) +X509_GET_CERT_PARAMS_BUILDER_STRING(ip) +X509_GET_CERT_PARAMS_BUILDER_STRING(xmpp) +X509_GET_CERT_PARAMS_BUILDER_STRING(challenge) + +int botan_x509_cert_params_builder_add_org_unit(botan_x509_cert_params_builder_t builder, const char* org_unit) { + if(org_unit == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } #if defined(BOTAN_HAS_X509_CERTIFICATES) - // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) - #define X509_GET_CERT_OPTS_VEC(FIELD_NAME) \ - int botan_x509_cert_opts_##FIELD_NAME(botan_x509_cert_opts_t opts, const char** value, size_t cnt) { \ - if(value == nullptr) { \ - return BOTAN_FFI_ERROR_NULL_POINTER; \ - } \ - return ffi_guard_thunk(__func__, [=]() -> int { \ - std::vector val; \ - for(size_t i = 0; i < cnt; i++) { \ - if(value[i] == nullptr) { \ - return BOTAN_FFI_ERROR_NULL_POINTER; \ - } \ - val.push_back(value[i]); \ - } \ - safe_get(opts).FIELD_NAME = val; \ - return BOTAN_FFI_SUCCESS; \ - }); \ - } -#else - // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) - #define X509_GET_CERT_OPTS_VEC(FIELD_NAME) \ - int botan_x509_cert_opts_##FIELD_NAME(botan_x509_cert_opts_t opts, const char** value, size_t cnt) { \ - if(value == nullptr) { \ - return BOTAN_FFI_ERROR_NULL_POINTER; \ - } \ - BOTAN_UNUSED(opts, cnt); \ - return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; \ + return ffi_guard_thunk(__func__, [=]() -> int { + auto& builder_ = safe_get(builder); + if(builder_.org_unit.empty()) { + builder_.org_unit = org_unit; + } else { + builder_.more_org_units.push_back(org_unit); } -#endif - -X509_GET_CERT_OPTS_VEC(more_org_units) -X509_GET_CERT_OPTS_VEC(more_dns) - -int botan_x509_cert_opts_ca_key(botan_x509_cert_opts_t opts, size_t limit) { -#if defined(BOTAN_HAS_X509_CERTIFICATES) - return BOTAN_FFI_VISIT(opts, [=](auto& o) { o.CA_key(limit); }); + return BOTAN_FFI_SUCCESS; + }); #else - BOTAN_UNUSED(opts, limit); + BOTAN_UNUSED(builder); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_cert_opts_padding_scheme(botan_x509_cert_opts_t opts, const char* scheme) { - if(scheme == nullptr) { +int botan_x509_cert_params_builder_add_dns(botan_x509_cert_params_builder_t builder, const char* dns) { + if(dns == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } - #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(opts).set_padding_scheme(scheme); + auto& builder_ = safe_get(builder); + if(builder_.dns.empty()) { + builder_.dns = dns; + } else { + builder_.more_dns.push_back(dns); + } return BOTAN_FFI_SUCCESS; }); #else - BOTAN_UNUSED(opts); + BOTAN_UNUSED(builder); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_cert_opts_not_before(botan_x509_cert_opts_t opts, botan_x509_time_t not_before) { +int botan_x509_cert_params_builder_mark_as_ca_key(botan_x509_cert_params_builder_t builder, size_t limit) { #if defined(BOTAN_HAS_X509_CERTIFICATES) - return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(opts).start = safe_get(not_before); - return BOTAN_FFI_SUCCESS; - }); + return BOTAN_FFI_VISIT(builder, [=](auto& o) { o.CA_key(limit); }); #else - BOTAN_UNUSED(opts, not_before); + BOTAN_UNUSED(builder, limit); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_cert_opts_not_after(botan_x509_cert_opts_t opts, botan_x509_time_t not_after) { +int botan_x509_cert_params_builder_add_not_before(botan_x509_cert_params_builder_t builder, uint64_t time_since_epoch) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(opts).end = safe_get(not_after); + auto& builder_ = safe_get(builder); + if(builder_.start.time_is_set()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + builder_.start = time_from_timestamp(time_since_epoch); return BOTAN_FFI_SUCCESS; }); #else - BOTAN_UNUSED(opts.not_after); + BOTAN_UNUSED(builder, not_before); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_cert_opts_constraints(botan_x509_cert_opts_t opts, uint32_t usage) { +int botan_x509_cert_params_builder_add_not_after(botan_x509_cert_params_builder_t builder, uint64_t time_since_epoch) { #if defined(BOTAN_HAS_X509_CERTIFICATES) - return BOTAN_FFI_VISIT(opts, [=](auto& o) { o.add_constraints(Botan::Key_Constraints(usage)); }); + return ffi_guard_thunk(__func__, [=]() -> int { + auto& builder_ = safe_get(builder); + if(builder_.end.time_is_set()) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } + builder_.end = time_from_timestamp(time_since_epoch); + return BOTAN_FFI_SUCCESS; + }); #else - BOTAN_UNUSED(opts, usage); + BOTAN_UNUSED(builder.not_after); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_cert_opts_ex_constraint(botan_x509_cert_opts_t opts, botan_asn1_oid_t oid) { +int botan_x509_cert_params_builder_add_constraints(botan_x509_cert_params_builder_t builder, uint32_t usage) { #if defined(BOTAN_HAS_X509_CERTIFICATES) - return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(opts).add_ex_constraint(safe_get(oid)); - return BOTAN_FFI_SUCCESS; - }); + return BOTAN_FFI_VISIT(builder, [=](auto& o) { o.add_constraints(Botan::Key_Constraints(usage)); }); #else - BOTAN_UNUSED(opts, oid); + BOTAN_UNUSED(builder, usage); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_create_time(botan_x509_time_t* time_obj, uint64_t time_since_epoch) { - if(time_obj == nullptr) { - return BOTAN_FFI_ERROR_NULL_POINTER; - } +int botan_x509_cert_params_builder_add_ex_constraint(botan_x509_cert_params_builder_t builder, botan_asn1_oid_t oid) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - auto tp = std::chrono::system_clock::time_point(std::chrono::seconds(time_since_epoch)); - auto time = std::make_unique(tp); - return ffi_new_object(time_obj, std::move(time)); + safe_get(builder).add_ex_constraint(safe_get(oid)); + return BOTAN_FFI_SUCCESS; }); #else - BOTAN_UNUSED(time_since_epoch); + BOTAN_UNUSED(builder, oid); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_cert_opts_ext_ip_addr_blocks(botan_x509_cert_opts_t opts, - botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) { +int botan_x509_cert_params_builder_add_ext_ip_addr_blocks(botan_x509_cert_params_builder_t builder, + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(opts).extensions.add(safe_get(ip_addr_blocks).copy()); + try { + safe_get(builder).extensions.add(safe_get(ip_addr_blocks).copy()); + } catch(Botan::Invalid_Argument&) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } return BOTAN_FFI_SUCCESS; }); #else - BOTAN_UNUSED(opts, as_blocks); + BOTAN_UNUSED(builder, as_blocks); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_cert_opts_ext_as_blocks(botan_x509_cert_opts_t opts, botan_x509_ext_as_blocks_t as_blocks) { +int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_builder_t builder, + botan_x509_ext_as_blocks_t as_blocks) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(opts).extensions.add(safe_get(as_blocks).copy()); + try { + safe_get(builder).extensions.add(safe_get(as_blocks).copy()); + } catch(Botan::Invalid_Argument&) { + return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; + } return BOTAN_FFI_SUCCESS; }); #else - BOTAN_UNUSED(opts, as_blocks); + BOTAN_UNUSED(builder, as_blocks); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, botan_privkey_t key, - botan_x509_cert_opts_t opts, + botan_x509_cert_params_builder_t builder, const char* hash_fn, const char* sig_padding, botan_rng_t rng) { @@ -812,37 +789,26 @@ int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { auto ca_cert = std::make_unique( - Botan::X509::create_self_signed_cert(safe_get(opts), safe_get(key), hash_fn, safe_get(rng))); + Botan::X509::create_self_signed_cert(safe_get(builder), safe_get(key), hash_fn, safe_get(rng))); return ffi_new_object(cert_obj, std::move(ca_cert)); }); #else - BOTAN_UNUSED(key, opts, rng); + BOTAN_UNUSED(key, builder, rng); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_create_ca(botan_x509_ca_t* ca_obj, - botan_x509_cert_t ca_cert, - botan_privkey_t key, - const char* hash_fn, - const char* sig_padding, - botan_rng_t rng) { - if(ca_obj == nullptr || hash_fn == nullptr || sig_padding == nullptr) { - return BOTAN_FFI_ERROR_NULL_POINTER; - } +int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req) { #if defined(BOTAN_HAS_X509_CERTIFICATES) - return ffi_guard_thunk(__func__, [=]() -> int { - auto ca = std::make_unique(safe_get(ca_cert), safe_get(key), hash_fn, sig_padding, safe_get(rng)); - return ffi_new_object(ca_obj, std::move(ca)); - }); + return BOTAN_FFI_CHECKED_DELETE(req); #else - BOTAN_UNUSED(ca_cert, key, rng); + BOTAN_UNUSED(req); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, - botan_x509_cert_opts_t opts, + botan_x509_cert_params_builder_t builder, botan_privkey_t key, const char* hash_fn, botan_rng_t rng) { @@ -852,32 +818,47 @@ int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { auto req = std::make_unique( - Botan::X509::create_cert_req(safe_get(opts), safe_get(key), hash_fn, safe_get(rng))); + Botan::X509::create_cert_req(safe_get(builder), safe_get(key), hash_fn, safe_get(rng))); return ffi_new_object(req_obj, std::move(req)); }); #else - BOTAN_UNUSED(opts, key, rng); + BOTAN_UNUSED(builder, key, rng); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_sign_req(botan_x509_cert_t* cert_obj, - botan_x509_ca_t ca, - botan_x509_pkcs10_req_t req, +int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(req, [=](const auto& r) -> int { return invoke_view_callback(view, ctx, r.PEM_encode()); }); +#else + BOTAN_UNUSED(req, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_sign_req(botan_x509_cert_t* subject_cert, + botan_x509_pkcs10_req_t subject_req, + botan_x509_cert_t issuing_cert, + botan_privkey_t issuing_key, botan_rng_t rng, - botan_x509_time_t not_before, - botan_x509_time_t not_after) { - if(cert_obj == nullptr) { + uint64_t not_before, + uint64_t not_after, + const char* hash_fn, + const char* padding) { + if(subject_cert == nullptr || hash_fn == nullptr || padding == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - auto cert = std::make_unique(safe_get(ca).sign_request( - safe_get(req), safe_get(rng), safe_get(not_before), safe_get(not_after))); - return ffi_new_object(cert_obj, std::move(cert)); + Botan::RandomNumberGenerator& rng_ = safe_get(rng); + auto ca = Botan::X509_CA(safe_get(issuing_cert), safe_get(issuing_key), hash_fn, padding, rng_); + + auto cert = std::make_unique(ca.sign_request( + safe_get(subject_req), safe_get(rng), time_from_timestamp(not_before), time_from_timestamp(not_after))); + return ffi_new_object(subject_cert, std::move(cert)); }); #else - BOTAN_UNUSED(ca, req, rng, not_before, not_after); + BOTAN_UNUSED(subject_req, issuing_cert, issuing_key, rng, not_before, not_after); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } diff --git a/src/lib/ffi/ffi_cert.h b/src/lib/ffi/ffi_cert.h index e333a3daf82..15d957489be 100644 --- a/src/lib/ffi/ffi_cert.h +++ b/src/lib/ffi/ffi_cert.h @@ -23,12 +23,10 @@ extern "C" { #if defined(BOTAN_HAS_X509_CERTIFICATES) +BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_params_builder_struct, Botan::X509_Cert_Options, 0x92597C7D); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_pkcs10_req_struct, Botan::PKCS10_Request, 0x87F0690A); BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_struct, Botan::X509_Certificate, 0x8F628937); BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910); -BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_opts_struct, Botan::X509_Cert_Options, 0x92597C7D); -BOTAN_FFI_DECLARE_STRUCT(botan_x509_time_struct, Botan::X509_Time, 0x739396FA); -BOTAN_FFI_DECLARE_STRUCT(botan_x509_ca_struct, Botan::X509_CA, 0x8BE2A8F1); -BOTAN_FFI_DECLARE_STRUCT(botan_x509_pkcs10_req_struct, Botan::PKCS10_Request, 0x87F0690A); #endif } diff --git a/src/lib/ffi/ffi_x509_rpki.cpp b/src/lib/ffi/ffi_x509_rpki.cpp index 802df7e24cb..e6e9a68ed82 100644 --- a/src/lib/ffi/ffi_x509_rpki.cpp +++ b/src/lib/ffi/ffi_x509_rpki.cpp @@ -75,15 +75,6 @@ extern "C" { using namespace Botan_FFI; -int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks) { -#if defined(BOTAN_HAS_X509_CERTIFICATES) - return BOTAN_FFI_CHECKED_DELETE(as_blocks); -#else - BOTAN_UNUSED(opts); - return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; -#endif -} - int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_CHECKED_DELETE(ip_addr_blocks); @@ -335,6 +326,15 @@ int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip #endif } +int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_CHECKED_DELETE(as_blocks); +#else + BOTAN_UNUSED(opts); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_ext_create_as_blocks(botan_x509_ext_as_blocks_t* as_blocks) { if(as_blocks == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; diff --git a/src/lib/ffi/ffi_x509_rpki.h b/src/lib/ffi/ffi_x509_rpki.h index af80a171b3c..72b01c7bf07 100644 --- a/src/lib/ffi/ffi_x509_rpki.h +++ b/src/lib/ffi/ffi_x509_rpki.h @@ -20,9 +20,11 @@ using namespace Botan_FFI; #if defined(BOTAN_HAS_X509_CERTIFICATES) -struct botan_x509_ext_as_blocks_struct final : public botan_struct { +struct botan_x509_ext_ip_addr_blocks_struct final + : public botan_struct { public: - explicit botan_x509_ext_as_blocks_struct(std::unique_ptr obj, bool writable) : + explicit botan_x509_ext_ip_addr_blocks_struct(std::unique_ptr obj, + bool writable) : botan_struct(std::move(obj)), m_writable(writable) {} bool writable() const { return m_writable; } @@ -31,11 +33,9 @@ struct botan_x509_ext_as_blocks_struct final : public botan_struct { +struct botan_x509_ext_as_blocks_struct final : public botan_struct { public: - explicit botan_x509_ext_ip_addr_blocks_struct(std::unique_ptr obj, - bool writable) : + explicit botan_x509_ext_as_blocks_struct(std::unique_ptr obj, bool writable) : botan_struct(std::move(obj)), m_writable(writable) {} bool writable() const { return m_writable; } diff --git a/src/python/botan3.py b/src/python/botan3.py index 1063eb11342..ae8dde7f31d 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -508,37 +508,7 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_cert_hostname_match, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_cert_verify, [POINTER(c_int), c_void_p, c_void_p, c_size_t, c_void_p, c_size_t, c_char_p, c_size_t, c_char_p, c_uint64]) - ffi_api(dll.botan_x509_cert_opts_destroy, [c_void_p]) ffi_api(dll.botan_x509_ext_ip_addr_blocks_destroy, [c_void_p]) - ffi_api(dll.botan_x509_ext_as_blocks_destroy, [c_void_p]) - ffi_api(dll.botan_x509_ca_destroy, [c_void_p]) - ffi_api(dll.botan_x509_pkcs10_req_destroy, [c_void_p]) - ffi_api(dll.botan_x509_time_destroy, [c_void_p]) - ffi_api(dll.botan_x509_create_cert_opts, [c_void_p, c_char_p, POINTER(c_uint32)]) - ffi_api(dll.botan_x509_cert_opts_common_name, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_country, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_organization, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_org_unit, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_locality, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_state, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_serial_number, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_email, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_uri, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_ip, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_dns, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_xmpp, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_challenge, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_more_org_units, [c_void_p, POINTER(c_char_p), c_size_t]) - ffi_api(dll.botan_x509_cert_opts_more_dns, [c_void_p, POINTER(c_char_p), c_size_t]) - ffi_api(dll.botan_x509_cert_opts_ca_key, [c_void_p, c_size_t]) - ffi_api(dll.botan_x509_cert_opts_padding_scheme, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_opts_not_before, [c_void_p, c_void_p]) - ffi_api(dll.botan_x509_cert_opts_not_after, [c_void_p, c_void_p]) - ffi_api(dll.botan_x509_cert_opts_constraints, [c_void_p, c_uint32]) - ffi_api(dll.botan_x509_cert_opts_ex_constraint, [c_void_p, c_void_p]) - ffi_api(dll.botan_x509_cert_opts_ext_ip_addr_blocks, [c_void_p, c_void_p]) - ffi_api(dll.botan_x509_cert_opts_ext_as_blocks, [c_void_p, c_void_p]) - ffi_api(dll.botan_x509_create_time, [c_void_p, c_uint64]) ffi_api(dll.botan_x509_ext_create_ip_addr_blocks, [c_void_p]) ffi_api(dll.botan_x509_ext_create_ip_addr_blocks_from_cert, [c_void_p, c_void_p]) ffi_api(dll.botan_x509_ext_ip_addr_blocks_add_ip_addr, @@ -550,6 +520,7 @@ def ffi_api(fn, args, allowed_errors=None): [c_void_p, c_int, c_size_t, POINTER(c_int), POINTER(c_uint8), POINTER(c_int), POINTER(c_size_t)]) ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_address, [c_void_p, c_int, c_size_t, c_size_t, c_char_p, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_ext_as_blocks_destroy, [c_void_p]) ffi_api(dll.botan_x509_ext_create_as_blocks, [c_void_p]) ffi_api(dll.botan_x509_ext_create_as_blocks_from_cert, [c_void_p, c_void_p]) ffi_api(dll.botan_x509_ext_as_blocks_add_asnum, [c_void_p, c_uint32, c_uint32]) @@ -562,14 +533,36 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_ext_as_blocks_get_asnum_at, [c_void_p, c_size_t, POINTER(c_uint32), POINTER(c_uint32)]) ffi_api(dll.botan_x509_ext_as_blocks_get_rdi, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) ffi_api(dll.botan_x509_ext_as_blocks_get_rdi_at, [c_void_p, c_size_t, POINTER(c_uint32), POINTER(c_uint32)]) + ffi_api(dll.botan_x509_cert_params_builder_destroy, [c_void_p]) + ffi_api(dll.botan_x509_create_cert_params_builder, [c_void_p, c_char_p, POINTER(c_uint32)]) + ffi_api(dll.botan_x509_cert_params_builder_add_common_name, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_country, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_organization, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_org_unit, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_locality, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_state, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_serial_number, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_email, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_uri, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_ip, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_dns, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_xmpp, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_challenge, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_mark_as_ca_key, [c_void_p, c_size_t]) + ffi_api(dll.botan_x509_cert_params_builder_add_not_before, [c_void_p, c_uint64]) + ffi_api(dll.botan_x509_cert_params_builder_add_not_after, [c_void_p, c_uint64]) + ffi_api(dll.botan_x509_cert_params_builder_add_constraints, [c_void_p, c_uint32]) + ffi_api(dll.botan_x509_cert_params_builder_add_ex_constraint, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_ext_ip_addr_blocks, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_ext_as_blocks, [c_void_p, c_void_p]) ffi_api(dll.botan_x509_create_self_signed_cert, [c_void_p, c_void_p, c_void_p, c_char_p, c_char_p, c_void_p]) - ffi_api(dll.botan_x509_create_ca, - [c_void_p, c_void_p, c_void_p, c_char_p, c_char_p, c_void_p]) + ffi_api(dll.botan_x509_pkcs10_req_destroy, [c_void_p]) ffi_api(dll.botan_x509_create_pkcs10_req, [c_void_p, c_void_p, c_void_p, c_char_p, c_void_p]) + ffi_api(dll.botan_x509_pkcs10_req_view_pem, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) ffi_api(dll.botan_x509_sign_req, - [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p]) + [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint64, c_char_p, c_char_p]) dll.botan_x509_cert_validation_status.argtypes = [c_int] dll.botan_x509_cert_validation_status.restype = c_char_p @@ -1871,101 +1864,66 @@ def _load_buf_or_file(filename, buf, file_fn, buf_fn): # # X.509 certificates # - -class X509Time: - def __init__(self, time_since_epoch): - self.__obj = c_void_p(0) - _DLL.botan_x509_create_time(byref(self.__obj), c_uint64(time_since_epoch)) - - def __del__(self): - _DLL.botan_x509_time_destroy(self.__obj) - - def handle_(self): - return self.__obj - - -class PKCS10Req: - def __init__(self): - self.__obj = c_void_p(0) - - def __del__(self): - _DLL.botan_x509_pkcs10_req_destroy(self.__obj) - - def handle_(self): - return self.__obj - - -class X509Opts: +class X509CertificateBuilder: def __init__(self, opts, expire_time=None): self.__obj = c_void_p(0) - _DLL.botan_x509_create_cert_opts(byref(self.__obj), _ctype_str(opts), c_uint32(expire_time) if expire_time else None) + _DLL.botan_x509_create_cert_params_builder(byref(self.__obj), _ctype_str(opts), c_uint32(expire_time) if expire_time else None) def __del__(self): - _DLL.botan_x509_cert_opts_destroy(self.__obj) + _DLL.botan_x509_cert_params_builder_destroy(self.__obj) def handle_(self): return self.__obj - def set_common_name(self, name): - _DLL.botan_x509_cert_opts_common_name(self.__obj, _ctype_str(name)) - - def set_country(self, country): - _DLL.botan_x509_cert_opts_country(self.__obj, _ctype_str(country)) - - def set_organization(self, organization): - _DLL.botan_x509_cert_opts_organization(self.__obj, _ctype_str(organization)) - - def set_org_unit(self, org_unit): - _DLL.botan_x509_cert_opts_org_unit(self.__obj, _ctype_str(org_unit)) + def add_common_name(self, name): + _DLL.botan_x509_cert_params_builder_add_common_name(self.__obj, _ctype_str(name)) - def set_locality(self, locality): - _DLL.botan_x509_cert_opts_locality(self.__obj, _ctype_str(locality)) + def add_country(self, country): + _DLL.botan_x509_cert_params_builder_add_country(self.__obj, _ctype_str(country)) - def set_state(self, state): - _DLL.botan_x509_cert_opts_state(self.__obj, _ctype_str(state)) + def add_organization(self, organization): + _DLL.botan_x509_cert_params_builder_add_organization(self.__obj, _ctype_str(organization)) - def set_serial_number(self, serial_number): - _DLL.botan_x509_cert_opts_serial_number(self.__obj, _ctype_str(serial_number)) + def add_org_unit(self, org_unit): + _DLL.botan_x509_cert_params_builder_add_org_unit(self.__obj, _ctype_str(org_unit)) - def set_email(self, email): - _DLL.botan_x509_cert_opts_email(self.__obj, _ctype_str(email)) + def add_locality(self, locality): + _DLL.botan_x509_cert_params_builder_add_locality(self.__obj, _ctype_str(locality)) - def set_uri(self, uri): - _DLL.botan_x509_cert_opts_uri(self.__obj, _ctype_str(uri)) + def add_state(self, state): + _DLL.botan_x509_cert_params_builder_add_state(self.__obj, _ctype_str(state)) - def set_ip(self, ip): - _DLL.botan_x509_cert_opts_ip(self.__obj, _ctype_str(ip)) + def add_serial_number(self, serial_number): + _DLL.botan_x509_cert_params_builder_add_serial_number(self.__obj, _ctype_str(serial_number)) - def set_dns(self, dns): - _DLL.botan_x509_cert_opts_dns(self.__obj, _ctype_str(dns)) + def add_email(self, email): + _DLL.botan_x509_cert_params_builder_add_email(self.__obj, _ctype_str(email)) - def set_xmpp(self, xmpp): - _DLL.botan_x509_cert_opts_xmpp(self.__obj, _ctype_str(xmpp)) + def add_uri(self, uri): + _DLL.botan_x509_cert_params_builder_add_uri(self.__obj, _ctype_str(uri)) - def set_challenge(self, challenge): - _DLL.botan_x509_cert_opts_challenge(self.__obj, _ctype_str(challenge)) + def add_ip(self, ip): + _DLL.botan_x509_cert_params_builder_add_ip(self.__obj, _ctype_str(ip)) - def set_more_org_units(self, more_org_units): - cnt = len(more_org_units) - _DLL.botan_x509_cert_opts_more_org_units(self.__obj, (c_char_p * cnt)(*(_ctype_str(x) for x in more_org_units)), c_size_t(cnt)) + def add_dns(self, dns): + _DLL.botan_x509_cert_params_builder_add_dns(self.__obj, _ctype_str(dns)) - def set_more_dns(self, more_dns): - cnt = len(more_dns) - _DLL.botan_x509_cert_opts_more_dns(self.__obj, (c_char_p * cnt)(*(_ctype_str(x) for x in more_dns)), c_size_t(cnt)) + def add_xmpp(self, xmpp): + _DLL.botan_x509_cert_params_builder_add_xmpp(self.__obj, _ctype_str(xmpp)) - def set_ca_key(self, limit): - _DLL.botan_x509_cert_opts_ca_key(self.__obj, c_size_t(limit)) + def add_challenge(self, challenge): + _DLL.botan_x509_cert_params_builder_add_challenge(self.__obj, _ctype_str(challenge)) - def set_padding_scheme(self, scheme): - _DLL.botan_x509_cert_opts_padding_scheme(self.__obj, _ctype_str(scheme)) + def mark_as_ca_key(self, limit): + _DLL.botan_x509_cert_params_builder_mark_as_ca_key(self.__obj, c_size_t(limit)) - def set_not_before(self, not_before): - _DLL.botan_x509_cert_opts_not_before(self.__obj, not_before.handle_()) + def add_not_before(self, time_since_epoch): + _DLL.botan_x509_cert_params_builder_add_not_before(self.__obj, time_since_epoch) - def set_not_after(self, not_after): - _DLL.botan_x509_cert_opts_not_after(self.__obj, not_after.handle_()) + def add_not_after(self, time_since_epoch): + _DLL.botan_x509_cert_params_builder_add_not_after(self.__obj, time_since_epoch) - def set_constraints(self, usage_list): + def add_constraints(self, usage_list): # TODO this sucks, where enum usage_values = {"NO_CONSTRAINTS": 0, "DIGITAL_SIGNATURE": 32768, @@ -1982,22 +1940,22 @@ def set_constraints(self, usage_list): if u not in usage_values: pass usage += usage_values[u] - _DLL.botan_x509_cert_opts_constraints(self.__obj, c_uint32(usage)) + _DLL.botan_x509_cert_params_builder_add_constraints(self.__obj, c_uint32(usage)) def add_ex_constraints(self, oid): - _DLL.botan_x509_cert_opts_ex_constraint(self.__obj, oid.handle_()) + _DLL.botan_x509_cert_params_builder_add_ex_constraint(self.__obj, oid.handle_()) + + def add_ext_ip_addr_blocks(self, ip_addr_blocks): + _DLL.botan_x509_cert_params_builder_add_ext_ip_addr_blocks(self.__obj, ip_addr_blocks.handle_()) + + def add_ext_as_blocks(self, as_blocks): + _DLL.botan_x509_cert_params_builder_add_ext_as_blocks(self.__obj, as_blocks.handle_()) def create_req(self, key, hash_fn, rng): req = PKCS10Req() _DLL.botan_x509_create_pkcs10_req(byref(req.handle_()), self.__obj, key.handle_(), _ctype_str(hash_fn), rng.handle_()) return req - def add_ext_ip_addr_blocks(self, ip_addr_blocks): - _DLL.botan_x509_cert_opts_ext_ip_addr_blocks(self.__obj, ip_addr_blocks.handle_()) - - def add_ext_as_blocks(self, as_blocks): - _DLL.botan_x509_cert_opts_ext_as_blocks(self.__obj, as_blocks.handle_()) - class X509ExtIPAddrBlocks: def __init__(self, cert=None): @@ -2013,10 +1971,10 @@ def __del__(self): def handle_(self): return self.__obj - def add_ip(self, ip, safi=None): - self.add_ip_range(ip, ip, safi) + def add_addr(self, ip, safi=None): + self.add_range(ip, ip, safi) - def add_ip_range(self, min_, max_, safi=None): + def add_range(self, min_, max_, safi=None): min_len = len(min_) if min_len not in (4, 16) or len(max_) != min_len: raise BotanException("Address must be 4 or 16 bytes long") @@ -2069,10 +2027,10 @@ def addresses(self): mi, ma, l)) - ranges.append((list(min_), list(max_))) + ranges.append((tuple(min_), tuple(max_))) safi = safi.value if has_safi.value == 1 else None - ranges = tuple(ranges) if ranges is not None else None + ranges = ranges if ranges is not None else None if ipv6 == 0: v4.append((safi, ranges)) else: @@ -2154,6 +2112,35 @@ def rdi(self): return rdis +class PKCS10Req: + def __init__(self): + self.__obj = c_void_p(0) + + def __del__(self): + _DLL.botan_x509_pkcs10_req_destroy(self.__obj) + + def handle_(self): + return self.__obj + + def sign(self, issuing_cert, issuing_key, rng, not_before, not_after, hash_fn, padding): + cert = X509Cert() + _DLL.botan_x509_sign_req( + byref(cert.handle_()), + self.__obj, + issuing_cert.handle_(), + issuing_key.handle_(), + rng.handle_(), + not_before, + not_after, + _ctype_str(hash_fn), + _ctype_str(padding) + ) + return cert + + def to_pem(self): + return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_x509_pkcs10_req_view_pem(self.__obj, vc, vfn)) + + class X509Cert: # pylint: disable=invalid-name def __init__(self, filename: str | None = None, buf: bytes | None = None): if not filename and not buf: @@ -2165,9 +2152,9 @@ def __del__(self): _DLL.botan_x509_cert_destroy(self.__obj) @classmethod - def create_self_signed(cls, key, opts, hash_fn, sig_padding, rng): + def create_self_signed(cls, key, opts, hash_fn, padding, rng): cert = X509Cert() - _DLL.botan_x509_create_self_signed_cert(byref(cert.handle_()), key.handle_(), opts.handle_(), _ctype_str(hash_fn), _ctype_str(sig_padding), rng.handle_()) + _DLL.botan_x509_create_self_signed_cert(byref(cert.handle_()), key.handle_(), opts.handle_(), _ctype_str(hash_fn), _ctype_str(padding), rng.handle_()) return cert def time_starts(self) -> datetime: @@ -2387,18 +2374,6 @@ def is_revoked(self, crl: X509CRL) -> bool: return rc == 0 -class X509Ca: - def __init__(self, cert, key, rng, hash_fn, sig_padding=""): - self.__obj = c_void_p(0) - _DLL.botan_x509_create_ca(byref(self.__obj), cert.handle_(), key.handle_(), _ctype_str(hash_fn), _ctype_str(sig_padding), rng.handle_()) - - def __del__(self): - _DLL.botan_x509_ca_destroy(self.__obj) - - def sign(self, req, rng, not_before, not_after): - cert = X509Cert() - _DLL.botan_x509_sign_req(byref(cert.handle_()), self.__obj, req.handle_(), rng.handle_(), not_before.handle_(), not_after.handle_()) - return cert # diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 9e92466c5af..6c2a769f103 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -828,10 +828,10 @@ def test_cert_creation(self): rng = botan.RandomNumberGenerator() ca_key = botan.PrivateKey.create("ECDSA", group, rng) - ca_opts = botan.X509Opts("Test CA/US/Botan Project/Testing") - ca_opts.set_ca_key(1) - ca_opts.set_constraints(["DIGITAL_SIGNATURE"]) - ca_cert = botan.X509Cert.create_self_signed(ca_key, ca_opts, hash_fn, padding_method, rng) + ca_builder = botan.X509CertificateBuilder("Test CA/US/Botan Project/Testing") + ca_builder.mark_as_ca_key(1) + ca_builder.add_constraints(["DIGITAL_SIGNATURE"]) + ca_cert = botan.X509Cert.create_self_signed(ca_key, ca_builder, hash_fn, padding_method, rng) constraints = ca_cert.key_constraints() self.assertEqual(len(constraints), 3) for item in ["DIGITAL_SIGNATURE", "KEY_CERT_SIGN", "CRL_SIGN"]: @@ -839,15 +839,15 @@ def test_cert_creation(self): self.assertTrue(ca_cert.is_self_signed()) self.assertEqual(ca_cert.key_constraints(), ["DIGITAL_SIGNATURE", "KEY_CERT_SIGN", "CRL_SIGN"]) - ca = botan.X509Ca(ca_cert, ca_key, rng, hash_fn) cert_key = botan.PrivateKey.create("ECDSA", group, rng) - req_opts = botan.X509Opts("Test CA/US/Botan Project/Testing") - req_opts.set_uri("https://botan.randombit.net") - req_opts.set_more_dns(["botan.randombit.net", "randombit.net"]) - req = req_opts.create_req(cert_key, hash_fn, rng) + req_builder = botan.X509CertificateBuilder("Test CA/US/Botan Project/Testing") + req_builder.add_uri("https://botan.randombit.net") + for item in ["imaginary.botan.randombit.net", "botan.randombit.net", "randombit.net"]: + req_builder.add_dns(item) + req = req_builder.create_req(cert_key, hash_fn, rng) - cert = ca.sign(req, rng, botan.X509Time(int(time.time())), botan.X509Time(int(time.time()) + 60)) + cert = req.sign(ca_cert, ca_key, rng, int(time.time()), int(time.time()) + 60, hash_fn, padding_method) self.assertFalse(cert.is_self_signed()) self.assertEqual(cert.key_constraints(), ["NO_CONSTRAINTS"]) @@ -866,17 +866,17 @@ def test_x509_rpki(self): rng = botan.RandomNumberGenerator() ca_key = botan.PrivateKey.create("ECDSA", group, rng) - ca_opts = botan.X509Opts("Test CA/US/Botan Project/Testing") - ca_opts.set_ca_key(1) - ca_opts.set_constraints(["DIGITAL_SIGNATURE"]) + ca_builder = botan.X509CertificateBuilder("Test CA/US/Botan Project/Testing") + ca_builder.mark_as_ca_key(1) + ca_builder.add_constraints(["DIGITAL_SIGNATURE"]) ca_ip_addr_blocks = botan.X509ExtIPAddrBlocks() - ca_ip_addr_blocks.add_ip([192, 168, 2, 1]) - ca_ip_addr_blocks.add_ip_range([10, 0, 0, 1], [10, 0, 255, 255]) + ca_ip_addr_blocks.add_addr([192, 168, 2, 1]) + ca_ip_addr_blocks.add_range([10, 0, 0, 1], [10, 0, 255, 255]) ca_ip_addr_blocks.restrict(False, 42) - ca_ip_addr_blocks.add_ip([0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]) + ca_ip_addr_blocks.add_addr([0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]) ca_ip_addr_blocks.restrict(True, 234) ca_as_blocks = botan.X509ExtASBlocks() @@ -884,28 +884,34 @@ def test_x509_rpki(self): ca_as_blocks.add_asnum_range(3000, 4999) ca_as_blocks.restrict_rdi() - ca_opts.add_ext_ip_addr_blocks(ca_ip_addr_blocks) - ca_opts.add_ext_as_blocks(ca_as_blocks) + ca_builder.add_ext_ip_addr_blocks(ca_ip_addr_blocks) + ca_builder.add_ext_as_blocks(ca_as_blocks) - ca_cert = botan.X509Cert.create_self_signed(ca_key, ca_opts, hash_fn, padding_method, rng) + with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): + ca_builder.add_ext_ip_addr_blocks(ca_ip_addr_blocks) + + with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): + ca_builder.add_ext_as_blocks(ca_as_blocks) + + ca_cert = botan.X509Cert.create_self_signed(ca_key, ca_builder, hash_fn, padding_method, rng) ca_ip_addr_blocks = ca_cert.ext_ip_addr_blocks() self.assertEqual(ca_ip_addr_blocks.addresses(), ( [ - (None, (([10, 0, 0, 1], [10, 0, 255, 255]), ([192, 168, 2, 1], [192, 168, 2, 1]))), - (42, ()) + (None, [((10, 0, 0, 1), (10, 0, 255, 255)), ((192, 168, 2, 1), (192, 168, 2, 1))]), + (42, []) ], [ - (None, (( - [0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], - [0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01] - ), )), - (234, ()) + (None, [( + (0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01), + (0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01) + )], ), + (234, []) ] )) with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): - ca_ip_addr_blocks.add_ip([123, 0, 0, 1]) + ca_ip_addr_blocks.add_addr([123, 0, 0, 1]) ca_as_blocks = ca_cert.ext_as_blocks() self.assertEqual(ca_as_blocks.asnum(), [(30, 30), (3000, 4999)]) @@ -914,14 +920,12 @@ def test_x509_rpki(self): with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): ca_as_blocks.add_asnum(999) - ca = botan.X509Ca(ca_cert, ca_key, rng, hash_fn) - cert_key = botan.PrivateKey.create("ECDSA", group, rng) - req_opts = botan.X509Opts("Test CA/US/Botan Project/Testing") + req_builder = botan.X509CertificateBuilder("Test CA/US/Botan Project/Testing") req_ip_addr_blocks = botan.X509ExtIPAddrBlocks() - req_ip_addr_blocks.add_ip([192, 168, 2, 1]) - req_ip_addr_blocks.add_ip_range([10, 0, 5, 5], [10, 0, 7, 7]) + req_ip_addr_blocks.add_addr([192, 168, 2, 1]) + req_ip_addr_blocks.add_range([10, 0, 5, 5], [10, 0, 7, 7]) req_ip_addr_blocks.restrict(False, 42) req_ip_addr_blocks.inherit(True) req_ip_addr_blocks.inherit(True, 234) @@ -930,17 +934,17 @@ def test_x509_rpki(self): req_as_blocks.add_asnum_range(3100, 4000) req_as_blocks.inherit_rdi() - req_opts.add_ext_ip_addr_blocks(req_ip_addr_blocks) - req_opts.add_ext_as_blocks(req_as_blocks) - req = req_opts.create_req(cert_key, hash_fn, rng) + req_builder.add_ext_ip_addr_blocks(req_ip_addr_blocks) + req_builder.add_ext_as_blocks(req_as_blocks) + req = req_builder.create_req(cert_key, hash_fn, rng) - cert = ca.sign(req, rng, botan.X509Time(int(time.time())), botan.X509Time(int(time.time()) + 60)) + cert = req.sign(ca_cert, ca_key, rng, int(time.time()), int(time.time()) + 60, hash_fn, padding_method) req_ip_addr_blocks = cert.ext_ip_addr_blocks() self.assertEqual(req_ip_addr_blocks.addresses(), ( [ - (None, (([10, 0, 5, 5], [10, 0, 7, 7]), ([192, 168, 2, 1], [192, 168, 2, 1]))), - (42, ()) + (None, [((10, 0, 5, 5), (10, 0, 7, 7)), ((192, 168, 2, 1), (192, 168, 2, 1))]), + (42, []) ], [ (None, None), diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index b5c0b6a5f79..9d57007971c 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -799,7 +799,6 @@ class FFI_Cert_Creation_Test final : public FFI_Test { void ffi_test(Test::Result& result, botan_rng_t rng) override { const std::string hash_fn{"SHA-256"}; - const std::string padding_method{"EMSA3(SHA-256)"}; botan_privkey_t ca_key; botan_privkey_t cert_key; @@ -811,42 +810,34 @@ class FFI_Cert_Creation_Test final : public FFI_Test { std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) .count(); - botan_x509_time_t not_before; - botan_x509_time_t not_after; - - if(TEST_FFI_INIT(botan_x509_create_time, (¬_before, now))) { - TEST_FFI_OK(botan_x509_create_time, (¬_after, now + 60 * 60)); - - botan_x509_cert_opts_t ca_opts; - TEST_FFI_OK(botan_x509_create_cert_opts, (&ca_opts, "Test CA/US/Botan Project/Testing", nullptr)); - TEST_FFI_OK(botan_x509_cert_opts_ca_key, (ca_opts, 1)); - TEST_FFI_OK(botan_x509_cert_opts_uri, (ca_opts, "https://botan.randombit.net")); - const char* dns_names[] = {"botan.randombit.net", "randombit.net"}; - TEST_FFI_OK(botan_x509_cert_opts_more_dns, (ca_opts, dns_names, 2)); + botan_x509_cert_params_builder_t ca_builder; + if(TEST_FFI_INIT(botan_x509_create_cert_params_builder, + (&ca_builder, "Test CA/US/Botan Project/Testing", nullptr))) { + TEST_FFI_OK(botan_x509_cert_params_builder_mark_as_ca_key, (ca_builder, 1)); + TEST_FFI_OK(botan_x509_cert_params_builder_add_uri, (ca_builder, "https://botan.randombit.net")); + for(const auto* dns : {"imaginary.botan.randombit.net", "botan.randombit.net", "randombit.net"}) { + TEST_FFI_OK(botan_x509_cert_params_builder_add_dns, (ca_builder, dns)); + } botan_x509_cert_t ca_cert; - TEST_FFI_OK(botan_x509_create_self_signed_cert, (&ca_cert, ca_key, ca_opts, hash_fn.c_str(), "", rng)); - - botan_x509_ca_t ca; - TEST_FFI_OK(botan_x509_create_ca, (&ca, ca_cert, ca_key, hash_fn.c_str(), padding_method.c_str(), rng)); + TEST_FFI_OK(botan_x509_create_self_signed_cert, + (&ca_cert, ca_key, ca_builder, hash_fn.c_str(), "", rng)); - botan_x509_cert_opts_t req_opts; - TEST_FFI_OK(botan_x509_create_cert_opts, (&req_opts, "Test CA/US/Botan Project/Testing", nullptr)); + botan_x509_cert_params_builder_t req_opts; + TEST_FFI_OK(botan_x509_create_cert_params_builder, + (&req_opts, "Test CA/US/Botan Project/Testing", nullptr)); botan_x509_pkcs10_req_t req; TEST_FFI_OK(botan_x509_create_pkcs10_req, (&req, req_opts, cert_key, hash_fn.c_str(), rng)); botan_x509_cert_t cert; - TEST_FFI_OK(botan_x509_sign_req, (&cert, ca, req, rng, not_before, not_after)); + TEST_FFI_OK(botan_x509_sign_req, (&cert, req, ca_cert, ca_key, rng, now, now + 60, hash_fn.c_str(), "")); int rc; TEST_FFI_RC(0, botan_x509_cert_verify, (&rc, cert, nullptr, 0, &ca_cert, 1, nullptr, 0, nullptr, 0)); - TEST_FFI_OK(botan_x509_time_destroy, (not_before)); - TEST_FFI_OK(botan_x509_time_destroy, (not_after)); - TEST_FFI_OK(botan_x509_cert_opts_destroy, (ca_opts)); - TEST_FFI_OK(botan_x509_cert_opts_destroy, (req_opts)); - TEST_FFI_OK(botan_x509_ca_destroy, (ca)); + TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (ca_builder)); + TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (req_opts)); TEST_FFI_OK(botan_x509_pkcs10_req_destroy, (req)); TEST_FFI_OK(botan_x509_cert_destroy, (ca_cert)); TEST_FFI_OK(botan_x509_cert_destroy, (cert)); @@ -876,15 +867,11 @@ class FFI_X509_RPKI_Test final : public FFI_Test { std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) .count(); - botan_x509_time_t not_before; - botan_x509_time_t not_after; - - if(TEST_FFI_INIT(botan_x509_create_time, (¬_before, now))) { - TEST_FFI_OK(botan_x509_create_time, (¬_after, now + 60 * 60)); - - botan_x509_cert_opts_t ca_opts; - TEST_FFI_OK(botan_x509_create_cert_opts, (&ca_opts, "Test CA/US/Botan Project/Testing", nullptr)); - TEST_FFI_OK(botan_x509_cert_opts_ca_key, (ca_opts, 1)); + // now + 60 * 60 + botan_x509_cert_params_builder_t ca_builder; + if(TEST_FFI_INIT(botan_x509_create_cert_params_builder, + (&ca_builder, "Test CA/US/Botan Project/Testing", nullptr))) { + TEST_FFI_OK(botan_x509_cert_params_builder_mark_as_ca_key, (ca_builder, 1)); botan_x509_ext_ip_addr_blocks_t ca_ip_addr_blocks; TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks, (&ca_ip_addr_blocks)); @@ -985,7 +972,10 @@ class FFI_X509_RPKI_Test final : public FFI_Test { result.confirm("index 1 has a value", present == 1); result.confirm("index 1 has entries", count == 0); - TEST_FFI_OK(botan_x509_cert_opts_ext_ip_addr_blocks, (ca_opts, ca_ip_addr_blocks)); + TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_ip_addr_blocks, (ca_builder, ca_ip_addr_blocks)); + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_cert_params_builder_add_ext_ip_addr_blocks, + (ca_builder, ca_ip_addr_blocks)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (ca_ip_addr_blocks)); botan_x509_ext_as_blocks_t ca_as_blocks; @@ -1019,37 +1009,40 @@ class FFI_X509_RPKI_Test final : public FFI_Test { botan_x509_ext_as_blocks_get_rdi_at, (ca_as_blocks, 0, &min_out_, &max_out_)); - TEST_FFI_OK(botan_x509_cert_opts_ext_as_blocks, (ca_opts, ca_as_blocks)); + TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_as_blocks, (ca_builder, ca_as_blocks)); + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_x509_cert_params_builder_add_ext_as_blocks, + (ca_builder, ca_as_blocks)); TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (ca_as_blocks)); botan_x509_cert_t ca_cert; - TEST_FFI_OK(botan_x509_create_self_signed_cert, (&ca_cert, ca_key, ca_opts, hash_fn.c_str(), "", rng)); + TEST_FFI_OK(botan_x509_create_self_signed_cert, + (&ca_cert, ca_key, ca_builder, hash_fn.c_str(), "", rng)); - botan_x509_ca_t ca; - TEST_FFI_OK(botan_x509_create_ca, (&ca, ca_cert, ca_key, hash_fn.c_str(), padding_method.c_str(), rng)); - - botan_x509_cert_opts_t req_opts; - TEST_FFI_OK(botan_x509_create_cert_opts, (&req_opts, "Test CA/US/Botan Project/Testing", nullptr)); + botan_x509_cert_params_builder_t req_builder; + TEST_FFI_OK(botan_x509_create_cert_params_builder, + (&req_builder, "Test CA/US/Botan Project/Testing", nullptr)); botan_x509_ext_ip_addr_blocks_t req_ip_addr_blocks; TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks, (&req_ip_addr_blocks)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_inherit, (req_ip_addr_blocks, 0, nullptr)); - TEST_FFI_OK(botan_x509_cert_opts_ext_ip_addr_blocks, (req_opts, req_ip_addr_blocks)); + TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_ip_addr_blocks, (req_builder, req_ip_addr_blocks)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (req_ip_addr_blocks)); botan_x509_ext_as_blocks_t req_as_blocks; TEST_FFI_OK(botan_x509_ext_create_as_blocks, (&req_as_blocks)); TEST_FFI_OK(botan_x509_ext_as_blocks_inherit_asnum, (req_as_blocks)); - TEST_FFI_OK(botan_x509_cert_opts_ext_as_blocks, (req_opts, req_as_blocks)); + TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_as_blocks, (req_builder, req_as_blocks)); TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (req_as_blocks)); botan_x509_pkcs10_req_t req; - TEST_FFI_OK(botan_x509_create_pkcs10_req, (&req, req_opts, cert_key, hash_fn.c_str(), rng)); + TEST_FFI_OK(botan_x509_create_pkcs10_req, (&req, req_builder, cert_key, hash_fn.c_str(), rng)); botan_x509_cert_t cert; - TEST_FFI_OK(botan_x509_sign_req, (&cert, ca, req, rng, not_before, not_after)); + TEST_FFI_OK(botan_x509_sign_req, + (&cert, req, ca_cert, ca_key, rng, now, now + 60, hash_fn.c_str(), padding_method.c_str())); botan_x509_ext_ip_addr_blocks_t req_ip_addr_blocks_from_cert; TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks_from_cert, (&req_ip_addr_blocks_from_cert, cert)); @@ -1126,12 +1119,8 @@ class FFI_X509_RPKI_Test final : public FFI_Test { int rc; TEST_FFI_RC(0, botan_x509_cert_verify, (&rc, cert, nullptr, 0, &ca_cert, 1, nullptr, 0, nullptr, 0)); - - TEST_FFI_OK(botan_x509_time_destroy, (not_before)); - TEST_FFI_OK(botan_x509_time_destroy, (not_after)); - TEST_FFI_OK(botan_x509_cert_opts_destroy, (ca_opts)); - TEST_FFI_OK(botan_x509_cert_opts_destroy, (req_opts)); - TEST_FFI_OK(botan_x509_ca_destroy, (ca)); + TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (ca_builder)); + TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (req_builder)); TEST_FFI_OK(botan_x509_pkcs10_req_destroy, (req)); TEST_FFI_OK(botan_x509_cert_destroy, (ca_cert)); TEST_FFI_OK(botan_x509_cert_destroy, (cert)); From 2df6d4a166f8248d1176bdf3b26aa0cd5a9e425a Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Wed, 16 Jul 2025 23:30:20 +0200 Subject: [PATCH 11/14] use certificate params builder --- doc/api_ref/ffi.rst | 59 +++++----- doc/api_ref/python.rst | 41 +++---- src/lib/ffi/ffi.h | 53 ++++----- src/lib/ffi/ffi_cert.cpp | 227 +++++++++++++++++-------------------- src/lib/ffi/ffi_cert.h | 3 +- src/python/botan3.py | 126 ++++++++++---------- src/scripts/test_python.py | 51 +++++---- src/tests/test_ffi.cpp | 64 ++++++----- 8 files changed, 298 insertions(+), 326 deletions(-) diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index 7b2165ff562..3baa82c032d 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -1918,13 +1918,11 @@ X.509 Certificates .. cpp:type:: opaque* botan_x509_cert_params_builder_t -.. cpp:function::int botan_x509_cert_opts_destroy(botan_x509_cert_opts_t opts) +.. cpp:function::int botan_x509_cert_params_builder_destroy(botan_x509_cert_params_builder_t builder) Destroy the options object. -.. cpp:function::int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* builder_obj, \ - const char* opts, \ - uint32_t* expire_time); +.. cpp:function::int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* builder_obj); Create a new certificate builder object. ``opts`` defines the common name (e.g. `common_name/country/organization/organizational_unit`). ``expire_time`` if given is the expiration time from current clock in seconds. @@ -1933,57 +1931,50 @@ X.509 Certificates .. cpp:function::int botan_x509_cert_params_builder_add_country(botan_x509_cert_params_builder_t builder, const char* country); -.. cpp:function::int botan_x509_cert_params_builder_add_organization(botan_x509_cert_params_builder_t builder, const char* organization); - -.. cpp:function::int botan_x509_cert_params_builder_add_org_unit(botan_x509_cert_params_builder_t builder, const char* org_unit); +.. cpp:function::int botan_x509_cert_params_builder_add_state(botan_x509_cert_params_builder_t builder, const char* state); .. cpp:function::int botan_x509_cert_params_builder_add_locality(botan_x509_cert_params_builder_t builder, const char* locality); -.. cpp:function::int botan_x509_cert_params_builder_add_state(botan_x509_cert_params_builder_t builder, const char* state); - .. cpp:function::int botan_x509_cert_params_builder_add_serial_number(botan_x509_cert_params_builder_t builder, const char* serial_number); -.. cpp:function::int botan_x509_cert_params_builder_add_email(botan_x509_cert_params_builder_t builder, const char* email); +.. cpp:function::int botan_x509_cert_params_builder_add_organization(botan_x509_cert_params_builder_t builder, const char* organization); -.. cpp:function::int botan_x509_cert_params_builder_add_uri(botan_x509_cert_params_builder_t builder, const char* uri); +.. cpp:function::int botan_x509_cert_params_builder_add_organizational_unit(botan_x509_cert_params_builder_t builder, const char* org_unit); -.. cpp:function::int botan_x509_cert_params_builder_add_ip(botan_x509_cert_params_builder_t builder, const char* ip); +.. cpp:function::int botan_x509_cert_params_builder_add_email(botan_x509_cert_params_builder_t builder, const char* email); .. cpp:function::int botan_x509_cert_params_builder_add_dns(botan_x509_cert_params_builder_t builder, const char* dns); -.. cpp:function::int botan_x509_cert_params_builder_add_xmpp(botan_x509_cert_params_builder_t builder, const char* xmpp); - -.. cpp:function::int botan_x509_cert_params_builder_add_challenge(botan_x509_cert_params_builder_t builder, const char* challenge); - -.. cpp:function::int botan_x509_cert_params_builder_mark_as_ca_key(botan_x509_cert_params_builder_t builder, size_t limit); +.. cpp:function::int botan_x509_cert_params_builder_add_uri(botan_x509_cert_params_builder_t builder, const char* uri); - Mark the certificate for CA usage. +.. cpp:function::int botan_x509_cert_params_builder_add_xmpp(botan_x509_cert_params_builder_t builder, const char* xmpp); -.. cpp:function::int botan_x509_cert_params_builder_add_not_before(botan_x509_cert_params_builder_t builder, uint64_t time_since_epoch); +.. cpp:function::int botan_x509_cert_params_builder_add_ip(botan_x509_cert_params_builder_t builder, uint32_t ipv4); - ``time_since_epoch`` is expected to be in seconds. +.. cpp:function::int botan_x509_cert_params_builder_add_allowed_usage(botan_x509_cert_params_builder_t builder, uint32_t usage); -.. cpp:function::int botan_x509_cert_params_builder_add_not_after(botan_x509_cert_params_builder_t builder, uint64_t time_since_epoch); +.. cpp:function::int botan_x509_cert_params_builder_add_allowed_extended_usage(botan_x509_cert_params_builder_t builder, botan_asn1_oid_t oid); -.. cpp:function::int botan_x509_cert_params_builder_add_constraints(botan_x509_cert_params_builder_t builder, uint32_t usage); +.. cpp:function::int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t limit); -.. cpp:function::int botan_x509_cert_params_builder_add_ex_constraint(botan_x509_cert_params_builder_t builder, botan_asn1_oid_t oid); + Mark the certificate for CA usage. .. cpp:function::int botan_x509_cert_params_builder_add_ext_ip_addr_blocks(botan_x509_cert_params_builder_t builder, \ - botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int is_critical); .. cpp:function::int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_builder_t builder, \ - botan_x509_ext_as_blocks_t as_blocks); + botan_x509_ext_as_blocks_t as_blocks, int is_critical); .. cpp:function::int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, \ botan_privkey_t key, \ - botan_x509_cert_opts_t opts, \ + botan_x509_cert_params_builder_t builder, \ + botan_rng_t rng, \ + uint64_t not_before, \ + uint64_t not_after, \ const char* hash_fn, \ - const char* padding, \ - botan_rng_t rng) - + const char* padding) - Create a new self-signed X.509 certificate. + Create a new self-signed X.509 certificate. ``not_before`` and ``not_after`` are expected to be the time since the UNIX epoch, in seconds. .. cpp:type:: opaque* botan_x509_pkcs10_req_t @@ -1994,12 +1985,14 @@ X.509 Certificates Destroy the PKCS #10 certificate request object. .. cpp:function::int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, \ - botan_x509_cert_opts_t opts, \ botan_privkey_t key, \ + botan_x509_cert_params_builder_t builder, \ + botan_rng_t rng, \ const char* hash_fn, \ - botan_rng_t rng) + const char* padding, \ + const char* challenge_password) - Create a PCKS #10 certificate request. + Create a PCKS #10 certificate request. ``challenge_password``, ``hash_fn`` and ``padding`` may be NULL. .. cpp:function::int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view) diff --git a/doc/api_ref/python.rst b/doc/api_ref/python.rst index e3e1e21ac5a..dbc3a6dd8cd 100644 --- a/doc/api_ref/python.rst +++ b/doc/api_ref/python.rst @@ -725,45 +725,42 @@ X509CertificateBuilder .. py:method:: add_country(country) - .. py:method:: add_organization(organization) - - .. py:method:: add_org_unit(org_unit) + .. py:method:: add_state(state) .. py:method:: add_locality(locality) - .. py:method:: add_state(state) - .. py:method:: add_serial_number(serial_number) - .. py:method:: add_email(email) + .. py:method:: add_organization(organization) - .. py:method:: add_uri(uri) + .. py:method:: add_organizational_unit(org_unit) - .. py:method:: add_ip(ip) + .. py:method:: add_email(email) .. py:method:: add_dns(dns) - .. py:method:: add_xmpp(xmpp) + .. py:method:: add_uri(uri) - .. py:method:: add_challenge(challenge) + .. py:method:: add_xmpp(xmpp) - .. py:method:: mark_as_ca_key(limit) + .. py:method:: add_ipv4(ipv4) - .. py:method:: add_not_before(time_since_epoch) + .. py:method:: add_allowed_usage(usage_list) - ``time_since_epoch`` is expected to be in seconds. + .. py:method:: add_allowed_extended_usage(oid) - .. py:method:: add_not_after(time_since_epoch) + .. py:method:: set_as_ca_certificate(limit) - .. py:method:: add_constraints(usage_list) + .. py:method:: add_ext_ip_addr_blocks(ip_addr_blocks, is_critical) - .. py:method:: add_ex_constraints(oid) + .. py:method:: add_ext_as_blocks(as_blocks, is_critical) - .. py:method:: add_ext_ip_addr_blocks(ip_addr_blocks) + .. py:method:: create_self_signed(key, rng, not_before, not_after, hash_fn=None, padding=None) - .. py:method:: add_ext_as_blocks(as_blocks) + Create a self-signed certificate from the given certificate options. + ``not_before`` and ``not_after`` are expected to be the time since the UNIX epoch, in seconds. - .. py:method:: create_req(key, hash_fn, rng) + .. py:method:: create_req(key, rng, hash_fn=None, padding=None, challenge_password=None) Create a PKCS #10 certificate request that can later be signed. @@ -839,7 +836,7 @@ PKCS10Req .. py:class:: PKCS10Req() - .. py:method:: sign(issuing_cert, issuing_key, rng, not_before, not_after, hash_fn, padding) + .. py:method:: sign(issuing_cert, issuing_key, rng, not_before, not_after, hash_fn=None, padding=None) ``not_before`` and ``not_after`` are expected to be the time since the UNIX epoch, in seconds. @@ -850,10 +847,6 @@ X509Cert .. py:class:: X509Cert(filename=None, buf=None) - .. py:classmethod:: create_self_signed(key, opts, hash_fn, sig_padding, rng) - - Create a self-signed certificate from the given certificate options. - .. py:method:: time_starts() Return the time the certificate becomes valid, as a string in form diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 72c7a7c3551..11269da5f41 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -2307,9 +2307,7 @@ typedef struct botan_x509_cert_params_builder_struct* botan_x509_cert_params_bui BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_params_builder_destroy(botan_x509_cert_params_builder_t builder); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* builder_obj, - const char* opts, - uint32_t* expire_time); +int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* builder_obj); BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_params_builder_add_common_name(botan_x509_cert_params_builder_t builder, const char* name); @@ -2318,61 +2316,56 @@ BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_params_builder_add_country(botan_x509_cert_params_builder_t builder, const char* country); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_add_organization(botan_x509_cert_params_builder_t builder, const char* organization); - -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_add_org_unit(botan_x509_cert_params_builder_t builder, const char* org_unit); +int botan_x509_cert_params_builder_add_state(botan_x509_cert_params_builder_t builder, const char* state); BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_params_builder_add_locality(botan_x509_cert_params_builder_t builder, const char* locality); -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_add_state(botan_x509_cert_params_builder_t builder, const char* state); - BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_params_builder_add_serial_number(botan_x509_cert_params_builder_t builder, const char* serial_number); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_add_email(botan_x509_cert_params_builder_t builder, const char* email); +int botan_x509_cert_params_builder_add_organization(botan_x509_cert_params_builder_t builder, const char* organization); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_add_uri(botan_x509_cert_params_builder_t builder, const char* uri); +int botan_x509_cert_params_builder_add_organizational_unit(botan_x509_cert_params_builder_t builder, + const char* org_unit); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_add_ip(botan_x509_cert_params_builder_t builder, const char* ip); +int botan_x509_cert_params_builder_add_email(botan_x509_cert_params_builder_t builder, const char* email); BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_params_builder_add_dns(botan_x509_cert_params_builder_t builder, const char* dns); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_add_xmpp(botan_x509_cert_params_builder_t builder, const char* xmpp); - -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_add_challenge(botan_x509_cert_params_builder_t builder, const char* challenge); +int botan_x509_cert_params_builder_add_uri(botan_x509_cert_params_builder_t builder, const char* uri); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_mark_as_ca_key(botan_x509_cert_params_builder_t builder, size_t limit); +int botan_x509_cert_params_builder_add_xmpp(botan_x509_cert_params_builder_t builder, const char* xmpp); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_add_not_before(botan_x509_cert_params_builder_t builder, uint64_t time_since_epoch); +int botan_x509_cert_params_builder_add_ipv4(botan_x509_cert_params_builder_t builder, uint32_t ipv4); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_add_not_after(botan_x509_cert_params_builder_t builder, uint64_t time_since_epoch); +int botan_x509_cert_params_builder_add_allowed_usage(botan_x509_cert_params_builder_t builder, uint32_t usage); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_add_constraints(botan_x509_cert_params_builder_t builder, uint32_t usage); +int botan_x509_cert_params_builder_add_allowed_extended_usage(botan_x509_cert_params_builder_t builder, + botan_asn1_oid_t oid); BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_add_ex_constraint(botan_x509_cert_params_builder_t builder, botan_asn1_oid_t oid); +int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t limit); BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_params_builder_add_ext_ip_addr_blocks(botan_x509_cert_params_builder_t builder, - botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int is_critical); BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_builder_t builder, - botan_x509_ext_as_blocks_t as_blocks); + botan_x509_ext_as_blocks_t as_blocks, + int is_critical); /* * X.509 cert creation @@ -2381,9 +2374,11 @@ BOTAN_FFI_EXPORT(3, 9) int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, botan_privkey_t key, botan_x509_cert_params_builder_t builder, + botan_rng_t rng, + uint64_t not_before, + uint64_t not_after, const char* hash_fn, - const char* padding, - botan_rng_t rng); + const char* padding); typedef struct botan_x509_pkcs10_req_struct* botan_x509_pkcs10_req_t; @@ -2391,10 +2386,12 @@ BOTAN_FFI_EXPORT(3, 9) int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t BOTAN_FFI_EXPORT(3, 9) int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, - botan_x509_cert_params_builder_t builder, botan_privkey_t key, + botan_x509_cert_params_builder_t builder, + botan_rng_t rng, const char* hash_fn, - botan_rng_t rng); + const char* padding, + const char* challenge_password); BOTAN_FFI_EXPORT(3, 9) int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view); diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index 628db63b00a..cf2a1590231 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -16,9 +16,12 @@ #include namespace { +std::chrono::system_clock::time_point timepoint_from_timestamp(uint64_t time_since_epoch) { + return std::chrono::system_clock::time_point(std::chrono::seconds(time_since_epoch)); +} + Botan::X509_Time time_from_timestamp(uint64_t time_since_epoch) { - auto tp = std::chrono::system_clock::time_point(std::chrono::seconds(time_since_epoch)); - return Botan::X509_Time(tp); + return Botan::X509_Time(timepoint_from_timestamp(time_since_epoch)); } } // namespace @@ -576,25 +579,17 @@ int botan_x509_cert_params_builder_destroy(botan_x509_cert_params_builder_t buil #endif } -int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* builder_obj, - const char* opts, - uint32_t* expire_time) { - if(builder_obj == nullptr || opts == nullptr) { +int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* builder_obj) { + if(builder_obj == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - std::unique_ptr co; - if(expire_time) { - co = std::make_unique(opts, *expire_time); - } else { - co = std::make_unique(opts); - } + auto co = std::make_unique(); return ffi_new_object(builder_obj, std::move(co)); }); #else - BOTAN_UNUSED(expire_time); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } @@ -607,14 +602,7 @@ int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* buil if(value == nullptr) { \ return BOTAN_FFI_ERROR_NULL_POINTER; \ } \ - return ffi_guard_thunk(__func__, [=]() -> int { \ - auto& builder_ = safe_get(builder); \ - if(!builder_.FIELD_NAME.empty()) { \ - return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; \ - } \ - builder_.FIELD_NAME = value; \ - return BOTAN_FFI_SUCCESS; \ - }); \ + return BOTAN_FFI_VISIT(builder, [=](auto& o) { o.add_##FIELD_NAME(value); }); \ } #else // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) @@ -631,148 +619,94 @@ int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* buil X509_GET_CERT_PARAMS_BUILDER_STRING(common_name) X509_GET_CERT_PARAMS_BUILDER_STRING(country) -X509_GET_CERT_PARAMS_BUILDER_STRING(organization) -X509_GET_CERT_PARAMS_BUILDER_STRING(locality) X509_GET_CERT_PARAMS_BUILDER_STRING(state) +X509_GET_CERT_PARAMS_BUILDER_STRING(locality) X509_GET_CERT_PARAMS_BUILDER_STRING(serial_number) +X509_GET_CERT_PARAMS_BUILDER_STRING(organization) +X509_GET_CERT_PARAMS_BUILDER_STRING(organizational_unit) X509_GET_CERT_PARAMS_BUILDER_STRING(email) +X509_GET_CERT_PARAMS_BUILDER_STRING(dns) X509_GET_CERT_PARAMS_BUILDER_STRING(uri) -X509_GET_CERT_PARAMS_BUILDER_STRING(ip) X509_GET_CERT_PARAMS_BUILDER_STRING(xmpp) -X509_GET_CERT_PARAMS_BUILDER_STRING(challenge) -int botan_x509_cert_params_builder_add_org_unit(botan_x509_cert_params_builder_t builder, const char* org_unit) { - if(org_unit == nullptr) { - return BOTAN_FFI_ERROR_NULL_POINTER; - } +int botan_x509_cert_params_builder_add_ipv4(botan_x509_cert_params_builder_t builder, uint32_t ipv4) { #if defined(BOTAN_HAS_X509_CERTIFICATES) - return ffi_guard_thunk(__func__, [=]() -> int { - auto& builder_ = safe_get(builder); - if(builder_.org_unit.empty()) { - builder_.org_unit = org_unit; - } else { - builder_.more_org_units.push_back(org_unit); - } - return BOTAN_FFI_SUCCESS; - }); + return BOTAN_FFI_VISIT(builder, [=](auto& o) { o.add_ipv4(ipv4); }); #else - BOTAN_UNUSED(builder); + BOTAN_UNUSED(builder, ipv4); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_cert_params_builder_add_dns(botan_x509_cert_params_builder_t builder, const char* dns) { - if(dns == nullptr) { - return BOTAN_FFI_ERROR_NULL_POINTER; - } +int botan_x509_cert_params_builder_add_allowed_usage(botan_x509_cert_params_builder_t builder, uint32_t usage) { #if defined(BOTAN_HAS_X509_CERTIFICATES) - return ffi_guard_thunk(__func__, [=]() -> int { - auto& builder_ = safe_get(builder); - if(builder_.dns.empty()) { - builder_.dns = dns; - } else { - builder_.more_dns.push_back(dns); - } - return BOTAN_FFI_SUCCESS; - }); -#else - BOTAN_UNUSED(builder); - return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; -#endif -} - -int botan_x509_cert_params_builder_mark_as_ca_key(botan_x509_cert_params_builder_t builder, size_t limit) { -#if defined(BOTAN_HAS_X509_CERTIFICATES) - return BOTAN_FFI_VISIT(builder, [=](auto& o) { o.CA_key(limit); }); -#else - BOTAN_UNUSED(builder, limit); - return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; -#endif -} - -int botan_x509_cert_params_builder_add_not_before(botan_x509_cert_params_builder_t builder, uint64_t time_since_epoch) { -#if defined(BOTAN_HAS_X509_CERTIFICATES) - return ffi_guard_thunk(__func__, [=]() -> int { - auto& builder_ = safe_get(builder); - if(builder_.start.time_is_set()) { - return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; - } - builder_.start = time_from_timestamp(time_since_epoch); - return BOTAN_FFI_SUCCESS; - }); + return BOTAN_FFI_VISIT(builder, [=](auto& o) { o.add_allowed_usage(Botan::Key_Constraints(usage)); }); #else - BOTAN_UNUSED(builder, not_before); + BOTAN_UNUSED(builder, usage); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_cert_params_builder_add_not_after(botan_x509_cert_params_builder_t builder, uint64_t time_since_epoch) { +int botan_x509_cert_params_builder_add_allowed_extended_usage(botan_x509_cert_params_builder_t builder, + botan_asn1_oid_t oid) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - auto& builder_ = safe_get(builder); - if(builder_.end.time_is_set()) { - return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; - } - builder_.end = time_from_timestamp(time_since_epoch); + safe_get(builder).add_allowed_extended_usage(safe_get(oid)); return BOTAN_FFI_SUCCESS; }); #else - BOTAN_UNUSED(builder.not_after); - return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; -#endif -} - -int botan_x509_cert_params_builder_add_constraints(botan_x509_cert_params_builder_t builder, uint32_t usage) { -#if defined(BOTAN_HAS_X509_CERTIFICATES) - return BOTAN_FFI_VISIT(builder, [=](auto& o) { o.add_constraints(Botan::Key_Constraints(usage)); }); -#else - BOTAN_UNUSED(builder, usage); + BOTAN_UNUSED(builder, oid); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } -int botan_x509_cert_params_builder_add_ex_constraint(botan_x509_cert_params_builder_t builder, botan_asn1_oid_t oid) { +int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t limit) { #if defined(BOTAN_HAS_X509_CERTIFICATES) - return ffi_guard_thunk(__func__, [=]() -> int { - safe_get(builder).add_ex_constraint(safe_get(oid)); - return BOTAN_FFI_SUCCESS; - }); + return BOTAN_FFI_VISIT(builder, [=](auto& o) { o.set_as_ca_certificate(limit); }); #else - BOTAN_UNUSED(builder, oid); + BOTAN_UNUSED(builder, limit); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } int botan_x509_cert_params_builder_add_ext_ip_addr_blocks(botan_x509_cert_params_builder_t builder, - botan_x509_ext_ip_addr_blocks_t ip_addr_blocks) { + botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, + int is_critical) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { + if(is_critical != 0 && is_critical != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } try { - safe_get(builder).extensions.add(safe_get(ip_addr_blocks).copy()); + safe_get(builder).add_extension(safe_get(ip_addr_blocks).copy(), is_critical); } catch(Botan::Invalid_Argument&) { return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; } return BOTAN_FFI_SUCCESS; }); #else - BOTAN_UNUSED(builder, as_blocks); + BOTAN_UNUSED(builder, ip_addr_blocks, is_critical); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_builder_t builder, - botan_x509_ext_as_blocks_t as_blocks) { + botan_x509_ext_as_blocks_t as_blocks, + int is_critical) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { + if(is_critical != 0 && is_critical != 1) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } try { - safe_get(builder).extensions.add(safe_get(as_blocks).copy()); + safe_get(builder).add_extension(safe_get(as_blocks).copy(), is_critical); } catch(Botan::Invalid_Argument&) { return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; } return BOTAN_FFI_SUCCESS; }); #else - BOTAN_UNUSED(builder, as_blocks); + BOTAN_UNUSED(builder, as_blocks, is_critical); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } @@ -780,20 +714,37 @@ int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_buil int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, botan_privkey_t key, botan_x509_cert_params_builder_t builder, + botan_rng_t rng, + uint64_t not_before, + uint64_t not_after, const char* hash_fn, - const char* sig_padding, - botan_rng_t rng) { - if(cert_obj == nullptr || hash_fn == nullptr || sig_padding == nullptr) { + const char* padding) { + if(cert_obj == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - auto ca_cert = std::make_unique( - Botan::X509::create_self_signed_cert(safe_get(builder), safe_get(key), hash_fn, safe_get(rng))); - return ffi_new_object(cert_obj, std::move(ca_cert)); + std::optional hash_fn_; + std::optional padding_; + if(hash_fn != nullptr) { + hash_fn_ = hash_fn; + } + if(padding != nullptr) { + padding_ = padding; + } + + auto cert = std::make_unique( + safe_get(builder).into_self_signed_cert(timepoint_from_timestamp(not_before), + timepoint_from_timestamp(not_after), + safe_get(key), + safe_get(rng), + hash_fn_, + padding_)); + + return ffi_new_object(cert_obj, std::move(cert)); }); #else - BOTAN_UNUSED(key, builder, rng); + BOTAN_UNUSED(key, builder, rng, not_before, not_after, hash_fn, padding); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } @@ -808,21 +759,36 @@ int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req) { } int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, - botan_x509_cert_params_builder_t builder, botan_privkey_t key, + botan_x509_cert_params_builder_t builder, + botan_rng_t rng, const char* hash_fn, - botan_rng_t rng) { - if(req_obj == nullptr || hash_fn == nullptr) { + const char* padding, + const char* challenge_password) { + if(req_obj == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { + std::optional hash_fn_; + std::optional padding_; + std::optional challenge_password_; + if(hash_fn != nullptr) { + hash_fn_ = hash_fn; + } + if(padding != nullptr) { + padding_ = padding; + } + if(challenge_password != nullptr) { + challenge_password_ = challenge_password; + } + auto req = std::make_unique( - Botan::X509::create_cert_req(safe_get(builder), safe_get(key), hash_fn, safe_get(rng))); + safe_get(builder).into_pkcs10_request(safe_get(key), safe_get(rng), hash_fn_, padding_, challenge_password_)); return ffi_new_object(req_obj, std::move(req)); }); #else - BOTAN_UNUSED(builder, key, rng); + BOTAN_UNUSED(key, builder, rng, padding, hash_fn, challenge_password); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } @@ -836,6 +802,9 @@ int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx c #endif } +// req_load +// view functions for req + int botan_x509_sign_req(botan_x509_cert_t* subject_cert, botan_x509_pkcs10_req_t subject_req, botan_x509_cert_t issuing_cert, @@ -845,20 +814,30 @@ int botan_x509_sign_req(botan_x509_cert_t* subject_cert, uint64_t not_after, const char* hash_fn, const char* padding) { - if(subject_cert == nullptr || hash_fn == nullptr || padding == nullptr) { + if(subject_cert == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - Botan::RandomNumberGenerator& rng_ = safe_get(rng); - auto ca = Botan::X509_CA(safe_get(issuing_cert), safe_get(issuing_key), hash_fn, padding, rng_); + auto& rng_ = safe_get(rng); + std::string hash_fn_; + std::string padding_; + if(hash_fn != nullptr) { + hash_fn_ = hash_fn; + } + if(padding != nullptr) { + padding_ = padding; + } + + auto ca = Botan::X509_CA(safe_get(issuing_cert), safe_get(issuing_key), hash_fn_, padding_, rng_); + + std::unique_ptr cert = std::make_unique( + ca.sign_request(safe_get(subject_req), rng_, time_from_timestamp(not_before), time_from_timestamp(not_after))); - auto cert = std::make_unique(ca.sign_request( - safe_get(subject_req), safe_get(rng), time_from_timestamp(not_before), time_from_timestamp(not_after))); return ffi_new_object(subject_cert, std::move(cert)); }); #else - BOTAN_UNUSED(subject_req, issuing_cert, issuing_key, rng, not_before, not_after); + BOTAN_UNUSED(subject_req, issuing_cert, issuing_key, rng, not_before, not_after, hash_fn, padding); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } diff --git a/src/lib/ffi/ffi_cert.h b/src/lib/ffi/ffi_cert.h index 15d957489be..32e2c1f6852 100644 --- a/src/lib/ffi/ffi_cert.h +++ b/src/lib/ffi/ffi_cert.h @@ -15,7 +15,6 @@ #include #include #include - #include #include #include #endif @@ -23,7 +22,7 @@ extern "C" { #if defined(BOTAN_HAS_X509_CERTIFICATES) -BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_params_builder_struct, Botan::X509_Cert_Options, 0x92597C7D); +BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_params_builder_struct, Botan::CertificateParametersBuilder, 0x92597C7D); BOTAN_FFI_DECLARE_STRUCT(botan_x509_pkcs10_req_struct, Botan::PKCS10_Request, 0x87F0690A); BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_struct, Botan::X509_Certificate, 0x8F628937); BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910); diff --git a/src/python/botan3.py b/src/python/botan3.py index ae8dde7f31d..973221b43aa 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -534,32 +534,29 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_ext_as_blocks_get_rdi, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) ffi_api(dll.botan_x509_ext_as_blocks_get_rdi_at, [c_void_p, c_size_t, POINTER(c_uint32), POINTER(c_uint32)]) ffi_api(dll.botan_x509_cert_params_builder_destroy, [c_void_p]) - ffi_api(dll.botan_x509_create_cert_params_builder, [c_void_p, c_char_p, POINTER(c_uint32)]) + ffi_api(dll.botan_x509_create_cert_params_builder, [c_void_p]) ffi_api(dll.botan_x509_cert_params_builder_add_common_name, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_cert_params_builder_add_country, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_params_builder_add_organization, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_params_builder_add_org_unit, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_params_builder_add_locality, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_cert_params_builder_add_state, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_locality, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_cert_params_builder_add_serial_number, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_organization, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_organizational_unit, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_cert_params_builder_add_email, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_params_builder_add_uri, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_params_builder_add_ip, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_cert_params_builder_add_dns, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_uri, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_cert_params_builder_add_xmpp, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_params_builder_add_challenge, [c_void_p, c_char_p]) - ffi_api(dll.botan_x509_cert_params_builder_mark_as_ca_key, [c_void_p, c_size_t]) - ffi_api(dll.botan_x509_cert_params_builder_add_not_before, [c_void_p, c_uint64]) - ffi_api(dll.botan_x509_cert_params_builder_add_not_after, [c_void_p, c_uint64]) - ffi_api(dll.botan_x509_cert_params_builder_add_constraints, [c_void_p, c_uint32]) - ffi_api(dll.botan_x509_cert_params_builder_add_ex_constraint, [c_void_p, c_void_p]) - ffi_api(dll.botan_x509_cert_params_builder_add_ext_ip_addr_blocks, [c_void_p, c_void_p]) - ffi_api(dll.botan_x509_cert_params_builder_add_ext_as_blocks, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_params_builder_add_ipv4, [c_void_p, c_uint32]) + ffi_api(dll.botan_x509_cert_params_builder_add_allowed_usage, [c_void_p, c_uint32]) + ffi_api(dll.botan_x509_cert_params_builder_add_allowed_extended_usage, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_params_builder_set_as_ca_certificate, [c_void_p, c_size_t]) + ffi_api(dll.botan_x509_cert_params_builder_add_ext_ip_addr_blocks, [c_void_p, c_void_p, c_int]) + ffi_api(dll.botan_x509_cert_params_builder_add_ext_as_blocks, [c_void_p, c_void_p, c_int]) ffi_api(dll.botan_x509_create_self_signed_cert, - [c_void_p, c_void_p, c_void_p, c_char_p, c_char_p, c_void_p]) + [c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint64, c_char_p, c_char_p]) ffi_api(dll.botan_x509_pkcs10_req_destroy, [c_void_p]) ffi_api(dll.botan_x509_create_pkcs10_req, - [c_void_p, c_void_p, c_void_p, c_char_p, c_void_p]) + [c_void_p, c_void_p, c_void_p, c_void_p, c_char_p, c_char_p, c_char_p]) ffi_api(dll.botan_x509_pkcs10_req_view_pem, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) ffi_api(dll.botan_x509_sign_req, [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint64, c_char_p, c_char_p]) @@ -1865,9 +1862,9 @@ def _load_buf_or_file(filename, buf, file_fn, buf_fn): # X.509 certificates # class X509CertificateBuilder: - def __init__(self, opts, expire_time=None): + def __init__(self): self.__obj = c_void_p(0) - _DLL.botan_x509_create_cert_params_builder(byref(self.__obj), _ctype_str(opts), c_uint32(expire_time) if expire_time else None) + _DLL.botan_x509_create_cert_params_builder(byref(self.__obj)) def __del__(self): _DLL.botan_x509_cert_params_builder_destroy(self.__obj) @@ -1881,49 +1878,37 @@ def add_common_name(self, name): def add_country(self, country): _DLL.botan_x509_cert_params_builder_add_country(self.__obj, _ctype_str(country)) - def add_organization(self, organization): - _DLL.botan_x509_cert_params_builder_add_organization(self.__obj, _ctype_str(organization)) - - def add_org_unit(self, org_unit): - _DLL.botan_x509_cert_params_builder_add_org_unit(self.__obj, _ctype_str(org_unit)) + def add_state(self, state): + _DLL.botan_x509_cert_params_builder_add_state(self.__obj, _ctype_str(state)) def add_locality(self, locality): _DLL.botan_x509_cert_params_builder_add_locality(self.__obj, _ctype_str(locality)) - def add_state(self, state): - _DLL.botan_x509_cert_params_builder_add_state(self.__obj, _ctype_str(state)) - def add_serial_number(self, serial_number): _DLL.botan_x509_cert_params_builder_add_serial_number(self.__obj, _ctype_str(serial_number)) - def add_email(self, email): - _DLL.botan_x509_cert_params_builder_add_email(self.__obj, _ctype_str(email)) + def add_organization(self, organization): + _DLL.botan_x509_cert_params_builder_add_organization(self.__obj, _ctype_str(organization)) - def add_uri(self, uri): - _DLL.botan_x509_cert_params_builder_add_uri(self.__obj, _ctype_str(uri)) + def add_organizational_unit(self, org_unit): + _DLL.botan_x509_cert_params_builder_add_organizational_unit(self.__obj, _ctype_str(org_unit)) - def add_ip(self, ip): - _DLL.botan_x509_cert_params_builder_add_ip(self.__obj, _ctype_str(ip)) + def add_email(self, email): + _DLL.botan_x509_cert_params_builder_add_email(self.__obj, _ctype_str(email)) def add_dns(self, dns): _DLL.botan_x509_cert_params_builder_add_dns(self.__obj, _ctype_str(dns)) + def add_uri(self, uri): + _DLL.botan_x509_cert_params_builder_add_uri(self.__obj, _ctype_str(uri)) + def add_xmpp(self, xmpp): _DLL.botan_x509_cert_params_builder_add_xmpp(self.__obj, _ctype_str(xmpp)) - def add_challenge(self, challenge): - _DLL.botan_x509_cert_params_builder_add_challenge(self.__obj, _ctype_str(challenge)) - - def mark_as_ca_key(self, limit): - _DLL.botan_x509_cert_params_builder_mark_as_ca_key(self.__obj, c_size_t(limit)) - - def add_not_before(self, time_since_epoch): - _DLL.botan_x509_cert_params_builder_add_not_before(self.__obj, time_since_epoch) - - def add_not_after(self, time_since_epoch): - _DLL.botan_x509_cert_params_builder_add_not_after(self.__obj, time_since_epoch) + def add_ipv4(self, ipv4): + _DLL.botan_x509_cert_params_builder_add_ip(self.__obj, ipv4) - def add_constraints(self, usage_list): + def add_allowed_usage(self, usage_list): # TODO this sucks, where enum usage_values = {"NO_CONSTRAINTS": 0, "DIGITAL_SIGNATURE": 32768, @@ -1940,23 +1925,48 @@ def add_constraints(self, usage_list): if u not in usage_values: pass usage += usage_values[u] - _DLL.botan_x509_cert_params_builder_add_constraints(self.__obj, c_uint32(usage)) + _DLL.botan_x509_cert_params_builder_add_allowed_usage(self.__obj, c_uint32(usage)) - def add_ex_constraints(self, oid): - _DLL.botan_x509_cert_params_builder_add_ex_constraint(self.__obj, oid.handle_()) + def add_allowed_extended_usage(self, oid): + _DLL.botan_x509_cert_params_builder_add_allowed_extended_usage(self.__obj, oid.handle_()) - def add_ext_ip_addr_blocks(self, ip_addr_blocks): - _DLL.botan_x509_cert_params_builder_add_ext_ip_addr_blocks(self.__obj, ip_addr_blocks.handle_()) + def set_as_ca_certificate(self, limit): + _DLL.botan_x509_cert_params_builder_set_as_ca_certificate(self.__obj, c_size_t(limit)) - def add_ext_as_blocks(self, as_blocks): - _DLL.botan_x509_cert_params_builder_add_ext_as_blocks(self.__obj, as_blocks.handle_()) + def add_ext_ip_addr_blocks(self, ip_addr_blocks, is_critical): + _DLL.botan_x509_cert_params_builder_add_ext_ip_addr_blocks(self.__obj, ip_addr_blocks.handle_(), 1 if is_critical else 0) - def create_req(self, key, hash_fn, rng): + def add_ext_as_blocks(self, as_blocks, is_critical): + _DLL.botan_x509_cert_params_builder_add_ext_as_blocks(self.__obj, as_blocks.handle_(), 1 if is_critical else 0) + + def create_self_signed(self, key, rng, not_before, not_after, hash_fn=None, padding=None): + cert = X509Cert() + _DLL.botan_x509_create_self_signed_cert( + byref(cert.handle_()), + key.handle_(), + self.__obj, + rng.handle_(), + not_before, + not_after, + _ctype_str(hash_fn), + _ctype_str(padding), + ) + return cert + + + def create_req(self, key, rng, hash_fn=None, padding=None, challenge_password=None): req = PKCS10Req() - _DLL.botan_x509_create_pkcs10_req(byref(req.handle_()), self.__obj, key.handle_(), _ctype_str(hash_fn), rng.handle_()) + _DLL.botan_x509_create_pkcs10_req( + byref(req.handle_()), + key.handle_(), + self.__obj, + rng.handle_(), + _ctype_str(hash_fn), + _ctype_str(padding), + _ctype_str(challenge_password) + ) return req - class X509ExtIPAddrBlocks: def __init__(self, cert=None): self.__obj = c_void_p(0) @@ -2122,7 +2132,7 @@ def __del__(self): def handle_(self): return self.__obj - def sign(self, issuing_cert, issuing_key, rng, not_before, not_after, hash_fn, padding): + def sign(self, issuing_cert, issuing_key, rng, not_before, not_after, hash_fn=None, padding=None): cert = X509Cert() _DLL.botan_x509_sign_req( byref(cert.handle_()), @@ -2151,12 +2161,6 @@ def __init__(self, filename: str | None = None, buf: bytes | None = None): def __del__(self): _DLL.botan_x509_cert_destroy(self.__obj) - @classmethod - def create_self_signed(cls, key, opts, hash_fn, padding, rng): - cert = X509Cert() - _DLL.botan_x509_create_self_signed_cert(byref(cert.handle_()), key.handle_(), opts.handle_(), _ctype_str(hash_fn), _ctype_str(padding), rng.handle_()) - return cert - def time_starts(self) -> datetime: starts = _call_fn_returning_str( 16, lambda b, bl: _DLL.botan_x509_cert_get_time_starts(self.__obj, b, bl)) diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 6c2a769f103..066fa48c917 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -823,31 +823,34 @@ def test_certs(self): def test_cert_creation(self): hash_fn = "SHA-256" - padding_method = "EMSA1(SHA-256)" group = "secp256r1" + now = int(time.time()) + rng = botan.RandomNumberGenerator() ca_key = botan.PrivateKey.create("ECDSA", group, rng) - ca_builder = botan.X509CertificateBuilder("Test CA/US/Botan Project/Testing") - ca_builder.mark_as_ca_key(1) - ca_builder.add_constraints(["DIGITAL_SIGNATURE"]) - ca_cert = botan.X509Cert.create_self_signed(ca_key, ca_builder, hash_fn, padding_method, rng) + ca_builder = botan.X509CertificateBuilder() + ca_builder.add_common_name("Test CA") + ca_builder.add_country("US") + ca_builder.add_organization("Botan Project") + ca_builder.add_organizational_unit("Testing") + ca_builder.set_as_ca_certificate(1) + ca_builder.add_allowed_usage(["DIGITAL_SIGNATURE"]) + ca_cert = ca_builder.create_self_signed(ca_key, rng, now, now + 60, hash_fn) constraints = ca_cert.key_constraints() self.assertEqual(len(constraints), 3) for item in ["DIGITAL_SIGNATURE", "KEY_CERT_SIGN", "CRL_SIGN"]: self.assertTrue(item in constraints) - self.assertTrue(ca_cert.is_self_signed()) - self.assertEqual(ca_cert.key_constraints(), ["DIGITAL_SIGNATURE", "KEY_CERT_SIGN", "CRL_SIGN"]) cert_key = botan.PrivateKey.create("ECDSA", group, rng) - req_builder = botan.X509CertificateBuilder("Test CA/US/Botan Project/Testing") + req_builder = botan.X509CertificateBuilder() req_builder.add_uri("https://botan.randombit.net") for item in ["imaginary.botan.randombit.net", "botan.randombit.net", "randombit.net"]: req_builder.add_dns(item) - req = req_builder.create_req(cert_key, hash_fn, rng) + req = req_builder.create_req(cert_key, rng, hash_fn) - cert = req.sign(ca_cert, ca_key, rng, int(time.time()), int(time.time()) + 60, hash_fn, padding_method) + cert = req.sign(ca_cert, ca_key, rng, now, now + 60, hash_fn) self.assertFalse(cert.is_self_signed()) self.assertEqual(cert.key_constraints(), ["NO_CONSTRAINTS"]) @@ -861,14 +864,14 @@ def test_cert_creation(self): def test_x509_rpki(self): hash_fn = "SHA-256" - padding_method = "EMSA1(SHA-256)" group = "secp256r1" + now = int(time.time()) + rng = botan.RandomNumberGenerator() ca_key = botan.PrivateKey.create("ECDSA", group, rng) - ca_builder = botan.X509CertificateBuilder("Test CA/US/Botan Project/Testing") - ca_builder.mark_as_ca_key(1) - ca_builder.add_constraints(["DIGITAL_SIGNATURE"]) + ca_builder = botan.X509CertificateBuilder() + ca_builder.set_as_ca_certificate(1) ca_ip_addr_blocks = botan.X509ExtIPAddrBlocks() @@ -884,16 +887,16 @@ def test_x509_rpki(self): ca_as_blocks.add_asnum_range(3000, 4999) ca_as_blocks.restrict_rdi() - ca_builder.add_ext_ip_addr_blocks(ca_ip_addr_blocks) - ca_builder.add_ext_as_blocks(ca_as_blocks) + ca_builder.add_ext_ip_addr_blocks(ca_ip_addr_blocks, True) + ca_builder.add_ext_as_blocks(ca_as_blocks, True) with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): - ca_builder.add_ext_ip_addr_blocks(ca_ip_addr_blocks) + ca_builder.add_ext_ip_addr_blocks(ca_ip_addr_blocks, True) with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): - ca_builder.add_ext_as_blocks(ca_as_blocks) + ca_builder.add_ext_as_blocks(ca_as_blocks, True) - ca_cert = botan.X509Cert.create_self_signed(ca_key, ca_builder, hash_fn, padding_method, rng) + ca_cert = ca_builder.create_self_signed(ca_key, rng, now, now + 60, hash_fn) ca_ip_addr_blocks = ca_cert.ext_ip_addr_blocks() self.assertEqual(ca_ip_addr_blocks.addresses(), ( @@ -921,7 +924,7 @@ def test_x509_rpki(self): ca_as_blocks.add_asnum(999) cert_key = botan.PrivateKey.create("ECDSA", group, rng) - req_builder = botan.X509CertificateBuilder("Test CA/US/Botan Project/Testing") + req_builder = botan.X509CertificateBuilder() req_ip_addr_blocks = botan.X509ExtIPAddrBlocks() req_ip_addr_blocks.add_addr([192, 168, 2, 1]) @@ -934,11 +937,11 @@ def test_x509_rpki(self): req_as_blocks.add_asnum_range(3100, 4000) req_as_blocks.inherit_rdi() - req_builder.add_ext_ip_addr_blocks(req_ip_addr_blocks) - req_builder.add_ext_as_blocks(req_as_blocks) - req = req_builder.create_req(cert_key, hash_fn, rng) + req_builder.add_ext_ip_addr_blocks(req_ip_addr_blocks, True) + req_builder.add_ext_as_blocks(req_as_blocks, True) + req = req_builder.create_req(cert_key, rng, hash_fn) - cert = req.sign(ca_cert, ca_key, rng, int(time.time()), int(time.time()) + 60, hash_fn, padding_method) + cert = req.sign(ca_cert, ca_key, rng, now, now + 60, hash_fn) req_ip_addr_blocks = cert.ext_ip_addr_blocks() self.assertEqual(req_ip_addr_blocks.addresses(), ( diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index 9d57007971c..bd022c4cfe1 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -799,21 +799,25 @@ class FFI_Cert_Creation_Test final : public FFI_Test { void ffi_test(Test::Result& result, botan_rng_t rng) override { const std::string hash_fn{"SHA-256"}; + const std::string group{"secp256r1"}; botan_privkey_t ca_key; botan_privkey_t cert_key; - if(TEST_FFI_INIT(botan_privkey_create_rsa, (&ca_key, rng, 4096))) { - TEST_FFI_OK(botan_privkey_create_rsa, (&cert_key, rng, 4096)); + if(TEST_FFI_INIT(botan_privkey_create, (&ca_key, "ECDSA", group.c_str(), rng))) { + TEST_FFI_OK(botan_privkey_create, (&cert_key, "ECDSA", group.c_str(), rng)); uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) .count(); botan_x509_cert_params_builder_t ca_builder; - if(TEST_FFI_INIT(botan_x509_create_cert_params_builder, - (&ca_builder, "Test CA/US/Botan Project/Testing", nullptr))) { - TEST_FFI_OK(botan_x509_cert_params_builder_mark_as_ca_key, (ca_builder, 1)); + if(TEST_FFI_INIT(botan_x509_create_cert_params_builder, (&ca_builder))) { + TEST_FFI_OK(botan_x509_cert_params_builder_add_common_name, (ca_builder, "Test CA")); + TEST_FFI_OK(botan_x509_cert_params_builder_add_country, (ca_builder, "US")); + TEST_FFI_OK(botan_x509_cert_params_builder_add_organization, (ca_builder, "Botan Project")); + TEST_FFI_OK(botan_x509_cert_params_builder_add_organizational_unit, (ca_builder, "Testing")); + TEST_FFI_OK(botan_x509_cert_params_builder_set_as_ca_certificate, (ca_builder, 1)); TEST_FFI_OK(botan_x509_cert_params_builder_add_uri, (ca_builder, "https://botan.randombit.net")); for(const auto* dns : {"imaginary.botan.randombit.net", "botan.randombit.net", "randombit.net"}) { TEST_FFI_OK(botan_x509_cert_params_builder_add_dns, (ca_builder, dns)); @@ -821,14 +825,14 @@ class FFI_Cert_Creation_Test final : public FFI_Test { botan_x509_cert_t ca_cert; TEST_FFI_OK(botan_x509_create_self_signed_cert, - (&ca_cert, ca_key, ca_builder, hash_fn.c_str(), "", rng)); + (&ca_cert, ca_key, ca_builder, rng, now, now + 60, hash_fn.c_str(), "")); - botan_x509_cert_params_builder_t req_opts; - TEST_FFI_OK(botan_x509_create_cert_params_builder, - (&req_opts, "Test CA/US/Botan Project/Testing", nullptr)); + botan_x509_cert_params_builder_t req_builder; + TEST_FFI_OK(botan_x509_create_cert_params_builder, (&req_builder)); botan_x509_pkcs10_req_t req; - TEST_FFI_OK(botan_x509_create_pkcs10_req, (&req, req_opts, cert_key, hash_fn.c_str(), rng)); + TEST_FFI_OK(botan_x509_create_pkcs10_req, + (&req, cert_key, req_builder, rng, hash_fn.c_str(), nullptr, nullptr)); botan_x509_cert_t cert; TEST_FFI_OK(botan_x509_sign_req, (&cert, req, ca_cert, ca_key, rng, now, now + 60, hash_fn.c_str(), "")); @@ -837,7 +841,7 @@ class FFI_Cert_Creation_Test final : public FFI_Test { TEST_FFI_RC(0, botan_x509_cert_verify, (&rc, cert, nullptr, 0, &ca_cert, 1, nullptr, 0, nullptr, 0)); TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (ca_builder)); - TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (req_opts)); + TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (req_builder)); TEST_FFI_OK(botan_x509_pkcs10_req_destroy, (req)); TEST_FFI_OK(botan_x509_cert_destroy, (ca_cert)); TEST_FFI_OK(botan_x509_cert_destroy, (cert)); @@ -855,23 +859,21 @@ class FFI_X509_RPKI_Test final : public FFI_Test { void ffi_test(Test::Result& result, botan_rng_t rng) override { const std::string hash_fn{"SHA-256"}; - const std::string padding_method{"EMSA3(SHA-256)"}; + const std::string group{"secp256r1"}; botan_privkey_t ca_key; botan_privkey_t cert_key; - if(TEST_FFI_INIT(botan_privkey_create_rsa, (&ca_key, rng, 4096))) { - TEST_FFI_OK(botan_privkey_create_rsa, (&cert_key, rng, 4096)); + if(TEST_FFI_INIT(botan_privkey_create, (&ca_key, "ECDSA", group.c_str(), rng))) { + TEST_FFI_OK(botan_privkey_create, (&cert_key, "ECDSA", group.c_str(), rng)); uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) .count(); - // now + 60 * 60 botan_x509_cert_params_builder_t ca_builder; - if(TEST_FFI_INIT(botan_x509_create_cert_params_builder, - (&ca_builder, "Test CA/US/Botan Project/Testing", nullptr))) { - TEST_FFI_OK(botan_x509_cert_params_builder_mark_as_ca_key, (ca_builder, 1)); + if(TEST_FFI_INIT(botan_x509_create_cert_params_builder, (&ca_builder))) { + TEST_FFI_OK(botan_x509_cert_params_builder_set_as_ca_certificate, (ca_builder, 1)); botan_x509_ext_ip_addr_blocks_t ca_ip_addr_blocks; TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks, (&ca_ip_addr_blocks)); @@ -972,10 +974,10 @@ class FFI_X509_RPKI_Test final : public FFI_Test { result.confirm("index 1 has a value", present == 1); result.confirm("index 1 has entries", count == 0); - TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_ip_addr_blocks, (ca_builder, ca_ip_addr_blocks)); + TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_ip_addr_blocks, (ca_builder, ca_ip_addr_blocks, 1)); TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, botan_x509_cert_params_builder_add_ext_ip_addr_blocks, - (ca_builder, ca_ip_addr_blocks)); + (ca_builder, ca_ip_addr_blocks, 1)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (ca_ip_addr_blocks)); botan_x509_ext_as_blocks_t ca_as_blocks; @@ -1009,40 +1011,42 @@ class FFI_X509_RPKI_Test final : public FFI_Test { botan_x509_ext_as_blocks_get_rdi_at, (ca_as_blocks, 0, &min_out_, &max_out_)); - TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_as_blocks, (ca_builder, ca_as_blocks)); + TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_as_blocks, (ca_builder, ca_as_blocks, 1)); TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, botan_x509_cert_params_builder_add_ext_as_blocks, - (ca_builder, ca_as_blocks)); + (ca_builder, ca_as_blocks, 1)); TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (ca_as_blocks)); botan_x509_cert_t ca_cert; TEST_FFI_OK(botan_x509_create_self_signed_cert, - (&ca_cert, ca_key, ca_builder, hash_fn.c_str(), "", rng)); + (&ca_cert, ca_key, ca_builder, rng, now, now + 60, hash_fn.c_str(), nullptr)); botan_x509_cert_params_builder_t req_builder; - TEST_FFI_OK(botan_x509_create_cert_params_builder, - (&req_builder, "Test CA/US/Botan Project/Testing", nullptr)); + TEST_FFI_OK(botan_x509_create_cert_params_builder, (&req_builder)); botan_x509_ext_ip_addr_blocks_t req_ip_addr_blocks; TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks, (&req_ip_addr_blocks)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_inherit, (req_ip_addr_blocks, 0, nullptr)); - TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_ip_addr_blocks, (req_builder, req_ip_addr_blocks)); + TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_ip_addr_blocks, (req_builder, req_ip_addr_blocks, 1)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (req_ip_addr_blocks)); botan_x509_ext_as_blocks_t req_as_blocks; TEST_FFI_OK(botan_x509_ext_create_as_blocks, (&req_as_blocks)); TEST_FFI_OK(botan_x509_ext_as_blocks_inherit_asnum, (req_as_blocks)); - TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_as_blocks, (req_builder, req_as_blocks)); + TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_as_blocks, (req_builder, req_as_blocks, 1)); TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (req_as_blocks)); botan_x509_pkcs10_req_t req; - TEST_FFI_OK(botan_x509_create_pkcs10_req, (&req, req_builder, cert_key, hash_fn.c_str(), rng)); + TEST_FFI_OK(botan_x509_create_pkcs10_req, + (&req, cert_key, req_builder, rng, hash_fn.c_str(), nullptr, nullptr)); botan_x509_cert_t cert; - TEST_FFI_OK(botan_x509_sign_req, - (&cert, req, ca_cert, ca_key, rng, now, now + 60, hash_fn.c_str(), padding_method.c_str())); + TEST_FFI_OK(botan_x509_sign_req, (&cert, req, ca_cert, ca_key, rng, now, now + 60, hash_fn.c_str(), "")); + + uint64_t test = 0; + TEST_FFI_OK(botan_x509_cert_not_after, (cert, &test)); botan_x509_ext_ip_addr_blocks_t req_ip_addr_blocks_from_cert; TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks_from_cert, (&req_ip_addr_blocks_from_cert, cert)); From 35478232d6d92fabd2809cd16a16dc99aba06484 Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:02:20 +0200 Subject: [PATCH 12/14] CRLs --- doc/api_ref/ffi.rst | 80 +++- doc/api_ref/python.rst | 57 ++- src/lib/ffi/ffi.h | 190 +++++--- src/lib/ffi/ffi_cert.cpp | 431 +++++++++++++++--- .../{ffi_x509_rpki.cpp => ffi_cert_ext.cpp} | 10 +- .../ffi/{ffi_x509_rpki.h => ffi_cert_ext.h} | 0 src/lib/ffi/info.txt | 2 +- src/python/botan3.py | 334 ++++++++++---- src/scripts/test_python.py | 77 +++- src/tests/test_ffi.cpp | 209 ++++++++- 10 files changed, 1099 insertions(+), 291 deletions(-) rename src/lib/ffi/{ffi_x509_rpki.cpp => ffi_cert_ext.cpp} (98%) rename src/lib/ffi/{ffi_x509_rpki.h => ffi_cert_ext.h} (100%) diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index 3baa82c032d..d80d4097540 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -1671,20 +1671,20 @@ X.509 Certificates Return the subject key ID set in the certificate, which may be empty. -.. cpp:function::int botan_x509_get_basic_constraints(botan_x509_cert_t cert, int* is_ca, size_t* limit) +.. cpp:function::int botan_x509_cert_is_ca(botan_x509_cert_t cert, int* is_ca, size_t* limit) Checks whether the certificate is a CA certificate and sets ``is_ca`` to 1 if it is, 0 otherwise. If it is a CA certificate, ``limit`` is set to the path limit, otherwise 0. -.. cpp:function::int botan_x509_get_key_constraints(botan_x509_cert_t cert, uint32_t* usage) +.. cpp:function::int botan_x509_cert_get_allowed_usage(botan_x509_cert_t cert, uint32_t* usage) Returns the key usage constraints. -.. cpp:function::int botan_x509_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) +.. cpp:function::int botan_x509_cert_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) Returns the OCSP responder. -.. cpp:function::int botan_x509_is_self_signed(botan_x509_cert_t cert, int* out) +.. cpp:function::int botan_x509_cert_is_self_signed(botan_x509_cert_t cert, int* out) Checks whether the certificate is self signed and sets ``out`` to 1 if it is, 0 otherwise. @@ -1801,11 +1801,11 @@ X.509 Certificates Destroy the IP Address Blocks object. -.. cpp:function::int botan_x509_ext_create_ip_addr_blocks(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) +.. cpp:function::int botan_x509_ext_ip_addr_blocks_create(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) Create a new IP Address Blocks object. -.. cpp:function::int botan_x509_ext_create_ip_addr_blocks_from_cert(botan_x509_cert_t cert, \ +.. cpp:function::int botan_x509_ext_ip_addr_blocks_create_from_cert(botan_x509_cert_t cert, \ botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) Get an IP Address Blocks object from a certificate. Cannot be mutated. @@ -1877,11 +1877,11 @@ X.509 Certificates Destroy the AS Blocks object. -.. cpp:function::int botan_x509_ext_create_as_blocks(botan_x509_ext_as_blocks_t* as_blocks) +.. cpp:function::int botan_x509_ext_as_blocks_create(botan_x509_ext_as_blocks_t* as_blocks) Create a new AS Blocks object. -.. cpp:function::int botan_x509_ext_create_as_blocks_from_cert(botan_x509_cert_t cert, botan_x509_ext_as_blocks_t* as_blocks) +.. cpp:function::int botan_x509_ext_as_blocks_create_from_cert(botan_x509_cert_t cert, botan_x509_ext_as_blocks_t* as_blocks) Get an AS Blocks object from a certificate. Cannot be mutated. @@ -1920,12 +1920,11 @@ X.509 Certificates .. cpp:function::int botan_x509_cert_params_builder_destroy(botan_x509_cert_params_builder_t builder) - Destroy the options object. + Destroy the Certificate Params Builder object. -.. cpp:function::int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* builder_obj); +.. cpp:function::int botan_x509_cert_params_builder_create(botan_x509_cert_params_builder_t* builder_obj); - Create a new certificate builder object. ``opts`` defines the common name (e.g. `common_name/country/organization/organizational_unit`). - ``expire_time`` if given is the expiration time from current clock in seconds. + Create a new Certificate Params Builder object. .. cpp:function::int botan_x509_cert_params_builder_add_common_name(botan_x509_cert_params_builder_t builder, const char* name); @@ -1955,7 +1954,7 @@ X.509 Certificates .. cpp:function::int botan_x509_cert_params_builder_add_allowed_extended_usage(botan_x509_cert_params_builder_t builder, botan_asn1_oid_t oid); -.. cpp:function::int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t limit); +.. cpp:function::int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t limit=None); Mark the certificate for CA usage. @@ -1965,12 +1964,13 @@ X.509 Certificates .. cpp:function::int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_builder_t builder, \ botan_x509_ext_as_blocks_t as_blocks, int is_critical); -.. cpp:function::int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, \ +.. cpp:function::int botan_x509_cert_create_self_signed(botan_x509_cert_t* cert_obj, \ botan_privkey_t key, \ botan_x509_cert_params_builder_t builder, \ botan_rng_t rng, \ uint64_t not_before, \ uint64_t not_after, \ + const botan_mp_t* serial_number, \ const char* hash_fn, \ const char* padding) @@ -1984,7 +1984,20 @@ X.509 Certificates Destroy the PKCS #10 certificate request object. -.. cpp:function::int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, \ +.. cpp:function::int botan_x509_pkcs10_req_load_file(botan_x509_pkcs10_req_t* req_obj, const char* req_path) + +.. cpp:function::int botan_x509_pkcs10_req_load(botan_x509_pkcs10_req_t* req_obj, const uint8_t req_bits[], size_t req_bits_len) + +.. cpp:function::int int botan_x509_pkcs10_req_get_public_key(botan_x509_pkcs10_req_t req, botan_pubkey_t* key) + +.. cpp:function::int int botan_x509_pkcs10_req_get_allowed_usage(botan_x509_pkcs10_req_t req, uint32_t* usage) + +.. cpp:function::int int botan_x509_pkcs10_req_is_ca(botan_x509_pkcs10_req_t req, int* is_ca, size_t* limit) + +.. cpp:function::int int botan_x509_pkcs10_req_verify_signature(botan_x509_pkcs10_req_t req, botan_pubkey_t key, int* result) + + +.. cpp:function::int botan_x509_pkcs10_req_create(botan_x509_pkcs10_req_t* req_obj, \ botan_privkey_t key, \ botan_x509_cert_params_builder_t builder, \ botan_rng_t rng, \ @@ -1996,13 +2009,16 @@ X.509 Certificates .. cpp:function::int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view) -.. cpp:function::int botan_x509_sign_req(botan_x509_cert_t* subject_cert, \ +.. cpp:function::int int botan_x509_pkcs10_req_view_der(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_bin_fn view) + +.. cpp:function::int botan_x509_pkcs10_req_sign(botan_x509_cert_t* subject_cert, \ botan_x509_pkcs10_req_t subject_req, \ botan_x509_cert_t issuing_cert, \ botan_privkey_t issuing_key, \ botan_rng_t rng, \ uint64_t not_before, \ uint64_t not_after, \ + const botan_mp_t* serial_number, \ const char* hash_fn, \ const char* padding) @@ -2024,6 +2040,38 @@ X.509 Certificate Revocation Lists Load a CRL from a file. +.. cpp:function:: int botan_x509_crl_create(botan_x509_crl_t* crl_obj, \ + botan_rng_t rng, \ + botan_x509_cert_t ca_cert, \ + botan_privkey_t ca_key, \ + uint64_t issue_time, \ + uint32_t next_update, \ + const char* hash_fn, \ + const char* padding) + +.. cpp:function:: int botan_x509_crl_update(botan_x509_crl_t* crl_obj, \ + botan_x509_crl_t last_crl, \ + botan_rng_t rng, \ + botan_x509_cert_t ca_cert, \ + botan_privkey_t ca_key, \ + uint64_t issue_time, \ + uint32_t next_update, \ + const botan_x509_cert_t* revoked, \ + size_t revoked_len, \ + uint8_t reason, \ + const char* hash_fn, \ + const char* padding) + +.. cpp:function:: int botan_x509_crl_get_count(botan_x509_crl_t crl, size_t* count); + +.. cpp:function:: int botan_x509_crl_get_entry(botan_x509_crl_t crl, size_t i, uint8_t serial[], size_t* serial_len, uint64_t* expire_time, uint8_t* reason) + +.. cpp:function:: int botan_x509_crl_verify_signature(botan_x509_crl_t crl, botan_pubkey_t key, int* result) + +.. cpp:function:: int botan_x509_crl_view_pem(botan_x509_crl_t crl, botan_view_ctx ctx, botan_view_str_fn view) + +.. cpp:function:: int botan_x509_crl_view_der(botan_x509_crl_t crl, botan_view_ctx ctx, botan_view_bin_fn view) + .. cpp:function:: int botan_x509_crl_destroy(botan_x509_crl_t crl) Destroy the CRL object. diff --git a/doc/api_ref/python.rst b/doc/api_ref/python.rst index dbc3a6dd8cd..94b74506d44 100644 --- a/doc/api_ref/python.rst +++ b/doc/api_ref/python.rst @@ -561,6 +561,14 @@ Multiple Precision Integers (MPI) Most of the usual arithmetic operators (``__add__``, ``__mul__``, etc) are defined. + .. py:classmethod:: from_bytes(buf) + + Create a new MPI object from the big-endian binary encoding produced by ``to_bytes()``. + + .. py:method:: to_bytes() + + Return a big-endian binary encoding of the number. + .. py:method:: inverse_mod(modulus) Return the inverse of ``self`` modulo ``modulus``, or zero if no inverse exists @@ -755,7 +763,7 @@ X509CertificateBuilder .. py:method:: add_ext_as_blocks(as_blocks, is_critical) - .. py:method:: create_self_signed(key, rng, not_before, not_after, hash_fn=None, padding=None) + .. py:method:: create_self_signed(key, rng, not_before, not_after, serial_number=None, hash_fn=None, padding=None) Create a self-signed certificate from the given certificate options. ``not_before`` and ``not_after`` are expected to be the time since the UNIX epoch, in seconds. @@ -771,12 +779,12 @@ X509ExtIPAddrBlocks .. py:class:: X509ExtIPAddrBlocks(cert=None) - .. py:method:: add_ip(ip, safi=None) + .. py:method:: add_addr(ip, safi=None) Add a single IP address to the extension. ``ip`` is expected to be a ``list[int]`` of length 4/16 for IPv4/IPv6. - .. py:method:: add_ip_range(min_, max_, safi=None) + .. py:method:: add_range(min_, max_, safi=None) Add an IP address range to the extension. @@ -836,12 +844,26 @@ PKCS10Req .. py:class:: PKCS10Req() + .. py:method:: public_key() + + Get the public key associated with the signing request. + + .. py:method:: allowed_usage() + + Return a list of all the key constraints listed in the signing request. + + .. py:method:: verify(key) + + Verify the signature of the signing request. + .. py:method:: sign(issuing_cert, issuing_key, rng, not_before, not_after, hash_fn=None, padding=None) ``not_before`` and ``not_after`` are expected to be the time since the UNIX epoch, in seconds. .. py:method:: to_pem() + .. py:method:: to_der() + X509Cert ----------------------------------------- @@ -921,9 +943,9 @@ X509Cert Return True if the certificates Key Usage extension contains all constraints given in ``usage_list``. Also return True if the certificate doesn't have this extension. - Example usage constraints are: ``"DIGITAL_SIGNATURE"``, ``"KEY_CERT_SIGN"``, ``"CRL_SIGN"``. + Example usage constraints are: ``X509KeyConstraints.DIGITAL_SIGNATURE"``, ``X509KeyConstraints.KEY_CERT_SIGN``, ``X509KeyConstraints.CRL_SIGN``. - .. py:method:: key_constraints() + .. py:method:: allowed_usages() Return a list of all the key constraints listed in the certificate. @@ -995,6 +1017,31 @@ X509CRL A CRL in PEM or DER format can be loaded from a file, with the ``filename`` argument, or from a bytestring, with the ``buf`` argument. + .. py:classmethod:: create(rng, ca_cert, ca_key, issue_time, next_update, hash_fn=None, padding=None) + + Create a new CRL for the given CA. + ``issue_time`` is expected to be the time since the UNIX epoch, in seconds, ``next_update`` the time in seconds until the next update. + + + .. py:method:: revoke(rng, ca_cert, ca_key, issue_time, next_update, revoked, reason, hash_fn=None, padding=None) + + Revoke certificates issued by the CA. + ``issue_time`` is expected to be the time since the UNIX epoch, in seconds, ``next_update`` the time in seconds until the next update. + Revoked is expected to be a list of certificates you want to revoked, reason should be of instance ``X509CRLReason``. + This method returns a new CRL, it does not modify the existing one! + + .. py:method:: revoked() + + Return entries listed in the CRL. + + .. py:method:: verify(key) + + Verify the signature of the CRL. + + .. py:method:: to_pem() + + .. py:method:: to_der() + diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 11269da5f41..073c506d937 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -2140,11 +2140,11 @@ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_serial_number(botan_x509_cert_t c BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_authority_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_subject_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_get_basic_constraints(botan_x509_cert_t cert, int* is_ca, size_t* limit); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_get_key_constraints(botan_x509_cert_t cert, uint32_t* usage); -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_is_self_signed(botan_x509_cert_t cert, int* out); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_is_ca(botan_x509_cert_t cert, int* is_ca, size_t* limit); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_get_allowed_usage(botan_x509_cert_t cert, uint32_t* usage); +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_is_self_signed(botan_x509_cert_t cert, int* out); BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_public_key_bits(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); @@ -2168,7 +2168,8 @@ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_to_string(botan_x509_cert_t cert, cha BOTAN_FFI_EXPORT(3, 0) int botan_x509_cert_view_as_string(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_view_pem(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_view_pem(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); /* Must match values of Key_Constraints in key_constraints.h */ enum botan_x509_cert_key_constraints /* NOLINT(*-enum-size) */ { @@ -2223,30 +2224,30 @@ BOTAN_FFI_EXPORT(2, 8) const char* botan_x509_cert_validation_status(int code); */ typedef struct botan_x509_ext_ip_addr_blocks_struct* botan_x509_ext_ip_addr_blocks_t; -BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_create_ip_addr_blocks(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_ip_addr_blocks_create(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks); -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_ext_create_ip_addr_blocks_from_cert(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks, +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_ip_addr_blocks_create_from_cert(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks, botan_x509_cert_t cert); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_ip_addr_blocks_add_ip_addr( botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, const uint8_t* min, const uint8_t* max, int ipv6, uint8_t* safi); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_ip_addr_blocks_restrict(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_ip_addr_blocks_inherit(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, uint8_t* safi); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_ip_addr_blocks_get_counts(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, size_t* v4_count, size_t* v6_count); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, size_t i, @@ -2255,7 +2256,7 @@ int botan_x509_ext_ip_addr_blocks_get_family(botan_x509_ext_ip_addr_blocks_t ip_ int* present, size_t* count); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int ipv6, size_t i, @@ -2266,37 +2267,37 @@ int botan_x509_ext_ip_addr_blocks_get_address(botan_x509_ext_ip_addr_blocks_t ip typedef struct botan_x509_ext_as_blocks_struct* botan_x509_ext_as_blocks_t; -BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_create_as_blocks(botan_x509_ext_as_blocks_t* as_blocks); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_create(botan_x509_ext_as_blocks_t* as_blocks); -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_ext_create_as_blocks_from_cert(botan_x509_ext_as_blocks_t* as_blocks, botan_x509_cert_t cert); +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_ext_as_blocks_create_from_cert(botan_x509_ext_as_blocks_t* as_blocks, botan_x509_cert_t cert); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_add_asnum(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_restrict_asnum(botan_x509_ext_as_blocks_t as_blocks); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_restrict_asnum(botan_x509_ext_as_blocks_t as_blocks); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_inherit_asnum(botan_x509_ext_as_blocks_t as_blocks); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_inherit_asnum(botan_x509_ext_as_blocks_t as_blocks); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_add_rdi(botan_x509_ext_as_blocks_t as_blocks, uint32_t min, uint32_t max); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_restrict_rdi(botan_x509_ext_as_blocks_t as_blocks); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_restrict_rdi(botan_x509_ext_as_blocks_t as_blocks); -BOTAN_FFI_EXPORT(3, 9) int botan_x509_ext_as_blocks_inherit_rdi(botan_x509_ext_as_blocks_t as_blocks); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_inherit_rdi(botan_x509_ext_as_blocks_t as_blocks); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_get_asnum(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_get_asnum_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_get_rdi(botan_x509_ext_as_blocks_t as_blocks, int* present, size_t* count); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_ext_as_blocks_get_rdi_at(botan_x509_ext_as_blocks_t as_blocks, size_t i, uint32_t* min, uint32_t* max); /* @@ -2304,65 +2305,65 @@ int botan_x509_ext_as_blocks_get_rdi_at(botan_x509_ext_as_blocks_t as_blocks, si */ typedef struct botan_x509_cert_params_builder_struct* botan_x509_cert_params_builder_t; -BOTAN_FFI_EXPORT(3, 9) int botan_x509_cert_params_builder_destroy(botan_x509_cert_params_builder_t builder); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_destroy(botan_x509_cert_params_builder_t builder); -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* builder_obj); +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_create(botan_x509_cert_params_builder_t* builder_obj); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_common_name(botan_x509_cert_params_builder_t builder, const char* name); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_country(botan_x509_cert_params_builder_t builder, const char* country); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_state(botan_x509_cert_params_builder_t builder, const char* state); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_locality(botan_x509_cert_params_builder_t builder, const char* locality); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_serial_number(botan_x509_cert_params_builder_t builder, const char* serial_number); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_organization(botan_x509_cert_params_builder_t builder, const char* organization); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_organizational_unit(botan_x509_cert_params_builder_t builder, const char* org_unit); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_email(botan_x509_cert_params_builder_t builder, const char* email); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_dns(botan_x509_cert_params_builder_t builder, const char* dns); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_uri(botan_x509_cert_params_builder_t builder, const char* uri); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_xmpp(botan_x509_cert_params_builder_t builder, const char* xmpp); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_ipv4(botan_x509_cert_params_builder_t builder, uint32_t ipv4); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_allowed_usage(botan_x509_cert_params_builder_t builder, uint32_t usage); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_allowed_extended_usage(botan_x509_cert_params_builder_t builder, botan_asn1_oid_t oid); -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t limit); +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t* limit); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_ext_ip_addr_blocks(botan_x509_cert_params_builder_t builder, botan_x509_ext_ip_addr_blocks_t ip_addr_blocks, int is_critical); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_builder_t builder, botan_x509_ext_as_blocks_t as_blocks, int is_critical); @@ -2370,22 +2371,37 @@ int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_buil /* * X.509 cert creation */ -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_create_self_signed(botan_x509_cert_t* cert_obj, botan_privkey_t key, botan_x509_cert_params_builder_t builder, botan_rng_t rng, uint64_t not_before, uint64_t not_after, + const botan_mp_t* serial_number, const char* hash_fn, const char* padding); typedef struct botan_x509_pkcs10_req_struct* botan_x509_pkcs10_req_t; -BOTAN_FFI_EXPORT(3, 9) int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req); -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, +BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_load_file(botan_x509_pkcs10_req_t* req_obj, const char* req_path); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_pkcs10_req_load(botan_x509_pkcs10_req_t* req_obj, const uint8_t req_bits[], size_t req_bits_len); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_get_public_key(botan_x509_pkcs10_req_t req, botan_pubkey_t* key); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_get_allowed_usage(botan_x509_pkcs10_req_t req, uint32_t* usage); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_is_ca(botan_x509_pkcs10_req_t req, int* is_ca, size_t* limit); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_pkcs10_req_verify_signature(botan_x509_pkcs10_req_t req, botan_pubkey_t key, int* result); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_pkcs10_req_create(botan_x509_pkcs10_req_t* req_obj, botan_privkey_t key, botan_x509_cert_params_builder_t builder, botan_rng_t rng, @@ -2393,19 +2409,23 @@ int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, const char* padding, const char* challenge_password); -BOTAN_FFI_EXPORT(3, 9) +BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view); -BOTAN_FFI_EXPORT(3, 9) -int botan_x509_sign_req(botan_x509_cert_t* subject_cert, - botan_x509_pkcs10_req_t subject_req, - botan_x509_cert_t issuing_cert, - botan_privkey_t issuing_key, - botan_rng_t rng, - uint64_t not_before, - uint64_t not_after, - const char* hash_fn, - const char* padding); +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_pkcs10_req_view_der(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_bin_fn view); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_pkcs10_req_sign(botan_x509_cert_t* subject_cert, + botan_x509_pkcs10_req_t subject_req, + botan_x509_cert_t issuing_cert, + botan_privkey_t issuing_key, + botan_rng_t rng, + uint64_t not_before, + uint64_t not_after, + const botan_mp_t* serial_number, + const char* hash_fn, + const char* padding); /* * X.509 CRL @@ -2417,6 +2437,42 @@ BOTAN_FFI_EXPORT(2, 13) int botan_x509_crl_load_file(botan_x509_crl_t* crl_obj, BOTAN_FFI_EXPORT(2, 13) int botan_x509_crl_load(botan_x509_crl_t* crl_obj, const uint8_t crl_bits[], size_t crl_bits_len); +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_crl_create(botan_x509_crl_t* crl_obj, + botan_rng_t rng, + botan_x509_cert_t ca_cert, + botan_privkey_t ca_key, + uint64_t issue_time, + uint32_t next_update, + const char* hash_fn, + const char* padding); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_crl_update(botan_x509_crl_t* crl_obj, + botan_x509_crl_t last_crl, + botan_rng_t rng, + botan_x509_cert_t ca_cert, + botan_privkey_t ca_key, + uint64_t issue_time, + uint32_t next_update, + const botan_x509_cert_t* revoked, + size_t revoked_len, + uint8_t reason, + const char* hash_fn, + const char* padding); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_crl_get_count(botan_x509_crl_t crl, size_t* count); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_crl_get_entry( + botan_x509_crl_t crl, size_t i, uint8_t serial[], size_t* serial_len, uint64_t* expire_time, uint8_t* reason); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_crl_verify_signature(botan_x509_crl_t crl, botan_pubkey_t key, int* result); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_crl_view_pem(botan_x509_crl_t crl, botan_view_ctx ctx, botan_view_str_fn view); + +BOTAN_FFI_EXPORT(3, 10) int botan_x509_crl_view_der(botan_x509_crl_t crl, botan_view_ctx ctx, botan_view_bin_fn view); + BOTAN_FFI_EXPORT(2, 13) int botan_x509_crl_destroy(botan_x509_crl_t crl); /** diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index cf2a1590231..86b58e4b6f5 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -8,11 +8,12 @@ #include #include +#include +#include #include #include #include #include -#include #include namespace { @@ -23,6 +24,30 @@ std::chrono::system_clock::time_point timepoint_from_timestamp(uint64_t time_sin Botan::X509_Time time_from_timestamp(uint64_t time_since_epoch) { return Botan::X509_Time(timepoint_from_timestamp(time_since_epoch)); } + +template +T default_from_ptr(U* value) { + T ret; + if(value != nullptr) { + ret = value; + } + return ret; +} + +template +std::optional optional_from_ptr(T* value) { + if(value != nullptr) { + return *value; + } + return std::nullopt; +} + +std::optional optional_from_ptr(const char* value) { + if(value != nullptr) { + return std::string(value); + } + return std::nullopt; +} } // namespace extern "C" { @@ -177,20 +202,14 @@ int botan_x509_cert_allowed_usage(botan_x509_cert_t cert, unsigned int key_usage #endif } -int botan_x509_get_basic_constraints(botan_x509_cert_t cert, int* is_ca, size_t* limit) { - if(is_ca == nullptr || limit == nullptr) { +int botan_x509_cert_get_allowed_usage(botan_x509_cert_t cert, uint32_t* usage) { + if(usage == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { - if(c.is_CA_cert()) { - *is_ca = 1; - *limit = c.path_limit(); - } else { - *is_ca = 0; - *limit = 0; - } + *usage = c.constraints().value(); return BOTAN_FFI_SUCCESS; }); #else @@ -199,14 +218,20 @@ int botan_x509_get_basic_constraints(botan_x509_cert_t cert, int* is_ca, size_t* #endif } -int botan_x509_get_key_constraints(botan_x509_cert_t cert, uint32_t* usage) { - if(usage == nullptr) { +int botan_x509_cert_is_ca(botan_x509_cert_t cert, int* is_ca, size_t* limit) { + if(is_ca == nullptr || limit == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { - *usage = c.constraints().value(); + if(c.is_CA_cert()) { + *is_ca = 1; + *limit = c.path_limit(); + } else { + *is_ca = 0; + *limit = 0; + } return BOTAN_FFI_SUCCESS; }); #else @@ -215,7 +240,7 @@ int botan_x509_get_key_constraints(botan_x509_cert_t cert, uint32_t* usage) { #endif } -int botan_x509_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) { +int botan_x509_cert_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { return invoke_view_callback(view, ctx, c.ocsp_responder()); }); @@ -225,7 +250,7 @@ int botan_x509_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, bo #endif } -int botan_x509_is_self_signed(botan_x509_cert_t cert, int* out) { +int botan_x509_cert_is_self_signed(botan_x509_cert_t cert, int* out) { if(out == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -473,6 +498,151 @@ int botan_x509_crl_load(botan_x509_crl_t* crl_obj, const uint8_t crl_bits[], siz #endif } +int botan_x509_crl_create(botan_x509_crl_t* crl_obj, + botan_rng_t rng, + botan_x509_cert_t ca_cert, + botan_privkey_t ca_key, + uint64_t issue_time, + uint32_t next_update, + const char* hash_fn, + const char* padding) { + if(crl_obj == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto& rng_ = safe_get(rng); + auto ca = Botan::X509_CA(safe_get(ca_cert), + safe_get(ca_key), + default_from_ptr(hash_fn), + default_from_ptr(padding), + rng_); + auto crl = std::make_unique( + ca.new_crl(rng_, timepoint_from_timestamp(issue_time), std::chrono::seconds(next_update))); + return ffi_new_object(crl_obj, std::move(crl)); + }); +#else + BOTAN_UNUSED(rng, ca_cert, ca_key, hash_fn, padding, issue_time, next_update); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_update(botan_x509_crl_t* crl_obj, + botan_x509_crl_t last_crl, + botan_rng_t rng, + botan_x509_cert_t ca_cert, + botan_privkey_t ca_key, + uint64_t issue_time, + uint32_t next_update, + const botan_x509_cert_t* revoked, + size_t revoked_len, + uint8_t reason, + const char* hash_fn, + const char* padding) { + if(crl_obj == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + if(revoked_len == 0) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + auto& rng_ = safe_get(rng); + auto ca = Botan::X509_CA(safe_get(ca_cert), + safe_get(ca_key), + default_from_ptr(hash_fn), + default_from_ptr(padding), + rng_); + + std::vector entries; + for(size_t i = 0; i < revoked_len; i++) { + entries.push_back(Botan::CRL_Entry(safe_get(revoked[i]), static_cast(reason))); + } + + auto crl = std::make_unique(ca.update_crl( + safe_get(last_crl), entries, rng_, timepoint_from_timestamp(issue_time), std::chrono::seconds(next_update))); + return ffi_new_object(crl_obj, std::move(crl)); + }); +#else + BOTAN_UNUSED( + last_crl, rng, ca_cert, ca_key, hash_fn, padding, issue_time, next_update, revoked, revoked_len, reason); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_get_count(botan_x509_crl_t crl, size_t* count) { + if(count == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(crl, [=](const auto& c) { + *count = c.get_revoked().size(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(crl); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_get_entry( + botan_x509_crl_t crl, size_t i, uint8_t serial[], size_t* serial_len, uint64_t* expire_time, uint8_t* reason) { + if(expire_time == nullptr || reason == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + const auto& entries = safe_get(crl).get_revoked(); + if(i >= entries.size()) { + return BOTAN_FFI_ERROR_BAD_PARAMETER; + } + *reason = static_cast(entries[i].reason_code()); + *expire_time = entries[i].expire_time().time_since_epoch(); + return write_vec_output(serial, serial_len, entries[i].serial_number()); +#else + BOTAN_UNUSED(crl, i, serial, serial_len); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_verify_signature(botan_x509_crl_t crl, botan_pubkey_t key, int* result) { + if(result == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + bool ok = safe_get(crl).check_signature(safe_get(key)); + if(ok) { + *result = 1; + } else { + *result = 0; + } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(crl, key); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_view_pem(botan_x509_crl_t crl, botan_view_ctx ctx, botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(crl, [=](const auto& c) -> int { return invoke_view_callback(view, ctx, c.PEM_encode()); }); +#else + BOTAN_UNUSED(crl, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_crl_view_der(botan_x509_crl_t crl, botan_view_ctx ctx, botan_view_bin_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(crl, [=](const auto& c) -> int { return invoke_view_callback(view, ctx, c.BER_encode()); }); +#else + BOTAN_UNUSED(crl, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_crl_destroy(botan_x509_crl_t crl) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_CHECKED_DELETE(crl); @@ -579,7 +749,7 @@ int botan_x509_cert_params_builder_destroy(botan_x509_cert_params_builder_t buil #endif } -int botan_x509_create_cert_params_builder(botan_x509_cert_params_builder_t* builder_obj) { +int botan_x509_cert_params_builder_create(botan_x509_cert_params_builder_t* builder_obj) { if(builder_obj == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -660,9 +830,12 @@ int botan_x509_cert_params_builder_add_allowed_extended_usage(botan_x509_cert_pa #endif } -int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t limit) { +int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t* limit) { #if defined(BOTAN_HAS_X509_CERTIFICATES) - return BOTAN_FFI_VISIT(builder, [=](auto& o) { o.set_as_ca_certificate(limit); }); + return BOTAN_FFI_VISIT(builder, [=](auto& o) { + auto limit_ = optional_from_ptr(limit); + o.set_as_ca_certificate(limit_); + }); #else BOTAN_UNUSED(builder, limit); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; @@ -678,7 +851,7 @@ int botan_x509_cert_params_builder_add_ext_ip_addr_blocks(botan_x509_cert_params return BOTAN_FFI_ERROR_BAD_PARAMETER; } try { - safe_get(builder).add_extension(safe_get(ip_addr_blocks).copy(), is_critical); + safe_get(builder).add_extension(safe_get(ip_addr_blocks).copy(), static_cast(is_critical)); } catch(Botan::Invalid_Argument&) { return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; } @@ -699,7 +872,7 @@ int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_buil return BOTAN_FFI_ERROR_BAD_PARAMETER; } try { - safe_get(builder).add_extension(safe_get(as_blocks).copy(), is_critical); + safe_get(builder).add_extension(safe_get(as_blocks).copy(), static_cast(is_critical)); } catch(Botan::Invalid_Argument&) { return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE; } @@ -711,12 +884,13 @@ int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_buil #endif } -int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, +int botan_x509_cert_create_self_signed(botan_x509_cert_t* cert_obj, botan_privkey_t key, botan_x509_cert_params_builder_t builder, botan_rng_t rng, uint64_t not_before, uint64_t not_after, + const botan_mp_t* serial_number, const char* hash_fn, const char* padding) { if(cert_obj == nullptr) { @@ -724,22 +898,22 @@ int botan_x509_create_self_signed_cert(botan_x509_cert_t* cert_obj, } #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - std::optional hash_fn_; - std::optional padding_; - if(hash_fn != nullptr) { - hash_fn_ = hash_fn; - } - if(padding != nullptr) { - padding_ = padding; - } + auto hash_fn_ = optional_from_ptr(hash_fn); + auto padding_ = optional_from_ptr(padding); - auto cert = std::make_unique( - safe_get(builder).into_self_signed_cert(timepoint_from_timestamp(not_before), - timepoint_from_timestamp(not_after), - safe_get(key), - safe_get(rng), - hash_fn_, - padding_)); + std::unique_ptr cert; + if(serial_number != nullptr && false) { + // TODO + auto serial_no = safe_get(*serial_number); + } else { + cert = std::make_unique( + safe_get(builder).into_self_signed_cert(timepoint_from_timestamp(not_before), + timepoint_from_timestamp(not_after), + safe_get(key), + safe_get(rng), + hash_fn_, + padding_)); + } return ffi_new_object(cert_obj, std::move(cert)); }); @@ -758,7 +932,38 @@ int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req) { #endif } -int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, +int botan_x509_pkcs10_req_load_file(botan_x509_pkcs10_req_t* req_obj, const char* req_path) { + if(req_obj == nullptr || req_path == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) + return ffi_guard_thunk(__func__, [=]() -> int { + auto req = std::make_unique(req_path); + return ffi_new_object(req_obj, std::move(req)); + }); +#else + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_load(botan_x509_pkcs10_req_t* req_obj, const uint8_t req_bits[], size_t req_bits_len) { + if(req_obj == nullptr || req_bits == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + Botan::DataSource_Memory bits(req_bits, req_bits_len); + auto req = std::make_unique(bits); + return ffi_new_object(req_obj, std::move(req)); + }); +#else + BOTAN_UNUSED(req_bits_len); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_create(botan_x509_pkcs10_req_t* req_obj, botan_privkey_t key, botan_x509_cert_params_builder_t builder, botan_rng_t rng, @@ -770,21 +975,12 @@ int botan_x509_create_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, } #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - std::optional hash_fn_; - std::optional padding_; - std::optional challenge_password_; - if(hash_fn != nullptr) { - hash_fn_ = hash_fn; - } - if(padding != nullptr) { - padding_ = padding; - } - if(challenge_password != nullptr) { - challenge_password_ = challenge_password; - } - auto req = std::make_unique( - safe_get(builder).into_pkcs10_request(safe_get(key), safe_get(rng), hash_fn_, padding_, challenge_password_)); + safe_get(builder).into_pkcs10_request(safe_get(key), + safe_get(rng), + optional_from_ptr(hash_fn), + optional_from_ptr(padding), + optional_from_ptr(challenge_password))); return ffi_new_object(req_obj, std::move(req)); }); #else @@ -802,37 +998,126 @@ int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx c #endif } -// req_load -// view functions for req +int botan_x509_pkcs10_req_view_der(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_bin_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(req, [=](const auto& r) -> int { return invoke_view_callback(view, ctx, r.BER_encode()); }); +#else + BOTAN_UNUSED(crl, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} -int botan_x509_sign_req(botan_x509_cert_t* subject_cert, - botan_x509_pkcs10_req_t subject_req, - botan_x509_cert_t issuing_cert, - botan_privkey_t issuing_key, - botan_rng_t rng, - uint64_t not_before, - uint64_t not_after, - const char* hash_fn, - const char* padding) { - if(subject_cert == nullptr) { +int botan_x509_pkcs10_req_get_public_key(botan_x509_pkcs10_req_t req, botan_pubkey_t* key) { + if(key == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } + #if defined(BOTAN_HAS_X509_CERTIFICATES) return ffi_guard_thunk(__func__, [=]() -> int { - auto& rng_ = safe_get(rng); - std::string hash_fn_; - std::string padding_; - if(hash_fn != nullptr) { - hash_fn_ = hash_fn; + auto public_key = safe_get(req).subject_public_key(); + return ffi_new_object(key, std::move(public_key)); + }); +#else + BOTAN_UNUSED(req); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_get_allowed_usage(botan_x509_pkcs10_req_t req, uint32_t* usage) { + if(usage == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(req, [=](const auto& r) -> int { + *usage = r.constraints().value(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_is_ca(botan_x509_pkcs10_req_t req, int* is_ca, size_t* limit) { + if(is_ca == nullptr || limit == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(req, [=](const auto& r) -> int { + if(r.is_CA()) { + *is_ca = 1; + // TODO + if(r.path_length_constraint().has_value()) { + *limit = r.path_length_constraint().value(); + } else { + *limit = 32; + } + } else { + *is_ca = 0; + *limit = 0; } - if(padding != nullptr) { - padding_ = padding; + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_pkcs10_req_verify_signature(botan_x509_pkcs10_req_t req, botan_pubkey_t key, int* result) { + if(result == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + bool ok = safe_get(req).check_signature(safe_get(key)); + if(ok) { + *result = 1; + } else { + *result = 0; } + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(req, key); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} - auto ca = Botan::X509_CA(safe_get(issuing_cert), safe_get(issuing_key), hash_fn_, padding_, rng_); +int botan_x509_pkcs10_req_sign(botan_x509_cert_t* subject_cert, + botan_x509_pkcs10_req_t subject_req, + botan_x509_cert_t issuing_cert, + botan_privkey_t issuing_key, + botan_rng_t rng, + uint64_t not_before, + uint64_t not_after, + const botan_mp_t* serial_number, + const char* hash_fn, + const char* padding) { + if(subject_cert == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto& rng_ = safe_get(rng); - std::unique_ptr cert = std::make_unique( - ca.sign_request(safe_get(subject_req), rng_, time_from_timestamp(not_before), time_from_timestamp(not_after))); + auto ca = Botan::X509_CA(safe_get(issuing_cert), + safe_get(issuing_key), + default_from_ptr(hash_fn), + default_from_ptr(padding), + rng_); + + std::unique_ptr cert; + if(serial_number != nullptr) { + auto serial_no = safe_get(*serial_number); + cert = std::make_unique(ca.sign_request( + safe_get(subject_req), rng_, serial_no, time_from_timestamp(not_before), time_from_timestamp(not_after))); + } else { + cert = std::make_unique(ca.sign_request( + safe_get(subject_req), rng_, time_from_timestamp(not_before), time_from_timestamp(not_after))); + } return ffi_new_object(subject_cert, std::move(cert)); }); diff --git a/src/lib/ffi/ffi_x509_rpki.cpp b/src/lib/ffi/ffi_cert_ext.cpp similarity index 98% rename from src/lib/ffi/ffi_x509_rpki.cpp rename to src/lib/ffi/ffi_cert_ext.cpp index e6e9a68ed82..b73b6f39e90 100644 --- a/src/lib/ffi/ffi_x509_rpki.cpp +++ b/src/lib/ffi/ffi_cert_ext.cpp @@ -8,8 +8,8 @@ #include #include +#include #include -#include #include namespace { @@ -84,7 +84,7 @@ int botan_x509_ext_ip_addr_blocks_destroy(botan_x509_ext_ip_addr_blocks_t ip_add #endif } -int botan_x509_ext_create_ip_addr_blocks(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) { +int botan_x509_ext_ip_addr_blocks_create(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks) { if(ip_addr_blocks == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -98,7 +98,7 @@ int botan_x509_ext_create_ip_addr_blocks(botan_x509_ext_ip_addr_blocks_t* ip_add #endif } -int botan_x509_ext_create_ip_addr_blocks_from_cert(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks, +int botan_x509_ext_ip_addr_blocks_create_from_cert(botan_x509_ext_ip_addr_blocks_t* ip_addr_blocks, botan_x509_cert_t cert) { if(ip_addr_blocks == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; @@ -335,7 +335,7 @@ int botan_x509_ext_as_blocks_destroy(botan_x509_ext_as_blocks_t as_blocks) { #endif } -int botan_x509_ext_create_as_blocks(botan_x509_ext_as_blocks_t* as_blocks) { +int botan_x509_ext_as_blocks_create(botan_x509_ext_as_blocks_t* as_blocks) { if(as_blocks == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -349,7 +349,7 @@ int botan_x509_ext_create_as_blocks(botan_x509_ext_as_blocks_t* as_blocks) { #endif } -int botan_x509_ext_create_as_blocks_from_cert(botan_x509_ext_as_blocks_t* as_blocks, botan_x509_cert_t cert) { +int botan_x509_ext_as_blocks_create_from_cert(botan_x509_ext_as_blocks_t* as_blocks, botan_x509_cert_t cert) { if(as_blocks == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } diff --git a/src/lib/ffi/ffi_x509_rpki.h b/src/lib/ffi/ffi_cert_ext.h similarity index 100% rename from src/lib/ffi/ffi_x509_rpki.h rename to src/lib/ffi/ffi_cert_ext.h diff --git a/src/lib/ffi/info.txt b/src/lib/ffi/info.txt index fb08b29c6e6..37455603776 100644 --- a/src/lib/ffi/info.txt +++ b/src/lib/ffi/info.txt @@ -9,13 +9,13 @@ brief -> "C API for Botan's functionality" ffi_cert.h +ffi_cert_ext.h ffi_ec.h ffi_mp.h ffi_oid.h ffi_pkey.h ffi_rng.h ffi_util.h -ffi_x509_rpki.h diff --git a/src/python/botan3.py b/src/python/botan3.py index 973221b43aa..28794b3cc2a 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -27,6 +27,7 @@ from binascii import hexlify from datetime import datetime from collections.abc import Iterable +from enum import IntEnum # This Python module requires the FFI API version introduced in Botan 3.10.0 # @@ -490,10 +491,10 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_cert_get_serial_number, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_get_authority_key_id, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_get_subject_key_id, [c_void_p, c_char_p, POINTER(c_size_t)]) - ffi_api(dll.botan_x509_get_basic_constraints, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) - ffi_api(dll.botan_x509_get_key_constraints, [c_void_p, POINTER(c_uint32)]) - ffi_api(dll.botan_x509_get_ocsp_responder, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) - ffi_api(dll.botan_x509_is_self_signed, [c_void_p, POINTER(c_int)]) + ffi_api(dll.botan_x509_cert_is_ca, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_cert_get_allowed_usage, [c_void_p, POINTER(c_uint32)]) + ffi_api(dll.botan_x509_cert_get_ocsp_responder, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) + ffi_api(dll.botan_x509_cert_is_self_signed, [c_void_p, POINTER(c_int)]) ffi_api(dll.botan_x509_cert_get_public_key_bits, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_view_public_key_bits, [c_void_p, c_void_p, VIEW_BIN_CALLBACK]) ffi_api(dll.botan_x509_cert_get_public_key, [c_void_p, c_void_p]) @@ -509,8 +510,8 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_cert_verify, [POINTER(c_int), c_void_p, c_void_p, c_size_t, c_void_p, c_size_t, c_char_p, c_size_t, c_char_p, c_uint64]) ffi_api(dll.botan_x509_ext_ip_addr_blocks_destroy, [c_void_p]) - ffi_api(dll.botan_x509_ext_create_ip_addr_blocks, [c_void_p]) - ffi_api(dll.botan_x509_ext_create_ip_addr_blocks_from_cert, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_create, [c_void_p]) + ffi_api(dll.botan_x509_ext_ip_addr_blocks_create_from_cert, [c_void_p, c_void_p]) ffi_api(dll.botan_x509_ext_ip_addr_blocks_add_ip_addr, [c_void_p, c_char_p, c_char_p, c_int, POINTER(c_uint8)]) ffi_api(dll.botan_x509_ext_ip_addr_blocks_restrict, [c_void_p, c_int, POINTER(c_uint8)]) @@ -521,8 +522,8 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_address, [c_void_p, c_int, c_size_t, c_size_t, c_char_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_ext_as_blocks_destroy, [c_void_p]) - ffi_api(dll.botan_x509_ext_create_as_blocks, [c_void_p]) - ffi_api(dll.botan_x509_ext_create_as_blocks_from_cert, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_create, [c_void_p]) + ffi_api(dll.botan_x509_ext_as_blocks_create_from_cert, [c_void_p, c_void_p]) ffi_api(dll.botan_x509_ext_as_blocks_add_asnum, [c_void_p, c_uint32, c_uint32]) ffi_api(dll.botan_x509_ext_as_blocks_restrict_asnum, [c_void_p]) ffi_api(dll.botan_x509_ext_as_blocks_inherit_asnum, [c_void_p]) @@ -534,7 +535,7 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_ext_as_blocks_get_rdi, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) ffi_api(dll.botan_x509_ext_as_blocks_get_rdi_at, [c_void_p, c_size_t, POINTER(c_uint32), POINTER(c_uint32)]) ffi_api(dll.botan_x509_cert_params_builder_destroy, [c_void_p]) - ffi_api(dll.botan_x509_create_cert_params_builder, [c_void_p]) + ffi_api(dll.botan_x509_cert_params_builder_create, [c_void_p]) ffi_api(dll.botan_x509_cert_params_builder_add_common_name, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_cert_params_builder_add_country, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_cert_params_builder_add_state, [c_void_p, c_char_p]) @@ -549,24 +550,41 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_cert_params_builder_add_ipv4, [c_void_p, c_uint32]) ffi_api(dll.botan_x509_cert_params_builder_add_allowed_usage, [c_void_p, c_uint32]) ffi_api(dll.botan_x509_cert_params_builder_add_allowed_extended_usage, [c_void_p, c_void_p]) - ffi_api(dll.botan_x509_cert_params_builder_set_as_ca_certificate, [c_void_p, c_size_t]) + ffi_api(dll.botan_x509_cert_params_builder_set_as_ca_certificate, [c_void_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_params_builder_add_ext_ip_addr_blocks, [c_void_p, c_void_p, c_int]) ffi_api(dll.botan_x509_cert_params_builder_add_ext_as_blocks, [c_void_p, c_void_p, c_int]) - ffi_api(dll.botan_x509_create_self_signed_cert, - [c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint64, c_char_p, c_char_p]) + ffi_api(dll.botan_x509_cert_create_self_signed, + [c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint64, c_void_p, c_char_p, c_char_p]) ffi_api(dll.botan_x509_pkcs10_req_destroy, [c_void_p]) - ffi_api(dll.botan_x509_create_pkcs10_req, + ffi_api(dll.botan_x509_pkcs10_req_load_file, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_pkcs10_req_load, [c_void_p, c_char_p, c_size_t]) + ffi_api(dll.botan_x509_pkcs10_req_get_public_key, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_pkcs10_req_get_allowed_usage, [c_void_p, POINTER(c_uint32)]) + ffi_api(dll.botan_x509_pkcs10_req_is_ca, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) + ffi_api(dll.botan_x509_pkcs10_req_verify_signature, [c_void_p, c_void_p, POINTER(c_int)]) + ffi_api(dll.botan_x509_pkcs10_req_create, [c_void_p, c_void_p, c_void_p, c_void_p, c_char_p, c_char_p, c_char_p]) ffi_api(dll.botan_x509_pkcs10_req_view_pem, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) - ffi_api(dll.botan_x509_sign_req, - [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint64, c_char_p, c_char_p]) + ffi_api(dll.botan_x509_pkcs10_req_view_der, [c_void_p, c_void_p, VIEW_BIN_CALLBACK]) + ffi_api(dll.botan_x509_pkcs10_req_sign, + [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint64, c_void_p, c_char_p, c_char_p]) dll.botan_x509_cert_validation_status.argtypes = [c_int] dll.botan_x509_cert_validation_status.restype = c_char_p # X509 CRL - ffi_api(dll.botan_x509_crl_load, [c_void_p, c_char_p, c_size_t]) ffi_api(dll.botan_x509_crl_load_file, [c_void_p, c_char_p]) + ffi_api(dll.botan_x509_crl_load, [c_void_p, c_char_p, c_size_t]) + ffi_api(dll.botan_x509_crl_create, + [c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint32, c_char_p, c_char_p]) + ffi_api(dll.botan_x509_crl_update, + [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint32, c_void_p, c_size_t, c_uint8, c_char_p, c_char_p]) + ffi_api(dll.botan_x509_crl_get_count, [c_void_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_crl_get_entry, + [c_void_p, c_size_t, c_char_p, POINTER(c_size_t), POINTER(c_uint64), POINTER(c_uint8)]) + ffi_api(dll.botan_x509_crl_verify_signature, [c_void_p, c_void_p, POINTER(c_int)]) + ffi_api(dll.botan_x509_crl_view_pem, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) + ffi_api(dll.botan_x509_crl_view_der, [c_void_p, c_void_p, VIEW_BIN_CALLBACK]) ffi_api(dll.botan_x509_crl_destroy, [c_void_p]) ffi_api(dll.botan_x509_is_revoked, [c_void_p, c_void_p], [-1]) ffi_api(dll.botan_x509_cert_verify_with_crl, @@ -1861,10 +1879,87 @@ def _load_buf_or_file(filename, buf, file_fn, buf_fn): # # X.509 certificates # +class X509KeyConstraints(IntEnum): + NO_CONSTRAINTS = 0 + DIGITAL_SIGNATURE = 1 << 15 + NON_REPUDIATION = 1 << 14 + KEY_ENCIPHERMENT = 1 << 13 + DATA_ENCIPHERMENT = 1 << 12 + KEY_AGREEMENT = 1 << 11 + KEY_CERT_SIGN = 1 << 10 + CRL_SIGN = 1 << 9 + ENCIPHER_ONLY = 1 << 8 + DECIPHER_ONLY = 1 << 7 + + @classmethod + def to_bits(cls, constraints): + con = 0 + for constraint in constraints: + con |= constraint.value + return con + + @classmethod + def from_bits(cls, bits): + if bits == 0: + return X509KeyConstraints.NO_CONSTRAINTS + all_constraints = [ + X509KeyConstraints.DIGITAL_SIGNATURE, + X509KeyConstraints.NON_REPUDIATION, + X509KeyConstraints.KEY_ENCIPHERMENT, + X509KeyConstraints.DATA_ENCIPHERMENT, + X509KeyConstraints.KEY_AGREEMENT, + X509KeyConstraints.KEY_CERT_SIGN, + X509KeyConstraints.CRL_SIGN, + X509KeyConstraints.ENCIPHER_ONLY, + X509KeyConstraints.DECIPHER_ONLY, + ] + + constraints = [] + for constraint in all_constraints: + if bits & constraint.value != 0: + constraints.append(constraint) + return constraints + + # TODO deprecate this in a future version + @classmethod + def from_string(cls, constraint): + try: + if isinstance(constraint, X509KeyConstraints): + return constraint + return cls[constraint] + except KeyError as exc: + raise BotanException("Not a valid key constraint") from exc + + @classmethod + def to_string(cls, constraint): + return constraint.name + + +class X509CRLReason(IntEnum): + UNSPECIFIED = 0 + KEY_COMPROMISE = 1 + CA_COMPROMISE = 2 + AFFILIATION_CHANGED = 3 + SUPERSEDED = 4 + CESSATION_OF_OPERATION = 5 + CERTIFICATE_HOLD = 6 + REMOVE_FROM_CRL = 8 + PRIVILEGE_WITHDRAWN = 9 + AA_COMPROMISE = 10 + + @classmethod + def to_bits(cls, reason): + return reason.value + + @classmethod + def from_bits(cls, reason): + return cls(reason) + + class X509CertificateBuilder: def __init__(self): self.__obj = c_void_p(0) - _DLL.botan_x509_create_cert_params_builder(byref(self.__obj)) + _DLL.botan_x509_cert_params_builder_create(byref(self.__obj)) def __del__(self): _DLL.botan_x509_cert_params_builder_destroy(self.__obj) @@ -1909,29 +2004,14 @@ def add_ipv4(self, ipv4): _DLL.botan_x509_cert_params_builder_add_ip(self.__obj, ipv4) def add_allowed_usage(self, usage_list): - # TODO this sucks, where enum - usage_values = {"NO_CONSTRAINTS": 0, - "DIGITAL_SIGNATURE": 32768, - "NON_REPUDIATION": 16384, - "KEY_ENCIPHERMENT": 8192, - "DATA_ENCIPHERMENT": 4096, - "KEY_AGREEMENT": 2048, - "KEY_CERT_SIGN": 1024, - "CRL_SIGN": 512, - "ENCIPHER_ONLY": 256, - "DECIPHER_ONLY": 128} - usage = 0 - for u in usage_list: - if u not in usage_values: - pass - usage += usage_values[u] + usage = X509KeyConstraints.to_bits(usage_list) _DLL.botan_x509_cert_params_builder_add_allowed_usage(self.__obj, c_uint32(usage)) def add_allowed_extended_usage(self, oid): _DLL.botan_x509_cert_params_builder_add_allowed_extended_usage(self.__obj, oid.handle_()) - def set_as_ca_certificate(self, limit): - _DLL.botan_x509_cert_params_builder_set_as_ca_certificate(self.__obj, c_size_t(limit)) + def set_as_ca_certificate(self, limit=None): + _DLL.botan_x509_cert_params_builder_set_as_ca_certificate(self.__obj, c_size_t(limit) if limit is not None else None) def add_ext_ip_addr_blocks(self, ip_addr_blocks, is_critical): _DLL.botan_x509_cert_params_builder_add_ext_ip_addr_blocks(self.__obj, ip_addr_blocks.handle_(), 1 if is_critical else 0) @@ -1939,24 +2019,25 @@ def add_ext_ip_addr_blocks(self, ip_addr_blocks, is_critical): def add_ext_as_blocks(self, as_blocks, is_critical): _DLL.botan_x509_cert_params_builder_add_ext_as_blocks(self.__obj, as_blocks.handle_(), 1 if is_critical else 0) - def create_self_signed(self, key, rng, not_before, not_after, hash_fn=None, padding=None): + def create_self_signed(self, key, rng, not_before, not_after, serial_number=None, hash_fn=None, padding=None): cert = X509Cert() - _DLL.botan_x509_create_self_signed_cert( + serial_no = byref(serial_number.handle_()) if serial_number is not None else None + _DLL.botan_x509_cert_create_self_signed( byref(cert.handle_()), key.handle_(), self.__obj, rng.handle_(), not_before, not_after, + serial_no, _ctype_str(hash_fn), _ctype_str(padding), ) return cert - def create_req(self, key, rng, hash_fn=None, padding=None, challenge_password=None): req = PKCS10Req() - _DLL.botan_x509_create_pkcs10_req( + _DLL.botan_x509_pkcs10_req_create( byref(req.handle_()), key.handle_(), self.__obj, @@ -1971,9 +2052,9 @@ class X509ExtIPAddrBlocks: def __init__(self, cert=None): self.__obj = c_void_p(0) if cert: - _DLL.botan_x509_ext_create_ip_addr_blocks_from_cert(byref(self.__obj), cert.handle_()) + _DLL.botan_x509_ext_ip_addr_blocks_create_from_cert(byref(self.__obj), cert.handle_()) else: - _DLL.botan_x509_ext_create_ip_addr_blocks(byref(self.__obj)) + _DLL.botan_x509_ext_ip_addr_blocks_create(byref(self.__obj)) def __del__(self): _DLL.botan_x509_ext_ip_addr_blocks_destroy(self.__obj) @@ -2053,9 +2134,9 @@ class X509ExtASBlocks: def __init__(self, cert=None): self.__obj = c_void_p(0) if cert: - _DLL.botan_x509_ext_create_as_blocks_from_cert(byref(self.__obj), cert.handle_()) + _DLL.botan_x509_ext_as_blocks_create_from_cert(byref(self.__obj), cert.handle_()) else: - _DLL.botan_x509_ext_create_as_blocks(byref(self.__obj)) + _DLL.botan_x509_ext_as_blocks_create(byref(self.__obj)) def __del__(self): _DLL.botan_x509_ext_as_blocks_destroy(self.__obj) @@ -2123,8 +2204,11 @@ def rdi(self): class PKCS10Req: - def __init__(self): - self.__obj = c_void_p(0) + def __init__(self, filename=None, buf=None): + if not filename and not buf: + self.__obj = c_void_p(0) + else: + self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_pkcs10_req_load_file, _DLL.botan_x509_pkcs10_req_load) def __del__(self): _DLL.botan_x509_pkcs10_req_destroy(self.__obj) @@ -2132,9 +2216,34 @@ def __del__(self): def handle_(self): return self.__obj - def sign(self, issuing_cert, issuing_key, rng, not_before, not_after, hash_fn=None, padding=None): + def public_key(self): + pub = c_void_p(0) + _DLL.botan_x509_pkcs10_req_get_public_key(self.__obj, byref(pub)) + return PublicKey(pub) + + def key_constraints(self): + usage = c_uint32(0) + _DLL.botan_x509_pkcs10_req_get_allowed_usage(self.__obj, byref(usage)) + return X509KeyConstraints.from_bits(usage.value) + + def is_ca(self): + is_ca = c_int(0) + limit = c_size_t(0) + _DLL.botan_x509_pkcs10_req_is_ca(self.__obj, byref(is_ca), byref(limit)) + if is_ca.value == 0: + return (False, 0) + else: + return (True, limit.value) + + def verify(self, key): + ret = c_int(0) + _DLL.botan_x509_pkcs10_req_verify_signature(self.__obj, key.handle_(), byref(ret)) + return ret.value == 1 + + def sign(self, issuing_cert, issuing_key, rng, not_before, not_after, serial_number=None, hash_fn=None, padding=None): cert = X509Cert() - _DLL.botan_x509_sign_req( + serial_no = byref(serial_number.handle_()) if serial_number is not None else None + _DLL.botan_x509_pkcs10_req_sign( byref(cert.handle_()), self.__obj, issuing_cert.handle_(), @@ -2142,6 +2251,7 @@ def sign(self, issuing_cert, issuing_key, rng, not_before, not_after, hash_fn=No rng.handle_(), not_before, not_after, + serial_no, _ctype_str(hash_fn), _ctype_str(padding) ) @@ -2150,6 +2260,9 @@ def sign(self, issuing_cert, issuing_key, rng, not_before, not_after, hash_fn=No def to_pem(self): return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_x509_pkcs10_req_view_pem(self.__obj, vc, vfn)) + def to_der(self): + return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_x509_pkcs10_req_view_der(self.__obj, vc, vfn)) + class X509Cert: # pylint: disable=invalid-name def __init__(self, filename: str | None = None, buf: bytes | None = None): @@ -2246,47 +2359,19 @@ def not_after(self) -> int: return time.value def allowed_usage(self, usage_list: List[str]) -> bool: - usage_values = {"NO_CONSTRAINTS": 0, - "DIGITAL_SIGNATURE": 32768, - "NON_REPUDIATION": 16384, - "KEY_ENCIPHERMENT": 8192, - "DATA_ENCIPHERMENT": 4096, - "KEY_AGREEMENT": 2048, - "KEY_CERT_SIGN": 1024, - "CRL_SIGN": 512, - "ENCIPHER_ONLY": 256, - "DECIPHER_ONLY": 128} - usage = 0 - for u in usage_list: - if u not in usage_values: - return False - usage += usage_values[u] - + usage = X509KeyConstraints.to_bits([X509KeyConstraints.from_string(x) for x in usage_list]) rc = _DLL.botan_x509_cert_allowed_usage(self.__obj, c_uint(usage)) return rc == 0 - def key_constraints(self): - usage_values = {"NO_CONSTRAINTS": 0, - "DIGITAL_SIGNATURE": 32768, - "NON_REPUDIATION": 16384, - "KEY_ENCIPHERMENT": 8192, - "DATA_ENCIPHERMENT": 4096, - "KEY_AGREEMENT": 2048, - "KEY_CERT_SIGN": 1024, - "CRL_SIGN": 512, - "ENCIPHER_ONLY": 256, - "DECIPHER_ONLY": 128} + def allowed_usages(self): usage = c_uint32(0) - _DLL.botan_x509_get_key_constraints(self.__obj, byref(usage)) - if usage.value == 0: - return ["NO_CONSTRAINTS"] - else: - return [name for name, bit in usage_values.items() if bit != 0 and usage.value & bit] + _DLL.botan_x509_cert_get_allowed_usage(self.__obj, byref(usage)) + return X509KeyConstraints.from_bits(usage.value) def is_ca(self): is_ca = c_int(0) limit = c_size_t(0) - _DLL.botan_x509_get_basic_constraints(self.__obj, byref(is_ca), byref(limit)) + _DLL.botan_x509_cert_is_ca(self.__obj, byref(is_ca), byref(limit)) if is_ca.value == 0: return (False, 0) else: @@ -2294,11 +2379,11 @@ def is_ca(self): def ocsp_responder(self): return _call_fn_viewing_str( - lambda vc, vfn: _DLL.botan_x509_get_ocsp_responder(self.__obj, vc, vfn)) + lambda vc, vfn: _DLL.botan_x509_cert_get_ocsp_responder(self.__obj, vc, vfn)) def is_self_signed(self): self_signed = c_int(0) - _DLL.botan_x509_is_self_signed(self.__obj, byref(self_signed)) + _DLL.botan_x509_cert_is_self_signed(self.__obj, byref(self_signed)) if self_signed.value == 0: return False else: @@ -2378,15 +2463,22 @@ def is_revoked(self, crl: X509CRL) -> bool: return rc == 0 - - # # X.509 Certificate revocation lists # +class X509CRLEntry: + def __init__(self, serial_number, expire_time, reason): + self.serial_number = serial_number + self.expire_time = expire_time + self.reason = reason + + class X509CRL: def __init__(self, filename: str | None = None, buf: bytes | None = None): - self.__obj = c_void_p(0) - self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_crl_load_file, _DLL.botan_x509_crl_load) + if not filename and not buf: + self.__obj = c_void_p(0) + else: + self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_crl_load_file, _DLL.botan_x509_crl_load) def __del__(self): _DLL.botan_x509_crl_destroy(self.__obj) @@ -2394,6 +2486,69 @@ def __del__(self): def handle_(self): return self.__obj + @classmethod + def create(cls, rng, ca_cert, ca_key, issue_time, next_update, hash_fn=None, padding=None): + crl = X509CRL() + _DLL.botan_x509_crl_create( + byref(crl.handle_()), + rng.handle_(), + ca_cert.handle_(), + ca_key.handle_(), + issue_time, + next_update, + _ctype_str(hash_fn), + _ctype_str(padding) + ) + return crl + + def revoke(self, rng, ca_cert, ca_key, issue_time, next_update, revoked, reason, hash_fn=None, padding=None): + crl = X509CRL() + c_revoked = len(revoked) * c_void_p + arr_revoked = c_revoked() + for i, cert in enumerate(revoked): + arr_revoked[i] = cert.handle_() + revoked_len = c_size_t(len(revoked)) + + _DLL.botan_x509_crl_update( + byref(crl.handle_()), + self.__obj, + rng.handle_(), + ca_cert.handle_(), + ca_key.handle_(), + issue_time, + next_update, + arr_revoked, + revoked_len, + X509CRLReason.to_bits(reason), + _ctype_str(hash_fn), + _ctype_str(padding) + ) + return crl + + def revoked(self): + count = c_size_t(0) + _DLL.botan_x509_crl_get_count(self.__obj, byref(count)) + revoked = [] + for i in range(count.value): + expire_time = c_uint64(0) + reason = c_uint8(0) + serial = _call_fn_returning_vec( + 32, lambda b, bl, i=i, expire_time=expire_time, reason=reason: _DLL.botan_x509_crl_get_entry(self.__obj, c_size_t(i), b, bl, byref(expire_time), byref(reason)) + ) + revoked.append(X509CRLEntry(serial, expire_time.value, X509CRLReason.from_bits(reason.value))) + return revoked + + def verify(self, key): + ret = c_int(0) + _DLL.botan_x509_crl_verify_signature(self.__obj, key.handle_(), byref(ret)) + return ret.value == 1 + + def to_pem(self): + return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_x509_crl_view_pem(self.__obj, vc, vfn)) + + def to_der(self): + return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_x509_crl_view_der(self.__obj, vc, vfn)) + class MPI: @@ -2426,6 +2581,13 @@ def random_range(cls, rng_obj: RandomNumberGenerator, lower: MPI, upper: MPI): _DLL.botan_mp_rand_range(bn.handle_(), rng_obj.handle_(), lower.handle_(), upper.handle_()) return bn + @classmethod + def from_bytes(cls, buf): + bn = MPI() + out_len = c_size_t(len(buf)) + _DLL.botan_mp_from_bin(bn.handle_(), buf, out_len) + return bn + def __del__(self): _DLL.botan_mp_destroy(self.__obj) diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 066fa48c917..c8be051ba38 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -778,6 +778,7 @@ def test_certs(self): self.assertTrue(cert.allowed_usage(["CRL_SIGN", "KEY_CERT_SIGN"])) self.assertTrue(cert.allowed_usage(["KEY_CERT_SIGN"])) + self.assertTrue(cert.allowed_usage([botan.X509KeyConstraints.CRL_SIGN, botan.X509KeyConstraints.KEY_CERT_SIGN])) self.assertFalse(cert.allowed_usage(["DIGITAL_SIGNATURE"])) self.assertFalse(cert.allowed_usage(["DIGITAL_SIGNATURE", "CRL_SIGN"])) @@ -822,10 +823,10 @@ def test_certs(self): self.assertTrue(end21.is_revoked(int21crl)) def test_cert_creation(self): - hash_fn = "SHA-256" group = "secp256r1" - now = int(time.time()) + not_before = now - 180 + not_after = now + 86400 rng = botan.RandomNumberGenerator() ca_key = botan.PrivateKey.create("ECDSA", group, rng) @@ -835,24 +836,29 @@ def test_cert_creation(self): ca_builder.add_organization("Botan Project") ca_builder.add_organizational_unit("Testing") ca_builder.set_as_ca_certificate(1) - ca_builder.add_allowed_usage(["DIGITAL_SIGNATURE"]) - ca_cert = ca_builder.create_self_signed(ca_key, rng, now, now + 60, hash_fn) - constraints = ca_cert.key_constraints() + ca_cert = ca_builder.create_self_signed(ca_key, rng, not_before, not_after) + constraints = ca_cert.allowed_usages() self.assertEqual(len(constraints), 3) - for item in ["DIGITAL_SIGNATURE", "KEY_CERT_SIGN", "CRL_SIGN"]: + for item in [ + botan.X509KeyConstraints.DIGITAL_SIGNATURE, + botan.X509KeyConstraints.KEY_CERT_SIGN, + botan.X509KeyConstraints.CRL_SIGN + ]: self.assertTrue(item in constraints) self.assertTrue(ca_cert.is_self_signed()) cert_key = botan.PrivateKey.create("ECDSA", group, rng) req_builder = botan.X509CertificateBuilder() + req_builder.add_allowed_usage([botan.X509KeyConstraints.DIGITAL_SIGNATURE]) req_builder.add_uri("https://botan.randombit.net") for item in ["imaginary.botan.randombit.net", "botan.randombit.net", "randombit.net"]: req_builder.add_dns(item) - req = req_builder.create_req(cert_key, rng, hash_fn) + req = req_builder.create_req(cert_key, rng) + self.assertTrue(req.verify(cert_key.get_public_key())) - cert = req.sign(ca_cert, ca_key, rng, now, now + 60, hash_fn) + cert = req.sign(ca_cert, ca_key, rng, not_before, not_after) self.assertFalse(cert.is_self_signed()) - self.assertEqual(cert.key_constraints(), ["NO_CONSTRAINTS"]) + self.assertEqual(cert.allowed_usages(), [botan.X509KeyConstraints.DIGITAL_SIGNATURE]) with self.assertRaisesRegex(botan.BotanException, r".*No value available.*"): _ = cert.ext_ip_addr_blocks() @@ -862,16 +868,57 @@ def test_cert_creation(self): self.assertEqual(cert.verify(None, [ca_cert]), 0) - def test_x509_rpki(self): - hash_fn = "SHA-256" + def test_cert_revocation(self): group = "secp256r1" - now = int(time.time()) + not_before = now - 180 + not_after = now + 86400 rng = botan.RandomNumberGenerator() ca_key = botan.PrivateKey.create("ECDSA", group, rng) ca_builder = botan.X509CertificateBuilder() ca_builder.set_as_ca_certificate(1) + ca_cert = ca_builder.create_self_signed(ca_key, rng, not_before, not_after, botan.MPI("0")) + + cert_key_1 = botan.PrivateKey.create("ECDSA", group, rng) + req_builder_1 = botan.X509CertificateBuilder() + req_1 = req_builder_1.create_req(cert_key_1, rng) + cert_1 = req_1.sign(ca_cert, ca_key, rng, not_before, not_after, botan.MPI("1")) + + cert_key_2 = botan.PrivateKey.create("ECDSA", group, rng) + req_builder_2 = botan.X509CertificateBuilder() + req_2 = req_builder_2.create_req(cert_key_2, rng) + cert_2 = req_2.sign(ca_cert, ca_key, rng, not_before, not_after, botan.MPI("2")) + + crl = botan.X509CRL.create(rng, ca_cert, ca_key, now, now + 600) + self.assertTrue(crl.verify(ca_cert.subject_public_key())) + self.assertEqual(cert_1.verify(None, [ca_cert], crls=[crl]), 0) + self.assertEqual(cert_2.verify(None, [ca_cert], crls=[crl]), 0) + + crl = crl.revoke(rng, ca_cert, ca_key, now, now + 86400, [cert_1], botan.X509CRLReason.KEY_COMPROMISE) + self.assertTrue(crl.verify(ca_cert.subject_public_key())) + self.assertEqual(cert_1.verify(None, [ca_cert], crls=[crl]), 5000) + self.assertEqual(cert_2.verify(None, [ca_cert], crls=[crl]), 0) + self.assertEqual(len(crl.revoked()), 1) + revoked_entry: botan.X509CRLEntry = crl.revoked()[0] + self.assertEqual(revoked_entry.reason, botan.X509CRLReason.KEY_COMPROMISE) + self.assertEqual(botan.MPI.from_bytes(revoked_entry.serial_number), botan.MPI("1")) + self.assertTrue(now - 5 <= revoked_entry.expire_time <= now + 5) + + pem = crl.to_pem() + crl_from_pem = botan.X509CRL(buf=pem) + self.assertEqual(len(crl_from_pem.revoked()), 1) + + def test_x509_rpki(self): + group = "secp256r1" + now = int(time.time()) + not_before = now - 180 + not_after = now + 86400 + + rng = botan.RandomNumberGenerator() + ca_key = botan.PrivateKey.create("ECDSA", group, rng) + ca_builder = botan.X509CertificateBuilder() + ca_builder.set_as_ca_certificate() ca_ip_addr_blocks = botan.X509ExtIPAddrBlocks() @@ -896,7 +943,7 @@ def test_x509_rpki(self): with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): ca_builder.add_ext_as_blocks(ca_as_blocks, True) - ca_cert = ca_builder.create_self_signed(ca_key, rng, now, now + 60, hash_fn) + ca_cert = ca_builder.create_self_signed(ca_key, rng, not_before, not_after) ca_ip_addr_blocks = ca_cert.ext_ip_addr_blocks() self.assertEqual(ca_ip_addr_blocks.addresses(), ( @@ -939,9 +986,9 @@ def test_x509_rpki(self): req_builder.add_ext_ip_addr_blocks(req_ip_addr_blocks, True) req_builder.add_ext_as_blocks(req_as_blocks, True) - req = req_builder.create_req(cert_key, rng, hash_fn) + req = req_builder.create_req(cert_key, rng) - cert = req.sign(ca_cert, ca_key, rng, now, now + 60, hash_fn) + cert = req.sign(ca_cert, ca_key, rng, not_before, not_after) req_ip_addr_blocks = cert.ext_ip_addr_blocks() self.assertEqual(req_ip_addr_blocks.addresses(), ( diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index bd022c4cfe1..9778ab2bc7e 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -154,6 +154,68 @@ class FFI_Test : public Test { virtual void ffi_test(Test::Result& result, botan_rng_t rng) = 0; }; +/** + * Helper class for testing "view"-style API functions that take a callback + * that gets passed a variable-length buffer of bytes. + * + * Example: + * botan_privkey_t priv; + * ViewBytesSink sink; + * botan_privkey_view_raw(priv, sink.delegate(), sink.callback()); + * std::cout << hex_encode(sink.get()) << std::endl; + */ +class ViewBytesSink final { + public: + void* delegate() { return this; } + + botan_view_bin_fn callback() { return &write_fn; } + + const std::vector& get() { return m_buf; } + + private: + static int write_fn(void* ctx, const uint8_t buf[], size_t len) { + if(ctx == nullptr || buf == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + auto* sink = static_cast(ctx); + sink->m_buf.assign(buf, buf + len); + + return 0; + } + + private: + std::vector m_buf; +}; + +/** + * See ViewBytesSink for how to use this. Works for `botan_view_str_fn` instead. +*/ +class ViewStringSink final { + public: + void* delegate() { return this; } + + botan_view_str_fn callback() { return &write_fn; } + + std::string_view get() { return m_str; } + + private: + static int write_fn(void* ctx, const char* str, size_t len) { + if(ctx == nullptr || str == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + auto* sink = static_cast(ctx); + // discard the null terminator + sink->m_str = std::string(str, len - 1); + + return 0; + } + + private: + std::string m_str; +}; + void ffi_test_pubkey_export(Test::Result& result, botan_pubkey_t pub, botan_privkey_t priv, botan_rng_t rng) { // export public key size_t pubkey_len = 0; @@ -802,53 +864,148 @@ class FFI_Cert_Creation_Test final : public FFI_Test { const std::string group{"secp256r1"}; botan_privkey_t ca_key; + botan_pubkey_t ca_key_pub; botan_privkey_t cert_key; + botan_pubkey_t cert_key_pub; if(TEST_FFI_INIT(botan_privkey_create, (&ca_key, "ECDSA", group.c_str(), rng))) { TEST_FFI_OK(botan_privkey_create, (&cert_key, "ECDSA", group.c_str(), rng)); + TEST_FFI_OK(botan_privkey_export_pubkey, (&ca_key_pub, ca_key)); + TEST_FFI_OK(botan_privkey_export_pubkey, (&cert_key_pub, cert_key)); + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) .count(); + uint64_t not_before = now - 180; + uint64_t not_after = now + 86400; botan_x509_cert_params_builder_t ca_builder; - if(TEST_FFI_INIT(botan_x509_create_cert_params_builder, (&ca_builder))) { + if(TEST_FFI_INIT(botan_x509_cert_params_builder_create, (&ca_builder))) { TEST_FFI_OK(botan_x509_cert_params_builder_add_common_name, (ca_builder, "Test CA")); TEST_FFI_OK(botan_x509_cert_params_builder_add_country, (ca_builder, "US")); TEST_FFI_OK(botan_x509_cert_params_builder_add_organization, (ca_builder, "Botan Project")); TEST_FFI_OK(botan_x509_cert_params_builder_add_organizational_unit, (ca_builder, "Testing")); - TEST_FFI_OK(botan_x509_cert_params_builder_set_as_ca_certificate, (ca_builder, 1)); + TEST_FFI_OK(botan_x509_cert_params_builder_set_as_ca_certificate, (ca_builder, nullptr)); TEST_FFI_OK(botan_x509_cert_params_builder_add_uri, (ca_builder, "https://botan.randombit.net")); for(const auto* dns : {"imaginary.botan.randombit.net", "botan.randombit.net", "randombit.net"}) { TEST_FFI_OK(botan_x509_cert_params_builder_add_dns, (ca_builder, dns)); } botan_x509_cert_t ca_cert; - TEST_FFI_OK(botan_x509_create_self_signed_cert, - (&ca_cert, ca_key, ca_builder, rng, now, now + 60, hash_fn.c_str(), "")); + TEST_FFI_OK(botan_x509_cert_create_self_signed, + (&ca_cert, ca_key, ca_builder, rng, not_before, not_after, nullptr, hash_fn.c_str(), "")); botan_x509_cert_params_builder_t req_builder; - TEST_FFI_OK(botan_x509_create_cert_params_builder, (&req_builder)); + TEST_FFI_OK(botan_x509_cert_params_builder_create, (&req_builder)); + TEST_FFI_OK(botan_x509_cert_params_builder_add_allowed_usage, (req_builder, 1 << 15)); botan_x509_pkcs10_req_t req; - TEST_FFI_OK(botan_x509_create_pkcs10_req, + TEST_FFI_OK(botan_x509_pkcs10_req_create, (&req, cert_key, req_builder, rng, hash_fn.c_str(), nullptr, nullptr)); + int res; + TEST_FFI_OK(botan_x509_pkcs10_req_verify_signature, (req, ca_key_pub, &res)); + result.confirm("signature is not valid", res == 0); + TEST_FFI_OK(botan_x509_pkcs10_req_verify_signature, (req, cert_key_pub, &res)); + result.confirm("signature is valid", res == 1); + + uint32_t usage; + TEST_FFI_OK(botan_x509_pkcs10_req_get_allowed_usage, (req, &usage)); + result.confirm("usage is correct", usage == 1 << 15); + + int is_ca; + size_t limit; + TEST_FFI_OK(botan_x509_pkcs10_req_is_ca, (req, &is_ca, &limit)); + result.confirm("cert is not a CA", is_ca == 0); + + botan_pubkey_t pubkey_from_req; + TEST_FFI_OK(botan_x509_pkcs10_req_get_public_key, (req, &pubkey_from_req)); + TEST_FFI_OK(botan_x509_pkcs10_req_verify_signature, (req, pubkey_from_req, &res)); + result.confirm("signature is valid", res == 1); + + ViewStringSink req_pem; + TEST_FFI_OK(botan_x509_pkcs10_req_view_pem, (req, req_pem.delegate(), req_pem.callback())); + std::string pem = {req_pem.get().begin(), req_pem.get().end()}; + + botan_x509_pkcs10_req_t req_from_pem; + TEST_FFI_OK(botan_x509_pkcs10_req_load, + (&req_from_pem, reinterpret_cast(pem.c_str()), pem.size())); + + TEST_FFI_OK(botan_x509_pkcs10_req_verify_signature, (req_from_pem, cert_key_pub, &res)); + result.confirm("signature is valid", res == 1); + + botan_mp_t serial; + TEST_FFI_OK(botan_mp_init, (&serial)); + TEST_FFI_OK(botan_mp_set_from_str, (serial, "12345")); + botan_x509_cert_t cert; - TEST_FFI_OK(botan_x509_sign_req, (&cert, req, ca_cert, ca_key, rng, now, now + 60, hash_fn.c_str(), "")); + TEST_FFI_OK(botan_x509_pkcs10_req_sign, + (&cert, req, ca_cert, ca_key, rng, not_before, not_after, &serial, hash_fn.c_str(), "")); + + std::vector serial_out(5); + size_t out_len = 5; + + botan_mp_t serial_from_cert; + TEST_FFI_OK(botan_mp_init, (&serial_from_cert)); + + TEST_FFI_OK(botan_x509_cert_get_serial_number, (cert, serial_out.data(), &out_len)); + TEST_FFI_OK(botan_mp_from_bin, (serial_from_cert, serial_out.data(), out_len)); + TEST_FFI_RC(1, botan_mp_equal, (serial, serial_from_cert)); int rc; TEST_FFI_RC(0, botan_x509_cert_verify, (&rc, cert, nullptr, 0, &ca_cert, 1, nullptr, 0, nullptr, 0)); + result.confirm("validation succeeds", rc == 0); + + botan_x509_crl_t crl; + TEST_FFI_OK(botan_x509_crl_create, + (&crl, rng, ca_cert, ca_key, not_before, 86400, hash_fn.c_str(), nullptr)); + + TEST_FFI_RC(0, + botan_x509_cert_verify_with_crl, + (&rc, cert, nullptr, 0, &ca_cert, 1, &crl, 1, nullptr, 0, nullptr, 0)); + botan_x509_crl_t new_crl; + botan_x509_cert_t certs[1] = {cert}; + TEST_FFI_OK( + botan_x509_crl_update, + (&new_crl, crl, rng, ca_cert, ca_key, not_before, 86400, certs, 1, 1, hash_fn.c_str(), nullptr)); + + TEST_FFI_RC(1, + botan_x509_cert_verify_with_crl, + (&rc, cert, nullptr, 0, &ca_cert, 1, &new_crl, 1, nullptr, 0, nullptr, 0)); + result.confirm("validation fails because cert is revoked", rc == 5000); + size_t count; + TEST_FFI_OK(botan_x509_crl_get_count, (new_crl, &count)); + result.confirm("crl has correct number of entires", count == 1); + + uint64_t expire_time; + uint8_t reason; + TEST_FFI_OK(botan_x509_crl_get_entry, (new_crl, 0, serial_out.data(), &out_len, &expire_time, &reason)); + botan_mp_t serial_from_crl; + TEST_FFI_OK(botan_mp_init, (&serial_from_crl)); + TEST_FFI_OK(botan_mp_from_bin, (serial_from_crl, serial_out.data(), out_len)); + TEST_FFI_RC(1, botan_mp_equal, (serial, serial_from_crl)); + result.confirm("expire time is correct", now - 5 <= expire_time && expire_time <= now + 5); + result.confirm("reason is correct", reason == 1); + + TEST_FFI_OK(botan_mp_destroy, (serial)); + TEST_FFI_OK(botan_mp_destroy, (serial_from_cert)); + TEST_FFI_OK(botan_mp_destroy, (serial_from_crl)); + TEST_FFI_OK(botan_x509_crl_destroy, (crl)); + TEST_FFI_OK(botan_x509_crl_destroy, (new_crl)); TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (ca_builder)); TEST_FFI_OK(botan_x509_cert_params_builder_destroy, (req_builder)); TEST_FFI_OK(botan_x509_pkcs10_req_destroy, (req)); + TEST_FFI_OK(botan_x509_pkcs10_req_destroy, (req_from_pem)); TEST_FFI_OK(botan_x509_cert_destroy, (ca_cert)); TEST_FFI_OK(botan_x509_cert_destroy, (cert)); + TEST_FFI_OK(botan_pubkey_destroy, (ca_key_pub)); + TEST_FFI_OK(botan_pubkey_destroy, (cert_key_pub)); + TEST_FFI_OK(botan_pubkey_destroy, (pubkey_from_req)); + TEST_FFI_OK(botan_privkey_destroy, (ca_key)); + TEST_FFI_OK(botan_privkey_destroy, (cert_key)); } - - TEST_FFI_OK(botan_privkey_destroy, (ca_key)); - TEST_FFI_OK(botan_privkey_destroy, (cert_key)); } } }; @@ -870,13 +1027,16 @@ class FFI_X509_RPKI_Test final : public FFI_Test { uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) .count(); + uint64_t not_before = now - 180; + uint64_t not_after = now + 86400; botan_x509_cert_params_builder_t ca_builder; - if(TEST_FFI_INIT(botan_x509_create_cert_params_builder, (&ca_builder))) { - TEST_FFI_OK(botan_x509_cert_params_builder_set_as_ca_certificate, (ca_builder, 1)); + if(TEST_FFI_INIT(botan_x509_cert_params_builder_create, (&ca_builder))) { + size_t limit = 1; + TEST_FFI_OK(botan_x509_cert_params_builder_set_as_ca_certificate, (ca_builder, &limit)); botan_x509_ext_ip_addr_blocks_t ca_ip_addr_blocks; - TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks, (&ca_ip_addr_blocks)); + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_create, (&ca_ip_addr_blocks)); std::vector ip_addr = {192, 168, 2, 1}; TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_add_ip_addr, (ca_ip_addr_blocks, ip_addr.data(), ip_addr.data(), 0, nullptr)); @@ -981,7 +1141,7 @@ class FFI_X509_RPKI_Test final : public FFI_Test { TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (ca_ip_addr_blocks)); botan_x509_ext_as_blocks_t ca_as_blocks; - TEST_FFI_OK(botan_x509_ext_create_as_blocks, (&ca_as_blocks)); + TEST_FFI_OK(botan_x509_ext_as_blocks_create, (&ca_as_blocks)); TEST_FFI_OK(botan_x509_ext_as_blocks_add_asnum, (ca_as_blocks, 0, 3000)); TEST_FFI_OK(botan_x509_ext_as_blocks_add_asnum, (ca_as_blocks, 4000, 5000)); @@ -1018,38 +1178,41 @@ class FFI_X509_RPKI_Test final : public FFI_Test { TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (ca_as_blocks)); botan_x509_cert_t ca_cert; - TEST_FFI_OK(botan_x509_create_self_signed_cert, - (&ca_cert, ca_key, ca_builder, rng, now, now + 60, hash_fn.c_str(), nullptr)); + TEST_FFI_OK( + botan_x509_cert_create_self_signed, + (&ca_cert, ca_key, ca_builder, rng, not_before, not_after, nullptr, hash_fn.c_str(), nullptr)); botan_x509_cert_params_builder_t req_builder; - TEST_FFI_OK(botan_x509_create_cert_params_builder, (&req_builder)); + TEST_FFI_OK(botan_x509_cert_params_builder_create, (&req_builder)); botan_x509_ext_ip_addr_blocks_t req_ip_addr_blocks; - TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks, (&req_ip_addr_blocks)); + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_create, (&req_ip_addr_blocks)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_inherit, (req_ip_addr_blocks, 0, nullptr)); TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_ip_addr_blocks, (req_builder, req_ip_addr_blocks, 1)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (req_ip_addr_blocks)); botan_x509_ext_as_blocks_t req_as_blocks; - TEST_FFI_OK(botan_x509_ext_create_as_blocks, (&req_as_blocks)); + TEST_FFI_OK(botan_x509_ext_as_blocks_create, (&req_as_blocks)); TEST_FFI_OK(botan_x509_ext_as_blocks_inherit_asnum, (req_as_blocks)); TEST_FFI_OK(botan_x509_cert_params_builder_add_ext_as_blocks, (req_builder, req_as_blocks, 1)); TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (req_as_blocks)); botan_x509_pkcs10_req_t req; - TEST_FFI_OK(botan_x509_create_pkcs10_req, + TEST_FFI_OK(botan_x509_pkcs10_req_create, (&req, cert_key, req_builder, rng, hash_fn.c_str(), nullptr, nullptr)); botan_x509_cert_t cert; - TEST_FFI_OK(botan_x509_sign_req, (&cert, req, ca_cert, ca_key, rng, now, now + 60, hash_fn.c_str(), "")); + TEST_FFI_OK( + botan_x509_pkcs10_req_sign, + (&cert, req, ca_cert, ca_key, rng, not_before, not_after, nullptr, hash_fn.c_str(), nullptr)); uint64_t test = 0; TEST_FFI_OK(botan_x509_cert_not_after, (cert, &test)); botan_x509_ext_ip_addr_blocks_t req_ip_addr_blocks_from_cert; - TEST_FFI_OK(botan_x509_ext_create_ip_addr_blocks_from_cert, (&req_ip_addr_blocks_from_cert, cert)); + TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_create_from_cert, (&req_ip_addr_blocks_from_cert, cert)); TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_get_counts, (req_ip_addr_blocks_from_cert, &v4_count, &v6_count)); @@ -1078,7 +1241,7 @@ class FFI_X509_RPKI_Test final : public FFI_Test { TEST_FFI_OK(botan_x509_ext_ip_addr_blocks_destroy, (req_ip_addr_blocks_from_cert)); botan_x509_ext_as_blocks_t req_as_blocks_from_cert; - TEST_FFI_OK(botan_x509_ext_create_as_blocks_from_cert, (&req_as_blocks_from_cert, cert)); + TEST_FFI_OK(botan_x509_ext_as_blocks_create_from_cert, (&req_as_blocks_from_cert, cert)); TEST_FFI_OK(botan_x509_ext_as_blocks_get_asnum, (req_as_blocks_from_cert, &present, &count)); result.confirm("ext inherits asnum", present == 0); From 54cd43169e5418a2009fe841711ab5552ba23f2e Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Fri, 29 Aug 2025 15:18:28 +0200 Subject: [PATCH 13/14] more cleanups --- doc/api_ref/ffi.rst | 60 +++++++++++------ src/lib/ffi/ffi.h | 63 ++++++++++++------ src/lib/ffi/ffi_cert.cpp | 129 ++++++++++++++++++++++++++----------- src/python/botan3.py | 38 +++++++---- src/scripts/test_python.py | 18 +++--- src/tests/test_ffi.cpp | 73 ++------------------- 6 files changed, 214 insertions(+), 167 deletions(-) diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index d80d4097540..4d612c88ae2 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -1631,12 +1631,6 @@ X.509 Certificates Destroy the certificate object -.. cpp:function:: int botan_x509_cert_gen_selfsigned(botan_x509_cert_t* cert, \ - botan_privkey_t key, \ - botan_rng_t rng, \ - const char* common_name, \ - const char* org_name) - .. cpp:function:: int botan_x509_cert_get_time_starts(botan_x509_cert_t cert, char out[], size_t* out_len) Return the time the certificate becomes valid, as a string in form @@ -1678,11 +1672,11 @@ X.509 Certificates .. cpp:function::int botan_x509_cert_get_allowed_usage(botan_x509_cert_t cert, uint32_t* usage) - Returns the key usage constraints. + Sets ``usage`` the key usage constraints. .. cpp:function::int botan_x509_cert_get_ocsp_responder(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) - Returns the OCSP responder. + Get the OCSP responder. .. cpp:function::int botan_x509_cert_is_self_signed(botan_x509_cert_t cert, int* out) @@ -1702,18 +1696,34 @@ X.509 Certificates Get the public key included in this certificate as a newly allocated object +.. cpp:function:: int botan_x509_cert_get_issuer_dn_count(botan_x509_cert_t cert, const char* key, size_t* len) + + Get the number of elements for a key in the isssuer DN field. + .. cpp:function:: int botan_x509_cert_get_issuer_dn(botan_x509_cert_t cert, \ const char* key, size_t index, \ uint8_t out[], size_t* out_len) Get a value from the issuer DN field. +.. cpp:function:: int botan_x509_cert_get_subject_dn_count(botan_x509_cert_t cert, const char* key, size_t* len) + + Get the number of elements for a key in the subject DN field. + .. cpp:function:: int botan_x509_cert_get_subject_dn(botan_x509_cert_t cert, \ const char* key, size_t index, \ uint8_t out[], size_t* out_len) Get a value from the subject DN field. +.. cpp:function:: int botan_x509_cert_get_subject_dn(botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len) + + Get the subject name. + +.. cpp:function:: int botan_x509_cert_get_issuer_name(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) + + Get the issuer name. + .. cpp:function:: int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len) Format the certificate as a free-form string. @@ -1952,6 +1962,8 @@ X.509 Certificates .. cpp:function::int botan_x509_cert_params_builder_add_allowed_usage(botan_x509_cert_params_builder_t builder, uint32_t usage); + See :cpp:enum:`botan_x509_cert_key_constraints` for allowed values. + .. cpp:function::int botan_x509_cert_params_builder_add_allowed_extended_usage(botan_x509_cert_params_builder_t builder, botan_asn1_oid_t oid); .. cpp:function::int botan_x509_cert_params_builder_set_as_ca_certificate(botan_x509_cert_params_builder_t builder, size_t limit=None); @@ -1964,7 +1976,7 @@ X.509 Certificates .. cpp:function::int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_builder_t builder, \ botan_x509_ext_as_blocks_t as_blocks, int is_critical); -.. cpp:function::int botan_x509_cert_create_self_signed(botan_x509_cert_t* cert_obj, \ +.. cpp:function::int botan_x509_cert_params_builder_into_self_signed(botan_x509_cert_t* cert_obj, \ botan_privkey_t key, \ botan_x509_cert_params_builder_t builder, \ botan_rng_t rng, \ @@ -1980,6 +1992,16 @@ X.509 Certificates An opaque data type for a PKCS #10 certificate request. Don't mess with it. +.. cpp:function::int botan_x509_cert_params_builder_into_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, \ + botan_privkey_t key, \ + botan_x509_cert_params_builder_t builder, \ + botan_rng_t rng, \ + const char* hash_fn, \ + const char* padding, \ + const char* challenge_password) + + Create a PCKS #10 certificate request. ``challenge_password``, ``hash_fn`` and ``padding`` may be NULL. + .. cpp:function::int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req) Destroy the PKCS #10 certificate request object. @@ -1996,17 +2018,6 @@ X.509 Certificates .. cpp:function::int int botan_x509_pkcs10_req_verify_signature(botan_x509_pkcs10_req_t req, botan_pubkey_t key, int* result) - -.. cpp:function::int botan_x509_pkcs10_req_create(botan_x509_pkcs10_req_t* req_obj, \ - botan_privkey_t key, \ - botan_x509_cert_params_builder_t builder, \ - botan_rng_t rng, \ - const char* hash_fn, \ - const char* padding, \ - const char* challenge_password) - - Create a PCKS #10 certificate request. ``challenge_password``, ``hash_fn`` and ``padding`` may be NULL. - .. cpp:function::int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view) .. cpp:function::int int botan_x509_pkcs10_req_view_der(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_bin_fn view) @@ -2049,6 +2060,13 @@ X.509 Certificate Revocation Lists const char* hash_fn, \ const char* padding) +.. cpp:enum:: botan_x509_crl_reason_code + + CRL Reason codes. Allowed values: `UNSPECIFIED`, + `KEY_COMPROMISE`, `CA_COMPROMISE`, `AFFILIATION_CHANGED`, + `SUPERSEDED`, `CESSATION_OF_OPERATION`, `CERTIFICATE_HOLD`, + `REMOVE_FROM_CRL`, `PRIVILIGE_WITHDRAWN`, `AA_COMPROMISE`. + .. cpp:function:: int botan_x509_crl_update(botan_x509_crl_t* crl_obj, \ botan_x509_crl_t last_crl, \ botan_rng_t rng, \ @@ -2064,7 +2082,7 @@ X.509 Certificate Revocation Lists .. cpp:function:: int botan_x509_crl_get_count(botan_x509_crl_t crl, size_t* count); -.. cpp:function:: int botan_x509_crl_get_entry(botan_x509_crl_t crl, size_t i, uint8_t serial[], size_t* serial_len, uint64_t* expire_time, uint8_t* reason) +.. cpp:function:: int botan_x509_crl_get_entry(botan_x509_crl_t crl, size_t i, botan_mp_t serial, uint64_t* expire_time, uint8_t* reason) .. cpp:function:: int botan_x509_crl_verify_signature(botan_x509_crl_t crl, botan_pubkey_t key, int* result) diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 073c506d937..253e1040e40 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -2153,16 +2153,26 @@ int botan_x509_cert_view_public_key_bits(botan_x509_cert_t cert, botan_view_ctx BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_public_key(botan_x509_cert_t cert, botan_pubkey_t* key); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_get_issuer_dn_count(botan_x509_cert_t cert, const char* key, size_t* len); + /* TODO(Botan4) this should use char for the out param */ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_issuer_dn( botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len); +BOTAN_FFI_EXPORT(3, 10) int botan_x509_cert_get_subject_dn_count(botan_x509_cert_t cert, const char* key, size_t* len); + /* TODO(Botan4) this should use char for the out param */ BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_get_subject_dn( botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len); +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_get_subject_name(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); + +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_get_issuer_name(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view); + BOTAN_FFI_EXPORT(2, 0) int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len); BOTAN_FFI_EXPORT(3, 0) @@ -2372,18 +2382,27 @@ int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_buil * X.509 cert creation */ BOTAN_FFI_EXPORT(3, 10) -int botan_x509_cert_create_self_signed(botan_x509_cert_t* cert_obj, - botan_privkey_t key, - botan_x509_cert_params_builder_t builder, - botan_rng_t rng, - uint64_t not_before, - uint64_t not_after, - const botan_mp_t* serial_number, - const char* hash_fn, - const char* padding); +int botan_x509_cert_params_builder_into_self_signed(botan_x509_cert_t* cert_obj, + botan_privkey_t key, + botan_x509_cert_params_builder_t builder, + botan_rng_t rng, + uint64_t not_before, + uint64_t not_after, + const botan_mp_t* serial_number, + const char* hash_fn, + const char* padding); typedef struct botan_x509_pkcs10_req_struct* botan_x509_pkcs10_req_t; +BOTAN_FFI_EXPORT(3, 10) +int botan_x509_cert_params_builder_into_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, + botan_privkey_t key, + botan_x509_cert_params_builder_t builder, + botan_rng_t rng, + const char* hash_fn, + const char* padding, + const char* challenge_password); + BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req); BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_load_file(botan_x509_pkcs10_req_t* req_obj, const char* req_path); @@ -2400,15 +2419,6 @@ BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_is_ca(botan_x509_pkcs10_req_t BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_verify_signature(botan_x509_pkcs10_req_t req, botan_pubkey_t key, int* result); -BOTAN_FFI_EXPORT(3, 10) -int botan_x509_pkcs10_req_create(botan_x509_pkcs10_req_t* req_obj, - botan_privkey_t key, - botan_x509_cert_params_builder_t builder, - botan_rng_t rng, - const char* hash_fn, - const char* padding, - const char* challenge_password); - BOTAN_FFI_EXPORT(3, 10) int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view); @@ -2447,6 +2457,20 @@ int botan_x509_crl_create(botan_x509_crl_t* crl_obj, const char* hash_fn, const char* padding); +/* Must match values of CRL_Code in pkix_enums.h */ +enum botan_x509_crl_reason_code /* NOLINT(*-enum-size) */ { + UNSPECIFIED = 0, + KEY_COMPROMISE = 1, + CA_COMPROMISE = 2, + AFFILIATION_CHANGED = 3, + SUPERSEDED = 4, + CESSATION_OF_OPERATION = 5, + CERTIFICATE_HOLD = 6, + REMOVE_FROM_CRL = 8, + PRIVILIGE_WITHDRAWN = 9, + AA_COMPROMISE = 10 +}; + BOTAN_FFI_EXPORT(3, 10) int botan_x509_crl_update(botan_x509_crl_t* crl_obj, botan_x509_crl_t last_crl, @@ -2464,8 +2488,7 @@ int botan_x509_crl_update(botan_x509_crl_t* crl_obj, BOTAN_FFI_EXPORT(3, 10) int botan_x509_crl_get_count(botan_x509_crl_t crl, size_t* count); BOTAN_FFI_EXPORT(3, 10) -int botan_x509_crl_get_entry( - botan_x509_crl_t crl, size_t i, uint8_t serial[], size_t* serial_len, uint64_t* expire_time, uint8_t* reason); +int botan_x509_crl_get_entry(botan_x509_crl_t crl, size_t i, botan_mp_t serial, uint64_t* expire_time, uint8_t* reason); BOTAN_FFI_EXPORT(3, 10) int botan_x509_crl_verify_signature(botan_x509_crl_t crl, botan_pubkey_t key, int* result); diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index 86b58e4b6f5..6f0f301c58b 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -124,6 +124,22 @@ int botan_x509_cert_get_public_key(botan_x509_cert_t cert, botan_pubkey_t* key) #endif } +int botan_x509_cert_get_issuer_dn_count(botan_x509_cert_t cert, const char* key, size_t* len) { + if(key == nullptr || len == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { + auto issuer_info = c.issuer_info(key); + *len = issuer_info.size(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_get_issuer_dn( botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len) { #if defined(BOTAN_HAS_X509_CERTIFICATES) @@ -142,6 +158,22 @@ int botan_x509_cert_get_issuer_dn( #endif } +int botan_x509_cert_get_subject_dn_count(botan_x509_cert_t cert, const char* key, size_t* len) { + if(key == nullptr || len == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int { + auto issuer_info = c.subject_info(key); + *len = issuer_info.size(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(cert); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_get_subject_dn( botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len) { #if defined(BOTAN_HAS_X509_CERTIFICATES) @@ -160,6 +192,26 @@ int botan_x509_cert_get_subject_dn( #endif } +int botan_x509_cert_get_subject_name(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, + [=](const auto& c) { return invoke_view_callback(view, ctx, c.subject_dn().to_string()); }); +#else + BOTAN_UNUSED(cert, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_x509_cert_get_issuer_name(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) { +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return BOTAN_FFI_VISIT(cert, + [=](const auto& c) { return invoke_view_callback(view, ctx, c.subject_dn().to_string()); }); +#else + BOTAN_UNUSED(cert, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return copy_view_str(reinterpret_cast(out), out_len, botan_x509_cert_view_as_string, cert); @@ -586,7 +638,7 @@ int botan_x509_crl_get_count(botan_x509_crl_t crl, size_t* count) { } int botan_x509_crl_get_entry( - botan_x509_crl_t crl, size_t i, uint8_t serial[], size_t* serial_len, uint64_t* expire_time, uint8_t* reason) { + botan_x509_crl_t crl, size_t i, botan_mp_t serial, uint64_t* expire_time, uint8_t* reason) { if(expire_time == nullptr || reason == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -598,9 +650,10 @@ int botan_x509_crl_get_entry( } *reason = static_cast(entries[i].reason_code()); *expire_time = entries[i].expire_time().time_since_epoch(); - return write_vec_output(serial, serial_len, entries[i].serial_number()); + safe_get(serial)._assign_from_bytes(entries[i].serial_number()); + return BOTAN_FFI_SUCCESS; #else - BOTAN_UNUSED(crl, i, serial, serial_len); + BOTAN_UNUSED(crl, i, serial); return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; #endif } @@ -884,15 +937,15 @@ int botan_x509_cert_params_builder_add_ext_as_blocks(botan_x509_cert_params_buil #endif } -int botan_x509_cert_create_self_signed(botan_x509_cert_t* cert_obj, - botan_privkey_t key, - botan_x509_cert_params_builder_t builder, - botan_rng_t rng, - uint64_t not_before, - uint64_t not_after, - const botan_mp_t* serial_number, - const char* hash_fn, - const char* padding) { +int botan_x509_cert_params_builder_into_self_signed(botan_x509_cert_t* cert_obj, + botan_privkey_t key, + botan_x509_cert_params_builder_t builder, + botan_rng_t rng, + uint64_t not_before, + uint64_t not_after, + const botan_mp_t* serial_number, + const char* hash_fn, + const char* padding) { if(cert_obj == nullptr) { return BOTAN_FFI_ERROR_NULL_POINTER; } @@ -923,6 +976,32 @@ int botan_x509_cert_create_self_signed(botan_x509_cert_t* cert_obj, #endif } +int botan_x509_cert_params_builder_into_pkcs10_req(botan_x509_pkcs10_req_t* req_obj, + botan_privkey_t key, + botan_x509_cert_params_builder_t builder, + botan_rng_t rng, + const char* hash_fn, + const char* padding, + const char* challenge_password) { + if(req_obj == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_X509_CERTIFICATES) + return ffi_guard_thunk(__func__, [=]() -> int { + auto req = std::make_unique( + safe_get(builder).into_pkcs10_request(safe_get(key), + safe_get(rng), + optional_from_ptr(hash_fn), + optional_from_ptr(padding), + optional_from_ptr(challenge_password))); + return ffi_new_object(req_obj, std::move(req)); + }); +#else + BOTAN_UNUSED(key, builder, rng, padding, hash_fn, challenge_password); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_x509_pkcs10_req_destroy(botan_x509_pkcs10_req_t req) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_CHECKED_DELETE(req); @@ -963,32 +1042,6 @@ int botan_x509_pkcs10_req_load(botan_x509_pkcs10_req_t* req_obj, const uint8_t r #endif } -int botan_x509_pkcs10_req_create(botan_x509_pkcs10_req_t* req_obj, - botan_privkey_t key, - botan_x509_cert_params_builder_t builder, - botan_rng_t rng, - const char* hash_fn, - const char* padding, - const char* challenge_password) { - if(req_obj == nullptr) { - return BOTAN_FFI_ERROR_NULL_POINTER; - } -#if defined(BOTAN_HAS_X509_CERTIFICATES) - return ffi_guard_thunk(__func__, [=]() -> int { - auto req = std::make_unique( - safe_get(builder).into_pkcs10_request(safe_get(key), - safe_get(rng), - optional_from_ptr(hash_fn), - optional_from_ptr(padding), - optional_from_ptr(challenge_password))); - return ffi_new_object(req_obj, std::move(req)); - }); -#else - BOTAN_UNUSED(key, builder, rng, padding, hash_fn, challenge_password); - return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; -#endif -} - int botan_x509_pkcs10_req_view_pem(botan_x509_pkcs10_req_t req, botan_view_ctx ctx, botan_view_str_fn view) { #if defined(BOTAN_HAS_X509_CERTIFICATES) return BOTAN_FFI_VISIT(req, [=](const auto& r) -> int { return invoke_view_callback(view, ctx, r.PEM_encode()); }); diff --git a/src/python/botan3.py b/src/python/botan3.py index 28794b3cc2a..b499d0d516b 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -498,10 +498,14 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_cert_get_public_key_bits, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_view_public_key_bits, [c_void_p, c_void_p, VIEW_BIN_CALLBACK]) ffi_api(dll.botan_x509_cert_get_public_key, [c_void_p, c_void_p]) + ffi_api(dll.botan_x509_cert_get_issuer_dn_count, [c_void_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_get_issuer_dn, [c_void_p, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_cert_get_subject_dn_count, [c_void_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_get_subject_dn, [c_void_p, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)]) + ffi_api(dll.botan_x509_cert_get_subject_name, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) + ffi_api(dll.botan_x509_cert_get_issuer_name, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) ffi_api(dll.botan_x509_cert_to_string, [c_void_p, c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_view_as_string, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) ffi_api(dll.botan_x509_cert_view_pem, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) @@ -553,8 +557,10 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_cert_params_builder_set_as_ca_certificate, [c_void_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_cert_params_builder_add_ext_ip_addr_blocks, [c_void_p, c_void_p, c_int]) ffi_api(dll.botan_x509_cert_params_builder_add_ext_as_blocks, [c_void_p, c_void_p, c_int]) - ffi_api(dll.botan_x509_cert_create_self_signed, + ffi_api(dll.botan_x509_cert_params_builder_into_self_signed, [c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint64, c_void_p, c_char_p, c_char_p]) + ffi_api(dll.botan_x509_cert_params_builder_into_pkcs10_req, + [c_void_p, c_void_p, c_void_p, c_void_p, c_char_p, c_char_p, c_char_p]) ffi_api(dll.botan_x509_pkcs10_req_destroy, [c_void_p]) ffi_api(dll.botan_x509_pkcs10_req_load_file, [c_void_p, c_char_p]) ffi_api(dll.botan_x509_pkcs10_req_load, [c_void_p, c_char_p, c_size_t]) @@ -562,8 +568,6 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_x509_pkcs10_req_get_allowed_usage, [c_void_p, POINTER(c_uint32)]) ffi_api(dll.botan_x509_pkcs10_req_is_ca, [c_void_p, POINTER(c_int), POINTER(c_size_t)]) ffi_api(dll.botan_x509_pkcs10_req_verify_signature, [c_void_p, c_void_p, POINTER(c_int)]) - ffi_api(dll.botan_x509_pkcs10_req_create, - [c_void_p, c_void_p, c_void_p, c_void_p, c_char_p, c_char_p, c_char_p]) ffi_api(dll.botan_x509_pkcs10_req_view_pem, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) ffi_api(dll.botan_x509_pkcs10_req_view_der, [c_void_p, c_void_p, VIEW_BIN_CALLBACK]) ffi_api(dll.botan_x509_pkcs10_req_sign, @@ -581,7 +585,7 @@ def ffi_api(fn, args, allowed_errors=None): [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint32, c_void_p, c_size_t, c_uint8, c_char_p, c_char_p]) ffi_api(dll.botan_x509_crl_get_count, [c_void_p, POINTER(c_size_t)]) ffi_api(dll.botan_x509_crl_get_entry, - [c_void_p, c_size_t, c_char_p, POINTER(c_size_t), POINTER(c_uint64), POINTER(c_uint8)]) + [c_void_p, c_size_t, c_void_p, POINTER(c_uint64), POINTER(c_uint8)]) ffi_api(dll.botan_x509_crl_verify_signature, [c_void_p, c_void_p, POINTER(c_int)]) ffi_api(dll.botan_x509_crl_view_pem, [c_void_p, c_void_p, VIEW_STR_CALLBACK]) ffi_api(dll.botan_x509_crl_view_der, [c_void_p, c_void_p, VIEW_BIN_CALLBACK]) @@ -2001,7 +2005,7 @@ def add_xmpp(self, xmpp): _DLL.botan_x509_cert_params_builder_add_xmpp(self.__obj, _ctype_str(xmpp)) def add_ipv4(self, ipv4): - _DLL.botan_x509_cert_params_builder_add_ip(self.__obj, ipv4) + _DLL.botan_x509_cert_params_builder_add_ipv4(self.__obj, ipv4) def add_allowed_usage(self, usage_list): usage = X509KeyConstraints.to_bits(usage_list) @@ -2019,10 +2023,10 @@ def add_ext_ip_addr_blocks(self, ip_addr_blocks, is_critical): def add_ext_as_blocks(self, as_blocks, is_critical): _DLL.botan_x509_cert_params_builder_add_ext_as_blocks(self.__obj, as_blocks.handle_(), 1 if is_critical else 0) - def create_self_signed(self, key, rng, not_before, not_after, serial_number=None, hash_fn=None, padding=None): + def into_self_signed(self, key, rng, not_before, not_after, serial_number=None, hash_fn=None, padding=None): cert = X509Cert() serial_no = byref(serial_number.handle_()) if serial_number is not None else None - _DLL.botan_x509_cert_create_self_signed( + _DLL.botan_x509_cert_params_builder_into_self_signed( byref(cert.handle_()), key.handle_(), self.__obj, @@ -2035,9 +2039,9 @@ def create_self_signed(self, key, rng, not_before, not_after, serial_number=None ) return cert - def create_req(self, key, rng, hash_fn=None, padding=None, challenge_password=None): + def into_request(self, key, rng, hash_fn=None, padding=None, challenge_password=None): req = PKCS10Req() - _DLL.botan_x509_pkcs10_req_create( + _DLL.botan_x509_cert_params_builder_into_pkcs10_req( byref(req.handle_()), key.handle_(), self.__obj, @@ -2048,6 +2052,7 @@ def create_req(self, key, rng, hash_fn=None, padding=None, challenge_password=No ) return req + class X509ExtIPAddrBlocks: def __init__(self, cert=None): self.__obj = c_void_p(0) @@ -2340,10 +2345,20 @@ def subject_dn(self, key: str, index: int) -> str: return _call_fn_returning_str( 0, lambda b, bl: _DLL.botan_x509_cert_get_subject_dn(self.__obj, _ctype_str(key), index, b, bl)) + def subject_name(self): + return _call_fn_viewing_str( + lambda vc, vfn: _DLL.botan_x509_cert_get_subject_name(self.__obj, vc, vfn) + ) + def issuer_dn(self, key: str, index: int) -> str: return _call_fn_returning_str( 0, lambda b, bl: _DLL.botan_x509_cert_get_issuer_dn(self.__obj, _ctype_str(key), index, b, bl)) + def issuer_name(self): + return _call_fn_viewing_str( + lambda vc, vfn: _DLL.botan_x509_cert_get_issuer_name(self.__obj, vc, vfn) + ) + def hostname_match(self, hostname: str) -> bool: rc = _DLL.botan_x509_cert_hostname_match(self.__obj, _ctype_str(hostname)) return rc == 0 @@ -2532,9 +2547,8 @@ def revoked(self): for i in range(count.value): expire_time = c_uint64(0) reason = c_uint8(0) - serial = _call_fn_returning_vec( - 32, lambda b, bl, i=i, expire_time=expire_time, reason=reason: _DLL.botan_x509_crl_get_entry(self.__obj, c_size_t(i), b, bl, byref(expire_time), byref(reason)) - ) + serial = MPI() + _DLL.botan_x509_crl_get_entry(self.__obj, c_size_t(i), serial.handle_(), byref(expire_time), byref(reason)) revoked.append(X509CRLEntry(serial, expire_time.value, X509CRLReason.from_bits(reason.value))) return revoked diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index c8be051ba38..941b7c92935 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -836,7 +836,8 @@ def test_cert_creation(self): ca_builder.add_organization("Botan Project") ca_builder.add_organizational_unit("Testing") ca_builder.set_as_ca_certificate(1) - ca_cert = ca_builder.create_self_signed(ca_key, rng, not_before, not_after) + ca_cert = ca_builder.into_self_signed(ca_key, rng, not_before, not_after) + self.assertEqual(ca_cert.subject_name(), 'CN="Test CA",C="US",O="Botan Project",OU="Testing"') constraints = ca_cert.allowed_usages() self.assertEqual(len(constraints), 3) for item in [ @@ -853,10 +854,11 @@ def test_cert_creation(self): req_builder.add_uri("https://botan.randombit.net") for item in ["imaginary.botan.randombit.net", "botan.randombit.net", "randombit.net"]: req_builder.add_dns(item) - req = req_builder.create_req(cert_key, rng) + req = req_builder.into_request(cert_key, rng) self.assertTrue(req.verify(cert_key.get_public_key())) cert = req.sign(ca_cert, ca_key, rng, not_before, not_after) + self.assertEqual(ca_cert.issuer_name(), 'CN="Test CA",C="US",O="Botan Project",OU="Testing"') self.assertFalse(cert.is_self_signed()) self.assertEqual(cert.allowed_usages(), [botan.X509KeyConstraints.DIGITAL_SIGNATURE]) @@ -878,16 +880,16 @@ def test_cert_revocation(self): ca_key = botan.PrivateKey.create("ECDSA", group, rng) ca_builder = botan.X509CertificateBuilder() ca_builder.set_as_ca_certificate(1) - ca_cert = ca_builder.create_self_signed(ca_key, rng, not_before, not_after, botan.MPI("0")) + ca_cert = ca_builder.into_self_signed(ca_key, rng, not_before, not_after, botan.MPI("0")) cert_key_1 = botan.PrivateKey.create("ECDSA", group, rng) req_builder_1 = botan.X509CertificateBuilder() - req_1 = req_builder_1.create_req(cert_key_1, rng) + req_1 = req_builder_1.into_request(cert_key_1, rng) cert_1 = req_1.sign(ca_cert, ca_key, rng, not_before, not_after, botan.MPI("1")) cert_key_2 = botan.PrivateKey.create("ECDSA", group, rng) req_builder_2 = botan.X509CertificateBuilder() - req_2 = req_builder_2.create_req(cert_key_2, rng) + req_2 = req_builder_2.into_request(cert_key_2, rng) cert_2 = req_2.sign(ca_cert, ca_key, rng, not_before, not_after, botan.MPI("2")) crl = botan.X509CRL.create(rng, ca_cert, ca_key, now, now + 600) @@ -902,7 +904,7 @@ def test_cert_revocation(self): self.assertEqual(len(crl.revoked()), 1) revoked_entry: botan.X509CRLEntry = crl.revoked()[0] self.assertEqual(revoked_entry.reason, botan.X509CRLReason.KEY_COMPROMISE) - self.assertEqual(botan.MPI.from_bytes(revoked_entry.serial_number), botan.MPI("1")) + self.assertEqual(revoked_entry.serial_number, botan.MPI("1")) self.assertTrue(now - 5 <= revoked_entry.expire_time <= now + 5) pem = crl.to_pem() @@ -943,7 +945,7 @@ def test_x509_rpki(self): with self.assertRaisesRegex(botan.BotanException, r".*Invalid object state.*"): ca_builder.add_ext_as_blocks(ca_as_blocks, True) - ca_cert = ca_builder.create_self_signed(ca_key, rng, not_before, not_after) + ca_cert = ca_builder.into_self_signed(ca_key, rng, not_before, not_after) ca_ip_addr_blocks = ca_cert.ext_ip_addr_blocks() self.assertEqual(ca_ip_addr_blocks.addresses(), ( @@ -986,7 +988,7 @@ def test_x509_rpki(self): req_builder.add_ext_ip_addr_blocks(req_ip_addr_blocks, True) req_builder.add_ext_as_blocks(req_as_blocks, True) - req = req_builder.create_req(cert_key, rng) + req = req_builder.into_request(cert_key, rng) cert = req.sign(ca_cert, ca_key, rng, not_before, not_after) diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index 9778ab2bc7e..db848ca09f2 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -154,68 +154,6 @@ class FFI_Test : public Test { virtual void ffi_test(Test::Result& result, botan_rng_t rng) = 0; }; -/** - * Helper class for testing "view"-style API functions that take a callback - * that gets passed a variable-length buffer of bytes. - * - * Example: - * botan_privkey_t priv; - * ViewBytesSink sink; - * botan_privkey_view_raw(priv, sink.delegate(), sink.callback()); - * std::cout << hex_encode(sink.get()) << std::endl; - */ -class ViewBytesSink final { - public: - void* delegate() { return this; } - - botan_view_bin_fn callback() { return &write_fn; } - - const std::vector& get() { return m_buf; } - - private: - static int write_fn(void* ctx, const uint8_t buf[], size_t len) { - if(ctx == nullptr || buf == nullptr) { - return BOTAN_FFI_ERROR_NULL_POINTER; - } - - auto* sink = static_cast(ctx); - sink->m_buf.assign(buf, buf + len); - - return 0; - } - - private: - std::vector m_buf; -}; - -/** - * See ViewBytesSink for how to use this. Works for `botan_view_str_fn` instead. -*/ -class ViewStringSink final { - public: - void* delegate() { return this; } - - botan_view_str_fn callback() { return &write_fn; } - - std::string_view get() { return m_str; } - - private: - static int write_fn(void* ctx, const char* str, size_t len) { - if(ctx == nullptr || str == nullptr) { - return BOTAN_FFI_ERROR_NULL_POINTER; - } - - auto* sink = static_cast(ctx); - // discard the null terminator - sink->m_str = std::string(str, len - 1); - - return 0; - } - - private: - std::string m_str; -}; - void ffi_test_pubkey_export(Test::Result& result, botan_pubkey_t pub, botan_privkey_t priv, botan_rng_t rng) { // export public key size_t pubkey_len = 0; @@ -893,7 +831,7 @@ class FFI_Cert_Creation_Test final : public FFI_Test { } botan_x509_cert_t ca_cert; - TEST_FFI_OK(botan_x509_cert_create_self_signed, + TEST_FFI_OK(botan_x509_cert_params_builder_into_self_signed, (&ca_cert, ca_key, ca_builder, rng, not_before, not_after, nullptr, hash_fn.c_str(), "")); botan_x509_cert_params_builder_t req_builder; @@ -901,7 +839,7 @@ class FFI_Cert_Creation_Test final : public FFI_Test { TEST_FFI_OK(botan_x509_cert_params_builder_add_allowed_usage, (req_builder, 1 << 15)); botan_x509_pkcs10_req_t req; - TEST_FFI_OK(botan_x509_pkcs10_req_create, + TEST_FFI_OK(botan_x509_cert_params_builder_into_pkcs10_req, (&req, cert_key, req_builder, rng, hash_fn.c_str(), nullptr, nullptr)); int res; @@ -981,10 +919,9 @@ class FFI_Cert_Creation_Test final : public FFI_Test { uint64_t expire_time; uint8_t reason; - TEST_FFI_OK(botan_x509_crl_get_entry, (new_crl, 0, serial_out.data(), &out_len, &expire_time, &reason)); botan_mp_t serial_from_crl; TEST_FFI_OK(botan_mp_init, (&serial_from_crl)); - TEST_FFI_OK(botan_mp_from_bin, (serial_from_crl, serial_out.data(), out_len)); + TEST_FFI_OK(botan_x509_crl_get_entry, (new_crl, 0, serial_from_crl, &expire_time, &reason)); TEST_FFI_RC(1, botan_mp_equal, (serial, serial_from_crl)); result.confirm("expire time is correct", now - 5 <= expire_time && expire_time <= now + 5); result.confirm("reason is correct", reason == 1); @@ -1179,7 +1116,7 @@ class FFI_X509_RPKI_Test final : public FFI_Test { botan_x509_cert_t ca_cert; TEST_FFI_OK( - botan_x509_cert_create_self_signed, + botan_x509_cert_params_builder_into_self_signed, (&ca_cert, ca_key, ca_builder, rng, not_before, not_after, nullptr, hash_fn.c_str(), nullptr)); botan_x509_cert_params_builder_t req_builder; @@ -1200,7 +1137,7 @@ class FFI_X509_RPKI_Test final : public FFI_Test { TEST_FFI_OK(botan_x509_ext_as_blocks_destroy, (req_as_blocks)); botan_x509_pkcs10_req_t req; - TEST_FFI_OK(botan_x509_pkcs10_req_create, + TEST_FFI_OK(botan_x509_cert_params_builder_into_pkcs10_req, (&req, cert_key, req_builder, rng, hash_fn.c_str(), nullptr, nullptr)); botan_x509_cert_t cert; From b9fef010f047817849f83824a6da521acf189d91 Mon Sep 17 00:00:00 2001 From: arckoor <33837362+arckoor@users.noreply.github.com> Date: Thu, 4 Sep 2025 17:47:27 +0200 Subject: [PATCH 14/14] python types --- src/python/botan3.py | 177 +++++++++++++++++++++++++++---------------- 1 file changed, 113 insertions(+), 64 deletions(-) diff --git a/src/python/botan3.py b/src/python/botan3.py index b499d0d516b..d23ab7f9c31 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -20,7 +20,7 @@ from __future__ import annotations from ctypes import CDLL, CFUNCTYPE, POINTER, byref, create_string_buffer, \ c_void_p, c_size_t, c_uint8, c_uint32, c_uint64, c_int, c_uint, c_char, c_char_p, addressof, Array -from typing import Callable, Any, Union, List +from typing import Callable, Any, Union, List, Tuple from sys import platform from time import strptime, mktime, time as system_time @@ -1896,14 +1896,14 @@ class X509KeyConstraints(IntEnum): DECIPHER_ONLY = 1 << 7 @classmethod - def to_bits(cls, constraints): + def to_bits(cls, constraints: List[X509KeyConstraints]) -> int: con = 0 for constraint in constraints: con |= constraint.value return con @classmethod - def from_bits(cls, bits): + def from_bits(cls, bits: int) -> List[X509KeyConstraints]: if bits == 0: return X509KeyConstraints.NO_CONSTRAINTS all_constraints = [ @@ -1926,7 +1926,7 @@ def from_bits(cls, bits): # TODO deprecate this in a future version @classmethod - def from_string(cls, constraint): + def from_string(cls, constraint: str) -> X509KeyConstraints: try: if isinstance(constraint, X509KeyConstraints): return constraint @@ -1935,7 +1935,7 @@ def from_string(cls, constraint): raise BotanException("Not a valid key constraint") from exc @classmethod - def to_string(cls, constraint): + def to_string(cls, constraint: X509KeyConstraints) -> str: return constraint.name @@ -1952,11 +1952,11 @@ class X509CRLReason(IntEnum): AA_COMPROMISE = 10 @classmethod - def to_bits(cls, reason): + def to_bits(cls, reason: X509CRLReason) -> int: return reason.value @classmethod - def from_bits(cls, reason): + def from_bits(cls, reason: int) -> X509CRLReason: return cls(reason) @@ -1971,59 +1971,68 @@ def __del__(self): def handle_(self): return self.__obj - def add_common_name(self, name): + def add_common_name(self, name: str): _DLL.botan_x509_cert_params_builder_add_common_name(self.__obj, _ctype_str(name)) - def add_country(self, country): + def add_country(self, country: str): _DLL.botan_x509_cert_params_builder_add_country(self.__obj, _ctype_str(country)) - def add_state(self, state): + def add_state(self, state: str): _DLL.botan_x509_cert_params_builder_add_state(self.__obj, _ctype_str(state)) - def add_locality(self, locality): + def add_locality(self, locality: str): _DLL.botan_x509_cert_params_builder_add_locality(self.__obj, _ctype_str(locality)) - def add_serial_number(self, serial_number): + def add_serial_number(self, serial_number: str): _DLL.botan_x509_cert_params_builder_add_serial_number(self.__obj, _ctype_str(serial_number)) - def add_organization(self, organization): + def add_organization(self, organization: str): _DLL.botan_x509_cert_params_builder_add_organization(self.__obj, _ctype_str(organization)) - def add_organizational_unit(self, org_unit): + def add_organizational_unit(self, org_unit: str): _DLL.botan_x509_cert_params_builder_add_organizational_unit(self.__obj, _ctype_str(org_unit)) - def add_email(self, email): + def add_email(self, email: str): _DLL.botan_x509_cert_params_builder_add_email(self.__obj, _ctype_str(email)) - def add_dns(self, dns): + def add_dns(self, dns: str): _DLL.botan_x509_cert_params_builder_add_dns(self.__obj, _ctype_str(dns)) - def add_uri(self, uri): + def add_uri(self, uri: str): _DLL.botan_x509_cert_params_builder_add_uri(self.__obj, _ctype_str(uri)) - def add_xmpp(self, xmpp): + def add_xmpp(self, xmpp: str): _DLL.botan_x509_cert_params_builder_add_xmpp(self.__obj, _ctype_str(xmpp)) - def add_ipv4(self, ipv4): + def add_ipv4(self, ipv4: int): _DLL.botan_x509_cert_params_builder_add_ipv4(self.__obj, ipv4) - def add_allowed_usage(self, usage_list): + def add_allowed_usage(self, usage_list: List[X509KeyConstraints]): usage = X509KeyConstraints.to_bits(usage_list) _DLL.botan_x509_cert_params_builder_add_allowed_usage(self.__obj, c_uint32(usage)) - def add_allowed_extended_usage(self, oid): + def add_allowed_extended_usage(self, oid: OID): _DLL.botan_x509_cert_params_builder_add_allowed_extended_usage(self.__obj, oid.handle_()) - def set_as_ca_certificate(self, limit=None): + def set_as_ca_certificate(self, limit: int | None = None): _DLL.botan_x509_cert_params_builder_set_as_ca_certificate(self.__obj, c_size_t(limit) if limit is not None else None) - def add_ext_ip_addr_blocks(self, ip_addr_blocks, is_critical): + def add_ext_ip_addr_blocks(self, ip_addr_blocks: X509ExtIPAddrBlocks, is_critical: bool): _DLL.botan_x509_cert_params_builder_add_ext_ip_addr_blocks(self.__obj, ip_addr_blocks.handle_(), 1 if is_critical else 0) - def add_ext_as_blocks(self, as_blocks, is_critical): + def add_ext_as_blocks(self, as_blocks: X509ExtASBlocks, is_critical: bool): _DLL.botan_x509_cert_params_builder_add_ext_as_blocks(self.__obj, as_blocks.handle_(), 1 if is_critical else 0) - def into_self_signed(self, key, rng, not_before, not_after, serial_number=None, hash_fn=None, padding=None): + def into_self_signed( + self, + key: PrivateKey, + rng: RandomNumberGenerator, + not_before: int, + not_after: int, + serial_number: MPI | None = None, + hash_fn: str | None = None, + padding: str | None = None + ) -> X509Cert: cert = X509Cert() serial_no = byref(serial_number.handle_()) if serial_number is not None else None _DLL.botan_x509_cert_params_builder_into_self_signed( @@ -2039,7 +2048,14 @@ def into_self_signed(self, key, rng, not_before, not_after, serial_number=None, ) return cert - def into_request(self, key, rng, hash_fn=None, padding=None, challenge_password=None): + def into_request( + self, + key: PrivateKey, + rng: RandomNumberGenerator, + hash_fn: str | None = None, + padding: str | None = None, + challenge_password: str | None = None + ) -> PKCS10Req: req = PKCS10Req() _DLL.botan_x509_cert_params_builder_into_pkcs10_req( byref(req.handle_()), @@ -2054,7 +2070,7 @@ def into_request(self, key, rng, hash_fn=None, padding=None, challenge_password= class X509ExtIPAddrBlocks: - def __init__(self, cert=None): + def __init__(self, cert: X509Cert | None = None): self.__obj = c_void_p(0) if cert: _DLL.botan_x509_ext_ip_addr_blocks_create_from_cert(byref(self.__obj), cert.handle_()) @@ -2067,10 +2083,10 @@ def __del__(self): def handle_(self): return self.__obj - def add_addr(self, ip, safi=None): + def add_addr(self, ip: List[int], safi: int | None = None): self.add_range(ip, ip, safi) - def add_range(self, min_, max_, safi=None): + def add_range(self, min_: List[int], max_: List[int], safi: int | None = None): min_len = len(min_) if min_len not in (4, 16) or len(max_) != min_len: raise BotanException("Address must be 4 or 16 bytes long") @@ -2079,17 +2095,20 @@ def add_range(self, min_, max_, safi=None): safi = byref(c_uint8(safi)) if safi is not None else None _DLL.botan_x509_ext_ip_addr_blocks_add_ip_addr(self.__obj, bytes(min_), bytes(max_), c_int(ipv6), safi) - def restrict(self, ipv6, safi=None): + def restrict(self, ipv6: bool, safi: int | None = None): ipv6 = 1 if ipv6 else 0 safi = byref(c_uint8(safi)) if safi is not None else None _DLL.botan_x509_ext_ip_addr_blocks_restrict(self.__obj, c_int(ipv6), safi) - def inherit(self, ipv6, safi=None): + def inherit(self, ipv6: bool, safi: int | None = None): ipv6 = 1 if ipv6 else 0 safi = byref(c_uint8(safi)) if safi is not None else None _DLL.botan_x509_ext_ip_addr_blocks_inherit(self.__obj, c_int(ipv6), safi) - def addresses(self): + def addresses(self) -> Tuple[ + List[Tuple[int | None, List[Tuple[List[int], List[int]]]]], + List[Tuple[int | None, List[Tuple[List[int], List[int]]]]] + ]: v4 = [] v6 = [] @@ -2136,7 +2155,7 @@ def addresses(self): class X509ExtASBlocks: - def __init__(self, cert=None): + def __init__(self, cert: X509Cert | None = None): self.__obj = c_void_p(0) if cert: _DLL.botan_x509_ext_as_blocks_create_from_cert(byref(self.__obj), cert.handle_()) @@ -2149,10 +2168,10 @@ def __del__(self): def handle_(self): return self.__obj - def add_asnum(self, asnum): + def add_asnum(self, asnum: int): self.add_asnum_range(asnum, asnum) - def add_asnum_range(self, min_, max_): + def add_asnum_range(self, min_: int, max_: int): _DLL.botan_x509_ext_as_blocks_add_asnum(self.__obj, c_uint32(min_), c_uint32(max_)) def restrict_asnum(self): @@ -2161,10 +2180,10 @@ def restrict_asnum(self): def inherit_asnum(self): _DLL.botan_x509_ext_as_blocks_inherit_asnum(self.__obj) - def add_rdi(self, rdi): + def add_rdi(self, rdi: int): self.add_rdi_range(rdi, rdi) - def add_rdi_range(self, min_, max_): + def add_rdi_range(self, min_: int, max_: int): _DLL.botan_x509_ext_as_blocks_add_rdi(self.__obj, c_uint32(min_), c_uint32(max_)) def restrict_rdi(self): @@ -2173,7 +2192,7 @@ def restrict_rdi(self): def inherit_rdi(self): _DLL.botan_x509_ext_as_blocks_inherit_rdi(self.__obj) - def asnum(self): + def asnum(self) -> List[Tuple[int, int]]: present = c_int(0) count = c_size_t(0) _DLL.botan_x509_ext_as_blocks_get_asnum(self.__obj, byref(present), byref(count)) @@ -2190,7 +2209,7 @@ def asnum(self): asnums.append((min_.value, max_.value)) return asnums - def rdi(self): + def rdi(self) -> List[Tuple[int, int]]: present = c_int(0) count = c_size_t(0) _DLL.botan_x509_ext_as_blocks_get_rdi(self.__obj, byref(present), byref(count)) @@ -2209,7 +2228,7 @@ def rdi(self): class PKCS10Req: - def __init__(self, filename=None, buf=None): + def __init__(self, filename: str | None = None, buf: bytes | None = None): if not filename and not buf: self.__obj = c_void_p(0) else: @@ -2221,17 +2240,17 @@ def __del__(self): def handle_(self): return self.__obj - def public_key(self): + def public_key(self) -> PublicKey: pub = c_void_p(0) _DLL.botan_x509_pkcs10_req_get_public_key(self.__obj, byref(pub)) return PublicKey(pub) - def key_constraints(self): + def key_constraints(self) -> List[X509KeyConstraints]: usage = c_uint32(0) _DLL.botan_x509_pkcs10_req_get_allowed_usage(self.__obj, byref(usage)) return X509KeyConstraints.from_bits(usage.value) - def is_ca(self): + def is_ca(self) -> Tuple[bool, int]: is_ca = c_int(0) limit = c_size_t(0) _DLL.botan_x509_pkcs10_req_is_ca(self.__obj, byref(is_ca), byref(limit)) @@ -2240,12 +2259,22 @@ def is_ca(self): else: return (True, limit.value) - def verify(self, key): + def verify(self, key: PublicKey) -> bool: ret = c_int(0) _DLL.botan_x509_pkcs10_req_verify_signature(self.__obj, key.handle_(), byref(ret)) return ret.value == 1 - def sign(self, issuing_cert, issuing_key, rng, not_before, not_after, serial_number=None, hash_fn=None, padding=None): + def sign( + self, + issuing_cert: X509Cert, + issuing_key: PrivateKey, + rng: RandomNumberGenerator, + not_before: int, + not_after: int, + serial_number: MPI | None = None, + hash_fn: str | None = None, + padding: str | None = None + ) -> X509Cert: cert = X509Cert() serial_no = byref(serial_number.handle_()) if serial_number is not None else None _DLL.botan_x509_pkcs10_req_sign( @@ -2262,10 +2291,10 @@ def sign(self, issuing_cert, issuing_key, rng, not_before, not_after, serial_num ) return cert - def to_pem(self): + def to_pem(self) -> str: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_x509_pkcs10_req_view_pem(self.__obj, vc, vfn)) - def to_der(self): + def to_der(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_x509_pkcs10_req_view_der(self.__obj, vc, vfn)) @@ -2311,7 +2340,7 @@ def to_string(self) -> str: return _call_fn_viewing_str( lambda vc, vfn: _DLL.botan_x509_cert_view_as_string(self.__obj, vc, vfn)) - def to_pem(self): + def to_pem(self) -> str: return _call_fn_viewing_str( lambda vc, vfn: _DLL.botan_x509_cert_view_pem(self.__obj, vc, vfn)) @@ -2345,7 +2374,7 @@ def subject_dn(self, key: str, index: int) -> str: return _call_fn_returning_str( 0, lambda b, bl: _DLL.botan_x509_cert_get_subject_dn(self.__obj, _ctype_str(key), index, b, bl)) - def subject_name(self): + def subject_name(self) -> str: return _call_fn_viewing_str( lambda vc, vfn: _DLL.botan_x509_cert_get_subject_name(self.__obj, vc, vfn) ) @@ -2354,7 +2383,7 @@ def issuer_dn(self, key: str, index: int) -> str: return _call_fn_returning_str( 0, lambda b, bl: _DLL.botan_x509_cert_get_issuer_dn(self.__obj, _ctype_str(key), index, b, bl)) - def issuer_name(self): + def issuer_name(self) -> str: return _call_fn_viewing_str( lambda vc, vfn: _DLL.botan_x509_cert_get_issuer_name(self.__obj, vc, vfn) ) @@ -2378,12 +2407,12 @@ def allowed_usage(self, usage_list: List[str]) -> bool: rc = _DLL.botan_x509_cert_allowed_usage(self.__obj, c_uint(usage)) return rc == 0 - def allowed_usages(self): + def allowed_usages(self) -> List[X509KeyConstraints]: usage = c_uint32(0) _DLL.botan_x509_cert_get_allowed_usage(self.__obj, byref(usage)) return X509KeyConstraints.from_bits(usage.value) - def is_ca(self): + def is_ca(self) -> Tuple[bool, int]: is_ca = c_int(0) limit = c_size_t(0) _DLL.botan_x509_cert_is_ca(self.__obj, byref(is_ca), byref(limit)) @@ -2392,11 +2421,11 @@ def is_ca(self): else: return (True, limit.value) - def ocsp_responder(self): + def ocsp_responder(self) -> str: return _call_fn_viewing_str( lambda vc, vfn: _DLL.botan_x509_cert_get_ocsp_responder(self.__obj, vc, vfn)) - def is_self_signed(self): + def is_self_signed(self) -> bool: self_signed = c_int(0) _DLL.botan_x509_cert_is_self_signed(self.__obj, byref(self_signed)) if self_signed.value == 0: @@ -2404,10 +2433,10 @@ def is_self_signed(self): else: return True - def ext_ip_addr_blocks(self): + def ext_ip_addr_blocks(self) -> X509ExtIPAddrBlocks: return X509ExtIPAddrBlocks(self) - def ext_as_blocks(self): + def ext_as_blocks(self) -> X509ExtASBlocks: return X509ExtASBlocks(self) def handle_(self): @@ -2482,7 +2511,7 @@ def is_revoked(self, crl: X509CRL) -> bool: # X.509 Certificate revocation lists # class X509CRLEntry: - def __init__(self, serial_number, expire_time, reason): + def __init__(self, serial_number: MPI, expire_time: int, reason: X509CRLReason): self.serial_number = serial_number self.expire_time = expire_time self.reason = reason @@ -2502,7 +2531,16 @@ def handle_(self): return self.__obj @classmethod - def create(cls, rng, ca_cert, ca_key, issue_time, next_update, hash_fn=None, padding=None): + def create( + cls, + rng: RandomNumberGenerator, + ca_cert: X509Cert, + ca_key: PrivateKey, + issue_time: int, + next_update: int, + hash_fn: str | None = None, + padding: str | None = None + ) -> X509CRL: crl = X509CRL() _DLL.botan_x509_crl_create( byref(crl.handle_()), @@ -2516,7 +2554,18 @@ def create(cls, rng, ca_cert, ca_key, issue_time, next_update, hash_fn=None, pad ) return crl - def revoke(self, rng, ca_cert, ca_key, issue_time, next_update, revoked, reason, hash_fn=None, padding=None): + def revoke( + self, + rng: RandomNumberGenerator, + ca_cert: X509Cert, + ca_key: PrivateKey, + issue_time: int, + next_update: int, + revoked: List[X509Cert], + reason: X509CRLReason, + hash_fn: str | None = None, + padding: str | None = None + ) -> X509CRL: crl = X509CRL() c_revoked = len(revoked) * c_void_p arr_revoked = c_revoked() @@ -2540,7 +2589,7 @@ def revoke(self, rng, ca_cert, ca_key, issue_time, next_update, revoked, reason, ) return crl - def revoked(self): + def revoked(self) -> List[X509CRLEntry]: count = c_size_t(0) _DLL.botan_x509_crl_get_count(self.__obj, byref(count)) revoked = [] @@ -2552,15 +2601,15 @@ def revoked(self): revoked.append(X509CRLEntry(serial, expire_time.value, X509CRLReason.from_bits(reason.value))) return revoked - def verify(self, key): + def verify(self, key: PublicKey) -> bool: ret = c_int(0) _DLL.botan_x509_crl_verify_signature(self.__obj, key.handle_(), byref(ret)) return ret.value == 1 - def to_pem(self): + def to_pem(self) -> str: return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_x509_crl_view_pem(self.__obj, vc, vfn)) - def to_der(self): + def to_der(self) -> bytes: return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_x509_crl_view_der(self.__obj, vc, vfn)) @@ -2596,7 +2645,7 @@ def random_range(cls, rng_obj: RandomNumberGenerator, lower: MPI, upper: MPI): return bn @classmethod - def from_bytes(cls, buf): + def from_bytes(cls, buf: bytes) -> MPI: bn = MPI() out_len = c_size_t(len(buf)) _DLL.botan_mp_from_bin(bn.handle_(), buf, out_len)