diff options
author | Peter Stadler <peter.stadler@student.uibk.ac.at> | 2020-02-04 13:10:00 +0100 |
---|---|---|
committer | Peter Stadler <peter.stadler@student.uibk.ac.at> | 2020-02-09 13:02:54 +0100 |
commit | e247efa254599f0a2cf9178b2ef16b5ac6cc8fb8 (patch) | |
tree | 6df72a3c49077843ae491d2619df390c8fd9ea59 /net/nginx-util/src/nginx-ssl-util.cpp | |
parent | fed1b3b11bf9361dbbd158a1018c701d8180e49b (diff) |
nginx-util: add tests, clean up and fix issues
Add tests for nginx-ssl-util and nginx-ssl-util-nopcre using (fake)chroot.
Clean the code up making nginx-ssl-util a header file.
Both changes are for better (future) code quality only.
There are minor functional improvements:
* fix compiler error of gcc7 by using std=c++17
* fix error if there is no lan/loopback interface
* notice instead of error message if there is no default server
* add ipv6-prefix-assignment.*.local-address.address for LAN
* add CONFLICTS in Makefile for choosing the right version
* add cast to release of unique_ptr to avoid warning
* add version message to help message
Signed-off-by: Peter Stadler <peter.stadler@student.uibk.ac.at>
Diffstat (limited to 'net/nginx-util/src/nginx-ssl-util.cpp')
-rw-r--r-- | net/nginx-util/src/nginx-ssl-util.cpp | 660 |
1 files changed, 0 insertions, 660 deletions
diff --git a/net/nginx-util/src/nginx-ssl-util.cpp b/net/nginx-util/src/nginx-ssl-util.cpp deleted file mode 100644 index 7b268cbd4..000000000 --- a/net/nginx-util/src/nginx-ssl-util.cpp +++ /dev/null @@ -1,660 +0,0 @@ -#include <thread> - -#ifdef NO_PCRE -#include <regex> -namespace rgx = std; -#else -#include "regex-pcre.hpp" -#endif - -#include "nginx-util.hpp" -#include "px5g-openssl.hpp" - - -#ifndef NO_UBUS -static constexpr auto UBUS_TIMEOUT = 1000; -#endif - -// once a year: -static constexpr auto CRON_INTERVAL = std::string_view{"3 3 12 12 *"}; - -static constexpr auto LAN_SSL_LISTEN = - std::string_view{"/var/lib/nginx/lan_ssl.listen"}; - -static constexpr auto LAN_SSL_LISTEN_DEFAULT = - std::string_view{"/var/lib/nginx/lan_ssl.listen.default"}; - -static constexpr auto ADD_SSL_FCT = std::string_view{"add_ssl"}; - -static constexpr auto SSL_SESSION_CACHE_ARG = - [](const std::string_view & /*name*/) -> std::string - { return "shared:SSL:32k"; }; - -static constexpr auto SSL_SESSION_TIMEOUT_ARG = std::string_view{"64m"}; - - -using _Line = - std::array< std::string (*)(const std::string &, const std::string &), 2 >; - -class Line { - -private: - - _Line _line; - -public: - - explicit Line(const _Line & line) noexcept : _line{line} {} - - template<const _Line & ...xn> - static auto build() noexcept -> Line - { - return Line{_Line{ - [](const std::string & p, const std::string & b) -> std::string - { return (... + xn[0](p, b)); }, - [](const std::string & p, const std::string & b) -> std::string - { return (... + xn[1](p, b)); } - }}; - } - - - [[nodiscard]] auto STR(const std::string & param, const std::string & begin) - const -> std::string - { return _line[0](param, begin); } - - - [[nodiscard]] auto RGX() const -> rgx::regex - { return rgx::regex{_line[1]("", "")}; } - -}; - - -auto get_if_missed(const std::string & conf, const Line & LINE, - const std::string & val, - const std::string & indent="\n ", bool compare=true) - -> std::string; - - -auto delete_if(const std::string & conf, const rgx::regex & rgx, - const std::string & val="", bool compare=false) - -> std::string; - - -void add_ssl_directives_to(const std::string & name, bool isdefault); - - -void create_ssl_certificate(const std::string & crtpath, - const std::string & keypath, - int days=792); - - -void use_cron_to_recreate_certificate(const std::string & name); - - -void add_ssl_if_needed(const std::string & name); - - -void del_ssl_directives_from(const std::string & name, bool isdefault); - - -void del_ssl(const std::string & name); - - -constexpr auto _begin = _Line{ - [](const std::string & /*param*/, const std::string & begin) -> std::string - { return begin; }, - - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { return R"([{;](?:\s*#[^\n]*(?=\n))*(\s*))"; } -}; - - -constexpr auto _space = _Line{ - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { return std::string{" "}; }, - - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { return R"(\s+)"; } -}; - - -constexpr auto _newline = _Line{ - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { return std::string{"\n"}; }, - - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { return std::string{"\n"}; } -}; - - -constexpr auto _end = _Line{ - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { return std::string{";"}; }, - - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { return std::string{R"(\s*;(?:[\t ]*#[^\n]*)?)"}; } -}; - - -template<char clim='\0'> -constexpr auto _capture = _Line{ - [](const std::string & param, const std::string & /*begin*/) -> std::string - { return '\'' + param + '\''; }, - - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { - const auto lim = clim=='\0' ? std::string{"\\s"} : std::string{clim}; - return std::string{R"(((?:(?:"[^"]*")|(?:[^'")"} + - lim + "][^" + lim + "]*)|(?:'[^']*'))+)"; - } -}; - - -template<const std::string_view & strptr, char clim='\0'> -constexpr auto _escape = _Line{ - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { - return clim=='\0' ? - std::string{strptr.data()} : - clim + std::string{strptr.data()} + clim; - }, - - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { - std::string ret{}; - for (char c : strptr) { - switch(c) { - case '^': ret += '\\'; [[fallthrough]]; - case '_': [[fallthrough]]; - case '-': ret += c; - break; - default: - if ((isalpha(c)!=0) || (isdigit(c)!=0)) { ret += c; } - else { ret += std::string{"["}+c+"]"; } - } - } - return "(?:"+ret+"|'"+ret+"'"+"|\""+ret+"\""+")"; - } -}; - - -constexpr std::string_view _server_name = "server_name"; - -constexpr std::string_view _include = "include"; - -constexpr std::string_view _ssl_certificate = "ssl_certificate"; - -constexpr std::string_view _ssl_certificate_key = "ssl_certificate_key"; - -constexpr std::string_view _ssl_session_cache = "ssl_session_cache"; - -constexpr std::string_view _ssl_session_timeout = "ssl_session_timeout"; - - -// For a compile time regex lib, this must be fixed, use one of these options: -// * Hand craft or macro concat them (loosing more or less flexibility). -// * Use Macro concatenation of __VA_ARGS__ with the help of: -// https://p99.gforge.inria.fr/p99-html/group__preprocessor__for.html -// * Use constexpr---not available for strings or char * for now---look at lib. - -static const auto CRON_CMD = Line::build - < _space, _escape<NGINX_UTIL>, _space, _escape<ADD_SSL_FCT,'\''>, _space, - _capture<>, _newline >(); - -static const auto NGX_SERVER_NAME = - Line::build<_begin, _escape<_server_name>, _space, _capture<';'>, _end>(); - -static const auto NGX_INCLUDE_LAN_LISTEN = Line::build - <_begin, _escape<_include>, _space, _escape<LAN_LISTEN,'\''>, _end>(); - -static const auto NGX_INCLUDE_LAN_LISTEN_DEFAULT = Line::build - < _begin, _escape<_include>, _space, - _escape<LAN_LISTEN_DEFAULT, '\''>, _end >(); - -static const auto NGX_INCLUDE_LAN_SSL_LISTEN = Line::build - <_begin, _escape<_include>, _space, _escape<LAN_SSL_LISTEN, '\''>, _end>(); - -static const auto NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT = Line::build - < _begin, _escape<_include>, _space, - _escape<LAN_SSL_LISTEN_DEFAULT, '\''>, _end >(); - -static const auto NGX_SSL_CRT = Line::build - <_begin, _escape<_ssl_certificate>, _space, _capture<';'>, _end>(); - -static const auto NGX_SSL_KEY = Line::build - <_begin, _escape<_ssl_certificate_key>, _space, _capture<';'>, _end>(); - -static const auto NGX_SSL_SESSION_CACHE = Line::build - <_begin, _escape<_ssl_session_cache>, _space, _capture<';'>, _end>(); - -static const auto NGX_SSL_SESSION_TIMEOUT = Line::build - <_begin, _escape<_ssl_session_timeout>, _space, _capture<';'>, _end>(); - - -auto get_if_missed(const std::string & conf, const Line & LINE, - const std::string & val, - const std::string & indent, bool compare) - -> std::string -{ - if (!compare || val.empty()) { - return rgx::regex_search(conf, LINE.RGX()) ? "" : LINE.STR(val, indent); - } - - rgx::smatch match; // assuming last capture has the value! - - for (auto pos = conf.begin(); - rgx::regex_search(pos, conf.end(), match, LINE.RGX()); - pos += match.position(0) + match.length(0)) - { - const std::string value = match.str(match.size() - 1); - - if (value==val || value=="'"+val+"'" || value=='"'+val+'"') { - return ""; - } - } - - return LINE.STR(val, indent); -} - - -auto delete_if(const std::string & conf, const rgx::regex & rgx, - const std::string & val, const bool compare) - -> std::string -{ - std::string ret{}; - auto pos = conf.begin(); - - for (rgx::smatch match; - rgx::regex_search(pos, conf.end(), match, rgx); - pos += match.position(0) + match.length(0)) - { - const std::string value = match.str(match.size() - 1); - auto len = match.position(1); - if (compare && value!=val && value!="'"+val+"'" && value!='"'+val+'"') { - len = match.position(0) + match.length(0); - } - ret.append(pos, pos + len); - } - - ret.append(pos, conf.end()); - return ret; -} - - -void add_ssl_directives_to(const std::string & name, const bool isdefault) -{ - const std::string prefix = std::string{CONF_DIR} + name; - - std::string conf = read_file(prefix+".conf"); - - const std::string & const_conf = conf; // iteration needs const string. - rgx::smatch match; // captures str(1)=indentation spaces, str(2)=server name - for (auto pos = const_conf.begin(); - rgx::regex_search(pos, const_conf.end(), match, NGX_SERVER_NAME.RGX()); - pos += match.position(0) + match.length(0)) - { - if (match.str(2).find(name) == std::string::npos) { continue; } - - const std::string indent = match.str(1); - - std::string adds = isdefault ? - get_if_missed(conf, NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT,"",indent) : - get_if_missed(conf, NGX_INCLUDE_LAN_SSL_LISTEN, "", indent); - - adds += get_if_missed(conf, NGX_SSL_CRT, prefix+".crt", indent); - - adds += get_if_missed(conf, NGX_SSL_KEY, prefix+".key", indent); - - adds += get_if_missed(conf, NGX_SSL_SESSION_CACHE, - SSL_SESSION_CACHE_ARG(name), indent, false); - - adds += get_if_missed(conf, NGX_SSL_SESSION_TIMEOUT, - std::string{SSL_SESSION_TIMEOUT_ARG}, indent, false); - - if (adds.length() > 0) { - pos += match.position(0) + match.length(0); - - conf = std::string(const_conf.begin(), pos) + adds + - std::string(pos, const_conf.end()); - - conf = isdefault ? - delete_if(conf, NGX_INCLUDE_LAN_LISTEN_DEFAULT.RGX()) : - delete_if(conf, NGX_INCLUDE_LAN_LISTEN.RGX()); - - write_file(prefix+".conf", conf); - - std::cerr<<"Added SSL directives to "<<prefix<<".conf: "; - std::cerr<<adds<<std::endl; - } - - return; - } - - auto errmsg = std::string{"add_ssl_directives_to error: "}; - errmsg += "cannot add SSL directives to " + name + ".conf, missing: "; - errmsg += NGX_SERVER_NAME.STR(name, "\n ") + "\n"; - throw std::runtime_error(errmsg.c_str()); -} - - -template<typename T> -inline auto num2hex(T bytes) -> std::array<char, 2*sizeof(bytes)+1> -{ - constexpr auto n = 2*sizeof(bytes); - std::array<char, n+1> str{}; - - for (size_t i=0; i<n; ++i) { - static const std::array<char, 17> hex{"0123456789ABCDEF"}; - static constexpr auto get = 0x0fU; - str.at(i) = hex.at(bytes & get); - - static constexpr auto move = 4U; - bytes >>= move; - } - - str[n] = '\0'; - return str; -} - - -template<typename T> -inline auto get_nonce(const T salt=0) -> T -{ - T nonce = 0; - - std::ifstream urandom{"/dev/urandom"}; - - static constexpr auto move = 6U; - - constexpr size_t steps = (sizeof(nonce)*8 - 1)/move + 1; - - for (size_t i=0; i<steps; ++i) { - if (!urandom.good()) { throw std::runtime_error("get_nonce error"); } - nonce = (nonce << move) + static_cast<unsigned>(urandom.get()); - } - - nonce ^= salt; - - return nonce; -} - - -void create_ssl_certificate(const std::string & crtpath, - const std::string & keypath, - const int days) -{ - size_t nonce = 0; - - try { nonce = get_nonce(nonce); } - - catch (...) { // the address of a variable should be random enough: - auto addr = &crtpath; - auto addrptr = static_cast<const size_t *>( - static_cast<const void *>(&addr) ); - nonce += *addrptr; - } - - auto noncestr = num2hex(nonce); - - const auto tmpcrtpath = crtpath + ".new-" + noncestr.data(); - const auto tmpkeypath = keypath + ".new-" + noncestr.data(); - - try { - auto pkey = gen_eckey(NID_secp384r1); - - write_key(pkey, tmpkeypath); - - std::string subject {"/C=ZZ/ST=Somewhere/L=None/CN=OpenWrt/O=OpenWrt"}; - subject += noncestr.data(); - - selfsigned(pkey, days, subject, tmpcrtpath); - - static constexpr auto to_seconds = 24*60*60; - static constexpr auto leeway = 42; - if (!checkend(tmpcrtpath, days*to_seconds - leeway)) { - throw std::runtime_error("bug: created certificate is not valid!!"); - } - - } catch (...) { - std::cerr<<"create_ssl_certificate error: "; - std::cerr<<"cannot create selfsigned certificate, "; - std::cerr<<"removing temporary files ..."<<std::endl; - - if (remove(tmpcrtpath.c_str())!=0) { - auto errmsg = "\t cannot remove "+tmpcrtpath; - perror(errmsg.c_str()); - } - - if (remove(tmpkeypath.c_str())!=0) { - auto errmsg = "\t cannot remove "+tmpkeypath; - perror(errmsg.c_str()); - } - - throw; - } - - if ( rename(tmpcrtpath.c_str(), crtpath.c_str())!=0 || - rename(tmpkeypath.c_str(), keypath.c_str())!=0 ) - { - auto errmsg = std::string{"create_ssl_certificate warning: "}; - errmsg += "cannot move "+tmpcrtpath+" to "+crtpath; - errmsg += " or "+tmpkeypath+" to "+keypath+", continuing ... "; - perror(errmsg.c_str()); - } - -} - - -void use_cron_to_recreate_certificate(const std::string & name) -{ - static const char * filename = "/etc/crontabs/root"; - - std::string conf{}; - try { conf = read_file(filename); } - catch (const std::ifstream::failure &) { /* is ok if not found, create. */ } - - const std::string add = get_if_missed(conf, CRON_CMD, name); - - if (add.length() > 0) { -#ifndef NO_UBUS - auto service = ubus::call("service","list",UBUS_TIMEOUT).filter("cron"); - - if (!service) { - std::string errmsg{"use_cron_to_recreate_certificate error: "}; - errmsg += "Cron unavailable to re-create the ssl certificate for "; - errmsg += name + "\n"; - throw std::runtime_error(errmsg.c_str()); - } // else active with or without instances: -#endif - - write_file(filename, std::string{CRON_INTERVAL}+add, std::ios::app); - -#ifndef NO_UBUS - call("/etc/init.d/cron", "reload"); -#endif - - std::cerr<<"Rebuild the ssl certificate for '"; - std::cerr<<name<<"' annually with cron."<<std::endl; - } -} - - -void add_ssl_if_needed(const std::string & name) -{ - try { add_ssl_directives_to(name, name==LAN_NAME); } - catch (...) { - std::cerr<<"add_ssl_if_needed error: "; - std::cerr<<"cannot add SSL directives to "<<name<<".conf"<<std::endl; - throw; - } - - const auto crtpath = std::string{CONF_DIR} + name + ".crt"; - const auto keypath = std::string{CONF_DIR} + name + ".key"; - constexpr auto remaining_seconds = (365 + 32)*24*60*60; - constexpr auto validity_days = 3*(365 + 31); - - bool is_valid = true; - - if (access(keypath.c_str(), R_OK) != 0 || - access(crtpath.c_str(), R_OK) != 0) - { is_valid = false; } - - else { - try { - if (!checkend(crtpath, remaining_seconds)) { - is_valid = false; - } - } - catch (...) { // something went wrong, maybe it is in DER format: - try { - if (!checkend(crtpath, remaining_seconds, false)) { - is_valid = false; - } - } - catch (...) { // it has neither DER nor PEM format, rebuild. - is_valid = false; - } - } - } - - if (!is_valid) { create_ssl_certificate(crtpath, keypath, validity_days); } - - try { use_cron_to_recreate_certificate(name); } - catch (...) { - std::cerr<<"add_ssl_if_needed warning: "; - std::cerr<<"cannot use cron to rebuild certificate for "<<name<<"\n"; - } -} - - -void del_ssl_directives_from(const std::string & name, const bool isdefault) -{ - const std::string prefix = std::string{CONF_DIR} + name; - - std::string conf = read_file(prefix+".conf"); - - const std::string & const_conf = conf; // iteration needs const string. - rgx::smatch match; // captures str(1)=indentation spaces, str(2)=server name - for (auto pos = const_conf.begin(); - rgx::regex_search(pos, const_conf.end(), match, NGX_SERVER_NAME.RGX()); - pos += match.position(0) + match.length(0)) - { - if (match.str(2).find(name) == std::string::npos) { continue; } - - const std::string indent = match.str(1); - - std::string adds = isdefault ? - get_if_missed(conf, NGX_INCLUDE_LAN_LISTEN_DEFAULT,"",indent) : - get_if_missed(conf, NGX_INCLUDE_LAN_LISTEN, "", indent); - - if (adds.length() > 0) { - pos += match.position(1); - - conf = std::string(const_conf.begin(), pos) + adds - + std::string(pos, const_conf.end()); - - conf = isdefault ? - delete_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT.RGX()) - : delete_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN.RGX()); - - const auto crtpath = prefix+".crt"; - conf = delete_if(conf, NGX_SSL_CRT.RGX(), crtpath, true); - - const auto keypath = prefix+".key"; - conf = delete_if(conf, NGX_SSL_KEY.RGX(), keypath, true); - - conf = delete_if(conf, NGX_SSL_SESSION_CACHE.RGX()); - - conf = delete_if(conf, NGX_SSL_SESSION_TIMEOUT.RGX()); - - write_file(prefix+".conf", conf); - - std::cerr<<"Deleted SSL directives from "<<prefix<<".conf\n"; - } - - return; - } - - auto errmsg = std::string{"del_ssl_directives_from error: "}; - errmsg += "cannot delete SSL directives from " + name + ".conf, missing: "; - errmsg += NGX_SERVER_NAME.STR(name, "\n ") + "\n"; - throw std::runtime_error(errmsg.c_str()); -} - - -void del_ssl(const std::string & name) -{ - static const char * filename = "/etc/crontabs/root"; - - try { - const auto const_conf = read_file(filename); - - bool changed = false; - auto conf = std::string{}; - - size_t prev = 0; - size_t curr = 0; - while ((curr=const_conf.find('\n', prev)) != std::string::npos) { - - auto line = const_conf.substr(prev, curr-prev+1); - - if (line==delete_if(line,CRON_CMD.RGX(),std::string{name},true)) { - conf += line; - } else { changed = true; } - - prev = curr + 1; - } - - if (changed) { - write_file(filename, conf); - - std::cerr<<"Do not rebuild the ssl certificate for '"; - std::cerr<<name<<"' annually with cron anymore."<<std::endl; - -#ifndef NO_UBUS - if (ubus::call("service", "list", UBUS_TIMEOUT).filter("cron")) - { call("/etc/init.d/cron", "reload"); } -#endif - } - - } catch (...) { - std::cerr<<"del_ssl warning: "; - std::cerr<<"cannot delete cron job for "<<name<<" in "<<filename<<"\n"; - } - - try { del_ssl_directives_from(name, name==LAN_NAME); } - catch (...) { - std::cerr<<"del_ssl error: "; - std::cerr<<"cannot delete SSL directives from "<<name<<".conf\n"; - throw; - } - - const auto crtpath = std::string{CONF_DIR} + name + ".crt"; - - if (remove(crtpath.c_str())!=0) { - auto errmsg = "del_ssl warning: cannot remove "+crtpath; - perror(errmsg.c_str()); - } - - const auto keypath = std::string{CONF_DIR} + name + ".key"; - - if (remove(keypath.c_str())!=0) { - auto errmsg = "del_ssl warning: cannot remove "+keypath; - perror(errmsg.c_str()); - } -} - - -// reuse main(...) and common functions: -#define NGINX_OPENSSL -#include "nginx-util.cpp" |