Switch to generating certs on the fly since Python packaging was being a pain.
This commit is contained in:
parent
423512ad00
commit
bdcf054de6
1
setup.py
1
setup.py
|
@ -410,7 +410,6 @@ setup(name="tahoe-lafs", # also set in __init__.py
|
||||||
"static/css/*.css",
|
"static/css/*.css",
|
||||||
],
|
],
|
||||||
"allmydata": ["ported-modules.txt"],
|
"allmydata": ["ported-modules.txt"],
|
||||||
"allmydata.test": ["certs/*"]
|
|
||||||
},
|
},
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
setup_requires=setup_requires,
|
setup_requires=setup_requires,
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
"""Utilities for generating TLS certificates."""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.x509.oid import NameOID
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
from cryptography.hazmat.primitives import serialization, hashes
|
||||||
|
|
||||||
|
from twisted.python.filepath import FilePath
|
||||||
|
|
||||||
|
|
||||||
|
def cert_to_file(path: FilePath, cert) -> FilePath:
|
||||||
|
"""
|
||||||
|
Write the given certificate to a file on disk. Returns the path.
|
||||||
|
"""
|
||||||
|
path.setContent(cert.public_bytes(serialization.Encoding.PEM))
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def private_key_to_file(path: FilePath, private_key) -> FilePath:
|
||||||
|
"""
|
||||||
|
Write the given key to a file on disk. Returns the path.
|
||||||
|
"""
|
||||||
|
path.setContent(
|
||||||
|
private_key.private_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||||
|
encryption_algorithm=serialization.NoEncryption(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def generate_private_key():
|
||||||
|
"""Create a RSA private key."""
|
||||||
|
return rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_certificate(
|
||||||
|
private_key,
|
||||||
|
expires_days: int = 10,
|
||||||
|
valid_in_days: int = 0,
|
||||||
|
org_name: str = "Yoyodyne",
|
||||||
|
):
|
||||||
|
"""Generate a certificate from a RSA private key."""
|
||||||
|
subject = issuer = x509.Name(
|
||||||
|
[x509.NameAttribute(NameOID.ORGANIZATION_NAME, org_name)]
|
||||||
|
)
|
||||||
|
starts = datetime.datetime.utcnow() + datetime.timedelta(days=valid_in_days)
|
||||||
|
expires = datetime.datetime.utcnow() + datetime.timedelta(days=expires_days)
|
||||||
|
return (
|
||||||
|
x509.CertificateBuilder()
|
||||||
|
.subject_name(subject)
|
||||||
|
.issuer_name(issuer)
|
||||||
|
.public_key(private_key.public_key())
|
||||||
|
.serial_number(x509.random_serial_number())
|
||||||
|
.not_valid_before(min(starts, expires))
|
||||||
|
.not_valid_after(expires)
|
||||||
|
.add_extension(
|
||||||
|
x509.SubjectAlternativeName([x509.DNSName("localhost")]),
|
||||||
|
critical=False,
|
||||||
|
# Sign our certificate with our private key
|
||||||
|
)
|
||||||
|
.sign(private_key, hashes.SHA256())
|
||||||
|
)
|
|
@ -1,19 +0,0 @@
|
||||||
-----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-----
|
|
|
@ -1,27 +0,0 @@
|
||||||
-----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-----
|
|
|
@ -25,6 +25,12 @@ from foolscap.api import Referenceable, RemoteException
|
||||||
from allmydata.interfaces import IStorageServer # really, IStorageClient
|
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 .certs import (
|
||||||
|
generate_certificate,
|
||||||
|
generate_private_key,
|
||||||
|
private_key_to_file,
|
||||||
|
cert_to_file,
|
||||||
|
)
|
||||||
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, listen_tls
|
from allmydata.storage.http_server import HTTPServer, listen_tls
|
||||||
from allmydata.storage.http_client import StorageClient
|
from allmydata.storage.http_client import StorageClient
|
||||||
|
@ -1057,20 +1063,16 @@ 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, using self-signed cert we generated
|
# Listen on randomly assigned port, using self-signed cert:
|
||||||
# manually:
|
private_key = generate_private_key()
|
||||||
certs_dir = FilePath(__file__).parent().child("certs")
|
certificate = generate_certificate(private_key)
|
||||||
_, endpoint_string = self._port_assigner.assign(reactor)
|
_, endpoint_string = self._port_assigner.assign(reactor)
|
||||||
nurl, listening_port = yield listen_tls(
|
nurl, listening_port = yield listen_tls(
|
||||||
http_storage_server,
|
http_storage_server,
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
serverFromString(reactor, endpoint_string),
|
serverFromString(reactor, endpoint_string),
|
||||||
# This is just a self-signed certificate with randomly generated
|
private_key_to_file(FilePath(self.mktemp()), private_key),
|
||||||
# private key; nothing at all special about it. You can regenerate
|
cert_to_file(FilePath(self.mktemp()), certificate),
|
||||||
# with code in allmydata.test.test_storage_https or with openssl
|
|
||||||
# CLI, with no meaningful change to the test.
|
|
||||||
certs_dir.child("private.key"),
|
|
||||||
certs_dir.child("domain.crt"),
|
|
||||||
)
|
)
|
||||||
self.addCleanup(listening_port.stopListening)
|
self.addCleanup(listening_port.stopListening)
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,10 @@ server authentication logic, which may one day apply outside of HTTP Storage
|
||||||
Protocol.
|
Protocol.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.x509.oid import NameOID
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
||||||
from cryptography.hazmat.primitives import serialization, hashes
|
|
||||||
|
|
||||||
from twisted.internet.endpoints import serverFromString
|
from twisted.internet.endpoints import serverFromString
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
|
@ -26,6 +22,12 @@ from twisted.python.filepath import FilePath
|
||||||
from treq.client import HTTPClient
|
from treq.client import HTTPClient
|
||||||
|
|
||||||
from .common import SyncTestCase, AsyncTestCase, SameProcessStreamEndpointAssigner
|
from .common import SyncTestCase, AsyncTestCase, SameProcessStreamEndpointAssigner
|
||||||
|
from .certs import (
|
||||||
|
generate_certificate,
|
||||||
|
generate_private_key,
|
||||||
|
private_key_to_file,
|
||||||
|
cert_to_file,
|
||||||
|
)
|
||||||
from ..storage.http_common import get_spki_hash
|
from ..storage.http_common import get_spki_hash
|
||||||
from ..storage.http_client import _StorageClientHTTPSPolicy
|
from ..storage.http_client import _StorageClientHTTPSPolicy
|
||||||
from ..storage.http_server import _TLSEndpointWrapper
|
from ..storage.http_server import _TLSEndpointWrapper
|
||||||
|
@ -100,68 +102,6 @@ class PinningHTTPSValidation(AsyncTestCase):
|
||||||
self.addCleanup(self._port_assigner.tearDown)
|
self.addCleanup(self._port_assigner.tearDown)
|
||||||
return AsyncTestCase.setUp(self)
|
return AsyncTestCase.setUp(self)
|
||||||
|
|
||||||
def _temp_file_with_data(self, data: bytes) -> FilePath:
|
|
||||||
"""
|
|
||||||
Write data to temporary file, return its path.
|
|
||||||
"""
|
|
||||||
path = self.mktemp()
|
|
||||||
with open(path, "wb") as f:
|
|
||||||
f.write(data)
|
|
||||||
return FilePath(path)
|
|
||||||
|
|
||||||
def cert_to_file(self, cert) -> FilePath:
|
|
||||||
"""
|
|
||||||
Write the given certificate to a temporary file on disk, return the
|
|
||||||
path.
|
|
||||||
"""
|
|
||||||
return self._temp_file_with_data(cert.public_bytes(serialization.Encoding.PEM))
|
|
||||||
|
|
||||||
def private_key_to_file(self, private_key) -> FilePath:
|
|
||||||
"""
|
|
||||||
Write the given key to a temporary file on disk, return the
|
|
||||||
path.
|
|
||||||
"""
|
|
||||||
return self._temp_file_with_data(
|
|
||||||
private_key.private_bytes(
|
|
||||||
encoding=serialization.Encoding.PEM,
|
|
||||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
|
||||||
encryption_algorithm=serialization.NoEncryption(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_private_key(self):
|
|
||||||
"""Create a RSA private key."""
|
|
||||||
return rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
||||||
|
|
||||||
def generate_certificate(
|
|
||||||
self,
|
|
||||||
private_key,
|
|
||||||
expires_days: int = 10,
|
|
||||||
valid_in_days: int = 0,
|
|
||||||
org_name: str = "Yoyodyne",
|
|
||||||
):
|
|
||||||
"""Generate a certificate from a RSA private key."""
|
|
||||||
subject = issuer = x509.Name(
|
|
||||||
[x509.NameAttribute(NameOID.ORGANIZATION_NAME, org_name)]
|
|
||||||
)
|
|
||||||
starts = datetime.datetime.utcnow() + datetime.timedelta(days=valid_in_days)
|
|
||||||
expires = datetime.datetime.utcnow() + datetime.timedelta(days=expires_days)
|
|
||||||
return (
|
|
||||||
x509.CertificateBuilder()
|
|
||||||
.subject_name(subject)
|
|
||||||
.issuer_name(issuer)
|
|
||||||
.public_key(private_key.public_key())
|
|
||||||
.serial_number(x509.random_serial_number())
|
|
||||||
.not_valid_before(min(starts, expires))
|
|
||||||
.not_valid_after(expires)
|
|
||||||
.add_extension(
|
|
||||||
x509.SubjectAlternativeName([x509.DNSName("localhost")]),
|
|
||||||
critical=False,
|
|
||||||
# Sign our certificate with our private key
|
|
||||||
)
|
|
||||||
.sign(private_key, hashes.SHA256())
|
|
||||||
)
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def listen(self, private_key_path: FilePath, cert_path: FilePath):
|
async def listen(self, private_key_path: FilePath, cert_path: FilePath):
|
||||||
"""
|
"""
|
||||||
|
@ -210,10 +150,11 @@ class PinningHTTPSValidation(AsyncTestCase):
|
||||||
If all conditions are met, a TLS client using the Tahoe-LAFS policy can
|
If all conditions are met, a TLS client using the Tahoe-LAFS policy can
|
||||||
connect to the server.
|
connect to the server.
|
||||||
"""
|
"""
|
||||||
private_key = self.generate_private_key()
|
private_key = generate_private_key()
|
||||||
certificate = self.generate_certificate(private_key)
|
certificate = generate_certificate(private_key)
|
||||||
async with self.listen(
|
async with self.listen(
|
||||||
self.private_key_to_file(private_key), self.cert_to_file(certificate)
|
private_key_to_file(FilePath(self.mktemp()), private_key),
|
||||||
|
cert_to_file(FilePath(self.mktemp()), certificate),
|
||||||
) as url:
|
) as url:
|
||||||
response = await self.request(url, certificate)
|
response = await self.request(url, certificate)
|
||||||
self.assertEqual(await response.content(), b"YOYODYNE")
|
self.assertEqual(await response.content(), b"YOYODYNE")
|
||||||
|
@ -224,13 +165,14 @@ class PinningHTTPSValidation(AsyncTestCase):
|
||||||
If the server's certificate hash doesn't match the hash the client
|
If the server's certificate hash doesn't match the hash the client
|
||||||
expects, the request to the server fails.
|
expects, the request to the server fails.
|
||||||
"""
|
"""
|
||||||
private_key1 = self.generate_private_key()
|
private_key1 = generate_private_key()
|
||||||
certificate1 = self.generate_certificate(private_key1)
|
certificate1 = generate_certificate(private_key1)
|
||||||
private_key2 = self.generate_private_key()
|
private_key2 = generate_private_key()
|
||||||
certificate2 = self.generate_certificate(private_key2)
|
certificate2 = generate_certificate(private_key2)
|
||||||
|
|
||||||
async with self.listen(
|
async with self.listen(
|
||||||
self.private_key_to_file(private_key1), self.cert_to_file(certificate1)
|
private_key_to_file(FilePath(self.mktemp()), private_key1),
|
||||||
|
cert_to_file(FilePath(self.mktemp()), certificate1),
|
||||||
) as url:
|
) as url:
|
||||||
with self.assertRaises(ResponseNeverReceived):
|
with self.assertRaises(ResponseNeverReceived):
|
||||||
await self.request(url, certificate2)
|
await self.request(url, certificate2)
|
||||||
|
@ -242,11 +184,12 @@ class PinningHTTPSValidation(AsyncTestCase):
|
||||||
succeeds if the hash matches the one the client expects; expiration has
|
succeeds if the hash matches the one the client expects; expiration has
|
||||||
no effect.
|
no effect.
|
||||||
"""
|
"""
|
||||||
private_key = self.generate_private_key()
|
private_key = generate_private_key()
|
||||||
certificate = self.generate_certificate(private_key, expires_days=-10)
|
certificate = generate_certificate(private_key, expires_days=-10)
|
||||||
|
|
||||||
async with self.listen(
|
async with self.listen(
|
||||||
self.private_key_to_file(private_key), self.cert_to_file(certificate)
|
private_key_to_file(FilePath(self.mktemp()), private_key),
|
||||||
|
cert_to_file(FilePath(self.mktemp()), certificate),
|
||||||
) as url:
|
) as url:
|
||||||
response = await self.request(url, certificate)
|
response = await self.request(url, certificate)
|
||||||
self.assertEqual(await response.content(), b"YOYODYNE")
|
self.assertEqual(await response.content(), b"YOYODYNE")
|
||||||
|
@ -258,13 +201,14 @@ class PinningHTTPSValidation(AsyncTestCase):
|
||||||
request to the server succeeds if the hash matches the one the client
|
request to the server succeeds if the hash matches the one the client
|
||||||
expects; start time has no effect.
|
expects; start time has no effect.
|
||||||
"""
|
"""
|
||||||
private_key = self.generate_private_key()
|
private_key = generate_private_key()
|
||||||
certificate = self.generate_certificate(
|
certificate = generate_certificate(
|
||||||
private_key, expires_days=10, valid_in_days=5
|
private_key, expires_days=10, valid_in_days=5
|
||||||
)
|
)
|
||||||
|
|
||||||
async with self.listen(
|
async with self.listen(
|
||||||
self.private_key_to_file(private_key), self.cert_to_file(certificate)
|
private_key_to_file(FilePath(self.mktemp()), private_key),
|
||||||
|
cert_to_file(FilePath(self.mktemp()), certificate),
|
||||||
) as url:
|
) as url:
|
||||||
response = await self.request(url, certificate)
|
response = await self.request(url, certificate)
|
||||||
self.assertEqual(await response.content(), b"YOYODYNE")
|
self.assertEqual(await response.content(), b"YOYODYNE")
|
||||||
|
|
Loading…
Reference in New Issue