Sign SAML Response in Python - python-3.x

I would need to sign a SAML assertion as it's possible to do on this page :
https://www.samltool.com/sign_response.php
From my side, I'm using Python, and my SAML is in a string.
What would you advise ?
Best regards,
Nico.

I finally made it using signxml lib.
Just to remind, I wanted to sign the SAML assertion.
I get the unsigned SAML from a file. Then I place this tag <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="placeholder"></ds:Signature> between Issuer tag and Subject tag, as it's recommended by signxml lib. And finally, I sign my SAML and verify the signature. Note that I changed the c14n_algorithm to be compatible with my services.
Here's the code :
import re
from lxml import etree
from signxml import XMLSigner, XMLVerifier
with open('saml_to_sign.xml', 'r') as file :
data_to_sign = file.read()
with open("/vagrant/my_cert.crt", "r") as cert,\
open("/vagrant/my_key.key", "r") as key:
certificate = cert.read()
private_key = key.read()
p = re.search('<Subject>', data_to_sign).start()
tmp_message = data_to_sign[:p]
tmp_message = tmp_message +\
'<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="placeholder"></ds:Signature>'
data_to_sign = tmp_message + data_to_sign[p:]
print(data_to_sign)
saml_root = etree.fromstring(data_to_sign)
signed_saml_root = XMLSigner(c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#")\
.sign(saml_root, key=private_key, cert=certificate)
verified_data = XMLVerifier().verify(signed_saml_root, x509_cert=certificate).signed_xml
signed_saml_root_str = etree.tostring(signed_saml_root, encoding='unicode')
print(signed_saml_root_str)

Related

Unable to authenticate Looker API in Databricks using Python

I want to access some charts -which I have saved in Looker- within Databricks. Part of this process is the authentication. I have one Looker auth-script which works but only pulls the tabular results into Databricks which corresponds to a Looker-View. Instead, I want ONLY the charts to be accessed in Databricks which will correspond to a Looker-look or Looker-space. However, when I follow the tutorial on https://discourse.looker.com/t/generating-a-powerpoint-presentation-from-all-looks-in-a-space/8191, I am not able to authenticate with their script. Hopefully, someone can help.
**Working auth-script for Looker-Views**
import looker_tools as tools
api=tools.LookerApi(
api_endpoint="abcd",
client_id=dbutils.secrets.get(scope="looker-api", key="looker_client_id"),
client_secret=dbutils.secrets.get(scope="looker-api",key="looker_client_secret")
)
token = api.login()
**Desired auth-script for Looker-Space/Looks as per tutorial link**
looker_instance = 'your-company.looker.com'
target_space = # 'Period over Period' Space on the Looker instance
client_id = 'xxxxxxxx'
client_secret = 'xxxxxxxx'
# instantiate Auth API
unauthenticated_client = looker_client.ApiClient(configuration=None)
unauthenticated_client.configuration.host = f'https://{looker_instance}:19999/api/3.0/'
unauthenticated_authApi = looker_client.ApiAuthApi(unauthenticated_client)
# authenticate client
token = unauthenticated_authApi.login(client_id=client_id, client_secret=client_secret)
client = looker_client.ApiClient(header_name='Authorization', header_value='token ' + token.access_token)
client.configuration.host = f'https://{looker_instance}:19999/api/3.0/'
I tried translating the code from Current to DESIRED auth-script but the error states the looker_client is not defined!
looker_instance = 'abcd'
target_space = 123
client_id = dbutils.secrets.get(scope="looker-api", key="looker_client_id")
client_secret = dbutils.secrets.get(scope="looker-api",key="looker_client_secret")
# instantiate Auth API
unauthenticated_client = looker_client.ApiClient(configuration=None) --> This line fails!!
unauthenticated_client.configuration.host = f'https://{looker_instance}:19999/api/3.0/'
unauthenticated_authApi = looker_client.ApiAuthApi(unauthenticated_client)
# authenticate client
token = unauthenticated_authApi.login(client_id=client_id, client_secret=client_secret)
client = looker_client.ApiClient(header_name='Authorization', header_value='token ' + token.access_token)
client.configuration.host = f'https://{looker_instance}:19999/api/3.0/'
I hope someone can help on how to define looker_client properly. Thanks.
It looks like this one was resolved here: https://discourse.looker.com/t/generating-a-powerpoint-presentation-from-all-looks-in-a-space/8191/15?u=izzy for those following along at home. There's another issue, but the NameError: name ‘looker_client’ is not defined error was resolved by adding a necessary import:
import looker_client_30 as looker_client

Python xmlsec XML Signature Value mismatch

I am new to xml signatures and currently I am using xmlsec to generate a signed xml. I do this with some modifications on the sample code:
from lxml import etree
import xmlsec
parser = etree.XMLParser(remove_blank_text=True)
template = etree.parse('unsigned.xml', parser).getroot()
signature_node = xmlsec.tree.find_node(template, xmlsec.constants.NodeSignature)
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_file('keys/private_key.pem', xmlsec.constants.KeyDataFormatPem)
ctx.key = key
sig_ = ctx.sign(signature_node)
formated = etree.tostring(template)
with open('signed_test.xml', 'wb') as the_file:
the_file.write(formated)
Now I have signed XML, and from here I am trying to learn where or how the values get generated. I am following this for context. I am able to verify the DigestValue and now I am trying to get the SignatureValue. From the link and some other questions here in stackoverflow, I just need to:
Canonized the whole SignedInfo element
Hash the result
Sign the Hash
In order to get the signature value. I am canonizing the SignedInfo Element using lxml:
from lxml import etree
parser = etree.XMLParser(remove_blank_text=True)
xmlTree = etree.parse('signed_info.xml', parser)
root = xmlTree.getroot()
formated = etree.tostring(root, method='c14n', exclusive=True)
# Write to file
with open('canon_sinfo.xml', 'wb') as the_file:
the_file.write(formated)
For info the following is the resulting SignedInfo:
<SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></SignatureMethod><Reference URI="#obj"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#base64"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod><DigestValue>izbIdQ4tSAg6VKGpr1zd6kU9QpVQi/Bcwxjxu/k2oKk=</DigestValue></Reference></SignedInfo>
I am using cryptography to try and generate the SignatureValue however, I cannot get the same result as that of what xmlsec generated.
Here is my code snippet in doing so:
sign_info = '''String of the Sign Info'''
digest_sinfo = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest_sinfo.update(bytes(sign_info, 'utf-8'))
digested_sinfo = digest_sinfo.finalize()
# Load private_key here...
# Sign the message
signature_1v15 = private_key.sign(
digested_sinfo,
padding.PKCS1v15(),
hashes.SHA256()
)
I also tried loading the SignedInfo into a seperate file, however I still get a mismatched SignatureValue. How do I accomplish this?
Your issue is that you do the hashing twice. The sign() function does the hashing for you so you can skip the middle part.
Just call the sign() with your canonized SignedInfo element:
signature_1v15 = private_key.sign(
sign_info,
padding.PKCS1v15(),
hashes.SHA256()
)

Watson speech to text: Invalid credentials error (Code: 401)

I am trying to use the IBM Watson speech to text API/service in the following Python program.
import json
import os
import sys
from watson_developer_cloud import SpeechToTextV1
def transcribe_audio(audio_file_name) :
IBM_USERNAME = "yourusername"
IBM_PASSWORD = "yourpassword"
#what changes should be made here instead of username and password
stt = SpeechToTextV1(username=IBM_USERNAME, password=IBM_PASSWORD)
audio_file = open(audio_file_name, "rb")
json_file = os.path.abspath("america")+".json";
with open(json_file, 'w') as fp:
result = stt.recognize(audio_file,timestamps=True,content_type='audio/wav', inactivity_timeout =-1,word_confidence = True)
result.get_result()
json.dump(result, fp, indent=2)
script = "Script is : "
for rows in result['results']:
script += rows['alternatives'][0]['transcript']
print(script)
transcribe_audio("america.wav")
This code gave me an authentication error as mentioned in the title because IBM changed the authorization method from username + password to apikey
very recently.
Could anybody tell me what changes should be made in this?
And also how to generate the apikey on IBM Watson speech to text with username and password?
I am new to speech recognition, please let me know. Thanks in advance.
All the information you want is in the API documentation, including how to obtain the API Key - https://cloud.ibm.com/apidocs/speech-to-text?code=python

call OAUTH2 api in python script

i have found this interesting article here https://developer.byu.edu/docs/consume-api/use-api/oauth-20/oauth-20-python-sample-code
in this article there is an example how to call an oauth2 api using authorization_code flow. the problem with this approach is that you need to open a new browser, get the code and paste in the script. i would open and get the code directly from python script. is it possible?
print "go to the following url on the browser and enter the code from the
returned url: "
print "--- " + authorization_redirect_url + " ---"
access_token = raw_input('access_token: ')
I have been battling with this same problem today and found that the following worked for me. You'll need:
An API ID
A secret key
the access token url
I then used requests_oauthlib: https://github.com/requests/requests-oauthlib
from requests_oauthlib import OAuth2Session
# Both id and secret should be provided by whoever owns the api
myid = 'ID_Supplied'
my_secret = 'Secret_pass'
access_token_url = "https://example.com/connect/token" # suffix is also an example
client = BackendApplicationClient(client_id=myid)
oauth = OAuth2Session(client=client)
token = oauth.fetch_token(token_url=access_token_url, client_id=myid,
client_secret=my_secret)
print(token)

CoSign API: Sending SOAP from Python client generated by suds

I need to access the signing appliance from Python. To this end, using suds, I generated a Python client from the WSDL at https://cosigndemo.arx.com:8080/sapiws/dss.asmx?wsdl .
I then used the generated client to build a simple signature request which more or less does the same as shown in the Java example provided by ARX (only, I'm asking for an invisible signature).
The problem is that when I send the request to the demo appliance, here's what I receive in return:
(DssSignResult){
Result =
(Result){
ResultMajor = "urn:oasis:names:tc:dss:1.0:resultmajor:ResponderError"
ResultMinor = "urn:oasis:names:tc:dss:1.0:resultminor:GeneralError"
ResultMessage =
(ResultMessage){
value = "Exception occured"
_lang = "en"
}
}
}
Here's the Python code I wrote:
from suds.client import Client
from suds.plugin import MessagePlugin
from suds.sax.attribute import Attribute
import logging
class MyPlugin(MessagePlugin):
def marshalled(self, context):
foo = context.envelope.getChild('ns2:Body').getChild('ns0:DssSign').getChild('ns0:SignRequest').getChild('ns1:InputDocuments').getChild('ns1:Document')
foo[0].attributes.append(Attribute('MimeType', 'application/pdf'))
print context.envelope
url = 'https://cosigndemo.arx.com:8080/sapiws/dss.asmx?wsdl'
client = Client(url, plugins=[MyPlugin()])
cid = client.factory.create("ns4:ClaimedIdentity")
cid.Name = "John Miller"
cid.SupportingInfo.LogonPassword = "12345678"
sigReq = client.factory.create("ns4:RequestBaseType")
sigReq._RequestID = "DummyRequestId"
sigReq.OptionalInputs.ClaimedIdentity = cid
sigReq.OptionalInputs.SignatureType="http://arx.com/SAPIWS/DSS/1.0/signature-field-create-sign"
sigReq.OptionalInputs.SAPISigFieldSettings._Name = "SigField"
sigReq.OptionalInputs.SAPISigFieldSettings._Invisible = "true"
sigReq.OptionalInputs.SAPISigFieldSettings._DependencyMode = "Independent"
sigReq.OptionalInputs.SAPISigFieldSettings._SignatureType = "Digital"
sigReq.OptionalInputs.SAPISigFieldSettings._EmptyFieldLabel = ""
sigReq.OptionalInputs.SAPISigFieldSettings._Page = "1"
sigReq.OptionalInputs.ReturnPDFTailOnly = "true"
sigReq.OptionalInputs.IncludeObject=None
sigReq.OptionalInputs.SignaturePlacement=None
doc = client.factory.create("ns4:DocumentType")
doc.Base64Data = open("/Users/mar/CoSignWSDL/factures-comptat217.pdf", "rb").read().encode("base64")
sigReq.InputDocuments.Document = doc
result = client.service.DssSign(sigReq)
print result
And here's the SOAP that (at least I think) it is sending to the appliance:
<SOAP-ENV:Envelope xmlns:ns3="http://arx.com/SAPIWS/DSS/1.0" xmlns:ns0="http://arx.com/SAPIWS/DSS/1.0/" xmlns:ns1="urn:oasis:names:tc:dss:1.0:core:schema" xmlns:ns2="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<ns2:Body>
<ns0:DssSign>
<ns0:SignRequest RequestID="DummyRequestId">
<ns1:OptionalInputs>
<ns1:ClaimedIdentity>
<ns1:Name>John Miller</ns1:Name>
<ns1:SupportingInfo>
<ns3:LogonPassword>12345678</ns3:LogonPassword>
</ns1:SupportingInfo>
</ns1:ClaimedIdentity>
<ns1:SignatureType>http://arx.com/SAPIWS/DSS/1.0/signature-field-create-sign</ns1:SignatureType>
<ns1:SAPISigFieldSettings Name="SigField" DependencyMode="Independent" SignatureType="Digital" Page="1" Invisible="true"/>
<ns1:ReturnPDFTailOnly>true</ns1:ReturnPDFTailOnly>
</ns1:OptionalInputs>
<ns1:InputDocuments>
<ns1:Document>
<ns1:Base64Data MimeType="application/pdf">...</ns1:Base64Data>
</ns1:Document>
</ns1:InputDocuments>
</ns0:SignRequest>
</ns0:DssSign>
</ns2:Body>
</SOAP-ENV:Envelope>
Note that I replaced the (very long) Base64Data content with "..." here for space reasons.
Why is it not working?
Update Problem solved thanks to the answer. Here is the working code that adds a sig to a PDF:
from suds.client import Client
from suds.plugin import MessagePlugin
from suds.sax.attribute import Attribute
from suds.bindings import binding
import logging
import xml.dom.minidom as minidom
import base64
class MyPlugin(MessagePlugin):
def marshalled(self, context):
documentNode = context.envelope.getChild('ns3:Body').getChild('ns0:DssSign').getChild('ns0:SignRequest').getChild('ns1:InputDocuments').getChild('ns1:Document')
documentNode[0].attributes.append(Attribute('MimeType', 'application/pdf'))
SAPISigFieldSettingsNode = context.envelope.getChild('ns3:Body').getChild('ns0:DssSign').getChild('ns0:SignRequest').getChild('ns1:OptionalInputs').getChild('ns1:SAPISigFieldSettings')
SAPISigFieldSettingsNode.setPrefix('ns2')
ReturnPDFTailOnlyNode = context.envelope.getChild('ns3:Body').getChild('ns0:DssSign').getChild('ns0:SignRequest').getChild('ns1:OptionalInputs').getChild('ns1:ReturnPDFTailOnly')
ReturnPDFTailOnlyNode.setPrefix('ns2')
signRequestNode = context.envelope.getChild('ns3:Body').getChild('ns0:DssSign').getChild('ns0:SignRequest')
signRequestNode.setPrefix('ns1')
binding.envns = ('SOAP-ENV', 'http://www.w3.org/2003/05/soap-envelope')
url = 'https://cosigndemo.arx.com:8080/sapiws/dss.asmx?wsdl'
client = Client(url, plugins=[MyPlugin()])
logging.basicConfig(level=logging.INFO)
logging.getLogger('suds.plugin').setLevel(logging.DEBUG)
cid = client.factory.create("ns4:ClaimedIdentity")
cid.Name = "John Miller"
cid.SupportingInfo.LogonPassword = "12345678"
sigReq = client.factory.create("ns4:RequestBaseType")
sigReq._RequestID = "DummyRequestId"
sigReq.OptionalInputs.ClaimedIdentity = cid
sigReq.OptionalInputs.SignatureType="http://arx.com/SAPIWS/DSS/1.0/signature-field-create-sign"
sigReq.OptionalInputs.SAPISigFieldSettings._Name = "SigField"
sigReq.OptionalInputs.SAPISigFieldSettings._Invisible = "true"
sigReq.OptionalInputs.SAPISigFieldSettings._DependencyMode = "Independent"
sigReq.OptionalInputs.SAPISigFieldSettings._SignatureType = "Digital"
sigReq.OptionalInputs.SAPISigFieldSettings._EmptyFieldLabel = ""
sigReq.OptionalInputs.SAPISigFieldSettings._Page = "1"
sigReq.OptionalInputs.ReturnPDFTailOnly = "true"
sigReq.OptionalInputs.IncludeObject=None
sigReq.OptionalInputs.SignaturePlacement=None
doc = client.factory.create("ns4:DocumentType")
f = open('/Users/mar/CoSignWSDL/factures-comptat217.pdf', 'r+b')
doc.Base64Data = f.read().encode("base64")
sigReq.InputDocuments.Document = doc
result = client.service.DssSign(sigReq)
signature = base64.b64decode(result.SignatureObject.Base64Signature.value)
f.seek(0, 2) #go to the end of the file
f.write(signature)
f.close()
Your SOAP request is passing ns0:SignRequest Should be: ns1:SignRequest
Also Bear in mind that there is a missing part in your code--checking the appliance's SSL certificate. It is not a must, but generally required in order to verify that the response wasn't sent by a fraudulent entity.
In .Net and Java it is done automatically when accessing web service in HTTPS (based on Microsoft certificate Store and Java certificate Store)- but as far as I see there is no equivalent in Python.

Resources