You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by jl...@apache.org on 2015/10/31 00:14:05 UTC

tomee git commit: TOMEE-1648: The password cipher is called each time the datasource is built even if we already have the clear password

Repository: tomee
Updated Branches:
  refs/heads/master 34c4cc7a2 -> 436089b65


TOMEE-1648: The password cipher is called each time the datasource is built even if we already have the clear password


Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/436089b6
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/436089b6
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/436089b6

Branch: refs/heads/master
Commit: 436089b658e239757e0a51b49be01974ca339627
Parents: 34c4cc7
Author: Jean-Louis Monteiro <jl...@apache.org>
Authored: Fri Oct 30 16:13:56 2015 -0700
Committer: Jean-Louis Monteiro <jl...@apache.org>
Committed: Fri Oct 30 16:13:56 2015 -0700

----------------------------------------------------------------------
 .../resource/jdbc/dbcp/BasicDataSource.java     |  20 +-
 .../jdbc/dbcp/BasicManagedDataSource.java       |  23 ++-
 .../jdbc/CipheredPasswordDataSourceTest.java    | 193 +++++++++++++++++++
 3 files changed, 233 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
index ffb5bba..995221c 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
@@ -55,6 +55,10 @@ public class BasicDataSource extends org.apache.commons.dbcp2.BasicDataSource im
      * not ciphered. The {@link org.apache.openejb.cipher.PlainTextPasswordCipher} can also be used.
      */
     private String passwordCipher;
+
+    // keep tracking the user configured password in case we need it to be decrypted again
+    private String initialPassword;
+
     private JMXBasicDataSource jmxDs;
     private CommonDataSource delegate;
     private String name;
@@ -128,6 +132,18 @@ public class BasicDataSource extends org.apache.commons.dbcp2.BasicDataSource im
         }
     }
 
