KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > security > srp > jaas > SRPLoginModule


1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */

22 package org.jboss.security.srp.jaas;
23
24 import java.rmi.Naming JavaDoc;
25 import java.security.Principal JavaDoc;
26 import java.util.Hashtable JavaDoc;
27 import java.util.Map JavaDoc;
28 import java.util.Set JavaDoc;
29 import java.util.ArrayList JavaDoc;
30 import java.io.Serializable JavaDoc;
31 import javax.naming.InitialContext JavaDoc;
32 import javax.security.auth.Subject JavaDoc;
33 import javax.security.auth.callback.CallbackHandler JavaDoc;
34 import javax.security.auth.callback.Callback JavaDoc;
35 import javax.security.auth.callback.NameCallback JavaDoc;
36 import javax.security.auth.callback.PasswordCallback JavaDoc;
37 import javax.security.auth.callback.TextInputCallback JavaDoc;
38 import javax.security.auth.callback.UnsupportedCallbackException JavaDoc;
39 import javax.security.auth.login.LoginException JavaDoc;
40 import javax.security.auth.spi.LoginModule JavaDoc;
41
42 import org.jboss.logging.Logger;
43 import org.jboss.security.Util;
44 import org.jboss.security.auth.callback.ByteArrayCallback;
45 import org.jboss.security.srp.SRPClientSession;
46 import org.jboss.security.srp.SRPParameters;
47 import org.jboss.security.srp.SRPServerInterface;
48
49 /** A login module that uses the SRP protocol documented in RFC2945
50  to authenticate a username & password in a secure fashion without
51  using an encrypted channel.
52  
53  The supported configuration options include:
54  <ul>
55  <li>principalClassName: the fully qualified class name of the java.security.Principal
56  implementation to use. The implementation must provide a public constructor that
57  accepts a single String argument representing the name of the principal.
58  If not specified this defaults to org.jboss.security.SimplePrincipal.
59  </li>
60  
61  <li>srpServerJndiName: the JNDI name of the SRPServerInterface object to use
62  for communicating with the SRP authentication server. If both srpServerJndiName
63  and srpServerRmiUrl options are specified, the srpServerJndiName is tried before
64  srpServerRmiUrl.
65  </li>
66  
67  <li>srpServerRmiUrl: the RMI protocol URL string for the location of the
68  SRPServerInterface proxy to use for communicating with the SRP authentication server.
69  </li>
70  </ul>
71  
72  This product uses the 'Secure Remote Password' cryptographic
73  authentication system developed by Tom Wu (tjw@CS.Stanford.EDU).
74  
75  @author Scott.Stark@jboss.org
76  @version $Revision: 40096 $
77  */

