我正在尝试使用封装签名,sha1摘要和rss-sha1签名在XMLDSIG规范之后签署xml文档,并且服务器不断返回“297 - 拒绝:签名与计算的[结果]不匹配” (“297 - Rejeicao:Assinatura difere do calculado”in Brazilian Portuguese original)
我的客户端应用程序需要是Mac OS X本机(所以Objective-C和Swift)。我坚持使用Apple的CryptoCompatibility指南并使用Security.framework的SecSignTransform和CommonCrypto的CC_SHA1。
以下是我正在尝试XMLDSIG的XML(没有PrettyPrint,省略了节省空间的术语):
<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe Id="NFe351503...1455341071" versao="2.00"><ide><cUF>35</cUF><cNF>45534107</cNF><natOp>VENDA</natOp><indPag>1</indPag><mod>55</mod>
...
</infNFe><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod><Reference URI="#NFe351503...1455341071"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></Transform><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue>H4l0eMA6H4ndKzY3ftwlsKpeX58=</DigestValue></Reference></SignedInfo><SignatureValue>QYMVPWvZOeF4XgorObl33Tm9DiZEW4N7zuuAbt9Jjop79V41SNAIO5qIXe06cLiJACghi1X+p3pROE3P/E/lhPhwGmA3G26Jm5hZqsGhURS1osHDNKDWARBpi+musgi5naHm4tKqlKKIKqARljyXyYRRVaoxOSrC3vmxPx2ClwwTrlgnqtDTODQU0yNN4OUXTxWAMYPm8rc2rO6OUohTK+eXE3mN5vgCB6GLMWj0Cp2k6N21264WNv/P+L45kHUllFnV+ByMshXFYzySvthujlq/4ClSG+1xOFYMATn1F6qvklMDXy7bS+Dqcp635ZFxfD97gTDriFUYH0+nEe95zw==</SignatureValue><KeyInfo><X509Data><X509Certificate>...</X509Certificate></X509Data></KeyInfo></Signature></NFe>
不幸的是,由于.Net和Java都为XMLDSIG提供了非常高级别的支持,因此无法在互联网上获取有关XML的哪些部分,要保留的内容以及要删除的内容的详细信息。除了W3.org自己的规格,这是相当干燥的,我发现的唯一深入的解释是:http://www.di-mgt.com.au/xmldsig2.html
我不确定sha1摘要或rsa-sha1签名中是否存在不匹配,返回代码不清楚。此外,我不知道我是否使用了错误的输入,或者我使用的Mac OS X库与服务器(基于.Net)不兼容。
以下是摘要的代码。请注意它使用相同文档的URI引用:
// Imports XML data from XML file
var xmlStr: String? = File.open(documentPath)
// gets Id (formated "NFe" + 44x[0-9]) to create SignedInfo reference URI
let myId = getNFeId(xmlStr!)
// creates a XML Document using String xmlStr and canonicalize "c14n"
var xmlDocument = XMLSupportClass.createXMLDocument(xmlStr!)
let canonicalXmlStr = xmlDocument.canonicalXMLStringPreservingComments(false)
var stringToDigest = ""
// retrieves element referenced by URI (#myId) to create digest
if (xmlDocument.rootElement() != nil) {
let xmlRoot: NSXMLElement = xmlDocument.rootElement()!
// let myURI = "#" + myId
// let nodesToTest: [NSXMLElement] = xmlRoot.elementsForLocalName("NFe", URI: myURI) as [NSXMLElement]
// let nodesToTest2: [NSXMLElement] = xmlRoot.elementsForName("infNFe") as [NSXMLElement]
let myXPath: String = "//*[@Id=\'" + myId + "\']"
let nodesToDigest = xmlRoot.nodesForXPath(myXPath, error: &xpathError) as [NSXMLElement]
if nodesToDigest.count > 0 {
stringToDigest = nodesToDigest[0].canonicalXMLStringPreservingComments(false)
} else { println(xpathError) }
} else {
println("I'm root-less!!")
}
// creates the digest using CryptoCompatibility
digestData = stringToDigest.sha1()
let digestDataAsString: String = digestData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.EncodingEndLineWithLineFeed)
代码中使用的额外方法:
func getNFeId(xml: String) -> String {
// mas como extrair o atributo Id do elemento <infNFe>
var myError: NSError?
let root = XMLSupportClass.createXMLDocument(xml).rootElement()! as NSXMLElement
let infNodes = root.elementsForName("infNFe") as [NSXMLElement]
if infNodes.count > 0 {
let idNode = infNodes[0].attributeForName("Id")! as NSXMLNode
let myId = idNode.objectValue as String
println(myId)
return myId
} else {
println("error extracting NFeId")
return "error extracting NFeId"
}
}
// SHA-1 Digest from CryptoCompatibility returning a Hex String
extension String {
func sha1() -> String {
let data = self.dataUsingEncoding(NSUTF8StringEncoding)!
var digest = [UInt8](count:Int(CC_SHA1_DIGEST_LENGTH), repeatedValue: 0)
CC_SHA1(data.bytes, CC_LONG(data.length), &digest)
let output = NSMutableString(capacity: Int(CC_SHA1_DIGEST_LENGTH))
for byte in digest {
output.appendFormat("%02x", byte)
}
return output
}
}
计算摘要后,将其插入预先格式化的签名XML字符串中,以创建XML文档,然后提取SignedInfo节点并用于生成SignatureValue:
// pre-formatted XML String for Signature node, leaving SignatureValue empty for filling in later
let xmlAssinatura = "<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><SignedInfo><CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/><SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><Reference URI=\"#\(myId)\"><Transforms><Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><Transform Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/></Transforms><DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><DigestValue>\(digestDataAsString)</DigestValue></Reference></SignedInfo><SignatureValue></SignatureValue><KeyInfo><X509Data><X509Certificate>\(certDataAsString)</X509Certificate></X509Data></KeyInfo></Signature>"
// tranforms xmlAssinatura String in NSXMLDocument
var xmlAssinaturaDocument = XMLSupportClass.createXMLDocument(xmlAssinatura)
let signatureNode = xmlAssinaturaDocument.rootElement()!
// and retrieves SignedInfo node, converts to NSData for signing
let xmlSignedInfoElement = (signatureNode.elementsForName("SignedInfo") as [NSXMLElement])[0]
**// ====> the line below was the problem!!!**
/*let signedInfoData = XMLSupportClass.createXMLDocument(xmlSignedInfoElement.canonicalXMLStringPreservingComments(false)).XMLData */
**// ====> and this is the fix:**
let signedInfoData = (XMLSupportClass.createXMLDocument(xmlSignedInfoElement.canonicalXMLStringPreservingComments(false), withTidyXML:true).rootElement()!.XMLString).dataUsingEncoding(NSUTF8StringEncoding)!
// creates SecTransform object
signer = SecSignTransformCreate(priKey, &error).takeRetainedValue()
if error != nil { print("signer transform creation error: ") ; println(error) }
// signer to use SHA1 digest method and use signedInfoData as input
SecTransformSetAttribute(signer, kSecDigestTypeAttribute, kSecDigestSHA1, &error)
if error != nil { print("verifier digest attribute setting error: ") ; println(error) }
SecTransformSetAttribute(signer, kSecTransformInputAttributeName, signedInfoData, &error)
if error != nil { print("signer attribute setting error: ") ; println(error) }
// executes the transform
signedData = (SecTransformExecute(signer, &error) as NSData)
let signedDataAsString = signedData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.EncodingEndLineWithLineFeed)
if error != nil { print("signer execute error: ") ; println(error) }
// inserts generated signedDataAsString in <SignatureValue> node
let signatureValueElements = signatureNode.elementsForName("SignatureValue") as [NSXMLElement]
if signatureValueElements.count > 0 { signatureValueElements[0].setStringValue(signedDataAsString, resolvingEntities: false) } else { println(signatureValueElements) }
signatureNode.detach()
xmlAssinaturaDocument = nil
// then replaces <Signature> placeholder node in xmlDocument
if (xmlDocument.rootElement() != nil) {
let xmlRoot: NSXMLElement = xmlDocument.rootElement()!
let signatureNodePlaceholder: NSXMLElement = (xmlRoot.elementsForName("Signature") as [NSXMLElement])[0]
let signatureNodeIndex = signatureNodePlaceholder.index
xmlRoot.replaceChildAtIndex(signatureNodeIndex, withNode: signatureNode)
// and creates xmlDocument canonicalized String
xmlStr = xmlRoot.canonicalXMLStringPreservingComments(false)
}
据我所知,一切都是正确的,符合W3.org的XMLDSIG规范。但是,服务器始终拒绝生成的XML。
我的智慧结束了。任何帮助和智慧将受到高度赞赏!!
答案 0 :(得分:0)
发现它!!
这就是我做错了:
我的代码将NSXMLDocument直接转换为NSData,但这意味着XML类型<?xml version="1.0" encoding="UTF-8"?>
正在包含在开头。
在一些摆弄找到正确的表达后,我替换了这段代码:
let signedInfoData = XMLSupportClass.createXMLDocument(xmlSignedInfoElement.canonicalXMLStringPreservingComments(false)).XMLData
使用此代码:
let signedInfoData = (XMLSupportClass.createXMLDocument(xmlSignedInfoElement.canonicalXMLStringPreservingComments(false), withTidyXML:true).rootElement()!.XMLString).dataUsingEncoding(NSUTF8StringEncoding)!
它有效!!
可能是因为包含了缺少的名称空间<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
,而不是XML类型声明......
正如所警告的那样,XMLDSIG非常注重细节。太糟糕了,Objective-C或Swift没有高级支持,但我猜Apple认为它过于陈旧。
经验丰富的程序员无法将我的代码转换为精心设计的库,并提供此高级支持。只是希望我的考验证明对任何追随我的人都有用......