simplify ALPN and OpenSSL on macOS

This commit is contained in:
Thomas Kriechbaumer 2016-12-01 10:36:18 +01:00
parent 741c2b7b66
commit 1a36efbb6a
10 changed files with 147 additions and 112 deletions

View File

@ -1,15 +1,6 @@
sudo: false sudo: false
language: python language: python
addons:
apt:
sources:
# Debian sid currently holds OpenSSL 1.0.2
# change this with future releases!
- debian-sid
packages:
- libssl-dev
env: env:
global: global:
- CI_DEPS=codecov>=2.0.5 - CI_DEPS=codecov>=2.0.5
@ -25,9 +16,21 @@ matrix:
language: generic language: generic
env: TOXENV=py35 BDIST=1 env: TOXENV=py35 BDIST=1
- python: 3.5 - python: 3.5
env: TOXENV=py35 BDIST=1 env: TOXENV=py35 OPENSSL_OLD
addons:
apt:
packages:
- libssl-dev
- python: 3.5 - python: 3.5
env: TOXENV=py35 NO_ALPN=1 env: TOXENV=py35 BDIST=1 OPENSSL_ALPN
addons:
apt:
sources:
# Debian sid currently holds OpenSSL 1.1.0
# change this with future releases!
- debian-sid
packages:
- libssl-dev
- python: 3.5 - python: 3.5
env: TOXENV=docs env: TOXENV=docs
git: git:
@ -39,10 +42,8 @@ install:
- | - |
if [[ $TRAVIS_OS_NAME == "osx" ]] if [[ $TRAVIS_OS_NAME == "osx" ]]
then then
brew update || brew update # try again if it fails brew update || brew update
brew upgrade brew outdated pyenv || brew upgrade pyenv
brew reinstall openssl
brew reinstall pyenv
eval "$(pyenv init -)" eval "$(pyenv init -)"
env PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install --skip-existing 3.5.2 env PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install --skip-existing 3.5.2
pyenv global 3.5.2 pyenv global 3.5.2
@ -52,8 +53,8 @@ install:
- pip install tox - pip install tox
script: script:
- tox -- --cov mitmproxy --cov pathod -v
- | - |
tox -- --cov mitmproxy --cov pathod -v
if [[ $BDIST == "1" ]] if [[ $BDIST == "1" ]]
then then
git fetch --unshallow --tags git fetch --unshallow --tags
@ -80,3 +81,4 @@ cache:
directories: directories:
- $HOME/.pyenv - $HOME/.pyenv
- $HOME/.cache/pip - $HOME/.cache/pip
# - $HOME/build/mitmproxy/mitmproxy/.tox

View File

@ -30,10 +30,7 @@ version_check.check_pyopenssl_version()
socket_fileobject = socket.SocketIO socket_fileobject = socket.SocketIO
EINTR = 4 EINTR = 4
if os.environ.get("NO_ALPN"): HAS_ALPN = SSL._lib.Cryptography_HAS_ALPN
HAS_ALPN = False
else:
HAS_ALPN = SSL._lib.Cryptography_HAS_ALPN
# To enable all SSL methods use: SSLv23 # To enable all SSL methods use: SSLv23
# then add options to disable certain methods # then add options to disable certain methods

14
test/conftest.py Normal file
View File

@ -0,0 +1,14 @@
import pytest
import OpenSSL
import mitmproxy.net.tcp
requires_alpn = pytest.mark.skipif(
not mitmproxy.net.tcp.HAS_ALPN,
reason='requires OpenSSL with ALPN support')
@pytest.fixture()
def disable_alpn(monkeypatch):
monkeypatch.setattr(mitmproxy.net.tcp, 'HAS_ALPN', False)
monkeypatch.setattr(OpenSSL.SSL._lib, 'Cryptography_HAS_ALPN', False)

View File

