Sunday, October 15, 2017

Sign SAML 2 AuthnRequest

Hi,

In order to continue on studying SAML requests. Let's complete the code of the last post to add a signature to the AuthnRequest. To run the following code the only requisite is to generate a key pair using Java KeyTool : https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html

Here there is the code :

package opensamltutorial;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.UUID;
import org.joda.time.DateTime;
import org.opensaml.core.config.InitializationException;
import org.opensaml.core.config.InitializationService;
import org.opensaml.core.xml.XMLObjectBuilderFactory;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.Marshaller;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.core.NameIDPolicy;
import org.opensaml.saml.saml2.core.RequestedAuthnContext;
import org.opensaml.saml.saml2.core.impl.AuthnContextClassRefBuilder;
import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder;
import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml.saml2.core.impl.NameIDPolicyBuilder;
import org.opensaml.saml.saml2.core.impl.RequestedAuthnContextBuilder;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.x509.BasicX509Credential;
import org.opensaml.xmlsec.signature.KeyInfo;
import org.opensaml.xmlsec.signature.Signature;
import org.opensaml.xmlsec.signature.X509Data;
import org.opensaml.xmlsec.signature.impl.KeyInfoBuilder;
import org.opensaml.xmlsec.signature.impl.SignatureBuilder;
import org.opensaml.xmlsec.signature.impl.X509CertificateBuilder;
import org.opensaml.xmlsec.signature.impl.X509DataBuilder;
import org.opensaml.xmlsec.signature.support.SignatureConstants;
import org.opensaml.xmlsec.signature.support.SignatureException;
import org.opensaml.xmlsec.signature.support.Signer;
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
public class AuthnGeneration {
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, KeyStoreException, NoSuchAlgorithmException, CertificateException, UnrecoverableEntryException, IOException {
try {
InitializationService.initialize();
} catch (InitializationException e) {
e.printStackTrace();
}
AuthnRequest authnRequest = generateAuthnRequest();
Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(authnRequest);
org.w3c.dom.Element authDOM;
try {
authDOM = marshaller.marshall(authnRequest);
try
{
Signer.signObject(authnRequest.getSignature());
}
catch (SignatureException e)
{
e.printStackTrace();
}
System.out.println(SerializeSupport.prettyPrintXML(authDOM));
} catch (MarshallingException e) {
e.printStackTrace();
}
}
private static AuthnRequest generateAuthnRequest()
throws IllegalArgumentException, SecurityException, IllegalAccessException, KeyStoreException, NoSuchAlgorithmException, CertificateException, UnrecoverableEntryException, IOException {
XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
AuthnRequestBuilder authnRequestBuilder = (AuthnRequestBuilder) builderFactory.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
AuthnRequest authnRequest = authnRequestBuilder.buildObject();
authnRequest.setForceAuthn(true);
authnRequest.setIsPassive(false);
authnRequest.setIssueInstant(new DateTime());
authnRequest.setDestination("http://idp.example.com/SSOService.php");
authnRequest.setProtocolBinding(SAMLConstants.SAML2_ARTIFACT_BINDING_URI);
authnRequest.setAssertionConsumerServiceURL("http://sp.example.com/demo1/index.php?acs");
authnRequest.setID("ONELOGIN_" + UUID.randomUUID().toString());
Issuer issuer = ((IssuerBuilder) builderFactory.getBuilder(Issuer.DEFAULT_ELEMENT_NAME)).buildObject();
issuer.setValue("http://sp.example.com/demo1/metadata.php");
authnRequest.setIssuer(issuer);
NameIDPolicy nameIDPolicy = ((NameIDPolicyBuilder) builderFactory.getBuilder(NameIDPolicy.DEFAULT_ELEMENT_NAME)).buildObject();
nameIDPolicy.setSPNameQualifier("SERVICE_PROVIDER_ID");
nameIDPolicy.setAllowCreate(true);
nameIDPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:transient");
authnRequest.setNameIDPolicy(nameIDPolicy);
RequestedAuthnContext requestedAuthnContext = ((RequestedAuthnContextBuilder) builderFactory.getBuilder(RequestedAuthnContext.DEFAULT_ELEMENT_NAME)).buildObject();
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.MINIMUM);
AuthnContextClassRef authnContextClassRef = ((AuthnContextClassRefBuilder) builderFactory.getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME)).buildObject();
authnContextClassRef.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");
requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
authnRequest.setRequestedAuthnContext(requestedAuthnContext);
// Add signature to request
Credential credential = intializeCredentials();
SignatureBuilder signFactory = new SignatureBuilder();
Signature signature = signFactory.buildObject(Signature.DEFAULT_ELEMENT_NAME);
signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
signature.setSigningCredential(credential);
// Add keyInfo to signature
KeyInfoBuilder keyInfoFactory = new KeyInfoBuilder();
KeyInfo keyInfo = keyInfoFactory.buildObject(KeyInfo.DEFAULT_ELEMENT_NAME);
X509DataBuilder X509DataFactory = new X509DataBuilder();
X509Data data = X509DataFactory.buildObject(X509Data.DEFAULT_ELEMENT_NAME);
X509CertificateBuilder X509CertificateFactory = new X509CertificateBuilder();
org.opensaml.xmlsec.signature.X509Certificate cert =
X509CertificateFactory.buildObject(org.opensaml.xmlsec.signature.X509Certificate.DEFAULT_ELEMENT_NAME);
String value =
org.apache.xml.security.utils.Base64.encode(credential.getPublicKey().getEncoded());
cert.setValue(value);
data.getX509Certificates().add(cert);
keyInfo.getX509Datas().add(data);
signature.setKeyInfo(keyInfo);
authnRequest.setSignature(signature);
return authnRequest;
}
private static Credential intializeCredentials() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableEntryException
{
KeyStore ks = null;
FileInputStream fis = null;
char[] password = "password".toCharArray();
String fileName = "/home/user1/.keystore";
String certificateAliasName = "password";
// Get Default Instance of KeyStore
ks = KeyStore.getInstance(KeyStore.getDefaultType());
// Read Ketstore as file Input Stream
fis = new FileInputStream(fileName);
// Load KeyStore
ks.load(fis, password);
// Close InputFileStream
fis.close();
// Get Private Key Entry From Certificate
KeyStore.PrivateKeyEntry pkEntry = null;
pkEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(certificateAliasName, new KeyStore.PasswordProtection(
password));
PrivateKey pk = pkEntry.getPrivateKey();
X509Certificate certificate = (X509Certificate) pkEntry.getCertificate();
BasicX509Credential credential = new BasicX509Credential(certificate);
credential.setPrivateKey(pk);
return credential;
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

It will generate the following request :

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest
AssertionConsumerServiceURL="http://sp.example.com/demo1/index.php?acs"
Destination="http://idp.example.com/SSOService.php"
ForceAuthn="true" ID="ONELOGIN_3043f7ab-7b8e-48ca-bfb3-a162e8d21d2a"
IsPassive="false" IssueInstant="2017-10-15T12:33:19.964Z"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/demo1/metadata.php</saml2:Issuer>
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference
URI="#ONELOGIN_3043f7ab-7b8e-48ca-bfb3-a162e8d21d2a">
<ds:Transforms>
<ds:Transform
Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>z+e0ezt92ipDEwuh14VkDASv3EIP3TgHoFoYq/sbluk=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
CNrX0mLErcEVy3BGLb2l3H16pcZE/wcpK9W6er9ZHJMxU8dXkpOAddyiflYcCyMs590LFJWy3kgU
Ri3Qp0CZv99yxBBHIRs4qLpS/YmPpNDO42zW5Z0QqqOuATZFge23E/tQdkTTxiIMlF62qImb2wDK
ln91mbGE77Yaow+nrdTBsPsEWtSjbHaz388+WXtoXYsJwqUCYSC6itfzaYSC58B0PpYFMUH3Q9hP
rOpqFPh8HDY2XUHqodjlENMv/B7qX7gzkybFE83w3qOBu7nk4LUTrxNt6dHzZ5t1iH1P7JUc7LAj
TKNSDVLB1y2fV3lwLtJ32LyKu2uIrOFlhUKHFg==
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArVmjIsZYSvdak9YKF4fhqZMKypqmoSjj
K4xfTaMQArOk5t5M3GAfyNTiNESVhZdNmboFbAJEeU2rBoI0BB5HFzdn8j6Bhrgxai9OBacPj5ki
6bcLfZJCixuJPUkWHZn1YMXjSYBHeGRwV9igqSgvS4/62CVo7o/OreTPcqzhZY8XzbUA4jIA4F7g
78Yueol6+lmIdthXqahO/SjShIPtDqbOL4lewuWzWThmJ6wAM9ObRPRNhEP55G2Xent3T4dbOUQ2
2WoxIAhbovx558XNfwosKOVLDg2or7zcvGbzGqcMW1V1V2R0z2XNKdK9ORxXcPnp02CT+1gO4316
4lgxxwIDAQAB</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<saml2p:NameIDPolicy AllowCreate="true"
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="SERVICE_PROVIDER_ID"/>
<saml2p:RequestedAuthnContext Comparison="minimum">
<saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>
</saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>
view raw gistfile1.txt hosted with ❤ by GitHub

To be sure the request is valid, you can verify with https://www.samltool.com/validate_authn_req.php

Sunday, October 1, 2017

Generate SAML 2 AuthnRequest by using OpenSAML 3

Hi,

I will start series of posts on SAML 2 about how to generate messages, parsing them and validating them.

First of all, to dig into the subject I really recommend to read the saml technical overview. It has everything needed to understand SAML and it is well written.

In this post I will show how to generate an AuthN request using Java and we will validate the request by using an online tool : https://www.samltool.com/online_tools.php.

You can check how AuthN requests are used in SAML protocol in the following diagram :

This diagram shows the use case "SP-Initiated SSO". Refer to the technical overview mentioned above to have all details.

To generate the AuthnRequest we will use OpenSAML 3. As the website is stating there isn't a lot of documentation on this version of OpenSAML so I took some time to adapt existing code to get the snippet bellow. The original code can be found on this blog :  http://www.john-james-andersen.com/blog/programming/sample-saml-2-0-authnrequest-in-java.html (thanks to the author of this blog)

package opensamltutorial;
import java.util.UUID;
import org.joda.time.DateTime;
import org.opensaml.core.config.InitializationException;
import org.opensaml.core.config.InitializationService;
import org.opensaml.core.xml.XMLObjectBuilderFactory;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.Marshaller;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.core.NameIDPolicy;
import org.opensaml.saml.saml2.core.RequestedAuthnContext;
import org.opensaml.saml.saml2.core.impl.AuthnContextClassRefBuilder;
import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder;
import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml.saml2.core.impl.NameIDPolicyBuilder;
import org.opensaml.saml.saml2.core.impl.RequestedAuthnContextBuilder;
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
public class AuthnGeneration {
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException {
try {
InitializationService.initialize();
} catch (InitializationException e) {
e.printStackTrace();
}
AuthnRequest authnRequest = generateAuthnRequest();
Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(authnRequest);
org.w3c.dom.Element authDOM;
try {
authDOM = marshaller.marshall(authnRequest);
System.out.println(SerializeSupport.prettyPrintXML(authDOM));
} catch (MarshallingException e) {
e.printStackTrace();
}
}
private static AuthnRequest generateAuthnRequest()
throws IllegalArgumentException, SecurityException, IllegalAccessException {
XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
AuthnRequestBuilder authnRequestBuilder = (AuthnRequestBuilder) builderFactory.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
AuthnRequest authnRequest = authnRequestBuilder.buildObject();
authnRequest.setForceAuthn(true);
authnRequest.setIsPassive(false);
authnRequest.setIssueInstant(new DateTime());
authnRequest.setDestination("http://idp.example.com/SSOService.php");
authnRequest.setProtocolBinding(SAMLConstants.SAML2_ARTIFACT_BINDING_URI);
authnRequest.setAssertionConsumerServiceURL("http://sp.example.com/demo1/index.php?acs");
authnRequest.setID("ONELOGIN_" + UUID.randomUUID().toString());
Issuer issuer = ((IssuerBuilder) builderFactory.getBuilder(Issuer.DEFAULT_ELEMENT_NAME)).buildObject();
issuer.setValue("http://sp.example.com/demo1/metadata.php");
authnRequest.setIssuer(issuer);
NameIDPolicy nameIDPolicy = ((NameIDPolicyBuilder) builderFactory.getBuilder(NameIDPolicy.DEFAULT_ELEMENT_NAME)).buildObject();
nameIDPolicy.setSPNameQualifier("SERVICE_PROVIDER_ID");
nameIDPolicy.setAllowCreate(true);
nameIDPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:transient");
authnRequest.setNameIDPolicy(nameIDPolicy);
RequestedAuthnContext requestedAuthnContext = ((RequestedAuthnContextBuilder) builderFactory.getBuilder(RequestedAuthnContext.DEFAULT_ELEMENT_NAME)).buildObject();
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.MINIMUM);
AuthnContextClassRef authnContextClassRef = ((AuthnContextClassRefBuilder) builderFactory.getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME)).buildObject();
authnContextClassRef.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");
requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
authnRequest.setRequestedAuthnContext(requestedAuthnContext);
return authnRequest;
}
}
view raw AuthnGeneration hosted with ❤ by GitHub

