Chromium Code Reviews| 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 fe3a3694069ef21c987acd99c6d463b7527f9768..da94ea27c50959c920231194266336e197acd398 100644 |
| --- a/net/socket/ssl_server_socket_openssl.cc |
| +++ b/net/socket/ssl_server_socket_openssl.cc |
| @@ -14,13 +14,46 @@ |
| #include "crypto/rsa_private_key.h" |
| #include "crypto/scoped_openssl_types.h" |
| #include "net/base/net_errors.h" |
| +#include "net/cert/cert_verify_result.h" |
| +#include "net/cert/client_cert_verifier.h" |
| +#include "net/cert/x509_util_openssl.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 { |
| + |
| +scoped_refptr<X509Certificate> CreateX509Certificate(X509* cert, |
| + STACK_OF(X509) * chain) { |
| + std::vector<base::StringPiece> der_chain; |
| + base::StringPiece der_cert; |
| + scoped_refptr<X509Certificate> client_cert; |
| + if (cert) { |
| + if (!x509_util::GetDER(cert, &der_cert)) |
| + return client_cert; |
| + der_chain.push_back(der_cert); |
| + } |
| + |
| + ScopedX509_STACK 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 (x509_util::GetDER(x, &der_cert)) { |
| + der_chain.push_back(der_cert); |
| + } else { |
| + return nullptr; |
| + } |
| + } |
| + |
| + return X509Certificate::CreateFromDERCertChain(der_chain); |
| +} |
| + |
| +} // namespace |
| + |
| void EnableSSLServerSockets() { |
| // No-op because CreateSSLServerSocket() calls crypto::EnsureOpenSSLInit(). |
| } |
| @@ -29,17 +62,17 @@ scoped_ptr<SSLServerSocket> CreateSSLServerSocket( |
| scoped_ptr<StreamSocket> socket, |
| X509Certificate* certificate, |
| crypto::RSAPrivateKey* key, |
| - const SSLServerConfig& ssl_config) { |
| + const SSLServerConfig& ssl_server_config) { |
| crypto::EnsureOpenSSLInit(); |
| - return scoped_ptr<SSLServerSocket>( |
| - new SSLServerSocketOpenSSL(socket.Pass(), certificate, key, ssl_config)); |
| + return scoped_ptr<SSLServerSocket>(new SSLServerSocketOpenSSL( |
| + socket.Pass(), certificate, key, ssl_server_config)); |
| } |
| SSLServerSocketOpenSSL::SSLServerSocketOpenSSL( |
| scoped_ptr<StreamSocket> transport_socket, |
| scoped_refptr<X509Certificate> certificate, |
| crypto::RSAPrivateKey* key, |
| - const SSLServerConfig& ssl_config) |
| + const SSLServerConfig& ssl_server_config) |
| : transport_send_busy_(false), |
| transport_recv_busy_(false), |
| transport_recv_eof_(false), |
| @@ -49,7 +82,7 @@ SSLServerSocketOpenSSL::SSLServerSocketOpenSSL( |
| ssl_(NULL), |
| transport_bio_(NULL), |
| transport_socket_(transport_socket.Pass()), |
| - ssl_config_(ssl_config), |
| + ssl_server_config_(ssl_server_config), |
| cert_(certificate), |
| next_handshake_state_(STATE_NONE), |
| completed_handshake_(false) { |
| @@ -244,8 +277,31 @@ NextProto SSLServerSocketOpenSSL::GetNegotiatedProtocol() const { |
| } |
| bool SSLServerSocketOpenSSL::GetSSLInfo(SSLInfo* ssl_info) { |
|
Ryan Sleevi
2015/12/17 03:47:36
I'm pretty sure this is just a wrong interface for
ryanchung
2015/12/18 00:00:55
Should this be in a new interface like GetSSLServe
|
| - NOTIMPLEMENTED(); |
| - return false; |
| + ssl_info->Reset(); |
| + if (!completed_handshake_) { |
| + return false; |
| + } |
|
Ryan Sleevi
2015/12/17 03:47:35
nit: We don't do braces for one-line if's in //net
ryanchung
2015/12/18 00:00:55
Done.
|
| + ExtractClientCert(); |
| + ssl_info->cert = client_cert_; |
| + |
| + const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl_); |
| + CHECK(cipher); |
| + ssl_info->security_bits = SSL_CIPHER_get_bits(cipher, NULL); |
| + |
| + SSLConnectionStatusSetCipherSuite( |
| + static_cast<uint16>(SSL_CIPHER_get_id(cipher)), |
| + &ssl_info->connection_status); |
| + SSLConnectionStatusSetVersion(GetNetSSLVersion(ssl_), |
| + &ssl_info->connection_status); |
| + |
| + if (!SSL_get_secure_renegotiation_support(ssl_)) |
| + ssl_info->connection_status |= SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION; |
| + |
| + ssl_info->handshake_type = SSL_session_reused(ssl_) |
| + ? SSLInfo::HANDSHAKE_RESUME |
| + : SSLInfo::HANDSHAKE_FULL; |
| + |
| + return true; |
| } |
| void SSLServerSocketOpenSSL::GetConnectionAttempts( |
| @@ -568,11 +624,19 @@ int SSLServerSocketOpenSSL::DoHandshake() { |
| if (rv == 1) { |
| completed_handshake_ = true; |
| + ExtractClientCert(); |
|
Ryan Sleevi
2015/12/17 03:47:35
This feels weird, in that it leaves multiple place
ryanchung
2015/12/18 00:00:55
This was supposed to be the only place this is cal
|
| } else { |
| int ssl_error = SSL_get_error(ssl_, rv); |
| 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. crbug.com/570351 |
| + 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); |
| @@ -618,10 +682,11 @@ int SSLServerSocketOpenSSL::Init() { |
| crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); |
| ScopedSSL_CTX ssl_ctx(SSL_CTX_new(SSLv23_server_method())); |
| - |
| - if (ssl_config_.require_client_cert) |
| - SSL_CTX_set_verify(ssl_ctx.get(), SSL_VERIFY_PEER, NULL); |
| - |
| + if (ssl_server_config_.require_client_cert) |
| + SSL_CTX_set_verify(ssl_ctx.get(), |
| + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); |
|
Ryan Sleevi
2015/12/17 03:47:36
Ironically, you should use braces here, even thoug
ryanchung
2015/12/18 00:00:55
Done.
|
| + SSL_CTX_set_cert_verify_callback(ssl_ctx.get(), CertVerifyCallback, |
| + ssl_server_config_.client_cert_verifier); |
| ssl_ = SSL_new(ssl_ctx.get()); |
| if (!ssl_) |
| return ERR_UNEXPECTED; |
| @@ -668,10 +733,10 @@ int SSLServerSocketOpenSSL::Init() { |
| return ERR_UNEXPECTED; |
| } |
| - DCHECK_LT(SSL3_VERSION, ssl_config_.version_min); |
| - DCHECK_LT(SSL3_VERSION, ssl_config_.version_max); |
| - SSL_set_min_version(ssl_, ssl_config_.version_min); |
| - SSL_set_max_version(ssl_, ssl_config_.version_max); |
| + DCHECK_LT(SSL3_VERSION, ssl_server_config_.version_min); |
| + DCHECK_LT(SSL3_VERSION, ssl_server_config_.version_max); |
| + SSL_set_min_version(ssl_, ssl_server_config_.version_min); |
| + SSL_set_max_version(ssl_, ssl_server_config_.version_max); |
| // OpenSSL defaults some options to on, others to off. To avoid ambiguity, |
| // set everything we care about to an absolute value. |
| @@ -695,11 +760,11 @@ int SSLServerSocketOpenSSL::Init() { |
| // as the handshake hash. |
| std::string command("DEFAULT:!SHA256:!SHA384:!AESGCM+AES256:!aPSK"); |
| - if (ssl_config_.require_ecdhe) |
| + if (ssl_server_config_.require_ecdhe) |
| command.append(":!kRSA:!kDHE"); |
| // Remove any disabled ciphers. |
| - for (uint16_t id : ssl_config_.disabled_cipher_suites) { |
| + for (uint16_t id : ssl_server_config_.disabled_cipher_suites) { |
| const SSL_CIPHER* cipher = SSL_get_cipher_by_value(id); |
| if (cipher) { |
| command.append(":!"); |
| @@ -714,7 +779,49 @@ int SSLServerSocketOpenSSL::Init() { |
| LOG_IF(WARNING, rv != 1) << "SSL_set_cipher_list('" << command |
| << "') returned " << rv; |
| + if (ssl_server_config_.require_client_cert) { |
| + if (!ssl_server_config_.client_cert_ca_list.empty()) { |
|
Ryan Sleevi
2015/12/17 03:47:36
Could you combine these two conditionals?
ryanchung
2015/12/18 00:00:55
Done.
|
| + ScopedX509_NAME_STACK stack(sk_X509_NAME_new_null()); |
| + for (const auto& certificate : ssl_server_config_.client_cert_ca_list) { |
| + ScopedX509 ca_cert = |
| + OSCertHandleToOpenSSL(certificate->os_cert_handle()); |
| + ScopedX509_NAME 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() { |
| + X509* cert = SSL_get_peer_certificate(ssl_); |
| + STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl_); |
| + client_cert_ = CreateX509Certificate(cert, chain); |
| +} |
|
Ryan Sleevi
2015/12/17 03:47:36
This feels like a weird API contract; why not just
ryanchung
2015/12/18 00:00:55
Done.
|
| + |
| +// static |
| +int SSLServerSocketOpenSSL::CertVerifyCallback(X509_STORE_CTX* store_ctx, |
| + void* arg) { |
| + ClientCertVerifier* verifier = reinterpret_cast<ClientCertVerifier*>(arg); |
| + // If a verifier was not supplied, all certificates are accepted. |
| + if (!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); |
| + STACK_OF(X509)* chain = store_ctx->untrusted; |
| + scoped_refptr<X509Certificate> client_cert( |
| + CreateX509Certificate(nullptr, chain)); |
| + |
| + int res = verifier->Verify(client_cert.get()); |
| + if (res == OK) { |
|
Ryan Sleevi
2015/12/17 03:47:36
DESIGN: Please handle errors first, which will red
ryanchung
2015/12/18 00:00:55
Done.
|
| + return 1; |
| + } else { |
| + X509_STORE_CTX_set_error(store_ctx, X509_V_ERR_CERT_REJECTED); |
| + return 0; |
| + } |
| +} |
| + |
| } // namespace net |