How to sign a SAML Assertion enveloped in a SAML Response tag



I am trying to implement Single Sign On Solutions ( I act as the IDP ). For the same I require the signed SAMLResponse XML data to be POST-ed to the ACS url of my SP's.


I am so far successfully able to sign the SAMLResponse.


This is my implementation ( Referenced from various sources ) so far ( Yea its a bit messy ) :



try {
String keyStoreFileName = "/WEB-INF/classes/saml-data/keystore.jks";
InputStream fis = getServletContext().getResource(keyStoreFileName)
.openStream();
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());

ks.load(fis, "pass".toCharArray());
fis.close();

// Get Private Key Entry From keystore

KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) ks
.getEntry("alias", new KeyStore.PasswordProtection(
"pass".toCharArray()));

PrivateKey privKey = pkEntry.getPrivateKey();

PublicKey pubKey = ks.getCertificate("alias").getPublicKey();

X509Certificate cert = (X509Certificate) ks
.getCertificate("alias");

XMLSignatureFactory sigFactory = XMLSignatureFactory
.getInstance("DOM");

List envelopedTransform = Collections.singletonList(sigFactory
.newTransform(Transform.ENVELOPED,
(TransformParameterSpec) null));

Reference ref = sigFactory.newReference("",
sigFactory.newDigestMethod(DigestMethod.SHA1, null),
envelopedTransform, null, null);

SignatureMethod signatureMethod = sigFactory.newSignatureMethod(
SignatureMethod.DSA_SHA1, null);

CanonicalizationMethod canonicalizationMethod = sigFactory
.newCanonicalizationMethod(
CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
(C14NMethodParameterSpec) null);

// Create the SignedInfo
SignedInfo signedInfo = sigFactory.newSignedInfo(
canonicalizationMethod, signatureMethod,
Collections.singletonList(ref));

// Create a KeyValue containing the DSA PublicKey
KeyInfoFactory keyInfoFactory = sigFactory.getKeyInfoFactory();
KeyValue keyValuePair = keyInfoFactory.newKeyValue(pubKey);

// Creating the x509 certificate data from Certificate object ( cert
// )

List x509 = new ArrayList();

x509.add(cert);

X509Data x509Data = keyInfoFactory.newX509Data(x509);

// Create a KeyInfo and add the KeyValue to it
// keyInfoItems.add(Collections.singletonList(keyValuePair));

// Adding the certificate data and the key value pair to the keyInfo

List keyInfoItems = new ArrayList();

keyInfoItems.add(x509Data);
// keyInfoItems.add(keyValuePair);

KeyInfo keyInfo = keyInfoFactory.newKeyInfo(keyInfoItems);

// Building the org.jdom.Document object from the samlResponse
// string
// ------------------------------------------------------------------
SAXBuilder builder = new SAXBuilder();
org.jdom.Document doc = builder.build(new ByteArrayInputStream(
strResponseXML.getBytes()));
// ------------------------------------------------------------------

// Convert the rootElement extracted from the doc to w3cElement
// ------------------------------------------------------------------

org.jdom.Element docRootElement = doc.getRootElement();
doc = docRootElement.getDocument();

XMLOutputter xmlOutputter = new XMLOutputter();
StringWriter elemStrWriter = new StringWriter();
xmlOutputter.output(doc, elemStrWriter);
byte[] xmlBytes = elemStrWriter.toString().getBytes();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);

org.w3c.dom.Element w3cElement = dbf.newDocumentBuilder()
.parse(new ByteArrayInputStream(xmlBytes))
.getDocumentElement();

// --------------------------------------------------------------------

// Create a DOMSignContext and specify the DSA PrivateKey and
// location of the resulting XMLSignature's parent element

System.out
.println("\n\n\nThe parent element to insert the signature is : ");
System.out.println(w3cElement);

DOMSignContext dsc = new DOMSignContext(privKey, w3cElement);

// compute the correct location to insert the signature xml
// (location is important because the SAML xsd's enforce sequence on
// signed
// info.)

org.w3c.dom.Node xmlSigInsertionPoint = null;

String JSR_105_PROVIDER = "org.jcp.xml.dsig.internal.dom.XMLDSigRI";
String SAML_PROTOCOL_NS_URI_V20 = "urn:oasis:names:tc:SAML:2.0:protocol";

