mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 19:02:52 +02:00
Merge pull request #399 from kuba/get_sans
get_sans_from_csr using pyOpenSSL
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
"""Let's Encrypt client crypto utility functions
|
"""Let's Encrypt client crypto utility functions.
|
||||||
|
|
||||||
.. todo:: Make the transition to use PSS rather than PKCS1_v1_5 when the server
|
.. todo:: Make the transition to use PSS rather than PKCS1_v1_5 when the server
|
||||||
is capable of handling the signatures.
|
is capable of handling the signatures.
|
||||||
@@ -13,6 +13,7 @@ import Crypto.PublicKey.RSA
|
|||||||
import Crypto.Signature.PKCS1_v1_5
|
import Crypto.Signature.PKCS1_v1_5
|
||||||
|
|
||||||
import M2Crypto
|
import M2Crypto
|
||||||
|
import OpenSSL
|
||||||
|
|
||||||
from letsencrypt import le_util
|
from letsencrypt import le_util
|
||||||
|
|
||||||
@@ -231,3 +232,44 @@ def make_ss_cert(key_str, domains, not_before=None,
|
|||||||
assert cert.verify()
|
assert cert.verify()
|
||||||
# print check_purpose(,0
|
# print check_purpose(,0
|
||||||
return cert.as_pem()
|
return cert.as_pem()
|
||||||
|
|
||||||
|
|
||||||
|
def _request_san(req): # TODO: implement directly in PyOpenSSL!
|
||||||
|
# constants based on implementation of
|
||||||
|
# OpenSSL.crypto.X509Error._subjectAltNameString
|
||||||
|
parts_separator = ", "
|
||||||
|
part_separator = ":"
|
||||||
|
extension_short_name = "subjectAltName"
|
||||||
|
|
||||||
|
# pylint: disable=protected-access,no-member
|
||||||
|
label = OpenSSL.crypto.X509Extension._prefixes[OpenSSL.crypto._lib.GEN_DNS]
|
||||||
|
assert parts_separator not in label
|
||||||
|
prefix = label + part_separator
|
||||||
|
|
||||||
|
extensions = [ext._subjectAltNameString().split(parts_separator)
|
||||||
|
for ext in req.get_extensions()
|
||||||
|
if ext.get_short_name() == extension_short_name]
|
||||||
|
# WARNING: this function assumes that no SAN can include
|
||||||
|
# parts_separator, hence the split!
|
||||||
|
|
||||||
|
return [part.split(part_separator)[1] for parts in extensions
|
||||||
|
for part in parts if part.startswith(prefix)]
|
||||||
|
|
||||||
|
|
||||||
|
def get_sans_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM):
|
||||||
|
"""Get list of Subject Alternative Names from signing request.
|
||||||
|
|
||||||
|
:param str csr: Certificate Signing Request in PEM format (must contain
|
||||||
|
one or more subjectAlternativeNames, or the function will fail,
|
||||||
|
raising ValueError)
|
||||||
|
|
||||||
|
:returns: List of referenced subject alternative names
|
||||||
|
:rtype: list
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
request = OpenSSL.crypto.load_certificate_request(typ, csr)
|
||||||
|
except OpenSSL.crypto.Error as error:
|
||||||
|
logging.exception(error)
|
||||||
|
raise
|
||||||
|
return _request_san(request)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import tempfile
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import M2Crypto
|
import M2Crypto
|
||||||
|
import OpenSSL
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
|
||||||
@@ -150,5 +151,41 @@ class MakeSSCertTest(unittest.TestCase):
|
|||||||
make_ss_cert(RSA512_KEY, ['example.com', 'www.example.com'])
|
make_ss_cert(RSA512_KEY, ['example.com', 'www.example.com'])
|
||||||
|
|
||||||
|
|
||||||
|
class GetSansFromCsrTest(unittest.TestCase):
|
||||||
|
"""Tests for letsencrypt.crypto_util.get_sans_from_csr."""
|
||||||
|
def test_extract_one_san(self):
|
||||||
|
from letsencrypt.crypto_util import get_sans_from_csr
|
||||||
|
csr = pkg_resources.resource_string(
|
||||||
|
__name__, os.path.join('testdata', 'csr.pem'))
|
||||||
|
self.assertEqual(get_sans_from_csr(csr), ['example.com'])
|
||||||
|
|
||||||
|
def test_extract_two_sans(self):
|
||||||
|
from letsencrypt.crypto_util import get_sans_from_csr
|
||||||
|
csr = pkg_resources.resource_string(
|
||||||
|
__name__, os.path.join('testdata', 'csr-san.pem'))
|
||||||
|
self.assertEqual(get_sans_from_csr(csr), ['example.com',
|
||||||
|
'www.example.com'])
|
||||||
|
|
||||||
|
def test_extract_six_sans(self):
|
||||||
|
from letsencrypt.crypto_util import get_sans_from_csr
|
||||||
|
csr = pkg_resources.resource_string(
|
||||||
|
__name__, os.path.join('testdata', 'csr-6sans.pem'))
|
||||||
|
self.assertEqual(get_sans_from_csr(csr),
|
||||||
|
["example.com", "example.org", "example.net",
|
||||||
|
"example.info", "subdomain.example.com",
|
||||||
|
"other.subdomain.example.com"])
|
||||||
|
|
||||||
|
def test_parse_non_csr(self):
|
||||||
|
from letsencrypt.crypto_util import get_sans_from_csr
|
||||||
|
self.assertRaises(OpenSSL.crypto.Error, get_sans_from_csr,
|
||||||
|
"hello there")
|
||||||
|
|
||||||
|
def test_parse_no_sans(self):
|
||||||
|
from letsencrypt.crypto_util import get_sans_from_csr
|
||||||
|
csr = pkg_resources.resource_string(
|
||||||
|
__name__, os.path.join('testdata', 'csr-nosans.pem'))
|
||||||
|
self.assertEqual([], get_sans_from_csr(csr))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main() # pragma: no cover
|
unittest.main() # pragma: no cover
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIBuzCCAWUCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE1pY2hpZ2FuMRIw
|
||||||
|
EAYDVQQHEwlBbm4gQXJib3IxDDAKBgNVBAoTA0VGRjEfMB0GA1UECxMWVW5pdmVy
|
||||||
|
c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wXDANBgkqhkiG
|
||||||
|
9w0BAQEFAANLADBIAkEA9LYRcVE3Nr+qleecEcX8JwVDnjeG1X7ucsCasuuZM0e0
|
||||||
|
9cmYuUzxIkMjO/9x4AVcvXXRXPEV+LzWWkfkTlzRMwIDAQABoIGGMIGDBgkqhkiG
|
||||||
|
9w0BCQ4xdjB0MHIGA1UdEQRrMGmCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZ4IL
|
||||||
|
ZXhhbXBsZS5uZXSCDGV4YW1wbGUuaW5mb4IVc3ViZG9tYWluLmV4YW1wbGUuY29t
|
||||||
|
ghtvdGhlci5zdWJkb21haW4uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADQQBd
|
||||||
|
k4BE5qvEvkYoZM/2++Xd9RrQ6wsdj0QiJQCozfsI4lQx6ZJnbtNc7HpDrX4W6XIv
|
||||||
|
IvzVBz/nD11drfz/RNuX
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIBFTCBwAIBADBbMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
|
||||||
|
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQDDAtleGFt
|
||||||
|
cGxlLm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQD0thFxUTc2v6qV55wRxfwn
|
||||||
|
BUOeN4bVfu5ywJqy65kzR7T1yZi5TPEiQyM7/3HgBVy9ddFc8RX4vNZaR+ROXNEz
|
||||||
|
AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAMikGL8Ch7hQCStXH7chhDp6+pt2+VSo
|
||||||
|
wgsrPQ2Bw4veDMlSemUrH+4e0TwbbntHfvXTDHWs9P3BiIDJLxFrjuA=
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
@@ -38,7 +38,8 @@ install_requires = [
|
|||||||
'psutil>=2.1.0', # net_connections introduced in 2.1.0
|
'psutil>=2.1.0', # net_connections introduced in 2.1.0
|
||||||
'pyasn1', # urllib3 InsecurePlatformWarning (#304)
|
'pyasn1', # urllib3 InsecurePlatformWarning (#304)
|
||||||
'pycrypto',
|
'pycrypto',
|
||||||
'PyOpenSSL',
|
# https://pyopenssl.readthedocs.org/en/latest/api/crypto.html#OpenSSL.crypto.X509Req.get_extensions
|
||||||
|
'PyOpenSSL>=0.15',
|
||||||
'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary?
|
'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary?
|
||||||
'pyrfc3339',
|
'pyrfc3339',
|
||||||
'python-augeas',
|
'python-augeas',
|
||||||
|
|||||||
Reference in New Issue
Block a user