在 XMLDSIG 上到底要消化和签署什么内容以及如何签署? (或者,OSX 本机客户端与服务器上计算的 XMLDSIG 不匹配)

问题描述 投票:0回答:1

我尝试使用封装签名、sha1 摘要和 rss-sha1 签名按照 XMLDSIG 规范对 xml 文档进行签名,并且服务器不断返回“297 - 拒绝:签名与计算的 [结果] 不匹配” (“297 - Rejeicao:Assinatura difere do calculado”巴西葡萄牙语原文)

我的客户端应用程序需要是 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
    }
}

计算摘要后,将其插入预先格式化的 Signature XML String 中,以创建 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。

我已经无计可施了。任何帮助和智慧将不胜感激!!

xml macos cocoa swift xml-dsig
1个回答
1
投票

找到了!!

这是我做错的事情:

我的代码将 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 认为它太过时了。

经验丰富的程序员可以将我的代码变成一个设计良好的库,并立即提供这种高级支持。只是希望我的磨难对任何追随我道路的人有用......

这是该方法在 Objective-C 中的实现代码(需要一个标头才能与 Swift 集成):

+(NSXMLDocument*)createXMLDocument:(NSString*)xmlString withTidyXML:(BOOL)tidyUp  {

NSXMLDocument *xmlDoc;
NSError *err = nil;

if (!xmlString) {
    NSLog(@"String is empty! %@.", xmlString);
    return nil;
}
if (!tidyUp) {
xmlDoc = [[NSXMLDocument alloc] initWithXMLString:xmlString options:(NSXMLNodePreserveWhitespace) error:&err]; 
}
if (xmlDoc == nil) {
    xmlDoc = [[NSXMLDocument alloc] initWithXMLString:xmlString options:NSXMLDocumentTidyXML error:&err];
    NSLog(@"Had to tidyXML the document!!");
}

if (err) {
    NSLog(@"Failed to create XMLDocument with following error: %@", [err localizedDescription]);
}

return xmlDoc;

}

© www.soinside.com 2019 - 2024. All rights reserved.