You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zookeeper.apache.org by sy...@apache.org on 2020/11/02 16:58:47 UTC

[zookeeper] branch master updated: ZOOKEEPER-3696: Support alternative algorithms for ACL digest

This is an automated email from the ASF dual-hosted git repository.

symat pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zookeeper.git


The following commit(s) were added to refs/heads/master by this push:
     new 392846c  ZOOKEEPER-3696: Support alternative algorithms for ACL digest
392846c is described below

commit 392846c534c51a57aa678691d411ad7d8d68e29a
Author: maoling <ma...@sina.com>
AuthorDate: Mon Nov 2 16:58:08 2020 +0000

    ZOOKEEPER-3696: Support alternative algorithms for ACL digest
    
    - [SHA1](https://shattered.io/) can be cracked now and it's not safe enough to use it, `SHA2` shares the the same algorithm idea with `SHA1`, its broken would be only a matter of time, so they created `SHA3` which's safe up to now.
    
    - The default value is: `SHA1` which will be deprecated in the future for security issues. Set this property the same value in all the servers.
    
    - How to support other more algorithms?
    
        - modify the `java.security` configuration file under `$JAVA_HOME/jre/lib/security/java.security` by specifying:
                 `security.provider.<n>=<provider class name>`.
    
                 ```
                 For example:
                 set zookeeper.DigestAuthenticationProvider.digestAlg=RipeMD160
                 security.provider.3=org.bouncycastle.jce.provider.BouncyCastleProvider
                 ```
    
        - copy the jar file to `$JAVA_HOME/jre/lib/ext/`.
    
                 ```
                 For example:
                 copy bcprov-jdk15on-1.60.jar to $JAVA_HOME/jre/lib/ext/
                 ```
    
    - For the same digest algorithm and input, the output of digest is the constant. You can use some online tools to play with it. Some UTs had covered it.
    
    - The unit cases have tested three algorithms: `SHA1`, `SHA-256`, `SHA3-256`(other algorithms share the same principle, so ignore them), and I also do a manual test to `RipeMD160`
    
    - For the invalid algorithm parameter
    ```
     Caused by: java.lang.RuntimeException: don't support this ACL digest algorithm: SHA3-996 in the current environment
    	at org.apache.zookeeper.server.auth.DigestAuthenticationProvider.<clinit>(DigestAuthenticationProvider.java:52)
    	... 6 more
    ```
    
    - How to migrate from one digest algorithm to another? For example: migrate from SHA1 to SHA3
    ```
    # Before I have SHA1 for digest
    superDigest=super:D/InIHSb7yEEbrWz8b9l71RjZJU=" //super:test
    
    [zk: 127.0.0.1:2180(CONNECTED) 0] addauth digest username1:password1
    [zk: 127.0.0.1:2180(CONNECTED) 2] setAcl /myapp-1 auth:username1:password1:crwad
    
    # After I transfer to SHA3, you will get Auth Exception when getData of a
    znode which already had a digest auth of old algorithm.
    
    Step ONE:  Regenerate `superDigest` when migrating to new algorithm which is always a good practice for users to survive from any uncomfortable auth issue.
    reset my superDigest to super:cRy/KPYuDpW/dtsepniTMpuiuupnWgdU9txltIfv3hA=
    [zk: 127.0.0.1:2180(CONNECTED) 0] addauth digest super:test
    [zk: 127.0.0.1:2180(CONNECTED) 1] get /myapp-1
    null
    
    Step TWO: re-setACl for that znode.
    [zk: 127.0.0.1:2180(CONNECTED) 0] addauth digest super:test
    [zk: 127.0.0.1:2180(CONNECTED) 1] addauth digest username1:password1
    # sometime use setAcl -R to setAcl recursively or you can also set ACL to 'world,'anyone
    (open to anyone)
    [zk: 127.0.0.1:2180(CONNECTED) 2] setAcl /myapp-1 auth:username1:password1:crwad
    ```
    
    - [TODO]: `ZOOKEEPER-3976: write a script to encapsulate DigestAuthenticationProvider#main as a tool to generate the digest with the algorithm users appoint`
    
    - more details in the [ZOOKEEPER-3696](https://issues.apache.org/jira/browse/ZOOKEEPER-3696)
    
    Author: maoling <ma...@sina.com>
    
    Reviewers: Enrico Olivelli <eo...@apache.org>, Andor Molnar <an...@apache.org>, Mate Szalay-Beko <sy...@apache.org>
    
    This patch had conflicts when merged, resolved by
    Committer: Mate Szalay-Beko <sy...@apache.org>
    
    Closes #1318 from maoling/ZOOKEEPER-3696
---
 .../src/main/resources/markdown/zookeeperAdmin.md  |  27 +++++
 .../zookeeper/server/ZooKeeperServerMain.java      |   2 +
 .../server/auth/DigestAuthenticationProvider.java  |  23 ++++-
 .../zookeeper/server/quorum/QuorumPeerMain.java    |   2 +
 .../org/apache/zookeeper/test/AuthSHA2Test.java    |  89 ++++++++++++++++
 .../org/apache/zookeeper/test/AuthSHA3Test.java    |  89 ++++++++++++++++
 .../java/org/apache/zookeeper/test/AuthTest.java   | 113 ++++++++++++++++++++-
 7 files changed, 343 insertions(+), 2 deletions(-)

diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
index a3a1c6c..d6087e6 100644
--- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
+++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
@@ -1461,6 +1461,33 @@ and [SASL authentication for ZooKeeper](https://cwiki.apache.org/confluence/disp
     localhost (not over the network) or over an encrypted
     connection.
 
+* *DigestAuthenticationProvider.digestAlg* :
+    (Java system property: **zookeeper.DigestAuthenticationProvider.digestAlg**)
+     **New in 3.7.0:**
+    Set ACL digest algorithm. The default value is: `SHA1` which will be deprecated in the future for security issues.
+    Set this property the same value in all the servers.
+
+    - How to support other more algorithms?
+        - modify the `java.security` configuration file under `$JAVA_HOME/jre/lib/security/java.security` by specifying:
+             `security.provider.<n>=<provider class name>`.
+
+             ```
+             For example:
+             set zookeeper.DigestAuthenticationProvider.digestAlg=RipeMD160
+             security.provider.3=org.bouncycastle.jce.provider.BouncyCastleProvider
+             ```
+
+        - copy the jar file to `$JAVA_HOME/jre/lib/ext/`.
+
+             ```
+             For example:
+             copy bcprov-jdk15on-1.60.jar to $JAVA_HOME/jre/lib/ext/
+             ```
+
+    - How to migrate from one digest algorithm to another?
+        - 1. Regenerate `superDigest` when migrating to new algorithm.
+        - 2. `SetAcl` for a znode which already had a digest auth of old algorithm.
+
 * *X509AuthenticationProvider.superUser* :
     (Java system property: **zookeeper.X509AuthenticationProvider.superUser**)
     The SSL-backed way to enable a ZooKeeper ensemble
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServerMain.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServerMain.java
index 7ddb1de..abb4d0d 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServerMain.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServerMain.java
@@ -31,6 +31,7 @@ import org.apache.zookeeper.metrics.impl.MetricsProviderBootstrap;
 import org.apache.zookeeper.server.admin.AdminServer;
 import org.apache.zookeeper.server.admin.AdminServer.AdminServerException;
 import org.apache.zookeeper.server.admin.AdminServerFactory;
+import org.apache.zookeeper.server.auth.ProviderRegistry;
 import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
 import org.apache.zookeeper.server.persistence.FileTxnSnapLog.DatadirException;
 import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
@@ -130,6 +131,7 @@ public class ZooKeeperServerMain {
                 throw new IOException("Cannot boot MetricsProvider " + config.getMetricsProviderClassName(), error);
             }
             ServerMetrics.metricsProviderInitialized(metricsProvider);
+            ProviderRegistry.initialize();
             // Note that this thread isn't going to be doing anything else,
             // so rather than spawning another thread, we will just call
             // run() in this thread.
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/DigestAuthenticationProvider.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/DigestAuthenticationProvider.java
index 573cba3..8b64154 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/DigestAuthenticationProvider.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/DigestAuthenticationProvider.java
@@ -31,6 +31,22 @@ public class DigestAuthenticationProvider implements AuthenticationProvider {
 
     private static final Logger LOG = LoggerFactory.getLogger(DigestAuthenticationProvider.class);
 
+    private static final String DEFAULT_DIGEST_ALGORITHM = "SHA1";
+
+    public static final String DIGEST_ALGORITHM_KEY = "zookeeper.DigestAuthenticationProvider.digestAlg";
+
+    private static final String DIGEST_ALGORITHM = System.getProperty(DIGEST_ALGORITHM_KEY, DEFAULT_DIGEST_ALGORITHM);
+
+    static {
+        try {
+            //sanity check, pre-check the availability of the algorithm to avoid some unexpected exceptions in the runtime
+            generateDigest(DIGEST_ALGORITHM);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("don't support this ACL digest algorithm: " + DIGEST_ALGORITHM + " in the current environment");
+        }
+        LOG.info("ACL digest algorithm is: {}", DIGEST_ALGORITHM);
+    }
+
     /** specify a command line property with key of
      * "zookeeper.DigestAuthenticationProvider.superDigest"
      * and value of "super:&lt;base64encoded(SHA1(password))&gt;" to enable
@@ -89,10 +105,15 @@ public class DigestAuthenticationProvider implements AuthenticationProvider {
 
     public static String generateDigest(String idPassword) throws NoSuchAlgorithmException {
         String[] parts = idPassword.split(":", 2);
-        byte[] digest = MessageDigest.getInstance("SHA1").digest(idPassword.getBytes(UTF_8));
+        byte[] digest = digest(idPassword);
         return parts[0] + ":" + base64Encode(digest);
     }
 
+    // @VisibleForTesting
+    public static byte[] digest(String idPassword) throws NoSuchAlgorithmException {
+        return MessageDigest.getInstance(DIGEST_ALGORITHM).digest(idPassword.getBytes(UTF_8));
+    }
+
     public KeeperException.Code handleAuthentication(ServerCnxn cnxn, byte[] authData) {
         String id = new String(authData);
         try {
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java
index 81140b6..76df5e4 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java
@@ -34,6 +34,7 @@ import org.apache.zookeeper.server.ServerMetrics;
 import org.apache.zookeeper.server.ZKDatabase;
 import org.apache.zookeeper.server.ZooKeeperServerMain;
 import org.apache.zookeeper.server.admin.AdminServer.AdminServerException;
+import org.apache.zookeeper.server.auth.ProviderRegistry;
 import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
 import org.apache.zookeeper.server.persistence.FileTxnSnapLog.DatadirException;
 import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
@@ -159,6 +160,7 @@ public class QuorumPeerMain {
         }
         try {
             ServerMetrics.metricsProviderInitialized(metricsProvider);
+            ProviderRegistry.initialize();
             ServerCnxnFactory cnxnFactory = null;
             ServerCnxnFactory secureCnxnFactory = null;
 
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/test/AuthSHA2Test.java b/zookeeper-server/src/test/java/org/apache/zookeeper/test/AuthSHA2Test.java
new file mode 100644
index 0000000..298e4fb
--- /dev/null
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/test/AuthSHA2Test.java
@@ -0,0 +1,89 @@
+/*
+ * 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.zookeeper.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.security.NoSuchAlgorithmException;
+import java.security.Security;
+import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class AuthSHA2Test extends AuthTest {
+
+    @BeforeAll
+    public static void setup() {
+        // use the BouncyCastle's Provider for testing
+        Security.addProvider(new BouncyCastleProvider());
+        // password is test
+        System.setProperty(DigestAuthenticationProvider.DIGEST_ALGORITHM_KEY, DigestAlgEnum.SHA_256.getName());
+        System.setProperty("zookeeper.DigestAuthenticationProvider.superDigest", "super:wjySwxg860UATFtciuZ1lpzrCHrPeov6SPu/ZD56uig=");
+        System.setProperty("zookeeper.authProvider.1", "org.apache.zookeeper.test.InvalidAuthProvider");
+    }
+
+    @AfterAll
+    public static void teardown() {
+        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+        System.clearProperty("zookeeper.DigestAuthenticationProvider.superDigest");
+        System.clearProperty(DigestAuthenticationProvider.DIGEST_ALGORITHM_KEY);
+    }
+
+    @Test
+    public void testBadAuthNotifiesWatch() throws Exception {
+        super.testBadAuthNotifiesWatch();
+    }
+
+    @Test
+    public void testBadAuthThenSendOtherCommands() throws Exception {
+        super.testBadAuthThenSendOtherCommands();
+    }
+
+    @Test
+    public void testSuper() throws Exception {
+        super.testSuper();
+    }
+
+    @Test
+    public void testSuperACL() throws Exception {
+        super.testSuperACL();
+    }
+
+    @Test
+    public void testOrdinaryACL() throws Exception {
+        super.testOrdinaryACL();
+    }
+
+    @Test
+    public void testGenerateDigest() throws NoSuchAlgorithmException {
+        assertEquals("super:wjySwxg860UATFtciuZ1lpzrCHrPeov6SPu/ZD56uig=", DigestAuthenticationProvider.generateDigest("super:test"));
+        assertEquals("super:Ie58Fw6KA4ucTEDj23imIltKrXNDxQg8Rwtu0biQFcU=", DigestAuthenticationProvider.generateDigest("super:zookeeper"));
+        assertEquals("super:rVOiTPnqEqlpIRXqSoE6+7h6SzbHUrfAe34i8n/gmRU=", DigestAuthenticationProvider.generateDigest(("super:foo")));
+        assertEquals("super:vs70GBagNcqIhGR4R6rXP8E3lvJPYhzMpAMx8ghbTUk=", DigestAuthenticationProvider.generateDigest(("super:bar")));
+    }
+
+    @Test
+    public void testDigest() throws NoSuchAlgorithmException {
+        assertEquals("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", getGeneratedDigestStr(DigestAuthenticationProvider.digest("test")));
+        assertEquals("456831beef3fc1500939995d7369695f48642664a02d5eab9d807592a08b2384", getGeneratedDigestStr(DigestAuthenticationProvider.digest("zookeeper")));
+        assertEquals("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", getGeneratedDigestStr(DigestAuthenticationProvider.digest(("foo"))));
+        assertEquals("fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9", getGeneratedDigestStr(DigestAuthenticationProvider.digest(("bar"))));
+    }
+}
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/test/AuthSHA3Test.java b/zookeeper-server/src/test/java/org/apache/zookeeper/test/AuthSHA3Test.java
new file mode 100644
index 0000000..38b070f
--- /dev/null
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/test/AuthSHA3Test.java
@@ -0,0 +1,89 @@
+/*
+ * 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.zookeeper.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.security.NoSuchAlgorithmException;
+import java.security.Security;
+import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class AuthSHA3Test extends AuthTest {
+
+    @BeforeAll
+    public static void setup() {
+        // use the BouncyCastle's Provider for testing
+        Security.addProvider(new BouncyCastleProvider());
+        // password is test
+        System.setProperty(DigestAuthenticationProvider.DIGEST_ALGORITHM_KEY, DigestAlgEnum.SHA3_256.getName());
+        System.setProperty("zookeeper.DigestAuthenticationProvider.superDigest", "super:cRy/KPYuDpW/dtsepniTMpuiuupnWgdU9txltIfv3hA=");
+        System.setProperty("zookeeper.authProvider.1", "org.apache.zookeeper.test.InvalidAuthProvider");
+    }
+
+    @AfterAll
+    public static void teardown() {
+        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+        System.clearProperty("zookeeper.DigestAuthenticationProvider.superDigest");
+        System.clearProperty(DigestAuthenticationProvider.DIGEST_ALGORITHM_KEY);
+    }
+
+    @Test
+    public void testBadAuthNotifiesWatch() throws Exception {
+        super.testBadAuthNotifiesWatch();
+    }
+
+    @Test
+    public void testBadAuthThenSendOtherCommands() throws Exception {
+        super.testBadAuthThenSendOtherCommands();
+    }
+
+    @Test
+    public void testSuper() throws Exception {
+        super.testSuper();
+    }
+
+    @Test
+    public void testSuperACL() throws Exception {
+        super.testSuperACL();
+    }
+
+    @Test
+    public void testOrdinaryACL() throws Exception {
+        super.testOrdinaryACL();
+    }
+
+    @Test
+    public void testGenerateDigest() throws NoSuchAlgorithmException {
+        assertEquals("super:cRy/KPYuDpW/dtsepniTMpuiuupnWgdU9txltIfv3hA=", DigestAuthenticationProvider.generateDigest("super:test"));
+        assertEquals("super:gM3M1QcrKC6b+h4oZ5Ixc4GTVaAsggI+AqkUaF6E1Is=", DigestAuthenticationProvider.generateDigest("super:zookeeper"));
+        assertEquals("super:2Ww7VUqTohd3lX/Vf4Nvw+GxbmOsX1p337L7Bnks4L8=", DigestAuthenticationProvider.generateDigest(("super:foo")));
+        assertEquals("super:Ft5s2Rtxr8zyz16feKiFR/8yqa6JoNEJ0In73aXojE8=", DigestAuthenticationProvider.generateDigest(("super:bar")));
+    }
+
+    @Test
+    public void testDigest() throws NoSuchAlgorithmException {
+        assertEquals("36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80", getGeneratedDigestStr(DigestAuthenticationProvider.digest("test")));
+        assertEquals("af4c1abc2deaa6edffc7ce34edeb8c03ee9a1488b64fd318ddb93b4b7f1c0746", getGeneratedDigestStr(DigestAuthenticationProvider.digest("zookeeper")));
+        assertEquals("76d3bc41c9f588f7fcd0d5bf4718f8f84b1c41b20882703100b9eb9413807c01", getGeneratedDigestStr(DigestAuthenticationProvider.digest(("foo"))));
+        assertEquals("cceefd7e0545bcf8b6d19f3b5750c8a3ee8350418877bc6fb12e32de28137355", getGeneratedDigestStr(DigestAuthenticationProvider.digest(("bar"))));
+    }
+}
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/test/AuthTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/test/AuthTest.java
index 211dfae..a1c95d8 100644
--- a/zookeeper-server/src/test/java/org/apache/zookeeper/test/AuthTest.java
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/test/AuthTest.java
@@ -18,8 +18,12 @@
 
 package org.apache.zookeeper.test;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.fail;
 import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import org.apache.zookeeper.CreateMode;
@@ -27,18 +31,32 @@ import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.TestableZooKeeper;
 import org.apache.zookeeper.WatchedEvent;
 import org.apache.zookeeper.Watcher.Event.KeeperState;
+import org.apache.zookeeper.ZooDefs;
 import org.apache.zookeeper.ZooDefs.Ids;
 import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Id;
+import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 public class AuthTest extends ClientBase {
 
-    static {
+    @BeforeAll
+    public static void setup() {
         // password is test
+        // the default digestAlg is: SHA1
         System.setProperty("zookeeper.DigestAuthenticationProvider.superDigest", "super:D/InIHSb7yEEbrWz8b9l71RjZJU=");
         System.setProperty("zookeeper.authProvider.1", "org.apache.zookeeper.test.InvalidAuthProvider");
     }
 
+    @AfterAll
+    public static void teardown() {
+        System.clearProperty("zookeeper.DigestAuthenticationProvider.superDigest");
+        System.clearProperty(DigestAuthenticationProvider.DIGEST_ALGORITHM_KEY);
+    }
+
     private final CountDownLatch authFailed = new CountDownLatch(1);
 
     @Override
@@ -160,4 +178,97 @@ public class AuthTest extends ClientBase {
         }
     }
 
+    @Test
+    public void testOrdinaryACL() throws Exception {
+        ZooKeeper zk = createClient();
+        try {
+            String path = "/path1";
+            zk.create(path, null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+            zk.addAuthInfo("digest", "username1:password1".getBytes());
+            List<ACL> list = new ArrayList<>();
+            int perm = ZooDefs.Perms.ALL;
+            String userPassword = "username1:password1";
+            Id id = new Id("auth", userPassword);
+            list.add(new ACL(perm, id));
+            zk.setACL(path, list, -1);
+            zk.close();
+
+            zk = createClient();
+            zk.addAuthInfo("digest", "super:test".getBytes());
+            zk.getData(path, false, null);
+            zk.close();
+
+            zk = createClient();
+            try {
+                zk.getData(path, false, null);
+                fail("should have NoAuthException");
+            } catch (KeeperException.NoAuthException e) {
+                // expected
+            }
+            zk.addAuthInfo("digest", "username1:password1".getBytes());
+            zk.getData(path, false, null);
+        } finally {
+            zk.close();
+        }
+    }
+
+    @Test
+    public void testGenerateDigest() throws NoSuchAlgorithmException {
+        assertEquals("super:D/InIHSb7yEEbrWz8b9l71RjZJU=", DigestAuthenticationProvider.generateDigest("super:test"));
+        assertEquals("super:yyuhPKumRtNj4r8GnSbbwuq1vhE=", DigestAuthenticationProvider.generateDigest("super:zookeeper"));
+        assertEquals("super:t6lQTvqID/Gl5Or0n4FYE6kKP8w=", DigestAuthenticationProvider.generateDigest(("super:foo")));
+        assertEquals("super:hTdNN4QH4isoRvCrQ1Jf7REREQ4=", DigestAuthenticationProvider.generateDigest(("super:bar")));
+    }
+
+    // This test is used to check the correctness of the algorithm
+    // For the same digest algorithm and input, the output of digest hash is the constant.
+    @Test
+    public void testDigest() throws NoSuchAlgorithmException {
+        assertEquals("a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", getGeneratedDigestStr(DigestAuthenticationProvider.digest("test")));
+        assertEquals("8a0444ded963cf1118dd34aa1acaafec268c654d", getGeneratedDigestStr(DigestAuthenticationProvider.digest("zookeeper")));
+        assertEquals("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33", getGeneratedDigestStr(DigestAuthenticationProvider.digest(("foo"))));
+        assertEquals("62cdb7020ff920e5aa642c3d4066950dd1f01f4d", getGeneratedDigestStr(DigestAuthenticationProvider.digest(("bar"))));
+    }
+
+    // this method is used to generate the digest String to help us to compare the result generated by some online tool easily
+    protected static String getGeneratedDigestStr(byte[] bytes) {
+        StringBuilder stringBuilder = new StringBuilder("");
+        if (bytes == null || bytes.length <= 0) {
+            return null;
+        }
+        for (int i = 0; i < bytes.length; i++) {
+            int v = bytes[i] & 0xFF;
+            String hv = Integer.toHexString(v);
+            if (hv.length() < 2) {
+                stringBuilder.append(0);
+            }
+            stringBuilder.append(hv);
+        }
+        return stringBuilder.toString();
+    }
+
+    public enum DigestAlgEnum {
+        SHA_1("SHA1"),
+        SHA_256("SHA-256"),
+        SHA3_256("SHA3-256");
+
+        private String name;
+
+        DigestAlgEnum(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return this.name;
+        }
+
+        public static List<String> getValues() {
+            List<String> digestList = new ArrayList<>();
+            for (DigestAlgEnum digest : values()) {
+                digestList.add(digest.getName());
+            }
+            return digestList;
+        }
+    }
+
 }