activemq-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From clebertsuco...@apache.org
Subject [2/5] activemq-artemis git commit: ARTEMIS-786 Store user's password in hash form by default - user passwords for PropertiesLoginModule stored using PBKDF2 algothrim by default - implements cli user command to help create and manage user/roles
Date Wed, 02 Nov 2016 19:53:15 GMT
ARTEMIS-786 Store user's password in hash form by default
  - user passwords for PropertiesLoginModule stored using PBKDF2 algothrim
    by default
  - implements cli user command to help create and manage user/roles
  - adds a mask cli command to mask passwords


Project: http://git-wip-us.apache.org/repos/asf/activemq-artemis/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq-artemis/commit/cd7b8389
Tree: http://git-wip-us.apache.org/repos/asf/activemq-artemis/tree/cd7b8389
Diff: http://git-wip-us.apache.org/repos/asf/activemq-artemis/diff/cd7b8389

Branch: refs/heads/master
Commit: cd7b838952dcc654527cb2d2cdd00b3d0f269bd1
Parents: 8b57516
Author: Howard Gao <howard.gao@gmail.com>
Authored: Thu Oct 27 11:06:24 2016 +0800
Committer: Clebert Suconic <clebertsuconic@apache.org>
Committed: Wed Nov 2 14:59:00 2016 -0400

----------------------------------------------------------------------
 artemis-cli/pom.xml                             |  12 +
 .../apache/activemq/artemis/cli/Artemis.java    |  10 +-
 .../activemq/artemis/cli/commands/Create.java   |   5 +-
 .../activemq/artemis/cli/commands/Mask.java     | 101 +++++
 .../artemis/cli/commands/user/AddUser.java      |  62 +++
 .../artemis/cli/commands/user/HelpUser.java     |  55 +++
 .../artemis/cli/commands/user/ListUser.java     |  38 ++
 .../artemis/cli/commands/user/RemoveUser.java   |  36 ++
 .../artemis/cli/commands/user/ResetUser.java    |  65 +++
 .../artemis/cli/commands/user/UserAction.java   | 107 +++++
 .../artemis/cli/commands/util/HashUtil.java     |  41 ++
 .../artemis/util/FileBasedSecStoreConfig.java   | 222 +++++++++++
 .../cli/commands/etc/artemis-roles.properties   |   3 +-
 .../cli/commands/etc/artemis-users.properties   |   3 +-
 .../apache/activemq/cli/test/ArtemisTest.java   | 391 +++++++++++++++++--
 .../activemq/cli/test/TestActionContext.java    |  50 +++
 .../apache/activemq/artemis/utils/ByteUtil.java |   8 +
 .../utils/DefaultSensitiveStringCodec.java      | 212 ++++++++--
 .../activemq/artemis/utils/HashProcessor.java   |  41 ++
 .../activemq/artemis/utils/NoHashProcessor.java |  35 ++
 .../artemis/utils/PasswordMaskingUtil.java      |  70 +++-
 .../artemis/utils/SecureHashProcessor.java      |  44 +++
 .../artemis/utils/SensitiveDataCodec.java       |   4 +-
 .../utils/DefaultSensitiveStringCodecTest.java  |  77 ++++
 .../artemis/utils/HashProcessorTest.java        |  66 ++++
 artemis-distribution/src/main/assembly/dep.xml  |   2 +
 .../security/jaas/PropertiesLoginModule.java    |  24 +-
 docs/user-manual/en/configuration-index.md      |  23 +-
 28 files changed, 1698 insertions(+), 109 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/pom.xml
----------------------------------------------------------------------
diff --git a/artemis-cli/pom.xml b/artemis-cli/pom.xml
index e8c8358..0589955 100644
--- a/artemis-cli/pom.xml
+++ b/artemis-cli/pom.xml
@@ -30,6 +30,8 @@
    <properties>
       <activemq.basedir>${project.basedir}/..</activemq.basedir>
       <winsw.version>1.18</winsw.version>
+      <commons.config.version>2.1</commons.config.version>
+      <commons.lang.version>3.0</commons.lang.version>
    </properties>
 
    <dependencies>
@@ -68,6 +70,16 @@
          <artifactId>airline</artifactId>
       </dependency>
       <dependency>
+         <groupId>org.apache.commons</groupId>
+         <artifactId>commons-configuration2</artifactId>
+         <version>${commons.config.version}</version>
+      </dependency>
+      <dependency>
+         <groupId>org.apache.commons</groupId>
+         <artifactId>commons-lang3</artifactId>
+         <version>${commons.lang.version}</version>
+      </dependency>
+      <dependency>
         <groupId>com.sun.winsw</groupId>
         <artifactId>winsw</artifactId>
         <version>${winsw.version}</version>

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java
index 16dcd03..17c4457 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/Artemis.java
@@ -27,6 +27,7 @@ import org.apache.activemq.artemis.cli.commands.ActionContext;
 import org.apache.activemq.artemis.cli.commands.Create;
 import org.apache.activemq.artemis.cli.commands.HelpAction;
 import org.apache.activemq.artemis.cli.commands.Kill;
+import org.apache.activemq.artemis.cli.commands.Mask;
 import org.apache.activemq.artemis.cli.commands.Run;
 import org.apache.activemq.artemis.cli.commands.Stop;
 import org.apache.activemq.artemis.cli.commands.destination.CreateDestination;
@@ -42,6 +43,11 @@ import org.apache.activemq.artemis.cli.commands.tools.HelpData;
 import org.apache.activemq.artemis.cli.commands.tools.PrintData;
 import org.apache.activemq.artemis.cli.commands.tools.XmlDataExporter;
 import org.apache.activemq.artemis.cli.commands.tools.XmlDataImporter;
