1   package com.atlassian.seraph.cookie;
2   
3   /**
4    * Insecure cooking encoder that uses an XOR mask with character offsets to "encode" the username and password
5    */
6   public class InsecureCookieEncoder implements CookieEncoder
7   {
8       // Character used to separate username and password in persistent cookies.
9       // 0x13 == "Device Control 3" non-printing ASCII char. Unlikely to appear in a username
10      private static final char DELIMITER = 0x13;
11  
12      // "Tweakable" parameters for the cookie encoding. NOTE: changing these
13      // and recompiling this class will essentially invalidate old cookies.
14      private final static int ENCODE_XORMASK = 0x5A;
15      private final static char ENCODE_CHAR_OFFSET1 = 'C';
16      private final static char ENCODE_CHAR_OFFSET2 = 'i';
17  
18      public String encodePasswordCookie(final String username, final String password, final String encoding)
19      {
20          final StringBuffer buf = new StringBuffer();
21          if ((username != null) && (password != null))
22          {
23              final char offset1 = ((encoding != null) && (encoding.length() > 1)) ? encoding.charAt(1) : InsecureCookieEncoder.ENCODE_CHAR_OFFSET1;
24              final char offset2 = ((encoding != null) && (encoding.length() > 2)) ? encoding.charAt(2) : InsecureCookieEncoder.ENCODE_CHAR_OFFSET2;
25  
26              final byte[] bytes = (username + InsecureCookieEncoder.DELIMITER + password).getBytes();
27              int b;
28  
29              for (int n = 0; n < bytes.length; n++)
30              {
31                  b = bytes[n] ^ (InsecureCookieEncoder.ENCODE_XORMASK + n);
32                  buf.append((char) (offset1 + (b & 0x0F)));
33                  buf.append((char) (offset2 + ((b >> 4) & 0x0F)));
34              }
35          }
36          return buf.toString();
37      }
38  
39      public String[] decodePasswordCookie(String cookieVal, final String encoding)
40      {
41          // check that the cookie value isn't null or zero-length
42          if ((cookieVal == null) || (cookieVal.length() <= 0))
43          {
44              return null;
45          }
46  
47          final char offset1 = ((encoding != null) && (encoding.length() > 1)) ? encoding.charAt(1) : InsecureCookieEncoder.ENCODE_CHAR_OFFSET1;
48          final char offset2 = ((encoding != null) && (encoding.length() > 2)) ? encoding.charAt(2) : InsecureCookieEncoder.ENCODE_CHAR_OFFSET2;
49  
50          // decode the cookie value
51          final char[] chars = cookieVal.toCharArray();
52          final byte[] bytes = new byte[chars.length / 2];
53          int b;
54          for (int n = 0, m = 0; n < bytes.length; n++)
55          {
56              b = chars[m++] - offset1;
57              b |= (chars[m++] - offset2) << 4;
58              bytes[n] = (byte) (b ^ (InsecureCookieEncoder.ENCODE_XORMASK + n));
59          }
60          cookieVal = new String(bytes);
61          final int pos = cookieVal.indexOf(InsecureCookieEncoder.DELIMITER);
62          final String username = (pos < 0) ? "" : cookieVal.substring(0, pos);
63          final String password = (pos < 0) ? "" : cookieVal.substring(pos + 1);
64  
65          return new String[] { username, password };
66      }
67  
68      /**
69       * Builds a cookie string containing a username and password.
70       * <p>
71       * Note: with open source this is not really secure, but it prevents users from snooping the cookie file of others and by changing the XOR mask
72       * and character offsets, you can easily tweak results.
73       * 
74       * @param username
75       *            The username.
76       * @param password
77       *            The password.
78       * @return String encoding the input parameters, an empty string if one of the arguments equals <code>null</code>.
79       * @deprecated only here to support {@link com.atlassian.seraph.util.CookieUtils#encodePasswordCookie(String, String)}
80       */
81      public String encodePasswordCookie(final String username, final String password)
82      {
83          return encodePasswordCookie(username, password, new String(new char[] { InsecureCookieEncoder.DELIMITER, InsecureCookieEncoder.ENCODE_CHAR_OFFSET1, InsecureCookieEncoder.ENCODE_CHAR_OFFSET2 }));
84      }
85  
86      /**
87       * Decodes a cookie string containing a username and password.
88       * 
89       * @param cookieVal
90       *            The cookie value.
91       * @return String[] containing the username at index 0 and the password at index 1, or <code>{ null, null }</code> if cookieVal equals
92       *         <code>null</code> or the empty string.
93       * @deprecated only here to support {@link com.atlassian.seraph.util.CookieUtils#decodePasswordCookie(String)}
94       */
95      public String[] decodePasswordCookie(final String cookieVal)
96      {
97          return decodePasswordCookie(cookieVal, new String(new char[] { InsecureCookieEncoder.DELIMITER, InsecureCookieEncoder.ENCODE_CHAR_OFFSET1, InsecureCookieEncoder.ENCODE_CHAR_OFFSET2 }));
98      }
99  }