#include <sys/socket.h>
#endif
+#include <grpc/grpc_security.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
extern "C" {
#include <openssl/bio.h>
#include <openssl/crypto.h> /* For OPENSSL_free */
+#include <openssl/engine.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
static gpr_once g_init_openssl_once = GPR_ONCE_INIT;
static int g_ssl_ctx_ex_factory_index = -1;
static const unsigned char kSslSessionIdContext[] = {'g', 'r', 'p', 'c'};
+#ifndef OPENSSL_IS_BORINGSSL
+static const char kSslEnginePrefix[] = "engine:";
+#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000
static gpr_mu* g_openssl_mutexes = nullptr;
return result;
}
-/* Loads an in-memory PEM private key into the SSL context. */
-static tsi_result ssl_ctx_use_private_key(SSL_CTX* context, const char* pem_key,
- size_t pem_key_size) {
+#ifndef OPENSSL_IS_BORINGSSL
+static tsi_result ssl_ctx_use_engine_private_key(SSL_CTX* context,
+ const char* pem_key,
+ size_t pem_key_size) {
+ tsi_result result = TSI_OK;
+ EVP_PKEY* private_key = nullptr;
+ ENGINE* engine = nullptr;
+ char* engine_name = nullptr;
+ // Parse key which is in following format engine:<engine_id>:<key_id>
+ do {
+ char* engine_start = (char*)pem_key + strlen(kSslEnginePrefix);
+ char* engine_end = (char*)strchr(engine_start, ':');
+ if (engine_end == nullptr) {
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ char* key_id = engine_end + 1;
+ int engine_name_length = engine_end - engine_start;
+ if (engine_name_length == 0) {
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ engine_name = static_cast<char*>(gpr_zalloc(engine_name_length + 1));
+ memcpy(engine_name, engine_start, engine_name_length);
+ gpr_log(GPR_DEBUG, "ENGINE key: %s", engine_name);
+ ENGINE_load_dynamic();
+ engine = ENGINE_by_id(engine_name);
+ if (engine == nullptr) {
+ // If not available at ENGINE_DIR, use dynamic to load from
+ // current working directory.
+ engine = ENGINE_by_id("dynamic");
+ if (engine == nullptr) {
+ gpr_log(GPR_ERROR, "Cannot load dynamic engine");
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ if (!ENGINE_ctrl_cmd_string(engine, "ID", engine_name, 0) ||
+ !ENGINE_ctrl_cmd_string(engine, "DIR_LOAD", "2", 0) ||
+ !ENGINE_ctrl_cmd_string(engine, "DIR_ADD", ".", 0) ||
+ !ENGINE_ctrl_cmd_string(engine, "LIST_ADD", "1", 0) ||
+ !ENGINE_ctrl_cmd_string(engine, "LOAD", NULL, 0)) {
+ gpr_log(GPR_ERROR, "Cannot find engine");
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ }
+ if (!ENGINE_set_default(engine, ENGINE_METHOD_ALL)) {
+ gpr_log(GPR_ERROR, "ENGINE_set_default with ENGINE_METHOD_ALL failed");
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ if (!ENGINE_init(engine)) {
+ gpr_log(GPR_ERROR, "ENGINE_init failed");
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ private_key = ENGINE_load_private_key(engine, key_id, 0, 0);
+ if (private_key == nullptr) {
+ gpr_log(GPR_ERROR, "ENGINE_load_private_key failed");
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ if (!SSL_CTX_use_PrivateKey(context, private_key)) {
+ gpr_log(GPR_ERROR, "SSL_CTX_use_PrivateKey failed");
+ result = TSI_INVALID_ARGUMENT;
+ break;
+ }
+ } while (0);
+ if (engine != nullptr) ENGINE_free(engine);
+ if (private_key != nullptr) EVP_PKEY_free(private_key);
+ if (engine_name != nullptr) gpr_free(engine_name);
+ return result;
+}
+#endif /* OPENSSL_IS_BORINGSSL */
+
+static tsi_result ssl_ctx_use_pem_private_key(SSL_CTX* context,
+ const char* pem_key,
+ size_t pem_key_size) {
tsi_result result = TSI_OK;
EVP_PKEY* private_key = nullptr;
BIO* pem;
return result;
}
+/* Loads an in-memory PEM private key into the SSL context. */
+static tsi_result ssl_ctx_use_private_key(SSL_CTX* context, const char* pem_key,
+ size_t pem_key_size) {
+// BoringSSL does not have ENGINE support
+#ifndef OPENSSL_IS_BORINGSSL
+ if (strncmp(pem_key, kSslEnginePrefix, strlen(kSslEnginePrefix)) == 0) {
+ return ssl_ctx_use_engine_private_key(context, pem_key, pem_key_size);
+ } else
+#endif /* OPENSSL_IS_BORINGSSL */
+ {
+ return ssl_ctx_use_pem_private_key(context, pem_key, pem_key_size);
+ }
+}
+
/* Loads in-memory PEM verification certs into the SSL context and optionally
returns the verification cert names (root_names can be NULL). */
static tsi_result x509_store_load_certs(X509_STORE* cert_store,
gpr_ref_init(&factory->refcount, 1);
}
+/* Gets the X509 cert chain in PEM format as a tsi_peer_property. */
+tsi_result tsi_ssl_get_cert_chain_contents(STACK_OF(X509) * peer_chain,
+ tsi_peer_property* property) {
+ BIO* bio = BIO_new(BIO_s_mem());
+ for (int i = 0; i < sk_X509_num(peer_chain); i++) {
+ if (!PEM_write_bio_X509(bio, sk_X509_value(peer_chain, i))) {
+ BIO_free(bio);
+ return TSI_INTERNAL_ERROR;
+ }
+ }
+ char* contents;
+ long len = BIO_get_mem_data(bio, &contents);
+ if (len <= 0) {
+ BIO_free(bio);
+ return TSI_INTERNAL_ERROR;
+ }
+ tsi_result result = tsi_construct_string_peer_property(
+ TSI_X509_PEM_CERT_CHAIN_PROPERTY, (const char*)contents,
+ static_cast<size_t>(len), property);
+ BIO_free(bio);
+ return result;
+}
+
/* --- tsi_handshaker_result methods implementation. ---*/
static tsi_result ssl_handshaker_result_extract_peer(
const tsi_handshaker_result* self, tsi_peer* peer) {
unsigned int alpn_selected_len;
const tsi_ssl_handshaker_result* impl =
reinterpret_cast<const tsi_ssl_handshaker_result*>(self);
- // TODO(yihuazhang): Return a full certificate chain as a peer property.
X509* peer_cert = SSL_get_peer_certificate(impl->ssl);
if (peer_cert != nullptr) {
result = peer_from_x509(peer_cert, 1, peer);
SSL_get0_next_proto_negotiated(impl->ssl, &alpn_selected,
&alpn_selected_len);
}
-
+ // When called on the client side, the stack also contains the
+ // peer's certificate; When called on the server side,
+ // the peer's certificate is not present in the stack
+ STACK_OF(X509)* peer_chain = SSL_get_peer_cert_chain(impl->ssl);
// 1 is for session reused property.
- size_t new_property_count = peer->property_count + 1;
+ size_t new_property_count = peer->property_count + 3;
if (alpn_selected != nullptr) new_property_count++;
+ if (peer_chain != nullptr) new_property_count++;
tsi_peer_property* new_properties = static_cast<tsi_peer_property*>(
gpr_zalloc(sizeof(*new_properties) * new_property_count));
for (size_t i = 0; i < peer->property_count; i++) {
}
if (peer->properties != nullptr) gpr_free(peer->properties);
peer->properties = new_properties;
-
+ // Add peer chain if available
+ if (peer_chain != nullptr) {
+ result = tsi_ssl_get_cert_chain_contents(
+ peer_chain, &peer->properties[peer->property_count]);
+ if (result == TSI_OK) peer->property_count++;
+ }
if (alpn_selected != nullptr) {
result = tsi_construct_string_peer_property(
TSI_SSL_ALPN_SELECTED_PROTOCOL,
if (result != TSI_OK) return result;
peer->property_count++;
}
+ // Add security_level peer property.
+ result = tsi_construct_string_peer_property_from_cstring(
+ TSI_SECURITY_LEVEL_PEER_PROPERTY,
+ tsi_security_level_to_string(TSI_PRIVACY_AND_INTEGRITY),
+ &peer->properties[peer->property_count]);
+ if (result != TSI_OK) return result;
+ peer->property_count++;
const char* session_reused = SSL_session_reused(impl->ssl) ? "true" : "false";
result = tsi_construct_string_peer_property_from_cstring(
tsi_ssl_handshaker_factory_unref(&impl->base);
return result;
}
- SSL_CTX_set_verify(ssl_context, SSL_VERIFY_PEER, nullptr);
+ if (options->skip_server_certificate_verification) {
+ SSL_CTX_set_verify(ssl_context, SSL_VERIFY_PEER, NullVerifyCallback);
+ } else {
+ SSL_CTX_set_verify(ssl_context, SSL_VERIFY_PEER, nullptr);
+ }
/* TODO(jboeuf): Add revocation verification. */
*factory = impl;