View Javadoc

1   // ========================================================================
2   // Copyright (c) 1998-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.http.security;
15  
16  import java.io.Serializable;
17  import java.security.MessageDigest;
18  
19  import org.eclipse.jetty.util.StringUtil;
20  import org.eclipse.jetty.util.TypeUtil;
21  import org.eclipse.jetty.util.log.Log;
22  
23  /* ------------------------------------------------------------ */
24  /**
25   * Credentials. The Credential class represents an abstract mechanism for
26   * checking authentication credentials. A credential instance either represents
27   * a secret, or some data that could only be derived from knowing the secret.
28   * <p>
29   * Often a Credential is related to a Password via a one way algorithm, so while
30   * a Password itself is a Credential, a UnixCrypt or MD5 digest of a a password
31   * is only a credential that can be checked against the password.
32   * <p>
33   * This class includes an implementation for unix Crypt an MD5 digest.
34   * 
35   * @see Password
36   * 
37   */
38  public abstract class Credential implements Serializable
39  {
40      private static final long serialVersionUID = -7760551052768181572L;
41  
42      /* ------------------------------------------------------------ */
43      /**
44       * Check a credential
45       * 
46       * @param credentials The credential to check against. This may either be
47       *                another Credential object, a Password object or a String
48       *                which is interpreted by this credential.
49       * @return True if the credentials indicated that the shared secret is known
50       *         to both this Credential and the passed credential.
51       */
52      public abstract boolean check(Object credentials);
53  
54      /* ------------------------------------------------------------ */
55      /**
56       * Get a credential from a String. If the credential String starts with a
57       * known Credential type (eg "CRYPT:" or "MD5:" ) then a Credential of that
58       * type is returned. Else the credential is assumed to be a Password.
59       * 
60       * @param credential String representation of the credential
61       * @return A Credential or Password instance.
62       */
63      public static Credential getCredential(String credential)
64      {
65          if (credential.startsWith(Crypt.__TYPE)) return new Crypt(credential);
66          if (credential.startsWith(MD5.__TYPE)) return new MD5(credential);
67  
68          return new Password(credential);
69      }
70  
71      /* ------------------------------------------------------------ */
72      /**
73       * Unix Crypt Credentials
74       */
75      public static class Crypt extends Credential
76      {
77          private static final long serialVersionUID = -2027792997664744210L;
78  
79          public static final String __TYPE = "CRYPT:";
80  
81          private final String _cooked;
82  
83          Crypt(String cooked)
84          {
85              _cooked = cooked.startsWith(Crypt.__TYPE) ? cooked.substring(__TYPE.length()) : cooked;
86          }
87  
88          @Override
89          public boolean check(Object credentials)
90          {
91              if (credentials instanceof char[])
92                  credentials=new String((char[])credentials);
93              if (!(credentials instanceof String) && !(credentials instanceof Password)) 
94                  Log.warn("Can't check " + credentials.getClass() + " against CRYPT");
95  
96              String passwd = credentials.toString();
97              return _cooked.equals(UnixCrypt.crypt(passwd, _cooked));
98          }
99  
100         public static String crypt(String user, String pw)
101         {
102             return "CRYPT:" + UnixCrypt.crypt(pw, user);
103         }
104     }
105 
106     /* ------------------------------------------------------------ */
107     /**
108      * MD5 Credentials
109      */
110     public static class MD5 extends Credential
111     {
112         private static final long serialVersionUID = 5533846540822684240L;
113 
114         public static final String __TYPE = "MD5:";
115 
116         public static final Object __md5Lock = new Object();
117 
118         private static MessageDigest __md;
119 
120         private final byte[] _digest;
121 
122         /* ------------------------------------------------------------ */
123         MD5(String digest)
124         {
125             digest = digest.startsWith(__TYPE) ? digest.substring(__TYPE.length()) : digest;
126             _digest = TypeUtil.parseBytes(digest, 16);
127         }
128 
129         /* ------------------------------------------------------------ */
130         public byte[] getDigest()
131         {
132             return _digest;
133         }
134 
135         /* ------------------------------------------------------------ */
136         @Override
137         public boolean check(Object credentials)
138         {
139             try
140             {
141                 byte[] digest = null;
142 
143                 if (credentials instanceof char[])
144                     credentials=new String((char[])credentials);
145                 if (credentials instanceof Password || credentials instanceof String)
146                 {
147                     synchronized (__md5Lock)
148                     {
149                         if (__md == null) __md = MessageDigest.getInstance("MD5");
150                         __md.reset();
151                         __md.update(credentials.toString().getBytes(StringUtil.__ISO_8859_1));
152                         digest = __md.digest();
153                     }
154                     if (digest == null || digest.length != _digest.length) return false;
155                     for (int i = 0; i < digest.length; i++)
156                         if (digest[i] != _digest[i]) return false;
157                     return true;
158                 }
159                 else if (credentials instanceof MD5)
160                 {
161                     MD5 md5 = (MD5) credentials;
162                     if (_digest.length != md5._digest.length) return false;
163                     for (int i = 0; i < _digest.length; i++)
164                         if (_digest[i] != md5._digest[i]) return false;
165                     return true;
166                 }
167                 else if (credentials instanceof Credential)
168                 {
169                     // Allow credential to attempt check - i.e. this'll work
170                     // for DigestAuthModule$Digest credentials
171                     return ((Credential) credentials).check(this);
172                 }
173                 else
174                 {
175                     Log.warn("Can't check " + credentials.getClass() + " against MD5");
176                     return false;
177                 }
178             }
179             catch (Exception e)
180             {
181                 Log.warn(e);
182                 return false;
183             }
184         }
185 
186         /* ------------------------------------------------------------ */
187         public static String digest(String password)
188         {
189             try
190             {
191                 byte[] digest;
192                 synchronized (__md5Lock)
193                 {
194                     if (__md == null)
195                     {
196                         try
197                         {
198                             __md = MessageDigest.getInstance("MD5");
199                         }
200                         catch (Exception e)
201                         {
202                             Log.warn(e);
203                             return null;
204                         }
205                     }
206 
207                     __md.reset();
208                     __md.update(password.getBytes(StringUtil.__ISO_8859_1));
209                     digest = __md.digest();
210                 }
211 
212                 return __TYPE + TypeUtil.toString(digest, 16);
213             }
214             catch (Exception e)
215             {
216                 Log.warn(e);
217                 return null;
218             }
219         }
220     }
221 }