From 61349dcf5fbfeab888ea345ebec3d887777a2782 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Mon, 8 Oct 2018 23:01:16 +0300 Subject: Add support for openssl qualified options --- bpkg/auth.cxx | 47 +++++++++++++++++++----------- bpkg/common.cli | 32 +++++++++++++++++---- bpkg/options-types.hxx | 58 +++++++++++++++++++++++++++++++++++++ bpkg/repository-signing.cli | 11 ++++---- bpkg/types-parsers.hxx | 9 ++++++ bpkg/types-parsers.txx | 69 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 200 insertions(+), 26 deletions(-) create mode 100644 bpkg/types-parsers.txx diff --git a/bpkg/auth.cxx b/bpkg/auth.cxx index af49f37..48ba56f 100644 --- a/bpkg/auth.cxx +++ b/bpkg/auth.cxx @@ -26,6 +26,13 @@ using namespace butl; namespace bpkg { + static const string openssl_rsautl ("rsautl"); + static const string openssl_x509 ("x509"); + + const char* openssl_commands[] = {openssl_rsautl.c_str (), + openssl_x509.c_str (), + nullptr}; + // Print process command line. // static void @@ -139,12 +146,15 @@ namespace bpkg dr << ": " << *e; }; + const path& openssl_path (co.openssl ()[openssl_x509]); + const strings& openssl_opts (co.openssl_option ()[openssl_x509]); + try { openssl os (print_command, fdstream_mode::text, fdstream_mode::text, 2, - co.openssl (), "x509", - co.openssl_option (), "-sha256", "-noout", "-fingerprint"); + openssl_path, openssl_x509, + openssl_opts, "-sha256", "-noout", "-fingerprint"); os.out << pem; os.out.close (); @@ -175,7 +185,7 @@ namespace bpkg } catch (const process_error& e) { - error << "unable to execute " << co.openssl () << ": " << e; + error << "unable to execute " << openssl_path << ": " << e; // Fall through. } @@ -223,6 +233,9 @@ namespace bpkg dr << ": " << *e; }; + const path& openssl_path (co.openssl ()[openssl_x509]); + const strings& openssl_opts (co.openssl_option ()[openssl_x509]); + try { // The order of the options we pass to openssl determines the order in @@ -247,12 +260,8 @@ namespace bpkg openssl os ( print_command, fdstream_mode::text, fdstream_mode::text, 2, - co.openssl (), "x509", - co.openssl_option (), - "-noout", - "-subject", - "-dates", - "-email", + openssl_path, openssl_x509, + openssl_opts, "-noout", "-subject", "-dates", "-email", // Previously we have used "RFC2253,sep_multiline" format to display // the requested fields, but that resulted in some undesirable @@ -448,7 +457,7 @@ namespace bpkg } catch (const process_error& e) { - error << "unable to execute " << co.openssl () << ": " << e; + error << "unable to execute " << openssl_path << ": " << e; // Fall through. } @@ -818,12 +827,15 @@ namespace bpkg dr << ": " << *e; }; + const path& openssl_path (co.openssl ()[openssl_rsautl]); + const strings& openssl_opts (co.openssl_option ()[openssl_rsautl]); + try { openssl os (print_command, path ("-"), fdstream_mode::text, 2, - co.openssl (), "rsautl", - co.openssl_option (), "-verify", "-certin", "-inkey", f); + openssl_path, openssl_rsautl, + openssl_opts, "-verify", "-certin", "-inkey", f); for (const auto& c: sm.signature) os.out.put (c); // Sets badbit on failure. @@ -851,7 +863,7 @@ namespace bpkg } catch (const process_error& e) { - error << "unable to execute " << co.openssl () << ": " << e; + error << "unable to execute " << openssl_path << ": " << e; // Fall through. } @@ -903,12 +915,15 @@ namespace bpkg dr << ": " << *e; }; + const path& openssl_path (co.openssl ()[openssl_rsautl]); + const strings& openssl_opts (co.openssl_option ()[openssl_rsautl]); + try { openssl os (print_command, fdstream_mode::text, path ("-"), 2, - co.openssl (), "rsautl", - co.openssl_option (), "-sign", "-inkey", key_name); + openssl_path, openssl_rsautl, + openssl_opts, "-sign", "-inkey", key_name); os.out << sha256sum; os.out.close (); @@ -925,7 +940,7 @@ namespace bpkg } catch (const process_error& e) { - error << "unable to execute " << co.openssl () << ": " << e; + error << "unable to execute " << openssl_path << ": " << e; // Fall through. } diff --git a/bpkg/common.cli b/bpkg/common.cli index f001608..a8c363e 100644 --- a/bpkg/common.cli +++ b/bpkg/common.cli @@ -217,21 +217,43 @@ namespace bpkg multiple tar options." } - path --openssl = "openssl" + // NOTE: when adding support for a new openssl command, don't forget to + // update the openssl_commands qualifier list. + // + qualified_option --openssl = "openssl" { "", "The openssl program to be used for crypto operations. You can also specify additional options that should be passed to the openssl program with \cb{--openssl-option}. If the openssl program is not - explicitly specified, then \cb{bpkg} will use \cb{openssl} by default." + explicitly specified, then \cb{bpkg} will use \cb{openssl} by default. + + The \cb{--openssl*} values can be optionally qualified with the openssl + command in the \c{\i{command}\b{:}\i{value}} form. This makes the value + only applicable to the specific command, for example: + + \ + bpkg rep-create \ + --openssl rsautl:/path/to/openssl \ + --openssl-option rsautl:-engine \ + --openssl-option rsautl:pkcs11 \ + ... + \ + + An unqualified value that contains a colon can be specified as + qualified with an empty command, for example, \cb{--openssl + :C:\\bin\\openssl}. To see openssl commands executed by \cb{bpkg}, use + the verbose mode (\cb{-v} option)." } - strings --openssl-option + qualified_option --openssl-option { "", "Additional option to be passed to the openssl program. See - \cb{--openssl} for more information on the openssl program. Repeat this - option to specify multiple openssl options." + \cb{--openssl} for more information on the openssl program. + The values can be optionally qualified with the openssl command, + as discussed in \cb{--openssl}. Repeat this option to specify + multiple openssl options." } bpkg::auth --auth = bpkg::auth::remote diff --git a/bpkg/options-types.hxx b/bpkg/options-types.hxx index 373ef5e..3438be1 100644 --- a/bpkg/options-types.hxx +++ b/bpkg/options-types.hxx @@ -5,6 +5,10 @@ #ifndef BPKG_OPTIONS_TYPES_HXX #define BPKG_OPTIONS_TYPES_HXX +#include +#include +#include // move() + namespace bpkg { enum class auth @@ -13,6 +17,60 @@ namespace bpkg remote, all }; + + // Qualified options. + // + // An option that uses this type can have its values qualified using the + // : form, for example, '--option foo:bar' An unqualified + // value that contains a colon can be specified as qualified with an empty + // qualifier, for example, '--option :http://example.org'. Unqualified + // values apply to all the qualifiers in the order specified. + // + // The second template argument is a NULL-terminated list of valid qualifier + // strings, for example: + // + // const char* option_qualifiers[] = {"foo", "bar", nullptr}; + // + template + class qualified_option: public std::map + { + public: + using base_type = std::map; + + template + explicit + qualified_option (T v) {this->emplace ("", std::move (v));} + + qualified_option (): qualified_option (V ()) {} + + using base_type::operator[]; + + const V& + operator[] (const string& q) const + { + auto verify = [&q] () + { + for (const char** p (Q); *p != nullptr; ++p) + { + if (q == *p) + return true; + } + return q.empty (); + }; + + assert (verify ()); + + typename base_type::const_iterator i (this->find (q)); + + if (i == this->end ()) + i = this->find (""); + + assert (i != this->end ()); + return i->second; + } + }; + + extern const char* openssl_commands[]; } #endif // BPKG_OPTIONS_TYPES_HXX diff --git a/bpkg/repository-signing.cli b/bpkg/repository-signing.cli index 0fb59c9..776d82b 100644 --- a/bpkg/repository-signing.cli +++ b/bpkg/repository-signing.cli @@ -190,13 +190,14 @@ media, store it in a secure, offline location, and remove the key from the build machine. To sign the repository with Yubikey specify the following options instead of -just \cb{--key} as at step 4 (\c{\"SIGN key\"} is the name for slot \c{9c}): +just \cb{--key} as at step 4 (\c{\"SIGN key\"} is the label for the slot +\c{9c} private key): \ -bpkg rep-create \ - --openssl-option -engine --openssl-option pkcs11 \ - --openssl-option -keyform --openssl-option engine \ - --key \"label_SIGN key\" /path/to/repository +bpkg rep-create \ + --openssl-option rsautl:-engine --openssl-option rsautl:pkcs11 \ + --openssl-option rsautl:-keyform --openssl-option rsautl:engine \ + --key \"pkcs11:object=SIGN%20key\" /path/to/repository \ || diff --git a/bpkg/types-parsers.hxx b/bpkg/types-parsers.hxx index 3425e9d..24cc92d 100644 --- a/bpkg/types-parsers.hxx +++ b/bpkg/types-parsers.hxx @@ -46,7 +46,16 @@ namespace bpkg static void parse (repository_type&, bool&, scanner&); }; + + template + struct parser> + { + static void + parse (qualified_option&, bool&, scanner&); + }; } } +#include + #endif // BPKG_TYPES_PARSERS_HXX diff --git a/bpkg/types-parsers.txx b/bpkg/types-parsers.txx new file mode 100644 index 0000000..7a40b0e --- /dev/null +++ b/bpkg/types-parsers.txx @@ -0,0 +1,69 @@ +// file : bpkg/types-parsers.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +namespace bpkg +{ + namespace cli + { + template + void parser>:: + parse (qualified_option& x, bool& xs, scanner& s) + { + xs = true; + const char* o (s.next ()); + + if (!s.more ()) + throw missing_value (o); + + string v (s.next ()); + + // Extract the qualifier from the option value. + // + string qv; + size_t n (v.find (':')); + + if (n != string::npos) + { + const char** q (Q); + for (; *q != nullptr; ++q) + { + if (v.compare (0, n, *q) == 0) + { + qv = *q; + v = string (v, n + 1); + break; + } + } + + // Fail it the qualifier is not recognized, unless it is a special + // empty qualifier. + // + if (*q == nullptr && n != 0) + throw invalid_value (o, v); + } + + // Parse the value for the extracted (possibly empty) qualifier. + // + int ac (2); + char* av[] = {const_cast (o), const_cast (v.c_str ())}; + bool dummy; + + { + argv_scanner s (0, ac, av); + parser::parse (x[qv], dummy, s); + } + + // Parse an unqualified value for all qualifiers. + // + if (qv.empty ()) + { + for (const char** q (Q); *q != nullptr; ++q) + { + argv_scanner s (0, ac, av); + parser::parse (x[*q], dummy, s); + } + } + } + } +} -- cgit v1.1