http-preapproved-server-certificate-name

This commit is contained in:
David Rose 2010-04-03 01:17:45 +00:00
parent 128097e19d
commit 3293a6574a
7 changed files with 438 additions and 94 deletions

View File

@ -64,9 +64,6 @@ ConfigVariableInt patcher_buffer_size
"Patcher::run(). Increasing this may help the Patcher "
"perform more work before returning."));
ConfigVariableList expected_ssl_server
("expected-ssl-server");
ConfigVariableBool http_proxy_tunnel
("http-proxy-tunnel", false,
PRC_DESC("This specifies the default value for HTTPChannel::set_proxy_tunnel(). "

View File

@ -38,8 +38,6 @@ extern ConfigVariableDouble decompressor_step_time;
extern ConfigVariableDouble extractor_step_time;
extern ConfigVariableInt patcher_buffer_size;
extern ConfigVariableList expected_ssl_server;
extern ConfigVariableBool http_proxy_tunnel;
extern ConfigVariableDouble http_connect_timeout;
extern ConfigVariableDouble http_timeout;

View File

@ -1626,90 +1626,91 @@ run_ssl_handshake() {
_bio->set_bio(_sbio);
_sbio = NULL;
X509 *cert = SSL_get_peer_certificate(ssl);
if (cert == (X509 *)NULL) {
downloader_cat.info()
<< "No certificate was presented by server.\n";
// This shouldn't be possible, per the SSL specs.
_status_entry._status_code = SC_ssl_invalid_server_certificate;
_state = S_failure;
return false;
}
X509_NAME *subject = X509_get_subject_name(cert);
if (downloader_cat.is_debug()) {
string org_name = get_x509_name_component(subject, NID_organizationName);
string org_unit_name = get_x509_name_component(subject, NID_organizationalUnitName);
string common_name = get_x509_name_component(subject, NID_commonName);
downloader_cat.debug()
<< "Server is " << common_name << " from " << org_unit_name
<< " / " << org_name << "\n";
if (downloader_cat.is_spam()) {
downloader_cat.spam()
<< "Received certificate from server:\n" << flush;
X509_print_fp(stderr, cert);
fflush(stderr);
}
}
bool cert_preapproved = false;
bool cert_name_preapproved = false;
check_preapproved_server_certificate(cert, cert_preapproved, cert_name_preapproved);
// Now verify the server certificate is valid.
long verify_result = SSL_get_verify_result(ssl);
bool cert_valid = true;
if (verify_result == X509_V_ERR_CERT_HAS_EXPIRED) {
downloader_cat.info()
<< "Expired certificate from " << _request.get_url().get_server_and_port() << "\n";
if (_client->get_verify_ssl() == HTTPClient::VS_normal) {
_status_entry._status_code = SC_ssl_invalid_server_certificate;
_state = S_failure;
return false;
if (_client->get_verify_ssl() == HTTPClient::VS_normal && !cert_preapproved) {
cert_valid = false;
}
} else if (verify_result == X509_V_ERR_CERT_NOT_YET_VALID) {
downloader_cat.info()
<< "Premature certificate from " << _request.get_url().get_server_and_port() << "\n";
if (_client->get_verify_ssl() == HTTPClient::VS_normal) {
_status_entry._status_code = SC_ssl_invalid_server_certificate;
_state = S_failure;
return false;
if (_client->get_verify_ssl() == HTTPClient::VS_normal && !cert_preapproved) {
cert_valid = false;
}
} else if (verify_result == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
verify_result == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) {
downloader_cat.info()
<< "Self-signed certificate from " << _request.get_url().get_server_and_port() << "\n";
if (_client->get_verify_ssl() == HTTPClient::VS_normal) {
_status_entry._status_code = SC_ssl_self_signed_server_certificate;
_state = S_failure;
return false;
if (_client->get_verify_ssl() != HTTPClient::VS_no_verify && !cert_preapproved) {
cert_valid = false;
}
} else if (verify_result != X509_V_OK) {
downloader_cat.info()
<< "Unable to verify identity of " << _request.get_url().get_server_and_port()
<< ", verify error code " << verify_result << "\n";
if (_client->get_verify_ssl() != HTTPClient::VS_no_verify) {
_status_entry._status_code = SC_ssl_invalid_server_certificate;
if (_client->get_verify_ssl() != HTTPClient::VS_no_verify && !cert_preapproved) {
cert_valid = false;
}
}
if (!cert_valid) {
_status_entry._status_code = SC_ssl_invalid_server_certificate;
_state = S_failure;
return false;
}
if (_client->get_verify_ssl() != HTTPClient::VS_no_verify && !cert_name_preapproved) {
// Check that the server is someone we expected to be talking
// to.
if (!validate_server_name(cert)) {
_status_entry._status_code = SC_ssl_unexpected_server;
_state = S_failure;
return false;
}
}
X509 *cert = SSL_get_peer_certificate(ssl);
if (cert == (X509 *)NULL) {
downloader_cat.info()
<< "No certificate was presented by server.\n";
// This shouldn't be possible, per the SSL specs.
if (_client->get_verify_ssl() != HTTPClient::VS_no_verify) {
_status_entry._status_code = SC_ssl_invalid_server_certificate;
_state = S_failure;
return false;
}
} else {
if (downloader_cat.is_debug()) {
downloader_cat.debug()
<< "Received certificate from server:\n" << flush;
X509_print_fp(stderr, cert);
fflush(stderr);
}
X509_NAME *subject = X509_get_subject_name(cert);
if (downloader_cat.is_debug()) {
string org_name = get_x509_name_component(subject, NID_organizationName);
string org_unit_name = get_x509_name_component(subject, NID_organizationalUnitName);
string common_name = get_x509_name_component(subject, NID_commonName);
downloader_cat.debug()
<< "Server is " << common_name << " from " << org_unit_name
<< " / " << org_name << "\n";
}
if (_client->get_verify_ssl() != HTTPClient::VS_no_verify) {
// Check that the server is someone we expected to be talking
// to.
if (!validate_server_name(cert)) {
_status_entry._status_code = SC_ssl_unexpected_server;
_state = S_failure;
return false;
}
}
X509_free(cert);
}
X509_free(cert);
_state = S_ready;
return false;
@ -3344,6 +3345,34 @@ certificate signing
*/
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::check_preapproved_server_certificate
// Access: Private
// Description: Checks to see if the indicated certificate is on the
// pre-approved list for the current server.
//
// If the full cert itself (including its key) is on the
// pre-approved list, sets both cert_preapproved and
// cert_name_preapproved to true.
//
// If the full cert is not on the pre-approved list, but
// its name matches a name on the pre-approved list,
// sets cert_name_preapproved to true, and
// cert_preapproved to false.
//
// Otherwise, sets both values to false. This doesn't
// mean the cert is necessarily invalid, just that it
// wasn't on the pre-approved list (which is usually
// empty anyway).
////////////////////////////////////////////////////////////////////
void HTTPChannel::
check_preapproved_server_certificate(X509 *cert, bool &cert_preapproved,
bool &cert_name_preapproved) const {
return _client->check_preapproved_server_certificate(_request.get_url(),
cert, cert_preapproved,
cert_name_preapproved);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::validate_server_name
// Access: Private
@ -3481,35 +3510,6 @@ get_x509_name_component(X509_NAME *name, int nid) {
return string((char *)data->data, data->length);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::x509_name_subset
// Access: Private, Static
// Description: Returns true if name_a is a subset of name_b: each
// property of name_a is defined in name_b, and the
// defined value is equivalent to that of name_a.
////////////////////////////////////////////////////////////////////
bool HTTPChannel::
x509_name_subset(X509_NAME *name_a, X509_NAME *name_b) {
int count_a = X509_NAME_entry_count(name_a);
for (int ai = 0; ai < count_a; ai++) {
X509_NAME_ENTRY *na = X509_NAME_get_entry(name_a, ai);
int bi = X509_NAME_get_index_by_OBJ(name_b, na->object, -1);
if (bi < 0) {
// This entry in name_a is not defined in name_b.
return false;
}
X509_NAME_ENTRY *nb = X509_NAME_get_entry(name_b, bi);
if (na->value->length != nb->value->length ||
memcmp(na->value->data, nb->value->data, na->value->length) != 0) {
// This entry in name_a doesn't match that of name_b.
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPChannel::make_header
// Access: Private

View File

@ -249,10 +249,11 @@ private:
void check_socket();
void check_preapproved_server_certificate(X509 *cert, bool &cert_preapproved,
bool &cert_name_preapproved) const;
bool validate_server_name(X509 *cert);
static bool match_cert_name(const string &cert_name, const string &hostname);
static string get_x509_name_component(X509_NAME *name, int nid);
static bool x509_name_subset(X509_NAME *name_a, X509_NAME *name_b);
void make_header();
void make_proxy_request_text();

View File

@ -137,6 +137,25 @@ HTTPClient() {
("http-client-certificate-passphrase", "",
PRC_DESC("This specifies the passphrase to use to decode the certificate named "
"by http-client-certificate-filename."));
ConfigVariableList http_preapproved_server_certificate_filename
("http-preapproved-server-certificate-filename",
PRC_DESC("This specifies a server hostname:port combination, followed by "
"at least one space, and the name of a PEM file that contains "
"an SSL certificate that we expect this server to offer. If "
"it does, we will accept the cert without further question, "
"even if the cert does not match any known certificate "
"authorities. This option may appear multiple times."));
ConfigVariableList http_preapproved_server_certificate_name
("http-preapproved-server-certificate-name",
PRC_DESC("This specifies a server hostname:port combination, followed by "
"at least one space, and a string describing the subject name "
"of an SSL certificate we expect this server to offer. If it "
"it does, we will accept the cert, but only if it also matches "
"a known certificate authority. This option may appear "
"multiple times."));
_http_version = HTTPEnum::HV_11;
_verify_ssl = verify_ssl ? VS_normal : VS_no_verify;
@ -168,6 +187,23 @@ HTTPClient() {
_client_certificate_pub = NULL;
_client_certificate_priv = NULL;
int num_server_certs = http_preapproved_server_certificate_filename.get_num_unique_values();
int si;
for (si = 0; si < num_server_certs; si++) {
string cert_line = http_preapproved_server_certificate_filename.get_unique_value(si);
string a, b;
split_whitespace(a, b, cert_line);
add_preapproved_server_certificate_filename(URLSpec(a, true), b);
}
num_server_certs = http_preapproved_server_certificate_name.get_num_unique_values();
for (si = 0; si < num_server_certs; si++) {
string cert_line = http_preapproved_server_certificate_name.get_unique_value(si);
string a, b;
split_whitespace(a, b, cert_line);
add_preapproved_server_certificate_name(URLSpec(a, true), b);
}
// The first time we create an HTTPClient, we must initialize the
// OpenSSL library. The OpenSSLWrapper object does that.
OpenSSLWrapper::get_global_ptr();
@ -900,6 +936,150 @@ load_client_certificate() {
_client_certificate_pub != (X509 *)NULL);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::add_preapproved_server_certificate_filename
// Access: Published
// Description: Adds the certificate defined in the indicated PEM
// filename as a "pre-approved" certificate for the
// indicated server, defined by the hostname and port
// (only) from the given URL.
//
// If the server offers this particular certificate on a
// secure connection, it will be accepted without
// question. This is particularly useful for
// communicating with a server using a known self-signed
// certificate.
//
// See also the similar
// add_preapproved_server_certificate_pem(), and the
// weaker add_preapproved_server_certificate_name().
////////////////////////////////////////////////////////////////////
bool HTTPClient::
add_preapproved_server_certificate_filename(const URLSpec &url, const Filename &filename) {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
string pem;
if (!vfs->read_file(filename, pem, true)) {
// Could not find or read file.
downloader_cat.warning()
<< "Could not read " << filename << ".\n";
return false;
}
return add_preapproved_server_certificate_pem(url, pem);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::add_preapproved_server_certificate_pem
// Access: Published
// Description: Adds the certificate defined in the indicated data
// string, formatted as a PEM block, as a "pre-approved"
// certificate for the indicated server, defined by the
// hostname and port (only) from the given URL.
//
// If the server offers this particular certificate on a
// secure connection, it will be accepted without
// question. This is particularly useful for
// communicating with a server using a known self-signed
// certificate.
//
// See also the similar
// add_preapproved_server_certificate_filename(), and
// the weaker add_preapproved_server_certificate_name().
////////////////////////////////////////////////////////////////////
bool HTTPClient::
add_preapproved_server_certificate_pem(const URLSpec &url, const string &pem) {
// Create an in-memory BIO to read the "file" from the memory
// buffer, and call the low-level routine to read the
// cert from the BIO.
BIO *mbio = BIO_new_mem_buf((void *)pem.data(), pem.length());
ERR_clear_error();
X509 *cert = PEM_read_bio_X509(mbio, NULL, NULL, NULL);
BIO_free(mbio);
if (cert == NULL) {
downloader_cat.warning()
<< "Could not parse PEM data\n";
return false;
}
string server_and_port = url.get_server_and_port();
PreapprovedServerCerts::iterator psci =
_preapproved_server_certs.insert(PreapprovedServerCerts::value_type(server_and_port, PreapprovedServerCert())).first;
PreapprovedServerCert &psc = (*psci).second;
psc._certs.push_back(cert);
return true;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::add_preapproved_server_certificate_name
// Access: Published
// Description: Adds the certificate *name* only, as a "pre-approved"
// certificate name for the indicated server, defined by
// the hostname and port (only) from the given URL.
//
// This is a weaker function than
// add_preapproved_server_certificate_filename(). This
// checks only the subject name of the certificate,
// without checking for a particular certificate by key.
// This means that a variety of server certificates may
// match the indicated name.
//
// Because this is a weaker verification, it only
// applies to server certificates that are signed by a
// recognized certificate authority. Thus, it cannot be
// used to pre-approve self-signed certificates, but it
// can be used to accept a server certificate offered by
// a different hostname than the one in the cert itself.
//
// The certificate name should be formatted in the form
// /type0=value0/type1=value1/type2=...
////////////////////////////////////////////////////////////////////
bool HTTPClient::
add_preapproved_server_certificate_name(const URLSpec &url, const string &name) {
X509_NAME *cert_name = parse_x509_name(name);
if (cert_name == NULL) {
downloader_cat.warning()
<< "Could not parse certificate name " << name << "\n";
return false;
}
string server_and_port = url.get_server_and_port();
PreapprovedServerCerts::iterator psci =
_preapproved_server_certs.insert(PreapprovedServerCerts::value_type(server_and_port, PreapprovedServerCert())).first;
PreapprovedServerCert &psc = (*psci).second;
psc._cert_names.push_back(cert_name);
return true;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::clear_preapproved_server_certificates
// Access: Published
// Description: Removes all preapproved server certificates for the
// indicated server and port.
////////////////////////////////////////////////////////////////////
void HTTPClient::
clear_preapproved_server_certificates(const URLSpec &url) {
string server_and_port = url.get_server_and_port();
_preapproved_server_certs.erase(server_and_port);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::clear_all_preapproved_server_certificates
// Access: Published
// Description: Removes all preapproved server certificates for all
// servers.
////////////////////////////////////////////////////////////////////
void HTTPClient::
clear_all_preapproved_server_certificates() {
_preapproved_server_certs.clear();
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_http_version_string
// Access: Published
@ -1085,6 +1265,72 @@ get_ssl_ctx() {
return _ssl_ctx;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::check_preapproved_server_certificate
// Access: Private
// Description: Checks to see if the indicated certificate is on the
// pre-approved list for the indicated server's URL.
//
// If the full cert itself (including its key) is on the
// pre-approved list, sets both cert_preapproved and
// cert_name_preapproved to true.
//
// If the full cert is not on the pre-approved list, but
// its name matches a name on the pre-approved list,
// sets cert_name_preapproved to true, and
// cert_preapproved to false.
//
// Otherwise, sets both values to false. This doesn't
// mean the cert is necessarily invalid, just that it
// wasn't on the pre-approved list (which is usually
// empty anyway).
////////////////////////////////////////////////////////////////////
void HTTPClient::
check_preapproved_server_certificate(const URLSpec &url, X509 *cert,
bool &cert_preapproved, bool &cert_name_preapproved) const {
cert_preapproved = false;
cert_name_preapproved = false;
string server_and_port = url.get_server_and_port();
PreapprovedServerCerts::const_iterator psci =
_preapproved_server_certs.find(server_and_port);
if (psci == _preapproved_server_certs.end()) {
// No preapproved certs defined for this server.
return;
}
const PreapprovedServerCert &psc = (*psci).second;
// See if we have an exact match on the cert itself.
ServerCerts::const_iterator sci;
for (sci = psc._certs.begin(); sci != psc._certs.end(); ++sci) {
if (X509_cmp(cert, *sci) == 0) {
cert_preapproved = true;
cert_name_preapproved = true;
downloader_cat.info()
<< "Server certificate is pre-approved.\n";
return;
}
}
// OK, look for a match on the cert's name only.
X509_NAME *subject = X509_get_subject_name(cert);
ServerCertNames::const_iterator scni;
for (scni = psc._cert_names.begin(); scni != psc._cert_names.end(); ++scni) {
X509_NAME *cert_name = (*scni);
if (x509_name_subset(cert_name, subject)) {
downloader_cat.info()
<< "Server certificate name is pre-approved.\n";
cert_name_preapproved = true;
return;
}
}
// Didn't find any match.
return;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_proxies_for_scheme
// Access: Private
@ -1421,6 +1667,65 @@ parse_x509_name(const string &source) {
return result;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::x509_name_subset
// Access: Private, Static
// Description: Returns true if name_a is a subset of name_b: each
// property of name_a is defined in name_b, and the
// defined value is equivalent to that of name_a.
////////////////////////////////////////////////////////////////////
bool HTTPClient::
x509_name_subset(X509_NAME *name_a, X509_NAME *name_b) {
int count_a = X509_NAME_entry_count(name_a);
for (int ai = 0; ai < count_a; ai++) {
X509_NAME_ENTRY *na = X509_NAME_get_entry(name_a, ai);
int bi = X509_NAME_get_index_by_OBJ(name_b, na->object, -1);
if (bi < 0) {
// This entry in name_a is not defined in name_b.
return false;
}
X509_NAME_ENTRY *nb = X509_NAME_get_entry(name_b, bi);
if (na->value->length != nb->value->length ||
memcmp(na->value->data, nb->value->data, na->value->length) != 0) {
// This entry in name_a doesn't match that of name_b.
return false;
}
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::split_whitespace
// Access: Private, Static
// Description: Puts the first word of c into a, and the remainder
// into b.
////////////////////////////////////////////////////////////////////
void HTTPClient::
split_whitespace(string &a, string &b, const string &c) {
size_t p = 0;
// Skip initial whitespace
while (p < c.length() && isspace(c[p])) {
++p;
}
// Find the length of the first word
size_t q = p;
while (p < c.length() && !isspace(c[p])) {
++p;
}
a = c.substr(q, p - q);
// Skip whitespace between words
while (p < c.length() && isspace(c[p])) {
++p;
}
b = c.substr(p);
}
#if defined(SSL_097) && !defined(NDEBUG)
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::ssl_msg_callback
@ -1485,4 +1790,22 @@ ssl_msg_callback(int write_p, int version, int content_type,
}
#endif // defined(SSL_097) && !defined(NDEBUG)
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::PreapprovedServerCert::Destructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
HTTPClient::PreapprovedServerCert::
~PreapprovedServerCert() {
ServerCerts::const_iterator sci;
for (sci = _certs.begin(); sci != _certs.end(); ++sci) {
X509_free(*sci);
}
ServerCertNames::const_iterator scni;
for (scni = _cert_names.begin(); scni != _cert_names.end(); ++scni) {
X509_NAME_free(*scni);
}
}
#endif // HAVE_OPENSSL

View File

@ -98,6 +98,12 @@ PUBLISHED:
INLINE void set_client_certificate_passphrase(const string &passphrase);
bool load_client_certificate();
bool add_preapproved_server_certificate_filename(const URLSpec &url, const Filename &filename);
bool add_preapproved_server_certificate_pem(const URLSpec &url, const string &pem);
bool add_preapproved_server_certificate_name(const URLSpec &url, const string &name);
void clear_preapproved_server_certificates(const URLSpec &url);
void clear_all_preapproved_server_certificates();
INLINE void set_http_version(HTTPEnum::HTTPVersion version);
INLINE HTTPEnum::HTTPVersion get_http_version() const;
string get_http_version_string() const;
@ -131,6 +137,9 @@ public:
SSL_CTX *get_ssl_ctx();
private:
void check_preapproved_server_certificate(const URLSpec &url, X509 *cert,
bool &cert_preapproved, bool &cert_name_preapproved) const;
bool get_proxies_for_scheme(const string &scheme,
pvector<URLSpec> &proxies) const;
@ -146,6 +155,9 @@ private:
void unload_client_certificate();
static X509_NAME *parse_x509_name(const string &source);
static bool x509_name_subset(X509_NAME *name_a, X509_NAME *name_b);
static void split_whitespace(string &a, string &b, const string &c);
#if defined(SSL_097) && !defined(NDEBUG)
static void ssl_msg_callback(int write_p, int version, int content_type,
@ -188,6 +200,19 @@ private:
X509 *_client_certificate_pub;
EVP_PKEY *_client_certificate_priv;
typedef pvector<X509 *> ServerCerts;
typedef pvector<X509_NAME *> ServerCertNames;
class PreapprovedServerCert {
public:
~PreapprovedServerCert();
ServerCerts _certs;
ServerCertNames _cert_names;
};
typedef pmap<string, PreapprovedServerCert> PreapprovedServerCerts;
PreapprovedServerCerts _preapproved_server_certs;
static PT(HTTPClient) _global_ptr;
friend class HTTPChannel;

View File

@ -62,7 +62,7 @@ do_receive_datagram(Datagram &dg) {
}
if (_data_expected == 0) {
// Read the first two bytes: the datagram length.
while (_data_so_far.length() < _tcp_header_size) {
while ((int)_data_so_far.length() < _tcp_header_size) {
int ch = _istream->get();
if (_istream->eof() || _istream->fail()) {
_istream->clear();