def test_reference_uris_and_custom_key_info(self): with open(os.path.join(os.path.dirname(__file__), "example.pem"), "rb") as fh: crt = fh.read() with open(os.path.join(os.path.dirname(__file__), "example.key"), "rb") as fh: key = fh.read() # Both ID and Id formats. XPath 1 doesn't have case insensitive attribute search for d in ['''<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="responseId"> <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="assertionId"> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="placeholder" /> </saml:Assertion> </samlp:Response>''', '''<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Id="responseId"> <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" Id="assertionId"> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="placeholder" /> </saml:Assertion> <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" Id="assertion2"> </saml:Assertion> </samlp:Response>''']: data = etree.fromstring(d) reference_uri = ["assertionId", "assertion2"] if "assertion2" in d else "assertionId" signed_root = XMLSigner().sign(data, reference_uri=reference_uri, key=key, cert=crt) signed_data_root = XMLVerifier().verify(etree.tostring(signed_root), x509_cert=crt, expect_references=True)[1] ref = signed_root.xpath('/samlp:Response/saml:Assertion/ds:Signature/ds:SignedInfo/ds:Reference', namespaces={"ds": "http://www.w3.org/2000/09/xmldsig#", "saml": "urn:oasis:names:tc:SAML:2.0:assertion", "samlp": "urn:oasis:names:tc:SAML:2.0:protocol"}) self.assertEqual("assertionId", ref[0].attrib['URI'][1:]) self.assertEqual("{urn:oasis:names:tc:SAML:2.0:assertion}Assertion", signed_data_root.tag) # Also test with detached signing for c14_transform_option in include_c14_transformation: XMLSigner(method=methods.detached, include_c14n_transform=c14_transform_option ).sign(data, reference_uri=reference_uri, key=key, cert=crt) # Test setting custom key info custom_key_info = etree.fromstring(''' <wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <wsse:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" URI="#uuid-639b8970-7644-4f9e-9bc4-9c2e367808fc-1"/> </wsse:SecurityTokenReference>''') XMLSigner().sign(data, reference_uri=reference_uri, key=key, cert=crt, key_info=custom_key_info)
def test_reference_uris_and_custom_key_info(self): with open(os.path.join(os.path.dirname(__file__), "example.pem"), "rb") as fh: crt = fh.read() with open(os.path.join(os.path.dirname(__file__), "example.key"), "rb") as fh: key = fh.read() # Both ID and Id formats. XPath 1 doesn't have case insensitive attribute search for d in [ '''<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="responseId"> <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="assertionId"> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="placeholder" /> </saml:Assertion> </samlp:Response>''', '''<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Id="responseId"> <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" Id="assertionId"> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="placeholder" /> </saml:Assertion> <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" Id="assertion2"> </saml:Assertion> </samlp:Response>''' ]: data = etree.fromstring(d) reference_uri = ["assertionId", "assertion2" ] if "assertion2" in d else "assertionId" signed_root = XMLSigner().sign(data, reference_uri=reference_uri, key=key, cert=crt) signed_data_root = XMLVerifier().verify( etree.tostring(signed_root), x509_cert=crt, expect_references=True)[1] ref = signed_root.xpath( '/samlp:Response/saml:Assertion/ds:Signature/ds:SignedInfo/ds:Reference', namespaces={ "ds": "http://www.w3.org/2000/09/xmldsig#", "saml": "urn:oasis:names:tc:SAML:2.0:assertion", "samlp": "urn:oasis:names:tc:SAML:2.0:protocol" }) self.assertEqual("assertionId", ref[0].attrib['URI'][1:]) self.assertEqual( "{urn:oasis:names:tc:SAML:2.0:assertion}Assertion", signed_data_root.tag) # Also test with detached signing ref_xpath = "/ds:Signature/ds:SignedInfo/ds:Reference" signer = XMLSigner(method=methods.detached) s = signer.sign(data, reference_uri=reference_uri, key=key, cert=crt) self.assertTrue( s.xpath(ref_xpath + "/ds:Transforms", namespaces=namespaces)) self.assertTrue( s.xpath(ref_xpath + "/ds:DigestMethod", namespaces=namespaces)) self.assertTrue( s.xpath(ref_xpath + "/ds:DigestValue", namespaces=namespaces)) self.assertTrue( s.xpath("/ds:Signature/ds:KeyInfo/ds:X509Data", namespaces=namespaces)) self.assertFalse( s.xpath("/ds:Signature/ds:KeyInfo/ds:KeyValue", namespaces=namespaces)) s2 = signer.sign(data, reference_uri=reference_uri, key=key, cert=crt, always_add_key_value=True) self.assertTrue( s2.xpath("/ds:Signature/ds:KeyInfo/ds:X509Data", namespaces=namespaces)) self.assertTrue( s2.xpath("/ds:Signature/ds:KeyInfo/ds:KeyValue", namespaces=namespaces)) # Test setting custom key info wsse_ns = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" with open( os.path.join(os.path.dirname(__file__), "wsse_keyinfo.xml")) as fh: custom_key_info = etree.fromstring(fh.read()) s3 = signer.sign(data, reference_uri=reference_uri, key=key, cert=crt, key_info=custom_key_info) self.assertTrue( s3.xpath( "/ds:Signature/ds:KeyInfo/wsse:SecurityTokenReference", namespaces=dict(namespaces, wsse=wsse_ns))) # Test setting both X509Data and KeyInfo s4 = XMLSigner().sign(data, reference_uri=reference_uri, key=key, cert=crt, always_add_key_value=True) with self.assertRaisesRegexp(InvalidInput, "Both X509Data and KeyValue found"): XMLVerifier().verify(s4, x509_cert=crt) expect_refs = etree.tostring(s4).decode().count("<ds:Reference") XMLVerifier().verify(s4, x509_cert=crt, ignore_ambiguous_key_info=True, expect_references=expect_refs)
def create_cpix_user_request( key_ids, media_id, user_private_key_path, user_public_cert_path, use_playready=False, use_widevine=False, use_fairplay=False, nsmap=None, ): document_public_cert_path = ( os.path.dirname(__file__) + '/keys/buydrm_qencode_public_cert.pem' ) """Creates CPIX request XML signed end user Arguments: key_ids {list} -- List of Key IDs and corresponding track quality types. The list is of the following format - { 'kid': [string in GUID/UUID format], 'track_type': [string track type]}. media_id {string} -- Some random name for your asset which is shown in KeyOS console and reports. nsmap {list} -- List of namespaces. Returns: string -- CPIX request XML signed end user """ nsmap = nsmap if nsmap is not None else NSMAP # Own private key and end user's private key used to sign the document end_user_private_key = open(user_private_key_path, 'rb').read() # Own public certificate and end user's public certificate to include into the CPIX request end_user_public_cert = open(user_public_cert_path, 'rb').read() document_public_cert = open(document_public_cert_path, 'rb').read() root = etree.Element('{%s}CPIX' % nsmap['cpix'], name=media_id, nsmap=nsmap) root.set('{%s}schemaLocation' % nsmap['xsi'], 'urn:dashif:org:cpix cpix.xsd') # Delivery data list delivery_data_list = etree.SubElement(root, '{%s}DeliveryDataList' % nsmap['cpix']) delivery_data = etree.SubElement( delivery_data_list, '{%s}DeliveryData' % nsmap['cpix'] ) delivery_key = etree.SubElement(delivery_data, '{%s}DeliveryKey' % nsmap['cpix']) # The public certificate of a partner. This certificate's public key will be used # to encrypt Document Key which will later be used to encrypt Contnet Keys. x509_data = etree.SubElement(delivery_key, '{%s}X509Data' % nsmap['ds']) x509_cert = etree.SubElement(x509_data, '{%s}X509Certificate' % nsmap['ds']) x509_cert.text = ( document_public_cert.replace('-----BEGIN CERTIFICATE-----', '') .replace('-----END CERTIFICATE-----', '') .replace('\n', '') ) # Content key list content_key_list = etree.SubElement(root, '{%s}ContentKeyList' % nsmap['cpix']) # Content key usage rules content_key_usage_list = etree.SubElement( root, '{%s}ContentKeyUsageRuleList' % nsmap['cpix'] ) # DRM systems list drm_system_list = etree.SubElement(root, '{%s}DRMSystemList' % nsmap['cpix']) for data in key_ids: etree.SubElement( content_key_list, '{%s}ContentKey' % nsmap['cpix'], kid=data['kid'] ) if use_playready: etree.SubElement( drm_system_list, '{%s}DRMSystem' % nsmap['cpix'], kid=data['kid'], systemId=SYSTEM_ID_PLAYREADY, ) if use_widevine: etree.SubElement( drm_system_list, '{%s}DRMSystem' % nsmap['cpix'], kid=data['kid'], systemId=SYSTEM_ID_WIDEVINE, ) if use_fairplay: etree.SubElement( drm_system_list, '{%s}DRMSystem' % nsmap['cpix'], kid=data['kid'], systemId=SYSTEM_ID_FAIRPLAY, ) etree.SubElement( content_key_usage_list, '{%s}ContentKeyUsageRule' % nsmap['cpix'], kid=data['kid'], intendedTrackType=data['track_type'], ) # Signing document with end user's data end_user_signed_root = XMLSigner( c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315', signature_algorithm='rsa-sha256', digest_algorithm='sha512', ).sign(root, key=end_user_private_key, cert=end_user_public_cert) x509_sign_cert = end_user_signed_root.xpath( '//ds:X509Certificate', namespaces=nsmap )[1] x509_sign_cert.text = x509_sign_cert.text.replace('\n', '') # return etree.tostring(end_user_signed_root).decode('utf-8')