Java NPE 在验证我自己的 XML 签名时
Java NPE while validating my own XML signature
我有一个已签名的 XML 请求,由于签名无效而被第 3 方拒绝。因此,我自己编写了签名验证代码,看看有什么问题。但是,我在验证刚刚创建的 XML 签名时遇到了 NPE。这是 XML 的样子(我删除了不相关的部分):
<?xml version="1.0" encoding="UTF-8"?>
<envelope xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="some_third_party.xsd">
<header>...</header>
<body>...</body>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="MySignature">
<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="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>AaV+ejxBF8GjJvIZA9Bonw81Z1Y=</DigestValue>
</Reference>
<Reference Type="http://www.w3.org/2000/09/xmldsig#SignatureProperties" URI="#SignatureProperties">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>qcofYVnQ/n7sxKJPT5rG0+UYbjg=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>XXX</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>XXX</X509Certificate>
</X509Data>
</KeyInfo>
<Object Id="SignatureProperties">
<SignatureProperties xmlns="">
<SignatureProperty Id="TimeStamp" Target="#MySignature">
<TimeStamp>
<Date>2017-03-01</Date>
<Time>09:06:36.779+01:00</Time>
</TimeStamp>
</SignatureProperty>
</SignatureProperties>
</Object>
</Signature>
</envelope>
当我尝试验证此签名时,我在注释行中收到了 NPE(所以它实际上发生在解组签名时,在实际验证之前):
// Omitted: extract the X509 Certificate from the document
DOMValidateContext valContext = new DOMValidateContext(cert.getPublicKey(), signature);
XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
XMLSignature xmlSignature;
try {
// Null pointer exception here!
xmlSignature = factory.unmarshalXMLSignature(valContext);
} catch (MarshalException e) {
// Handle exception
}
为了完整起见,我是这样创建签名的:
try {
XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance("DOM");
Reference ref = xmlSignatureFactory.newReference("",
xmlSignatureFactory.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList(xmlSignatureFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)),
null,
null);
Reference signatureRef = xmlSignatureFactory.newReference("#SignatureProperties",
xmlSignatureFactory.newDigestMethod(DigestMethod.SHA1, null),
null,
"http://www.w3.org/2000/09/xmldsig#SignatureProperties",
null);
SignedInfo signedInfo = xmlSignatureFactory.newSignedInfo(xmlSignatureFactory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null),
xmlSignatureFactory.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
Arrays.asList(ref, signatureRef));
XMLObject xmlObject = xmlSignatureFactory.newXMLObject(Collections.singletonList(new DOMStructure(timestamp)),
"SignatureProperties", null, null);
KeyInfoFactory keyInfoFactory = xmlSignatureFactory.getKeyInfoFactory();
List<Object> x509Content = new ArrayList<>();
x509Content.add(certificate);
X509Data xd = keyInfoFactory.newX509Data(x509Content);
KeyInfo keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(xd));
XMLSignature xmlSignature = xmlSignatureFactory.newXMLSignature(signedInfo, keyInfo,
Collections.singletonList(xmlObject), SIGNATURE_ID, null);
DOMSignContext signContext = new DOMSignContext(privateKey, document.getDocumentElement());
xmlSignature.sign(signContext);
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
// Handle exception
}
知道我做错了什么吗?
编辑:添加了堆栈跟踪以显示 NPE 的准确抛出位置:
java.lang.NullPointerException: null
at org.jcp.xml.dsig.internal.dom.DOMXMLObject.<init>(DOMXMLObject.java:120)
at org.jcp.xml.dsig.internal.dom.DOMXMLSignature.<init>(DOMXMLSignature.java:171)
at org.jcp.xml.dsig.internal.dom.DOMXMLSignatureFactory.unmarshal(DOMXMLSignatureFactory.java:193)
at org.jcp.xml.dsig.internal.dom.DOMXMLSignatureFactory.unmarshalXMLSignature(DOMXMLSignatureFactory.java:150)
编辑 2:我还应该提到,SignedValue 和 X509Certificate 内容在创建签名后都有空格,这对我来说很奇怪。例如
<X509Certificate>MIIG8DCCBdigAwIBAgIUJZSmBORuGuXyx48f04sNGHT+RhwwDQYJKoZIhvcNAQELBQAwWzELMAkG
A1UEBhMCQ0gxEzARBgNVBAoTClBvc3QgQ0ggQUcxDTALBgNVBAsTBFBST0QxKDAmBgNVBAMTH1BL
SSBTd2lzc1Bvc3QgTWFjaGluZSBBRVAgQ0EgRzMwHhcNMTcwMTI2MTIzMTQ0WhcNMjAwMTI2MTIz
MTQ0WjCBsTELMAkGA1UEBhMCQ0gxCzAJBgNVBAgTAkZSMSAwHgYDVQQKExdEaWUgU2Nod2VpemVy
...
经过一番调试,我发现了问题。 DOMXMLObject
尝试获取 SignatureProperties 节点的本地名称,returns 为空。我稍微修改了我创建该元素的方式,现在它工作正常。
我正在添加那段代码,以防以后对其他人有用:
private Element createTimestamp(Document doc) {
// Use createElementNS instead of createElement, otherwise you will get the aforementioned NPE
Element signatureProperties = doc.createElementNS("", "SignatureProperties");
Element signatureProperty = signatureProperties.getOwnerDocument().createElementNS("","SignatureProperty");
signatureProperty.setAttribute("Target", "#" + SIGNATURE_ID);
signatureProperty.setAttribute("Id", "TimeStamp");
Element timeStamp = signatureProperty.getOwnerDocument().createElementNS("", "TimeStamp");
ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());
Element date = timeStamp.getOwnerDocument().createElementNS("", "Date");
date.setTextContent(now.toLocalDate().toString());
Element time = timeStamp.getOwnerDocument().createElementNS("", "Time");
time.setTextContent(now.toLocalTime().toString() + now.getOffset().toString());
timeStamp.appendChild(date);
timeStamp.appendChild(time);
signatureProperty.appendChild(timeStamp);
signatureProperties.appendChild(signatureProperty);
return signatureProperties;
}
此方法的结果随后用于此行:
XMLObject xmlObject = xmlSignatureFactory.newXMLObject(Collections.singletonList(new DOMStructure(createTimestamp(doc))),
"SignatureProperties", null, null);
我有一个已签名的 XML 请求,由于签名无效而被第 3 方拒绝。因此,我自己编写了签名验证代码,看看有什么问题。但是,我在验证刚刚创建的 XML 签名时遇到了 NPE。这是 XML 的样子(我删除了不相关的部分):
<?xml version="1.0" encoding="UTF-8"?>
<envelope xmlns:ns2="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="some_third_party.xsd">
<header>...</header>
<body>...</body>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="MySignature">
<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="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>AaV+ejxBF8GjJvIZA9Bonw81Z1Y=</DigestValue>
</Reference>
<Reference Type="http://www.w3.org/2000/09/xmldsig#SignatureProperties" URI="#SignatureProperties">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>qcofYVnQ/n7sxKJPT5rG0+UYbjg=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>XXX</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>XXX</X509Certificate>
</X509Data>
</KeyInfo>
<Object Id="SignatureProperties">
<SignatureProperties xmlns="">
<SignatureProperty Id="TimeStamp" Target="#MySignature">
<TimeStamp>
<Date>2017-03-01</Date>
<Time>09:06:36.779+01:00</Time>
</TimeStamp>
</SignatureProperty>
</SignatureProperties>
</Object>
</Signature>
</envelope>
当我尝试验证此签名时,我在注释行中收到了 NPE(所以它实际上发生在解组签名时,在实际验证之前):
// Omitted: extract the X509 Certificate from the document
DOMValidateContext valContext = new DOMValidateContext(cert.getPublicKey(), signature);
XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
XMLSignature xmlSignature;
try {
// Null pointer exception here!
xmlSignature = factory.unmarshalXMLSignature(valContext);
} catch (MarshalException e) {
// Handle exception
}
为了完整起见,我是这样创建签名的:
try {
XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance("DOM");
Reference ref = xmlSignatureFactory.newReference("",
xmlSignatureFactory.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList(xmlSignatureFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)),
null,
null);
Reference signatureRef = xmlSignatureFactory.newReference("#SignatureProperties",
xmlSignatureFactory.newDigestMethod(DigestMethod.SHA1, null),
null,
"http://www.w3.org/2000/09/xmldsig#SignatureProperties",
null);
SignedInfo signedInfo = xmlSignatureFactory.newSignedInfo(xmlSignatureFactory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null),
xmlSignatureFactory.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
Arrays.asList(ref, signatureRef));
XMLObject xmlObject = xmlSignatureFactory.newXMLObject(Collections.singletonList(new DOMStructure(timestamp)),
"SignatureProperties", null, null);
KeyInfoFactory keyInfoFactory = xmlSignatureFactory.getKeyInfoFactory();
List<Object> x509Content = new ArrayList<>();
x509Content.add(certificate);
X509Data xd = keyInfoFactory.newX509Data(x509Content);
KeyInfo keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(xd));
XMLSignature xmlSignature = xmlSignatureFactory.newXMLSignature(signedInfo, keyInfo,
Collections.singletonList(xmlObject), SIGNATURE_ID, null);
DOMSignContext signContext = new DOMSignContext(privateKey, document.getDocumentElement());
xmlSignature.sign(signContext);
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
// Handle exception
}
知道我做错了什么吗?
编辑:添加了堆栈跟踪以显示 NPE 的准确抛出位置:
java.lang.NullPointerException: null
at org.jcp.xml.dsig.internal.dom.DOMXMLObject.<init>(DOMXMLObject.java:120)
at org.jcp.xml.dsig.internal.dom.DOMXMLSignature.<init>(DOMXMLSignature.java:171)
at org.jcp.xml.dsig.internal.dom.DOMXMLSignatureFactory.unmarshal(DOMXMLSignatureFactory.java:193)
at org.jcp.xml.dsig.internal.dom.DOMXMLSignatureFactory.unmarshalXMLSignature(DOMXMLSignatureFactory.java:150)
编辑 2:我还应该提到,SignedValue 和 X509Certificate 内容在创建签名后都有空格,这对我来说很奇怪。例如
<X509Certificate>MIIG8DCCBdigAwIBAgIUJZSmBORuGuXyx48f04sNGHT+RhwwDQYJKoZIhvcNAQELBQAwWzELMAkG
A1UEBhMCQ0gxEzARBgNVBAoTClBvc3QgQ0ggQUcxDTALBgNVBAsTBFBST0QxKDAmBgNVBAMTH1BL
SSBTd2lzc1Bvc3QgTWFjaGluZSBBRVAgQ0EgRzMwHhcNMTcwMTI2MTIzMTQ0WhcNMjAwMTI2MTIz
MTQ0WjCBsTELMAkGA1UEBhMCQ0gxCzAJBgNVBAgTAkZSMSAwHgYDVQQKExdEaWUgU2Nod2VpemVy
...
经过一番调试,我发现了问题。 DOMXMLObject
尝试获取 SignatureProperties 节点的本地名称,returns 为空。我稍微修改了我创建该元素的方式,现在它工作正常。
我正在添加那段代码,以防以后对其他人有用:
private Element createTimestamp(Document doc) {
// Use createElementNS instead of createElement, otherwise you will get the aforementioned NPE
Element signatureProperties = doc.createElementNS("", "SignatureProperties");
Element signatureProperty = signatureProperties.getOwnerDocument().createElementNS("","SignatureProperty");
signatureProperty.setAttribute("Target", "#" + SIGNATURE_ID);
signatureProperty.setAttribute("Id", "TimeStamp");
Element timeStamp = signatureProperty.getOwnerDocument().createElementNS("", "TimeStamp");
ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());
Element date = timeStamp.getOwnerDocument().createElementNS("", "Date");
date.setTextContent(now.toLocalDate().toString());
Element time = timeStamp.getOwnerDocument().createElementNS("", "Time");
time.setTextContent(now.toLocalTime().toString() + now.getOffset().toString());
timeStamp.appendChild(date);
timeStamp.appendChild(time);
signatureProperty.appendChild(timeStamp);
signatureProperties.appendChild(signatureProperty);
return signatureProperties;
}
此方法的结果随后用于此行:
XMLObject xmlObject = xmlSignatureFactory.newXMLObject(Collections.singletonList(new DOMStructure(createTimestamp(doc))),
"SignatureProperties", null, null);