Skip to content

Commit 5395344

Browse files
ioparaskevc00kiemon5ter
authored andcommitted
Load certificates using cryptography
- Use cryptography.x509 load_pem_x509_certificate or load_der_x509_certificate depending on the cert type. This ensures 1) the certificate is a valid certificate 2) trailing newlines and whitespaces will be ignored - Ignore cer/crt as certificate type since these are file extensions and do not guarrantee the certificate encoding. Uses "pem" as default type for backwards compatibility. Only other valid option is "der". Everything else falls back to "pem". Signed-off-by: Ivan Kanakarakis <[email protected]>
1 parent 9d1e8a4 commit 5395344

File tree

8 files changed

+93
-45
lines changed

8 files changed

+93
-45
lines changed

src/saml2/metadata.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#!/usr/bin/env python
2+
from cryptography import x509
3+
from cryptography.hazmat.backends import default_backend
4+
from cryptography.hazmat.primitives.serialization import Encoding
25
from saml2.algsupport import algorithm_support_in_metadata
36
from saml2.md import AttributeProfile
4-
from saml2.sigver import security_context
7+
from saml2.sigver import security_context, read_cert_from_file
58
from saml2.config import Config
69
from saml2.validate import valid_instance
710
from saml2.time_util import in_a_while
@@ -688,14 +691,14 @@ def entity_descriptor(confd):
688691
enc_cert = None
689692
if confd.cert_file is not None:
690693
mycert = []
691-
mycert.append("".join(read_cert(confd.cert_file)))
694+
mycert.append(read_cert_from_file(confd.cert_file))
692695
if confd.additional_cert_files is not None:
693696
for _cert_file in confd.additional_cert_files:
694-
mycert.append("".join(read_cert(_cert_file)))
697+
mycert.append(read_cert_from_file(_cert_file))
695698
if confd.encryption_keypairs is not None:
696699
enc_cert = []
697700
for _encryption in confd.encryption_keypairs:
698-
enc_cert.append("".join(read_cert(_encryption["cert_file"])))
701+
enc_cert.append(read_cert_from_file(_encryption["cert_file"]))
699702

700703
entd = md.EntityDescriptor()
701704
entd.entity_id = confd.entityid
@@ -844,9 +847,3 @@ def sign_entity_descriptor(edesc, ident, secc, sign_alg=None, digest_alg=None):
844847
xmldoc = secc.sign_statement("%s" % edesc, class_name(edesc))
845848
edesc = md.entity_descriptor_from_string(xmldoc)
846849
return edesc, xmldoc
847-
848-
849-
def read_cert(path):
850-
with open(path) as fp:
851-
lines = fp.readlines()
852-
return lines[1:-1]

src/saml2/sigver.py

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import six
1414
import sys
1515
from uuid import uuid4 as gen_random_key
16+
1617
from time import mktime
1718
from tempfile import NamedTemporaryFile
1819
from subprocess import Popen
@@ -27,6 +28,10 @@
2728

2829
from OpenSSL import crypto
2930

31+
from cryptography import x509
32+
from cryptography.hazmat.backends import default_backend
33+
from cryptography.hazmat.primitives.serialization import Encoding
34+
3035
import pytz
3136

3237
from six.moves.urllib import parse
@@ -632,46 +637,31 @@ def make_str(txt):
632637
return txt.decode()
633638

634639

