我需要用 java 1.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.sign() 方法中收到 StackOverflow 错误。有一个对 DOMXMLSignature.digestReference() 方法的调用,并且该函数无限期地调用自身。
如果 URI 为 "" 且 ID 为空,即当我需要对整个 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()方法。
DOMXMLSignature.digestReference() 方法验证引用是否已经被消化 - 它不是。所以他得到了 URI,验证这是否是同文档 URI - 它是 - ,确认文档中的 id 是 URI 中的 id。然后,问题是:通过 signatureIdMap.get(parsedId) 获得的实例总是 DOMReference 类型,因此该方法无限期地调用自己。
这发生在 Java 1.6 中,在本机类中没有任何变化。如何解决此问题并签署特定的 XML 标记?在调用 DOMXMLSignature.sign() 方法之前是否必须计算摘要值?