Skip to content

Commit bedd806

Browse files
committed
New SAML 2.0 implementation for GAM
1 parent bec6c12 commit bedd806

File tree

15 files changed

+1433
-0
lines changed

15 files changed

+1433
-0
lines changed

gamsaml20/pom.xml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>com.genexus</groupId>
8+
<artifactId>parent</artifactId>
9+
<version>${revision}${changelist}</version>
10+
</parent>
11+
12+
<artifactId>gamusaml20</artifactId>
13+
<name>GAM Saml 2.0 EO</name>
14+
15+
<properties>
16+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
17+
</properties>
18+
<dependencies>
19+
<dependency>
20+
<groupId>org.bouncycastle</groupId>
21+
<artifactId>bcprov-jdk18on</artifactId>
22+
<version>1.78.1</version>
23+
<scope>compile</scope>
24+
</dependency>
25+
26+
<dependency>
27+
<groupId>org.apache.logging.log4j</groupId>
28+
<artifactId>log4j-core</artifactId>
29+
<version>${log4j.version}</version>
30+
<scope>compile</scope>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.apache.santuario</groupId>
34+
<artifactId>xmlsec</artifactId>
35+
<version>3.0.3</version>
36+
</dependency>
37+
<dependency>
38+
<groupId>commons-io</groupId>
39+
<artifactId>commons-io</artifactId>
40+
<version>2.11.0</version>
41+
<scope>compile</scope>
42+
</dependency>
43+
</dependencies>
44+
45+
<build>
46+
<finalName>gamsaml20</finalName>
47+
<plugins>
48+
<plugin>
49+
<groupId>org.apache.maven.plugins</groupId>
50+
<artifactId>maven-compiler-plugin</artifactId>
51+
<version>3.8.0</version>
52+
</plugin>
53+
</plugins>
54+
</build>
55+
56+
</project>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.genexus;
2+
3+
@SuppressWarnings("unused")
4+
public abstract class Binding {
5+
6+
abstract void init(String input);
7+
static String login(SamlParms parms, String relayState) { return ""; }
8+
static String logout(SamlParms parms, String relayState) { return ""; }
9+
abstract boolean verifySignatures(SamlParms parms);
10+
abstract String getLoginAssertions();
11+
abstract String getLoginAttribute(String name);
12+
abstract String getRoles(String name);
13+
abstract String getLogoutAssertions();
14+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.genexus;
2+
3+
import com.genexus.utils.DSig;
4+
import com.genexus.utils.Encoding;
5+
import com.genexus.utils.SamlAssertionUtils;
6+
import org.apache.logging.log4j.LogManager;
7+
import org.apache.logging.log4j.Logger;
8+
import org.w3c.dom.Document;
9+
10+
import java.text.MessageFormat;
11+
12+
@SuppressWarnings("unused")
13+
public class PostBinding extends Binding{
14+
15+
private static final Logger logger = LogManager.getLogger(PostBinding.class);
16+
17+
private Document xmlDoc;
18+
19+
public PostBinding()
20+
{
21+
logger.trace("PostBinding constructor");
22+
xmlDoc = null;
23+
}
24+
// EXTERNAL OBJECT PUBLIC METHODS - BEGIN
25+
26+
27+
public void init(String xml)
28+
{
29+
logger.trace("init");
30+
this.xmlDoc = SamlAssertionUtils.canonicalizeXml(xml);
31+
logger.debug(MessageFormat.format("Init - XML IdP response: {0}", Encoding.documentToString(xmlDoc)));
32+
}
33+
34+
public static String login(SamlParms parms, String relayState)
35+
{
36+
//not implemented yet
37+
logger.error("login - NOT IMPLEMENTED");
38+
return "";
39+
}
40+
41+
public static String logout(SamlParms parms, String relayState)
42+
{
43+
//not implemented yet
44+
logger.error("logout - NOT IMPLEMENTED");
45+
return "";
46+
}
47+
48+
public boolean verifySignatures(SamlParms parms)
49+
{
50+
return DSig.validateSignatures(this.xmlDoc, parms.getTrustCertPath(), parms.getTrustCertAlias(), parms.getTrustCertPass());
51+
}
52+
53+
public String getLoginAssertions()
54+
{
55+
logger.trace("getLoginAssertions");
56+
return SamlAssertionUtils.getLoginInfo(this.xmlDoc);
57+
}
58+
59+
public String getLogoutAssertions()
60+
{
61+
logger.trace("getLogoutAssertions");
62+
return SamlAssertionUtils.getLogoutInfo(this.xmlDoc);
63+
}
64+
65+
public String getLoginAttribute(String name)
66+
{
67+
logger.trace("getLoginAttribute");
68+
return SamlAssertionUtils.getLoginAttribute(this.xmlDoc, name);
69+
}
70+
71+
public String getRoles(String name)
72+
{
73+
logger.debug("getRoles");
74+
return SamlAssertionUtils.getRoles(this.xmlDoc, name);
75+
}
76+
77+
// EXTERNAL OBJECT PUBLIC METHODS - END
78+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package com.genexus;
2+
3+
import com.genexus.utils.*;
4+
import org.apache.logging.log4j.LogManager;
5+
import org.apache.logging.log4j.Logger;
6+
import org.bouncycastle.crypto.Signer;
7+
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
8+
import org.bouncycastle.crypto.signers.RSADigestSigner;
9+
import org.bouncycastle.util.encoders.Base64;
10+
import org.w3c.dom.Document;
11+
12+
import java.io.ByteArrayInputStream;
13+
import java.io.InputStream;
14+
import java.net.URLDecoder;
15+
import java.net.URLEncoder;
16+
import java.nio.charset.StandardCharsets;
17+
import java.text.MessageFormat;
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
21+
@SuppressWarnings("unused")
22+
public class RedirectBinding extends Binding{
23+
24+
private static final Logger logger = LogManager.getLogger(RedirectBinding.class);
25+
26+
private Document xmlDoc;
27+
private Map<String, String> redirectMessage;
28+
29+
// EXTERNAL OBJECT PUBLIC METHODS - BEGIN
30+
31+
32+
public RedirectBinding()
33+
{
34+
logger.trace("RedirectBinding constructor");
35+
}
36+
37+
public void init(String queryString)
38+
{
39+
logger.trace("init");
40+
logger.debug(MessageFormat.format("init - queryString : {0}", queryString));
41+
this.redirectMessage = parseRedirect(queryString);
42+
String xml = Encoding.decodeAndInflateXmlParameter(this.redirectMessage.get("SAMLResponse"));
43+
logger.debug("init - inflated xml: {0}", xml);
44+
this.xmlDoc = SamlAssertionUtils.canonicalizeXml(xml);
45+
logger.debug(MessageFormat.format("init - XML IdP response: {0}", Encoding.documentToString(xmlDoc)));
46+
}
47+
48+
49+
public static String login(SamlParms parms, String relayState)
50+
{
51+
Document request = SamlAssertionUtils.createLoginRequest(parms.getId(), parms.getDestination(), parms.getAcs(), parms.getIssuer(), parms.getPolicyFormat(), parms.getAuthnContext(), parms.getSPName(), parms.getForceAuthn());
52+
return generateQuery(request, parms.getDestination(), parms.getCertPath(), parms.getCertPass(), parms.getCertAlias(), relayState);
53+
}
54+
55+
public static String logout(SamlParms parms, String relayState)
56+
{
57+
Document request = SamlAssertionUtils.createLogoutRequest(parms.getId(), parms.getIssuer(), parms.getNameID(), parms.getSessionIndex(), parms.getDestination());
58+
return generateQuery(request, parms.getDestination(), parms.getCertPath(), parms.getCertPass(), parms.getCertAlias(), relayState);
59+
}
60+
61+
public boolean verifySignatures(SamlParms parms)
62+
{
63+
logger.debug("verifySignatures");
64+
65+
try
66+
{
67+
return DSig.validateSignatures(this.xmlDoc, parms.getTrustCertPath(), parms.getTrustCertAlias(), parms.getTrustCertPass());
68+
}catch(Exception e)
69+
{
70+
logger.error("verifySignature", e);
71+
return false;
72+
}
73+
}
74+
75+
public String getLogoutAssertions()
76+
{
77+
logger.trace("getLogoutAssertions");
78+
return SamlAssertionUtils.getLogoutInfo(this.xmlDoc);
79+
}
80+
81+
public String getRelayState()
82+
{
83+
logger.trace("getRelayState");
84+
try {
85+
return this.redirectMessage.get("RelayState") == null ? "" : URLDecoder.decode(this.redirectMessage.get("RelayState"), StandardCharsets.UTF_8.name());
86+
}catch (Exception e)
87+
{
88+
logger.error("getRelayState", e);
89+
return "";
90+
}
91+
}
92+
93+
public String getLoginAssertions()
94+
{
95+
//Getting user's data by URL parms (GET) is deemed insecure so we are not implementing this method for redirect binding
96+
logger.error("getLoginAssertions - NOT IMPLEMENTED insecure SAML implementation");
97+
return "";
98+
}
99+
100+
public String getRoles(String name)
101+
{
102+
//Getting user's data by URL parms (GET) is deemed insecure so we are not implementing this method for redirect binding
103+
logger.error("getRoles - NOT IMPLEMENTED insecure SAML implementation");
104+
return "";
105+
}
106+
107+
public String getLoginAttribute(String name)
108+
{
109+
//Getting user's data by URL parms (GET) is deemed insecure so we are not implementing this method for redirect binding
110+
logger.error("getLoginAttribute - NOT IMPLEMENTED insecure SAML implementation");
111+
return "";
112+
}
113+
114+
// EXTERNAL OBJECT PUBLIC METHODS - END
115+
116+
private static Map<String, String> parseRedirect(String request)
117+
{
118+
logger.trace("parseRedirect");
119+
Map<String,String> result = new HashMap<>();
120+
String[] redirect = request.split("&");
121+
122+
for(String s : redirect)
123+
{
124+
String[] res = s.split("=");
125+
result.put(res[0], res[1]);
126+
}
127+
return result;
128+
}
129+
130+
private static String generateQuery(Document request, String destination, String certPath, String certPass, String alias, String relayState)
131+
{
132+
logger.trace("generateQuery");
133+
try {
134+
String samlRequestParameter = Encoding.delfateAndEncodeXmlParameter(Encoding.documentToString(request));
135+
String relayStateParameter = URLEncoder.encode(relayState, StandardCharsets.UTF_8.name());
136+
Hash hash = Keys.isBase64(certPath) ? Hash.getHash(certPass.toUpperCase().trim()) : Hash.getHash(Keys.getHash(certPath, alias, certPass));
137+
138+
String sigAlgParameter = URLEncoder.encode(Hash.getSigAlg(hash), StandardCharsets.UTF_8.name());
139+
String query = MessageFormat.format("SAMLRequest={0}&RelayState={1}&SigAlg={2}", samlRequestParameter, relayStateParameter, sigAlgParameter);
140+
String signatureParameter = URLEncoder.encode(signRequest_RedirectBinding(query, certPath, certPass, hash, alias), StandardCharsets.UTF_8.name());
141+
142+
query += MessageFormat.format("&Signature={0}", signatureParameter);
143+
144+
logger.debug(MessageFormat.format("generateQuery - query: {0}", query));
145+
return MessageFormat.format("{0}?{1}", destination, query);
146+
}catch (Exception e)
147+
{
148+
logger.error("generateQuery", e);
149+
return "";
150+
}
151+
152+
}
153+
154+
private static String signRequest_RedirectBinding(String query, String path, String password, Hash hash, String alias)
155+
{
156+
logger.trace("signRequest_RedirectBinding");
157+
RSADigestSigner signer= new RSADigestSigner(Hash.getDigest(hash));
158+
byte[] inputText = query.getBytes(StandardCharsets.UTF_8);
159+
try (InputStream inputStream = new ByteArrayInputStream(inputText)) {
160+
setUpSigner(signer, inputStream, Keys.loadPrivateKey(path, alias, password), true);
161+
byte[] outputBytes = signer.generateSignature();
162+
return Base64.toBase64String(outputBytes);
163+
}catch (Exception e)
164+
{
165+
logger.error("signRequest_RedirectBinding", e);
166+
return "";
167+
}
168+
}
169+
170+
private static void setUpSigner(Signer signer, InputStream input, AsymmetricKeyParameter asymmetricKeyParameter,
171+
boolean toSign) {
172+
logger.trace("setUpSigner");
173+
byte[] buffer = new byte[8192];
174+
int n;
175+
try {
176+
signer.init(toSign, asymmetricKeyParameter);
177+
while ((n = input.read(buffer)) > 0) {
178+
signer.update(buffer, 0, n);
179+
}
180+
} catch (Exception e) {
181+
logger.error("setUpSigner", e);
182+
}
183+
}
184+
}

0 commit comments

Comments
 (0)