diff options
Diffstat (limited to 'bpkg/auth.cxx')
-rw-r--r-- | bpkg/auth.cxx | 222 |
1 files changed, 163 insertions, 59 deletions
diff --git a/bpkg/auth.cxx b/bpkg/auth.cxx index e661ad0..191da0a 100644 --- a/bpkg/auth.cxx +++ b/bpkg/auth.cxx @@ -7,11 +7,11 @@ #include <limits> // numeric_limits #include <iterator> // ostreambuf_iterator -#include <libbutl/sha256.mxx> -#include <libbutl/base64.mxx> -#include <libbutl/openssl.mxx> -#include <libbutl/timestamp.mxx> -#include <libbutl/filesystem.mxx> +#include <libbutl/base64.hxx> +#include <libbutl/openssl.hxx> +#include <libbutl/timestamp.hxx> +#include <libbutl/filesystem.hxx> +#include <libbutl/semantic-version.hxx> #include <bpkg/package.hxx> #include <bpkg/package-odb.hxx> @@ -23,11 +23,15 @@ using namespace butl; namespace bpkg { - static const string openssl_rsautl ("rsautl"); - static const string openssl_x509 ("x509"); - - const char* openssl_commands[3] = {openssl_rsautl.c_str (), - openssl_x509.c_str (), + static const string openssl_version_cmd ("version"); + static const string openssl_pkeyutl_cmd ("pkeyutl"); + static const string openssl_rsautl_cmd ("rsautl"); + static const string openssl_x509_cmd ("x509"); + + const char* openssl_commands[5] = {openssl_version_cmd.c_str (), + openssl_pkeyutl_cmd.c_str (), + openssl_rsautl_cmd.c_str (), + openssl_x509_cmd.c_str (), nullptr}; // Print process command line. @@ -39,6 +43,73 @@ namespace bpkg print_process (args, n); } + // Query the openssl information and return the openssl version. Cache the + // version on the first function call. Fail on the underlying process and IO + // error. Return the 0.0.0 version if unable to parse the openssl stdout. + // + static optional<semantic_version> openssl_ver; + + static const semantic_version& + openssl_version (const common_options& co) + { + const path& openssl_path (co.openssl ()[openssl_version_cmd]); + + if (!openssl_ver) + try + { + optional<openssl_info> oi ( + openssl::info (print_command, 2, openssl_path)); + + openssl_ver = (oi && oi->name == "OpenSSL" + ? move (oi->version) + : semantic_version ()); + } + catch (const process_error& e) + { + fail << "unable to execute " << openssl_path << ": " << e << endf; + } + catch (const io_error& e) + { + fail << "unable to read '" << openssl_path << "' output: " << e + << endf; + } + + return *openssl_ver; + } + + // Return true if the openssl version is greater or equal to 3.0.0 and so + // pkeyutl needs to be used instead of rsautl. + // + // Note that openssl 3.0.0 deprecates rsautl in favor of pkeyutl. + // + // Also note that pkeyutl is only implemented in openssl version 1.0.0 and + // its -verifyrecover mode is broken in the [1.1.1 1.1.1d] version range + // (see the 'pkeyutl -verifyrecover error "input data too long to be a + // hash"' issue report for details). + // + static inline bool + use_openssl_pkeyutl (const common_options& co) + { + return openssl_version (co) >= semantic_version {3, 0, 0}; + } + + // Return true if some openssl commands (openssl x509 -fingerprint, etc) may + // issue the 'Reading certificate from stdin since no -in or -new option is + // given' warning. This is the case for the openssl version in the [3.2.0 + // 3.3.0) range (see GH issue #353 for details). + // + // Note that there is no easy way to suppress this warning on Windows and + // thus we don't define this function there. + // +#ifndef _WIN32 + static inline bool + openssl_warn_stdin (const common_options& co) + { + const semantic_version& v (openssl_version (co)); + return v >= semantic_version {3, 2, 0} && v < semantic_version {3, 3, 0}; + } +#endif + // Find the repository location prefix that ends with the version component. // We consider all repositories under this location to be related. // @@ -61,14 +132,14 @@ namespace bpkg p /= "."; - // If this is a remote location then use the canonical name prefix. For - // a local location this doesn't always work. Consider: + // If this is a remote location then use the canonical name prefix. For a + // local location this doesn't always work. Consider: // // .../pkg/1/build2.org/common/hello // - // In this case we will end with an empty canonical name (because of - // the special pkg/1 treatment). So in case of local locations we will - // use the location rather than the name prefix. + // In this case we will end with an empty canonical name (because of the + // special pkg/1 treatment). So in case of local locations we will use the + // location rather than the name prefix. // if (rl.remote ()) return repository_location ( @@ -143,15 +214,25 @@ namespace bpkg dr << ": " << *e; }; - const path& openssl_path (co.openssl ()[openssl_x509]); - const strings& openssl_opts (co.openssl_option ()[openssl_x509]); + const path& openssl_path (co.openssl ()[openssl_x509_cmd]); + const strings& openssl_opts (co.openssl_option ()[openssl_x509_cmd]); try { openssl os (print_command, fdstream_mode::text, fdstream_mode::text, 2, - openssl_path, openssl_x509, - openssl_opts, "-sha256", "-noout", "-fingerprint"); + openssl_path, openssl_x509_cmd, + openssl_opts, + "-sha256", + "-noout", + "-fingerprint" +#ifndef _WIN32 + , + (openssl_warn_stdin (co) + ? cstrings ({"-in", "/dev/stdin"}) + : cstrings ()) +#endif + ); os.out << pem; os.out.close (); @@ -160,21 +241,32 @@ namespace bpkg getline (os.in, s); os.in.close (); - try + if (os.wait ()) { - const size_t n (19); - - if (os.wait () && - s.size () > n && s.compare (0, n, "SHA256 Fingerprint=") == 0) + // Normally the output is: + // + // SHA256 Fingerprint=<fingerprint> + // + // But it can be translated and SHA spelled in lower case (LC_ALL=C + // doesn't seem to help in some cases). + // + if (icasecmp (s, "SHA256", 6) == 0) { - string fp (s, n); - string ab (fingerprint_to_sha256 (fp, 16)); - return {move (fp), move (ab)}; + size_t p (s.find ('=')); + if (p != string::npos) + { + try + { + string fp (s, p + 1); + string ab (fingerprint_to_sha256 (fp, 16)); + return {move (fp), move (ab)}; + } + catch (const invalid_argument&) + { + } + } } } - catch (const invalid_argument&) - { - } calc_failed (); @@ -230,8 +322,8 @@ namespace bpkg dr << ": " << *e; }; - const path& openssl_path (co.openssl ()[openssl_x509]); - const strings& openssl_opts (co.openssl_option ()[openssl_x509]); + const path& openssl_path (co.openssl ()[openssl_x509_cmd]); + const strings& openssl_opts (co.openssl_option ()[openssl_x509_cmd]); try { @@ -257,7 +349,7 @@ namespace bpkg openssl os ( print_command, fdstream_mode::text, fdstream_mode::text, 2, - openssl_path, openssl_x509, + openssl_path, openssl_x509_cmd, openssl_opts, "-noout", "-subject", "-dates", "-email", // Previously we have used "RFC2253,sep_multiline" format to display @@ -289,6 +381,13 @@ namespace bpkg // sep_multiline - display field per line. // "-nameopt", "utf8,esc_ctrl,dump_nostr,dump_der,sname,sep_multiline" + +#ifndef _WIN32 + , + (openssl_warn_stdin (co) + ? cstrings ({"-in", "/dev/stdin"}) + : cstrings ()) +#endif ); // We unset failbit to provide the detailed error description (which @@ -527,7 +626,8 @@ namespace bpkg return cert_auth {move (cert), true}; } - if (dependent_trust && *dependent_trust == cert->fingerprint) + if (dependent_trust && + icasecmp (*dependent_trust, cert->fingerprint) == 0) { if (verb >= 2) info << "certificate for repository " << rl.canonical_name () << @@ -560,7 +660,6 @@ namespace bpkg // static shared_ptr<certificate> auth_cert (const common_options& co, - const dir_path& conf, database& db, const optional<string>& pem, const repository_location& rl, @@ -602,7 +701,7 @@ namespace bpkg // if (pem) { - path f (conf / certs_dir / path (cert->id + ".pem")); + path f (db.config_orig / certs_dir / path (cert->id + ".pem")); try { @@ -623,6 +722,7 @@ namespace bpkg shared_ptr<const certificate> authenticate_certificate (const common_options& co, const dir_path* conf, + database* db, const optional<string>& pem, const repository_location& rl, const optional<string>& dependent_trust) @@ -632,15 +732,12 @@ namespace bpkg if (co.trust_no () && co.trust_yes ()) fail << "--trust-yes and --trust-no are mutually exclusive"; - if (conf != nullptr && conf->empty ()) - conf = exists (bpkg_dir) ? ¤t_dir : nullptr; - - assert (conf == nullptr || !conf->empty ()); - shared_ptr<certificate> r; if (conf == nullptr) { + assert (db == nullptr); + // If we have no configuration, go straight to authenticating a new // certificate. // @@ -649,20 +746,21 @@ namespace bpkg ? auth_real (co, fp, *pem, rl, dependent_trust).cert : auth_dummy (co, fp.abbreviated, rl); } - else if (transaction::has_current ()) + else if (db != nullptr) { + assert (transaction::has_current ()); + r = auth_cert (co, - *conf, - transaction::current ().database (), + *db, pem, rl, dependent_trust); } else { - database db (open (*conf, trace)); + database db (*conf, trace, false /* pre_attach */); transaction t (db); - r = auth_cert (co, *conf, db, pem, rl, dependent_trust); + r = auth_cert (co, db, pem, rl, dependent_trust); t.commit (); } @@ -679,11 +777,6 @@ namespace bpkg { tracer trace ("authenticate_repository"); - if (conf != nullptr && conf->empty ()) - conf = exists (bpkg_dir) ? ¤t_dir : nullptr; - - assert (conf == nullptr || !conf->empty ()); - path f; auto_rmfile rm; @@ -698,7 +791,7 @@ namespace bpkg try { - rm = tmp_file ("cert"); + rm = tmp_file (conf != nullptr ? *conf : empty_dir_path, "cert"); f = rm.path; ofdstream ofs (f); @@ -824,15 +917,22 @@ namespace bpkg dr << ": " << *e; }; - const path& openssl_path (co.openssl ()[openssl_rsautl]); - const strings& openssl_opts (co.openssl_option ()[openssl_rsautl]); + bool ku (use_openssl_pkeyutl (co)); + const string& cmd (ku ? openssl_pkeyutl_cmd : openssl_rsautl_cmd); + + const path& openssl_path (co.openssl ()[cmd]); + const strings& openssl_opts (co.openssl_option ()[cmd]); try { openssl os (print_command, path ("-"), fdstream_mode::text, 2, - openssl_path, openssl_rsautl, - openssl_opts, "-verify", "-certin", "-inkey", f); + openssl_path, cmd, + openssl_opts, + ku ? "-verifyrecover" : "-verify", + "-certin", + "-inkey", + f); for (const auto& c: sm.signature) os.out.put (c); // Sets badbit on failure. @@ -913,14 +1013,18 @@ namespace bpkg dr << ": " << *e; }; - const path& openssl_path (co.openssl ()[openssl_rsautl]); - const strings& openssl_opts (co.openssl_option ()[openssl_rsautl]); + const string& cmd (use_openssl_pkeyutl (co) + ? openssl_pkeyutl_cmd + : openssl_rsautl_cmd); + + const path& openssl_path (co.openssl ()[cmd]); + const strings& openssl_opts (co.openssl_option ()[cmd]); try { openssl os (print_command, fdstream_mode::text, path ("-"), 2, - openssl_path, openssl_rsautl, + openssl_path, cmd, openssl_opts, "-sign", "-inkey", key_name); os.out << sha256sum; |