首页 > 解决方案 > 使用 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;amp;2.16.840.1.113883.17.4095.1&amp;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

不确定这是否已经是一个不同的问题,但我仍然希望能够让它工作。任何建议将不胜感激。

标签: javasamlsaml-2.0opensaml

解决方案


推荐阅读