mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 19:02:52 +02:00
DVSNIResponse.gen_cert, fix verify_cert, add tests.
This commit is contained in:
+42
-15
@@ -6,6 +6,8 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography import x509
|
||||||
import OpenSSL
|
import OpenSSL
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
@@ -245,10 +247,25 @@ class DVSNIResponse(ChallengeResponse):
|
|||||||
return z.hexdigest().encode()
|
return z.hexdigest().encode()
|
||||||
|
|
||||||
def z_domain(self, chall):
|
def z_domain(self, chall):
|
||||||
"""Domain name for certificate subjectAltName."""
|
"""Domain name for certificate subjectAltName.
|
||||||
|
|
||||||
|
:rtype bytes:
|
||||||
|
|
||||||
|
"""
|
||||||
return self.z(chall) + self.DOMAIN_SUFFIX
|
return self.z(chall) + self.DOMAIN_SUFFIX
|
||||||
|
|
||||||
def simple_verify(self, chall, domain, key, **kwargs):
|
def gen_cert(self, chall, domain, key):
|
||||||
|
"""Generate DVSNI certificate.
|
||||||
|
|
||||||
|
:param .DVSNI chall: Corresponding challenge.
|
||||||
|
:param unicode domain:
|
||||||
|
:param OpenSSL.crypto.PKey
|
||||||
|
|
||||||
|
"""
|
||||||
|
return crypto_util.gen_ss_cert(key, [
|
||||||
|
domain, chall.nonce_domain.decode(), self.z_domain(chall).decode()])
|
||||||
|
|
||||||
|
def simple_verify(self, chall, domain, public_key, **kwargs):
|
||||||
"""Simple verify.
|
"""Simple verify.
|
||||||
|
|
||||||
Probes DVSNI certificate and checks it using `verify_cert`;
|
Probes DVSNI certificate and checks it using `verify_cert`;
|
||||||
@@ -260,37 +277,47 @@ class DVSNIResponse(ChallengeResponse):
|
|||||||
except errors.Error as error:
|
except errors.Error as error:
|
||||||
logger.debug(error, exc_info=True)
|
logger.debug(error, exc_info=True)
|
||||||
return False
|
return False
|
||||||
# TODO: check "It is a valid self-signed certificate" and
|
return self.verify_cert(chall, domain, public_key, cert)
|
||||||
# return False if not
|
|
||||||
return self.verify_cert(chall, domain, key, cert)
|
|
||||||
|
|
||||||
def verify_cert(self, chall, domain, key, cert):
|
def verify_cert(self, chall, domain, public_key, cert):
|
||||||
"""Verify DVSNI certificate.
|
"""Verify DVSNI certificate.
|
||||||
|
|
||||||
:param .challenges.DVSNI chall: Corresponding challenge.
|
:param .challenges.DVSNI chall: Corresponding challenge.
|
||||||
:param str domain: Domain name being validated.
|
:param str domain: Domain name being validated.
|
||||||
:param OpenSSL.crypto.PKey key: Public key for the key pair
|
:param public_key: Public key for the key pair
|
||||||
being authorized. If ``None`` key verification is not
|
being authorized. If ``None`` key verification is not
|
||||||
performed!
|
performed!
|
||||||
|
:type public_key:
|
||||||
|
`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`
|
||||||
|
or
|
||||||
|
`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`
|
||||||
|
or
|
||||||
|
`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`
|
||||||
|
wrapped in `.ComparableKey
|
||||||
|
:param OpenSSL.crypto.X509 cert:
|
||||||
|
|
||||||
:returns: ``True`` iff client's control of the domain has been
|
:returns: ``True`` iff client's control of the domain has been
|
||||||
verified, ``False`` otherwise.
|
verified, ``False`` otherwise.
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# TODO: check "It is a valid self-signed certificate" and
|
||||||
|
# return False if not
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
sans = crypto_util._pyopenssl_cert_or_req_san(cert)
|
sans = crypto_util._pyopenssl_cert_or_req_san(cert)
|
||||||
logging.debug('Certificate %s. SANs: %s', cert.digest('sha1'), sans)
|
logging.debug('Certificate %s. SANs: %s', cert.digest('sha1'), sans)
|
||||||
|
|
||||||
key_filetype = OpenSSL.crypto.FILETYPE_ASN1
|
cert = x509.load_der_x509_certificate(
|
||||||
if key is None:
|
OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, cert),
|
||||||
logging.warn('No key verification is performed')
|
default_backend())
|
||||||
keys_match = key is None or OpenSSL.crypto.dump_privatekey(
|
|
||||||
key_filetype, key) == OpenSSL.crypto.dump_privatekey(
|
|
||||||
key_filetype, cert.get_pubkey())
|
|
||||||
|
|
||||||
return (keys_match and domain in sans and
|
if public_key is None:
|
||||||
self.z_domain(chall).decode() in sans)
|
logging.warn('No key verification is performed')
|
||||||
|
elif public_key != jose.ComparableKey(cert.public_key()):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return domain in sans and self.z_domain(chall).decode() in sans
|
||||||
|
|
||||||
|
|
||||||
@Challenge.register
|
@Challenge.register
|
||||||
|
|||||||
@@ -210,26 +210,31 @@ class DVSNIResponseTest(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
from acme.challenges import DVSNIResponse
|
from acme.challenges import DVSNIResponse
|
||||||
self.msg = DVSNIResponse(
|
# pylint: disable=invalid-name
|
||||||
s=b'\xf5\xd6\xe3\xb2]\xe0L\x0bN\x9cKJ\x14I\xa1K\xa3#\xf9\xa8'
|
s = '9dbjsl3gTAtOnEtKFEmhS6Mj-ajNjDcOmRkp3Lfzm3c'
|
||||||
b'\xcd\x8c7\x0e\x99\x19)\xdc\xb7\xf3\x9bw')
|
self.msg = DVSNIResponse(s=jose.decode_b64jose(s))
|
||||||
self.jmsg = {
|
self.jmsg = {
|
||||||
'resource': 'challenge',
|
'resource': 'challenge',
|
||||||
'type': 'dvsni',
|
'type': 'dvsni',
|
||||||
's': '9dbjsl3gTAtOnEtKFEmhS6Mj-ajNjDcOmRkp3Lfzm3c',
|
's': s,
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_z_and_domain(self):
|
|
||||||
from acme.challenges import DVSNI
|
from acme.challenges import DVSNI
|
||||||
challenge = DVSNI(
|
self.chall = DVSNI(
|
||||||
r=b"O*\xb4-\xad\xec\x95>\xed\xa9\r0\x94\xe8\x97\x9c&6"
|
r=jose.decode_b64jose('Tyq0La3slT7tqQ0wlOiXnCY2vyez7Zo5blgPJ1xt5xI'),
|
||||||
b"\xbf'\xb3\xed\x9a9nX\x0f'\\m\xe7\x12",
|
nonce=jose.decode_b64jose('a82d5ff8ef740d12881f6d3c2277ab2e'))
|
||||||
nonce=int('439736375371401115242521957580409149254868992063'
|
self.z = (b'38e612b0397cc2624a07d351d7ef50e4'
|
||||||
'44333654741504362774620418661'))
|
b'6134c0213d9ed52f7d7c611acaeed41b')
|
||||||
|
self.domain = 'foo.com'
|
||||||
|
self.key = test_util.load_pyopenssl_private_key('rsa512_key.pem')
|
||||||
|
self.public_key = test_util.load_rsa_private_key(
|
||||||
|
'rsa512_key.pem').public_key()
|
||||||
|
|
||||||
|
def test_z_and_domain(self):
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
z = b'38e612b0397cc2624a07d351d7ef50e46134c0213d9ed52f7d7c611acaeed41b'
|
self.assertEqual(self.z, self.msg.z(self.chall))
|
||||||
self.assertEqual(z, self.msg.z(challenge))
|
self.assertEqual(
|
||||||
self.assertEqual(z + b'.acme.invalid', self.msg.z_domain(challenge))
|
self.z + b'.acme.invalid', self.msg.z_domain(self.chall))
|
||||||
|
|
||||||
def test_to_partial_json(self):
|
def test_to_partial_json(self):
|
||||||
self.assertEqual(self.jmsg, self.msg.to_partial_json())
|
self.assertEqual(self.jmsg, self.msg.to_partial_json())
|
||||||
@@ -258,18 +263,28 @@ class DVSNIResponseTest(unittest.TestCase):
|
|||||||
chall = mock.Mock()
|
chall = mock.Mock()
|
||||||
chall.probe_cert.side_effect = errors.Error
|
chall.probe_cert.side_effect = errors.Error
|
||||||
self.assertFalse(self.msg.simple_verify(
|
self.assertFalse(self.msg.simple_verify(
|
||||||
chall=chall, domain=None, key=None))
|
chall=chall, domain=None, public_key=None))
|
||||||
|
|
||||||
def test_verify_cert_postive(self):
|
def test_gen_verify_cert_postive_no_key(self):
|
||||||
pass # XXX
|
cert = self.msg.gen_cert(self.chall, self.domain, self.key)
|
||||||
|
self.assertTrue(self.msg.verify_cert(
|
||||||
|
self.chall, self.domain, public_key=None, cert=cert))
|
||||||
|
|
||||||
def test_verify_cert_negative(self):
|
def test_gen_verify_cert_postive_with_key(self):
|
||||||
chall = mock.MagicMock()
|
cert = self.msg.gen_cert(self.chall, self.domain, self.key)
|
||||||
|
self.assertTrue(self.msg.verify_cert(
|
||||||
|
self.chall, self.domain, public_key=self.public_key, cert=cert))
|
||||||
|
|
||||||
|
def test_gen_verify_cert_negative_with_wrong_key(self):
|
||||||
|
cert = self.msg.gen_cert(self.chall, self.domain, self.key)
|
||||||
|
key = test_util.load_rsa_private_key('rsa256_key.pem').public_key()
|
||||||
self.assertFalse(self.msg.verify_cert(
|
self.assertFalse(self.msg.verify_cert(
|
||||||
chall, domain="example.com", key=None,
|
self.chall, self.domain, public_key=key, cert=cert))
|
||||||
cert=OpenSSL.crypto.load_certificate(
|
|
||||||
OpenSSL.crypto.FILETYPE_PEM,
|
def test_gen_verify_cert_negative(self):
|
||||||
test_util.load_vector('cert.pem'))))
|
cert = self.msg.gen_cert(self.chall, self.domain + 'x', self.key)
|
||||||
|
self.assertFalse(self.msg.verify_cert(
|
||||||
|
self.chall, self.domain, public_key=None, cert=cert))
|
||||||
|
|
||||||
|
|
||||||
class RecoveryContactTest(unittest.TestCase):
|
class RecoveryContactTest(unittest.TestCase):
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ def _pyopenssl_cert_or_req_san(cert_or_req):
|
|||||||
:type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
|
:type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
|
||||||
|
|
||||||
:returns: A list of Subject Alternative Names.
|
:returns: A list of Subject Alternative Names.
|
||||||
:rtype: list
|
:rtype: `list` of `unicode`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# constants based on implementation of
|
# constants based on implementation of
|
||||||
@@ -153,3 +153,43 @@ def _pyopenssl_cert_or_req_san(cert_or_req):
|
|||||||
|
|
||||||
return [part.split(part_separator)[1] for parts in san_extensions
|
return [part.split(part_separator)[1] for parts in san_extensions
|
||||||
for part in parts if part.startswith(prefix)]
|
for part in parts if part.startswith(prefix)]
|
||||||
|
|
||||||
|
|
||||||
|
def gen_ss_cert(key, domains, not_before=None, validity=(7 * 24 * 60 * 60)):
|
||||||
|
"""Generate new self-signed certificate.
|
||||||
|
|
||||||
|
:type domains: `list` of `unicode`
|
||||||
|
:param OpenSSL.crypto.PKey key:
|
||||||
|
|
||||||
|
Uses key and contains all domains.
|
||||||
|
|
||||||
|
"""
|
||||||
|
assert domains, "Must provide one or more hostnames for the cert."
|
||||||
|
cert = OpenSSL.crypto.X509()
|
||||||
|
cert.set_serial_number(1337)
|
||||||
|
cert.set_version(2)
|
||||||
|
|
||||||
|
extensions = [
|
||||||
|
OpenSSL.crypto.X509Extension(
|
||||||
|
b"basicConstraints", True, b"CA:TRUE, pathlen:0"),
|
||||||
|
]
|
||||||
|
|
||||||
|
cert.get_subject().CN = domains[0]
|
||||||
|
# TODO: what to put into cert.get_subject()?
|
||||||
|
cert.set_issuer(cert.get_subject())
|
||||||
|
|
||||||
|
if len(domains) > 1:
|
||||||
|
extensions.append(OpenSSL.crypto.X509Extension(
|
||||||
|
b"subjectAltName",
|
||||||
|
critical=False,
|
||||||
|
value=b", ".join(b"DNS:" + d.encode() for d in domains)
|
||||||
|
))
|
||||||
|
|
||||||
|
cert.add_extensions(extensions)
|
||||||
|
|
||||||
|
cert.gmtime_adj_notBefore(0 if not_before is None else not_before)
|
||||||
|
cert.gmtime_adj_notAfter(validity)
|
||||||
|
|
||||||
|
cert.set_pubkey(key)
|
||||||
|
cert.sign(key, "sha256")
|
||||||
|
return cert
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ from acme.jose.jws import (
|
|||||||
|
|
||||||
from acme.jose.util import (
|
from acme.jose.util import (
|
||||||
ComparableX509,
|
ComparableX509,
|
||||||
|
ComparableKey,
|
||||||
ComparableRSAKey,
|
ComparableRSAKey,
|
||||||
ImmutableMap,
|
ImmutableMap,
|
||||||
)
|
)
|
||||||
|
|||||||
+26
-16
@@ -66,14 +66,14 @@ class ComparableX509(object): # pylint: disable=too-few-public-methods
|
|||||||
return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped)
|
return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped)
|
||||||
|
|
||||||
|
|
||||||
class ComparableRSAKey(object): # pylint: disable=too-few-public-methods
|
class ComparableKey(object): # pylint: disable=too-few-public-methods
|
||||||
"""Wrapper for `cryptography` RSA keys.
|
"""Comparable wrapper for `cryptography` keys.
|
||||||
|
|
||||||
Wraps around:
|
See https://github.com/pyca/cryptography/issues/2122.
|
||||||
- `cryptography.hazmat.primitives.assymetric.RSAPrivateKey`
|
|
||||||
- `cryptography.hazmat.primitives.assymetric.RSAPublicKey`
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
__hash__ = NotImplemented
|
||||||
|
|
||||||
def __init__(self, wrapped):
|
def __init__(self, wrapped):
|
||||||
self._wrapped = wrapped
|
self._wrapped = wrapped
|
||||||
|
|
||||||
@@ -85,19 +85,36 @@ class ComparableRSAKey(object): # pylint: disable=too-few-public-methods
|
|||||||
if (not isinstance(other, self.__class__) or
|
if (not isinstance(other, self.__class__) or
|
||||||
self._wrapped.__class__ is not other._wrapped.__class__):
|
self._wrapped.__class__ is not other._wrapped.__class__):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
# RSA*KeyWithSerialization requires cryptography>=0.8
|
elif hasattr(self._wrapped, 'private_numbers'):
|
||||||
if isinstance(self._wrapped, rsa.RSAPrivateKeyWithSerialization):
|
|
||||||
return self.private_numbers() == other.private_numbers()
|
return self.private_numbers() == other.private_numbers()
|
||||||
elif isinstance(self._wrapped, rsa.RSAPublicKeyWithSerialization):
|
elif hasattr(self._wrapped, 'public_numbers'):
|
||||||
return self.public_numbers() == other.public_numbers()
|
return self.public_numbers() == other.public_numbers()
|
||||||
else:
|
else:
|
||||||
return False # we shouldn't reach here...
|
return NotImplemented
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self == other
|
return not self == other
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped)
|
||||||
|
|
||||||
|
def public_key(self):
|
||||||
|
"""Get wrapped public key."""
|
||||||
|
return self.__class__(self._wrapped.public_key())
|
||||||
|
|
||||||
|
|
||||||
|
class ComparableRSAKey(ComparableKey): # pylint: disable=too-few-public-methods
|
||||||
|
"""Wrapper for `cryptography` RSA keys.
|
||||||
|
|
||||||
|
Wraps around:
|
||||||
|
- `cryptography.hazmat.primitives.assymetric.RSAPrivateKey`
|
||||||
|
- `cryptography.hazmat.primitives.assymetric.RSAPublicKey`
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
# public_numbers() hasn't got stable hash!
|
# public_numbers() hasn't got stable hash!
|
||||||
|
# https://github.com/pyca/cryptography/issues/2143
|
||||||
if isinstance(self._wrapped, rsa.RSAPrivateKeyWithSerialization):
|
if isinstance(self._wrapped, rsa.RSAPrivateKeyWithSerialization):
|
||||||
priv = self.private_numbers()
|
priv = self.private_numbers()
|
||||||
pub = priv.public_numbers
|
pub = priv.public_numbers
|
||||||
@@ -107,13 +124,6 @@ class ComparableRSAKey(object): # pylint: disable=too-few-public-methods
|
|||||||
pub = self.public_numbers()
|
pub = self.public_numbers()
|
||||||
return hash((self.__class__, pub.n, pub.e))
|
return hash((self.__class__, pub.n, pub.e))
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped)
|
|
||||||
|
|
||||||
def public_key(self):
|
|
||||||
"""Get wrapped public key."""
|
|
||||||
return self.__class__(self._wrapped.public_key())
|
|
||||||
|
|
||||||
|
|
||||||
class ImmutableMap(collections.Mapping, collections.Hashable):
|
class ImmutableMap(collections.Mapping, collections.Hashable):
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
|
|||||||
@@ -55,3 +55,9 @@ def load_rsa_private_key(*names):
|
|||||||
serialization.load_der_private_key)
|
serialization.load_der_private_key)
|
||||||
return jose.ComparableRSAKey(loader(
|
return jose.ComparableRSAKey(loader(
|
||||||
load_vector(*names), password=None, backend=default_backend()))
|
load_vector(*names), password=None, backend=default_backend()))
|
||||||
|
|
||||||
|
def load_pyopenssl_private_key(*names):
|
||||||
|
"""Load pyOpenSSL private key."""
|
||||||
|
loader = _guess_loader(
|
||||||
|
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
|
||||||
|
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import socket
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import OpenSSL
|
||||||
import zope.interface
|
import zope.interface
|
||||||
|
|
||||||
from acme import challenges
|
from acme import challenges
|
||||||
|
from acme import crypto_util as acme_crypto_util
|
||||||
|
|
||||||
from letsencrypt import achallenges
|
from letsencrypt import achallenges
|
||||||
from letsencrypt import constants as core_constants
|
from letsencrypt import constants as core_constants
|
||||||
@@ -271,14 +273,17 @@ class NginxConfigurator(common.Plugin):
|
|||||||
def _get_snakeoil_paths(self):
|
def _get_snakeoil_paths(self):
|
||||||
# TODO: generate only once
|
# TODO: generate only once
|
||||||
tmp_dir = os.path.join(self.config.work_dir, "snakeoil")
|
tmp_dir = os.path.join(self.config.work_dir, "snakeoil")
|
||||||
key = crypto_util.init_save_key(
|
le_key = crypto_util.init_save_key(
|
||||||
key_size=1024, key_dir=tmp_dir, keyname="key.pem")
|
key_size=1024, key_dir=tmp_dir, keyname="key.pem")
|
||||||
cert_pem = crypto_util.make_ss_cert(
|
key = OpenSSL.crypto.load_privatekey(
|
||||||
key.pem, domains=[socket.gethostname()])
|
OpenSSL.crypto.FILETYPE_PEM, le_key.pem)
|
||||||
cert = os.path.join(tmp_dir, "cert.pem")
|
cert = acme_crypto_util.gen_ss_cert(key, domains=[socket.gethostname()])
|
||||||
with open(cert, 'w') as cert_file:
|
cert_path = os.path.join(tmp_dir, "cert.pem")
|
||||||
|
cert_pem = OpenSSL.crypto.dump_certificate(
|
||||||
|
OpenSSL.crypto.FILETYPE_PEM, cert)
|
||||||
|
with open(cert_path, 'w') as cert_file:
|
||||||
cert_file.write(cert_pem)
|
cert_file.write(cert_pem)
|
||||||
return cert, key.file
|
return cert_path, le_key.file
|
||||||
|
|
||||||
def _make_server_ssl(self, vhost):
|
def _make_server_ssl(self, vhost):
|
||||||
"""Make a server SSL.
|
"""Make a server SSL.
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ Note, that all annotated challenges act as a proxy objects::
|
|||||||
achall.token == challb.token
|
achall.token == challb.token
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import OpenSSL
|
||||||
|
|
||||||
from acme import challenges
|
from acme import challenges
|
||||||
from acme.jose import util as jose_util
|
from acme.jose import util as jose_util
|
||||||
|
|
||||||
@@ -48,7 +50,7 @@ class DVSNI(AnnotatedChallenge):
|
|||||||
acme_type = challenges.DVSNI
|
acme_type = challenges.DVSNI
|
||||||
|
|
||||||
def gen_cert_and_response(self, s=None): # pylint: disable=invalid-name
|
def gen_cert_and_response(self, s=None): # pylint: disable=invalid-name
|
||||||
"""Generate a DVSNI cert and save it to filepath.
|
"""Generate a DVSNI cert and response.
|
||||||
|
|
||||||
:returns: ``(cert_pem, response)`` tuple, where ``cert_pem`` is the PEM
|
:returns: ``(cert_pem, response)`` tuple, where ``cert_pem`` is the PEM
|
||||||
encoded certificate and ``response`` is an instance
|
encoded certificate and ``response`` is an instance
|
||||||
@@ -56,9 +58,12 @@ class DVSNI(AnnotatedChallenge):
|
|||||||
:rtype: tuple
|
:rtype: tuple
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
key = crypto_util.private_jwk_to_pyopenssl(self.key)
|
||||||
response = challenges.DVSNIResponse(s=s)
|
response = challenges.DVSNIResponse(s=s)
|
||||||
cert_pem = crypto_util.make_ss_cert(self.key, [
|
cert = response.gen_cert(self.challb.chall, self.domain, key)
|
||||||
self.domain, self.nonce_domain, response.z_domain(self.challb)])
|
cert_pem = OpenSSL.crypto.dump_certificate(
|
||||||
|
OpenSSL.crypto.FILETYPE_PEM, cert)
|
||||||
|
|
||||||
return cert_pem, response
|
return cert_pem, response
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from cryptography.hazmat.primitives import serialization
|
|||||||
import OpenSSL
|
import OpenSSL
|
||||||
|
|
||||||
from acme import crypto_util as acme_crypto_util
|
from acme import crypto_util as acme_crypto_util
|
||||||
from acme import jose
|
|
||||||
|
|
||||||
from letsencrypt import errors
|
from letsencrypt import errors
|
||||||
from letsencrypt import le_util
|
from letsencrypt import le_util
|
||||||
@@ -216,49 +215,13 @@ def pyopenssl_load_certificate(data):
|
|||||||
return _pyopenssl_load(data, OpenSSL.crypto.load_certificate)
|
return _pyopenssl_load(data, OpenSSL.crypto.load_certificate)
|
||||||
|
|
||||||
|
|
||||||
def make_ss_cert(key, domains, not_before=None,
|
def private_jwk_to_pyopenssl(jwk):
|
||||||
validity=(7 * 24 * 60 * 60)):
|
"""Convert private JWK to pyOpenSSL key."""
|
||||||
"""Returns new self-signed cert in PEM form.
|
key_pem = jwk.key.private_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
Uses key and contains all domains.
|
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||||
|
encryption_algorithm=serialization.NoEncryption())
|
||||||
"""
|
return OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_pem)
|
||||||
if isinstance(key, jose.JWK):
|
|
||||||
key = key.key.private_bytes(
|
|
||||||
encoding=serialization.Encoding.PEM,
|
|
||||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
|
||||||
encryption_algorithm=serialization.NoEncryption())
|
|
||||||
|
|
||||||
assert domains, "Must provide one or more hostnames for the cert."
|
|
||||||
pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
|
|
||||||
cert = OpenSSL.crypto.X509()
|
|
||||||
cert.set_serial_number(1337)
|
|
||||||
cert.set_version(2)
|
|
||||||
|
|
||||||
extensions = [
|
|
||||||
OpenSSL.crypto.X509Extension(
|
|
||||||
"basicConstraints", True, 'CA:TRUE, pathlen:0'),
|
|
||||||
]
|
|
||||||
|
|
||||||
cert.get_subject().CN = domains[0]
|
|
||||||
# TODO: what to put into cert.get_subject()?
|
|
||||||
cert.set_issuer(cert.get_subject())
|
|
||||||
|
|
||||||
if len(domains) > 1:
|
|
||||||
extensions.append(OpenSSL.crypto.X509Extension(
|
|
||||||
"subjectAltName",
|
|
||||||
critical=False,
|
|
||||||
value=", ".join("DNS:%s" % d for d in domains)
|
|
||||||
))
|
|
||||||
|
|
||||||
cert.add_extensions(extensions)
|
|
||||||
|
|
||||||
cert.gmtime_adj_notBefore(0 if not_before is None else not_before)
|
|
||||||
cert.gmtime_adj_notAfter(validity)
|
|
||||||
|
|
||||||
cert.set_pubkey(pkey)
|
|
||||||
cert.sign(pkey, "sha256")
|
|
||||||
return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_sans_from_cert_or_req(
|
def _get_sans_from_cert_or_req(
|
||||||
|
|||||||
@@ -160,15 +160,6 @@ class ValidPrivkeyTest(unittest.TestCase):
|
|||||||
self.assertFalse(self._call('foo bar'))
|
self.assertFalse(self._call('foo bar'))
|
||||||
|
|
||||||
|
|
||||||
class MakeSSCertTest(unittest.TestCase):
|
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
"""Tests for letsencrypt.crypto_util.make_ss_cert."""
|
|
||||||
|
|
||||||
def test_it(self): # pylint: disable=no-self-use
|
|
||||||
from letsencrypt.crypto_util import make_ss_cert
|
|
||||||
make_ss_cert(RSA512_KEY, ['example.com', 'www.example.com'])
|
|
||||||
|
|
||||||
|
|
||||||
class GetSANsFromCertTest(unittest.TestCase):
|
class GetSANsFromCertTest(unittest.TestCase):
|
||||||
"""Tests for letsencrypt.crypto_util.get_sans_from_cert."""
|
"""Tests for letsencrypt.crypto_util.get_sans_from_cert."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user