def encrypt_using_xmlsec(xmlsec, data, template, epath=None, key=None, key_file=None, key_file_type="pubkey-pem", session_key=None): """encrypting a value using xmlsec. :param xmlsec: Path to the xmlsec1 binary :param data: A XML document from which the value should be picked. :param template: The encyption part template :param epath: Which value to encrypt, if not the whole document should be encrypted. :param key: The key to be used for the encrypting, either this or :param key_file: The file where the key can be found :param key_file_type: pubkey-pem, pubkey-der, pubkey-cert-pem, pubkey-cert-der, privkey-der, privkey-pem, ... :param session_key: Key algorithm :return: The signed statement """ if not key_file and key: _, key_file = make_temp("%s" % key, ".pem") ntf = NamedTemporaryFile() xpath = create_xpath(epath) com_list = [xmlsec, "encrypt", "--output", ntf.name, "--xml-data", data, '--node-xpath', xpath, key_file_type, key_file ] if session_key: com_list.extend(["--session-key", session_key]) _, fil = make_temp("%s" % template, decode=False) com_list.append(fil) pof = Popen(com_list, stderr=PIPE, stdout=PIPE) p_out = pof.stdout.read() p_err = pof.stderr.read() # this doesn't work if --store-signatures are used if p_out == "": ntf.seek(0) encrypted_statement = ntf.read() if not encrypted_statement: logger.error(p_err) raise Exception("Encryption failed") else: return encrypted_statement else: logger.error(LOG_LINE % (p_out, p_err)) raise Exception("Encryption failed")
def test_encrypted_response_1(self): cert_str_advice, cert_key_str_advice = generate_cert() _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=False, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice=cert_str_advice, ) _resp = "%s" % _resp sresponse = response_from_string(_resp) assert sresponse.signature is None _, key_file = make_temp(cert_key_str_advice, decode=False) decr_text = self.server.sec.decrypt(_resp, key_file) resp = samlp.response_from_string(decr_text) self.verify_advice_assertion(resp, decr_text)
def test_encrypted_response_3(self): cert_str_assertion, cert_key_str_assertion = generate_cert() _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, encrypted_advice_attributes=False, encrypt_cert_assertion=cert_str_assertion ) sresponse = response_from_string(_resp) assert sresponse.signature is None _, key_file = make_temp(cert_key_str_assertion, decode=False) decr_text = self.server.sec.decrypt(_resp, key_file) resp = samlp.response_from_string(decr_text) assert resp.encrypted_assertion[0].extension_elements assertion = extension_elements_to_elements(resp.encrypted_assertion[0].extension_elements, [saml, samlp]) self.verify_encrypted_assertion(assertion, decr_text)
def __init__(self, entity_type, config=None, config_file="", virtual_organization=""): self.entity_type = entity_type self.users = None if config: self.config = config elif config_file: self.config = config_factory(entity_type, config_file) else: raise SAMLError("Missing configuration") for item in ["cert_file", "key_file", "ca_certs"]: _val = getattr(self.config, item, None) if not _val: continue if _val.startswith("http"): r = requests.request("GET", _val) if r.status_code == 200: _, filename = make_temp(r.text, ".pem", False) setattr(self.config, item, filename) else: raise Exception( "Could not fetch certificate from %s" % _val) try: self.signkey = RSA.importKey( open(self.config.getattr("key_file", ""), 'r').read(), passphrase=self.config.key_file_passphrase) except (KeyError, TypeError): self.signkey = None HTTPBase.__init__(self, self.config.verify_ssl_cert, self.config.ca_certs, self.config.key_file, self.config.cert_file) if self.config.vorg: for vo in self.config.vorg.values(): vo.sp = self self.metadata = self.config.metadata self.config.setup_logger() self.debug = self.config.debug self.sec = security_context(self.config) if virtual_organization: if isinstance(virtual_organization, basestring): self.vorg = self.config.vorg[virtual_organization] elif isinstance(virtual_organization, VirtualOrg): self.vorg = virtual_organization else: self.vorg = None self.artifact = {} if self.metadata: self.sourceid = self.metadata.construct_source_id() else: self.sourceid = {}
def test_xbox_non_ascii_ava(): conf = config.SPConfig() conf.load_file("server_conf") md = MetadataStore([saml, samlp], None, conf) md.load("local", IDP_EXAMPLE) conf.metadata = md conf.only_use_keys_in_metadata = False sec = sigver.security_context(conf) assertion = factory( saml.Assertion, version="2.0", id="11111", issue_instant="2009-10-30T13:20:28Z", signature=sigver.pre_signature_part("11111", sec.my_cert, 1), attribute_statement=do_attribute_statement( { ("", "", "surName"): ("Föö", ""), ("", "", "givenName"): ("Bär", ""), } ) ) sigass = sec.sign_statement( assertion, class_name(assertion), key_file=PRIV_KEY, node_id=assertion.id, ) _ass0 = saml.assertion_from_string(sigass) encrypted_assertion = EncryptedAssertion() encrypted_assertion.add_extension_element(_ass0) _, pre = make_temp( str(pre_encryption_part()).encode('utf-8'), decode=False ) enctext = sec.crypto.encrypt( str(encrypted_assertion), conf.cert_file, pre, "des-192", '/*[local-name()="EncryptedAssertion"]/*[local-name()="Assertion"]', ) decr_text = sec.decrypt(enctext, key_file=PRIV_KEY) _seass = saml.encrypted_assertion_from_string(decr_text) assertions = [] assers = extension_elements_to_elements( _seass.extension_elements, [saml, samlp] ) for ass in assers: _txt = sec.verify_signature( str(ass), PUB_KEY, node_name=class_name(assertion) ) if _txt: assertions.append(ass) assert assertions print(assertions)
def _encrypt_assertion(self, encrypt_cert, sp_entity_id, response, node_xpath=None): """ Encryption of assertions. :param encrypt_cert: Certificate to be used for encryption. :param sp_entity_id: Entity ID for the calling service provider. :param response: A samlp.Response :param node_xpath: Unquie path to the element to be encrypted. :return: A new samlp.Resonse with the designated assertion encrypted. """ _certs = [] cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) if encrypt_cert: _certs = [] _certs.append(encrypt_cert) elif sp_entity_id is not None: _certs = self.metadata.certs(sp_entity_id, "any", "encryption") exception = None for _cert in _certs: try: begin_cert = "-----BEGIN CERTIFICATE-----\n" end_cert = "\n-----END CERTIFICATE-----\n" if begin_cert not in _cert: _cert = "%s%s" % (begin_cert, _cert) if end_cert not in _cert: _cert = "%s%s" % (_cert, end_cert) _, cert_file = make_temp(_cert.encode('ascii'), decode=False) response = cbxs.encrypt_assertion(response, cert_file, pre_encryption_part(), node_xpath=node_xpath) return response except Exception as ex: exception = ex pass if exception: raise exception return response
def test_encrypted_signed_response_4(self): cert_str, cert_key_str = generate_cert() signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=True, sign_assertion=True, encrypt_assertion=True, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice=cert_str, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature(signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid decr_text = self.server.sec.decrypt(signed_resp, self.client.config.encryption_keypairs[1]["key_file"]) resp = samlp.response_from_string(decr_text) resp.assertion = extension_elements_to_elements(resp.encrypted_assertion[0].extension_elements, [saml, samlp]) valid = self.server.sec.verify_signature(decr_text, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=resp.assertion[0].id, id_attr="") assert valid _, key_file = make_temp(cert_key_str, decode=False) decr_text = self.server.sec.decrypt(decr_text, key_file) resp = samlp.response_from_string(decr_text) assertion = extension_elements_to_elements(resp.encrypted_assertion[0].extension_elements, [saml, samlp]) assertion = \ extension_elements_to_elements(assertion[0].advice.encrypted_assertion[0].extension_elements,[saml, samlp]) self.verify_assertion(assertion) #PEFIM never signs assertion in advice assert assertion[0].signature is None #valid = self.server.sec.verify_signature(decr_text, # self.server.config.cert_file, # node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', # node_id=assertion[0].id, # id_attr="") assert valid
def test_encrypted_response_2(self): cert_str_advice, cert_key_str_advice = generate_cert() _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice=cert_str_advice, ) sresponse = response_from_string(_resp) assert sresponse.signature is None decr_text_1 = self.server.sec.decrypt(_resp, self.client.config.encryption_keypairs[1]["key_file"]) _, key_file = make_temp(cert_key_str_advice, decode=False) decr_text_2 = self.server.sec.decrypt(decr_text_1, key_file) resp = samlp.response_from_string(decr_text_2) resp.assertion = extension_elements_to_elements(resp.encrypted_assertion[0].extension_elements, [saml, samlp]) self.verify_advice_assertion(resp, decr_text_2)
def test_encrypted_signed_response_2(self): name_id = self.server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp", "id12") ava = {"givenName": ["Derek"], "surName": ["Jeter"], "mail": ["*****@*****.**"], "title": "The man"} cert_str, cert_key_str = generate_cert() signed_resp = self.server.create_authn_response( ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=name_id, sign_response=True, sign_assertion=True, encrypt_assertion=True, encrypt_assertion_self_contained=True, encrypt_cert=cert_str, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature(signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid _, key_file = make_temp("%s" % cert_key_str, decode=False) decr_text = self.server.sec.decrypt(signed_resp, key_file) resp = samlp.response_from_string(decr_text) assert resp.encrypted_assertion[0].extension_elements assertion = extension_elements_to_elements(resp.encrypted_assertion[0].extension_elements, [saml, samlp]) assert assertion assert assertion[0].attribute_statement ava = get_ava(assertion[0]) assert ava ==\ {'mail': ['*****@*****.**'], 'givenname': ['Derek'], 'surname': ['Jeter'], 'title': ['The man']} assert 'EncryptedAssertion><encas2:Assertion xmlns:encas0="http://www.w3.org/2000/09/xmldsig#" ' \ 'xmlns:encas1="http://www.w3.org/2001/XMLSchema-instance" ' \ 'xmlns:encas2="urn:oasis:names:tc:SAML:2.0:assertion"' in decr_text valid = self.server.sec.verify_signature(decr_text, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=assertion[0].id, id_attr="") assert valid
def _response(self, in_response_to, consumer_url=None, status=None, issuer=None, sign=False, to_sign=None, encrypt_assertion=False, encrypt_cert=None, **kwargs): """ Create a Response. :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param status: The status of the response :param issuer: The issuer of the response :param sign: Whether the response should be signed or not :param to_sign: If there are other parts to sign :param kwargs: Extra key word arguments :return: A Response instance """ if not status: status = success_status_factory() _issuer = self._issuer(issuer) response = response_factory(issuer=_issuer, in_response_to=in_response_to, status=status) if consumer_url: response.destination = consumer_url self._add_info(response, **kwargs) if not sign and to_sign and not encrypt_assertion: return signed_instance_factory(response, self.sec, to_sign) if encrypt_assertion: if sign: response.signature = pre_signature_part(response.id, self.sec.my_cert, 1) cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) _, cert_file = make_temp("%s" % encrypt_cert, decode=False) response = cbxs.encrypt_assertion(response, cert_file, pre_encryption_part()) # template(response.assertion.id)) if sign: if to_sign: signed_instance_factory(response, self.sec, to_sign) else: # default is to sign the whole response if anything sign_class = [(class_name(response), response.id)] return signed_instance_factory(response, self.sec, sign_class) else: return response if sign: return self.sign(response, to_sign=to_sign) else: return response
def _parse_response(self, xmlstr, response_cls, service, binding, outstanding_certs=None, **kwargs): """ Deal with a Response :param xmlstr: The response as a xml string :param response_cls: What type of response it is :param binding: What type of binding this message came through. :param kwargs: Extra key word arguments :return: None if the reply doesn't contain a valid SAML Response, otherwise the response. """ response = None if self.config.accepted_time_diff: kwargs["timeslack"] = self.config.accepted_time_diff if "asynchop" not in kwargs: if binding in [BINDING_SOAP, BINDING_PAOS]: kwargs["asynchop"] = False else: kwargs["asynchop"] = True if xmlstr: if "return_addrs" not in kwargs: if binding in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]: try: # expected return address kwargs["return_addrs"] = self.config.endpoint( service, binding=binding) except Exception: logger.info("Not supposed to handle this!") return None try: response = response_cls(self.sec, **kwargs) except Exception, exc: logger.info("%s" % exc) raise xmlstr = self.unravel(xmlstr, binding, response_cls.msgtype) origxml = xmlstr if outstanding_certs is not None: _response = samlp.any_response_from_string(xmlstr) if len(_response.encrypted_assertion) > 0: _, cert_file = make_temp("%s" % outstanding_certs[_response.in_response_to]["key"], decode=False) cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) xmlstr = cbxs.decrypt(xmlstr, cert_file) if not xmlstr: # Not a valid reponse return None try: response = response.loads(xmlstr, False, origxml=origxml) except SigverError, err: logger.error("Signature Error: %s" % err) raise
def _certs(self, key_descriptors, typ): certs = {} for key_desc in key_descriptors: use = key_desc.use for cert in cert_from_key_info(key_desc.key_info): chash = signature("", [cert]) try: cert = self._keys[chash] except KeyError: if typ == "pem": cert = make_temp(pem_format(cert), ".pem", False) elif typ == "der": cert = make_temp(cert, suffix=".der") self._keys[chash] = cert try: certs[use].append(chash) except KeyError: certs[use] = [chash] return certs
def test_xbox(): conf = config.SPConfig() conf.load_file("server_conf") md = MetadataStore([saml, samlp], None, conf) md.load("local", full_path("idp_example.xml")) conf.metadata = md conf.only_use_keys_in_metadata = False sec = sigver.security_context(conf) assertion = factory( saml.Assertion, version="2.0", id="11111", issue_instant="2009-10-30T13:20:28Z", signature=sigver.pre_signature_part("11111", sec.my_cert, 1), attribute_statement=do_attribute_statement( {("", "", "surName"): ("Foo", ""), ("", "", "givenName"): ("Bar", "")} ), ) sigass = sec.sign_statement(assertion, class_name(assertion), key_file=full_path("test.key"), node_id=assertion.id) _ass0 = saml.assertion_from_string(sigass) encrypted_assertion = EncryptedAssertion() encrypted_assertion.add_extension_element(_ass0) _, pre = make_temp(str(pre_encryption_part()).encode("utf-8"), decode=False) enctext = sec.crypto.encrypt( str(encrypted_assertion), conf.cert_file, pre, "des-192", '/*[local-name()="EncryptedAssertion"]/*[local-name()="Assertion"]', ) decr_text = sec.decrypt(enctext) _seass = saml.encrypted_assertion_from_string(decr_text) assertions = [] assers = extension_elements_to_elements(_seass.extension_elements, [saml, samlp]) sign_cert_file = full_path("test.pem") for ass in assers: _ass = "%s" % ass # _ass = _ass.replace('xsi:nil="true" ', '') # assert sigass == _ass _txt = sec.verify_signature(_ass, sign_cert_file, node_name=class_name(assertion)) if _txt: assertions.append(ass) print(assertions)
def test_encrypted_signed_response_3(self): cert_str, cert_key_str = generate_cert() signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=True, sign_assertion=True, encrypt_assertion=True, encrypt_assertion_self_contained=False, encrypt_cert_assertion=cert_str, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid _, key_file = make_temp(cert_key_str, decode=False) decr_text = self.server.sec.decrypt(signed_resp, key_file) resp = samlp.response_from_string(decr_text) resp.assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) valid = self.server.sec.verify_signature( decr_text, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=resp.assertion[0].id, id_attr="") assert valid self.verify_assertion(resp.assertion) assert 'xmlns:encas' not in decr_text
def test_encrypted_signed_response_3(self): cert_str, cert_key_str = generate_cert() signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=True, sign_assertion=True, encrypt_assertion=True, encrypt_assertion_self_contained=False, encrypt_cert_assertion=cert_str, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature(signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid _, key_file = make_temp(cert_key_str, decode=False) decr_text = self.server.sec.decrypt(signed_resp, key_file) resp = samlp.response_from_string(decr_text) resp.assertion = extension_elements_to_elements(resp.encrypted_assertion[0].extension_elements, [saml, samlp]) valid = self.server.sec.verify_signature(decr_text, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=resp.assertion[0].id, id_attr="") assert valid self.verify_assertion(resp.assertion) assert 'xmlns:encas' not in decr_text
def test_encrypted_signed_response_4(self): cert_str, cert_key_str = generate_cert() signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=True, sign_assertion=True, encrypt_assertion=True, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice=cert_str, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid decr_text = self.server.sec.decrypt( signed_resp, self.client.config.encryption_keypairs[1]["key_file"]) resp = samlp.response_from_string(decr_text) resp.assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) valid = self.server.sec.verify_signature( decr_text, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=resp.assertion[0].id, id_attr="") assert valid _, key_file = make_temp(cert_key_str, decode=False) decr_text = self.server.sec.decrypt(decr_text, key_file) resp = samlp.response_from_string(decr_text) assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) assertion = \ extension_elements_to_elements(assertion[0].advice.encrypted_assertion[0].extension_elements,[saml, samlp]) self.verify_assertion(assertion) #PEFIM never signs assertion in advice assert assertion[0].signature is None #valid = self.server.sec.verify_signature(decr_text, # self.server.config.cert_file, # node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', # node_id=assertion[0].id, # id_attr="") assert valid
def decrypt_message(enctext, xmlsec_binary, key_file=None, key_file_type="privkey-pem", cafile=None, epath=None, id_attr="", node_name="", node_id=None, debug=False): """ Decrypts an encrypted part of a XML document. :param enctext: XML document containing an encrypted part :param xmlsec_binary: The xmlsec1 binaries to be used :param key_file: The key used to decrypt the message :param key_file_type: The key file type :param node_name: The SAML class of the root node in the message :param node_id: The identifier of the root node if any :param id_attr: Should normally be one of "id", "Id" or "ID" :param debug: To debug or not :return: The decrypted document if all was OK otherwise will raise an exception. """ if not id_attr: id_attr = ID_ATTR _, fil = make_temp(enctext, decode=False) com_list = [xmlsec_binary, "--decrypt", "--%s" % key_file_type, key_file] if key_file_type in [ "privkey-pem", "privkey-der", "pkcs8-pem", "pkcs8-der" ]: if isinstance(cafile, basestring): com_list.append(cafile) else: com_list.extend(cafile) if id_attr: com_list.extend(["--id-attr:%s" % id_attr, node_name]) elif epath: xpath = create_xpath(epath) com_list.extend(['--node-xpath', xpath]) # if debug: # com_list.append("--store-signatures") if node_id: com_list.extend(["--node-id", node_id]) com_list.append(fil) if debug: try: print " ".join(com_list) except TypeError: print "key_file_type", key_file_type print "key_file", key_file print "node_name", node_name print "fil", fil raise print "%s: %s" % (key_file, os.access(key_file, os.F_OK)) print "%s: %s" % (fil, os.access(fil, os.F_OK)) pof = Popen(com_list, stderr=PIPE, stdout=PIPE) p_out = pof.stdout.read() try: p_err = pof.stderr.read() if debug: print p_err verified = parse_xmlsec_output(p_err) except XmlsecError, exc: logger(LOG_LINE % (p_out, exc)) raise DecryptionError("%s" % (exc, ))
logger.error("Signature Error: %s" % err) raise except UnsolicitedResponse: logger.error("Unsolicited response") raise except Exception, err: if "not well-formed" in "%s" % err: logger.error("Not well-formed XML") raise logger.debug("XMLSTR: %s" % xmlstr) if response: if outstanding_certs: _, key_file = make_temp( "%s" % outstanding_certs[ response.in_response_to]["key"], decode=False) else: key_file = "" response = response.verify(key_file) if not response: return None #logger.debug(response) return response # ------------------------------------------------------------------------ def parse_logout_request_response(self, xmlstr, binding=BINDING_SOAP):
def _response(self, in_response_to, consumer_url=None, status=None, issuer=None, sign=False, to_sign=None, encrypt_assertion=False, encrypt_assertion_self_contained=False, encrypted_advice_attributes=False, encrypt_cert=None, **kwargs): """ Create a Response. Encryption: encrypt_assertion must be true for encryption to be performed. If encrypted_advice_attributes also is true, then will the function try to encrypt the assertion in the the advice element of the main assertion. Only one assertion element is allowed in the advice element, if multiple assertions exists in the advice element the main assertion will be encrypted instead, since it's no point to encrypt If encrypted_advice_attributes is false the main assertion will be encrypted. Since the same key :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param status: The status of the response :param issuer: The issuer of the response :param sign: Whether the response should be signed or not :param to_sign: If there are other parts to sign :param kwargs: Extra key word arguments :return: A Response instance """ if not status: status = success_status_factory() _issuer = self._issuer(issuer) response = response_factory(issuer=_issuer, in_response_to=in_response_to, status=status) if consumer_url: response.destination = consumer_url self._add_info(response, **kwargs) if not sign and to_sign and not encrypt_assertion: return signed_instance_factory(response, self.sec, to_sign) if encrypt_assertion: node_xpath = None if sign: response.signature = pre_signature_part(response.id, self.sec.my_cert, 1) sign_class = [(class_name(response), response.id)] cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) if encrypted_advice_attributes and response.assertion.advice is not None \ and len(response.assertion.advice.assertion) == 1: tmp_assertion = response.assertion.advice.assertion[0] response.assertion.advice.encrypted_assertion = [] response.assertion.advice.encrypted_assertion.append(EncryptedAssertion()) if isinstance(tmp_assertion, list): response.assertion.advice.encrypted_assertion[0].add_extension_elements(tmp_assertion) else: response.assertion.advice.encrypted_assertion[0].add_extension_element(tmp_assertion) response.assertion.advice.assertion = [] if encrypt_assertion_self_contained: advice_tag = response.assertion.advice._to_element_tree().tag assertion_tag = tmp_assertion._to_element_tree().tag response = response.get_xml_string_with_self_contained_assertion_within_advice_encrypted_assertion( assertion_tag, advice_tag) node_xpath = ''.join(["/*[local-name()=\"%s\"]" % v for v in ["Response", "Assertion", "Advice", "EncryptedAssertion", "Assertion"]]) elif encrypt_assertion_self_contained: assertion_tag = response.assertion._to_element_tree().tag response = pre_encrypt_assertion(response) response = response.get_xml_string_with_self_contained_assertion_within_encrypted_assertion( assertion_tag) else: response = pre_encrypt_assertion(response) if to_sign: response = signed_instance_factory(response, self.sec, to_sign) _, cert_file = make_temp("%s" % encrypt_cert, decode=False) response = cbxs.encrypt_assertion(response, cert_file, pre_encryption_part(), node_xpath=node_xpath) # template(response.assertion.id)) if sign: return signed_instance_factory(response, self.sec, sign_class) else: return response if sign: return self.sign(response, to_sign=to_sign) else: return response
def _response(self, in_response_to, consumer_url=None, status=None, issuer=None, sign=False, to_sign=None, encrypt_assertion=False, encrypt_cert=None, **kwargs): """ Create a Response. :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param status: The status of the response :param issuer: The issuer of the response :param sign: Whether the response should be signed or not :param to_sign: If there are other parts to sign :param kwargs: Extra key word arguments :return: A Response instance """ if not status: status = success_status_factory() _issuer = self._issuer(issuer) response = response_factory(issuer=_issuer, in_response_to=in_response_to, status=status) if consumer_url: response.destination = consumer_url self._add_info(response, **kwargs) if not sign and to_sign and not encrypt_assertion: return signed_instance_factory(response, self.sec, to_sign) if encrypt_assertion: if sign: response.signature = pre_signature_part( response.id, self.sec.my_cert, 1) cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) _, cert_file = make_temp("%s" % encrypt_cert, decode=False) response = cbxs.encrypt_assertion(response, cert_file, pre_encryption_part()) # template(response.assertion.id)) if sign: if to_sign: signed_instance_factory(response, self.sec, to_sign) else: # default is to sign the whole response if anything sign_class = [(class_name(response), response.id)] return signed_instance_factory(response, self.sec, sign_class) else: return response if sign: return self.sign(response, to_sign=to_sign) else: return response
def __init__(self, entity_type, config=None, config_file="", virtual_organization=""): self.entity_type = entity_type self.users = None if config: self.config = config elif config_file: self.config = config_factory(entity_type, config_file) else: raise SAMLError("Missing configuration") for item in ["cert_file", "key_file", "ca_certs"]: _val = getattr(self.config, item, None) if not _val: continue if _val.startswith("http"): r = requests.request("GET", _val) if r.status_code == 200: _, filename = make_temp(r.text, ".pem", False) setattr(self.config, item, filename) else: raise Exception("Could not fetch certificate from %s" % _val) try: self.signkey = RSA.importKey( open(self.config.getattr("key_file", ""), 'r').read()) except (KeyError, TypeError): self.signkey = None HTTPBase.__init__(self, self.config.verify_ssl_cert, self.config.ca_certs, self.config.key_file, self.config.cert_file) if self.config.vorg: for vo in self.config.vorg.values(): vo.sp = self self.metadata = self.config.metadata self.config.setup_logger() self.debug = self.config.debug self.seed = rndstr(32) self.sec = security_context(self.config) if virtual_organization: if isinstance(virtual_organization, basestring): self.vorg = self.config.vorg[virtual_organization] elif isinstance(virtual_organization, VirtualOrg): self.vorg = virtual_organization else: self.vorg = None self.artifact = {} if self.metadata: self.sourceid = self.metadata.construct_source_id() else: self.sourceid = {}
logger.error("Signature Error: %s" % err) raise except UnsolicitedResponse: logger.error("Unsolicited response") raise except Exception, err: if "not well-formed" in "%s" % err: logger.error("Not well-formed XML") raise logger.debug("XMLSTR: %s" % xmlstr) if response: if outstanding_certs: _, key_file = make_temp( "%s" % outstanding_certs[response.in_response_to]["key"], decode=False) else: key_file = "" response = response.verify(key_file) if not response: return None #logger.debug(response) return response # ------------------------------------------------------------------------ def parse_logout_request_response(self, xmlstr, binding=BINDING_SOAP):
def _response(self, in_response_to, consumer_url=None, status=None, issuer=None, sign=False, to_sign=None, encrypt_assertion=False, encrypt_assertion_self_contained=False, encrypted_advice_attributes=False, encrypt_cert=None, **kwargs): """ Create a Response. Encryption: encrypt_assertion must be true for encryption to be performed. If encrypted_advice_attributes also is true, then will the function try to encrypt the assertion in the the advice element of the main assertion. Only one assertion element is allowed in the advice element, if multiple assertions exists in the advice element the main assertion will be encrypted instead, since it's no point to encrypt If encrypted_advice_attributes is false the main assertion will be encrypted. Since the same key :param in_response_to: The session identifier of the request :param consumer_url: The URL which should receive the response :param status: The status of the response :param issuer: The issuer of the response :param sign: Whether the response should be signed or not :param to_sign: If there are other parts to sign :param kwargs: Extra key word arguments :return: A Response instance """ if not status: status = success_status_factory() _issuer = self._issuer(issuer) response = response_factory(issuer=_issuer, in_response_to=in_response_to, status=status) if consumer_url: response.destination = consumer_url self._add_info(response, **kwargs) if not sign and to_sign and not encrypt_assertion: return signed_instance_factory(response, self.sec, to_sign) if encrypt_assertion: node_xpath = None if sign: response.signature = pre_signature_part( response.id, self.sec.my_cert, 1) sign_class = [(class_name(response), response.id)] cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) if encrypted_advice_attributes and response.assertion.advice is not None \ and len(response.assertion.advice.assertion) == 1: tmp_assertion = response.assertion.advice.assertion[0] response.assertion.advice.encrypted_assertion = [] response.assertion.advice.encrypted_assertion.append( EncryptedAssertion()) if isinstance(tmp_assertion, list): response.assertion.advice.encrypted_assertion[ 0].add_extension_elements(tmp_assertion) else: response.assertion.advice.encrypted_assertion[ 0].add_extension_element(tmp_assertion) response.assertion.advice.assertion = [] if encrypt_assertion_self_contained: advice_tag = response.assertion.advice._to_element_tree( ).tag assertion_tag = tmp_assertion._to_element_tree().tag response = response.get_xml_string_with_self_contained_assertion_within_advice_encrypted_assertion( assertion_tag, advice_tag) node_xpath = ''.join([ "/*[local-name()=\"%s\"]" % v for v in [ "Response", "Assertion", "Advice", "EncryptedAssertion", "Assertion" ] ]) elif encrypt_assertion_self_contained: assertion_tag = response.assertion._to_element_tree().tag response = pre_encrypt_assertion(response) response = response.get_xml_string_with_self_contained_assertion_within_encrypted_assertion( assertion_tag) else: response = pre_encrypt_assertion(response) if to_sign: response = signed_instance_factory(response, self.sec, to_sign) _, cert_file = make_temp("%s" % encrypt_cert, decode=False) response = cbxs.encrypt_assertion(response, cert_file, pre_encryption_part(), node_xpath=node_xpath) # template(response.assertion.id)) if sign: return signed_instance_factory(response, self.sec, sign_class) else: return response if sign: return self.sign(response, to_sign=to_sign) else: return response
def decrypt_message(enctext, xmlsec_binary, key_file=None, key_file_type="privkey-pem", cafile=None, epath=None, id_attr="", node_name="", node_id=None, debug=False): """ Decrypts an encrypted part of a XML document. :param enctext: XML document containing an encrypted part :param xmlsec_binary: The xmlsec1 binaries to be used :param key_file: The key used to decrypt the message :param key_file_type: The key file type :param node_name: The SAML class of the root node in the message :param node_id: The identifier of the root node if any :param id_attr: Should normally be one of "id", "Id" or "ID" :param debug: To debug or not :return: The decrypted document if all was OK otherwise will raise an exception. """ if not id_attr: id_attr = ID_ATTR _, fil = make_temp(enctext, decode=False) com_list = [xmlsec_binary, "--decrypt", "--%s" % key_file_type, key_file] if key_file_type in ["privkey-pem", "privkey-der", "pkcs8-pem", "pkcs8-der"]: if isinstance(cafile, basestring): com_list.append(cafile) else: com_list.extend(cafile) if id_attr: com_list.extend(["--id-attr:%s" % id_attr, node_name]) elif epath: xpath = create_xpath(epath) com_list.extend(['--node-xpath', xpath]) # if debug: # com_list.append("--store-signatures") if node_id: com_list.extend(["--node-id", node_id]) com_list.append(fil) if debug: try: print " ".join(com_list) except TypeError: print "key_file_type", key_file_type print "key_file", key_file print "node_name", node_name print "fil", fil raise print "%s: %s" % (key_file, os.access(key_file, os.F_OK)) print "%s: %s" % (fil, os.access(fil, os.F_OK)) pof = Popen(com_list, stderr=PIPE, stdout=PIPE) p_out = pof.stdout.read() try: p_err = pof.stderr.read() if debug: print p_err verified = parse_xmlsec_output(p_err) except XmlsecError, exc: logger(LOG_LINE % (p_out, exc)) raise DecryptionError("%s" % (exc,))
def _parse_response(self, xmlstr, response_cls, service, binding, outstanding_certs=None, **kwargs): """ Deal with a Response :param xmlstr: The response as a xml string :param response_cls: What type of response it is :param binding: What type of binding this message came through. :param kwargs: Extra key word arguments :return: None if the reply doesn't contain a valid SAML Response, otherwise the response. """ response = None if self.config.accepted_time_diff: kwargs["timeslack"] = self.config.accepted_time_diff if "asynchop" not in kwargs: if binding in [BINDING_SOAP, BINDING_PAOS]: kwargs["asynchop"] = False else: kwargs["asynchop"] = True if xmlstr: if "return_addrs" not in kwargs: if binding in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]: try: # expected return address kwargs["return_addrs"] = self.config.endpoint( service, binding=binding) except Exception: logger.info("Not supposed to handle this!") return None try: response = response_cls(self.sec, **kwargs) except Exception as exc: logger.info("%s" % exc) raise xmlstr = self.unravel(xmlstr, binding, response_cls.msgtype) origxml = xmlstr if outstanding_certs is not None: _response = samlp.any_response_from_string(xmlstr) if len(_response.encrypted_assertion) > 0: _, cert_file = make_temp( "%s" % outstanding_certs[_response.in_response_to]["key"], decode=False) cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) xmlstr = cbxs.decrypt(xmlstr, cert_file) if not xmlstr: # Not a valid reponse return None try: response = response.loads(xmlstr, False, origxml=origxml) except SigverError as err: logger.error("Signature Error: %s" % err) raise except UnsolicitedResponse: logger.error("Unsolicited response") raise except Exception as err: if "not well-formed" in "%s" % err: logger.error("Not well-formed XML") raise logger.debug("XMLSTR: %s" % xmlstr) if hasattr(response.response, 'encrypted_assertion'): for encrypted_assertion in response.response\ .encrypted_assertion: if encrypted_assertion.extension_elements is not None: assertion_list = extension_elements_to_elements( encrypted_assertion.extension_elements, [saml]) for assertion in assertion_list: _assertion = saml.assertion_from_string( str(assertion)) response.response.assertion.append(_assertion) if response: response = response.verify() if not response: return None #logger.debug(response) return response
def test_encrypted_signed_response_4(self): name_id = self.server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp", "id12") ava = { "givenName": ["Derek"], "surName": ["Jeter"], "mail": ["*****@*****.**"], "title": "The man" } cert_str, cert_key_str = generate_cert() signed_resp = self.server.create_authn_response( ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=name_id, sign_response=True, sign_assertion=True, encrypt_assertion=True, encrypted_advice_attributes=True, encrypt_cert=cert_str, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid _, key_file = make_temp("%s" % cert_key_str, decode=False) decr_text = self.server.sec.decrypt(signed_resp, key_file) resp = samlp.response_from_string(decr_text) valid = self.server.sec.verify_signature( decr_text, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=resp.assertion[0].id, id_attr="") assert valid assert resp.assertion[0].advice.encrypted_assertion[ 0].extension_elements assertion = extension_elements_to_elements( resp.assertion[0].advice.encrypted_assertion[0].extension_elements, [saml, samlp]) assert assertion assert assertion[0].attribute_statement ava = ava = get_ava(assertion[0]) assert ava ==\ {'mail': ['*****@*****.**'], 'givenname': ['Derek'], 'surname': ['Jeter'], 'title': ['The man']} #Should work, but I suspect that xmlsec manipulates the xml to much while encrypting that the signature #is no longer working. :( assert 'xmlns:encas' not in decr_text valid = self.server.sec.verify_signature( decr_text, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=assertion[0].id, id_attr="") assert valid
def test_encrypted_signed_response_2(self): name_id = self.server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp", "id12") ava = { "givenName": ["Derek"], "surName": ["Jeter"], "mail": ["*****@*****.**"], "title": "The man" } cert_str, cert_key_str = generate_cert() signed_resp = self.server.create_authn_response( ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=name_id, sign_response=True, sign_assertion=True, encrypt_assertion=True, encrypt_assertion_self_contained=True, encrypt_cert=cert_str, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid _, key_file = make_temp("%s" % cert_key_str, decode=False) decr_text = self.server.sec.decrypt(signed_resp, key_file) resp = samlp.response_from_string(decr_text) assert resp.encrypted_assertion[0].extension_elements assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) assert assertion assert assertion[0].attribute_statement ava = get_ava(assertion[0]) assert ava ==\ {'mail': ['*****@*****.**'], 'givenname': ['Derek'], 'surname': ['Jeter'], 'title': ['The man']} assert 'EncryptedAssertion><encas2:Assertion xmlns:encas0="http://www.w3.org/2000/09/xmldsig#" ' \ 'xmlns:encas1="http://www.w3.org/2001/XMLSchema-instance" ' \ 'xmlns:encas2="urn:oasis:names:tc:SAML:2.0:assertion"' in decr_text valid = self.server.sec.verify_signature( decr_text, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=assertion[0].id, id_attr="") assert valid
raise except Exception, err: if "not well-formed" in "%s" % err: logger.error("Not well-formed XML") raise logger.debug("XMLSTR: %s" % xmlstr) if response: if outstanding_certs: try: cert = outstanding_certs[response.in_response_to] except KeyError: key_file = "" else: _, key_file = make_temp("%s" % cert["key"], decode=False) else: key_file = "" response = response.verify(key_file) if not response: return None #logger.debug(response) return response # ------------------------------------------------------------------------ def parse_logout_request_response(self, xmlstr, binding=BINDING_SOAP): return self._parse_response(xmlstr, LogoutResponse,
def test_encrypted_signed_response_4(self): name_id = self.server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp", "id12") ava = {"givenName": ["Derek"], "surName": ["Jeter"], "mail": ["*****@*****.**"], "title": "The man"} cert_str, cert_key_str = generate_cert() signed_resp = self.server.create_authn_response( ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=name_id, sign_response=True, sign_assertion=True, encrypt_assertion=True, encrypted_advice_attributes=True, encrypt_cert=cert_str, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature(signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid _, key_file = make_temp("%s" % cert_key_str, decode=False) decr_text = self.server.sec.decrypt(signed_resp, key_file) resp = samlp.response_from_string(decr_text) valid = self.server.sec.verify_signature(decr_text, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=resp.assertion[0].id, id_attr="") assert valid assert resp.assertion[0].advice.encrypted_assertion[0].extension_elements assertion = extension_elements_to_elements(resp.assertion[0].advice.encrypted_assertion[0].extension_elements, [saml, samlp]) assert assertion assert assertion[0].attribute_statement ava = ava = get_ava(assertion[0]) assert ava ==\ {'mail': ['*****@*****.**'], 'givenname': ['Derek'], 'surname': ['Jeter'], 'title': ['The man']} #Should work, but I suspect that xmlsec manipulates the xml to much while encrypting that the signature #is no longer working. :( assert 'xmlns:encas' not in decr_text valid = self.server.sec.verify_signature(decr_text, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=assertion[0].id, id_attr="") assert valid
def _parse_response(self, xmlstr, response_cls, service, binding, outstanding_certs=None, **kwargs): """ Deal with a Response :param xmlstr: The response as a xml string :param response_cls: What type of response it is :param binding: What type of binding this message came through. :param outstanding_certs: Certificates that belongs to me that the IdP may have used to encrypt a response/assertion/.. :param kwargs: Extra key word arguments :return: None if the reply doesn't contain a valid SAML Response, otherwise the response. """ response = None if self.config.accepted_time_diff: kwargs["timeslack"] = self.config.accepted_time_diff if "asynchop" not in kwargs: if binding in [BINDING_SOAP, BINDING_PAOS]: kwargs["asynchop"] = False else: kwargs["asynchop"] = True if xmlstr: if "return_addrs" not in kwargs: if binding in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]: try: # expected return address kwargs["return_addrs"] = self.config.endpoint( service, binding=binding) except Exception: logger.info("Not supposed to handle this!") return None try: response = response_cls(self.sec, **kwargs) except Exception as exc: logger.info("%s" % exc) raise xmlstr = self.unravel(xmlstr, binding, response_cls.msgtype) origxml = xmlstr if not xmlstr: # Not a valid reponse return None try: response = response.loads(xmlstr, False, origxml=origxml) except SigverError as err: logger.error("Signature Error: %s" % err) raise except UnsolicitedResponse: logger.error("Unsolicited response") raise except Exception as err: if "not well-formed" in "%s" % err: logger.error("Not well-formed XML") raise logger.debug("XMLSTR: %s" % xmlstr) if response: if outstanding_certs: try: cert = outstanding_certs[response.in_response_to] except KeyError: key_file = "" else: _, key_file = make_temp("%s" % cert["key"], decode=False) else: key_file = "" only_identity_in_encrypted_assertion = False if "only_identity_in_encrypted_assertion" in kwargs: only_identity_in_encrypted_assertion = kwargs["only_identity_in_encrypted_assertion"] decrypt = True if "decrypt" in kwargs: decrypt = kwargs["decrypt"] response = response.verify(key_file, decrypt=decrypt) if not response: return None #logger.debug(response) return response