![JAR search and dependency download from the Maven repository](/logo.png)
de.tsl2.nano.h5.WebSecurity Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tsl2.nano.h5 Show documentation
Show all versions of tsl2.nano.h5 Show documentation
TSL2 Framework Html5 Extensions (WebServer, Html5Presentation, RuleCover, BeanConfigurator, LogicTable-Sheet, Expression-Descriptors for Actions, Rules, URLs, Queries)
package de.tsl2.nano.h5;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import de.tsl2.nano.core.ENV;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.secure.Crypt;
import de.tsl2.nano.core.util.DateUtil;
import de.tsl2.nano.core.util.MapUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.h5.NanoHTTPD.Response;
/**
* tries to resolve simple OWASP security aspects.
* asks ENV for properties starting with "app.session."
*
* see: https://cheatsheetseries.owasp.org/IndexTopTen.html
* see: https://infosec.mozilla.org/guidelines/web_security#cross-origin-resource-sharing
*
* @author ts
*/
public class WebSecurity {
private String antiCSRFKey;
private static final String ENV_PREF = "app.session.";
private static final String PREF_ANTICSRF = ENV_PREF + "anticsrf";
public static final String CSRF_TOKEN = "csrftoken";
public static final String DEF_ALG = "AES";
private static final String ETAG = "ETag";
private static final String REQUEST_COOKIE = "cookie";
private static final String SET_COOKIE = "Set-Cookie";
private static final String SEP = "---";
private static final String SESSION_ID = "session-id";
private static final String STANDARD_HEADER =
"""
Referrer-Policy: same-origin
X-XSS-Protection: 1;mode=block
X-Permitted-Cross-Domain-Policies: master-only
X-Frame-Options: sameorigin
Content-Security-Policy: default-src 'self';
X-Content-Type-Options: nosniff;
Strict-Transport-Security: maxage=31536000;
IncludeSubDomains: true;
Content-Security-Policy: script-src 'self' 'unsafe-inline' 'XnXoXnXcXe-${requestId}';
Content-Security-Policy: frame-src 'self';
Content-Security-Policy: frame-ancestors 'self';
Content-Security-Policy: form-action 'self';
Content-Security-Policy: default-src 'self' 'unsafe-inline' filesystem ${service.url} ${websocket.url};
""";
public static boolean useAntiCSRFToken() {
return ENV.get(PREF_ANTICSRF, true);
}
public static boolean useAntiCSRFTokenInContent() {
return useAntiCSRFToken() && ENV.get(PREF_ANTICSRF + ".incontent", true);
}
public static boolean useAntiCSRFTokenInHeader() {
return useAntiCSRFToken() && ENV.get(PREF_ANTICSRF + ".inheader", true);
}
public String createAntiCSRFToken(NanoH5Session session) {
try {
return Crypt.encrypt(session.getKey() + SEP
+ (session.getWorkingObject() != null ? session.getWorkingObject().getId() : "NOTHING") + SEP
+ System.currentTimeMillis(), getAntiCSRFKey(), ENV.get(PREF_ANTICSRF + ".algorithm", DEF_ALG));
} catch (Exception e) {
if (session != null)
session.close();
ManagedException.forward(e);
return null;
}
}
private String getAntiCSRFKey() {
if (antiCSRFKey == null)
antiCSRFKey = Crypt.generatePassword((byte)16);
return antiCSRFKey;
}
private void checkAntCSRFToken(NanoH5Session session, String token) {
if (!useAntiCSRFToken())
return;
if (token == null)
throw new IllegalStateException("request is missing anti-csrf token");
String sessionInfo = Crypt.decrypt(token, getAntiCSRFKey(), ENV.get(PREF_ANTICSRF + ".algorithm", DEF_ALG));
String[] splitInfo = sessionInfo.split("[-]{3}");
boolean attack = false;
if (splitInfo[0].equals(session.getKey())) {
Date now = new Date();
Date tokenAge = new Date(Long.valueOf(splitInfo[2]) + ENV.get(PREF_ANTICSRF + ".maxage.milliseconds", 3600*1000));
if (tokenAge.before(now))
attack = true;
else {
if (ENV.get(PREF_ANTICSRF + ".check.request", true)) {
if (session.getWorkingObject() != null) // session closed directly before
if (!splitInfo[1].equals(String.valueOf(session.getWorkingObject().getId())))
attack = true;
}
}
}
else
attack = true;
if (attack) {
session.close();
throw new IllegalStateException("request outdated or unauthorized! closing session!");
}
}
public void checkSession(NanoH5Session session, String method, Map header, Map params) {
if (session.isNew())
return;
Map sessionValues = getCookieValues(header);
try {
checkSessionID(session, sessionValues);
if (session.getUserAuthorization() != null) {
if (useAntiCSRFTokenInHeader())
checkAntCSRFToken(session, sessionValues.get(CSRF_TOKEN));
if (method.equals("POST") && useAntiCSRFTokenInContent())
checkAntCSRFToken(session, params.get(CSRF_TOKEN));
}
} catch (Exception ex) {
session.close();
ManagedException.forward(ex);
}
}
private Map getCookieValues(Map header) {
String sessionTag = header.get(REQUEST_COOKIE);
String[] hs = sessionTag.split("[;]");
Map sessionValues = new LinkedHashMap<>(hs.length);
Arrays.stream(hs).forEach(e -> MapUtil.add(sessionValues, e.trim().split("\\s*=\\s*")));
return sessionValues;
}
private void checkSessionID(NanoH5Session session, Map sessionValues) {
String sessionId = sessionValues.get(SESSION_ID);
if (sessionId == null)
throw new IllegalStateException("missing " + SESSION_ID);
if (!sessionId.equals(session.getKey()))
throw new IllegalStateException("bad " + SESSION_ID);
}
public Response addSessionHeader(NanoH5Session session, Response response) {
if (session != null) {
addSessionID(session, response);
if (session.getUserAuthorization() != null && useAntiCSRFTokenInHeader()) {
response.addHeader(getSessionTagName(), CSRF_TOKEN + "=" + createAntiCSRFToken(session));
}
}
Properties p = provideProperties(session);
String header = getStandardHeader();
String[] keyValues = header.split("\n");
String k, v, kk = "", vv = "";
for (int i = 0; i < keyValues.length; i++) {
k = StringUtil.substring(keyValues[i], null, ":").trim();
v = (k.equals(kk) ? vv + " ": "") + StringUtil.substring(keyValues[i], ":",null).trim();
v = StringUtil.insertProperties(v, p);
if (k.trim().length() > 0 && v.trim().length() > 0)
response.addHeader(k, v);
kk = k;
vv = v;
}
return response;
}
private Properties provideProperties(NanoH5Session session) {
Properties p = new Properties(System.getProperties());
String serviceUrl = ENV.get("service.url", "");
String websocketUrl = session != null ? StringUtil.substring(serviceUrl.replace("http", "ws"), null, ":", true)
+ ":" + session.getWebsocketPort() : "";
p.put("service.url", serviceUrl);
p.put("websocket.url", websocketUrl);
p.put("requestId", session != null ? session.getRequestId() : "");
return p;
}
private String getStandardHeader() {
String header = ENV.get(ENV_PREF + "httpheader",
STANDARD_HEADER);
return header;
}
public static Object getSessionID(Map header, InetAddress requestor) {
//header keys are case insenstive and are stored by NanoHttpd in lower case!
return header.containsKey("if-none-match") ? header.get("if-none-match")
: header.containsKey(REQUEST_COOKIE) ? StringUtil.substring(header.get(REQUEST_COOKIE), SESSION_ID + "=", ";")
: requestor;
}
protected void addSessionID(NanoH5Session session, Response response) {
String sessionMode = getSessionTagName();
String maxAge = getMaxAge();
if (sessionMode.equals(SET_COOKIE)) {
String secure = ENV.get("app.ssl.activate", false) ? "secure; " : ";";
response.addHeader(SET_COOKIE, SESSION_ID + "=" + session.getKey() + ";" + secure + maxAge
+ ENV.get(ENV_PREF + "cookie.parameter", "SameSite=Strict; HttpOnly; Path=/"));
} else if (sessionMode.equals(ETAG)) {
addETag(session.getKey(), response, maxAge);
} else { // client-ip
}
}
private String getMaxAge() {
return "Max-Age=" + (ENV.get(ENV_PREF + "timeout.millis", 30 * DateUtil.T_MINUTE) / 1000) + ";";
}
private String getSessionTagName() {
return ENV.get(ENV_PREF + "tag", SET_COOKIE);
}
public void addETag(String name, Response response, String maxAge) {
response.addHeader(ETAG, "\"" + name + "\"");
// response.addHeader("Vary", "User-Agent");
response.addHeader("Cache-Control",maxAge);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy