Start hooking up end-to-end tests with TLS, fixing bugs along the way.

At this point the issue is that the client fails certificate validation (which
is expected lacking the pinning validation logic, which should be added next).
This commit is contained in:
Itamar Turner-Trauring 2022-03-23 16:33:29 -04:00
parent eef99c1f22
commit 5310747eaa
6 changed files with 82 additions and 25 deletions

View File

@ -3,7 +3,6 @@ HTTP client that talks to the HTTP storage server.
""" """
from typing import Union, Set, Optional from typing import Union, Set, Optional
from treq.testing import StubTreq
from base64 import b64encode from base64 import b64encode
@ -77,7 +76,7 @@ class StorageClient(object):
self._treq = treq self._treq = treq
@classmethod @classmethod
def from_furl(cls, furl: DecodedURL) -> "StorageClient": def from_furl(cls, furl: DecodedURL, treq=treq) -> "StorageClient":
""" """
Create a ``StorageClient`` for the given furl. Create a ``StorageClient`` for the given furl.
""" """
@ -86,6 +85,9 @@ class StorageClient(object):
swissnum = furl.path[0].encode("ascii") swissnum = furl.path[0].encode("ascii")
certificate_hash = furl.user.encode("ascii") certificate_hash = furl.user.encode("ascii")
https_url = DecodedURL().replace(scheme="https", host=furl.host, port=furl.port)
return cls(https_url, swissnum, treq)
def relative_url(self, path): def relative_url(self, path):
"""Get a URL relative to the base URL.""" """Get a URL relative to the base URL."""
return self._base_url.click(path) return self._base_url.click(path)

View File

@ -48,4 +48,4 @@ def get_spki_hash(certificate: Certificate) -> bytes:
public_key_bytes = certificate.public_key().public_bytes( public_key_bytes = certificate.public_key().public_bytes(
Encoding.DER, PublicFormat.SubjectPublicKeyInfo Encoding.DER, PublicFormat.SubjectPublicKeyInfo
) )
return b64encode(sha256(public_key_bytes).digest()).strip() return b64encode(sha256(public_key_bytes).digest()).strip().rstrip(b"=")

View File

