Add openssl utility functions. Review: https://reviews.apache.org/r/35854
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/0b04fec9 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/0b04fec9 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/0b04fec9 Branch: refs/heads/master Commit: 0b04fec909afc0f744503ac59b842707d8f5d624 Parents: 69e0f5a Author: Joris Van Remoortere <[email protected]> Authored: Thu Jun 25 21:05:31 2015 -0700 Committer: Benjamin Hindman <[email protected]> Committed: Thu Jun 25 21:05:31 2015 -0700 ---------------------------------------------------------------------- 3rdparty/libprocess/Makefile.am | 4 +- 3rdparty/libprocess/src/openssl_util.cpp | 248 ++++++++++++++++++++++++++ 3rdparty/libprocess/src/openssl_util.hpp | 97 ++++++++++ 3 files changed, 348 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/0b04fec9/3rdparty/libprocess/Makefile.am ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/Makefile.am b/3rdparty/libprocess/Makefile.am index c781f6c..0a14eb9 100644 --- a/3rdparty/libprocess/Makefile.am +++ b/3rdparty/libprocess/Makefile.am @@ -66,7 +66,9 @@ if ENABLE_SSL libprocess_la_SOURCES += \ src/libevent_ssl_socket.cpp \ src/openssl.cpp \ - src/openssl.hpp + src/openssl.hpp \ + src/openssl_util.cpp \ + src/openssl_util.hpp endif libprocess_la_CPPFLAGS = \ http://git-wip-us.apache.org/repos/asf/mesos/blob/0b04fec9/3rdparty/libprocess/src/openssl_util.cpp ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/src/openssl_util.cpp b/3rdparty/libprocess/src/openssl_util.cpp new file mode 100644 index 0000000..cd38f17 --- /dev/null +++ b/3rdparty/libprocess/src/openssl_util.cpp @@ -0,0 +1,248 @@ +#include "openssl_util.hpp" + +#include <openssl/err.h> +#include <openssl/rsa.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> + +#include <stout/net.hpp> +#include <stout/stringify.hpp> + +// TODO(jmlvanre): Add higher level abstractions for key and +// certificate generation. + +namespace process { +namespace network { +namespace openssl { + +Try<EVP_PKEY*> generate_private_rsa_key(int bits, unsigned long _exponent) +{ + // Allocate the in-memory structure for the private key. + EVP_PKEY* private_key = EVP_PKEY_new(); + if (private_key == NULL) { + return Error("Failed to allocate key: EVP_PKEY_new"); + } + + // Allocate space for the exponent. + BIGNUM* exponent = BN_new(); + if (exponent == NULL) { + EVP_PKEY_free(private_key); + return Error("Failed to allocate exponent: BN_new"); + } + + // Assign the exponent. + if (BN_set_word(exponent, _exponent) != 1) { + BN_free(exponent); + EVP_PKEY_free(private_key); + return Error("Failed to set exponent: BN_set_word"); + } + + // Allocate the in-memory structure for the key pair. + RSA* rsa = RSA_new(); + if (rsa == NULL) { + BN_free(exponent); + EVP_PKEY_free(private_key); + return Error("Failed to allocate RSA: RSA_new"); + } + + // Generate the RSA key pair. + if (RSA_generate_key_ex(rsa, bits, exponent, NULL) != 1) { + RSA_free(rsa); + BN_free(exponent); + EVP_PKEY_free(private_key); + return Error(ERR_error_string(ERR_get_error(), NULL)); + } + + // We no longer need the exponent, so let's free it. + BN_free(exponent); + + // Associate the RSA key with the private key. If this association + // is successful, then the RSA key will be freed when the private + // key is freed. + if (EVP_PKEY_assign_RSA(private_key, rsa) != 1) { + RSA_free(rsa); + EVP_PKEY_free(private_key); + return Error("Failed to assign RSA key: EVP_PKEY_assign_RSA"); + } + + return private_key; +} + + +Try<X509*> generate_x509( + EVP_PKEY* subject_key, + EVP_PKEY* sign_key, + const Option<X509*>& parent_certificate, + int serial, + int days, + Option<std::string> hostname) +{ + Option<X509_NAME*> issuer_name = None(); + if (parent_certificate.isNone()) { + // If there is no parent certificate, then the subject and + // signing key must be the same. + if (subject_key != sign_key) { + return Error("Subject vs signing key mismatch"); + } + } else { + // If there is a parent certificate, then set the issuer name to + // be that of the parent. + issuer_name = X509_get_subject_name(parent_certificate.get()); + + if (issuer_name.get() == NULL) { + return Error("Failed to get subject name of parent certificate: " + "X509_get_subject_name"); + } + } + + // Allocate the in-memory structure for the certificate. + X509* x509 = X509_new(); + if (x509 == NULL) { + return Error("Failed to allocate certification: X509_new"); + } + + // Set the version to V3. + if (X509_set_version(x509, 2) != 1) { + X509_free(x509); + return Error("Failed to set version: X509_set_version"); + } + + // Set the serial number. + if (ASN1_INTEGER_set(X509_get_serialNumber(x509), serial) != 1) { + X509_free(x509); + return Error("Failed to set serial number: ASN1_INTEGER_set"); + } + + // Make this certificate valid for 'days' number of days from now. + if (X509_gmtime_adj(X509_get_notBefore(x509), 0) == NULL || + X509_gmtime_adj(X509_get_notAfter(x509), + 60L * 60L * 24L * days) == NULL) { + X509_free(x509); + return Error("Failed to set valid days of certificate: X509_gmtime_adj"); + } + + // Set the public key for our certificate based on the subject key. + if (X509_set_pubkey(x509, subject_key) != 1) { + X509_free(x509); + return Error("Failed to set public key: X509_set_pubkey"); + } + + // Figure out our hostname if one was not provided. + if (hostname.isNone()) { + const Try<std::string> _hostname = net::hostname(); + if (_hostname.isError()) { + X509_free(x509); + return Error("Failed to determine hostname"); + } + + hostname = _hostname.get(); + } + + // Grab the subject name of the new certificate. + X509_NAME* name = X509_get_subject_name(x509); + if (name == NULL) { + X509_free(x509); + return Error("Failed to get subject name: X509_get_subject_name"); + } + + // Set the country code, organization, and common name. + if (X509_NAME_add_entry_by_txt( + name, + "C", + MBSTRING_ASC, + reinterpret_cast<const unsigned char*>("US"), + -1, + -1, + 0) != 1) { + X509_free(x509); + return Error("Failed to set country code: X509_NAME_add_entry_by_txt"); + } + + if (X509_NAME_add_entry_by_txt( + name, + "O", + MBSTRING_ASC, + reinterpret_cast<const unsigned char*>("Test"), + -1, + -1, + 0) != 1) { + X509_free(x509); + return Error("Failed to set organization name: X509_NAME_add_entry_by_txt"); + } + + if (X509_NAME_add_entry_by_txt( + name, + "CN", + MBSTRING_ASC, + reinterpret_cast<const unsigned char*>(hostname.get().c_str()), + -1, + -1, + 0) != 1) { + X509_free(x509); + return Error("Failed to set common name: X509_NAME_add_entry_by_txt"); + } + + // Set the issuer name to be the same as the subject if it is not + // already set (this is a self-signed certificate). + if (issuer_name.isNone()) { + issuer_name = name; + } + + CHECK_SOME(issuer_name); + if (X509_set_issuer_name(x509, issuer_name.get()) != 1) { + X509_free(x509); + return Error("Failed to set issuer name: X509_set_issuer_name"); + } + + // Sign the certificate with the sign key. + if (X509_sign(x509, sign_key, EVP_sha1()) == 0) { + X509_free(x509); + return Error("Failed to sign certificate: X509_sign"); + } + + return x509; +} + + +Try<Nothing> write_key_file(EVP_PKEY* private_key, const Path& path) +{ + // We use 'FILE*' here because it is an API requirement by openssl. + FILE* file = fopen(path.value.c_str(), "wb"); + if (file == NULL) { + return Error("Failed to open file '" + stringify(path) + "' for writing"); + } + + if (PEM_write_PrivateKey(file, private_key, NULL, NULL, 0, NULL, NULL) != 1) { + fclose(file); + return Error("Failed to write private key to file '" + stringify(path) + + "': PEM_write_PrivateKey"); + } + + fclose(file); + + return Nothing(); +} + + +Try<Nothing> write_certificate_file(X509* x509, const Path& path) +{ + // We use 'FILE*' here because it is an API requirement by openssl. + FILE* file = fopen(path.value.c_str(), "wb"); + if (file == NULL) { + return Error("Failed to open file '" + stringify(path) + "' for writing"); + } + + if (PEM_write_X509(file, x509) != 1) { + fclose(file); + return Error("Failed to write certificate to file '" + stringify(path) + + "': PEM_write_X509"); + } + + fclose(file); + + return Nothing(); +} + +} // namespace openssl { +} // namespace network { +} // namespace process { http://git-wip-us.apache.org/repos/asf/mesos/blob/0b04fec9/3rdparty/libprocess/src/openssl_util.hpp ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/src/openssl_util.hpp b/3rdparty/libprocess/src/openssl_util.hpp new file mode 100644 index 0000000..f855e27 --- /dev/null +++ b/3rdparty/libprocess/src/openssl_util.hpp @@ -0,0 +1,97 @@ +#ifndef __OPENSSL_UTIL_HPP__ +#define __OPENSSL_UTIL_HPP__ + +#include <openssl/ssl.h> +#include <openssl/x509.h> + +#include <stout/nothing.hpp> +#include <stout/path.hpp> +#include <stout/try.hpp> + +namespace process { +namespace network { +namespace openssl { + +/** + * Generates an RSA key. + * + * The caller is responsible for calling `EVP_PKEY_free` on the + * returned EVP_PKEY. + * @see <a href="https://www.openssl.org/docs/crypto/RSA_generate_key.html">RSA_generate_key_ex</a> // NOLINT + * + * @param bits The modulus size used to generate the key. + * @param exponent The public exponent, an odd number. + * + * @return A pointer to an EVP_PKEY if successful otherwise an Error. + */ +Try<EVP_PKEY*> generate_private_rsa_key( + int bits = 2048, + unsigned long exponent = RSA_F4); + + +/** + * Generates an X509 certificate. + * + * The X509 certificate is generated for the @param subject_key and + * signed by the @param sign_key. The caller is responsible for + * calling `X509_free` on the returned X509 object. + * The common name of the certificate will be set to @param hostname + * or that of the localhost if @param hostname is not provided. + * If a @param parent_certificate is provided, then the issuer name of + * the certificate will be set to the subject name of the + * @param parent_certificate. Otherwise, it is assumed this is a + * self-signed certificate in which case the @param subject_key must + * be the same as the @param sign_key, and the issuer name will be the + * same as the subject name. + * + * @param subject_key The key that will be made public by the + * certificate. + * @param sign_key The private key used to sign the certificate. + * @param parent_certificate An optional parent certificate that will + * be used to set the issuer name. + * @param serial The serial number of the certificate. + * @param days The number of days from the current time that the + * certificate will be valid. + * @param hostname An optional hostname used to set the common name of + * the certificate. + * + * @return A pointer to an X509 certificate if successful otherwise an + * Error. + */ +Try<X509*> generate_x509( + EVP_PKEY* subject_key, + EVP_PKEY* sign_key, + const Option<X509*>& parent_certificate = None(), + int serial = 1, + int days = 365, + Option<std::string> hostname = None()); + + +/** + * Writes a private key (EVP_PKEY) to a file on disk. + * @see <a href="https://www.openssl.org/docs/crypto/pem.html">PEM_write_PrivateKey</a> // NOLINT + * + * @param private_key The private key to write. + * @param path The file location to create the file. + * + * @return Nothing if successful otherwise an Error. + */ +Try<Nothing> write_key_file(EVP_PKEY* private_key, const Path& path); + + +/** + * Writes an X509 certificate (X509) to a file on disk. + * @see <a href="https://www.openssl.org/docs/crypto/pem.html">PEM_write_X509</a> // NOLINT + * + * @param x509 The certificate to write. + * @param path The file location to create the file. + * + * @return Nothing if successful otherwise an Error. + */ +Try<Nothing> write_certificate_file(X509* x509, const Path& path); + +} // namespace openssl { +} // namespace network { +} // namespace process { + +#endif // __OPENSSL_UTIL_HPP__
