diff --git a/docs/gce/_update_dev.sh b/docs/gce/_update_dev.sh
new file mode 100644
index 00000000..2c2befae
--- /dev/null
+++ b/docs/gce/_update_dev.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+set -e
+
+# Directory paths used for nogotofail.
+INSTALL_DIR=/opt/nogotofail
+CONFIG_DIR=/etc/nogotofail
+LOG_DIR=/var/log/nogotofail
+
+# Stop the nogotofail-mitm and other associated services if they're running.
+if (ps ax | grep -v grep | grep nogotofail-mitm > /dev/null) then
+sudo /etc/init.d/nogotofail-mitm stop
+fi
+if (ps ax | grep -v grep | grep dnsmasq > /dev/null) then
+sudo /etc/init.d/dnsmasq stop
+fi
+if (ps ax | grep -v grep | grep openvpn > /dev/null) then
+sudo /etc/init.d/openvpn stop
+fi
+# Remove Python files and compiled versions i.e. *.py and *.pyc files.
+# TODO: Find a more elegant method for uninstalling a Python program.
+#rm -rf $INSTALL_DIR
+#rm -rf $CONFIG_DIR
+#rm -rf $LOG_DIR
+find $INSTALL_DIR -type f -name '*.py' -delete
+find $INSTALL_DIR -type f -name '*.pyc' -delete
+
+# Install toolchain dependencies
+sudo apt-get update
+sudo apt-get -y upgrade
+#sudo apt-get -y install patch make gcc libssl-dev python-openssl liblzo2-dev libpam-dev
+
+# Install OpenVPN and dnsmasq
+#sudo apt-get -y install openvpn dnsmasq
+
+# Build and install a patched version of OpenVPN.
+# This is needed because the OpenVPN 2.3.x still does not properly handle
+# floating clients (those whose source IP address as seen by the server changes
+# from time to time) which is a regular occurrence in the mobile world.
+# OpenVPN 2.4 might ship with proper support out of the box. In that case, this
+# kludge can be removed.
+#./build_openvpn.sh
+
+# Build and install a patched version of dnsmasq.
+# This is needed because GCE does not support IPv6. We thus blackhole IPv6
+# traffic from clients so that they are forced to use IPv4. However, default
+# DNS servers will still resolve hostnames to IPv6 addresses causing clients to
+# attempt IPv6. To avoid clients attempting IPv6, we run a patched dnsmasq DNS
+# server which empties AAAA records thus causing clients to go for A records
+# which provide IPv4 addresses.
+#./build_dnsmasq.sh
+
+# Set up OpenVPN server
+#sudo ./setup_openvpn.sh
+
+# Set up the MiTM daemons
+sudo ./setup_mitm.sh
+
+# Move dev mitm.conf file into /etc/nogotofail directory
+sudo cp /home/michael/noseyp_setup/ngtf_mitm.conf /etc/nogotofail/mitm.conf
+
+# Restart all the relevant daemons
+sudo /etc/init.d/dnsmasq start
+sudo /etc/init.d/openvpn start
+#sudo /etc/init.d/nogotofail-mitm stop || true
+sudo /etc/init.d/nogotofail-mitm start
diff --git a/nogotofail/clients/android/app/src/main/java/net/nogotofail/AttacksPreferenceFragment.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/AttacksPreferenceFragment.java
index 68fe9d23..ad6d630c 100644
--- a/nogotofail/clients/android/app/src/main/java/net/nogotofail/AttacksPreferenceFragment.java
+++ b/nogotofail/clients/android/app/src/main/java/net/nogotofail/AttacksPreferenceFragment.java
@@ -49,6 +49,7 @@ public class AttacksPreferenceFragment extends PreferenceFragment {
BUNDLED_SUPPORTED_DATA_ATTACK_IDS.add("httpdetection");
BUNDLED_SUPPORTED_DATA_ATTACK_IDS.add("imagereplace");
BUNDLED_SUPPORTED_DATA_ATTACK_IDS.add("sslstrip");
+ BUNDLED_SUPPORTED_DATA_ATTACK_IDS.add("sunsetsha1");
}
private static final String ATTACK_ENABLED_PREF_KEY_PREFIX = "attack_enabled_";
diff --git a/nogotofail/clients/android/app/src/main/res/values/strings.xml b/nogotofail/clients/android/app/src/main/res/values/strings.xml
index 11057cf8..67948c4f 100644
--- a/nogotofail/clients/android/app/src/main/res/values/strings.xml
+++ b/nogotofail/clients/android/app/src/main/res/values/strings.xml
@@ -94,6 +94,8 @@
Downgrade of HTTPS to HTTP
Downgrade of STARTTLS-protected XMPP to cleartext
+
+ Certificate uses SHA-1 and expires after a Chrome sunset date
Notifications
Notifications
@@ -159,6 +161,8 @@
XMPP credentials/auth token compromise
XMPP STARTTLS strip
Downgrade of STARTTLS-protected XMPP to cleartext
+ Certificate uses SHA-1 and expires after sunset date
+ Certificate uses SHA-1 and expires after a Chrome sunset date
Advanced
diff --git a/nogotofail/mitm/connection/handlers/data/ssl.py b/nogotofail/mitm/connection/handlers/data/ssl.py
index 30e3a74e..6a73b763 100644
--- a/nogotofail/mitm/connection/handlers/data/ssl.py
+++ b/nogotofail/mitm/connection/handlers/data/ssl.py
@@ -19,7 +19,12 @@
from nogotofail.mitm.connection.handlers.data import DataHandler
from nogotofail.mitm.connection.handlers.store import handler
from nogotofail.mitm.event import connection
+from nogotofail.mitm import util
from nogotofail.mitm.util import ssl2, tls, vuln
+from nogotofail.mitm.util.tls.types import HandshakeMessage
+from datetime import datetime
+import OpenSSL.crypto
+
class _TlsRecordHandler(DataHandler):
"""Base class for a handler that acts on TlsRecords in a Tls connection.
@@ -172,3 +177,110 @@ def on_ssl(self, client_hello):
self.log(logging.ERROR,
"Client enabled SSLv3 protocol without TLS_FALLBACK_SCSV")
self.log_attack_event(data="SSLv3")
+
+
+@handler.passive(handlers)
+class SunsetSHA1(_TlsRecordHandler):
+ name = "sunsetsha1"
+ description = (
+ "Detects TLS certificates using the SHA-1 signature algorithm that "
+ "expire during of after the Google Chrome sunset period.")
+
+ # Certificate type constants
+ LEAF_CERT = 1
+ INTERMEDIATE_CERT = 2
+
+ def on_tls_response(self, record):
+ CRT_DATE_FORMAT = "%Y%m%d%H%M%SZ"
+ try:
+ for i, message in enumerate(record.messages):
+ # Check for Certificate message
+ if (isinstance(message, tls.types.HandshakeMessage) and
+ message.type == HandshakeMessage.TYPE.CERTIFICATE):
+ certificate = message.obj
+ cert_chain_length = len(certificate.certificates)
+ cert_chain_index = 0
+ # Loop through certificate chain starting from the leaf
+ # cert to the last intermediate cert (before the root).
+ for cert_byte_string in certificate.certificates[
+ :cert_chain_length-1]:
+ if (cert_chain_index == 0):
+ crt_type = self.LEAF_CERT
+ else:
+ crt_type = self.INTERMEDIATE_CERT
+ a_cert = OpenSSL.crypto.load_certificate(
+ OpenSSL.crypto.FILETYPE_ASN1, cert_byte_string)
+ # Check certificates in chain for SHA-1 sunset issue
+ crt_signature_algorithm = \
+ a_cert.get_signature_algorithm()
+ if ("sha1" in crt_signature_algorithm):
+ crt_CN = a_cert.get_subject().CN
+ crt_not_before = a_cert.get_notBefore()
+ crt_not_after = a_cert.get_notAfter()
+ debug_message = \
+ ["Certicate using SHA-1 with attributes ",
+ "- CN \"", crt_CN, "\", notBefore \"",
+ str(crt_not_before or ''),
+ "\", notAfter \"", str(crt_not_after or ''),
+ "\", signature_algorithm \"",
+ str(crt_signature_algorithm or ''), "\""]
+ self.log(logging.DEBUG, "".join(debug_message))
+ crt_not_after = datetime.strptime(crt_not_after,
+ CRT_DATE_FORMAT)
+ self._alert_on_sunset_sha1(crt_not_after, crt_CN,
+ crt_type)
+ cert_chain_index += 1
+ except AttributeError:
+ pass
+ return record.to_bytes()
+
+ def _alert_on_sunset_sha1(self, crt_not_after, crt_CN, crt_type):
+ """ Raises an alert if a certificate signature algorithm uses SHA-1 and
+ it expires during or after the Google Chrome sunset period.
+ """
+ # SHA-1 sunset dates based on Google Chrome dates published dates. See
+ # http://googleonlinesecurity.blogspot.com/2014/09/gradually-sunsetting-sha-1.html
+ logging_level = logging.WARNING
+ CRT_DATE_FORMAT = "%d-%m-%Y"
+ sunset_critical_date = datetime.strptime("31-12-2016", CRT_DATE_FORMAT)
+ sunset_error_date = datetime.strptime("30-06-2016", CRT_DATE_FORMAT)
+ sunset_warning_date = datetime.strptime("31-12-2015", CRT_DATE_FORMAT)
+
+ if (crt_type == self.LEAF_CERT):
+ crt_type_name = "Leaf"
+ elif (crt_type == self.INTERMEDIATE_CERT):
+ crt_type_name = "Intermediate"
+
+ if (crt_not_after > sunset_critical_date):
+ if (crt_type == self.LEAF_CERT):
+ logging_level = logging.CRITICAL
+ elif (crt_type == self.INTERMEDIATE_CERT):
+ logging_level = logging.WARNING
+ log_message = \
+ [crt_type_name, " certificate with CN ", crt_CN,
+ " uses SHA-1 and expires after 31 Dec 2016"]
+ self.log(logging_level, "".join(log_message))
+ self.log_event(logging_level, connection.AttackEvent(
+ self.connection, self.name, True, ""))
+ self.connection.vuln_notify(util.vuln.VULN_SUNSET_SHA1)
+ elif (crt_not_after > sunset_error_date):
+ if (crt_type == self.LEAF_CERT):
+ logging_level = logging.ERROR
+ elif (crt_type == self.INTERMEDIATE_CERT):
+ logging_level = logging.WARNING
+ log_message = \
+ [crt_type_name, " certificate with CN ", crt_CN,
+ " uses SHA-1 and expires after 30 Jun 2016"]
+ self.log(logging_level, "".join(log_message))
+ self.log_event(logging_level, connection.AttackEvent(
+ self.connection, self.name, True, ""))
+ self.connection.vuln_notify(util.vuln.VULN_SUNSET_SHA1)
+ elif (crt_not_after > sunset_warning_date):
+ logging_level = logging.WARNING
+ log_message = \
+ [crt_type_name, " certificate with CN ", crt_CN,
+ " uses SHA-1 and expires after 31 Dec 2015"]
+ self.log(logging_level, "".join(log_message))
+ self.log_event(logging_level, connection.AttackEvent(
+ self.connection, self.name, True, ""))
+ self.connection.vuln_notify(util.vuln.VULN_SUNSET_SHA1)
diff --git a/nogotofail/mitm/util/vuln.py b/nogotofail/mitm/util/vuln.py
index 2de2f0f4..4be47a0a 100644
--- a/nogotofail/mitm/util/vuln.py
+++ b/nogotofail/mitm/util/vuln.py
@@ -30,3 +30,4 @@
VULN_WEAK_TLS_VERSION = "weaktlsversion"
VULN_TLS_SERVER_KEY_REPLACEMENT = "serverkeyreplace"
VULN_TLS_SUPERFISH_TRUSTED = "superfishca"
+VULN_SUNSET_SHA1 = "sunsetsha1"