Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(653)

Unified Diff: net/socket/ssl_server_socket_openssl.cc

Issue 994743003: Support for client certs in ssl_server_socket. Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: net/socket/ssl_server_socket_openssl.cc
diff --git a/net/socket/ssl_server_socket_openssl.cc b/net/socket/ssl_server_socket_openssl.cc
index 6bc13e6a3469942dcf75cd31ee29be8185780980..fcdbb53527d30361bfc360922d52a3a8241ac1a0 100644
--- a/net/socket/ssl_server_socket_openssl.cc
+++ b/net/socket/ssl_server_socket_openssl.cc
@@ -13,13 +13,114 @@
#include "crypto/rsa_private_key.h"
#include "crypto/scoped_openssl_types.h"
#include "net/base/net_errors.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
#include "net/ssl/openssl_ssl_util.h"
#include "net/ssl/scoped_openssl_types.h"
+#include "net/ssl/ssl_connection_status_flags.h"
+#include "net/ssl/ssl_info.h"
#define GotoState(s) next_handshake_state_ = s
namespace net {
+namespace {
+
+// TODO(dougsteed) These definitions copied from ssl_client_socket_openssl.cc.
+// Might want to consider putting them in a common place.
Ryan Sleevi 2015/03/19 04:38:25 Yes. Copy pasta bad.
+void FreeX509Stack(STACK_OF(X509) * ptr) {
+ sk_X509_pop_free(ptr, X509_free);
+}
+
+void FreeX509NameStack(STACK_OF(X509_NAME) * ptr) {
+ sk_X509_NAME_pop_free(ptr, X509_NAME_free);
+}
+
+typedef crypto::ScopedOpenSSL<X509_NAME, X509_NAME_free> ScopedX509Name;
+typedef crypto::ScopedOpenSSL<STACK_OF(X509), FreeX509Stack> ScopedX509Stack;
+typedef crypto::ScopedOpenSSL<STACK_OF(X509_NAME), FreeX509NameStack>
+ ScopedX509NameStack;
+
+#if OPENSSL_VERSION_NUMBER < 0x1000103fL
Ryan Sleevi 2015/03/19 04:38:25 Does this matter at all David?
+// This method doesn't seem to have made it into the OpenSSL headers.
+unsigned long SSL_CIPHER_get_id(const SSL_CIPHER* cipher) {
+ return cipher->id;
+}
+#endif
+
+// Used for encoding the |connection_status| field of an SSLInfo object.
+int EncodeSSLConnectionStatus(int cipher_suite, int compression, int version) {
+ return (cipher_suite & SSL_CONNECTION_CIPHERSUITE_MASK) |
+ ((compression & SSL_CONNECTION_COMPRESSION_MASK)
+ << SSL_CONNECTION_COMPRESSION_SHIFT) |
+ ((version & SSL_CONNECTION_VERSION_MASK)
+ << SSL_CONNECTION_VERSION_SHIFT);
+}
+
+// Returns the net SSL version number (see ssl_connection_status_flags.h) for
+// this SSL connection.
+int GetNetSSLVersion(SSL* ssl) {
+ switch (SSL_version(ssl)) {
+ case SSL2_VERSION:
+ return SSL_CONNECTION_VERSION_SSL2;
+ case SSL3_VERSION:
+ return SSL_CONNECTION_VERSION_SSL3;
+ case TLS1_VERSION:
+ return SSL_CONNECTION_VERSION_TLS1;
+ case 0x0302:
+ return SSL_CONNECTION_VERSION_TLS1_1;
+ case 0x0303:
+ return SSL_CONNECTION_VERSION_TLS1_2;
+ default:
+ return SSL_CONNECTION_VERSION_UNKNOWN;
+ }
+}
+
+bool GetX509AsDER(X509* cert, base::StringPiece* sp) {
Ryan Sleevi 2015/03/19 04:38:25 name better
+ unsigned char* cert_data = NULL;
+ int cert_data_length = i2d_X509(cert, &cert_data);
+ if (!cert_data_length || !cert_data) {
+ return false;
+ }
+ sp->set(reinterpret_cast<char*>(cert_data), cert_data_length);
+ return true;
+}
+
+scoped_refptr<X509Certificate> CreateX509Certificate(X509* cert,
+ STACK_OF(X509) * chain) {
+ DCHECK(cert);
+ std::vector<base::StringPiece> der_chain;
+ base::StringPiece der_cert;
+ scoped_refptr<X509Certificate> client_cert;
+ if (!GetX509AsDER(cert, &der_cert))
+ return client_cert;
+ der_chain.push_back(der_cert);
+
+ ScopedX509Stack openssl_chain(X509_chain_up_ref(chain));
+ for (size_t i = 0; i < sk_X509_num(openssl_chain.get()); ++i) {
+ X509* x = sk_X509_value(openssl_chain.get(), i);
+ if (GetX509AsDER(x, &der_cert)) {
+ der_chain.push_back(der_cert);
+ }
+ }
+
+ client_cert = X509Certificate::CreateFromDERCertChain(der_chain);
+
+ for (size_t i = 0; i < der_chain.size(); ++i) {
+ OPENSSL_free(const_cast<char*>(der_chain[i].data()));
+ }
+ if (der_chain.size() - 1 !=
+ static_cast<size_t>(sk_X509_num(openssl_chain.get()))) {
+ client_cert = NULL;
+ }
+ return client_cert;
+}
+
+void DoNothingOnCompletion(int ignore) {
+}
+
+} // namespace
+
void EnableSSLServerSockets() {
// No-op because CreateSSLServerSocket() calls crypto::EnsureOpenSSLInit().
}
@@ -51,7 +152,9 @@ SSLServerSocketOpenSSL::SSLServerSocketOpenSSL(
ssl_config_(ssl_config),
cert_(certificate),
next_handshake_state_(STATE_NONE),
- completed_handshake_(false) {
+ completed_handshake_(false),
+ client_cert_ca_list_(),
+ client_cert_verifier_(NULL) {
// TODO(byungchul): Need a better way to clone a key.
std::vector<uint8> key_bytes;
CHECK(key->ExportPrivateKey(&key_bytes));
@@ -98,6 +201,20 @@ int SSLServerSocketOpenSSL::Handshake(const CompletionCallback& callback) {
return rv > OK ? OK : rv;
}
+void SSLServerSocketOpenSSL::SetAllowClientCert(bool allow_client_cert) {
+ ssl_config_.send_client_cert = allow_client_cert;
+}
+
+void SSLServerSocketOpenSSL::SetClientCertCAList(
+ const CertificateList& client_cert_ca_list) {
+ client_cert_ca_list_ = client_cert_ca_list;
+}
+
+void SSLServerSocketOpenSSL::SetClientCertVerifier(
+ CertVerifier* client_cert_verifier) {
+ client_cert_verifier_ = client_cert_verifier;
+}
+
int SSLServerSocketOpenSSL::ExportKeyingMaterial(
const base::StringPiece& label,
bool has_context,
@@ -243,8 +360,34 @@ NextProto SSLServerSocketOpenSSL::GetNegotiatedProtocol() const {
}
bool SSLServerSocketOpenSSL::GetSSLInfo(SSLInfo* ssl_info) {
- NOTIMPLEMENTED();
- return false;
+ ssl_info->Reset();
+ if (!completed_handshake_) {
+ return false;
+ }
+ ExtractClientCert();
+ ssl_info->cert = client_cert_;
+ ssl_info->client_cert_sent =
+ ssl_config_.send_client_cert && client_cert_.get();
+
+ const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl_);
+ CHECK(cipher);
+ ssl_info->security_bits = SSL_CIPHER_get_bits(cipher, NULL);
+
+ ssl_info->connection_status =
+ EncodeSSLConnectionStatus(SSL_CIPHER_get_id(cipher),
+ 0 /* no compression */, GetNetSSLVersion(ssl_));
+
+ if (!SSL_get_secure_renegotiation_support(ssl_))
+ ssl_info->connection_status |= SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION;
+
+ if (ssl_config_.version_fallback)
+ ssl_info->connection_status |= SSL_CONNECTION_VERSION_FALLBACK;
+
+ ssl_info->handshake_type = SSL_session_reused(ssl_)
+ ? SSLInfo::HANDSHAKE_RESUME
+ : SSLInfo::HANDSHAKE_FULL;
+
Ryan Sleevi 2015/03/19 04:38:25 Much more readable construction ;)
+ return true;
}
void SSLServerSocketOpenSSL::OnSendComplete(int result) {
@@ -563,6 +706,13 @@ int SSLServerSocketOpenSSL::DoHandshake() {
OpenSSLErrorInfo error_info;
net_error = MapOpenSSLErrorWithDetails(ssl_error, err_tracer, &error_info);
+ // This hack is necessary because the mapping of SSL error codes to
+ // net_errors assumes (correctly for client sockets, but erroneously for
+ // server sockets) that peer cert verification failure can only occur if
+ // the cert changed during a renego.
+ if (net_error == ERR_SSL_SERVER_CERT_CHANGED)
+ net_error = ERR_BAD_SSL_CLIENT_AUTH_CERT;
+
// If not done, stay in this state
if (net_error == ERR_IO_PENDING) {
GotoState(STATE_HANDSHAKE);
@@ -608,6 +758,7 @@ int SSLServerSocketOpenSSL::Init() {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
ScopedSSL_CTX ssl_ctx(SSL_CTX_new(SSLv23_server_method()));
+ SSL_CTX_set_cert_verify_callback(ssl_ctx.get(), CertVerifyCallback, this);
ssl_ = SSL_new(ssl_ctx.get());
if (!ssl_)
return ERR_UNEXPECTED;
@@ -685,7 +836,60 @@ int SSLServerSocketOpenSSL::Init() {
SSL_set_mode(ssl_, mode.set_mask);
SSL_clear_mode(ssl_, mode.clear_mask);
+ if (ssl_config_.send_client_cert) {
+ ssl_->verify_mode = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
+ if (client_cert_verifier_)
+ ssl_->verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+ if (!client_cert_ca_list_.empty()) {
+ ScopedX509NameStack stack(sk_X509_NAME_new_null());
+ for (CertificateList::iterator it = client_cert_ca_list_.begin();
Ryan Sleevi 2015/03/19 04:38:25 for (const auto& certificate : client_cert_ca_list
+ it != client_cert_ca_list_.end(); it++) {
+ X509* ca_cert = it->get()->os_cert_handle();
+ ScopedX509Name subj(X509_NAME_dup(ca_cert->cert_info->subject));
+ sk_X509_NAME_push(stack.get(), subj.release());
+ }
+ SSL_set_client_CA_list(ssl_, stack.release());
+ }
+ }
+
return OK;
}
+void SSLServerSocketOpenSSL::ExtractClientCert() {
+ if (client_cert_.get() || !completed_handshake_) {
+ return;
+ }
+ X509* cert = SSL_get_peer_certificate(ssl_);
+ STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl_);
+ client_cert_ = CreateX509Certificate(cert, chain);
+}
+
+// static
+int SSLServerSocketOpenSSL::CertVerifyCallback(X509_STORE_CTX* store_ctx,
+ void* arg) {
+ SSLServerSocketOpenSSL* self = reinterpret_cast<SSLServerSocketOpenSSL*>(arg);
+ DCHECK(self);
+ if (!self->client_cert_verifier_)
+ return 1;
+ SSL* ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(
+ store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
+ DCHECK(ssl);
+ X509* x = store_ctx->cert;
+ STACK_OF(X509)* chain = store_ctx->chain;
+ scoped_refptr<X509Certificate> client_cert(CreateX509Certificate(x, chain));
+
+ CertVerifyResult ignore_result;
+ CertVerifier::RequestHandle ignore_handle;
+ int res = self->client_cert_verifier_->Verify(
+ client_cert.get(), std::string(), 0, NULL, &ignore_result,
+ base::Bind(&DoNothingOnCompletion), &ignore_handle, self->net_log_);
+ if (res == OK) {
+ self->client_cert_ = client_cert;
+ return 1;
+ } else {
+ X509_STORE_CTX_set_error(store_ctx, X509_V_ERR_CERT_REJECTED);
+ return 0;
+ }
+}
+
} // namespace net

Powered by Google App Engine
This is Rietveld 408576698