From 56488bdbcb2f73ec4a85196cbb1a6d6e86dff35e Mon Sep 17 00:00:00 2001
From: Matt Traudt <sirmatt@ksu.edu>
Date: Sat, 2 Jun 2018 16:16:57 -0700
Subject: [PATCH] Add TLS verify option to destinations

---
 sbws/core/scanner.py    |  3 ++-
 sbws/lib/destination.py | 33 ++++++++++++++++++++++++++++++---
 sbws/util/config.py     |  6 +++---
 3 files changed, 35 insertions(+), 7 deletions(-)

diff --git a/sbws/core/scanner.py b/sbws/core/scanner.py
index d75d7943..74081d44 100644
--- a/sbws/core/scanner.py
+++ b/sbws/core/scanner.py
@@ -39,7 +39,8 @@ def timed_recv_from_server(session, dest, byte_range):
     # - What other exceptions can this throw?
     # - Do we have to read the content, or did requests already do so?
     try:
-        requests_utils.get(session, dest.url, headers=headers)
+        requests_utils.get(
+            session, dest.url, headers=headers, verify=dest.verify)
     except requests.exceptions.ConnectionError as e:
         return False, e
     except requests.exceptions.ReadTimeout as e:
diff --git a/sbws/lib/destination.py b/sbws/lib/destination.py
index d7056680..f9a00a93 100644
--- a/sbws/lib/destination.py
+++ b/sbws/lib/destination.py
@@ -1,6 +1,7 @@
 import logging
 import random
 import time
+import os
 from threading import RLock
 import requests
 from urllib.parse import urlparse
@@ -11,6 +12,22 @@ import sbws.util.requests as requests_utils
 log = logging.getLogger(__name__)
 
 
+def _parse_verify_option(conf_section):
+    if 'verify' not in conf_section:
+        return True
+    try:
+        return conf_section.getboolean('verify')
+    except ValueError:
+        log.warning(
+            'Currently sbws only supports verify=true/false, not a CA bundle '
+            'file. We think %s is not a bool, and thus must be a CA bundle '
+            'file. This is supposed to be allowed by the Python Requests '
+            'library, but pastly couldn\'t get it to work in his afternoon '
+            'of testing. So we will allow this, but expect Requests to throw '
+            'SSLError exceptions later. Have fun!', conf_section['verify'])
+        return conf_section['verify']
+
+
 def connect_to_destination_over_circuit(dest, circ_id, session, cont, max_dl):
     '''
     Connect to **dest* over the given **circ_id** using the given Requests
@@ -56,7 +73,7 @@ def connect_to_destination_over_circuit(dest, circ_id, session, cont, max_dl):
         try:
             # TODO:
             # - What other exceptions can this throw?
-            head = requests_utils.head(session, dest.url)
+            head = requests_utils.head(session, dest.url, verify=dest.verify)
         except (requests.exceptions.ConnectionError,
                 requests.exceptions.ReadTimeout) as e:
             return False, 'Could not connect to {} over circ {} {}: {}'.format(
@@ -78,7 +95,7 @@ def connect_to_destination_over_circuit(dest, circ_id, session, cont, max_dl):
 
 
 class Destination:
-    def __init__(self, url, default_path, max_dl):
+    def __init__(self, url, default_path, max_dl, verify):
         self._max_dl = max_dl
         u = urlparse(url)
         # these things should have been verified in verify_config
@@ -89,11 +106,16 @@ class Destination:
             parts = u[0:2] + default_path + u[2:]
             u = urlparse('{}://{}{}{}{}{}'.format(*parts))
         self._url = u
+        self._verify = verify
 
     def is_usable(self, circ_id, session, cont):
         ''' Use **connect_to_destination_over_circuit** to determine if this
         destination is usable and return what it returns. Just a small wrapper.
         '''
+        if not isinstance(self.verify, bool):
+            if not os.path.isfile(self.verify):
+                return False, '{} is believed to be a CA bundle file on disk '\
+                    'but it does not exist'.format(self.verify)
         return connect_to_destination_over_circuit(
             self, circ_id, session, cont, self._max_dl)
 
@@ -101,6 +123,10 @@ class Destination:
     def url(self):
         return self._url.geturl()
 
+    @property
+    def verify(self):
+        return self._verify
+
     @property
     def hostname(self):
         return self._url.hostname
@@ -123,7 +149,8 @@ class Destination:
     def from_config(conf_section, default_path, max_dl):
         assert 'url' in conf_section
         url = conf_section['url']
-        return Destination(url, default_path, max_dl)
+        verify = _parse_verify_option(conf_section)
+        return Destination(url, default_path, max_dl, verify)
 
 
 class DestinationList:
diff --git a/sbws/util/config.py b/sbws/util/config.py
index 57864b90..fb5a85d3 100644
--- a/sbws/util/config.py
+++ b/sbws/util/config.py
@@ -250,14 +250,14 @@ def _validate_destinations(conf):
     urls = {
         'url': {},
     }
-    all_valid_keys = list(urls.keys())
+    all_valid_keys = list(urls.keys()) + ['verify']
     for sec in dest_sections:
         if sec not in conf:
             errors.append('{} is an enabled destination but is not a '
                           'section in the config'.format(sec))
             continue
-        errors.extend(_validate_section_keys(conf, sec, all_valid_keys,
-                                             err_tmpl))
+        errors.extend(_validate_section_keys(
+            conf, sec, all_valid_keys, err_tmpl, allow_missing=['verify']))
         errors.extend(_validate_section_urls(conf, sec, urls, err_tmpl))
     return errors
 
-- 
GitLab