Skip to content

Commit

Permalink
Add fingerprint support, check local IdP cert with xml cert
Browse files Browse the repository at this point in the history
  • Loading branch information
pitbulk committed Jun 26, 2014
1 parent 5d2e297 commit eabe609
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 15 deletions.
10 changes: 9 additions & 1 deletion example.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from BaseHTTPServer import HTTPServer

from onelogin.saml import AuthRequest, Response
from onelogin.saml.Utils import format_finger_print, calculate_x509_fingerprint


__version__ = '0.1'

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

with open(cert_path) as f:
settings['idp_cert_fingerprint'] = f.read()
cert = f.read()
fingerprint = calculate_x509_fingerprint(cert)
if fingerprint:
settings['idp_cert_fingerprint'] = fingerprint
else:
formated = format_finger_print(settings['idp_cert_fingerprint'])
settings['idp_cert_fingerprint'] = formated

parts = urlparse.urlparse(settings['assertion_consumer_service_url'])
SampleAppHTTPRequestHandler.protocol_version = 'HTTP/1.0'
Expand Down
1 change: 1 addition & 0 deletions onelogin/saml/Response.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from onelogin.saml import SignatureVerifier


namespaces = dict(
samlp='urn:oasis:names:tc:SAML:2.0:protocol',
saml='urn:oasis:names:tc:SAML:2.0:assertion',
Expand Down
25 changes: 11 additions & 14 deletions onelogin/saml/SignatureVerifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from lxml import etree

from onelogin.saml.Utils import calculate_x509_fingerprint, format_cert

log = logging.getLogger(__name__)


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

certificateNodes = document.xpath("//ds:X509Certificate", namespaces={'ds': 'http://www.w3.org/2000/09/xmldsig#'})

if not certificateNodes or calculate_x509_fingerprint(certificateNodes[0].text) != signature:
return False
else:
# use the x509 cert instead of fingerprint required by xmlsec
signature = format_cert(certificateNodes[0].text)

xmlsec_bin = _get_xmlsec_bin()

verified = False
Expand All @@ -94,20 +104,7 @@ def verify(document, signature, _etree=None, _tempfile=None, _subprocess=None,
xml_fp.write(doc_str)
xml_fp.seek(0)
with _tempfile.NamedTemporaryFile(delete=False) as cert_fp:
if signature.startswith(
'-----BEGIN CERTIFICATE-----'
):
# If there's no matching 'END CERTIFICATE'
# cryptpAppKeyLoad will fail
cert_fp.write(signature)
else:
cert_fp.write(
'{begin}\n{signature}\n{end}'.format(
begin='-----BEGIN CERTIFICATE-----',
signature=signature,
end='-----END CERTIFICATE-----',
)
)
cert_fp.write(signature)
cert_fp.seek(0)

cert_filename = cert_fp.name
Expand Down
79 changes: 79 additions & 0 deletions onelogin/saml/Utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import base64
from hashlib import sha1
from textwrap import wrap


def format_finger_print(fingerprint):
"""
Formates a fingerprint.
:param fingerprint: fingerprint
:type: string
:returns: Formated fingerprint
:rtype: string
"""
formated_fingerprint = fingerprint.replace(':', '')
return formated_fingerprint.lower()


def calculate_x509_fingerprint(x509_cert):
"""
Calculates the fingerprint of a x509cert.
:param x509_cert: x509 cert
:type: string
:returns: Formated fingerprint
:rtype: string
"""
assert isinstance(x509_cert, basestring)

lines = x509_cert.split('\n')
data = ''

for line in lines:
# Remove '\r' from end of line if present.
line = line.rstrip()
if line == '-----BEGIN CERTIFICATE-----':
# Delete junk from before the certificate.
data = ''
elif line == '-----END CERTIFICATE-----':
# Ignore data after the certificate.
break
elif line == '-----BEGIN PUBLIC KEY-----' or line == '-----BEGIN RSA PRIVATE KEY-----':
# This isn't an X509 certificate.
return None
else:
# Append the current line to the certificate data.
data += line
# "data" now contains the certificate as a base64-encoded string. The
# fingerprint of the certificate is the sha1-hash of the certificate.
return sha1(base64.b64decode(data)).hexdigest().lower()


def format_cert(cert, heads=True):
"""
Returns a x509 cert (adding header & footer if required).
:param cert: A x509 unformated cert
:type: string
:param heads: True if we want to include head and footer
:type: boolean
:returns: Formated cert
:rtype: string
"""
x509_cert = cert.replace('\x0D', '')
x509_cert = x509_cert.replace('\r', '')
x509_cert = x509_cert.replace('\n', '')
if len(x509_cert) > 0:
x509_cert = x509_cert.replace('-----BEGIN CERTIFICATE-----', '')
x509_cert = x509_cert.replace('-----END CERTIFICATE-----', '')
x509_cert = x509_cert.replace(' ', '')

if heads:
x509_cert = '-----BEGIN CERTIFICATE-----\n' + '\n'.join(wrap(x509_cert, 64)) + '\n-----END CERTIFICATE-----\n'

return x509_cert

0 comments on commit eabe609

Please sign in to comment.