mirror of
https://github.com/certbot/certbot.git
synced 2026-05-28 04:34:11 -04:00
commit
d209b5ea20
39 changed files with 403 additions and 515 deletions
|
|
@ -187,7 +187,7 @@ class KeyAuthorizationChallenge(_TokenDVChallenge):
|
|||
key_authorization=self.key_authorization(account_key))
|
||||
|
||||
@abc.abstractmethod
|
||||
def validation(self, account_key):
|
||||
def validation(self, account_key, **kwargs):
|
||||
"""Generate validation for the challenge.
|
||||
|
||||
Subclasses must implement this method, but they are likely to
|
||||
|
|
@ -201,7 +201,7 @@ class KeyAuthorizationChallenge(_TokenDVChallenge):
|
|||
"""
|
||||
raise NotImplementedError() # pragma: no cover
|
||||
|
||||
def response_and_validation(self, account_key):
|
||||
def response_and_validation(self, account_key, *args, **kwargs):
|
||||
"""Generate response and validation.
|
||||
|
||||
Convenience function that return results of `response` and
|
||||
|
|
@ -211,7 +211,8 @@ class KeyAuthorizationChallenge(_TokenDVChallenge):
|
|||
:rtype: tuple
|
||||
|
||||
"""
|
||||
return (self.response(account_key), self.validation(account_key))
|
||||
return (self.response(account_key),
|
||||
self.validation(account_key, *args, **kwargs))
|
||||
|
||||
|
||||
@ChallengeResponse.register
|
||||
|
|
@ -220,6 +221,12 @@ class HTTP01Response(KeyAuthorizationChallengeResponse):
|
|||
typ = "http-01"
|
||||
|
||||
PORT = 80
|
||||
"""Verification port as defined by the protocol.
|
||||
|
||||
You can override it (e.g. for testing) by passing ``port`` to
|
||||
`simple_verify`.
|
||||
|
||||
"""
|
||||
|
||||
def simple_verify(self, chall, domain, account_public_key, port=None):
|
||||
"""Simple verify.
|
||||
|
|
@ -246,7 +253,7 @@ class HTTP01Response(KeyAuthorizationChallengeResponse):
|
|||
# request URI, if it's standard.
|
||||
if port is not None and port != self.PORT:
|
||||
logger.warning(
|
||||
"Using non-standard port for SimpleHTTP verification: %s", port)
|
||||
"Using non-standard port for http-01 verification: %s", port)
|
||||
domain += ":{0}".format(port)
|
||||
|
||||
uri = chall.uri(domain)
|
||||
|
|
@ -308,7 +315,7 @@ class HTTP01(KeyAuthorizationChallenge):
|
|||
"""
|
||||
return "http://" + domain + self.path
|
||||
|
||||
def validation(self, account_key):
|
||||
def validation(self, account_key, **unused_kwargs):
|
||||
"""Generate validation.
|
||||
|
||||
:param JWK account_key:
|
||||
|
|
@ -318,89 +325,50 @@ class HTTP01(KeyAuthorizationChallenge):
|
|||
return self.key_authorization(account_key)
|
||||
|
||||
|
||||
@Challenge.register # pylint: disable=too-many-ancestors
|
||||
class DVSNI(_TokenDVChallenge):
|
||||
"""ACME "dvsni" challenge.
|
||||
|
||||
:ivar bytes token: Random data, **not** base64-encoded.
|
||||
|
||||
"""
|
||||
typ = "dvsni"
|
||||
|
||||
PORT = 443
|
||||
"""Port to perform DVSNI challenge."""
|
||||
|
||||
def gen_response(self, account_key, alg=jose.RS256, **kwargs):
|
||||
"""Generate response.
|
||||
|
||||
:param .JWK account_key: Private account key.
|
||||
:rtype: .DVSNIResponse
|
||||
|
||||
"""
|
||||
return DVSNIResponse(validation=jose.JWS.sign(
|
||||
payload=self.json_dumps(sort_keys=True).encode('utf-8'),
|
||||
key=account_key, alg=alg, **kwargs))
|
||||
|
||||
|
||||
@ChallengeResponse.register
|
||||
class DVSNIResponse(ChallengeResponse):
|
||||
"""ACME "dvsni" challenge response.
|
||||
|
||||
:param bytes s: Random data, **not** base64-encoded.
|
||||
|
||||
"""
|
||||
typ = "dvsni"
|
||||
class TLSSNI01Response(KeyAuthorizationChallengeResponse):
|
||||
"""ACME tls-sni-01 challenge response."""
|
||||
typ = "tls-sni-01"
|
||||
|
||||
DOMAIN_SUFFIX = b".acme.invalid"
|
||||
"""Domain name suffix."""
|
||||
|
||||
PORT = DVSNI.PORT
|
||||
"""Port to perform DVSNI challenge."""
|
||||
PORT = 443
|
||||
"""Verification port as defined by the protocol.
|
||||
|
||||
validation = jose.Field("validation", decoder=jose.JWS.from_json)
|
||||
You can override it (e.g. for testing) by passing ``port`` to
|
||||
`simple_verify`.
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def z(self): # pylint: disable=invalid-name
|
||||
"""The ``z`` parameter.
|
||||
def z(self):
|
||||
"""``z`` value used for verification.
|
||||
|
||||
:rtype: bytes
|
||||
:rtype bytes:
|
||||
|
||||
"""
|
||||
# Instance of 'Field' has no 'signature' member
|
||||
# pylint: disable=no-member
|
||||
return hashlib.sha256(self.validation.signature.encode(
|
||||
"signature").encode("utf-8")).hexdigest().encode()
|
||||
return hashlib.sha256(
|
||||
self.key_authorization.encode("utf-8")).hexdigest().lower().encode()
|
||||
|
||||
@property
|
||||
def z_domain(self):
|
||||
"""Domain name for certificate subjectAltName.
|
||||
"""Domain name used for verification, generated from `z`.
|
||||
|
||||
:rtype: bytes
|
||||
:rtype bytes:
|
||||
|
||||
"""
|
||||
z = self.z # pylint: disable=invalid-name
|
||||
return z[:32] + b'.' + z[32:] + self.DOMAIN_SUFFIX
|
||||
|
||||
@property
|
||||
def chall(self):
|
||||
"""Get challenge encoded in the `validation` payload.
|
||||
|
||||
:rtype: challenges.DVSNI
|
||||
|
||||
"""
|
||||
# pylint: disable=no-member
|
||||
return DVSNI.json_loads(self.validation.payload.decode('utf-8'))
|
||||
return self.z[:32] + b'.' + self.z[32:] + self.DOMAIN_SUFFIX
|
||||
|
||||
def gen_cert(self, key=None, bits=2048):
|
||||
"""Generate DVSNI certificate.
|
||||
"""Generate tls-sni-01 certificate.
|
||||
|
||||
:param OpenSSL.crypto.PKey key: Optional private key used in
|
||||
certificate generation. If not provided (``None``), then
|
||||
fresh key will be generated.
|
||||
:param int bits: Number of bits for newly generated key.
|
||||
|
||||
:rtype: `tuple` of `OpenSSL.crypto.X509` and
|
||||
`OpenSSL.crypto.PKey`
|
||||
:rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`
|
||||
|
||||
"""
|
||||
if key is None:
|
||||
|
|
@ -411,11 +379,12 @@ class DVSNIResponse(ChallengeResponse):
|
|||
'dummy', self.z_domain.decode()], force_san=True), key
|
||||
|
||||
def probe_cert(self, domain, **kwargs):
|
||||
"""Probe DVSNI challenge certificate.
|
||||
"""Probe tls-sni-01 challenge certificate.
|
||||
|
||||
:param unicode domain:
|
||||
|
||||
"""
|
||||
# TODO: domain is not necessary if host is provided
|
||||
if "host" not in kwargs:
|
||||
host = socket.gethostbyname(domain)
|
||||
logging.debug('%s resolved to %s', domain, host)
|
||||
|
|
@ -428,7 +397,7 @@ class DVSNIResponse(ChallengeResponse):
|
|||
return crypto_util.probe_sni(**kwargs)
|
||||
|
||||
def verify_cert(self, cert):
|
||||
"""Verify DVSNI challenge certificate."""
|
||||
"""Verify tls-sni-01 challenge certificate."""
|
||||
# pylint: disable=protected-access
|
||||
sans = crypto_util._pyopenssl_cert_or_req_san(cert)
|
||||
logging.debug('Certificate %s. SANs: %s', cert.digest('sha1'), sans)
|
||||
|
|
@ -439,14 +408,15 @@ class DVSNIResponse(ChallengeResponse):
|
|||
"""Simple verify.
|
||||
|
||||
Verify ``validation`` using ``account_public_key``, optionally
|
||||
probe DVSNI certificate and check using `verify_cert`.
|
||||
probe tls-sni-01 certificate and check using `verify_cert`.
|
||||
|
||||
:param .challenges.DVSNI chall: Corresponding challenge.
|
||||
:param .challenges.TLSSNI01 chall: Corresponding challenge.
|
||||
:param str domain: Domain name being validated.
|
||||
:param JWK account_public_key:
|
||||
:param OpenSSL.crypto.X509 cert: Optional certificate. If not
|
||||
provided (``None``) certificate will be retrieved using
|
||||
`probe_cert`.
|
||||
:param int port: Port used to probe the certificate.
|
||||
|
||||
|
||||
:returns: ``True`` iff client's control of the domain has been
|
||||
|
|
@ -454,20 +424,8 @@ class DVSNIResponse(ChallengeResponse):
|
|||
:rtype: bool
|
||||
|
||||
"""
|
||||
# pylint: disable=no-member
|
||||
if not self.validation.verify(key=account_public_key):
|
||||
return False
|
||||
|
||||
# TODO: it's not checked that payload has exectly 2 fields!
|
||||
try:
|
||||
decoded_chall = self.chall
|
||||
except jose.DeserializationError as error:
|
||||
logger.debug(error, exc_info=True)
|
||||
return False
|
||||
|
||||
if decoded_chall.token != chall.token:
|
||||
logger.debug("Wrong token: expected %r, found %r",
|
||||
chall.token, decoded_chall.token)
|
||||
if not self.verify(chall, account_public_key):
|
||||
logger.debug("Verification of key authorization in response failed")
|
||||
return False
|
||||
|
||||
if cert is None:
|
||||
|
|
@ -480,6 +438,29 @@ class DVSNIResponse(ChallengeResponse):
|
|||
return self.verify_cert(cert)
|
||||
|
||||
|
||||
@Challenge.register # pylint: disable=too-many-ancestors
|
||||
class TLSSNI01(KeyAuthorizationChallenge):
|
||||
"""ACME tls-sni-01 challenge."""
|
||||
response_cls = TLSSNI01Response
|
||||
typ = response_cls.typ
|
||||
|
||||
# boulder#962, ietf-wg-acme#22
|
||||
#n = jose.Field("n", encoder=int, decoder=int)
|
||||
|
||||
def validation(self, account_key, **kwargs):
|
||||
"""Generate validation.
|
||||
|
||||
:param JWK account_key:
|
||||
:param OpenSSL.crypto.PKey cert_key: Optional private key used
|
||||
in certificate generation. If not provided (``None``), then
|
||||
fresh key will be generated.
|
||||
|
||||
:rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`
|
||||
|
||||
"""
|
||||
return self.response(account_key).gen_cert(key=kwargs.get('cert_key'))
|
||||
|
||||
|
||||
@Challenge.register
|
||||
class RecoveryContact(ContinuityChallenge):
|
||||
"""ACME "recoveryContact" challenge.
|
||||
|
|
|
|||
|
|
@ -186,14 +186,112 @@ class HTTP01Test(unittest.TestCase):
|
|||
self.msg.update(token=b'..').good_token)
|
||||
|
||||
|
||||
class DVSNITest(unittest.TestCase):
|
||||
class TLSSNI01ResponseTest(unittest.TestCase):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
|
||||
def setUp(self):
|
||||
from acme.challenges import DVSNI
|
||||
self.msg = DVSNI(
|
||||
from acme.challenges import TLSSNI01
|
||||
self.chall = TLSSNI01(
|
||||
token=jose.b64decode(b'a82d5ff8ef740d12881f6d3c2277ab2e'))
|
||||
|
||||
self.response = self.chall.response(KEY)
|
||||
self.jmsg = {
|
||||
'resource': 'challenge',
|
||||
'type': 'tls-sni-01',
|
||||
'keyAuthorization': self.response.key_authorization,
|
||||
}
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
label1 = b'dc38d9c3fa1a4fdcc3a5501f2d38583f'
|
||||
label2 = b'b7793728f084394f2a1afd459556bb5c'
|
||||
self.z = label1 + label2
|
||||
self.z_domain = label1 + b'.' + label2 + b'.acme.invalid'
|
||||
self.domain = 'foo.com'
|
||||
|
||||
def test_z_and_domain(self):
|
||||
self.assertEqual(self.z, self.response.z)
|
||||
self.assertEqual(self.z_domain, self.response.z_domain)
|
||||
|
||||
def test_to_partial_json(self):
|
||||
self.assertEqual(self.jmsg, self.response.to_partial_json())
|
||||
|
||||
def test_from_json(self):
|
||||
from acme.challenges import TLSSNI01Response
|
||||
self.assertEqual(self.response, TLSSNI01Response.from_json(self.jmsg))
|
||||
|
||||
def test_from_json_hashable(self):
|
||||
from acme.challenges import TLSSNI01Response
|
||||
hash(TLSSNI01Response.from_json(self.jmsg))
|
||||
|
||||
@mock.patch('acme.challenges.socket.gethostbyname')
|
||||
@mock.patch('acme.challenges.crypto_util.probe_sni')
|
||||
def test_probe_cert(self, mock_probe_sni, mock_gethostbyname):
|
||||
mock_gethostbyname.return_value = '127.0.0.1'
|
||||
self.response.probe_cert('foo.com')
|
||||
mock_gethostbyname.assert_called_once_with('foo.com')
|
||||
mock_probe_sni.assert_called_once_with(
|
||||
host='127.0.0.1', port=self.response.PORT,
|
||||
name=self.z_domain)
|
||||
|
||||
self.response.probe_cert('foo.com', host='8.8.8.8')
|
||||
mock_probe_sni.assert_called_with(
|
||||
host='8.8.8.8', port=mock.ANY, name=mock.ANY)
|
||||
|
||||
self.response.probe_cert('foo.com', port=1234)
|
||||
mock_probe_sni.assert_called_with(
|
||||
host=mock.ANY, port=1234, name=mock.ANY)
|
||||
|
||||
self.response.probe_cert('foo.com', bar='baz')
|
||||
mock_probe_sni.assert_called_with(
|
||||
host=mock.ANY, port=mock.ANY, name=mock.ANY, bar='baz')
|
||||
|
||||
self.response.probe_cert('foo.com', name=b'xxx')
|
||||
mock_probe_sni.assert_called_with(
|
||||
host=mock.ANY, port=mock.ANY,
|
||||
name=self.z_domain)
|
||||
|
||||
def test_gen_verify_cert(self):
|
||||
key1 = test_util.load_pyopenssl_private_key('rsa512_key.pem')
|
||||
cert, key2 = self.response.gen_cert(key1)
|
||||
self.assertEqual(key1, key2)
|
||||
self.assertTrue(self.response.verify_cert(cert))
|
||||
|
||||
def test_gen_verify_cert_gen_key(self):
|
||||
cert, key = self.response.gen_cert()
|
||||
self.assertTrue(isinstance(key, OpenSSL.crypto.PKey))
|
||||
self.assertTrue(self.response.verify_cert(cert))
|
||||
|
||||
def test_verify_bad_cert(self):
|
||||
self.assertFalse(self.response.verify_cert(
|
||||
test_util.load_cert('cert.pem')))
|
||||
|
||||
def test_simple_verify_bad_key_authorization(self):
|
||||
key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem'))
|
||||
self.response.simple_verify(self.chall, "local", key2.public_key())
|
||||
|
||||
@mock.patch('acme.challenges.TLSSNI01Response.verify_cert', autospec=True)
|
||||
def test_simple_verify(self, mock_verify_cert):
|
||||
mock_verify_cert.return_value = mock.sentinel.verification
|
||||
self.assertEqual(mock.sentinel.verification, self.response.simple_verify(
|
||||
self.chall, self.domain, KEY.public_key(),
|
||||
cert=mock.sentinel.cert))
|
||||
mock_verify_cert.assert_called_once_with(self.response, mock.sentinel.cert)
|
||||
|
||||
@mock.patch('acme.challenges.TLSSNI01Response.probe_cert')
|
||||
def test_simple_verify_false_on_probe_error(self, mock_probe_cert):
|
||||
mock_probe_cert.side_effect = errors.Error
|
||||
self.assertFalse(self.response.simple_verify(
|
||||
self.chall, self.domain, KEY.public_key()))
|
||||
|
||||
|
||||
class TLSSNI01Test(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
from acme.challenges import TLSSNI01
|
||||
self.msg = TLSSNI01(
|
||||
token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e'))
|
||||
self.jmsg = {
|
||||
'type': 'dvsni',
|
||||
'type': 'tls-sni-01',
|
||||
'token': 'a82d5ff8ef740d12881f6d3c2277ab2e',
|
||||
}
|
||||
|
||||
|
|
@ -201,144 +299,25 @@ class DVSNITest(unittest.TestCase):
|
|||
self.assertEqual(self.jmsg, self.msg.to_partial_json())
|
||||
|
||||
def test_from_json(self):
|
||||
from acme.challenges import DVSNI
|
||||
self.assertEqual(self.msg, DVSNI.from_json(self.jmsg))
|
||||
from acme.challenges import TLSSNI01
|
||||
self.assertEqual(self.msg, TLSSNI01.from_json(self.jmsg))
|
||||
|
||||
def test_from_json_hashable(self):
|
||||
from acme.challenges import DVSNI
|
||||
hash(DVSNI.from_json(self.jmsg))
|
||||
from acme.challenges import TLSSNI01
|
||||
hash(TLSSNI01.from_json(self.jmsg))
|
||||
|
||||
def test_from_json_invalid_token_length(self):
|
||||
from acme.challenges import DVSNI
|
||||
from acme.challenges import TLSSNI01
|
||||
self.jmsg['token'] = jose.encode_b64jose(b'abcd')
|
||||
self.assertRaises(
|
||||
jose.DeserializationError, DVSNI.from_json, self.jmsg)
|
||||
jose.DeserializationError, TLSSNI01.from_json, self.jmsg)
|
||||
|
||||
def test_gen_response(self):
|
||||
from acme.challenges import DVSNI
|
||||
self.assertEqual(self.msg, DVSNI.json_loads(
|
||||
self.msg.gen_response(KEY).validation.payload.decode()))
|
||||
|
||||
|
||||
class DVSNIResponseTest(unittest.TestCase):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
|
||||
def setUp(self):
|
||||
from acme.challenges import DVSNI
|
||||
self.chall = DVSNI(
|
||||
token=jose.b64decode(b'a82d5ff8ef740d12881f6d3c2277ab2e'))
|
||||
|
||||
from acme.challenges import DVSNIResponse
|
||||
self.validation = jose.JWS.sign(
|
||||
payload=self.chall.json_dumps(sort_keys=True).encode(),
|
||||
key=KEY, alg=jose.RS256)
|
||||
self.msg = DVSNIResponse(validation=self.validation)
|
||||
self.jmsg_to = {
|
||||
'resource': 'challenge',
|
||||
'type': 'dvsni',
|
||||
'validation': self.validation,
|
||||
}
|
||||
self.jmsg_from = {
|
||||
'resource': 'challenge',
|
||||
'type': 'dvsni',
|
||||
'validation': self.validation.to_json(),
|
||||
}
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
label1 = b'e2df3498860637c667fedadc5a8494ec'
|
||||
label2 = b'09dcc75553c9b3bd73662b50e71b1e42'
|
||||
self.z = label1 + label2
|
||||
self.z_domain = label1 + b'.' + label2 + b'.acme.invalid'
|
||||
self.domain = 'foo.com'
|
||||
|
||||
def test_z_and_domain(self):
|
||||
self.assertEqual(self.z, self.msg.z)
|
||||
self.assertEqual(self.z_domain, self.msg.z_domain)
|
||||
|
||||
def test_to_partial_json(self):
|
||||
self.assertEqual(self.jmsg_to, self.msg.to_partial_json())
|
||||
|
||||
def test_from_json(self):
|
||||
from acme.challenges import DVSNIResponse
|
||||
self.assertEqual(self.msg, DVSNIResponse.from_json(self.jmsg_from))
|
||||
|
||||
def test_from_json_hashable(self):
|
||||
from acme.challenges import DVSNIResponse
|
||||
hash(DVSNIResponse.from_json(self.jmsg_from))
|
||||
|
||||
@mock.patch('acme.challenges.socket.gethostbyname')
|
||||
@mock.patch('acme.challenges.crypto_util.probe_sni')
|
||||
def test_probe_cert(self, mock_probe_sni, mock_gethostbyname):
|
||||
mock_gethostbyname.return_value = '127.0.0.1'
|
||||
self.msg.probe_cert('foo.com')
|
||||
mock_gethostbyname.assert_called_once_with('foo.com')
|
||||
mock_probe_sni.assert_called_once_with(
|
||||
host='127.0.0.1', port=self.msg.PORT,
|
||||
name=self.z_domain)
|
||||
|
||||
self.msg.probe_cert('foo.com', host='8.8.8.8')
|
||||
mock_probe_sni.assert_called_with(
|
||||
host='8.8.8.8', port=mock.ANY, name=mock.ANY)
|
||||
|
||||
self.msg.probe_cert('foo.com', port=1234)
|
||||
mock_probe_sni.assert_called_with(
|
||||
host=mock.ANY, port=1234, name=mock.ANY)
|
||||
|
||||
self.msg.probe_cert('foo.com', bar='baz')
|
||||
mock_probe_sni.assert_called_with(
|
||||
host=mock.ANY, port=mock.ANY, name=mock.ANY, bar='baz')
|
||||
|
||||
self.msg.probe_cert('foo.com', name=b'xxx')
|
||||
mock_probe_sni.assert_called_with(
|
||||
host=mock.ANY, port=mock.ANY,
|
||||
name=self.z_domain)
|
||||
|
||||
def test_gen_verify_cert(self):
|
||||
key1 = test_util.load_pyopenssl_private_key('rsa512_key.pem')
|
||||
cert, key2 = self.msg.gen_cert(key1)
|
||||
self.assertEqual(key1, key2)
|
||||
self.assertTrue(self.msg.verify_cert(cert))
|
||||
|
||||
def test_gen_verify_cert_gen_key(self):
|
||||
cert, key = self.msg.gen_cert()
|
||||
self.assertTrue(isinstance(key, OpenSSL.crypto.PKey))
|
||||
self.assertTrue(self.msg.verify_cert(cert))
|
||||
|
||||
def test_verify_bad_cert(self):
|
||||
self.assertFalse(self.msg.verify_cert(test_util.load_cert('cert.pem')))
|
||||
|
||||
def test_simple_verify_wrong_account_key(self):
|
||||
self.assertFalse(self.msg.simple_verify(
|
||||
self.chall, self.domain, jose.JWKRSA.load(
|
||||
test_util.load_vector('rsa256_key.pem')).public_key()))
|
||||
|
||||
def test_simple_verify_wrong_payload(self):
|
||||
for payload in b'', b'{}':
|
||||
msg = self.msg.update(validation=jose.JWS.sign(
|
||||
payload=payload, key=KEY, alg=jose.RS256))
|
||||
self.assertFalse(msg.simple_verify(
|
||||
self.chall, self.domain, KEY.public_key()))
|
||||
|
||||
def test_simple_verify_wrong_token(self):
|
||||
msg = self.msg.update(validation=jose.JWS.sign(
|
||||
payload=self.chall.update(token=(b'b' * 20)).json_dumps().encode(),
|
||||
key=KEY, alg=jose.RS256))
|
||||
self.assertFalse(msg.simple_verify(
|
||||
self.chall, self.domain, KEY.public_key()))
|
||||
|
||||
@mock.patch('acme.challenges.DVSNIResponse.verify_cert', autospec=True)
|
||||
def test_simple_verify(self, mock_verify_cert):
|
||||
mock_verify_cert.return_value = mock.sentinel.verification
|
||||
self.assertEqual(mock.sentinel.verification, self.msg.simple_verify(
|
||||
self.chall, self.domain, KEY.public_key(),
|
||||
cert=mock.sentinel.cert))
|
||||
mock_verify_cert.assert_called_once_with(self.msg, mock.sentinel.cert)
|
||||
|
||||
@mock.patch('acme.challenges.DVSNIResponse.probe_cert')
|
||||
def test_simple_verify_false_on_probe_error(self, mock_probe_cert):
|
||||
mock_probe_cert.side_effect = errors.Error
|
||||
self.assertFalse(self.msg.simple_verify(
|
||||
self.chall, self.domain, KEY.public_key()))
|
||||
@mock.patch('acme.challenges.TLSSNI01Response.gen_cert')
|
||||
def test_validation(self, mock_gen_cert):
|
||||
mock_gen_cert.return_value = ('cert', 'key')
|
||||
self.assertEqual(('cert', 'key'), self.msg.validation(
|
||||
KEY, cert_key=mock.sentinel.cert_key))
|
||||
mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key)
|
||||
|
||||
|
||||
class RecoveryContactTest(unittest.TestCase):
|
||||
|
|
@ -571,8 +550,6 @@ class ProofOfPossessionResponseTest(unittest.TestCase):
|
|||
class DNSTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.account_key = jose.JWKRSA.load(
|
||||
test_util.load_vector('rsa512_key.pem'))
|
||||
from acme.challenges import DNS
|
||||
self.msg = DNS(token=jose.b64decode(
|
||||
b'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA'))
|
||||
|
|
@ -594,34 +571,33 @@ class DNSTest(unittest.TestCase):
|
|||
|
||||
def test_gen_check_validation(self):
|
||||
self.assertTrue(self.msg.check_validation(
|
||||
self.msg.gen_validation(self.account_key),
|
||||
self.account_key.public_key()))
|
||||
self.msg.gen_validation(KEY), KEY.public_key()))
|
||||
|
||||
def test_gen_check_validation_wrong_key(self):
|
||||
key2 = jose.JWKRSA.load(test_util.load_vector('rsa1024_key.pem'))
|
||||
self.assertFalse(self.msg.check_validation(
|
||||
self.msg.gen_validation(self.account_key), key2.public_key()))
|
||||
self.msg.gen_validation(KEY), key2.public_key()))
|
||||
|
||||
def test_check_validation_wrong_payload(self):
|
||||
validations = tuple(
|
||||
jose.JWS.sign(payload=payload, alg=jose.RS256, key=self.account_key)
|
||||
jose.JWS.sign(payload=payload, alg=jose.RS256, key=KEY)
|
||||
for payload in (b'', b'{}')
|
||||
)
|
||||
for validation in validations:
|
||||
self.assertFalse(self.msg.check_validation(
|
||||
validation, self.account_key.public_key()))
|
||||
validation, KEY.public_key()))
|
||||
|
||||
def test_check_validation_wrong_fields(self):
|
||||
bad_validation = jose.JWS.sign(
|
||||
payload=self.msg.update(token=b'x' * 20).json_dumps().encode('utf-8'),
|
||||
alg=jose.RS256, key=self.account_key)
|
||||
alg=jose.RS256, key=KEY)
|
||||
self.assertFalse(self.msg.check_validation(
|
||||
bad_validation, self.account_key.public_key()))
|
||||
bad_validation, KEY.public_key()))
|
||||
|
||||
def test_gen_response(self):
|
||||
with mock.patch('acme.challenges.DNS.gen_validation') as mock_gen:
|
||||
mock_gen.return_value = mock.sentinel.validation
|
||||
response = self.msg.gen_response(self.account_key)
|
||||
response = self.msg.gen_response(KEY)
|
||||
from acme.challenges import DNSResponse
|
||||
self.assertTrue(isinstance(response, DNSResponse))
|
||||
self.assertEqual(response.validation, mock.sentinel.validation)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from acme import errors
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# DVSNI certificate serving and probing is not affected by SSL
|
||||
# TLSSNI01 certificate serving and probing is not affected by SSL
|
||||
# vulnerabilities: prober needs to check certificate for expected
|
||||
# contents anyway. Working SNI is the only thing that's necessary for
|
||||
# the challenge and thus scoping down SSL/TLS method (version) would
|
||||
|
|
@ -23,7 +23,7 @@ logger = logging.getLogger(__name__)
|
|||
# https://www.openssl.org/docs/ssl/SSLv23_method.html). _serve_sni
|
||||
# should be changed to use "set_options" to disable SSLv2 and SSLv3,
|
||||
# in case it's used for things other than probing/serving!
|
||||
_DEFAULT_DVSNI_SSL_METHOD = OpenSSL.SSL.SSLv23_METHOD
|
||||
_DEFAULT_TLSSNI01_SSL_METHOD = OpenSSL.SSL.SSLv23_METHOD
|
||||
|
||||
|
||||
class SSLSocket(object): # pylint: disable=too-few-public-methods
|
||||
|
|
@ -35,7 +35,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
|
|||
:ivar method: See `OpenSSL.SSL.Context` for allowed values.
|
||||
|
||||
"""
|
||||
def __init__(self, sock, certs, method=_DEFAULT_DVSNI_SSL_METHOD):
|
||||
def __init__(self, sock, certs, method=_DEFAULT_TLSSNI01_SSL_METHOD):
|
||||
self.sock = sock
|
||||
self.certs = certs
|
||||
self.method = method
|
||||
|
|
@ -103,7 +103,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
|
|||
|
||||
|
||||
def probe_sni(name, host, port=443, timeout=300,
|
||||
method=_DEFAULT_DVSNI_SSL_METHOD, source_address=('0', 0)):
|
||||
method=_DEFAULT_TLSSNI01_SSL_METHOD, source_address=('0', 0)):
|
||||
"""Probe SNI server for SSL certificate.
|
||||
|
||||
:param bytes name: Byte string to send as the server name in the
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class TLSServer(socketserver.TCPServer):
|
|||
self.certs = kwargs.pop("certs", {})
|
||||
self.method = kwargs.pop(
|
||||
# pylint: disable=protected-access
|
||||
"method", crypto_util._DEFAULT_DVSNI_SSL_METHOD)
|
||||
"method", crypto_util._DEFAULT_TLSSNI01_SSL_METHOD)
|
||||
self.allow_reuse_address = kwargs.pop("allow_reuse_address", True)
|
||||
socketserver.TCPServer.__init__(self, *args, **kwargs)
|
||||
|
||||
|
|
@ -50,8 +50,8 @@ class ACMEServerMixin: # pylint: disable=old-style-class
|
|||
allow_reuse_address = True
|
||||
|
||||
|
||||
class DVSNIServer(TLSServer, ACMEServerMixin):
|
||||
"""DVSNI Server."""
|
||||
class TLSSNI01Server(TLSServer, ACMEServerMixin):
|
||||
"""TLSSNI01 Server."""
|
||||
|
||||
def __init__(self, server_address, certs):
|
||||
TLSServer.__init__(
|
||||
|
|
@ -134,8 +134,8 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||
cls, simple_http_resources=simple_http_resources)
|
||||
|
||||
|
||||
def simple_dvsni_server(cli_args, forever=True):
|
||||
"""Run simple standalone DVSNI server."""
|
||||
def simple_tls_sni_01_server(cli_args, forever=True):
|
||||
"""Run simple standalone TLSSNI01 server."""
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
|
@ -158,7 +158,7 @@ def simple_dvsni_server(cli_args, forever=True):
|
|||
OpenSSL.crypto.load_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, cert_contents))
|
||||
|
||||
server = DVSNIServer(('', int(args.port)), certs=certs)
|
||||
server = TLSSNI01Server(('', int(args.port)), certs=certs)
|
||||
six.print_("Serving at https://localhost:{0}...".format(
|
||||
server.socket.getsockname()[1]))
|
||||
if forever: # pragma: no cover
|
||||
|
|
@ -168,4 +168,4 @@ def simple_dvsni_server(cli_args, forever=True):
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(simple_dvsni_server(sys.argv)) # pragma: no cover
|
||||
sys.exit(simple_tls_sni_01_server(sys.argv)) # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ class TLSServerTest(unittest.TestCase):
|
|||
server.server_close() # pylint: disable=no-member
|
||||
|
||||
|
||||
class DVSNIServerTest(unittest.TestCase):
|
||||
"""Test for acme.standalone.DVSNIServer."""
|
||||
class TLSSNI01ServerTest(unittest.TestCase):
|
||||
"""Test for acme.standalone.TLSSNI01Server."""
|
||||
|
||||
def setUp(self):
|
||||
self.certs = {
|
||||
|
|
@ -37,8 +37,8 @@ class DVSNIServerTest(unittest.TestCase):
|
|||
# pylint: disable=protected-access
|
||||
test_util.load_cert('cert.pem')._wrapped),
|
||||
}
|
||||
from acme.standalone import DVSNIServer
|
||||
self.server = DVSNIServer(("", 0), certs=self.certs)
|
||||
from acme.standalone import TLSSNI01Server
|
||||
self.server = TLSSNI01Server(("", 0), certs=self.certs)
|
||||
# pylint: disable=no-member
|
||||
self.thread = threading.Thread(target=self.server.serve_forever)
|
||||
self.thread.start()
|
||||
|
|
@ -106,8 +106,8 @@ class HTTP01ServerTest(unittest.TestCase):
|
|||
self.assertFalse(self._test_http01(add=False))
|
||||
|
||||
|
||||
class TestSimpleDVSNIServer(unittest.TestCase):
|
||||
"""Tests for acme.standalone.simple_dvsni_server."""
|
||||
class TestSimpleTLSSNI01Server(unittest.TestCase):
|
||||
"""Tests for acme.standalone.simple_tls_sni_01_server."""
|
||||
|
||||
def setUp(self):
|
||||
# mirror ../examples/standalone
|
||||
|
|
@ -118,12 +118,14 @@ class TestSimpleDVSNIServer(unittest.TestCase):
|
|||
shutil.copy(test_util.vector_path('rsa512_key.pem'),
|
||||
os.path.join(localhost_dir, 'key.pem'))
|
||||
|
||||
from acme.standalone import simple_dvsni_server
|
||||
from acme.standalone import simple_tls_sni_01_server
|
||||
self.port = 1234
|
||||
self.thread = threading.Thread(target=simple_dvsni_server, kwargs={
|
||||
'cli_args': ('xxx', '--port', str(self.port)),
|
||||
'forever': False,
|
||||
})
|
||||
self.thread = threading.Thread(
|
||||
target=simple_tls_sni_01_server, kwargs={
|
||||
'cli_args': ('xxx', '--port', str(self.port)),
|
||||
'forever': False,
|
||||
},
|
||||
)
|
||||
self.old_cwd = os.getcwd()
|
||||
os.chdir(self.test_cwd)
|
||||
self.thread.start()
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ certificate for some domain name by solving challenges received from
|
|||
the ACME server. From the protocol, there are essentially two
|
||||
different types of challenges. Challenges that must be solved by
|
||||
individual plugins in order to satisfy domain validation (subclasses
|
||||
of `~.DVChallenge`, i.e. `~.challenges.DVSNI`,
|
||||
of `~.DVChallenge`, i.e. `~.challenges.TLSSNI01`,
|
||||
`~.challenges.HTTP01`, `~.challenges.DNS`) and continuity specific
|
||||
challenges (subclasses of `~.ContinuityChallenge`,
|
||||
i.e. `~.challenges.RecoveryToken`, `~.challenges.RecoveryContact`,
|
||||
|
|
@ -160,7 +160,7 @@ always handled by the `~.ContinuityAuthenticator`, while plugins are
|
|||
expected to handle `~.DVChallenge` types.
|
||||
Right now, we have two authenticator plugins, the `~.ApacheConfigurator`
|
||||
and the `~.StandaloneAuthenticator`. The Standalone and Apache
|
||||
authenticators only solve the `~.challenges.DVSNI` challenge currently.
|
||||
authenticators only solve the `~.challenges.TLSSNI01` challenge currently.
|
||||
(You can set which challenges your authenticator can handle through the
|
||||
:meth:`~.IAuthenticator.get_chall_pref`.
|
||||
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ Plugin A I Notes and status
|
|||
========== = = ================================================================
|
||||
standalone Y N Very stable. Uses port 80 (force by
|
||||
``--standalone-supported-challenges http-01``) or 443
|
||||
(force by ``--standalone-supported-challenges dvsni``).
|
||||
(force by ``--standalone-supported-challenges tls-sni-01``).
|
||||
apache Y Y Alpha. Automates Apache installation, works fairly well but on
|
||||
Debian-based distributions only for now.
|
||||
webroot Y N Works with already running webserver, by writing necessary files
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ server = https://acme-staging.api.letsencrypt.org/directory
|
|||
|
||||
# Uncomment to use the standalone authenticator on port 443
|
||||
# authenticator = standalone
|
||||
# standalone-supported-challenges = dvsni
|
||||
# standalone-supported-challenges = tls-sni-01
|
||||
|
||||
# Uncomment to use the webroot authenticator. Replace webroot-path with the
|
||||
# path to the public_html / webroot folder being served by your web server.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import zope.interface
|
|||
|
||||
from acme import challenges
|
||||
|
||||
from letsencrypt import achallenges
|
||||
from letsencrypt import errors
|
||||
from letsencrypt import interfaces
|
||||
from letsencrypt import le_util
|
||||
|
|
@ -1117,7 +1116,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
###########################################################################
|
||||
def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use
|
||||
"""Return list of challenge preferences."""
|
||||
return [challenges.DVSNI]
|
||||
return [challenges.TLSSNI01]
|
||||
|
||||
def perform(self, achalls):
|
||||
"""Perform the configuration related challenge.
|
||||
|
|
@ -1132,11 +1131,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
apache_dvsni = dvsni.ApacheDvsni(self)
|
||||
|
||||
for i, achall in enumerate(achalls):
|
||||
if isinstance(achall, achallenges.DVSNI):
|
||||
# Currently also have dvsni hold associated index
|
||||
# of the challenge. This helps to put all of the responses back
|
||||
# together when they are all complete.
|
||||
apache_dvsni.add_chall(achall, i)
|
||||
# Currently also have dvsni hold associated index
|
||||
# of the challenge. This helps to put all of the responses back
|
||||
# together when they are all complete.
|
||||
apache_dvsni.add_chall(achall, i)
|
||||
|
||||
sni_response = apache_dvsni.perform()
|
||||
if sni_response:
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ from letsencrypt_apache import obj
|
|||
from letsencrypt_apache import parser
|
||||
|
||||
|
||||
class ApacheDvsni(common.Dvsni):
|
||||
class ApacheDvsni(common.TLSSNI01):
|
||||
"""Class performs DVSNI challenges within the Apache configurator.
|
||||
|
||||
:ivar configurator: ApacheConfigurator object
|
||||
:type configurator: :class:`~apache.configurator.ApacheConfigurator`
|
||||
|
||||
:ivar list achalls: Annotated :class:`~letsencrypt.achallenges.DVSNI`
|
||||
challenges.
|
||||
:ivar list achalls: Annotated tls-sni-01
|
||||
(`.KeyAuthorizationAnnotatedChallenge`) challenges.
|
||||
|
||||
:param list indices: Meant to hold indices of challenges in a
|
||||
larger array. ApacheDvsni is capable of solving many challenges
|
||||
|
|
@ -62,7 +62,7 @@ class ApacheDvsni(common.Dvsni):
|
|||
|
||||
# Prepare the server for HTTPS
|
||||
self.configurator.prepare_server_https(
|
||||
str(self.configurator.config.dvsni_port), True)
|
||||
str(self.configurator.config.tls_sni_01_port), True)
|
||||
|
||||
responses = []
|
||||
|
||||
|
|
@ -114,14 +114,15 @@ class ApacheDvsni(common.Dvsni):
|
|||
|
||||
# TODO: Checkout _default_ rules.
|
||||
dvsni_addrs = set()
|
||||
default_addr = obj.Addr(("*", str(self.configurator.config.dvsni_port)))
|
||||
default_addr = obj.Addr(("*", str(
|
||||
self.configurator.config.tls_sni_01_port)))
|
||||
|
||||
for addr in vhost.addrs:
|
||||
if "_default_" == addr.get_addr():
|
||||
dvsni_addrs.add(default_addr)
|
||||
else:
|
||||
dvsni_addrs.add(
|
||||
addr.get_sni_addr(self.configurator.config.dvsni_port))
|
||||
addr.get_sni_addr(self.configurator.config.tls_sni_01_port))
|
||||
|
||||
return dvsni_addrs
|
||||
|
||||
|
|
@ -144,8 +145,8 @@ class ApacheDvsni(common.Dvsni):
|
|||
def _get_config_text(self, achall, ip_addrs):
|
||||
"""Chocolate virtual server configuration text
|
||||
|
||||
:param achall: Annotated DVSNI challenge.
|
||||
:type achall: :class:`letsencrypt.achallenges.DVSNI`
|
||||
:param .KeyAuthorizationAnnotatedChallenge achall: Annotated
|
||||
DVSNI challenge.
|
||||
|
||||
:param list ip_addrs: addresses of challenged domain
|
||||
:class:`list` of type `~.obj.Addr`
|
||||
|
|
@ -164,7 +165,7 @@ class ApacheDvsni(common.Dvsni):
|
|||
# https://docs.python.org/2.7/reference/lexical_analysis.html
|
||||
return self.VHOST_TEMPLATE.format(
|
||||
vhost=ips,
|
||||
server_name=achall.gen_response(achall.account_key).z_domain,
|
||||
server_name=achall.response(achall.account_key).z_domain,
|
||||
ssl_options_conf_path=self.configurator.mod_ssl_conf,
|
||||
cert_path=self.get_cert_path(achall),
|
||||
key_path=self.get_key_path(achall),
|
||||
|
|
|
|||
|
|
@ -382,8 +382,8 @@ class TwoVhost80Test(util.ApacheTest):
|
|||
account_key, achall1, achall2 = self.get_achalls()
|
||||
|
||||
dvsni_ret_val = [
|
||||
achall1.gen_response(account_key),
|
||||
achall2.gen_response(account_key),
|
||||
achall1.response(account_key),
|
||||
achall2.response(account_key),
|
||||
]
|
||||
|
||||
mock_dvsni_perform.return_value = dvsni_ret_val
|
||||
|
|
@ -592,15 +592,15 @@ class TwoVhost80Test(util.ApacheTest):
|
|||
def get_achalls(self):
|
||||
"""Return testing achallenges."""
|
||||
account_key = self.rsa512jwk
|
||||
achall1 = achallenges.DVSNI(
|
||||
achall1 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.DVSNI(
|
||||
challenges.TLSSNI01(
|
||||
token="jIq_Xy1mXGN37tb4L6Xj_es58fW571ZNyXekdZzhh7Q"),
|
||||
"pending"),
|
||||
domain="encryption-example.demo", account_key=account_key)
|
||||
achall2 = achallenges.DVSNI(
|
||||
achall2 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.DVSNI(
|
||||
challenges.TLSSNI01(
|
||||
token="uqnaPzxtrndteOqtrXb0Asl5gOJfWAnnx6QJyvcmlDU"),
|
||||
"pending"),
|
||||
domain="letsencrypt.demo", account_key=account_key)
|
||||
|
|
|
|||
|
|
@ -13,15 +13,15 @@ from letsencrypt_apache.tests import util
|
|||
class DvsniPerformTest(util.ApacheTest):
|
||||
"""Test the ApacheDVSNI challenge."""
|
||||
|
||||
auth_key = common_test.DvsniTest.auth_key
|
||||
achalls = common_test.DvsniTest.achalls
|
||||
auth_key = common_test.TLSSNI01Test.auth_key
|
||||
achalls = common_test.TLSSNI01Test.achalls
|
||||
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super(DvsniPerformTest, self).setUp()
|
||||
|
||||
config = util.get_apache_configurator(
|
||||
self.config_path, self.config_dir, self.work_dir)
|
||||
config.config.dvsni_port = 443
|
||||
config.config.tls_sni_01_port = 443
|
||||
|
||||
from letsencrypt_apache import dvsni
|
||||
self.sni = dvsni.ApacheDvsni(config)
|
||||
|
|
@ -46,7 +46,7 @@ class DvsniPerformTest(util.ApacheTest):
|
|||
|
||||
achall = self.achalls[0]
|
||||
self.sni.add_chall(achall)
|
||||
response = self.achalls[0].gen_response(self.auth_key)
|
||||
response = self.achalls[0].response(self.auth_key)
|
||||
mock_setup_cert = mock.MagicMock(return_value=response)
|
||||
# pylint: disable=protected-access
|
||||
self.sni._setup_challenge_cert = mock_setup_cert
|
||||
|
|
@ -72,7 +72,7 @@ class DvsniPerformTest(util.ApacheTest):
|
|||
acme_responses = []
|
||||
for achall in self.achalls:
|
||||
self.sni.add_chall(achall)
|
||||
acme_responses.append(achall.gen_response(self.auth_key))
|
||||
acme_responses.append(achall.response(self.auth_key))
|
||||
|
||||
mock_setup_cert = mock.MagicMock(side_effect=acme_responses)
|
||||
# pylint: disable=protected-access
|
||||
|
|
@ -100,7 +100,7 @@ class DvsniPerformTest(util.ApacheTest):
|
|||
z_domains = []
|
||||
for achall in self.achalls:
|
||||
self.sni.add_chall(achall)
|
||||
z_domain = achall.gen_response(self.auth_key).z_domain
|
||||
z_domain = achall.response(self.auth_key).z_domain
|
||||
z_domains.append(set([z_domain]))
|
||||
|
||||
self.sni._mod_config() # pylint: disable=protected-access
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ def test_authenticator(plugin, config, temp_dir):
|
|||
"Plugin failed to complete %s for %s in %s",
|
||||
type(achalls[i]), achalls[i].domain, config)
|
||||
success = False
|
||||
elif isinstance(responses[i], challenges.DVSNIResponse):
|
||||
elif isinstance(responses[i], challenges.TLSSNI01):
|
||||
verify = functools.partial(responses[i].simple_verify, achalls[i],
|
||||
achalls[i].domain,
|
||||
util.JWK.public_key(),
|
||||
|
|
@ -68,10 +68,10 @@ def test_authenticator(plugin, config, temp_dir):
|
|||
port=plugin.https_port)
|
||||
if _try_until_true(verify):
|
||||
logger.info(
|
||||
"DVSNI verification for %s succeeded", achalls[i].domain)
|
||||
"tls-sni-01 verification for %s succeeded", achalls[i].domain)
|
||||
else:
|
||||
logger.error(
|
||||
"DVSNI verification for %s in %s failed",
|
||||
"tls-sni-01 verification for %s in %s failed",
|
||||
achalls[i].domain, config)
|
||||
success = False
|
||||
|
||||
|
|
@ -99,12 +99,12 @@ def _create_achalls(plugin):
|
|||
for domain in names:
|
||||
prefs = plugin.get_chall_pref(domain)
|
||||
for chall_type in prefs:
|
||||
if chall_type == challenges.DVSNI:
|
||||
chall = challenges.DVSNI(
|
||||
token=os.urandom(challenges.DVSNI.TOKEN_SIZE))
|
||||
if chall_type == challenges.TLSSNI01:
|
||||
chall = challenges.TLSSNI01(
|
||||
token=os.urandom(challenges.TLSSNI01.TOKEN_SIZE))
|
||||
challb = acme_util.chall_to_challb(
|
||||
chall, messages.STATUS_PENDING)
|
||||
achall = achallenges.DVSNI(
|
||||
achall = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=challb, domain=domain, account_key=util.JWK)
|
||||
achalls.append(achall)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import zope.interface
|
|||
from acme import challenges
|
||||
from acme import crypto_util as acme_crypto_util
|
||||
|
||||
from letsencrypt import achallenges
|
||||
from letsencrypt import constants as core_constants
|
||||
from letsencrypt import crypto_util
|
||||
from letsencrypt import errors
|
||||
|
|
@ -297,7 +296,7 @@ class NginxConfigurator(common.Plugin):
|
|||
"""Make a server SSL.
|
||||
|
||||
Make a server SSL based on server_name and filename by adding a
|
||||
``listen IConfig.dvsni_port ssl`` directive to the server block.
|
||||
``listen IConfig.tls_sni_01_port ssl`` directive to the server block.
|
||||
|
||||
.. todo:: Maybe this should create a new block instead of modifying
|
||||
the existing one?
|
||||
|
|
@ -307,7 +306,7 @@ class NginxConfigurator(common.Plugin):
|
|||
|
||||
"""
|
||||
snakeoil_cert, snakeoil_key = self._get_snakeoil_paths()
|
||||
ssl_block = [['listen', '{0} ssl'.format(self.config.dvsni_port)],
|
||||
ssl_block = [['listen', '{0} ssl'.format(self.config.tls_sni_01_port)],
|
||||
# access and error logs necessary for integration
|
||||
# testing (non-root)
|
||||
['access_log', os.path.join(
|
||||
|
|
@ -321,7 +320,8 @@ class NginxConfigurator(common.Plugin):
|
|||
vhost.filep, vhost.names, ssl_block)
|
||||
vhost.ssl = True
|
||||
vhost.raw.extend(ssl_block)
|
||||
vhost.addrs.add(obj.Addr('', str(self.config.dvsni_port), True, False))
|
||||
vhost.addrs.add(obj.Addr(
|
||||
'', str(self.config.tls_sni_01_port), True, False))
|
||||
|
||||
def get_all_certs_keys(self):
|
||||
"""Find all existing keys, certs from configuration.
|
||||
|
|
@ -536,7 +536,7 @@ class NginxConfigurator(common.Plugin):
|
|||
###########################################################################
|
||||
def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use
|
||||
"""Return list of challenge preferences."""
|
||||
return [challenges.DVSNI]
|
||||
return [challenges.TLSSNI01]
|
||||
|
||||
# Entry point in main.py for performing challenges
|
||||
def perform(self, achalls):
|
||||
|
|
@ -552,11 +552,10 @@ class NginxConfigurator(common.Plugin):
|
|||
nginx_dvsni = dvsni.NginxDvsni(self)
|
||||
|
||||
for i, achall in enumerate(achalls):
|
||||
if isinstance(achall, achallenges.DVSNI):
|
||||
# Currently also have dvsni hold associated index
|
||||
# of the challenge. This helps to put all of the responses back
|
||||
# together when they are all complete.
|
||||
nginx_dvsni.add_chall(achall, i)
|
||||
# Currently also have dvsni hold associated index
|
||||
# of the challenge. This helps to put all of the responses back
|
||||
# together when they are all complete.
|
||||
nginx_dvsni.add_chall(achall, i)
|
||||
|
||||
sni_response = nginx_dvsni.perform()
|
||||
# Must restart in order to activate the challenges.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from letsencrypt_nginx import nginxparser
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NginxDvsni(common.Dvsni):
|
||||
class NginxDvsni(common.TLSSNI01):
|
||||
"""Class performs DVSNI challenges within the Nginx configurator.
|
||||
|
||||
:ivar configurator: NginxConfigurator object
|
||||
|
|
@ -48,7 +48,7 @@ class NginxDvsni(common.Dvsni):
|
|||
|
||||
addresses = []
|
||||
default_addr = "{0} default_server ssl".format(
|
||||
self.configurator.config.dvsni_port)
|
||||
self.configurator.config.tls_sni_01_port)
|
||||
|
||||
for achall in self.achalls:
|
||||
vhost = self.configurator.choose_vhost(achall.domain)
|
||||
|
|
@ -141,7 +141,7 @@ class NginxDvsni(common.Dvsni):
|
|||
block = [['listen', str(addr)] for addr in addrs]
|
||||
|
||||
block.extend([['server_name',
|
||||
achall.gen_response(achall.account_key).z_domain],
|
||||
achall.response(achall.account_key).z_domain],
|
||||
['include', self.configurator.parser.loc["ssl_options"]],
|
||||
# access and error logs necessary for
|
||||
# integration testing (non-root)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
errors.PluginError, self.config.enhance, 'myhost', 'redirect')
|
||||
|
||||
def test_get_chall_pref(self):
|
||||
self.assertEqual([challenges.DVSNI],
|
||||
self.assertEqual([challenges.TLSSNI01],
|
||||
self.config.get_chall_pref('myhost'))
|
||||
|
||||
def test_save(self):
|
||||
|
|
@ -210,22 +210,22 @@ class NginxConfiguratorTest(util.NginxTest):
|
|||
def test_perform(self, mock_restart, mock_dvsni_perform):
|
||||
# Only tests functionality specific to configurator.perform
|
||||
# Note: As more challenges are offered this will have to be expanded
|
||||
achall1 = achallenges.DVSNI(
|
||||
achall1 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=messages.ChallengeBody(
|
||||
chall=challenges.DVSNI(token="kNdwjwOeX0I_A8DXt9Msmg"),
|
||||
chall=challenges.TLSSNI01(token="kNdwjwOeX0I_A8DXt9Msmg"),
|
||||
uri="https://ca.org/chall0_uri",
|
||||
status=messages.Status("pending"),
|
||||
), domain="localhost", account_key=self.rsa512jwk)
|
||||
achall2 = achallenges.DVSNI(
|
||||
achall2 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=messages.ChallengeBody(
|
||||
chall=challenges.DVSNI(token="m8TdO1qik4JVFtgPPurJmg"),
|
||||
chall=challenges.TLSSNI01(token="m8TdO1qik4JVFtgPPurJmg"),
|
||||
uri="https://ca.org/chall1_uri",
|
||||
status=messages.Status("pending"),
|
||||
), domain="example.com", account_key=self.rsa512jwk)
|
||||
|
||||
dvsni_ret_val = [
|
||||
achall1.gen_response(self.rsa512jwk),
|
||||
achall2.gen_response(self.rsa512jwk),
|
||||
achall1.response(self.rsa512jwk),
|
||||
achall2.response(self.rsa512jwk),
|
||||
]
|
||||
|
||||
mock_dvsni_perform.return_value = dvsni_ret_val
|
||||
|
|
|
|||
|
|
@ -19,22 +19,22 @@ from letsencrypt_nginx.tests import util
|
|||
class DvsniPerformTest(util.NginxTest):
|
||||
"""Test the NginxDVSNI challenge."""
|
||||
|
||||
account_key = common_test.DvsniTest.auth_key
|
||||
account_key = common_test.TLSSNI01Test.auth_key
|
||||
achalls = [
|
||||
achallenges.DVSNI(
|
||||
achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.DVSNI(token="kNdwjwOeX0I_A8DXt9Msmg"), "pending"),
|
||||
challenges.TLSSNI01(token="kNdwjwOeX0I_A8DXt9Msmg"), "pending"),
|
||||
domain="www.example.com", account_key=account_key),
|
||||
achallenges.DVSNI(
|
||||
achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.DVSNI(
|
||||
challenges.TLSSNI01(
|
||||
token="\xba\xa9\xda?<m\xaewmx\xea\xad\xadv\xf4\x02\xc9y"
|
||||
"\x80\xe2_X\t\xe7\xc7\xa4\t\xca\xf7&\x945"
|
||||
), "pending"),
|
||||
domain="blah", account_key=account_key),
|
||||
achallenges.DVSNI(
|
||||
achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.DVSNI(
|
||||
challenges.TLSSNI01(
|
||||
token="\x8c\x8a\xbf_-f\\cw\xee\xd6\xf8/\xa5\xe3\xfd"
|
||||
"\xeb9\xf1\xf5\xb9\xefVM\xc9w\xa4u\x9c\xe1\x87\xb4"
|
||||
), "pending"),
|
||||
|
|
@ -70,7 +70,7 @@ class DvsniPerformTest(util.NginxTest):
|
|||
@mock.patch("letsencrypt_nginx.configurator.NginxConfigurator.save")
|
||||
def test_perform1(self, mock_save):
|
||||
self.sni.add_chall(self.achalls[0])
|
||||
response = self.achalls[0].gen_response(self.account_key)
|
||||
response = self.achalls[0].response(self.account_key)
|
||||
mock_setup_cert = mock.MagicMock(return_value=response)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
|
|
@ -92,7 +92,7 @@ class DvsniPerformTest(util.NginxTest):
|
|||
acme_responses = []
|
||||
for achall in self.achalls:
|
||||
self.sni.add_chall(achall)
|
||||
acme_responses.append(achall.gen_response(self.account_key))
|
||||
acme_responses.append(achall.response(self.account_key))
|
||||
|
||||
mock_setup_cert = mock.MagicMock(side_effect=acme_responses)
|
||||
# pylint: disable=protected-access
|
||||
|
|
@ -139,9 +139,9 @@ class DvsniPerformTest(util.NginxTest):
|
|||
|
||||
for vhost in vhs:
|
||||
if vhost.addrs == set(v_addr1):
|
||||
response = self.achalls[0].gen_response(self.account_key)
|
||||
response = self.achalls[0].response(self.account_key)
|
||||
else:
|
||||
response = self.achalls[2].gen_response(self.account_key)
|
||||
response = self.achalls[2].response(self.account_key)
|
||||
self.assertEqual(vhost.addrs, set(v_addr2))
|
||||
self.assertEqual(vhost.names, set([response.z_domain]))
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ def get_nginx_configurator(
|
|||
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
|
||||
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
|
||||
server="https://acme-server.org:443/new",
|
||||
dvsni_port=5001,
|
||||
tls_sni_01_port=5001,
|
||||
),
|
||||
name="nginx",
|
||||
version=version)
|
||||
|
|
|
|||
|
|
@ -49,40 +49,10 @@ class KeyAuthorizationAnnotatedChallenge(AnnotatedChallenge):
|
|||
"""Client annotated `KeyAuthorizationChallenge` challenge."""
|
||||
__slots__ = ('challb', 'domain', 'account_key')
|
||||
|
||||
def response_and_validation(self):
|
||||
def response_and_validation(self, *args, **kwargs):
|
||||
"""Generate response and validation."""
|
||||
return self.challb.chall.response_and_validation(self.account_key)
|
||||
|
||||
|
||||
class DVSNI(AnnotatedChallenge):
|
||||
"""Client annotated "dvsni" ACME challenge.
|
||||
|
||||
:ivar .JWK account_key: Authorized Account Key
|
||||
|
||||
"""
|
||||
__slots__ = ('challb', 'domain', 'account_key')
|
||||
acme_type = challenges.DVSNI
|
||||
|
||||
def gen_cert_and_response(self, key=None, bits=2048, alg=jose.RS256):
|
||||
"""Generate a DVSNI cert and response.
|
||||
|
||||
:param OpenSSL.crypto.PKey key: Private key used for
|
||||
certificate generation. If none provided, a fresh key will
|
||||
be generated.
|
||||
:param int bits: Number of bits for fresh key generation.
|
||||
:param .JWAAlgorithm alg:
|
||||
|
||||
:returns: ``(response, cert_pem, key_pem)`` tuple, where
|
||||
``response`` is an instance of
|
||||
`acme.challenges.DVSNIResponse`, ``cert`` is a certificate
|
||||
(`OpenSSL.crypto.X509`) and ``key`` is a private key
|
||||
(`OpenSSL.crypto.PKey`).
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
response = self.challb.chall.gen_response(self.account_key, alg=alg)
|
||||
cert, key = response.gen_cert(key=key, bits=bits)
|
||||
return response, cert, key
|
||||
return self.challb.chall.response_and_validation(
|
||||
self.account_key, *args, **kwargs)
|
||||
|
||||
|
||||
class DNS(AnnotatedChallenge):
|
||||
|
|
|
|||
|
|
@ -344,8 +344,8 @@ def challb_to_achall(challb, account_key, domain):
|
|||
chall = challb.chall
|
||||
logger.info("%s challenge for %s", chall.typ, domain)
|
||||
|
||||
if isinstance(chall, challenges.DVSNI):
|
||||
return achallenges.DVSNI(
|
||||
if isinstance(chall, challenges.KeyAuthorizationChallenge):
|
||||
return achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=challb, domain=domain, account_key=account_key)
|
||||
elif isinstance(chall, challenges.DNS):
|
||||
return achallenges.DNS(challb=challb, domain=domain)
|
||||
|
|
@ -355,9 +355,6 @@ def challb_to_achall(challb, account_key, domain):
|
|||
elif isinstance(chall, challenges.ProofOfPossession):
|
||||
return achallenges.ProofOfPossession(
|
||||
challb=challb, domain=domain)
|
||||
elif isinstance(chall, challenges.KeyAuthorizationChallenge):
|
||||
return achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=challb, domain=domain, account_key=account_key)
|
||||
else:
|
||||
raise errors.Error(
|
||||
"Received unsupported challenge of type: %s", chall.typ)
|
||||
|
|
|
|||
|
|
@ -850,8 +850,9 @@ def prepare_and_parse_args(plugins, args):
|
|||
help=config_help("no_verify_ssl"),
|
||||
default=flag_default("no_verify_ssl"))
|
||||
helpful.add(
|
||||
"testing", "--dvsni-port", type=int, default=flag_default("dvsni_port"),
|
||||
help=config_help("dvsni_port"))
|
||||
"testing", "--tls-sni-01-port", type=int,
|
||||
default=flag_default("tls_sni_01_port"),
|
||||
help=config_help("tls_sni_01_port"))
|
||||
helpful.add("testing", "--http-01-port", dest="http01_port", type=int,
|
||||
help=config_help("http01_port"))
|
||||
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@ class NamespaceConfig(object):
|
|||
def __init__(self, namespace):
|
||||
self.namespace = namespace
|
||||
|
||||
if self.http01_port == self.dvsni_port:
|
||||
if self.http01_port == self.tls_sni_01_port:
|
||||
raise errors.Error(
|
||||
"Trying to run http-01 and DVSNI "
|
||||
"on the same port ({0})".format(self.dvsni_port))
|
||||
"Trying to run http-01 and tls-sni-01 "
|
||||
"on the same port ({0})".format(self.tls_sni_01_port))
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.namespace, name)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ CLI_DEFAULTS = dict(
|
|||
work_dir="/var/lib/letsencrypt",
|
||||
logs_dir="/var/log/letsencrypt",
|
||||
no_verify_ssl=False,
|
||||
dvsni_port=challenges.DVSNI.PORT,
|
||||
tls_sni_01_port=challenges.TLSSNI01Response.PORT,
|
||||
|
||||
auth_cert_path="./cert.pem",
|
||||
auth_chain_path="./chain.pem",
|
||||
|
|
@ -41,7 +41,7 @@ RENEWER_DEFAULTS = dict(
|
|||
|
||||
|
||||
EXCLUSIVE_CHALLENGES = frozenset([frozenset([
|
||||
challenges.DVSNI, challenges.HTTP01])])
|
||||
challenges.TLSSNI01, challenges.HTTP01])])
|
||||
"""Mutually exclusive challenges."""
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ class DvAuthError(AuthorizationError):
|
|||
|
||||
|
||||
# Authenticator - Challenge specific errors
|
||||
class DvsniError(DvAuthError):
|
||||
"""Let's Encrypt DVSNI error."""
|
||||
class TLSSNI01Error(DvAuthError):
|
||||
"""Let's Encrypt TLSSNI01 error."""
|
||||
|
||||
|
||||
# Plugin Errors
|
||||
|
|
|
|||
|
|
@ -219,8 +219,8 @@ class IConfig(zope.interface.Interface):
|
|||
|
||||
no_verify_ssl = zope.interface.Attribute(
|
||||
"Disable SSL certificate verification.")
|
||||
dvsni_port = zope.interface.Attribute(
|
||||
"Port number to perform DVSNI challenge. "
|
||||
tls_sni_01_port = zope.interface.Attribute(
|
||||
"Port number to perform tls-sni-01 challenge. "
|
||||
"Boulder in testing mode defaults to 5001.")
|
||||
|
||||
http01_port = zope.interface.Attribute(
|
||||
|
|
|
|||
|
|
@ -135,22 +135,22 @@ class Addr(object):
|
|||
return self.__class__((self.tup[0], port))
|
||||
|
||||
|
||||
class Dvsni(object):
|
||||
"""Class that perform DVSNI challenges."""
|
||||
class TLSSNI01(object):
|
||||
"""Class that performs tls-sni-01 challenges."""
|
||||
|
||||
def __init__(self, configurator):
|
||||
self.configurator = configurator
|
||||
self.achalls = []
|
||||
self.indices = []
|
||||
self.challenge_conf = os.path.join(
|
||||
configurator.config.config_dir, "le_dvsni_cert_challenge.conf")
|
||||
configurator.config.config_dir, "le_tls_sni_01_cert_challenge.conf")
|
||||
# self.completed = 0
|
||||
|
||||
def add_chall(self, achall, idx=None):
|
||||
"""Add challenge to DVSNI object to perform at once.
|
||||
"""Add challenge to TLSSNI01 object to perform at once.
|
||||
|
||||
:param achall: Annotated DVSNI challenge.
|
||||
:type achall: :class:`letsencrypt.achallenges.DVSNI`
|
||||
:param .KeyAuthorizationAnnotatedChallenge achall: Annotated
|
||||
TLSSNI01 challenge.
|
||||
|
||||
:param int idx: index to challenge in a larger array
|
||||
|
||||
|
|
@ -162,8 +162,8 @@ class Dvsni(object):
|
|||
def get_cert_path(self, achall):
|
||||
"""Returns standardized name for challenge certificate.
|
||||
|
||||
:param achall: Annotated DVSNI challenge.
|
||||
:type achall: :class:`letsencrypt.achallenges.DVSNI`
|
||||
:param .KeyAuthorizationAnnotatedChallenge achall: Annotated
|
||||
tls-sni-01 challenge.
|
||||
|
||||
:returns: certificate file name
|
||||
:rtype: str
|
||||
|
|
@ -177,7 +177,7 @@ class Dvsni(object):
|
|||
return os.path.join(self.configurator.config.work_dir,
|
||||
achall.chall.encode("token") + '.pem')
|
||||
|
||||
def _setup_challenge_cert(self, achall, s=None):
|
||||
def _setup_challenge_cert(self, achall, cert_key=None):
|
||||
|
||||
"""Generate and write out challenge certificate."""
|
||||
cert_path = self.get_cert_path(achall)
|
||||
|
|
@ -186,7 +186,8 @@ class Dvsni(object):
|
|||
self.configurator.reverter.register_file_creation(True, key_path)
|
||||
self.configurator.reverter.register_file_creation(True, cert_path)
|
||||
|
||||
response, cert, key = achall.gen_cert_and_response(s)
|
||||
response, (cert, key) = achall.response_and_validation(
|
||||
cert_key=cert_key)
|
||||
cert_pem = OpenSSL.crypto.dump_certificate(
|
||||
OpenSSL.crypto.FILETYPE_PEM, cert)
|
||||
key_pem = OpenSSL.crypto.dump_privatekey(
|
||||
|
|
|
|||
|
|
@ -115,24 +115,24 @@ class AddrTest(unittest.TestCase):
|
|||
self.assertEqual(set_a, set_b)
|
||||
|
||||
|
||||
class DvsniTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.plugins.common.DvsniTest."""
|
||||
class TLSSNI01Test(unittest.TestCase):
|
||||
"""Tests for letsencrypt.plugins.common.TLSSNI01."""
|
||||
|
||||
auth_key = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem"))
|
||||
achalls = [
|
||||
achallenges.DVSNI(
|
||||
achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.DVSNI(token=b'dvsni1'), "pending"),
|
||||
challenges.TLSSNI01(token=b'token1'), "pending"),
|
||||
domain="encryption-example.demo", account_key=auth_key),
|
||||
achallenges.DVSNI(
|
||||
achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.DVSNI(token=b'dvsni2'), "pending"),
|
||||
challenges.TLSSNI01(token=b'token2'), "pending"),
|
||||
domain="letsencrypt.demo", account_key=auth_key),
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
from letsencrypt.plugins.common import Dvsni
|
||||
self.sni = Dvsni(configurator=mock.MagicMock())
|
||||
from letsencrypt.plugins.common import TLSSNI01
|
||||
self.sni = TLSSNI01(configurator=mock.MagicMock())
|
||||
|
||||
def test_add_chall(self):
|
||||
self.sni.add_chall(self.achalls[0], 0)
|
||||
|
|
@ -146,11 +146,11 @@ class DvsniTest(unittest.TestCase):
|
|||
# http://www.voidspace.org.uk/python/mock/helpers.html#mock.mock_open
|
||||
mock_open, mock_safe_open = mock.mock_open(), mock.mock_open()
|
||||
|
||||
response = challenges.DVSNIResponse(validation=mock.Mock())
|
||||
response = challenges.TLSSNI01Response()
|
||||
achall = mock.MagicMock()
|
||||
key = test_util.load_pyopenssl_private_key("rsa512_key.pem")
|
||||
achall.gen_cert_and_response.return_value = (
|
||||
response, test_util.load_cert("cert.pem"), key)
|
||||
achall.response_and_validation.return_value = (
|
||||
response, (test_util.load_cert("cert.pem"), key))
|
||||
|
||||
with mock.patch("letsencrypt.plugins.common.open",
|
||||
mock_open, create=True):
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class Authenticator(common.Plugin):
|
|||
run as a privileged process. Alternatively shows instructions on how
|
||||
to use Python's built-in HTTP server.
|
||||
|
||||
.. todo:: Support for `~.challenges.DVSNI`.
|
||||
.. todo:: Support for `~.challenges.TLSSNI01`.
|
||||
|
||||
"""
|
||||
zope.interface.implements(interfaces.IAuthenticator)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import six
|
|||
import zope.interface
|
||||
|
||||
from acme import challenges
|
||||
from acme import crypto_util as acme_crypto_util
|
||||
from acme import standalone as acme_standalone
|
||||
|
||||
from letsencrypt import errors
|
||||
|
|
@ -28,9 +27,9 @@ class ServerManager(object):
|
|||
|
||||
Manager for `ACMEServer` and `ACMETLSServer` instances.
|
||||
|
||||
`certs` and `simple_http_resources` correspond to
|
||||
`certs` and `http_01_resources` correspond to
|
||||
`acme.crypto_util.SSLSocket.certs` and
|
||||
`acme.crypto_util.SSLSocket.simple_http_resources` respectively. All
|
||||
`acme.crypto_util.SSLSocket.http_01_resources` respectively. All
|
||||
created servers share the same certificates and resources, so if
|
||||
you're running both TLS and non-TLS instances, HTTP01 handlers
|
||||
will serve the same URLs!
|
||||
|
|
@ -38,10 +37,10 @@ class ServerManager(object):
|
|||
"""
|
||||
_Instance = collections.namedtuple("_Instance", "server thread")
|
||||
|
||||
def __init__(self, certs, simple_http_resources):
|
||||
def __init__(self, certs, http_01_resources):
|
||||
self._instances = {}
|
||||
self.certs = certs
|
||||
self.simple_http_resources = simple_http_resources
|
||||
self.http_01_resources = http_01_resources
|
||||
|
||||
def run(self, port, challenge_type):
|
||||
"""Run ACME server on specified ``port``.
|
||||
|
|
@ -51,23 +50,23 @@ class ServerManager(object):
|
|||
|
||||
:param int port: Port to run the server on.
|
||||
:param challenge_type: Subclass of `acme.challenges.Challenge`,
|
||||
either `acme.challenge.HTTP01` or `acme.challenges.DVSNI`.
|
||||
either `acme.challenge.HTTP01` or `acme.challenges.TLSSNI01`.
|
||||
|
||||
:returns: Server instance.
|
||||
:rtype: ACMEServerMixin
|
||||
|
||||
"""
|
||||
assert challenge_type in (challenges.DVSNI, challenges.HTTP01)
|
||||
assert challenge_type in (challenges.TLSSNI01, challenges.HTTP01)
|
||||
if port in self._instances:
|
||||
return self._instances[port].server
|
||||
|
||||
address = ("", port)
|
||||
try:
|
||||
if challenge_type is challenges.DVSNI:
|
||||
server = acme_standalone.DVSNIServer(address, self.certs)
|
||||
if challenge_type is challenges.TLSSNI01:
|
||||
server = acme_standalone.TLSSNI01Server(address, self.certs)
|
||||
else: # challenges.HTTP01
|
||||
server = acme_standalone.HTTP01Server(
|
||||
address, self.simple_http_resources)
|
||||
address, self.http_01_resources)
|
||||
except socket.error as error:
|
||||
raise errors.StandaloneBindError(error, port)
|
||||
|
||||
|
|
@ -109,7 +108,7 @@ class ServerManager(object):
|
|||
in six.iteritems(self._instances))
|
||||
|
||||
|
||||
SUPPORTED_CHALLENGES = set([challenges.DVSNI, challenges.HTTP01])
|
||||
SUPPORTED_CHALLENGES = set([challenges.TLSSNI01, challenges.HTTP01])
|
||||
|
||||
|
||||
def supported_challenges_validator(data):
|
||||
|
|
@ -138,7 +137,7 @@ class Authenticator(common.Plugin):
|
|||
"""Standalone Authenticator.
|
||||
|
||||
This authenticator creates its own ephemeral TCP listener on the
|
||||
necessary port in order to respond to incoming DVSNI and HTTP01
|
||||
necessary port in order to respond to incoming tls-sni-01 and http-01
|
||||
challenges from the certificate authority. Therefore, it does not
|
||||
rely on any existing server program.
|
||||
"""
|
||||
|
|
@ -150,12 +149,9 @@ class Authenticator(common.Plugin):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super(Authenticator, self).__init__(*args, **kwargs)
|
||||
|
||||
# one self-signed key for all DVSNI and HTTP01 certificates
|
||||
# one self-signed key for all tls-sni-01 certificates
|
||||
self.key = OpenSSL.crypto.PKey()
|
||||
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, bits=2048)
|
||||
# TODO: generate only when the first HTTP01 challenge is solved
|
||||
self.simple_http_cert = acme_crypto_util.gen_ss_cert(
|
||||
self.key, domains=["temp server"])
|
||||
|
||||
self.served = collections.defaultdict(set)
|
||||
|
||||
|
|
@ -164,9 +160,9 @@ class Authenticator(common.Plugin):
|
|||
# GIL, the operations are safe, c.f.
|
||||
# https://docs.python.org/2/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
|
||||
self.certs = {}
|
||||
self.simple_http_resources = set()
|
||||
self.http_01_resources = set()
|
||||
|
||||
self.servers = ServerManager(self.certs, self.simple_http_resources)
|
||||
self.servers = ServerManager(self.certs, self.http_01_resources)
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, add):
|
||||
|
|
@ -186,15 +182,16 @@ class Authenticator(common.Plugin):
|
|||
necessary_ports = set()
|
||||
if challenges.HTTP01 in self.supported_challenges:
|
||||
necessary_ports.add(self.config.http01_port)
|
||||
if challenges.DVSNI in self.supported_challenges:
|
||||
necessary_ports.add(self.config.dvsni_port)
|
||||
if challenges.TLSSNI01 in self.supported_challenges:
|
||||
necessary_ports.add(self.config.tls_sni_01_port)
|
||||
return necessary_ports
|
||||
|
||||
def more_info(self): # pylint: disable=missing-docstring
|
||||
return("This authenticator creates its own ephemeral TCP listener "
|
||||
"on the necessary port in order to respond to incoming DVSNI "
|
||||
"and HTTP01 challenges from the certificate authority. "
|
||||
"Therefore, it does not rely on any existing server program.")
|
||||
"on the necessary port in order to respond to incoming "
|
||||
"tls-sni-01 and http-01 challenges from the certificate "
|
||||
"authority. Therefore, it does not rely on any existing "
|
||||
"server program.")
|
||||
|
||||
def prepare(self): # pylint: disable=missing-docstring
|
||||
pass
|
||||
|
|
@ -240,17 +237,16 @@ class Authenticator(common.Plugin):
|
|||
server = self.servers.run(
|
||||
self.config.http01_port, challenges.HTTP01)
|
||||
response, validation = achall.response_and_validation()
|
||||
self.simple_http_resources.add(
|
||||
self.http_01_resources.add(
|
||||
acme_standalone.HTTP01RequestHandler.HTTP01Resource(
|
||||
chall=achall.chall, response=response,
|
||||
validation=validation))
|
||||
cert = self.simple_http_cert
|
||||
domain = achall.domain
|
||||
else: # DVSNI
|
||||
server = self.servers.run(self.config.dvsni_port, challenges.DVSNI)
|
||||
response, cert, _ = achall.gen_cert_and_response(self.key)
|
||||
domain = response.z_domain
|
||||
self.certs[domain] = (self.key, cert)
|
||||
else: # tls-sni-01
|
||||
server = self.servers.run(
|
||||
self.config.tls_sni_01_port, challenges.TLSSNI01)
|
||||
response, (cert, _) = achall.response_and_validation(
|
||||
cert_key=self.key)
|
||||
self.certs[response.z_domain] = (self.key, cert)
|
||||
self.served[server].add(achall)
|
||||
responses.append(response)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ class ServerManagerTest(unittest.TestCase):
|
|||
def setUp(self):
|
||||
from letsencrypt.plugins.standalone import ServerManager
|
||||
self.certs = {}
|
||||
self.simple_http_resources = {}
|
||||
self.mgr = ServerManager(self.certs, self.simple_http_resources)
|
||||
self.http_01_resources = {}
|
||||
self.mgr = ServerManager(self.certs, self.http_01_resources)
|
||||
|
||||
def test_init(self):
|
||||
self.assertTrue(self.mgr.certs is self.certs)
|
||||
self.assertTrue(
|
||||
self.mgr.simple_http_resources is self.simple_http_resources)
|
||||
self.mgr.http_01_resources is self.http_01_resources)
|
||||
|
||||
def _test_run_stop(self, challenge_type):
|
||||
server = self.mgr.run(port=0, challenge_type=challenge_type)
|
||||
|
|
@ -39,10 +39,10 @@ class ServerManagerTest(unittest.TestCase):
|
|||
self.mgr.stop(port=port)
|
||||
self.assertEqual(self.mgr.running(), {})
|
||||
|
||||
def test_run_stop_dvsni(self):
|
||||
self._test_run_stop(challenges.DVSNI)
|
||||
def test_run_stop_tls_sni_01(self):
|
||||
self._test_run_stop(challenges.TLSSNI01)
|
||||
|
||||
def test_run_stop_simplehttp(self):
|
||||
def test_run_stop_http_01(self):
|
||||
self._test_run_stop(challenges.HTTP01)
|
||||
|
||||
def test_run_idempotent(self):
|
||||
|
|
@ -73,10 +73,10 @@ class SupportedChallengesValidatorTest(unittest.TestCase):
|
|||
return supported_challenges_validator(data)
|
||||
|
||||
def test_correct(self):
|
||||
self.assertEqual("dvsni", self._call("dvsni"))
|
||||
self.assertEqual("tls-sni-01", self._call("tls-sni-01"))
|
||||
self.assertEqual("http-01", self._call("http-01"))
|
||||
self.assertEqual("dvsni,http-01", self._call("dvsni,http-01"))
|
||||
self.assertEqual("http-01,dvsni", self._call("http-01,dvsni"))
|
||||
self.assertEqual("tls-sni-01,http-01", self._call("tls-sni-01,http-01"))
|
||||
self.assertEqual("http-01,tls-sni-01", self._call("http-01,tls-sni-01"))
|
||||
|
||||
def test_unrecognized(self):
|
||||
assert "foo" not in challenges.Challenge.TYPES
|
||||
|
|
@ -92,24 +92,24 @@ class AuthenticatorTest(unittest.TestCase):
|
|||
def setUp(self):
|
||||
from letsencrypt.plugins.standalone import Authenticator
|
||||
self.config = mock.MagicMock(
|
||||
dvsni_port=1234, http01_port=4321,
|
||||
standalone_supported_challenges="dvsni,http-01")
|
||||
tls_sni_01_port=1234, http01_port=4321,
|
||||
standalone_supported_challenges="tls-sni-01,http-01")
|
||||
self.auth = Authenticator(self.config, name="standalone")
|
||||
|
||||
def test_supported_challenges(self):
|
||||
self.assertEqual(self.auth.supported_challenges,
|
||||
set([challenges.DVSNI, challenges.HTTP01]))
|
||||
set([challenges.TLSSNI01, challenges.HTTP01]))
|
||||
|
||||
def test_more_info(self):
|
||||
self.assertTrue(isinstance(self.auth.more_info(), six.string_types))
|
||||
|
||||
def test_get_chall_pref(self):
|
||||
self.assertEqual(set(self.auth.get_chall_pref(domain=None)),
|
||||
set([challenges.DVSNI, challenges.HTTP01]))
|
||||
set([challenges.TLSSNI01, challenges.HTTP01]))
|
||||
|
||||
@mock.patch("letsencrypt.plugins.standalone.util")
|
||||
def test_perform_alredy_listening(self, mock_util):
|
||||
for chall, port in ((challenges.DVSNI.typ, 1234),
|
||||
for chall, port in ((challenges.TLSSNI01.typ, 1234),
|
||||
(challenges.HTTP01.typ, 4321)):
|
||||
mock_util.already_listening.return_value = True
|
||||
self.config.standalone_supported_challenges = chall
|
||||
|
|
@ -153,10 +153,10 @@ class AuthenticatorTest(unittest.TestCase):
|
|||
def test_perform2(self):
|
||||
domain = b'localhost'
|
||||
key = jose.JWK.load(test_util.load_vector('rsa512_key.pem'))
|
||||
simple_http = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
http_01 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.HTTP01_P, domain=domain, account_key=key)
|
||||
dvsni = achallenges.DVSNI(
|
||||
challb=acme_util.DVSNI_P, domain=domain, account_key=key)
|
||||
tls_sni_01 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.TLSSNI01_P, domain=domain, account_key=key)
|
||||
|
||||
self.auth.servers = mock.MagicMock()
|
||||
|
||||
|
|
@ -164,24 +164,24 @@ class AuthenticatorTest(unittest.TestCase):
|
|||
return "server{0}".format(port)
|
||||
|
||||
self.auth.servers.run.side_effect = _run
|
||||
responses = self.auth.perform2([simple_http, dvsni])
|
||||
responses = self.auth.perform2([http_01, tls_sni_01])
|
||||
|
||||
self.assertTrue(isinstance(responses, list))
|
||||
self.assertEqual(2, len(responses))
|
||||
self.assertTrue(isinstance(responses[0], challenges.HTTP01Response))
|
||||
self.assertTrue(isinstance(responses[1], challenges.DVSNIResponse))
|
||||
self.assertTrue(isinstance(responses[1], challenges.TLSSNI01Response))
|
||||
|
||||
self.assertEqual(self.auth.servers.run.mock_calls, [
|
||||
mock.call(4321, challenges.HTTP01),
|
||||
mock.call(1234, challenges.DVSNI),
|
||||
mock.call(1234, challenges.TLSSNI01),
|
||||
])
|
||||
self.assertEqual(self.auth.served, {
|
||||
"server1234": set([dvsni]),
|
||||
"server4321": set([simple_http]),
|
||||
"server1234": set([tls_sni_01]),
|
||||
"server4321": set([http_01]),
|
||||
})
|
||||
self.assertEqual(1, len(self.auth.simple_http_resources))
|
||||
self.assertEqual(2, len(self.auth.certs))
|
||||
self.assertEqual(list(self.auth.simple_http_resources), [
|
||||
self.assertEqual(1, len(self.auth.http_01_resources))
|
||||
self.assertEqual(1, len(self.auth.certs))
|
||||
self.assertEqual(list(self.auth.http_01_resources), [
|
||||
acme_standalone.HTTP01RequestHandler.HTTP01Resource(
|
||||
acme_util.HTTP01, responses[0], mock.ANY)])
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ def renew(cert, old_version):
|
|||
# XXX: this loses type data (for example, the fact that key_size
|
||||
# was an int, not a str)
|
||||
config.rsa_key_size = int(config.rsa_key_size)
|
||||
config.dvsni_port = int(config.dvsni_port)
|
||||
config.tls_sni_01_port = int(config.tls_sni_01_port)
|
||||
config.namespace.http01_port = int(config.namespace.http01_port)
|
||||
zope.component.provideUtility(config)
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
"""Tests for letsencrypt.achallenges."""
|
||||
import unittest
|
||||
|
||||
import OpenSSL
|
||||
|
||||
from acme import challenges
|
||||
from acme import jose
|
||||
|
||||
from letsencrypt.tests import acme_util
|
||||
from letsencrypt.tests import test_util
|
||||
|
||||
|
||||
class DVSNITest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.achallenges.DVSNI."""
|
||||
|
||||
def setUp(self):
|
||||
self.challb = acme_util.chall_to_challb(acme_util.DVSNI, "pending")
|
||||
key = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem"))
|
||||
from letsencrypt.achallenges import DVSNI
|
||||
self.achall = DVSNI(
|
||||
challb=self.challb, domain="example.com", account_key=key)
|
||||
|
||||
def test_proxy(self):
|
||||
self.assertEqual(self.challb.token, self.achall.token)
|
||||
|
||||
def test_gen_cert_and_response(self):
|
||||
response, cert, key = self.achall.gen_cert_and_response()
|
||||
self.assertTrue(isinstance(response, challenges.DVSNIResponse))
|
||||
self.assertTrue(isinstance(cert, OpenSSL.crypto.X509))
|
||||
self.assertTrue(isinstance(key, OpenSSL.crypto.PKey))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
@ -14,7 +14,7 @@ KEY = test_util.load_rsa_private_key('rsa512_key.pem')
|
|||
# Challenges
|
||||
HTTP01 = challenges.HTTP01(
|
||||
token="evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA")
|
||||
DVSNI = challenges.DVSNI(
|
||||
TLSSNI01 = challenges.TLSSNI01(
|
||||
token=jose.b64decode(b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJyPCt92wrDoA"))
|
||||
DNS = challenges.DNS(token="17817c66b60ce2e4012dfad92657527a")
|
||||
RECOVERY_CONTACT = challenges.RecoveryContact(
|
||||
|
|
@ -41,7 +41,7 @@ POP = challenges.ProofOfPossession(
|
|||
)
|
||||
)
|
||||
|
||||
CHALLENGES = [HTTP01, DVSNI, DNS, RECOVERY_CONTACT, POP]
|
||||
CHALLENGES = [HTTP01, TLSSNI01, DNS, RECOVERY_CONTACT, POP]
|
||||
DV_CHALLENGES = [chall for chall in CHALLENGES
|
||||
if isinstance(chall, challenges.DVChallenge)]
|
||||
CONT_CHALLENGES = [chall for chall in CHALLENGES
|
||||
|
|
@ -79,13 +79,13 @@ def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name
|
|||
|
||||
|
||||
# Pending ChallengeBody objects
|
||||
DVSNI_P = chall_to_challb(DVSNI, messages.STATUS_PENDING)
|
||||
TLSSNI01_P = chall_to_challb(TLSSNI01, messages.STATUS_PENDING)
|
||||
HTTP01_P = chall_to_challb(HTTP01, messages.STATUS_PENDING)
|
||||
DNS_P = chall_to_challb(DNS, messages.STATUS_PENDING)
|
||||
RECOVERY_CONTACT_P = chall_to_challb(RECOVERY_CONTACT, messages.STATUS_PENDING)
|
||||
POP_P = chall_to_challb(POP, messages.STATUS_PENDING)
|
||||
|
||||
CHALLENGES_P = [HTTP01_P, DVSNI_P, DNS_P, RECOVERY_CONTACT_P, POP_P]
|
||||
CHALLENGES_P = [HTTP01_P, TLSSNI01_P, DNS_P, RECOVERY_CONTACT_P, POP_P]
|
||||
DV_CHALLENGES_P = [challb for challb in CHALLENGES_P
|
||||
if isinstance(challb.chall, challenges.DVChallenge)]
|
||||
CONT_CHALLENGES_P = [
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class ChallengeFactoryTest(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
[achall.chall for achall in cont_c], [acme_util.RECOVERY_CONTACT])
|
||||
self.assertEqual([achall.chall for achall in dv_c], [acme_util.DVSNI])
|
||||
self.assertEqual([achall.chall for achall in dv_c], [acme_util.TLSSNI01])
|
||||
|
||||
def test_unrecognized(self):
|
||||
self.handler.authzr["failure.com"] = acme_util.gen_authzr(
|
||||
|
|
@ -70,7 +70,7 @@ class GetAuthorizationsTest(unittest.TestCase):
|
|||
self.mock_dv_auth = mock.MagicMock(name="ApacheConfigurator")
|
||||
self.mock_cont_auth = mock.MagicMock(name="ContinuityAuthenticator")
|
||||
|
||||
self.mock_dv_auth.get_chall_pref.return_value = [challenges.DVSNI]
|
||||
self.mock_dv_auth.get_chall_pref.return_value = [challenges.TLSSNI01]
|
||||
self.mock_cont_auth.get_chall_pref.return_value = [
|
||||
challenges.RecoveryContact]
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ class GetAuthorizationsTest(unittest.TestCase):
|
|||
logging.disable(logging.NOTSET)
|
||||
|
||||
@mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges")
|
||||
def test_name1_dvsni1(self, mock_poll):
|
||||
def test_name1_tls_sni_01_1(self, mock_poll):
|
||||
self.mock_net.request_domain_challenges.side_effect = functools.partial(
|
||||
gen_dom_authzr, challs=acme_util.DV_CHALLENGES)
|
||||
|
||||
|
|
@ -107,14 +107,14 @@ class GetAuthorizationsTest(unittest.TestCase):
|
|||
|
||||
self.assertEqual(self.mock_dv_auth.cleanup.call_count, 1)
|
||||
self.assertEqual(self.mock_cont_auth.cleanup.call_count, 0)
|
||||
# Test if list first element is DVSNI, use typ because it is an achall
|
||||
# Test if list first element is TLSSNI01, use typ because it is an achall
|
||||
self.assertEqual(
|
||||
self.mock_dv_auth.cleanup.call_args[0][0][0].typ, "dvsni")
|
||||
self.mock_dv_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01")
|
||||
|
||||
self.assertEqual(len(authzr), 1)
|
||||
|
||||
@mock.patch("letsencrypt.auth_handler.AuthHandler._poll_challenges")
|
||||
def test_name3_dvsni3_rectok_3(self, mock_poll):
|
||||
def test_name3_tls_sni_01_3_rectok_3(self, mock_poll):
|
||||
self.mock_net.request_domain_challenges.side_effect = functools.partial(
|
||||
gen_dom_authzr, challs=acme_util.CHALLENGES)
|
||||
|
||||
|
|
@ -309,9 +309,9 @@ class GenChallengePathTest(unittest.TestCase):
|
|||
return gen_challenge_path(challbs, preferences, combinations)
|
||||
|
||||
def test_common_case(self):
|
||||
"""Given DVSNI and HTTP01 with appropriate combos."""
|
||||
challbs = (acme_util.DVSNI_P, acme_util.HTTP01_P)
|
||||
prefs = [challenges.DVSNI]
|
||||
"""Given TLSSNI01 and HTTP01 with appropriate combos."""
|
||||
challbs = (acme_util.TLSSNI01_P, acme_util.HTTP01_P)
|
||||
prefs = [challenges.TLSSNI01]
|
||||
combos = ((0,), (1,))
|
||||
|
||||
# Smart then trivial dumb path test
|
||||
|
|
@ -324,9 +324,9 @@ class GenChallengePathTest(unittest.TestCase):
|
|||
def test_common_case_with_continuity(self):
|
||||
challbs = (acme_util.POP_P,
|
||||
acme_util.RECOVERY_CONTACT_P,
|
||||
acme_util.DVSNI_P,
|
||||
acme_util.TLSSNI01_P,
|
||||
acme_util.HTTP01_P)
|
||||
prefs = [challenges.ProofOfPossession, challenges.DVSNI]
|
||||
prefs = [challenges.ProofOfPossession, challenges.TLSSNI01]
|
||||
combos = acme_util.gen_combos(challbs)
|
||||
self.assertEqual(self._call(challbs, prefs, combos), (0, 2))
|
||||
|
||||
|
|
@ -336,14 +336,14 @@ class GenChallengePathTest(unittest.TestCase):
|
|||
def test_full_cont_server(self):
|
||||
challbs = (acme_util.RECOVERY_CONTACT_P,
|
||||
acme_util.POP_P,
|
||||
acme_util.DVSNI_P,
|
||||
acme_util.TLSSNI01_P,
|
||||
acme_util.HTTP01_P,
|
||||
acme_util.DNS_P)
|
||||
# Typical webserver client that can do everything except DNS
|
||||
# Attempted to make the order realistic
|
||||
prefs = [challenges.ProofOfPossession,
|
||||
challenges.HTTP01,
|
||||
challenges.DVSNI,
|
||||
challenges.TLSSNI01,
|
||||
challenges.RecoveryContact]
|
||||
combos = acme_util.gen_combos(challbs)
|
||||
self.assertEqual(self._call(challbs, prefs, combos), (1, 3))
|
||||
|
|
@ -352,8 +352,8 @@ class GenChallengePathTest(unittest.TestCase):
|
|||
self.assertTrue(self._call(challbs, prefs, None))
|
||||
|
||||
def test_not_supported(self):
|
||||
challbs = (acme_util.POP_P, acme_util.DVSNI_P)
|
||||
prefs = [challenges.DVSNI]
|
||||
challbs = (acme_util.POP_P, acme_util.TLSSNI01_P)
|
||||
prefs = [challenges.TLSSNI01]
|
||||
combos = ((0, 1),)
|
||||
|
||||
self.assertRaises(
|
||||
|
|
@ -411,7 +411,7 @@ class IsPreferredTest(unittest.TestCase):
|
|||
def _call(cls, chall, satisfied):
|
||||
from letsencrypt.auth_handler import is_preferred
|
||||
return is_preferred(chall, satisfied, exclusive_groups=frozenset([
|
||||
frozenset([challenges.DVSNI, challenges.HTTP01]),
|
||||
frozenset([challenges.TLSSNI01, challenges.HTTP01]),
|
||||
frozenset([challenges.DNS, challenges.HTTP01]),
|
||||
]))
|
||||
|
||||
|
|
@ -421,11 +421,11 @@ class IsPreferredTest(unittest.TestCase):
|
|||
def test_mutually_exclusvie(self):
|
||||
self.assertFalse(
|
||||
self._call(
|
||||
acme_util.DVSNI_P, frozenset([acme_util.HTTP01_P])))
|
||||
acme_util.TLSSNI01_P, frozenset([acme_util.HTTP01_P])))
|
||||
|
||||
def test_mutually_exclusive_same_type(self):
|
||||
self.assertTrue(
|
||||
self._call(acme_util.DVSNI_P, frozenset([acme_util.DVSNI_P])))
|
||||
self._call(acme_util.TLSSNI01_P, frozenset([acme_util.TLSSNI01_P])))
|
||||
|
||||
|
||||
class ReportFailedChallsTest(unittest.TestCase):
|
||||
|
|
@ -446,15 +446,15 @@ class ReportFailedChallsTest(unittest.TestCase):
|
|||
domain="example.com",
|
||||
account_key="key")
|
||||
|
||||
kwargs["chall"] = acme_util.DVSNI
|
||||
self.dvsni_same = achallenges.DVSNI(
|
||||
kwargs["chall"] = acme_util.TLSSNI01
|
||||
self.tls_sni_same = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
# pylint: disable=star-args
|
||||
challb=messages.ChallengeBody(**kwargs),
|
||||
domain="example.com",
|
||||
account_key="key")
|
||||
|
||||
kwargs["error"] = messages.Error(typ="dnssec", detail="detail")
|
||||
self.dvsni_diff = achallenges.DVSNI(
|
||||
self.tls_sni_diff = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
# pylint: disable=star-args
|
||||
challb=messages.ChallengeBody(**kwargs),
|
||||
domain="foo.bar",
|
||||
|
|
@ -464,7 +464,7 @@ class ReportFailedChallsTest(unittest.TestCase):
|
|||
def test_same_error_and_domain(self, mock_zope):
|
||||
from letsencrypt import auth_handler
|
||||
|
||||
auth_handler._report_failed_challs([self.http01, self.dvsni_same])
|
||||
auth_handler._report_failed_challs([self.http01, self.tls_sni_same])
|
||||
call_list = mock_zope().add_message.call_args_list
|
||||
self.assertTrue(len(call_list) == 1)
|
||||
self.assertTrue("Domains: example.com\n" in call_list[0][0][0])
|
||||
|
|
@ -473,7 +473,7 @@ class ReportFailedChallsTest(unittest.TestCase):
|
|||
def test_different_errors_and_domains(self, mock_zope):
|
||||
from letsencrypt import auth_handler
|
||||
|
||||
auth_handler._report_failed_challs([self.http01, self.dvsni_diff])
|
||||
auth_handler._report_failed_challs([self.http01, self.tls_sni_diff])
|
||||
self.assertTrue(mock_zope().add_message.call_count == 2)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ class NamespaceConfigTest(unittest.TestCase):
|
|||
self.namespace = mock.MagicMock(
|
||||
config_dir='/tmp/config', work_dir='/tmp/foo', foo='bar',
|
||||
server='https://acme-server.org:443/new',
|
||||
dvsni_port=1234, http01_port=4321)
|
||||
tls_sni_01_port=1234, http01_port=4321)
|
||||
from letsencrypt.configuration import NamespaceConfig
|
||||
self.config = NamespaceConfig(self.namespace)
|
||||
|
||||
def test_init_same_ports(self):
|
||||
self.namespace.dvsni_port = 4321
|
||||
self.namespace.tls_sni_01_port = 4321
|
||||
from letsencrypt.configuration import NamespaceConfig
|
||||
self.assertRaises(errors.Error, NamespaceConfig, self.namespace)
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class PerformTest(unittest.TestCase):
|
|||
def test_unexpected(self):
|
||||
self.assertRaises(
|
||||
errors.ContAuthError, self.auth.perform, [
|
||||
achallenges.DVSNI(
|
||||
achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=None, domain="0", account_key="invalid_key")])
|
||||
|
||||
def test_chall_pref(self):
|
||||
|
|
@ -53,7 +53,7 @@ class CleanupTest(unittest.TestCase):
|
|||
mock.MagicMock(server="demo_server.org"), None)
|
||||
|
||||
def test_unexpected(self):
|
||||
unexpected = achallenges.DVSNI(
|
||||
unexpected = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=None, domain="0", account_key="dummy_key")
|
||||
self.assertRaises(errors.ContAuthError, self.auth.cleanup, [unexpected])
|
||||
|
||||
|
|
|
|||
|
|
@ -688,7 +688,7 @@ class RenewableCertTests(BaseRenewableCertTest):
|
|||
self.test_rc.configfile["renewalparams"]["rsa_key_size"] = "2048"
|
||||
self.test_rc.configfile["renewalparams"]["server"] = "acme.example.com"
|
||||
self.test_rc.configfile["renewalparams"]["authenticator"] = "fake"
|
||||
self.test_rc.configfile["renewalparams"]["dvsni_port"] = "4430"
|
||||
self.test_rc.configfile["renewalparams"]["tls_sni_01_port"] = "4430"
|
||||
self.test_rc.configfile["renewalparams"]["http01_port"] = "1234"
|
||||
self.test_rc.configfile["renewalparams"]["account"] = "abcde"
|
||||
mock_auth = mock.MagicMock()
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ common() {
|
|||
"$@"
|
||||
}
|
||||
|
||||
common --domains le1.wtf --standalone-supported-challenges dvsni auth
|
||||
common --domains le1.wtf --standalone-supported-challenges tls-sni-01 auth
|
||||
common --domains le2.wtf --standalone-supported-challenges http-01 run
|
||||
common -a manual -d le.wtf auth
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ letsencrypt_test () {
|
|||
letsencrypt \
|
||||
--server "${SERVER:-http://localhost:4000/directory}" \
|
||||
--no-verify-ssl \
|
||||
--dvsni-port 5001 \
|
||||
--tls-sni-01-port 5001 \
|
||||
--http-01-port 5002 \
|
||||
--manual-test-mode \
|
||||
$store_flags \
|
||||
|
|
|
|||
Loading…
Reference in a new issue