PLUGIN ssl # TODO list # - 18/11/2025 populate relevant said traces in plugin # - Translate openssl interruptions into SVM interruptions for clean scheduling policies application # - verify callback evolution to take svm pointer as parameter... # - Complete documentation # - Add tests lang: "C++17" title: "ssl" author: "Julien TALLON" changelog: %{ svm-plugin-ssl (0.0.0) UNRELEASED; urgency=medium * First release of this plugin -- Julien TALLON Tue, 31 Mar 2026 19:05:21 +0200 %} maintainer: "Julien TALLON Julien BRUGUIER " date: "2026-03-31" version: "0.0.0" synopsis: %{ A plugin to implement secure communications, supporting client and server behaviours. %} description: %{ Prior to install this plugin, the OpenSSL library at least 3.0.2 shall be installed in developer mode. Under Debian, this can be done by the command: .nf apt install libssl-dev .fi .P Disclaimer: This plugin is provided as it, as no tests have been provided (They will be added later). Use at your own risk. %} example: "basic" %{ .nf TO BE COMPLETED .fi %} link: "-lssl" # mandatory to link to openssl library on system # compile: "-g" # include debug symbols for enabling analysis of callstacks if necessary. seealso: %{ .BR svm_plugin_com (7) for com plugin documentation. %} includes: %{ #include #include #include #include #include #include #include #include #include %} initialisation: %{ // Make sure the library is available or interrupt SSL_library_init(); // Initialize source of randomness to /dev/random RAND_load_file("/dev/urandom", 1024); debug = ::svm_value_boolean_get(svm,::svm_plugin_get_option(svm, CONST_PEP(ssl,debug))); %} finalisation: %{ %} code: %{ static bool debug = false; using std::string_literals::operator""s; void printerr(X509* iCert) { int rc = X509_print_fp(stderr, iCert); if (rc <= 0) { std::ostringstream oss; oss << "Error printing X509 certificate to stderr " << ERR_get_error() << "\n"; // if(debug) { // ::svm_machine_trace__string(svm, NEW_STRING(oss.str())); // } } } int verify_callback(int ok, X509_STORE_CTX* store) { if(!ok) { X509* cert = X509_STORE_CTX_get_current_cert(store); int depth = X509_STORE_CTX_get_error_depth(store); int err = X509_STORE_CTX_get_error(store); STACK_OF(X509)* theChain = X509_STORE_CTX_get_chain(store); if(nullptr != theChain) { int theChain_size = sk_X509_num(theChain); for(int i = 0; i < theChain_size; ++i) { X509* aRing = sk_X509_value(theChain, i); } } std::ostringstream oss; // oss << __FUNCTION__ << " : cert chain " << '\n'; oss << __FUNCTION__ << " : Error " << ok << " with certificate " << err << ":" << X509_verify_cert_error_string(err) << '\n'; oss << __FUNCTION__ << " : depth: " << depth << '\n'; // ::svm_machine_trace__string(svm, NEW_STRING(oss.str())); printerr(cert); } return ok; } template using deleted_unique_ptr = std::unique_ptr>; using SSLCTXP = deleted_unique_ptr; auto SSLCTX_delete = [](SSL_CTX* ictx) -> void { SSL_CTX_free(ictx); }; using BIOP = deleted_unique_ptr; auto BIO_delete = [](BIO* iobio) -> void { BIO_free(iobio); }; using SSLP = deleted_unique_ptr; auto SSL_delete = [](SSL* iossl) -> void { SSL_free(iossl); }; std::stringstream print_error_stack(std::string iMessage) { std::stringstream oss; oss << "Error " << iMessage << '\n'; auto aBio = BIOP(BIO_new(BIO_s_mem()), BIO_delete); ERR_print_errors(aBio.get()); char *buf; std::size_t len = BIO_get_mem_data(aBio.get(), &buf); oss << std::string(buf, len) << '\n'; return oss; } SSLP attach(const void* svm, SSL_CTX* ctx, BIO* iPeer) { auto sslconnection = SSLP(SSL_new(ctx), SSL_delete); if(nullptr == sslconnection){ auto oss = print_error_stack("creating SSL connection"); ERROR_INTERNAL(DEVICE, oss.str().c_str()); } SSL_set_bio(sslconnection.get(), iPeer, iPeer); return sslconnection; } BIOP connect(const void* svm, std::string iSocket) { auto aUnderlyingConnection = BIOP(BIO_new_connect(iSocket.c_str()), BIO_delete); if(nullptr == aUnderlyingConnection){ auto oss = print_error_stack("creating client socket "s + iSocket); ERROR_INTERNAL(DEVICE, oss.str().c_str()); } if(BIO_do_connect(aUnderlyingConnection.get()) <= 0) { auto oss = print_error_stack("binding client socket "s + iSocket); ERROR_INTERNAL(DEVICE, oss.str().c_str()); } return aUnderlyingConnection; } BIOP listen(const void* svm, std::string iSocket) { auto aUnderlyingConnection = BIOP(BIO_new_accept(iSocket.c_str()), BIO_delete); if(nullptr == aUnderlyingConnection){ auto oss = print_error_stack("creating server socket "s + iSocket); ERROR_INTERNAL(DEVICE, oss.str().c_str()); } if(BIO_do_accept(aUnderlyingConnection.get()) <= 0) { auto oss = print_error_stack("binding server socket "s + iSocket); ERROR_INTERNAL(DEVICE, oss.str().c_str()); } return aUnderlyingConnection; } BIOP await_client(const void* svm, BIO* iUnderlyingConnection) { if(BIO_do_accept(iUnderlyingConnection) <= 0) { auto oss = print_error_stack("binding client"s); ERROR_INTERNAL(DEVICE, oss.str().c_str()); } return BIOP(BIO_pop(iUnderlyingConnection), BIO_delete); } %} DEFINE OPTION ssl.debug -d BLN help: %{ When this option is active, the plugin will provide debug traces. By default, no special debug traces are exposed. %} TYPE ssl.context %{ SSLCTXP _context; %} delete default: %{} STRUCT ssl.context_wrapper %{} delete: %{} INSTRUCTION ssl.new STR : certificate STR : privatekey ('NOVERIF' | STR : trustedstorefileorpath | PEP {.*}) -> ssl.context %{ using std::string_literals::operator""s; auto certificatefilename = ARGV_VALUE(0, string); auto privatekeyfilename = ARGV_VALUE(1, string); auto ctx = SSLCTXP(SSL_CTX_new(TLS_method()), SSLCTX_delete); if(0 >= SSL_CTX_use_certificate_chain_file(ctx.get(), certificatefilename.string)) { auto oss = print_error_stack("using certificate chain file"s); ERROR_INTERNAL(FAILURE, oss.str().c_str()); } if(0 >= SSL_CTX_use_PrivateKey_file(ctx.get(), privatekeyfilename.string, SSL_FILETYPE_PEM)) { auto oss = print_error_stack("using private key file"s); ERROR_INTERNAL(FAILURE, oss.str().c_str()); } if(::svm_parameter_type_is_keyword(svm, argv[2])) { SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_NONE, nullptr); } else { SVM_Value param = ::svm_parameter_value_get(svm, argv[2]); if(::svm_value_type_is_string(svm, param)) { auto trustedstorefileorpathstr = ::svm_value_string_get(svm, param); auto trustedstorefileorpath = std::filesystem::path(RAW_STRING(trustedstorefileorpathstr)); // Enforce default verifications SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER, nullptr /* verify_callback */); if(0 == SSL_CTX_set_default_verify_paths(ctx.get())) { auto oss = print_error_stack("setting default verification locations"s); ERROR_INTERNAL(FAILURE, oss.str().c_str()); } if(!std::filesystem::exists(trustedstorefileorpath)) { ERROR_INTERNAL(FAILURE, "trust store file or path does not exist"); } else { if(std::filesystem::is_directory(trustedstorefileorpath)) { if(0 == SSL_CTX_load_verify_locations(ctx.get(), nullptr, trustedstorefileorpath.c_str()) ) { auto oss = print_error_stack("loading trusted certificate directory \""s + trustedstorefileorpath.string().c_str() + "\""s); ERROR_INTERNAL(FAILURE, oss.str().c_str()); } } else if(std::filesystem::is_regular_file(trustedstorefileorpath)) { if(0 == SSL_CTX_load_verify_locations(ctx.get(), trustedstorefileorpath.c_str(), nullptr) ) { auto oss = print_error_stack("loading trusted certificate file \""s + trustedstorefileorpath.string().c_str() + "\""s); ERROR_INTERNAL(FAILURE, oss.str().c_str()); } } else { ERROR_INTERNAL(FAILURE, "trust store file or path points to incorrect target"); } } } else { SVM_Parameter* parameters = ::svm_parameter_array_new(svm,argc-5); SVM_Structure theContext = NEW_STRUCT(ssl, context_wrapper, ctx.get()); parameters[0] = ::svm_parameter_structure_new(svm, theContext); for(SVM_Index i = 1; i < argc - 5; ++i){ parameters[i] = argv[i+3]; } ::svm_function_call(svm, param, argc-5, parameters); } } return NEW_PLUGIN(ssl, context, new type_context{std::move(ctx)}); %} help: %{ TODO document instruction And recall to raise attention to the callback injection When injecting a callback, the first line to put in your plugin function is : SSL_CTX* theContext = reinterpret_cast(::svm_structure_get_internal(svm,CONST_PEP(ssl, context_wrapper),::svm_parameter_structure_get(svm,argv[0]))); %} # Alternative API design # INSTRUCTION ssl.cert_parse STR -> ssl.cert # INSTRUCTION ssl.cert_open STR:iFile -> ssl.cert # INSTRUCTION ssl.cert_new -> ssl.cert # CONST ssl.cert "......" -> ssl.certw STRUCT ssl.tcp %{ BIOP _bio; // listening socket SSLP _ssl; // communication socket %} delete default: %{} FUNCTION ssl.device_tcp_open ( > STR : ip STR : port ssl.context | < STR : ip STR : port ) -> $ssl.tcp %{ std::string type = ARGV_MARKER(0); SVM_String ip = ARGV_VALUE(1, string); SVM_String port = ARGV_VALUE(2, string); std::string socket = RAW_STRING(ip) + ":" + RAW_STRING(port); SSLP aTls; BIOP aBio; if(type == "<") { // Server aBio = listen(svm, socket); } else { // Client auto ctx = ARGV_PLUGIN(3, ssl, context); aBio = connect(svm, socket); aTls = attach(svm, ctx->_context.get(), aBio.release()); if(SSL_connect(aTls.get()) <= 0) { auto aSS = print_error_stack("creating SSL transaction context"s); ERROR_INTERNAL(DEVICE, aSS.str().c_str()); } } auto* t = new struct_tcp{std::move(aBio), std::move(aTls)}; return NEW_STRUCT(ssl, tcp, t); %} FUNCTION ssl.device_tcp_close $ssl.tcp -> BLN %{ auto ssl = ARGV_STRUCT(0, ssl, tcp); if(ssl->_ssl != nullptr) { if(SSL_get_shutdown(ssl->_ssl.get()) & SSL_RECEIVED_SHUTDOWN) { SSL_shutdown(ssl->_ssl.get()); } else { SSL_clear(ssl->_ssl.get()); } } return NEW_VALUE(boolean, TRUE); %} FUNCTION ssl.device_tcp_print $ssl.tcp -> STR %{ auto ssl = ARGV_STRUCT(0, ssl, tcp); if(ssl->_ssl != nullptr) { auto bio = BIOP(BIO_new(BIO_s_mem()), BIO_delete); SSL_SESSION *session = SSL_get_session(ssl->_ssl.get()); if (session) { SSL_SESSION_print(bio.get(), session); } char *data; long len = BIO_get_mem_data(bio.get(), &data); SVM_String valueToPrint = ::svm_string_new(svm, data, len); return NEW_VALUE(string, valueToPrint); // return NEW_VALUE(string, NEW_STRING("Device is a communicating socket"s)); } else if(ssl->_bio != nullptr) { return NEW_VALUE(string, NEW_STRING("Device is a listening socket"s)); } else { ERROR_INTERNAL(DEVICE, "Printing corrupted device"); } RETURN; %} FUNCTION ssl.device_tcp_read $ssl.tcp -> STR ? %{ auto ssl = ARGV_STRUCT(0, ssl, tcp); if(ssl->_ssl == nullptr) { ERROR_INTERNAL(DEVICE, "Invalid read from listening SSL socket"); } std::array buffer; int err_or_read = SSL_read(ssl->_ssl.get(), buffer.data(), 1024); if(err_or_read<0) { int details = SSL_get_error(ssl->_ssl.get(), err_or_read); switch(details) { case SSL_ERROR_ZERO_RETURN: { return NEW_NULL_VALUE(string); } case SSL_ERROR_WANT_READ: [[fallthrough]]; case SSL_ERROR_WANT_WRITE: { return ::svm_value_string_new__raw(svm,""); } default: { std::stringstream oss = print_error_stack("Invalid read"); ERROR_INTERNAL(DEVICE, oss.str().c_str()); } } } return ::svm_value_string_new__buffer(svm,buffer.data(),err_or_read); %} FUNCTION ssl.device_tcp_write $ssl.tcp STR %{ auto ssl = ARGV_STRUCT(0, ssl, tcp); SVM_String query = ARGV_VALUE(1, string); if(ssl->_ssl == nullptr) { ERROR_INTERNAL(DEVICE, "Invalid write to listening SSL socket"); } auto err = SSL_write(ssl->_ssl.get(), query.string, query.size); if(err <= 0) { std::stringstream oss = print_error_stack("Invalid write"); ERROR_INTERNAL(DEVICE, oss.str().c_str()); } %} ## Not implemented as file pointers to be returned would cause idle cancelling during TLS tunnel implementation proces (not only at app level) #FUNCTION ssl.device_tcp_idle $ssl.tcp MUTABLE INT 3 #%{ #%} FUNCTION ssl.device_tcp_command $ssl.tcp .* -> VALUE ? %{ auto ssl = ARGV_STRUCT(0, ssl, tcp); for(SVM_Index i = 1; i_bio) { ERROR_INTERNAL(DEVICE, "Invalid client acceptation from SSL socket device."); } ++i; if(not (i < argc) ) { ERROR_INTERNAL(FAILURE, "Missing ssl.context parameter"); } auto context = ARGV_PLUGIN(i, ssl, context); /// TODO jtallon 25/02/2025 /// determine error code that BIO_do_accept may return in case of interruption while waiting for a client to isolate the case with dedicated SVM interruption com.interrupted SVM_Process current_process = ::svm_process_get_current(svm); ::svm_process_pause(svm); ::svm_process_interruptionnotification_enable(svm,current_process); BIOP client = await_client(svm, ssl->_bio.get()); ::svm_process_interruptionnotification_disable(svm,current_process); ::svm_process_resume(svm); auto tunnel = attach(svm, context->_context.get(), client.release()); if(SSL_accept(tunnel.get()) <= 0) { auto aSS = print_error_stack("Creating SSL transaction context"); ERROR_INTERNAL(DEVICE, aSS.str().c_str()); } auto* clientCStruct = new struct_tcp{nullptr, std::move(tunnel)}; auto clientStruct = NEW_STRUCT(ssl, tcp, clientCStruct); SVM_Parameter clientParam = ::svm_parameter_structure_new(svm, clientStruct); SVM_Parameter clientParamArray[] = { clientParam }; auto clientDevice = ::svm_function_call(svm, CONST_PEP(com,device), 1, clientParamArray); return clientDevice; } else { ERROR_INTERNAL(FAILURE, "Invalid command"); } } RETURN; %} help: %{ %}