+    @Override
+    public void setPassword(final String password) {
+        final ReentrantLock l = lock;
+        l.lock();
+        try {
+            // keep the encrypted value if it's encrypted
+            this.initialPassword = password;
+            super.setPassword(password);
+        } finally {
+            l.unlock();
+        }
+    }
 
     public String getUserName() {
         final ReentrantLock l = lock;
@@ -226,7 +242,9 @@ public class BasicDataSource extends org.apache.commons.dbcp2.BasicDataSource im
             // check password codec if available
             if (null != passwordCipher && !"PlainText".equals(passwordCipher)) {
                 final PasswordCipher cipher = PasswordCipherFactory.getPasswordCipher(passwordCipher);
-                final String plainPwd = cipher.decrypt(getPassword().toCharArray());
+
+                // always use the initial encrypted value
+                final String plainPwd = cipher.decrypt(initialPassword.toCharArray());
 
                 // override previous password value
                 super.setPassword(plainPwd);

http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
index ec604b0..0846f21 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
@@ -54,6 +54,10 @@ public class BasicManagedDataSource extends org.apache.commons.dbcp2.managed.Bas
      * not ciphered. The {@link org.apache.openejb.cipher.PlainTextPasswordCipher} can also be used.
      */
     private String passwordCipher;
+
+    // keep tracking the user configured password in case we need it to be decrypted again
+    private String initialPassword;
+
     private JMXBasicDataSource jmxDs;
 
     public BasicManagedDataSource(final String name) {
@@ -85,7 +89,7 @@ public class BasicManagedDataSource extends org.apache.commons.dbcp2.managed.Bas
 
     private void setJndiXaDataSource(final String xaDataSource) {
         setXaDataSourceInstance( // proxy cause we don't know if this datasource was created before or not the delegate
-            XADataSourceResource.proxy(getDriverClassLoader() != null ? getDriverClassLoader() : Thread.currentThread().getContextClassLoader(), xaDataSource));
+                XADataSourceResource.proxy(getDriverClassLoader() != null ? getDriverClassLoader() : Thread.currentThread().getContextClassLoader(), xaDataSource));
 
         if (getTransactionManager() == null) {
             setTransactionManager(OpenEJB.getTransactionManager());
@@ -133,6 +137,19 @@ public class BasicManagedDataSource extends org.apache.commons.dbcp2.managed.Bas
         }
     }
 
+    @Override
+    public void setPassword(final String password) {
+        final ReentrantLock l = lock;
+        l.lock();
+        try {
+            // keep the encrypted value if it's encrypted
+            this.initialPassword = password;
+            super.setPassword(password);
+        } finally {
+            l.unlock();
+        }
+    }
+
     public String getUserName() {
         final ReentrantLock l = lock;
         l.lock();
@@ -229,7 +246,9 @@ public class BasicManagedDataSource extends org.apache.commons.dbcp2.managed.Bas
             // check password codec if available
             if (null != passwordCipher) {
                 final PasswordCipher cipher = PasswordCipherFactory.getPasswordCipher(passwordCipher);
-                final String plainPwd = cipher.decrypt(getPassword().toCharArray());
+
+                // always use the initial encrypted value
+                final String plainPwd = cipher.decrypt(initialPassword.toCharArray());
 
                 // override previous password value
                 super.setPassword(plainPwd);

http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java b/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
new file mode 100644
index 0000000..0f1224f
--- /dev/null
+++ b/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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.openejb.resource.jdbc;
+
+import org.apache.commons.dbcp.PoolableConnection;
+import org.apache.openejb.cipher.PasswordCipher;
+import org.apache.openejb.jee.EjbJar;
+import org.apache.openejb.jee.SingletonBean;
+import org.apache.openejb.junit.ApplicationComposer;
+import org.apache.openejb.resource.jdbc.dbcp.BasicManagedDataSource;
+import org.apache.openejb.testing.Configuration;
+import org.apache.openejb.testing.Module;
+import org.apache.openejb.util.Strings;
+import org.apache.openejb.util.reflection.Reflections;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import javax.annotation.Resource;
+import javax.ejb.EJB;
+import javax.ejb.EJBContext;
+import javax.ejb.LocalBean;
+import javax.ejb.Singleton;
+import javax.ejb.Startup;
+import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.DriverPropertyInfo;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(ApplicationComposer.class)
+public class CipheredPasswordDataSourceTest {
+    private static final String URL = "jdbc:fake://sthg:3306";
+    private static final String USER = "pete";
+    private static final String ENCRYPTED_PASSWORD = "This is the encrypted value.";
+
+    @EJB
+    private Persister persistManager;
+
+    @Resource
+    private DataSource ds;
+
+    @Configuration
+    public Properties config() {
+        final Properties p = new Properties();
+        p.put("openejb.jdbc.datasource-creator", "dbcp");
+
+        p.put("managed", "new://Resource?type=DataSource");
+        p.put("managed.JdbcDriver", FakeDriver.class.getName());
+        p.put("managed.JdbcUrl", URL);
+        p.put("managed.UserName", USER);
+        p.put("managed.Password", ENCRYPTED_PASSWORD);
+        p.put("managed.PasswordCipher", EmptyPasswordCipher.class.getName());
+        p.put("managed.JtaManaged", "true");
+        p.put("managed.initialSize", "10");
+        p.put("managed.maxActive", "10");
+        p.put("managed.maxIdle", "10");
+        p.put("managed.minIdle", "10");
+        p.put("managed.maxWait", "200");
+        p.put("managed.defaultAutoCommit", "false");
+        p.put("managed.defaultReadOnly", "false");
+        p.put("managed.testOnBorrow", "true");
+        p.put("managed.testOnReturn", "true");
+        p.put("managed.testWhileIdle", "true");
+
+        return p;
+    }
+
+    @Module
+    public EjbJar app() throws Exception {
+        return new EjbJar()
+                .enterpriseBean(new SingletonBean(Persister.class).localBean());
+
+    }
+
+    @Before
+    public void createDatasource(){
+        // This is to make sure TomEE created the data source.
+        persistManager.save();
+    }
+
+    @Test
+    public void rebuild() {
+        // because of the exception at startup, the pool creating aborted
+        // before fixing this bug, the password was already decrypted, but the data source field
+        // wasn't yet initialized, so this.data source == null
+        // but the next time we try to initialize it, it tries to decrypt and already decrypted password
+
+        try {
+            ds.getConnection();
+
+        } catch (final SQLException e) {
+            System.out.println(e.getMessage());
+            // success - it throws the SQLException from the connect
+            // but it does not fail with the IllegalArgumentException from the Password Cipher
+        }
+    }
+
+    public static class EmptyPasswordCipher implements PasswordCipher {
+
+        @Override
+        public char[] encrypt(String plainPassword) {
+            throw new RuntimeException("Should never be called in this test.");
+        }
+
+        @Override
+        public String decrypt(char[] encryptedPassword) {
+            System.out.println(String.format(">>> Decrypt password '%s'", new String(encryptedPassword)));
+
+            // we want to know if the password as already been called
+            if (encryptedPassword.length == 0) {
+                throw new IllegalArgumentException("Can only decrypt a non empty string.");
+            }
+
+            return "";
+        }
+    }
+
+    public static class FakeDriver implements java.sql.Driver {
+        public boolean acceptsURL(final String url) throws SQLException {
+            return false;
+        }
+
+        public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+            return null;
+        }
+
+        public Connection connect(final String url, final Properties info) throws SQLException {
+            throw new SQLException("Pete said it first fails here!");
+        }
+
+        public int getMajorVersion() {
+            return 0;
+        }
+
+        public int getMinorVersion() {
+            return 0;
+        }
+
+        public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) throws SQLException {
+            return new DriverPropertyInfo[0];
+        }
+
+        public boolean jdbcCompliant() {
+            return false;
+        }
+    }
+
+    @LocalBean
+    @Singleton
+    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
+    public static class Persister {
+
+        @Resource(name = "managed")
+        private DataSource ds;
+
+        public void save() {
+            try {
+                ds.getConnection();
+
+            } catch (SQLException e) {
+                System.err.println("Generated SQL error > " + e.getMessage());
+            }
+        }
+    }
+
+
+}


Re: Fwd: tomee git commit: TOMEE-1648: The password cipher is called each time the datasource is built even if we already have the clear password

Posted by Jean-Louis Monteiro <jl...@tomitribe.com>.
oh that is all perfect feedback

--
Jean-Louis Monteiro
http://twitter.com/jlouismonteiro
http://www.tomitribe.com

On Mon, Nov 2, 2015 at 11:36 AM, Romain Manni-Bucau <rm...@gmail.com>
wrote:

> Ok just checked and we didnt expose the setPassword to JMX for dbcp so we
> are fine, sorry for the noise!
>
>
> Romain Manni-Bucau
> @rmannibucau <https://twitter.com/rmannibucau> |  Blog
> <http://rmannibucau.wordpress.com> | Github <
> https://github.com/rmannibucau> |
> LinkedIn <https://www.linkedin.com/in/rmannibucau> | Tomitriber
> <http://www.tomitribe.com>
>
> 2015-10-30 17:39 GMT-07:00 Jean-Louis Monteiro <jl...@tomitribe.com>:
>
> > Sure. Maybe the fix can be improved. Let's discuss
> > Le 30 oct. 2015 17:37, "Romain Manni-Bucau" <rm...@gmail.com> a
> > écrit :
> >
> > > It prevents to reset the pwd at runtime if i read it correctly. It
> should
> > > be fixed since it is used at least through jmx.
> > > ---------- Message transféré ----------
> > > De : <jl...@apache.org>
> > > Date : 30 oct. 2015 16:14
> > > Objet : tomee git commit: TOMEE-1648: The password cipher is called
> each
> > > time the datasource is built even if we already have the clear password
> > > À : <co...@tomee.apache.org>
> > > Cc :
> > >
> > > Repository: tomee
> > > Updated Branches:
> > >   refs/heads/master 34c4cc7a2 -> 436089b65
> > >
> > >
> > > TOMEE-1648: The password cipher is called each time the datasource is
> > built
> > > even if we already have the clear password
> > >
> > >
> > > Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
> > > Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/436089b6
> > > Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/436089b6
> > > Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/436089b6
> > >
> > > Branch: refs/heads/master
> > > Commit: 436089b658e239757e0a51b49be01974ca339627
> > > Parents: 34c4cc7
> > > Author: Jean-Louis Monteiro <jl...@apache.org>
> > > Authored: Fri Oct 30 16:13:56 2015 -0700
> > > Committer: Jean-Louis Monteiro <jl...@apache.org>
> > > Committed: Fri Oct 30 16:13:56 2015 -0700
> > >
> > > ----------------------------------------------------------------------
> > >  .../resource/jdbc/dbcp/BasicDataSource.java     |  20 +-
> > >  .../jdbc/dbcp/BasicManagedDataSource.java       |  23 ++-
> > >  .../jdbc/CipheredPasswordDataSourceTest.java    | 193
> > +++++++++++++++++++
> > >  3 files changed, 233 insertions(+), 3 deletions(-)
> > > ----------------------------------------------------------------------
> > >
> > >
> > >
> > >
> >
> http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> > > ----------------------------------------------------------------------
> > > diff --git
> > >
> > >
> >
> a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> > >
> > >
> >
> b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> > > index ffb5bba..995221c 100644
> > > ---
> > >
> > >
> >
> a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> > > +++
> > >
> > >
> >
> b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> > > @@ -55,6 +55,10 @@ public class BasicDataSource extends
> > > org.apache.commons.dbcp2.BasicDataSource im
> > >       * not ciphered. The {@link
> > > org.apache.openejb.cipher.PlainTextPasswordCipher} can also be used.
> > >       */
> > >      private String passwordCipher;
> > > +
> > > +    // keep tracking the user configured password in case we need it
> to
> > be
> > > decrypted again
> > > +    private String initialPassword;
> > > +
> > >      private JMXBasicDataSource jmxDs;
> > >      private CommonDataSource delegate;
> > >      private String name;
> > > @@ -128,6 +132,18 @@ public class BasicDataSource extends
> > > org.apache.commons.dbcp2.BasicDataSource im
> > >          }
> > >      }
> > >
> > > +    @Override
> > > +    public void setPassword(final String password) {
> > > +        final ReentrantLock l = lock;
> > > +        l.lock();
> > > +        try {
> > > +            // keep the encrypted value if it's encrypted
> > > +            this.initialPassword = password;
> > > +            super.setPassword(password);
> > > +        } finally {
> > > +            l.unlock();
> > > +        }
> > > +    }
> > >
> > >      public String getUserName() {
> > >          final ReentrantLock l = lock;
> > > @@ -226,7 +242,9 @@ public class BasicDataSource extends
> > > org.apache.commons.dbcp2.BasicDataSource im
> > >              // check password codec if available
> > >              if (null != passwordCipher &&
> > > !"PlainText".equals(passwordCipher)) {
> > >                  final PasswordCipher cipher =
> > > PasswordCipherFactory.getPasswordCipher(passwordCipher);
> > > -                final String plainPwd =
> > > cipher.decrypt(getPassword().toCharArray());
> > > +
> > > +                // always use the initial encrypted value
> > > +                final String plainPwd =
> > > cipher.decrypt(initialPassword.toCharArray());
> > >
> > >                  // override previous password value
> > >                  super.setPassword(plainPwd);
> > >
> > >
> > >
> >
> http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> > > ----------------------------------------------------------------------
> > > diff --git
> > >
> > >
> >
> a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> > >
> > >
> >
> b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> > > index ec604b0..0846f21 100644
> > > ---
> > >
> > >
> >
> a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> > > +++
> > >
> > >
> >
> b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> > > @@ -54,6 +54,10 @@ public class BasicManagedDataSource extends
> > > org.apache.commons.dbcp2.managed.Bas
> > >       * not ciphered. The {@link
> > > org.apache.openejb.cipher.PlainTextPasswordCipher} can also be used.
> > >       */
> > >      private String passwordCipher;
> > > +
> > > +    // keep tracking the user configured password in case we need it
> to
> > be
> > > decrypted again
> > > +    private String initialPassword;
> > > +
> > >      private JMXBasicDataSource jmxDs;
> > >
> > >      public BasicManagedDataSource(final String name) {
> > > @@ -85,7 +89,7 @@ public class BasicManagedDataSource extends
> > > org.apache.commons.dbcp2.managed.Bas
> > >
> > >      private void setJndiXaDataSource(final String xaDataSource) {
> > >          setXaDataSourceInstance( // proxy cause we don't know if this
> > > datasource was created before or not the delegate
> > > -            XADataSourceResource.proxy(getDriverClassLoader() != null
> ?
> > > getDriverClassLoader() :
> Thread.currentThread().getContextClassLoader(),
> > > xaDataSource));
> > > +                XADataSourceResource.proxy(getDriverClassLoader() !=
> > null
> > > ? getDriverClassLoader() :
> > Thread.currentThread().getContextClassLoader(),
> > > xaDataSource));
> > >
> > >          if (getTransactionManager() == null) {
> > >              setTransactionManager(OpenEJB.getTransactionManager());
> > > @@ -133,6 +137,19 @@ public class BasicManagedDataSource extends
> > > org.apache.commons.dbcp2.managed.Bas
> > >          }
> > >      }
> > >
> > > +    @Override
> > > +    public void setPassword(final String password) {
> > > +        final ReentrantLock l = lock;
> > > +        l.lock();
> > > +        try {
> > > +            // keep the encrypted value if it's encrypted
> > > +            this.initialPassword = password;
> > > +            super.setPassword(password);
> > > +        } finally {
> > > +            l.unlock();
> > > +        }
> > > +    }
> > > +
> > >      public String getUserName() {
> > >          final ReentrantLock l = lock;
> > >          l.lock();
> > > @@ -229,7 +246,9 @@ public class BasicManagedDataSource extends
> > > org.apache.commons.dbcp2.managed.Bas
> > >              // check password codec if available
> > >              if (null != passwordCipher) {
> > >                  final PasswordCipher cipher =
> > > PasswordCipherFactory.getPasswordCipher(passwordCipher);
> > > -                final String plainPwd =
> > > cipher.decrypt(getPassword().toCharArray());
> > > +
> > > +                // always use the initial encrypted value
> > > +                final String plainPwd =
> > > cipher.decrypt(initialPassword.toCharArray());
> > >
> > >                  // override previous password value
> > >                  super.setPassword(plainPwd);
> > >
> > >
> > >
> >
> http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
> > > ----------------------------------------------------------------------
> > > diff --git
> > >
> > >
> >
> a/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
> > >
> > >
> >
> b/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
> > > new file mode 100644
> > > index 0000000..0f1224f
> > > --- /dev/null
> > > +++
> > >
> > >
> >
> b/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
> > > @@ -0,0 +1,193 @@
> > > +/*
> > > + * 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.openejb.resource.jdbc;
> > > +
> > > +import org.apache.commons.dbcp.PoolableConnection;
> > > +import org.apache.openejb.cipher.PasswordCipher;
> > > +import org.apache.openejb.jee.EjbJar;
> > > +import org.apache.openejb.jee.SingletonBean;
> > > +import org.apache.openejb.junit.ApplicationComposer;
> > > +import org.apache.openejb.resource.jdbc.dbcp.BasicManagedDataSource;
> > > +import org.apache.openejb.testing.Configuration;
> > > +import org.apache.openejb.testing.Module;
> > > +import org.apache.openejb.util.Strings;
> > > +import org.apache.openejb.util.reflection.Reflections;
> > > +import org.junit.Before;
> > > +import org.junit.BeforeClass;
> > > +import org.junit.Test;
> > > +import org.junit.runner.RunWith;
> > > +
> > > +import javax.annotation.Resource;
> > > +import javax.ejb.EJB;
> > > +import javax.ejb.EJBContext;
> > > +import javax.ejb.LocalBean;
> > > +import javax.ejb.Singleton;
> > > +import javax.ejb.Startup;
> > > +import javax.ejb.TransactionAttribute;
> > > +import javax.ejb.TransactionAttributeType;
> > > +import javax.sql.DataSource;
> > > +import java.sql.Connection;
> > > +import java.sql.DriverManager;
> > > +import java.sql.DriverPropertyInfo;
> > > +import java.sql.ResultSet;
> > > +import java.sql.SQLException;
> > > +import java.sql.SQLFeatureNotSupportedException;
> > > +import java.sql.Statement;
> > > +import java.util.Properties;
> > > +import java.util.logging.Logger;
> > > +
> > > +import static org.junit.Assert.assertFalse;
> > > +import static org.junit.Assert.assertTrue;
> > > +
> > > +@RunWith(ApplicationComposer.class)
> > > +public class CipheredPasswordDataSourceTest {
> > > +    private static final String URL = "jdbc:fake://sthg:3306";
> > > +    private static final String USER = "pete";
> > > +    private static final String ENCRYPTED_PASSWORD = "This is the
> > > encrypted value.";
> > > +
> > > +    @EJB
> > > +    private Persister persistManager;
> > > +
> > > +    @Resource
> > > +    private DataSource ds;
> > > +
> > > +    @Configuration
> > > +    public Properties config() {
> > > +        final Properties p = new Properties();
> > > +        p.put("openejb.jdbc.datasource-creator", "dbcp");
> > > +
> > > +        p.put("managed", "new://Resource?type=DataSource");
> > > +        p.put("managed.JdbcDriver", FakeDriver.class.getName());
> > > +        p.put("managed.JdbcUrl", URL);
> > > +        p.put("managed.UserName", USER);
> > > +        p.put("managed.Password", ENCRYPTED_PASSWORD);
> > > +        p.put("managed.PasswordCipher",
> > > EmptyPasswordCipher.class.getName());
> > > +        p.put("managed.JtaManaged", "true");
> > > +        p.put("managed.initialSize", "10");
> > > +        p.put("managed.maxActive", "10");
> > > +        p.put("managed.maxIdle", "10");
> > > +        p.put("managed.minIdle", "10");
> > > +        p.put("managed.maxWait", "200");
> > > +        p.put("managed.defaultAutoCommit", "false");
> > > +        p.put("managed.defaultReadOnly", "false");
> > > +        p.put("managed.testOnBorrow", "true");
> > > +        p.put("managed.testOnReturn", "true");
> > > +        p.put("managed.testWhileIdle", "true");
> > > +
> > > +        return p;
> > > +    }
> > > +
> > > +    @Module
> > > +    public EjbJar app() throws Exception {
> > > +        return new EjbJar()
> > > +                .enterpriseBean(new
> > > SingletonBean(Persister.class).localBean());
> > > +
> > > +    }
> > > +
> > > +    @Before
> > > +    public void createDatasource(){
> > > +        // This is to make sure TomEE created the data source.
> > > +        persistManager.save();
> > > +    }
> > > +
> > > +    @Test
> > > +    public void rebuild() {
> > > +        // because of the exception at startup, the pool creating
> > aborted
> > > +        // before fixing this bug, the password was already decrypted,
> > but
> > > the data source field
> > > +        // wasn't yet initialized, so this.data source == null
> > > +        // but the next time we try to initialize it, it tries to
> > decrypt
> > > and already decrypted password
> > > +
> > > +        try {
> > > +            ds.getConnection();
> > > +
> > > +        } catch (final SQLException e) {
> > > +            System.out.println(e.getMessage());
> > > +            // success - it throws the SQLException from the connect
> > > +            // but it does not fail with the IllegalArgumentException
> > from
> > > the Password Cipher
> > > +        }
> > > +    }
> > > +
> > > +    public static class EmptyPasswordCipher implements PasswordCipher
> {
> > > +
> > > +        @Override
> > > +        public char[] encrypt(String plainPassword) {
> > > +            throw new RuntimeException("Should never be called in this
> > > test.");
> > > +        }
> > > +
> > > +        @Override
> > > +        public String decrypt(char[] encryptedPassword) {
> > > +            System.out.println(String.format(">>> Decrypt password
> > '%s'",
> > > new String(encryptedPassword)));
> > > +
> > > +            // we want to know if the password as already been called
> > > +            if (encryptedPassword.length == 0) {
> > > +                throw new IllegalArgumentException("Can only decrypt a
> > non
> > > empty string.");
> > > +            }
> > > +
> > > +            return "";
> > > +        }
> > > +    }
> > > +
> > > +    public static class FakeDriver implements java.sql.Driver {
> > > +        public boolean acceptsURL(final String url) throws
> SQLException
> > {
> > > +            return false;
> > > +        }
> > > +
> > > +        public Logger getParentLogger() throws
> > > SQLFeatureNotSupportedException {
> > > +            return null;
> > > +        }
> > > +
> > > +        public Connection connect(final String url, final Properties
> > info)
> > > throws SQLException {
> > > +            throw new SQLException("Pete said it first fails here!");
> > > +        }
> > > +
> > > +        public int getMajorVersion() {
> > > +            return 0;
> > > +        }
> > > +
> > > +        public int getMinorVersion() {
> > > +            return 0;
> > > +        }
> > > +
> > > +        public DriverPropertyInfo[] getPropertyInfo(final String url,
> > > final Properties info) throws SQLException {
> > > +            return new DriverPropertyInfo[0];
> > > +        }
> > > +
> > > +        public boolean jdbcCompliant() {
> > > +            return false;
> > > +        }
> > > +    }
> > > +
> > > +    @LocalBean
> > > +    @Singleton
> > > +    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
> > > +    public static class Persister {
> > > +
> > > +        @Resource(name = "managed")
> > > +        private DataSource ds;
> > > +
> > > +        public void save() {
> > > +            try {
> > > +                ds.getConnection();
> > > +
> > > +            } catch (SQLException e) {
> > > +                System.err.println("Generated SQL error > " +
> > > e.getMessage());
> > > +            }
> > > +        }
> > > +    }
> > > +
> > > +
> > > +}
> > >
> >
>

Re: Fwd: tomee git commit: TOMEE-1648: The password cipher is called each time the datasource is built even if we already have the clear password

Posted by Romain Manni-Bucau <rm...@gmail.com>.
Ok just checked and we didnt expose the setPassword to JMX for dbcp so we
are fine, sorry for the noise!


Romain Manni-Bucau
@rmannibucau <https://twitter.com/rmannibucau> |  Blog
<http://rmannibucau.wordpress.com> | Github <https://github.com/rmannibucau> |
LinkedIn <https://www.linkedin.com/in/rmannibucau> | Tomitriber
<http://www.tomitribe.com>

2015-10-30 17:39 GMT-07:00 Jean-Louis Monteiro <jl...@tomitribe.com>:

> Sure. Maybe the fix can be improved. Let's discuss
> Le 30 oct. 2015 17:37, "Romain Manni-Bucau" <rm...@gmail.com> a
> écrit :
>
> > It prevents to reset the pwd at runtime if i read it correctly. It should
> > be fixed since it is used at least through jmx.
> > ---------- Message transféré ----------
> > De : <jl...@apache.org>
> > Date : 30 oct. 2015 16:14
> > Objet : tomee git commit: TOMEE-1648: The password cipher is called each
> > time the datasource is built even if we already have the clear password
> > À : <co...@tomee.apache.org>
> > Cc :
> >
> > Repository: tomee
> > Updated Branches:
> >   refs/heads/master 34c4cc7a2 -> 436089b65
> >
> >
> > TOMEE-1648: The password cipher is called each time the datasource is
> built
> > even if we already have the clear password
> >
> >
> > Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
> > Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/436089b6
> > Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/436089b6
> > Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/436089b6
> >
> > Branch: refs/heads/master
> > Commit: 436089b658e239757e0a51b49be01974ca339627
> > Parents: 34c4cc7
> > Author: Jean-Louis Monteiro <jl...@apache.org>
> > Authored: Fri Oct 30 16:13:56 2015 -0700
> > Committer: Jean-Louis Monteiro <jl...@apache.org>
> > Committed: Fri Oct 30 16:13:56 2015 -0700
> >
> > ----------------------------------------------------------------------
> >  .../resource/jdbc/dbcp/BasicDataSource.java     |  20 +-
> >  .../jdbc/dbcp/BasicManagedDataSource.java       |  23 ++-
> >  .../jdbc/CipheredPasswordDataSourceTest.java    | 193
> +++++++++++++++++++
> >  3 files changed, 233 insertions(+), 3 deletions(-)
> > ----------------------------------------------------------------------
> >
> >
> >
> >
> http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> > ----------------------------------------------------------------------
> > diff --git
> >
> >
> a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> >
> >
> b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> > index ffb5bba..995221c 100644
> > ---
> >
> >
> a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> > +++
> >
> >
> b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> > @@ -55,6 +55,10 @@ public class BasicDataSource extends
> > org.apache.commons.dbcp2.BasicDataSource im
> >       * not ciphered. The {@link
> > org.apache.openejb.cipher.PlainTextPasswordCipher} can also be used.
> >       */
> >      private String passwordCipher;
> > +
> > +    // keep tracking the user configured password in case we need it to
> be
> > decrypted again
> > +    private String initialPassword;
> > +
> >      private JMXBasicDataSource jmxDs;
> >      private CommonDataSource delegate;
> >      private String name;
> > @@ -128,6 +132,18 @@ public class BasicDataSource extends
> > org.apache.commons.dbcp2.BasicDataSource im
> >          }
> >      }
> >
> > +    @Override
> > +    public void setPassword(final String password) {
> > +        final ReentrantLock l = lock;
> > +        l.lock();
> > +        try {
> > +            // keep the encrypted value if it's encrypted
> > +            this.initialPassword = password;
> > +            super.setPassword(password);
> > +        } finally {
> > +            l.unlock();
> > +        }
> > +    }
> >
> >      public String getUserName() {
> >          final ReentrantLock l = lock;
> > @@ -226,7 +242,9 @@ public class BasicDataSource extends
> > org.apache.commons.dbcp2.BasicDataSource im
> >              // check password codec if available
> >              if (null != passwordCipher &&
> > !"PlainText".equals(passwordCipher)) {
> >                  final PasswordCipher cipher =
> > PasswordCipherFactory.getPasswordCipher(passwordCipher);
> > -                final String plainPwd =
> > cipher.decrypt(getPassword().toCharArray());
> > +
> > +                // always use the initial encrypted value
> > +                final String plainPwd =
> > cipher.decrypt(initialPassword.toCharArray());
> >
> >                  // override previous password value
> >                  super.setPassword(plainPwd);
> >
> >
> >
> http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> > ----------------------------------------------------------------------
> > diff --git
> >
> >
> a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> >
> >
> b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> > index ec604b0..0846f21 100644
> > ---
> >
> >
> a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> > +++
> >
> >
> b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> > @@ -54,6 +54,10 @@ public class BasicManagedDataSource extends
> > org.apache.commons.dbcp2.managed.Bas
> >       * not ciphered. The {@link
> > org.apache.openejb.cipher.PlainTextPasswordCipher} can also be used.
> >       */
> >      private String passwordCipher;
> > +
> > +    // keep tracking the user configured password in case we need it to
> be
> > decrypted again
> > +    private String initialPassword;
> > +
> >      private JMXBasicDataSource jmxDs;
> >
> >      public BasicManagedDataSource(final String name) {
> > @@ -85,7 +89,7 @@ public class BasicManagedDataSource extends
> > org.apache.commons.dbcp2.managed.Bas
> >
> >      private void setJndiXaDataSource(final String xaDataSource) {
> >          setXaDataSourceInstance( // proxy cause we don't know if this
> > datasource was created before or not the delegate
> > -            XADataSourceResource.proxy(getDriverClassLoader() != null ?
> > getDriverClassLoader() : Thread.currentThread().getContextClassLoader(),
> > xaDataSource));
> > +                XADataSourceResource.proxy(getDriverClassLoader() !=
> null
> > ? getDriverClassLoader() :
> Thread.currentThread().getContextClassLoader(),
> > xaDataSource));
> >
> >          if (getTransactionManager() == null) {
> >              setTransactionManager(OpenEJB.getTransactionManager());
> > @@ -133,6 +137,19 @@ public class BasicManagedDataSource extends
> > org.apache.commons.dbcp2.managed.Bas
> >          }
> >      }
> >
> > +    @Override
> > +    public void setPassword(final String password) {
> > +        final ReentrantLock l = lock;
> > +        l.lock();
> > +        try {
> > +            // keep the encrypted value if it's encrypted
> > +            this.initialPassword = password;
> > +            super.setPassword(password);
> > +        } finally {
> > +            l.unlock();
> > +        }
> > +    }
> > +
> >      public String getUserName() {
> >          final ReentrantLock l = lock;
> >          l.lock();
> > @@ -229,7 +246,9 @@ public class BasicManagedDataSource extends
> > org.apache.commons.dbcp2.managed.Bas
> >              // check password codec if available
> >              if (null != passwordCipher) {
> >                  final PasswordCipher cipher =
> > PasswordCipherFactory.getPasswordCipher(passwordCipher);
> > -                final String plainPwd =
> > cipher.decrypt(getPassword().toCharArray());
> > +
> > +                // always use the initial encrypted value
> > +                final String plainPwd =
> > cipher.decrypt(initialPassword.toCharArray());
> >
> >                  // override previous password value
> >                  super.setPassword(plainPwd);
> >
> >
> >
> http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
> > ----------------------------------------------------------------------
> > diff --git
> >
> >
> a/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
> >
> >
> b/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
> > new file mode 100644
> > index 0000000..0f1224f
> > --- /dev/null
> > +++
> >
> >
> b/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
> > @@ -0,0 +1,193 @@
> > +/*
> > + * 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.openejb.resource.jdbc;
> > +
> > +import org.apache.commons.dbcp.PoolableConnection;
> > +import org.apache.openejb.cipher.PasswordCipher;
> > +import org.apache.openejb.jee.EjbJar;
> > +import org.apache.openejb.jee.SingletonBean;
> > +import org.apache.openejb.junit.ApplicationComposer;
> > +import org.apache.openejb.resource.jdbc.dbcp.BasicManagedDataSource;
> > +import org.apache.openejb.testing.Configuration;
> > +import org.apache.openejb.testing.Module;
> > +import org.apache.openejb.util.Strings;
> > +import org.apache.openejb.util.reflection.Reflections;
> > +import org.junit.Before;
> > +import org.junit.BeforeClass;
> > +import org.junit.Test;
> > +import org.junit.runner.RunWith;
> > +
> > +import javax.annotation.Resource;
> > +import javax.ejb.EJB;
> > +import javax.ejb.EJBContext;
> > +import javax.ejb.LocalBean;
> > +import javax.ejb.Singleton;
> > +import javax.ejb.Startup;
> > +import javax.ejb.TransactionAttribute;
> > +import javax.ejb.TransactionAttributeType;
> > +import javax.sql.DataSource;
> > +import java.sql.Connection;
> > +import java.sql.DriverManager;
> > +import java.sql.DriverPropertyInfo;
> > +import java.sql.ResultSet;
> > +import java.sql.SQLException;
> > +import java.sql.SQLFeatureNotSupportedException;
> > +import java.sql.Statement;
> > +import java.util.Properties;
> > +import java.util.logging.Logger;
> > +
> > +import static org.junit.Assert.assertFalse;
> > +import static org.junit.Assert.assertTrue;
> > +
> > +@RunWith(ApplicationComposer.class)
> > +public class CipheredPasswordDataSourceTest {
> > +    private static final String URL = "jdbc:fake://sthg:3306";
> > +    private static final String USER = "pete";
> > +    private static final String ENCRYPTED_PASSWORD = "This is the
> > encrypted value.";
> > +
> > +    @EJB
> > +    private Persister persistManager;
> > +
> > +    @Resource
> > +    private DataSource ds;
> > +
> > +    @Configuration
> > +    public Properties config() {
> > +        final Properties p = new Properties();
> > +        p.put("openejb.jdbc.datasource-creator", "dbcp");
> > +
> > +        p.put("managed", "new://Resource?type=DataSource");
> > +        p.put("managed.JdbcDriver", FakeDriver.class.getName());
> > +        p.put("managed.JdbcUrl", URL);
> > +        p.put("managed.UserName", USER);
> > +        p.put("managed.Password", ENCRYPTED_PASSWORD);
> > +        p.put("managed.PasswordCipher",
> > EmptyPasswordCipher.class.getName());
> > +        p.put("managed.JtaManaged", "true");
> > +        p.put("managed.initialSize", "10");
> > +        p.put("managed.maxActive", "10");
> > +        p.put("managed.maxIdle", "10");
> > +        p.put("managed.minIdle", "10");
> > +        p.put("managed.maxWait", "200");
> > +        p.put("managed.defaultAutoCommit", "false");
> > +        p.put("managed.defaultReadOnly", "false");
> > +        p.put("managed.testOnBorrow", "true");
> > +        p.put("managed.testOnReturn", "true");
> > +        p.put("managed.testWhileIdle", "true");
> > +
> > +        return p;
> > +    }
> > +
> > +    @Module
> > +    public EjbJar app() throws Exception {
> > +        return new EjbJar()
> > +                .enterpriseBean(new
> > SingletonBean(Persister.class).localBean());
> > +
> > +    }
> > +
> > +    @Before
> > +    public void createDatasource(){
> > +        // This is to make sure TomEE created the data source.
> > +        persistManager.save();
> > +    }
> > +
> > +    @Test
> > +    public void rebuild() {
> > +        // because of the exception at startup, the pool creating
> aborted
> > +        // before fixing this bug, the password was already decrypted,
> but
> > the data source field
> > +        // wasn't yet initialized, so this.data source == null
> > +        // but the next time we try to initialize it, it tries to
> decrypt
> > and already decrypted password
> > +
> > +        try {
> > +            ds.getConnection();
> > +
> > +        } catch (final SQLException e) {
> > +            System.out.println(e.getMessage());
> > +            // success - it throws the SQLException from the connect
> > +            // but it does not fail with the IllegalArgumentException
> from
> > the Password Cipher
> > +        }
> > +    }
> > +
> > +    public static class EmptyPasswordCipher implements PasswordCipher {
> > +
> > +        @Override
> > +        public char[] encrypt(String plainPassword) {
> > +            throw new RuntimeException("Should never be called in this
> > test.");
> > +        }
> > +
> > +        @Override
> > +        public String decrypt(char[] encryptedPassword) {
> > +            System.out.println(String.format(">>> Decrypt password
> '%s'",
> > new String(encryptedPassword)));
> > +
> > +            // we want to know if the password as already been called
> > +            if (encryptedPassword.length == 0) {
> > +                throw new IllegalArgumentException("Can only decrypt a
> non
> > empty string.");
> > +            }
> > +
> > +            return "";
> > +        }
> > +    }
> > +
> > +    public static class FakeDriver implements java.sql.Driver {
> > +        public boolean acceptsURL(final String url) throws SQLException
> {
> > +            return false;
> > +        }
> > +
> > +        public Logger getParentLogger() throws
> > SQLFeatureNotSupportedException {
> > +            return null;
> > +        }
> > +
> > +        public Connection connect(final String url, final Properties
> info)
> > throws SQLException {
> > +            throw new SQLException("Pete said it first fails here!");
> > +        }
> > +
> > +        public int getMajorVersion() {
> > +            return 0;
> > +        }
> > +
> > +        public int getMinorVersion() {
> > +            return 0;
> > +        }
> > +
> > +        public DriverPropertyInfo[] getPropertyInfo(final String url,
> > final Properties info) throws SQLException {
> > +            return new DriverPropertyInfo[0];
> > +        }
> > +
> > +        public boolean jdbcCompliant() {
> > +            return false;
> > +        }
> > +    }
> > +
> > +    @LocalBean
> > +    @Singleton
> > +    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
> > +    public static class Persister {
> > +
> > +        @Resource(name = "managed")
> > +        private DataSource ds;
> > +
> > +        public void save() {
> > +            try {
> > +                ds.getConnection();
> > +
> > +            } catch (SQLException e) {
> > +                System.err.println("Generated SQL error > " +
> > e.getMessage());
> > +            }
> > +        }
> > +    }
> > +
> > +
> > +}
> >
>

Re: Fwd: tomee git commit: TOMEE-1648: The password cipher is called each time the datasource is built even if we already have the clear password

Posted by Jean-Louis Monteiro <jl...@tomitribe.com>.
Sure. Maybe the fix can be improved. Let's discuss
Le 30 oct. 2015 17:37, "Romain Manni-Bucau" <rm...@gmail.com> a
écrit :

> It prevents to reset the pwd at runtime if i read it correctly. It should
> be fixed since it is used at least through jmx.
> ---------- Message transféré ----------
> De : <jl...@apache.org>
> Date : 30 oct. 2015 16:14
> Objet : tomee git commit: TOMEE-1648: The password cipher is called each
> time the datasource is built even if we already have the clear password
> À : <co...@tomee.apache.org>
> Cc :
>
> Repository: tomee
> Updated Branches:
>   refs/heads/master 34c4cc7a2 -> 436089b65
>
>
> TOMEE-1648: The password cipher is called each time the datasource is built
> even if we already have the clear password
>
>
> Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
> Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/436089b6
> Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/436089b6
> Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/436089b6
>
> Branch: refs/heads/master
> Commit: 436089b658e239757e0a51b49be01974ca339627
> Parents: 34c4cc7
> Author: Jean-Louis Monteiro <jl...@apache.org>
> Authored: Fri Oct 30 16:13:56 2015 -0700
> Committer: Jean-Louis Monteiro <jl...@apache.org>
> Committed: Fri Oct 30 16:13:56 2015 -0700
>
> ----------------------------------------------------------------------
>  .../resource/jdbc/dbcp/BasicDataSource.java     |  20 +-
>  .../jdbc/dbcp/BasicManagedDataSource.java       |  23 ++-
>  .../jdbc/CipheredPasswordDataSourceTest.java    | 193 +++++++++++++++++++
>  3 files changed, 233 insertions(+), 3 deletions(-)
> ----------------------------------------------------------------------
>
>
>
> http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> ----------------------------------------------------------------------
> diff --git
>
> a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
>
> b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> index ffb5bba..995221c 100644
> ---
>
> a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> +++
>
> b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
> @@ -55,6 +55,10 @@ public class BasicDataSource extends
> org.apache.commons.dbcp2.BasicDataSource im
>       * not ciphered. The {@link
> org.apache.openejb.cipher.PlainTextPasswordCipher} can also be used.
>       */
>      private String passwordCipher;
> +
> +    // keep tracking the user configured password in case we need it to be
> decrypted again
> +    private String initialPassword;
> +
>      private JMXBasicDataSource jmxDs;
>      private CommonDataSource delegate;
>      private String name;
> @@ -128,6 +132,18 @@ public class BasicDataSource extends
> org.apache.commons.dbcp2.BasicDataSource im
>          }
>      }
>
> +    @Override
> +    public void setPassword(final String password) {
> +        final ReentrantLock l = lock;
> +        l.lock();
> +        try {
> +            // keep the encrypted value if it's encrypted
> +            this.initialPassword = password;
> +            super.setPassword(password);
> +        } finally {
> +            l.unlock();
> +        }
> +    }
>
>      public String getUserName() {
>          final ReentrantLock l = lock;
> @@ -226,7 +242,9 @@ public class BasicDataSource extends
> org.apache.commons.dbcp2.BasicDataSource im
>              // check password codec if available
>              if (null != passwordCipher &&
> !"PlainText".equals(passwordCipher)) {
>                  final PasswordCipher cipher =
> PasswordCipherFactory.getPasswordCipher(passwordCipher);
> -                final String plainPwd =
> cipher.decrypt(getPassword().toCharArray());
> +
> +                // always use the initial encrypted value
> +                final String plainPwd =
> cipher.decrypt(initialPassword.toCharArray());
>
>                  // override previous password value
>                  super.setPassword(plainPwd);
>
>
> http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> ----------------------------------------------------------------------
> diff --git
>
> a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
>
> b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> index ec604b0..0846f21 100644
> ---
>
> a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> +++
>
> b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
> @@ -54,6 +54,10 @@ public class BasicManagedDataSource extends
> org.apache.commons.dbcp2.managed.Bas
>       * not ciphered. The {@link
> org.apache.openejb.cipher.PlainTextPasswordCipher} can also be used.
>       */
>      private String passwordCipher;
> +
> +    // keep tracking the user configured password in case we need it to be
> decrypted again
> +    private String initialPassword;
> +
>      private JMXBasicDataSource jmxDs;
>
>      public BasicManagedDataSource(final String name) {
> @@ -85,7 +89,7 @@ public class BasicManagedDataSource extends
> org.apache.commons.dbcp2.managed.Bas
>
>      private void setJndiXaDataSource(final String xaDataSource) {
>          setXaDataSourceInstance( // proxy cause we don't know if this
> datasource was created before or not the delegate
> -            XADataSourceResource.proxy(getDriverClassLoader() != null ?
> getDriverClassLoader() : Thread.currentThread().getContextClassLoader(),
> xaDataSource));
> +                XADataSourceResource.proxy(getDriverClassLoader() != null
> ? getDriverClassLoader() : Thread.currentThread().getContextClassLoader(),
> xaDataSource));
>
>          if (getTransactionManager() == null) {
>              setTransactionManager(OpenEJB.getTransactionManager());
> @@ -133,6 +137,19 @@ public class BasicManagedDataSource extends
> org.apache.commons.dbcp2.managed.Bas
>          }
>      }
>
> +    @Override
> +    public void setPassword(final String password) {
> +        final ReentrantLock l = lock;
> +        l.lock();
> +        try {
> +            // keep the encrypted value if it's encrypted
> +            this.initialPassword = password;
> +            super.setPassword(password);
> +        } finally {
> +            l.unlock();
> +        }
> +    }
> +
>      public String getUserName() {
>          final ReentrantLock l = lock;
>          l.lock();
> @@ -229,7 +246,9 @@ public class BasicManagedDataSource extends
> org.apache.commons.dbcp2.managed.Bas
>              // check password codec if available
>              if (null != passwordCipher) {
>                  final PasswordCipher cipher =
> PasswordCipherFactory.getPasswordCipher(passwordCipher);
> -                final String plainPwd =
> cipher.decrypt(getPassword().toCharArray());
> +
> +                // always use the initial encrypted value
> +                final String plainPwd =
> cipher.decrypt(initialPassword.toCharArray());
>
>                  // override previous password value
>                  super.setPassword(plainPwd);
>
>
> http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
> ----------------------------------------------------------------------
> diff --git
>
> a/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
>
> b/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
> new file mode 100644
> index 0000000..0f1224f
> --- /dev/null
> +++
>
> b/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
> @@ -0,0 +1,193 @@
> +/*
> + * 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.openejb.resource.jdbc;
> +
> +import org.apache.commons.dbcp.PoolableConnection;
> +import org.apache.openejb.cipher.PasswordCipher;
> +import org.apache.openejb.jee.EjbJar;
> +import org.apache.openejb.jee.SingletonBean;
> +import org.apache.openejb.junit.ApplicationComposer;
> +import org.apache.openejb.resource.jdbc.dbcp.BasicManagedDataSource;
> +import org.apache.openejb.testing.Configuration;
> +import org.apache.openejb.testing.Module;
> +import org.apache.openejb.util.Strings;
> +import org.apache.openejb.util.reflection.Reflections;
> +import org.junit.Before;
> +import org.junit.BeforeClass;
> +import org.junit.Test;
> +import org.junit.runner.RunWith;
> +
> +import javax.annotation.Resource;
> +import javax.ejb.EJB;
> +import javax.ejb.EJBContext;
> +import javax.ejb.LocalBean;
> +import javax.ejb.Singleton;
> +import javax.ejb.Startup;
> +import javax.ejb.TransactionAttribute;
> +import javax.ejb.TransactionAttributeType;
> +import javax.sql.DataSource;
> +import java.sql.Connection;
> +import java.sql.DriverManager;
> +import java.sql.DriverPropertyInfo;
> +import java.sql.ResultSet;
> +import java.sql.SQLException;
> +import java.sql.SQLFeatureNotSupportedException;
> +import java.sql.Statement;
> +import java.util.Properties;
> +import java.util.logging.Logger;
> +
> +import static org.junit.Assert.assertFalse;
> +import static org.junit.Assert.assertTrue;
> +
> +@RunWith(ApplicationComposer.class)
> +public class CipheredPasswordDataSourceTest {
> +    private static final String URL = "jdbc:fake://sthg:3306";
> +    private static final String USER = "pete";
> +    private static final String ENCRYPTED_PASSWORD = "This is the
> encrypted value.";
> +
> +    @EJB
> +    private Persister persistManager;
> +
> +    @Resource
> +    private DataSource ds;
> +
> +    @Configuration
> +    public Properties config() {
> +        final Properties p = new Properties();
> +        p.put("openejb.jdbc.datasource-creator", "dbcp");
> +
> +        p.put("managed", "new://Resource?type=DataSource");
> +        p.put("managed.JdbcDriver", FakeDriver.class.getName());
> +        p.put("managed.JdbcUrl", URL);
> +        p.put("managed.UserName", USER);
> +        p.put("managed.Password", ENCRYPTED_PASSWORD);
> +        p.put("managed.PasswordCipher",
> EmptyPasswordCipher.class.getName());
> +        p.put("managed.JtaManaged", "true");
> +        p.put("managed.initialSize", "10");
> +        p.put("managed.maxActive", "10");
> +        p.put("managed.maxIdle", "10");
> +        p.put("managed.minIdle", "10");
> +        p.put("managed.maxWait", "200");
> +        p.put("managed.defaultAutoCommit", "false");
> +        p.put("managed.defaultReadOnly", "false");
> +        p.put("managed.testOnBorrow", "true");
> +        p.put("managed.testOnReturn", "true");
> +        p.put("managed.testWhileIdle", "true");
> +
> +        return p;
> +    }
> +
> +    @Module
> +    public EjbJar app() throws Exception {
> +        return new EjbJar()
> +                .enterpriseBean(new
> SingletonBean(Persister.class).localBean());
> +
> +    }
> +
> +    @Before
> +    public void createDatasource(){
> +        // This is to make sure TomEE created the data source.
> +        persistManager.save();
> +    }
> +
> +    @Test
> +    public void rebuild() {
> +        // because of the exception at startup, the pool creating aborted
> +        // before fixing this bug, the password was already decrypted, but
> the data source field
> +        // wasn't yet initialized, so this.data source == null
> +        // but the next time we try to initialize it, it tries to decrypt
> and already decrypted password
> +
> +        try {
> +            ds.getConnection();
> +
> +        } catch (final SQLException e) {
> +            System.out.println(e.getMessage());
> +            // success - it throws the SQLException from the connect
> +            // but it does not fail with the IllegalArgumentException from
> the Password Cipher
> +        }
> +    }
> +
> +    public static class EmptyPasswordCipher implements PasswordCipher {
> +
> +        @Override
> +        public char[] encrypt(String plainPassword) {
> +            throw new RuntimeException("Should never be called in this
> test.");
> +        }
> +
> +        @Override
> +        public String decrypt(char[] encryptedPassword) {
> +            System.out.println(String.format(">>> Decrypt password '%s'",
> new String(encryptedPassword)));
> +
> +            // we want to know if the password as already been called
> +            if (encryptedPassword.length == 0) {
> +                throw new IllegalArgumentException("Can only decrypt a non
> empty string.");
> +            }
> +
> +            return "";
> +        }
> +    }
> +
> +    public static class FakeDriver implements java.sql.Driver {
> +        public boolean acceptsURL(final String url) throws SQLException {
> +            return false;
> +        }
> +
> +        public Logger getParentLogger() throws
> SQLFeatureNotSupportedException {
> +            return null;
> +        }
> +
> +        public Connection connect(final String url, final Properties info)
> throws SQLException {
> +            throw new SQLException("Pete said it first fails here!");
> +        }
> +
> +        public int getMajorVersion() {
> +            return 0;
> +        }
> +
> +        public int getMinorVersion() {
> +            return 0;
> +        }
> +
> +        public DriverPropertyInfo[] getPropertyInfo(final String url,
> final Properties info) throws SQLException {
> +            return new DriverPropertyInfo[0];
> +        }
> +
> +        public boolean jdbcCompliant() {
> +            return false;
> +        }
> +    }
> +
> +    @LocalBean
> +    @Singleton
> +    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
> +    public static class Persister {
> +
> +        @Resource(name = "managed")
> +        private DataSource ds;
> +
> +        public void save() {
> +            try {
> +                ds.getConnection();
> +
> +            } catch (SQLException e) {
> +                System.err.println("Generated SQL error > " +
> e.getMessage());
> +            }
> +        }
> +    }
> +
> +
> +}
>

Fwd: tomee git commit: TOMEE-1648: The password cipher is called each time the datasource is built even if we already have the clear password

Posted by Romain Manni-Bucau <rm...@gmail.com>.
It prevents to reset the pwd at runtime if i read it correctly. It should
be fixed since it is used at least through jmx.
---------- Message transféré ----------
De : <jl...@apache.org>
Date : 30 oct. 2015 16:14
Objet : tomee git commit: TOMEE-1648: The password cipher is called each
time the datasource is built even if we already have the clear password
À : <co...@tomee.apache.org>
Cc :

Repository: tomee
Updated Branches:
  refs/heads/master 34c4cc7a2 -> 436089b65


TOMEE-1648: The password cipher is called each time the datasource is built
even if we already have the clear password


Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/436089b6
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/436089b6
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/436089b6

Branch: refs/heads/master
Commit: 436089b658e239757e0a51b49be01974ca339627
Parents: 34c4cc7
Author: Jean-Louis Monteiro <jl...@apache.org>
Authored: Fri Oct 30 16:13:56 2015 -0700
Committer: Jean-Louis Monteiro <jl...@apache.org>
Committed: Fri Oct 30 16:13:56 2015 -0700

----------------------------------------------------------------------
 .../resource/jdbc/dbcp/BasicDataSource.java     |  20 +-
 .../jdbc/dbcp/BasicManagedDataSource.java       |  23 ++-
 .../jdbc/CipheredPasswordDataSourceTest.java    | 193 +++++++++++++++++++
 3 files changed, 233 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
----------------------------------------------------------------------
diff --git
a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
index ffb5bba..995221c 100644
---
a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
+++
b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicDataSource.java
@@ -55,6 +55,10 @@ public class BasicDataSource extends
org.apache.commons.dbcp2.BasicDataSource im
      * not ciphered. The {@link
org.apache.openejb.cipher.PlainTextPasswordCipher} can also be used.
      */
     private String passwordCipher;
+
+    // keep tracking the user configured password in case we need it to be
decrypted again
+    private String initialPassword;
+
     private JMXBasicDataSource jmxDs;
     private CommonDataSource delegate;
     private String name;
@@ -128,6 +132,18 @@ public class BasicDataSource extends
org.apache.commons.dbcp2.BasicDataSource im
         }
     }

+    @Override
+    public void setPassword(final String password) {
+        final ReentrantLock l = lock;
+        l.lock();
+        try {
+            // keep the encrypted value if it's encrypted
+            this.initialPassword = password;
+            super.setPassword(password);
+        } finally {
+            l.unlock();
+        }
+    }

     public String getUserName() {
         final ReentrantLock l = lock;
@@ -226,7 +242,9 @@ public class BasicDataSource extends
org.apache.commons.dbcp2.BasicDataSource im
             // check password codec if available
             if (null != passwordCipher &&
!"PlainText".equals(passwordCipher)) {
                 final PasswordCipher cipher =
PasswordCipherFactory.getPasswordCipher(passwordCipher);
-                final String plainPwd =
cipher.decrypt(getPassword().toCharArray());
+
+                // always use the initial encrypted value
+                final String plainPwd =
cipher.decrypt(initialPassword.toCharArray());

                 // override previous password value
                 super.setPassword(plainPwd);

http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
----------------------------------------------------------------------
diff --git
a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
index ec604b0..0846f21 100644
---
a/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
+++
b/container/openejb-core/src/main/java/org/apache/openejb/resource/jdbc/dbcp/BasicManagedDataSource.java
@@ -54,6 +54,10 @@ public class BasicManagedDataSource extends
org.apache.commons.dbcp2.managed.Bas
      * not ciphered. The {@link
org.apache.openejb.cipher.PlainTextPasswordCipher} can also be used.
      */
     private String passwordCipher;
+
+    // keep tracking the user configured password in case we need it to be
decrypted again
+    private String initialPassword;
+
     private JMXBasicDataSource jmxDs;

     public BasicManagedDataSource(final String name) {
@@ -85,7 +89,7 @@ public class BasicManagedDataSource extends
org.apache.commons.dbcp2.managed.Bas

     private void setJndiXaDataSource(final String xaDataSource) {
         setXaDataSourceInstance( // proxy cause we don't know if this
datasource was created before or not the delegate
-            XADataSourceResource.proxy(getDriverClassLoader() != null ?
getDriverClassLoader() : Thread.currentThread().getContextClassLoader(),
xaDataSource));
+                XADataSourceResource.proxy(getDriverClassLoader() != null
? getDriverClassLoader() : Thread.currentThread().getContextClassLoader(),
xaDataSource));

         if (getTransactionManager() == null) {
             setTransactionManager(OpenEJB.getTransactionManager());
@@ -133,6 +137,19 @@ public class BasicManagedDataSource extends
org.apache.commons.dbcp2.managed.Bas
         }
     }

+    @Override
+    public void setPassword(final String password) {
+        final ReentrantLock l = lock;
+        l.lock();
+        try {
+            // keep the encrypted value if it's encrypted
+            this.initialPassword = password;
+            super.setPassword(password);
+        } finally {
+            l.unlock();
+        }
+    }
+
     public String getUserName() {
         final ReentrantLock l = lock;
         l.lock();
@@ -229,7 +246,9 @@ public class BasicManagedDataSource extends
org.apache.commons.dbcp2.managed.Bas
             // check password codec if available
             if (null != passwordCipher) {
                 final PasswordCipher cipher =
PasswordCipherFactory.getPasswordCipher(passwordCipher);
-                final String plainPwd =
cipher.decrypt(getPassword().toCharArray());
+
+                // always use the initial encrypted value
+                final String plainPwd =
cipher.decrypt(initialPassword.toCharArray());

                 // override previous password value
                 super.setPassword(plainPwd);

http://git-wip-us.apache.org/repos/asf/tomee/blob/436089b6/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
----------------------------------------------------------------------
diff --git
a/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
b/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
new file mode 100644
index 0000000..0f1224f
--- /dev/null
+++
b/container/openejb-core/src/test/java/org/apache/openejb/resource/jdbc/CipheredPasswordDataSourceTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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.openejb.resource.jdbc;
+
+import org.apache.commons.dbcp.PoolableConnection;
+import org.apache.openejb.cipher.PasswordCipher;
+import org.apache.openejb.jee.EjbJar;
+import org.apache.openejb.jee.SingletonBean;
+import org.apache.openejb.junit.ApplicationComposer;
+import org.apache.openejb.resource.jdbc.dbcp.BasicManagedDataSource;
+import org.apache.openejb.testing.Configuration;
+import org.apache.openejb.testing.Module;
+import org.apache.openejb.util.Strings;
+import org.apache.openejb.util.reflection.Reflections;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import javax.annotation.Resource;
+import javax.ejb.EJB;
+import javax.ejb.EJBContext;
+import javax.ejb.LocalBean;
+import javax.ejb.Singleton;
+import javax.ejb.Startup;
+import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.DriverPropertyInfo;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(ApplicationComposer.class)
+public class CipheredPasswordDataSourceTest {
+    private static final String URL = "jdbc:fake://sthg:3306";
+    private static final String USER = "pete";
+    private static final String ENCRYPTED_PASSWORD = "This is the
encrypted value.";
+
+    @EJB
+    private Persister persistManager;
+
+    @Resource
+    private DataSource ds;
+
+    @Configuration
+    public Properties config() {
+        final Properties p = new Properties();
+        p.put("openejb.jdbc.datasource-creator", "dbcp");
+
+        p.put("managed", "new://Resource?type=DataSource");
+        p.put("managed.JdbcDriver", FakeDriver.class.getName());
+        p.put("managed.JdbcUrl", URL);
+        p.put("managed.UserName", USER);
+        p.put("managed.Password", ENCRYPTED_PASSWORD);
+        p.put("managed.PasswordCipher",
EmptyPasswordCipher.class.getName());
+        p.put("managed.JtaManaged", "true");
+        p.put("managed.initialSize", "10");
+        p.put("managed.maxActive", "10");
+        p.put("managed.maxIdle", "10");
+        p.put("managed.minIdle", "10");
+        p.put("managed.maxWait", "200");
+        p.put("managed.defaultAutoCommit", "false");
+        p.put("managed.defaultReadOnly", "false");
+        p.put("managed.testOnBorrow", "true");
+        p.put("managed.testOnReturn", "true");
+        p.put("managed.testWhileIdle", "true");
+
+        return p;
+    }
+
+    @Module
+    public EjbJar app() throws Exception {
+        return new EjbJar()
+                .enterpriseBean(new
SingletonBean(Persister.class).localBean());
+
+    }
+
+    @Before
+    public void createDatasource(){
+        // This is to make sure TomEE created the data source.
+        persistManager.save();
+    }
+
+    @Test
+    public void rebuild() {
+        // because of the exception at startup, the pool creating aborted
+        // before fixing this bug, the password was already decrypted, but
the data source field
+        // wasn't yet initialized, so this.data source == null
+        // but the next time we try to initialize it, it tries to decrypt
and already decrypted password
+
+        try {
+            ds.getConnection();
+
+        } catch (final SQLException e) {
+            System.out.println(e.getMessage());
+            // success - it throws the SQLException from the connect
+            // but it does not fail with the IllegalArgumentException from
the Password Cipher
+        }
+    }
+
+    public static class EmptyPasswordCipher implements PasswordCipher {
+
+        @Override
+        public char[] encrypt(String plainPassword) {
+            throw new RuntimeException("Should never be called in this
test.");
+        }
+
+        @Override
+        public String decrypt(char[] encryptedPassword) {
+            System.out.println(String.format(">>> Decrypt password '%s'",
new String(encryptedPassword)));
+
+            // we want to know if the password as already been called
+            if (encryptedPassword.length == 0) {
+                throw new IllegalArgumentException("Can only decrypt a non
empty string.");
+            }
+
+            return "";
+        }
+    }
+
+    public static class FakeDriver implements java.sql.Driver {
+        public boolean acceptsURL(final String url) throws SQLException {
+            return false;
+        }
+
+        public Logger getParentLogger() throws
SQLFeatureNotSupportedException {
+            return null;
+        }
+
+        public Connection connect(final String url, final Properties info)
throws SQLException {
+            throw new SQLException("Pete said it first fails here!");
+        }
+
+        public int getMajorVersion() {
+            return 0;
+        }
+
+        public int getMinorVersion() {
+            return 0;
+        }
+
+        public DriverPropertyInfo[] getPropertyInfo(final String url,
final Properties info) throws SQLException {
+            return new DriverPropertyInfo[0];
+        }
+
+        public boolean jdbcCompliant() {
+            return false;
+        }
+    }
+
+    @LocalBean
+    @Singleton
+    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
+    public static class Persister {
+
+        @Resource(name = "managed")
+        private DataSource ds;
+
+        public void save() {
+            try {
+                ds.getConnection();
+
+            } catch (SQLException e) {
+                System.err.println("Generated SQL error > " +
e.getMessage());
+            }
+        }
+    }
+
+
+}