2019-01-29 08:53:02 -05:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2019 New Vector Ltd
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
import os.path
|
2019-06-10 11:06:25 -04:00
|
|
|
import subprocess
|
|
|
|
|
|
|
|
from zope.interface import implementer
|
2019-01-29 08:53:02 -05:00
|
|
|
|
|
|
|
from OpenSSL import SSL
|
2019-06-10 11:06:25 -04:00
|
|
|
from OpenSSL.SSL import Connection
|
|
|
|
from twisted.internet.interfaces import IOpenSSLServerConnectionCreator
|
2019-11-01 10:07:44 -04:00
|
|
|
from twisted.internet.ssl import Certificate, trustRootFromCertificates
|
|
|
|
from twisted.web.client import BrowserLikePolicyForHTTPS # noqa: F401
|
|
|
|
from twisted.web.iweb import IPolicyForHTTPS # noqa: F401
|
|
|
|
|
|
|
|
|
|
|
|
def get_test_https_policy():
|
|
|
|
"""Get a test IPolicyForHTTPS which trusts the test CA cert
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
IPolicyForHTTPS
|
|
|
|
"""
|
|
|
|
ca_file = get_test_ca_cert_file()
|
|
|
|
with open(ca_file) as stream:
|
|
|
|
content = stream.read()
|
|
|
|
cert = Certificate.loadPEM(content)
|
|
|
|
trust_root = trustRootFromCertificates([cert])
|
|
|
|
return BrowserLikePolicyForHTTPS(trustRoot=trust_root)
|
2019-06-10 11:06:25 -04:00
|
|
|
|
|
|
|
|
|
|
|
def get_test_ca_cert_file():
|
|
|
|
"""Get the path to the test CA cert
|
|
|
|
|
|
|
|
The keypair is generated with:
|
|
|
|
|
|
|
|
openssl genrsa -out ca.key 2048
|
|
|
|
openssl req -new -x509 -key ca.key -days 3650 -out ca.crt \
|
|
|
|
-subj '/CN=synapse test CA'
|
|
|
|
"""
|
|
|
|
return os.path.join(os.path.dirname(__file__), "ca.crt")
|
|
|
|
|
|
|
|
|
|
|
|
def get_test_key_file():
|
|
|
|
"""get the path to the test key
|
|
|
|
|
|
|
|
The key file is made with:
|
|
|
|
|
|
|
|
openssl genrsa -out server.key 2048
|
|
|
|
"""
|
|
|
|
return os.path.join(os.path.dirname(__file__), "server.key")
|
|
|
|
|
|
|
|
|
|
|
|
cert_file_count = 0
|
|
|
|
|
|
|
|
CONFIG_TEMPLATE = b"""\
|
|
|
|
[default]
|
|
|
|
basicConstraints = CA:FALSE
|
|
|
|
keyUsage=nonRepudiation, digitalSignature, keyEncipherment
|
2019-06-10 12:55:01 -04:00
|
|
|
subjectAltName = %(sanentries)s
|
2019-06-10 11:06:25 -04:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def create_test_cert_file(sanlist):
|
|
|
|
"""build an x509 certificate file
|
|
|
|
|
|
|
|
Args:
|
|
|
|
sanlist: list[bytes]: a list of subjectAltName values for the cert
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
str: the path to the file
|
|
|
|
"""
|
|
|
|
global cert_file_count
|
|
|
|
csr_filename = "server.csr"
|
|
|
|
cnf_filename = "server.%i.cnf" % (cert_file_count,)
|
|
|
|
cert_filename = "server.%i.crt" % (cert_file_count,)
|
|
|
|
cert_file_count += 1
|
|
|
|
|
|
|
|
# first build a CSR
|
2019-06-10 12:41:10 -04:00
|
|
|
subprocess.check_call(
|
2019-06-10 11:06:25 -04:00
|
|
|
[
|
|
|
|
"openssl",
|
|
|
|
"req",
|
|
|
|
"-new",
|
|
|
|
"-key",
|
|
|
|
get_test_key_file(),
|
|
|
|
"-subj",
|
|
|
|
"/",
|
|
|
|
"-out",
|
|
|
|
csr_filename,
|
2019-06-10 12:41:10 -04:00
|
|
|
]
|
2019-06-10 11:06:25 -04:00
|
|
|
)
|
2019-01-29 08:53:02 -05:00
|
|
|
|
2019-06-10 11:06:25 -04:00
|
|
|
# now a config file describing the right SAN entries
|
|
|
|
sanentries = b",".join(sanlist)
|
|
|
|
with open(cnf_filename, "wb") as f:
|
|
|
|
f.write(CONFIG_TEMPLATE % {b"sanentries": sanentries})
|
2019-01-29 08:53:02 -05:00
|
|
|
|
2019-06-10 11:06:25 -04:00
|
|
|
# finally the cert
|
|
|
|
ca_key_filename = os.path.join(os.path.dirname(__file__), "ca.key")
|
|
|
|
ca_cert_filename = get_test_ca_cert_file()
|
2019-06-10 12:41:10 -04:00
|
|
|
subprocess.check_call(
|
2019-06-10 11:06:25 -04:00
|
|
|
[
|
|
|
|
"openssl",
|
|
|
|
"x509",
|
|
|
|
"-req",
|
|
|
|
"-in",
|
|
|
|
csr_filename,
|
|
|
|
"-CA",
|
|
|
|
ca_cert_filename,
|
|
|
|
"-CAkey",
|
|
|
|
ca_key_filename,
|
|
|
|
"-set_serial",
|
|
|
|
"1",
|
|
|
|
"-extfile",
|
|
|
|
cnf_filename,
|
|
|
|
"-out",
|
|
|
|
cert_filename,
|
2019-06-10 12:41:10 -04:00
|
|
|
]
|
2019-06-10 11:06:25 -04:00
|
|
|
)
|
2019-01-29 08:53:02 -05:00
|
|
|
|
2019-06-10 11:06:25 -04:00
|
|
|
return cert_filename
|
2019-01-29 08:53:02 -05:00
|
|
|
|
|
|
|
|
2019-06-10 11:06:25 -04:00
|
|
|
@implementer(IOpenSSLServerConnectionCreator)
|
|
|
|
class TestServerTLSConnectionFactory(object):
|
|
|
|
"""An SSL connection creator which returns connections which present a certificate
|
|
|
|
signed by our test CA."""
|
2019-05-10 01:12:11 -04:00
|
|
|
|
2019-06-10 11:06:25 -04:00
|
|
|
def __init__(self, sanlist):
|
|
|
|
"""
|
|
|
|
Args:
|
|
|
|
sanlist: list[bytes]: a list of subjectAltName values for the cert
|
|
|
|
"""
|
|
|
|
self._cert_file = create_test_cert_file(sanlist)
|
2019-01-29 08:53:02 -05:00
|
|
|
|
2019-06-10 11:06:25 -04:00
|
|
|
def serverConnectionForTLS(self, tlsProtocol):
|
2019-01-29 08:53:02 -05:00
|
|
|
ctx = SSL.Context(SSL.TLSv1_METHOD)
|
2019-06-10 11:06:25 -04:00
|
|
|
ctx.use_certificate_file(self._cert_file)
|
|
|
|
ctx.use_privatekey_file(get_test_key_file())
|
|
|
|
return Connection(ctx, None)
|