What exactly to digest and sign on XMLDSIG and how? (or, OSX-native client mismatches XMLDSIG calculated on server)

我的未来我决定 提交于 2019-12-11 12:26:29

问题


I'm trying to sign a xml document following XMLDSIG specification with an enveloped signature, sha1 digest and rss-sha1 signature, and the server keeps returning a "297 - Rejection: Signature does not match calculated [result]" ("297 - Rejeicao: Assinatura difere do calculado" in Brazilian Portuguese original)

My client application needs to be Mac OS X native (so Objective-C and Swift). I'm adhering to Apple's CryptoCompatibility guidelines and using Security.framework's SecSignTransform and CommonCrypto's CC_SHA1.

Here is the XML I'm trying to XMLDSIG (did not PrettyPrint and omitted terms to save space):

<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>

Unfortunately, since both .Net and Java offer very high level support for XMLDSIG, detailed information about which parts of the XML to get, what to retain and what to remove, are scant on the internet. Apart from W3.org's own specs, which is pretty dry, the only in-depth explanation I found was: http://www.di-mgt.com.au/xmldsig2.html

I'm not sure if the mismatch is in the sha1 digest or the rsa-sha1 signature, the return code is unclear. Also, I don't know if I'm using the wrong input, or if Mac OS X libraries I'm using are not compatible with the server (which is .Net based).

Here is the code for the digest. Please notice it uses a same-document URI reference:

// 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)

Extra methods used in code:

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
    }
}

After calculating the digest, it is inserted in a pre-formatted Signature XML String, to create a XML Document, and then the SignedInfo node is extracted and used to generate the 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)
}

As far as I can see, everything is correct and compliant with W3.org's specifications for XMLDSIG. Still, the server always rejects the generated XML.

I'm at wits end. Any help and wisdom will be highly appreciated!!


回答1:


Found it!!

Here is what I was doing wrong:

My code converted the NSXMLDocument straight into NSData, but this meant the XML type <?xml version="1.0" encoding="UTF-8"?> was being include in the beginning.

When after some fiddling to find the right expression, I replaced this code:

let signedInfoData = XMLSupportClass.createXMLDocument(xmlSignedInfoElement.canonicalXMLStringPreservingComments(false)).XMLData

with this code:

let signedInfoData = (XMLSupportClass.createXMLDocument(xmlSignedInfoElement.canonicalXMLStringPreservingComments(false), withTidyXML:true).rootElement()!.XMLString).dataUsingEncoding(NSUTF8StringEncoding)!

it worked!!

Probably because the missing namespace <SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> was included, but not the XML type declaration...

As warned, XMLDSIG is extremely detail-oriented. Too bad there is no high-level support in Objective-C or Swift, but I guess Apple considers it too dated.

An experienced programmer could not turn my code into a well-designed library and provide this high level support now. Just hope my ordeal proves useful to anyone following my path...



来源:https://stackoverflow.com/questions/29042452/what-exactly-to-digest-and-sign-on-xmldsig-and-how-or-osx-native-client-misma

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!