From bedd806888791513a2afaafd267b53b553e77d5a Mon Sep 17 00:00:00 2001 From: sgrampone Date: Thu, 13 Mar 2025 13:10:55 -0300 Subject: [PATCH 01/18] New SAML 2.0 implementation for GAM --- gamsaml20/pom.xml | 56 +++ .../src/main/java/com/genexus/Binding.java | 14 + .../main/java/com/genexus/PostBinding.java | 78 +++++ .../java/com/genexus/RedirectBinding.java | 184 ++++++++++ .../src/main/java/com/genexus/SamlParms.java | 203 +++++++++++ .../src/main/java/com/genexus/utils/DSig.java | 92 +++++ .../main/java/com/genexus/utils/Encoding.java | 87 +++++ .../src/main/java/com/genexus/utils/Hash.java | 83 +++++ .../src/main/java/com/genexus/utils/Keys.java | 139 ++++++++ .../java/com/genexus/utils/PolicyFormat.java | 93 +++++ .../com/genexus/utils/SamlAssertionUtils.java | 319 ++++++++++++++++++ .../java/com/genexus/utils/xml/Attribute.java | 33 ++ .../java/com/genexus/utils/xml/Element.java | 43 +++ .../java/com/genexus/utils/xml/XmlTypes.java | 8 + pom.xml | 1 + 15 files changed, 1433 insertions(+) create mode 100644 gamsaml20/pom.xml create mode 100644 gamsaml20/src/main/java/com/genexus/Binding.java create mode 100644 gamsaml20/src/main/java/com/genexus/PostBinding.java create mode 100644 gamsaml20/src/main/java/com/genexus/RedirectBinding.java create mode 100644 gamsaml20/src/main/java/com/genexus/SamlParms.java create mode 100644 gamsaml20/src/main/java/com/genexus/utils/DSig.java create mode 100644 gamsaml20/src/main/java/com/genexus/utils/Encoding.java create mode 100644 gamsaml20/src/main/java/com/genexus/utils/Hash.java create mode 100644 gamsaml20/src/main/java/com/genexus/utils/Keys.java create mode 100644 gamsaml20/src/main/java/com/genexus/utils/PolicyFormat.java create mode 100644 gamsaml20/src/main/java/com/genexus/utils/SamlAssertionUtils.java create mode 100644 gamsaml20/src/main/java/com/genexus/utils/xml/Attribute.java create mode 100644 gamsaml20/src/main/java/com/genexus/utils/xml/Element.java create mode 100644 gamsaml20/src/main/java/com/genexus/utils/xml/XmlTypes.java diff --git a/gamsaml20/pom.xml b/gamsaml20/pom.xml new file mode 100644 index 000000000..3b8250cc4 --- /dev/null +++ b/gamsaml20/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + + com.genexus + parent + ${revision}${changelist} + + + gamusaml20 + 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 + + + + + \ No newline at end of file diff --git a/gamsaml20/src/main/java/com/genexus/Binding.java b/gamsaml20/src/main/java/com/genexus/Binding.java new file mode 100644 index 000000000..4ae7f8034 --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/Binding.java @@ -0,0 +1,14 @@ +package com.genexus; + +@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(); +} diff --git a/gamsaml20/src/main/java/com/genexus/PostBinding.java b/gamsaml20/src/main/java/com/genexus/PostBinding.java new file mode 100644 index 000000000..b4bbfd207 --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/PostBinding.java @@ -0,0 +1,78 @@ +package com.genexus; + +import com.genexus.utils.DSig; +import com.genexus.utils.Encoding; +import com.genexus.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); + } + + public String getRoles(String name) + { + logger.debug("getRoles"); + return SamlAssertionUtils.getRoles(this.xmlDoc, name); + } + + // EXTERNAL OBJECT PUBLIC METHODS - END +} diff --git a/gamsaml20/src/main/java/com/genexus/RedirectBinding.java b/gamsaml20/src/main/java/com/genexus/RedirectBinding.java new file mode 100644 index 000000000..80b87f9a0 --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/RedirectBinding.java @@ -0,0 +1,184 @@ +package com.genexus; + +import com.genexus.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.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.getDestination(), parms.getAcs(), parms.getIssuer(), parms.getPolicyFormat(), parms.getAuthnContext(), parms.getSPName(), parms.getForceAuthn()); + return generateQuery(request, parms.getDestination(), parms.getCertPath(), parms.getCertPass(), parms.getCertAlias(), relayState); + } + + public static String logout(SamlParms parms, String relayState) + { + Document request = SamlAssertionUtils.createLogoutRequest(parms.getId(), parms.getIssuer(), parms.getNameID(), parms.getSessionIndex(), parms.getDestination()); + return generateQuery(request, parms.getDestination(), parms.getCertPath(), parms.getCertPass(), parms.getCertAlias(), relayState); + } + + public boolean verifySignatures(SamlParms parms) + { + logger.debug("verifySignatures"); + + try + { + return DSig.validateSignatures(this.xmlDoc, parms.getTrustCertPath(), parms.getTrustCertAlias(), parms.getTrustCertPass()); + }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 ""; + } + + // EXTERNAL OBJECT PUBLIC METHODS - END + + 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/SamlParms.java b/gamsaml20/src/main/java/com/genexus/SamlParms.java new file mode 100644 index 000000000..a68963545 --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/SamlParms.java @@ -0,0 +1,203 @@ +package com.genexus; + +@SuppressWarnings("unused") +public class SamlParms { + + private String id; + private String destination; + private String acs; + private String issuer; + private String certPath; + private String certPass; + private String certAlias; + private String policyFormat; + private String authnContext; + private String spName; + private boolean forceAuthn; + private String nameID; + private String sessionIndex; + private String trustCertPath; + private String trustCertPass; + private String trustCertAlias; + + + public SamlParms() + { + id = ""; + destination = ""; + acs = ""; + issuer = ""; + certPath = ""; + certPass = ""; + certAlias = ""; + policyFormat = ""; + authnContext = ""; + spName = ""; + forceAuthn = false; + nameID = ""; + sessionIndex = ""; + trustCertAlias = ""; + trustCertPass = ""; + trustCertPath = ""; + } + + public void setId(String value) + { + id = value; + } + + public String getId() + { + return id; + } + + public void setDestination(String value) + { + destination = value; + } + + public String getDestination() + { + return destination; + } + + public void setAcs(String value) + { + acs = value; + } + + public String getAcs() + { + return acs; + } + + public void setIssuer(String value) + { + issuer = value; + } + + public String getIssuer() + { + return issuer; + } + + 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 setSPName(String value) + { + spName = value; + } + + public String getSPName() + { + return spName; + } + + 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/utils/DSig.java b/gamsaml20/src/main/java/com/genexus/utils/DSig.java new file mode 100644 index 000000000..efe4941e6 --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/utils/DSig.java @@ -0,0 +1,92 @@ +package com.genexus.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; + +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); + + 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), "") ; + if (!signature.checkSignatureValue(cert)){ + return false; + } + }catch (Exception e) + { + logger.error("validateSignatures", e); + return false; + } + } + return true; + } + + + 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/utils/Encoding.java b/gamsaml20/src/main/java/com/genexus/utils/Encoding.java new file mode 100644 index 000000000..fccaca2eb --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/utils/Encoding.java @@ -0,0 +1,87 @@ +package com.genexus.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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +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; +import java.util.zip.InflaterInputStream; + +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; + } + } +} diff --git a/gamsaml20/src/main/java/com/genexus/utils/Hash.java b/gamsaml20/src/main/java/com/genexus/utils/Hash.java new file mode 100644 index 000000000..45dea67fd --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/utils/Hash.java @@ -0,0 +1,83 @@ +package com.genexus.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; + } + } +} diff --git a/gamsaml20/src/main/java/com/genexus/utils/Keys.java b/gamsaml20/src/main/java/com/genexus/utils/Keys.java new file mode 100644 index 000000000..2dec1fe3a --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/utils/Keys.java @@ -0,0 +1,139 @@ +package com.genexus.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.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.util.PrivateKeyFactory; +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.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); + } + + 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/utils/PolicyFormat.java b/gamsaml20/src/main/java/com/genexus/utils/PolicyFormat.java new file mode 100644 index 000000000..14d1857e4 --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/utils/PolicyFormat.java @@ -0,0 +1,93 @@ +package com.genexus.utils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@SuppressWarnings("LoggingSimilarMessage") +public enum PolicyFormat { + + UNSPECIFIED, EMAIL, ENCRYPTED, TRANSIENT, ENTITY, KERBEROS, WIN_DOMAIN_QUALIFIED, X509_SUBJECT; + + private static final Logger logger = LogManager.getLogger(PolicyFormat.class); + + public static PolicyFormat getPolicyFormat(String format) + { + logger.trace("GetPolicyFormat"); + switch(format.toUpperCase().trim()) + { + case "UNSPECIFIED": + return UNSPECIFIED; + case "EMAIL": + return EMAIL; + case "ENCRYPTED": + return ENCRYPTED; + case "TRANSIENT": + return TRANSIENT; + case "ENTITY": + return ENTITY; + case "KERBEROS": + return KERBEROS; + case "WIN_DOMAIN_QUALIFIED": + return WIN_DOMAIN_QUALIFIED; + case "X509_SUBJECT": + return X509_SUBJECT; + default: + logger.error("Unknown policy format"); + return null; + } + } + + public static String valueOf(PolicyFormat format) + { + logger.trace("ValueOf"); + switch (format) + { + case UNSPECIFIED: + return "UNSPECIFIED"; + case EMAIL: + return "EMAIL"; + case ENCRYPTED: + return "ENCRYPTED"; + case TRANSIENT: + return "TRANSIENT"; + case ENTITY: + return "ENTITY"; + case KERBEROS: + return "KERBEROS"; + case WIN_DOMAIN_QUALIFIED: + return "WIN_DOMAIN_QUALIFIED"; + case X509_SUBJECT: + return "X509_SUBJECT"; + default: + logger.error("Unknown policy format"); + return ""; + } + } + + public static String getPolicyFormatXmlValue(PolicyFormat format) + { + logger.trace("GetPolicyFormatXmlValue"); + switch (format) + { + case UNSPECIFIED: + return "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; + case EMAIL: + return "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"; + case ENCRYPTED: + return "urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted"; + case TRANSIENT: + return "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"; + case ENTITY: + return "urn:oasis:names:tc:SAML:2.0:nameid-format:entity"; + case KERBEROS: + return "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos"; + case WIN_DOMAIN_QUALIFIED: + return "urn:oasis:names:tc:SAML:2.0:nameid-format:windowsDomainQualifiedName"; + case X509_SUBJECT: + return "urn:oasis:names:tc:SAML:2.0:nameid-format:x509Subject"; + default: + logger.error("Unknown policy format"); + return ""; + } + } +} diff --git a/gamsaml20/src/main/java/com/genexus/utils/SamlAssertionUtils.java b/gamsaml20/src/main/java/com/genexus/utils/SamlAssertionUtils.java new file mode 100644 index 000000000..61fb10d7d --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/utils/SamlAssertionUtils.java @@ -0,0 +1,319 @@ +package com.genexus.utils; + +import com.genexus.utils.xml.Attribute; +import com.genexus.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.text.SimpleDateFormat; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Date; +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 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"; + + PolicyFormat pf = PolicyFormat.getPolicyFormat(policyFormat); + + 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"); + assert pf != null; + policy.setAttribute("Format", PolicyFormat.getPolicyFormatXmlValue(pf)); + 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 = getAttributeContent(nodes.item(i)); + 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 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/utils/xml/Attribute.java b/gamsaml20/src/main/java/com/genexus/utils/xml/Attribute.java new file mode 100644 index 000000000..cb8165795 --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/utils/xml/Attribute.java @@ -0,0 +1,33 @@ +package com.genexus.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/utils/xml/Element.java b/gamsaml20/src/main/java/com/genexus/utils/xml/Element.java new file mode 100644 index 000000000..e7ba2e54a --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/utils/xml/Element.java @@ -0,0 +1,43 @@ +package com.genexus.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/utils/xml/XmlTypes.java b/gamsaml20/src/main/java/com/genexus/utils/xml/XmlTypes.java new file mode 100644 index 000000000..eb7ba4a56 --- /dev/null +++ b/gamsaml20/src/main/java/com/genexus/utils/xml/XmlTypes.java @@ -0,0 +1,8 @@ +package com.genexus.utils.xml; + +import org.w3c.dom.Document; + +public abstract class XmlTypes { + + abstract String printJson(Document doc); +} diff --git a/pom.xml b/pom.xml index 011604a21..5d47fad92 100644 --- a/pom.xml +++ b/pom.xml @@ -111,6 +111,7 @@ gxcloudstorage-tests gxobservability gxcloudstorage-awss3-v2 + gamsaml20 From a148a96b2a1a92e3fe3d4c18aa52416813d0db33 Mon Sep 17 00:00:00 2001 From: sgrampone Date: Thu, 13 Mar 2025 17:14:49 -0300 Subject: [PATCH 02/18] Change package name to com.genexus.saml20 --- .../java/com/genexus/{ => saml20}/Binding.java | 2 +- .../java/com/genexus/{ => saml20}/PostBinding.java | 8 ++++---- .../com/genexus/{ => saml20}/RedirectBinding.java | 3 ++- .../java/com/genexus/{ => saml20}/SamlParms.java | 2 +- .../java/com/genexus/{ => saml20}/utils/DSig.java | 2 +- .../com/genexus/{ => saml20}/utils/Encoding.java | 5 +---- .../java/com/genexus/{ => saml20}/utils/Hash.java | 2 +- .../java/com/genexus/{ => saml20}/utils/Keys.java | 2 +- .../genexus/{ => saml20}/utils/PolicyFormat.java | 2 +- .../{ => saml20}/utils/SamlAssertionUtils.java | 14 ++++++-------- .../genexus/{ => saml20}/utils/xml/Attribute.java | 2 +- .../genexus/{ => saml20}/utils/xml/Element.java | 2 +- .../genexus/{ => saml20}/utils/xml/XmlTypes.java | 2 +- 13 files changed, 22 insertions(+), 26 deletions(-) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/Binding.java (94%) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/PostBinding.java (91%) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/RedirectBinding.java (99%) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/SamlParms.java (99%) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/utils/DSig.java (98%) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/utils/Encoding.java (94%) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/utils/Hash.java (98%) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/utils/Keys.java (99%) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/utils/PolicyFormat.java (98%) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/utils/SamlAssertionUtils.java (96%) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/utils/xml/Attribute.java (94%) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/utils/xml/Element.java (95%) rename gamsaml20/src/main/java/com/genexus/{ => saml20}/utils/xml/XmlTypes.java (74%) diff --git a/gamsaml20/src/main/java/com/genexus/Binding.java b/gamsaml20/src/main/java/com/genexus/saml20/Binding.java similarity index 94% rename from gamsaml20/src/main/java/com/genexus/Binding.java rename to gamsaml20/src/main/java/com/genexus/saml20/Binding.java index 4ae7f8034..eaa61fcb5 100644 --- a/gamsaml20/src/main/java/com/genexus/Binding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/Binding.java @@ -1,4 +1,4 @@ -package com.genexus; +package com.genexus.saml20; @SuppressWarnings("unused") public abstract class Binding { diff --git a/gamsaml20/src/main/java/com/genexus/PostBinding.java b/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java similarity index 91% rename from gamsaml20/src/main/java/com/genexus/PostBinding.java rename to gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java index b4bbfd207..b29c5467e 100644 --- a/gamsaml20/src/main/java/com/genexus/PostBinding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java @@ -1,8 +1,8 @@ -package com.genexus; +package com.genexus.saml20; -import com.genexus.utils.DSig; -import com.genexus.utils.Encoding; -import com.genexus.utils.SamlAssertionUtils; +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; diff --git a/gamsaml20/src/main/java/com/genexus/RedirectBinding.java b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java similarity index 99% rename from gamsaml20/src/main/java/com/genexus/RedirectBinding.java rename to gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java index 80b87f9a0..6873dbada 100644 --- a/gamsaml20/src/main/java/com/genexus/RedirectBinding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java @@ -1,5 +1,6 @@ -package com.genexus; +package com.genexus.saml20; +import com.genexus.saml20.utils.*; import com.genexus.utils.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/gamsaml20/src/main/java/com/genexus/SamlParms.java b/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java similarity index 99% rename from gamsaml20/src/main/java/com/genexus/SamlParms.java rename to gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java index a68963545..86782f0e8 100644 --- a/gamsaml20/src/main/java/com/genexus/SamlParms.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java @@ -1,4 +1,4 @@ -package com.genexus; +package com.genexus.saml20; @SuppressWarnings("unused") public class SamlParms { diff --git a/gamsaml20/src/main/java/com/genexus/utils/DSig.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java similarity index 98% rename from gamsaml20/src/main/java/com/genexus/utils/DSig.java rename to gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java index efe4941e6..cf89ebc43 100644 --- a/gamsaml20/src/main/java/com/genexus/utils/DSig.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java @@ -1,4 +1,4 @@ -package com.genexus.utils; +package com.genexus.saml20.utils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/gamsaml20/src/main/java/com/genexus/utils/Encoding.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java similarity index 94% rename from gamsaml20/src/main/java/com/genexus/utils/Encoding.java rename to gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java index fccaca2eb..98a05d10a 100644 --- a/gamsaml20/src/main/java/com/genexus/utils/Encoding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java @@ -1,4 +1,4 @@ -package com.genexus.utils; +package com.genexus.saml20.utils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -9,10 +9,8 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.StringWriter; -import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -20,7 +18,6 @@ import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; -import java.util.zip.InflaterInputStream; public class Encoding { diff --git a/gamsaml20/src/main/java/com/genexus/utils/Hash.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/Hash.java similarity index 98% rename from gamsaml20/src/main/java/com/genexus/utils/Hash.java rename to gamsaml20/src/main/java/com/genexus/saml20/utils/Hash.java index 45dea67fd..71a82e059 100644 --- a/gamsaml20/src/main/java/com/genexus/utils/Hash.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/Hash.java @@ -1,4 +1,4 @@ -package com.genexus.utils; +package com.genexus.saml20.utils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/gamsaml20/src/main/java/com/genexus/utils/Keys.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java similarity index 99% rename from gamsaml20/src/main/java/com/genexus/utils/Keys.java rename to gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java index 2dec1fe3a..9f1cfde64 100644 --- a/gamsaml20/src/main/java/com/genexus/utils/Keys.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java @@ -1,4 +1,4 @@ -package com.genexus.utils; +package com.genexus.saml20.utils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/gamsaml20/src/main/java/com/genexus/utils/PolicyFormat.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/PolicyFormat.java similarity index 98% rename from gamsaml20/src/main/java/com/genexus/utils/PolicyFormat.java rename to gamsaml20/src/main/java/com/genexus/saml20/utils/PolicyFormat.java index 14d1857e4..10fea0022 100644 --- a/gamsaml20/src/main/java/com/genexus/utils/PolicyFormat.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/PolicyFormat.java @@ -1,4 +1,4 @@ -package com.genexus.utils; +package com.genexus.saml20.utils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/gamsaml20/src/main/java/com/genexus/utils/SamlAssertionUtils.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java similarity index 96% rename from gamsaml20/src/main/java/com/genexus/utils/SamlAssertionUtils.java rename to gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java index 61fb10d7d..08b4d083c 100644 --- a/gamsaml20/src/main/java/com/genexus/utils/SamlAssertionUtils.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java @@ -1,7 +1,7 @@ -package com.genexus.utils; +package com.genexus.saml20.utils; -import com.genexus.utils.xml.Attribute; -import com.genexus.utils.xml.Element; +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; @@ -15,11 +15,9 @@ import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.text.MessageFormat; -import java.text.SimpleDateFormat; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Date; import java.util.List; public class SamlAssertionUtils { @@ -199,7 +197,7 @@ public static String getLoginInfo(Document xmlDoc) atributeList.add(new Attribute(_saml_protocolNS, "Response", "Destination")); atributeList.add(new Attribute(_saml_protocolNS, "StatusCode", "Value")); - List elementList = new ArrayList(); + 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")); @@ -210,12 +208,12 @@ public static String getLoginInfo(Document xmlDoc) public static String getLogoutInfo(Document doc) { logger.trace("getLogoutInfo"); - List atributeList = new ArrayList(); + 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(); + List elementList = new ArrayList(); elementList.add(new Element(_saml_assertionNS, "Issuer")); return printJson(doc, atributeList, elementList); diff --git a/gamsaml20/src/main/java/com/genexus/utils/xml/Attribute.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Attribute.java similarity index 94% rename from gamsaml20/src/main/java/com/genexus/utils/xml/Attribute.java rename to gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Attribute.java index cb8165795..fc266d675 100644 --- a/gamsaml20/src/main/java/com/genexus/utils/xml/Attribute.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Attribute.java @@ -1,4 +1,4 @@ -package com.genexus.utils.xml; +package com.genexus.saml20.utils.xml; import org.w3c.dom.Document; diff --git a/gamsaml20/src/main/java/com/genexus/utils/xml/Element.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Element.java similarity index 95% rename from gamsaml20/src/main/java/com/genexus/utils/xml/Element.java rename to gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Element.java index e7ba2e54a..2de7206fa 100644 --- a/gamsaml20/src/main/java/com/genexus/utils/xml/Element.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Element.java @@ -1,4 +1,4 @@ -package com.genexus.utils.xml; +package com.genexus.saml20.utils.xml; import org.w3c.dom.Document; diff --git a/gamsaml20/src/main/java/com/genexus/utils/xml/XmlTypes.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/XmlTypes.java similarity index 74% rename from gamsaml20/src/main/java/com/genexus/utils/xml/XmlTypes.java rename to gamsaml20/src/main/java/com/genexus/saml20/utils/xml/XmlTypes.java index eb7ba4a56..6107bdb2a 100644 --- a/gamsaml20/src/main/java/com/genexus/utils/xml/XmlTypes.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/XmlTypes.java @@ -1,4 +1,4 @@ -package com.genexus.utils.xml; +package com.genexus.saml20.utils.xml; import org.w3c.dom.Document; From 0ca4d74780bd055d1b73f4a6ff1438217c7aa77d Mon Sep 17 00:00:00 2001 From: sgrampone Date: Thu, 13 Mar 2025 17:20:21 -0300 Subject: [PATCH 03/18] Fix RedirectBinding refactor error --- gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java | 1 - 1 file changed, 1 deletion(-) diff --git a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java index 6873dbada..41b43f628 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java @@ -1,7 +1,6 @@ package com.genexus.saml20; import com.genexus.saml20.utils.*; -import com.genexus.utils.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.crypto.Signer; From eed14a3dc3534aad3c73a562646d1205a8ee2629 Mon Sep 17 00:00:00 2001 From: sgrampone Date: Thu, 13 Mar 2025 17:29:59 -0300 Subject: [PATCH 04/18] Fix code quality issue --- .../main/java/com/genexus/saml20/Binding.java | 15 ++- .../java/com/genexus/saml20/PostBinding.java | 29 ++---- .../com/genexus/saml20/RedirectBinding.java | 63 +++++------- .../java/com/genexus/saml20/SamlParms.java | 99 +++++++------------ .../java/com/genexus/saml20/utils/DSig.java | 36 +++---- .../com/genexus/saml20/utils/Encoding.java | 28 ++---- .../java/com/genexus/saml20/utils/Hash.java | 24 ++--- .../java/com/genexus/saml20/utils/Keys.java | 41 +++----- .../genexus/saml20/utils/PolicyFormat.java | 18 ++-- .../saml20/utils/SamlAssertionUtils.java | 91 +++++++---------- .../com/genexus/saml20/utils/xml/Element.java | 24 ++--- .../genexus/saml20/utils/xml/XmlTypes.java | 2 +- 12 files changed, 174 insertions(+), 296 deletions(-) diff --git a/gamsaml20/src/main/java/com/genexus/saml20/Binding.java b/gamsaml20/src/main/java/com/genexus/saml20/Binding.java index eaa61fcb5..dd5a98959 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/Binding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/Binding.java @@ -4,11 +4,22 @@ 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 ""; } + + 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(); } diff --git a/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java b/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java index b29c5467e..55382b5d6 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java @@ -10,66 +10,57 @@ import java.text.MessageFormat; @SuppressWarnings("unused") -public class PostBinding extends Binding{ +public class PostBinding extends Binding { private static final Logger logger = LogManager.getLogger(PostBinding.class); private Document xmlDoc; - public PostBinding() - { + public PostBinding() { logger.trace("PostBinding constructor"); xmlDoc = null; } // EXTERNAL OBJECT PUBLIC METHODS - BEGIN - public void init(String xml) - { + 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) - { + 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) - { + public static String logout(SamlParms parms, String relayState) { //not implemented yet logger.error("logout - NOT IMPLEMENTED"); return ""; } - public boolean verifySignatures(SamlParms parms) - { + public boolean verifySignatures(SamlParms parms) { return DSig.validateSignatures(this.xmlDoc, parms.getTrustCertPath(), parms.getTrustCertAlias(), parms.getTrustCertPass()); } - public String getLoginAssertions() - { + public String getLoginAssertions() { logger.trace("getLoginAssertions"); return SamlAssertionUtils.getLoginInfo(this.xmlDoc); } - public String getLogoutAssertions() - { + public String getLogoutAssertions() { logger.trace("getLogoutAssertions"); return SamlAssertionUtils.getLogoutInfo(this.xmlDoc); } - public String getLoginAttribute(String name) - { + public String getLoginAttribute(String name) { logger.trace("getLoginAttribute"); return SamlAssertionUtils.getLoginAttribute(this.xmlDoc, name); } - public String getRoles(String name) - { + public String getRoles(String name) { logger.debug("getRoles"); return SamlAssertionUtils.getRoles(this.xmlDoc, name); } diff --git a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java index 41b43f628..95059c930 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java @@ -19,7 +19,7 @@ import java.util.Map; @SuppressWarnings("unused") -public class RedirectBinding extends Binding{ +public class RedirectBinding extends Binding { private static final Logger logger = LogManager.getLogger(RedirectBinding.class); @@ -29,13 +29,11 @@ public class RedirectBinding extends Binding{ // EXTERNAL OBJECT PUBLIC METHODS - BEGIN - public RedirectBinding() - { + public RedirectBinding() { logger.trace("RedirectBinding constructor"); } - public void init(String queryString) - { + public void init(String queryString) { logger.trace("init"); logger.debug(MessageFormat.format("init - queryString : {0}", queryString)); this.redirectMessage = parseRedirect(queryString); @@ -46,66 +44,55 @@ public void init(String queryString) } - public static String login(SamlParms parms, String relayState) - { + public static String login(SamlParms parms, String relayState) { Document request = SamlAssertionUtils.createLoginRequest(parms.getId(), parms.getDestination(), parms.getAcs(), parms.getIssuer(), parms.getPolicyFormat(), parms.getAuthnContext(), parms.getSPName(), parms.getForceAuthn()); return generateQuery(request, parms.getDestination(), parms.getCertPath(), parms.getCertPass(), parms.getCertAlias(), relayState); } - public static String logout(SamlParms parms, String relayState) - { + public static String logout(SamlParms parms, String relayState) { Document request = SamlAssertionUtils.createLogoutRequest(parms.getId(), parms.getIssuer(), parms.getNameID(), parms.getSessionIndex(), parms.getDestination()); return generateQuery(request, parms.getDestination(), parms.getCertPath(), parms.getCertPass(), parms.getCertAlias(), relayState); } - public boolean verifySignatures(SamlParms parms) - { + public boolean verifySignatures(SamlParms parms) { logger.debug("verifySignatures"); - try - { + try { return DSig.validateSignatures(this.xmlDoc, parms.getTrustCertPath(), parms.getTrustCertAlias(), parms.getTrustCertPass()); - }catch(Exception e) - { + } catch (Exception e) { logger.error("verifySignature", e); return false; } } - public String getLogoutAssertions() - { + public String getLogoutAssertions() { logger.trace("getLogoutAssertions"); return SamlAssertionUtils.getLogoutInfo(this.xmlDoc); } - public String getRelayState() - { + 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) - { + } catch (Exception e) { logger.error("getRelayState", e); return ""; } } - public String getLoginAssertions() - { + 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) - { + 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) - { + 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 ""; @@ -113,22 +100,19 @@ public String getLoginAttribute(String name) // EXTERNAL OBJECT PUBLIC METHODS - END - private static Map parseRedirect(String request) - { + private static Map parseRedirect(String request) { logger.trace("parseRedirect"); - Map result = new HashMap<>(); + Map result = new HashMap<>(); String[] redirect = request.split("&"); - for(String s : redirect) - { + 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) - { + 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)); @@ -143,25 +127,22 @@ private static String generateQuery(Document request, String destination, String logger.debug(MessageFormat.format("generateQuery - query: {0}", query)); return MessageFormat.format("{0}?{1}", destination, query); - }catch (Exception e) - { + } catch (Exception e) { logger.error("generateQuery", e); return ""; } } - private static String signRequest_RedirectBinding(String query, String path, String password, Hash hash, String alias) - { + 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)); + 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) - { + } catch (Exception e) { logger.error("signRequest_RedirectBinding", e); return ""; } diff --git a/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java b/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java index 86782f0e8..0ad69657e 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java @@ -21,8 +21,7 @@ public class SamlParms { private String trustCertAlias; - public SamlParms() - { + public SamlParms() { id = ""; destination = ""; acs = ""; @@ -41,163 +40,131 @@ public SamlParms() trustCertPath = ""; } - public void setId(String value) - { + public void setId(String value) { id = value; } - public String getId() - { + public String getId() { return id; } - public void setDestination(String value) - { + public void setDestination(String value) { destination = value; } - public String getDestination() - { + public String getDestination() { return destination; } - public void setAcs(String value) - { + public void setAcs(String value) { acs = value; } - public String getAcs() - { + public String getAcs() { return acs; } - public void setIssuer(String value) - { + public void setIssuer(String value) { issuer = value; } - public String getIssuer() - { + public String getIssuer() { return issuer; } - public void setCertPath(String value) - { + public void setCertPath(String value) { certPath = value; } - public String getCertPath() - { + public String getCertPath() { return certPath; } - public void setCertPass(String value) - { + public void setCertPass(String value) { certPass = value; } - public String getCertPass() - { + public String getCertPass() { return certPass; } - public void setCertAlias(String value) - { + public void setCertAlias(String value) { certAlias = value; } - public String getCertAlias() - { + public String getCertAlias() { return certAlias; } - public void setPolicyFormat(String value) - { + public void setPolicyFormat(String value) { policyFormat = value; } - public String getPolicyFormat() - { + public String getPolicyFormat() { return policyFormat; } - public void setAuthnContext(String value) - { + public void setAuthnContext(String value) { authnContext = value; } - public String getAuthnContext() - { + public String getAuthnContext() { return authnContext; } - public void setSPName(String value) - { + public void setSPName(String value) { spName = value; } - public String getSPName() - { + public String getSPName() { return spName; } - public void setForceAuthn(boolean value) - { + public void setForceAuthn(boolean value) { forceAuthn = value; } - public boolean getForceAuthn() - { + public boolean getForceAuthn() { return forceAuthn; } - public void setNameID(String value) - { + public void setNameID(String value) { nameID = value; } - public String getNameID() - { + public String getNameID() { return nameID; } - public void setSessionIndex(String value) - { + public void setSessionIndex(String value) { sessionIndex = value; } - public String getSessionIndex() - { + public String getSessionIndex() { return sessionIndex; } - public void setTrustCertPath(String value) - { + public void setTrustCertPath(String value) { trustCertPath = value; } - public String getTrustCertPath() - { + public String getTrustCertPath() { return trustCertPath; } - public void setTrustCertPass(String value) - { + public void setTrustCertPass(String value) { trustCertPass = value; } - public String getTrustCertPass() - { + public String getTrustCertPass() { return trustCertPass; } - public void setTrustCertAlias(String value) - { + public void setTrustCertAlias(String value) { trustCertAlias = value; } - public String getTrustCertAlias() - { + 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 index cf89ebc43..dccab3e70 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java @@ -23,25 +23,22 @@ public static boolean validateSignatures(Document xmlDoc, String certPath, Strin logger.trace("validateSignatures"); X509Certificate cert = Keys.loadCertificate(certPath, certAlias, certPassword); - NodeList nodes = findElementsByPath(xmlDoc,"//*[@ID]"); + NodeList nodes = findElementsByPath(xmlDoc, "//*[@ID]"); NodeList signatures = xmlDoc.getElementsByTagNameNS(Constants.SignatureSpecNS, Constants._TAG_SIGNATURE); - for (int i = 0; i < signatures.getLength(); i++) - { + for (int i = 0; i < signatures.getLength(); i++) { Element signedElement = findNodeById(nodes, getSignatureID((Element) signatures.item(i))); - if(signedElement == null) - { + if (signedElement == null) { return false; } signedElement.setIdAttribute("ID", true); try { - XMLSignature signature = new XMLSignature((Element) signatures.item(i), "") ; - if (!signature.checkSignatureValue(cert)){ + XMLSignature signature = new XMLSignature((Element) signatures.item(i), ""); + if (!signature.checkSignatureValue(cert)) { return false; } - }catch (Exception e) - { + } catch (Exception e) { logger.error("validateSignatures", e); return false; } @@ -50,38 +47,31 @@ public static boolean validateSignatures(Document xmlDoc, String certPath, Strin } - private static String getSignatureID(Element signatureElement) - { + 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) - { + 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) - { + } catch (Exception e) { logger.error("findElementsByPath", e); return null; } } - private static Element findNodeById(NodeList nodes, String id) - { + private static Element findNodeById(NodeList nodes, String id) { logger.trace("findNodeById"); - if(nodes == null) - { + 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))) - { + 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); } } diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java index 98a05d10a..1b41cf9a3 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java @@ -23,8 +23,7 @@ public class Encoding { private static final Logger logger = LogManager.getLogger(Encoding.class); - public static String delfateAndEncodeXmlParameter(String parm) - { + public static String delfateAndEncodeXmlParameter(String parm) { logger.trace("delfateAndEncodeXmlParameter"); try { @@ -34,20 +33,18 @@ public static String delfateAndEncodeXmlParameter(String parm) deflaterStream.write(parm.getBytes(StandardCharsets.UTF_8)); deflaterStream.finish(); - String base64 = Base64.toBase64String(bytesOut.toByteArray()); + String base64 = Base64.toBase64String(bytesOut.toByteArray()); logger.debug(MessageFormat.format("Base64: {0}", base64)); - return URLEncoder.encode(base64, StandardCharsets.UTF_8.name()); - }catch(Exception e) - { + return URLEncoder.encode(base64, StandardCharsets.UTF_8.name()); + } catch (Exception e) { logger.error("delfateAndEncodeXmlParameter", e); return ""; } } - public static String decodeAndInflateXmlParameter(String parm) - { + public static String decodeAndInflateXmlParameter(String parm) { logger.trace("decodeAndInflateXmlParameter"); - try{ + try { String base64 = URLDecoder.decode(parm, StandardCharsets.UTF_8.name()); byte[] bytes = Base64.decode(base64); byte[] uncompressedData = new byte[4096]; @@ -56,27 +53,22 @@ public static String decodeAndInflateXmlParameter(String parm) int len = inflater.inflate(uncompressedData); inflater.end(); return new String(uncompressedData, 0, len, StandardCharsets.UTF_8); - }catch (Exception e) - { + } catch (Exception e) { logger.error("decodeAndInflateXmlParameter", e); return ""; } } - public static String documentToString(Document doc) - { + public static String documentToString(Document doc) { logger.trace("documentToString"); - try(StringWriter writer = new StringWriter()) - { + 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) - { + } catch (Exception e) { logger.error("documentToString", 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 index 71a82e059..413062f34 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/Hash.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/Hash.java @@ -15,11 +15,9 @@ public enum Hash { private static final Logger logger = LogManager.getLogger(Hash.class); - public static Hash getHash(String hash) - { + public static Hash getHash(String hash) { logger.trace("GetHash"); - switch(hash.toUpperCase().trim()) - { + switch (hash.toUpperCase().trim()) { case "SHA1": return SHA1; case "SHA256": @@ -32,10 +30,8 @@ public static Hash getHash(String hash) } } - public static String valueOf(Hash hash) - { - switch(hash) - { + public static String valueOf(Hash hash) { + switch (hash) { case SHA1: return "SHA1"; case SHA256: @@ -47,11 +43,9 @@ public static String valueOf(Hash hash) } } - public static String getSigAlg(Hash hash) - { + public static String getSigAlg(Hash hash) { logger.trace("GetSigAlg"); - switch(hash) - { + switch (hash) { case SHA1: return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha1"; case SHA256: @@ -64,11 +58,9 @@ public static String getSigAlg(Hash hash) } } - public static Digest getDigest(Hash hash) - { + public static Digest getDigest(Hash hash) { logger.trace("getDigest"); - switch (hash) - { + switch (hash) { case SHA1: return new SHA1Digest(); case SHA256: diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java index 9f1cfde64..83f926f81 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java @@ -25,27 +25,23 @@ 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 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 X509Certificate loadCertificate(String path, String alias, String password) { + return isBase64(path) ? loadCertificateFromBase64(path) : loadCertificateFromJKS(path, alias, password); } - private static String getCertPath(String path) - { + private static String getCertPath(String path) { //boolean isAbsolute = new File(path).isAbsolute(); - logger.debug("cuurent dir: " +new File(".").toPath().toAbsolutePath()); + logger.debug("cuurent dir: " + new File(".").toPath().toAbsolutePath()); return System.getProperty("user.dir"); } - private static AsymmetricKeyParameter privateKeyFromBase64(String b64) - { + private static AsymmetricKeyParameter privateKeyFromBase64(String b64) { logger.trace("privateKeyFromBase64"); try { byte[] keyBytes = Base64.decode(b64); @@ -59,8 +55,7 @@ private static AsymmetricKeyParameter privateKeyFromBase64(String b64) } } - private static X509Certificate loadCertificateFromBase64(String b64) - { + private static X509Certificate loadCertificateFromBase64(String b64) { logger.trace("loadCertificateFromBase64"); try { byte[] dataBuffer = Base64.decode(b64); @@ -73,8 +68,7 @@ private static X509Certificate loadCertificateFromBase64(String b64) } } - private static AsymmetricKeyParameter loadPrivateKeyFromJKS(String path, String alias, String password) - { + 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); @@ -86,7 +80,7 @@ private static AsymmetricKeyParameter loadPrivateKeyFromJKS(String path, String } if (ks.getKey(alias, password.toCharArray()) != null) { PrivateKey privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray()); - PrivateKeyInfo keyinfo = PrivateKeyInfo.getInstance(privateKey.getEncoded()); + PrivateKeyInfo keyinfo = PrivateKeyInfo.getInstance(privateKey.getEncoded()); return PrivateKeyFactory.createKey(keyinfo); } } catch (Exception e) { @@ -102,8 +96,8 @@ private static X509Certificate loadCertificateFromJKS(String path, String 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()); + 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()); @@ -119,18 +113,15 @@ private static X509Certificate loadCertificateFromJKS(String path, String alias, } - public static boolean isBase64(String path) - { - try - { + public static boolean isBase64(String path) { + try { Base64.decode(path); return true; - } - catch(Exception e) - { + } 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"); diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/PolicyFormat.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/PolicyFormat.java index 10fea0022..ba450f113 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/PolicyFormat.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/PolicyFormat.java @@ -10,11 +10,9 @@ public enum PolicyFormat { private static final Logger logger = LogManager.getLogger(PolicyFormat.class); - public static PolicyFormat getPolicyFormat(String format) - { + public static PolicyFormat getPolicyFormat(String format) { logger.trace("GetPolicyFormat"); - switch(format.toUpperCase().trim()) - { + switch (format.toUpperCase().trim()) { case "UNSPECIFIED": return UNSPECIFIED; case "EMAIL": @@ -37,11 +35,9 @@ public static PolicyFormat getPolicyFormat(String format) } } - public static String valueOf(PolicyFormat format) - { + public static String valueOf(PolicyFormat format) { logger.trace("ValueOf"); - switch (format) - { + switch (format) { case UNSPECIFIED: return "UNSPECIFIED"; case EMAIL: @@ -64,11 +60,9 @@ public static String valueOf(PolicyFormat format) } } - public static String getPolicyFormatXmlValue(PolicyFormat format) - { + public static String getPolicyFormatXmlValue(PolicyFormat format) { logger.trace("GetPolicyFormatXmlValue"); - switch (format) - { + switch (format) { case UNSPECIFIED: return "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; case EMAIL: diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java index 08b4d083c..7aeba9c82 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java @@ -27,8 +27,7 @@ public class SamlAssertionUtils { 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) - { + public static Document loadDocument(String xml) { logger.trace("loadDocument"); try { ByteArrayInputStream inputStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); @@ -44,15 +43,13 @@ public static Document loadDocument(String xml) DocumentBuilder db = dbf.newDocumentBuilder(); return db.parse(inputStream); - }catch (Exception e) - { + } catch (Exception e) { logger.error("loadDocument", e); return null; } } - public static Document createLogoutRequest(String id, String issuer, String nameID, String sessionIndex, String destination) - { + 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); @@ -69,12 +66,12 @@ public static Document createLogoutRequest(String id, String issuer, String name Document doc = builder.newDocument(); org.w3c.dom.Element request = doc.createElementNS(saml2p, "saml2p:LogoutRequest"); - request.setAttribute("ID",id); + 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"); + 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); @@ -94,8 +91,7 @@ public static Document createLogoutRequest(String id, String issuer, String name return doc; } - public static Document createLoginRequest(String id, String destination, String acsUrl, String issuer, String policyFormat, String authContext, String spname, boolean forceAuthn) - { + 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); @@ -113,7 +109,7 @@ public static Document createLoginRequest(String id, String destination, String Document doc = builder.newDocument(); org.w3c.dom.Element request = doc.createElementNS(samlp, "saml2p:AuthnRequest"); - request.setAttribute("ID",id); + 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"); @@ -121,18 +117,18 @@ public static Document createLoginRequest(String id, String destination, String request.setAttribute("AssertionConsumerServiceURL", acsUrl); request.setAttribute("ForceAuthn", Boolean.toString(forceAuthn)); - org.w3c.dom.Element issuerElem = doc.createElementNS(saml,"saml2:Issuer"); + 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"); + org.w3c.dom.Element policy = doc.createElementNS(samlp, "saml2p:NameIDPolicy"); assert pf != null; policy.setAttribute("Format", PolicyFormat.getPolicyFormatXmlValue(pf)); policy.setAttribute("AllowCreate", "true"); policy.setAttribute("SPNameQualifier", spname); request.appendChild(policy); - org.w3c.dom.Element authContextElem = doc.createElementNS(samlp,"saml2p:RequestedAuthnContext"); + org.w3c.dom.Element authContextElem = doc.createElementNS(samlp, "saml2p:RequestedAuthnContext"); authContextElem.setAttribute("Comparison", "exact"); org.w3c.dom.Element authnContextClass = doc.createElementNS(saml, "saml2:AuthnContextClassRef"); @@ -147,21 +143,19 @@ public static Document createLoginRequest(String id, String destination, String return doc; } - private static DocumentBuilder createDocumentBuilder() { + private static DocumentBuilder createDocumentBuilder() { logger.trace("createDocumentBuilder"); try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); return factory.newDocumentBuilder(); - }catch (Exception e) - { + } catch (Exception e) { logger.error("createDocumentBuilder", e); return null; } } - public static Document canonicalizeXml(String xml) - { + public static Document canonicalizeXml(String xml) { //delete comments from the xml - security meassure logger.trace("canoncalizeXml"); logger.debug(MessageFormat.format("xmlString: {0}", xml)); @@ -179,15 +173,13 @@ public static Document canonicalizeXml(String xml) return loadDocument(canonicalizedXML); - }catch (Exception e) - { + } catch (Exception e) { logger.error("canoncalizeXml", e); return null; } } - public static String getLoginInfo(Document xmlDoc) - { + 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")); @@ -205,8 +197,7 @@ public static String getLoginInfo(Document xmlDoc) return printJson(xmlDoc, atributeList, elementList); } - public static String getLogoutInfo(Document doc) - { + public static String getLogoutInfo(Document doc) { logger.trace("getLogoutInfo"); List atributeList = new ArrayList(); atributeList.add(new Attribute(_saml_protocolNS, "LogoutResponse", "Destination")); @@ -219,13 +210,11 @@ public static String getLogoutInfo(Document doc) return printJson(doc, atributeList, elementList); } - public static String getLoginAttribute(Document doc, String name) - { + public static String getLoginAttribute(Document doc, String name) { logger.trace("getLoginAttribute"); NodeList nodes = getAtttributeElements(doc); - for(int i= 0; i < nodes.getLength(); i++) - { + for (int i = 0; i < nodes.getLength(); i++) { if (nodes.item(i).getAttributes().getNamedItem("Name").getNodeValue().equals(name)) { String value = getAttributeContent(nodes.item(i)); logger.debug(MessageFormat.format("getLoginAttribute -- attribute name: {0}, value: {1}", name, value)); @@ -236,19 +225,15 @@ public static String getLoginAttribute(Document doc, String name) return ""; } - public static String getRoles(Document doc, String name) - { + 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++) - { + 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 atributes, List elements) - { + private static String printJson(Document xmlDoc, List atributes, List elements) { logger.trace("PrintJson"); StringBuilder json = new StringBuilder("{"); - for (Attribute at : atributes) - { + for (Attribute at : atributes) { String value = at.printJson(xmlDoc); - if (value != null) - { + if (value != null) { json.append(MessageFormat.format("{0},", value)); } } int counter = 0; - for (Element el : elements) - { + for (Element el : elements) { String value = el.printJson(xmlDoc); - if (value != null) - { - if (counter != elements.size() - 1) - { + if (value != null) { + if (counter != elements.size() - 1) { json.append(MessageFormat.format("{0},", value)); - } - else - { + } else { json.append(MessageFormat.format("{0} }", value)); } } 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 index 2de7206fa..4337910ab 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Element.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Element.java @@ -4,40 +4,34 @@ import java.text.MessageFormat; -public class Element extends XmlTypes{ +public class Element extends XmlTypes { private final String namespace; private final String tag; - public Element(String namespace, String tag) - { + public Element(String namespace, String tag) { this.namespace = namespace; this.tag = tag; } - public String getNamespace() - { - return namespace; + public String getNamespace() { + return namespace; } - public String getTag() - { + public String getTag() { return tag; } - public org.w3c.dom.Node getElement(Document doc) - { + public org.w3c.dom.Node getElement(Document doc) { return doc.getElementsByTagNameNS(namespace, tag).item(0); } - public String findValue(Document doc) - { + public String findValue(Document doc) { return getElement(doc).getTextContent(); } - public String printJson(Document xmlDoc) - { + public String printJson(Document xmlDoc) { String value = findValue(xmlDoc); - return value == null ? null : MessageFormat.format( "\"{0}\": \"{1}\"", tag, value) ; + 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 index 6107bdb2a..03eb2170e 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/XmlTypes.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/XmlTypes.java @@ -4,5 +4,5 @@ public abstract class XmlTypes { - abstract String printJson(Document doc); + abstract String printJson(Document doc); } From 24197d4831323a6617e5bb925aa23d77424b958a Mon Sep 17 00:00:00 2001 From: sgrampone Date: Thu, 13 Mar 2025 19:11:43 -0300 Subject: [PATCH 05/18] Fix Attribute class code quality --- .../com/genexus/saml20/utils/xml/Attribute.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) 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 index fc266d675..132898ed1 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Attribute.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/xml/Attribute.java @@ -4,30 +4,26 @@ import java.text.MessageFormat; -public class Attribute extends XmlTypes{ +public class Attribute extends XmlTypes { Element element; private final String tag; - public Attribute(String namespace, String element, String tag) - { + public Attribute(String namespace, String element, String tag) { this.element = new Element(namespace, element); this.tag = tag; } - public String getTag() - { + public String getTag() { return tag; } - public String findValue(Document doc) - { + public String findValue(Document doc) { return element.getElement(doc).getAttributes().getNamedItem(tag).getNodeValue(); } - public String printJson(Document xmlDoc) - { + public String printJson(Document xmlDoc) { String value = findValue(xmlDoc); - return value == null ? null : MessageFormat.format( "\"{0}\": \"{1}\"", tag, value); + return value == null ? null : MessageFormat.format("\"{0}\": \"{1}\"", tag, value); } } \ No newline at end of file From 762b0b90c38dea2b80b76ba87a5caa738261e1ff Mon Sep 17 00:00:00 2001 From: sgrampone Date: Wed, 19 Mar 2025 17:55:51 -0300 Subject: [PATCH 06/18] Change parameter's names for GAM compatibility --- .../com/genexus/saml20/RedirectBinding.java | 8 ++-- .../java/com/genexus/saml20/SamlParms.java | 46 +++++++++++-------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java index 95059c930..00e568400 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java @@ -45,13 +45,13 @@ public void init(String queryString) { public static String login(SamlParms parms, String relayState) { - Document request = SamlAssertionUtils.createLoginRequest(parms.getId(), parms.getDestination(), parms.getAcs(), parms.getIssuer(), parms.getPolicyFormat(), parms.getAuthnContext(), parms.getSPName(), parms.getForceAuthn()); - return generateQuery(request, parms.getDestination(), parms.getCertPath(), parms.getCertPass(), parms.getCertAlias(), 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.getIssuer(), parms.getNameID(), parms.getSessionIndex(), parms.getDestination()); - return generateQuery(request, parms.getDestination(), parms.getCertPath(), parms.getCertPass(), parms.getCertAlias(), 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) { diff --git a/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java b/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java index 0ad69657e..589de28b1 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/SamlParms.java @@ -4,15 +4,16 @@ public class SamlParms { private String id; - private String destination; + private String endPointLocation; //IdP Login URL + private String singleLogoutEndpoint; //IdP Logout URL private String acs; - private String issuer; + private String identityProviderEntityID; //issuer private String certPath; private String certPass; private String certAlias; private String policyFormat; private String authnContext; - private String spName; + private String serviceProviderEntityID; //spName private boolean forceAuthn; private String nameID; private String sessionIndex; @@ -23,15 +24,16 @@ public class SamlParms { public SamlParms() { id = ""; - destination = ""; + endPointLocation = ""; + singleLogoutEndpoint = ""; acs = ""; - issuer = ""; + identityProviderEntityID = ""; certPath = ""; certPass = ""; certAlias = ""; policyFormat = ""; authnContext = ""; - spName = ""; + serviceProviderEntityID = ""; forceAuthn = false; nameID = ""; sessionIndex = ""; @@ -48,12 +50,20 @@ public String getId() { return id; } - public void setDestination(String value) { - destination = value; + public void setEndPointLocation(String value) { + endPointLocation = value; } - public String getDestination() { - return destination; + public String getEndPointLocation() { + return endPointLocation; + } + + public void setSingleLogoutEndpoint(String value) { + singleLogoutEndpoint = value; + } + + public String getSingleLogoutEndpoint() { + return singleLogoutEndpoint; } public void setAcs(String value) { @@ -64,12 +74,12 @@ public String getAcs() { return acs; } - public void setIssuer(String value) { - issuer = value; + public void setIdentityProviderEntityID(String value) { + identityProviderEntityID = value; } - public String getIssuer() { - return issuer; + public String getIdentityProviderEntityID() { + return identityProviderEntityID; } public void setCertPath(String value) { @@ -112,12 +122,12 @@ public String getAuthnContext() { return authnContext; } - public void setSPName(String value) { - spName = value; + public void setServiceProviderEntityID(String value) { + serviceProviderEntityID = value; } - public String getSPName() { - return spName; + public String getServiceProviderEntityID() { + return serviceProviderEntityID; } public void setForceAuthn(boolean value) { From 53dfed1deb934ce7b103b6332162a72291ea8f33 Mon Sep 17 00:00:00 2001 From: sgrampone Date: Thu, 20 Mar 2025 18:32:23 -0300 Subject: [PATCH 07/18] Fix signature verification on RedirectBinding, add security measures for DSig signature validation --- .../com/genexus/saml20/RedirectBinding.java | 32 +++++++++++++++++++ .../java/com/genexus/saml20/utils/DSig.java | 27 +++++++++++++++- .../com/genexus/saml20/utils/Encoding.java | 12 +++++++ .../java/com/genexus/saml20/utils/Hash.java | 15 +++++++++ .../java/com/genexus/saml20/utils/Keys.java | 15 +++++++++ 5 files changed, 100 insertions(+), 1 deletion(-) diff --git a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java index 00e568400..f963fa019 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java @@ -6,6 +6,7 @@ import org.bouncycastle.crypto.Signer; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.signers.RSADigestSigner; +import org.bouncycastle.jcajce.provider.asymmetric.RSA; import org.bouncycastle.util.encoders.Base64; import org.w3c.dom.Document; @@ -14,6 +15,7 @@ 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; @@ -100,6 +102,36 @@ public String getLoginAttribute(String name) { // 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, certPass, certAlias); + + 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<>(); diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java index dccab3e70..e3aa3a623 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java @@ -14,6 +14,9 @@ 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 { @@ -26,7 +29,10 @@ public static boolean validateSignatures(Document xmlDoc, String certPath, Strin 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) { @@ -35,6 +41,10 @@ public static boolean validateSignatures(Document xmlDoc, String certPath, Strin 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; } @@ -46,6 +56,21 @@ public static boolean validateSignatures(Document xmlDoc, String certPath, Strin 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(); diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java index 1b41cf9a3..4b9bfa523 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java @@ -73,4 +73,16 @@ public static String documentToString(Document doc) { 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 index 413062f34..7b18ba104 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/Hash.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/Hash.java @@ -72,4 +72,19 @@ public static Digest getDigest(Hash 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 index 83f926f81..9547bae3f 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java @@ -5,8 +5,10 @@ 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; @@ -18,6 +20,7 @@ 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; @@ -33,6 +36,18 @@ public static X509Certificate loadCertificate(String path, String alias, String 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(); From 968d0ef73d8b8c5d37138d963e7b6d0df58a70c6 Mon Sep 17 00:00:00 2001 From: sgrampone Date: Thu, 20 Mar 2025 19:52:44 -0300 Subject: [PATCH 08/18] Fix format issues and change signature verification on RedirectBinding --- .../main/java/com/genexus/saml20/RedirectBinding.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java index f963fa019..cb004a0ef 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java @@ -6,7 +6,6 @@ import org.bouncycastle.crypto.Signer; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.signers.RSADigestSigner; -import org.bouncycastle.jcajce.provider.asymmetric.RSA; import org.bouncycastle.util.encoders.Base64; import org.w3c.dom.Document; @@ -60,7 +59,7 @@ public boolean verifySignatures(SamlParms parms) { logger.debug("verifySignatures"); try { - return DSig.validateSignatures(this.xmlDoc, parms.getTrustCertPath(), parms.getTrustCertAlias(), parms.getTrustCertPass()); + return verifySignature_internal(parms.getCertPath(), parms.getCertPass(), parms.getCertAlias()); } catch (Exception e) { logger.error("verifySignature", e); return false; @@ -102,8 +101,8 @@ public String getLoginAttribute(String name) { // EXTERNAL OBJECT PUBLIC METHODS - END - private boolean VerifySignature_internal(String certPath, String certPass, String certAlias) { - logger.trace("VerifySignature_internal"); + private boolean verifySignature_internal(String certPath, String certPass, String certAlias) { + logger.trace("verifySignature_internal"); byte[] signature = Encoding.decodeParameter(this.redirectMessage.get("Signature")); @@ -127,7 +126,7 @@ private boolean VerifySignature_internal(String certPath, String certPass, Strin setUpSigner(signer, inputStream, Keys.getAsymmetricKeyParameter(cert), false); return signer.verifySignature(signature); } catch (Exception e) { - logger.error("VerifySignature_internal", e); + logger.error("verifySignature_internal", e); return false; } } From b216d7e5a1417ca906d8d894f99a40eae07148ba Mon Sep 17 00:00:00 2001 From: sgrampone Date: Fri, 21 Mar 2025 12:40:05 -0300 Subject: [PATCH 09/18] Change certificate parameters --- .../src/main/java/com/genexus/saml20/RedirectBinding.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java index cb004a0ef..aed6332a1 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java @@ -59,7 +59,7 @@ public boolean verifySignatures(SamlParms parms) { logger.debug("verifySignatures"); try { - return verifySignature_internal(parms.getCertPath(), parms.getCertPass(), parms.getCertAlias()); + return verifySignature_internal(parms.getTrustCertPath(), parms.getTrustCertPass(), parms.getTrustCertAlias()); } catch (Exception e) { logger.error("verifySignature", e); return false; @@ -118,7 +118,7 @@ private boolean verifySignature_internal(String certPath, String certPass, Strin byte[] query = signedMessage.getBytes(StandardCharsets.UTF_8); - X509Certificate cert = Keys.loadCertificate(certPath, certPass, certAlias); + 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()); From 970ae63e2ec84162082f52290c3f58929bddc793 Mon Sep 17 00:00:00 2001 From: sgrampone Date: Fri, 21 Mar 2025 14:40:40 -0300 Subject: [PATCH 10/18] Fix typing mistake on artifact id --- gamsaml20/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamsaml20/pom.xml b/gamsaml20/pom.xml index 3b8250cc4..bda24aca0 100644 --- a/gamsaml20/pom.xml +++ b/gamsaml20/pom.xml @@ -9,7 +9,7 @@ ${revision}${changelist} - gamusaml20 + gamsaml20 GAM Saml 2.0 EO From c8ca34509dd725255f561404f9b163ac97406ad0 Mon Sep 17 00:00:00 2001 From: sgrampone Date: Thu, 10 Apr 2025 19:19:23 -0300 Subject: [PATCH 11/18] Add isLogout function --- gamsaml20/src/main/java/com/genexus/saml20/Binding.java | 4 ++++ gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java | 4 ++++ .../src/main/java/com/genexus/saml20/RedirectBinding.java | 4 ++++ .../java/com/genexus/saml20/utils/SamlAssertionUtils.java | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/gamsaml20/src/main/java/com/genexus/saml20/Binding.java b/gamsaml20/src/main/java/com/genexus/saml20/Binding.java index dd5a98959..b6b4b4402 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/Binding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/Binding.java @@ -1,5 +1,7 @@ package com.genexus.saml20; +import com.genexus.saml20.utils.SamlAssertionUtils; + @SuppressWarnings("unused") public abstract class Binding { @@ -22,4 +24,6 @@ static String logout(SamlParms parms, String relayState) { 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 index 55382b5d6..d1290ec92 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java @@ -65,5 +65,9 @@ public String getRoles(String name) { 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 index aed6332a1..53c6ee927 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java @@ -99,6 +99,10 @@ public String getLoginAttribute(String name) { return ""; } + public boolean isLogout(){ + return SamlAssertionUtils.isLogout(this.xmlDoc); + } + // EXTERNAL OBJECT PUBLIC METHODS - END private boolean verifySignature_internal(String certPath, String certPass, String certAlias) { diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java index 7aeba9c82..ec1201c48 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java @@ -49,6 +49,10 @@ public static Document loadDocument(String xml) { } } + public static boolean isLogout(Document xmlDoc){ + return xmlDoc.getDocumentElement().getLocalName().equals("LogoutResponse"); + } + public static Document createLogoutRequest(String id, String issuer, String nameID, String sessionIndex, String destination) { logger.trace("createLogoutRequest"); From 15b1e7d99cded8b0dd243fc6d2f6e69961d6c3b2 Mon Sep 17 00:00:00 2001 From: sgrampone Date: Fri, 11 Apr 2025 16:06:12 -0300 Subject: [PATCH 12/18] Fix conflict with SecurityAPI modules in master pom --- pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5d47fad92..fb0e0cdb5 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,13 @@ gxcloudstorage-tests gxobservability gxcloudstorage-awss3-v2 - gamsaml20 + securityapicommons + gxjwt + gxcryptography + gxxmlsignature + gxsftp + gxftps + gamsaml20 From 5802b39151a1d6e67fe185b52d01796bd04c52ee Mon Sep 17 00:00:00 2001 From: sgrampone Date: Fri, 11 Apr 2025 16:09:59 -0300 Subject: [PATCH 13/18] Fix conflict with ordering --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fb0e0cdb5..bc5a915b9 100644 --- a/pom.xml +++ b/pom.xml @@ -111,13 +111,13 @@ gxcloudstorage-tests gxobservability gxcloudstorage-awss3-v2 + gamsaml20 securityapicommons gxjwt gxcryptography gxxmlsignature gxsftp gxftps - gamsaml20 From 6ebd469fdc803d64677a5b6cc0dce19fe75e3509 Mon Sep 17 00:00:00 2001 From: sgrampone Date: Mon, 28 Apr 2025 15:09:20 -0300 Subject: [PATCH 14/18] Fix issue finding attributes on Agesic message --- .../main/java/com/genexus/saml20/utils/SamlAssertionUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java index ec1201c48..ed26237ba 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java @@ -220,7 +220,7 @@ public static String getLoginAttribute(Document doc, String name) { for (int i = 0; i < nodes.getLength(); i++) { if (nodes.item(i).getAttributes().getNamedItem("Name").getNodeValue().equals(name)) { - String value = getAttributeContent(nodes.item(i)); + 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; } From 04e78296c9c0955ef63ca66558018127450c71ca Mon Sep 17 00:00:00 2001 From: sgrampone Date: Wed, 7 May 2025 15:54:13 -0300 Subject: [PATCH 15/18] Set policy format directly from GAM --- .../genexus/saml20/utils/PolicyFormat.java | 87 ------------------- .../saml20/utils/SamlAssertionUtils.java | 4 +- 2 files changed, 1 insertion(+), 90 deletions(-) delete mode 100644 gamsaml20/src/main/java/com/genexus/saml20/utils/PolicyFormat.java diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/PolicyFormat.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/PolicyFormat.java deleted file mode 100644 index ba450f113..000000000 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/PolicyFormat.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.genexus.saml20.utils; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@SuppressWarnings("LoggingSimilarMessage") -public enum PolicyFormat { - - UNSPECIFIED, EMAIL, ENCRYPTED, TRANSIENT, ENTITY, KERBEROS, WIN_DOMAIN_QUALIFIED, X509_SUBJECT; - - private static final Logger logger = LogManager.getLogger(PolicyFormat.class); - - public static PolicyFormat getPolicyFormat(String format) { - logger.trace("GetPolicyFormat"); - switch (format.toUpperCase().trim()) { - case "UNSPECIFIED": - return UNSPECIFIED; - case "EMAIL": - return EMAIL; - case "ENCRYPTED": - return ENCRYPTED; - case "TRANSIENT": - return TRANSIENT; - case "ENTITY": - return ENTITY; - case "KERBEROS": - return KERBEROS; - case "WIN_DOMAIN_QUALIFIED": - return WIN_DOMAIN_QUALIFIED; - case "X509_SUBJECT": - return X509_SUBJECT; - default: - logger.error("Unknown policy format"); - return null; - } - } - - public static String valueOf(PolicyFormat format) { - logger.trace("ValueOf"); - switch (format) { - case UNSPECIFIED: - return "UNSPECIFIED"; - case EMAIL: - return "EMAIL"; - case ENCRYPTED: - return "ENCRYPTED"; - case TRANSIENT: - return "TRANSIENT"; - case ENTITY: - return "ENTITY"; - case KERBEROS: - return "KERBEROS"; - case WIN_DOMAIN_QUALIFIED: - return "WIN_DOMAIN_QUALIFIED"; - case X509_SUBJECT: - return "X509_SUBJECT"; - default: - logger.error("Unknown policy format"); - return ""; - } - } - - public static String getPolicyFormatXmlValue(PolicyFormat format) { - logger.trace("GetPolicyFormatXmlValue"); - switch (format) { - case UNSPECIFIED: - return "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; - case EMAIL: - return "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"; - case ENCRYPTED: - return "urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted"; - case TRANSIENT: - return "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"; - case ENTITY: - return "urn:oasis:names:tc:SAML:2.0:nameid-format:entity"; - case KERBEROS: - return "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos"; - case WIN_DOMAIN_QUALIFIED: - return "urn:oasis:names:tc:SAML:2.0:nameid-format:windowsDomainQualifiedName"; - case X509_SUBJECT: - return "urn:oasis:names:tc:SAML:2.0:nameid-format:x509Subject"; - default: - logger.error("Unknown policy format"); - return ""; - } - } -} diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java index ed26237ba..fe1737a02 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java @@ -105,7 +105,6 @@ public static Document createLoginRequest(String id, String destination, String String samlp = "urn:oasis:names:tc:SAML:2.0:protocol"; String saml = "urn:oasis:names:tc:SAML:2.0:assertion"; - PolicyFormat pf = PolicyFormat.getPolicyFormat(policyFormat); DocumentBuilder builder = createDocumentBuilder(); @@ -126,8 +125,7 @@ public static Document createLoginRequest(String id, String destination, String request.appendChild(issuerElem); org.w3c.dom.Element policy = doc.createElementNS(samlp, "saml2p:NameIDPolicy"); - assert pf != null; - policy.setAttribute("Format", PolicyFormat.getPolicyFormatXmlValue(pf)); + policy.setAttribute("Format", policyFormat.trim()); policy.setAttribute("AllowCreate", "true"); policy.setAttribute("SPNameQualifier", spname); request.appendChild(policy); From e9ffd32437ffe76917954e991a5111cb8c2a8539 Mon Sep 17 00:00:00 2001 From: sgrampone Date: Mon, 19 May 2025 17:41:12 -0300 Subject: [PATCH 16/18] Manage exception in IsLogout --- .../com/genexus/saml20/utils/SamlAssertionUtils.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java index fe1737a02..b8b91ee5c 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/utils/SamlAssertionUtils.java @@ -50,7 +50,14 @@ public static Document loadDocument(String xml) { } public static boolean isLogout(Document xmlDoc){ - return xmlDoc.getDocumentElement().getLocalName().equals("LogoutResponse"); + 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) { From e8f461c8adc98c3c50fc298d1b76edb20779a432 Mon Sep 17 00:00:00 2001 From: sgrampone Date: Thu, 22 May 2025 20:24:25 -0300 Subject: [PATCH 17/18] Simple fix to read assertion attributes --- gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java b/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java index d1290ec92..885f354bf 100644 --- a/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java +++ b/gamsaml20/src/main/java/com/genexus/saml20/PostBinding.java @@ -57,7 +57,7 @@ public String getLogoutAssertions() { public String getLoginAttribute(String name) { logger.trace("getLoginAttribute"); - return SamlAssertionUtils.getLoginAttribute(this.xmlDoc, name); + return SamlAssertionUtils.getLoginAttribute(this.xmlDoc, name).trim(); } public String getRoles(String name) { From 56a1ef872874b34408bc2232cea2a6fd13e8e26d Mon Sep 17 00:00:00 2001 From: sgrampone Date: Thu, 29 May 2025 15:57:38 -0300 Subject: [PATCH 18/18] Saml20 EO tests --- gamsaml20/pom.xml | 13 + .../com/genexus/test/PostBindingTest.java | 84 ++++++ .../com/genexus/test/RedirectBindingTest.java | 240 ++++++++++++++++++ gamsaml20/src/test/resources/keystore.jks | Bin 0 -> 2377 bytes gamsaml20/src/test/resources/mykeystore.jks | Bin 0 -> 2378 bytes 5 files changed, 337 insertions(+) create mode 100644 gamsaml20/src/test/java/com/genexus/test/PostBindingTest.java create mode 100644 gamsaml20/src/test/java/com/genexus/test/RedirectBindingTest.java create mode 100644 gamsaml20/src/test/resources/keystore.jks create mode 100644 gamsaml20/src/test/resources/mykeystore.jks diff --git a/gamsaml20/pom.xml b/gamsaml20/pom.xml index bda24aca0..f5a9c64f8 100644 --- a/gamsaml20/pom.xml +++ b/gamsaml20/pom.xml @@ -50,7 +50,20 @@ 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/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 0000000000000000000000000000000000000000..7b0d9e9f9f0de6063a2221cff2924cd3a37133e9 GIT binary patch literal 2377 zcmd5-_fymP7R@&W2oMk>y$U=c{R<^&%ye^Is^iNG7I>t02OAJ zMJ;-H6aqm&K@$7`eEg0 zX#POWRLqmi6CKVs&8uX4*|U``cbXLLGn!W$LS)ek93dp5MuvpmTFT=-iBLn|?%w_d z`E2j2=<)UwNu8LGzNQytwfBOv;%+@YrQHJ{Jj(lfgOL;1_OtJ{%_!t~Dt#QU`}Sf} zX%#mr=WU?qW8s4+%->Q=HMPGdI*Y!2$YRoLrdGTLa$KOCyKC$$aJ05Ql77evj7X zRJSTFGeu+fUaWMI*t8+#8f5L9C#h+_Tv=CsWrVkh(Qh6Tqs4vMC%In;sIb`kMS`!T zQ>)j;XkF79S1mYCY+vm^*U4ccyV5r&d(w1X()Jv;#*c2Y-q;Dvdow|ehZ(=9G`@^S zTS_lJ-fN5Uy<*&R>dr^EZx(<%Z&<~%9s_iJc%OkIkv1OCGTcT`Z$10~K9FUN@MyFr ziNp7B#3!;O6@P`o+8H}?!eFAI$MJWN-!Uch2NRz@``#V8!_P-{l-jBJV*7rT&gfd4 zisL?|hG|Y+a1wZW-GosllZ(GJtX;fnXZlsktJdgcwNyD}?U}bn@V?}zpfIg=YqPv3 zFMiZOw&7!K1!YH@`=vsUcLp6gUXYt|Cw#8QC*yXs&(UU5SPGSMeaXYm-cqeW-@&T; z)h@i4l20Rsj(;`}k?MybzS@xoK)ps+?ImwNF)PLsZ=qI>P}Z9aUTb>r0*zacoLnkm z*=d<}?FKQkqnCCd-Ia8Bu|?c#&|U#`;!6tGg){PWuV}V`(J`{m*Z%%zl>IHXTfSCQ z?eXLi-UubdR6)_WaGJxV9=sq+j#s!;s@krE$urCQV72pa6_>?)(sdU*wp(t7RcWUj z_SVRqnW3Gaf(iMm@~>f2>?Yq($5b&!7O0wBMA72{yYI1Svy7$On{sGt(JlL1W2RaU zx;rOc?KPiX4mrr1TheuN>wlwa@0V=s)luKO7m)np%iDH}Onl9ayWZcQXTe1Fn-8{$ zUi#;6&u>kYOg=YXg*Mi>h}j0G$6%Y&PzKV!O4i6vf#?Jn3Xy07Mr>R z>O`RbWcy%c97TsS_&VZKuvP%_8*(jk9wVDvP$jFh`LFmiO??_|wy17>$7qdPHdUQ9 zk02ZyDo>r1->-;Dk9;cFkvJbX`#RB1ouDZ*@{Hm3LhPeG5{&HfjLS!FO>Sax(#RP|o zILYViRUwXQ*j$2n_N$G2WcYcp)}Ug^=IxaHmcz1@FA~M2EUoc+%>_-l zj;xq1J{*}{&UV~uort9faSL>h=@#Sf4mAL;qmJqvQ0+m3O;h$p*r=0f;<&m$N~cSE zlZH_jz~s2gQ(p;{U`KgZ>J+97uVm7LZ&91n7R1Qu3Uu4)vlbg}pN@W0;)cC^A)f0Q zh<#FdWBWayW|YxU;o{mN(J+%F4dvW4ioL-Vbma=`^V4)kx6kG8!mMWYUl(cc-1(z# zEiYPx^wp@B=aQ`v*hkniYJg|v{U{Gz68q~YB5akg;28)6 zj$j@{03<<|AHkpi6pDmI%C!_u?EtPIiUlbbdm(lk1ppif2pE;6!T};#VAgOJG}PKo z1r7521Z$-~#Jp){C`1=v?D$=0j-#Z`yKHk44I-bmjLW77(?tV8wF@BUP2;#ug zDyOkH6`Va2Vwn*4Z}|VaI0=yar^(EZ2u1?9As`80hmrsQQa`|w!SD+E@K6tX9nx2M zYSC5s+R4yO7F@SJw1A~Wdc!QeWl@j)+YxK_sDh0l*G^Z$f%EEfnb&O&(Uj%gcudzL zG;IOT7mzsOUbI+utbHeUxzbHV551zCD;Ce28te+b`O^hU=o$4BN7~meX=n5bFiu7PBW(%_ltN%*RO|2sOA;XLl6(ePh zQ>;?c6J+J;vp>$^7|G<}7qj2n6k(5TZbfx8lm@!7^#-M}X^1ML&I)D7@t&*0zJbbp zAzRLN-iyqxmE;i$)Y!f_Fmn=(2yJZ{FI?DqxW8AZ?)h1FIF&2KnTC}{hAi9$kA-uf z?_XrA_~2k#=s#UICFSXgX-!nm0nQK{V$#S1tN|9n;x1v-O zC3k(wb(Ct^+^=C0Lip-)KHtyze9!s*0pA~<=R6;e=i|K2^E}SuJYVb6>(d|*2)tRq zp9Nqyy9}d?55_?tC=D!cY&aPX?kdJSSvSlf{ zU#tJLnoeY0%}VZR`LUd^rsAnnJermvG{!mqR;I1!;?P-cw?G=N^CSU(>^jomSu0(o zLYnKHa;dwfr3pN>Z!3Zy9J?eGG_{8GG8Z=h_1(8X5m9Xux z`_f{$o~6QAayLc+A}BgW*<$XGo>{$Ez(GU?FkrFpPWbj#+AKcmVwv@&+wYFs<^u57Qy!g!9;AednDWU+$?T*Q)6jiY2I&L z0=(qBF86s3*|+r-t~_CWZ^;RE{%w~j^oPp?Ra=Hg=Khq$Bd*aNFAg;%(Y(S)%3_@j zaWJJLbr5ZI%qg5(vd}GbF@!_@PLrYO2E{wg<$3LD!c{0Rc3)r}R4;15`@&nZ><{ld z$2nbrWm(s`Zxf}VM+986ha?N9HoW)C~LwutyckMj7H+=j(KDG$e|8279!5Jx>3S&I?=S6dy zb1Hkxjm-lur`il+`;$nNK$oBG)VUNmF+64l?a-seRSDe(PTo`ERuR`XtwC*0nAqMs&GeYOvVIubFV1EXH@EKCjly z`RRivEG5}tUB*TuKb}nJzKu!CInfc5;D=_H*yHC+V(Ky@kQM$z@>xSX5B^TF29ehp=P3ZM4@6?|}ErHr{n?S2q3gDnecL_F;@b zAQ9;1MFcQR@I)>I41mE1PyszZ;wr<0jX}Z?+A+ssmXH9j1q#Akxq;n+LBJpq9PZ~6=o20ij74E2e=%FcMT30^ zAygtQ0=o^f^%n&ZM+Ey(y#j*Bfj(GujOyPzAOM-|e^1Q*dPt8z4!|hOBK7xS^z{v~ zcmpE?jGHXd0J8}V{(}F%i!%Y$f114c6G51OBnZO<#K24d0M%s01iBPjkWpyGWqwrb z*&mB%$Tp~oWg>9$X8Q0nWy(hG(}`w_ji2F}rzbP?(eG46Y6HFX3#3M!&b3zuzgalr z9xz+W4ODcyjugZ#A(zLvPsJ=MEVY!2~HR;{Al_U?%37c0ge6|pLDe+5js*2pF zm;J1r3i8ySF(dT%@g=N2Mbyq5L0(z(bpf5K7q8Z?5=Vj|00_uCfw9LZZAL>$LPSPH z+9ILyn^G0Ar?tE-_rb_UFQ(#26h`IOSWQF@Bg6RL0`Xr0sm-iI0AS;vo^J+w({liX z_ZD^(sEoghU0JZ#cQ0H~U~fNGCa* zqRP3d@F&Iuqqwl5H_n)0p3v6Z`OcIHjUv%um3Dqvzl1Yqd|!)V<1dY5-m;o(bcwt) z__c{0&S1W{m^0H9C)Qn12R;0QnOsUSadMxM!{yXxloG%pQnm4En&P{hf4*M4lNf>G zpN;4%LL7E<3^k9NvG{rJGYwzLS{#Afg^{giuGR^w!H&G9`)-}j-YIkK!B@&9P55){ zNxiFsqX=XoNg#)7z~5)Uyh_pM`*^xp?_~FS$VXNBC^&1Q#{E{*XID&lKvxkYBVmVI Qc(*W-8mC!L_sLNEH%KiSasU7T literal 0 HcmV?d00001