From e054f34f74ee05639189ee6ca5089a63fca386d4 Mon Sep 17 00:00:00 2001 From: Peter Stadler Date: Thu, 5 Dec 2019 08:48:20 +0100 Subject: nginx-util: add package This can do the main work of nginx/nginx-ssl init script. For nginx-ssl it can create selfsigned certificates, too. It uses libpcre and libopenssl iff nginx(-ssl) uses them. Signed-off-by: Peter Stadler --- net/nginx-util/src/nginx-ssl-util.cpp | 658 ++++++++++++++++++++++++++++++++++ 1 file changed, 658 insertions(+) create mode 100644 net/nginx-util/src/nginx-ssl-util.cpp (limited to 'net/nginx-util/src/nginx-ssl-util.cpp') diff --git a/net/nginx-util/src/nginx-ssl-util.cpp b/net/nginx-util/src/nginx-ssl-util.cpp new file mode 100644 index 000000000..f4a857397 --- /dev/null +++ b/net/nginx-util/src/nginx-ssl-util.cpp @@ -0,0 +1,658 @@ +#include + +#ifdef NO_PCRE +#include +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 + 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); + + +static 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*))"; } +}; + + +static 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+)"; } +}; + + +static 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"}; } +}; + + +static 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*;)"}; } +}; + + +template +static 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 +static constexpr auto _escape = _Line{ + [](const std::string & /*param*/, const std::string & /*begin*/) + -> std::string + { return 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+"\""+")"; + } +}; + + +static constexpr std::string_view _server_name = "server_name"; + +static constexpr std::string_view _include = "include"; + +static constexpr std::string_view _ssl_certificate = "ssl_certificate"; + +static constexpr std::string_view _ssl_certificate_key = "ssl_certificate_key"; + +static constexpr std::string_view _ssl_session_cache = "ssl_session_cache"; + +static 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, _space, _escape, _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, _end>(); + +static const auto NGX_INCLUDE_LAN_LISTEN_DEFAULT = Line::build + < _begin, _escape<_include>, _space, + _escape, _end >(); + +static const auto NGX_INCLUDE_LAN_SSL_LISTEN = Line::build + <_begin, _escape<_include>, _space, _escape, _end>(); + +static const auto NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT = Line::build + < _begin, _escape<_include>, _space, + _escape, _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_view 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_view value = match.str(match.size() - 1); + + auto skip = 1; // one for delimiter! + if (compare && value!=val && value!="'"+val+"'" && value!='"'+val+'"') { + skip = match.length(0); + } + ret.append(pos, pos + match.position(0) + skip); + } + + 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 "< +inline auto num2hex(T bytes) -> std::array +{ + constexpr auto n = 2*sizeof(bytes); + std::array str{}; + + for (size_t i=0; i 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 +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(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( + static_cast(&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 ..."< 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< 0) { + pos += match.position(0) + 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 "<