How to write a very simple Java proxy (which can be compiled to a native Linux ELF) to fix SAML requests

Jakub Jóźwicki
2 min readOct 14, 2022

--

Because sometimes it’s not possible to change sender and receiver and you must have a man in the middle..

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
public class SAMLGateway implements HttpHandler {

private final static AtomicReference<String> httpRedirectUrlRef = new AtomicReference<>();
private final static AtomicReference<String> acsUrlRef = new AtomicReference<>();

public static void main(String[] args) throws Exception {
String httpRedirectUrl = (args.length > 0 && args[0].startsWith("https://")) ? args[0] :
"https://forgerock.sso:8443/openam/SSOPOST/metaAlias/SSO/idp";
String acsUrl = (args.length > 1 && args[1].startsWith("https://")) ? args[1] : null;
httpRedirectUrlRef.set(httpRedirectUrl);
acsUrlRef.set(acsUrl);
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 50);
System.out.println("SAMLGateway starting on port 8080, SSO="+httpRedirectUrl+", ACS="+acsUrl);
server.createContext("/", new SAMLGateway());
server.setExecutor(null);
server.start();
}
@Override
public void handle(HttpExchange t) throws IOException {
try {
URI uri = t.getRequestURI();
System.out.println(t.getRequestMethod()+" "+t.getRequestURI()+" :::");
if (uri.getQuery()!=null) {
AtomicReference<String> samlRef = new AtomicReference<>();
final String START = "SAMLRequest=";
Pattern.compile("&").splitAsStream(uri.getQuery()).forEach( s -> {
if (s.startsWith(START)) {
samlRef.set(s.substring(START.length()));
}
});
String saml = samlRef.get();
if (saml!=null && saml.length()>0) {
byte[] payload = Base64.getDecoder().decode(saml);
byte[] inflatedData = new byte[10 * payload.length];
Inflater decompressor = new Inflater(true);
decompressor.setInput(payload, 0, payload.length);
int inflatedBytesLength = decompressor.inflate(inflatedData);
decompressor.end();
String samlXml = new String(inflatedData, 0, inflatedBytesLength);
samlXml = samlXml.replace("1.1:nameid-format:unspecified", "1.1:nameid-format:emailAddress");
{
final String DEST_START = "Destination=\"http";
int idxStart = samlXml.indexOf(DEST_START) + 13;
int idxEnd = samlXml.indexOf("\"", idxStart + 4);
samlXml = samlXml.substring(0, idxStart) + httpRedirectUrlRef.get() + samlXml.substring(idxEnd);
}
if (acsUrlRef.get()!=null) {
final String ACS_START = "AssertionConsumerServiceURL=\"http";
int idxStart = samlXml.indexOf(ACS_START) + 29;
int idxEnd = samlXml.indexOf("\"", idxStart + 4);
samlXml = samlXml.substring(0, idxStart) + acsUrlRef.get() + samlXml.substring(idxEnd);
}
Deflater compressor = new Deflater(Deflater.DEFLATED, true);
byte[] buffer = samlXml.getBytes();
compressor.setInput(buffer);
compressor.finish();
int deflatedBytesLength = compressor.deflate(buffer);
compressor.end();
String samlModified = new String(Base64.getEncoder().encode(ByteBuffer.wrap(buffer, 0, deflatedBytesLength)).array());
System.out.println(samlXml);
String queryPlus = t.getRequestURI().toString();
String queryMinus = queryPlus.substring(queryPlus.indexOf("&", queryPlus.indexOf("SAMLRequest=")+12));
String query = "SAMLRequest="+URLEncoder.encode(samlModified, "UTF-8")+queryMinus;
t.getResponseHeaders().set("Location", httpRedirectUrlRef.get()+"?"+query);
t.getResponseHeaders().set("Cache-Control", "no-cache, no-store, must-revalidate");
t.getResponseHeaders().set("Pragma", "no-cache");
t.getResponseHeaders().set("Expires", "0");
t.sendResponseHeaders(302, -1);
return;
}
}
String response = (""+uri.getQuery()).toLowerCase().contains("healthcheck") ? "OK" : "Invalid request";
t.sendResponseHeaders("OK".equals(response) ? 200: 400, response.length());
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
catch (DataFormatException dfe) {
dfe.printStackTrace();
throw new IOException(dfe);
}
catch (Exception e) {
e.printStackTrace();
throw e;
}
}
}

Here is Dockerfile

FROM ghcr.io/graalvm/native-image:22.2.0 AS build
COPY SAMLGateway.class /build/
RUN cd /build && native-image --static --enable-http --no-fallback -J--add-modules -JALL-SYSTEM -cp . SAMLGateway -H:Name=samlgw
RUN curl https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox -o /build/sh && chmod +x /build/sh
ENV SSO_URL="https://forgerock.sso:8443/openam/SSOPOST/sso/idp"
ENV ACS_URL="https://application/samlresponse"
FROM scratch
COPY --from=build /build/samlgw /opt/samlgw
COPY --from=build /build/sh /bin/sh
CMD /opt/samlgw $SSO_URL $ACS_URL

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Jakub Jóźwicki
Jakub Jóźwicki

No responses yet

Write a response