1   package com.atlassian.seraph.cookie;
2   
3   import com.atlassian.seraph.config.SecurityConfigFactory;
4   import com.atlassian.seraph.util.EncryptionUtils;
5   import org.apache.log4j.Logger;
6   
7   import java.net.URLEncoder;
8   import java.net.URLDecoder;
9   import java.io.UnsupportedEncodingException;
10  
11  /**
12   * Encrypts the username and password using password-based-encryption (MD5 and DES).
13   */
14  public class EncryptedCookieEncoder implements CookieEncoder
15  {
16      private static final Logger LOG = Logger.getLogger(EncryptedCookieEncoder.class);
17  
18      private static final String URL_ENCODING = "UTF-8";
19  
20      private static final String SEPARATOR = "^^^";
21  
22      private final EncryptionUtils encryptionUtils;
23  
24      public EncryptedCookieEncoder()
25      {
26          this(SecurityConfigFactory.getInstance().getCookieEncoding());
27      }
28  
29      protected EncryptedCookieEncoder(final String password)
30      {
31          if ((password == null) || (password.length() == 0))
32          {
33              throw new IllegalArgumentException("The password must be specified");
34          }
35          encryptionUtils = new EncryptionUtils();
36          encryptionUtils.setPassword(password);
37      }
38  
39      public String encodePasswordCookie(final String username, final String password, final String encoding)
40      {
41          final StringBuffer sb = new StringBuffer();
42          sb.append(username).append(EncryptedCookieEncoder.SEPARATOR).append(password);
43          return escapeInvalidCookieCharacters(encryptionUtils.encrypt(sb.toString()));
44      }
45  
46      public String[] decodePasswordCookie(final String cookieVal, final String encoding)
47      {
48          final String[] result = new String[2];
49          try
50          {
51              final String text = encryptionUtils.decrypt(unescapeInvalidCookieCharacters(cookieVal));
52              final int pos = text.indexOf(EncryptedCookieEncoder.SEPARATOR);
53              if (pos < 0)
54              {
55                  EncryptedCookieEncoder.LOG.info("Successfully decrypted password cookie, but decrypted value '" + text + "' is invalid because separator ('" + EncryptedCookieEncoder.SEPARATOR + "') was not found. Returning null.");
56                  return null;
57              }
58              result[0] = text.substring(0, pos);
59              result[1] = text.substring(pos + EncryptedCookieEncoder.SEPARATOR.length());
60          }
61          catch (final RuntimeException ex)
62          {
63              EncryptedCookieEncoder.LOG.info("Password cookie could not be decrypted, trying old insecure method of decoding it");
64              return new InsecureCookieEncoder().decodePasswordCookie(cookieVal, encoding);
65          }
66          return result;
67      }
68  
69      /**
70       * Escape invalid cookie characters, see SER-117
71       * 
72       * @param s
73       *            the String to escape characters for.
74       * @return the encoded string.
75       * @see #unescapeInvalidCookieCharacters(String)
76       */
77      private static String escapeInvalidCookieCharacters(final String s)
78      {
79          try
80          {
81              return URLEncoder.encode(s, EncryptedCookieEncoder.URL_ENCODING);
82          }
83          catch (final UnsupportedEncodingException e)
84          {
85              throw new AssertionError(e);
86          }
87      }
88  
89      /**
90       * Un-escape invalid cookie characters, see SER-117
91       * 
92       * @param s
93       *            the String to escape characters for.
94       * @return the encoded string.
95       * @see #escapeInvalidCookieCharacters(String)
96       */
97      private static String unescapeInvalidCookieCharacters(final String s)
98      {
99          try
100         {
101             return URLDecoder.decode(s, EncryptedCookieEncoder.URL_ENCODING);
102         }
103         catch (final UnsupportedEncodingException e)
104         {
105             EncryptedCookieEncoder.LOG.fatal("UTF-8 encoding unsupported !!?!! How is that possible?", e);
106             throw new RuntimeException(e);
107         }
108     }
109 }