在java中为特定xml标记签名时发生xml dsig错误
我需要用Java1.6签署一个特定的XML标记。我的XML是这样的:
<RecepcionarLoteRps>
<EnviarLoteRpsEnvio xmlns="http://isscuritiba.curitiba.pr.gov.br/iss/nfse.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://isscuritiba.curitiba.pr.gov.br/iss/nfse.xsd">
<LoteRps>
<ListaRps>
<Rps>
<InfRps id="1">
. . .
</InfRps>
</Rps>
</ListaRps>
</EnviarLoteRpsEnvio>
我签署它的java代码(不包括密钥库部分)是:
try {
String ArqAssinar = "file.xml";
String Charset = "UTF-8";
/* URI and ID */
String idRef = "1";
String uriRef = "#" + idRef;
XMLSignatureFactory XmlSignFac = XMLSignatureFactory.getInstance("DOM");
DigestMethod DigMet = XmlSignFac.newDigestMethod(DigestMethod.SHA1, null);
Transform TransfRef1 = XmlSignFac.newTransform(CanonicalizationMethod.INCLUSIVE, (TransformParameterSpec) null);
Transform TransfRef2 = XmlSignFac.newTransform(SignatureMethod.RSA_SHA1, (C14NMethodParameterSpec) null);
List<Transform> Transfs = new ArrayList<Transform>();
Transfs.add(TransfRef1);
Transfs.add(TransfRef2);
/* Reference - where I use URI and ID */
Reference Ref = XmlSignFac.newReference(uriRef, DigMet, Transfs, null, idRef);
CanonicalizationMethod CannMet = XmlSignFac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null);
SignatureMethod SignMet = XmlSignFac.newSignatureMethod(SignatureMethod.RSA_SHA1, null);
/* SignedInfo - where I use Reference */
SignedInfo SignInfo = XmlSignFac.newSignedInfo(CannMet, SignMet, Collections.singletonList(Ref));
KeyInfoFactory keyInfFac = XmlSignFac.getKeyInfoFactory();
List<X509Certificate> X509Content = new ArrayList<X509Certificate>();
X509Content.add(Certif);
X509Data X509dados = keyInfFac.newX509Data(X509Content);
KeyInfo KeyInf = keyInfFac.newKeyInfo(Collections.singletonList(X509dados));
/* Process file for input */
DocumentBuilderFactory DocBuilderFactory = DocumentBuilderFactory.newInstance();
DocBuilderFactory.setNamespaceAware(true);
DocumentBuilder DocBuilder = DocBuilderFactory.newDocumentBuilder();
InputStream Input = new FileInputStream(arq);
Reader Leitor = new InputStreamReader(Input, Charset);
InputSource Origem = new InputSource(Leitor);
Document Doc = DocBuilder.parse(Origem);
/* Search for tag in document using ID */
XPathFactory factory = XPathFactory.newInstance();
XPath xpathPesquisa = factory.newXPath();
XPathExpression expr = xpathPesquisa.compile(String.format("//*[@id='%s']", idRef));
NodeList nodes = (NodeList) expr.evaluate(docParaAssinar, XPathConstants.NODESET);
Node Tag = null;
DOMSignContext DocSignCont = null;
XMLSignature Signature = null;
if (nodes.getLength() != 0) {
tagComId = nodes.item(0);
Tag = tagComId.getParentNode();
DocSignCont = new DOMSignContext(PrivPass, Tag);
/* Do the signature */
Signature = this.XmlSignFac.newXMLSignature(SignInfo, KeyInf);
Signature.sign(DocSignCont);
/* Updates the file */
OutputStream Saida = new FileOutputStream(arqAtualizar);
Writer Escritor = new OutputStreamWriter(Saida, Charset);
StreamResult Resultado = new StreamResult(Escritor);
TransformerFactory TransformFac = TransformerFactory.newInstance();
Transformer Transf = TransformFac.newTransformer();
Transf.transform(new DOMSource(docAssinado), Resultado);
} else {
. . .
}
} catch (Exception E) {
. . .
}
当我在Eclipse中运行这段代码时,我在DOMXMLSignature中得到了一个StackOverflow错误。sign()方法。有人打电话要签名。digestReference()方法,该函数将无限期地调用自身
如果URI为“”,ID为null,也就是说,当我需要对整个XML签名时,这段代码就可以工作
要获得特定的XML标记,我需要做哪些不同的操作
我对这段代码有一些问题:
public final class DOMXMLSignature extends DOMStructure
implements XMLSignature {
@Override
public void sign(XMLSignContext signContext)
throws MarshalException, XMLSignatureException
{
if (signContext == null) {
throw new NullPointerException("signContext cannot be null");
}
DOMSignContext context = (DOMSignContext)signContext;
marshal(context.getParent(), context.getNextSibling(),
DOMUtils.getSignaturePrefix(context), context);
// generate references and signature value
List<Reference> allReferences = new ArrayList<>();
// traverse the Signature and register all objects with IDs that
// may contain References
signatureIdMap = new HashMap<>();
signatureIdMap.put(id, this);
signatureIdMap.put(si.getId(), si);
@SuppressWarnings("unchecked")
List<Reference> refs = si.getReferences();
for (Reference ref : refs) {
signatureIdMap.put(ref.getId(), ref);
}
for (XMLObject obj : objects) {
signatureIdMap.put(obj.getId(), obj);
@SuppressWarnings("unchecked")
List<XMLStructure> content = obj.getContent();
for (XMLStructure xs : content) {
if (xs instanceof Manifest) {
Manifest man = (Manifest)xs;
signatureIdMap.put(man.getId(), man);
@SuppressWarnings("unchecked")
List<Reference> manRefs = man.getReferences();
for (Reference ref : manRefs) {
allReferences.add(ref);
signatureIdMap.put(ref.getId(), ref);
}
}
}
}
// always add SignedInfo references after Manifest references so
// that Manifest reference are digested first
allReferences.addAll(refs);
// generate/digest each reference
for (Reference ref : allReferences) {
digestReference((DOMReference)ref, signContext);
}
// do final sweep to digest any references that were skipped or missed
for (Reference ref : allReferences) {
if (((DOMReference)ref).isDigested()) {
continue;
}
((DOMReference)ref).digest(signContext);
}
Key signingKey = null;
try {
KeySelectorResult keySelectorResult = signContext.getKeySelector().select(ki,
KeySelector.Purpose.SIGN,
si.getSignatureMethod(),
signContext);
signingKey = keySelectorResult.getKey();
if (signingKey == null) {
throw new XMLSignatureException("the keySelector did not " +
"find a signing key");
}
ksr = keySelectorResult;
} catch (KeySelectorException kse) {
throw new XMLSignatureException("cannot find signing key", kse);
}
// calculate signature value
try {
byte[] val = ((AbstractDOMSignatureMethod)
si.getSignatureMethod()).sign(signingKey, si, signContext);
((DOMSignatureValue)sv).setValue(val);
} catch (InvalidKeyException ike) {
throw new XMLSignatureException(ike);
}
this.localSigElem = sigElem;
}
private void digestReference(DOMReference ref, XMLSignContext signContext)
throws XMLSignatureException
{
if (ref.isDigested()) {
return;
}
// check dependencies
String uri = ref.getURI();
if (Utils.sameDocumentURI(uri)) {
String parsedId = Utils.parseIdFromSameDocumentURI(uri);
if (parsedId != null && signatureIdMap.containsKey(parsedId)) {
XMLStructure xs = signatureIdMap.get(parsedId);
if (xs instanceof DOMReference) {
digestReference((DOMReference)xs, signContext);
} else if (xs instanceof Manifest) {
Manifest man = (Manifest)xs;
List<Reference> manRefs = DOMManifest.getManifestReferences(man);
for (int i = 0, size = manRefs.size(); i < size; i++) {
digestReference((DOMReference)manRefs.get(i),
signContext);
}
}
}
// if uri="" and there are XPath Transforms, there may be
// reference dependencies in the XPath Transform - so be on
// the safe side, and skip and do at end in the final sweep
if (uri.length() == 0) {
List<Transform> transforms = ref.getTransforms();
for (Transform transform : transforms) {
String transformAlg = transform.getAlgorithm();
if (transformAlg.equals(Transform.XPATH) ||
transformAlg.equals(Transform.XPATH2)) {
return;
}
}
}
}
ref.digest(signContext);
}
}
public final class Utils {
/**
* Returns the ID from a same-document URI (ex: "#id")
*/
public static String parseIdFromSameDocumentURI(String uri) {
if (uri.length() == 0) {
return null;
}
String id = uri.substring(1);
if (id != null && id.startsWith("xpointer(id(")) {
int i1 = id.indexOf('\'');
int i2 = id.indexOf('\'', i1+1);
id = id.substring(i1+1, i2);
}
return id;
}
/**
* Returns true if uri is a same-document URI, false otherwise.
*/
public static boolean sameDocumentURI(String uri) {
return uri != null && (uri.length() == 0 || uri.charAt(0) == '#');
}
}
这只是DOMXMLSignature类和Util类的一部分,只是与案例有关的方法
我的代码高于类DOMXMLSignature。sign()方法。这个方法执行marshall,获取引用,获取引用id,调用DOMXMLSignature。digestReference()方法
签名。digestReference()方法验证引用是否已完全消化,而不是。因此,他获取URI,验证这是否是同一个文档URI——它是-,确认文档中的id就是URI中的id。然后,问题是:使用signatureIdMap获得的实例。get(parsedId)总是DOMReference类型,因此该方法无限期地调用自己
这是在Java1.6中发生的,在本机类中没有改变。如何解决这个问题并签署特定的XML标记?在调用DOMXMLSignature之前,我必须计算digestvalue。sign()方法
# 1 楼答案
哇
首先,您的代码不是一个可复制的示例:
tagComId docParaAssinar docAssinado
未声明,后两个从未设置。我为第一个添加了类型Node
,并将后两个替换为Doc
。仅供参考:对本地人的名字(也包括字段或方法)使用首字母大写是违反传统风格的,因此感觉很奇怪,但确实有效,所以我没有费心修改它们SignatureMethod.RSA_SHA1
不是有效的Transform
,试图将其用作一个抛出异常的对象。在引用的Transforms列表中也没有意义,因为签名不是单独应用于一个引用,而是应用于SignedInfo,其中可能包含多个引用和其他数据。我把它拿走了此外,为了更方便地进行测试,我用屏蔽输入和控制台输出替换了文件I/O(看起来还行)
通过这些更改,我确实复制了stackoverflow。这是因为您指定了#1作为URI,1作为引用的id,所以要解析引用,它必须转到被引用的元素并解析它,而被引用的元素本身就是(相同的)引用,所以它必须转到已标识的被引用元素并解析它,所以它必须等等。你不能使引用的id与你试图引用的元素相同,不应该使它与任何其他元素相同,而且你不需要引用本身,所以最好不要为它设置任何id。我将
newReference
中的id更改为null有了这些变化,我确实可以签署“”(整个文件),但不能签署“#1”。我发现之所以是因为#x没有引用(一个元素)名为的属性“id”,而是引用(一个元素)模式中定义的属性,或者DTD作为id,而你的^{没有定义为id。参见
No working ID attribute when parsing XML in java
Java XML DOM: how are id Attributes special?
Xml id attribute to work with Java's getElementById?
您引用的模式仅将该属性定义为字符串而不是id,并且由于默认情况下DOM解析器不使用模式,因此不会使用该属性。我没有使用模式编辑和,而是写了一个(非常!)使该属性成为id的最小DTD,下面的代码对我来说可以为所需的元素签名(使用我创建的伪密钥和证书):
为清晰起见,对其进行了轻微编辑:
在您的环境或应用程序中,这种形式的XML定义还是另一种形式是可以接受的,我不能说
最后,如果你不知道,SHA1在2017年因碰撞而被破坏,大多数主管安全机构不再允许使用它(直接或间接)签名。(对于SSL/TLS证书,包括这样的网站,自2014-2015年以来,SHA1已经被禁止使用。)但这是个离题的话题