Remove OpenSSL from cert_parser

Utilize cryptography package instead of OpenSSL in the cert_parser

Change-Id: I66aa62d7f1f9ce1898bbb8593e4aedd1a1163137
Closes-Bug: #1532344
This commit is contained in:
ptoohill1 2016-01-08 16:33:19 -06:00
parent 578a750181
commit bb34d71c77
2 changed files with 97 additions and 94 deletions

View File

@ -15,16 +15,18 @@
from cryptography.hazmat import backends from cryptography.hazmat import backends
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
import neutron_lbaas.common.exceptions as exceptions from cryptography import x509
from OpenSSL import crypto from neutron.i18n import _LE
from OpenSSL import SSL from oslo_log import log as logging
import pyasn1.codec.der.decoder as decoder
import pyasn1_modules.rfc2459 as rfc2459
import six import six
import neutron_lbaas.common.exceptions as exceptions
X509_BEG = "-----BEGIN CERTIFICATE-----" X509_BEG = "-----BEGIN CERTIFICATE-----"
X509_END = "-----END CERTIFICATE-----" X509_END = "-----END CERTIFICATE-----"
LOG = logging.getLogger(__name__)
def validate_cert(certificate, private_key=None, def validate_cert(certificate, private_key=None,
private_key_passphrase=None, intermediates=None): private_key_passphrase=None, intermediates=None):
@ -40,32 +42,32 @@ def validate_cert(certificate, private_key=None,
:param intermediates: PEM encoded intermediate certificates :param intermediates: PEM encoded intermediate certificates
:returns: boolean :returns: boolean
""" """
x509 = _get_x509_from_pem_bytes(certificate)
cert = _get_x509_from_pem_bytes(certificate)
if intermediates: if intermediates:
for x509Pem in _split_x509s(intermediates): for x509Pem in _split_x509s(intermediates):
_get_x509_from_pem_bytes(x509Pem) _get_x509_from_pem_bytes(x509Pem)
if private_key: if private_key:
pkey = _read_privatekey(private_key, passphrase=private_key_passphrase) pkey = _read_privatekey(private_key, passphrase=private_key_passphrase)
ctx = SSL.Context(SSL.TLSv1_METHOD) pknum = pkey.public_key().public_numbers()
ctx.use_certificate(x509) certnum = cert.public_key().public_numbers()
try: if pknum != certnum:
ctx.use_privatekey(pkey)
ctx.check_privatekey()
except Exception:
raise exceptions.MisMatchedKey raise exceptions.MisMatchedKey
return True return True
def _read_privatekey(privatekey_pem, passphrase=None): def _read_privatekey(privatekey_pem, passphrase=None):
def cb(*args): if passphrase:
if passphrase: if six.PY2:
if six.PY2: passphrase = passphrase.encode("utf-8")
return passphrase.encode("utf-8") elif six.PY3:
elif six.PY3: passphrase = six.b(passphrase)
return six.b(passphrase)
else: try:
raise exceptions.NeedsPassphrase return serialization.load_pem_private_key(privatekey_pem, passphrase,
return crypto.load_privatekey(crypto.FILETYPE_PEM, privatekey_pem, cb) backends.default_backend())
except Exception:
raise exceptions.NeedsPassphrase
def _split_x509s(x509Str): def _split_x509s(x509Str):
@ -128,8 +130,7 @@ def dump_private_key(private_key, private_key_passphrase=None):
def get_host_names(certificate): def get_host_names(certificate):
""" """Extract the host names from the Pem encoded X509 certificate
Extract the host names from the Pem encoded X509 certificate
:param certificate: A PEM encoded certificate :param certificate: A PEM encoded certificate
:returns: A dictionary containing the following keys: :returns: A dictionary containing the following keys:
@ -138,27 +139,29 @@ def get_host_names(certificate):
'dns_names' is a list of dNSNames (possibly empty) from 'dns_names' is a list of dNSNames (possibly empty) from
the SubjectAltNames of the certificate. the SubjectAltNames of the certificate.
""" """
try:
certificate = certificate.encode('ascii')
x509 = _get_x509_from_pem_bytes(certificate) cert = _get_x509_from_pem_bytes(certificate)
hostNames = {} cn = cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0]
if hasattr(x509.get_subject(), 'CN'): host_names = {
hostNames['cn'] = x509.get_subject().CN 'cn': cn.value.lower(),
hostNames['dns_names'] = [] 'dns_names': []
num_exts = x509.get_extension_count() }
for i in range(0, num_exts): try:
ext = x509.get_extension(i) ext = cert.extensions.get_extension_for_oid(
short_name = ext.get_short_name() x509.OID_SUBJECT_ALTERNATIVE_NAME
if short_name == six.b('subjectAltName'): )
data = ext.get_data() host_names['dns_names'] = ext.value.get_values_for_type(
general_names_container = decoder.decode( x509.DNSName)
data, asn1Spec=rfc2459.GeneralNames()) except x509.ExtensionNotFound:
for general_names in general_names_container[0]: LOG.debug("%s extension not found",
currName = general_names.getName() x509.OID_SUBJECT_ALTERNATIVE_NAME)
if currName == 'dNSName':
octets = general_names.getComponent().asOctets() return host_names
decoded = octets.decode("utf-8") except Exception:
hostNames['dns_names'].append(decoded) LOG.exception(_LE("Unreadable certificate."))
return hostNames raise exceptions.UnreadableCert
def _get_x509_from_pem_bytes(certificate_pem): def _get_x509_from_pem_bytes(certificate_pem):
@ -166,11 +169,11 @@ def _get_x509_from_pem_bytes(certificate_pem):
Parse X509 data from a PEM encoded certificate Parse X509 data from a PEM encoded certificate
:param certificate_pem: Certificate in PEM format :param certificate_pem: Certificate in PEM format
:returns: pyOpenSSL high-level x509 data from the PEM string :returns: crypto high-level x509 data from the PEM string
""" """
try: try:
x509 = crypto.load_certificate(crypto.FILETYPE_PEM, x509cert = x509.load_pem_x509_certificate(certificate_pem,
certificate_pem) backends.default_backend())
except Exception: except Exception:
raise exceptions.UnreadableCert raise exceptions.UnreadableCert
return x509 return x509cert