+import org.apache.activemq.artemis.cli.commands.user.AddUser;
+import org.apache.activemq.artemis.cli.commands.user.HelpUser;
+import org.apache.activemq.artemis.cli.commands.user.ListUser;
+import org.apache.activemq.artemis.cli.commands.user.RemoveUser;
+import org.apache.activemq.artemis.cli.commands.user.ResetUser;
 
 /**
  * Artemis is the main CLI entry point for managing/running a broker.
@@ -120,7 +126,7 @@ public class Artemis {
 
    private static Cli.CliBuilder<Action> builder(File artemisInstance) {
       String instance = artemisInstance != null ? artemisInstance.getAbsolutePath() : System.getProperty("artemis.instance");
-      Cli.CliBuilder<Action> builder = Cli.<Action>builder("artemis").withDescription("ActiveMQ Artemis Command Line").withCommand(HelpAction.class).withCommand(Producer.class).withCommand(Consumer.class).withCommand(Browse.class).withDefaultCommand(HelpAction.class);
+      Cli.CliBuilder<Action> builder = Cli.<Action>builder("artemis").withDescription("ActiveMQ Artemis Command Line").withCommand(HelpAction.class).withCommand(Producer.class).withCommand(Consumer.class).withCommand(Browse.class).withCommand(Mask.class).withDefaultCommand(HelpAction.class);
 
       builder.withGroup("destination").withDescription("Destination tools group (create|delete) (example ./artemis destination create)").
          withDefaultCommand(HelpDestination.class).withCommands(CreateDestination.class, DeleteDestination.class);
@@ -128,6 +134,8 @@ public class Artemis {
       if (instance != null) {
          builder.withGroup("data").withDescription("data tools group (print|exp|imp|exp|encode|decode|compact) (example ./artemis data print)").
             withDefaultCommand(HelpData.class).withCommands(PrintData.class, XmlDataExporter.class, XmlDataImporter.class, DecodeJournal.class, EncodeJournal.class, CompactJournal.class);
+         builder.withGroup("user").withDescription("default file-based user management (add|rm|list|reset) (example ./artemis user list)").
+                 withDefaultCommand(HelpUser.class).withCommands(ListUser.class, AddUser.class, RemoveUser.class, ResetUser.class);
          builder = builder.withCommands(Run.class, Stop.class, Kill.class);
       } else {
          builder.withGroup("data").withDescription("data tools group (print) (example ./artemis data print)").

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
index be788cd..5bd55e9 100644
--- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Create.java
@@ -38,6 +38,7 @@ import io.airlift.airline.Arguments;
 import io.airlift.airline.Command;
 import io.airlift.airline.Option;
 import org.apache.activemq.artemis.cli.CLIException;
+import org.apache.activemq.artemis.cli.commands.util.HashUtil;
 import org.apache.activemq.artemis.cli.commands.util.SyncCalculation;
 import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
 import org.apache.activemq.artemis.jlibaio.LibaioContext;
@@ -415,9 +416,11 @@ public class Create extends InputAbstract {
    public String getPassword() {
 
       if (password == null) {
-         this.password = inputPassword("--password", "Please provide the default password:", "admin");
+         password = inputPassword("--password", "Please provide the default password:", "admin");
       }
 
+      password = HashUtil.tryHash(context, password);
+
       return password;
    }
 

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Mask.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Mask.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Mask.java
new file mode 100644
index 0000000..7b845a2
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/Mask.java
@@ -0,0 +1,101 @@
+/*
+ * 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.activemq.artemis.cli.commands;
+
+import io.airlift.airline.Arguments;
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
+import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+@Command(name = "mask", description = "mask a password and print it out")
+public class Mask implements Action {
+
+   @Arguments(description = "The password to be masked", required = true)
+   String password;
+
+   @Option(name = "--hash", description = "whether to use hash (one-way), default false")
+   boolean hash = false;
+
+   @Option(name = "--key", description = "the key (Blowfish) to mask a password")
+   String key;
+
+   private DefaultSensitiveStringCodec codec;
+
+   @Override
+   public Object execute(ActionContext context) throws Exception {
+      Map<String, String> params = new HashMap<>();
+
+      if (hash) {
+         params.put(DefaultSensitiveStringCodec.ALGORITHM, DefaultSensitiveStringCodec.ONE_WAY);
+      }
+
+      if (key != null) {
+         if (hash) {
+            context.out.println("Option --key ignored in case of hashing");
+         } else {
+            params.put(DefaultSensitiveStringCodec.BLOWFISH_KEY, key);
+         }
+      }
+
+      codec = PasswordMaskingUtil.getDefaultCodec();
+      codec.init(params);
+
+      String masked = codec.encode(password);
+      context.out.println("result: " + masked);
+      return masked;
+   }
+
+   @Override
+   public boolean isVerbose() {
+      return false;
+   }
+
+   @Override
+   public void setHomeValues(File brokerHome, File brokerInstance) {
+   }
+
+   @Override
+   public String getBrokerInstance() {
+      return null;
+   }
+
+   @Override
+   public String getBrokerHome() {
+      return null;
+   }
+
+   public void setPassword(String password) {
+      this.password = password;
+   }
+
+   public void setHash(boolean hash) {
+      this.hash = hash;
+   }
+
+   public void setKey(String key) {
+      this.key = key;
+   }
+
+   public DefaultSensitiveStringCodec getCodec() {
+      return codec;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/AddUser.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/AddUser.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/AddUser.java
new file mode 100644
index 0000000..cbb8f60
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/AddUser.java
@@ -0,0 +1,62 @@
+/*
+ * 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.activemq.artemis.cli.commands.user;
+
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.activemq.artemis.cli.commands.ActionContext;
+import org.apache.activemq.artemis.cli.commands.util.HashUtil;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Adding a new user, example:
+ * ./artemis user add --username guest --role admin --password ***
+ */
+@Command(name = "add", description = "Add a new user")
+public class AddUser extends UserAction {
+
+   @Option(name = "--password", description = "the password (Default: input)")
+   String password;
+
+   @Option(name = "--role", description = "user's role(s), comma separated", required = true)
+   String role;
+
+   @Option(name = "--plaintext", description = "using plaintext (Default false)")
+   boolean plaintext = false;
+
+   @Override
+   public Object execute(ActionContext context) throws Exception {
+      super.execute(context);
+
+      if (password == null) {
+         password = inputPassword("--password", "Please provide the password:", null);
+      }
+
+      String hash = plaintext ? password : HashUtil.tryHash(context, password);
+      add(hash, StringUtils.split(role, ","));
+
+      return null;
+   }
+
+   public void setPassword(String password) {
+      this.password = password;
+   }
+
+   public void setRole(String role) {
+      this.role = role;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/HelpUser.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/HelpUser.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/HelpUser.java
new file mode 100644
index 0000000..7a898be
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/HelpUser.java
@@ -0,0 +1,55 @@
+/*
+ * 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.activemq.artemis.cli.commands.user;
+
+import io.airlift.airline.Help;
+import org.apache.activemq.artemis.cli.commands.Action;
+import org.apache.activemq.artemis.cli.commands.ActionContext;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class HelpUser extends Help implements Action {
+
+   @Override
+   public boolean isVerbose() {
+      return false;
+   }
+
+   @Override
+   public void setHomeValues(File brokerHome, File brokerInstance) {
+   }
+
+   @Override
+   public String getBrokerInstance() {
+      return null;
+   }
+
+   @Override
+   public String getBrokerHome() {
+      return null;
+   }
+
+   @Override
+   public Object execute(ActionContext context) throws Exception {
+      List<String> commands = new ArrayList<>(1);
+      commands.add("user");
+      help(global, commands);
+      return null;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ListUser.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ListUser.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ListUser.java
new file mode 100644
index 0000000..cb3ff39
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ListUser.java
@@ -0,0 +1,38 @@
+/*
+ * 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.activemq.artemis.cli.commands.user;
+
+import io.airlift.airline.Command;
+import org.apache.activemq.artemis.cli.commands.ActionContext;
+
+/**
+ * list existing users, example:
+ * ./artemis user list --username guest
+ */
+@Command(name = "list", description = "List existing user(s)")
+public class ListUser extends UserAction {
+
+   @Override
+   public Object execute(ActionContext context) throws Exception {
+      super.execute(context);
+
+      list();
+
+      return null;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/RemoveUser.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/RemoveUser.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/RemoveUser.java
new file mode 100644
index 0000000..172a76d
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/RemoveUser.java
@@ -0,0 +1,36 @@
+/*
+ * 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.activemq.artemis.cli.commands.user;
+
+import io.airlift.airline.Command;
+import org.apache.activemq.artemis.cli.commands.ActionContext;
+
+/**
+ * Remove a user, example:
+ * ./artemis user rm --username guest
+ */
+@Command(name = "rm", description = "Remove an existing user")
+public class RemoveUser extends UserAction {
+
+   @Override
+   public Object execute(ActionContext context) throws Exception {
+      super.execute(context);
+      remove();
+      return null;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ResetUser.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ResetUser.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ResetUser.java
new file mode 100644
index 0000000..27da6c7
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/ResetUser.java
@@ -0,0 +1,65 @@
+/*
+ * 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.activemq.artemis.cli.commands.user;
+
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
+import org.apache.activemq.artemis.cli.commands.ActionContext;
+import org.apache.activemq.artemis.cli.commands.util.HashUtil;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Reset a user's password or roles, example:
+ * ./artemis user reset --username guest --role admin --password ***
+ */
+@Command(name = "reset", description = "Reset user's password or roles")
+public class ResetUser extends UserAction {
+
+   @Option(name = "--password", description = "the password (Default: input)")
+   String password;
+
+   @Option(name = "--role", description = "user's role(s), comma separated")
+   String role;
+
+   @Option(name = "--plaintext", description = "using plaintext (Default false)")
+   boolean plaintext = false;
+
+   @Override
+   public Object execute(ActionContext context) throws Exception {
+      super.execute(context);
+
+      if (password != null) {
+         password = plaintext ? password : HashUtil.tryHash(context, password);
+      }
+
+      String[] roles = null;
+      if (role != null) {
+         roles = StringUtils.split(role, ",");
+      }
+
+      reset(password, roles);
+      return null;
+   }
+
+   public void setPassword(String password) {
+      this.password = password;
+   }
+
+   public void setRole(String role) {
+      this.role = role;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/UserAction.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/UserAction.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/UserAction.java
new file mode 100644
index 0000000..918338e
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/user/UserAction.java
@@ -0,0 +1,107 @@
+/*
+ * 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.activemq.artemis.cli.commands.user;
+
+import io.airlift.airline.Option;
+import org.apache.activemq.artemis.cli.commands.InputAbstract;
+import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule;
+import org.apache.activemq.artemis.util.FileBasedSecStoreConfig;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import java.io.File;
+import java.util.List;
+
+import static org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule.ROLE_FILE_PROP_NAME;
+import static org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule.USER_FILE_PROP_NAME;
+
+public abstract class UserAction extends InputAbstract {
+
+   @Option(name = "--user", description = "The user name")
+   String username = null;
+
+   /**
+    * Adding a new user
+    * @param hash the password
+    * @param role the role
+    * @throws IllegalArgumentException if user exists
+    */
+   protected void add(String hash, String... role) throws Exception {
+      FileBasedSecStoreConfig config = getConfiguration();
+      config.addNewUser(username, hash, role);
+      config.save();
+      context.out.println("User added successfully.");
+   }
+
+   /**
+    * list a single user or all users
+    * if username is not specified
+    */
+   protected void list() throws Exception {
+      FileBasedSecStoreConfig config = getConfiguration();
+      List<String> result = config.listUser(username);
+      for (String str : result) {
+         context.out.println(str);
+      }
+   }
+
+   protected void remove() throws Exception {
+      FileBasedSecStoreConfig config = getConfiguration();
+      config.removeUser(username);
+      config.save();
+      context.out.println("User removed.");
+   }
+
+   protected void reset(String password, String[] roles) throws Exception {
+      if (password == null && roles == null) {
+         context.err.println("Nothing to update.");
+         return;
+      }
+      FileBasedSecStoreConfig config = getConfiguration();
+      config.updateUser(username, password, roles);
+      config.save();
+      context.out.println("User updated");
+   }
+
+   private FileBasedSecStoreConfig getConfiguration() throws Exception {
+
+      Configuration securityConfig = Configuration.getConfiguration();
+      AppConfigurationEntry[] entries = securityConfig.getAppConfigurationEntry("activemq");
+
+      for (AppConfigurationEntry entry : entries) {
+         if (entry.getLoginModuleName().equals(PropertiesLoginModule.class.getName())) {
+            String userFileName = (String) entry.getOptions().get(USER_FILE_PROP_NAME);
+            String roleFileName = (String) entry.getOptions().get(ROLE_FILE_PROP_NAME);
+
+            File etcDir = new File(getBrokerInstance(), "etc");
+            File userFile = new File(etcDir, userFileName);
+            File roleFile = new File(etcDir, roleFileName);
+
+            if (!userFile.exists() || !roleFile.exists()) {
+               throw new IllegalArgumentException("Couldn't find user file or role file!");
+            }
+
+            return new FileBasedSecStoreConfig(userFile, roleFile);
+         }
+      }
+      throw new IllegalArgumentException("Failed to load security file");
+   }
+
+   public void setUsername(String username) {
+      this.username = username;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/HashUtil.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/HashUtil.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/HashUtil.java
new file mode 100644
index 0000000..67b9e44
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/util/HashUtil.java
@@ -0,0 +1,41 @@
+/*
+ * 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.activemq.artemis.cli.commands.util;
+
+import org.apache.activemq.artemis.cli.commands.ActionContext;
+import org.apache.activemq.artemis.utils.HashProcessor;
+import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
+
+public class HashUtil {
+
+   private static final HashProcessor HASH_PROCESSOR = PasswordMaskingUtil.getHashProcessor();
+
+   //calculate the hash for plaintext.
+   //any exception will cause plaintext returned unchanged.
+   public static String tryHash(ActionContext context, String plaintext) {
+
+      try {
+         String hash = HASH_PROCESSOR.hash(plaintext);
+         return hash;
+      } catch (Exception e) {
+         context.err.println("Warning: Failed to calculate hash value for password using " + HASH_PROCESSOR);
+         context.err.println("Reason: " + e.getMessage());
+         e.printStackTrace();
+      }
+      return plaintext;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/java/org/apache/activemq/artemis/util/FileBasedSecStoreConfig.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/util/FileBasedSecStoreConfig.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/util/FileBasedSecStoreConfig.java
new file mode 100644
index 0000000..c3f30b3
--- /dev/null
+++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/util/FileBasedSecStoreConfig.java
@@ -0,0 +1,222 @@
+/*
+ * 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.activemq.artemis.util;
+
+import org.apache.activemq.artemis.api.core.Pair;
+import org.apache.activemq.artemis.utils.StringUtil;
+import org.apache.commons.configuration2.PropertiesConfiguration;
+import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
+import org.apache.commons.configuration2.builder.fluent.Configurations;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class FileBasedSecStoreConfig {
+
+   private static final String LICENSE_HEADER =
+           "## ---------------------------------------------------------------------------\n" +
+           "## Licensed to the Apache Software Foundation (ASF) under one or more\n" +
+           "## contributor license agreements.  See the NOTICE file distributed with\n" +
+           "## this work for additional information regarding copyright ownership.\n" +
+           "## The ASF licenses this file to You under the Apache License, Version 2.0\n" +
+           "## (the \"License\"); you may not use this file except in compliance with\n" +
+           "## the License.  You may obtain a copy of the License at\n" +
+           "##\n" +
+           "## http://www.apache.org/licenses/LICENSE-2.0\n" +
+           "##\n" +
+           "## Unless required by applicable law or agreed to in writing, software\n" +
+           "## distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
+           "## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
+           "## See the License for the specific language governing permissions and\n" +
+           "## limitations under the License.\n" +
+           "## ---------------------------------------------------------------------------\n";
+   private FileBasedConfigurationBuilder<PropertiesConfiguration> userBuilder;
+   private FileBasedConfigurationBuilder<PropertiesConfiguration> roleBuilder;
+   private PropertiesConfiguration userConfig;
+   private PropertiesConfiguration roleConfig;
+
+   public FileBasedSecStoreConfig(File userFile, File roleFile) throws Exception {
+      Configurations configs = new Configurations();
+      userBuilder = configs.propertiesBuilder(userFile);
+      roleBuilder = configs.propertiesBuilder(roleFile);
+      userConfig = userBuilder.getConfiguration();
+      roleConfig = roleBuilder.getConfiguration();
+
+      String roleHeader = roleConfig.getLayout().getHeaderComment();
+      String userHeader = userConfig.getLayout().getHeaderComment();
+
+      if (userHeader == null) {
+         if (userConfig.isEmpty()) {
+            //clean and reset header
+            userConfig.clear();
+            userConfig.setHeader(LICENSE_HEADER);
+         }
+      }
+
+      if (roleHeader == null) {
+         if (roleConfig.isEmpty()) {
+            //clean and reset header
+            roleConfig.clear();
+            roleConfig.setHeader(LICENSE_HEADER);
+         }
+      }
+   }
+
+   public void addNewUser(String username, String hash, String... roles) throws Exception {
+      if (userConfig.getString(username) != null) {
+         throw new IllegalArgumentException("User already exist: " + username);
+      }
+      userConfig.addProperty(username, hash);
+      addRoles(username, roles);
+   }
+
+   public void save() throws Exception {
+      userBuilder.save();
+      roleBuilder.save();
+   }
+
+   public void removeUser(String username) throws Exception {
+      if (userConfig.getProperty(username) == null) {
+         throw new IllegalArgumentException("user " + username + " doesn't exist.");
+      }
+      userConfig.clearProperty(username);
+      removeRoles(username);
+   }
+
+   public List<String> listUser(String username) {
+      List<String> result = new ArrayList<>();
+      result.add("--- \"user\"(roles) ---\n");
+
+      int totalUsers = 0;
+      if (username != null) {
+         String roles = findRoles(username);
+         result.add("\"" + username + "\"(" + roles + ")");
+         totalUsers++;
+      } else {
+         Iterator<String> iter = userConfig.getKeys();
+         while (iter.hasNext()) {
+            String keyUser = iter.next();
+            String roles = findRoles(keyUser);
+            result.add("\"" + keyUser + "\"(" + roles + ")");
+            totalUsers++;
+         }
+      }
+      result.add("\n Total: " + totalUsers);
+      return result;
+   }
+
+   private String findRoles(String uname) {
+      Iterator<String> iter = roleConfig.getKeys();
+      StringBuilder builder = new StringBuilder();
+      boolean first = true;
+      while (iter.hasNext()) {
+         String role = iter.next();
+         List<String> names = roleConfig.getList(String.class, role);
+         for (String value : names) {
+            //each value may be a comma separated list
+            String[] items = value.split(",");
+            for (String item : items) {
+               if (item.equals(uname)) {
+                  if (!first) {
+                     builder.append(",");
+                  }
+                  builder.append(role);
+                  first = false;
+               }
+            }
+         }
+      }
+
+      return builder.toString();
+   }
+
+   public void updateUser(String username, String password, String[] roles) {
+      String oldPassword = (String) userConfig.getProperty(username);
+      if (oldPassword == null) {
+         throw new IllegalArgumentException("user " + username + " doesn't exist.");
+      }
+
+      if (password != null) {
+         userConfig.setProperty(username, password);
+      }
+
+      if (roles != null && roles.length > 0) {
+
+         removeRoles(username);
+         addRoles(username, roles);
+      }
+   }
+
+   private void addRoles(String username, String[] roles) {
+      for (String role : roles) {
+         List<String> users = roleConfig.getList(String.class, role);
+         if (users == null) {
+            users = new ArrayList<>();
+         }
+         users.add(username);
+         roleConfig.setProperty(role, StringUtil.joinStringList(users, ","));
+      }
+   }
+
+   private void removeRoles(String username) {
+
+      Iterator<String> iterKeys = roleConfig.getKeys();
+
+      List<Pair<String, List<String>>> updateMap = new ArrayList<>();
+      while (iterKeys.hasNext()) {
+         String theRole = iterKeys.next();
+
+         List<String> userList = roleConfig.getList(String.class, theRole);
+         List<String> newList = new ArrayList<>();
+
+         boolean roleChaned = false;
+         for (String value : userList) {
+            //each value may be comma separated.
+            List<String> update = new ArrayList<>();
+            String[] items = value.split(",");
+            boolean found = false;
+            for (String item : items) {
+               if (!item.equals(username)) {
+                  update.add(item);
+               } else {
+                  found = true;
+                  roleChaned = true;
+               }
+            }
+            if (found) {
+               if (update.size() > 0) {
+                  newList.add(StringUtil.joinStringList(update, ","));
+               }
+            }
+         }
+         if (roleChaned) {
+            updateMap.add(new Pair(theRole, newList));
+         }
+      }
+      //do update
+      Iterator<Pair<String, List<String>>> iterUpdate = updateMap.iterator();
+      while (iterUpdate.hasNext()) {
+         Pair<String, List<String>> entry = iterUpdate.next();
+         roleConfig.clearProperty(entry.getA());
+         if (entry.getB().size() > 0) {
+            roleConfig.addProperty(entry.getA(), entry.getB());
+         }
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles.properties
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles.properties b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles.properties
index c9443dd..74f4266 100644
--- a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles.properties
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-roles.properties
@@ -14,4 +14,5 @@
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
 ## ---------------------------------------------------------------------------
-${role}=${user}
\ No newline at end of file
+
+${role} = ${user}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-users.properties
----------------------------------------------------------------------
diff --git a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-users.properties b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-users.properties
index 81462f8..b437025 100644
--- a/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-users.properties
+++ b/artemis-cli/src/main/resources/org/apache/activemq/artemis/cli/commands/etc/artemis-users.properties
@@ -14,4 +14,5 @@
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
 ## ---------------------------------------------------------------------------
-${user}=${password}
\ No newline at end of file
+
+${user} = ${password}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
index 2359f1d..3d89aa8 100644
--- a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
+++ b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
@@ -26,6 +26,7 @@ import javax.xml.parsers.ParserConfigurationException;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.activemq.artemis.api.core.SimpleString;
@@ -35,16 +36,29 @@ import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
 import org.apache.activemq.artemis.api.core.client.ServerLocator;
 import org.apache.activemq.artemis.cli.Artemis;
 import org.apache.activemq.artemis.cli.CLIException;
+import org.apache.activemq.artemis.cli.commands.ActionContext;
 import org.apache.activemq.artemis.cli.commands.Create;
+import org.apache.activemq.artemis.cli.commands.Mask;
 import org.apache.activemq.artemis.cli.commands.Run;
 import org.apache.activemq.artemis.cli.commands.tools.LockAbstract;
+import org.apache.activemq.artemis.cli.commands.user.AddUser;
+import org.apache.activemq.artemis.cli.commands.user.ListUser;
+import org.apache.activemq.artemis.cli.commands.user.RemoveUser;
+import org.apache.activemq.artemis.cli.commands.user.ResetUser;
 import org.apache.activemq.artemis.cli.commands.util.SyncCalculation;
 import org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl;
 import org.apache.activemq.artemis.jlibaio.LibaioContext;
 import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
 import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
 import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoader;
+import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
 import org.apache.activemq.artemis.utils.ThreadLeakCheckRule;
+import org.apache.activemq.artemis.utils.HashProcessor;
+import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
+import org.apache.activemq.artemis.utils.StringUtil;
+import org.apache.commons.configuration2.PropertiesConfiguration;
+import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
+import org.apache.commons.configuration2.builder.fluent.Configurations;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -55,6 +69,11 @@ import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.xml.sax.SAXException;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 /**
  * Test to validate that the CLI doesn't throw improper exceptions when invoked.
  */
@@ -134,7 +153,7 @@ public class ArtemisTest {
       System.out.println("TotalAvg = " + totalAvg);
       long nanoTime = SyncCalculation.toNanos(totalAvg, writes);
       System.out.println("nanoTime avg = " + nanoTime);
-      Assert.assertEquals(0, LibaioContext.getTotalMaxIO());
+      assertEquals(0, LibaioContext.getTotalMaxIO());
 
    }
 
@@ -146,47 +165,47 @@ public class ArtemisTest {
       File instance1 = new File(temporaryFolder.getRoot(), "instance1");
       Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-fsync");
       File bootstrapFile = new File(new File(instance1, "etc"), "bootstrap.xml");
-      Assert.assertTrue(bootstrapFile.exists());
+      assertTrue(bootstrapFile.exists());
       Document config = parseXml(bootstrapFile);
       Element webElem = (Element) config.getElementsByTagName("web").item(0);
 
       String bindAttr = webElem.getAttribute("bind");
       String bindStr = "http://localhost:" + Create.HTTP_PORT;
 
-      Assert.assertEquals(bindAttr, bindStr);
+      assertEquals(bindAttr, bindStr);
       //no any of those
-      Assert.assertFalse(webElem.hasAttribute("keyStorePath"));
-      Assert.assertFalse(webElem.hasAttribute("keyStorePassword"));
-      Assert.assertFalse(webElem.hasAttribute("clientAuth"));
-      Assert.assertFalse(webElem.hasAttribute("trustStorePath"));
-      Assert.assertFalse(webElem.hasAttribute("trustStorePassword"));
+      assertFalse(webElem.hasAttribute("keyStorePath"));
+      assertFalse(webElem.hasAttribute("keyStorePassword"));
+      assertFalse(webElem.hasAttribute("clientAuth"));
+      assertFalse(webElem.hasAttribute("trustStorePath"));
+      assertFalse(webElem.hasAttribute("trustStorePassword"));
 
       //instance2: https
       File instance2 = new File(temporaryFolder.getRoot(), "instance2");
       Artemis.main("create", instance2.getAbsolutePath(), "--silent", "--ssl-key", "etc/keystore", "--ssl-key-password", "password1", "--no-fsync");
       bootstrapFile = new File(new File(instance2, "etc"), "bootstrap.xml");
-      Assert.assertTrue(bootstrapFile.exists());
+      assertTrue(bootstrapFile.exists());
       config = parseXml(bootstrapFile);
       webElem = (Element) config.getElementsByTagName("web").item(0);
 
       bindAttr = webElem.getAttribute("bind");
       bindStr = "https://localhost:" + Create.HTTP_PORT;
-      Assert.assertEquals(bindAttr, bindStr);
+      assertEquals(bindAttr, bindStr);
 
       String keyStr = webElem.getAttribute("keyStorePath");
-      Assert.assertEquals("etc/keystore", keyStr);
+      assertEquals("etc/keystore", keyStr);
       String keyPass = webElem.getAttribute("keyStorePassword");
-      Assert.assertEquals("password1", keyPass);
+      assertEquals("password1", keyPass);
 
-      Assert.assertFalse(webElem.hasAttribute("clientAuth"));
-      Assert.assertFalse(webElem.hasAttribute("trustStorePath"));
-      Assert.assertFalse(webElem.hasAttribute("trustStorePassword"));
+      assertFalse(webElem.hasAttribute("clientAuth"));
+      assertFalse(webElem.hasAttribute("trustStorePath"));
+      assertFalse(webElem.hasAttribute("trustStorePassword"));
 
       //instance3: https with clientAuth
       File instance3 = new File(temporaryFolder.getRoot(), "instance3");
       Artemis.main("create", instance3.getAbsolutePath(), "--silent", "--ssl-key", "etc/keystore", "--ssl-key-password", "password1", "--use-client-auth", "--ssl-trust", "etc/truststore", "--ssl-trust-password", "password2", "--no-fsync");
       bootstrapFile = new File(new File(instance3, "etc"), "bootstrap.xml");
-      Assert.assertTrue(bootstrapFile.exists());
+      assertTrue(bootstrapFile.exists());
 
       byte[] contents = Files.readAllBytes(bootstrapFile.toPath());
       String cfgText = new String(contents);
@@ -197,19 +216,298 @@ public class ArtemisTest {
 
       bindAttr = webElem.getAttribute("bind");
       bindStr = "https://localhost:" + Create.HTTP_PORT;
-      Assert.assertEquals(bindAttr, bindStr);
+      assertEquals(bindAttr, bindStr);
 
       keyStr = webElem.getAttribute("keyStorePath");
-      Assert.assertEquals("etc/keystore", keyStr);
+      assertEquals("etc/keystore", keyStr);
       keyPass = webElem.getAttribute("keyStorePassword");
-      Assert.assertEquals("password1", keyPass);
+      assertEquals("password1", keyPass);
 
       String clientAuthAttr = webElem.getAttribute("clientAuth");
-      Assert.assertEquals("true", clientAuthAttr);
+      assertEquals("true", clientAuthAttr);
       String trustPathAttr = webElem.getAttribute("trustStorePath");
-      Assert.assertEquals("etc/truststore", trustPathAttr);
+      assertEquals("etc/truststore", trustPathAttr);
       String trustPass = webElem.getAttribute("trustStorePassword");
-      Assert.assertEquals("password2", trustPass);
+      assertEquals("password2", trustPass);
+   }
+
+   @Test
+   public void testUserCommand() throws Exception {
+      Run.setEmbedded(true);
+      File instance1 = new File(temporaryFolder.getRoot(), "instance_user");
+      System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config");
+      Artemis.main("create", instance1.getAbsolutePath(), "--silent");
+      System.setProperty("artemis.instance", instance1.getAbsolutePath());
+
+      File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties");
+      File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties");
+
+      ListUser listCmd = new ListUser();
+      TestActionContext context = new TestActionContext();
+      listCmd.execute(context);
+
+      String result = context.getStdout();
+      System.out.println("output1:\n" + result);
+
+      //default only one user admin with role amq
+      assertTrue(result.contains("\"admin\"(amq)"));
+      checkRole("admin", roleFile, "amq");
+
+      //add a simple user
+      AddUser addCmd = new AddUser();
+      addCmd.setUsername("guest");
+      addCmd.setPassword("guest123");
+      addCmd.setRole("admin");
+      addCmd.execute(new TestActionContext());
+
+      //verify use list cmd
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output2:\n" + result);
+
+      assertTrue(result.contains("\"admin\"(amq)"));
+      assertTrue(result.contains("\"guest\"(admin)"));
+
+      checkRole("guest", roleFile, "admin");
+      assertTrue(checkPassword("guest", "guest123", userFile));
+
+      //add a user with 2 roles
+      addCmd = new AddUser();
+      addCmd.setUsername("scott");
+      addCmd.setPassword("tiger");
+      addCmd.setRole("admin,operator");
+      addCmd.execute(ActionContext.system());
+
+      //verify
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output3:\n" + result);
+
+      assertTrue(result.contains("\"admin\"(amq)"));
+      assertTrue(result.contains("\"guest\"(admin)"));
+      assertTrue(result.contains("\"scott\"(admin,operator)"));
+
+      checkRole("scott", roleFile, "admin", "operator");
+      assertTrue(checkPassword("scott", "tiger", userFile));
+
+      //add an existing user
+      addCmd = new AddUser();
+      addCmd.setUsername("scott");
+      addCmd.setPassword("password");
+      addCmd.setRole("visitor");
+      try {
+         addCmd.execute(ActionContext.system());
+         fail("should throw an exception if adding a existing user");
+      } catch (IllegalArgumentException expected) {
+      }
+
+      //check existing users are intact
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output4:\n" + result);
+
+      assertTrue(result.contains("\"admin\"(amq)"));
+      assertTrue(result.contains("\"guest\"(admin)"));
+      assertTrue(result.contains("\"scott\"(admin,operator)"));
+
+      //remove a user
+      RemoveUser rmCmd = new RemoveUser();
+      rmCmd.setUsername("guest");
+      rmCmd.execute(ActionContext.system());
+
+      //check
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output5:\n" + result);
+
+      assertTrue(result.contains("\"admin\"(amq)"));
+      assertFalse(result.contains("\"guest\"(admin)"));
+      assertTrue(result.contains("\"scott\"(admin,operator)") || result.contains("\"scott\"(operator,admin)"));
+      assertTrue(result.contains("Total: 2"));
+
+      //remove another
+      rmCmd = new RemoveUser();
+      rmCmd.setUsername("scott");
+      rmCmd.execute(ActionContext.system());
+
+      //check
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output6:\n" + result);
+
+      assertTrue(result.contains("\"admin\"(amq)"));
+      assertFalse(result.contains("\"guest\"(admin)"));
+      assertFalse(result.contains("\"scott\"(admin,operator)") || result.contains("\"scott\"(operator,admin)"));
+      assertTrue(result.contains("Total: 1"));
+
+      //remove non-exist
+      rmCmd = new RemoveUser();
+      rmCmd.setUsername("alien");
+      try {
+         rmCmd.execute(ActionContext.system());
+         fail("should throw exception when removing a non-existing user");
+      } catch (IllegalArgumentException expected) {
+      }
+
+      //check
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output7:\n" + result);
+      assertTrue(result.contains("\"admin\"(amq)"));
+      assertTrue(result.contains("Total: 1"));
+
+      //now remove last
+      rmCmd = new RemoveUser();
+      rmCmd.setUsername("admin");
+      rmCmd.execute(ActionContext.system());
+
+      //check
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output8:\n" + result);
+
+      assertTrue(result.contains("Total: 0"));
+   }
+
+   @Test
+   public void testUserCommandReset() throws Exception {
+      Run.setEmbedded(true);
+      File instance1 = new File(temporaryFolder.getRoot(), "instance_user");
+      System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config");
+      Artemis.main("create", instance1.getAbsolutePath(), "--silent");
+      System.setProperty("artemis.instance", instance1.getAbsolutePath());
+
+      File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties");
+      File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties");
+
+      ListUser listCmd = new ListUser();
+      TestActionContext context = new TestActionContext();
+      listCmd.execute(context);
+
+      String result = context.getStdout();
+      System.out.println("output1:\n" + result);
+
+      //default only one user admin with role amq
+      assertTrue(result.contains("\"admin\"(amq)"));
+
+      //remove a user
+      RemoveUser rmCmd = new RemoveUser();
+      rmCmd.setUsername("admin");
+      rmCmd.execute(ActionContext.system());
+
+      //check
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output8:\n" + result);
+
+      assertTrue(result.contains("Total: 0"));
+
+      //add some users
+      AddUser addCmd = new AddUser();
+      addCmd.setUsername("guest");
+      addCmd.setPassword("guest123");
+      addCmd.setRole("admin");
+      addCmd.execute(new TestActionContext());
+
+      addCmd.setUsername("user1");
+      addCmd.setPassword("password1");
+      addCmd.setRole("admin,manager");
+      addCmd.execute(new TestActionContext());
+      assertTrue(checkPassword("user1", "password1", userFile));
+
+      addCmd.setUsername("user2");
+      addCmd.setPassword("password2");
+      addCmd.setRole("admin,manager,master");
+      addCmd.execute(new TestActionContext());
+
+      addCmd.setUsername("user3");
+      addCmd.setPassword("password3");
+      addCmd.setRole("system,master");
+      addCmd.execute(new TestActionContext());
+
+      //verify use list cmd
+      context = new TestActionContext();
+      listCmd.execute(context);
+      result = context.getStdout();
+      System.out.println("output2:\n" + result);
+
+      assertTrue(result.contains("Total: 4"));
+      assertTrue(result.contains("\"guest\"(admin)"));
+      assertTrue(result.contains("\"user1\"(admin,manager)"));
+      assertTrue(result.contains("\"user2\"(admin,manager,master)"));
+      assertTrue(result.contains("\"user3\"(master,system)"));
+
+      checkRole("user1", roleFile, "admin", "manager");
+
+      //reset password
+      context = new TestActionContext();
+      ResetUser resetCommand = new ResetUser();
+      resetCommand.setUsername("user1");
+      resetCommand.setPassword("newpassword1");
+      resetCommand.execute(context);
+
+      checkRole("user1", roleFile, "admin", "manager");
+      assertFalse(checkPassword("user1", "password1", userFile));
+      assertTrue(checkPassword("user1", "newpassword1", userFile));
+
+      //reset role
+      resetCommand.setUsername("user2");
+      resetCommand.setRole("manager,master,operator");
+      resetCommand.execute(new TestActionContext());
+
+      checkRole("user2", roleFile, "manager", "master", "operator");
+
+      //reset both
+      resetCommand.setUsername("user3");
+      resetCommand.setPassword("newpassword3");
+      resetCommand.setRole("admin,system");
+      resetCommand.execute(new ActionContext());
+
+      checkRole("user3", roleFile, "admin", "system");
+      assertTrue(checkPassword("user3", "newpassword3", userFile));
+   }
+
+   @Test
+   public void testMaskCommand() throws Exception {
+
+      String password1 = "password";
+      String encrypt1 = "3a34fd21b82bf2a822fa49a8d8fa115d";
+      String newKey = "artemisfun";
+      String encrypt2 = "-2b8e3b47950b9b481a6f3100968e42e9";
+
+
+      TestActionContext context = new TestActionContext();
+      Mask mask = new Mask();
+      mask.setPassword(password1);
+
+      String result = (String) mask.execute(context);
+      System.out.println(context.getStdout());
+      assertEquals(encrypt1, result);
+
+      context = new TestActionContext();
+      mask = new Mask();
+      mask.setPassword(password1);
+      mask.setHash(true);
+      result = (String) mask.execute(context);
+      System.out.println(context.getStdout());
+      DefaultSensitiveStringCodec codec = mask.getCodec();
+      codec.verify(password1.toCharArray(), result);
+
+      context = new TestActionContext();
+      mask = new Mask();
+      mask.setPassword(password1);
+      mask.setKey(newKey);
+      result = (String) mask.execute(context);
+      System.out.println(context.getStdout());
+      assertEquals(encrypt2, result);
    }
 
    @Test
@@ -251,11 +549,11 @@ public class ArtemisTest {
               ClientSession coreSession = factory.createSession("admin", "admin", false, true, true, false, 0)) {
             for (String str : queues.split(",")) {
                ClientSession.QueueQuery queryResult = coreSession.queueQuery(SimpleString.toSimpleString("jms.queue." + str));
-               Assert.assertTrue("Couldn't find queue " + str, queryResult.isExists());
+               assertTrue("Couldn't find queue " + str, queryResult.isExists());
             }
             for (String str : topics.split(",")) {
                ClientSession.QueueQuery queryResult = coreSession.queueQuery(SimpleString.toSimpleString("jms.topic." + str));
-               Assert.assertTrue("Couldn't find topic " + str, queryResult.isExists());
+               assertTrue("Couldn't find topic " + str, queryResult.isExists());
             }
          }
 
@@ -266,8 +564,8 @@ public class ArtemisTest {
          }
          Artemis.internalExecute("data", "print", "--f");
 
-         Assert.assertEquals(Integer.valueOf(100), Artemis.internalExecute("producer", "--message-count", "100", "--verbose", "--user", "admin", "--password", "admin"));
-         Assert.assertEquals(Integer.valueOf(100), Artemis.internalExecute("consumer", "--verbose", "--break-on-null", "--receive-timeout", "100", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(100), Artemis.internalExecute("producer", "--message-count", "100", "--verbose", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(100), Artemis.internalExecute("consumer", "--verbose", "--break-on-null", "--receive-timeout", "100", "--user", "admin", "--password", "admin"));
 
          ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("tcp://localhost:61616");
          Connection connection = cf.createConnection("admin", "admin");
@@ -288,20 +586,20 @@ public class ArtemisTest {
          connection.close();
          cf.close();
 
-         Assert.assertEquals(Integer.valueOf(1), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--filter", "fruit='banana'", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(1), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--filter", "fruit='banana'", "--user", "admin", "--password", "admin"));
 
-         Assert.assertEquals(Integer.valueOf(100), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--filter", "fruit='orange'", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(100), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--filter", "fruit='orange'", "--user", "admin", "--password", "admin"));
 
-         Assert.assertEquals(Integer.valueOf(101), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(101), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--user", "admin", "--password", "admin"));
 
          // should only receive 10 messages on browse as I'm setting messageCount=10
-         Assert.assertEquals(Integer.valueOf(10), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--message-count", "10", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(10), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--message-count", "10", "--user", "admin", "--password", "admin"));
 
          // Nothing was consumed until here as it was only browsing, check it's receiving again
-         Assert.assertEquals(Integer.valueOf(1), Artemis.internalExecute("consumer", "--txt-size", "50", "--verbose", "--break-on-null", "--receive-timeout", "100", "--filter", "fruit='banana'", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(1), Artemis.internalExecute("consumer", "--txt-size", "50", "--verbose", "--break-on-null", "--receive-timeout", "100", "--filter", "fruit='banana'", "--user", "admin", "--password", "admin"));
 
          // Checking it was acked before
-         Assert.assertEquals(Integer.valueOf(100), Artemis.internalExecute("consumer", "--txt-size", "50", "--verbose", "--break-on-null", "--receive-timeout", "100", "--user", "admin", "--password", "admin"));
+         assertEquals(Integer.valueOf(100), Artemis.internalExecute("consumer", "--txt-size", "50", "--verbose", "--break-on-null", "--receive-timeout", "100", "--user", "admin", "--password", "admin"));
       } finally {
          stopServer();
       }
@@ -312,7 +610,7 @@ public class ArtemisTest {
          Artemis.main(args);
       } catch (Exception e) {
          e.printStackTrace();
-         Assert.fail("Exception caught " + e.getMessage());
+         fail("Exception caught " + e.getMessage());
       }
    }
 
@@ -322,8 +620,8 @@ public class ArtemisTest {
 
    private void stopServer() throws Exception {
       Artemis.internalExecute("stop");
-      Assert.assertTrue(Run.latchRunning.await(5, TimeUnit.SECONDS));
-      Assert.assertEquals(0, LibaioContext.getTotalMaxIO());
+      assertTrue(Run.latchRunning.await(5, TimeUnit.SECONDS));
+      assertEquals(0, LibaioContext.getTotalMaxIO());
    }
 
    private static Document parseXml(File xmlFile) throws ParserConfigurationException, IOException, SAXException {
@@ -332,4 +630,27 @@ public class ArtemisTest {
       return domBuilder.parse(xmlFile);
    }
 
+   private void checkRole(String user, File roleFile, String... roles) throws Exception {
+      Configurations configs = new Configurations();
+      FileBasedConfigurationBuilder<PropertiesConfiguration> roleBuilder = configs.propertiesBuilder(roleFile);
+      PropertiesConfiguration roleConfig = roleBuilder.getConfiguration();
+
+      for (String r : roles) {
+         String storedUsers = (String) roleConfig.getProperty(r);
+
+         System.out.println("users in role: " + r + " ; " + storedUsers);
+         List<String> userList = StringUtil.splitStringList(storedUsers, ",");
+         assertTrue(userList.contains(user));
+      }
+   }
+
+   private boolean checkPassword(String user, String password, File userFile) throws Exception {
+      Configurations configs = new Configurations();
+      FileBasedConfigurationBuilder<PropertiesConfiguration> userBuilder = configs.propertiesBuilder(userFile);
+      PropertiesConfiguration userConfig = userBuilder.getConfiguration();
+      String storedPassword = (String) userConfig.getProperty(user);
+      HashProcessor processor = PasswordMaskingUtil.getHashProcessor(storedPassword);
+      return processor.compare(password.toCharArray(), storedPassword);
+   }
+
 }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-cli/src/test/java/org/apache/activemq/cli/test/TestActionContext.java
----------------------------------------------------------------------
diff --git a/artemis-cli/src/test/java/org/apache/activemq/cli/test/TestActionContext.java b/artemis-cli/src/test/java/org/apache/activemq/cli/test/TestActionContext.java
new file mode 100644
index 0000000..0a2da11
--- /dev/null
+++ b/artemis-cli/src/test/java/org/apache/activemq/cli/test/TestActionContext.java
@@ -0,0 +1,50 @@
+/*
+ * 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.activemq.cli.test;
+
+import org.apache.activemq.artemis.cli.commands.ActionContext;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+public class TestActionContext extends ActionContext {
+
+   public ByteArrayOutputStream stdout;
+   public ByteArrayOutputStream stderr;
+   private int bufferSize;
+
+   public TestActionContext(int bufferSize) {
+      this.bufferSize = bufferSize;
+      this.stdout = new ByteArrayOutputStream(bufferSize);
+      this.stderr = new ByteArrayOutputStream(bufferSize);
+      this.in = System.in;
+      this.out = new PrintStream(stdout);
+      this.err = new PrintStream(stderr);
+   }
+
+   public TestActionContext() {
+      this(4096);
+   }
+
+   public String getStdout() {
+      return stdout.toString();
+   }
+
+   public String getStderr() {
+      return stderr.toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ByteUtil.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ByteUtil.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ByteUtil.java
index c9143f5..c22f37e 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ByteUtil.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/ByteUtil.java
@@ -134,6 +134,14 @@ public class ByteUtil {
       return buffer.array();
    }
 
+   public static byte[] hexToBytes(String hexStr) {
+      byte[] bytes = new byte[hexStr.length() / 2];
+      for (int i = 0; i < bytes.length; i++) {
+         bytes[i] = (byte) Integer.parseInt(hexStr.substring(2 * i, 2 * i + 2), 16);
+      }
+      return bytes;
+   }
+
    public static String readLine(ActiveMQBuffer buffer) {
       StringBuilder sb = new StringBuilder("");
       char c = buffer.readChar();

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodec.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodec.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodec.java
index 02bec5f..227a60b 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodec.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/DefaultSensitiveStringCodec.java
@@ -16,15 +16,19 @@
  */
 package org.apache.activemq.artemis.utils;
 
-import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
 import javax.crypto.spec.SecretKeySpec;
 import java.math.BigInteger;
-import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.Properties;
 
 /**
  * A DefaultSensitiveDataCodec
@@ -34,52 +38,39 @@ import java.util.Map;
  * file to use a masked password but doesn't give a
  * codec implementation.
  *
- * The decode() and encode() method is copied originally from
- * JBoss AS code base.
+ * It supports one-way hash (digest) and two-way (encrypt-decrpt) algorithms
+ * The two-way uses "Blowfish" algorithm
+ * The one-way uses "PBKDF2" hash algorithm
  */
 public class DefaultSensitiveStringCodec implements SensitiveDataCodec<String> {
 
-   private byte[] internalKey = "clusterpassword".getBytes();
+   public static final String ALGORITHM = "algorithm";
+   public static final String BLOWFISH_KEY = "key";
+   public static final String ONE_WAY = "one-way";
+   public static final String TWO_WAY = "two-way";
 
-   @Override
-   public String decode(Object secret) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
-      SecretKeySpec key = new SecretKeySpec(internalKey, "Blowfish");
-
-      BigInteger n = new BigInteger((String) secret, 16);
-      byte[] encoding = n.toByteArray();
-
-      // JBAS-3457: fix leading zeros
-      if (encoding.length % 8 != 0) {
-         int length = encoding.length;
-         int newLength = ((length / 8) + 1) * 8;
-         int pad = newLength - length; // number of leading zeros
-         byte[] old = encoding;
-         encoding = new byte[newLength];
-         System.arraycopy(old, 0, encoding, pad, old.length);
-      }
+   private CodecAlgorithm algorithm = new BlowfishAlgorithm(Collections.EMPTY_MAP);
 
-      Cipher cipher = Cipher.getInstance("Blowfish");
-      cipher.init(Cipher.DECRYPT_MODE, key);
-      byte[] decode = cipher.doFinal(encoding);
-
-      return new String(decode);
+   @Override
+   public String decode(Object secret) throws Exception {
+      return algorithm.decode((String) secret);
    }
 
-   public Object encode(String secret) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
-      SecretKeySpec key = new SecretKeySpec(internalKey, "Blowfish");
-
-      Cipher cipher = Cipher.getInstance("Blowfish");
-      cipher.init(Cipher.ENCRYPT_MODE, key);
-      byte[] encoding = cipher.doFinal(secret.getBytes());
-      BigInteger n = new BigInteger(encoding);
-      return n.toString(16);
+   @Override
+   public String encode(Object secret) throws Exception {
+      return algorithm.encode((String) secret);
    }
 
    @Override
-   public void init(Map<String, String> params) {
-      String key = params.get("key");
-      if (key != null) {
-         updateKey(key);
+   public void init(Map<String, String> params) throws Exception {
+      String algorithm = params.get(ALGORITHM);
+      if (algorithm == null || algorithm.equals(TWO_WAY)) {
+         //two way
+         this.algorithm = new BlowfishAlgorithm(params);
+      } else if (algorithm.equals(ONE_WAY)) {
+         this.algorithm = new PBKDF2Algorithm(params);
+      } else {
+         throw new IllegalArgumentException("Invalid algorithm: " + algorithm);
       }
    }
 
@@ -96,12 +87,149 @@ public class DefaultSensitiveStringCodec implements SensitiveDataCodec<String> {
          System.exit(-1);
       }
       DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
+      Map<String, String> params = new HashMap<>();
+      Properties properties = System.getProperties();
+      for (final String name: properties.stringPropertyNames()) {
+         params.put(name, properties.getProperty(name));
+      }
+      codec.init(params);
       Object encode = codec.encode(args[0]);
+
       System.out.println("Encoded password (without quotes): \"" + encode + "\"");
    }
 
-   private void updateKey(String key) {
-      this.internalKey = key.getBytes();
+   public boolean verify(char[] inputValue, String storedValue) {
+      return algorithm.verify(inputValue, storedValue);
+   }
+
+   private abstract class CodecAlgorithm {
+
+      protected Map<String, String> params;
+
+      CodecAlgorithm(Map<String, String> params) {
+         this.params = params;
+      }
+
+      public abstract String decode(String secret) throws Exception;
+      public abstract String encode(String secret) throws Exception;
+
+      public boolean verify(char[] inputValue, String storedValue) {
+         return false;
+      }
    }
 
+   private class BlowfishAlgorithm extends CodecAlgorithm {
+
+      private byte[] internalKey = "clusterpassword".getBytes();
+
+      BlowfishAlgorithm(Map<String, String> params) {
+         super(params);
+         String key = params.get(BLOWFISH_KEY);
+         if (key != null) {
+            updateKey(key);
+         }
+      }
+
+      private void updateKey(String key) {
+         this.internalKey = key.getBytes();
+      }
+
+      @Override
+      public String decode(String secret) throws Exception {
+         SecretKeySpec key = new SecretKeySpec(internalKey, "Blowfish");
+
+         BigInteger n = new BigInteger((String) secret, 16);
+         byte[] encoding = n.toByteArray();
+
+         if (encoding.length % 8 != 0) {
+            int length = encoding.length;
+            int newLength = ((length / 8) + 1) * 8;
+            int pad = newLength - length; // number of leading zeros
+            byte[] old = encoding;
+            encoding = new byte[newLength];
+            System.arraycopy(old, 0, encoding, pad, old.length);
+         }
+
+         Cipher cipher = Cipher.getInstance("Blowfish");
+         cipher.init(Cipher.DECRYPT_MODE, key);
+         byte[] decode = cipher.doFinal(encoding);
+
+         return new String(decode);
+      }
+
+      @Override
+      public String encode(String secret) throws Exception {
+         SecretKeySpec key = new SecretKeySpec(internalKey, "Blowfish");
+
+         Cipher cipher = Cipher.getInstance("Blowfish");
+         cipher.init(Cipher.ENCRYPT_MODE, key);
+         byte[] encoding = cipher.doFinal(secret.getBytes());
+         BigInteger n = new BigInteger(encoding);
+         return n.toString(16);
+      }
+   }
+
+   private class PBKDF2Algorithm extends CodecAlgorithm {
+      private static final String SEPERATOR = ":";
+      private String sceretKeyAlgorithm = "PBKDF2WithHmacSHA1";
+      private String randomScheme = "SHA1PRNG";
+      private int keyLength = 64 * 8;
+      private int saltLength = 32;
+      private int iterations = 1024;
+      private SecretKeyFactory skf;
+
+      PBKDF2Algorithm(Map<String, String> params) throws NoSuchAlgorithmException {
+         super(params);
+         skf = SecretKeyFactory.getInstance(sceretKeyAlgorithm);
+      }
+
+      @Override
+      public String decode(String secret) throws Exception {
+         throw new IllegalArgumentException("Algorithm doesn't support decoding");
+      }
+
+      public byte[] getSalt() throws NoSuchAlgorithmException {
+         byte[] salt = new byte[this.saltLength];
+
+         SecureRandom sr = SecureRandom.getInstance(this.randomScheme);
+         sr.nextBytes(salt);
+         return salt;
+      }
+
+      @Override
+      public String encode(String secret) throws Exception {
+         char[] chars = secret.toCharArray();
+         byte[] salt = getSalt();
+
+         StringBuilder builder = new StringBuilder();
+         builder.append(iterations).append(SEPERATOR).append(ByteUtil.bytesToHex(salt)).append(SEPERATOR);
+
+         PBEKeySpec spec = new PBEKeySpec(chars, salt, iterations, keyLength);
+
+         byte[] hash = skf.generateSecret(spec).getEncoded();
+         String hexValue = ByteUtil.bytesToHex(hash);
+         builder.append(hexValue);
+
+         return builder.toString();
+      }
+
+      @Override
+      public boolean verify(char[] plainChars, String storedValue) {
+         String[] parts = storedValue.split(SEPERATOR);
+         int originalIterations = Integer.parseInt(parts[0]);
+         byte[] salt = ByteUtil.hexToBytes(parts[1]);
+         byte[] originalHash = ByteUtil.hexToBytes(parts[2]);
+
+         PBEKeySpec spec = new PBEKeySpec(plainChars, salt, originalIterations, originalHash.length * 8);
+         byte[] newHash;
+
+         try {
+            newHash = skf.generateSecret(spec).getEncoded();
+         } catch (InvalidKeySpecException e) {
+            return false;
+         }
+
+         return Arrays.equals(newHash, originalHash);
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/HashProcessor.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/HashProcessor.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/HashProcessor.java
new file mode 100644
index 0000000..0b44132
--- /dev/null
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/HashProcessor.java
@@ -0,0 +1,41 @@
+/*
+ * 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.activemq.artemis.utils;
+
+
+/**
+ * Used to process Hash text for passwords
+ */
+public interface HashProcessor {
+
+   /**
+    * produce hash text from plain text
+    * @param plainText Plain text input
+    * @return the Hash value of the input plain text
+    * @throws Exception
+    */
+   String hash(String plainText) throws Exception;
+
+   /**
+    * compare the plain char array against the hash value
+    * @param inputValue value of the plain text
+    * @param storedHash the existing hash value
+    * @return true if the char array matches the hash value,
+    * otherwise false.
+    */
+   boolean compare(char[] inputValue, String storedHash);
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/NoHashProcessor.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/NoHashProcessor.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/NoHashProcessor.java
new file mode 100644
index 0000000..67e7f6a
--- /dev/null
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/NoHashProcessor.java
@@ -0,0 +1,35 @@
+/*
+ * 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.activemq.artemis.utils;
+
+import java.util.Arrays;
+
+/**
+ * A hash processor that just does plain text comparison
+ */
+public class NoHashProcessor implements HashProcessor {
+
+   @Override
+   public String hash(String plainText) throws Exception {
+      return plainText;
+   }
+
+   @Override
+   public boolean compare(char[] inputValue, String storedHash) {
+      return Arrays.equals(inputValue, storedHash.toCharArray());
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/cd7b8389/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java
----------------------------------------------------------------------
diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java
index 2ef0daa..bee3861 100644
--- a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java
+++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/PasswordMaskingUtil.java
@@ -27,6 +27,64 @@ import org.apache.activemq.artemis.logs.ActiveMQUtilBundle;
 
 public class PasswordMaskingUtil {
 
+   private static final String PLAINTEXT_PROCESSOR = "plaintext";
+   private static final String SECURE_PROCESSOR = "secure";
+
+   private static final Map<String, HashProcessor> processors = new HashMap<>();
+
+   //stored password takes 2 forms, ENC() or plain text
+   public static HashProcessor getHashProcessor(String storedPassword) throws Exception {
+
+      if (!isEncoded(storedPassword)) {
+         return getPlaintextProcessor();
+      }
+      return getSecureProcessor();
+   }
+
+   private static boolean isEncoded(String storedPassword) {
+      if (storedPassword == null) {
+         return true;
+      }
+
+      if (storedPassword.startsWith("ENC(") && storedPassword.endsWith(")")) {
+         return true;
+      }
+      return false;
+   }
+
+   public static HashProcessor getHashProcessor() {
+      HashProcessor processor = null;
+      try {
+         processor = getSecureProcessor();
+      } catch (Exception e) {
+         processor = getPlaintextProcessor();
+      }
+      return processor;
+   }
+
+   public static HashProcessor getPlaintextProcessor() {
+      synchronized (processors) {
+         HashProcessor plain = processors.get(PLAINTEXT_PROCESSOR);
+         if (plain == null) {
+            plain = new NoHashProcessor();
+            processors.put(PLAINTEXT_PROCESSOR, plain);
+         }
+         return plain;
+      }
+   }
+
+   public static HashProcessor getSecureProcessor() throws Exception {
+      synchronized (processors) {
+         HashProcessor processor = processors.get(SECURE_PROCESSOR);
+         if (processor == null) {
+            DefaultSensitiveStringCodec codec = (DefaultSensitiveStringCodec) getCodec("org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;algorithm=one-way");
+            processor = new SecureHashProcessor(codec);
+            processors.put(SECURE_PROCESSOR, processor);
+         }
+         return processor;
+      }
+   }
+
    /*
     * Loading the codec class.
     *
@@ -37,7 +95,7 @@ public class PasswordMaskingUtil {
     * Where only <full qualified class name> is required. key/value pairs are optional
     */
    public static SensitiveDataCodec<String> getCodec(String codecDesc) throws ActiveMQException {
-      SensitiveDataCodec<String> codecInstance = null;
+      SensitiveDataCodec<String> codecInstance;
 
       // semi colons
       String[] parts = codecDesc.split(";");
@@ -70,13 +128,19 @@ public class PasswordMaskingUtil {
                throw ActiveMQUtilBundle.BUNDLE.invalidProperty(parts[i]);
             props.put(keyVal[0], keyVal[1]);
          }
-         codecInstance.init(props);
+         try {
+            codecInstance.init(props);
+         } catch (Exception e) {
+            throw new ActiveMQException("Fail to init codec", e, ActiveMQExceptionType.SECURITY_EXCEPTION);
+         }
       }
 
       return codecInstance;
    }
 
-   public static SensitiveDataCodec<String> getDefaultCodec() {
+   public static DefaultSensitiveStringCodec getDefaultCodec() {
       return new DefaultSensitiveStringCodec();
    }
+
+
 }


Mime
View raw message