To make it run, add the following dependencies in your Maven project

<dependency>
    <groupId>org.opensaml</groupId>
    <artifactId>opensaml-core</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>org.opensaml</groupId>
    <artifactId>opensaml-saml-api</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>org.opensaml</groupId>
    <artifactId>opensaml-saml-impl</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>net.shibboleth.utilities</groupId>
    <artifactId>java-support</artifactId>
    <version>7.3.0</version>
</dependency>

The last dependency (net.shibboleth.utilities) is for the pretty print of the SAML message.

By running the code, here there is the SAML AuthnRequest that is generated :

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest
    AssertionConsumerServiceURL="http://sp.example.com/demo1/index.php?acs"
    Destination="http://idp.example.com/SSOService.php"
    ForceAuthn="true" ID="ONELOGIN_6f319749-6db0-4ac6-be72-cb223d5870a4"
    IsPassive="false" IssueInstant="2017-10-01T18:20:31.096Z"
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
    Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
    <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/demo1/metadata.php</saml2:Issuer>
    <saml2p:NameIDPolicy AllowCreate="true"
        Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="SERVICE_PROVIDER_ID"/>
    <saml2p:RequestedAuthnContext Comparison="minimum">
        <saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>
    </saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>

The last thing to do is to validate this message, we will use this nice online tool : https://www.samltool.com/validate_authn_req.php. Another way to validate the message would by using OpenSAML itself. It may be the subject of another post on this blog.


I hope this article will help you getting started with SAML message generation. To continue on the subject I will try to add digital signature to this generated message in order to improve security but it will be in another post.

Thank you for reading!