mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-23 23:02:25 +00:00
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",
|
||||
],
|
||||
"allmydata": ["ported-modules.txt"],
|
||||
"allmydata.test": ["certs/*"]
|
||||
},
|
||||
include_package_data=True,
|
||||
setup_requires=setup_requires,
|
||||
|
66
src/allmydata/test/certs.py
Normal file
66
src/allmydata/test/certs.py
Normal file
@ -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 .common_system import SystemTestMixin
|
||||
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.http_server import HTTPServer, listen_tls
|
||||
from allmydata.storage.http_client import StorageClient
|
||||
@ -1057,20 +1063,16 @@ class _HTTPMixin(_SharedMixin):
|
||||
swissnum = b"1234"
|
||||
http_storage_server = HTTPServer(self.server, swissnum)
|
||||
|
||||
# Listen on randomly assigned port, using self-signed cert we generated
|
||||
# manually:
|
||||
certs_dir = FilePath(__file__).parent().child("certs")
|
||||
# Listen on randomly assigned port, using self-signed cert:
|
||||
private_key = generate_private_key()
|
||||
certificate = generate_certificate(private_key)
|
||||
_, endpoint_string = self._port_assigner.assign(reactor)
|
||||
nurl, listening_port = yield listen_tls(
|
||||
http_storage_server,
|
||||
"127.0.0.1",
|
||||
serverFromString(reactor, endpoint_string),
|
||||
# This is just a self-signed certificate with randomly generated
|
||||
# private key; nothing at all special about it. You can regenerate
|
||||
# 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"),
|
||||
private_key_to_file(FilePath(self.mktemp()), private_key),
|
||||
cert_to_file(FilePath(self.mktemp()), certificate),
|
||||
)
|
||||
self.addCleanup(listening_port.stopListening)
|
||||
|
||||
|
@ -6,14 +6,10 @@ server authentication logic, which may one day apply outside of HTTP Storage
|
||||
Protocol.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from functools import wraps
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
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 import reactor
|
||||
@ -26,6 +22,12 @@ from twisted.python.filepath import FilePath
|
||||
from treq.client import HTTPClient
|
||||
|
||||
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_client import _StorageClientHTTPSPolicy
|
||||
from ..storage.http_server import _TLSEndpointWrapper
|
||||
@ -100,68 +102,6 @@ class PinningHTTPSValidation(AsyncTestCase):
|
||||
self.addCleanup(self._port_assigner.tearDown)
|
||||
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
|
||||
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
|
||||
connect to the server.
|
||||
"""
|
||||
private_key = self.generate_private_key()
|
||||
certificate = self.generate_certificate(private_key)
|
||||
private_key = generate_private_key()
|
||||
certificate = generate_certificate(private_key)
|
||||
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:
|
||||
response = await self.request(url, certificate)
|
||||
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
|
||||
expects, the request to the server fails.
|
||||
"""
|
||||
private_key1 = self.generate_private_key()
|
||||
certificate1 = self.generate_certificate(private_key1)
|
||||
private_key2 = self.generate_private_key()
|
||||
certificate2 = self.generate_certificate(private_key2)
|
||||
private_key1 = generate_private_key()
|
||||
certificate1 = generate_certificate(private_key1)
|
||||
private_key2 = generate_private_key()
|
||||
certificate2 = generate_certificate(private_key2)
|
||||
|
||||
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:
|
||||
with self.assertRaises(ResponseNeverReceived):
|
||||
await self.request(url, certificate2)
|
||||
@ -242,11 +184,12 @@ class PinningHTTPSValidation(AsyncTestCase):
|
||||
succeeds if the hash matches the one the client expects; expiration has
|
||||
no effect.
|
||||
"""
|
||||
private_key = self.generate_private_key()
|
||||
certificate = self.generate_certificate(private_key, expires_days=-10)
|
||||
private_key = generate_private_key()
|
||||
certificate = generate_certificate(private_key, expires_days=-10)
|
||||
|
||||
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:
|
||||
response = await self.request(url, certificate)
|
||||
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
|
||||
expects; start time has no effect.
|
||||
"""
|
||||
private_key = self.generate_private_key()
|
||||
certificate = self.generate_certificate(
|
||||
private_key = generate_private_key()
|
||||
certificate = generate_certificate(
|
||||
private_key, expires_days=10, valid_in_days=5
|
||||
)
|
||||
|
||||
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:
|
||||
response = await self.request(url, certificate)
|
||||
self.assertEqual(await response.content(), b"YOYODYNE")
|
||||
|
Loading…
Reference in New Issue
Block a user