@ -6,7 +6,7 @@ import random
import os import os
import threading import threading
import mock import mock
import pytest
from OpenSSL import SSL from OpenSSL import SSL
from mitmproxy import certs from mitmproxy import certs
@ -15,6 +15,7 @@ from mitmproxy.test import tutils
from mitmproxy import exceptions from mitmproxy import exceptions
from . import tservers from . import tservers
from ...conftest import requires_alpn
class EchoHandler(tcp.BaseHandler): class EchoHandler(tcp.BaseHandler):
@ -526,40 +527,47 @@ class TestTimeOut(tservers.ServerTestBase):
tutils.raises(exceptions.TcpTimeout, c.rfile.read, 10) tutils.raises(exceptions.TcpTimeout, c.rfile.read, 10)
class TestCryptographyALPN:
def test_has_alpn(self):
if 'OPENSSL_ALPN' in os.environ:
assert tcp.HAS_ALPN
assert SSL._lib.Cryptography_HAS_ALPN
elif 'OPENSSL_OLD' in os.environ:
assert not tcp.HAS_ALPN
assert not SSL._lib.Cryptography_HAS_ALPN
class TestALPNClient(tservers.ServerTestBase): class TestALPNClient(tservers.ServerTestBase):
handler = ALPNHandler handler = ALPNHandler
ssl = dict( ssl = dict(
alpn_select=b"bar" alpn_select=b"bar"
) )
if tcp.HAS_ALPN: @requires_alpn
def test_alpn(self): @pytest.mark.parametrize('has_alpn,alpn_protos, expected_negotiated, expected_response', [
c = tcp.TCPClient(("127.0.0.1", self.port)) (True, [b"foo", b"bar", b"fasel"], b'bar', b'bar'),
with c.connect(): (True, [], b'', b'NONE'),
c.convert_to_ssl(alpn_protos=[b"foo", b"bar", b"fasel"]) (True, None, b'', b'NONE'),
assert c.get_alpn_proto_negotiated() == b"bar" (False, [b"foo", b"bar", b"fasel"], b'', b'NONE'),
assert c.rfile.readline().strip() == b"bar" (False, [], b'', b'NONE'),
(False, None, b'', b'NONE'),
])
def test_alpn(self, monkeypatch, has_alpn, alpn_protos, expected_negotiated, expected_response):
monkeypatch.setattr(tcp, 'HAS_ALPN', has_alpn)
monkeypatch.setattr(SSL._lib, 'Cryptography_HAS_ALPN', has_alpn)
def test_no_alpn(self):
c = tcp.TCPClient(("127.0.0.1", self.port)) c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect(): with c.connect():
c.convert_to_ssl() c.convert_to_ssl(alpn_protos=alpn_protos)
assert c.get_alpn_proto_negotiated() == b"" assert c.get_alpn_proto_negotiated() == expected_negotiated
assert c.rfile.readline().strip() == b"NONE" assert c.rfile.readline().strip() == expected_response
else:
def test_none_alpn(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
c.convert_to_ssl(alpn_protos=[b"foo", b"bar", b"fasel"])
assert c.get_alpn_proto_negotiated() == b""
assert c.rfile.readline() == b"NONE"
class TestNoSSLNoALPNClient(tservers.ServerTestBase): class TestNoSSLNoALPNClient(tservers.ServerTestBase):
handler = ALPNHandler handler = ALPNHandler
def test_no_ssl_no_alpn(self): def test_no_ssl_no_alpn(self, disable_alpn):
c = tcp.TCPClient(("127.0.0.1", self.port)) c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect(): with c.connect():
assert c.get_alpn_proto_negotiated() == b"" assert c.get_alpn_proto_negotiated() == b""

View File

@ -1,7 +1,6 @@
# coding=utf-8 # coding=utf-8
import pytest
import os import os
import tempfile import tempfile
import traceback import traceback
@ -17,6 +16,7 @@ from mitmproxy import exceptions
from mitmproxy.net.http import http1, http2 from mitmproxy.net.http import http1, http2
from .. import tservers from .. import tservers
from ...conftest import requires_alpn
import logging import logging
logging.getLogger("hyper.packages.hpack.hpack").setLevel(logging.WARNING) logging.getLogger("hyper.packages.hpack.hpack").setLevel(logging.WARNING)
@ -27,11 +27,6 @@ logging.getLogger("PIL.Image").setLevel(logging.WARNING)
logging.getLogger("PIL.PngImagePlugin").setLevel(logging.WARNING) logging.getLogger("PIL.PngImagePlugin").setLevel(logging.WARNING)
requires_alpn = pytest.mark.skipif(
not mitmproxy.net.tcp.HAS_ALPN,
reason='requires OpenSSL with ALPN support')
# inspect the log: # inspect the log:
# for msg in self.proxy.tmaster.tlog: # for msg in self.proxy.tmaster.tlog:
# print(msg) # print(msg)

View File

@ -51,14 +51,14 @@ class TestDumpMaster(mastertest.MasterTest):
assert "error" in o.tfile.getvalue() assert "error" in o.tfile.getvalue()
def test_replay(self): def test_replay(self):
o = dump.Options(server_replay=["nonexistent"], replay_kill_extra=True) o = dump.Options(http2=False, server_replay=["nonexistent"], replay_kill_extra=True)
tutils.raises(exceptions.OptionsError, dump.DumpMaster, o, proxy.DummyServer()) tutils.raises(exceptions.OptionsError, dump.DumpMaster, o, proxy.DummyServer())
with tutils.tmpdir() as t: with tutils.tmpdir() as t:
p = os.path.join(t, "rep") p = os.path.join(t, "rep")
self.flowfile(p) self.flowfile(p)
o = dump.Options(server_replay=[p], replay_kill_extra=True) o = dump.Options(http2=False, server_replay=[p], replay_kill_extra=True)
o.verbosity = 0 o.verbosity = 0
o.flow_detail = 0 o.flow_detail = 0
m = dump.DumpMaster(o, proxy.DummyServer()) m = dump.DumpMaster(o, proxy.DummyServer())
@ -66,13 +66,13 @@ class TestDumpMaster(mastertest.MasterTest):
self.cycle(m, b"content") self.cycle(m, b"content")
self.cycle(m, b"content") self.cycle(m, b"content")
o = dump.Options(server_replay=[p], replay_kill_extra=False) o = dump.Options(http2=False, server_replay=[p], replay_kill_extra=False)
o.verbosity = 0 o.verbosity = 0
o.flow_detail = 0 o.flow_detail = 0
m = dump.DumpMaster(o, proxy.DummyServer()) m = dump.DumpMaster(o, proxy.DummyServer())
self.cycle(m, b"nonexistent") self.cycle(m, b"nonexistent")
o = dump.Options(client_replay=[p], replay_kill_extra=False) o = dump.Options(http2=False, client_replay=[p], replay_kill_extra=False)
o.verbosity = 0 o.verbosity = 0
o.flow_detail = 0 o.flow_detail = 0
m = dump.DumpMaster(o, proxy.DummyServer()) m = dump.DumpMaster(o, proxy.DummyServer())

View File

@ -1,8 +1,8 @@
import io import io
from mock import Mock from mock import Mock
import pytest
from mitmproxy.net import http from mitmproxy.net import http
from mitmproxy.net import tcp
from mitmproxy.net.http import http1 from mitmproxy.net.http import http1
from mitmproxy import exceptions from mitmproxy import exceptions
@ -11,6 +11,7 @@ from pathod.protocols.http2 import HTTP2StateProtocol
from mitmproxy.test import tutils from mitmproxy.test import tutils
from . import tservers from . import tservers
from ..conftest import requires_alpn
def test_response(): def test_response():
@ -211,8 +212,7 @@ class TestDaemonHTTP2(PathocTestDaemon):
ssl = True ssl = True
explain = False explain = False
if tcp.HAS_ALPN: @requires_alpn
def test_http2(self): def test_http2(self):
c = pathoc.Pathoc( c = pathoc.Pathoc(
("127.0.0.1", self.d.port), ("127.0.0.1", self.d.port),
@ -227,6 +227,7 @@ class TestDaemonHTTP2(PathocTestDaemon):
) )
assert c.protocol == http1 assert c.protocol == http1
@requires_alpn
def test_http2_alpn(self): def test_http2_alpn(self):
c = pathoc.Pathoc( c = pathoc.Pathoc(
("127.0.0.1", self.d.port), ("127.0.0.1", self.d.port),
@ -243,6 +244,7 @@ class TestDaemonHTTP2(PathocTestDaemon):
_, kwargs = c.convert_to_ssl.call_args _, kwargs = c.convert_to_ssl.call_args
assert set(kwargs['alpn_protos']) == set([b'http/1.1', b'h2']) assert set(kwargs['alpn_protos']) == set([b'http/1.1', b'h2'])
@requires_alpn
def test_request(self): def test_request(self):
c = pathoc.Pathoc( c = pathoc.Pathoc(
("127.0.0.1", self.d.port), ("127.0.0.1", self.d.port),
@ -253,3 +255,14 @@ class TestDaemonHTTP2(PathocTestDaemon):
with c.connect(): with c.connect():
resp = c.request("get:/p/200") resp = c.request("get:/p/200")
assert resp.status_code == 200 assert resp.status_code == 200
def test_failing_request(self, disable_alpn):
c = pathoc.Pathoc(
("127.0.0.1", self.d.port),
fp=None,
ssl=True,
use_http2=True,
)
with pytest.raises(NotImplementedError):
with c.connect():
c.request("get:/p/200")

View File

@ -1,11 +1,14 @@
import io import io
import pytest
from pathod import pathod from pathod import pathod
from mitmproxy.net import tcp from mitmproxy.net import tcp
from mitmproxy import exceptions from mitmproxy import exceptions
from mitmproxy.test import tutils from mitmproxy.test import tutils
from . import tservers from . import tservers
from ..conftest import requires_alpn
class TestPathod: class TestPathod:
@ -257,8 +260,11 @@ class TestHTTP2(tservers.DaemonTests):
ssl = True ssl = True
nohang = True nohang = True
if tcp.HAS_ALPN: @requires_alpn
def test_http2(self): def test_http2(self):
r, _ = self.pathoc(["GET:/"], ssl=True, use_http2=True) r, _ = self.pathoc(["GET:/"], ssl=True, use_http2=True)
assert r[0].status_code == 800 assert r[0].status_code == 800
def test_no_http2(self, disable_alpn):
with pytest.raises(NotImplementedError):
r, _ = self.pathoc(["GET:/"], ssl=True, use_http2=True)

View File

@ -11,6 +11,8 @@ from ..mitmproxy.net import tservers as net_tservers
from pathod.protocols.http2 import HTTP2StateProtocol, TCPHandler from pathod.protocols.http2 import HTTP2StateProtocol, TCPHandler
from ..conftest import requires_alpn
class TestTCPHandlerWrapper: class TestTCPHandlerWrapper:
def test_wrapped(self): def test_wrapped(self):
@ -66,14 +68,13 @@ class TestProtocol:
assert mock_server_method.called assert mock_server_method.called
@requires_alpn
class TestCheckALPNMatch(net_tservers.ServerTestBase): class TestCheckALPNMatch(net_tservers.ServerTestBase):
handler = EchoHandler handler = EchoHandler
ssl = dict( ssl = dict(
alpn_select=b'h2', alpn_select=b'h2',
) )
if tcp.HAS_ALPN:
def test_check_alpn(self): def test_check_alpn(self):
c = tcp.TCPClient(("127.0.0.1", self.port)) c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect(): with c.connect():
@ -82,14 +83,13 @@ class TestCheckALPNMatch(net_tservers.ServerTestBase):
assert protocol.check_alpn() assert protocol.check_alpn()
@requires_alpn
class TestCheckALPNMismatch(net_tservers.ServerTestBase): class TestCheckALPNMismatch(net_tservers.ServerTestBase):
handler = EchoHandler handler = EchoHandler
ssl = dict( ssl = dict(
alpn_select=None, alpn_select=None,
) )
if tcp.HAS_ALPN:
def test_check_alpn(self): def test_check_alpn(self):
c = tcp.TCPClient(("127.0.0.1", self.port)) c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect(): with c.connect():

View File

@ -8,7 +8,7 @@ basepython = python3.5
deps = deps =
{env:CI_DEPS:} {env:CI_DEPS:}
-rrequirements.txt -rrequirements.txt
passenv = CODECOV_TOKEN CI CI_* TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* SNAPSHOT_* passenv = CODECOV_TOKEN CI CI_* TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* SNAPSHOT_* OPENSSL_*
setenv = HOME = {envtmpdir} setenv = HOME = {envtmpdir}
commands = commands =
mitmdump --sysinfo mitmdump --sysinfo