635-
def read_cert_from_file(cert_file, cert_type):
640+
def read_cert_from_file(cert_file, cert_type="pem"):
636641
""" Reads a certificate from a file. The assumption is that there is
637642
only one certificate in the file
638643
639644
:param cert_file: The name of the file
640645
:param cert_type: The certificate type
641646
:return: A base64 encoded certificate as a string or the empty string
642647
"""
643-
644648
if not cert_file:
645-
return ''
646-
647-
if cert_type == 'pem':
648-
_a = read_file(cert_file, 'rb').decode()
649-
_b = _a.replace('\r\n', '\n')
650-
lines = _b.split('\n')
651-
652-
for pattern in (
653-
'-----BEGIN CERTIFICATE-----',
654-
'-----BEGIN PUBLIC KEY-----'):
655-
if pattern in lines:
656-
lines = lines[lines.index(pattern) + 1:]
657-
break
658-
else:
659-
raise CertificateError('Strange beginning of PEM file')
660-
661-
for pattern in (
662-
'-----END CERTIFICATE-----',
663-
'-----END PUBLIC KEY-----'):
664-
if pattern in lines:
665-
lines = lines[:lines.index(pattern)]
666-
break
667-
else:
668-
raise CertificateError('Strange end of PEM file')
669-
return make_str(''.join(lines).encode())
649+
return ""
670650

671-
if cert_type in ['der', 'cer', 'crt']:
672-
data = read_file(cert_file, 'rb')
673-
_cert = base64.b64encode(data)
674-
return make_str(_cert)
651+
with open(cert_file, "rb") as fp:
652+
data = fp.read()
653+
654+
cert_reader = (x509.load_der_x509_certificate
655+
if cert_type == 'der'
656+
else x509.load_pem_x509_certificate)
657+
658+
try:
659+
cert = cert_reader(data, default_backend())
660+
pem_data = cert.public_bytes(Encoding.PEM)
661+
except Exception as e:
662+
raise CertificateError(e)
663+
else:
664+
return "".join(pem_data.decode().splitlines()[1:-1])
675665

676666

677667
class CryptoBackend(object):

tests/extra_lines.crt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICITCCAYoCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV
3+
BAgMAnp6MQ0wCwYDVQQHDAR6enp6MQ4wDAYDVQQKDAVaenp6ejEOMAwGA1UECwwF
4+
Wnp6enoxDTALBgNVBAMMBHRlc3QwIBcNMTkwNDEyMTk1MDM0WhgPMzAxODA4MTMx
5+
OTUwMzRaMFgxCzAJBgNVBAYTAnp6MQswCQYDVQQIDAJ6ejENMAsGA1UEBwwEenp6
6+
ejEOMAwGA1UECgwFWnp6enoxDjAMBgNVBAsMBVp6enp6MQ0wCwYDVQQDDAR0ZXN0
7+
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjW0kJM+4baWKtvO24ZsGXNvNK
8+
KkwTMz7OW5Z6BRqhSOq2WA0c5NCpMk6rD8Z2OTFEolPojEjf8dVyd/Ds/hrjFKQv
9+
8wQgbdXLN51YTIsgd6h+hBJO+vzhl0PT4aT7M0JKo5ALtS6qk4tsworW2BnwyvsG
10+
SAinwfeWt4t/b1J3kwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAFtj7WArQQBugmh/
11+
KQjjlfTQ5A052QeXfgTyO9vv1S6MRIi7qgiaEv49cGXnJv/TWbySkMKObPMUApjg
12+
6z8PqcxuShew5FCTkNvwhABFPiyu0fUj3e2FEPHfsBu76jz4ugtmhUqjqhzwFY9c
13+
tnWRkkl6J0AjM3LnHOSgjNIclDZG
14+
-----END CERTIFICATE-----
15+
16+
17+
18+
19+

