expected-ssl-server
This commit is contained in:
parent
92a763eee1
commit
078b8b8193
|
|
@ -17,36 +17,6 @@
|
|||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::Constructor
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE HTTPClient::
|
||||
HTTPClient() {
|
||||
make_ctx();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::Copy Constructor
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE HTTPClient::
|
||||
HTTPClient(const HTTPClient ©) : _proxy(copy._proxy) {
|
||||
make_ctx();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::Copy Assignment Operator
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE void HTTPClient::
|
||||
operator = (const HTTPClient ©) {
|
||||
_proxy = copy._proxy;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::set_proxy
|
||||
// Access: Published
|
||||
|
|
|
|||
|
|
@ -32,10 +32,55 @@
|
|||
|
||||
bool HTTPClient::_ssl_initialized = false;
|
||||
|
||||
// Never freed.
|
||||
// This is created once and never freed.
|
||||
X509_STORE *HTTPClient::_x509_store = NULL;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::Constructor
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
HTTPClient::
|
||||
HTTPClient() {
|
||||
_verify_ssl = verify_ssl;
|
||||
make_ctx();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::Copy Constructor
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
HTTPClient::
|
||||
HTTPClient(const HTTPClient ©) {
|
||||
_verify_ssl = verify_ssl;
|
||||
make_ctx();
|
||||
|
||||
(*this) = copy;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::Copy Assignment Operator
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void HTTPClient::
|
||||
operator = (const HTTPClient ©) {
|
||||
_proxy = copy._proxy;
|
||||
set_verify_ssl(copy._verify_ssl);
|
||||
clear_expected_servers();
|
||||
|
||||
ExpectedServers::const_iterator ei;
|
||||
for (ei = copy._expected_servers.begin();
|
||||
ei != copy._expected_servers.end();
|
||||
++ei) {
|
||||
X509_NAME *orig_name = (*ei);
|
||||
X509_NAME *new_name = X509_NAME_dup(orig_name);
|
||||
_expected_servers.push_back(new_name);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::Destructor
|
||||
// Access: Published
|
||||
|
|
@ -49,6 +94,9 @@ HTTPClient::
|
|||
nassertv(_ssl_ctx->cert_store == _x509_store);
|
||||
_ssl_ctx->cert_store = NULL;
|
||||
SSL_CTX_free(_ssl_ctx);
|
||||
|
||||
// Free all of the expected server definitions.
|
||||
clear_expected_servers();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -89,6 +137,52 @@ load_certificates(const Filename &filename) {
|
|||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::add_expected_server
|
||||
// Access: Published
|
||||
// Description: Adds the indicated string as a definition of a valid
|
||||
// server to contact via https. If no servers have been
|
||||
// been added, an https connection will be allowed to
|
||||
// any server. If at least one server has been added,
|
||||
// an https connection will be allowed to any of the
|
||||
// named servers, but none others.
|
||||
//
|
||||
// The string passed in defines a subset of the server
|
||||
// properties that are to be insisted on, using the X509
|
||||
// naming convention, e.g. O=WDI/OU=VRStudio/CN=ttown.
|
||||
//
|
||||
// It makes sense to use this in conjunction with
|
||||
// set_verify_ssl(), which insists that the https
|
||||
// connection uses a verifiable certificate.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool HTTPClient::
|
||||
add_expected_server(const string &server_attributes) {
|
||||
X509_NAME *name = parse_x509_name(server_attributes);
|
||||
if (name == (X509_NAME *)NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_expected_servers.push_back(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::clear_expected_servers
|
||||
// Access: Published
|
||||
// Description: Clears the set of expected servers; the HTTPClient
|
||||
// will allow an https connection to any server.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void HTTPClient::
|
||||
clear_expected_servers() {
|
||||
for (ExpectedServers::iterator ei = _expected_servers.begin();
|
||||
ei != _expected_servers.end();
|
||||
++ei) {
|
||||
X509_NAME *name = (*ei);
|
||||
X509_NAME_free(name);
|
||||
}
|
||||
_expected_servers.clear();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::get_document
|
||||
// Access: Published
|
||||
|
|
@ -143,11 +237,38 @@ make_ctx() {
|
|||
#endif
|
||||
|
||||
// Insist on verifying servers if we are configured to.
|
||||
set_verify_ssl(verify_ssl);
|
||||
if (_verify_ssl) {
|
||||
SSL_CTX_set_verify(_ssl_ctx, SSL_VERIFY_PEER, NULL);
|
||||
} else {
|
||||
SSL_CTX_set_verify(_ssl_ctx, SSL_VERIFY_NONE, NULL);
|
||||
}
|
||||
|
||||
// Get the configured set of expected servers.
|
||||
{
|
||||
// Load in any default certificates listed in the Configrc file.
|
||||
Config::ConfigTable::Symbol expected_servers;
|
||||
config_express.GetAll("expected-ssl-server", expected_servers);
|
||||
|
||||
// When we use GetAll(), we might inadvertently read duplicate
|
||||
// lines. Filter them out with a set.
|
||||
pset<string> already_read;
|
||||
|
||||
Config::ConfigTable::Symbol::iterator si;
|
||||
for (si = expected_servers.begin(); si != expected_servers.end(); ++si) {
|
||||
string expected_server = (*si).Val();
|
||||
if (already_read.insert(expected_server).second) {
|
||||
add_expected_server(expected_server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_x509_store != (X509_STORE *)NULL) {
|
||||
// If we've already created an x509 store object, use it for this
|
||||
// context.
|
||||
// If we've already created an x509 store object, share it with
|
||||
// this context. It would be better to make a copy of the store
|
||||
// object for each context, so we could locally add certificates,
|
||||
// but (a) there doesn't seem to be an interface for this, and (b)
|
||||
// something funny about loading certificates that seems to save
|
||||
// some persistent global state anyway.
|
||||
SSL_CTX_set_cert_store(_ssl_ctx, _x509_store);
|
||||
|
||||
} else {
|
||||
|
|
@ -470,6 +591,7 @@ make_https_connection(BIO *bio, const URLSpec &url) const {
|
|||
#ifndef NDEBUG
|
||||
ERR_print_errors_fp(stderr);
|
||||
#endif
|
||||
BIO_free_all(bio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -485,6 +607,13 @@ make_https_connection(BIO *bio, const URLSpec &url) const {
|
|||
downloader_cat.info()
|
||||
<< "No certificate was presented by server.\n";
|
||||
|
||||
if (!_expected_servers.empty()) {
|
||||
downloader_cat.info()
|
||||
<< "Not allowing connection since no certificates could be matched.\n";
|
||||
BIO_free_all(sbio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (downloader_cat.is_debug()) {
|
||||
downloader_cat.debug()
|
||||
|
|
@ -492,6 +621,23 @@ make_https_connection(BIO *bio, const URLSpec &url) const {
|
|||
X509_print_fp(stderr, cert);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
X509_NAME *subject = X509_get_subject_name(cert);
|
||||
|
||||
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.info()
|
||||
<< "Server is " << common_name << " from " << org_unit_name
|
||||
<< " of " << org_name << "\n";
|
||||
|
||||
if (!verify_server(subject)) {
|
||||
downloader_cat.info()
|
||||
<< "Server does not match any expected server.\n";
|
||||
BIO_free_all(sbio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
X509_free(cert);
|
||||
}
|
||||
|
|
@ -500,6 +646,7 @@ make_https_connection(BIO *bio, const URLSpec &url) const {
|
|||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Certificate verify error codes:
|
||||
|
||||
|
|
@ -714,6 +861,205 @@ send_get_request(BIO *bio,
|
|||
BIO_puts(bio, request_str.c_str());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::verify_server
|
||||
// Access: Private
|
||||
// Description: Returns true if the indicated server matches one of
|
||||
// our expected servers (or the list of expected servers
|
||||
// is empty), or false if it does not match any of our
|
||||
// expected servers.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
bool HTTPClient::
|
||||
verify_server(X509_NAME *subject) const {
|
||||
if (_expected_servers.empty()) {
|
||||
if (downloader_cat.is_debug()) {
|
||||
downloader_cat.debug()
|
||||
<< "No expected servers on list; allowing any https connection.\n";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (downloader_cat.is_debug()) {
|
||||
downloader_cat.debug() << "checking server: " << flush;
|
||||
X509_NAME_print_ex_fp(stderr, subject, 0, 0);
|
||||
fflush(stderr);
|
||||
downloader_cat.debug(false) << "\n";
|
||||
}
|
||||
|
||||
ExpectedServers::const_iterator ei;
|
||||
for (ei = _expected_servers.begin();
|
||||
ei != _expected_servers.end();
|
||||
++ei) {
|
||||
X509_NAME *expected_name = (*ei);
|
||||
if (x509_name_subset(expected_name, subject)) {
|
||||
if (downloader_cat.is_debug()) {
|
||||
downloader_cat.debug()
|
||||
<< "Match found!\n";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// None of the lines matched.
|
||||
if (downloader_cat.is_debug()) {
|
||||
downloader_cat.debug()
|
||||
<< "No match found against any of the following expected servers:\n";
|
||||
|
||||
for (ei = _expected_servers.begin();
|
||||
ei != _expected_servers.end();
|
||||
++ei) {
|
||||
X509_NAME *expected_name = (*ei);
|
||||
X509_NAME_print_ex_fp(stderr, expected_name, 0, 0);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::parse_x509_name
|
||||
// Access: Private, Static
|
||||
// Description: Parses a string of the form
|
||||
// /type0=value0/type1=value1/type2=... into a newly
|
||||
// allocated X509_NAME object. Returns NULL if the
|
||||
// string is invalid.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
X509_NAME *HTTPClient::
|
||||
parse_x509_name(const string &source) {
|
||||
X509_NAME *result = NULL;
|
||||
|
||||
result = X509_NAME_new();
|
||||
bool added_any = false;
|
||||
|
||||
string::const_iterator si;
|
||||
si = source.begin();
|
||||
while (si != source.end()) {
|
||||
if ((*si) == '/') {
|
||||
// Skip a slash delimiter.
|
||||
++si;
|
||||
} else {
|
||||
string type;
|
||||
while (si != source.end() && (*si) != '=' && (*si) != '/') {
|
||||
if ((*si) == '\\') {
|
||||
++si;
|
||||
if (si != source.end()) {
|
||||
type += (*si);
|
||||
++si;
|
||||
}
|
||||
} else {
|
||||
type += (*si);
|
||||
++si;
|
||||
}
|
||||
}
|
||||
|
||||
int nid = OBJ_txt2nid(type.c_str());
|
||||
if (nid == NID_undef) {
|
||||
downloader_cat.info()
|
||||
<< "Unknown type " << type << " in X509 name: " << source
|
||||
<< "\n";
|
||||
X509_NAME_free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
string value;
|
||||
|
||||
if (si != source.end() && (*si) == '=') {
|
||||
++si;
|
||||
while (si != source.end() && (*si) != '/') {
|
||||
if ((*si) == '\\') {
|
||||
++si;
|
||||
if (si != source.end()) {
|
||||
value += (*si);
|
||||
++si;
|
||||
}
|
||||
} else {
|
||||
value += (*si);
|
||||
++si;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!value.empty()) {
|
||||
int add_result =
|
||||
X509_NAME_add_entry_by_NID(result, nid, V_ASN1_APP_CHOOSE,
|
||||
(unsigned char *)value.c_str(), -1, -1, 0);
|
||||
if (!add_result) {
|
||||
downloader_cat.info()
|
||||
<< "Unable to add " << type << "=" << value << " in X509 name: "
|
||||
<< source << "\n";
|
||||
X509_NAME_free(result);
|
||||
return NULL;
|
||||
}
|
||||
added_any = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!added_any) {
|
||||
downloader_cat.info()
|
||||
<< "Invalid empty X509 name: " << source << "\n";
|
||||
X509_NAME_free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::get_x509_name_component
|
||||
// Access: Private, Static
|
||||
// Description: Returns the indicated component of the X509 name as a
|
||||
// string, if defined, or empty string if it is not.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
string HTTPClient::
|
||||
get_x509_name_component(X509_NAME *name, int nid) {
|
||||
ASN1_OBJECT *obj = OBJ_nid2obj(nid);
|
||||
|
||||
if (obj == NULL) {
|
||||
// Unknown nid. See openssl/objects.h.
|
||||
return string();
|
||||
}
|
||||
|
||||
int i = X509_NAME_get_index_by_OBJ(name, obj, -1);
|
||||
if (i < 0) {
|
||||
return string();
|
||||
}
|
||||
|
||||
ASN1_STRING *data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i));
|
||||
return string((char *)data->data, data->length);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// 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;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: HTTPClient::show_send
|
||||
|
|
|
|||
|
|
@ -47,9 +47,9 @@ class Filename;
|
|||
////////////////////////////////////////////////////////////////////
|
||||
class EXPCL_PANDAEXPRESS HTTPClient {
|
||||
PUBLISHED:
|
||||
INLINE HTTPClient();
|
||||
INLINE HTTPClient(const HTTPClient ©);
|
||||
INLINE void operator = (const HTTPClient ©);
|
||||
HTTPClient();
|
||||
HTTPClient(const HTTPClient ©);
|
||||
void operator = (const HTTPClient ©);
|
||||
~HTTPClient();
|
||||
|
||||
INLINE void set_proxy(const URLSpec &proxy);
|
||||
|
|
@ -60,6 +60,9 @@ PUBLISHED:
|
|||
INLINE void set_verify_ssl(bool verify_ssl);
|
||||
INLINE bool get_verify_ssl() const;
|
||||
|
||||
bool add_expected_server(const string &server_attributes);
|
||||
void clear_expected_servers();
|
||||
|
||||
PT(HTTPDocument) get_document(const URLSpec &url, const string &body = string());
|
||||
|
||||
private:
|
||||
|
|
@ -76,6 +79,11 @@ private:
|
|||
void send_get_request(BIO *bio,
|
||||
const string &path, const string &server,
|
||||
const string &body) const;
|
||||
bool verify_server(X509_NAME *subject) const;
|
||||
|
||||
static X509_NAME *parse_x509_name(const string &source);
|
||||
static string get_x509_name_component(X509_NAME *name, int nid);
|
||||
static bool x509_name_subset(X509_NAME *name_a, X509_NAME *name_b);
|
||||
|
||||
#ifndef NDEBUG
|
||||
static void show_send(const string &message);
|
||||
|
|
@ -89,6 +97,12 @@ private:
|
|||
|
||||
URLSpec _proxy;
|
||||
bool _verify_ssl;
|
||||
|
||||
// List of allowable SSL servers to connect to. If the list is
|
||||
// empty, any server is acceptable.
|
||||
typedef pvector<X509_NAME *> ExpectedServers;
|
||||
ExpectedServers _expected_servers;
|
||||
|
||||
SSL_CTX *_ssl_ctx;
|
||||
|
||||
static bool _ssl_initialized;
|
||||
|
|
|
|||
Loading…
Reference in New Issue