summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractAuthenticationManager.java3
-rw-r--r--java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractScramAuthenticationManager.java397
-rw-r--r--java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramAuthUser.java4
-rw-r--r--java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramSHA1AuthenticationManager.java349
-rw-r--r--java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramSHA256AuthenticationManager.java66
-rw-r--r--java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/scram/ScramSaslServer.java (renamed from java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/scram/ScramSHA1SaslServer.java)49
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/sasl.js36
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js2
-rw-r--r--java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties1
-rw-r--r--java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties1
-rw-r--r--java/client/src/main/java/org/apache/qpid/client/security/scram/AbstractScramSaslClient.java350
-rw-r--r--java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA1SaslClient.java309
-rw-r--r--java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA1SaslClientFactory.java4
-rw-r--r--java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA256SaslClient.java34
-rw-r--r--java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA256SaslClientFactory.java61
15 files changed, 983 insertions, 683 deletions
diff --git a/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractAuthenticationManager.java b/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractAuthenticationManager.java
index 597b9e3bb1..a2bb5cb91a 100644
--- a/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractAuthenticationManager.java
+++ b/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractAuthenticationManager.java
@@ -26,7 +26,6 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
-import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.log4j.Logger;
@@ -148,8 +147,6 @@ public abstract class AbstractAuthenticationManager<T extends AbstractAuthentica
if(childClass == PreferencesProvider.class)
{
attributes = new HashMap<String, Object>(attributes);
- attributes.put(ConfiguredObject.ID, UUID.randomUUID());
- attributes.put(ConfiguredObject.DESIRED_STATE, State.ACTIVE);
PreferencesProvider pp = getObjectFactory().create(PreferencesProvider.class, attributes, this);
_preferencesProvider = pp;
diff --git a/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractScramAuthenticationManager.java b/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractScramAuthenticationManager.java
new file mode 100644
index 0000000000..f08c37008a
--- /dev/null
+++ b/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractScramAuthenticationManager.java
@@ -0,0 +1,397 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.manager;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.security.auth.login.AccountNotFoundException;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+import javax.xml.bind.DatatypeConverter;
+
+import org.apache.qpid.server.configuration.updater.Task;
+import org.apache.qpid.server.configuration.updater.VoidTaskWithException;
+import org.apache.qpid.server.model.Broker;
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.PasswordCredentialManagingAuthenticationProvider;
+import org.apache.qpid.server.model.State;
+import org.apache.qpid.server.model.User;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.security.auth.AuthenticationResult;
+import org.apache.qpid.server.security.auth.UsernamePrincipal;
+import org.apache.qpid.server.security.auth.sasl.scram.ScramSaslServer;
+
+public abstract class AbstractScramAuthenticationManager<X extends AbstractScramAuthenticationManager<X>>
+ extends AbstractAuthenticationManager<X>
+ implements PasswordCredentialManagingAuthenticationProvider<X>
+{
+ public static final String SCRAM_USER_TYPE = "scram";
+
+ static final Charset ASCII = Charset.forName("ASCII");
+ private final SecureRandom _random = new SecureRandom();
+
+ private int _iterationCount = 4096;
+
+ private Map<String, ScramAuthUser> _users = new ConcurrentHashMap<String, ScramAuthUser>();
+
+
+ protected AbstractScramAuthenticationManager(final Map<String, Object> attributes, final Broker broker)
+ {
+ super(attributes, broker);
+ }
+
+ @Override
+ public void initialise()
+ {
+
+ }
+
+ @Override
+ public String getMechanisms()
+ {
+ return getMechanismName();
+ }
+
+ protected abstract String getMechanismName();
+
+ @Override
+ public SaslServer createSaslServer(final String mechanism,
+ final String localFQDN,
+ final Principal externalPrincipal)
+ throws SaslException
+ {
+ return new ScramSaslServer(this, getMechanismName(), getHmacName(), getDigestName());
+ }
+
+ protected abstract String getDigestName();
+
+ @Override
+ public AuthenticationResult authenticate(final SaslServer server, final byte[] response)
+ {
+ try
+ {
+ // Process response from the client
+ byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]);
+
+ if (server.isComplete())
+ {
+ final String userId = server.getAuthorizationID();
+ return new AuthenticationResult(new UsernamePrincipal(userId));
+ }
+ else
+ {
+ return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE);
+ }
+ }
+ catch (SaslException e)
+ {
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e);
+ }
+ }
+
+ @Override
+ public AuthenticationResult authenticate(final String username, final String password)
+ {
+ ScramAuthUser user = getUser(username);
+ if(user != null)
+ {
+ final String[] usernamePassword = user.getPassword().split(",");
+ byte[] salt = DatatypeConverter.parseBase64Binary(usernamePassword[0]);
+ try
+ {
+ if(Arrays.equals(DatatypeConverter.parseBase64Binary(usernamePassword[1]),
+ createSaltedPassword(salt, password)))
+ {
+ return new AuthenticationResult(new UsernamePrincipal(username));
+ }
+ }
+ catch (SaslException e)
+ {
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR,e);
+ }
+
+ }
+
+ return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR);
+
+
+ }
+
+
+ public int getIterationCount()
+ {
+ return _iterationCount;
+ }
+
+ public byte[] getSalt(final String username)
+ {
+ ScramAuthUser user = getUser(username);
+
+ if(user == null)
+ {
+ // don't disclose that the user doesn't exist, just generate random data so the failure is indistinguishable
+ // from the "wrong password" case
+
+ byte[] salt = new byte[32];
+ _random.nextBytes(salt);
+ return salt;
+ }
+ else
+ {
+ return DatatypeConverter.parseBase64Binary(user.getPassword().split(",")[0]);
+ }
+ }
+
+ private static final byte[] INT_1 = new byte[]{0, 0, 0, 1};
+
+ public byte[] getSaltedPassword(final String username) throws SaslException
+ {
+ ScramAuthUser user = getUser(username);
+ if(user == null)
+ {
+ throw new SaslException("Authentication Failed");
+ }
+ else
+ {
+ return DatatypeConverter.parseBase64Binary(user.getPassword().split(",")[1]);
+ }
+ }
+
+ private ScramAuthUser getUser(final String username)
+ {
+ return _users.get(username);
+ }
+
+ private byte[] createSaltedPassword(byte[] salt, String password) throws SaslException
+ {
+ Mac mac = createSha1Hmac(password.getBytes(ASCII));
+
+ mac.update(salt);
+ mac.update(INT_1);
+ byte[] result = mac.doFinal();
+
+ byte[] previous = null;
+ for(int i = 1; i < getIterationCount(); i++)
+ {
+ mac.update(previous != null? previous: result);
+ previous = mac.doFinal();
+ for(int x = 0; x < result.length; x++)
+ {
+ result[x] ^= previous[x];
+ }
+ }
+
+ return result;
+
+ }
+
+ private Mac createSha1Hmac(final byte[] keyBytes)
+ throws SaslException
+ {
+ try
+ {
+ SecretKeySpec key = new SecretKeySpec(keyBytes, getHmacName());
+ Mac mac = Mac.getInstance(getHmacName());
+ mac.init(key);
+ return mac;
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new SaslException(e.getMessage(), e);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new SaslException(e.getMessage(), e);
+ }
+ }
+
+ protected abstract String getHmacName();
+
+ @Override
+ public boolean createUser(final String username, final String password, final Map<String, String> attributes)
+ {
+ return runTask(new Task<Boolean>()
+ {
+ @Override
+ public Boolean execute()
+ {
+ getSecurityManager().authoriseUserOperation(Operation.CREATE, username);
+ if (_users.containsKey(username))
+ {
+ throw new IllegalArgumentException("User '" + username + "' already exists");
+ }
+ try
+ {
+ Map<String, Object> userAttrs = new HashMap<String, Object>();
+ userAttrs.put(User.ID, UUID.randomUUID());
+ userAttrs.put(User.NAME, username);
+ userAttrs.put(User.PASSWORD, createStoredPassword(password));
+ userAttrs.put(User.TYPE, SCRAM_USER_TYPE);
+ ScramAuthUser user = new ScramAuthUser(userAttrs, AbstractScramAuthenticationManager.this);
+ user.create();
+
+ return true;
+ }
+ catch (SaslException e)
+ {
+ throw new IllegalArgumentException(e);
+ }
+ }
+ });
+ }
+
+ org.apache.qpid.server.security.SecurityManager getSecurityManager()
+ {
+ return getBroker().getSecurityManager();
+ }
+
+ @Override
+ public void deleteUser(final String user) throws AccountNotFoundException
+ {
+ runTask(new VoidTaskWithException<AccountNotFoundException>()
+ {
+ @Override
+ public void execute() throws AccountNotFoundException
+ {
+ final ScramAuthUser authUser = getUser(user);
+ if(authUser != null)
+ {
+ authUser.setState(State.DELETED);
+ }
+ else
+ {
+ throw new AccountNotFoundException("No such user: '" + user + "'");
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setPassword(final String username, final String password) throws AccountNotFoundException
+ {
+ runTask(new VoidTaskWithException<AccountNotFoundException>()
+ {
+ @Override
+ public void execute() throws AccountNotFoundException
+ {
+
+ final ScramAuthUser authUser = getUser(username);
+ if (authUser != null)
+ {
+ authUser.setPassword(password);
+ }
+ else
+ {
+ throw new AccountNotFoundException("No such user: '" + username + "'");
+ }
+ }
+ });
+
+ }
+
+ @Override
+ public Map<String, Map<String, String>> getUsers()
+ {
+ return runTask(new Task<Map<String, Map<String, String>>>()
+ {
+ @Override
+ public Map<String, Map<String, String>> execute()
+ {
+
+ Map<String, Map<String, String>> users = new HashMap<String, Map<String, String>>();
+ for (String user : _users.keySet())
+ {
+ users.put(user, Collections.<String, String>emptyMap());
+ }
+ return users;
+ }
+ });
+ }
+
+ @Override
+ public void reload() throws IOException
+ {
+
+ }
+
+ @Override
+ public void recoverUser(final User user)
+ {
+ _users.put(user.getName(), (ScramAuthUser) user);
+ }
+
+ protected String createStoredPassword(final String password) throws SaslException
+ {
+ byte[] salt = new byte[32];
+ _random.nextBytes(salt);
+ byte[] passwordBytes = createSaltedPassword(salt, password);
+ return DatatypeConverter.printBase64Binary(salt) + "," + DatatypeConverter.printBase64Binary(passwordBytes);
+ }
+
+ @Override
+ public <C extends ConfiguredObject> C addChild(final Class<C> childClass,
+ final Map<String, Object> attributes,
+ final ConfiguredObject... otherParents)
+ {
+ if(childClass == User.class)
+ {
+ String username = (String) attributes.get("name");
+ String password = (String) attributes.get("password");
+
+ if(createUser(username, password,null))
+ {
+ @SuppressWarnings("unchecked")
+ C user = (C) _users.get(username);
+ return user;
+ }
+ else
+ {
+ return null;
+
+ }
+ }
+ return super.addChild(childClass, attributes, otherParents);
+ }
+
+ void doDeleted()
+ {
+ deleted();
+ }
+
+ Map<String, ScramAuthUser> getUserMap()
+ {
+ return _users;
+ }
+
+}
diff --git a/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramAuthUser.java b/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramAuthUser.java
index 15cd1d82a6..9a2d27f512 100644
--- a/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramAuthUser.java
+++ b/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramAuthUser.java
@@ -44,12 +44,12 @@ import org.apache.qpid.server.security.access.Operation;
class ScramAuthUser extends AbstractConfiguredObject<ScramAuthUser> implements User<ScramAuthUser>
{
- private ScramSHA1AuthenticationManager _authenticationManager;
+ private AbstractScramAuthenticationManager _authenticationManager;
@ManagedAttributeField
private String _password;
@ManagedObjectFactoryConstructor
- ScramAuthUser(final Map<String, Object> attributes, ScramSHA1AuthenticationManager parent)
+ ScramAuthUser(final Map<String, Object> attributes, AbstractScramAuthenticationManager parent)
{
super(parentsMap(parent), attributes);
_authenticationManager = parent;
diff --git a/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramSHA1AuthenticationManager.java b/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramSHA1AuthenticationManager.java
index 2138f4899e..6a48d29a71 100644
--- a/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramSHA1AuthenticationManager.java
+++ b/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramSHA1AuthenticationManager.java
@@ -20,53 +20,23 @@
*/
package org.apache.qpid.server.security.auth.manager;
-import java.io.IOException;
import java.nio.charset.Charset;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import javax.security.auth.login.AccountNotFoundException;
-import javax.security.sasl.SaslException;
-import javax.security.sasl.SaslServer;
-import javax.xml.bind.DatatypeConverter;
-
-import org.apache.qpid.server.configuration.updater.Task;
-import org.apache.qpid.server.configuration.updater.VoidTaskWithException;
import org.apache.qpid.server.model.Broker;
-import org.apache.qpid.server.model.ConfiguredObject;
import org.apache.qpid.server.model.ManagedObject;
import org.apache.qpid.server.model.ManagedObjectFactoryConstructor;
-import org.apache.qpid.server.model.PasswordCredentialManagingAuthenticationProvider;
-import org.apache.qpid.server.model.State;
-import org.apache.qpid.server.model.User;
-import org.apache.qpid.server.security.SecurityManager;
-import org.apache.qpid.server.security.access.Operation;
-import org.apache.qpid.server.security.auth.AuthenticationResult;
-import org.apache.qpid.server.security.auth.UsernamePrincipal;
-import org.apache.qpid.server.security.auth.sasl.scram.ScramSHA1SaslServer;
@ManagedObject( category = false, type = "SCRAM-SHA-1" )
public class ScramSHA1AuthenticationManager
- extends AbstractAuthenticationManager<ScramSHA1AuthenticationManager>
- implements PasswordCredentialManagingAuthenticationProvider<ScramSHA1AuthenticationManager>
+ extends AbstractScramAuthenticationManager<ScramSHA1AuthenticationManager>
{
- public static final String SCRAM_USER_TYPE = "scram";
public static final String PROVIDER_TYPE = "SCRAM-SHA-1";
+ private static final String HMAC_NAME = "HmacSHA1";
+
static final Charset ASCII = Charset.forName("ASCII");
- public static final String HMAC_SHA_1 = "HmacSHA1";
- private final SecureRandom _random = new SecureRandom();
- private int _iterationCount = 4096;
- private Map<String, ScramAuthUser> _users = new ConcurrentHashMap<String, ScramAuthUser>();
+ private static final String MECHANISM = "SCRAM-SHA-1";
+ private static final String DIGEST_NAME = "SHA-1";
@ManagedObjectFactoryConstructor
@@ -76,318 +46,21 @@ public class ScramSHA1AuthenticationManager
}
@Override
- public void initialise()
- {
-
- }
-
- @Override
- public String getMechanisms()
- {
- return ScramSHA1SaslServer.MECHANISM;
- }
-
- @Override
- public SaslServer createSaslServer(final String mechanism,
- final String localFQDN,
- final Principal externalPrincipal)
- throws SaslException
- {
- return new ScramSHA1SaslServer(this);
- }
-
- @Override
- public AuthenticationResult authenticate(final SaslServer server, final byte[] response)
- {
- try
- {
- // Process response from the client
- byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]);
-
- if (server.isComplete())
- {
- final String userId = server.getAuthorizationID();
- return new AuthenticationResult(new UsernamePrincipal(userId));
- }
- else
- {
- return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE);
- }
- }
- catch (SaslException e)
- {
- return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e);
- }
- }
-
- @Override
- public AuthenticationResult authenticate(final String username, final String password)
- {
- ScramAuthUser user = getUser(username);
- if(user != null)
- {
- final String[] usernamePassword = user.getPassword().split(",");
- byte[] salt = DatatypeConverter.parseBase64Binary(usernamePassword[0]);
- try
- {
- if(Arrays.equals(DatatypeConverter.parseBase64Binary(usernamePassword[1]),createSaltedPassword(salt, password)))
- {
- return new AuthenticationResult(new UsernamePrincipal(username));
- }
- }
- catch (SaslException e)
- {
- return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR,e);
- }
-
- }
-
- return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR);
-
-
- }
-
-
- public int getIterationCount()
- {
- return _iterationCount;
- }
-
- public byte[] getSalt(final String username)
- {
- ScramAuthUser user = getUser(username);
-
- if(user == null)
- {
- // don't disclose that the user doesn't exist, just generate random data so the failure is indistinguishable
- // from the "wrong password" case
-
- byte[] salt = new byte[32];
- _random.nextBytes(salt);
- return salt;
- }
- else
- {
- return DatatypeConverter.parseBase64Binary(user.getPassword().split(",")[0]);
- }
- }
-
- private static final byte[] INT_1 = new byte[]{0, 0, 0, 1};
-
- public byte[] getSaltedPassword(final String username) throws SaslException
- {
- ScramAuthUser user = getUser(username);
- if(user == null)
- {
- throw new SaslException("Authentication Failed");
- }
- else
- {
- return DatatypeConverter.parseBase64Binary(user.getPassword().split(",")[1]);
- }
- }
-
- private ScramAuthUser getUser(final String username)
- {
- return _users.get(username);
- }
-
- private byte[] createSaltedPassword(byte[] salt, String password) throws SaslException
- {
- Mac mac = createSha1Hmac(password.getBytes(ASCII));
-
- mac.update(salt);
- mac.update(INT_1);
- byte[] result = mac.doFinal();
-
- byte[] previous = null;
- for(int i = 1; i < getIterationCount(); i++)
- {
- mac.update(previous != null? previous: result);
- previous = mac.doFinal();
- for(int x = 0; x < result.length; x++)
- {
- result[x] ^= previous[x];
- }
- }
-
- return result;
-
- }
-
- private Mac createSha1Hmac(final byte[] keyBytes)
- throws SaslException
- {
- try
- {
- SecretKeySpec key = new SecretKeySpec(keyBytes, HMAC_SHA_1);
- Mac mac = Mac.getInstance(HMAC_SHA_1);
- mac.init(key);
- return mac;
- }
- catch (NoSuchAlgorithmException e)
- {
- throw new SaslException(e.getMessage(), e);
- }
- catch (InvalidKeyException e)
- {
- throw new SaslException(e.getMessage(), e);
- }
- }
-
- @Override
- public boolean createUser(final String username, final String password, final Map<String, String> attributes)
- {
- return runTask(new Task<Boolean>()
- {
- @Override
- public Boolean execute()
- {
- getSecurityManager().authoriseUserOperation(Operation.CREATE, username);
- if (_users.containsKey(username))
- {
- throw new IllegalArgumentException("User '" + username + "' already exists");
- }
- try
- {
- Map<String, Object> userAttrs = new HashMap<String, Object>();
- userAttrs.put(User.ID, UUID.randomUUID());
- userAttrs.put(User.NAME, username);
- userAttrs.put(User.PASSWORD, createStoredPassword(password));
- userAttrs.put(User.TYPE, SCRAM_USER_TYPE);
- ScramAuthUser user = new ScramAuthUser(userAttrs, ScramSHA1AuthenticationManager.this);
- user.create();
-
- return true;
- }
- catch (SaslException e)
- {
- throw new IllegalArgumentException(e);
- }
- }
- });
- }
-
- SecurityManager getSecurityManager()
- {
- return getBroker().getSecurityManager();
- }
-
- @Override
- public void deleteUser(final String user) throws AccountNotFoundException
- {
- runTask(new VoidTaskWithException<AccountNotFoundException>()
- {
- @Override
- public void execute() throws AccountNotFoundException
- {
- final ScramAuthUser authUser = getUser(user);
- if(authUser != null)
- {
- authUser.setState(State.DELETED);
- }
- else
- {
- throw new AccountNotFoundException("No such user: '" + user + "'");
- }
- }
- });
- }
-
- @Override
- public void setPassword(final String username, final String password) throws AccountNotFoundException
+ protected String getMechanismName()
{
- runTask(new VoidTaskWithException<AccountNotFoundException>()
- {
- @Override
- public void execute() throws AccountNotFoundException
- {
-
- final ScramAuthUser authUser = getUser(username);
- if (authUser != null)
- {
- authUser.setPassword(password);
- }
- else
- {
- throw new AccountNotFoundException("No such user: '" + username + "'");
- }
- }
- });
-
- }
-
- @Override
- public Map<String, Map<String, String>> getUsers()
- {
- return runTask(new Task<Map<String, Map<String, String>>>()
- {
- @Override
- public Map<String, Map<String, String>> execute()
- {
-
- Map<String, Map<String, String>> users = new HashMap<String, Map<String, String>>();
- for (String user : _users.keySet())
- {
- users.put(user, Collections.<String, String>emptyMap());
- }
- return users;
- }
- });
- }
-
- @Override
- public void reload() throws IOException
- {
-
+ return MECHANISM;
}
@Override
- public void recoverUser(final User user)
- {
- _users.put(user.getName(), (ScramAuthUser) user);
- }
-
- protected String createStoredPassword(final String password) throws SaslException
+ protected String getDigestName()
{
- byte[] salt = new byte[32];
- _random.nextBytes(salt);
- byte[] passwordBytes = createSaltedPassword(salt, password);
- return DatatypeConverter.printBase64Binary(salt) + "," + DatatypeConverter.printBase64Binary(passwordBytes);
+ return DIGEST_NAME;
}
@Override
- public <C extends ConfiguredObject> C addChild(final Class<C> childClass,
- final Map<String, Object> attributes,
- final ConfiguredObject... otherParents)
+ protected String getHmacName()
{
- if(childClass == User.class)
- {
- String username = (String) attributes.get("name");
- String password = (String) attributes.get("password");
-
- if(createUser(username, password,null))
- {
- @SuppressWarnings("unchecked")
- C user = (C) _users.get(username);
- return user;
- }
- else
- {
- return null;
-
- }
- }
- return super.addChild(childClass, attributes, otherParents);
+ return HMAC_NAME;
}
- void doDeleted()
- {
- deleted();
- }
-
- Map<String, ScramAuthUser> getUserMap()
- {
- return _users;
- }
}
diff --git a/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramSHA256AuthenticationManager.java b/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramSHA256AuthenticationManager.java
new file mode 100644
index 0000000000..ea5b82fdd5
--- /dev/null
+++ b/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ScramSHA256AuthenticationManager.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.auth.manager;
+
+import java.nio.charset.Charset;
+import java.util.Map;
+
+import org.apache.qpid.server.model.Broker;
+import org.apache.qpid.server.model.ManagedObject;
+import org.apache.qpid.server.model.ManagedObjectFactoryConstructor;
+
+@ManagedObject( category = false, type = "SCRAM-SHA-256" )
+public class ScramSHA256AuthenticationManager
+ extends AbstractScramAuthenticationManager<ScramSHA256AuthenticationManager>
+{
+ public static final String PROVIDER_TYPE = "SCRAM-SHA-256";
+ private static final String HMAC_NAME = "HmacSHA256";
+
+ static final Charset ASCII = Charset.forName("ASCII");
+ private static final String MECHANISM = "SCRAM-SHA-256";
+ private static final String DIGEST_NAME = "SHA-256";
+
+
+ @ManagedObjectFactoryConstructor
+ protected ScramSHA256AuthenticationManager(final Map<String, Object> attributes, final Broker broker)
+ {
+ super(attributes, broker);
+ }
+
+ @Override
+ protected String getMechanismName()
+ {
+ return MECHANISM;
+ }
+
+ @Override
+ protected String getDigestName()
+ {
+ return DIGEST_NAME;
+ }
+
+ @Override
+ protected String getHmacName()
+ {
+ return HMAC_NAME;
+ }
+
+}
diff --git a/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/scram/ScramSHA1SaslServer.java b/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/scram/ScramSaslServer.java
index 71ef386e3e..f510ec32d8 100644
--- a/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/scram/ScramSHA1SaslServer.java
+++ b/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/scram/ScramSaslServer.java
@@ -20,13 +20,6 @@
*/
package org.apache.qpid.server.security.auth.sasl.scram;
-import org.apache.qpid.server.security.auth.manager.ScramSHA1AuthenticationManager;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import javax.security.sasl.SaslException;
-import javax.security.sasl.SaslServer;
-import javax.xml.bind.DatatypeConverter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
@@ -35,13 +28,23 @@ import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.UUID;
-public class ScramSHA1SaslServer implements SaslServer
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+import javax.xml.bind.DatatypeConverter;
+
+import org.apache.qpid.server.security.auth.manager.AbstractScramAuthenticationManager;
+
+public class ScramSaslServer implements SaslServer
{
- public static final String MECHANISM = "SCRAM-SHA-1";
+ public final String _mechanism;
+ public final String _hmacName;
+ public final String _digestName;
private static final Charset ASCII = Charset.forName("ASCII");
- private final ScramSHA1AuthenticationManager _authManager;
+ private final AbstractScramAuthenticationManager _authManager;
private State _state = State.INITIAL;
private String _nonce;
private String _username;
@@ -50,9 +53,15 @@ public class ScramSHA1SaslServer implements SaslServer
private String _clientFirstMessageBare;
private byte[] _serverSignature;
- public ScramSHA1SaslServer(final ScramSHA1AuthenticationManager authenticationManager)
+ public ScramSaslServer(final AbstractScramAuthenticationManager authenticationManager,
+ final String mechanism,
+ final String hmacName,
+ final String digestName)
{
_authManager = authenticationManager;
+ _mechanism = mechanism;
+ _hmacName = hmacName;
+ _digestName = digestName;
}
enum State
@@ -65,7 +74,7 @@ public class ScramSHA1SaslServer implements SaslServer
@Override
public String getMechanismName()
{
- return MECHANISM;
+ return _mechanism;
}
@Override
@@ -174,11 +183,11 @@ public class ScramSHA1SaslServer implements SaslServer
byte[] saltedPassword = _authManager.getSaltedPassword(_username);
- byte[] clientKey = computeHmacSHA1(saltedPassword, "Client Key");
+ byte[] clientKey = computeHmac(saltedPassword, "Client Key");
- byte[] storedKey = MessageDigest.getInstance("SHA1").digest(clientKey);
+ byte[] storedKey = MessageDigest.getInstance(_digestName).digest(clientKey);
- byte[] clientSignature = computeHmacSHA1(storedKey, authMessage);
+ byte[] clientSignature = computeHmac(storedKey, authMessage);
byte[] clientProof = clientKey.clone();
for(int i = 0 ; i < clientProof.length; i++)
@@ -190,8 +199,8 @@ public class ScramSHA1SaslServer implements SaslServer
{
throw new SaslException("Authentication failed");
}
- byte[] serverKey = computeHmacSHA1(saltedPassword, "Server Key");
- String finalResponse = "v=" + DatatypeConverter.printBase64Binary(computeHmacSHA1(serverKey, authMessage));
+ byte[] serverKey = computeHmac(saltedPassword, "Server Key");
+ String finalResponse = "v=" + DatatypeConverter.printBase64Binary(computeHmac(serverKey, authMessage));
return finalResponse.getBytes(ASCII);
}
@@ -241,7 +250,7 @@ public class ScramSHA1SaslServer implements SaslServer
}
- private byte[] computeHmacSHA1(final byte[] key, final String string)
+ private byte[] computeHmac(final byte[] key, final String string)
throws SaslException, UnsupportedEncodingException
{
Mac mac = createSha1Hmac(key);
@@ -255,8 +264,8 @@ public class ScramSHA1SaslServer implements SaslServer
{
try
{
- SecretKeySpec key = new SecretKeySpec(keyBytes, "HmacSHA1");
- Mac mac = Mac.getInstance("HmacSHA1");
+ SecretKeySpec key = new SecretKeySpec(keyBytes, _hmacName);
+ Mac mac = Mac.getInstance(_hmacName);
mac.init(key);
return mac;
}
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/sasl.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/sasl.js
index c00f0eae19..82404d100c 100644
--- a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/sasl.js
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/sasl.js
@@ -138,10 +138,20 @@ var saslCramMD5 = function saslCramMD5(user, password, saslMechanism, callbackFu
};
- var saslScramSha1 = function saslScramSha1(user, password, saslMechanism, callbackFunction)
- {
+ var saslScramSha1 = function saslScramSha1(user, password, saslMechanism, callbackFunction) {
+ saslScram("sha1",user,password,saslMechanism,callbackFunction);
+ };
+
+ var saslScramSha256 = function saslScramSha1(user, password, saslMechanism, callbackFunction) {
+ saslScram("sha256",user,password,saslMechanism,callbackFunction);
+ };
+
+ var saslScram = function saslScramSha1(mechanism, user, password, saslMechanism, callbackFunction) {
- script.get("webjars/cryptojs/3.1.2/rollups/hmac-sha1.js").then( function()
+ var DIGEST = mechanism.toUpperCase();
+ var HMAC = "Hmac"+DIGEST;
+
+ script.get("webjars/cryptojs/3.1.2/rollups/hmac-"+mechanism+".js").then( function()
{
script.get("webjars/cryptojs/3.1.2/components/enc-base64-min.js").then ( function()
{
@@ -187,7 +197,7 @@ var saslCramMD5 = function saslCramMD5(user, password, saslMechanism, callbackFu
var generateSaltedPassword = function generateSaltedPassword(salt, password, iterationCount)
{
- var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA1, password);
+ var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo[DIGEST], password);
hmac.update(salt);
hmac.update(CryptoJS.enc.Hex.parse("00000001"));
@@ -196,7 +206,7 @@ var saslCramMD5 = function saslCramMD5(user, password, saslMechanism, callbackFu
var previous = null;
for(var i = 1 ;i < iterationCount; i++)
{
- hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA1, password);
+ hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo[DIGEST], password);
hmac.update( previous != null ? previous : result );
previous = hmac.finalize();
result = xor(result, previous);
@@ -238,12 +248,12 @@ var saslCramMD5 = function saslCramMD5(user, password, saslMechanism, callbackFu
var saltedPassword = generateSaltedPassword(salt, password, iterationCount)
var clientFinalMessageWithoutProof = "c=" + toBase64(GS2_HEADER) + ",r=" + nonce;
var authMessage = clientFirstMessageBare + "," + serverFirstMessage + "," + clientFinalMessageWithoutProof;
- var clientKey = CryptoJS.HmacSHA1("Client Key", saltedPassword);
- var storedKey = CryptoJS.SHA1(clientKey);
- var clientSignature = CryptoJS.HmacSHA1(authMessage, storedKey);
+ var clientKey = CryptoJS[HMAC]("Client Key", saltedPassword);
+ var storedKey = CryptoJS[DIGEST](clientKey);
+ var clientSignature = CryptoJS[HMAC](authMessage, storedKey);
var clientProof = xor(clientKey, clientSignature);
- var serverKey = CryptoJS.HmacSHA1("Server Key", saltedPassword);
- serverSignature = CryptoJS.HmacSHA1(authMessage, serverKey);
+ var serverKey = CryptoJS[HMAC]("Server Key", saltedPassword);
+ serverSignature = CryptoJS[HMAC](authMessage, serverKey);
dojo.xhrPost({
// The URL of the request
url: saslServiceUrl,
@@ -300,7 +310,11 @@ SaslClient.authenticate = function(username, password, callbackFunction)
}).then(function(data)
{
var mechMap = data.mechanisms;
- if(containsMechanism(mechMap, "SCRAM-SHA-1"))
+ if(containsMechanism(mechMap, "SCRAM-SHA-256"))
+ {
+ saslScramSha256(username, password, "SCRAM-SHA-256", callbackFunction)
+ }
+ else if(containsMechanism(mechMap, "SCRAM-SHA-1"))
{
saslScramSha1(username, password, "SCRAM-SHA-1", callbackFunction)
}
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js
index 46d0cfa35d..1391d7d5ff 100644
--- a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js
@@ -140,7 +140,7 @@ define(["dojo/_base/xhr",
util.isProviderManagingUsers = function(type)
{
- return (type === "PlainPasswordFile" || type === "Base64MD5PasswordFile" || type === "SCRAM-SHA-1");
+ return (type === "PlainPasswordFile" || type === "Base64MD5PasswordFile" || type === "SCRAM-SHA-1" || type === "SCRAM-SHA-256");
};
util.showSetAttributesDialog = function(attributeWidgetFactories, data, putURL, dialogTitle, appendNameToUrl)
diff --git a/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties b/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties
index 7573664187..8f02ee2c38 100644
--- a/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties
+++ b/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties
@@ -32,4 +32,5 @@ AMQPLAIN.5=org.apache.qpid.client.security.UsernamePasswordCallbackHandler
PLAIN.6=org.apache.qpid.client.security.UsernamePasswordCallbackHandler
ANONYMOUS.7=org.apache.qpid.client.security.UsernamePasswordCallbackHandler
SCRAM-SHA-1.8=org.apache.qpid.client.security.UsernamePasswordCallbackHandler
+SCRAM-SHA-256.9=org.apache.qpid.client.security.UsernamePasswordCallbackHandler
diff --git a/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties b/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties
index fd52935fe5..24a76982ef 100644
--- a/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties
+++ b/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties
@@ -20,3 +20,4 @@ AMQPLAIN=org.apache.qpid.client.security.amqplain.AmqPlainSaslClientFactory
CRAM-MD5-HASHED=org.apache.qpid.client.security.crammd5hashed.CRAMMD5HashedSaslClientFactory
ANONYMOUS=org.apache.qpid.client.security.anonymous.AnonymousSaslClientFactory
SCRAM-SHA-1=org.apache.qpid.client.security.scram.ScramSHA1SaslClientFactory
+SCRAM-SHA-256=org.apache.qpid.client.security.scram.ScramSHA256SaslClientFactory
diff --git a/java/client/src/main/java/org/apache/qpid/client/security/scram/AbstractScramSaslClient.java b/java/client/src/main/java/org/apache/qpid/client/security/scram/AbstractScramSaslClient.java
new file mode 100644
index 0000000000..1e67567b8b
--- /dev/null
+++ b/java/client/src/main/java/org/apache/qpid/client/security/scram/AbstractScramSaslClient.java
@@ -0,0 +1,350 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.client.security.scram;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.UUID;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+import javax.xml.bind.DatatypeConverter;
+
+public abstract class AbstractScramSaslClient implements SaslClient
+{
+
+ private static final byte[] INT_1 = new byte[]{0, 0, 0, 1};
+ private static final String GS2_HEADER = "n,,";
+ private static final Charset ASCII = Charset.forName("ASCII");
+
+ private final String _digestName;
+ private final String _hmacName;
+
+ private String _username;
+ private final String _clientNonce = UUID.randomUUID().toString();
+ private String _serverNonce;
+ private byte[] _salt;
+ private int _iterationCount;
+ private String _clientFirstMessageBare;
+ private byte[] _serverSignature;
+
+ enum State
+ {
+ INITIAL,
+ CLIENT_FIRST_SENT,
+ CLIENT_PROOF_SENT,
+ COMPLETE
+ }
+
+ public final String _mechanism;
+
+ private final CallbackHandler _callbackHandler;
+
+ private State _state = State.INITIAL;
+
+ public AbstractScramSaslClient(final CallbackHandler cbh,
+ final String mechanism,
+ final String digestName,
+ final String hmacName)
+ {
+ _callbackHandler = cbh;
+ _mechanism = mechanism;
+ _digestName = digestName;
+ _hmacName = hmacName;
+
+ }
+
+ @Override
+ public String getMechanismName()
+ {
+ return _mechanism;
+ }
+
+ @Override
+ public boolean hasInitialResponse()
+ {
+ return true;
+ }
+
+ @Override
+ public byte[] evaluateChallenge(final byte[] challenge) throws SaslException
+ {
+ byte[] response;
+ switch(_state)
+ {
+ case INITIAL:
+ response = initialResponse();
+ _state = State.CLIENT_FIRST_SENT;
+ break;
+ case CLIENT_FIRST_SENT:
+ response = calculateClientProof(challenge);
+ _state = State.CLIENT_PROOF_SENT;
+ break;
+ case CLIENT_PROOF_SENT:
+ evaluateOutcome(challenge);
+ response = null;
+ _state = State.COMPLETE;
+ break;
+ default:
+ throw new SaslException("No challenge expected in state " + _state);
+ }
+ return response;
+ }
+
+ private void evaluateOutcome(final byte[] challenge) throws SaslException
+ {
+ String serverFinalMessage = new String(challenge, ASCII);
+ String[] parts = serverFinalMessage.split(",");
+ if(!parts[0].startsWith("v="))
+ {
+ throw new SaslException("Server final message did not contain verifier");
+ }
+ byte[] serverSignature = DatatypeConverter.parseBase64Binary(parts[0].substring(2));
+ if(!Arrays.equals(_serverSignature, serverSignature))
+ {
+ throw new SaslException("Server signature did not match");
+ }
+ }
+
+ private byte[] calculateClientProof(final byte[] challenge) throws SaslException
+ {
+ try
+ {
+ String serverFirstMessage = new String(challenge, ASCII);
+ String[] parts = serverFirstMessage.split(",");
+ if(parts.length < 3)
+ {
+ throw new SaslException("Server challenge '" + serverFirstMessage + "' cannot be parsed");
+ }
+ else if(parts[0].startsWith("m="))
+ {
+ throw new SaslException("Server requires mandatory extension which is not supported: " + parts[0]);
+ }
+ else if(!parts[0].startsWith("r="))
+ {
+ throw new SaslException("Server challenge '" + serverFirstMessage + "' cannot be parsed, cannot find nonce");
+ }
+ String nonce = parts[0].substring(2);
+ if(!nonce.startsWith(_clientNonce))
+ {
+ throw new SaslException("Server challenge did not use correct client nonce");
+ }
+ _serverNonce = nonce;
+ if(!parts[1].startsWith("s="))
+ {
+ throw new SaslException("Server challenge '" + serverFirstMessage + "' cannot be parsed, cannot find salt");
+ }
+ String base64Salt = parts[1].substring(2);
+ _salt = DatatypeConverter.parseBase64Binary(base64Salt);
+ if(!parts[2].startsWith("i="))
+ {
+ throw new SaslException("Server challenge '" + serverFirstMessage + "' cannot be parsed, cannot find iteration count");
+ }
+ String iterCountString = parts[2].substring(2);
+ _iterationCount = Integer.parseInt(iterCountString);
+ if(_iterationCount <= 0)
+ {
+ throw new SaslException("Iteration count " + _iterationCount + " is not a positive integer");
+ }
+ PasswordCallback passwordCallback = new PasswordCallback("Password", false);
+ _callbackHandler.handle(new Callback[] { passwordCallback });
+ byte[] passwordBytes = saslPrep(new String(passwordCallback.getPassword())).getBytes("UTF-8");
+
+ byte[] saltedPassword = generateSaltedPassword(passwordBytes);
+
+
+ String clientFinalMessageWithoutProof =
+ "c=" + DatatypeConverter.printBase64Binary(GS2_HEADER.getBytes(ASCII))
+ + ",r=" + _serverNonce;
+
+ String authMessage = _clientFirstMessageBare + "," + serverFirstMessage + "," + clientFinalMessageWithoutProof;
+
+ byte[] clientKey = computeHmac(saltedPassword, "Client Key");
+ byte[] storedKey = MessageDigest.getInstance(_digestName).digest(clientKey);
+
+ byte[] clientSignature = computeHmac(storedKey, authMessage);
+
+ byte[] clientProof = clientKey.clone();
+ for(int i = 0 ; i < clientProof.length; i++)
+ {
+ clientProof[i] ^= clientSignature[i];
+ }
+ byte[] serverKey = computeHmac(saltedPassword, "Server Key");
+ _serverSignature = computeHmac(serverKey, authMessage);
+
+ String finalMessageWithProof = clientFinalMessageWithoutProof
+ + ",p=" + DatatypeConverter.printBase64Binary(clientProof);
+ return finalMessageWithProof.getBytes();
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new SaslException(e.getMessage(), e);
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new SaslException(e.getMessage(), e);
+ }
+ catch (UnsupportedCallbackException e)
+ {
+ throw new SaslException(e.getMessage(), e);
+ }
+ catch (IOException e)
+ {
+ throw new SaslException(e.getMessage(), e);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new SaslException(e.getMessage(), e);
+ }
+ }
+
+ private byte[] computeHmac(final byte[] key, final String string)
+ throws SaslException, UnsupportedEncodingException
+ {
+ Mac mac = createHmac(key);
+ mac.update(string.getBytes(ASCII));
+ return mac.doFinal();
+ }
+
+ private byte[] generateSaltedPassword(final byte[] passwordBytes) throws SaslException
+ {
+ Mac mac = createHmac(passwordBytes);
+
+ mac.update(_salt);
+ mac.update(INT_1);
+ byte[] result = mac.doFinal();
+
+ byte[] previous = null;
+ for(int i = 1; i < _iterationCount; i++)
+ {
+ mac.update(previous != null? previous: result);
+ previous = mac.doFinal();
+ for(int x = 0; x < result.length; x++)
+ {
+ result[x] ^= previous[x];
+ }
+ }
+
+ return result;
+ }
+
+ private Mac createHmac(final byte[] keyBytes)
+ throws SaslException
+ {
+ try
+ {
+ SecretKeySpec key = new SecretKeySpec(keyBytes, _hmacName);
+ Mac mac = Mac.getInstance(_hmacName);
+ mac.init(key);
+ return mac;
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new SaslException(e.getMessage(), e);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new SaslException(e.getMessage(), e);
+ }
+ }
+
+
+ private byte[] initialResponse() throws SaslException
+ {
+ try
+ {
+ StringBuffer buf = new StringBuffer("n=");
+ NameCallback nameCallback = new NameCallback("Username?");
+ _callbackHandler.handle(new Callback[] { nameCallback });
+ _username = nameCallback.getName();
+ buf.append(saslPrep(_username));
+ buf.append(",r=");
+ buf.append(_clientNonce);
+ _clientFirstMessageBare = buf.toString();
+ return (GS2_HEADER + _clientFirstMessageBare).getBytes(ASCII);
+ }
+ catch (UnsupportedCallbackException e)
+ {
+ throw new SaslException(e.getMessage(), e);
+ }
+ catch (IOException e)
+ {
+ throw new SaslException(e.getMessage(), e);
+ }
+ }
+
+ private String saslPrep(String name) throws SaslException
+ {
+ // TODO - a real implementation of SaslPrep
+
+ if(!ASCII.newEncoder().canEncode(name))
+ {
+ throw new SaslException("Can only encode names and passwords which are restricted to ASCII characters");
+ }
+
+ name = name.replace("=", "=3D");
+ name = name.replace(",", "=2C");
+ return name;
+ }
+
+ @Override
+ public boolean isComplete()
+ {
+ return _state == State.COMPLETE;
+ }
+
+ @Override
+ public byte[] unwrap(final byte[] incoming, final int offset, final int len) throws SaslException
+ {
+ throw new IllegalStateException("No security layer supported");
+ }
+
+ @Override
+ public byte[] wrap(final byte[] outgoing, final int offset, final int len) throws SaslException
+ {
+ throw new IllegalStateException("No security layer supported");
+ }
+
+ @Override
+ public Object getNegotiatedProperty(final String propName)
+ {
+ return null;
+ }
+
+ @Override
+ public void dispose() throws SaslException
+ {
+
+ }
+
+}
diff --git a/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA1SaslClient.java b/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA1SaslClient.java
index 91c03e18c1..b6704e9d94 100644
--- a/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA1SaslClient.java
+++ b/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA1SaslClient.java
@@ -20,320 +20,15 @@
*/
package org.apache.qpid.client.security.scram;
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.sasl.SaslClient;
-import javax.security.sasl.SaslException;
-import javax.xml.bind.DatatypeConverter;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-import java.util.UUID;
-public class ScramSHA1SaslClient implements SaslClient
+public class ScramSHA1SaslClient extends AbstractScramSaslClient
{
- private static final byte[] INT_1 = new byte[]{0, 0, 0, 1};
- private static final String GS2_HEADER = "n,,";
- private static final Charset ASCII = Charset.forName("ASCII");
-
- private String _username;
- private final String _clientNonce = UUID.randomUUID().toString();
- private String _serverNonce;
- private byte[] _salt;
- private int _iterationCount;
- private String _clientFirstMessageBare;
- private byte[] _serverSignature;
-
- enum State
- {
- INITIAL,
- CLIENT_FIRST_SENT,
- CLIENT_PROOF_SENT,
- COMPLETE
- }
-
public static final String MECHANISM = "SCRAM-SHA-1";
- private final CallbackHandler _callbackHandler;
-
- private State _state = State.INITIAL;
-
public ScramSHA1SaslClient(final CallbackHandler cbh)
{
- _callbackHandler = cbh;
- }
-
- @Override
- public String getMechanismName()
- {
- return MECHANISM;
- }
-
- @Override
- public boolean hasInitialResponse()
- {
- return true;
- }
-
- @Override
- public byte[] evaluateChallenge(final byte[] challenge) throws SaslException
- {
- byte[] response;
- switch(_state)
- {
- case INITIAL:
- response = initialResponse();
- _state = State.CLIENT_FIRST_SENT;
- break;
- case CLIENT_FIRST_SENT:
- response = calculateClientProof(challenge);
- _state = State.CLIENT_PROOF_SENT;
- break;
- case CLIENT_PROOF_SENT:
- evaluateOutcome(challenge);
- response = null;
- _state = State.COMPLETE;
- break;
- default:
- throw new SaslException("No challenge expected in state " + _state);
- }
- return response;
- }
-
- private void evaluateOutcome(final byte[] challenge) throws SaslException
- {
- String serverFinalMessage = new String(challenge, ASCII);
- String[] parts = serverFinalMessage.split(",");
- if(!parts[0].startsWith("v="))
- {
- throw new SaslException("Server final message did not contain verifier");
- }
- byte[] serverSignature = DatatypeConverter.parseBase64Binary(parts[0].substring(2));
- if(!Arrays.equals(_serverSignature, serverSignature))
- {
- throw new SaslException("Server signature did not match");
- }
- }
-
- private byte[] calculateClientProof(final byte[] challenge) throws SaslException
- {
- try
- {
- String serverFirstMessage = new String(challenge, ASCII);
- String[] parts = serverFirstMessage.split(",");
- if(parts.length < 3)
- {
- throw new SaslException("Server challenge '" + serverFirstMessage + "' cannot be parsed");
- }
- else if(parts[0].startsWith("m="))
- {
- throw new SaslException("Server requires mandatory extension which is not supported: " + parts[0]);
- }
- else if(!parts[0].startsWith("r="))
- {
- throw new SaslException("Server challenge '" + serverFirstMessage + "' cannot be parsed, cannot find nonce");
- }
- String nonce = parts[0].substring(2);
- if(!nonce.startsWith(_clientNonce))
- {
- throw new SaslException("Server challenge did not use correct client nonce");
- }
- _serverNonce = nonce;
- if(!parts[1].startsWith("s="))
- {
- throw new SaslException("Server challenge '" + serverFirstMessage + "' cannot be parsed, cannot find salt");
- }
- String base64Salt = parts[1].substring(2);
- _salt = DatatypeConverter.parseBase64Binary(base64Salt);
- if(!parts[2].startsWith("i="))
- {
- throw new SaslException("Server challenge '" + serverFirstMessage + "' cannot be parsed, cannot find iteration count");
- }
- String iterCountString = parts[2].substring(2);
- _iterationCount = Integer.parseInt(iterCountString);
- if(_iterationCount <= 0)
- {
- throw new SaslException("Iteration count " + _iterationCount + " is not a positive integer");
- }
- PasswordCallback passwordCallback = new PasswordCallback("Password", false);
- _callbackHandler.handle(new Callback[] { passwordCallback });
- byte[] passwordBytes = saslPrep(new String(passwordCallback.getPassword())).getBytes("UTF-8");
-
- byte[] saltedPassword = generateSaltedPassword(passwordBytes);
-
-
- String clientFinalMessageWithoutProof =
- "c=" + DatatypeConverter.printBase64Binary(GS2_HEADER.getBytes(ASCII))
- + ",r=" + _serverNonce;
-
- String authMessage = _clientFirstMessageBare + "," + serverFirstMessage + "," + clientFinalMessageWithoutProof;
-
- byte[] clientKey = computeHmacSHA1(saltedPassword, "Client Key");
- byte[] storedKey = MessageDigest.getInstance("SHA1").digest(clientKey);
-
- byte[] clientSignature = computeHmacSHA1(storedKey, authMessage);
-
- byte[] clientProof = clientKey.clone();
- for(int i = 0 ; i < clientProof.length; i++)
- {
- clientProof[i] ^= clientSignature[i];
- }
- byte[] serverKey = computeHmacSHA1(saltedPassword, "Server Key");
- _serverSignature = computeHmacSHA1(serverKey, authMessage);
-
- String finalMessageWithProof = clientFinalMessageWithoutProof
- + ",p=" + DatatypeConverter.printBase64Binary(clientProof);
- return finalMessageWithProof.getBytes();
- }
- catch (UnsupportedEncodingException e)
- {
- throw new SaslException(e.getMessage(), e);
- }
- catch (IllegalArgumentException e)
- {
- throw new SaslException(e.getMessage(), e);
- }
- catch (UnsupportedCallbackException e)
- {
- throw new SaslException(e.getMessage(), e);
- }
- catch (IOException e)
- {
- throw new SaslException(e.getMessage(), e);
- }
- catch (NoSuchAlgorithmException e)
- {
- throw new SaslException(e.getMessage(), e);
- }
- }
-
- private byte[] computeHmacSHA1(final byte[] key, final String string)
- throws SaslException, UnsupportedEncodingException
- {
- Mac mac = createSha1Hmac(key);
- mac.update(string.getBytes(ASCII));
- return mac.doFinal();
- }
-
- private byte[] generateSaltedPassword(final byte[] passwordBytes) throws SaslException
- {
- Mac mac = createSha1Hmac(passwordBytes);
-
- mac.update(_salt);
- mac.update(INT_1);
- byte[] result = mac.doFinal();
-
- byte[] previous = null;
- for(int i = 1; i < _iterationCount; i++)
- {
- mac.update(previous != null? previous: result);
- previous = mac.doFinal();
- for(int x = 0; x < result.length; x++)
- {
- result[x] ^= previous[x];
- }
- }
-
- return result;
- }
-
- private Mac createSha1Hmac(final byte[] keyBytes)
- throws SaslException
- {
- try
- {
- SecretKeySpec key = new SecretKeySpec(keyBytes, "HmacSHA1");
- Mac mac = Mac.getInstance("HmacSHA1");
- mac.init(key);
- return mac;
- }
- catch (NoSuchAlgorithmException e)
- {
- throw new SaslException(e.getMessage(), e);
- }
- catch (InvalidKeyException e)
- {
- throw new SaslException(e.getMessage(), e);
- }
- }
-
-
- private byte[] initialResponse() throws SaslException
- {
- try
- {
- StringBuffer buf = new StringBuffer("n=");
- NameCallback nameCallback = new NameCallback("Username?");
- _callbackHandler.handle(new Callback[] { nameCallback });
- _username = nameCallback.getName();
- buf.append(saslPrep(_username));
- buf.append(",r=");
- buf.append(_clientNonce);
- _clientFirstMessageBare = buf.toString();
- return (GS2_HEADER + _clientFirstMessageBare).getBytes(ASCII);
- }
- catch (UnsupportedCallbackException e)
- {
- throw new SaslException(e.getMessage(), e);
- }
- catch (IOException e)
- {
- throw new SaslException(e.getMessage(), e);
- }
- }
-
- private String saslPrep(String name) throws SaslException
- {
- // TODO - a real implementation of SaslPrep
-
- if(!ASCII.newEncoder().canEncode(name))
- {
- throw new SaslException("Can only encode names and passwords which are restricted to ASCII characters");
- }
-
- name = name.replace("=", "=3D");
- name = name.replace(",", "=2C");
- return name;
- }
-
- @Override
- public boolean isComplete()
- {
- return _state == State.COMPLETE;
- }
-
- @Override
- public byte[] unwrap(final byte[] incoming, final int offset, final int len) throws SaslException
- {
- throw new IllegalStateException("No security layer supported");
- }
-
- @Override
- public byte[] wrap(final byte[] outgoing, final int offset, final int len) throws SaslException
- {
- throw new IllegalStateException("No security layer supported");
+ super(cbh, MECHANISM, "SHA-1", "HmacSHA1");
}
-
- @Override
- public Object getNegotiatedProperty(final String propName)
- {
- return null;
- }
-
- @Override
- public void dispose() throws SaslException
- {
-
- }
-
}
diff --git a/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA1SaslClientFactory.java b/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA1SaslClientFactory.java
index 8a54abbc04..59ef236bde 100644
--- a/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA1SaslClientFactory.java
+++ b/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA1SaslClientFactory.java
@@ -20,14 +20,16 @@
*/
package org.apache.qpid.client.security.scram;
+import java.util.Map;
+
import javax.security.auth.callback.CallbackHandler;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslClientFactory;
import javax.security.sasl.SaslException;
-import java.util.Map;
public class ScramSHA1SaslClientFactory implements SaslClientFactory
{
+
@Override
public SaslClient createSaslClient(final String[] mechanisms,
final String authorizationId,
diff --git a/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA256SaslClient.java b/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA256SaslClient.java
new file mode 100644
index 0000000000..8779c36f0d
--- /dev/null
+++ b/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA256SaslClient.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.client.security.scram;
+
+import javax.security.auth.callback.CallbackHandler;
+
+public class ScramSHA256SaslClient extends AbstractScramSaslClient
+{
+
+ public static final String MECHANISM = "SCRAM-SHA-256";
+
+ public ScramSHA256SaslClient(final CallbackHandler cbh)
+ {
+ super(cbh, MECHANISM, "SHA-256", "HmacSHA256");
+ }
+}
diff --git a/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA256SaslClientFactory.java b/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA256SaslClientFactory.java
new file mode 100644
index 0000000000..fff762f8ba
--- /dev/null
+++ b/java/client/src/main/java/org/apache/qpid/client/security/scram/ScramSHA256SaslClientFactory.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.client.security.scram;
+
+import java.util.Map;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslClientFactory;
+import javax.security.sasl.SaslException;
+
+public class ScramSHA256SaslClientFactory implements SaslClientFactory
+{
+
+ @Override
+ public SaslClient createSaslClient(final String[] mechanisms,
+ final String authorizationId,
+ final String protocol,
+ final String serverName,
+ final Map<String, ?> props,
+ final CallbackHandler cbh) throws SaslException
+ {
+ for (int i = 0; i < mechanisms.length; i++)
+ {
+ if (mechanisms[i].equals(ScramSHA256SaslClient.MECHANISM))
+ {
+ if (cbh == null)
+ {
+ throw new SaslException("CallbackHandler must not be null");
+ }
+ return new ScramSHA256SaslClient(cbh);
+ }
+
+ }
+ return null;
+ }
+
+ @Override
+ public String[] getMechanismNames(final Map<String, ?> props)
+ {
+ return new String[0];
+ }
+}