Auto merge of #6935 - jdm:sslverify, r=Manishearth

Make SSL cert verification errors work again. Add a horrible, no-good…

…, very bad regression test.

Here are the list of awful things this test exploits:
- Servo can't load HTTPS content in WPT tests (#6919)
- Our web workers don't report error events to the parent worker object after the initial network load completes
- Our worker resource load don't have a same-origin check

The good news is that this test should start failing if any of those "features" change, so this should not silently break on us.

Other attempts to test this included:
- iframes (didn't work because of #6672 and #3939)
- XMLHttpRequest (I was hit by CORS, I think; maybe I could have made it work if I returned the right headers)

r? @Ms2ger

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/6935)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2015-11-13 22:00:57 +05:30
commit 0735cec351
4 changed files with 63 additions and 20 deletions

View file

@ -27,6 +27,7 @@ use msg::constellation_msg::{PipelineId};
use net_traits::ProgressMsg::{Done, Payload}; use net_traits::ProgressMsg::{Done, Payload};
use net_traits::hosts::replace_hosts; use net_traits::hosts::replace_hosts;
use net_traits::{CookieSource, IncludeSubdomains, LoadConsumer, LoadData, Metadata}; use net_traits::{CookieSource, IncludeSubdomains, LoadConsumer, LoadData, Metadata};
use openssl::ssl::error::{SslError, OpensslError};
use openssl::ssl::{SSL_VERIFY_PEER, SslContext, SslMethod}; use openssl::ssl::{SSL_VERIFY_PEER, SslContext, SslMethod};
use resource_task::{CancellationListener, send_error, start_sending_sniffed_opt}; use resource_task::{CancellationListener, send_error, start_sending_sniffed_opt};
use std::borrow::ToOwned; use std::borrow::ToOwned;
@ -210,29 +211,21 @@ impl HttpRequestFactory for NetworkHttpRequestFactory {
fn create(&self, url: Url, method: Method) -> Result<WrappedHttpRequest, LoadError> { fn create(&self, url: Url, method: Method) -> Result<WrappedHttpRequest, LoadError> {
let connection = Request::with_connector(method, url.clone(), &*self.connector); let connection = Request::with_connector(method, url.clone(), &*self.connector);
let ssl_err_string = "Some(OpenSslErrors([UnknownError { library: \"SSL routines\", \ if let Err(HttpError::Ssl(ref error)) = connection {
function: \"SSL3_GET_SERVER_CERTIFICATE\", \ let error: &(Error + Send + 'static) = &**error;
reason: \"certificate verify failed\" }]))"; if let Some(&SslError::OpenSslErrors(ref errors)) = error.downcast_ref::<SslError>() {
if errors.iter().any(is_cert_verify_error) {
return Err(
LoadError::Ssl(url, format!("ssl error: {:?} {:?}",
error.description(),
error.cause())));
}
}
}
let request = match connection { let request = match connection {
Ok(req) => req, Ok(req) => req,
Err(HttpError::Io(ref io_error)) if (
io_error.kind() == io::ErrorKind::Other &&
io_error.description() == "Error in OpenSSL" &&
// FIXME: This incredibly hacky. Make it more robust, and at least test it.
format!("{:?}", io_error.cause()) == ssl_err_string
) => {
return Err(
LoadError::Ssl(
url,
format!("ssl error {:?}: {:?} {:?}",
io_error.kind(),
io_error.description(),
io_error.cause())
)
)
},
Err(e) => { Err(e) => {
return Err(LoadError::Connection(url, e.description().to_owned())) return Err(LoadError::Connection(url, e.description().to_owned()))
} }
@ -756,3 +749,14 @@ fn send_data<R: Read>(reader: &mut R,
let _ = progress_chan.send(Done(Ok(()))); let _ = progress_chan.send(Done(Ok(())));
} }
// FIXME: This incredibly hacky. Make it more robust, and at least test it.
fn is_cert_verify_error(error: &OpensslError) -> bool {
match error {
&OpensslError::UnknownError { ref library, ref function, ref reason } => {
library == "SSL routines" &&
function == "SSL3_GET_SERVER_CERTIFICATE" &&
reason == "certificate verify failed"
}
}
}

View file

@ -4559,6 +4559,12 @@
"url": "/_mozilla/mozilla/MouseEvent.html" "url": "/_mozilla/mozilla/MouseEvent.html"
} }
], ],
"mozilla/bad_cert_detected.html": [
{
"path": "mozilla/bad_cert_detected.html",
"url": "/_mozilla/mozilla/bad_cert_detected.html"
}
],
"mozilla/blob.html": [ "mozilla/blob.html": [
{ {
"path": "mozilla/blob.html", "path": "mozilla/blob.html",
@ -9622,4 +9628,4 @@
"rev": null, "rev": null,
"url_base": "/_mozilla/", "url_base": "/_mozilla/",
"version": 2 "version": 2
} }

View file

@ -0,0 +1,28 @@
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/origin_helpers.js?pipe=sub"></script>
</head>
<body>
<script>
var t = async_test("Invalid SSL cert noticed");
t.step(function() {
var target = location.href.replace(HTTP_ORIGIN, HTTPS_ORIGIN)
.replace('bad_cert_detected.html',
'resources/origin_helpers.js');
// Servo currently lacks the ability to introspect any content that is blocked
// due to a cert error, so we use a roundabout method to infer that that's happened.
// When the worker has a cert failure, that translates into attempting to evaluate the
// contents of badcert.html as JS, which triggers an exception that currently does not
// propagate to the parent scope. If we _do_ get an error event in the parent scope,
// that means that the cert verification was treated no different than any other
// network error, since we dispatch an error event in that case.
var w = new Worker(target);
w.addEventListener('error', t.unreached_func("cert not detected as invalid"), false);
// We infer that we detected an invalid cert if nothing happens for a few seconds.
setTimeout(function() { t.done() }, 3000);
});
</script>
</body>
</html>

View file

@ -0,0 +1,5 @@
var HTTP_PORT = '{{ports[http][0]}}';
var HTTPS_PORT = '{{ports[https][0]}}';
var ORIGINAL_HOST = '\'{{host}}\'';
var HTTP_ORIGIN = 'http://' + ORIGINAL_HOST + ':' + HTTP_PORT;
var HTTPS_ORIGIN = 'https://' + ORIGINAL_HOST + ':' + HTTPS_PORT;