Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UI for bypassing SSL handshake failures #26716

Merged
merged 7 commits into from Jun 10, 2020

Some generated files are not rendered by default. Learn more.

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

This comment has been minimized.

Copy link
@asajeffrey

asajeffrey May 29, 2020

Member

This looks a lot like a poor dev's weak Rc. Is it?

This comment has been minimized.

Copy link
@jdm

jdm May 29, 2020

Author Member

It does have the property of removing the hashmap entry when the last user goes away, so it's slightly more complex than Rc/Arc.

This comment has been minimized.

Copy link
@asajeffrey

asajeffrey Jun 2, 2020

Member

Yeah, we'd need to add some Drop code to remove the cert from the hash table. So it's borderline whether it's better to reuse weak refcounts.

This comment has been minimized.

Copy link
@Manishearth

Manishearth Jun 2, 2020

Member

Yeah I could go in either direction here

}

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,14 +168,56 @@ 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
}

pub fn create_http_client<E>(tls_config: TlsConfig, executor: E) -> Client<Connector, Body>
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(),
);
}
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.