Skip to content

Commit a924435

Browse files
committed
Merge branch 'feat-cert-load'
2 parents f36b06a + e68304f commit a924435

16 files changed

+311
-91
lines changed

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ scripts =
4848
tools/parse_xsd2.py
4949
python_requires = >=3.6, <4
5050
install_requires =
51-
cryptography >= 1.4
51+
cryptography >= 3.1
5252
defusedxml
5353
pyOpenSSL
5454
python-dateutil

src/saml2/cert.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
import dateutil.parser
66
import pytz
77
import six
8-
from OpenSSL import crypto
9-
from os.path import join
108
from os import remove
9+
from os.path import join
10+
11+
from OpenSSL import crypto
1112

1213
import saml2.cryptography.pki
1314

@@ -323,8 +324,7 @@ def verify(self, signing_cert_str, cert_str):
323324
cert_algorithm = cert_algorithm.decode('ascii')
324325
cert_str = cert_str.encode('ascii')
325326

326-
cert_crypto = saml2.cryptography.pki.load_pem_x509_certificate(
327-
cert_str)
327+
cert_crypto = saml2.cryptography.pki.load_pem_x509_certificate(cert_str)
328328

329329
try:
330330
crypto.verify(ca_cert, cert_crypto.signature,
@@ -335,3 +335,28 @@ def verify(self, signing_cert_str, cert_str):
335335
return False, "Certificate is incorrectly signed."
336336
except Exception as e:
337337
return False, "Certificate is not valid for an unknown reason. %s" % str(e)
338+
339+
340+
def read_cert_from_file(cert_file, cert_type="pem"):
341+
"""Read a certificate from a file.
342+
343+
If there are multiple certificates in the file, the first is returned.
344+
345+
:param cert_file: The name of the file
346+
:param cert_type: The certificate type
347+
:return: A base64 encoded certificate as a string or the empty string
348+
"""
349+
if not cert_file:
350+
return ""
351+
352+
with open(cert_file, "rb") as fp:
353+
data = fp.read()
354+
355+
try:
356+
cert = saml2.cryptography.pki.load_x509_certificate(data, cert_type)
357+
pem_data = saml2.cryptography.pki.get_public_bytes_from_cert(cert)
358+
except Exception as e:
359+
raise CertificateError(e)
360+
361+
pem_data_no_headers = "".join(pem_data.splitlines()[1:-1])
362+
return pem_data_no_headers

src/saml2/cryptography/asymmetric.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
"""This module provides methods for asymmetric cryptography."""
22

3-
import cryptography.hazmat.backends as _backends
43
import cryptography.hazmat.primitives.asymmetric as _asymmetric
54
import cryptography.hazmat.primitives.hashes as _hashes
65
import cryptography.hazmat.primitives.serialization as _serialization
76

87

9-
def load_pem_private_key(data, password):
8+
def load_pem_private_key(data, password=None):
109
"""Load RSA PEM certificate."""
11-
key = _serialization.load_pem_private_key(
12-
data, password, _backends.default_backend())
10+
key = _serialization.load_pem_private_key(data, password)
1311
return key
1412

1513

src/saml2/cryptography/pki.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,48 @@
11
"""This module provides methods for PKI operations."""
22

3-
import cryptography.hazmat.backends as _backends
3+
from logging import getLogger as get_logger
4+
45
import cryptography.x509 as _x509
6+
from cryptography.hazmat.primitives.serialization import Encoding as _cryptography_encoding
7+
8+
9+
logger = get_logger(__name__)
10+
11+
DEFAULT_CERT_TYPE = "pem"
512

613

714
def load_pem_x509_certificate(data):
815
"""Load X.509 PEM certificate."""
9-
return _x509.load_pem_x509_certificate(data, _backends.default_backend())
16+
return _x509.load_pem_x509_certificate(data)
17+
18+
19+
def load_der_x509_certificate(data):
20+
"""Load X.509 DER certificate."""
21+
return _x509.load_der_x509_certificate(data)
22+
23+
24+
def load_x509_certificate(data, cert_type="pem"):
25+
cert_reader = _x509_loaders.get(cert_type)
26+
27+
if not cert_reader:
28+
cert_reader = _x509_loaders.get("pem")
29+
context = {
30+
"message": "Unknown cert_type, falling back to default",
31+
"cert_type": cert_type,
32+
"default": DEFAULT_CERT_TYPE,
33+
}
34+
logger.warning(context)
35+
36+
cert = cert_reader(data)
37+
return cert
38+
39+
40+
def get_public_bytes_from_cert(cert):
41+
data = cert.public_bytes(_cryptography_encoding.PEM).decode()
42+
return data
43+
44+
45+
_x509_loaders = {
46+
"pem": load_pem_x509_certificate,
47+
"der": load_der_x509_certificate,
48+
}

src/saml2/cryptography/symmetric.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from warnings import warn as _warn
1111

1212
import cryptography.fernet as _fernet
13-
import cryptography.hazmat.backends as _backends
1413
import cryptography.hazmat.primitives.ciphers as _ciphers
1514

1615
from .errors import SymmetricCryptographyError
@@ -158,10 +157,7 @@ def build_cipher(self, alg='aes_128_cbc'):
158157
except KeyError:
159158
raise Exception('Unsupported chaining mode: {}'.format(cmode))
160159

161-
cipher = _ciphers.Cipher(
162-
_ciphers.algorithms.AES(self.key),
163-
mode(iv),
164-
backend=_backends.default_backend())
160+
cipher = _ciphers.Cipher(_ciphers.algorithms.AES(self.key), mode(iv))
165161

166162
return cipher, iv
167163

src/saml2/metadata.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from saml2.algsupport import algorithm_support_in_metadata
33
from saml2.md import AttributeProfile
44
from saml2.sigver import security_context
5+
from saml2.cert import read_cert_from_file
56
from saml2.config import Config
67
from saml2.validate import valid_instance
78
from saml2.time_util import in_a_while
@@ -688,14 +689,14 @@ def entity_descriptor(confd):
688689
enc_cert = None
689690
if confd.cert_file is not None:
690691
mycert = []
691-
mycert.append("".join(read_cert(confd.cert_file)))
692+
mycert.append(read_cert_from_file(confd.cert_file))
692693
if confd.additional_cert_files is not None:
693694
for _cert_file in confd.additional_cert_files:
694-
mycert.append("".join(read_cert(_cert_file)))
695+
mycert.append(read_cert_from_file(_cert_file))
695696
if confd.encryption_keypairs is not None:
696697
enc_cert = []
697698
for _encryption in confd.encryption_keypairs:
698-
enc_cert.append("".join(read_cert(_encryption["cert_file"])))
699+
enc_cert.append(read_cert_from_file(_encryption["cert_file"]))
699700

700701
entd = md.EntityDescriptor()
701702
entd.entity_id = confd.entityid
@@ -844,9 +845,3 @@ def sign_entity_descriptor(edesc, ident, secc, sign_alg=None, digest_alg=None):
844845
xmldoc = secc.sign_statement("%s" % edesc, class_name(edesc))
845846
edesc = md.entity_descriptor_from_string(xmldoc)
846847
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: 6 additions & 60 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
@@ -43,6 +44,8 @@
4344
from saml2 import saml
4445
from saml2 import ExtensionElement
4546
from saml2.cert import OpenSSLWrapper
47+
from saml2.cert import read_cert_from_file
48+
from saml2.cert import CertificateError
4649
from saml2.extension import pefim
4750
from saml2.extension.pefim import SPCertEnc
4851
from saml2.saml import EncryptedAssertion
@@ -108,10 +111,6 @@ class BadSignature(SigverError):
108111
pass
109112

110113

111-
class CertificateError(SigverError):
112-
pass
113-
114-
115114
def get_pem_wrapped_unwrapped(cert):
116115
begin_cert = "-----BEGIN CERTIFICATE-----\n"
117116
end_cert = "\n-----END CERTIFICATE-----\n"
@@ -120,11 +119,6 @@ def get_pem_wrapped_unwrapped(cert):
120119
return wrapped_cert, unwrapped_cert
121120

122121

123-
def read_file(*args, **kwargs):
124-
with open(*args, **kwargs) as handler:
125-
return handler.read()
126-
127-
128122
def rm_xmltag(statement):
129123
XMLTAG = "<?xml version='1.0'?>"
130124
PREFIX1 = "<?xml version='1.0' encoding='UTF-8'?>"
@@ -489,8 +483,9 @@ def pem_format(key):
489483

490484

491485
def import_rsa_key_from_file(filename):
492-
data = read_file(filename, 'rb')
493-
key = saml2.cryptography.asymmetric.load_pem_private_key(data, None)
486+
with open(filename, "rb") as fd:
487+
data = fd.read()
488+
key = saml2.cryptography.asymmetric.load_pem_private_key(data)
494489
return key
495490

496491

@@ -625,55 +620,6 @@ def verify_redirect_signature(saml_msg, crypto, cert=None, sigkey=None):
625620
return bool(signer.verify(string, _sign, _key))
626621

627622

628-
def make_str(txt):
629-
if isinstance(txt, six.string_types):
630-
return txt
631-
else:
632-
return txt.decode()
633-
634-
635-
def read_cert_from_file(cert_file, cert_type):
636-
""" Reads a certificate from a file. The assumption is that there is
637-
only one certificate in the file
638-
639-
:param cert_file: The name of the file
640-
:param cert_type: The certificate type
641-
:return: A base64 encoded certificate as a string or the empty string
642-
"""
643-
644-
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())
670-
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)
675-
676-
677623
class CryptoBackend(object):
678624
def version(self):
679625
raise NotImplementedError()

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.

0 commit comments

Comments
 (0)