我正在尝试创建一个SAML断言,然后将其发布到销售人员 OAUTH 终结点,用于在我的 Office 沙盒环境中获取访问令牌以响应。
下面的代码基于此存储库中提到的代码:https://github.com/salesforceidentity/apex-saml-bearer-flow/blob/master/SAMLBearerAssertion.apex.txt我试图转换 将代码转换为 Python。
import datetime
import random
import hashlib
import binascii
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from base64 import b64encode, b64decode,urlsafe_b64decode,urlsafe_b64encode
import requests
from xml.etree import ElementTree
class SFSAMLAssertion:
def __init__(self, subject, issuer, audience, action):
self.subject = subject
self.issuer = issuer
self.action = action
self.audience = audience
self.not_before = "2019-08-16T06:35:13.654Z"
self.not_on_or_after = "2024-08-14T06:35:13.654Z"
self.assertion_id = self.create_assertion_id()
self.encoded_key = ""
self.preCannonicalizedResponse = """<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="ASSERTION_ID" IssueInstant="NOT_BEFORE" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">ISSUER</saml:Issuer><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">SUBJECT</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="NOT_ON_OR_AFTER" Recipient="RECIPIENT"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="NOT_BEFORE" NotOnOrAfter="NOT_ON_OR_AFTER"><saml:AudienceRestriction><saml:Audience>AUDIENCE</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="NOT_BEFORE"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement></saml:Assertion>"""
self.preCannonicalizedSignedInfo = """<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod><ds:Reference URI="#ASSERTION_ID"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod><ds:DigestValue>DIGEST</ds:DigestValue></ds:Reference></ds:SignedInfo>"""
self.signatureBlock = """<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">SIGNED_INFO<ds:SignatureValue>SIGNATURE_VALUE</ds:SignatureValue></ds:Signature><saml:Subject>"""
def create_assertion_id(self):
num = random.randint(1, 999999)
hash = hashlib.sha256()
hash.update(bytes(num))
digest = hash.digest()
return binascii.hexlify(digest).decode('UTF-8')
def sign_signedinfo(self, data):
f = open('../etc/privateKey.pem', 'rb')
key = b64decode(self.encoded_key)
rsakey = RSA.importKey(f.read())
signer = PKCS1_v1_5.new(rsakey)
digest = SHA256.new()
digest.update(b64encode(data))
sign = signer.sign(digest)
return b64encode(sign)
def create_assertion(self):
self.preCannonicalizedResponse = self.preCannonicalizedResponse.replace('ASSERTION_ID',self.assertion_id)
self.preCannonicalizedResponse = self.preCannonicalizedResponse.replace('ISSUER', self.issuer)
self.preCannonicalizedResponse = self.preCannonicalizedResponse.replace('AUDIENCE', self.audience)
self.preCannonicalizedResponse = self.preCannonicalizedResponse.replace('RECIPIENT', self.action)
self.preCannonicalizedResponse = self.preCannonicalizedResponse.replace('SUBJECT', self.subject)
self.preCannonicalizedResponse = self.preCannonicalizedResponse.replace('NOT_BEFORE', self.not_before)
self.preCannonicalizedResponse = self.preCannonicalizedResponse.replace('NOT_ON_OR_AFTER', self.not_on_or_after)
# Prepare the digest
m = hashlib.sha256()
m.update(bytes(self.preCannonicalizedResponse, encoding='UTF-8'))
digest = b64encode(m.digest())
self.preCannonicalizedSignedInfo = self.preCannonicalizedSignedInfo.replace('ASSERTION_ID',self.assertion_id)
self.preCannonicalizedSignedInfo = self.preCannonicalizedSignedInfo.replace('DIGEST',digest.decode('UTF-8'))
# Prepare the signedinfo
input = bytes(self.preCannonicalizedSignedInfo,encoding='UTF-8')
signature = self.sign_signedinfo(input)
signature = b64encode(signature)
# Prepare the signature block
self.signatureBlock = self.signatureBlock.replace('SIGNED_INFO',self.preCannonicalizedSignedInfo)
self.signatureBlock = self.signatureBlock.replace('SIGNATURE_VALUE',signature.decode('UTF-8'))
self.preCannonicalizedResponse = self.preCannonicalizedResponse.replace('<saml:Subject>',self.signatureBlock)
return self.preCannonicalizedResponse
def get_base64urlencode_string(self):
data = self.create_assertion()
data = '<?xml version="1.0" encoding = "UTF-8"?>' + data
print("Assertion: "+data)
tree = ElementTree.ElementTree(ElementTree.fromstring(data))
root = tree.getroot()
output = urlsafe_b64encode(bytes(ElementTree.tostring(root, encoding='UTF-8',method = 'xml').decode(encoding='UTF-8'),encoding='UTF-8'))
return output
def postSAML(self):
url = "https://<sandbox_domain>.my.salesforce.com/services/oauth2/token"
assertion = self.get_base64urlencode_string()
print(assertion.decode('UTF-8'))
headers = {
"Content-Type" : "application/x-www-form-urlencoded"
}
params = {
"grant-type" : "urn:ietf:params:oauth:grant-type:saml2-bearer",
"assertion" : assertion.decode('UTF-8')
}
r = requests.post(url,params=params,headers=headers)
print(r.status_code)
print(r.content)
print(r.headers)
print(r.url)
目前,我使用我的python代码收到"无效断言"的错误。我的代码出了什么问题?
您从 SAMLBearerAssertion.apex 复制的代码使用 SHA-1 作为消息摘要,使用 RSA-SHA1 作为签名,而您的代码具有 SHA-256/RSA-SHA256。将相关的 XML 片段更改为以下内容:
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></ds:SignatureMethod>
有关Algorithm
的其他值,请参阅 IANA 分配备忘单。