究竟要消化和签署XMLDSIG以及如何? (或者,OSX本机客户端与服务器上计算的XMLDSIG不匹配)

时间:2015-03-13 21:43:13

标签: xml macos cocoa swift xml-dsig

我正在尝试使用封装签名,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。

我的智慧结束了。任何帮助和智慧将受到高度赞赏!!

1 个答案:

答案 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认为它过于陈旧。

经验丰富的程序员无法将我的代码转换为精心设计的库,并提供此高级支持。只是希望我的考验证明对任何追随我的人都有用......