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 :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
It will generate the following request :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
To be sure the request is valid, you can verify with https://www.samltool.com/validate_authn_req.php