1

I am attempting to validate a SignatureValue inside a SAML assertion.

The idea is that I will receive a SOAP request which contains a SAML assertion in the header and I need to validate it and check the certificate is valid and so on.

But the first thing is to just validate the SignatureValue using the Signature provided in the Assertion element.

I've put together some code using code I've found on StackOverflow and other places, but the validation is failing, and I'm not sure if it's something in my code or if it's the Assertion that is invalid.

Here is the code i'm currently trying:

        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 

And here is the Assertion element in the Soap message:

        <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>

UPDATE: I've update my code to now look like this:

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")));
    }
}

And my POM.xml file as follows:

<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>

And this code makes more sense to me. A call to validateAssertion is succeeding, which is good i guess.

But the call to verifySignature is failing with the following error:

Caused by: org.apache.xml.security.signature.XMLSignatureException: Signature length not correct: got 512 but was expecting 1368

Not sure if this is already a different problem, but i'd still like to be able to get this working. Any advice would be much appreciated.

Quintonn
  • 770
  • 8
  • 29
  • what do you mean by "the validation is failing" ? Do you have a log message or a stack trace ? – e.g78 Aug 14 '19 at 06:28
  • this code is returning false: "boolean okay = verifier.verify(sigHex.getBytes());" – Quintonn Aug 14 '19 at 12:48
  • I'm not sure but maybe if you add -Djava.security.debug=all you could get more informations about the validation failing – e.g78 Aug 14 '19 at 14:25
  • Your approach is too low level. OpenSAML 2.x example: https://stackoverflow.com/questions/17890642/opensaml-2-0-signature-validation-not-working or a much better example which correctly shows that you must do a number of other validation steps in addition to signature https://github.com/apache/cxf/blob/master/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLProtocolResponseValidator.java – identigral Aug 14 '19 at 16:18
  • I am aware there are other steps, i will attempt those after i complete this part. Am I wrong in thinking I should/could use the Signature KeyInfo to validate the SignatureValue? Is that not how it works? – Quintonn Aug 15 '19 at 06:13
  • probably commenting too late, but ur approach of taking the public key from the SAML response might not work as it normally will not contain the cert chain. I have tried ur own code with the public key with full chain and the validation worked – Sandeep Mar 06 '22 at 17:27

0 Answers0