java - 使用 Java 在 SAML 断言中验证 SignatureValue
问题描述
我正在尝试验证 SAML 断言中的 SignatureValue。
这个想法是我将收到一个 SOAP 请求,该请求在标头中包含一个 SAML 断言,我需要验证它并检查证书是否有效等等。
但第一件事是使用 Assertion 元素中提供的 Signature 验证 SignatureValue。
我已经使用在 StackOverflow 和其他地方找到的代码组合了一些代码,但是验证失败,我不确定它是否在我的代码中,或者是否是无效的断言。
这是我目前正在尝试的代码:
private static String nodeToString(Node node) {
StringWriter sw = new StringWriter();
try {
Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
t.setOutputProperty(OutputKeys.INDENT, "yes");
t.transform(new DOMSource(node), new StreamResult(sw));
} catch (TransformerException te) {
System.out.println("nodeToString Transformer Exception");
}
return sw.toString();
}
public static String toHex(String arg) throws UnsupportedEncodingException {
return String.format("%040x", new BigInteger(1, arg.getBytes("UTF-8")));
}
// --> Main Method
File file = new File("files\\ITI-39 Request Sample.xml");
byte[] data = Files.readAllBytes(file.toPath());
ByteArrayInputStream is = new ByteArrayInputStream(data);
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbFactory.newDocumentBuilder();
Document doc = builder.parse(is);
org.w3c.dom.Node node = doc.getFirstChild();
Element env = doc.getDocumentElement();
Node security = env.getChildNodes().item(1).getChildNodes().item(11);
Node assertionNode = security.getChildNodes().item(3);
Node signatureNode = assertionNode.getChildNodes().item(3);
Element el = SAMLUtil.loadElementFromString(nodeToString(assertionNode));
Element signatureElement = SAMLUtil.loadElementFromString(nodeToString(signatureNode));
Envelope envelope = new EnvelopeBuilder().buildObject(SAMLUtil.loadElementFromString(nodeToString(env)));
System.out.println(envelope.toString());
Assertion assertion = new AssertionBuilder().buildObject(el);
Signature signature = assertion.getSignature();
System.out.println(assertion.isSigned());
SignatureUnmarshaller sigMarshal = new SignatureUnmarshaller();
SignatureImpl sig = new SignatureBuilder().buildObject(signatureElement);
SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
XMLSignature s = new XMLSignature(signatureElement, "http://www.w3.org/2000/09/xmldsig#");
byte[] sigValueBytes = s.getSignatureValue();
final XPathFactory xPathfactory = XPathFactory.newInstance();
final XPath xpath = xPathfactory.newXPath();
XPathExpression expr = xpath.compile("/Envelope/Header/Security/Assertion/Signature");
expr = xpath.compile("/Envelope/Header/Security/Assertion/Signature/KeyInfo/KeyValue/RSAKeyValue/Modulus");
String modulusString = (String)expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("/Envelope/Header/Security/Assertion/Signature/KeyInfo/KeyValue/RSAKeyValue/Exponent");
String exponentString = (String)expr.evaluate(doc, XPathConstants.STRING);
expr = xpath.compile("/Envelope/Header/Security/Assertion/Signature/SignatureValue");
String signatureValue = (String)expr.evaluate(doc, XPathConstants.STRING);
String modulusHex = toHex(modulusString);
BigInteger modulus = new BigInteger(modulusHex.getBytes());
String exponentHex = toHex(exponentString).replaceFirst("^0+(?!$)", ""); // replace leading zeros
BigInteger exponent = new BigInteger(exponentHex.getBytes());
RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
KeyFactory factory = KeyFactory.getInstance("RSA");
PublicKey pub = factory.generatePublic(spec);
java.security.Signature verifier = java.security.Signature.getInstance("SHA1withRSA");
verifier.initVerify(pub);
String sigHex = toHex(signatureValue);
boolean okay = verifier.verify(sigHex.getBytes());
System.out.println("Signature is verified = " + okay); // this should be true, but is false
这是 Soap 消息中的 Assertion 元素:
<Assertion ID="_b287fab4-1254-4053-b7a8-23585adbcfbf" IssueInstant="2019-07-30T21:12:17.501Z" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
<Issuer Format="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName">CN=</Issuer>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="#_b287fab4-1254-4053-b7a8-23585adbcfbf">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>FH0IVPNlZSvmmNhiNWCzvhE516k=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>sO5+FPqVFQ25+TfrjcPP9a/NPPPb6xLiqpe3Q5wnrGJWfYi5TBSJ53xivcMvLzJmo50gUGbDI2QxrEg3YRyHoizLRzKegwRvllXrqo4bo0VNKVn25FjM/b7pawfNzuR4/D64idDBuOw+u+Oj/fnPcgxrtw1wtEgMWxx15yE6e9OsABnQq3kugYalwwQSXPKzu2qtMjLOJDVILLKMHEn9mKiTL6HYVTDV4h6lvns/10avlFjNt51eNRkO2Sn3K7x9zW48JdnhvJVQdXqVsiiV8B7BDYJ4voIsFBEf9v6UZll6Fv7faEZ/7cIbbbL2/fBw356qV2qt0c2Rq+MuIleEYvZ4//QsEJgaw64mi5Qsw11cTs8I5F83qF5ALLHAAq+B7VDQsl6BqVcWmAwRdPAWVO4jNVaK5uFuWwxdz7rWEyrczfhA1UFUGnnZJH9K+oA4EcCMUHnqJCBjxVWFW32F54scngjMqzBdb+Sg0cW+MFBgIwi25GnwlPQejeQ7MKSihOVJ3ts9DiKsF8FT8wC8msfNw4ln2v8hN1kvZoTPZvPw5tZDUWO+KoIoUYKhSaj2rtYpNN731d8ssnBPdRVZn0P7ylj7LBc5IXTUYAdZq7/cLWVrIe9YBTSb1WxO6wkJ7lF1CO8twhtFOOEe233XIPGrcnarBgg9gwFo7vqKqLk=</SignatureValue>
<KeyInfo>
<KeyValue>
<RSAKeyValue>
<Modulus>zay6vRXa3SXLu19kEHSc9hLkEl8qTSaOwjVMMEv0B6Utwi7dqDB6wYdWCSQ5kDx7ARa8UyYKcAXXwP9DLjsIQdEhO/9DWkZ7rpynKg9deXkb4tGewOUt1K4gWnOgcg+ujTpdRzt91geKnvt6dms5vSyuMe+noFAlcACu+0DZmujg34pXW3A6Juvkh3tH+gX7DVCVhFC8+lgovgw/NJoHDBfrxN4g53tjcJ32A4dzSeMtSlUcqk37BTA/eaNonMtGPoUpQobaycjVKyfyHeQsU75I5wJGdgdnKRP/s87yvZWByJwAILk6TlhZpBQa2KKrwHboQF8KpyBKCrusPus1fMi5EtRvKVoX3S4EcDgrmUF8vuF2cjZYJXGsI1We9iZ9b5S/d/QiegUOw8W/l1PhMnbTWTedC2/Q2zgRfAgCH+mWAGObsJN+MBnP0eJXFMv1NvCNxzjylih61G8xLU722GI1Tg5k4I3f0LB8Cxrd8fs3DuFglY1n4u9+L0JG3mF9OpgrPAHQHniScqPymcrX55Il/sdgNxtnO2UBjrgMIFvuJWClzTWxUz8SmB7D6UdUisymSSFl504PUZo/HAidOdr2BaY/26SP/GOQ2ej+dSBpbBwj7ZCp44r+jM7bTeNz7ugzItrjQ46ODMtPFdx8PdD8xusR0SkogGTeU42QXeE=</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
<Subject>
<NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName">CN=</NameID>
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:holder-of-key">
<SubjectConfirmationData a:type="KeyInfoConfirmationDataType" xmlns:a="http://www.w3.org/2001/XMLSchema-instance">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<KeyValue>
<RSAKeyValue>
<Modulus>zay6vRXa3SXLu19kEHSc9hLkEl8qTSaOwjVMMEv0B6Utwi7dqDB6wYdWCSQ5kDx7ARa8UyYKcAXXwP9DLjsIQdEhO/9DWkZ7rpynKg9deXkb4tGewOUt1K4gWnOgcg+ujTpdRzt91geKnvt6dms5vSyuMe+noFAlcACu+0DZmujg34pXW3A6Juvkh3tH+gX7DVCVhFC8+lgovgw/NJoHDBfrxN4g53tjcJ32A4dzSeMtSlUcqk37BTA/eaNonMtGPoUpQobaycjVKyfyHeQsU75I5wJGdgdnKRP/s87yvZWByJwAILk6TlhZpBQa2KKrwHboQF8KpyBKCrusPus1fMi5EtRvKVoX3S4EcDgrmUF8vuF2cjZYJXGsI1We9iZ9b5S/d/QiegUOw8W/l1PhMnbTWTedC2/Q2zgRfAgCH+mWAGObsJN+MBnP0eJXFMv1NvCNxzjylih61G8xLU722GI1Tg5k4I3f0LB8Cxrd8fs3DuFglY1n4u9+L0JG3mF9OpgrPAHQHniScqPymcrX55Il/sdgNxtnO2UBjrgMIFvuJWClzTWxUz8SmB7D6UdUisymSSFl504PUZo/HAidOdr2BaY/26SP/GOQ2ej+dSBpbBwj7ZCp44r+jM7bTeNz7ugzItrjQ46ODMtPFdx8PdD8xusR0SkogGTeU42QXeE=</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
</KeyInfo>
</SubjectConfirmationData>
</SubjectConfirmation>
</Subject>
<Conditions NotBefore="2019-07-30T21:12:17.501Z" NotOnOrAfter="2019-07-30T21:17:17.501Z">
<AudienceRestriction>
<Audience>https://testclientcert.redoxengine.com/soap/crossgatewayretrieve</Audience>
</AudienceRestriction>
</Conditions>
<AttributeStatement>
<Attribute Name="urn:oasis:names:tc:xspa:1.0:subject:subject-id">
<AttributeValue>Chart Request</AttributeValue>
</Attribute>
<Attribute Name="urn:oasis:names:tc:xspa:1.0:subject:organization">
<AttributeValue>Inovalon inc</AttributeValue>
</Attribute>
<Attribute Name="urn:oasis:names:tc:xspa:1.0:subject:organization-id">
<AttributeValue>urn:2.16.840.1.113883.17.4095.1</AttributeValue>
</Attribute>
<Attribute Name="urn:nhin:names:saml:homeCommunityId">
<AttributeValue>urn:2.16.840.1.113883.17.4095.1</AttributeValue>
</Attribute>
<Attribute Name="urn:oasis:names:tc:xacml:2.0:subject:role">
<AttributeValue>
<Role xsi:type="CE" code="224608005" codeSystem="2.16.840.1.113883.6.96" codeSystemName="SNOMED_CT" displayName="EhrIntegration" xmlns="urn:hl7-org:v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
</AttributeValue>
</Attribute>
<Attribute Name="urn:oasis:names:tc:xspa:1.0:subject:purposeofuse">
<AttributeValue>
<PurposeOfUse xsi:type="CE" code="string" codeSystem="2.16.840.1.113883.3.18.7.1" codeSystemName="nhin-purpose" displayName="Treatment" xmlns="urn:hl7-org:v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
</AttributeValue>
</Attribute>
<Attribute Name="urn:oasis:names:tc:xacml:2.0:resource:resource-id">
<AttributeValue>123456^^^&amp;2.16.840.1.113883.17.4095.1&amp;ISO</AttributeValue>
</Attribute>
</AttributeStatement>
<AuthnStatement AuthnInstant="2019-07-30T21:12:17.501Z">
<SubjectLocality Address="128.23.80.106" DNSName="inovalon.com" />
<AuthnContext>
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef>
</AuthnContext>
</AuthnStatement>
</Assertion>
更新: 我已将代码更新为现在如下所示:
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.common.saml.SAMLKeyInfo;
import org.apache.wss4j.common.saml.SamlAssertionWrapper;
import org.opensaml.DefaultBootstrap;
import org.opensaml.xml.signature.KeyValue;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class Main {
private static ByteArrayInputStream readFile(final String filePath) throws IOException {
return new ByteArrayInputStream(Files.readAllBytes(new File(filePath).toPath()));
}
public static void main(String[] args) {
try {
DefaultBootstrap.bootstrap();
//InitializationService.initialize();
final ByteArrayInputStream is = readFile("files\\ITI-39 Request Sample.xml");
final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
dbFactory.setNamespaceAware(true);
final DocumentBuilder builder = dbFactory.newDocumentBuilder();
final Document doc = builder.parse(is);
//Element ass = DocumentBuilderFactory.newInstance().newDocumentBuilder()
// .parse(readFile("files\\assertion.xml")).getDocumentElement();
final NodeList assertionList = doc.getElementsByTagName("Assertion");
//final NodeList signatureList = doc.getElementsByTagName("Signature");
final Node assertionNode = assertionList.item(0);
//final Node signatureNode = signatureList.item(0);
final Element assertionElement = (Element) assertionNode;
//final Element signatureElement = (Element)signatureNode;
//final UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
//final Unmarshaller assertionUnmarshaller = unmarshallerFactory.getUnmarshaller(assertionElement);
//XMLObject assertionObject = assertionUnmarshaller.unmarshall(assertionElement);
//org.apache.xml.security.utils.I18n.init("", "");
final SamlAssertionWrapper wrapper = new SamlAssertionWrapper(assertionElement);
final List<KeyValue> keyValues = wrapper.getSignature().getKeyInfo().getKeyValues();
final String modulusString = keyValues.get(0).getRSAKeyValue().getModulus().getValue();
final String exponentString = keyValues.get(0).getRSAKeyValue().getExponent().getValue();
try {
wrapper.validateAssertion(true);
} catch (WSSecurityException ex) {
System.out.println("Assertion not validated" + ex.getMessage());
ex.printStackTrace();
}
final String modulusHex = toHex(modulusString);
final BigInteger modulus = new BigInteger(modulusHex.getBytes());
final String exponentHex = toHex(exponentString).replaceFirst("^0+(?!$)", ""); // replace leading zeros
final BigInteger exponent = new BigInteger(exponentHex.getBytes());
final RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
final KeyFactory factory = KeyFactory.getInstance("RSA");
final PublicKey publicKey = factory.generatePublic(spec);
System.out.println(wrapper.getSignatureValue().length);
String signatureValue = new String(wrapper.getSignatureValue());
System.out.println(signatureValue.length()+"");
signatureValue = Base64.getEncoder().encodeToString(wrapper.getSignatureValue());
System.out.println(signatureValue.length()+"");
System.out.println(signatureValue);
byte[] signatureBytes = Base64.getEncoder().encode(wrapper.getSignatureValue());
System.out.println(new String(signatureBytes));
System.out.println(signatureBytes.length+"");
signatureBytes = Base64.getDecoder().decode(signatureValue);
System.out.println(new String(signatureBytes));
System.out.println(signatureBytes.length+"");
/*
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(publicKey);
sig.update("FH0IVPNlZSvmmNhiNWCzvhE516k=".getBytes());
boolean verifies = sig.verify(wrapper.getSignatureValue());
System.out.println("verifies = " + verifies);
*/
try {
final SAMLKeyInfo samlKeyInfo = new SAMLKeyInfo(publicKey);
wrapper.verifySignature(samlKeyInfo);
} catch (WSSecurityException ex) {
System.out.println("Signature not verified\n"+ex.getMessage());
ex.printStackTrace();
}
System.out.println("Done");
} catch (Exception e) {
e.printStackTrace();
}
}
public static String toHex(String arg) throws UnsupportedEncodingException {
return String.format("%040x", new BigInteger(1, arg.getBytes("UTF-8")));
}
}
而我的 POM.xml 文件如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.rhapsody</groupId>
<artifactId>consoletest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.opensaml</groupId>
<artifactId>opensaml</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.wss4j</groupId>
<artifactId>wss4j-ws-security-common</artifactId>
<version>2.0.10</version>
</dependency>
<dependency>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.4.01</version>
</dependency>
</dependencies>
</project>
这段代码对我来说更有意义。对 validateAssertion 的调用成功了,我想这很好。
但是对verifySignature的调用失败并出现以下错误:
引起:org.apache.xml.security.signature.XMLSignatureException:签名长度不正确:得到 512 但期待 1368
不确定这是否已经是一个不同的问题,但我仍然希望能够让它工作。任何建议将不胜感激。
解决方案
推荐阅读
- excel - 将大型 Excel 工作表加载到 Informix 表的最佳方法是什么?
- c++ - 如何在c ++中返回复合函数的函数指针?
- mongodb - 如何理解 mongostat 的结果?
- javascript - 在多个元素上应用 onchange 事件
- reactjs - 如何在 onClick 上创建动态可编辑反应表
- java - 是否可以在堆栈上使用提供的 Java 集合方法,例如 max、min、sort 等...?
- python - 将函数参数作为参数传递给python中的另一个函数
- angular - Angular 通用 webpack 配置
- performance - 在 Jmeter for Oracle Application 中出现权限不足错误
- java - 在类路径上发现多个绑定 & 在类路径上检测到 log4j-over-slf4j.jar 和 slf4j-log4j12.jar