View Javadoc

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