mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Auto merge of #26716 - jdm:selfsigned, r=Manishearth,asajeffrey
Add UI for bypassing SSL handshake failures There are several parts to these changes: 1. resurrecting the network error classification code to distinguish between SSL failures and other network errors 1. adding an SSL verification callback to support verifying certs against a list that can change at runtime, rather than just at program initialization 1. exposing a privileged chrome://allowcert URI which accepts the PEM cert contents along with a secret token 1. extracting the PEM cert contents out of the network layer when a handshake failure occurs, and getting them into the HTML that is parsed when an SSL failure occurs 1. adding a button in the handshake failure page that performs an XHR to chrome://allowcert with knowledge of the secret token and the PEM cert contents, before reloading the original URL that failed The presence of the secret token means that while the chrome://allowcert URL is currently visible to web content, they cannot make use of it to inject arbitrary certs into the verification process. --- - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #26683 - [x] These changes do not require tests because the UI requires user activation and can't clearly be automated
This commit is contained in:
commit
0b0ea17dca
14 changed files with 362 additions and 41 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3614,6 +3614,7 @@ dependencies = [
|
|||
"pixels",
|
||||
"serde",
|
||||
"servo_arc",
|
||||
"servo_rand",
|
||||
"servo_url",
|
||||
"std_test_override",
|
||||
"time",
|
||||
|
|
|
@ -8,8 +8,13 @@ use hyper::client::HttpConnector as HyperHttpConnector;
|
|||
use hyper::rt::Future;
|
||||
use hyper::{Body, Client};
|
||||
use hyper_openssl::HttpsConnector;
|
||||
use openssl::ssl::{SslConnector, SslConnectorBuilder, SslMethod, SslOptions};
|
||||
use openssl::x509;
|
||||
use openssl::ex_data::Index;
|
||||
use openssl::ssl::{
|
||||
Ssl, SslConnector, SslConnectorBuilder, SslContext, SslMethod, SslOptions, SslVerifyMode,
|
||||
};
|
||||
use openssl::x509::{self, X509StoreContext};
|
||||
use std::collections::hash_map::{Entry, HashMap};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::prelude::future::Executor;
|
||||
|
||||
pub const BUF_SIZE: usize = 32768;
|
||||
|
@ -30,6 +35,38 @@ const SIGNATURE_ALGORITHMS: &'static str = concat!(
|
|||
"RSA+SHA512:RSA+SHA384:RSA+SHA256"
|
||||
);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConnectionCerts {
|
||||
certs: Arc<Mutex<HashMap<String, (Vec<u8>, u32)>>>,
|
||||
}
|
||||
|
||||
impl ConnectionCerts {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
certs: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
fn store(&self, host: String, cert_bytes: Vec<u8>) {
|
||||
let mut certs = self.certs.lock().unwrap();
|
||||
let entry = certs.entry(host).or_insert((cert_bytes, 0));
|
||||
entry.1 += 1;
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&self, host: String) -> Option<Vec<u8>> {
|
||||
match self.certs.lock().unwrap().entry(host) {
|
||||
Entry::Vacant(_) => return None,
|
||||
Entry::Occupied(mut e) => {
|
||||
e.get_mut().1 -= 1;
|
||||
if e.get().1 == 0 {
|
||||
return Some((e.remove_entry().1).0);
|
||||
}
|
||||
Some(e.get().0.clone())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HttpConnector {
|
||||
inner: HyperHttpConnector,
|
||||
}
|
||||
|
@ -60,7 +97,34 @@ impl Connect for HttpConnector {
|
|||
pub type Connector = HttpsConnector<HttpConnector>;
|
||||
pub type TlsConfig = SslConnectorBuilder;
|
||||
|
||||
pub fn create_tls_config(certs: &str, alpn: &[u8]) -> TlsConfig {
|
||||
#[derive(Clone)]
|
||||
pub struct ExtraCerts(Arc<Mutex<Vec<Vec<u8>>>>);
|
||||
|
||||
impl ExtraCerts {
|
||||
pub fn new() -> Self {
|
||||
Self(Arc::new(Mutex::new(vec![])))
|
||||
}
|
||||
|
||||
pub fn add(&self, bytes: Vec<u8>) {
|
||||
self.0.lock().unwrap().push(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
struct Host(String);
|
||||
|
||||
lazy_static! {
|
||||
static ref EXTRA_INDEX: Index<SslContext, ExtraCerts> = SslContext::new_ex_index().unwrap();
|
||||
static ref CONNECTION_INDEX: Index<SslContext, ConnectionCerts> =
|
||||
SslContext::new_ex_index().unwrap();
|
||||
static ref HOST_INDEX: Index<Ssl, Host> = Ssl::new_ex_index().unwrap();
|
||||
}
|
||||
|
||||
pub fn create_tls_config(
|
||||
certs: &str,
|
||||
alpn: &[u8],
|
||||
extra_certs: ExtraCerts,
|
||||
connection_certs: ConnectionCerts,
|
||||
) -> TlsConfig {
|
||||
// certs include multiple certificates. We could add all of them at once,
|
||||
// but if any of them were already added, openssl would fail to insert all
|
||||
// of them.
|
||||
|
@ -104,6 +168,44 @@ pub fn create_tls_config(certs: &str, alpn: &[u8]) -> TlsConfig {
|
|||
SslOptions::NO_COMPRESSION,
|
||||
);
|
||||
|
||||
cfg.set_ex_data(*EXTRA_INDEX, extra_certs);
|
||||
cfg.set_ex_data(*CONNECTION_INDEX, connection_certs);
|
||||
cfg.set_verify_callback(SslVerifyMode::PEER, |verified, x509_store_context| {
|
||||
if verified {
|
||||
return true;
|
||||
}
|
||||
|
||||
let ssl_idx = X509StoreContext::ssl_idx().unwrap();
|
||||
let ssl = x509_store_context.ex_data(ssl_idx).unwrap();
|
||||
|
||||
// Obtain the cert bytes for this connection.
|
||||
let cert = match x509_store_context.current_cert() {
|
||||
Some(cert) => cert,
|
||||
None => return false,
|
||||
};
|
||||
let pem = match cert.to_pem() {
|
||||
Ok(pem) => pem,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
let ssl_context = ssl.ssl_context();
|
||||
|
||||
// Ensure there's an entry stored in the set of known connection certs for this connection.
|
||||
if let Some(host) = ssl.ex_data(*HOST_INDEX) {
|
||||
let connection_certs = ssl_context.ex_data(*CONNECTION_INDEX).unwrap();
|
||||
connection_certs.store((*host).0.clone(), pem.clone());
|
||||
}
|
||||
|
||||
// Fall back to the dynamic set of allowed certs.
|
||||
let extra_certs = ssl_context.ex_data(*EXTRA_INDEX).unwrap();
|
||||
for cert in &*extra_certs.0.lock().unwrap() {
|
||||
if pem == *cert {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
});
|
||||
|
||||
cfg
|
||||
}
|
||||
|
||||
|
@ -111,7 +213,11 @@ pub fn create_http_client<E>(tls_config: TlsConfig, executor: E) -> Client<Conne
|
|||
where
|
||||
E: Executor<Box<dyn Future<Error = (), Item = ()> + Send + 'static>> + Sync + Send + 'static,
|
||||
{
|
||||
let connector = HttpsConnector::with_connector(HttpConnector::new(), tls_config).unwrap();
|
||||
let mut connector = HttpsConnector::with_connector(HttpConnector::new(), tls_config).unwrap();
|
||||
connector.set_callback(|configuration, destination| {
|
||||
configuration.set_ex_data(*HOST_INDEX, Host(destination.host().to_owned()));
|
||||
Ok(())
|
||||
});
|
||||
|
||||
Client::builder()
|
||||
.http1_title_case_headers(true)
|
||||
|
|
|
@ -15,17 +15,19 @@ use headers::{AccessControlExposeHeaders, ContentType, HeaderMapExt, Range};
|
|||
use http::header::{self, HeaderMap, HeaderName};
|
||||
use hyper::Method;
|
||||
use hyper::StatusCode;
|
||||
use ipc_channel::ipc::IpcReceiver;
|
||||
use ipc_channel::ipc::{self, IpcReceiver};
|
||||
use mime::{self, Mime};
|
||||
use net_traits::blob_url_store::{parse_blob_url, BlobURLStoreError};
|
||||
use net_traits::filemanager_thread::{FileTokenCheck, RelativePos};
|
||||
use net_traits::request::{
|
||||
is_cors_safelisted_method, is_cors_safelisted_request_header, Origin, ResponseTainting, Window,
|
||||
};
|
||||
use net_traits::request::{CredentialsMode, Destination, Referrer, Request, RequestMode};
|
||||
use net_traits::request::{
|
||||
BodyChunkRequest, CredentialsMode, Destination, Referrer, Request, RequestMode,
|
||||
};
|
||||
use net_traits::response::{Response, ResponseBody, ResponseType};
|
||||
use net_traits::{FetchTaskTarget, NetworkError, ReferrerPolicy, ResourceFetchTiming};
|
||||
use net_traits::{ResourceAttribute, ResourceTimeValue};
|
||||
use net_traits::{ResourceAttribute, ResourceTimeValue, ResourceTimingType};
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use servo_url::ServoUrl;
|
||||
use std::borrow::Cow;
|
||||
|
@ -282,7 +284,10 @@ pub fn main_fetch(
|
|||
false
|
||||
};
|
||||
|
||||
if (same_origin && !cors_flag) || current_url.scheme() == "data" {
|
||||
if (same_origin && !cors_flag) ||
|
||||
current_url.scheme() == "data" ||
|
||||
current_url.scheme() == "chrome"
|
||||
{
|
||||
// Substep 1.
|
||||
request.response_tainting = ResponseTainting::Basic;
|
||||
|
||||
|
@ -606,6 +611,17 @@ fn range_not_satisfiable_error(response: &mut Response) {
|
|||
response.raw_status = Some((StatusCode::RANGE_NOT_SATISFIABLE.as_u16(), reason.into()));
|
||||
}
|
||||
|
||||
fn create_blank_reply(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
|
||||
let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
|
||||
response
|
||||
.headers
|
||||
.typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
|
||||
*response.body.lock().unwrap() = ResponseBody::Done(vec![]);
|
||||
response.status = Some((StatusCode::OK, "OK".to_string()));
|
||||
response.raw_status = Some((StatusCode::OK.as_u16(), b"OK".to_vec()));
|
||||
response
|
||||
}
|
||||
|
||||
/// [Scheme fetch](https://fetch.spec.whatwg.org#scheme-fetch)
|
||||
fn scheme_fetch(
|
||||
request: &mut Request,
|
||||
|
@ -617,15 +633,31 @@ fn scheme_fetch(
|
|||
let url = request.current_url();
|
||||
|
||||
match url.scheme() {
|
||||
"about" if url.path() == "blank" => {
|
||||
let mut response = Response::new(url, ResourceFetchTiming::new(request.timing_type()));
|
||||
response
|
||||
.headers
|
||||
.typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
|
||||
*response.body.lock().unwrap() = ResponseBody::Done(vec![]);
|
||||
response.status = Some((StatusCode::OK, "OK".to_string()));
|
||||
response.raw_status = Some((StatusCode::OK.as_u16(), b"OK".to_vec()));
|
||||
response
|
||||
"about" if url.path() == "blank" => create_blank_reply(url, request.timing_type()),
|
||||
|
||||
"chrome" if url.path() == "allowcert" => {
|
||||
let data = request.body.as_mut().and_then(|body| {
|
||||
let stream = body.take_stream();
|
||||
let (body_chan, body_port) = ipc::channel().unwrap();
|
||||
let _ = stream.send(BodyChunkRequest::Connect(body_chan));
|
||||
let _ = stream.send(BodyChunkRequest::Chunk);
|
||||
body_port.recv().ok()
|
||||
});
|
||||
let data = data.as_ref().and_then(|b| {
|
||||
let idx = b.iter().position(|b| *b == b'&')?;
|
||||
Some(b.split_at(idx))
|
||||
});
|
||||
|
||||
if let Some((secret, bytes)) = data {
|
||||
let secret = str::from_utf8(secret).ok().and_then(|s| s.parse().ok());
|
||||
if secret == Some(*net_traits::PRIVILEGED_SECRET) {
|
||||
if let Ok(bytes) = base64::decode(&bytes[1..]) {
|
||||
context.state.extra_certs.add(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
create_blank_reply(url, request.timing_type())
|
||||
},
|
||||
|
||||
"http" | "https" => http_fetch(
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::connector::{create_http_client, Connector, TlsConfig};
|
||||
use crate::connector::{create_http_client, ConnectionCerts, Connector, ExtraCerts, TlsConfig};
|
||||
use crate::cookie;
|
||||
use crate::cookie_storage::CookieStorage;
|
||||
use crate::decoder::Decoder;
|
||||
|
@ -89,6 +89,8 @@ pub struct HttpState {
|
|||
pub auth_cache: RwLock<AuthCache>,
|
||||
pub history_states: RwLock<HashMap<HistoryStateId, Vec<u8>>>,
|
||||
pub client: Client<Connector, Body>,
|
||||
pub extra_certs: ExtraCerts,
|
||||
pub connection_certs: ConnectionCerts,
|
||||
}
|
||||
|
||||
impl HttpState {
|
||||
|
@ -104,6 +106,8 @@ impl HttpState {
|
|||
tls_config,
|
||||
HANDLE.lock().unwrap().as_ref().unwrap().executor(),
|
||||
),
|
||||
extra_certs: ExtraCerts::new(),
|
||||
connection_certs: ConnectionCerts::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -527,11 +531,19 @@ fn obtain_response(
|
|||
let method = method.clone();
|
||||
let send_start = precise_time_ms();
|
||||
|
||||
let host = request.uri().host().unwrap_or("").to_owned();
|
||||
let host_clone = request.uri().host().unwrap_or("").to_owned();
|
||||
let connection_certs = context.state.connection_certs.clone();
|
||||
let connection_certs_clone = context.state.connection_certs.clone();
|
||||
|
||||
let headers = headers.clone();
|
||||
Box::new(
|
||||
client
|
||||
.request(request)
|
||||
.and_then(move |res| {
|
||||
// We no longer need to track the cert for this connection.
|
||||
connection_certs.remove(host);
|
||||
|
||||
let send_end = precise_time_ms();
|
||||
|
||||
// TODO(#21271) response_start: immediately after receiving first byte of response
|
||||
|
@ -564,7 +576,9 @@ fn obtain_response(
|
|||
};
|
||||
Ok((Decoder::detect(res), msg))
|
||||
})
|
||||
.map_err(move |e| NetworkError::from_hyper_error(&e)),
|
||||
.map_err(move |e| {
|
||||
NetworkError::from_hyper_error(&e, connection_certs_clone.remove(host_clone))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1557,7 +1571,7 @@ fn http_network_fetch(
|
|||
&url,
|
||||
&request.method,
|
||||
&request.headers,
|
||||
request.body.as_mut().and_then(|body| body.take_stream()),
|
||||
request.body.as_mut().map(|body| body.take_stream()),
|
||||
&request.pipeline_id,
|
||||
request_id.as_ref().map(Deref::deref),
|
||||
is_xhr,
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
//! A thread that takes a URL and streams back the binary data.
|
||||
|
||||
use crate::connector::{create_http_client, create_tls_config, ALPN_H2_H1};
|
||||
use crate::connector::{
|
||||
create_http_client, create_tls_config, ConnectionCerts, ExtraCerts, ALPN_H2_H1,
|
||||
};
|
||||
use crate::cookie;
|
||||
use crate::cookie_storage::CookieStorage;
|
||||
use crate::fetch::cors_cache::CorsCache;
|
||||
|
@ -143,6 +145,9 @@ fn create_http_states(
|
|||
None => resources::read_string(Resource::SSLCertificates),
|
||||
};
|
||||
|
||||
let extra_certs = ExtraCerts::new();
|
||||
let connection_certs = ConnectionCerts::new();
|
||||
|
||||
let http_state = HttpState {
|
||||
hsts_list: RwLock::new(hsts_list),
|
||||
cookie_jar: RwLock::new(cookie_jar),
|
||||
|
@ -151,11 +156,21 @@ fn create_http_states(
|
|||
http_cache: RwLock::new(http_cache),
|
||||
http_cache_state: Mutex::new(HashMap::new()),
|
||||
client: create_http_client(
|
||||
create_tls_config(&certs, ALPN_H2_H1),
|
||||
create_tls_config(
|
||||
&certs,
|
||||
ALPN_H2_H1,
|
||||
extra_certs.clone(),
|
||||
connection_certs.clone(),
|
||||
),
|
||||
HANDLE.lock().unwrap().as_ref().unwrap().executor(),
|
||||
),
|
||||
extra_certs,
|
||||
connection_certs,
|
||||
};
|
||||
|
||||
let extra_certs = ExtraCerts::new();
|
||||
let connection_certs = ConnectionCerts::new();
|
||||
|
||||
let private_http_state = HttpState {
|
||||
hsts_list: RwLock::new(HstsList::from_servo_preload()),
|
||||
cookie_jar: RwLock::new(CookieStorage::new(150)),
|
||||
|
@ -164,9 +179,16 @@ fn create_http_states(
|
|||
http_cache: RwLock::new(HttpCache::new()),
|
||||
http_cache_state: Mutex::new(HashMap::new()),
|
||||
client: create_http_client(
|
||||
create_tls_config(&certs, ALPN_H2_H1),
|
||||
create_tls_config(
|
||||
&certs,
|
||||
ALPN_H2_H1,
|
||||
extra_certs.clone(),
|
||||
connection_certs.clone(),
|
||||
),
|
||||
HANDLE.lock().unwrap().as_ref().unwrap().executor(),
|
||||
),
|
||||
extra_certs,
|
||||
connection_certs,
|
||||
};
|
||||
|
||||
(Arc::new(http_state), Arc::new(private_http_state))
|
||||
|
@ -705,6 +727,8 @@ impl CoreResourceManager {
|
|||
action_receiver,
|
||||
http_state.clone(),
|
||||
self.certificate_path.clone(),
|
||||
http_state.extra_certs.clone(),
|
||||
http_state.connection_certs.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ use hyper::body::Body;
|
|||
use hyper::{Request as HyperRequest, Response as HyperResponse};
|
||||
use mime::{self, Mime};
|
||||
use msg::constellation_msg::TEST_PIPELINE_ID;
|
||||
use net::connector::{create_tls_config, ALPN_H2_H1};
|
||||
use net::connector::{create_tls_config, ConnectionCerts, ExtraCerts, ALPN_H2_H1};
|
||||
use net::fetch::cors_cache::CorsCache;
|
||||
use net::fetch::methods::{self, CancellationListener, FetchContext};
|
||||
use net::filemanager_thread::FileManager;
|
||||
|
@ -682,7 +682,12 @@ fn test_fetch_with_hsts() {
|
|||
let (server, url) = make_ssl_server(handler, cert_path.clone(), key_path.clone());
|
||||
|
||||
let certs = fs::read_to_string(cert_path).expect("Couldn't find certificate file");
|
||||
let tls_config = create_tls_config(&certs, ALPN_H2_H1);
|
||||
let tls_config = create_tls_config(
|
||||
&certs,
|
||||
ALPN_H2_H1,
|
||||
ExtraCerts::new(),
|
||||
ConnectionCerts::new(),
|
||||
);
|
||||
|
||||
let mut context = FetchContext {
|
||||
state: Arc::new(HttpState::new(tls_config)),
|
||||
|
@ -735,7 +740,12 @@ fn test_load_adds_host_to_hsts_list_when_url_is_https() {
|
|||
url.as_mut_url().set_scheme("https").unwrap();
|
||||
|
||||
let certs = fs::read_to_string(cert_path).expect("Couldn't find certificate file");
|
||||
let tls_config = create_tls_config(&certs, ALPN_H2_H1);
|
||||
let tls_config = create_tls_config(
|
||||
&certs,
|
||||
ALPN_H2_H1,
|
||||
ExtraCerts::new(),
|
||||
ConnectionCerts::new(),
|
||||
);
|
||||
|
||||
let mut context = FetchContext {
|
||||
state: Arc::new(HttpState::new(tls_config)),
|
||||
|
@ -776,6 +786,85 @@ fn test_load_adds_host_to_hsts_list_when_url_is_https() {
|
|||
.is_host_secure(url.host_str().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fetch_self_signed() {
|
||||
let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
|
||||
*response.body_mut() = b"Yay!".to_vec().into();
|
||||
};
|
||||
let client_cert_path = Path::new("../../resources/certs").canonicalize().unwrap();
|
||||
let cert_path = Path::new("../../resources/self_signed_certificate_for_testing.crt")
|
||||
.canonicalize()
|
||||
.unwrap();
|
||||
let key_path = Path::new("../../resources/privatekey_for_testing.key")
|
||||
.canonicalize()
|
||||
.unwrap();
|
||||
let (_server, mut url) = make_ssl_server(handler, cert_path.clone(), key_path.clone());
|
||||
url.as_mut_url().set_scheme("https").unwrap();
|
||||
|
||||
let cert_data = fs::read_to_string(cert_path.clone()).expect("Couldn't find certificate file");
|
||||
let client_cert_data =
|
||||
fs::read_to_string(client_cert_path.clone()).expect("Couldn't find certificate file");
|
||||
let extra_certs = ExtraCerts::new();
|
||||
let tls_config = create_tls_config(
|
||||
&client_cert_data,
|
||||
ALPN_H2_H1,
|
||||
extra_certs.clone(),
|
||||
ConnectionCerts::new(),
|
||||
);
|
||||
|
||||
let mut context = FetchContext {
|
||||
state: Arc::new(HttpState::new(tls_config)),
|
||||
user_agent: DEFAULT_USER_AGENT.into(),
|
||||
devtools_chan: None,
|
||||
filemanager: FileManager::new(create_embedder_proxy(), Weak::new()),
|
||||
file_token: FileTokenCheck::NotRequired,
|
||||
cancellation_listener: Arc::new(Mutex::new(CancellationListener::new(None))),
|
||||
timing: ServoArc::new(Mutex::new(ResourceFetchTiming::new(
|
||||
ResourceTimingType::Navigation,
|
||||
))),
|
||||
};
|
||||
|
||||
let mut request = RequestBuilder::new(url.clone())
|
||||
.method(Method::GET)
|
||||
.body(None)
|
||||
.destination(Destination::Document)
|
||||
.origin(url.clone().origin())
|
||||
.pipeline_id(Some(TEST_PIPELINE_ID))
|
||||
.build();
|
||||
|
||||
let response = fetch_with_context(&mut request, &mut context);
|
||||
|
||||
assert!(matches!(
|
||||
response.get_network_error(),
|
||||
Some(NetworkError::SslValidation(..))
|
||||
));
|
||||
|
||||
extra_certs.add(cert_data.as_bytes().into());
|
||||
|
||||
// FIXME: something weird happens inside the SSL server after the first
|
||||
// connection encounters a verification error, and it no longer
|
||||
// accepts new connections that should work fine. We are forced
|
||||
// to start a new server and connect to that to verfiy that
|
||||
// the self-signed cert is now accepted.
|
||||
|
||||
let (server, mut url) = make_ssl_server(handler, cert_path.clone(), key_path.clone());
|
||||
url.as_mut_url().set_scheme("https").unwrap();
|
||||
|
||||
let mut request = RequestBuilder::new(url.clone())
|
||||
.method(Method::GET)
|
||||
.body(None)
|
||||
.destination(Destination::Document)
|
||||
.origin(url.clone().origin())
|
||||
.pipeline_id(Some(TEST_PIPELINE_ID))
|
||||
.build();
|
||||
|
||||
let response = fetch_with_context(&mut request, &mut context);
|
||||
|
||||
assert!(response.status.unwrap().0.is_success());
|
||||
|
||||
let _ = server.close();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fetch_with_sri_network_error() {
|
||||
static MESSAGE: &'static [u8] = b"alert('Hello, Network Error');";
|
||||
|
|
|
@ -29,7 +29,7 @@ use hyper::server::conn::Http;
|
|||
use hyper::server::Server as HyperServer;
|
||||
use hyper::service::service_fn_ok;
|
||||
use hyper::{Body, Request as HyperRequest, Response as HyperResponse};
|
||||
use net::connector::{create_tls_config, ALPN_H2_H1};
|
||||
use net::connector::{create_tls_config, ConnectionCerts, ExtraCerts, ALPN_H2_H1};
|
||||
use net::fetch::cors_cache::CorsCache;
|
||||
use net::fetch::methods::{self, CancellationListener, FetchContext};
|
||||
use net::filemanager_thread::FileManager;
|
||||
|
@ -91,7 +91,12 @@ fn new_fetch_context(
|
|||
pool_handle: Option<Weak<CoreResourceThreadPool>>,
|
||||
) -> FetchContext {
|
||||
let certs = resources::read_string(Resource::SSLCertificates);
|
||||
let tls_config = create_tls_config(&certs, ALPN_H2_H1);
|
||||
let tls_config = create_tls_config(
|
||||
&certs,
|
||||
ALPN_H2_H1,
|
||||
ExtraCerts::new(),
|
||||
ConnectionCerts::new(),
|
||||
);
|
||||
let sender = fc.unwrap_or_else(|| create_embedder_proxy());
|
||||
|
||||
FetchContext {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::connector::{create_tls_config, ALPN_H1};
|
||||
use crate::connector::{create_tls_config, ConnectionCerts, ExtraCerts, ALPN_H1};
|
||||
use crate::cookie::Cookie;
|
||||
use crate::fetch::methods::should_be_blocked_due_to_bad_port;
|
||||
use crate::hosts::replace_host;
|
||||
|
@ -38,6 +38,8 @@ struct Client<'a> {
|
|||
event_sender: &'a IpcSender<WebSocketNetworkEvent>,
|
||||
protocol_in_use: Option<String>,
|
||||
certificate_path: Option<String>,
|
||||
extra_certs: ExtraCerts,
|
||||
connection_certs: ConnectionCerts,
|
||||
}
|
||||
|
||||
impl<'a> Factory for Client<'a> {
|
||||
|
@ -167,7 +169,12 @@ impl<'a> Handler for Client<'a> {
|
|||
WebSocketErrorKind::Protocol,
|
||||
format!("Unable to parse domain from {}. Needed for SSL.", url),
|
||||
))?;
|
||||
let tls_config = create_tls_config(&certs, ALPN_H1);
|
||||
let tls_config = create_tls_config(
|
||||
&certs,
|
||||
ALPN_H1,
|
||||
self.extra_certs.clone(),
|
||||
self.connection_certs.clone(),
|
||||
);
|
||||
tls_config
|
||||
.build()
|
||||
.connect(domain, stream)
|
||||
|
@ -181,6 +188,8 @@ pub fn init(
|
|||
dom_action_receiver: IpcReceiver<WebSocketDomAction>,
|
||||
http_state: Arc<HttpState>,
|
||||
certificate_path: Option<String>,
|
||||
extra_certs: ExtraCerts,
|
||||
connection_certs: ConnectionCerts,
|
||||
) {
|
||||
thread::Builder::new()
|
||||
.name(format!("WebSocket connection to {}", req_builder.url))
|
||||
|
@ -229,6 +238,8 @@ pub fn init(
|
|||
event_sender: &resource_event_sender,
|
||||
protocol_in_use: None,
|
||||
certificate_path,
|
||||
extra_certs,
|
||||
connection_certs,
|
||||
};
|
||||
let mut ws = WebSocket::new(client).unwrap();
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ piston_image = { package = "image", version = "0.23" }
|
|||
pixels = { path = "../pixels" }
|
||||
serde = "1.0"
|
||||
servo_arc = { path = "../servo_arc" }
|
||||
servo_rand = { path = "../rand" }
|
||||
servo_url = { path = "../url" }
|
||||
time = "0.1"
|
||||
url = "2.0"
|
||||
|
|
|
@ -30,6 +30,7 @@ use ipc_channel::router::ROUTER;
|
|||
use ipc_channel::Error as IpcError;
|
||||
use mime::Mime;
|
||||
use msg::constellation_msg::HistoryStateId;
|
||||
use servo_rand::RngCore;
|
||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||
use time::precise_time_ns;
|
||||
use webrender_api::{ImageData, ImageDescriptor, ImageKey};
|
||||
|
@ -712,12 +713,17 @@ pub enum NetworkError {
|
|||
Internal(String),
|
||||
LoadCancelled,
|
||||
/// SSL validation error that has to be handled in the HTML parser
|
||||
SslValidation(ServoUrl, String),
|
||||
SslValidation(String, Vec<u8>),
|
||||
}
|
||||
|
||||
impl NetworkError {
|
||||
pub fn from_hyper_error(error: &HyperError) -> Self {
|
||||
NetworkError::Internal(error.to_string())
|
||||
pub fn from_hyper_error(error: &HyperError, cert_bytes: Option<Vec<u8>>) -> Self {
|
||||
let s = error.to_string();
|
||||
if s.contains("the handshake failed") {
|
||||
NetworkError::SslValidation(s, cert_bytes.unwrap_or_default())
|
||||
} else {
|
||||
NetworkError::Internal(s)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_http_error(error: &HttpError) -> Self {
|
||||
|
@ -806,3 +812,7 @@ impl WebrenderIpcSender {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref PRIVILEGED_SECRET: u32 = servo_rand::ServoRng::new().next_u32();
|
||||
}
|
||||
|
|
|
@ -164,7 +164,7 @@ impl RequestBody {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn take_stream(&mut self) -> Option<IpcSender<BodyChunkRequest>> {
|
||||
pub fn take_stream(&mut self) -> IpcSender<BodyChunkRequest> {
|
||||
if self.read_from {
|
||||
match self.source {
|
||||
BodySource::Null => panic!(
|
||||
|
@ -174,12 +174,12 @@ impl RequestBody {
|
|||
let (chan, port) = ipc::channel().unwrap();
|
||||
let _ = self.chan.send(BodyChunkRequest::Extract(port));
|
||||
self.chan = chan.clone();
|
||||
return Some(chan);
|
||||
return chan;
|
||||
},
|
||||
}
|
||||
}
|
||||
self.read_from = true;
|
||||
Some(self.chan.clone())
|
||||
self.chan.clone()
|
||||
}
|
||||
|
||||
pub fn source_is_null(&self) -> bool {
|
||||
|
|
|
@ -731,9 +731,9 @@ impl FetchResponseListener for ParserContext {
|
|||
FetchMetadata::Unfiltered(m) => m,
|
||||
FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
|
||||
}),
|
||||
Err(NetworkError::SslValidation(url, reason)) => {
|
||||
ssl_error = Some(reason);
|
||||
let mut meta = Metadata::default(url);
|
||||
Err(NetworkError::SslValidation(reason, cert_bytes)) => {
|
||||
ssl_error = Some((reason, cert_bytes));
|
||||
let mut meta = Metadata::default(self.url.clone());
|
||||
let mime: Option<Mime> = "text/html".parse().ok();
|
||||
meta.set_content_type(mime.as_ref());
|
||||
Some(meta)
|
||||
|
@ -815,10 +815,14 @@ impl FetchResponseListener for ParserContext {
|
|||
},
|
||||
Some(ref mime) if mime.type_() == mime::TEXT && mime.subtype() == mime::HTML => {
|
||||
// Handle text/html
|
||||
if let Some(reason) = ssl_error {
|
||||
if let Some((reason, bytes)) = ssl_error {
|
||||
self.is_synthesized_document = true;
|
||||
let page = resources::read_string(Resource::BadCertHTML);
|
||||
let page = page.replace("${reason}", &reason);
|
||||
let page =
|
||||
page.replace("${bytes}", std::str::from_utf8(&bytes).unwrap_or_default());
|
||||
let page =
|
||||
page.replace("${secret}", &net_traits::PRIVILEGED_SECRET.to_string());
|
||||
parser.push_string_input_chunk(page);
|
||||
parser.parse_sync();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,26 @@
|
|||
<title>Certificate error</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>${reason}</p>
|
||||
<p>${reason}</p>
|
||||
<pre id="bytes">${bytes}</pre>
|
||||
<button id="leave" onclick="history.back()">Go back (recommended)</button>
|
||||
<button id="allow">Allow certificate temporarily</button>
|
||||
<script>
|
||||
let bytes = document.getElementById('bytes').textContent;
|
||||
let button = document.getElementById('allow');
|
||||
let exitButton = document.getElementById('leave');
|
||||
if (bytes.length) {
|
||||
button.onclick = function() {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'chrome:allowcert');
|
||||
xhr.onloadend = function() {
|
||||
location.reload(true);
|
||||
};
|
||||
xhr.send("${secret}&" + btoa(bytes));
|
||||
};
|
||||
} else {
|
||||
button.style.display = "none";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
button {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
background: white;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue