package servlets.hstsSuperCookie;
import java.io.IOException;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* Servlet implementation class HSTSServlet
*/
public class HstsSuperCookieStartServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public static final int NUM_ID_CHAINS = 8;
public static final int ID_LENGTH = 4;
private Pattern uriPattern;
/**
* @see HttpServlet#HttpServlet()
*/
public HstsSuperCookieStartServlet() {
super();
this.uriPattern = Pattern.compile("/hstsSuperCookie/(start|midpoint)/(\\d+)(:?;jsessionid=.*)?$");
}
/**
* Serves requests for hsts0.browserprint.info/hstsSuperCookie/start
* The very first (and middle) request to start the whole supercookie process.
*
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if(request.getServerName().startsWith("hsts0.") == false){
System.err.println("HstsSuperCookieStartServlet: Unexpected domain. Start page must be subdomain hsts0. Subdomain = <" + request.getServerName() + ">.");
response.sendError(404);
return;
}
Matcher uriMatcher = uriPattern.matcher(request.getRequestURI());
if(uriMatcher.matches() == false){
System.err.println("HstsSuperCookieStartServlet: Invalid URI = <" + request.getRequestURI() + ">");
response.sendError(404);
return;
}
String action = uriMatcher.group(1);
Integer hstsGroup = Integer.parseInt(uriMatcher.group(2));
if(hstsGroup > NUM_ID_CHAINS){
System.err.println("HstsSuperCookieStartServlet: Invalid HSTS group = <" + hstsGroup + ">");
response.sendError(404);
return;
}
Integer hstsGroupStartSubdomainNumber = ((hstsGroup - 1) * ID_LENGTH + 1);
if(request.getServerPort() == 80){
//New client. Generate them a new, unused ID, and encode it in the redirect URL.
response.sendRedirect("https://hsts" + hstsGroupStartSubdomainNumber + "." + getServletContext().getInitParameter("websiteBaseURL")
+ response.encodeRedirectURL("/hstsSuperCookie/newID/" + generateNewUnassignedIdChunk()));
return;
}
else if(request.getServerPort() == 443){
//HTTPS is enabled, that means either there is an existing ID or we just finished setting an ID.
if(action.equals("midpoint")){
//The URI means we just got done setting an ID.
HttpSession session = request.getSession();
synchronized(session){
Integer numIdChainsCompleted = (Integer) session.getAttribute("numIdChainsCompleted");
if(numIdChainsCompleted == null){
numIdChainsCompleted = 0;
}
++numIdChainsCompleted;
if(numIdChainsCompleted >= NUM_ID_CHAINS){
//ID has been saved fully. Enable HSTS so next time the client visits contacts this subdomain it will be using HTTPS.
response.setHeader("Strict-Transport-Security", "max-age=31622400");
}
else{
session.setAttribute("numIdChainsCompleted", numIdChainsCompleted);
}
}
}
/*else{ //action must be "start" since otherwise it would have been caught by the uriMatcher regex.
//The URI means there is an existing ID.
//No need to set HSTS. That's what upgraded us to HTTPS here.
//Just continue.
}*/
//Redirect to extracting ID.
//Redirect to the first in the chain for extracting IDs.
response.sendRedirect("http://hsts" + hstsGroupStartSubdomainNumber + "." + getServletContext().getInitParameter("websiteBaseURL") + "/hstsSuperCookie/existingID/");
return;
}
else{
System.err.println("HstsSuperCookieStartServlet: Unexpected protocol. Port = <" + request.getServerPort() + ">.");
response.sendError(404);
return;
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
/**
* Ideally this would generate an unused ID, but that's not very important in our example.
* @return
*/
private String generateNewUnassignedIdChunk() {
String id = "";
Random rand = new Random();
int idBits = rand.nextInt();
for(int i = 0; i < 4; ++i){
id += (idBits & 1);
idBits >>= 1;
}
return id;
}
}