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!

Monday, May 1, 2017

The Name Constraints Extension

A very interesting article about Name Constraints Extension on certificates : https://medium.com/netflix-techblog/bettertls-c9915cd255c0 . @Netflix , Thanks !