diff options
Diffstat (limited to 'openssl/client')
-rw-r--r-- | openssl/client/client.cxx | 191 | ||||
-rw-r--r-- | openssl/client/options.cli | 109 |
2 files changed, 300 insertions, 0 deletions
diff --git a/openssl/client/client.cxx b/openssl/client/client.cxx new file mode 100644 index 0000000..42c4f92 --- /dev/null +++ b/openssl/client/client.cxx @@ -0,0 +1,191 @@ +// file : openssl/client/client.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <iostream> // cin, cout + +#include <libbutl/pager.mxx> + +#include <openssl/protocol.hxx> +#include <openssl/diagnostics.hxx> + +#include <openssl/client/options.hxx> + +namespace openssl +{ + namespace client + { + using namespace std; + using namespace butl; + + static int + main (int argc, char* argv[]) + try + { + // Parse the command/options. + // + string cmd; + options ops; + + int i (1); + if (argc > i && *argv[i] != '-') + cmd = argv[i++]; + + cli::argv_scanner scan (i, argc, argv); + + ops.parse (scan); + + // Version. + // + if (ops.version ()) + { + cout << "openssl-client " << OPENSSL_AGENT_VERSION_ID << endl + << "libbutl " << LIBBUTL_VERSION_ID << endl + << "Copyright (c) 2014-2018 Code Synthesis Ltd" << endl + << "This is free software released under the MIT license." + << endl; + + return 0; + } + + // Help. + // + if (ops.help ()) + { + pager p ("openssl-client help", false); + print_openssl_client_usage (p.stream ()); + + // If the pager failed, assume it has issued some diagnostics. + // + return p.wait () ? 0 : 1; + } + + if (cmd != "rsautl") + fail << "openssl-client command expected" << + info << "run '" << argv[0] << " --help' for more information"; + + if (!ops.sign ()) + fail << "-sign option is required"; + + if (!ops.keyform_specified ()) + fail << "-keyform option is required"; + + if (ops.keyform () != "engine") + fail << "invalid value '" << ops.keyform () + << "' for option -keyform"; + + if (!ops.engine_specified ()) + fail << "-engine option is required"; + + if (ops.engine () != "pkcs11") + fail << "invalid value '" << ops.engine () << "' for option -engine"; + + if (!ops.inkey_specified ()) + fail << "-inkey option is required"; + + // Obtain the agent socket path. + // + path sock_path; + + try + { + optional<string> p (getenv ("OPENSSL_AGENT_PKCS11_SOCK")); + + if (!p) + fail << "OPENSSL_AGENT_PKCS11_SOCK environment variable is not set"; + + sock_path = path (move (*p)); + } + catch (const invalid_path& e) + { + fail << "invalid OPENSSL_AGENT_PKCS11_SOCK environment variable " + << "value '" << e.path << "'"; + } + + // Read the data to sign from stdin. + // + vector<char> data; + + try + { + stdin_fdmode (fdstream_mode::binary); + + cin.exceptions (ostream::badbit | ostream::failbit); + + data = vector<char> (istreambuf_iterator<char> (cin), + istreambuf_iterator<char> ()); + } + catch (const io_error&) + { + fail << "unable to read data from stdin"; + } + + // Sign the data. + // + vector<char> signature; + + try + { + auto_fd sock (connect (sock_path)); + + ifdstream is (fddup (sock.get ())); + ofdstream os (move (sock)); + + string s (ops.simulate_specified () + ? to_string (ops.simulate ()) + : ""); + + os << request ("sign", + strings ({ops.inkey (), move (s)}), + move (data)); + os.close (); + + response r; + is >> r; + + if (r.status != 0) + { + text << r.error; + return r.status; + } + + signature = move (r.output); + } + catch (const io_error&) + { + fail << "unable to communicate with openssl-agent-pkcs11"; + } + + // Write it out. + // + try + { + stdout_fdmode (fdstream_mode::binary); + + cout.exceptions (ostream::badbit | ostream::failbit); + cout.write (signature.data (), signature.size ()); + } + catch (const io_error&) + { + fail << "error: unable to write signature to stdout"; + } + + return 0; + } + catch (const failed&) + { + return 1; // Diagnostics has already been issued. + } + catch (const cli::exception& e) + { + error << e; + return 1; + } + } +} + +int +main (int argc, char* argv[]) +{ + return openssl::client::main (argc, argv); +} diff --git a/openssl/client/options.cli b/openssl/client/options.cli new file mode 100644 index 0000000..4a2872a --- /dev/null +++ b/openssl/client/options.cli @@ -0,0 +1,109 @@ +// file : openssl/client/options.cli +// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +include <openssl/options.cli>; + +"\section=1" +"\name=openssl-client" +"\summary=OpenSSL client" + +namespace openssl +{ + namespace client + { + { + "<options>", + + " + \h|SYNOPSIS| + + \c{\b{openssl-client --help}\n + \b{openssl-client --version}\n + \b{openssl-client} rsautl [<options>]} + + \h|DESCRIPTION| + + The \cb{rsautl} command is a drop-in replacement for the + \cb{openssl-rsautl(1)} cryptographic operations. Instead of performing + the operations itself, it forwards the request to an OpenSSL key agent + that keeps the private key unlocked for the session. + + Currently, data signing with a private key stored in a \cb{PKCS#11} + token is the only supported arrangement. This limits the + \cb{openssl-rsautl(1)} options and values to the following usage: + + \ + $ openssl-client rsautl -sign -keyform engine -engine pkcs11 -inkey pkcs11:... + \ + + This command reads data from \cb{stdin}, asks + \cb{openssl-agent-pkcs11(1)} to sign it using the specified unlocked + private key, and prints the resulting signature to \cb{stdout}. + + The command can be simulated without actually performing any + cryptographic operations. If the \cb{--simulate} option is specified + with the \cb{success} outcome, then the command prints a dummy signature + produced by the agent and exits with zero status. The \cb{failure} + outcome causes it to print the diagnostics to \cb{stderr} and exit with + non-zero status. This mode is mostly useful for OpenSSL key agents + testing. + " + } + + class options + { + "\h|OPTIONS|" + + bool --help {"Print usage information and exit."} + bool --version {"Print version and exit."} + + bool -sign + { + "Sign data read from \cb{stdin}." + } + + string -keyform + { + "<form>", + "Private key format. The only supported format is \cb{engine}." + } + + string -engine + { + "<engine>", + "Engine to use for the cryptographic operation. The only supported + engine is \cb{pkcs11}." + } + + string -inkey + { + "<location>", + "Private key location. Its format (file path, URL, etc) depends on the + engine used. For the \cb{pkcs11} engine it should be a \cb{PKCS#11} + URL." + } + + simulate_outcome --simulate + { + "<outcome>", + "Ask the agent to simulate the cryptographic operation instead of + performing it for real." + } + }; + + " + \h|ENVIRONMENT| + + If \cb{-engine} is \cb{pkcs11}, then the \cb{OPENSSL_AGENT_PKCS11_SOCK} + environment variable should be set to the Unix-domain socket of the + \cb{openssl-agent-pkcs11(1)} daemon. + " + + " + \h|EXIT STATUS| + + Non-zero exit status is returned in case of an error. + " + } +} |