Skip to content

Commit 466af97

Browse files
committed
Refactor certificate loading
Signed-off-by: Ivan Kanakarakis <[email protected]>
1 parent 5395344 commit 466af97

File tree

7 files changed

+87
-66
lines changed

7 files changed

+87
-66
lines changed

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+
The assumption is that there is only one certificate in the file.
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import cryptography.hazmat.primitives.serialization as _serialization
66

77

8-
def load_pem_private_key(data, password):
8+
def load_pem_private_key(data, password=None):
99
"""Load RSA PEM certificate."""
1010
key = _serialization.load_pem_private_key(data, password)
1111
return key

src/saml2/cryptography/pki.py

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

3+
from logging import getLogger as get_logger
4+
35
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"
412

513

614
def load_pem_x509_certificate(data):
715
"""Load X.509 PEM certificate."""
816
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/metadata.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
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
52
from saml2.algsupport import algorithm_support_in_metadata
63
from saml2.md import AttributeProfile
7-
from saml2.sigver import security_context, read_cert_from_file
4+
from saml2.sigver import security_context
5+
from saml2.cert import read_cert_from_file
86
from saml2.config import Config
97
from saml2.validate import valid_instance
108
from saml2.time_util import in_a_while

src/saml2/sigver.py

Lines changed: 5 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@
2828

2929
from OpenSSL import crypto
3030

31-
from cryptography import x509
32-
from cryptography.hazmat.backends import default_backend
33-
from cryptography.hazmat.primitives.serialization import Encoding
34-
3531
import pytz
3632

3733
from six.moves.urllib import parse
@@ -48,6 +44,8 @@
4844
from saml2 import saml
4945
from saml2 import ExtensionElement
5046
from saml2.cert import OpenSSLWrapper
47+
from saml2.cert import read_cert_from_file
48+
from saml2.cert import CertificateError
5149
from saml2.extension import pefim
5250
from saml2.extension.pefim import SPCertEnc
5351
from saml2.saml import EncryptedAssertion
@@ -113,10 +111,6 @@ class BadSignature(SigverError):
113111
pass
114112

115113

116-
class CertificateError(SigverError):
117-
pass
118-
119-
120114
def get_pem_wrapped_unwrapped(cert):
121115
begin_cert = "-----BEGIN CERTIFICATE-----\n"
122116
end_cert = "\n-----END CERTIFICATE-----\n"
@@ -125,11 +119,6 @@ def get_pem_wrapped_unwrapped(cert):
125119
return wrapped_cert, unwrapped_cert
126120

127121

128-
def read_file(*args, **kwargs):
129-
with open(*args, **kwargs) as handler:
130-
return handler.read()
131-
132-
133122
def rm_xmltag(statement):
134123
XMLTAG = "<?xml version='1.0'?>"
135124
PREFIX1 = "<?xml version='1.0' encoding='UTF-8'?>"
@@ -494,8 +483,9 @@ def pem_format(key):
494483

495484

496485
def import_rsa_key_from_file(filename):
497-
data = read_file(filename, 'rb')
498-
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)
499489
return key
500490

501491

@@ -630,40 +620,6 @@ def verify_redirect_signature(saml_msg, crypto, cert=None, sigkey=None):
630620
return bool(signer.verify(string, _sign, _key))
631621

632622

633-
def make_str(txt):
634-
if isinstance(txt, six.string_types):
635-
return txt
636-
else:
637-
return txt.decode()
638-
639-
640-
def read_cert_from_file(cert_file, cert_type="pem"):
641-
""" Reads a certificate from a file. The assumption is that there is
642-
only one certificate in the file
643-
644-
:param cert_file: The name of the file
645-
:param cert_type: The certificate type
646-
:return: A base64 encoded certificate as a string or the empty string
647-
"""
648-
if not cert_file:
649-
return ""
650-
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])
665-
666-
667623
class CryptoBackend(object):
668624
def version(self):
669625
raise NotImplementedError()

tests/test_39_metadata.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
from saml2.config import SPConfig
33
from saml2.metadata import create_metadata_string
44
from saml2.metadata import entity_descriptor
5-
from saml2.metadata import read_cert
5+
from saml2.cert import read_cert_from_file as read_cert
6+
from saml2.cert import CertificateError
67
from saml2.saml import NAME_FORMAT_URI, NAME_FORMAT_BASIC
78
from saml2 import sigver
89

910
from pathutils import full_path
1011

11-
__author__ = 'roland'
12+
from pytest import raises
13+
1214

1315
sp_conf = {
1416
"entityid": "urn:mace:umu.se:saml:roland:sp",
@@ -70,7 +72,7 @@ def test_cert_trailing_newlines_ignored():
7072

7173

7274
def test_invalid_cert_raises_error():
73-
with pytest.raises(ValueError):
75+
with raises(CertificateError):
7476
read_cert(full_path("malformed.crt"))
7577

7678

tests/test_40_sigver.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
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, read_cert_from_file
12+
from saml2.cert import read_cert_from_file
13+
from saml2.cert import CertificateError
14+
from saml2.sigver import pre_encryption_part
1315
from saml2.sigver import make_temp
1416
from saml2.sigver import XmlsecError
15-
from saml2.sigver import CertificateError
1617
from saml2.mdstore import MetadataStore
1718
from saml2.saml import assertion_from_string
1819
from saml2.saml import EncryptedAssertion
@@ -97,8 +98,7 @@ def test_cert_from_instance_1():
9798
assert certs[0] == CERT1
9899

99100

100-
@pytest.mark.skipif(not decoder,
101-
reason="pyasn1 is not installed")
101+
@pytest.mark.skipif(not decoder, reason="pyasn1 is not installed")
102102
def test_cert_from_instance_ssp():
103103
with open(SIMPLE_SAML_PHP_RESPONSE) as fp:
104104
xml_response = fp.read()
@@ -1120,7 +1120,7 @@ def test_cert_trailing_newlines_ignored():
11201120

11211121

11221122
def test_invalid_cert_raises_error():
1123-
with pytest.raises(CertificateError):
1123+
with raises(CertificateError):
11241124
read_cert_from_file(full_path("malformed.crt"))
11251125

11261126

0 commit comments

Comments
 (0)