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