diff --git a/README.md b/README.md
index ec4cbe14..c137514d 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-#nogotofail
+# nogotofail
Nogotofail is a network security testing tool designed to help developers and
@@ -7,18 +7,18 @@ cleartext traffic on devices and applications in a flexible, scalable, powerful
It includes testing for common SSL certificate verification issues, HTTPS and TLS/SSL
library bugs, SSL and STARTTLS stripping issues, cleartext issues, and more.
-##Design
+## Design
Nogotofail is composed of an on-path network MiTM and optional clients for the devices being tested.
See [docs/design.md](docs/design.md) for the overview and design goals of nogotofail.
-##Dependencies
+## Dependencies
Nogotofail depends only on Python 2.7 and pyOpenSSL>=0.13. The MiTM is designed to work on Linux
machines and the transparent traffic capture modes are Linux specific and require iptables as well.
Additionally the Linux client depends on [psutil](https://pypi.python.org/pypi/psutil).
-##Getting started
+## Getting started
See [docs/getting_started.md](docs/getting_started.md) for setup and a walkthrough of nogotofail.
-##Discussion
+## Discussion
For discussion please use our [nogotofail Google Group](https://groups.google.com/forum/#!forum/nogotofail).
diff --git a/docs/design.md b/docs/design.md
index 648f463f..6aea3adb 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -1,4 +1,4 @@
-#Design Goals
+# Design Goals
Nogotofail was designed to be an automated, powerful, flexible and scalable tool
@@ -20,13 +20,13 @@ such that it does not get in the way of using devices as normal. Tests
that are destructive are by default run only when necessary and with low
probability.
-##The building blocks of nogotofail
+## The building blocks of nogotofail
Nogotofail is centered around an on path man in the middle tool written in python
with an optional client application to provide additional attribution and
configuration support.
-###Man in The Middle
+### Man in The Middle
The core of nogotofail is the on path network MiTM named nogotofail.mitm that
intercepts TCP traffic. It is designed to primarily run on path and centers
@@ -36,7 +36,7 @@ nogotofail is completely port agnostic and instead detects vulnerable traffic
using DPI instead of based on port numbers. Additionally, because it uses DPI,
it is capable of testing TLS/SSL traffic in protocols that use STARTTLS.
-####Why attack probabilistically?
+#### Why attack probabilistically?
Nogotofail does not destructively attack all TLS/SSL connections it sees because
such attacks lead to non-vulnerable clients aborting attacked connections. If
@@ -56,15 +56,15 @@ devices we’ve seen tend to work as usual.
Of course, if you want to test a specific connection aggressively you can push
the probability up to 100%.
-####Protocol sensing
+#### Protocol sensing
Protocol sensing for a TLS/SSL testing tool is critical because only attacking
traffic on port 443 has two flaws. First, it misses TLS/SSL traffic on
non-standard ports, and second, it fails to test protocols that use STARTTLS.
-###Client *(optional)*
+### Client *(optional)*
-####Why have a client?
+#### Why have a client?
When testing on real devices it can be very difficult to determine what component or app made a
vulnerable connection. Even seeing the contents and the destination isn’t always
@@ -83,7 +83,7 @@ Finally, the client receives notifications of vulnerabilities from the MiTM. Thi
were issues, and it helps you understand exactly what action triggered the
vulnerability.
-####What the client does
+#### What the client does
The client exists to provide additional details about connections, allow the
client to configure attack settings, and to be notified when vulnerabilities are
diff --git a/docs/gce/build_openvpn.sh b/docs/gce/build_openvpn.sh
index 67772e71..1694ed73 100755
--- a/docs/gce/build_openvpn.sh
+++ b/docs/gce/build_openvpn.sh
@@ -2,27 +2,22 @@
set -e
-OPENVPN_VERSION="2.3.5"
+OPENVPN_VERSION="2.4.2"
# Download OpenVPN and verify the signature on the archive
rm -f openvpn-$OPENVPN_VERSION.tar.gz*
-wget http://swupdate.openvpn.org/community/releases/openvpn-$OPENVPN_VERSION.tar.gz
-wget http://swupdate.openvpn.org/community/releases/openvpn-$OPENVPN_VERSION.tar.gz.asc
+wget https://swupdate.openvpn.org/community/releases/openvpn-$OPENVPN_VERSION.tar.gz
+wget https://swupdate.openvpn.org/community/releases/openvpn-$OPENVPN_VERSION.tar.gz.asc
rm -f tmp.keyring*
gpg --no-default-keyring --keyring ./tmp.keyring --import openvpn-pgp-key.asc
gpg --no-default-keyring --keyring ./tmp.keyring --verify openvpn-$OPENVPN_VERSION.tar.gz.asc
rm -f tmp.keyring*
-# Download the patch for improved handling of floating clients
-rm -f tlsfloat.2.patch
-wget https://community.openvpn.net/openvpn/raw-attachment/ticket/49/tlsfloat.2.patch
-
-# Unpack, patch, build, and install.
+# Unpack, build, and install.
rm -Rf openvpn-$OPENVPN_VERSION
tar zxvf openvpn-$OPENVPN_VERSION.tar.gz
cd openvpn-$OPENVPN_VERSION
-patch -p1 < ../tlsfloat.2.patch
-./configure
+./configure --prefix=/usr
make
sudo make install
cd -
diff --git a/docs/gce/nogotofail.ovpn.template b/docs/gce/nogotofail.ovpn.template
index ac37d5bc..b3ad1c7a 100644
--- a/docs/gce/nogotofail.ovpn.template
+++ b/docs/gce/nogotofail.ovpn.template
@@ -3,8 +3,7 @@ proto udp
client
verb 4
dev tun
-tun-ipv6
-redirect-gateway
+redirect-gateway ipv6
# keepalive 60 120
ping 60
@@ -14,3 +13,10 @@ persist-tun
persist-key
comp-lzo
+
+# Accept only server certificates which are whitelisted (via Key Usage and Extended Key Usage) for
+# server authentication
+remote-cert-tls server
+
+# Symmetric tunnel crypto config
+cipher AES-128-GCM
diff --git a/docs/gce/openvpn-client-cert-extfile.cfg b/docs/gce/openvpn-client-cert-extfile.cfg
new file mode 100644
index 00000000..34390879
--- /dev/null
+++ b/docs/gce/openvpn-client-cert-extfile.cfg
@@ -0,0 +1,2 @@
+keyUsage = digitalSignature, keyAgreement
+extendedKeyUsage = clientAuth
diff --git a/docs/gce/openvpn-pgp-key.asc b/docs/gce/openvpn-pgp-key.asc
index c5135814..66d89877 100644
--- a/docs/gce/openvpn-pgp-key.asc
+++ b/docs/gce/openvpn-pgp-key.asc
@@ -1,30 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
-Version: GnuPG v1.4.9 (GNU/Linux)
+Version: GnuPG v1
-mQGiBEsHuu4RBACnPwEKcLYmlwe8v2e8xizlO1fCeqOA7zj6tU/T/1+YTJhrVbgW
-PiRYSNKAmAq0uLFLQ14KpIDsrtdi5ySeUTf64kJtDrBa2si6h0HUyNHf9EX6rUVC
-g/CTpsfYEkqlfMoBH7w7L5O2yidwWA+F4RGWhruzP7i1z+bBsIguSxiBzwCg5qPh
-pgkFGeWArp/OUBHkaqmPZ00D/08dmkrez9d7C/PoR/cFq0nQBqL3zmsRxv66I6fM
-TUqwaRpweWHh9P6XR+pTJjBglVSvk9kLv+PYCvk7yxbT3M6OA/GrSEp/53itlzOU
-MPkv/OF6BmbRbYJK5HAsZgHGbuZxUHUqm4qJ+t4+WZaz9i8WtYbOM6T9aNWQrVUW
-dUMqA/4tZlHJzCrd1NbfEetQVeso9rzzWWWmDAusbvkowfrFHXJGUjfL0hBmxj/9
-JmZtwU+i8G+MKQS0w9rCVLEMLoHLLxPH+Jiknz3Y2xE6CbiSvL+8cvOolgADz/06
-MniHKOZb4tPFPw7ObESeAGp4T9FgT53fJ14AMjGLyHv6EXbfvbQsU2FtdWxpIFNl
-cHDDpG5lbiA8c2FtdWxpLnNlcHBhbmVuQGdtYWlsLmNvbT6IYAQTEQIAIAUCSwe6
-7gIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEMKdl+0ZjSKjsfsAoK6khXtq
-w2xDtNBv/UhRhuVH0NQOAKCDWiB2zYNvHWLYnuIpAlE1sAnGPrkCDQRLB7ruEAgA
-jwSEfTWLJsIW5qlKNEhySIjmRmcVgqB/NTaZ+Nd/r++stYSan1qb7qlQ3B3w48p1
-gB0SPfwKRmMkiYsHNcbRr5KIHWTnYrMI/5OAjPIiz/2j294wRnObzrbJK3T+qJSL
-A2mEXXPPK7i0hUwH12ZJej/h98lPQA+NiDgDOaq4asyq4pcHrR2T2NyiiR2+Xi/L
-2Lz1zKj4iQ3f5g0ktmAEdGcDtV7tI0xZeXWEtesRXeXmqPmjLskJozUoZP1GXXAz
-80PySK2HsEQ/846q1Ybl5KYwbSH+l8jLIyqMDTQnCYG+Ft1moCk3HLyc4c1ALVov
-1Rvom8u3dM5tUtpuZMwcJwADBQf+MCohqLqGJmEdiTEnmggsiKSoZTIBJhcujRaL
-pxPpBlXz6P2bvlprUedBs+zxEEI+Q/CqIlyYaN+Kca1FK4YG9iQoHmb9IIVHf4C/
-lyWSx1xK+BnIk7SEfMjpGAjofNzNc34NmebnosHfP/g3ruLo6EgtjQ68iUty9PgX
-Q1bZQ/SeXk16b8Nn0xQa9S+hg5LAxA+DuSvXbMqU5q2p8JlPgGEFVKzaVcxPhppB
-Kcv/2CxjsqXj/6sW3nFSw+8Jd4SWL1+cPZ1v1WHG3SUMFoLAjSmVj3X8roG5EiLi
-QxSGOUz8uVtvumfKyd25MYmgHMELL7fxhrZcw2OVdo977lt2fIhJBBgRAgAJBQJL
-B7ruAhsMAAoJEMKdl+0ZjSKjgrAAoLeln17YxSQA7RUHwTbquOA92odMAKDiq7c8
-p2hUs3rZaXY1aMmExyB0gQ==
-=l5lk
+mQENBFilZHYBCADGVuvyV9yg2GW7bslnPylaa9cxb3IXmb0qC7hUJueGnz0vLdit
+/fPPPfsI3/hgcQYK1Y8cP5p2Pq+CZL0TVQWBEu2naH2unwxtfNm1EJcWDsky9DzW
+CZQrcZ/v/coaV4UqMTVzGQaxQOzzeaP5nRgdX95dVKqXqsG8wKoIJmBuILAqkOPi
+4EG9NQt2Lbqaiszo3LdsqyeGYK2yc745xBX4UDgIN7XTrXcQDyUOb4dsJynbM+Z9
+8NMQxdA5q0s6BwWSA1xK/gKUCzfF7D1fwWuO2MoedHveB45rOMSFlfVUgr7fa1CR
+zCe7lccu0APfgXrTnNWwWMVoQMO8HIyk2iGnABEBAAG0JVNhbXVsaSBTZXBww6Ru
+ZW4gPHNhbXVsaUBvcGVudnBuLm5ldD6JATgEEwECACIFAlilZtwCGwMGCwkIBwMC
+BhUIAgkKCwQWAgMBAh4BAheAAAoJEClYTZ9AhkV46tEH/Aot7SnpcLHpEkkCX7Jm
+ERrWuqIwYJp7fQlbOPAVZG1+iC/3KlhYxHmH1/Dj6rP3LEEfWpCQSHSbBFkzPtZ6
+AGnEfaxovXjso/tgnAAjYnxy9R0+1t0g5T6anXzCAjl3+mOssjzWBICBDZaFW9Rd
+R47vCA92Fp9kAy3N+AMOv1HfTabaPo6p8HbaBSUQtgdOrfoBSXaFzaPSp8uwonQW
+xRvpG91XtDrEoQio13460025ww+sZe5mIH4c7xhKBEZPswO2xnFszcFp3u12Glbj
+eloAn8oxNycEuw11DfsHf2ctlbQCOLlJJxh2MND5SyL0SjCWMqO7v2c8UUUe4igS
+xeuIRgQQEQIABgUCWKVo6wAKCRDCnZftGY0ioxDUAJ45kbXxCH3hiUexMvlJzvgN
+mZmpyACg0UKbcmHUiFhnhyjtTTmAS5TjB8G0LFNhbXVsaSBTZXBww6RuZW4gPHNh
+bXVsaS5zZXBwYW5lbkBnbWFpbC5jb20+iQE4BBMBAgAiBQJYpWR2AhsDBgsJCAcD
+AgYVCAIJCgsEFgIDAQIeAQIXgAAKCRApWE2fQIZFeLAeB/9lGhVfON8TR6o6+lbm
+GslU2xqV3PQ3hVuAlEttxpP4hCTKU0PwLLb7gtc0UF642qyB7ho2RtU+bg1tiq5z
+R93Ka92Aex4yJDI4viEJ04MTX2WLRv6ogGTRrytIqmYGbYHTFXlnMnQD7Tf+O4sv
+8tJj5gguB/zT8MXQGqU6zq9CF6b3XXdPSITkC7df/CU425HI4V5HvluC/4GrzFZI
+za4Hv/d8G1tXzHXDqoLIBdS44g6GRdXak3PfROKsuk7sG/MmtfbfUPnyBI+yaGQk
+jhlj3BRY0b1dg7T5SiZ6NoMXFH9zKEh7KnG8CaoqiNWDSp2sazy8kbZR5HUp2jOt
+yXmgiEYEEBECAAYFAlilaOsACgkQwp2X7RmNIqOStQCePGpvkvmpISX4fR+lGAlt
+VtWf3XgAmwQTECYXlq3NMdefzLxA5dnxstlEuQENBFilZHYBCADEe46V63aYL+VL
+nZbmBz78KA0fOb5qopFQsOp79FdCQevGXa6JtdibaOLhWUiaMNgkGXma0rSzv/yc
+kDX310JSSrNvbXtbn29MdmCZhWum3lT0bhHltF2w23ha913AEneUq1TAESZz74zJ
+wGtoej7f2H0e3qjOKtwIzItnHRQSHXFRZUh1IRbZAqXQKqRRWiYVLG3pgF1iC9gA
+jLcihK9P89G8jUmB8Ko+9Guw6JszKN+l5SVuK+ttrKCRi8hrkOIiazQUL4gu9PZs
+aGPxNdwnzKGHGZKT0WglXavZFMWHunb6I9/CrCK3ekyHWAvYF7IY95r4SH+CtKqj
+QoW8fOeVABEBAAGJAR8EGAECAAkFAlilZHYCGwwACgkQKVhNn0CGRXiO1QgAh3/I
+EELh+pTiII5IiolHXEKEmgJ6WUU4RzM26Pfv3yMQKqUKBeEvKc21ZWmMKzPWXOE8
+1np7DVXcp0ayiXrfGheGbXSpFP5WGlquYdYjVegBgRJ+v/r/QR+Oy2kbq0lsWuNz
+Eia08fEHr7PM7mct0d1rFVuSS1m+1YOZNN8e/eSox84HvboSq6xk+3IC1NGXXdUQ
+qObWceUyU0KmmBFMV86pUgI/YbA2uMxkFK8XGsOqMgTBdBWHTTcSOfmPsu/04zDl
+MuQ+GC2WcUHoTtxytA432TzOixF5wfunqTzXeZxAybQPkETmAFgHT0BmUVShwPQ0
+XuwT7RpGDZ6jBfphYQ==
+=FKLE
-----END PGP PUBLIC KEY BLOCK-----
diff --git a/docs/gce/openvpn-server-cert-extfile.cfg b/docs/gce/openvpn-server-cert-extfile.cfg
new file mode 100644
index 00000000..1b4b1a4f
--- /dev/null
+++ b/docs/gce/openvpn-server-cert-extfile.cfg
@@ -0,0 +1,2 @@
+keyUsage = digitalSignature, keyAgreement
+extendedKeyUsage = serverAuth
diff --git a/docs/gce/openvpn.conf b/docs/gce/openvpn.conf
index 7fa18b13..e2a540fb 100644
--- a/docs/gce/openvpn.conf
+++ b/docs/gce/openvpn.conf
@@ -3,6 +3,7 @@ proto udp
dev tun
server 10.8.0.0 255.255.255.0
+server-ipv6 fd12:3456:789a:bcde::/64
topology subnet
# Because GCE doesn't support IPv6 we
@@ -11,16 +12,6 @@ topology subnet
# This forces clients to switch to using IPv4 addresses.
push "dhcp-option DNS 10.8.0.1"
-# Blackhole IPv6 traffic because GCE does not support IPv6.
-# This is achieved by making OpenVPN server have a fake IPv6 address
-# (otherwise OpenVPN server will not push IPv6 information to
-# client) and pushing a route to the client to blackhole IPv6 traffic
-# client-side.
-server-ipv6 2001:db8:123::/64
-# OpenVPN 2.3 doesn't work well with IPv6. Push a route to client
-# to blackhole IPv6 traffic on the client.
-push "route-ipv6 2000::/3"
-
# Enabling floating mode to work around the issue where
# some clients's source IP+port may change mid-session because
# of NAT.
@@ -57,4 +48,9 @@ ca /etc/openvpn/ca_cert.pem
cert /etc/openvpn/server_cert.pem
key /etc/openvpn/server_key.pem
-dh /etc/openvpn/dhparam2048.pem
+dh none
+
+tls-cipher TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384
+
+# Symmetric tunnel crypto config
+cipher AES-256-GCM
diff --git a/docs/gce/readme.md b/docs/gce/readme.md
index 7d65f752..11cb9704 100644
--- a/docs/gce/readme.md
+++ b/docs/gce/readme.md
@@ -1,4 +1,4 @@
-#Nogotofail MiTM on Google Compute Engine VM instance
+# Nogotofail MiTM on Google Compute Engine VM instance
## Overview
In this setup, traffic from clients to be MiTM'd is routed through a Google
diff --git a/docs/gce/setup_openvpn.sh b/docs/gce/setup_openvpn.sh
index 6b9b621c..bf313b24 100755
--- a/docs/gce/setup_openvpn.sh
+++ b/docs/gce/setup_openvpn.sh
@@ -20,17 +20,15 @@ echo "Generating server public key pair and certificate..."
openssl genrsa -out $CONFIG_DIR/server_key.pem 2048
openssl req -new -key $CONFIG_DIR/server_key.pem -out $CONFIG_DIR/server_csr.pem -subj '/CN=server.vpn.nogotofail'
chmod 600 $CONFIG_DIR/server_key.pem
-openssl x509 -req -in $CONFIG_DIR/server_csr.pem -CA $CONFIG_DIR/ca_cert.pem -CAkey $CONFIG_DIR/ca_key.pem -CAcreateserial -out $CONFIG_DIR/server_cert.pem -sha256 -days 365
+openssl x509 -req -in $CONFIG_DIR/server_csr.pem -CA $CONFIG_DIR/ca_cert.pem -CAkey $CONFIG_DIR/ca_key.pem -CAcreateserial -out $CONFIG_DIR/server_cert.pem -sha256 -days 365 -extfile openvpn-server-cert-extfile.cfg
rm $CONFIG_DIR/server_csr.pem
echo "Generating client public key pair and certificate..."
openssl genrsa -out $CONFIG_DIR/client_key.pem 2048
openssl req -new -key $CONFIG_DIR/client_key.pem -out $CONFIG_DIR/client_csr.pem -subj '/CN=client.vpn.nogotofail'
-openssl x509 -req -in $CONFIG_DIR/client_csr.pem -CA $CONFIG_DIR/ca_cert.pem -CAkey $CONFIG_DIR/ca_key.pem -CAcreateserial -out $CONFIG_DIR/client_cert.pem -sha256 -days 365
+openssl x509 -req -in $CONFIG_DIR/client_csr.pem -CA $CONFIG_DIR/ca_cert.pem -CAkey $CONFIG_DIR/ca_key.pem -CAcreateserial -out $CONFIG_DIR/client_cert.pem -sha256 -days 365 -extfile openvpn-client-cert-extfile.cfg
rm $CONFIG_DIR/client_csr.pem
-openssl dhparam 2048 > $CONFIG_DIR/dhparam2048.pem
-
cp "$SRC_DIR/openvpn.conf" $CONFIG_DIR/
# Determine external IP address of this host.
diff --git a/docs/getting_started.md b/docs/getting_started.md
index f6cf590e..334730c7 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -1,10 +1,10 @@
-#Getting Started
-##Files you’ll need to provide
+# Getting Started
+## Files you’ll need to provide
Before running nogotofail there are some files you’ll need to create or provide.
-###MiTM Server certificate
+### MiTM Server certificate
The connection between clients and the MiTM is protected by a self-signed
@@ -16,12 +16,12 @@ For example the OpenSSL command to generate such a certificate is:
$ openssl req -x509 -newkey rsa:2048 -sha256 -subj "/CN=mitm.nogotofail/" -nodes -keyout server.crt -out server.crt
-###Invalid Hostname Certificate
+### Invalid Hostname Certificate
The Invalid hostname attack attempts a MiTM by presenting a trusted certificate
for another domain name. For example a trusted certificate for evil.com being
presented for a connection to example.com. If the application does not do
-hostname verification correctly it will incorrect trust the MiTM. This has
+hostname verification correctly, it will incorrectly trust the MiTM. This has
historically been one of the common SSL issues besides not checking chain of
trust of SSL certificates. To test for this issue you will need to provide a
trusted certificate chain for an arbitrary domain. You have two options for how
@@ -44,13 +44,13 @@ To verify the chain is correct
$ openssl verify -CApath /etc/ssl/certs/ -untrusted trusted-cert.pem trusted-cert.pem
You should see OK as the output.
-###ImageReplace Image
+### ImageReplace Image
If you decide to use the image replacement data attack you’ll need to provide an image to
replace with in the form of replace.png in nogotofail.mitm’s working directory.
We recommend something noticeable that scales well.
-##Example Walkthrough
+## Example Walkthrough
Here is a quick walkthrough of running and testing the MiTM locally.
@@ -171,7 +171,7 @@ attack for later analysis.
6. The connection closes
-###Getting on path
+### Getting on path
Now that you’ve set up nogotofail and seen how it runs the next step is to put
@@ -199,7 +199,7 @@ OpenVPN as there is lots of documentation for how to set up an OpenVPN server.
Our main setup has been OpenVPN running on a Google Compute Engine instance. See instructions in
[gce/readme.md](gce/readme.md).
-####Testing Android
+#### Testing Android
For testing Android devices we have included our [Android client](/nogotofail/clients/android) ready
to be imported into Eclipse. You will have to build the app and install it on your test device.
@@ -207,7 +207,7 @@ For testing you can use the access point nogotofail setups or on devices >=JB y
the OpenVPN setup and a third party VPN application to route your traffic.
-#####Getting on path on a Linux machine
+##### Getting on path on a Linux machine
On a Linux machine with the following example topology:
@@ -237,7 +237,7 @@ Now traffic will be flowing through the MiTM box from the test device to the
Internet.
-###Now you’re on path
+### Now you’re on path
By default clients connect to the MiTM using hostname mitm.nogotofail
@@ -255,7 +255,7 @@ in [example.conf](example.conf), and run it with `python -m nogotofail.mitm -c <
If you’re running in an iptables mode you’ll also need to run nogotofail.mitm as
root so it can set up the routing rules to intercept traffic.
-####Useful arguments
+#### Useful arguments
@@ -286,7 +286,7 @@ important ones you’ll want to tweak.
You can see all the options by running `python -m nogotofail.mitm --help`.
-#####Logging
+##### Logging
Additionally, you will probably want to log to files in addition to stdout.
diff --git a/docs/mitm.md b/docs/mitm.md
index 22226ded..4870cfeb 100644
--- a/docs/mitm.md
+++ b/docs/mitm.md
@@ -1,5 +1,5 @@
-#MiTM Details
-##How MiTM intercepts traffic
+# MiTM Details
+## How MiTM intercepts traffic
Nogotofail currently supports three traffic interception modes that can be
selected using the --mode option. The tproxy and redirect modes are
@@ -7,49 +7,49 @@ transparent to the client and destination, and are for running nogotofail on-pat
Note they require the MiTM to be run as root in order to create the iptables
and routing rules to capture traffic.
-###tproxy
+### tproxy
Tproxy mode uses iptables tproxy and ip mark routing rules to route all traffic
passing through the device to nogotofail.
-###redirect
+### redirect
Redirect mode uses iptables nat redirection to route all traffic passing
through the device to nogotofail. Redirect is the older and more tested way of
routing traffic to nogotofail but has poor IPv6 support, requiring bleeding
edge iptables and Linux kernel >= 3.7.
-###socks
+### socks
Socks mode has nogotofail listening as a SOCKS5 proxy. Unlike the iptables
rules this doesn’t require nogotofail to be on path or have root access, but it
does lose the transparency of those modes.
It is also useful when testing changes to nogotofail locally, as it requires minimal setup.
-#Architecture Overview
+# Architecture Overview
-##Connections
+## Connections
Every TCP connection is routed to a nogotofail connection which is responsible
for bridging traffic, detecting TLS/SSL traffic and sending events to handlers
which implement attacks and detection.
-##Handlers
+## Handlers
All the actual vulnerability detection is done in small event handlers. In
nogotofail there are two types of handlers, connection handlers and data
handlers. You can see all the events handlers receive and their documentation in
(nogotofail/mitm/connection/handlers/base.py)[nogotofail/mitm/connection/handlers/base.py]
-###Connection Handlers
+### Connection Handlers
Each connection in nogotofail has only one connection handler which is
responsible for doing connection level testing on TLS/SSL. These are used for
vulnerabilities like accepting self-signed certificates or heartbleed.
-###Data Handlers
+### Data Handlers
Data handlers are responsible for detecting issues in traffic or modifying
traffic to test for vulnerabilities. Unlike Connection Handlers each connection
can have multiple data handlers whose outputs are chained together. These are
used for vulnerabilities like detecting auth tokens in cleartext or attempting
ssl stripping attacks.
-##Life of a connection
+## Life of a connection
1. When a connection is first created it selects the initial connection handler and the list of data handler.
2. Each handler’s on_select method is called.
3. Once the connection to the remote is successful each handler’s on_establish is called
@@ -58,7 +58,7 @@ ssl stripping attacks.
6. If a TLS/SSL Client Hello is detected by the connection the connection checks if it should MiTM’d. See ‘on TLS Client Hello’.
7. When the connection is closed each handler’s on_close is called
-###On TLS/SSL Client Hello
+### On TLS/SSL Client Hello
1. When a TLS/SSL client hello is detected the connection selects a new connection handler for the TLS/SSL connection.
2. If handler.on_ssl returns False the connection will continue simply bridging traffic as before
3. Otherwise the connection to the server is wrapped with TLS/SSL and handler.on_certificate is called with the cert presented by the server to generate a certificate to present to the client
diff --git a/explicit_curve.pem b/explicit_curve.pem
new file mode 100644
index 00000000..2b63fc34
--- /dev/null
+++ b/explicit_curve.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIDpTCCAyygAwIBAgIUaM8IdFkj075azIaOuFQjCezhxsAwCgYIKoZIzj0EAwIw
+XjELMAkGA1UEBhMCTkExEzARBgNVBAgMCk5vZ290b2ZhaWwxEzARBgNVBAoMCk5v
+Z290b2ZhaWwxDTALBgNVBAsMBE1pVE0xFjAUBgNVBAMMDUNWRS0yMDIwLTA2MDEw
+HhcNMjAwMTE2MDcwNzQ4WhcNMjIxMTA1MDcwNzQ4WjBeMQswCQYDVQQGEwJOQTET
+MBEGA1UECAwKTm9nb3RvZmFpbDETMBEGA1UECgwKTm9nb3RvZmFpbDENMAsGA1UE
+CwwETWlUTTEWMBQGA1UEAwwNQ1ZFLTIwMjAtMDYwMTCCAcwwggFkBgcqhkjOPQIB
+MIIBVwIBATA8BgcqhkjOPQEBAjEA////////////////////////////////////
+//////7/////AAAAAAAAAAD/////MHsEMP//////////////////////////////
+///////////+/////wAAAAAAAAAA/////AQwszEvp+I+5+SYjgVr4/gtGRgdnG7+
+gUESAxQIj1ATh1rGVjmNii7RnSqFyO3T7CrvAxUAozWSaqMZonodAIlqZ3OkgnrN
+rHMEYQQarFRaqfloI+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO
+7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/
+ijTjeXkCMQD////////////////////////////////HY02B9Dct31gaDbJIsKd6
+7OwZaszFKXMCAQEDYgAEGqxUWqn5aCPnetUkb1PGWthLq8bVttHmc3Gu3ZzWDGH9
+26CJA7gFFOxXzu5dP+Ihs8731Ip54KODfi2X0GHE8ZncJZFjq38wo7Rw4sehM5zz
+vy5cU7Ffs30yf4o043l5o1MwUTAdBgNVHQ4EFgQUOuEJhtTPGcKWdnRJdtzgNcZj
+Y5owHwYDVR0jBBgwFoAUOuEJhtTPGcKWdnRJdtzgNcZjY5owDwYDVR0TAQH/BAUw
+AwEB/zAKBggqhkjOPQQDAgNnADBkAjB23iiHtHfRRQIEDKxoy5h9ZMf4Ki37Shxs
+vCmTk4lvMAUDYlE+ls43g2yDsFh77gMCMAavhDOlDlysDs0+T/KF9KirXVxWVJu1
+iW4LXegLiRSRPOw07z+SoTluCyhXX6igZA==
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIICDAIBADCCAWQGByqGSM49AgEwggFXAgEBMDwGByqGSM49AQECMQD/////////
+/////////////////////////////////v////8AAAAAAAAAAP////8wewQw////
+//////////////////////////////////////7/////AAAAAAAAAAD////8BDCz
+MS+n4j7n5JiOBWvj+C0ZGB2cbv6BQRIDFAiPUBOHWsZWOY2KLtGdKoXI7dPsKu8D
+FQCjNZJqoxmieh0AiWpnc6SCes2scwRhBBqsVFqp+Wgj53rVJG9TxlrYS6vG1bbR
+5nNxrt2c1gxh/dugiQO4BRTsV87uXT/iIbPO99SKeeCjg34tl9BhxPGZ3CWRY6t/
+MKO0cOLHoTOc878uXFOxX7N9Mn+KNON5eQIxAP//////////////////////////
+/////8djTYH0Ny3fWBoNskiwp3rs7BlqzMUpcwIBAQSBnjCBmwIBAQQwAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoWQDYgAE
+GqxUWqn5aCPnetUkb1PGWthLq8bVttHmc3Gu3ZzWDGH926CJA7gFFOxXzu5dP+Ih
+s8731Ip54KODfi2X0GHE8ZncJZFjq38wo7Rw4sehM5zzvy5cU7Ffs30yf4o043l5
+-----END PRIVATE KEY-----
diff --git a/nogotofail/clients/android/.classpath b/nogotofail/clients/android/.classpath
deleted file mode 100644
index 7bc01d9a..00000000
--- a/nogotofail/clients/android/.classpath
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/nogotofail/clients/android/.gitignore b/nogotofail/clients/android/.gitignore
index d97b4807..67495841 100644
--- a/nogotofail/clients/android/.gitignore
+++ b/nogotofail/clients/android/.gitignore
@@ -4,3 +4,30 @@ gen
# Bazel
bazel-*
WORKSPACE
+
+# Generated files
+bin/
+gen/
+
+# Gradle files
+.gradle/
+build/
+gradle/
+gradlew*
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Mac OS X clutter
+*.DS_Store
+
+# Windows clutter
+Thumbs.db
+
+# Android Studio project files (see https://intellij-support.jetbrains.com/entries/23393067)
+*.iml
+.idea/
+*.iws
+
+# Android Studio Navigation editor temp files
+.navigation/
diff --git a/nogotofail/clients/android/.project b/nogotofail/clients/android/.project
deleted file mode 100644
index bd856ba5..00000000
--- a/nogotofail/clients/android/.project
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
- nogotofail
-
-
-
-
-
- com.android.ide.eclipse.adt.ResourceManagerBuilder
-
-
-
-
- com.android.ide.eclipse.adt.PreCompilerBuilder
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- com.android.ide.eclipse.adt.ApkBuilder
-
-
-
-
-
- com.android.ide.eclipse.adt.AndroidNature
- org.eclipse.jdt.core.javanature
-
-
-
-
diff --git a/nogotofail/clients/android/BUILD b/nogotofail/clients/android/BUILD
index a432a7d9..a2860898 100644
--- a/nogotofail/clients/android/BUILD
+++ b/nogotofail/clients/android/BUILD
@@ -2,10 +2,10 @@ android_binary(
name = "nogotofail",
custom_package = "net.nogotofail",
srcs = glob([
- "src/**/*.java",
+ "app/src/main/java/**/.java",
]),
resource_files = glob([
- "res/**/*",
+ "app/src/main/res/**/*",
]),
- manifest = "AndroidManifest.xml",
+ manifest = "app/src/main/AndroidManifest.xml",
)
diff --git a/nogotofail/clients/android/app/build.gradle b/nogotofail/clients/android/app/build.gradle
new file mode 100644
index 00000000..ebe635bf
--- /dev/null
+++ b/nogotofail/clients/android/app/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ applicationId "net.nogotofail"
+ minSdkVersion 14
+ targetSdkVersion 21
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+}
diff --git a/nogotofail/clients/android/AndroidManifest.xml b/nogotofail/clients/android/app/src/main/AndroidManifest.xml
similarity index 100%
rename from nogotofail/clients/android/AndroidManifest.xml
rename to nogotofail/clients/android/app/src/main/AndroidManifest.xml
diff --git a/nogotofail/clients/android/src/net/nogotofail/AdvancedPreferenceFragment.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/AdvancedPreferenceFragment.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/AdvancedPreferenceFragment.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/AdvancedPreferenceFragment.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/AttacksPreferenceFragment.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/AttacksPreferenceFragment.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/AttacksPreferenceFragment.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/AttacksPreferenceFragment.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/CertWhitelistDialogFragment.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/CertWhitelistDialogFragment.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/CertWhitelistDialogFragment.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/CertWhitelistDialogFragment.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/CertWhitelistPromptActivity.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/CertWhitelistPromptActivity.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/CertWhitelistPromptActivity.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/CertWhitelistPromptActivity.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/Closeables.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/Closeables.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/Closeables.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/Closeables.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/FeedbackReporter.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/FeedbackReporter.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/FeedbackReporter.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/FeedbackReporter.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/HexEncoding.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/HexEncoding.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/HexEncoding.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/HexEncoding.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/MuteNotificationsReceiver.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/MuteNotificationsReceiver.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/MuteNotificationsReceiver.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/MuteNotificationsReceiver.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/NoGotoFailApplication.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/NoGotoFailApplication.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/NoGotoFailApplication.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/NoGotoFailApplication.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/NotificationsPreferenceFragment.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/NotificationsPreferenceFragment.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/NotificationsPreferenceFragment.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/NotificationsPreferenceFragment.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/PreferencesBackedPinningX509TrustManager.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/PreferencesBackedPinningX509TrustManager.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/PreferencesBackedPinningX509TrustManager.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/PreferencesBackedPinningX509TrustManager.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/RouterConnectionHandler.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/RouterConnectionHandler.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/RouterConnectionHandler.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/RouterConnectionHandler.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/RouterSocketClient.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/RouterSocketClient.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/RouterSocketClient.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/RouterSocketClient.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/RouterSocketService.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/RouterSocketService.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/RouterSocketService.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/RouterSocketService.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/SettingsActivity.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/SettingsActivity.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/SettingsActivity.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/SettingsActivity.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/StatusActivity.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/StatusActivity.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/StatusActivity.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/StatusActivity.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/TcpClientInfoCommandHandler.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/TcpClientInfoCommandHandler.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/TcpClientInfoCommandHandler.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/TcpClientInfoCommandHandler.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/Utils.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/Utils.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/Utils.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/Utils.java
diff --git a/nogotofail/clients/android/src/net/nogotofail/VulnNotifyCommandHandler.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/VulnNotifyCommandHandler.java
similarity index 100%
rename from nogotofail/clients/android/src/net/nogotofail/VulnNotifyCommandHandler.java
rename to nogotofail/clients/android/app/src/main/java/net/nogotofail/VulnNotifyCommandHandler.java
diff --git a/nogotofail/clients/android/res/drawable-xhdpi/ic_notify_default.png b/nogotofail/clients/android/app/src/main/res/drawable-xhdpi/ic_notify_default.png
similarity index 100%
rename from nogotofail/clients/android/res/drawable-xhdpi/ic_notify_default.png
rename to nogotofail/clients/android/app/src/main/res/drawable-xhdpi/ic_notify_default.png
diff --git a/nogotofail/clients/android/res/drawable-xhdpi/ic_notify_vuln.png b/nogotofail/clients/android/app/src/main/res/drawable-xhdpi/ic_notify_vuln.png
similarity index 100%
rename from nogotofail/clients/android/res/drawable-xhdpi/ic_notify_vuln.png
rename to nogotofail/clients/android/app/src/main/res/drawable-xhdpi/ic_notify_vuln.png
diff --git a/nogotofail/clients/android/res/drawable-xxhdpi/ic_launcher.png b/nogotofail/clients/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from nogotofail/clients/android/res/drawable-xxhdpi/ic_launcher.png
rename to nogotofail/clients/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png
diff --git a/nogotofail/clients/android/res/drawable-xxxhdpi/ic_launcher.png b/nogotofail/clients/android/app/src/main/res/drawable-xxxhdpi/ic_launcher.png
similarity index 100%
rename from nogotofail/clients/android/res/drawable-xxxhdpi/ic_launcher.png
rename to nogotofail/clients/android/app/src/main/res/drawable-xxxhdpi/ic_launcher.png
diff --git a/nogotofail/clients/android/res/layout/status_activity.xml b/nogotofail/clients/android/app/src/main/res/layout/status_activity.xml
similarity index 100%
rename from nogotofail/clients/android/res/layout/status_activity.xml
rename to nogotofail/clients/android/app/src/main/res/layout/status_activity.xml
diff --git a/nogotofail/clients/android/res/menu/status_activity_actions.xml b/nogotofail/clients/android/app/src/main/res/menu/status_activity_actions.xml
similarity index 100%
rename from nogotofail/clients/android/res/menu/status_activity_actions.xml
rename to nogotofail/clients/android/app/src/main/res/menu/status_activity_actions.xml
diff --git a/nogotofail/clients/android/res/values/constants.xml b/nogotofail/clients/android/app/src/main/res/values/constants.xml
similarity index 100%
rename from nogotofail/clients/android/res/values/constants.xml
rename to nogotofail/clients/android/app/src/main/res/values/constants.xml
diff --git a/nogotofail/clients/android/res/values/strings.xml b/nogotofail/clients/android/app/src/main/res/values/strings.xml
similarity index 100%
rename from nogotofail/clients/android/res/values/strings.xml
rename to nogotofail/clients/android/app/src/main/res/values/strings.xml
diff --git a/nogotofail/clients/android/res/values/themes.xml b/nogotofail/clients/android/app/src/main/res/values/themes.xml
similarity index 100%
rename from nogotofail/clients/android/res/values/themes.xml
rename to nogotofail/clients/android/app/src/main/res/values/themes.xml
diff --git a/nogotofail/clients/android/res/xml/advanced_settings.xml b/nogotofail/clients/android/app/src/main/res/xml/advanced_settings.xml
similarity index 100%
rename from nogotofail/clients/android/res/xml/advanced_settings.xml
rename to nogotofail/clients/android/app/src/main/res/xml/advanced_settings.xml
diff --git a/nogotofail/clients/android/res/xml/attacks_settings.xml b/nogotofail/clients/android/app/src/main/res/xml/attacks_settings.xml
similarity index 100%
rename from nogotofail/clients/android/res/xml/attacks_settings.xml
rename to nogotofail/clients/android/app/src/main/res/xml/attacks_settings.xml
diff --git a/nogotofail/clients/android/res/xml/notifications_settings.xml b/nogotofail/clients/android/app/src/main/res/xml/notifications_settings.xml
similarity index 100%
rename from nogotofail/clients/android/res/xml/notifications_settings.xml
rename to nogotofail/clients/android/app/src/main/res/xml/notifications_settings.xml
diff --git a/nogotofail/clients/android/res/xml/preference_headers.xml b/nogotofail/clients/android/app/src/main/res/xml/preference_headers.xml
similarity index 100%
rename from nogotofail/clients/android/res/xml/preference_headers.xml
rename to nogotofail/clients/android/app/src/main/res/xml/preference_headers.xml
diff --git a/nogotofail/clients/android/art/ic_launcher.xcf b/nogotofail/clients/android/art/ic_launcher.xcf
deleted file mode 100644
index 37c86a54..00000000
Binary files a/nogotofail/clients/android/art/ic_launcher.xcf and /dev/null differ
diff --git a/nogotofail/clients/android/build.gradle b/nogotofail/clients/android/build.gradle
new file mode 100644
index 00000000..05abe952
--- /dev/null
+++ b/nogotofail/clients/android/build.gradle
@@ -0,0 +1,19 @@
+task wrapper(type: Wrapper) {
+ gradleVersion = '2.4'
+}
+
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.3.0'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/nogotofail/clients/android/project.properties b/nogotofail/clients/android/project.properties
deleted file mode 100644
index 6e18427a..00000000
--- a/nogotofail/clients/android/project.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-21
diff --git a/nogotofail/clients/android/settings.gradle b/nogotofail/clients/android/settings.gradle
new file mode 100644
index 00000000..e7b4def4
--- /dev/null
+++ b/nogotofail/clients/android/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/nogotofail/mitm/__main__.py b/nogotofail/mitm/__main__.py
index b6550331..5c7717e4 100644
--- a/nogotofail/mitm/__main__.py
+++ b/nogotofail/mitm/__main__.py
@@ -30,7 +30,7 @@
import sys
from nogotofail.mitm.blame import Server as AppBlameServer
-from nogotofail.mitm.connection import Server, RedirectConnection, SocksConnection, TproxyConnection
+from nogotofail.mitm.connection import Server, RedirectConnection, SocksConnection, TproxyConnection, ReverseProxyConnection
from nogotofail.mitm.connection import handlers
from nogotofail.mitm.connection.handlers import preconditions
from nogotofail.mitm.looper import MitmLoop
@@ -127,6 +127,15 @@ def set_tproxy_rules(args):
routing.enable_tproxy_rules(port, ipv6=ipv6)
atexit.register(routing.disable_tproxy_rules, ipv6=ipv6)
+def set_reverse_rules(args):
+ if args.target_addr is None:
+ raise ValueError("Target address is required")
+ if args.target_port < 1 or args.target_port > 65535:
+ raise ValueError("Target port must be between 1 and 65535")
+
+ ReverseProxyConnection.target_addr = args.target_addr
+ ReverseProxyConnection.target_port = args.target_port
+
# Traffic capture modes
Mode = collections.namedtuple("Mode", ["cls", "setup", "description"])
modes = {
@@ -139,6 +148,9 @@ def set_tproxy_rules(args):
"socks": Mode(SocksConnection,
None,
"Listen as a SOCKS server to route traffic"),
+ "reverse": Mode(ReverseProxyConnection,
+ set_reverse_rules,
+ "Listen as a reverse proxy and forward to target host"),
}
default_mode = "tproxy"
@@ -211,6 +223,10 @@ def parse_args():
parser.add_argument(
"--cport", help="Port to listen for nogotofail clients on", action="store",
type=int, default=8443)
+ parser.add_argument(
+ "--target_addr", help="Address to forward reverse proxy connections to", action="store")
+ parser.add_argument(
+ "--target_port", help="Port to forward reverse proxy connections to", action="store", type=int)
parser.add_argument(
"-6", "--ipv6",
help=("Route IPv6 traffic. "
@@ -309,7 +325,12 @@ def run():
signal.signal(signal.SIGTERM, sigterm_handler)
mode = modes[args.mode]
if mode.setup:
- mode.setup(args)
+ try:
+ mode.setup(args)
+ except ValueError as e:
+ print("Error: %s" % e.message)
+ sys.exit(2)
+
blame = (
build_blame(
args.cport, args.serverssl, args.probability, attack_cls,
diff --git a/nogotofail/mitm/blame/app_blame.py b/nogotofail/mitm/blame/app_blame.py
index 87d3c2dd..eb7622ff 100644
--- a/nogotofail/mitm/blame/app_blame.py
+++ b/nogotofail/mitm/blame/app_blame.py
@@ -76,7 +76,7 @@ def on_select(self):
def check_timeouts(self):
"""Returns if the connection or any of its callbacks have timed out."""
- now = time.time
+ now = time.time()
if now - self.last_used > self.CLIENT_TIMEOUT:
return False
for callback in self.queries.values():
@@ -141,7 +141,7 @@ def _generate_on_vuln_notify_fn(self, callback):
def on_vuln_notify(success, data=None):
if not success:
callback(False)
- self.server.remove_client(self.address)
+ self.server.remove_client(self)
return
callback(True, data == "OK")
return on_vuln_notify
@@ -150,7 +150,7 @@ def _generate_on_get_applications_fn(self, callback):
def on_get_applications(success, data=None):
if not success:
callback(False)
- self.server.remove_client(self.address)
+ self.server.remove_client(self)
return
platform_info = self.info.get(
@@ -323,6 +323,7 @@ def __init__(self, port, cert, default_prob, default_attacks, default_data):
self.fd_map = {}
self.logger = logging.getLogger("nogotofail.mitm")
self.server_socket = None
+ self.last_prune = time.time()
def start_listening(self):
self.server_socket = socket.socket()
@@ -349,18 +350,18 @@ def _on_server_socket_select(self):
old_client = self.clients.get(client_addr, None)
if old_client:
- self.remove_client(client_address)
- self.fd_map[client_socket] = client_addr
- self.clients[client_addr] = Client(client_socket, self)
+ self.remove_client(old_client)
+ client = Client(client_socket, self)
+ self.fd_map[client_socket] = client
+ self.clients[client_addr] = client
def _on_socket_select(self, sock):
if sock is self.server_socket:
self._on_server_socket_select()
return
- client_addr = self.fd_map[sock]
- client = self.clients[client_addr]
+ client = self.fd_map[sock]
if not client.on_select():
- self.remove_client(client_addr)
+ self.remove_client(client)
def client_available(self, client_addr):
"""Returns if the app blame client is running on client_addr.
@@ -388,7 +389,7 @@ def fn(success, platform_info=None, applications=None)
return False
if not self.clients[client_addr].get_applications_async(client_port,
server_addr, server_port, callback, timeout):
- self.remove_client(client_addr)
+ self.remove_client(self.clients[client_addr])
return False
return True
@@ -416,24 +417,24 @@ def callback(success, result=False)
result = self.clients[client_addr].vuln_notify_async(server_addr, server_port,
id, type, applications, callback, timeout)
if not result:
- self.remove_client(client_addr)
+ self.remove_client(self.clients[client_addr])
return result
- def remove_client(self, client_addr):
+ def remove_client(self, client):
"""Remove and close a blame client."""
- if client_addr not in self.clients:
+ if client.address not in self.clients:
return
- client = self.clients[client_addr]
- del self.clients[client_addr]
+ del self.clients[client.address]
del self.fd_map[client.socket]
client.close()
- def check_timeouts(self):
- """Check the timeouts on all clients and remove those that have timed out."""
- for client_addr in self.clients.keys():
- if not self.clients[client_addr].check_timeouts():
- self.logger.info("Blame: Client %s timed out", client_addr)
- self.remove_client(client_addr)
+ def prune_old_clients(self):
+ """Prune old clients that have not been heard from in a long time"""
+ for client in self.clients.values():
+ if not client.check_timeouts():
+ self.logger.info("Blame: Client %s timed out", client.address)
+ self.remove_client(client)
+
@property
def select_fds(self):
@@ -446,6 +447,10 @@ def on_select(self, r, w, x):
provided by select_fds."""
for fd in set(r + w + x):
self._on_socket_select(fd)
+ # Prune old clients
+ if time.time() - self.last_prune > 3600:
+ self.prune_old_clients()
+ self.last_prune = time.time()
def shutdown(self):
"""Shutdown the Blame server. The server should not be used after this point."""
diff --git a/nogotofail/mitm/connection/__init__.py b/nogotofail/mitm/connection/__init__.py
index 93163dc8..5b74c24f 100644
--- a/nogotofail/mitm/connection/__init__.py
+++ b/nogotofail/mitm/connection/__init__.py
@@ -13,6 +13,6 @@
See the License for the specific language governing permissions and
limitations under the License.
'''
-from connection import RedirectConnection, SocksConnection, TproxyConnection
+from connection import RedirectConnection, SocksConnection, TproxyConnection, ReverseProxyConnection
from server import Server
import handlers
diff --git a/nogotofail/mitm/connection/connection.py b/nogotofail/mitm/connection/connection.py
index 6d4e7636..3b94d026 100644
--- a/nogotofail/mitm/connection/connection.py
+++ b/nogotofail/mitm/connection/connection.py
@@ -28,35 +28,81 @@
import os
class ConnectionWrapper(object):
- """Wrapper around OpenSSL's Connection object to make recv act like socket.recv()
+ """Wrapper around OpenSSL's Connection object to make it act like a real socket.
"""
def __init__(self, connection):
self._connection = connection
+ self.buffer = ""
+ self._is_short_send = False
def __getattr__(self, name):
return getattr(self._connection, name)
- def recv(self, size):
+ def recv(self, size, flags=0):
"""Wrapper around pyOpenSSL's Connection.recv
PyOpenSSL doesn't return "" on error like socket.recv does,
instead it throws a SSL.ZeroReturnError or (-1, "Unexpected EOF") erorrs.
Wrap recv so we don't have to deal with that noise.
"""
- buf = ""
+ if flags & socket.MSG_PEEK == 0:
+ return self._recv(size)
+ if len(self.buffer) >= size:
+ return self.buffer[:size]
try:
- buf = self._connection.recv(size)
+ self.buffer += self._recv(size - len(self.buffer))
+ except SSL.WantReadError:
+ pass
+ return self.buffer[:size]
+
+ def _recv(self, size):
+ if size <= len(self.buffer):
+ out = self.buffer[:size]
+ self.buffer = self.buffer[size:]
+ return out
+ buf = self.buffer
+ size -= len(buf)
+ try:
+ buf += self._connection.recv(size)
except SSL.SysCallError as e:
if e.args != (-1, "Unexpected EOF"):
raise e
except SSL.ZeroReturnError:
pass
+ except SSL.WantReadError as e:
+ # Rethrow the WantRead if we really have no data
+ if not buf:
+ raise e
except SSL.Error as e:
if e.args != (-1, "Unexpected EOF"):
raise e
+ self.buffer = ""
return buf
+ def send(self, string):
+ sent = self._connection.send(string)
+ # Track short send state for our awful fileno hacks
+ self._is_short_send = sent != len(string)
+ return sent
+
+ _always_read_fd = None
+ def always_read_fd(self):
+ """Return an fd that is always ready for read when passed to select.select. See fileno for why this is needed."""
+ if ConnectionWrapper._always_read_fd:
+ return ConnectionWrapper._always_read_fd
+ ConnectionWrapper._always_read_fd = open("/dev/zero")
+ return ConnectionWrapper._always_read_fd
+
+ def fileno(self):
+ # _AWFUL_ HACK to support MSG_PEEK without breaking select.select.
+ # If we read data with a peeking recv then return a fd that is always selectable on read to make sure the connection keeps flowing.
+ # Note that if the conneciton is handling a short send then we're only waiting for write not read, so use the underlying connection.
+ # Once the backlog is sent the connection will start trying to read again and we'll return the always_read_fd.
+ if self.buffer and not self._is_short_send:
+ return self.always_read_fd().fileno()
+
+ return self._connection.fileno()
def stub_verify(conn, cert, errno, errdepth, code):
"""We don't verify the server when we attempt a MiTM.
@@ -360,7 +406,7 @@ def _check_for_ssl(self, client_request):
Returns if client_request was used(and should not be sent to the server)
"""
# check for a TLS Client Hello
- record = tls.parse_tls(client_request)
+ record, ignored = tls.parse_tls(client_request)
client_hello = None
if record:
first = record.messages[0]
@@ -411,17 +457,20 @@ def _handle_hello(self, client_hello):
def _bridge_client(self):
try:
- # Check for a TLS client hello we might need to intercept
- if not self.ssl:
+ try:
client_request = self.client_socket.recv(65536, socket.MSG_PEEK)
- if not client_request:
- return False
- # If a MiTM was attempted discard client_request, we used it
- # for establishing a MiTM with the client.
- if self._check_for_ssl(client_request):
+ handled = self.handler.peek_request(client_request)
+ if handled:
return not self.closed
-
- try:
+ for handler in self.data_handlers:
+ if handler.peek_request(client_request):
+ return not self.closed
+ # Check for a TLS client hello we might need to intercept
+ if not self.ssl:
+ # If a MiTM was attempted discard client_request, we used it
+ # for establishing a MiTM with the client.
+ if self._check_for_ssl(client_request):
+ return not self.closed
client_request = self.client_socket.recv(65536)
except (socket.error, SSL.WantReadError):
# recv can still time out even if select returned this socket
@@ -456,6 +505,13 @@ def _bridge_client(self):
def _bridge_server(self):
try:
try:
+ server_response = self.server_socket.recv(65536, socket.MSG_PEEK)
+ handled = self.handler.peek_response(server_response)
+ if handled:
+ return not self.closed
+ for handler in self.data_handlers:
+ if handler.peek_response(server_response):
+ return not self.closed
server_response = self.server_socket.recv(65536)
except (socket.error, SSL.WantReadError):
# recv can still time out even if select returned this socket
@@ -605,6 +661,35 @@ def inject_response(self, response):
break
self.client_socket.sendall(response)
+class ReverseProxyConnection(BaseConnection):
+ """Connection based on reverse proxy"""
+
+ def start(self):
+ """Setup the remote end of the connection and client connection
+ to be ready to start bridging traffic.
+
+ This method should be implemented based on how connections are routed to nogotofail.mitm
+ such as iptables redirect or proxies.
+
+ This should call handler.on_select and handler.on_establish when appropriate
+ and set server_addr and server_port to the remote endpoint's address."""
+ self.server_addr, self.server_port = ReverseProxyConnection.target_addr, ReverseProxyConnection.target_port
+
+ self.handler.on_select()
+ for handler in self.data_handlers:
+ handler.on_select()
+ try:
+ self._start_server_connect_nonblocking()
+ except socket.error:
+ return False
+ return True
+
+ def _get_client_remote_name(self):
+ """Get the addr, port of the what the client thinks is their remote
+ This is used for blame, so this should correspond to some tcp connection
+ on the client"""
+ return self.client_socket.getsockname()[:2]
+
class RedirectConnection(BaseConnection):
"""Connection based on getting traffic from iptables redirect rules"""
@@ -634,13 +719,16 @@ def start(self):
return True
def _get_original_dest(self, sock):
+ family = sock.family
SO_ORIGINAL_DST = 80
- dst = sock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 28)
- family = struct.unpack_from("H", dst)[0]
# Parse the raw_ip and raw port from the struct sockaddr_in/in6
if family == socket.AF_INET:
+ dst = sock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 28)
raw_port, raw_ip = struct.unpack_from("!2xH4s", dst)
elif family == socket.AF_INET6:
+ # From socket.h
+ SOL_IPV6 = 41
+ dst = sock.getsockopt(SOL_IPV6, SO_ORIGINAL_DST, 64)
raw_port, raw_ip = struct.unpack_from("!2xH4x16s", dst)
else:
raise ValueError("Unsupported sa_family_t %d" % family)
diff --git a/nogotofail/mitm/connection/handlers/base.py b/nogotofail/mitm/connection/handlers/base.py
index 912c02c5..f4c8d99e 100644
--- a/nogotofail/mitm/connection/handlers/base.py
+++ b/nogotofail/mitm/connection/handlers/base.py
@@ -101,6 +101,24 @@ def on_ssl(self, client_hello):
"""
pass
+ def peek_request(self, request):
+ """Called with the data from a request _before_ it has been read from the socket.
+
+ This can be used to prempt the socket recv and handle data yourself.
+
+ Returns if the request should be considered handled and recv should not be called on the underlying socket
+ """
+ return False
+
+ def peek_response(self, response):
+ """Called with the data from a response _before_ it has been read from the socket.
+
+ This can be used to prempt the socket recv and handle data yourself.
+
+ Returns if the response should be considered handled and recv should not be called on the underlying socket
+ """
+ return False
+
class BaseConnectionHandler(BaseHandler):
diff --git a/nogotofail/mitm/connection/handlers/connection/ccs.py b/nogotofail/mitm/connection/handlers/connection/ccs.py
index 83ee170a..7b15b6f3 100644
--- a/nogotofail/mitm/connection/handlers/connection/ccs.py
+++ b/nogotofail/mitm/connection/handlers/connection/ccs.py
@@ -42,7 +42,7 @@ def on_request(self, request):
if not self.ssl or self.bridge:
return request
try:
- record, size = TlsRecord.from_stream(request)
+ record, remaining = tls.parse_tls(request)
message = record.messages[0]
if not self.clienthello_handled:
self.clienthello_handled = True
diff --git a/nogotofail/mitm/connection/handlers/connection/heartbleed.py b/nogotofail/mitm/connection/handlers/connection/heartbleed.py
index 953b448d..dcef7a5a 100644
--- a/nogotofail/mitm/connection/handlers/connection/heartbleed.py
+++ b/nogotofail/mitm/connection/handlers/connection/heartbleed.py
@@ -36,9 +36,9 @@ class ClientHeartbleedHandler(LoggingHandler):
def on_request(self, request):
# parse out request and check for heartbeat
try:
- index = 0
- while index < len(request):
- record, size = tls.types.TlsRecord.from_stream(request[index:])
+ remaining = request
+ while remaining:
+ record, remaining = tls.parse_tls(remaining)
if record.content_type == TlsRecord.CONTENT_TYPE.HEARTBEAT:
self.log(logging.CRITICAL, "Heartbleed response received")
self.log_event(
@@ -49,7 +49,6 @@ def on_request(self, request):
self.connection.vuln_notify(
util.vuln.VULN_TLS_CLIENT_HEARTBLEED)
self.success = True
- index += size
except:
pass
return request
@@ -66,11 +65,11 @@ def on_close(self, handler_initiated):
def on_response(self, response):
if self.first:
try:
- record, size = tls.types.TlsRecord.from_stream(response)
+ record, remaining = tls.parse_tls(response)
version = record.version
- response = (response[:size]
+ response = (record.to_bytes()
+ self.heartbleed % (version.to_bytes())
- + response[size:])
+ + remaining)
except:
self.log(logging.INFO, "Failed to parse TLS record from server")
self.first = False
diff --git a/nogotofail/mitm/connection/handlers/connection/selfsigned.py b/nogotofail/mitm/connection/handlers/connection/selfsigned.py
index a3bb54a3..57af7bef 100644
--- a/nogotofail/mitm/connection/handlers/connection/selfsigned.py
+++ b/nogotofail/mitm/connection/handlers/connection/selfsigned.py
@@ -93,3 +93,11 @@ class SuperFishMITM(SelfSignedMITM):
description = "Attempt a MiTM using the compromised superfish MITM CA"
ca = util.CertificateAuthority("superfish.pem")
vuln = util.vuln.VULN_TLS_SUPERFISH_TRUSTED
+
+@handler(handlers, default=True)
+@preconditions.requires_files(files=["explicit_curve.pem"])
+class ExplicitCurveMiTM(SelfSignedMITM):
+ name = "explicitcurvemitm"
+ description = "Attempt a MiTM exploiting CVE-2020-0601"
+ ca = util.CertificateAuthority("explicit_curve.pem")
+ vuln = util.vuln.VULN_TLS_EXPLICIT_CURVE
diff --git a/nogotofail/mitm/connection/handlers/connection/serverkeyreplace.py b/nogotofail/mitm/connection/handlers/connection/serverkeyreplace.py
index aebec4f7..88a4e146 100644
--- a/nogotofail/mitm/connection/handlers/connection/serverkeyreplace.py
+++ b/nogotofail/mitm/connection/handlers/connection/serverkeyreplace.py
@@ -18,6 +18,7 @@
from nogotofail.mitm.connection.handlers.connection import LoggingHandler
from nogotofail.mitm.connection.handlers.connection import handlers
from nogotofail.mitm.connection.handlers.store import handler
+from nogotofail.mitm.util.tls import tls
from nogotofail.mitm.util.tls.types import Alert, Extension, HandshakeMessage, OpaqueMessage, TlsRecord
from nogotofail.mitm.event import connection
@@ -56,7 +57,7 @@ def on_request(self, request):
if not self.ssl:
return request
try:
- record, size = TlsRecord.from_stream(request)
+ record, remaining = tls.parse_tls(request)
message = record.messages[0]
if not self.clienthello_adjusted:
self.clienthello_adjusted = True
@@ -68,7 +69,7 @@ def on_request(self, request):
if ext.type == Extension.TYPE.SESSIONTICKET:
ext.raw_data = []
# Retain in ClientHello only cipher suites which require the
- # server to send a ServerKeyExchange message: emphemeral (EC)DH
+ # server to send a ServerKeyExchange message: ephemeral (EC)DH
# and RSA_EXPORT cipher suites. Also retain pseudo/signalling
# cipher suites because they don't affect this attack/test.
hello.ciphers = [c for c in hello.ciphers
@@ -124,9 +125,10 @@ def on_response(self, response):
self.buffer = ""
# Tamper with the ServerKeyExchange message.
try:
- index = 0
- while index < len(response):
- record, size = TlsRecord.from_stream(response[index:])
+ remaining = response
+ new_response = ""
+ while remaining:
+ record, remaining = tls.parse_tls(remaining, throw_on_incomplete=True)
version = record.version
for i, message in enumerate(record.messages):
if (isinstance(message, HandshakeMessage)
@@ -134,15 +136,14 @@ def on_response(self, response):
HandshakeMessage.TYPE.SERVER_KEY_EXCHANGE)):
tampered_record_bytes = (
self._tamper_with_server_key_exchange(record, i))
- response = (response[:index] +
+ response = (new_response +
tampered_record_bytes +
- response[index+size:])
+ remaining)
self.signature_tampered = True
return response
+ new_response += record.to_bytes()
- index += size
-
- except ValueError:
+ except tls.types.TlsNotEnoughDataError:
# Failed to parse TLS, this is probably due to a short read of a TLS
# record. Buffer the response to try and get more data.
self.buffer = response
diff --git a/nogotofail/mitm/connection/handlers/data/ssl.py b/nogotofail/mitm/connection/handlers/data/ssl.py
index b3df9dcc..98ef1d90 100644
--- a/nogotofail/mitm/connection/handlers/data/ssl.py
+++ b/nogotofail/mitm/connection/handlers/data/ssl.py
@@ -21,6 +21,88 @@
from nogotofail.mitm.event import connection
from nogotofail.mitm.util import ssl2, tls, vuln
+class _TlsRecordHandler(DataHandler):
+ """Base class for a handler that acts on TlsRecords in a Tls connection.
+
+ Handlers should subclass this and implement on_tls_request and on_tls_response
+ to handle TlsRecords in the connection.
+
+ This class handles buffering and dealing with multiple records in one message
+ and can be used for active or passive handlers as needed.
+ """
+ ssl = False
+ class _TlsRecordBuffer():
+ MAX_BUFFER = 65536
+ def __init__(self):
+ self.buffer = ""
+ self.should_buffer = True
+ client_buffer = None
+ server_buffer = None
+
+ def on_tls_request(self, record):
+ """Called when the client sends a tls record to the server
+
+ record: tls.types.TlsRecord the client sent
+ Returns the bytes to be sent in place of the record
+ """
+ return record.to_bytes()
+
+ def on_tls_response(self, record):
+ """Called when the server sends a tls record to the client
+
+ record: tls.types.TlsRecord the client sent
+ Returns the bytes to be sent in place of the record
+ """
+ return record.to_bytes()
+
+ def on_ssl(self, client_hello):
+ self.ssl = True
+ self.client_buffer = _TlsRecordHandler._TlsRecordBuffer()
+ self.server_buffer = _TlsRecordHandler._TlsRecordBuffer()
+
+ def _handle_message(self, message, buffer, record_fn):
+ """Build a response calling record_fn on all the TlsRecords in message
+
+ message: bytes to parse as TlsRecords
+ record_fn: one of on_tls_request, on_tls_response to handle the record
+ Returns tuple containing the bytes to send for all the records handled and any remaining unparsed data
+ """
+ out = ""
+ message = buffer.buffer + message
+ buffer.buffer = ""
+ remaining = message
+ while remaining:
+ record = None
+ try:
+ record, remaining = tls.parse_tls(remaining, throw_on_incomplete=True)
+ except tls.types.TlsNotEnoughDataError:
+ if buffer.should_buffer:
+ buffer.buffer = remaining
+ if len(buffer.buffer) >= buffer.MAX_BUFFER:
+ buffer.buffer = ""
+ return out
+ if not record:
+ return out
+ record_bytes = record_fn(record)
+ # In a passive handler on_tls_* could return None, so make sure not to cause an error
+ # out doesn't matter on a passive handler.
+ if record_bytes:
+ out += record_bytes
+ # Once we read a CHANGE_CIPHER_SPEC stop trying to buffer, its probably encrypted
+ if record.content_type == record.CONTENT_TYPE.CHANGE_CIPHER_SPEC:
+ buffer.should_buffer = False
+ return out
+
+ def on_request(self, request):
+ if not self.ssl:
+ return request
+ return self._handle_message(request, self.client_buffer, self.on_tls_request)
+
+ def on_response(self, response):
+ if not self.ssl:
+ return response
+ return self._handle_message(response, self.server_buffer, self.on_tls_response)
+
@handler.passive(handlers)
class InsecureCipherDetectionHandler(DataHandler):
name = "insecurecipherdetection"
@@ -58,7 +140,7 @@ def on_ssl(self, client_hello):
# Check for export ciphers since they're horribly weak
export_ciphers = [str(c) for c in client_hello.ciphers if "EXPORT" in str(c)]
if export_ciphers:
- self._handle_bad_ciphers(integ_ciphers,
+ self._handle_bad_ciphers(export_ciphers,
"Client enabled export TLS/SSL cipher suites %s" %
(", ".join(export_ciphers)))
diff --git a/nogotofail/mitm/util/ca.py b/nogotofail/mitm/util/ca.py
index 16ae3db6..fbb19d1c 100644
--- a/nogotofail/mitm/util/ca.py
+++ b/nogotofail/mitm/util/ca.py
@@ -15,6 +15,8 @@
'''
import tempfile
import OpenSSL.crypto
+import atexit
+import shutil
import os
import random
from nogotofail.mitm.util import extras
@@ -22,8 +24,11 @@
class CertificateAuthority(object):
"""Simple CA for generating certs based on CNs and sans."""
- def __init__(self, ca_file='ca.pem', cert_dir=tempfile.gettempdir()):
+ def __init__(self, ca_file='ca.pem', cert_dir=None):
self.ca_file = ca_file
+ if cert_dir is None:
+ cert_dir = tempfile.mkdtemp(prefix="ngtf_ca_" + ca_file)
+ atexit.register(shutil.rmtree, cert_dir)
self.cert_dir = cert_dir
self._loaded = False
@@ -45,8 +50,8 @@ def _generate_ca(self):
self.cert.set_version(2)
self.cert.set_serial_number(1)
self.cert.get_subject().CN = 'ca.nogotofail'
- self.cert.set_notBefore("19300101000000+0000")
- self.cert.set_notAfter("203012310000+0000")
+ cert.set_notBefore("20200101000000Z")
+ cert.set_notAfter("20210101000000Z")
self.cert.set_issuer(self.cert.get_subject())
self.cert.set_pubkey(self.key)
self.cert.add_extensions([
@@ -63,7 +68,7 @@ def _generate_ca(self):
False,
'hash',
subject=self.cert)])
- self.cert.sign(self.key, 'sha1')
+ self.cert.sign(self.key, 'sha256')
with open(self.ca_file, 'w') as f:
f.write(
@@ -94,12 +99,12 @@ def _generate_cert(self, cn, san, path):
cert.set_pubkey(key)
cert.set_serial_number(random.randint(0, 2**20))
# Use a huge range so we dont have to worry about bad clocks
- cert.set_notBefore("19300101000000+0000")
- cert.set_notAfter("203012310000+0000")
+ cert.set_notBefore("20200101000000Z")
+ cert.set_notAfter("20210101000000Z")
cert.set_issuer(self.cert.get_subject())
if san:
cert.add_extensions([san])
- cert.sign(self.key, 'sha1')
+ cert.sign(self.key, 'sha256')
with open(path, 'w') as f:
f.write(
diff --git a/nogotofail/mitm/util/tls/__init__.py b/nogotofail/mitm/util/tls/__init__.py
index 0c0b401b..ecc83920 100644
--- a/nogotofail/mitm/util/tls/__init__.py
+++ b/nogotofail/mitm/util/tls/__init__.py
@@ -13,5 +13,5 @@
See the License for the specific language governing permissions and
limitations under the License.
'''
-from tls import parse_tls
import types
+from tls import parse_tls
diff --git a/nogotofail/mitm/util/tls/tls.py b/nogotofail/mitm/util/tls/tls.py
index 98e53211..718d4094 100644
--- a/nogotofail/mitm/util/tls/tls.py
+++ b/nogotofail/mitm/util/tls/tls.py
@@ -16,20 +16,41 @@
import struct
from nogotofail.mitm.util.tls import types
+def parse_tls(message, throw_on_incomplete=False):
+ """Try and parse a TLS record. If the message is fragmented over multiple TLS records this
+ will return one TLS record with the defragmented payload
-def parse_tls(message, enforce_length=True):
- """Try and parse a TLS Record from message.
+ Arguments:
+ message -- wire representation of a TLS message.
+ throw_on_incomplete -- throw a TlsRecordIncompleteError or TlsMessageFragmentedError if
+ message is not complete, otherwise return None
- Message should be the byte representation of a TLS Record.
- Returns a nogotofail.mitm.util.tls.TlsRecord on success or None or error.
- If enforce_length is True then parse_tls will return None if there are bytes
- remaining in
- message after parsing the record.
+ Returns (nogotofail.mitm.util.tls.TlsRecord, remaining_message) if message consumed
+ or None, message if parsing was unsuccessful
"""
+ extra_fragment_data = ""
+ original_message = message
try:
- record, size = types.TlsRecord.from_stream(message)
- if enforce_length and size != len(message):
- return None
- return record
- except (IndexError, ValueError, struct.error) as e:
- return None
+ while message:
+ try:
+ record, size = types.TlsRecord.from_stream(message,
+ previous_fragment_data=extra_fragment_data)
+ return record, message[size:]
+ except types.TlsMessageFragmentedError as e:
+ # If we're fragmented try and keep parsing
+ extra_fragment_data += e.fragment_data
+ message = message[e.data_consumed:]
+ # If we're fragmented but out of data error out
+ if not message:
+ if throw_on_incomplete:
+ raise e
+ else:
+ return None, original_message
+ except (IndexError, ValueError, struct.error):
+ return None, original_message
+ except types.TlsRecordIncompleteError as e:
+ if throw_on_incomplete:
+ raise e
+ else:
+ return None, original_message
+ return None, original_message
diff --git a/nogotofail/mitm/util/tls/types/__init__.py b/nogotofail/mitm/util/tls/types/__init__.py
index 9ab69668..b528eb1a 100644
--- a/nogotofail/mitm/util/tls/types/__init__.py
+++ b/nogotofail/mitm/util/tls/types/__init__.py
@@ -13,6 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
'''
+import parse
+from errors import TlsNotEnoughDataError, TlsRecordIncompleteError, TlsMessageFragmentedError
from cipher import Cipher
from compression_method import CompressionMethod
from simple import Version, Random
diff --git a/nogotofail/mitm/util/tls/types/errors.py b/nogotofail/mitm/util/tls/types/errors.py
new file mode 100644
index 00000000..3fb51dda
--- /dev/null
+++ b/nogotofail/mitm/util/tls/types/errors.py
@@ -0,0 +1,33 @@
+r'''
+Copyright 2015 Google Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+'''
+
+class TlsNotEnoughDataError(Exception):
+ """Error in TLS parsing where the TLS record is so far valid but incomplete"""
+ pass
+
+class TlsRecordIncompleteError(TlsNotEnoughDataError):
+ """Error for when a TLS Record appears valid but is not enough data is present to parse
+ the record"""
+ def __init__(self, data_available, record_size):
+ self.data_available = data_available
+ self.record_size = record_size
+
+class TlsMessageFragmentedError(TlsNotEnoughDataError):
+ """Error for when not enough data is present to parse a TLS message because of
+ fragmentation"""
+ def __init__(self, fragment_data, data_consumed):
+ self.fragment_data = fragment_data
+ self.data_consumed = data_consumed
diff --git a/nogotofail/mitm/util/tls/types/handshake.py b/nogotofail/mitm/util/tls/types/handshake.py
index 0eff37b0..627abf67 100644
--- a/nogotofail/mitm/util/tls/types/handshake.py
+++ b/nogotofail/mitm/util/tls/types/handshake.py
@@ -16,6 +16,7 @@
from nogotofail.mitm.util import Constants
from nogotofail.mitm.util.tls.types import parse
from nogotofail.mitm.util.tls.types import Cipher, Extension, Version, Random, CompressionMethod
+from nogotofail.mitm.util.tls.types import TlsNotEnoughDataError
import base64
import struct
@@ -140,7 +141,7 @@ def _parse_certificate(buf):
length = struct.unpack_from("!I", "\x00" + buf[:3])[0]
data = buf[3:3+length]
if len(data) != length:
- raise ValueError("Not enough data in buffer to parse certificate need %d bytes but read %d" % (length, len(data)))
+ raise ValueError("Not enough data to parse certificate")
return data, length + 3
@staticmethod
@@ -294,9 +295,12 @@ def from_stream(body):
# Parse The Handshake. Length is 24bits which struct doesn't support
# well.
msg_type, length = struct.unpack("!BI", body[0] + "\x00" + body[1:4])
+ # sanity check
+ if msg_type not in name_map:
+ raise ValueError("Unknown HanshakeMessage type %d" % msg_type)
body = body[4:4 + length]
if length != len(body):
- raise ValueError("Not enough data in body")
+ raise TlsNotEnoughDataError()
# Check this is a supported type
type = HandshakeMessage.type_map.get(msg_type, OpaqueMessage)
obj, size = type.from_stream(body)
diff --git a/nogotofail/mitm/util/tls/types/record.py b/nogotofail/mitm/util/tls/types/record.py
index dc7959f0..ea9553ab 100644
--- a/nogotofail/mitm/util/tls/types/record.py
+++ b/nogotofail/mitm/util/tls/types/record.py
@@ -16,6 +16,7 @@
from nogotofail.mitm.util import Constants
from nogotofail.mitm.util.tls.types import parse
from nogotofail.mitm.util.tls.types import HandshakeMessage, Version, ChangeCipherSpec, Alert
+from nogotofail.mitm.util.tls.types import TlsRecordIncompleteError, TlsMessageFragmentedError, TlsNotEnoughDataError
import base64
import struct
@@ -46,34 +47,50 @@ def __init__(self, content_type, version, messages):
self.messages = messages
@staticmethod
- def from_stream(body):
+ def from_stream(body, previous_fragment_data=""):
# Parse the TLS Record
content_type, version_major, version_minor, length = (
struct.unpack_from("!BBBH", body, 0))
+ # Sanity checks
+ if version_major != 3 or version_minor > 3:
+ raise ValueError("Bad TLS Version for SSL3-TLS1.2 parsing")
+ if content_type not in name_map:
+ raise ValueError("Unknown content type %d" % content_type)
+
fragment = body[5:5 + length]
- # Sanity check
- if length != len(fragment):
- raise ValueError("Not enough data in fragment")
- # Check this is a Handshake message
+ original_fragment = fragment
+ # Merge in any old fragmented data
+ fragment = previous_fragment_data + fragment
+ if len(fragment) < length:
+ raise TlsRecordIncompleteError(len(fragment), length)
+ # Start parsing the objects from the record
type = TlsRecord.type_map.get(content_type, OpaqueFragment)
objs = []
- if fragment == "":
- obj, size = type.from_stream(fragment)
- objs.append(obj)
-
- while fragment != "":
- obj, size = type.from_stream(fragment)
- objs.append(obj)
- fragment = fragment[size:]
+ try:
+ if fragment == "":
+ obj, size = type.from_stream(fragment)
+ objs.append(obj)
+
+ while fragment != "":
+ obj, size = type.from_stream(fragment)
+ objs.append(obj)
+ fragment = fragment[size:]
+ except TlsNotEnoughDataError:
+ # In the event of not enough data throw what we have up to a higher
+ # level
+ raise TlsMessageFragmentedError(original_fragment, 5 + length)
return TlsRecord(content_type, Version(version_major, version_minor),
objs), 5 + length
- def to_bytes(self):
+ def to_bytes(self, max_fragment_size = 2 ** 12):
bytes = "".join([message.to_bytes() for message in self.messages])
- return (struct.pack("B", self.content_type)
- + self.version.to_bytes()
- + struct.pack("!H", len(bytes))
- + bytes)
+ # Fragment the record as needed
+ num_fragments = len(bytes)/max_fragment_size
+ fragments = [bytes[i: i + max_fragment_size] for i in range (0, len(bytes), max_fragment_size)]
+ return "".join([(struct.pack("B", self.content_type)
+ + self.version.to_bytes()
+ + struct.pack("!H", len(fragment))
+ + fragment) for fragment in fragments])
def __str__(self):
return ("TLS Record %s %s (%d)"
diff --git a/nogotofail/mitm/util/vuln.py b/nogotofail/mitm/util/vuln.py
index 2de2f0f4..d85068f0 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_TLS_EXPLICIT_CURVE = "explicitcurve"
diff --git a/nogotofail/test/__init__.py b/nogotofail/test/__init__.py
new file mode 100644
index 00000000..2106176a
--- /dev/null
+++ b/nogotofail/test/__init__.py
@@ -0,0 +1,15 @@
+r'''
+Copyright 2015 Google Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+'''
diff --git a/nogotofail/test/android/.classpath b/nogotofail/test/android/.classpath
deleted file mode 100644
index 7bc01d9a..00000000
--- a/nogotofail/test/android/.classpath
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/nogotofail/test/android/.gitignore b/nogotofail/test/android/.gitignore
index 9a0e78f0..67495841 100644
--- a/nogotofail/test/android/.gitignore
+++ b/nogotofail/test/android/.gitignore
@@ -1,3 +1,33 @@
-.settings
bin
gen
+
+# Bazel
+bazel-*
+WORKSPACE
+
+# Generated files
+bin/
+gen/
+
+# Gradle files
+.gradle/
+build/
+gradle/
+gradlew*
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Mac OS X clutter
+*.DS_Store
+
+# Windows clutter
+Thumbs.db
+
+# Android Studio project files (see https://intellij-support.jetbrains.com/entries/23393067)
+*.iml
+.idea/
+*.iws
+
+# Android Studio Navigation editor temp files
+.navigation/
diff --git a/nogotofail/test/android/.project b/nogotofail/test/android/.project
deleted file mode 100644
index 26962c9b..00000000
--- a/nogotofail/test/android/.project
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
- nogotofail-mitm-tester
-
-
-
-
-
- com.android.ide.eclipse.adt.ResourceManagerBuilder
-
-
-
-
- com.android.ide.eclipse.adt.PreCompilerBuilder
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- com.android.ide.eclipse.adt.ApkBuilder
-
-
-
-
-
- com.android.ide.eclipse.adt.AndroidNature
- org.eclipse.jdt.core.javanature
-
-
-
-
diff --git a/nogotofail/test/android/app/build.gradle b/nogotofail/test/android/app/build.gradle
new file mode 100644
index 00000000..9a83f1c0
--- /dev/null
+++ b/nogotofail/test/android/app/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ applicationId "net.nogotofail.mitmtester"
+ minSdkVersion 14
+ targetSdkVersion 21
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+}
diff --git a/nogotofail/test/android/AndroidManifest.xml b/nogotofail/test/android/app/src/main/AndroidManifest.xml
similarity index 100%
rename from nogotofail/test/android/AndroidManifest.xml
rename to nogotofail/test/android/app/src/main/AndroidManifest.xml
diff --git a/nogotofail/test/android/src/net/nogotofail/mitmtester/BackgroundTest.java b/nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/BackgroundTest.java
similarity index 100%
rename from nogotofail/test/android/src/net/nogotofail/mitmtester/BackgroundTest.java
rename to nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/BackgroundTest.java
diff --git a/nogotofail/test/android/src/net/nogotofail/mitmtester/MainActivity.java b/nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/MainActivity.java
similarity index 100%
rename from nogotofail/test/android/src/net/nogotofail/mitmtester/MainActivity.java
rename to nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/MainActivity.java
diff --git a/nogotofail/test/android/src/net/nogotofail/mitmtester/TestActivity.java b/nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/TestActivity.java
similarity index 100%
rename from nogotofail/test/android/src/net/nogotofail/mitmtester/TestActivity.java
rename to nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/TestActivity.java
diff --git a/nogotofail/test/android/src/net/nogotofail/mitmtester/http/CleartextHttpCredentialsTest.java b/nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/http/CleartextHttpCredentialsTest.java
similarity index 100%
rename from nogotofail/test/android/src/net/nogotofail/mitmtester/http/CleartextHttpCredentialsTest.java
rename to nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/http/CleartextHttpCredentialsTest.java
diff --git a/nogotofail/test/android/src/net/nogotofail/mitmtester/http/HttpTestActivity.java b/nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/http/HttpTestActivity.java
similarity index 100%
rename from nogotofail/test/android/src/net/nogotofail/mitmtester/http/HttpTestActivity.java
rename to nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/http/HttpTestActivity.java
diff --git a/nogotofail/test/android/src/net/nogotofail/mitmtester/tls/DefaultConfigTlsHandshakeTest.java b/nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/tls/DefaultConfigTlsHandshakeTest.java
similarity index 100%
rename from nogotofail/test/android/src/net/nogotofail/mitmtester/tls/DefaultConfigTlsHandshakeTest.java
rename to nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/tls/DefaultConfigTlsHandshakeTest.java
diff --git a/nogotofail/test/android/src/net/nogotofail/mitmtester/tls/NoSslCertificateChainOfTrustCheckTest.java b/nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/tls/NoSslCertificateChainOfTrustCheckTest.java
similarity index 100%
rename from nogotofail/test/android/src/net/nogotofail/mitmtester/tls/NoSslCertificateChainOfTrustCheckTest.java
rename to nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/tls/NoSslCertificateChainOfTrustCheckTest.java
diff --git a/nogotofail/test/android/src/net/nogotofail/mitmtester/tls/NoSslCertificateHostnameVerificationTest.java b/nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/tls/NoSslCertificateHostnameVerificationTest.java
similarity index 100%
rename from nogotofail/test/android/src/net/nogotofail/mitmtester/tls/NoSslCertificateHostnameVerificationTest.java
rename to nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/tls/NoSslCertificateHostnameVerificationTest.java
diff --git a/nogotofail/test/android/src/net/nogotofail/mitmtester/tls/NoTlsServerAutenticationRequiredTest.java b/nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/tls/NoTlsServerAutenticationRequiredTest.java
similarity index 100%
rename from nogotofail/test/android/src/net/nogotofail/mitmtester/tls/NoTlsServerAutenticationRequiredTest.java
rename to nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/tls/NoTlsServerAutenticationRequiredTest.java
diff --git a/nogotofail/test/android/src/net/nogotofail/mitmtester/tls/TlsTestActivity.java b/nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/tls/TlsTestActivity.java
similarity index 100%
rename from nogotofail/test/android/src/net/nogotofail/mitmtester/tls/TlsTestActivity.java
rename to nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/tls/TlsTestActivity.java
diff --git a/nogotofail/test/android/src/net/nogotofail/mitmtester/util/AcceptAllX509TrustManager.java b/nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/util/AcceptAllX509TrustManager.java
similarity index 100%
rename from nogotofail/test/android/src/net/nogotofail/mitmtester/util/AcceptAllX509TrustManager.java
rename to nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/util/AcceptAllX509TrustManager.java
diff --git a/nogotofail/test/android/src/net/nogotofail/mitmtester/util/TlsUtils.java b/nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/util/TlsUtils.java
similarity index 100%
rename from nogotofail/test/android/src/net/nogotofail/mitmtester/util/TlsUtils.java
rename to nogotofail/test/android/app/src/main/java/net/nogotofail/mitmtester/util/TlsUtils.java
diff --git a/nogotofail/test/android/res/layout/http_test_activity.xml b/nogotofail/test/android/app/src/main/res/layout/http_test_activity.xml
similarity index 100%
rename from nogotofail/test/android/res/layout/http_test_activity.xml
rename to nogotofail/test/android/app/src/main/res/layout/http_test_activity.xml
diff --git a/nogotofail/test/android/res/layout/main_activity.xml b/nogotofail/test/android/app/src/main/res/layout/main_activity.xml
similarity index 100%
rename from nogotofail/test/android/res/layout/main_activity.xml
rename to nogotofail/test/android/app/src/main/res/layout/main_activity.xml
diff --git a/nogotofail/test/android/res/layout/tls_test_activity.xml b/nogotofail/test/android/app/src/main/res/layout/tls_test_activity.xml
similarity index 100%
rename from nogotofail/test/android/res/layout/tls_test_activity.xml
rename to nogotofail/test/android/app/src/main/res/layout/tls_test_activity.xml
diff --git a/nogotofail/test/android/res/values/strings.xml b/nogotofail/test/android/app/src/main/res/values/strings.xml
similarity index 100%
rename from nogotofail/test/android/res/values/strings.xml
rename to nogotofail/test/android/app/src/main/res/values/strings.xml
diff --git a/nogotofail/test/android/build.gradle b/nogotofail/test/android/build.gradle
new file mode 100644
index 00000000..05abe952
--- /dev/null
+++ b/nogotofail/test/android/build.gradle
@@ -0,0 +1,19 @@
+task wrapper(type: Wrapper) {
+ gradleVersion = '2.4'
+}
+
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.3.0'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/nogotofail/test/android/project.properties b/nogotofail/test/android/project.properties
deleted file mode 100644
index 6e18427a..00000000
--- a/nogotofail/test/android/project.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-21
diff --git a/nogotofail/test/android/settings.gradle b/nogotofail/test/android/settings.gradle
new file mode 100644
index 00000000..e7b4def4
--- /dev/null
+++ b/nogotofail/test/android/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/nogotofail/test/mitm/__init__.py b/nogotofail/test/mitm/__init__.py
new file mode 100644
index 00000000..2106176a
--- /dev/null
+++ b/nogotofail/test/mitm/__init__.py
@@ -0,0 +1,15 @@
+r'''
+Copyright 2015 Google Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+'''
diff --git a/nogotofail/test/mitm/util/__init__.py b/nogotofail/test/mitm/util/__init__.py
new file mode 100644
index 00000000..2106176a
--- /dev/null
+++ b/nogotofail/test/mitm/util/__init__.py
@@ -0,0 +1,15 @@
+r'''
+Copyright 2015 Google Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+'''
diff --git a/nogotofail/test/mitm/util/tls/__init__.py b/nogotofail/test/mitm/util/tls/__init__.py
new file mode 100644
index 00000000..2106176a
--- /dev/null
+++ b/nogotofail/test/mitm/util/tls/__init__.py
@@ -0,0 +1,15 @@
+r'''
+Copyright 2015 Google Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+'''
diff --git a/nogotofail/test/mitm/util/tls/test_handshake.py b/nogotofail/test/mitm/util/tls/test_handshake.py
new file mode 100644
index 00000000..5cacdad0
--- /dev/null
+++ b/nogotofail/test/mitm/util/tls/test_handshake.py
@@ -0,0 +1,104 @@
+r'''
+Copyright 2015 Google Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+'''
+import unittest
+from nogotofail.mitm.util import tls
+
+# Basic client hello from `openssl s_client -tls1_2 -cipher HIGH`
+BASIC_HELLO = ("FgMBASIBAAEeAwOVqPajcw+kjqqu6OY0g/k/UqYSjwdXHdiYwhfn+ZpNPgAAiMAwwCzAKMAkwBTA\n"
+ "CgCjAJ8AawBqADkAOACIAIfAGQCnAG0AOgCJwDLALsAqwCbAD8AFAJ0APQA1AITAEsAIABYAE8AX\n"
+ "ABvADcADAArAL8ArwCfAI8ATwAkAogCeAGcAQAAzADIARQBEwBgApgBsADQARsAxwC3AKcAlwA7A\n"
+ "BACcADwALwBBAP8BAABtAAsABAMAAQIACgA0ADIADgANABkACwAMABgACQAKABYAFwAIAAYABwAU\n"
+ "ABUABAAFABIAEwABAAIAAwAPABAAEQAjAAAADQAgAB4GAQYCBgMFAQUCBQMEAQQCBAMDAQMCAwMC\n"
+ "AQICAgMADwABAQ==\n").decode("base64")
+
+
+# BASIC_HELLO split into two fragments
+FRAGMENTED_BASIC_HELLO = (
+ "FgMBADIBAAEeAwOVqPajcw+kjqqu6OY0g/k/UqYSjwdXHdiYwhfn+ZpNPgAAiMAwwCzAKMAkwBYD\n"
+ "AQDwFMAKAKMAnwBrAGoAOQA4AIgAh8AZAKcAbQA6AInAMsAuwCrAJsAPwAUAnQA9ADUAhMASwAgA\n"
+ "FgATwBcAG8ANwAMACsAvwCvAJ8AjwBPACQCiAJ4AZwBAADMAMgBFAETAGACmAGwANABGwDHALcAp\n"
+ "wCXADsAEAJwAPAAvAEEA/wEAAG0ACwAEAwABAgAKADQAMgAOAA0AGQALAAwAGAAJAAoAFgAXAAgA\n"
+ "BgAHABQAFQAEAAUAEgATAAEAAgADAA8AEAARACMAAAANACAAHgYBBgIGAwUBBQIFAwQBBAIEAwMB\n"
+ "AwIDAwIBAgICAwAPAAEB\n").decode("base64")
+
+class TestClientHelloParsing(unittest.TestCase):
+
+ def check_basic_hello_record(self, record):
+ # Verify parsing was successful
+ self.assertIsNotNone(record, "parse_tls failed")
+
+ # Check the record is sane
+ self.assertEqual(record.version.major, 3)
+ self.assertEqual(record.version.minor, 1)
+ self.assertEqual(record.content_type, record.CONTENT_TYPE.HANDSHAKE)
+ self.assertEqual(len(record.messages), 1)
+
+ # Check the HandshakeMessage
+ message = record.messages[0]
+ self.assertIsInstance(message, tls.types.HandshakeMessage)
+ self.assertEqual(message.type, message.TYPE.CLIENT_HELLO)
+
+ # Check the ClientHello itself
+ hello = message.obj
+ self.assertIsInstance(hello, tls.types.ClientHello)
+ self.assertEqual(hello.version.major, 3)
+ self.assertEqual(hello.version.minor, 3)
+ self.assertEqual(hello.session_id, [])
+ self.assertEqual(hello.random.bytes,
+ "730fa48eaaaee8e63483f93f52a6128f07571dd898c217e7f99a4d3e".decode("hex"))
+ # TODO: Check that the contents are correct/correct order
+ self.assertEqual(len(hello.ciphers), 68)
+ self.assertEqual(len(hello.extensions), 5)
+ for key in [35, 10, 11, 13, 15]:
+ self.assertTrue(key in hello.extensions)
+
+ def test_parse_basic_hello(self):
+ record, remaining = tls.parse_tls(BASIC_HELLO)
+ self.assertIsNotNone(record)
+ self.assertEquals(remaining, "")
+ self.check_basic_hello_record(record)
+
+ def test_parse_basic_hello_extra(self):
+ extra_bytes = "\x11\x22\x33\x44"
+ record, remaining = tls.parse_tls(BASIC_HELLO + extra_bytes)
+ self.assertIsNotNone(record)
+ self.assertEquals(remaining, extra_bytes)
+ self.check_basic_hello_record(record)
+
+ def test_fragmented_basic_hello(self):
+ record, remaining = tls.parse_tls(FRAGMENTED_BASIC_HELLO)
+ self.assertIsNotNone(record)
+ self.assertEquals(remaining, "")
+ self.check_basic_hello_record(record)
+
+ def test_fragmenting(self):
+ record, remaining = tls.parse_tls(BASIC_HELLO)
+ self.assertIsNotNone(record)
+ self.assertEquals(remaining, "")
+ # Fragment on purpose
+ bytes = record.to_bytes(max_fragment_size=50)
+ # sanity check this parses back out
+ parsed_rec, remaining = tls.parse_tls(bytes)
+ self.assertIsNotNone(parsed_rec)
+ self.assertEqual(remaining, "")
+ self.check_basic_hello_record(parsed_rec)
+
+ # Now check that they got split by parsing for only one record
+ with self.assertRaises(tls.types.TlsNotEnoughDataError):
+ tls.types.TlsRecord.from_stream(bytes)
+
+if __name__ == "__main__":
+ unittest.main()