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 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 |