001 /*
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements. See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership. The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License. You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied. See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 *
020 */
021 package org.apache.qpid.server.security.auth.rmi;
022
023 import java.io.IOException;
024 import java.io.UnsupportedEncodingException;
025 import java.security.MessageDigest;
026 import java.security.NoSuchAlgorithmException;
027 import java.util.Arrays;
028 import java.util.Collections;
029
030 import javax.management.remote.JMXAuthenticator;
031 import javax.management.remote.JMXPrincipal;
032 import javax.security.auth.Subject;
033 import javax.security.auth.callback.PasswordCallback;
034 import javax.security.auth.login.AccountNotFoundException;
035
036 import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase;
037 import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase;
038 import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
039 import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
040
041 public class RMIPasswordAuthenticator implements JMXAuthenticator
042 {
043 static final String UNABLE_TO_LOOKUP = "The broker was unable to lookup the user details";
044 static final String SHOULD_BE_STRING_ARRAY = "User details should be String[]";
045 static final String SHOULD_HAVE_2_ELEMENTS = "User details should have 2 elements, username, password";
046 static final String SHOULD_BE_NON_NULL = "Supplied username and password should be non-null";
047 static final String INVALID_CREDENTIALS = "Invalid user details supplied";
048 static final String CREDENTIALS_REQUIRED = "User details are required. " +
049 "Please ensure you are using an up to date management console to connect.";
050
051 public static final String DEFAULT_ENCODING = "utf-8";
052 private PrincipalDatabase _db = null;
053
054 public RMIPasswordAuthenticator()
055 {
056 }
057
058 public void setPrincipalDatabase(PrincipalDatabase pd)
059 {
060 this._db = pd;
061 }
062
063 public Subject authenticate(Object credentials) throws SecurityException
064 {
065 // Verify that credential's are of type String[].
066 if (!(credentials instanceof String[]))
067 {
068 if (credentials == null)
069 {
070 throw new SecurityException(CREDENTIALS_REQUIRED);
071 }
072 else
073 {
074 throw new SecurityException(SHOULD_BE_STRING_ARRAY);
075 }
076 }
077
078 // Verify that required number of credential's.
079 final String[] userCredentials = (String[]) credentials;
080 if (userCredentials.length != 2)
081 {
082 throw new SecurityException(SHOULD_HAVE_2_ELEMENTS);
083 }
084
085 String username = (String) userCredentials[0];
086 String password = (String) userCredentials[1];
087
088 // Verify that all required credential's are actually present.
089 if (username == null || password == null)
090 {
091 throw new SecurityException(SHOULD_BE_NON_NULL);
092 }
093
094 boolean authenticated = false;
095
096 // Perform authentication
097 try
098 {
099 PasswordCallback pwCallback = new PasswordCallback("prompt",false);
100 UsernamePrincipal uname = new UsernamePrincipal(username);
101
102 if (_db instanceof Base64MD5PasswordFilePrincipalDatabase)
103 {
104 //retrieve the stored password for the given user
105 _db.setPassword(uname, pwCallback);
106
107 //compare the MD5Hash of the given password with the stored value
108 if (Arrays.equals(getMD5Hash(password), pwCallback.getPassword()))
109 {
110 authenticated = true;
111 }
112 }
113 else if (_db instanceof PlainPasswordFilePrincipalDatabase)
114 {
115 //retrieve the users stored password and compare with given value
116 _db.setPassword(uname, pwCallback);
117
118 if (password.equals(new String(pwCallback.getPassword())))
119 {
120 authenticated = true;
121 }
122 }
123 else
124 {
125 throw new SecurityException(UNABLE_TO_LOOKUP);
126 }
127 }
128 catch (AccountNotFoundException e)
129 {
130 throw new SecurityException(INVALID_CREDENTIALS);
131 }
132 catch (UnsupportedEncodingException e)
133 {
134 throw new SecurityException(UNABLE_TO_LOOKUP);
135 }
136 catch (NoSuchAlgorithmException e)
137 {
138 throw new SecurityException(UNABLE_TO_LOOKUP);
139 }
140 catch (IOException e)
141 {
142 throw new SecurityException(UNABLE_TO_LOOKUP);
143 }
144
145 if (authenticated)
146 {
147 //credential's check out, return the appropriate JAAS Subject
148 return new Subject(true,
149 Collections.singleton(new JMXPrincipal(username)),
150 Collections.EMPTY_SET,
151 Collections.EMPTY_SET);
152 }
153 else
154 {
155 throw new SecurityException(INVALID_CREDENTIALS);
156 }
157 }
158
159 public static char[] getMD5Hash(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException
160 {
161 byte[] data = text.getBytes(DEFAULT_ENCODING);
162
163 MessageDigest md = MessageDigest.getInstance("MD5");
164
165 for (byte b : data)
166 {
167 md.update(b);
168 }
169
170 byte[] digest = md.digest();
171
172 char[] hash = new char[digest.length ];
173
174 int index = 0;
175 for (byte b : digest)
176 {
177 hash[index++] = (char) b;
178 }
179
180 return hash;
181 }
182 }
|