diff options
Diffstat (limited to 'src/python')
-rw-r--r-- | src/python/__init__.py | 4 | ||||
-rw-r--r-- | src/python/core.cpp | 232 | ||||
-rw-r--r-- | src/python/filter.cpp | 177 | ||||
-rw-r--r-- | src/python/python_botan.h | 86 | ||||
-rw-r--r-- | src/python/rsa.cpp | 220 | ||||
-rw-r--r-- | src/python/x509.cpp | 88 |
6 files changed, 807 insertions, 0 deletions
diff --git a/src/python/__init__.py b/src/python/__init__.py new file mode 100644 index 000000000..2df9a456f --- /dev/null +++ b/src/python/__init__.py @@ -0,0 +1,4 @@ +from _botan import * + +# Initialize the library when the module is imported +init = LibraryInitializer() diff --git a/src/python/core.cpp b/src/python/core.cpp new file mode 100644 index 000000000..6dcceee74 --- /dev/null +++ b/src/python/core.cpp @@ -0,0 +1,232 @@ +/* +* Boost.Python module definition +* (C) 1999-2007 Jack Lloyd +*/ + +#include <botan/init.h> +#include <botan/pipe.h> +#include <botan/lookup.h> +#include <botan/cryptobox.h> +#include <botan/pbkdf2.h> +#include <botan/hmac.h> +using namespace Botan; + +#include "python_botan.h" + +class Py_Cipher + { + public: + Py_Cipher(std::string algo_name, std::string direction, + std::string key); + + std::string cipher_noiv(const std::string& text); + + std::string cipher(const std::string& text, + const std::string& iv); + + std::string name() const { return algo_name; } + private: + std::string algo_name; + Keyed_Filter* filter; + Pipe pipe; + }; + +std::string Py_Cipher::cipher(const std::string& input, + const std::string& iv_str) + { + if(iv_str.size()) + { + const byte* iv_bytes = reinterpret_cast<const byte*>(iv_str.data()); + u32bit iv_len = iv_str.size(); + filter->set_iv(InitializationVector(iv_bytes, iv_len)); + } + + pipe.process_msg(input); + return pipe.read_all_as_string(Pipe::LAST_MESSAGE); + } + +// For IV-less algorithms +std::string Py_Cipher::cipher_noiv(const std::string& input) + { + pipe.process_msg(input); + return pipe.read_all_as_string(Pipe::LAST_MESSAGE); + } + +Py_Cipher::Py_Cipher(std::string algo_name, + std::string direction, + std::string key_str) + { + const byte* key_bytes = reinterpret_cast<const byte*>(key_str.data()); + u32bit key_len = key_str.size(); + + Cipher_Dir dir; + + if(direction == "encrypt") + dir = ENCRYPTION; + else if(direction == "decrypt") + dir = DECRYPTION; + else + throw std::invalid_argument("Bad cipher direction " + direction); + + filter = get_cipher(algo_name, dir); + filter->set_key(SymmetricKey(key_bytes, key_len)); + pipe.append(filter); + } + +class Py_HashFunction + { + public: + Py_HashFunction(const std::string& algo_name) + { + hash = get_hash(algo_name); + } + + ~Py_HashFunction() { delete hash; } + + void update(const std::string& input) + { + hash->update(input); + } + + std::string final() + { + std::string out(output_length(), 0); + hash->final(reinterpret_cast<byte*>(&out[0])); + return out; + } + + std::string name() const + { + return hash->name(); + } + + u32bit output_length() const + { + return hash->output_length(); + } + + private: + HashFunction* hash; + }; + +class Py_MAC + { + public: + + Py_MAC(const std::string& name, const std::string& key_str) + { + mac = get_mac(name); + + mac->set_key(reinterpret_cast<const byte*>(key_str.data()), + key_str.size()); + } + + ~Py_MAC() { delete mac; } + + u32bit output_length() const { return mac->output_length(); } + + std::string name() const { return mac->name(); } + + void update(const std::string& in) { mac->update(in); } + + std::string final() + { + std::string out(output_length(), 0); + mac->final(reinterpret_cast<byte*>(&out[0])); + return out; + } + private: + MessageAuthenticationCode* mac; + }; + +std::string cryptobox_encrypt(const std::string& in, + const std::string& passphrase, + Python_RandomNumberGenerator& rng) + { + const byte* in_bytes = reinterpret_cast<const byte*>(in.data()); + + return CryptoBox::encrypt(in_bytes, in.size(), + passphrase, rng.get_underlying_rng()); + } + +std::string cryptobox_decrypt(const std::string& in, + const std::string& passphrase) + { + const byte* in_bytes = reinterpret_cast<const byte*>(in.data()); + + return CryptoBox::decrypt(in_bytes, in.size(), + passphrase); + } + +std::string python_pbkdf2(const std::string& passphrase, + const std::string& salt, + u32bit iterations, + u32bit output_size, + const std::string& hash_fn) + { + PKCS5_PBKDF2 pbkdf2(new HMAC(get_hash(hash_fn))); + + return make_string( + pbkdf2.derive_key(output_size, + passphrase, + reinterpret_cast<const byte*>(salt.data()), + salt.size(), + iterations).bits_of()); + } + +std::string python_kdf2(const std::string& param, + const std::string& masterkey, + u32bit outputlength) + { + std::unique_ptr<KDF> kdf(get_kdf("KDF2(SHA-1)")); + + return make_string( + kdf->derive_key(outputlength, + reinterpret_cast<const byte*>(masterkey.data()), + masterkey.length(), + param)); + } + +BOOST_PYTHON_MODULE(_botan) + { + python::class_<LibraryInitializer>("LibraryInitializer") + .def(python::init< python::optional<std::string> >()); + + python::class_<Python_RandomNumberGenerator>("RandomNumberGenerator") + .def(python::init<>()) + .def("__str__", &Python_RandomNumberGenerator::name) + .def("name", &Python_RandomNumberGenerator::name) + .def("reseed", &Python_RandomNumberGenerator::reseed) + .def("add_entropy", &Python_RandomNumberGenerator::add_entropy) + .def("gen_random_byte", &Python_RandomNumberGenerator::gen_random_byte) + .def("gen_random", &Python_RandomNumberGenerator::gen_random); + + python::class_<Py_Cipher, boost::noncopyable> + ("Cipher", python::init<std::string, std::string, std::string>()) + .def("name", &Py_Cipher::name) + .def("cipher", &Py_Cipher::cipher) + .def("cipher", &Py_Cipher::cipher_noiv); + + python::class_<Py_HashFunction, boost::noncopyable> + ("HashFunction", python::init<std::string>()) + .def("update", &Py_HashFunction::update) + .def("final", &Py_HashFunction::final) + .def("name", &Py_HashFunction::name) + .def("output_length", &Py_HashFunction::output_length); + + python::class_<Py_MAC, boost::noncopyable> + ("MAC", python::init<std::string, std::string>()) + .def("update", &Py_MAC::update) + .def("final", &Py_MAC::final) + .def("name", &Py_MAC::name) + .def("output_length", &Py_MAC::output_length); + + python::def("cryptobox_encrypt", cryptobox_encrypt); + python::def("cryptobox_decrypt", cryptobox_decrypt); + python::def("pbkdf2", python_pbkdf2); + python::def("derive_key", python_kdf2); + + export_filters(); + export_rsa(); + export_x509(); + } diff --git a/src/python/filter.cpp b/src/python/filter.cpp new file mode 100644 index 000000000..e329ed708 --- /dev/null +++ b/src/python/filter.cpp @@ -0,0 +1,177 @@ +/* +* Boost.Python module definition +* (C) 1999-2007 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include <boost/python.hpp> +using namespace boost::python; + +#include <botan/pipe.h> +#include <botan/lookup.h> +using namespace Botan; + +class Py_Filter : public Filter + { + public: + virtual void write_str(const std::string&) = 0; + + std::string name() const { return "Py_Filter_FIXME"; } + + void write(const byte data[], size_t length) + { + write_str(std::string((const char*)data, length)); + } + + void send_str(const std::string& str) + { + send((const byte*)str.data(), str.length()); + } + }; + +class FilterWrapper : public Py_Filter, public wrapper<Py_Filter> + { + public: + void start_msg() + { + if(override start_msg = this->get_override("start_msg")) + start_msg(); + } + + void end_msg() + { + if(override end_msg = this->get_override("end_msg")) + end_msg(); + } + + void default_start_msg() {} + void default_end_msg() {} + + virtual void write_str(const std::string& str) + { + this->get_override("write")(str); + } + }; + +Filter* return_or_raise(Filter* filter, const std::string& name) + { + if(filter) + return filter; + throw Invalid_Argument("Filter " + name + " could not be found"); + } + +Filter* make_filter1(const std::string& name) + { + Filter* filter = 0; + + if(have_hash(name)) filter = new Hash_Filter(name); + else if(name == "Hex_Encoder") filter = new Hex_Encoder; + else if(name == "Hex_Decoder") filter = new Hex_Decoder; + else if(name == "Base64_Encoder") filter = new Base64_Encoder; + else if(name == "Base64_Decoder") filter = new Base64_Decoder; + + return return_or_raise(filter, name); + } + +Filter* make_filter2(const std::string& name, + const SymmetricKey& key) + { + Filter* filter = 0; + + if(have_mac(name)) + filter = new MAC_Filter(name, key); + else if(have_stream_cipher(name)) + filter = new StreamCipher_Filter(name, key); + + return return_or_raise(filter, name); + } + +// FIXME: add new wrapper for Keyed_Filter here +Filter* make_filter3(const std::string& name, + const SymmetricKey& key, + Cipher_Dir direction) + { + return return_or_raise( + get_cipher(name, key, direction), + name); + } + +Filter* make_filter4(const std::string& name, + const SymmetricKey& key, + const InitializationVector& iv, + Cipher_Dir direction) + { + return return_or_raise( + get_cipher(name, key, iv, direction), + name); + } + +void append_filter(Pipe& pipe, std::auto_ptr<Filter> filter) + { + pipe.append(filter.get()); + filter.release(); + } + +void prepend_filter(Pipe& pipe, std::auto_ptr<Filter> filter) + { + pipe.prepend(filter.get()); + filter.release(); + } + +void do_send(std::auto_ptr<FilterWrapper> filter, const std::string& data) + { + filter->send_str(data); + } + +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(rallas_ovls, read_all_as_string, 0, 1) + +void export_filters() + { + class_<Filter, std::auto_ptr<Filter>, boost::noncopyable> + ("__Internal_FilterObj", no_init); + + def("make_filter", make_filter1, + return_value_policy<manage_new_object>()); + def("make_filter", make_filter2, + return_value_policy<manage_new_object>()); + def("make_filter", make_filter3, + return_value_policy<manage_new_object>()); + def("make_filter", make_filter4, + return_value_policy<manage_new_object>()); + + // This might not work - Pipe will delete the filter, but Python + // might have allocated the space with malloc() or who-knows-what -> bad + class_<FilterWrapper, std::auto_ptr<FilterWrapper>, + bases<Filter>, boost::noncopyable> + ("FilterObj") + .def("write", pure_virtual(&Py_Filter::write_str)) + .def("send", &do_send) + .def("start_msg", &Filter::start_msg, &FilterWrapper::default_start_msg) + .def("end_msg", &Filter::end_msg, &FilterWrapper::default_end_msg); + + implicitly_convertible<std::auto_ptr<FilterWrapper>, + std::auto_ptr<Filter> >(); + + void (Pipe::*pipe_write_str)(const std::string&) = &Pipe::write; + void (Pipe::*pipe_process_str)(const std::string&) = &Pipe::process_msg; + + class_<Pipe, boost::noncopyable>("PipeObj") + .def(init<>()) + /* + .def_readonly("LAST_MESSAGE", &Pipe::LAST_MESSAGE) + .def_readonly("DEFAULT_MESSAGE", &Pipe::DEFAULT_MESSAGE) + */ + .add_property("default_msg", &Pipe::default_msg, &Pipe::set_default_msg) + .add_property("msg_count", &Pipe::message_count) + .def("append", append_filter) + .def("prepend", prepend_filter) + .def("reset", &Pipe::reset) + .def("pop", &Pipe::pop) + .def("end_of_data", &Pipe::end_of_data) + .def("start_msg", &Pipe::start_msg) + .def("end_msg", &Pipe::end_msg) + .def("write", pipe_write_str) + .def("process_msg", pipe_process_str) + .def("read_all", &Pipe::read_all_as_string, rallas_ovls()); + } diff --git a/src/python/python_botan.h b/src/python/python_botan.h new file mode 100644 index 000000000..501f4b9eb --- /dev/null +++ b/src/python/python_botan.h @@ -0,0 +1,86 @@ +/* +* (C) 2009 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#ifndef BOTAN_BOOST_PYTHON_COMMON_H__ +#define BOTAN_BOOST_PYTHON_COMMON_H__ + +#include <botan/exceptn.h> +#include <botan/parsing.h> +#include <botan/secmem.h> +using namespace Botan; + +#include <boost/python.hpp> +namespace python = boost::python; + +extern void export_filters(); +extern void export_rsa(); +extern void export_x509(); + +class Bad_Size : public Exception + { + public: + Bad_Size(u32bit got, u32bit expected) : + Exception("Bad size detected in Python/C++ conversion layer: got " + + std::to_string(got) + " bytes, expected " + + std::to_string(expected)) + {} + }; + +inline std::string make_string(const byte input[], u32bit length) + { + return std::string((const char*)input, length); + } + +template<typename Alloc> +inline std::string make_string(const std::vector<byte, Alloc>& in) + { + return make_string(&in[0], in.size()); + } + +inline void string2binary(const std::string& from, byte to[], u32bit expected) + { + if(from.size() != expected) + throw Bad_Size(from.size(), expected); + std::memcpy(to, from.data(), expected); + } + +template<typename T> +inline python::object get_owner(T* me) + { + return python::object( + python::handle<>( + python::borrowed(python::detail::wrapper_base_::get_owner(*me)))); + } + +class Python_RandomNumberGenerator + { + public: + Python_RandomNumberGenerator() + { rng = RandomNumberGenerator::make_rng(); } + ~Python_RandomNumberGenerator() { delete rng; } + + std::string name() const { return rng->name(); } + + void reseed() { rng->reseed(192); } + + int gen_random_byte() { return rng->next_byte(); } + + std::string gen_random(int n) + { + std::string s(n, 0); + rng->randomize(reinterpret_cast<byte*>(&s[0]), n); + return s; + } + + void add_entropy(const std::string& in) + { rng->add_entropy(reinterpret_cast<const byte*>(in.c_str()), in.length()); } + + RandomNumberGenerator& get_underlying_rng() { return *rng; } + private: + RandomNumberGenerator* rng; + }; + +#endif diff --git a/src/python/rsa.cpp b/src/python/rsa.cpp new file mode 100644 index 000000000..770082945 --- /dev/null +++ b/src/python/rsa.cpp @@ -0,0 +1,220 @@ +/* +* Boost.Python module definition +* (C) 1999-2007 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include <botan/rsa.h> +#include <botan/pubkey.h> +#include <botan/x509_key.h> +using namespace Botan; + +#include "python_botan.h" +#include <sstream> + +std::string bigint2str(const BigInt& n) + { + std::ostringstream out; + out << n; + return out.str(); + } + +class Py_RSA_PrivateKey + { + public: + Py_RSA_PrivateKey(std::string pem_str, + Python_RandomNumberGenerator& rng, + std::string pass); + Py_RSA_PrivateKey(std::string pem_str, + Python_RandomNumberGenerator& rng); + + Py_RSA_PrivateKey(u32bit bits, Python_RandomNumberGenerator& rng); + ~Py_RSA_PrivateKey() { delete rsa_key; } + + std::string to_string() const + { + return PKCS8::PEM_encode(*rsa_key); + } + + std::string to_ber() const + { + secure_vector<byte> bits = PKCS8::BER_encode(*rsa_key); + return std::string(reinterpret_cast<const char*>(&bits[0]), bits.size()); + } + + std::string get_N() const { return bigint2str(get_bigint_N()); } + std::string get_E() const { return bigint2str(get_bigint_E()); } + + const BigInt& get_bigint_N() const { return rsa_key->get_n(); } + const BigInt& get_bigint_E() const { return rsa_key->get_e(); } + + std::string decrypt(const std::string& in, + const std::string& padding); + + std::string sign(const std::string& in, + const std::string& padding, + Python_RandomNumberGenerator& rng); + private: + RSA_PrivateKey* rsa_key; + }; + +std::string Py_RSA_PrivateKey::decrypt(const std::string& in, + const std::string& padding) + { + PK_Decryptor_EME dec(*rsa_key, padding); + + const byte* in_bytes = reinterpret_cast<const byte*>(in.data()); + + return make_string(dec.decrypt(in_bytes, in.size())); + } + +std::string Py_RSA_PrivateKey::sign(const std::string& in, + const std::string& padding, + Python_RandomNumberGenerator& rng) + { + PK_Signer sign(*rsa_key, padding); + const byte* in_bytes = reinterpret_cast<const byte*>(in.data()); + sign.update(in_bytes, in.size()); + return make_string(sign.signature(rng.get_underlying_rng())); + } + +Py_RSA_PrivateKey::Py_RSA_PrivateKey(u32bit bits, + Python_RandomNumberGenerator& rng) + { + rsa_key = new RSA_PrivateKey(rng.get_underlying_rng(), bits); + } + +Py_RSA_PrivateKey::Py_RSA_PrivateKey(std::string pem_str, + Python_RandomNumberGenerator& rng) + { + DataSource_Memory in(pem_str); + + Private_Key* pkcs8_key = + PKCS8::load_key(in, + rng.get_underlying_rng()); + + rsa_key = dynamic_cast<RSA_PrivateKey*>(pkcs8_key); + + if(!rsa_key) + throw std::invalid_argument("Key is not an RSA key"); + } + +Py_RSA_PrivateKey::Py_RSA_PrivateKey(std::string pem_str, + Python_RandomNumberGenerator& rng, + std::string passphrase) + { + DataSource_Memory in(pem_str); + + Private_Key* pkcs8_key = + PKCS8::load_key(in, + rng.get_underlying_rng(), + passphrase); + + rsa_key = dynamic_cast<RSA_PrivateKey*>(pkcs8_key); + + if(!rsa_key) + throw std::invalid_argument("Key is not an RSA key"); + } + +class Py_RSA_PublicKey + { + public: + Py_RSA_PublicKey(std::string pem_str); + Py_RSA_PublicKey(const Py_RSA_PrivateKey&); + ~Py_RSA_PublicKey() { delete rsa_key; } + + std::string get_N() const { return bigint2str(get_bigint_N()); } + std::string get_E() const { return bigint2str(get_bigint_E()); } + + const BigInt& get_bigint_N() const { return rsa_key->get_n(); } + const BigInt& get_bigint_E() const { return rsa_key->get_e(); } + + std::string to_string() const + { + return X509::PEM_encode(*rsa_key); + } + + std::string to_ber() const + { + std::vector<byte> bits = X509::BER_encode(*rsa_key); + + return std::string(reinterpret_cast<const char*>(&bits[0]), + bits.size()); + } + + std::string encrypt(const std::string& in, + const std::string& padding, + Python_RandomNumberGenerator& rng); + + bool verify(const std::string& in, + const std::string& padding, + const std::string& signature); + private: + RSA_PublicKey* rsa_key; + }; + +Py_RSA_PublicKey::Py_RSA_PublicKey(const Py_RSA_PrivateKey& priv) + { + rsa_key = new RSA_PublicKey(priv.get_bigint_N(), priv.get_bigint_E()); + } + +Py_RSA_PublicKey::Py_RSA_PublicKey(std::string pem_str) + { + DataSource_Memory in(pem_str); + Public_Key* x509_key = X509::load_key(in); + + rsa_key = dynamic_cast<RSA_PublicKey*>(x509_key); + + if(!rsa_key) + throw std::invalid_argument("Key is not an RSA key"); + } + +std::string Py_RSA_PublicKey::encrypt(const std::string& in, + const std::string& padding, + Python_RandomNumberGenerator& rng) + { + PK_Encryptor_EME enc(*rsa_key, padding); + + const byte* in_bytes = reinterpret_cast<const byte*>(in.data()); + + return make_string(enc.encrypt(in_bytes, in.size(), + rng.get_underlying_rng())); + } + +bool Py_RSA_PublicKey::verify(const std::string& in, + const std::string& signature, + const std::string& padding) + { + PK_Verifier ver(*rsa_key, padding); + + const byte* in_bytes = reinterpret_cast<const byte*>(in.data()); + const byte* sig_bytes = reinterpret_cast<const byte*>(signature.data()); + + ver.update(in_bytes, in.size()); + return ver.check_signature(sig_bytes, signature.size()); + } + +void export_rsa() + { + python::class_<Py_RSA_PublicKey> + ("RSA_PublicKey", python::init<std::string>()) + .def(python::init<const Py_RSA_PrivateKey&>()) + .def("to_string", &Py_RSA_PublicKey::to_string) + .def("to_ber", &Py_RSA_PublicKey::to_ber) + .def("encrypt", &Py_RSA_PublicKey::encrypt) + .def("verify", &Py_RSA_PublicKey::verify) + .def("get_N", &Py_RSA_PublicKey::get_N) + .def("get_E", &Py_RSA_PublicKey::get_E); + + python::class_<Py_RSA_PrivateKey> + ("RSA_PrivateKey", python::init<std::string, Python_RandomNumberGenerator&, std::string>()) + .def(python::init<std::string, Python_RandomNumberGenerator&>()) + .def(python::init<u32bit, Python_RandomNumberGenerator&>()) + .def("to_string", &Py_RSA_PrivateKey::to_string) + .def("to_ber", &Py_RSA_PrivateKey::to_ber) + .def("decrypt", &Py_RSA_PrivateKey::decrypt) + .def("sign", &Py_RSA_PrivateKey::sign) + .def("get_N", &Py_RSA_PrivateKey::get_N) + .def("get_E", &Py_RSA_PrivateKey::get_E); + } diff --git a/src/python/x509.cpp b/src/python/x509.cpp new file mode 100644 index 000000000..57beb7e4a --- /dev/null +++ b/src/python/x509.cpp @@ -0,0 +1,88 @@ +/* +* Boost.Python module definition +* (C) 2009 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include <botan/oids.h> +#include <botan/pipe.h> +#include <botan/filters.h> +#include <botan/x509cert.h> +#include <botan/x509_crl.h> +using namespace Botan; + +#include <boost/python.hpp> +namespace python = boost::python; + +template<typename T> +class vector_to_list + { + public: + static PyObject* convert(const std::vector<T>& in) + { + python::list out; + typename std::vector<T>::const_iterator i = in.begin(); + while(i != in.end()) + { + out.append(*i); + ++i; + } + return python::incref(out.ptr()); + } + + vector_to_list() + { + python::to_python_converter<std::vector<T>, vector_to_list<T> >(); + } + }; + +template<typename T> +class memvec_to_hexstr + { + public: + static PyObject* convert(const T& in) + { + Pipe pipe(new Hex_Encoder); + pipe.process_msg(in); + std::string result = pipe.read_all_as_string(); + return python::incref(python::str(result).ptr()); + } + + memvec_to_hexstr() + { + python::to_python_converter<T, memvec_to_hexstr<T> >(); + } + }; + +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(add_cert_ols, add_cert, 1, 2) +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(validate_cert_ols, validate_cert, 1, 2) + +void export_x509() + { + vector_to_list<std::string>(); + vector_to_list<X509_Certificate>(); + memvec_to_hexstr<std::vector<byte> >(); + + python::class_<X509_Certificate> + ("X509_Certificate", python::init<std::string>()) + .def(python::self == python::self) + .def(python::self != python::self) + .add_property("version", &X509_Certificate::x509_version) + .add_property("is_CA", &X509_Certificate::is_CA_cert) + .add_property("self_signed", &X509_Certificate::is_self_signed) + .add_property("pathlimit", &X509_Certificate::path_limit) + .add_property("as_pem", &X509_Object::PEM_encode) + .def("start_time", &X509_Certificate::start_time) + .def("end_time", &X509_Certificate::end_time) + .def("subject_info", &X509_Certificate::subject_info) + .def("issuer_info", &X509_Certificate::issuer_info) + .def("ex_constraints", &X509_Certificate::ex_constraints) + .def("policies", &X509_Certificate::policies) + .def("subject_key_id", &X509_Certificate::subject_key_id) + .def("authority_key_id", &X509_Certificate::authority_key_id); + + python::class_<X509_CRL> + ("X509_CRL", python::init<std::string>()) + .add_property("as_pem", &X509_Object::PEM_encode); + } |