tests/malformed.crt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICITCCAYoCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV
3+
BAgMAnp6MQ0wCwYDVQQHDAR6enp6MQ4wDAYDVQQKDAVaenp6ejEOMAwGA1UECwwF
4+
Wnp6enoxDTALBgNVBAMMBHRlc3QwIBcNMTkwNDEyMTk1MDM0WhgPMzAxODA4MTMx
5+
OTUwMzRaMFgxCzAJBgNVBAYTAnp6MQswCQYDVQQIDAJ6ejENMAsGA1UEBwwEenp6
6+
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjW0kJM+4baWKtvO24ZsGXNvNK
7+
KkwTMz7OW5Z6BRqhSOq2WA0c5NCpMk6rD8Z2OTFEolPojEjf8dVyd/Ds/hrjFKQv
8+
8wQgbdXLN51YTIsgd6h+hBJO+vzhl0PT4aT7M0JKo5ALtS6qk4tsworW2BnwyvsG
9+
SAinwfeWt4t/b1J3kwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAFtj7WArQQBugmh/
10+
KQjjlfTQ5A052QeXfgTyO9vv1S6MRIi7qgiaEv49cGXnJv/TWbySkMKObPMUApjg
11+
6z8PqcxuShew5FCTkNvwhABFPiyu0fUj3e2FEPHfsBu76jz4ugtmhUqjqhzwFY9c
12+
tnWRkkl6J0AjM3LnHOSgjNIclDZG

tests/test_1.der

549 Bytes
Binary file not shown.

tests/test_39_metadata.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import copy
22
from saml2.config import SPConfig
3-
from saml2.metadata import create_metadata_string, entity_descriptor
3+
from saml2.metadata import create_metadata_string
4+
from saml2.metadata import entity_descriptor
5+
from saml2.metadata import read_cert
46
from saml2.saml import NAME_FORMAT_URI, NAME_FORMAT_BASIC
57
from saml2 import sigver
68

@@ -62,5 +64,18 @@ def test_signed_metadata_proper_str_bytes_handling():
6264
sp_metadata = create_metadata_string('', config=cnf, sign=True)
6365

6466

67+
def test_cert_trailing_newlines_ignored():
68+
assert "".join(read_cert(full_path("extra_lines.crt"))) \
69+
== "".join(read_cert(full_path("test_2.crt")))
70+
71+
72+
def test_invalid_cert_raises_error():
73+
with pytest.raises(ValueError):
74+
read_cert(full_path("malformed.crt"))
75+
76+
6577
if __name__ == '__main__':
6678
test_requested_attribute_name_format()
79+
test_cert_trailing_newlines_ignored()
80+
test_invalid_cert_raises_error()
81+
test_signed_metadata_proper_str_bytes_handling()

tests/test_40_sigver.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
from saml2 import time_util
1010
from saml2 import saml, samlp
1111
from saml2 import config
12-
from saml2.sigver import pre_encryption_part
12+
from saml2.sigver import pre_encryption_part, read_cert_from_file
1313
from saml2.sigver import make_temp
1414
from saml2.sigver import XmlsecError
15-
from saml2.sigver import SigverError
15+
from saml2.sigver import CertificateError
1616
from saml2.mdstore import MetadataStore
1717
from saml2.saml import assertion_from_string
1818
from saml2.saml import EncryptedAssertion
@@ -1114,6 +1114,21 @@ def test_xmlsec_output_line_parsing():
11141114
sigver.parse_xmlsec_output(output4)
11151115

11161116

1117+
def test_cert_trailing_newlines_ignored():
1118+
assert read_cert_from_file(full_path("extra_lines.crt")) \
1119+
== read_cert_from_file(full_path("test_2.crt"))
1120+
1121+
1122+
def test_invalid_cert_raises_error():
1123+
with pytest.raises(CertificateError):
1124+
read_cert_from_file(full_path("malformed.crt"))
1125+
1126+
1127+
def test_der_certificate_loading():
1128+
assert read_cert_from_file(full_path("test_1.der"), "der") == \
1129+
read_cert_from_file(full_path("test_1.crt"))
1130+
1131+
11171132
if __name__ == "__main__":
11181133
# t = TestSecurity()
11191134
# t.setup_class()

tests/test_82_pefim.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
client = Saml2Client(conf)
1919

2020
# place a certificate in an authn request
21-
cert = read_cert_from_file(full_path("test.pem"), "pem")
21+
cert = read_cert_from_file(full_path("test.pem"))
2222

2323
spcertenc = SPCertEnc(
2424
x509_data=ds.X509Data(

0 commit comments

Comments
 (0)