diff --git a/gamsaml20/pom.xml b/gamsaml20/pom.xml
new file mode 100644
index 000000000..f5a9c64f8
--- /dev/null
+++ b/gamsaml20/pom.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+
+
+ com.genexus
+ parent
+ ${revision}${changelist}
+
+
+ gamsaml20
+ GAM Saml 2.0 EO
+
+
+ UTF-8
+
+
+
+ org.bouncycastle
+ bcprov-jdk18on
+ 1.78.1
+ compile
+
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j.version}
+ compile
+
+
+ org.apache.santuario
+ xmlsec
+ 3.0.3
+
+
+ commons-io
+ commons-io
+ 2.11.0
+ compile
+
+
+
+
+ gamsaml20
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.0
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.1.1
+
+
+
+ test-jar
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gamsaml20/src/main/java/com/genexus/saml20/Binding.java b/gamsaml20/src/main/java/com/genexus/saml20/Binding.java
new file mode 100644
index 000000000..b6b4b4402
--- /dev/null
+++ b/gamsaml20/src/main/java/com/genexus/saml20/Binding.java
@@ -0,0 +1,29 @@
+package com.genexus.saml20;
+
+import com.genexus.saml20.utils.SamlAssertionUtils;
+
+@SuppressWarnings("unused")
+public abstract class Binding {
+
+ abstract void init(String input);
+
+ static String login(SamlParms parms, String relayState) {
+ return "";
+ }
+
+ static String logout(SamlParms parms, String relayState) {
+ return "";
+ }
+
+ abstract boolean verifySignatures(SamlParms parms);
+
+ abstract String getLoginAssertions();
+
+ abstract String getLoginAttribute(String name);
+
+ abstract String getRoles(String name);
+
+ abstract String getLogoutAssertions();
+
+ abstract boolean isLogout();
+}
diff --git a/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java b/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java
new file mode 100644
index 000000000..885f354bf
--- /dev/null
+++ b/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java
@@ -0,0 +1,73 @@
+package com.genexus.saml20;
+
+import com.genexus.saml20.utils.DSig;
+import com.genexus.saml20.utils.Encoding;
+import com.genexus.saml20.utils.SamlAssertionUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.w3c.dom.Document;
+
+import java.text.MessageFormat;
+
+@SuppressWarnings("unused")
+public class PostBinding extends Binding {
+
+ private static final Logger logger = LogManager.getLogger(PostBinding.class);
+
+ private Document xmlDoc;
+
+ public PostBinding() {
+ logger.trace("PostBinding constructor");
+ xmlDoc = null;
+ }
+ // EXTERNAL OBJECT PUBLIC METHODS - BEGIN
+
+
+ public void init(String xml) {
+ logger.trace("init");
+ this.xmlDoc = SamlAssertionUtils.canonicalizeXml(xml);
+ logger.debug(MessageFormat.format("Init - XML IdP response: {0}", Encoding.documentToString(xmlDoc)));
+ }
+
+ public static String login(SamlParms parms, String relayState) {
+ //not implemented yet
+ logger.error("login - NOT IMPLEMENTED");
+ return "";
+ }
+
+ public static String logout(SamlParms parms, String relayState) {
+ //not implemented yet
+ logger.error("logout - NOT IMPLEMENTED");
+ return "";
+ }
+
+ public boolean verifySignatures(SamlParms parms) {
+ return DSig.validateSignatures(this.xmlDoc, parms.getTrustCertPath(), parms.getTrustCertAlias(), parms.getTrustCertPass());
+ }
+
+ public String getLoginAssertions() {
+ logger.trace("getLoginAssertions");
+ return SamlAssertionUtils.getLoginInfo(this.xmlDoc);
+ }
+
+ public String getLogoutAssertions() {
+ logger.trace("getLogoutAssertions");
+ return SamlAssertionUtils.getLogoutInfo(this.xmlDoc);
+ }
+
+ public String getLoginAttribute(String name) {
+ logger.trace("getLoginAttribute");
+ return SamlAssertionUtils.getLoginAttribute(this.xmlDoc, name).trim();
+ }
+
+ public String getRoles(String name) {
+ logger.debug("getRoles");
+ return SamlAssertionUtils.getRoles(this.xmlDoc, name);
+ }
+
+ public boolean isLogout(){
+ return SamlAssertionUtils.isLogout(this.xmlDoc);
+ }
+
+ // EXTERNAL OBJECT PUBLIC METHODS - END
+}
diff --git a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java
new file mode 100644
index 000000000..53c6ee927
--- /dev/null
+++ b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java
@@ -0,0 +1,200 @@
+package com.genexus.saml20;
+
+import com.genexus.saml20.utils.*;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.signers.RSADigestSigner;
+import org.bouncycastle.util.encoders.Base64;
+import org.w3c.dom.Document;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.X509Certificate;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+@SuppressWarnings("unused")
+public class RedirectBinding extends Binding {
+
+ private static final Logger logger = LogManager.getLogger(RedirectBinding.class);
+
+ private Document xmlDoc;
+ private Map redirectMessage;
+
+ // EXTERNAL OBJECT PUBLIC METHODS - BEGIN
+
+
+ public RedirectBinding() {
+ logger.trace("RedirectBinding constructor");
+ }
+
+ public void init(String queryString) {
+ logger.trace("init");
+ logger.debug(MessageFormat.format("init - queryString : {0}", queryString));
+ this.redirectMessage = parseRedirect(queryString);
+ String xml = Encoding.decodeAndInflateXmlParameter(this.redirectMessage.get("SAMLResponse"));
+ logger.debug("init - inflated xml: {0}", xml);
+ this.xmlDoc = SamlAssertionUtils.canonicalizeXml(xml);
+ logger.debug(MessageFormat.format("init - XML IdP response: {0}", Encoding.documentToString(xmlDoc)));
+ }
+
+
+ public static String login(SamlParms parms, String relayState) {
+ Document request = SamlAssertionUtils.createLoginRequest(parms.getId(), parms.getEndPointLocation(), parms.getAcs(), parms.getIdentityProviderEntityID(), parms.getPolicyFormat(), parms.getAuthnContext(), parms.getServiceProviderEntityID(), parms.getForceAuthn());
+ return generateQuery(request, parms.getEndPointLocation(), parms.getCertPath(), parms.getCertPass(), parms.getCertAlias(), relayState);
+ }
+
+ public static String logout(SamlParms parms, String relayState) {
+ Document request = SamlAssertionUtils.createLogoutRequest(parms.getId(), parms.getServiceProviderEntityID(), parms.getNameID(), parms.getSessionIndex(), parms.getSingleLogoutEndpoint());
+ return generateQuery(request, parms.getSingleLogoutEndpoint(), parms.getCertPath(), parms.getCertPass(), parms.getCertAlias(), relayState);
+ }
+
+ public boolean verifySignatures(SamlParms parms) {
+ logger.debug("verifySignatures");
+
+ try {
+ return verifySignature_internal(parms.getTrustCertPath(), parms.getTrustCertPass(), parms.getTrustCertAlias());
+ } catch (Exception e) {
+ logger.error("verifySignature", e);
+ return false;
+ }
+ }
+
+ public String getLogoutAssertions() {
+ logger.trace("getLogoutAssertions");
+ return SamlAssertionUtils.getLogoutInfo(this.xmlDoc);
+ }
+
+ public String getRelayState() {
+ logger.trace("getRelayState");
+ try {
+ return this.redirectMessage.get("RelayState") == null ? "" : URLDecoder.decode(this.redirectMessage.get("RelayState"), StandardCharsets.UTF_8.name());
+ } catch (Exception e) {
+ logger.error("getRelayState", e);
+ return "";
+ }
+ }
+
+ public String getLoginAssertions() {
+ //Getting user's data by URL parms (GET) is deemed insecure so we are not implementing this method for redirect binding
+ logger.error("getLoginAssertions - NOT IMPLEMENTED insecure SAML implementation");
+ return "";
+ }
+
+ public String getRoles(String name) {
+ //Getting user's data by URL parms (GET) is deemed insecure so we are not implementing this method for redirect binding
+ logger.error("getRoles - NOT IMPLEMENTED insecure SAML implementation");
+ return "";
+ }
+
+ public String getLoginAttribute(String name) {
+ //Getting user's data by URL parms (GET) is deemed insecure so we are not implementing this method for redirect binding
+ logger.error("getLoginAttribute - NOT IMPLEMENTED insecure SAML implementation");
+ return "";
+ }
+
+ public boolean isLogout(){
+ return SamlAssertionUtils.isLogout(this.xmlDoc);
+ }
+
+ // EXTERNAL OBJECT PUBLIC METHODS - END
+
+ private boolean verifySignature_internal(String certPath, String certPass, String certAlias) {
+ logger.trace("verifySignature_internal");
+
+ byte[] signature = Encoding.decodeParameter(this.redirectMessage.get("Signature"));
+
+ String signedMessage;
+ if (this.redirectMessage.containsKey("RelayState")) {
+ signedMessage = MessageFormat.format("SAMLResponse={0}", this.redirectMessage.get("SAMLResponse"));
+ signedMessage += MessageFormat.format("&RelayState={0}", this.redirectMessage.get("RelayState"));
+ signedMessage += MessageFormat.format("&SigAlg={0}", this.redirectMessage.get("SigAlg"));
+ } else {
+ signedMessage = MessageFormat.format("SAMLResponse={0}", this.redirectMessage.get("SAMLResponse"));
+ signedMessage += MessageFormat.format("&SigAlg={0}", this.redirectMessage.get("SigAlg"));
+ }
+
+ byte[] query = signedMessage.getBytes(StandardCharsets.UTF_8);
+
+ X509Certificate cert = Keys.loadCertificate(certPath, certAlias, certPass);
+
+ try (InputStream inputStream = new ByteArrayInputStream(query)) {
+ String sigalg = URLDecoder.decode(this.redirectMessage.get("SigAlg"), StandardCharsets.UTF_8.name());
+ RSADigestSigner signer = new RSADigestSigner(Hash.getDigest(Hash.getHashFromSigAlg(sigalg)));
+ setUpSigner(signer, inputStream, Keys.getAsymmetricKeyParameter(cert), false);
+ return signer.verifySignature(signature);
+ } catch (Exception e) {
+ logger.error("verifySignature_internal", e);
+ return false;
+ }
+ }
+
+ private static Map parseRedirect(String request) {
+ logger.trace("parseRedirect");
+ Map result = new HashMap<>();
+ String[] redirect = request.split("&");
+
+ for (String s : redirect) {
+ String[] res = s.split("=");
+ result.put(res[0], res[1]);
+ }
+ return result;
+ }
+
+ private static String generateQuery(Document request, String destination, String certPath, String certPass, String alias, String relayState) {
+ logger.trace("generateQuery");
+ try {
+ String samlRequestParameter = Encoding.delfateAndEncodeXmlParameter(Encoding.documentToString(request));
+ String relayStateParameter = URLEncoder.encode(relayState, StandardCharsets.UTF_8.name());
+ Hash hash = Keys.isBase64(certPath) ? Hash.getHash(certPass.toUpperCase().trim()) : Hash.getHash(Keys.getHash(certPath, alias, certPass));
+
+ String sigAlgParameter = URLEncoder.encode(Hash.getSigAlg(hash), StandardCharsets.UTF_8.name());
+ String query = MessageFormat.format("SAMLRequest={0}&RelayState={1}&SigAlg={2}", samlRequestParameter, relayStateParameter, sigAlgParameter);
+ String signatureParameter = URLEncoder.encode(signRequest_RedirectBinding(query, certPath, certPass, hash, alias), StandardCharsets.UTF_8.name());
+
+ query += MessageFormat.format("&Signature={0}", signatureParameter);
+
+ logger.debug(MessageFormat.format("generateQuery - query: {0}", query));
+ return MessageFormat.format("{0}?{1}", destination, query);
+ } catch (Exception e) {
+ logger.error("generateQuery", e);
+ return "";
+ }
+
+ }
+
+ private static String signRequest_RedirectBinding(String query, String path, String password, Hash hash, String alias) {
+ logger.trace("signRequest_RedirectBinding");
+ RSADigestSigner signer = new RSADigestSigner(Hash.getDigest(hash));
+ byte[] inputText = query.getBytes(StandardCharsets.UTF_8);
+ try (InputStream inputStream = new ByteArrayInputStream(inputText)) {
+ setUpSigner(signer, inputStream, Keys.loadPrivateKey(path, alias, password), true);
+ byte[] outputBytes = signer.generateSignature();
+ return Base64.toBase64String(outputBytes);
+ } catch (Exception e) {
+ logger.error("signRequest_RedirectBinding", e);
+ return "";
+ }
+ }
+
+ private static void setUpSigner(Signer signer, InputStream input, AsymmetricKeyParameter asymmetricKeyParameter,
+ boolean toSign) {
+ logger.trace("setUpSigner");
+ byte[] buffer = new byte[8192];
+ int n;
+ try {
+ signer.init(toSign, asymmetricKeyParameter);
+ while ((n = input.read(buffer)) > 0) {
+ signer.update(buffer, 0, n);
+ }
+ } catch (Exception e) {
+ logger.error("setUpSigner", e);
+ }
+ }
+}
diff --git a/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java b/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java
new file mode 100644
index 000000000..589de28b1
--- /dev/null
+++ b/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java
@@ -0,0 +1,180 @@
+package com.genexus.saml20;
+
+@SuppressWarnings("unused")
+public class SamlParms {
+
+ private String id;
+ private String endPointLocation; //IdP Login URL
+ private String singleLogoutEndpoint; //IdP Logout URL
+ private String acs;
+ private String identityProviderEntityID; //issuer
+ private String certPath;
+ private String certPass;
+ private String certAlias;
+ private String policyFormat;
+ private String authnContext;
+ private String serviceProviderEntityID; //spName
+ private boolean forceAuthn;
+ private String nameID;
+ private String sessionIndex;
+ private String trustCertPath;
+ private String trustCertPass;
+ private String trustCertAlias;
+
+
+ public SamlParms() {
+ id = "";
+ endPointLocation = "";
+ singleLogoutEndpoint = "";
+ acs = "";
+ identityProviderEntityID = "";
+ certPath = "";
+ certPass = "";
+ certAlias = "";
+ policyFormat = "";
+ authnContext = "";
+ serviceProviderEntityID = "";
+ forceAuthn = false;
+ nameID = "";
+ sessionIndex = "";
+ trustCertAlias = "";
+ trustCertPass = "";
+ trustCertPath = "";
+ }
+
+ public void setId(String value) {
+ id = value;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setEndPointLocation(String value) {
+ endPointLocation = value;
+ }
+
+ public String getEndPointLocation() {
+ return endPointLocation;
+ }
+
+ public void setSingleLogoutEndpoint(String value) {
+ singleLogoutEndpoint = value;
+ }
+
+ public String getSingleLogoutEndpoint() {
+ return singleLogoutEndpoint;
+ }
+
+ public void setAcs(String value) {
+ acs = value;
+ }
+
+ public String getAcs() {
+ return acs;
+ }
+
+ public void setIdentityProviderEntityID(String value) {
+ identityProviderEntityID = value;
+ }
+
+ public String getIdentityProviderEntityID() {
+ return identityProviderEntityID;
+ }
+
+ public void setCertPath(String value) {
+ certPath = value;
+ }
+
+ public String getCertPath() {
+ return certPath;
+ }
+
+ public void setCertPass(String value) {
+ certPass = value;
+ }
+
+ public String getCertPass() {
+ return certPass;
+ }
+
+ public void setCertAlias(String value) {
+ certAlias = value;
+ }
+
+ public String getCertAlias() {
+ return certAlias;
+ }
+
+ public void setPolicyFormat(String value) {
+ policyFormat = value;
+ }
+
+ public String getPolicyFormat() {
+ return policyFormat;
+ }
+
+ public void setAuthnContext(String value) {
+ authnContext = value;
+ }
+
+ public String getAuthnContext() {
+ return authnContext;
+ }
+
+ public void setServiceProviderEntityID(String value) {
+ serviceProviderEntityID = value;
+ }
+
+ public String getServiceProviderEntityID() {
+ return serviceProviderEntityID;
+ }
+
+ public void setForceAuthn(boolean value) {
+ forceAuthn = value;
+ }
+
+ public boolean getForceAuthn() {
+ return forceAuthn;
+ }
+
+ public void setNameID(String value) {
+ nameID = value;
+ }
+
+ public String getNameID() {
+ return nameID;
+ }
+
+ public void setSessionIndex(String value) {
+ sessionIndex = value;
+ }
+
+ public String getSessionIndex() {
+ return sessionIndex;
+ }
+
+ public void setTrustCertPath(String value) {
+ trustCertPath = value;
+ }
+
+ public String getTrustCertPath() {
+ return trustCertPath;
+ }
+
+ public void setTrustCertPass(String value) {
+ trustCertPass = value;
+ }
+
+ public String getTrustCertPass() {
+ return trustCertPass;
+ }
+
+ public void setTrustCertAlias(String value) {
+ trustCertAlias = value;
+ }
+
+ public String getTrustCertAlias() {
+ return trustCertAlias;
+ }
+}
diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java
new file mode 100644
index 000000000..e3aa3a623
--- /dev/null
+++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java
@@ -0,0 +1,107 @@
+package com.genexus.saml20.utils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.xml.security.signature.XMLSignature;
+import org.apache.xml.security.utils.Constants;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathFactory;
+import java.security.cert.X509Certificate;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class DSig {
+
+ private static final Logger logger = LogManager.getLogger(DSig.class);
+
+ public static boolean validateSignatures(Document xmlDoc, String certPath, String certAlias, String certPassword) {
+ logger.trace("validateSignatures");
+ X509Certificate cert = Keys.loadCertificate(certPath, certAlias, certPassword);
+
+ NodeList nodes = findElementsByPath(xmlDoc, "//*[@ID]");
+
+ NodeList signatures = xmlDoc.getElementsByTagNameNS(Constants.SignatureSpecNS, Constants._TAG_SIGNATURE);
+ //check the message is signed - security measure
+ if(signatures.getLength() == 0){
+ return false;
+ }
+ for (int i = 0; i < signatures.getLength(); i++) {
+ Element signedElement = findNodeById(nodes, getSignatureID((Element) signatures.item(i)));
+ if (signedElement == null) {
+ return false;
+ }
+ signedElement.setIdAttribute("ID", true);
+ try {
+ XMLSignature signature = new XMLSignature((Element) signatures.item(i), "");
+ //verifies the signature algorithm is one expected - security meassure
+ if (!verifySignatureAlgorithm((Element) signatures.item(i))) {
+ return false;
+ }
+ if (!signature.checkSignatureValue(cert)) {
+ return false;
+ }
+ } catch (Exception e) {
+ logger.error("validateSignatures", e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean verifySignatureAlgorithm(Element elem) {
+ logger.trace("verifySignatureAlgorithm");
+ NodeList signatureMethod = elem.getElementsByTagNameNS(Constants.SignatureSpecNS, Constants._TAG_SIGNATUREMETHOD);
+ String signatureAlgorithm = signatureMethod.item(0).getAttributes().getNamedItem(Constants._ATT_ALGORITHM).getNodeValue();
+ logger.debug(MessageFormat.format("verifySignatureAlgorithm - algorithm: {0}", signatureAlgorithm));
+ String[] algorithm = signatureAlgorithm.split("#");
+ List validAlgorithms = Arrays.asList("rsa-sha1", "rsa-sha256", "rsa-sha512");
+ for (String alg : validAlgorithms) {
+ if (algorithm[1].trim().equals(alg)) {
+ return true;
+ }
+ }
+ logger.error(MessageFormat.format("verifySignatureAlgorithm - Invalid Signature algorithm {0}", algorithm[1]));
+ return false;
+ }
+
+ private static String getSignatureID(Element signatureElement) {
+ return signatureElement.getElementsByTagNameNS(Constants.SignatureSpecNS, Constants._TAG_REFERENCE).item(0).getAttributes().getNamedItem(Constants._ATT_URI).getNodeValue();
+ }
+
+ private static NodeList findElementsByPath(Document doc, String xPath) {
+ logger.trace("findElementsByPath");
+ try {
+ XPathFactory xpathFactory = XPathFactory.newInstance();
+ XPath xpath = xpathFactory.newXPath();
+ XPathExpression expr = xpath.compile(xPath);
+ return (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
+ } catch (Exception e) {
+ logger.error("findElementsByPath", e);
+ return null;
+ }
+ }
+
+ private static Element findNodeById(NodeList nodes, String id) {
+ logger.trace("findNodeById");
+ if (nodes == null) {
+ logger.error("findNodeById - Document node list is empty");
+ return null;
+ }
+ for (int i = 0; i < nodes.getLength(); i++) {
+ if (nodes.item(i).getAttributes().getNamedItem("ID").getNodeValue().equals(id.substring(1))) {
+ return (Element) nodes.item(i);
+ }
+ }
+ logger.error(MessageFormat.format("Element with id {0} not found", id.substring(1)));
+ return null;
+ }
+
+}
diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java
new file mode 100644
index 000000000..4b9bfa523
--- /dev/null
+++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java
@@ -0,0 +1,88 @@
+package com.genexus.saml20.utils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.util.encoders.Base64;
+import org.w3c.dom.Document;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.ByteArrayOutputStream;
+import java.io.StringWriter;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.Inflater;
+
+public class Encoding {
+
+ private static final Logger logger = LogManager.getLogger(Encoding.class);
+
+ public static String delfateAndEncodeXmlParameter(String parm) {
+ logger.trace("delfateAndEncodeXmlParameter");
+
+ try {
+ ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+ Deflater deflater = new Deflater(Deflater.DEFLATED, true);
+ DeflaterOutputStream deflaterStream = new DeflaterOutputStream(bytesOut, deflater);
+ deflaterStream.write(parm.getBytes(StandardCharsets.UTF_8));
+ deflaterStream.finish();
+
+ String base64 = Base64.toBase64String(bytesOut.toByteArray());
+ logger.debug(MessageFormat.format("Base64: {0}", base64));
+ return URLEncoder.encode(base64, StandardCharsets.UTF_8.name());
+ } catch (Exception e) {
+ logger.error("delfateAndEncodeXmlParameter", e);
+ return "";
+ }
+ }
+
+ public static String decodeAndInflateXmlParameter(String parm) {
+ logger.trace("decodeAndInflateXmlParameter");
+ try {
+ String base64 = URLDecoder.decode(parm, StandardCharsets.UTF_8.name());
+ byte[] bytes = Base64.decode(base64);
+ byte[] uncompressedData = new byte[4096];
+ Inflater inflater = new Inflater(true);
+ inflater.setInput(bytes);
+ int len = inflater.inflate(uncompressedData);
+ inflater.end();
+ return new String(uncompressedData, 0, len, StandardCharsets.UTF_8);
+ } catch (Exception e) {
+ logger.error("decodeAndInflateXmlParameter", e);
+ return "";
+ }
+ }
+
+ public static String documentToString(Document doc) {
+ logger.trace("documentToString");
+ try (StringWriter writer = new StringWriter()) {
+ DOMSource domSource = new DOMSource(doc);
+ StreamResult result = new StreamResult(writer);
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ transformer.transform(domSource, result);
+ return writer.toString();
+ } catch (Exception e) {
+ logger.error("documentToString", e);
+ return null;
+ }
+ }
+
+ public static byte[] decodeParameter(String parm) {
+ logger.trace("decodeParameter");
+ try {
+ String base64 = URLDecoder.decode(parm, StandardCharsets.UTF_8.name());
+ return Base64.decode(base64);
+ } catch (Exception e) {
+ logger.error("decodeParameter", e);
+ return null;
+ }
+
+ }
+}
diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/Hash.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/Hash.java
new file mode 100644
index 000000000..7b18ba104
--- /dev/null
+++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/Hash.java
@@ -0,0 +1,90 @@
+package com.genexus.saml20.utils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+
+import java.text.MessageFormat;
+
+public enum Hash {
+
+ SHA1, SHA256, SHA512;
+
+ private static final Logger logger = LogManager.getLogger(Hash.class);
+
+ public static Hash getHash(String hash) {
+ logger.trace("GetHash");
+ switch (hash.toUpperCase().trim()) {
+ case "SHA1":
+ return SHA1;
+ case "SHA256":
+ return SHA256;
+ case "SHA512":
+ return SHA512;
+ default:
+ logger.error(MessageFormat.format("GetHash - not implemented signature hash: {0}", hash));
+ return null;
+ }
+ }
+
+ public static String valueOf(Hash hash) {
+ switch (hash) {
+ case SHA1:
+ return "SHA1";
+ case SHA256:
+ return "SHA256";
+ case SHA512:
+ return "SHA512";
+ default:
+ return "";
+ }
+ }
+
+ public static String getSigAlg(Hash hash) {
+ logger.trace("GetSigAlg");
+ switch (hash) {
+ case SHA1:
+ return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha1";
+ case SHA256:
+ return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
+ case SHA512:
+ return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512";
+ default:
+ logger.error("GetSigAlg - not implemented signature hash");
+ return "";
+ }
+ }
+
+ public static Digest getDigest(Hash hash) {
+ logger.trace("getDigest");
+ switch (hash) {
+ case SHA1:
+ return new SHA1Digest();
+ case SHA256:
+ return new SHA256Digest();
+ case SHA512:
+ return new SHA512Digest();
+ default:
+ logger.error("getDigest - unknown hash");
+ return null;
+ }
+ }
+
+ public static Hash getHashFromSigAlg(String sigAlg) {
+ logger.trace("getHashFromSigAlg");
+ switch (sigAlg.trim()) {
+ case "http://www.w3.org/2001/04/xmldsig-more#rsa-sha1":
+ return SHA1;
+ case "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256":
+ return SHA256;
+ case "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512":
+ return SHA512;
+ default:
+ logger.error(MessageFormat.format("getHashFromSigAlg - not implemented signature algorithm: {0}", sigAlg));
+ return null;
+ }
+ }
+}
diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java
new file mode 100644
index 000000000..9547bae3f
--- /dev/null
+++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java
@@ -0,0 +1,145 @@
+package com.genexus.saml20.utils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
+import org.bouncycastle.util.encoders.Base64;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.text.MessageFormat;
+
+public class Keys {
+
+ private static final Logger logger = LogManager.getLogger(Keys.class);
+
+ public static AsymmetricKeyParameter loadPrivateKey(String path, String alias, String password) {
+ return isBase64(path) ? privateKeyFromBase64(path) : loadPrivateKeyFromJKS(path, alias, password);
+ }
+
+ public static X509Certificate loadCertificate(String path, String alias, String password) {
+ return isBase64(path) ? loadCertificateFromBase64(path) : loadCertificateFromJKS(path, alias, password);
+ }
+
+ public static AsymmetricKeyParameter getAsymmetricKeyParameter(X509Certificate cert) {
+ logger.trace("getAsymmetricKeyParameter");
+ try {
+ PublicKey publicKey = cert.getPublicKey();
+ SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
+ return PublicKeyFactory.createKey(subjectPublicKeyInfo);
+ } catch (Exception e) {
+ logger.error("getAsymmetricKeyParameter", e);
+ return null;
+ }
+ }
+
+ private static String getCertPath(String path) {
+ //boolean isAbsolute = new File(path).isAbsolute();
+
+ logger.debug("cuurent dir: " + new File(".").toPath().toAbsolutePath());
+
+ return System.getProperty("user.dir");
+ }
+
+ private static AsymmetricKeyParameter privateKeyFromBase64(String b64) {
+ logger.trace("privateKeyFromBase64");
+ try {
+ byte[] keyBytes = Base64.decode(b64);
+ try (ASN1InputStream istream = new ASN1InputStream(keyBytes)) {
+ ASN1Sequence seq = (ASN1Sequence) istream.readObject();
+ return PrivateKeyFactory.createKey(PrivateKeyInfo.getInstance(seq));
+ }
+ } catch (Exception e) {
+ logger.error("privateKeyFromBase64", e);
+ return null;
+ }
+ }
+
+ private static X509Certificate loadCertificateFromBase64(String b64) {
+ logger.trace("loadCertificateFromBase64");
+ try {
+ byte[] dataBuffer = Base64.decode(b64);
+ ByteArrayInputStream bI = new ByteArrayInputStream(dataBuffer);
+ CertificateFactory cf = new CertificateFactory();
+ return (X509Certificate) cf.engineGenerateCertificate(bI);
+ } catch (Exception e) {
+ logger.error("loadCertificateFromBase64", e);
+ return null;
+ }
+ }
+
+ private static AsymmetricKeyParameter loadPrivateKeyFromJKS(String path, String alias, String password) {
+ logger.trace("loadPrivateKeyFromJKS");
+ logger.debug(MessageFormat.format("Path: {0} , alias: {1}", path, alias));
+ getCertPath(path);
+ try (InputStream in = new DataInputStream(Files.newInputStream(new File(path).toPath()))) {
+ KeyStore ks = KeyStore.getInstance("JKS");
+ ks.load(in, password.toCharArray());
+ if (alias.isEmpty()) {
+ alias = ks.aliases().nextElement();
+ }
+ if (ks.getKey(alias, password.toCharArray()) != null) {
+ PrivateKey privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray());
+ PrivateKeyInfo keyinfo = PrivateKeyInfo.getInstance(privateKey.getEncoded());
+ return PrivateKeyFactory.createKey(keyinfo);
+ }
+ } catch (Exception e) {
+ logger.error("loadPrivateKeyFromJKS", e);
+ return null;
+ }
+ return null;
+ }
+
+ private static X509Certificate loadCertificateFromJKS(String path, String alias, String password) {
+ logger.trace("loadCertificateFromJKS");
+ logger.debug("alias: " + alias);
+ logger.debug("pasword: " + password);
+ logger.debug(MessageFormat.format("path: {0}, alias: {1}", path, alias));
+ Path p = new File(path).toPath();
+ logger.debug("Res path: " + p.toAbsolutePath());
+ System.out.println("Res path: " + p.toAbsolutePath());
+ try (InputStream in = new DataInputStream(Files.newInputStream(p))) {
+ KeyStore ks = KeyStore.getInstance("JKS");
+ ks.load(in, password.toCharArray());
+ if (alias.isEmpty()) {
+ alias = ks.aliases().nextElement();
+ }
+ return (X509Certificate) ks.getCertificate(alias);
+
+ } catch (Exception e) {
+ logger.error("loadCertificateFromJKS", e);
+ return null;
+ }
+
+ }
+
+ public static boolean isBase64(String path) {
+ try {
+ Base64.decode(path);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public static String getHash(String path, String alias, String password) {
+ String algorithmWithHash = loadCertificateFromJKS(path, alias, password).getSigAlgName();
+ String[] aux = algorithmWithHash.toUpperCase().split("WITH");
+ return aux[0];
+ }
+}
\ No newline at end of file
diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java
new file mode 100644
index 000000000..b8b91ee5c
--- /dev/null
+++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java
@@ -0,0 +1,301 @@
+package com.genexus.saml20.utils;
+
+import com.genexus.saml20.utils.xml.Attribute;
+import com.genexus.saml20.utils.xml.Element;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.xml.security.c14n.Canonicalizer;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SamlAssertionUtils {
+
+ private static final Logger logger = LogManager.getLogger(SamlAssertionUtils.class);
+
+ private static final String _saml_protocolNS = "urn:oasis:names:tc:SAML:2.0:protocol"; //saml2p
+ private static final String _saml_assertionNS = "urn:oasis:names:tc:SAML:2.0:assertion"; //saml2
+
+ public static Document loadDocument(String xml) {
+ logger.trace("loadDocument");
+ try {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ dbf.setNamespaceAware(true);
+
+
+ //disable parser's DTD reading - security meassure
+ dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ return db.parse(inputStream);
+ } catch (Exception e) {
+ logger.error("loadDocument", e);
+ return null;
+ }
+ }
+
+ public static boolean isLogout(Document xmlDoc){
+ logger.trace("isLogout");
+ try {
+ return xmlDoc.getDocumentElement().getLocalName().equals("LogoutResponse");
+ }catch (Exception e)
+ {
+ logger.error("isLogout", e);
+ return false;
+ }
+ }
+
+ public static Document createLogoutRequest(String id, String issuer, String nameID, String sessionIndex, String destination) {
+ logger.trace("createLogoutRequest");
+
+ ZonedDateTime nowUtc = ZonedDateTime.now(java.time.ZoneOffset.UTC);
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+ String issueInstant = nowUtc.format(formatter);
+
+
+ String saml2p = "urn:oasis:names:tc:SAML:2.0:protocol";
+ String saml2 = "urn:oasis:names:tc:SAML:2.0:assertion";
+
+ DocumentBuilder builder = createDocumentBuilder();
+
+ assert builder != null;
+ Document doc = builder.newDocument();
+
+ org.w3c.dom.Element request = doc.createElementNS(saml2p, "saml2p:LogoutRequest");
+ request.setAttribute("ID", id);
+ request.setAttribute("Version", "2.0");
+ request.setAttribute("IssueInstant", issueInstant);
+ //request.setAttribute("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
+ request.setAttribute("Destination", destination);
+ request.setAttribute("Reason", "urn:oasis:names:tc:SAML:2.0:logout:user");
+
+ org.w3c.dom.Element issuerElem = doc.createElementNS(saml2, "saml2:Issuer");
+ issuerElem.setTextContent(issuer);
+ request.appendChild(issuerElem);
+
+ org.w3c.dom.Element nameIDElem = doc.createElementNS(saml2, "saml2:NameID");
+ nameIDElem.setTextContent(nameID);
+ request.appendChild(nameIDElem);
+
+ org.w3c.dom.Element sessionElem = doc.createElementNS(saml2p, "saml2p:SessionIndex");
+ sessionElem.setTextContent(sessionIndex);
+ request.appendChild(sessionElem);
+
+ doc.appendChild(request);
+
+ logger.debug(MessageFormat.format("createLogoutRequest - XML request: {0}", Encoding.documentToString(doc)));
+ return doc;
+ }
+
+ public static Document createLoginRequest(String id, String destination, String acsUrl, String issuer, String policyFormat, String authContext, String spname, boolean forceAuthn) {
+ logger.trace("createLoginRequest");
+
+ ZonedDateTime nowUtc = ZonedDateTime.now(java.time.ZoneOffset.UTC);
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+ String issueInstant = nowUtc.format(formatter);
+
+ String samlp = "urn:oasis:names:tc:SAML:2.0:protocol";
+ String saml = "urn:oasis:names:tc:SAML:2.0:assertion";
+
+
+ DocumentBuilder builder = createDocumentBuilder();
+
+ assert builder != null;
+ Document doc = builder.newDocument();
+
+ org.w3c.dom.Element request = doc.createElementNS(samlp, "saml2p:AuthnRequest");
+ request.setAttribute("ID", id);
+ request.setAttribute("Version", "2.0");
+ request.setAttribute("IssueInstant", issueInstant);
+ //request.setAttribute("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
+ request.setAttribute("Destination", destination);
+ request.setAttribute("AssertionConsumerServiceURL", acsUrl);
+ request.setAttribute("ForceAuthn", Boolean.toString(forceAuthn));
+
+ org.w3c.dom.Element issuerElem = doc.createElementNS(saml, "saml2:Issuer");
+ issuerElem.setTextContent(issuer);
+ request.appendChild(issuerElem);
+
+ org.w3c.dom.Element policy = doc.createElementNS(samlp, "saml2p:NameIDPolicy");
+ policy.setAttribute("Format", policyFormat.trim());
+ policy.setAttribute("AllowCreate", "true");
+ policy.setAttribute("SPNameQualifier", spname);
+ request.appendChild(policy);
+
+ org.w3c.dom.Element authContextElem = doc.createElementNS(samlp, "saml2p:RequestedAuthnContext");
+ authContextElem.setAttribute("Comparison", "exact");
+
+ org.w3c.dom.Element authnContextClass = doc.createElementNS(saml, "saml2:AuthnContextClassRef");
+ authnContextClass.setTextContent(authContext);
+ authContextElem.appendChild(authnContextClass);
+ request.appendChild(authContextElem);
+
+
+ doc.appendChild(request);
+
+ logger.debug(MessageFormat.format("CreateLoginRequest - XML request: {0}", Encoding.documentToString(doc)));
+ return doc;
+ }
+
+ private static DocumentBuilder createDocumentBuilder() {
+ logger.trace("createDocumentBuilder");
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ return factory.newDocumentBuilder();
+ } catch (Exception e) {
+ logger.error("createDocumentBuilder", e);
+ return null;
+ }
+ }
+
+ public static Document canonicalizeXml(String xml) {
+ //delete comments from the xml - security meassure
+ logger.trace("canoncalizeXml");
+ logger.debug(MessageFormat.format("xmlString: {0}", xml));
+ try {
+ org.apache.xml.security.Init.init();
+
+ Document doc = loadDocument(xml);
+
+ Canonicalizer canonicalizer = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_OMIT_COMMENTS);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ canonicalizer.canonicalizeSubtree(doc, out);
+
+ String canonicalizedXML = out.toString(StandardCharsets.UTF_8.name());
+
+ return loadDocument(canonicalizedXML);
+
+ } catch (Exception e) {
+ logger.error("canoncalizeXml", e);
+ return null;
+ }
+ }
+
+ public static String getLoginInfo(Document xmlDoc) {
+ List atributeList = new ArrayList();
+ atributeList.add(new Attribute(_saml_assertionNS, "SubjectConfirmationData", "InResponseTo"));
+ atributeList.add(new Attribute(_saml_assertionNS, "Conditions", "NotOnOrAfter"));
+ atributeList.add(new Attribute(_saml_assertionNS, "Conditions", "NotBefore"));
+ atributeList.add(new Attribute(_saml_assertionNS, "SubjectConfirmationData", "Recipient"));
+ atributeList.add(new Attribute(_saml_assertionNS, "AuthnStatement", "SessionIndex"));
+ atributeList.add(new Attribute(_saml_protocolNS, "Response", "Destination"));
+ atributeList.add(new Attribute(_saml_protocolNS, "StatusCode", "Value"));
+
+ List elementList = new ArrayList();
+ elementList.add(new Element(_saml_assertionNS, "Issuer"));
+ elementList.add(new Element(_saml_assertionNS, "Audience"));
+ elementList.add(new Element(_saml_assertionNS, "NameID"));
+
+ return printJson(xmlDoc, atributeList, elementList);
+ }
+
+ public static String getLogoutInfo(Document doc) {
+ logger.trace("getLogoutInfo");
+ List atributeList = new ArrayList();
+ atributeList.add(new Attribute(_saml_protocolNS, "LogoutResponse", "Destination"));
+ atributeList.add(new Attribute(_saml_protocolNS, "LogoutResponse", "InResponseTo"));
+ atributeList.add(new Attribute(_saml_protocolNS, "StatusCode", "Value"));
+
+ List elementList = new ArrayList();
+ elementList.add(new Element(_saml_assertionNS, "Issuer"));
+
+ return printJson(doc, atributeList, elementList);
+ }
+
+ public static String getLoginAttribute(Document doc, String name) {
+ logger.trace("getLoginAttribute");
+ NodeList nodes = getAtttributeElements(doc);
+
+ for (int i = 0; i < nodes.getLength(); i++) {
+ if (nodes.item(i).getAttributes().getNamedItem("Name").getNodeValue().equals(name)) {
+ String value = nodes.item(i).getTextContent() == null ? getAttributeContent(nodes.item(i)): nodes.item(i).getTextContent();
+ logger.debug(MessageFormat.format("getLoginAttribute -- attribute name: {0}, value: {1}", name, value));
+ return value;
+ }
+ }
+ logger.error(MessageFormat.format("getLoginAttribute -- Could not find attribute with name {0}", name));
+ return "";
+ }
+
+ public static String getRoles(Document doc, String name) {
+ logger.trace("getRoles");
+ NodeList nodes = getAtttributeElements(doc);
+ List roles = new ArrayList<>();
+ for (int i = 0; i < nodes.getLength(); i++) {
+ if (nodes.item(i).getAttributes().getNamedItem("Name").getNodeValue().equals(name)) {
+ NodeList nList = nodes.item(i).getChildNodes();
+ for (int j = 0; j < nList.getLength(); j++) {
+ if (!nList.item(j).getTextContent().trim().isEmpty()) {
+ roles.add(nList.item(j).getTextContent().trim());
+ }
+ }
+ if (roles.isEmpty()) {
+ NodeList eList = ((org.w3c.dom.Element) nodes.item(i)).getElementsByTagName("AttributeValue");
+ for (int j = 0; j < eList.getLength(); j++) {
+ if (!nList.item(j).getTextContent().trim().isEmpty()) {
+ roles.add(nList.item(j).getTextContent().trim());
+ }
+ }
+ }
+ return String.join(",", roles);
+
+ }
+ }
+ logger.debug(MessageFormat.format("GetRoles -- Could not find attribute with name {0}", name));
+ return "";
+ }
+
+ private static String getAttributeContent(Node node) {
+ String value = node.getChildNodes().item(0).getTextContent().trim();
+ return value.isEmpty() ? ((org.w3c.dom.Element) node).getElementsByTagName("AttributeValue").item(0).getTextContent() : value;
+ }
+
+ private static NodeList getAtttributeElements(Document doc) {
+ NodeList nodes = doc.getElementsByTagNameNS(_saml_assertionNS, "Attribute");
+ return nodes.getLength() == 0 ? doc.getElementsByTagName("Attribute") : nodes;
+ }
+
+ private static String printJson(Document xmlDoc, List atributes, List elements) {
+ logger.trace("PrintJson");
+ StringBuilder json = new StringBuilder("{");
+ for (Attribute at : atributes) {
+ String value = at.printJson(xmlDoc);
+ if (value != null) {
+ json.append(MessageFormat.format("{0},", value));
+ }
+
+ }
+
+ int counter = 0;
+ for (Element el : elements) {
+ String value = el.printJson(xmlDoc);
+ if (value != null) {
+ if (counter != elements.size() - 1) {
+ json.append(MessageFormat.format("{0},", value));
+ } else {
+ json.append(MessageFormat.format("{0} }", value));
+ }
+ }
+ counter++;
+ }
+ logger.debug(MessageFormat.format("printJson -- json: {0}", json.toString()));
+ return json.toString();
+ }
+}
diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Attribute.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Attribute.java
new file mode 100644
index 000000000..132898ed1
--- /dev/null
+++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Attribute.java
@@ -0,0 +1,29 @@
+package com.genexus.saml20.utils.xml;
+
+import org.w3c.dom.Document;
+
+import java.text.MessageFormat;
+
+public class Attribute extends XmlTypes {
+
+ Element element;
+ private final String tag;
+
+ public Attribute(String namespace, String element, String tag) {
+ this.element = new Element(namespace, element);
+ this.tag = tag;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ public String findValue(Document doc) {
+ return element.getElement(doc).getAttributes().getNamedItem(tag).getNodeValue();
+ }
+
+ public String printJson(Document xmlDoc) {
+ String value = findValue(xmlDoc);
+ return value == null ? null : MessageFormat.format("\"{0}\": \"{1}\"", tag, value);
+ }
+}
\ No newline at end of file
diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Element.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Element.java
new file mode 100644
index 000000000..4337910ab
--- /dev/null
+++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Element.java
@@ -0,0 +1,37 @@
+package com.genexus.saml20.utils.xml;
+
+import org.w3c.dom.Document;
+
+import java.text.MessageFormat;
+
+public class Element extends XmlTypes {
+
+ private final String namespace;
+ private final String tag;
+
+ public Element(String namespace, String tag) {
+ this.namespace = namespace;
+ this.tag = tag;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ public org.w3c.dom.Node getElement(Document doc) {
+ return doc.getElementsByTagNameNS(namespace, tag).item(0);
+ }
+
+ public String findValue(Document doc) {
+ return getElement(doc).getTextContent();
+ }
+
+ public String printJson(Document xmlDoc) {
+ String value = findValue(xmlDoc);
+ return value == null ? null : MessageFormat.format("\"{0}\": \"{1}\"", tag, value);
+ }
+}
diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/XmlTypes.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/XmlTypes.java
new file mode 100644
index 000000000..03eb2170e
--- /dev/null
+++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/XmlTypes.java
@@ -0,0 +1,8 @@
+package com.genexus.saml20.utils.xml;
+
+import org.w3c.dom.Document;
+
+public abstract class XmlTypes {
+
+ abstract String printJson(Document doc);
+}
diff --git a/gamsaml20/src/test/java/com/genexus/test/PostBindingTest.java b/gamsaml20/src/test/java/com/genexus/test/PostBindingTest.java
new file mode 100644
index 000000000..b350d1f8e
--- /dev/null
+++ b/gamsaml20/src/test/java/com/genexus/test/PostBindingTest.java
@@ -0,0 +1,84 @@
+package com.genexus.test;
+
+import com.genexus.saml20.PostBinding;
+import com.genexus.saml20.SamlParms;
+import org.bouncycastle.util.encoders.Base64;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+
+public class PostBindingTest {
+
+ public static final String resources = System.getProperty("user.dir").concat("/src/test/resources");
+
+ private static PostBinding postBindingLoginResponse;
+ private static PostBinding postBindingLogoutResponse;
+ private static String alias;
+ private static String password;
+
+ @BeforeClass
+ public static void setUp() {
+ postBindingLoginResponse = new PostBinding();
+ String b64Login = "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOGU4ZGM1ZjY5YTk4Y2M0YzFmZjM0MjdlNWNlMzQ2MDZmZDY3MmY5MWU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0OFoiIERlc3RpbmF0aW9uPSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl80ZmVlM2IwNDYzOTVjNGU3NTEwMTFlOTdmODkwMGI1MjczZDU2Njg1Ij4KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPgogIDxzYW1scDpTdGF0dXM+CiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIiAvPgogIDwvc2FtbHA6U3RhdHVzPgogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJfZDcxYTNhOGU5ZmNjNDVjOWU5ZDI0OGVmNzA0OTM5M2ZjOGYwNGU1Zjc1IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0OFoiPgogICAgPHNhbWw6SXNzdWVyPmh0dHA6Ly9pZHAuZXhhbXBsZS5jb20vbWV0YWRhdGEucGhwPC9zYW1sOklzc3Vlcj4KICAgIDxzYW1sOlN1YmplY3Q+CiAgICAgIDxzYW1sOk5hbWVJRCBTUE5hbWVRdWFsaWZpZXI9Imh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9tZXRhZGF0YS5waHAiIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6dHJhbnNpZW50Ij5fY2UzZDI5NDhiNGNmMjAxNDZkZWUwYTBiM2RkNmY2OWI2Y2Y4NmY2MmQ3PC9zYW1sOk5hbWVJRD4KICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPgogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyNC0wMS0xOFQwNjoyMTo0OFoiIFJlY2lwaWVudD0iaHR0cDovL3NwLmV4YW1wbGUuY29tL2RlbW8xL2luZGV4LnBocD9hY3MiIEluUmVzcG9uc2VUbz0iT05FTE9HSU5fNGZlZTNiMDQ2Mzk1YzRlNzUxMDExZTk3Zjg5MDBiNTI3M2Q1NjY4NSIgLz4KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+CiAgICA8L3NhbWw6U3ViamVjdD4KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE0LTA3LTE3VDAxOjAxOjE4WiIgTm90T25PckFmdGVyPSIyMDI0LTAxLTE4VDA2OjIxOjQ4WiI+CiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+CiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3NwLmV4YW1wbGUuY29tL2RlbW8xL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+CiAgICA8L3NhbWw6Q29uZGl0aW9ucz4KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0OFoiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMjQtMDctMTdUMDk6MDE6NDhaIiBTZXNzaW9uSW5kZXg9Il9iZTk5NjdhYmQ5MDRkZGNhZTNjMGViNDE4OWFkYmUzZjcxZTMyN2NmOTMiPgogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+CiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+CiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+CiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+CiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+CiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJ1aWQiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPgogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnRlc3Q8L3NhbWw6QXR0cmlidXRlVmFsdWU+CiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+CiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj50ZXN0QGV4YW1wbGUuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPgogICAgICA8L3NhbWw6QXR0cmlidXRlPgogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0iZWR1UGVyc29uQWZmaWxpYXRpb24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPgogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnVzZXJzPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPgogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPmV4YW1wbGVyb2xlMTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+CiAgPFNpZ25hdHVyZSB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PFNpZ25lZEluZm8+PENhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy9UUi8yMDAxL1JFQy14bWwtYzE0bi0yMDAxMDMxNSIgLz48U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxkc2lnLW1vcmUjcnNhLXNoYTI1NiIgLz48UmVmZXJlbmNlIFVSST0iI19kNzFhM2E4ZTlmY2M0NWM5ZTlkMjQ4ZWY3MDQ5MzkzZmM4ZjA0ZTVmNzUiPjxUcmFuc2Zvcm1zPjxUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIgLz48L1RyYW5zZm9ybXM+PERpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIgLz48RGlnZXN0VmFsdWU+dWNwMm1qT2ZCNDZaaWxHbFpKUzhWR0VMVWpRZ0xQdnl3d3hIVjJHdjNmdz08L0RpZ2VzdFZhbHVlPjwvUmVmZXJlbmNlPjwvU2lnbmVkSW5mbz48U2lnbmF0dXJlVmFsdWU+cUdSWExNVHF4dHRqWldrWkovOEo5MWZkTVNSa2NZbW9DUTVhaFhGYUk5QzNobmlyQ0lUVjNIM1M1VDRiQTg0RC9tRVcyZDkwNmZ6ZnVhZWR1TnJRMkU1U0VtTUFFV25EdEwyak9GTzBrSGlZMHdoS2s4TkZSRnc2eC9kVk5uVUxQOXhnOGN5cmV6dVNxY1VSd1RWKzlhMXI4NGQ2VlpoLzJLTTg3WUMrODh2RVN2QXBUTXdmMGI2RUdmb3FvbDlJNG9KMDcrME9KMEdyU3VFajExTU1hTkZNakhTYmVKOXpGaWluV0lPSUVRdW9XRWVrUHhLNzQxTzJyMTNyNm5PbnVSajIwdThuVmRLb2tncGR2L1VwL0VtOVNGUVFsVDN5TUFhY0JlbjhPL1ZLOTF3TjdMbTQvZkpkWjF1UTFHOHVrcTRLSHZZWGc3K3VyKzJHbHVYY2JRPT08L1NpZ25hdHVyZVZhbHVlPjxLZXlJbmZvPjxYNTA5RGF0YT48WDUwOUNlcnRpZmljYXRlPk1JSUVBVENDQXVtZ0F3SUJBZ0lKQUlBcXZLSForZ0ZoTUEwR0NTcUdTSWIzRFFFQkN3VUFNSUdXTVFzd0NRWURWUVFHRXdKVldURVRNQkVHQTFVRUNBd0tUVzl1ZEdWMmFXUmxiekVUTUJFR0ExVUVCd3dLVFc5dWRHVjJhV1JsYnpFUU1BNEdBMVVFQ2d3SFIyVnVaVmgxY3pFUk1BOEdBMVVFQ3d3SVUyVmpkWEpwZEhreEVqQVFCZ05WQkFNTUNYTm5jbUZ0Y0c5dVpURWtNQ0lHQ1NxR1NJYjNEUUVKQVJZVmMyZHlZVzF3YjI1bFFHZGxibVY0ZFhNdVkyOXRNQjRYRFRJd01EY3dPREU0TlRjeE4xb1hEVEkxTURjd056RTROVGN4TjFvd2daWXhDekFKQmdOVkJBWVRBbFZaTVJNd0VRWURWUVFJREFwTmIyNTBaWFpwWkdWdk1STXdFUVlEVlFRSERBcE5iMjUwWlhacFpHVnZNUkF3RGdZRFZRUUtEQWRIWlc1bFdIVnpNUkV3RHdZRFZRUUxEQWhUWldOMWNtbDBlVEVTTUJBR0ExVUVBd3dKYzJkeVlXMXdiMjVsTVNRd0lnWUpLb1pJaHZjTkFRa0JGaFZ6WjNKaGJYQnZibVZBWjJWdVpYaDFjeTVqYjIwd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUMxemdhVStXaDYzcDlETldvQXk2NDI1MkV2WmpONDlBWTN4MFFDbkFhOEpPOVBrN3puUXdyeEVGVUtnWnp2MEdIRVlXNytYK3V5SnI3Qlc0VEE2ZnVKSjhhZ0UvYm1aUlp5amRKam91ZTBGTUw2ZmJtQ1o5VHN4cHhlNHB6aXNweVdROGpZVDRLbDRJM2ZkWk5VU240WFNpZG5ES0JJU2VDMDVtcmNjaERLaElucGlZREo0ODFsc0I0SlRFdGkzUzRYeS9Ub0t3WTR0NmF0dHc2ejVRRGhCYytZcm8rWVVxcnVsaU9BS3FjZnliZTlrMDdqd01DdkZWTTFocllZSjdod0hEU0ZvM01Ld1oweTJndzB3NlNnVkJ4TEZvK0tZUDNxNjNiNXdWaEQ4bHphU2grOFVjeWlITTIveWpFZWo3RW5SRnpkY2xUU05YUkZOYWlMbkVWZEFnTUJBQUdqVURCT01CMEdBMVVkRGdRV0JCUXRRQVdKUldOci9Pc3dQU0Fkd0NRaDBFZWkvREFmQmdOVkhTTUVHREFXZ0JRdFFBV0pSV05yL09zd1BTQWR3Q1FoMEVlaS9EQU1CZ05WSFJNRUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDakhlM0piTkt2MFl3YzF6bExhY1VOV2NqTGJtenZuanM4V3E1b3h0ZjV3RzVQVWxoTFNZWjlNUGh1Zjk1UGxpYm5yTy94VlkyOTJQNWxvNE5LaFM3Vk9vbnBiUFEvUHJDTU84NFB6MUxHZk0vd0NXUUlvd2g2VkhxMThQaVprYTl6YndsNlNvMHRnQ2xLa0ZTUms0d3BLcldYMytNMytZK0QwYnJkOHNFdEE2ZFhlWUhEdHFWMFlnaktkWklJT3gwdkRUNGFsQ29WUXJRMXlBSXE1SU5UM2NTTGdKZXpJaEVhZER2M1RjN2JNeE1GZUwrODFxSG05Wi85L0tFNlorSkIwWkVPa0YvMk5TUUpkK1o3TUJSOEN4T2RUUWlzM2x0TW9YRGF0TmtqWjJFbnY0MHN3NE5JQ0I4WVloc1dNSWFyZXc1dU5UK1JTMjhZSE5sYm1vZ2g8L1g1MDlDZXJ0aWZpY2F0ZT48L1g1MDlEYXRhPjwvS2V5SW5mbz48L1NpZ25hdHVyZT48L3NhbWw6QXNzZXJ0aW9uPgo8L3NhbWxwOlJlc3BvbnNlPg==";
+ postBindingLoginResponse.init(new String(Base64.decode(b64Login), StandardCharsets.UTF_8));
+
+ postBindingLogoutResponse = new PostBinding();
+ String b64Logout = "PHNhbWxwOkxvZ291dFJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfNmMzNzM3MjgyZjAwNzcyMGU3MzZmMGY0MDI4ZmVlZDhjYjliNDAyOTFjIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xOFQwMToxMzowNloiIERlc3RpbmF0aW9uPSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl8yMWRmOTFhODk3Njc4NzlmYzBmN2RmNmExNDkwYzYwMDBjODE2NDRkIj4NCiAgPHNhbWw6SXNzdWVyPmh0dHA6Ly9pZHAuZXhhbXBsZS5jb20vbWV0YWRhdGEucGhwPC9zYW1sOklzc3Vlcj4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIiAvPg0KICA8L3NhbWxwOlN0YXR1cz4NCjxTaWduYXR1cmUgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxTaWduZWRJbmZvPjxDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvVFIvMjAwMS9SRUMteG1sLWMxNG4tMjAwMTAzMTUiIC8+PFNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiIC8+PFJlZmVyZW5jZSBVUkk9IiNfNmMzNzM3MjgyZjAwNzcyMGU3MzZmMGY0MDI4ZmVlZDhjYjliNDAyOTFjIj48VHJhbnNmb3Jtcz48VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiIC8+PC9UcmFuc2Zvcm1zPjxEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiIC8+PERpZ2VzdFZhbHVlPlYwdDFxcVQzaENuNVJsYmFqWU5Va21wYUxpRzhsVXRobWx4aW1vMzJ5dEk9PC9EaWdlc3RWYWx1ZT48L1JlZmVyZW5jZT48L1NpZ25lZEluZm8+PFNpZ25hdHVyZVZhbHVlPkJId2ZzS3lzSXZFMlh3ajdiVDVlRzBIVER5NXFrK25jUjRGQmlsZDI3d3dnWG9UTDdnMXZ1bDVsNCtIeU9QeUZMMXdmbC9DTWR4SE9pNDBLcytOTXJWcUpKRkRDT1NqdGJDWGdHQTg2ZzV1KzRjM2ZRNlNSckxMbUJZa0JSdjlGeGRCVzdTbEhGM2hiall2azFLVXNRV1llTVR5bW00UERHSCtuNGtsbmdFcklrWDBvSHZHcUJDZmt0bGdWTmwvanpHcWNoUnk1Tnpta2lrZGd5bGNJUUZjNS9hUWlEYXE0ODMrZVBhVjIrVHdKNWlBeFRrRXFsVHBLMGVZcjNILzkwRXpid1BaL0F0TjB1RGk4YkdZYmVBeUszR3oyVWFKUEpVWHpzM1dMWDc4UTN1a3hIM0FxaUpHM21PZUIvclBDeDdSeVBrZEZNS2xPeHVkTWM5NDBkdz09PC9TaWduYXR1cmVWYWx1ZT48S2V5SW5mbz48WDUwOURhdGE+PFg1MDlDZXJ0aWZpY2F0ZT5NSUlFQVRDQ0F1bWdBd0lCQWdJSkFJQXF2S0haK2dGaE1BMEdDU3FHU0liM0RRRUJDd1VBTUlHV01Rc3dDUVlEVlFRR0V3SlZXVEVUTUJFR0ExVUVDQXdLVFc5dWRHVjJhV1JsYnpFVE1CRUdBMVVFQnd3S1RXOXVkR1YyYVdSbGJ6RVFNQTRHQTFVRUNnd0hSMlZ1WlZoMWN6RVJNQThHQTFVRUN3d0lVMlZqZFhKcGRIa3hFakFRQmdOVkJBTU1DWE5uY21GdGNHOXVaVEVrTUNJR0NTcUdTSWIzRFFFSkFSWVZjMmR5WVcxd2IyNWxRR2RsYm1WNGRYTXVZMjl0TUI0WERUSXdNRGN3T0RFNE5UY3hOMW9YRFRJMU1EY3dOekU0TlRjeE4xb3dnWll4Q3pBSkJnTlZCQVlUQWxWWk1STXdFUVlEVlFRSURBcE5iMjUwWlhacFpHVnZNUk13RVFZRFZRUUhEQXBOYjI1MFpYWnBaR1Z2TVJBd0RnWURWUVFLREFkSFpXNWxXSFZ6TVJFd0R3WURWUVFMREFoVFpXTjFjbWwwZVRFU01CQUdBMVVFQXd3SmMyZHlZVzF3YjI1bE1TUXdJZ1lKS29aSWh2Y05BUWtCRmhWelozSmhiWEJ2Ym1WQVoyVnVaWGgxY3k1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDMXpnYVUrV2g2M3A5RE5Xb0F5NjQyNTJFdlpqTjQ5QVkzeDBRQ25BYThKTzlQazd6blF3cnhFRlVLZ1p6djBHSEVZVzcrWCt1eUpyN0JXNFRBNmZ1Sko4YWdFL2JtWlJaeWpkSmpvdWUwRk1MNmZibUNaOVRzeHB4ZTRwemlzcHlXUThqWVQ0S2w0STNmZFpOVVNuNFhTaWRuREtCSVNlQzA1bXJjY2hES2hJbnBpWURKNDgxbHNCNEpURXRpM1M0WHkvVG9Ld1k0dDZhdHR3Nno1UURoQmMrWXJvK1lVcXJ1bGlPQUtxY2Z5YmU5azA3andNQ3ZGVk0xaHJZWUo3aHdIRFNGbzNNS3daMHkyZ3cwdzZTZ1ZCeExGbytLWVAzcTYzYjV3VmhEOGx6YVNoKzhVY3lpSE0yL3lqRWVqN0VuUkZ6ZGNsVFNOWFJGTmFpTG5FVmRBZ01CQUFHalVEQk9NQjBHQTFVZERnUVdCQlF0UUFXSlJXTnIvT3N3UFNBZHdDUWgwRWVpL0RBZkJnTlZIU01FR0RBV2dCUXRRQVdKUldOci9Pc3dQU0Fkd0NRaDBFZWkvREFNQmdOVkhSTUVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ2pIZTNKYk5LdjBZd2MxemxMYWNVTldjakxibXp2bmpzOFdxNW94dGY1d0c1UFVsaExTWVo5TVBodWY5NVBsaWJuck8veFZZMjkyUDVsbzROS2hTN1ZPb25wYlBRL1ByQ01PODRQejFMR2ZNL3dDV1FJb3doNlZIcTE4UGlaa2E5emJ3bDZTbzB0Z0NsS2tGU1JrNHdwS3JXWDMrTTMrWStEMGJyZDhzRXRBNmRYZVlIRHRxVjBZZ2pLZFpJSU94MHZEVDRhbENvVlFyUTF5QUlxNUlOVDNjU0xnSmV6SWhFYWREdjNUYzdiTXhNRmVMKzgxcUhtOVovOS9LRTZaK0pCMFpFT2tGLzJOU1FKZCtaN01CUjhDeE9kVFFpczNsdE1vWERhdE5raloyRW52NDBzdzROSUNCOFlZaHNXTUlhcmV3NXVOVCtSUzI4WUhObGJtb2doPC9YNTA5Q2VydGlmaWNhdGU+PC9YNTA5RGF0YT48L0tleUluZm8+PC9TaWduYXR1cmU+PC9zYW1scDpMb2dvdXRSZXNwb25zZT4=";
+ postBindingLogoutResponse.init(new String(Base64.decode(b64Logout), StandardCharsets.UTF_8));
+ alias = "1";
+ password = "dummy1";
+ }
+
+ @Test
+ public void testSignatureValidation_true() {
+ SamlParms parms = new SamlParms();
+ parms.setTrustCertPath(resources.concat("/keystore.jks"));
+ parms.setTrustCertAlias(alias);
+ parms.setTrustCertPass(password);
+ Assert.assertTrue("testSignatureValidation_true Login", postBindingLoginResponse.verifySignatures(parms));
+ Assert.assertTrue("testSignatureValidation_true Logout", postBindingLogoutResponse.verifySignatures(parms));
+ }
+
+ @Test
+ public void testSignatureValidation_false() {
+ SamlParms parms = new SamlParms();
+ parms.setTrustCertPath(resources.concat("/mykeystore.jks"));
+ parms.setTrustCertAlias(alias);
+ parms.setTrustCertPass(password);
+ Assert.assertFalse("testSignatureValidation_false Login", postBindingLoginResponse.verifySignatures(parms));
+ Assert.assertFalse("testSignatureValidation_false Logout", postBindingLogoutResponse.verifySignatures(parms));
+ }
+
+ @Test
+ public void testIsLogout() {
+ Assert.assertFalse("testIsLogout Login", postBindingLoginResponse.isLogout());
+ Assert.assertTrue("testIsLogout Logout", postBindingLogoutResponse.isLogout());
+ }
+
+ @Test
+ public void testGetLoginAssertions() {
+ String expected = "{\"InResponseTo\": \"ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685\",\"NotOnOrAfter\": \"2024-01-18T06:21:48Z\",\"NotBefore\": \"2014-07-17T01:01:18Z\",\"Recipient\": \"http://sp.example.com/demo1/index.php?acs\",\"SessionIndex\": \"_be9967abd904ddcae3c0eb4189adbe3f71e327cf93\",\"Destination\": \"http://sp.example.com/demo1/index.php?acs\",\"Value\": \"urn:oasis:names:tc:SAML:2.0:status:Success\",\"Issuer\": \"http://idp.example.com/metadata.php\",\"Audience\": \"http://sp.example.com/demo1/metadata.php\",\"NameID\": \"_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7\" }";
+ Assert.assertEquals(expected, postBindingLoginResponse.getLoginAssertions());
+ }
+
+ @Test
+ public void testGetLogoutAssertions() {
+ String expected = "{\"Destination\": \"http://sp.example.com/demo1/index.php?acs\",\"InResponseTo\": \"ONELOGIN_21df91a89767879fc0f7df6a1490c6000c81644d\",\"Value\": \"urn:oasis:names:tc:SAML:2.0:status:Success\",\"Issuer\": \"http://idp.example.com/metadata.php\" }";
+ Assert.assertEquals(expected, postBindingLogoutResponse.getLogoutAssertions());
+ }
+
+ @Test
+ public void testGetRoles() {
+ String expected = "users,examplerole1";
+ Assert.assertEquals(expected, postBindingLoginResponse.getRoles("eduPersonAffiliation"));
+ }
+
+ @Test
+ public void testGetLoginAttribute() {
+ String expected = "test@example.com";
+ Assert.assertEquals(expected, postBindingLoginResponse.getLoginAttribute("mail"));
+ }
+
+}
diff --git a/gamsaml20/src/test/java/com/genexus/test/RedirectBindingTest.java b/gamsaml20/src/test/java/com/genexus/test/RedirectBindingTest.java
new file mode 100644
index 000000000..003058c23
--- /dev/null
+++ b/gamsaml20/src/test/java/com/genexus/test/RedirectBindingTest.java
@@ -0,0 +1,240 @@
+package com.genexus.test;
+
+import com.genexus.saml20.RedirectBinding;
+import com.genexus.saml20.SamlParms;
+import com.genexus.saml20.utils.Encoding;
+import com.genexus.saml20.utils.Hash;
+import com.genexus.saml20.utils.Keys;
+import com.genexus.saml20.utils.SamlAssertionUtils;
+import org.bouncycastle.crypto.Signer;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.signers.RSADigestSigner;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.w3c.dom.Document;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.X509Certificate;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+public class RedirectBindingTest {
+
+ public static final String resources = System.getProperty("user.dir").concat("/src/test/resources");
+
+ private static String alias;
+ private static String password;
+ private static RedirectBinding redirectBindingLogoutResponse;
+
+ @BeforeClass
+ public static void setUp() {
+ alias = "1";
+ password = "dummy1";
+
+ redirectBindingLogoutResponse = new RedirectBinding();
+ String logoutResponse = "SAMLResponse=fVHLauQwEPwVo7tsSX7JwnYICVkCyR52ZnPYyyBLPYnBozZueZPPX8%2BEOQSWHJvqquqqbm8%2BTlPyFxYaMXRMpoIlEBz6Mbx27Pf%2BgWuWULTB2wkDdCwgu%2BlbsqdpNk%2F4imv8BTRjIEg2pUDmAnVsXYJBSyOZYE9AJjqzu31%2BMioVZl4wosOJJfdAcQw2XszfYpzJZNmEzk5vSDH7cft8OLMOdxgCuIjLT4gPmaUJU0vzB0se7zt2GKq8sMo7nov6yItKAdd60FwcRV0rXRWFhm01XC%2Fd40aq68ZrbS23FQheQJVz7SvNS%2B%2B9ODYbLO1GIlrhMZwbiB1TQpVclFw2e1kaKY3KU1XWf1jycm1wy8f69kJbPhv5vgtLBMs5P%2Buv%2BSlS%2Bj4Gj%2B%2BUBohZCa4ehmPDpa7VFs81fFBCcitBatk01ZCXWZt9el5%2Fs4s2rvR1ukMPyYudVvj%2BJrpsm93qHBCxrG%2Bzr6LZ%2F%2F7f%2FwM%3D&RelayState=http%3A%2F%2Frelaystate.com&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=ZhxqgSDAmtwxtUAXCafCNAXKLwL9iPgsqInuZfQ97dyPsGyszpgJftjgHBtoQpz159NjFpX0dGicVier2TQa82JBqgxUvdPT6mg%2FppdG7Z%2BnOXNttflqCd7mA3b%2FUOmWE4XgODz2mym%2BNPBmETAYmKofXo5ghpQc8IgGpI166%2F5VOwwhLcrg76HeYSxubxS4BoFUtLmpRnkaww9VQPZPIyh4kBmsCqe%2FV4QvM626ehdXDjPIciBgylt2ENMfQGZo83ubMB7KxgDNdErBgmTpILxftLn3ZH0FJAbM%2B3bzj6DFJ1yLuyUnUbdxOjoKaRskil853jKqmbvQtxRQ4QvZIg%3D%3D";
+ redirectBindingLogoutResponse.init(logoutResponse);
+ }
+
+ @Test
+ public void testSignatureValidation_true() {
+ SamlParms parms = new SamlParms();
+ parms.setTrustCertPath(resources.concat("/mykeystore.jks"));
+ parms.setTrustCertAlias(alias);
+ parms.setTrustCertPass(password);
+ Assert.assertTrue("testSignatureValidation_true Logout", redirectBindingLogoutResponse.verifySignatures(parms));
+ }
+
+ @Test
+ public void testSignatureValidation_false() {
+ SamlParms parms = new SamlParms();
+ parms.setTrustCertPath(resources.concat("/keystore.jks"));
+ parms.setTrustCertAlias(alias);
+ parms.setTrustCertPass(password);
+ Assert.assertFalse("testSignatureValidation_false Logout", redirectBindingLogoutResponse.verifySignatures(parms));
+ }
+
+
+ @Test
+ public void testGetLogoutAssertions() {
+ String expected = "{\"Destination\": \"https://localhost/GAM_SAML_ConnectorNetF/aslo.aspx\",\"InResponseTo\": \"_779d88aa-a6e0-4e63-8d68-5ddd0f97791a\",\"Value\": \"urn:oasis:names:tc:SAML:2.0:status:Success\",\"Issuer\": \"https://sts.windows.net/5ec7bbf9-1872-46c9-b201-a1e181996b35/\" }";
+ Assert.assertEquals(expected, redirectBindingLogoutResponse.getLogoutAssertions());
+ }
+
+ @Test
+ public void testIsLogout() {
+ String loginResponse = "SAMLResponse=5Vbfb%2BJGEH7uSf0frH03%2FoGNjRW40qSpkJJcFOipvZdoWQ%2FgO3vX3V0Hcn99Zw0mYHqE3lWqTn2yPDOe%2Beb7dmZ98XZd5NYTSJUJPiBexyUWcCbSjC8G5LfptR0TS2nKU5oLDgPCBXk7vFC0yP0yeQBVCq7AwiRcJRvrgFSSJ4KqTCWcFqASzZLJ6PYm8TtuUkqhBRM52X6zVgOy1LpMHGe1WnVW3Y6QC8d3Xc%2F5%2FfZmwpZQUGJdgdIZp7oGuQ3PBaP5Uijt%2FDq6fTQFHi8F58C0kHegrx1KmepQVa6JNb4akCy1w27kRj0v7rluHHleEEZe0I17YRB3%2BxjEm36mYkAeGcy7bBa5duy5gR0wd2bHaRrazAv6EaPhPEgR2VipCsbcUKQHxHf90HZD23enXj8JeokXdmK394FY7xuKkQQy%2FPHNDxsOk%2Fp7uU%2Fgaf6oUiAND8S6FrKg%2BnS4sWDj8zo0Aa4z%2FUyGe4SLT5p2mCgcWH%2Fyn0q6iP7sxn88FM%2FTMI0unH2QL6jLZKKprpSxtEyXIgXrPc0rOA1M1dHJpGIMlCJOndw5yr5ladR0%2FVVEbdWPfb%2FXDyK%2F6wZ9PAhhL%2FK9KHY9P%2Br77jco2ZLyv5ClgTCpZh9xAGpTY7vDWuOrV2F5Ha8Nq6BZPkpTaQQa0hw%2B4hqQovMZZjTPhfppYQIMygbQptRB9S0inMx5ZrIaDW9BL0V6miFWJDOgEuSG4lP5rqimXze8d0K%2F4%2B%2FkaK5BtiUPvY3kEUr%2BACwrMzDn4p8vH2fLh%2FPFBjYKOscSbnvG2DQzgcog%2FhlQHzg6oXtwz2zrQKdRlWKDDJBFLTO2g3UUMJzcY8sN2p31oMcvJWv8Lw3ttzmq9JKb0YcCqbbq1zPmcYLHE1ONeQrrc5VvtY6FEJKGtW73%2FOK5zHGjPMB8eHLpsISZODTf42MlZHqPFx7qCelUUo4nVOoXlv4me4vHFrIDx46qAxI1Mj6rNBx6j9yWmVUcQVySv5gxJrXhnN1FMYWxNmtihnGsNaW7MvVNsLvqs9fvejurFWeAPwgqS%2FRziSjXuPExIV%2Bct4YOq7cobXyv0nKdSaXN6%2FdCzWKdwtO%2Fy8GNWGT8%2F3k0jqwHA7XzNn8ZaNz9vjQ30fAv&RelayState=http%3A%2F%2Frelaystate.com&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256&Signature=aeNkIQLkkrNIxVgd1slzZXJpkEzvU0LIwqMR9wWLRT%2FjMHo7ldaCeGlFk3H%2Bbr4l3qEttjsTBWgTGgPDgzax7DDCUSvJPdAh0YB14T9oZ243cxap2OOi483TkBPt%2BwM6Q4AaePWbH1NdUvFUmP9ovl4Ub3iC4O%2FmZFRR3l4TU4z5ZR5OO8%2FFm%2BppvYXf%2FJDbsTLkKgF72a1lD1YhNWdqYKx3%2BQ22x94osmXis3omG7cdNDlo8ULesWL2RVXzftjmHa9zqWidTrHjyA6fSouTV3pQHmzrI8t9g3tuk5jKzTbOPmF2KBhEPzvN26jH2Bdy5b4PCvkJ1L9VeJKlGwBejQ%3D%3D";
+ RedirectBinding redirectBindingLoginResponse = new RedirectBinding();
+ redirectBindingLoginResponse.init(loginResponse);
+ Assert.assertTrue("testIsLogout Logout", redirectBindingLogoutResponse.isLogout());
+ Assert.assertFalse("testIsLogout Login", redirectBindingLoginResponse.isLogout());
+ }
+
+ @Test
+ public void testGetRelayState() {
+ String expected = "http://relaystate.com";
+ Assert.assertEquals(expected, redirectBindingLogoutResponse.getRelayState());
+ }
+
+ @Test
+ public void testLoginRequest() {
+ String function = "Login";
+ RedirectBinding redirectBinding = new RedirectBinding();
+ SamlParms parms = createParameters();
+ String samlRequest = redirectBinding.login(parms, "http://relaystate.com");
+ String queryString = getQueryString(samlRequest);
+ Map redirectMessage = parseRedirect(queryString);
+
+ //test login request parameters
+ testRequestParameters(redirectMessage, function);
+
+ //test login signature
+ testRequestSignature(redirectMessage, function);
+
+ //test login request xml parameters
+ String xml = Encoding.decodeAndInflateXmlParameter(redirectMessage.get("SAMLRequest"));
+ String expectedXml = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport";
+ Assert.assertEquals("Test Login request xml parameters", expectedXml, xml);
+
+ }
+
+ @Test
+ public void testLogoutRequest() {
+ String function = "Logout";
+ RedirectBinding redirectBinding = new RedirectBinding();
+ SamlParms parms = createParameters();
+ String samlRequest = redirectBinding.logout(parms, "http://relaystate.com");
+ String queryString = getQueryString(samlRequest);
+ Map redirectMessage = parseRedirect(queryString);
+
+ //test logout request parameters
+ testRequestParameters(redirectMessage, function);
+
+ //test logout signature
+ testRequestSignature(redirectMessage, function);
+
+ //test logout request xml parameters
+ String xml = Encoding.decodeAndInflateXmlParameter(redirectMessage.get("SAMLRequest"));
+ String expectedXml = "SPEntityIDnameID123456789";
+ Assert.assertEquals("Test Logout request xml parameters", expectedXml, xml);
+
+ }
+
+ private void testRequestParameters(Map redirectMessage, String function) {
+ String relayState = decodeParm(redirectMessage.get("RelayState"));
+ Assert.assertEquals(MessageFormat.format("Test {0} parameters RelayState", function), "http://relaystate.com", relayState);
+ String sigAlg = decodeParm(redirectMessage.get("SigAlg"));
+ Assert.assertEquals(MessageFormat.format("Test {0} request parameters SigAlg", function), "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", sigAlg);
+ }
+
+ private void testRequestSignature(Map redirectMessage, String function) {
+ boolean verifies = verifySignature_internal(resources.concat("/mykeystore.jks"), password, alias, redirectMessage);
+ Assert.assertTrue(MessageFormat.format("Test {0} request signature", function), verifies);
+ }
+
+ private static String getIssuerInstant(String xml, String name) {
+ Document doc = SamlAssertionUtils.canonicalizeXml(xml);
+ return doc.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol", name).item(0).getAttributes().getNamedItem("IssueInstant").getNodeValue();
+
+ }
+
+ private static String decodeParm(String parm) {
+ try {
+ return URLDecoder.decode(parm, StandardCharsets.UTF_8.name());
+ } catch (Exception e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+
+ private static String getQueryString(String samlRequest) {
+ try {
+ java.net.URL url = new java.net.URL(samlRequest);
+ return url.getQuery();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+
+ private static SamlParms createParameters() {
+ SamlParms parms = new SamlParms();
+ parms.setCertPath(resources.concat("/mykeystore.jks"));
+ parms.setCertPass(password);
+ parms.setCertAlias(alias);
+ parms.setAcs("http://myapp.com/acs");
+ parms.setForceAuthn(false);
+ parms.setServiceProviderEntityID("EntityID");
+ parms.setServiceProviderEntityID("SPEntityID");
+ parms.setPolicyFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");
+ parms.setAuthnContext("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");
+ parms.setEndPointLocation("http://endpoint/saml");
+ parms.setId("_idtralala");
+ parms.setSingleLogoutEndpoint("http://idp.com/slo");
+ parms.setSessionIndex("123456789");
+ parms.setServiceProviderEntityID("SPEntityID");
+ parms.setNameID("nameID");
+ return parms;
+ }
+
+
+ private static Map parseRedirect(String request) {
+ Map result = new HashMap<>();
+ String[] redirect = request.split("&");
+
+ for (String s : redirect) {
+ String[] res = s.split("=");
+ result.put(res[0], res[1]);
+ }
+ return result;
+ }
+
+ private static boolean verifySignature_internal(String certPath, String certPass, String certAlias, Map redirectMessage) {
+
+
+ byte[] signature = Encoding.decodeParameter(redirectMessage.get("Signature"));
+
+ String signedMessage;
+ if (redirectMessage.containsKey("RelayState")) {
+ signedMessage = MessageFormat.format("SAMLRequest={0}", redirectMessage.get("SAMLRequest"));
+ signedMessage += MessageFormat.format("&RelayState={0}", redirectMessage.get("RelayState"));
+ signedMessage += MessageFormat.format("&SigAlg={0}", redirectMessage.get("SigAlg"));
+ } else {
+ signedMessage = MessageFormat.format("SAMLRequest={0}", redirectMessage.get("SAMLRequest"));
+ signedMessage += MessageFormat.format("&SigAlg={0}", redirectMessage.get("SigAlg"));
+ }
+
+ byte[] query = signedMessage.getBytes(StandardCharsets.UTF_8);
+
+ X509Certificate cert = Keys.loadCertificate(certPath, certAlias, certPass);
+
+ try (InputStream inputStream = new ByteArrayInputStream(query)) {
+ String sigalg = URLDecoder.decode(redirectMessage.get("SigAlg"), StandardCharsets.UTF_8.name());
+ RSADigestSigner signer = new RSADigestSigner(Hash.getDigest(Hash.getHashFromSigAlg(sigalg)));
+ setUpSigner(signer, inputStream, Keys.getAsymmetricKeyParameter(cert), false);
+ return signer.verifySignature(signature);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ private static void setUpSigner(Signer signer, InputStream input, AsymmetricKeyParameter asymmetricKeyParameter, boolean toSign) {
+
+ byte[] buffer = new byte[8192];
+ int n;
+ try {
+ signer.init(toSign, asymmetricKeyParameter);
+ while ((n = input.read(buffer)) > 0) {
+ signer.update(buffer, 0, n);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/gamsaml20/src/test/resources/keystore.jks b/gamsaml20/src/test/resources/keystore.jks
new file mode 100644
index 000000000..7b0d9e9f9
Binary files /dev/null and b/gamsaml20/src/test/resources/keystore.jks differ
diff --git a/gamsaml20/src/test/resources/mykeystore.jks b/gamsaml20/src/test/resources/mykeystore.jks
new file mode 100644
index 000000000..f859734c8
Binary files /dev/null and b/gamsaml20/src/test/resources/mykeystore.jks differ
diff --git a/pom.xml b/pom.xml
index 400cf850e..88220bfce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -114,6 +114,7 @@
gxcloudstorage-awss3-v2
gxcompress
securityapicommons
+ gamsaml20
gxjwt
gxcryptography
gxxmlsignature