@ -534,6 +534,8 @@ def listen_tls(
The hostname is the external IP or hostname clients will connect to; it The hostname is the external IP or hostname clients will connect to; it
does not modify what interfaces the server listens on. To set the does not modify what interfaces the server listens on. To set the
listening interface, use the ``interface`` argument. listening interface, use the ``interface`` argument.
Port can be 0 to choose a random port.
""" """
endpoint_string = "ssl:privateKey={}:certKey={}:port={}".format( endpoint_string = "ssl:privateKey={}:certKey={}:port={}".format(
quoteStringArgument(str(private_key_path)), quoteStringArgument(str(private_key_path)),
@ -545,13 +547,19 @@ def listen_tls(
endpoint = serverFromString(reactor, endpoint_string) endpoint = serverFromString(reactor, endpoint_string)
def build_furl(listening_port: IListeningPort) -> DecodedURL: def build_furl(listening_port: IListeningPort) -> DecodedURL:
furl = DecodedURL() furl = DecodedURL().replace(
furl.fragment = "v=1" # HTTP-based fragment="v=1", # HTTP-based
furl.host = hostname host=hostname,
furl.port = listening_port.getHost().port port=listening_port.getHost().port,
furl.path = (server._swissnum,) path=(str(server._swissnum, "ascii"),),
furl.user = get_spki_hash(load_pem_x509_certificate(cert_path.read_bytes())) userinfo=[
furl.scheme = "pb" str(
get_spki_hash(load_pem_x509_certificate(cert_path.read_bytes())),
"ascii",
)
],
scheme="pb",
)
return furl return furl
return endpoint.listen(Site(server.get_resource())).addCallback( return endpoint.listen(Site(server.get_resource())).addCallback(

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDCzCCAfMCFHrs4pMBs35SlU3ZGMnVY5qp5MfZMA0GCSqGSIb3DQEBCwUAMEIx
CzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl
ZmF1bHQgQ29tcGFueSBMdGQwHhcNMjIwMzIzMjAwNTM0WhcNMjIwNDIyMjAwNTM0
WjBCMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQK
DBNEZWZhdWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAzj0C3W4OiugEb3nr7NVQfrgzL3Tet5ze8pJCew0lIsDNrZiESF/Lvnrc
VcQtjraC3ySZO3rLDLhCwALLC1TVw3lp2ou+02kYtfJJVr1XyEcVCpsxx89u/W5B
VgMyBMoVZLS2BoBA1652XZnphvgm8n/daf9HH2Y4ifRDTIl1Bgl+4XtqlCKNFGYO
7zeadViKeI3fJOyzQaT+WTONQRWtAMP6c/n6VnlrdNughUEM+X0HqwVmr7UgatuA
LrJpfAkfKfOTZ70p2iOvegBH5ryR3InsB/O0/fp2+esNCEU3pfXWzg8ACif+ZQJc
AukUpQ4iJB7r1AK6iTZUqHnFgu9gYwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCv
BzVSinez5lcSWWCRve/TlMePEJK5d7OFcc90n7kmn+rkEYrel3a7Q+ctJxY9AKYG
A9X1AMDSH9z3243KFNaRJ1xKg0Mg8J/BLN9iphM5AnAuiAkCqs8VbD4hF4hZHLMZ
BVNyuLSdo+lBzbS57/Lz+lUWcxrXR5qgsEWSbjP+SrsDQKODfyoxKuU0XrxmNLd2
dTswbZKsqXBs80/T1jHwJjJXLp6YUsZqN1TGYtk8hEcE7bGaC3n7WhRjBP1WghNl
OG7FFRPte2w5seQRgkrBodLb9OCkhU4xfdyLnFICqkQHQAqIdXksEvir9uGY/yjC
zxAh60LEEQz6Kz29jYog
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAzj0C3W4OiugEb3nr7NVQfrgzL3Tet5ze8pJCew0lIsDNrZiE
SF/LvnrcVcQtjraC3ySZO3rLDLhCwALLC1TVw3lp2ou+02kYtfJJVr1XyEcVCpsx
x89u/W5BVgMyBMoVZLS2BoBA1652XZnphvgm8n/daf9HH2Y4ifRDTIl1Bgl+4Xtq
lCKNFGYO7zeadViKeI3fJOyzQaT+WTONQRWtAMP6c/n6VnlrdNughUEM+X0HqwVm
r7UgatuALrJpfAkfKfOTZ70p2iOvegBH5ryR3InsB/O0/fp2+esNCEU3pfXWzg8A
Cif+ZQJcAukUpQ4iJB7r1AK6iTZUqHnFgu9gYwIDAQABAoIBAG71rGDuIazif+Bq
PGDDs/c5q3BQ9LLdF6Zywonp3J0CFqbbc/BsefYVrA4I6mnqECd2TWsO+cfyKxeb
aRrDne75l9YZcaXU2ZKqtIKShHQgqlV2giX6mMCJXWWlenfRMglooLaGslxYZR6e
/GG9iVbXLI0m52EhYjH21W6MVgXUhsrDoI16pB87zk7jFZzyNsjRU5+bwr4L2jed
A1PseE6AI2kIpJCl8IIu6hRhVwjr8MIkaAtI3G8WmSAru99apHNttf6sgB2kcq2r
Qp1uXEXNVFQiJqcwMPOlWZ5X0kMIBxmFe10MkJbmCUoE/jPqO90XN2jyZPSONZU8
4yqd9GECgYEA5qQ+tfgOJFJ86t103pwkPm0+PxuQUTXry5ETnez+wxqqwbDxrEHi
MQoPZuVXPkbQ6g80KSpdI7AkFvu6BzcNlgpOI3gLZ30PHTF4HJTP01fKfbbVhg8N
WJS0yUh+kQDrGVcZbIbB5Q1vS5hu8ftk5ukns5BdFf/NS7fBfU8b3K0CgYEA5Onm
V2D1D9kdhTjta8f0d0s6+TdHoV86SRbkAEgnqzwlHWV17LlpXQic6iwokfS4TQSl
+1Z23Dt+OhLm/N0N3LgCxBhzTMnWGdy+w9co4GifwqR6T72JAxGOVoqIWk2cVpa5
8qJx0eAFXqcvpIASEoxYrdoKFUh60mAiQE6JQ08CgYB8wCoLUviTPOrEPrSQE/Sm
r4ATsl0FEB1SJk5uBVpnPW1PBt4xRhGKZN6f0Ty3OqaVc1PLUFbAju12YQHmFSkM
Ftbc6HmCqGocaD2HeBZRQhMMnHAx6sJVP1np5YRP+icvtaTSxrDpq7KfOPwJdujE
3SfUQCmZVJs+cU3+8WMooQKBgQCdvvl2eWAm/a00IxipT2+NzY/kMU3xTFg0Ccww
zYhYnefNrB9pdBPBgq/vR2LlwchHes6OtvTNq0m+50u6MPLeiQeO7nJ2FhiuVco3
1staaX6+eO24iZojPTPjOy/fWuBDYzbcl0jsIf5RTdCtAXxyv7hUhY6xP/Mzif/Q
ZM5+TQKBgQCF7pB6yLIAp6YJQ4uqOVbC2bMLr6tVWaNdEykiDd9zQkoacw/DPX1Y
FKehY/9EKraJ4t6d2/BBpJQyuIU4/gz8QMvjqQGP3NIfVeqBAPYo/nTYKOK0PSxB
Kl28Axxz6rjEeK4BixOES5PXuq2nNJXT8OSPYZQxQdTHstCWOP4Z6g==
-----END RSA PRIVATE KEY-----

View File

@ -24,12 +24,11 @@ else:
from random import Random from random import Random
from unittest import SkipTest from unittest import SkipTest
from pathlib import Path
from twisted.internet.defer import inlineCallbacks, returnValue, succeed from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from twisted.internet.task import Clock from twisted.internet.task import Clock
from twisted.internet import reactor from twisted.internet import reactor
from twisted.internet.endpoints import serverFromString
from twisted.web.server import Site
from twisted.web.client import Agent, HTTPConnectionPool from twisted.web.client import Agent, HTTPConnectionPool
from hyperlink import DecodedURL from hyperlink import DecodedURL
from treq.client import HTTPClient from treq.client import HTTPClient
@ -40,7 +39,7 @@ from allmydata.interfaces import IStorageServer # really, IStorageClient
from .common_system import SystemTestMixin from .common_system import SystemTestMixin
from .common import AsyncTestCase, SameProcessStreamEndpointAssigner from .common import AsyncTestCase, SameProcessStreamEndpointAssigner
from allmydata.storage.server import StorageServer # not a IStorageServer!! from allmydata.storage.server import StorageServer # not a IStorageServer!!
from allmydata.storage.http_server import HTTPServer from allmydata.storage.http_server import HTTPServer, listen_tls
from allmydata.storage.http_client import StorageClient from allmydata.storage.http_client import StorageClient
from allmydata.storage_client import _HTTPStorageServer from allmydata.storage_client import _HTTPStorageServer
@ -1074,27 +1073,29 @@ class _HTTPMixin(_SharedMixin):
swissnum = b"1234" swissnum = b"1234"
http_storage_server = HTTPServer(self.server, swissnum) http_storage_server = HTTPServer(self.server, swissnum)
# Listen on randomly assigned port: # Listen on randomly assigned port, using self-signed cert we generated
tcp_address, endpoint_string = self._port_assigner.assign(reactor) # manually:
_, host, port = tcp_address.split(":") certs_dir = Path(__file__).parent / "certs"
port = int(port) furl, listening_port = yield listen_tls(
endpoint = serverFromString(reactor, endpoint_string) reactor,
listening_port = yield endpoint.listen(Site(http_storage_server.get_resource())) http_storage_server,
"127.0.0.1",
0,
certs_dir / "private.key",
certs_dir / "domain.crt",
interface="127.0.0.1",
)
self.addCleanup(listening_port.stopListening) self.addCleanup(listening_port.stopListening)
# Create HTTP client with non-persistent connections, so we don't leak # Create HTTP client with non-persistent connections, so we don't leak
# state across tests: # state across tests:
treq_client = HTTPClient( treq_client = HTTPClient(
Agent(reactor, HTTPConnectionPool(reactor, persistent=False)) Agent(reactor, pool=HTTPConnectionPool(reactor, persistent=False))
) )
returnValue( returnValue(
_HTTPStorageServer.from_http_client( _HTTPStorageServer.from_http_client(
StorageClient( StorageClient.from_furl(furl, treq_client)
DecodedURL().replace(scheme="http", host=host, port=port),
swissnum,
treq=treq_client,
)
) )
) )
# Eventually should also: # Eventually should also: