Skip to content

Commit eabe609

Browse files
committed
Add fingerprint support, check local IdP cert with xml cert
1 parent 5d2e297 commit eabe609

File tree

4 files changed

+100
-15
lines changed

4 files changed

+100
-15
lines changed

example.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from BaseHTTPServer import HTTPServer
1111

1212
from onelogin.saml import AuthRequest, Response
13+
from onelogin.saml.Utils import format_finger_print, calculate_x509_fingerprint
14+
1315

1416
__version__ = '0.1'
1517

@@ -143,7 +145,13 @@ def main(config_file):
143145
cert_path = os.path.abspath(cert_path)
144146

145147
with open(cert_path) as f:
146-
settings['idp_cert_fingerprint'] = f.read()
148+
cert = f.read()
149+
fingerprint = calculate_x509_fingerprint(cert)
150+
if fingerprint:
151+
settings['idp_cert_fingerprint'] = fingerprint
152+
else:
153+
formated = format_finger_print(settings['idp_cert_fingerprint'])
154+
settings['idp_cert_fingerprint'] = formated
147155

148156
parts = urlparse.urlparse(settings['assertion_consumer_service_url'])
149157
SampleAppHTTPRequestHandler.protocol_version = 'HTTP/1.0'

onelogin/saml/Response.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from onelogin.saml import SignatureVerifier
77

8+
89
namespaces = dict(
910
samlp='urn:oasis:names:tc:SAML:2.0:protocol',
1011
saml='urn:oasis:names:tc:SAML:2.0:assertion',

onelogin/saml/SignatureVerifier.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from lxml import etree
88

9+
from onelogin.saml.Utils import calculate_x509_fingerprint, format_cert
10+
911
log = logging.getLogger(__name__)
1012

1113

@@ -81,6 +83,14 @@ def verify(document, signature, _etree=None, _tempfile=None, _subprocess=None,
8183
if signatureNodes and signatureNodes[0].getparent().tag == '{urn:oasis:names:tc:SAML:2.0:protocol}Response':
8284
parent_id_container = 'urn:oasis:names:tc:SAML:2.0:protocol:Response'
8385

86+
certificateNodes = document.xpath("//ds:X509Certificate", namespaces={'ds': 'http://www.w3.org/2000/09/xmldsig#'})
87+
88+
if not certificateNodes or calculate_x509_fingerprint(certificateNodes[0].text) != signature:
89+
return False
90+
else:
91+
# use the x509 cert instead of fingerprint required by xmlsec
92+
signature = format_cert(certificateNodes[0].text)
93+
8494
xmlsec_bin = _get_xmlsec_bin()
8595

8696
verified = False
@@ -94,20 +104,7 @@ def verify(document, signature, _etree=None, _tempfile=None, _subprocess=None,
94104
xml_fp.write(doc_str)
95105
xml_fp.seek(0)
96106
with _tempfile.NamedTemporaryFile(delete=False) as cert_fp:
97-
if signature.startswith(
98-
'-----BEGIN CERTIFICATE-----'
99-
):
100-
# If there's no matching 'END CERTIFICATE'
101-
# cryptpAppKeyLoad will fail
102-
cert_fp.write(signature)
103-
else:
104-
cert_fp.write(
105-
'{begin}\n{signature}\n{end}'.format(
106-
begin='-----BEGIN CERTIFICATE-----',
107-
signature=signature,
108-
end='-----END CERTIFICATE-----',
109-
)
110-
)
107+
cert_fp.write(signature)
111108
cert_fp.seek(0)
112109

113110
cert_filename = cert_fp.name

onelogin/saml/Utils.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import base64
2+
from hashlib import sha1
3+
from textwrap import wrap
4+
5+
6+
def format_finger_print(fingerprint):
7+
"""
8+
Formates a fingerprint.
9+
10+
:param fingerprint: fingerprint
11+
:type: string
12+
13+
:returns: Formated fingerprint
14+
:rtype: string
15+
"""
16+
formated_fingerprint = fingerprint.replace(':', '')
17+
return formated_fingerprint.lower()
18+
19+
20+
def calculate_x509_fingerprint(x509_cert):
21+
"""
22+
Calculates the fingerprint of a x509cert.
23+
24+
:param x509_cert: x509 cert
25+
:type: string
26+
27+
:returns: Formated fingerprint
28+
:rtype: string
29+
"""
30+
assert isinstance(x509_cert, basestring)
31+
32+
lines = x509_cert.split('\n')
33+
data = ''
34+
35+
for line in lines:
36+
# Remove '\r' from end of line if present.
37+
line = line.rstrip()
38+
if line == '-----BEGIN CERTIFICATE-----':
39+
# Delete junk from before the certificate.
40+
data = ''
41+
elif line == '-----END CERTIFICATE-----':
42+
# Ignore data after the certificate.
43+
break
44+
elif line == '-----BEGIN PUBLIC KEY-----' or line == '-----BEGIN RSA PRIVATE KEY-----':
45+
# This isn't an X509 certificate.
46+
return None
47+
else:
48+
# Append the current line to the certificate data.
49+
data += line
50+
# "data" now contains the certificate as a base64-encoded string. The
51+
# fingerprint of the certificate is the sha1-hash of the certificate.
52+
return sha1(base64.b64decode(data)).hexdigest().lower()
53+
54+
55+
def format_cert(cert, heads=True):
56+
"""
57+
Returns a x509 cert (adding header & footer if required).
58+
59+
:param cert: A x509 unformated cert
60+
:type: string
61+
62+
:param heads: True if we want to include head and footer
63+
:type: boolean
64+
65+
:returns: Formated cert
66+
:rtype: string
67+
"""
68+
x509_cert = cert.replace('\x0D', '')
69+
x509_cert = x509_cert.replace('\r', '')
70+
x509_cert = x509_cert.replace('\n', '')
71+
if len(x509_cert) > 0:
72+
x509_cert = x509_cert.replace('-----BEGIN CERTIFICATE-----', '')
73+
x509_cert = x509_cert.replace('-----END CERTIFICATE-----', '')
74+
x509_cert = x509_cert.replace(' ', '')
75+
76+
if heads:
77+
x509_cert = '-----BEGIN CERTIFICATE-----\n' + '\n'.join(wrap(x509_cert, 64)) + '\n-----END CERTIFICATE-----\n'
78+
79+
return x509_cert

0 commit comments

Comments
 (0)