View File

@ -13,49 +13,49 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from cryptography import x509
import neutron_lbaas.common.exceptions as exceptions import neutron_lbaas.common.exceptions as exceptions
import neutron_lbaas.common.tls_utils.cert_parser as cert_parser import neutron_lbaas.common.tls_utils.cert_parser as cert_parser
from neutron_lbaas.tests import base from neutron_lbaas.tests import base
ALT_EXT_CRT = """-----BEGIN CERTIFICATE----- ALT_EXT_CRT = """-----BEGIN CERTIFICATE-----
MIIGxDCCBaygAwIBAgIGAUp0fCElMA0GCSqGSIb3DQEBDQUAMIGLMQswCQYDVQQG MIIGqjCCBZKgAwIBAgIJAIApBg8slSSiMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD
EwJVUzEOMAwGA1UECAwFVGV4YXMxFDASBgNVBAcMC1NhbiBBbnRvbmlvMR4wHAYD VQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxFDASBgNVBAcMC1NhbiBBbnRvbmlvMR4w
VQQKDBVPcGVuU3RhY2sgRXhwZXJpbWVudHMxFjAUBgNVBAsMDU5ldXRyb24gTGJh HAYDVQQKDBVPcGVuU3RhY2sgRXhwZXJpbWVudHMxFjAUBgNVBAsMDU5ldXRyb24g
YXMxHjAcBgNVBAMMFXd3dy5DTkZyb21TdWJqZWN0Lm9yZzAeFw0xNDExMjIwMDEx TGJhYXMxHjAcBgNVBAMMFXd3dy5DTkZyb21TdWJqZWN0Lm9yZzAeFw0xNTA1MjEy
MzlaFw0yMjEyMjEwMDExMzlaMIGLMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4 MDMzMjNaFw0yNTA1MTgyMDMzMjNaMIGLMQswCQYDVQQGEwJVUzEOMAwGA1UECAwF
YXMxFDASBgNVBAcMC1NhbiBBbnRvbmlvMR4wHAYDVQQKDBVPcGVuU3RhY2sgRXhw VGV4YXMxFDASBgNVBAcMC1NhbiBBbnRvbmlvMR4wHAYDVQQKDBVPcGVuU3RhY2sg
ZXJpbWVudHMxFjAUBgNVBAsMDU5ldXRyb24gTGJhYXMxHjAcBgNVBAMMFXd3dy5D RXhwZXJpbWVudHMxFjAUBgNVBAsMDU5ldXRyb24gTGJhYXMxHjAcBgNVBAMMFXd3
TkZyb21TdWJqZWN0Lm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB dy5DTkZyb21TdWJqZWN0Lm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ALL1nmbDPUDps84i1sM3rhHrc+Dlu0N/wKQWKZFeiWUtF/pot19V3o0yXDpsg7W5 ggEBALL1nmbDPUDps84i1sM3rhHrc+Dlu0N/wKQWKZFeiWUtF/pot19V3o0yXDps
RkLMTFkZEcnQpyGdpAGjTjzmNXMZw99EzxsmrR3l6hUEISifVbvEuftYZT6jPxM5 g7W5RkLMTFkZEcnQpyGdpAGjTjzmNXMZw99EzxsmrR3l6hUEISifVbvEuftYZT6j
ML6WAjFNaBEZPWtZi8CgX5xdjdrDNndwyHob49n7Nc/h1kVqqBqMILabTqC6yEcx PxM5ML6WAjFNaBEZPWtZi8CgX5xdjdrDNndwyHob49n7Nc/h1kVqqBqMILabTqC6
S/B+DugVuuYbEdYYYElQUMfM+mUdULrSqIVl2n5AvvSFjWzWzfgPyp4QKn+f7HVR yEcxS/B+DugVuuYbEdYYYElQUMfM+mUdULrSqIVl2n5AvvSFjWzWzfgPyp4QKn+f
T62bh/XjQ88n1tMYNAEqixRZTPgqY1LFl9VJVgRp9fdL6ttMurOR3C0STJ5qCdKB 7HVRT62bh/XjQ88n1tMYNAEqixRZTPgqY1LFl9VJVgRp9fdL6ttMurOR3C0STJ5q
L7LrpbY4u8dEragRC6YAyI8CAwEAAaOCAyowggMmMAwGA1UdEwEB/wQCMAAwDgYD CdKBL7LrpbY4u8dEragRC6YAyI8CAwEAAaOCAw0wggMJMAkGA1UdEwQCMAAwCwYD
VR0PAQH/BAQDAgO4MIIDBAYDVR0RBIIC+zCCAveCGXd3dy5ob3N0RnJvbV9kTlNO VR0PBAQDAgXgMIIC7QYDVR0RBIIC5DCCAuCCGHd3dy5ob3N0RnJvbUROU05hbWUx
YW1lMS5jb22CGXd3dy5ob3N0RnJvbV9kTlNOYW1lMi5jb22CGXd3dy5ob3N0RnJv LmNvbYIYd3d3Lmhvc3RGcm9tRE5TTmFtZTIuY29tghh3d3cuaG9zdEZyb21ETlNO
bV9kTlNOYW1lMy5jb22BEW5vb25lQG5vd2hlcmUub3JnpIGPMIGMMQswCQYDVQQG YW1lMy5jb22CGHd3dy5ob3N0RnJvbUROU05hbWU0LmNvbYcECgECA4cQASNFZ4mr
EwJVUzEOMAwGA1UECAwFVGV4YXMxFDASBgNVBAcMC1NhbiBBbnRvbmlvMR4wHAYD ze/3s9WR5qLEgIYWaHR0cDovL3d3dy5leGFtcGxlLmNvbaSBjzCBjDELMAkGA1UE
VQQKDBVPcGVuU3RhY2sgRXhwZXJpbWVudHMxFjAUBgNVBAsMDU5ldXRyb24gTGJh BhMCVVMxDjAMBgNVBAgMBVRleGFzMRQwEgYDVQQHDAtTYW4gQW50b25pbzEeMBwG
YXMxHzAdBgNVBAMMFnd3dy5jbkZyb21BbHROYW1lMS5vcmekgY8wgYwxCzAJBgNV A1UECgwVT3BlblN0YWNrIEV4cGVyaW1lbnRzMRYwFAYDVQQLDA1OZXV0cm9uIExi
BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEUMBIGA1UEBwwLU2FuIEFudG9uaW8xHjAc YWFzMR8wHQYDVQQDDBZ3d3cuY25Gcm9tQWx0TmFtZTEub3JnpIGPMIGMMQswCQYD
BgNVBAoMFU9wZW5TdGFjayBFeHBlcmltZW50czEWMBQGA1UECwwNTmV1dHJvbiBM VQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxFDASBgNVBAcMC1NhbiBBbnRvbmlvMR4w
YmFhczEfMB0GA1UEAwwWd3d3LmNuRnJvbUFsdE5hbWUyLm9yZ6SBjzCBjDELMAkG HAYDVQQKDBVPcGVuU3RhY2sgRXhwZXJpbWVudHMxFjAUBgNVBAsMDU5ldXRyb24g
A1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRQwEgYDVQQHDAtTYW4gQW50b25pbzEe TGJhYXMxHzAdBgNVBAMMFnd3dy5jbkZyb21BbHROYW1lMi5vcmekgY8wgYwxCzAJ
MBwGA1UECgwVT3BlblN0YWNrIEV4cGVyaW1lbnRzMRYwFAYDVQQLDA1OZXV0cm9u BgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEUMBIGA1UEBwwLU2FuIEFudG9uaW8x
IExiYWFzMR8wHQYDVQQDDBZ3d3cuY25Gcm9tQWx0TmFtZTMub3JnpIGPMIGMMQsw HjAcBgNVBAoMFU9wZW5TdGFjayBFeHBlcmltZW50czEWMBQGA1UECwwNTmV1dHJv
CQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxFDASBgNVBAcMC1NhbiBBbnRvbmlv biBMYmFhczEfMB0GA1UEAwwWd3d3LmNuRnJvbUFsdE5hbWUzLm9yZ6SBjzCBjDEL
MR4wHAYDVQQKDBVPcGVuU3RhY2sgRXhwZXJpbWVudHMxFjAUBgNVBAsMDU5ldXRy MAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRQwEgYDVQQHDAtTYW4gQW50b25p
b24gTGJhYXMxHzAdBgNVBAMMFnd3dy5jbkZyb21BbHROYW1lNC5vcmeHBAoBAgOH bzEeMBwGA1UECgwVT3BlblN0YWNrIEV4cGVyaW1lbnRzMRYwFAYDVQQLDA1OZXV0
EAEjRWeJq83v97PVkeaixICGFmh0dHA6Ly93d3cuZXhhbXBsZS5jb22CGXd3dy5o cm9uIExiYWFzMR8wHQYDVQQDDBZ3d3cuY25Gcm9tQWx0TmFtZTQub3JnMA0GCSqG
b3N0RnJvbV9kTlNOYW1lNC5jb20wDQYJKoZIhvcNAQENBQADggEBAICUCDMhDf0f SIb3DQEBBQUAA4IBAQCS6iDn6R3C+qJLZibaqrBSkM9yu5kwRsQ6lQ+DODvVYGWq
cvX5mVnq4Q3+SM/nl03Gse6J0JdpFivS4hl+uZs0TAFYpfEPpAa7KKxD229kbCiQ eGkkh5o2c6WbJlH44yF280+HvnJcuISD7epPHJN0vUM9+WMtXfEli9avFHgu2JxP
kyxf8fzADSl77RQbL6Lxa8K/c66mVNiuVvTHV4r/nDNcRYN9fGArw/Ho7VX+HVQ6 3P0ixK2kaJnqKQkSEdnA/v/eWP1Cd2v6rbKCIo9d2gSP0cnpdtlX9Zk3SzEh0V7s
UW1t/uvXeyg695t7kzZmvg0ChD5kS848d2rXu2MhwHwXA8rbuK6gxVY97fbzBNlj RjSdfZoAvz0aAnpDHlTerLcz5T2aiRae2wSt/RLA3qDO1Ji05tWvQBmKuepxS6A1
aiPJUAb8lqZMShd+3yVCgMmV0J20u2b5pSdO+LHQ7NfVqURk2pcHD8slfHzXT58q tL4Drm+OCXJwTrE7ClTMCwcrZnLl4tI+Z+X3DV92WQB8ldST/QFjz1hgs/4zrADA
YB90v0pSVP6mzHGyLxETZZz0nhaH9EjOyFkQI84ORT8Kmd5Y04gSI0LTKKF1eMNE elu2c/X7MR4ObOjhDfaVGQ8kMhYf5hx69qyNDsGi
TyNC+MtsRdA=
-----END CERTIFICATE----- -----END CERTIFICATE-----
""" """
@ -237,11 +237,11 @@ def _get_rsa_numbers(private_key, private_key_passphrase=None):
class TestTLSParseUtils(base.BaseTestCase): class TestTLSParseUtils(base.BaseTestCase):
def test_alt_subject_name_parses(self): def test_alt_subject_name_parses(self):
hosts = cert_parser.get_host_names(ALT_EXT_CRT) hosts = cert_parser.get_host_names(ALT_EXT_CRT)
self.assertEqual('www.CNFromSubject.org', hosts['cn']) self.assertEqual('www.cnfromsubject.org', hosts['cn'])
self.assertEqual('www.hostFrom_dNSName1.com', hosts['dns_names'][0]) self.assertEqual('www.hostfromdnsname1.com', hosts['dns_names'][0])
self.assertEqual('www.hostFrom_dNSName2.com', hosts['dns_names'][1]) self.assertEqual('www.hostfromdnsname2.com', hosts['dns_names'][1])
self.assertEqual('www.hostFrom_dNSName3.com', hosts['dns_names'][2]) self.assertEqual('www.hostfromdnsname3.com', hosts['dns_names'][2])
self.assertEqual('www.hostFrom_dNSName4.com', hosts['dns_names'][3]) self.assertEqual('www.hostfromdnsname4.com', hosts['dns_names'][3])
def test_x509_parses(self): def test_x509_parses(self):
self.assertRaises(exceptions.UnreadableCert, self.assertRaises(exceptions.UnreadableCert,
@ -260,19 +260,17 @@ class TestTLSParseUtils(base.BaseTestCase):
self.assertRaises(exceptions.NeedsPassphrase, self.assertRaises(exceptions.NeedsPassphrase,
cert_parser._read_privatekey, cert_parser._read_privatekey,
ENCRYPTED_PKCS8_CRT_KEY) ENCRYPTED_PKCS8_CRT_KEY)
epkey = cert_parser._read_privatekey( cert_parser._read_privatekey(
ENCRYPTED_PKCS8_CRT_KEY, ENCRYPTED_PKCS8_CRT_KEY,
passphrase=ENCRYPTED_PKCS8_CRT_KEY_PASSPHRASE) passphrase=ENCRYPTED_PKCS8_CRT_KEY_PASSPHRASE)
self.assertTrue(epkey.check())
def test_read_private_key_unicode(self): def test_read_private_key_unicode(self):
self.assertRaises(exceptions.NeedsPassphrase, self.assertRaises(exceptions.NeedsPassphrase,
cert_parser._read_privatekey, cert_parser._read_privatekey,
ENCRYPTED_PKCS8_CRT_KEY) ENCRYPTED_PKCS8_CRT_KEY)
epkey = cert_parser._read_privatekey( cert_parser._read_privatekey(
ENCRYPTED_PKCS8_CRT_KEY, ENCRYPTED_PKCS8_CRT_KEY,
passphrase=u'{0}'.format(ENCRYPTED_PKCS8_CRT_KEY_PASSPHRASE)) passphrase=u'{0}'.format(ENCRYPTED_PKCS8_CRT_KEY_PASSPHRASE))
self.assertTrue(epkey.check())
def test_dump_private_key(self): def test_dump_private_key(self):
self.assertRaises(exceptions.NeedsPassphrase, self.assertRaises(exceptions.NeedsPassphrase,
@ -303,4 +301,6 @@ class TestTLSParseUtils(base.BaseTestCase):
imds.append(cert_parser._get_x509_from_pem_bytes(x509Pem)) imds.append(cert_parser._get_x509_from_pem_bytes(x509Pem))
for i in range(0, len(imds)): for i in range(0, len(imds)):
self.assertEqual(EXPECTED_IMD_SUBJS[i], imds[i].get_subject().CN) self.assertEqual(EXPECTED_IMD_SUBJS[i],
imds[i].subject.get_attributes_for_oid(
x509.OID_COMMON_NAME)[0].value)