78 public class SRPLoginModule implements LoginModule JavaDoc
79 {
80    private Subject JavaDoc subject;
81    private CallbackHandler JavaDoc handler;
82    private Map JavaDoc sharedState;
83    private Hashtable JavaDoc jndiEnv;
84    private String JavaDoc principalClassName;
85    private String JavaDoc srpServerRmiUrl;
86    private String JavaDoc srpServerJndiName;
87    private String JavaDoc username;
88    private char[] password;
89    private SRPServerInterface srpServer;
90    private SRPParameters params;
91    private Principal JavaDoc userPrincipal;
92    private Integer JavaDoc sessionID;
93    private byte[] sessionKey;
94    private byte[] abytes;
95    private Object JavaDoc auxChallenge;
96    private boolean externalRandomA;
97    private boolean hasAuxChallenge;
98    private boolean multipleSessions;
99    private boolean loginFailed;
100    private Logger log;
101    
102    /** Creates new SRPLoginModule */
103    public SRPLoginModule()
104    {
105    }
106    
107    // --- Begin LoginModule interface methods
108
/**
109     @param subject - the subject to authenticate
110     @param handler - the app CallbackHandler used to obtain username & password
111     @param sharedState - used to propagate the authenticated principal and
112     credential hash.
113     @param options - the login module options. These include:
114     principalClassName: the java.security.Principal class name implimentation to use.
115     srpServerJndiName: the jndi name of the SRPServerInterface implimentation to use. This
116     is tried before srpServerRmiUrl.
117     srpServerRmiUrl: the rmi url for the SRPServerInterface implimentation to use.
118     externalRandomA: a true/false flag indicating if the random component of
119       the client public key A should come from the user callback.
120     hasAuxChallenge: A true/false flag indicating an that a string will be sent to the
121     server as an additional challenge for the server to validate. If the client session
122     supports an encryption cipher then a temporary cipher will be created and the challenge
123     object sent as a SealedObject.
124     multipleSessions: a true/false flag indicating if a given client may have multiple
125     SRP login session active simultaneously.
126     */

127    public void initialize(Subject JavaDoc subject, CallbackHandler JavaDoc handler, Map JavaDoc sharedState,
128       Map JavaDoc options)
129    {
130       log = Logger.getLogger(getClass());
131       this.jndiEnv = new Hashtable JavaDoc(options);
132       this.subject = subject;
133       this.handler = handler;
134       this.sharedState = sharedState;
135       principalClassName = (String JavaDoc) options.get("principalClassName");
136       if( principalClassName != null )
137          log.warn("The principalClassName is no longer used, its always SRPPrincipal");
138       srpServerJndiName = (String JavaDoc) options.get("srpServerJndiName");
139       srpServerRmiUrl = (String JavaDoc) options.get("srpServerRmiUrl");
140       String JavaDoc tmp = (String JavaDoc) options.get("externalRandomA");
141       if( tmp != null )
142          externalRandomA = Boolean.valueOf(tmp).booleanValue();
143       multipleSessions = false;
144       tmp = (String JavaDoc) options.get("multipleSessions");
145       if( tmp != null )
146          multipleSessions = Boolean.valueOf(tmp).booleanValue();
147       tmp = (String JavaDoc) options.get("hasAuxChallenge");
148       if( tmp != null )
149          hasAuxChallenge = Boolean.valueOf(tmp).booleanValue();
150
151       /* Remove all standard options and if there are any left, use these as
152          the JNDI InitialContext env
153       */

154       jndiEnv.remove("principalClassName");
155       jndiEnv.remove("srpServerJndiName");
156       jndiEnv.remove("srpServerRmiUrl");
157       jndiEnv.remove("externalRandomA");
158       jndiEnv.remove("multipleSessions");
159       jndiEnv.remove("hasAuxChallenge");
160
161    }
162
163    /** This is where the SRP protocol exchange occurs.
164     @return true is login succeeds, false if login does not apply.
165     @exception LoginException thrown on login failure.
166     */

167    public boolean login() throws LoginException JavaDoc
168    {
169       boolean trace = log.isTraceEnabled();
170       loginFailed = true;
171       getUserInfo();
172       // First try to locate an SRPServerInterface using JNDI
173
if( srpServerJndiName != null )
174       {
175          srpServer = loadServerFromJndi(srpServerJndiName);
176       }
177       else if( srpServerRmiUrl != null )
178       {
179          srpServer = loadServer(srpServerRmiUrl);
180       }
181       else
182       {
183          throw new LoginException JavaDoc("No option specified to access a SRPServerInterface instance");
184       }
185       if( srpServer == null )
186          throw new LoginException JavaDoc("Failed to access a SRPServerInterface instance");
187       
188       byte[] M1, M2;
189       SRPClientSession client = null;
190       try
191       { // Perform the SRP login protocol
192
if( trace )
193             log.trace("Getting SRP parameters for username: "+username);
194          Util.init();
195          Object JavaDoc[] sessionInfo = srpServer.getSRPParameters(username, multipleSessions);
196          params = (SRPParameters) sessionInfo[0];
197          sessionID = (Integer JavaDoc) sessionInfo[1];
198          if( sessionID == null )
199             sessionID = new Integer JavaDoc(0);
200          if( trace )
201          {
202             log.trace("SessionID: "+sessionID);
203             log.trace("N: "+Util.tob64(params.N));
204             log.trace("g: "+Util.tob64(params.g));
205             log.trace("s: "+Util.tob64(params.s));
206             log.trace("cipherAlgorithm: "+params.cipherAlgorithm);
207             log.trace("hashAlgorithm: "+params.hashAlgorithm);
208          }
209          byte[] hn = Util.newDigest().digest(params.N);
210          if( trace )
211             log.trace("H(N): "+Util.tob64(hn));
212          byte[] hg = Util.newDigest().digest(params.g);
213          if( trace )
214          {
215             log.trace("H(g): "+Util.tob64(hg));
216             log.trace("Creating SRPClientSession");
217          }
218
219          if( abytes != null )
220             client = new SRPClientSession(username, password, params, abytes);
221          else
222             client = new SRPClientSession(username, password, params);
223          if( trace )
224             log.trace("Generating client public key");
225
226          byte[] A = client.exponential();
227          if( trace )
228             log.trace("Exchanging public keys");
229          byte[] B = srpServer.init(username, A, sessionID.intValue());
230          if( trace )
231             log.trace("Generating server challenge");
232          M1 = client.response(B);
233
234          if( trace )
235             log.trace("Exchanging challenges");
236          sessionKey = client.getSessionKey();
237          if( auxChallenge != null )
238          {
239             auxChallenge = encryptAuxChallenge(auxChallenge, params.cipherAlgorithm,
240                   params.cipherIV, sessionKey);
241             M2 = srpServer.verify(username, M1, auxChallenge, sessionID.intValue());
242          }
243          else
244          {
245             M2 = srpServer.verify(username, M1, sessionID.intValue());
246          }
247       }
248       catch(Exception JavaDoc e)
249       {
250          log.warn("Failed to complete SRP login", e);
251          throw new LoginException JavaDoc("Failed to complete SRP login, msg="+e.getMessage());
252       }
253
254       if( trace )
255          log.trace("Verifying server response");
256       if( client.verify(M2) == false )
257          throw new LoginException JavaDoc("Failed to validate server reply");
258       if( trace )
259          log.trace("Login succeeded");
260       
261       // Put the principal and the client challenge into the sharedState map
262
userPrincipal = new SRPPrincipal(username, sessionID);
263       sharedState.put("javax.security.auth.login.name", userPrincipal);
264       sharedState.put("javax.security.auth.login.password", M1);
265       loginFailed = false;
266       return true;
267    }
268
269    /** All login modules have completed the login() phase, comit if we
270     succeeded. This entails adding an instance of principalClassName to the
271     subject principals set and the private session key to the PrivateCredentials
272     set.
273     
274     @return false, if the login() failed, true if the commit succeeds.
275     @exception LoginException thrown on failure to create a Principal.
276     */

277    public boolean commit() throws LoginException JavaDoc
278    {
279       if( loginFailed == true )
280          return false;
281       
282       // Associate an instance of Principal with the subject
283
subject.getPrincipals().add(userPrincipal);
284       Set JavaDoc privateCredentials = subject.getPrivateCredentials();
285       privateCredentials.add(sessionKey);
286       if( sessionID != null )
287          privateCredentials.add(sessionID);
288       if( params.cipherAlgorithm != null )
289       {
290          Object JavaDoc secretKey = createSecretKey(params.cipherAlgorithm, sessionKey);
291          privateCredentials.add(secretKey);
292       }
293       privateCredentials.add(params);
294
295       return true;
296    }
297
298    public boolean abort() throws LoginException JavaDoc
299    {
300       username = null;
301       password = null;
302       return true;
303    }
304
305    /** Remove the userPrincipal associated with the subject.
306     @return true always.
307     @exception LoginException thrown on exception during remove of the Principal
308     added during the commit.
309     */

310    public boolean logout() throws LoginException JavaDoc
311    {
312       try
313       {
314          if( subject.isReadOnly() == false )
315          { // Remove userPrincipal
316
Set JavaDoc s = subject.getPrincipals(userPrincipal.getClass());
317             s.remove(userPrincipal);
318             subject.getPrivateCredentials().remove(sessionKey);
319          }
320          if( srpServer != null )
321          {
322             srpServer.close(username, sessionID.intValue());
323          }
324       }
325       catch(Exception JavaDoc e)
326       {
327          throw new LoginException JavaDoc("Failed to remove user principal, "+e.getMessage());
328       }
329       return true;
330    }
331
332 // --- End LoginModule interface methods
333

334    private void getUserInfo() throws LoginException JavaDoc
335    {
336       // See if there is a shared username & password
337
String JavaDoc _username = (String JavaDoc) sharedState.get("javax.security.auth.login.name");
338       char[] _password = null;
339       if( _username != null )
340       {
341          Object JavaDoc pw = sharedState.get("javax.security.auth.login.password");
342          if( pw instanceof char[] )
343             _password = (char[]) pw;
344          else if( pw != null )
345             _password = pw.toString().toCharArray();
346       }
347       
348       // If we have a username, password return
349
if( _username != null && _password != null )
350       {
351          username = _username;
352          password = _password;
353          return;
354       }
355       
356       // Request a username and password
357
if( handler == null )
358          throw new LoginException JavaDoc("No CallbackHandler provied to SRPLoginModule");
359       
360       NameCallback JavaDoc nc = new NameCallback JavaDoc("Username: ", "guest");
361       PasswordCallback JavaDoc pc = new PasswordCallback JavaDoc("Password: ", false);
362       ByteArrayCallback bac = new ByteArrayCallback("Public key random number: ");
363       TextInputCallback JavaDoc tic = new TextInputCallback JavaDoc("Auxillary challenge token: ");
364       ArrayList JavaDoc tmpList = new ArrayList JavaDoc();
365       tmpList.add(nc);
366       tmpList.add(pc);
367       if( externalRandomA == true )
368          tmpList.add(bac);
369       if( hasAuxChallenge == true )
370          tmpList.add(tic);
371       Callback JavaDoc[] callbacks = new Callback JavaDoc[tmpList.size()];
372       tmpList.toArray(callbacks);
373       try
374       {
375          handler.handle(callbacks);
376          username = nc.getName();
377          _password = pc.getPassword();
378          if( _password != null )
379             password = _password;
380          pc.clearPassword();
381          if( externalRandomA == true )
382             abytes = bac.getByteArray();
383          if( hasAuxChallenge == true )
384             this.auxChallenge = tic.getText();
385       }
386       catch(java.io.IOException JavaDoc e)
387       {
388          throw new LoginException JavaDoc(e.toString());
389       }
390       catch(UnsupportedCallbackException JavaDoc uce)
391       {
392          throw new LoginException JavaDoc("UnsupportedCallback: " + uce.getCallback().toString());
393       }
394    }
395
396    private SRPServerInterface loadServerFromJndi(String JavaDoc jndiName)
397    {
398       SRPServerInterface server = null;
399       try
400       {
401          InitialContext JavaDoc ctx = new InitialContext JavaDoc(jndiEnv);
402          server = (SRPServerInterface) ctx.lookup(jndiName);
403       }
404       catch(Exception JavaDoc e)
405       {
406          log.error("Failed to lookup("+jndiName+")", e);
407       }
408       return server;
409    }
410    private SRPServerInterface loadServer(String JavaDoc rmiUrl)
411    {
412       SRPServerInterface server = null;
413       try
414       {
415          server = (SRPServerInterface) Naming.lookup(rmiUrl);
416       }
417       catch(Exception JavaDoc e)
418       {
419          log.error("Failed to lookup("+rmiUrl+")", e);
420       }
421       return server;
422    }
423
424    /** If there is a cipher algorithm and JCE is available, encrypt the challenge, else
425     * just return the challenge as the raw object.
426     */

427    private Object JavaDoc encryptAuxChallenge(Object JavaDoc challenge, String JavaDoc cipherAlgorithm,
428       byte[] cipherIV, Object JavaDoc key)
429       throws LoginException JavaDoc
430    {
431       if( cipherAlgorithm == null )
432          return challenge;
433       Object JavaDoc sealedObject = null;
434       try
435       {
436          Serializable JavaDoc data = (Serializable JavaDoc) challenge;
437          Object JavaDoc tmpKey = Util.createSecretKey(cipherAlgorithm, key);
438          sealedObject = Util.createSealedObject(cipherAlgorithm, tmpKey, cipherIV, data);
439       }
440       catch(Exception JavaDoc e)
441       {
442          log.error("Failed to encrypt aux challenge", e);
443          throw new LoginException JavaDoc("Failed to encrypt aux challenge");
444       }
445       return sealedObject;
446    }
447
448    /** Use reflection to create a javax.crypto.spec.SecretKeySpec to avoid
449     an explicit reference to SecretKeySpec so that the JCE is not needed
450     unless the SRP parameters indicate that encryption is needed.
451    */

452    private Object JavaDoc createSecretKey(String JavaDoc cipherAlgorithm, Object JavaDoc key) throws LoginException JavaDoc
453    {
454       Object JavaDoc secretKey = null;
455       try
456       {
457          secretKey = Util.createSecretKey(cipherAlgorithm, key);
458       }
459       catch(Exception JavaDoc e)
460       {
461          log.error("Failed to create SecretKey", e);
462          throw new LoginException JavaDoc("Failed to create SecretKey");
463       }
464       return secretKey;
465    }
466 }
467
Popular Tags