diff options
author | lloyd <[email protected]> | 2012-04-24 20:10:20 +0000 |
---|---|---|
committer | lloyd <[email protected]> | 2012-04-24 20:10:20 +0000 |
commit | bef6fe2a92286e89042a944b4daee657ec51aa27 (patch) | |
tree | b39089674652d8667e4b85b73a0b6004eb1ea93f /doc | |
parent | 25f329b8a45b6f84f9a01a0326db48f6853dc59c (diff) | |
parent | bf3f967353053ce408f3bbee58d183487e569f7e (diff) |
propagate from branch 'net.randombit.botan' (head 494c5d548ce3f370c2b771ca6b11e5f41e720da2)
to branch 'net.randombit.botan.tls-state-machine' (head b2cd26ff6f093caa79aecb2d674205f45b6aadff)
Diffstat (limited to 'doc')
-rw-r--r-- | doc/contents.txt | 3 | ||||
-rw-r--r-- | doc/examples/GNUmakefile | 3 | ||||
-rw-r--r-- | doc/examples/asio_tls_server.cpp | 335 | ||||
-rw-r--r-- | doc/examples/cms_dec.cpp | 120 | ||||
-rw-r--r-- | doc/examples/cms_enc.cpp | 59 | ||||
-rw-r--r-- | doc/examples/credentials.h | 284 | ||||
-rw-r--r-- | doc/examples/self_sig.cpp | 3 | ||||
-rw-r--r-- | doc/examples/socket.h | 20 | ||||
-rw-r--r-- | doc/examples/tls_client.cpp | 253 | ||||
-rw-r--r-- | doc/examples/tls_server.cpp | 200 | ||||
-rw-r--r-- | doc/index.txt | 2 | ||||
-rw-r--r-- | doc/ssl.txt | 58 | ||||
-rw-r--r-- | doc/tls.txt | 151 |
13 files changed, 1162 insertions, 329 deletions
diff --git a/doc/contents.txt b/doc/contents.txt index dd600d587..141c9188f 100644 --- a/doc/contents.txt +++ b/doc/contents.txt @@ -12,7 +12,8 @@ Contents filters pubkey x509 - ssl + tls + credentials_manager bigint lowlevel secmem diff --git a/doc/examples/GNUmakefile b/doc/examples/GNUmakefile index a5da47a7c..ea5c8dfe4 100644 --- a/doc/examples/GNUmakefile +++ b/doc/examples/GNUmakefile @@ -19,3 +19,6 @@ clean: eax_test: eax_test.cpp $(CXX) $(CFLAGS) $? $(LIBS) -lboost_regex -o $@ + +asio_tls_server: asio_tls_server.cpp credentials.h + $(CXX) $(CFLAGS) $< $(LIBS) -lboost_thread -lboost_system -o $@ diff --git a/doc/examples/asio_tls_server.cpp b/doc/examples/asio_tls_server.cpp new file mode 100644 index 000000000..e721d0455 --- /dev/null +++ b/doc/examples/asio_tls_server.cpp @@ -0,0 +1,335 @@ +#include <iostream> +#include <string> +#include <vector> + +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <boost/thread.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include <botan/tls_server.h> +#include <botan/x509cert.h> +#include <botan/pkcs8.h> +#include <botan/auto_rng.h> +#include <botan/init.h> + +#include "credentials.h" + +using Botan::byte; +using boost::asio::ip::tcp; + +class tls_server_session : public boost::enable_shared_from_this<tls_server_session> + { + public: + typedef boost::shared_ptr<tls_server_session> pointer; + + static pointer create(boost::asio::io_service& io_service, + Botan::TLS::Session_Manager& session_manager, + Botan::Credentials_Manager& credentials, + Botan::TLS::Policy& policy, + Botan::RandomNumberGenerator& rng) + { + return pointer( + new tls_server_session( + io_service, + session_manager, + credentials, + policy, + rng) + ); + } + + tcp::socket& socket() { return m_socket; } + + void start() + { + m_socket.async_read_some( + boost::asio::buffer(m_read_buf, sizeof(m_read_buf)), + m_strand.wrap( + boost::bind(&tls_server_session::handle_read, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + + void stop() { m_socket.close(); } + + private: + tls_server_session(boost::asio::io_service& io_service, + Botan::TLS::Session_Manager& session_manager, + Botan::Credentials_Manager& credentials, + Botan::TLS::Policy& policy, + Botan::RandomNumberGenerator& rng) : + m_strand(io_service), + m_socket(io_service), + m_tls(boost::bind(&tls_server_session::tls_output_wanted, this, _1, _2), + boost::bind(&tls_server_session::tls_data_recv, this, _1, _2, _3), + boost::bind(&tls_server_session::tls_handshake_complete, this, _1), + session_manager, + credentials, + policy, + rng) + { + } + + void handle_read(const boost::system::error_code& error, + size_t bytes_transferred) + { + if(!error) + { + try + { + m_tls.received_data(m_read_buf, bytes_transferred); + } + catch(std::exception& e) + { + std::cout << "Read failed " << e.what() << "\n"; + stop(); + return; + } + + m_socket.async_read_some( + boost::asio::buffer(m_read_buf, sizeof(m_read_buf)), + m_strand.wrap(boost::bind(&tls_server_session::handle_read, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + else + { + stop(); + } + } + + void handle_write(const boost::system::error_code& error) + { + if(!error) + { + m_write_buf.clear(); + + // initiate another write if needed + tls_output_wanted(NULL, 0); + } + else + { + stop(); + } + } + + void tls_output_wanted(const byte buf[], size_t buf_len) + { + if(buf_len > 0) + m_outbox.insert(m_outbox.end(), buf, buf + buf_len); + + // no write pending and have output pending + if(m_write_buf.empty() && !m_outbox.empty()) + { + std::swap(m_outbox, m_write_buf); + + boost::asio::async_write(m_socket, + boost::asio::buffer(&m_write_buf[0], m_write_buf.size()), + m_strand.wrap( + boost::bind(&tls_server_session::handle_write, + shared_from_this(), + boost::asio::placeholders::error))); + } + } + + void tls_data_recv(const byte buf[], size_t buf_len, Botan::TLS::Alert alert) + { + if(alert.is_valid()) + { + if(alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY) + { + m_tls.close(); + return; + } + } + + if(buf_len > 4) // FIXME: ghetto + { + std::string out; + out += "\r\n"; + out += "HTTP/1.0 200 OK\r\n"; + out += "Server: Botan ASIO test server\r\n"; + if(m_hostname != "") + out += "Host: " + m_hostname + "\r\n"; + out += "Content-Type: text/html\r\n"; + out += "\r\n"; + out += "<html><body>Greets. You said: "; + out += std::string((const char*)buf, buf_len); + out += "</body></html>\r\n\r\n"; + + m_tls.send(reinterpret_cast<const byte*>(&out[0]), + out.size()); + m_tls.close(); + } + } + + bool tls_handshake_complete(const Botan::TLS::Session& session) + { + m_hostname = session.sni_hostname(); + return true; + } + + boost::asio::io_service::strand m_strand; // serialization + + tcp::socket m_socket; + Botan::TLS::Server m_tls; + std::string m_hostname; + + unsigned char m_read_buf[Botan::TLS::MAX_TLS_RECORD_SIZE]; + + // used to hold the data currently being written by the system + std::vector<byte> m_write_buf; + + // used to hold data queued for writing + std::vector<byte> m_outbox; + }; + +class Session_Manager_Locked : public Botan::TLS::Session_Manager + { + public: + bool load_from_session_id(const Botan::MemoryRegion<byte>& session_id, + Botan::TLS::Session& session) + { + boost::lock_guard<boost::mutex> lock(m_mutex); + return m_session_manager.load_from_session_id(session_id, session); + } + + bool load_from_host_info(const std::string& hostname, Botan::u16bit port, + Botan::TLS::Session& session) + { + boost::lock_guard<boost::mutex> lock(m_mutex); + return m_session_manager.load_from_host_info(hostname, port, session); + }; + + void remove_entry(const Botan::MemoryRegion<byte>& session_id) + { + boost::lock_guard<boost::mutex> lock(m_mutex); + m_session_manager.remove_entry(session_id); + } + + void save(const Botan::TLS::Session& session) + { + boost::lock_guard<boost::mutex> lock(m_mutex); + m_session_manager.save(session); + } + + Botan::u32bit session_lifetime() const + { + return m_session_manager.session_lifetime(); + } + + private: + boost::mutex m_mutex; + Botan::TLS::Session_Manager_In_Memory m_session_manager; + + }; + +class tls_server + { + public: + typedef tls_server_session session; + + tls_server(boost::asio::io_service& io_service, unsigned short port) : + m_acceptor(io_service, tcp::endpoint(tcp::v4(), port)), + m_creds(m_rng) + { + session::pointer new_session = make_session(); + + m_acceptor.async_accept( + new_session->socket(), + boost::bind( + &tls_server::handle_accept, + this, + new_session, + boost::asio::placeholders::error) + ); + } + + private: + session::pointer make_session() + { + return session::create( + m_acceptor.get_io_service(), + m_session_manager, + m_creds, + m_policy, + m_rng + ); + } + + void handle_accept(session::pointer new_session, + const boost::system::error_code& error) + { + if (!error) + { + new_session->start(); + + new_session = make_session(); + + m_acceptor.async_accept( + new_session->socket(), + boost::bind( + &tls_server::handle_accept, + this, + new_session, + boost::asio::placeholders::error) + ); + } + } + + tcp::acceptor m_acceptor; + + Botan::AutoSeeded_RNG m_rng; + Session_Manager_Locked m_session_manager; + Botan::TLS::Policy m_policy; + Credentials_Manager_Simple m_creds; + }; + +size_t choose_thread_count() + { + size_t result = boost::thread::hardware_concurrency(); + + if(result) + return result; + + return 2; + } + +int main() + { + try + { + Botan::LibraryInitializer init("thread_safe=true"); + boost::asio::io_service io_service; + + unsigned short port = 4434; + tls_server server(io_service, port); + + const size_t num_threads = choose_thread_count(); + + std::cout << "Using " << num_threads << " threads\n"; + + std::vector<boost::shared_ptr<boost::thread> > threads; + + for(size_t i = 0; i != num_threads; ++i) + { + boost::shared_ptr<boost::thread> thread( + new boost::thread( + boost::bind(&boost::asio::io_service::run, &io_service))); + threads.push_back(thread); + } + + // Wait for all threads in the pool to exit. + for (size_t i = 0; i < threads.size(); ++i) + threads[i]->join(); + } + catch (std::exception& e) + { + std::cerr << e.what() << std::endl; + } + + return 0; + } + diff --git a/doc/examples/cms_dec.cpp b/doc/examples/cms_dec.cpp deleted file mode 100644 index 84355fb4a..000000000 --- a/doc/examples/cms_dec.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/* -* (C) 2009 Jack Lloyd -* -* Distributed under the terms of the Botan license -*/ - -#include <botan/botan.h> -#include <botan/pkcs8.h> -#include <botan/cms_dec.h> -using namespace Botan; - -#include <iostream> -#include <memory> - -int main(int argc, char* argv[]) - { - if(argc != 2) - { - std::cout << "Usage: " << argv[0] << " <filename>\n"; - return 1; - } - - Botan::LibraryInitializer init; - - try { - AutoSeeded_RNG rng; - - X509_Certificate mycert("mycert.pem"); - PKCS8_PrivateKey* mykey = PKCS8::load_key("mykey.pem", rng, "cut"); - - X509_Certificate yourcert("yourcert.pem"); - X509_Certificate cacert("cacert.pem"); - X509_Certificate int_ca("int_ca.pem"); - - X509_Store store; - store.add_cert(mycert); - store.add_cert(yourcert); - store.add_cert(cacert, true); - store.add_cert(int_ca); - - DataSource_Stream message(argv[1]); - - CMS_Decoder decoder(message, store, mykey); - - while(decoder.layer_type() != CMS_Decoder::DATA) - { - CMS_Decoder::Status status = decoder.layer_status(); - CMS_Decoder::Content_Type content = decoder.layer_type(); - - if(status == CMS_Decoder::FAILURE) - { - std::cout << "Failure reading CMS data" << std::endl; - break; - } - - if(content == CMS_Decoder::DIGESTED) - { - std::cout << "Digested data, hash = " << decoder.layer_info() - << std::endl; - std::cout << "Hash is " - << ((status == CMS_Decoder::GOOD) ? "good" : "bad") - << std::endl; - } - - if(content == CMS_Decoder::SIGNED) - { - // how to handle multiple signers? they can all exist within a - // single level... - - std::cout << "Signed by " << decoder.layer_info() << std::endl; - //std::cout << "Sign time: " << decoder.xxx() << std::endl; - std::cout << "Signature is "; - if(status == CMS_Decoder::GOOD) - std::cout << "valid"; - else if(status == CMS_Decoder::BAD) - std::cout << "bad"; - else if(status == CMS_Decoder::NO_KEY) - std::cout << "(cannot check, no known cert)"; - std::cout << std::endl; - } - if(content == CMS_Decoder::ENVELOPED || - content == CMS_Decoder::COMPRESSED || - content == CMS_Decoder::AUTHENTICATED) - { - if(content == CMS_Decoder::ENVELOPED) - std::cout << "Enveloped"; - if(content == CMS_Decoder::COMPRESSED) - std::cout << "Compressed"; - if(content == CMS_Decoder::AUTHENTICATED) - std::cout << "MACed"; - - std::cout << ", algo = " << decoder.layer_info() << std::endl; - - if(content == CMS_Decoder::AUTHENTICATED) - { - std::cout << "MAC status is "; - if(status == CMS_Decoder::GOOD) - std::cout << "valid"; - else if(status == CMS_Decoder::BAD) - std::cout << "bad"; - else if(status == CMS_Decoder::NO_KEY) - std::cout << "(cannot check, no key)"; - std::cout << std::endl; - } - } - decoder.next_layer(); - } - - if(decoder.layer_type() == CMS_Decoder::DATA) - std::cout << "Message is \"" << decoder.get_data() - << '"' << std::endl; - else - std::cout << "No data anywhere?" << std::endl; - } - catch(std::exception& e) - { - std::cerr << e.what() << std::endl; - } - return 0; - } diff --git a/doc/examples/cms_enc.cpp b/doc/examples/cms_enc.cpp deleted file mode 100644 index 2cf813987..000000000 --- a/doc/examples/cms_enc.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* -* (C) 2009 Jack Lloyd -* -* Distributed under the terms of the Botan license -*/ - -#include <botan/botan.h> -#include <botan/cms_enc.h> -using namespace Botan; - -#include <iostream> -#include <fstream> -#include <memory> - -int main() - { - Botan::LibraryInitializer init; - - try { - - X509_Certificate mycert("mycert.pem"); - X509_Certificate mycert2("mycert2.pem"); - X509_Certificate yourcert("yourcert.pem"); - X509_Certificate cacert("cacert.pem"); - X509_Certificate int_ca("int_ca.pem"); - - AutoSeeded_RNG rng; - - X509_Store store; - store.add_cert(mycert); - store.add_cert(mycert2); - store.add_cert(yourcert); - store.add_cert(int_ca); - store.add_cert(cacert, true); - - const std::string msg = "prioncorp: we don't toy\n"; - - CMS_Encoder encoder(msg); - - encoder.compress("Zlib"); - encoder.digest(); - encoder.encrypt(rng, mycert); - - /* - PKCS8_PrivateKey* mykey = PKCS8::load_key("mykey.pem", rng, "cut"); - encoder.sign(store, *mykey); - */ - - SecureVector<byte> raw = encoder.get_contents(); - std::ofstream out("out.der"); - - out.write((const char*)raw.begin(), raw.size()); - } - catch(std::exception& e) - { - std::cerr << e.what() << std::endl; - } - return 0; - } diff --git a/doc/examples/credentials.h b/doc/examples/credentials.h new file mode 100644 index 000000000..6d59c3749 --- /dev/null +++ b/doc/examples/credentials.h @@ -0,0 +1,284 @@ + +#ifndef EXAMPLE_CREDENTIALS_MANAGER_H__ +#define EXAMPLE_CREDENTIALS_MANAGER_H__ + +#include <botan/credentials_manager.h> +#include <botan/x509self.h> +#include <botan/rsa.h> +#include <botan/dsa.h> +#include <botan/srp6.h> +#include <botan/srp6_files.h> +#include <botan/ecdsa.h> +#include <iostream> +#include <fstream> +#include <memory> + +bool value_exists(const std::vector<std::string>& vec, + const std::string& val) + { + for(size_t i = 0; i != vec.size(); ++i) + if(vec[i] == val) + return true; + return false; + } + +class Credentials_Manager_Simple : public Botan::Credentials_Manager + { + public: + Credentials_Manager_Simple(Botan::RandomNumberGenerator& rng) : rng(rng) {} + + std::string srp_identifier(const std::string& type, + const std::string& hostname) + { + if(type == "tls-client" && hostname == "srp-host") + return "user"; + return ""; + } + + bool attempt_srp(const std::string& type, + const std::string& hostname) + { + if(hostname == "srp-host") + return true; + return false; + } + + std::vector<Botan::X509_Certificate> + trusted_certificate_authorities(const std::string& type, + const std::string& hostname) + { + + std::vector<Botan::X509_Certificate> certs; + + if(type == "tls-server") + { + Botan::X509_Certificate testca("testCA.crt"); + certs.push_back(testca); + } + + if(type == "tls-client" && hostname == "twitter.com") + { + Botan::X509_Certificate verisign("/usr/share/ca-certificates/mozilla/VeriSign_Class_3_Public_Primary_Certification_Authority_-_G5.crt"); + certs.push_back(verisign); + } + + return certs; + } + + void verify_certificate_chain( + const std::string& type, + const std::string& purported_hostname, + const std::vector<Botan::X509_Certificate>& cert_chain) + { + try + { + Botan::Credentials_Manager::verify_certificate_chain(type, + purported_hostname, + cert_chain); + } + catch(std::exception& e) + { + std::cout << "Certificate verification failed - " << e.what() << " - but will ignore\n"; + } + } + + std::string srp_password(const std::string& type, + const std::string& hostname, + const std::string& identifier) + { + if(type == "tls-client" && hostname == "localhost" && identifier == "user") + return "password"; + + return ""; + } + + bool srp_verifier(const std::string& type, + const std::string& context, + const std::string& identifier, + std::string& group_id, + Botan::BigInt& verifier, + Botan::MemoryRegion<Botan::byte>& salt, + bool generate_fake_on_unknown) + { + + std::string pass = srp_password("tls-client", context, identifier); + if(pass == "") + { + if(!generate_fake_on_unknown) + return false; + + pass.resize(16); + Botan::global_state().global_rng().randomize((Botan::byte*)&pass[0], pass.size()); + } + + group_id = "modp/srp/2048"; + + salt.resize(16); + Botan::global_state().global_rng().randomize(&salt[0], salt.size()); + + verifier = Botan::generate_srp6_verifier(identifier, + pass, + salt, + group_id, + "SHA-1"); + + return true; + } + + std::string psk_identity_hint(const std::string&, + const std::string&) + { + return ""; + } + + std::string psk_identity(const std::string&, const std::string&, + const std::string& identity_hint) + { + //return "lloyd"; + return "Client_identity"; + } + + Botan::SymmetricKey psk(const std::string& type, const std::string& context, + const std::string& identity) + { + if(type == "tls-server" && context == "session-ticket") + { + if(session_ticket_key.length() == 0) + session_ticket_key = Botan::SymmetricKey(rng, 32); + return session_ticket_key; + } + + if(identity == "Client_identity") + return Botan::SymmetricKey("b5a72e1387552e6dc10766dc0eda12961f5b21e17f98ef4c41e6572e53bd7527"); + if(identity == "lloyd") + return Botan::SymmetricKey("85b3c1b7dc62b507636ac767999c9630"); + + throw Botan::Internal_Error("No PSK set for " + identity); + } + + std::pair<Botan::X509_Certificate,Botan::Private_Key*> + load_or_make_cert(const std::string& hostname, + const std::string& key_type, + Botan::RandomNumberGenerator& rng) + { + using namespace Botan; + + const std::string key_fsname_prefix = hostname + "." + key_type + "."; + const std::string key_file_name = key_fsname_prefix + "key"; + const std::string cert_file_name = key_fsname_prefix + "crt"; + + try + { + X509_Certificate cert(cert_file_name); + Private_Key* key = PKCS8::load_key(key_file_name, rng); + + //std::cout << "Loaded existing key/cert from " << cert_file_name << " and " << key_file_name << "\n"; + + return std::make_pair(cert, key); + } + catch(...) {} + + // Failed. Instead, make a new one + + std::cout << "Creating new certificate for identifier '" << hostname << "'\n"; + + X509_Cert_Options opts; + + opts.common_name = hostname; + opts.country = "US"; + opts.email = "root@" + hostname; + opts.dns = hostname; + + std::auto_ptr<Private_Key> key; + if(key_type == "rsa") + key.reset(new RSA_PrivateKey(rng, 1024)); + else if(key_type == "dsa") + key.reset(new DSA_PrivateKey(rng, DL_Group("dsa/jce/1024"))); + else if(key_type == "ecdsa") + key.reset(new ECDSA_PrivateKey(rng, EC_Group("secp256r1"))); + else + throw std::runtime_error("Don't know what to do about key type '" + key_type + "'"); + + X509_Certificate cert = + X509::create_self_signed_cert(opts, *key, "SHA-1", rng); + + // Now save both + + std::cout << "Saving new " << key_type << " key to " << key_file_name << "\n"; + std::ofstream key_file(key_file_name.c_str()); + key_file << PKCS8::PEM_encode(*key, rng, ""); + key_file.close(); + + std::cout << "Saving new " << key_type << " cert to " << key_file_name << "\n"; + std::ofstream cert_file(cert_file_name.c_str()); + cert_file << cert.PEM_encode() << "\n"; + cert_file.close(); + + return std::make_pair(cert, key.release()); + } + + std::vector<Botan::X509_Certificate> cert_chain( + const std::vector<std::string>& cert_key_types, + const std::string& type, + const std::string& context) + { + using namespace Botan; + + std::vector<X509_Certificate> certs; + + try + { + if(type == "tls-server") + { + const std::string hostname = (context == "" ? "localhost" : context); + + if(hostname == "nosuchname") + return std::vector<Botan::X509_Certificate>(); + + std::string key_name = ""; + + if(value_exists(cert_key_types, "RSA")) + key_name = "rsa"; + else if(value_exists(cert_key_types, "DSA")) + key_name = "dsa"; + else if(value_exists(cert_key_types, "ECDSA")) + key_name = "ecdsa"; + + std::pair<X509_Certificate, Private_Key*> cert_and_key = + load_or_make_cert(hostname, key_name, rng); + + certs_and_keys[cert_and_key.first] = cert_and_key.second; + certs.push_back(cert_and_key.first); + } + else if(type == "tls-client") + { + X509_Certificate cert("user-rsa.crt"); + Private_Key* key = PKCS8::load_key("user-rsa.key", rng); + + certs_and_keys[cert] = key; + certs.push_back(cert); + } + } + catch(std::exception& e) + { + std::cout << e.what() << "\n"; + } + + return certs; + } + + Botan::Private_Key* private_key_for(const Botan::X509_Certificate& cert, + const std::string& type, + const std::string& context) + { + return certs_and_keys[cert]; + } + + private: + Botan::RandomNumberGenerator& rng; + + Botan::SymmetricKey session_ticket_key; + std::map<Botan::X509_Certificate, Botan::Private_Key*> certs_and_keys; + }; + +#endif diff --git a/doc/examples/self_sig.cpp b/doc/examples/self_sig.cpp index 64b778b71..7cb159db9 100644 --- a/doc/examples/self_sig.cpp +++ b/doc/examples/self_sig.cpp @@ -36,6 +36,9 @@ int main(int argc, char* argv[]) AutoSeeded_RNG rng; RSA_PrivateKey key(rng, 2048); + //DL_Group group(rng, DL_Group::DSA_Kosherizer, 2048, 256); + + //DSA_PrivateKey key(rng, group); std::ofstream priv_key("private.pem"); priv_key << PKCS8::PEM_encode(key, rng, argv[1]); diff --git a/doc/examples/socket.h b/doc/examples/socket.h index f7ce98fea..9e16ab36a 100644 --- a/doc/examples/socket.h +++ b/doc/examples/socket.h @@ -48,6 +48,7 @@ #include <netdb.h> #include <unistd.h> #include <errno.h> + #include <fcntl.h> typedef int socket_t; const socket_t invalid_socket = -1; @@ -66,7 +67,7 @@ class Socket { public: - size_t read(unsigned char[], size_t); + size_t read(unsigned char[], size_t, bool dont_block = false); void write(const unsigned char[], size_t); std::string peer_id() const { return peer; } @@ -158,23 +159,28 @@ Socket::Socket(const std::string& host, unsigned short port) : peer(host) throw std::runtime_error("Socket: connect failed"); } + //fcntl(fd, F_SETFL, O_NONBLOCK); + sockfd = fd; } /** * Read from a Unix socket */ -size_t Socket::read(unsigned char buf[], size_t length) +size_t Socket::read(unsigned char buf[], size_t length, bool partial) { if(sockfd == invalid_socket) throw std::runtime_error("Socket::read: Socket not connected"); size_t got = 0; + int flags = MSG_NOSIGNAL; + while(length) { - ssize_t this_time = ::recv(sockfd, (char*)buf + got, - length, MSG_NOSIGNAL); + ssize_t this_time = ::recv(sockfd, (char*)buf + got, length, flags); + + const bool full_ret = (this_time == (ssize_t)length); if(this_time == 0) break; @@ -183,13 +189,19 @@ size_t Socket::read(unsigned char buf[], size_t length) { if(socket_error_code == EINTR) this_time = 0; + else if(socket_error_code == EAGAIN) + break; else throw std::runtime_error("Socket::read: Socket read failed"); } got += this_time; length -= this_time; + + if(partial && !full_ret) + break; } + return got; } diff --git a/doc/examples/tls_client.cpp b/doc/examples/tls_client.cpp index cedfe1ca8..a787af1fe 100644 --- a/doc/examples/tls_client.cpp +++ b/doc/examples/tls_client.cpp @@ -1,91 +1,242 @@ #include <botan/botan.h> #include <botan/tls_client.h> -#include "socket.h" - -using namespace Botan; - +#include <botan/pkcs8.h> +#include <botan/hex.h> #include <stdio.h> #include <string> #include <iostream> #include <memory> -class Client_TLS_Policy : public TLS_Policy +#include <sys/types.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#if defined(BOTAN_HAS_TLS_SQLITE_SESSION_MANAGER) + #include <botan/tls_sqlite_sess_mgr.h> +#endif + +#include "credentials.h" + +using namespace Botan; + +using namespace std::tr1::placeholders; + +int connect_to_host(const std::string& host, u16bit port) { - public: - bool check_cert(const std::vector<X509_Certificate>& certs) const - { - for(size_t i = 0; i != certs.size(); ++i) - { - std::cout << certs[i].to_string(); - } + hostent* host_addr = ::gethostbyname(host.c_str()); + + if(host_addr == 0) + throw std::runtime_error("gethostbyname failed for " + host); + + if(host_addr->h_addrtype != AF_INET) // FIXME + throw std::runtime_error(host + " has IPv6 address"); + + int fd = ::socket(PF_INET, SOCK_STREAM, 0); + if(fd == -1) + throw std::runtime_error("Unable to acquire socket"); + + sockaddr_in socket_info; + ::memset(&socket_info, 0, sizeof(socket_info)); + socket_info.sin_family = AF_INET; + socket_info.sin_port = htons(port); + + ::memcpy(&socket_info.sin_addr, + host_addr->h_addr, + host_addr->h_length); + + socket_info.sin_addr = *(struct in_addr*)host_addr->h_addr; // FIXME + + if(::connect(fd, (sockaddr*)&socket_info, sizeof(struct sockaddr)) != 0) + { + ::close(fd); + throw std::runtime_error("connect failed"); + } + + return fd; + } + +bool handshake_complete(const TLS::Session& session) + { + std::cout << "Handshake complete!\n"; + std::cout << "Protocol version " << session.version().to_string() << "\n"; + std::cout << "Ciphersuite " << std::hex << session.ciphersuite().to_string() << "\n"; + std::cout << "Session ID " << hex_encode(session.session_id()) << "\n"; + std::cout << "Session ticket " << hex_encode(session.session_ticket()) << "\n"; - std::cout << "Warning: not checking cert signatures\n"; + return true; + } + +void socket_write(int sockfd, const byte buf[], size_t length) + { + size_t offset = 0; - return true; + while(length) + { + ssize_t sent = ::send(sockfd, (const char*)buf + offset, + length, MSG_NOSIGNAL); + + if(sent == -1) + { + if(errno == EINTR) + sent = 0; + else + throw std::runtime_error("Socket::write: Socket write failed"); } - }; -int main(int argc, char* argv[]) + offset += sent; + length -= sent; + } + } + +bool got_alert = false; + +void process_data(const byte buf[], size_t buf_size, TLS::Alert alert) { - if(argc != 2 && argc != 3) + if(alert.is_valid()) { - printf("Usage: %s host [port]\n", argv[0]); - return 1; + std::cout << "Alert: " << alert.type_string() << "\n"; + got_alert = true; } - try + for(size_t i = 0; i != buf_size; ++i) { - LibraryInitializer botan_init; + std::cout << buf[i]; + } + } - std::string host = argv[1]; - u32bit port = argc == 3 ? Botan::to_u32bit(argv[2]) : 443; +std::string protocol_chooser(const std::vector<std::string>& protocols) + { + for(size_t i = 0; i != protocols.size(); ++i) + std::cout << "Protocol " << i << " = " << protocols[i] << "\n"; + return "http/1.1"; + } - printf("Connecting to %s:%d...\n", host.c_str(), port); +void doit(RandomNumberGenerator& rng, + TLS::Policy& policy, + TLS::Session_Manager& session_manager, + Credentials_Manager& creds, + const std::string& host, + u16bit port) + { + int sockfd = connect_to_host(host, port); - SocketInitializer socket_init; + TLS::Client client(std::tr1::bind(socket_write, sockfd, _1, _2), + process_data, + handshake_complete, + session_manager, + creds, + policy, + rng, + host); - Socket sock(argv[1], port); + fd_set readfds; - AutoSeeded_RNG rng; + while(true) + { + FD_ZERO(&readfds); + FD_SET(sockfd, &readfds); + FD_SET(STDIN_FILENO, &readfds); - Client_TLS_Policy policy; + ::select(sockfd + 1, &readfds, NULL, NULL, NULL); - TLS_Client tls(std::tr1::bind(&Socket::read, std::tr1::ref(sock), _1, _2), - std::tr1::bind(&Socket::write, std::tr1::ref(sock), _1, _2), - policy, rng); + if(client.is_closed()) + break; - printf("Handshake extablished...\n"); + if(FD_ISSET(sockfd, &readfds)) + { + byte buf[64] = { 0 }; -#if 0 - std::string http_command = "GET / HTTP/1.1\r\n" - "Server: " + host + ':' + to_string(port) + "\r\n\r\n"; -#else - std::string http_command = "GET / HTTP/1.0\r\n\r\n"; -#endif + size_t to_read = rand() % sizeof(buf); + if(to_read == 0) + to_read = 1; - tls.write((const Botan::byte*)http_command.c_str(), - http_command.length()); + ssize_t got = read(sockfd, buf, to_read); - size_t total_got = 0; + if(got == 0) + { + std::cout << "EOF on socket\n"; + break; + } + else if(got == -1) + { + std::cout << "Socket error: " << errno << " " << strerror(errno) << "\n"; + continue; + } - while(true) + const size_t needed = client.received_data(buf, got); + //std::cout << "Socket - got " << got << " bytes, need " << needed << "\n"; + } + else if(FD_ISSET(STDIN_FILENO, &readfds)) { - if(tls.is_closed()) - break; + byte buf[1024] = { 0 }; + ssize_t got = read(STDIN_FILENO, buf, sizeof(buf)); - Botan::byte buf[128+1] = { 0 }; - size_t got = tls.read(buf, sizeof(buf)-1); - printf("%s", buf); - fflush(0); + if(got == 0) + { + std::cout << "EOF on stdin\n"; + client.close(); + break; + } + else if(got == -1) + { + std::cout << "Stdin error: " << errno << " " << strerror(errno) << "\n"; + continue; + } - total_got += got; + if(got == 2 && (buf[0] == 'R' || buf[0] == 'r') && buf[1] == '\n') + { + std::cout << "Client initiated renegotiation\n"; + client.renegotiate((buf[0] == 'R')); + } + + if(buf[0] == 'H') + client.heartbeat(&buf[1], got-1); + else + client.send(buf, got); } + } + + ::close(sockfd); + } + +int main(int argc, char* argv[]) + { + if(argc != 2 && argc != 3) + { + std::cout << "Usage " << argv[0] << " host [port]\n"; + return 1; + } + + try + { + LibraryInitializer botan_init; + AutoSeeded_RNG rng; + TLS::Policy policy; + +#if defined(BOTAN_HAS_TLS_SQLITE_SESSION_MANAGER) + TLS::Session_Manager_SQLite session_manager("my secret passphrase", rng, + "sessions.db"); +#else + TLS::Session_Manager_In_Memory session_manager; +#endif + + Credentials_Manager_Simple creds(rng); + + std::string host = argv[1]; + u32bit port = argc == 3 ? Botan::to_u32bit(argv[2]) : 443; + + //while(true) + doit(rng, policy, session_manager, creds, host, port); - printf("\nRetrieved %d bytes total\n", total_got); } catch(std::exception& e) { - printf("%s\n", e.what()); + std::cout << "Exception: " << e.what() << "\n"; return 1; } return 0; diff --git a/doc/examples/tls_server.cpp b/doc/examples/tls_server.cpp index 153b26d04..bdc9c0b8a 100644 --- a/doc/examples/tls_server.cpp +++ b/doc/examples/tls_server.cpp @@ -1,33 +1,140 @@ #include <botan/botan.h> #include <botan/tls_server.h> +#include <botan/hex.h> #include <botan/rsa.h> #include <botan/dsa.h> #include <botan/x509self.h> +#include <botan/secqueue.h> #include "socket.h" +#include "credentials.h" using namespace Botan; +using namespace std::tr1::placeholders; + #include <stdio.h> #include <string> #include <iostream> #include <memory> -class Server_TLS_Policy : public TLS_Policy +class Blocking_TLS_Server { public: - bool check_cert(const std::vector<X509_Certificate>& certs) const + Blocking_TLS_Server(std::tr1::function<void (const byte[], size_t)> output_fn, + std::tr1::function<size_t (byte[], size_t)> input_fn, + std::vector<std::string>& protocols, + TLS::Session_Manager& sessions, + Credentials_Manager& creds, + TLS::Policy& policy, + RandomNumberGenerator& rng) : + input_fn(input_fn), + server( + output_fn, + std::tr1::bind(&Blocking_TLS_Server::reader_fn, std::tr1::ref(*this), _1, _2, _3), + std::tr1::bind(&Blocking_TLS_Server::handshake_complete, std::tr1::ref(*this), _1), + sessions, + creds, + policy, + rng, + protocols), + exit(false) + { + read_loop(); + } + + bool handshake_complete(const TLS::Session& session) + { + std::cout << "Handshake complete: " + << session.version().to_string() << " " + << session.ciphersuite().to_string() << " " + << "SessionID: " << hex_encode(session.session_id()) << "\n"; + + if(session.srp_identifier() != "") + std::cout << "SRP identifier: " << session.srp_identifier() << "\n"; + + if(server.next_protocol() != "") + std::cout << "Next protocol: " << server.next_protocol() << "\n"; + + /* + std::vector<X509_Certificate> peer_certs = session.peer_certs(); + if(peer_certs.size()) + std::cout << peer_certs[0].to_string(); + */ + + return true; + } + + size_t read(byte buf[], size_t buf_len) { - for(size_t i = 0; i != certs.size(); ++i) + size_t got = read_queue.read(buf, buf_len); + + while(!exit && !got) { - std::cout << certs[i].to_string(); + read_loop(TLS::TLS_HEADER_SIZE); + got = read_queue.read(buf, buf_len); } - std::cout << "Warning: not checking cert signatures\n"; + return got; + } - return true; + void write(const byte buf[], size_t buf_len) + { + server.send(buf, buf_len); + } + + void close() { server.close(); } + + bool is_active() const { return server.is_active(); } + + TLS::Server& underlying() { return server; } + private: + void read_loop(size_t init_desired = 0) + { + size_t desired = init_desired; + + byte buf[4096]; + while(!exit && (!server.is_active() || desired)) + { + const size_t asking = std::max(sizeof(buf), std::min(desired, static_cast<size_t>(1))); + + const size_t socket_got = input_fn(&buf[0], asking); + + if(socket_got == 0) // eof? + { + close(); + printf("got eof on socket\n"); + exit = true; + } + + desired = server.received_data(&buf[0], socket_got); + } + } + + void reader_fn(const byte buf[], size_t buf_len, TLS::Alert alert) + { + if(alert.is_valid()) + { + printf("Alert %s\n", alert.type_string().c_str()); + //exit = true; + } + + printf("Got %d bytes: ", (int)buf_len); + for(size_t i = 0; i != buf_len; ++i) + { + if(isprint(buf[i])) + printf("%c", buf[i]); + } + printf("\n"); + + read_queue.write(buf, buf_len); } + + std::tr1::function<size_t (byte[], size_t)> input_fn; + TLS::Server server; + SecureQueue read_queue; + bool exit; }; int main(int argc, char* argv[]) @@ -40,59 +147,82 @@ int main(int argc, char* argv[]) try { LibraryInitializer botan_init; - SocketInitializer socket_init; + //SocketInitializer socket_init; AutoSeeded_RNG rng; - //RSA_PrivateKey key(rng, 1024); - DSA_PrivateKey key(rng, DL_Group("dsa/jce/1024")); + Server_Socket listener(port); - X509_Cert_Options options( - "localhost/US/Syn Ack Labs/Mathematical Munitions Dept"); + TLS::Policy policy; - X509_Certificate cert = - X509::create_self_signed_cert(options, key, "SHA-1", rng); + TLS::Session_Manager_In_Memory sessions; - Server_Socket listener(port); + Credentials_Manager_Simple creds(rng); - Server_TLS_Policy policy; + std::vector<std::string> protocols; + + /* + * These are the protocols we advertise to the client, but the + * client will send back whatever it actually plans on talking, + * which may or may not take into account what we advertise. + */ + protocols.push_back("echo/1.0"); + protocols.push_back("echo/1.1"); while(true) { try { printf("Listening for new connection on port %d\n", port); - Socket* sock = listener.accept(); + std::auto_ptr<Socket> sock(listener.accept()); printf("Got new connection\n"); - TLS_Server tls( - std::tr1::bind(&Socket::read, std::tr1::ref(sock), _1, _2), - std::tr1::bind(&Socket::write, std::tr1::ref(sock), _1, _2), - policy, - rng, - cert, - key); + Blocking_TLS_Server tls( + std::tr1::bind(&Socket::write, std::tr1::ref(sock), _1, _2), + std::tr1::bind(&Socket::read, std::tr1::ref(sock), _1, _2, true), + protocols, + sessions, + creds, + policy, + rng); - std::string hostname = tls.requested_hostname(); + const char* msg = "Welcome to the best echo server evar\n"; + tls.write((const Botan::byte*)msg, strlen(msg)); - if(hostname != "") - printf("Client requested host '%s'\n", hostname.c_str()); + std::string line; - printf("Writing some text\n"); + while(tls.is_active()) + { + byte b; + size_t got = tls.read(&b, 1); - char msg[] = "Foo\nBar\nBaz\nQuux\n"; - tls.write((const Botan::byte*)msg, strlen(msg)); + if(got == 0) + break; + + line += (char)b; + if(b == '\n') + { + //std::cout << line; + + tls.write(reinterpret_cast<const byte*>(line.data()), line.size()); - printf("Now trying a read...\n"); + if(line == "quit\n") + { + tls.close(); + break; + } - char buf[1024] = { 0 }; - u32bit got = tls.read((Botan::byte*)buf, sizeof(buf)-1); - printf("%d: '%s'\n", got, buf); + if(line == "reneg\n") + tls.underlying().renegotiate(false); + else if(line == "RENEG\n") + tls.underlying().renegotiate(true); - tls.close(); + line.clear(); + } + } } - catch(std::exception& e) { printf("%s\n", e.what()); } + catch(std::exception& e) { printf("Connection problem: %s\n", e.what()); } } } catch(std::exception& e) diff --git a/doc/index.txt b/doc/index.txt index 75e2ca434..33de06f55 100644 --- a/doc/index.txt +++ b/doc/index.txt @@ -4,7 +4,7 @@ Welcome Botan is a :doc:`BSD-licensed <license>` crypto library for C++. It provides applications with most any :doc:`cryptographic algorithm -<algos>` you might be looking for, along with :doc:`SSL/TLS <ssl>`, +<algos>` you might be looking for, along with :doc:`SSL/TLS <tls>`, :doc:`X.509 certificates and CRLs <x509>`, a :doc:`pipeline-style message processing system <filters>`, and a wide variety of other features. A third party open source implementation of `SSHv2 diff --git a/doc/ssl.txt b/doc/ssl.txt deleted file mode 100644 index f536b7198..000000000 --- a/doc/ssl.txt +++ /dev/null @@ -1,58 +0,0 @@ - -.. _ssl_api: - -SSL and TLS -======================================== - -.. versionadded:: 1.9.4 - -Botan supports both client and server implementations of the SSL/TLS -protocols, including SSL v3, TLS v1.0, and TLS v1.1. The insecure and -obsolete SSL v2 is not supported. - -The implementation uses ``std::tr1::function``, so it may not have -been compiled into the version you are using; you can test for the -feature macro ``BOTAN_HAS_SSL_TLS`` to check. - -TLS Clients ----------------------------------------- - -.. cpp:class:: TLS_Client - - .. cpp:function:: TLS_Client( \ - std::tr1::function<size_t, byte*, size_t> input_fn, \ - std::tr1::function<void, const byte*, size_t> output_fn, \ - const TLS_Policy& policy, RandomNumberGenerator& rng) - - Creates a TLS client. It will call *input_fn* to read bytes from - the network and call *output_fn* when bytes need to be written to - the network. - - .. cpp:function:: size_t read(byte* buf, size_t buf_len) - - Reads up to *buf_len* bytes from the open connection into *buf*, - returning the number of bytes actually written. - - .. cpp:function:: void write(const byte* buf, size_t buf_len) - - Writes *buf_len* bytes in *buf* to the remote side - - .. cpp:function:: void close() - - Closes the connection - - .. cpp:function:: std::vector<X509_Certificate> peer_cert_chain() - - Returns the certificate chain of the server - -A simple TLS client example: - -.. literalinclude:: examples/tls_client.cpp - - -TLS Servers ----------------------------------------- - -A simple TLS server - -.. literalinclude:: examples/tls_server.cpp diff --git a/doc/tls.txt b/doc/tls.txt new file mode 100644 index 000000000..267fb6e62 --- /dev/null +++ b/doc/tls.txt @@ -0,0 +1,151 @@ + +.. _tls_api: + +SSL and TLS +======================================== + +.. versionadded:: 1.10.2 + +Botan supports both client and server implementations of the SSL/TLS +protocols, including SSL v3, TLS v1.0, TLS v1.1, and TLS v1.2 (the +insecure and obsolete SSL v2 protocol is not supported, beyond +processing SSL v2 client hellos which some clients still send for +backwards compatability with ancient servers). + +The implementation uses ``std::tr1::function`` for callbacks, so it +may not have been compiled into the version you are using; you can +test for the feature macro ``BOTAN_HAS_TLS`` to check. + +General TLS Interface +---------------------------------------- + +TLS servers and clients share most of an interface, called +`TLS_Channel`. The primary difference is in terms of how the objects +are constructed. A TLS channel (either client or server object) has +these methods available: + +.. cpp:class:: TLS_Channel + + .. cpp:function size_t received_data(const byte buf[], size_t buf_size) + + This function is used to provide data sent by the counterparty (eg + data that you read off the socket layer). Depending on the current + protocol state and the amount of data provided this may result in one + or more callback functions that were provided to the constructor being + called. + + .. cpp:function void send(const byte buf[], size_t buf_size) + + If the connection has completed the initial handshake process, the + data provided is sent to the counterparty as TLS + traffic. Otherwise, an exception is thrown. + + .. cpp:function:: void close() + + A close notification is sent to the counterparty, and the internal + state is cleared. + + .. cpp:function:: bool is_active() + + Returns true if and only if a handshake has been completed on this + connection. + + .. cpp:function:: bool is_closed() + + Returns true if and only if a close notification has been sent or + received, or if a fatal alert of any kind was received from the + counterparty. + + .. cpp:function:: void renegotiate() + + Initiates a renegotiation. The counterparty is allowed by the + protocol to ignore this request. If a successful renegotiation + occurs, the *handshake_complete* callback will be called again. + + .. cpp:function:: std::vector<X509_Certificate> peer_cert_chain() + + Returns the certificate chain of the server + + + +TLS Clients +---------------------------------------- + +.. cpp:class:: TLS_Client + + .. cpp:function:: TLS_Client( \ + std::tr1::function<void, const byte*, size_t> socket_output_fn, \ + std::tr1::function<void, const byte*, size_t, u16bit> proc_fn, \ + std::tr1::function<bool, const TLS_Session&> handshake_complete, \ + TLS_Session_Manager& session_manager, \ + Credentials_Manager& credendials_manager, \ + const TLS_Policy& policy, \ + RandomNumberGenerator& rng, \ + const std::string& servername = "", \ + std::tr1::function<std::string, std::vector<std::string> > next_protocol) + + Initialize a new TLS client. The constructor will immediately + initiate a new session. The *socket_output_fn* callback will be + called with output that should be sent to the counterparty. + + The *proc_fn* will be called with data sent by the counterparty + after it has been processed. The byte array and size_t represent + the plaintext; the u16bit value provides notification if the + counterparty sent an alert via the TLS alert system. Possible values + of alert data are included in the Alert_Type enum. Particularly + relevant is the CLOSE_NOTIFY value. + + The *handshake_complete* function is called when a handshake + (either initial or renegotiation) is completed. The return value of + the callback specifies if the session should be cached for later + resumption. If the function for some reason desires to prevent the + connection from completing, it should throw an exception + (preferably a TLS_Exception, which can provide more specific alert + information to the counterparty). + + The *session_manager* is an interface for storing TLS sessions, + which allows for session resumption upon reconnecting to a server. + In the absence of a need for persistent sessions, use + `TLS_Session_Manager_In_Memory` which caches connections for the + lifetime of a single process. + + The *credentials_manager* is an interface that will be called to + retrieve any certificates, secret keys, pre-shared keys, or SRP + intformation; see :doc:`credentials <credentials_manager>` for more + information. + + Use *servername* to specify the DNS name of the server you are + attempting to connect to, if you know it. + + The optional *next_protocol* callback is called if the server + indicates it supports the next protocol notification extension. + The callback wlil be called with a list of protocol names that the + server advertises, and the client can select from them or return an + unadvertised protocol. + +A simple TLS client example: + +.. literalinclude:: examples/tls_client.cpp + + +TLS Servers +---------------------------------------- + +.. cpp:class:: TLS_Server + + .. cpp:function:: TLS_Server(std::tr1::function<void, const byte*, size_t> socket_output_fn, \ + std::tr1::function<void, const byte*, size_t, u16bit> proc_fn, \ + std::tr1::function<bool, const TLS_Session&> handshake_complete, \ + TLS_Session_Manager& session_manager, \ + Credentials_Manager& creds, \ + const TLS_Policy& policy, \ + RandomNumberGenerator& rng, \ + const std::vector<std::string>& protocols) + +The first 7 arguments are treated similiarly to `TLS_Client`. The +final (optional) argument, protocols, specifies the protocols the +server is willing to advertise it supports. + +A TLS server that can handle concurrent connections using asio: + +.. literalinclude:: examples/asio_tls_server.cpp |