1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
"""
Copyright (c) 2023 Proton AG
This file is part of Proton.
Proton is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Proton is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ProtonVPN. If not, see <https://www.gnu.org/licenses/>.
"""
import unittest
class TestTLSValidation(unittest.IsolatedAsyncioTestCase):
async def test_successful(self):
from proton.session import Session
from proton.session.environments import ProdEnvironment
s = Session()
s.environment = ProdEnvironment()
assert await s.async_api_request('/tests/ping') == {'Code': 1000}
async def test_without_pinning(self):
from proton.session import Session
from proton.session.environments import ProdEnvironment
class ProdWithoutPinningEnvironment(ProdEnvironment):
@property
def tls_pinning_hashes(self):
return None
@property
def tls_pinning_hashes_ar(self):
return None
s = Session()
s.environment = ProdWithoutPinningEnvironment()
assert await s.async_api_request('/tests/ping') == {'Code': 1000}
async def test_bad_pinning_url_changed(self):
from proton.session import Session
from proton.session.environments import ProdEnvironment
from proton.session.exceptions import ProtonAPINotReachable
from proton.session.transports.aiohttp import AiohttpTransport
class BrokenProdEnvironment(ProdEnvironment):
@property
def http_base_url(self):
# This is one of the URLs, but it uses different certificates than prod api, so pinning will fail
return "https://www.protonvpn.com/api/"
s = Session()
s.environment = BrokenProdEnvironment()
s.transport_factory = AiohttpTransport
with self.assertRaises(ProtonAPINotReachable) as e:
assert await s.async_api_request('/tests/ping') == {'Code': 1000}
assert str(e.exception).startswith('TLS pinning verification failed')
async def test_bad_pinning_fingerprint_changed(self):
from proton.session import Session
from proton.session.environments import ProdEnvironment
from proton.session.exceptions import ProtonAPINotReachable
from proton.session.transports.aiohttp import AiohttpTransport
class BrokenProdEnvironment(ProdEnvironment):
@property
def tls_pinning_hashes(self):
# This is an invalid hash
return set([
"aaaaaaakFkM8qJClsuWgUzxgBkePfRCkRpqUesyDmeE=",
])
s = Session()
s.environment = BrokenProdEnvironment()
s.transport_factory = AiohttpTransport
with self.assertRaises(ProtonAPINotReachable) as e:
assert await s.async_api_request('/tests/ping') == {'Code': 1000}
assert str(e.exception).startswith('TLS pinning verification failed')
async def test_pinning_disabled(self):
from proton.session import Session
from proton.session.environments import ProdEnvironment
from proton.session.exceptions import ProtonAPINotReachable
class PinningDisabledProdEnvironment(ProdEnvironment):
@property
def http_base_url(self):
# This is one of the URLs, but it uses different certificates than prod api, so pinning would fail if it was used
return "https://www.protonvpn.com/api/"
@property
def tls_pinning_hashes(self):
return None
s = Session()
s.environment = PinningDisabledProdEnvironment()
with self.assertRaises(ProtonAPINotReachable) as e:
assert await s.async_api_request('/tests/ping') == {'Code': 1000}
# Will probably return "API returned non-json results"
assert not str(e.exception).startswith('TLS pinning verification failed')
async def test_bad_ssl(self):
from proton.session import Session
from proton.session.environments import ProdEnvironment
from proton.session.exceptions import ProtonAPINotReachable
from proton.session.transports.aiohttp import AiohttpTransport
class BrokenProdEnvironment(ProdEnvironment):
@property
def http_base_url(self):
# This will break, as it's a self signed certificate
return "https://self-signed.badssl.com/"
@property
def tls_pinning_hashes(self):
return None
s = Session()
s.environment = BrokenProdEnvironment()
s.transport_factory = AiohttpTransport
with self.assertRaises(ProtonAPINotReachable) as e:
assert await s.async_api_request('/tests/ping') == {'Code': 1000}
|