/** Created by Tim Heinrich on 27.08.2015. */
public final class CookieSessionHandler {
  private static final String session = "session";
  private static final String symmetricEncryptionAlgorithm = "AES";
  private static final FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration();

  private final SecretKey symmetricEncryptionKey;
  private final SecretKey hmacKey;

  static {
    conf.setPreferSpeed(true);
  }

  public CookieSessionHandler(String encryptionKey) throws NoSuchAlgorithmException {

    byte[] key = hash(encryptionKey.getBytes());
    key = Arrays.copyOf(key, 16);
    this.symmetricEncryptionKey = new SecretKeySpec(key, symmetricEncryptionAlgorithm);
    this.hmacKey = new SecretKeySpec(key, "HmacSHA1");
  }

  public CookieSession readSession(HttpServletRequest request)
      throws InvalidKeyException, NoSuchAlgorithmException, BadPaddingException,
          IllegalBlockSizeException, NoSuchPaddingException {
    Cookie sessionCookie = getCookie(request, CookieSessionHandler.session);
    if (sessionCookie == null) {
      return new CookieSession(request);
    }
    CookieSessionContent cookieSessionContent = new CookieSessionContent(sessionCookie.getValue());

    // check the signature
    if (!verify(cookieSessionContent.getContent(), cookieSessionContent.getSignature())) {
      return new CookieSession(request);
    }

    // now decrypt the content
    final Cipher symmetricalCipher = Cipher.getInstance(symmetricEncryptionAlgorithm);
    symmetricalCipher.init(Cipher.DECRYPT_MODE, this.symmetricEncryptionKey);
    byte[] decodedContent = symmetricalCipher.doFinal(cookieSessionContent.getContent());

    // deserialize content
    return (CookieSession) conf.asObject(decodedContent);
  }

  public void writeSession(HttpServletRequest request, HttpServletResponse response)
      throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException,
          BadPaddingException, IllegalBlockSizeException {
    SparkHttpRequestWrapper sparkRequest = (SparkHttpRequestWrapper) request;
    if (!sparkRequest.sessionAccessed()) return;
    CookieSession session = (CookieSession) request.getSession();

    // serialize session
    byte[] sessionBytes = conf.asByteArray(session);

    // encrypt content
    final Cipher symmetricalCipher = Cipher.getInstance(symmetricEncryptionAlgorithm);
    symmetricalCipher.init(Cipher.ENCRYPT_MODE, this.symmetricEncryptionKey);
    byte[] encryptedBytes = symmetricalCipher.doFinal(sessionBytes);

    // sign content
    byte[] signature = sign(encryptedBytes);
    byte[] cookieContent = new byte[encryptedBytes.length + signature.length];

    System.arraycopy(encryptedBytes, 0, cookieContent, 0, encryptedBytes.length);
    System.arraycopy(
        signature, 0, cookieContent, cookieContent.length - signature.length, signature.length);

    String base64CookieContent = Base64.getEncoder().encodeToString(cookieContent);
    addCookie(base64CookieContent, response);
  }

  private byte[] hash(byte[] encryptedBytes) throws NoSuchAlgorithmException {
    MessageDigest instance = MessageDigest.getInstance("SHA-1");
    return instance.digest(encryptedBytes);
  }

  private void addCookie(String cookieContent, HttpServletResponse response) {
    Cookie cookie = new Cookie(CookieSessionHandler.session, cookieContent);
    cookie.setSecure(SparkBase.isSecure());
    cookie.setHttpOnly(true);
    cookie.setMaxAge(-1);
    response.addCookie(cookie);
  }

  private boolean verify(byte[] dataToVerify, byte[] signature)
      throws NoSuchAlgorithmException, InvalidKeyException {
    Mac hmacSHA1 = Mac.getInstance("HmacSHA1");
    hmacSHA1.init(this.hmacKey);
    return Arrays.equals(hmacSHA1.doFinal(dataToVerify), signature);
  }

  private byte[] sign(byte[] dataToSign) throws NoSuchAlgorithmException, InvalidKeyException {
    Mac hmacSHA1 = Mac.getInstance("HmacSHA1");
    hmacSHA1.init(this.hmacKey);
    return hmacSHA1.doFinal(dataToSign);
  }

  private Cookie getCookie(HttpServletRequest request, String cookieName) {
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
      for (Cookie cookie : cookies) {
        if (cookieName.equals(cookie.getName())) {
          return cookie;
        }
      }
    }
    return null;
  }
}
 protected FSTConfiguration getTestConfiguration() {
   FSTConfiguration.isAndroid = false;
   return FSTConfiguration.createDefaultConfiguration();
 }