From commits-return-8580-archive-asf-public=cust-asf.ponee.io@zookeeper.apache.org Mon Nov 2 16:58:48 2020 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mailroute1-lw-us.apache.org (mailroute1-lw-us.apache.org [207.244.88.153]) by mx-eu-01.ponee.io (Postfix) with ESMTPS id B41AD180658 for ; Mon, 2 Nov 2020 17:58:48 +0100 (CET) Received: from mail.apache.org (localhost [127.0.0.1]) by mailroute1-lw-us.apache.org (ASF Mail Server at mailroute1-lw-us.apache.org) with SMTP id E6B3A122023 for ; Mon, 2 Nov 2020 16:58:47 +0000 (UTC) Received: (qmail 88171 invoked by uid 500); 2 Nov 2020 16:58:47 -0000 Mailing-List: contact commits-help@zookeeper.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@zookeeper.apache.org Delivered-To: mailing list commits@zookeeper.apache.org Received: (qmail 88159 invoked by uid 99); 2 Nov 2020 16:58:47 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 02 Nov 2020 16:58:47 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id 2BFBD820EB; Mon, 2 Nov 2020 16:58:47 +0000 (UTC) Date: Mon, 02 Nov 2020 16:58:47 +0000 To: "commits@zookeeper.apache.org" Subject: [zookeeper] branch master updated: ZOOKEEPER-3696: Support alternative algorithms for ACL digest MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Message-ID: <160433632699.32008.489597438601149573@gitbox.apache.org> From: symat@apache.org X-Git-Host: gitbox.apache.org X-Git-Repo: zookeeper X-Git-Refname: refs/heads/master X-Git-Reftype: branch X-Git-Oldrev: 13fe0d0ffb9fd2c379b9b430aaaf9ee75acfceba X-Git-Newrev: 392846c534c51a57aa678691d411ad7d8d68e29a X-Git-Rev: 392846c534c51a57aa678691d411ad7d8d68e29a X-Git-NotificationType: ref_changed_plus_diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated 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 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.=`. ``` 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.(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 Reviewers: Enrico Olivelli , Andor Molnar , Mate Szalay-Beko This patch had conflicts when merged, resolved by Committer: Mate Szalay-Beko 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.=`. + + ``` + 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:<base64encoded(SHA1(password))>" 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 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 getValues() { + List digestList = new ArrayList<>(); + for (DigestAlgEnum digest : values()) { + digestList.add(digest.getName()); + } + return digestList; + } + } + }