org.w3c.dom.NodeList nodeList = w3cElement.getElementsByTagNameNS(
SAML_PROTOCOL_NS_URI_V20, "Extensions");
if (nodeList.getLength() == 0) {
nodeList = w3cElement.getElementsByTagNameNS(
SAML_PROTOCOL_NS_URI_V20, "Status");

}

xmlSigInsertionPoint = nodeList.item(nodeList.getLength() - 1);
dsc.setNextSibling(xmlSigInsertionPoint);

// Marshal, generate (and sign) the enveloped signature
XMLSignature signature = sigFactory.newXMLSignature(signedInfo,
keyInfo);
signature.sign(dsc);

// Create the root dom element from the w3cElement using DOMBuilder
DOMBuilder domBuilder = new DOMBuilder();
org.jdom.Element signedElement = domBuilder.build(w3cElement);

doc.setRootElement((org.jdom.Element) signedElement.detach());
xmlOutputter = new XMLOutputter();
strResponseXML = xmlOutputter.outputString(doc);

System.out.println("The signed SAML Response is : "
+ strResponseXML);

} catch (Exception e) {
System.out
.println("Exception while attempting to sign the SAML Response.");
e.printStackTrace();
}


Note that the strResponseXML contains the XML format string containing the SAML Response.


A Sample of the same would be :



<samlp:Response xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_75d9acf07505f761e6593a19842b520f"
Version="2.0" IssueInstant="2014-08-06T22:26:39+05:30" Destination="VALID_ DESTINATION_URL_NOT_SHOWN_FOR_SECURITY"
InResponseTo="_79e704d1fb4e40bc91dfabe6ad119a551407344">
<saml:Issuer>VALID_ ISSUER_URL_NOT_SHOWN_FOR_SECURITY</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"></samlp:StatusCode>
</samlp:Status>
<saml:Assertion
xmlns:xs="http://ift.tt/tphNwY"
xmlns:xsi="http://ift.tt/ra1lAU" Version="2.0"
ID="_a6f5c4ab91e4a9c65e2adf8d79703d19" IssueInstant="2014-08-06T22:26:39+05:30">
<saml:Issuer>VALID_ISSUER_URL_NOT_SHOWN_FOR_SECURITY</saml:Issuer>
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">VALID_EMAIL_ID</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
NotOnOrAfter="2014-08-06T22:31:39+05:30" Recipient="VALID_RECIPIENT_URL_NOT_SHOWN_FOR_SECURITY" InResponseTo="_79e704d1fb4e40bc91dfabe6
19a551407344" />
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2014-08-06T22:26:39+05:30" NotOnOrAfter="2014-08-06T22:31:39+05:30">
<saml:AudienceRestriction>
<saml:Audience>VALID_ACS_URL_NOT_SHOWN_FOR_SECURITY</saml:Audience>
<saml:Audience>VALID_ISSUER_URL_THATS_MY_ADDRESS</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2014-08-06T22:26:39+05:30"
SessionNotOnOrAfter="2014-08-06T22:31:39+05:30">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="Destination"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xmlns:xsi="http://ift.tt/ra1lAU"
xsi:type="xs:string">DOMAIN_NAME</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>


The above SAML Response when signed as a whole logs me into Salesforce. However Google apps does not log me in. I get redirected to an error page with the following error message ( after HTTP_POST to the google apps ACS url.)



Google Apps - This account cannot be accessed because we could not parse the login request.


When I tried to log into Zoho using a similarly signed SAMLResponse, it reverted me with an error "Signature Validation Failed".


I have a feeling that the Assertion needs to be signed and wrapped around the Response. This conclusion I had gotten from analyzing the HTTP-POST of SAMLResponse done by onelogin which had successfully logged into all the SP's. I decoded the SAML Response POST parameter and analyzed it using the salesforce's Assertion Validator tool, which revealed that the Response was unsigned while the Assertion was signed.


Now my question is how do I sign the SAML Assertion alone and inject the signature in the SAML Response.


Also since I am new to Java any feedback on the current implementation as to whether the same is clean / correct / conforming to SAML2.0 specs etc would be extremely educative for me.


Thanks.


No comments:

Post a Comment