Implement keyring change password
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 5 Feb 2018 13:42:55 +0000 (14:42 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 5 Feb 2018 13:42:55 +0000 (14:42 +0100)
17 files changed:
org.argeo.cms.ui.workbench.rap/META-INF/spring/commands.xml
org.argeo.cms.ui.workbench.rap/META-INF/spring/osgi.xml
org.argeo.cms.ui.workbench/src/org/argeo/cms/ui/workbench/commands/OpenChangePasswordDialog.java
org.argeo.cms/ext/test/org/argeo/cms/security/PasswordBasedEncryptionTest.java
org.argeo.cms/src/org/argeo/cms/auth/AnonymousLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/KeyringLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java
org.argeo.cms/src/org/argeo/cms/security/AbstractKeyring.java
org.argeo.cms/src/org/argeo/cms/security/JcrKeyring.java
org.argeo.cms/src/org/argeo/cms/security/PasswordBasedEncryption.java [deleted file]
org.argeo.node.api/src/org/argeo/node/NodeConstants.java
org.argeo.node.api/src/org/argeo/node/security/CryptoKeyring.java
org.argeo.node.api/src/org/argeo/node/security/Keyring.java
org.argeo.util/src/org/argeo/util/PasswordEncryption.java [new file with mode: 0644]

index d9c72b419b22fe895df5db305c278c137d927f5f..2bfa179a4cbca6b54c27cdd32dfabfea0de2d82f 100644 (file)
@@ -9,6 +9,7 @@
                scope="prototype">
                <property name="userAdmin" ref="userAdmin" />
                <property name="userTransaction" ref="userTransaction" />
+               <property name="keyring" ref="keyring" />
        </bean>
 
        <!-- RAP Specific command and corresponding service to enable open file -->
index 6e0f3b332a88b0111d34d7d5a263902adeba472f..231b7d883bc7d8707ab525159f9bf99de02b626d 100644 (file)
@@ -10,4 +10,5 @@
 \r
        <reference id="userAdmin" interface="org.osgi.service.useradmin.UserAdmin" />\r
        <reference id="userTransaction" interface="javax.transaction.UserTransaction" />\r
+       <reference id="keyring" interface="org.argeo.node.security.CryptoKeyring" />\r
 </beans:beans>\r
index 64f4ff9b800f1b1f15990b55d2c94b9d9f84be2b..30836b948c19593f6fd15661524b568861c2785f 100644 (file)
@@ -35,6 +35,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.cms.CmsException;
 import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
+import org.argeo.node.security.CryptoKeyring;
 import org.eclipse.core.commands.AbstractHandler;
 import org.eclipse.core.commands.ExecutionEvent;
 import org.eclipse.core.commands.ExecutionException;
@@ -59,6 +60,7 @@ public class OpenChangePasswordDialog extends AbstractHandler {
        private final static Log log = LogFactory.getLog(OpenChangePasswordDialog.class);
        private UserAdmin userAdmin;
        private UserTransaction userTransaction;
+       private CryptoKeyring keyring = null;
 
        public Object execute(ExecutionEvent event) throws ExecutionException {
                ChangePasswordDialog dialog = new ChangePasswordDialog(HandlerUtil.getActiveShell(event), userAdmin);
@@ -87,6 +89,10 @@ public class OpenChangePasswordDialog extends AbstractHandler {
                try {
                        userTransaction.begin();
                        user.getCredentials().put(null, newPassword);
+                       if (keyring != null) {
+                               keyring.changePassword(oldPassword, newPassword);
+                               // TODO change secret keys in the CMS session
+                       }
                        userTransaction.commit();
                } catch (Exception e) {
                        try {
@@ -162,4 +168,9 @@ public class OpenChangePasswordDialog extends AbstractHandler {
        public void setUserTransaction(UserTransaction userTransaction) {
                this.userTransaction = userTransaction;
        }
+
+       public void setKeyring(CryptoKeyring keyring) {
+               this.keyring = keyring;
+       }
+
 }
index 49319f15a15517c92c989d3c2ac122fbcfe1b7b4..5c43e34f37dbaa5ac8036c980e53eec561a61d1b 100644 (file)
@@ -35,7 +35,7 @@ import junit.framework.TestCase;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.security.PasswordBasedEncryption;
+import org.argeo.util.PasswordEncryption;
 
 public class PasswordBasedEncryptionTest extends TestCase {
        private final static Log log = LogFactory
@@ -43,7 +43,7 @@ public class PasswordBasedEncryptionTest extends TestCase {
 
        public void testEncryptDecrypt() {
                final String password = "test long password since they are safer";
-               PasswordBasedEncryption pbeEnc = new PasswordBasedEncryption(
+               PasswordEncryption pbeEnc = new PasswordEncryption(
                                password.toCharArray());
                String message = "Hello World!";
                log.info("Password:\t'" + password + "'");
@@ -51,7 +51,7 @@ public class PasswordBasedEncryptionTest extends TestCase {
                byte[] encrypted = pbeEnc.encryptString(message);
                log.info("Encrypted:\t'"
                                + DatatypeConverter.printBase64Binary(encrypted) + "'");
-               PasswordBasedEncryption pbeDec = new PasswordBasedEncryption(
+               PasswordEncryption pbeDec = new PasswordEncryption(
                                password.toCharArray());
                InputStream in = null;
                in = new ByteArrayInputStream(encrypted);
index eca28e8b9bd444192f18f3bacbeb49468fdd7731..19c0d60edff4f892ec1170775bd28f452628b847 100644 (file)
@@ -55,6 +55,7 @@ public class AnonymousLoginModule implements LoginModule {
                if (request != null)
                        locale = request.getLocale();
                CmsAuthUtils.addAuthorization(subject, authorization, locale, request);
+               CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
                if (log.isTraceEnabled())
                        log.trace("Anonymous logged in to CMS: " + subject);
                return true;
index 661cc6905fb9a33753348cfa1e81d5b47a508e47..aa313ee0a954e6b2b1fa088f9c1881d9e093de72 100644 (file)
@@ -93,7 +93,7 @@ class CmsAuthUtils {
                        throw new CmsException("Cannot commit", e);
                }
 
-               registerSessionAuthorization(request, subject, authorization, locale);
+               // registerSessionAuthorization(request, subject, authorization, locale);
        }
 
        private static void checkSubjectEmpty(Subject subject) {
@@ -121,7 +121,7 @@ class CmsAuthUtils {
                // subject.getPrincipals().removeAll(subject.getPrincipals(AnonymousPrincipal.class));
        }
 
-       private synchronized static void registerSessionAuthorization(HttpServletRequest request, Subject subject,
+       synchronized static void registerSessionAuthorization(HttpServletRequest request, Subject subject,
                        Authorization authorization, Locale locale) {
                // synchronized in order to avoid multiple registrations
                // TODO move it to a service in order to avoid static synchronization
@@ -144,6 +144,8 @@ class CmsAuthUtils {
                                                throw new CmsException("Inconsistent user " + authorization.getName()
                                                                + " for existing CMS session " + cmsSession);
                                        }
+                                       // keyring
+                                       subject.getPrivateCredentials().addAll(cmsSession.getSecretKeys());
                                } else {// anonymous
                                        if (cmsSession.getAuthorization().getName() != null) {
                                                cmsSession.close();
@@ -151,10 +153,9 @@ class CmsAuthUtils {
                                                cmsSession = null;
                                        }
                                }
-                       }
-
-                       if (cmsSession == null)
+                       } else if (cmsSession == null) {
                                cmsSession = new WebCmsSessionImpl(subject, authorization, locale, request);
+                       }
                        // request.setAttribute(CmsSession.class.getName(), cmsSession);
                        CmsSessionId nodeSessionId = new CmsSessionId(cmsSession.getUuid());
                        if (subject.getPrivateCredentials(CmsSessionId.class).size() == 0)
@@ -165,7 +166,9 @@ class CmsAuthUtils {
                                throw new CmsException(
                                                "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")");
                        }
-               } else {
+               } else
+
+               {
                        // TODO desktop, CLI
                }
        }
index ccd02b5b2783f8a26535958e7ebdcf5c89b5480b..81ca5baf3778727783b1f73f6dd045b6a0d893f2 100644 (file)
@@ -4,6 +4,7 @@ import java.io.IOException;
 import java.security.cert.X509Certificate;
 import java.util.Base64;
 import java.util.Collection;
+import java.util.Locale;
 import java.util.Map;
 import java.util.StringTokenizer;
 
@@ -119,7 +120,9 @@ public class HttpSessionLoginModule implements LoginModule {
                }
 
                if (authorization != null) {
-                       CmsAuthUtils.addAuthorization(subject, authorization, request.getLocale(), request);
+                       Locale locale = request.getLocale();
+                       CmsAuthUtils.addAuthorization(subject, authorization,locale , request);
+                       CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
                        cleanUp();
                        return true;
                } else {
index 2c495825456fa5c231e18bf0210a08d81aef81c6..09fece03aa560fb9e6e350b2ae811b3e47b40e1e 100644 (file)
@@ -30,8 +30,8 @@ import javax.security.auth.callback.PasswordCallback;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 
-import org.argeo.cms.security.PasswordBasedEncryption;
 import org.argeo.node.security.PBEKeySpecCallback;
+import org.argeo.util.PasswordEncryption;
 
 /** Adds a secret key to the private credentials */
 public class KeyringLoginModule implements LoginModule {
@@ -39,8 +39,8 @@ public class KeyringLoginModule implements LoginModule {
        private CallbackHandler callbackHandler;
        private SecretKey secretKey;
 
-       public void initialize(Subject subject, CallbackHandler callbackHandler,
-                       Map<String, ?> sharedState, Map<String, ?> options) {
+       public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
+                       Map<String, ?> options) {
                this.subject = subject;
                if (subject == null) {
                        subject = Subject.getSubject(AccessController.getContext());
@@ -49,9 +49,9 @@ public class KeyringLoginModule implements LoginModule {
        }
 
        public boolean login() throws LoginException {
-               Set<SecretKey> pbes = subject.getPrivateCredentials(SecretKey.class);
-               if (pbes.size() > 0)
-                       return true;
+//             Set<SecretKey> pbes = subject.getPrivateCredentials(SecretKey.class);
+//             if (pbes.size() > 0)
+//                     return true;
                PasswordCallback pc = new PasswordCallback("Master password", false);
                PBEKeySpecCallback pbeCb = new PBEKeySpecCallback();
                Callback[] callbacks = { pc, pbeCb };
@@ -59,21 +59,17 @@ public class KeyringLoginModule implements LoginModule {
                        callbackHandler.handle(callbacks);
                        char[] password = pc.getPassword();
 
-                       SecretKeyFactory keyFac = SecretKeyFactory.getInstance(pbeCb
-                                       .getSecretKeyFactory());
+                       SecretKeyFactory keyFac = SecretKeyFactory.getInstance(pbeCb.getSecretKeyFactory());
                        PBEKeySpec keySpec;
                        if (pbeCb.getKeyLength() != null)
-                               keySpec = new PBEKeySpec(password, pbeCb.getSalt(),
-                                               pbeCb.getIterationCount(), pbeCb.getKeyLength());
+                               keySpec = new PBEKeySpec(password, pbeCb.getSalt(), pbeCb.getIterationCount(), pbeCb.getKeyLength());
                        else
-                               keySpec = new PBEKeySpec(password, pbeCb.getSalt(),
-                                               pbeCb.getIterationCount());
+                               keySpec = new PBEKeySpec(password, pbeCb.getSalt(), pbeCb.getIterationCount());
 
                        String secKeyEncryption = pbeCb.getSecretKeyEncryption();
                        if (secKeyEncryption != null) {
                                SecretKey tmp = keyFac.generateSecret(keySpec);
-                               secretKey = new SecretKeySpec(tmp.getEncoded(),
-                                               secKeyEncryption);
+                               secretKey = new SecretKeySpec(tmp.getEncoded(), secKeyEncryption);
                        } else {
                                secretKey = keyFac.generateSecret(keySpec);
                        }
@@ -86,8 +82,10 @@ public class KeyringLoginModule implements LoginModule {
        }
 
        public boolean commit() throws LoginException {
-               if (secretKey != null)
+               if (secretKey != null) {
+                       subject.getPrivateCredentials().removeAll(subject.getPrivateCredentials(SecretKey.class));
                        subject.getPrivateCredentials().add(secretKey);
+               }
                return true;
        }
 
@@ -96,8 +94,7 @@ public class KeyringLoginModule implements LoginModule {
        }
 
        public boolean logout() throws LoginException {
-               Set<PasswordBasedEncryption> pbes = subject
-                               .getPrivateCredentials(PasswordBasedEncryption.class);
+               Set<PasswordEncryption> pbes = subject.getPrivateCredentials(PasswordEncryption.class);
                pbes.clear();
                return true;
        }
index e39918e4002d40d18584ad91819a2a86545c92e5..683d13b21a0f9541ce318254026eb3dba71fea64 100644 (file)
@@ -29,11 +29,13 @@ import org.apache.commons.logging.LogFactory;
 import org.argeo.cms.CmsException;
 import org.argeo.cms.internal.kernel.Activator;
 import org.argeo.naming.LdapAttrs;
+import org.argeo.node.security.CryptoKeyring;
 import org.argeo.osgi.useradmin.AuthenticatingUser;
 import org.argeo.osgi.useradmin.IpaUtils;
 import org.argeo.osgi.useradmin.OsUserUtils;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
 import org.osgi.service.useradmin.Authorization;
 import org.osgi.service.useradmin.User;
 import org.osgi.service.useradmin.UserAdmin;
@@ -122,6 +124,8 @@ public class UserAdminLoginModule implements LoginModule {
                                password = passwordCallback.getPassword();
                        else
                                throw new CredentialNotFoundException("No credentials provided");
+                       sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, username);
+                       sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, password);
                }
                User user = searchForUser(userAdmin, username);
                if (user == null)
@@ -204,9 +208,38 @@ public class UserAdminLoginModule implements LoginModule {
                                throw new LoginException(
                                                "User admin found no authorization for authenticated user " + authenticatingUser.getName());
                }
+
                // Log and monitor new login
-               CmsAuthUtils.addAuthorization(subject, authorization, locale,
-                               (HttpServletRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST));
+               HttpServletRequest request = (HttpServletRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
+               CmsAuthUtils.addAuthorization(subject, authorization, locale, request);
+
+               // Unlock keyring (underlying login to the JCR repository)
+               char[] password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD);
+               if (password != null) {
+                       ServiceReference<CryptoKeyring> keyringSr = bc.getServiceReference(CryptoKeyring.class);
+                       if (keyringSr != null) {
+                               CryptoKeyring keyring = bc.getService(keyringSr);
+                               Subject.doAs(subject, new PrivilegedAction<Void>() {
+
+                                       @Override
+                                       public Void run() {
+                                               try {
+                                                       keyring.unlock(password);
+                                               } catch (Exception e) {
+                                                       e.printStackTrace();
+                                                       log.warn("Could not unlock keyring with the password provided by " + authorization.getName()
+                                                                       + ": " + e.getMessage());
+                                               }
+                                               return null;
+                                       }
+
+                               });
+                       }
+               }
+               
+               // Register CmsSession with initial subject
+               CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
+
                if (log.isDebugEnabled())
                        log.debug("Logged in to CMS: " + subject);
                return true;
index 9b667717beaf80a60463d7f5068b87c99979798a..863f7c202511bee640d447c129cd3981a35b12a3 100644 (file)
@@ -15,6 +15,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
+import javax.crypto.SecretKey;
 import javax.jcr.Repository;
 import javax.jcr.Session;
 import javax.naming.InvalidNameException;
@@ -122,6 +123,10 @@ public class CmsSessionImpl implements CmsSession {
        private Subject getSubject() {
                return Subject.getSubject(initialContext);
        }
+       
+       public Set<SecretKey> getSecretKeys() {
+               return getSubject().getPrivateCredentials(SecretKey.class);
+       }
 
        public synchronized Session getDataSession(String cn, String workspace, Repository repository) {
                // FIXME make it more robust
index 7f4e960cda0a9ce70e27dc411ccfddad4c848ab1..779406a1dabc043172eabc4ab58682794d727bda 100644 (file)
@@ -25,7 +25,6 @@ import java.io.OutputStreamWriter;
 import java.io.Reader;
 import java.io.Writer;
 import java.security.AccessController;
-import java.security.MessageDigest;
 import java.security.Provider;
 import java.security.Security;
 import java.util.Arrays;
@@ -43,15 +42,16 @@ import javax.security.auth.login.LoginException;
 
 import org.apache.commons.io.IOUtils;
 import org.argeo.cms.CmsException;
+import org.argeo.node.NodeConstants;
 import org.argeo.node.security.CryptoKeyring;
 import org.argeo.node.security.Keyring;
 import org.argeo.node.security.PBEKeySpecCallback;
 
 /** username / password based keyring. TODO internationalize */
 public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
-       public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING";
+       // public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING";
 
-       private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT;
+       // private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT;
        private CallbackHandler defaultCallbackHandler;
 
        private String charset = "UTF-8";
@@ -82,16 +82,18 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
        protected abstract InputStream decrypt(String path);
 
        /** Triggers lazy initialization */
-       protected SecretKey getSecretKey() {
+       protected SecretKey getSecretKey(char[] password) {
                Subject subject = Subject.getSubject(AccessController.getContext());
                // we assume only one secrete key is available
                Iterator<SecretKey> iterator = subject.getPrivateCredentials(SecretKey.class).iterator();
-               if (!iterator.hasNext()) {// not initialized
-                       CallbackHandler callbackHandler = new KeyringCallbackHandler();
+               if (!iterator.hasNext() || password!=null) {// not initialized
+                       CallbackHandler callbackHandler = password == null ? new KeyringCallbackHandler()
+                                       : new PasswordProvidedCallBackHandler(password);
                        ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
                        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
                        try {
-                               LoginContext loginContext = new LoginContext(loginContextName, subject, callbackHandler);
+                               LoginContext loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_KEYRING, subject,
+                                               callbackHandler);
                                loginContext.login();
                                // FIXME will login even if password is wrong
                                iterator = subject.getPrivateCredentials(SecretKey.class).iterator();
@@ -119,48 +121,55 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
        }
 
        public char[] getAsChars(String path) {
-               InputStream in = getAsStream(path);
-               CharArrayWriter writer = null;
-               Reader reader = null;
-               try {
-                       writer = new CharArrayWriter();
-                       reader = new InputStreamReader(in, charset);
+               // InputStream in = getAsStream(path);
+               // CharArrayWriter writer = null;
+               // Reader reader = null;
+               try (InputStream in = getAsStream(path);
+                               CharArrayWriter writer = new CharArrayWriter();
+                               Reader reader = new InputStreamReader(in, charset);) {
                        IOUtils.copy(reader, writer);
                        return writer.toCharArray();
                } catch (IOException e) {
                        throw new CmsException("Cannot decrypt to char array", e);
                } finally {
-                       IOUtils.closeQuietly(reader);
-                       IOUtils.closeQuietly(in);
-                       IOUtils.closeQuietly(writer);
+                       // IOUtils.closeQuietly(reader);
+                       // IOUtils.closeQuietly(in);
+                       // IOUtils.closeQuietly(writer);
                }
        }
 
        public void set(String path, char[] arr) {
-               ByteArrayOutputStream out = new ByteArrayOutputStream();
-               ByteArrayInputStream in = null;
-               Writer writer = null;
-               try {
-                       writer = new OutputStreamWriter(out, charset);
+               // ByteArrayOutputStream out = new ByteArrayOutputStream();
+               // ByteArrayInputStream in = null;
+               // Writer writer = null;
+               try (ByteArrayOutputStream out = new ByteArrayOutputStream();
+                               Writer writer = new OutputStreamWriter(out, charset);) {
+                       // writer = new OutputStreamWriter(out, charset);
                        writer.write(arr);
                        writer.flush();
-                       in = new ByteArrayInputStream(out.toByteArray());
-                       set(path, in);
+                       // in = new ByteArrayInputStream(out.toByteArray());
+                       try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());) {
+                               set(path, in);
+                       }
                } catch (IOException e) {
                        throw new CmsException("Cannot encrypt to char array", e);
                } finally {
-                       IOUtils.closeQuietly(writer);
-                       IOUtils.closeQuietly(out);
-                       IOUtils.closeQuietly(in);
+                       // IOUtils.closeQuietly(writer);
+                       // IOUtils.closeQuietly(out);
+                       // IOUtils.closeQuietly(in);
                }
        }
 
-       protected Provider getSecurityProvider() {
-               return Security.getProvider(securityProviderName);
+       public void unlock(char[] password) {
+               if (!isSetup())
+                       setup(password);
+               SecretKey secretKey = getSecretKey(password);
+               if (secretKey == null)
+                       throw new CmsException("Could not unlock keyring");
        }
 
-       public void setLoginContextName(String loginContextName) {
-               this.loginContextName = loginContextName;
+       protected Provider getSecurityProvider() {
+               return Security.getProvider(securityProviderName);
        }
 
        public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) {
@@ -175,31 +184,32 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
                this.securityProviderName = securityProviderName;
        }
 
-       @Deprecated
-       protected static byte[] hash(char[] password, byte[] salt, Integer iterationCount) {
-               ByteArrayOutputStream out = null;
-               OutputStreamWriter writer = null;
-               try {
-                       out = new ByteArrayOutputStream();
-                       writer = new OutputStreamWriter(out, "UTF-8");
-                       writer.write(password);
-                       MessageDigest pwDigest = MessageDigest.getInstance("SHA-256");
-                       pwDigest.reset();
-                       pwDigest.update(salt);
-                       byte[] btPass = pwDigest.digest(out.toByteArray());
-                       for (int i = 0; i < iterationCount; i++) {
-                               pwDigest.reset();
-                               btPass = pwDigest.digest(btPass);
-                       }
-                       return btPass;
-               } catch (Exception e) {
-                       throw new CmsException("Cannot hash", e);
-               } finally {
-                       IOUtils.closeQuietly(out);
-                       IOUtils.closeQuietly(writer);
-               }
-
-       }
+       // @Deprecated
+       // protected static byte[] hash(char[] password, byte[] salt, Integer
+       // iterationCount) {
+       // ByteArrayOutputStream out = null;
+       // OutputStreamWriter writer = null;
+       // try {
+       // out = new ByteArrayOutputStream();
+       // writer = new OutputStreamWriter(out, "UTF-8");
+       // writer.write(password);
+       // MessageDigest pwDigest = MessageDigest.getInstance("SHA-256");
+       // pwDigest.reset();
+       // pwDigest.update(salt);
+       // byte[] btPass = pwDigest.digest(out.toByteArray());
+       // for (int i = 0; i < iterationCount; i++) {
+       // pwDigest.reset();
+       // btPass = pwDigest.digest(btPass);
+       // }
+       // return btPass;
+       // } catch (Exception e) {
+       // throw new CmsException("Cannot hash", e);
+       // } finally {
+       // IOUtils.closeQuietly(out);
+       // IOUtils.closeQuietly(writer);
+       // }
+       //
+       // }
 
        /**
         * Convenience method using the underlying callback to ask for a password
@@ -223,7 +233,7 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
                        // checks
                        if (callbacks.length != 2)
                                throw new IllegalArgumentException(
-                                               "Keyring required 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
+                                               "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
                        if (!(callbacks[0] instanceof PasswordCallback))
                                throw new UnsupportedCallbackException(callbacks[0]);
                        if (!(callbacks[1] instanceof PBEKeySpecCallback))
@@ -266,4 +276,30 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
                }
 
        }
+
+       class PasswordProvidedCallBackHandler implements CallbackHandler {
+               private final char[] password;
+
+               public PasswordProvidedCallBackHandler(char[] password) {
+                       this.password = password;
+               }
+
+               @Override
+               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                       // checks
+                       if (callbacks.length != 2)
+                               throw new IllegalArgumentException(
+                                               "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
+                       if (!(callbacks[0] instanceof PasswordCallback))
+                               throw new UnsupportedCallbackException(callbacks[0]);
+                       if (!(callbacks[1] instanceof PBEKeySpecCallback))
+                               throw new UnsupportedCallbackException(callbacks[0]);
+
+                       PasswordCallback passwordCb = (PasswordCallback) callbacks[0];
+                       passwordCb.setPassword(password);
+                       PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
+                       handleKeySpecCallback(pbeCb);
+               }
+
+       }
 }
index 43eab4b3cb467c69666bb7a5a26a00afd01ebee1..04e3eb9db76a99ff9d90113477de5cbf8f60fea5 100644 (file)
@@ -17,8 +17,11 @@ package org.argeo.cms.security;
 
 import java.io.ByteArrayInputStream;
 import java.io.CharArrayReader;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
 import java.security.Provider;
 import java.security.SecureRandom;
 
@@ -28,12 +31,16 @@ import javax.crypto.SecretKey;
 import javax.crypto.spec.IvParameterSpec;
 import javax.jcr.Binary;
 import javax.jcr.Node;
+import javax.jcr.NodeIterator;
 import javax.jcr.Property;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.query.Query;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.argeo.cms.ArgeoNames;
 import org.argeo.cms.ArgeoTypes;
 import org.argeo.cms.CmsException;
@@ -44,9 +51,9 @@ import org.argeo.node.security.PBEKeySpecCallback;
 
 /** JCR based implementation of a keyring */
 public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
+       private final static Log log = LogFactory.getLog(JcrKeyring.class);
        /**
-        * Stronger with 256, but causes problem with Oracle JVM, force 128 in this
-        * case
+        * Stronger with 256, but causes problem with Oracle JVM, force 128 in this case
         */
        public final static Long DEFAULT_SECRETE_KEY_LENGTH = 256l;
        public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
@@ -54,9 +61,9 @@ public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
        public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding";
 
        private Integer iterationCountFactor = 200;
-       private Long secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
-       private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
-       private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
+       private Long secretKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
+       private String secretKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
+       private String secretKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
        private String cipherName = DEFAULT_CIPHER_NAME;
 
        private final Repository repository;
@@ -71,19 +78,19 @@ public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
 
        // FIXME is it really still needed?
        /**
-        * When setup is called the session has not yet been saved and we don't want
-        * to save it since there maybe other data which would be inconsistent. So
-        * we keep a reference to this node which will then be used (an reset to
-        * null) when handling the PBE callback. We keep one per thread in case
-        * multiple users are accessing the same instance of a keyring.
+        * When setup is called the session has not yet been saved and we don't want to
+        * save it since there maybe other data which would be inconsistent. So we keep
+        * a reference to this node which will then be used (an reset to null) when
+        * handling the PBE callback. We keep one per thread in case multiple users are
+        * accessing the same instance of a keyring.
         */
-       private ThreadLocal<Node> notYetSavedKeyring = new ThreadLocal<Node>() {
-
-               @Override
-               protected Node initialValue() {
-                       return null;
-               }
-       };
+//     private ThreadLocal<Node> notYetSavedKeyring = new ThreadLocal<Node>() {
+//
+//             @Override
+//             protected Node initialValue() {
+//                     return null;
+//             }
+//     };
 
        public JcrKeyring(Repository repository) {
                this.repository = repository;
@@ -107,11 +114,12 @@ public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
        }
 
        @Override
-       protected Boolean isSetup() {
+       protected synchronized Boolean isSetup() {
                try {
-                       if (notYetSavedKeyring.get() != null)
-                               return true;
+//                     if (notYetSavedKeyring.get() != null)
+//                             return true;
 
+                       session().refresh(true);
                        Node userHome = NodeUtils.getUserHome(session());
                        return userHome.hasNode(ARGEO_KEYRING);
                } catch (RepositoryException e) {
@@ -120,28 +128,33 @@ public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
        }
 
        @Override
-       protected void setup(char[] password) {
+       protected synchronized void setup(char[] password) {
                Binary binary = null;
-               InputStream in = null;
+               // InputStream in = null;
                try {
+                       session().refresh(true);
                        Node userHome = NodeUtils.getUserHome(session());
-                       if (userHome.hasNode(ARGEO_KEYRING))
-                               throw new ArgeoJcrException("Keyring already setup");
-                       Node keyring = userHome.addNode(ARGEO_KEYRING);
+                       Node keyring;
+                       if (userHome.hasNode(ARGEO_KEYRING)) {
+                               throw new CmsException("Keyring already set up");
+                       } else {
+                               keyring = userHome.addNode(ARGEO_KEYRING);
+                       }
                        keyring.addMixin(ArgeoTypes.ARGEO_PBE_SPEC);
 
                        // deterministic salt and iteration count based on username
                        String username = session().getUserID();
                        byte[] salt = new byte[8];
-                       byte[] usernameBytes = username.getBytes();
+                       byte[] usernameBytes = username.getBytes(StandardCharsets.UTF_8);
                        for (int i = 0; i < salt.length; i++) {
                                if (i < usernameBytes.length)
                                        salt[i] = usernameBytes[i];
                                else
                                        salt[i] = 0;
                        }
-                       in = new ByteArrayInputStream(salt);
-                       binary = session().getValueFactory().createBinary(in);
+                       try (InputStream in = new ByteArrayInputStream(salt);) {
+                               binary = session().getValueFactory().createBinary(in);
+                       }
                        keyring.setProperty(ARGEO_SALT, binary);
 
                        Integer iterationCount = username.length() * iterationCountFactor;
@@ -149,12 +162,12 @@ public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
 
                        // default algo
                        // TODO check if algo and key length are available, use DES if not
-                       keyring.setProperty(ARGEO_SECRET_KEY_FACTORY, secreteKeyFactoryName);
-                       keyring.setProperty(ARGEO_KEY_LENGTH, secreteKeyLength);
-                       keyring.setProperty(ARGEO_SECRET_KEY_ENCRYPTION, secreteKeyEncryption);
+                       keyring.setProperty(ARGEO_SECRET_KEY_FACTORY, secretKeyFactoryName);
+                       keyring.setProperty(ARGEO_KEY_LENGTH, secretKeyLength);
+                       keyring.setProperty(ARGEO_SECRET_KEY_ENCRYPTION, secretKeyEncryption);
                        keyring.setProperty(ARGEO_CIPHER, cipherName);
 
-                       // keyring.getSession().save();
+                       keyring.getSession().save();
 
                        // encrypted password hash
                        // IOUtils.closeQuietly(in);
@@ -164,25 +177,26 @@ public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
                        // binary = session().getValueFactory().createBinary(in);
                        // keyring.setProperty(ARGEO_PASSWORD, binary);
 
-                       notYetSavedKeyring.set(keyring);
+//                     notYetSavedKeyring.set(keyring);
                } catch (Exception e) {
                        throw new ArgeoJcrException("Cannot setup keyring", e);
                } finally {
                        JcrUtils.closeQuietly(binary);
-                       IOUtils.closeQuietly(in);
+                       // IOUtils.closeQuietly(in);
                        // JcrUtils.discardQuietly(session());
                }
        }
 
        @Override
-       protected void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) {
+       protected synchronized void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) {
                try {
+                       session().refresh(true);
                        Node userHome = NodeUtils.getUserHome(session());
                        Node keyring;
                        if (userHome.hasNode(ARGEO_KEYRING))
                                keyring = userHome.getNode(ARGEO_KEYRING);
-                       else if (notYetSavedKeyring.get() != null)
-                               keyring = notYetSavedKeyring.get();
+//                     else if (notYetSavedKeyring.get() != null)
+//                             keyring = notYetSavedKeyring.get();
                        else
                                throw new ArgeoJcrException("Keyring not setup");
 
@@ -192,8 +206,8 @@ public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
                                        (int) keyring.getProperty(ARGEO_KEY_LENGTH).getLong(),
                                        keyring.getProperty(ARGEO_SECRET_KEY_ENCRYPTION).getString());
 
-                       if (notYetSavedKeyring.get() != null)
-                               notYetSavedKeyring.remove();
+//                     if (notYetSavedKeyring.get() != null)
+//                             notYetSavedKeyring.remove();
                } catch (RepositoryException e) {
                        throw new ArgeoJcrException("Cannot handle key spec callback", e);
                }
@@ -203,12 +217,13 @@ public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
        @Override
        protected synchronized void encrypt(String path, InputStream unencrypted) {
                // should be called first for lazy initialization
-               SecretKey secretKey = getSecretKey();
+               SecretKey secretKey = getSecretKey(null);
+               Cipher cipher = createCipher();
 
-               Binary binary = null;
-               InputStream in = null;
+               // Binary binary = null;
+               // InputStream in = null;
                try {
-                       Cipher cipher = createCipher();
+                       session().refresh(true);
                        Node node;
                        if (!session().nodeExists(path)) {
                                String parentPath = JcrUtils.parentPath(path);
@@ -219,6 +234,36 @@ public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
                        } else {
                                node = session().getNode(path);
                        }
+                       encrypt(secretKey, cipher, node, unencrypted);
+                       // node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED);
+                       // SecureRandom random = new SecureRandom();
+                       // byte[] iv = new byte[16];
+                       // random.nextBytes(iv);
+                       // cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
+                       // JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv);
+                       //
+                       // try (InputStream in = new CipherInputStream(unencrypted, cipher);) {
+                       // binary = session().getValueFactory().createBinary(in);
+                       // node.setProperty(Property.JCR_DATA, binary);
+                       // session().save();
+                       // }
+               } catch (RepositoryException e) {
+                       throw new ArgeoJcrException("Cannot encrypt", e);
+               } finally {
+                       try {
+                               unencrypted.close();
+                       } catch (IOException e) {
+                               // silent
+                       }
+                       // IOUtils.closeQuietly(unencrypted);
+                       // IOUtils.closeQuietly(in);
+                       // JcrUtils.closeQuietly(binary);
+                       JcrUtils.logoutQuietly(session());
+               }
+       }
+
+       protected synchronized void encrypt(SecretKey secretKey, Cipher cipher, Node node, InputStream unencrypted) {
+               try {
                        node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED);
                        SecureRandom random = new SecureRandom();
                        byte[] iv = new byte[16];
@@ -226,58 +271,70 @@ public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
                        cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
                        JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv);
 
-                       in = new CipherInputStream(unencrypted, cipher);
-                       binary = session().getValueFactory().createBinary(in);
-                       node.setProperty(Property.JCR_DATA, binary);
-                       session().save();
+                       Binary binary = null;
+                       try (InputStream in = new CipherInputStream(unencrypted, cipher);) {
+                               binary = session().getValueFactory().createBinary(in);
+                               node.setProperty(Property.JCR_DATA, binary);
+                               session().save();
+                       } finally {
+                               JcrUtils.closeQuietly(binary);
+                       }
                } catch (Exception e) {
                        throw new ArgeoJcrException("Cannot encrypt", e);
                } finally {
-                       IOUtils.closeQuietly(unencrypted);
-                       IOUtils.closeQuietly(in);
-                       JcrUtils.closeQuietly(binary);
-                       JcrUtils.logoutQuietly(session());
+                       try {
+                               unencrypted.close();
+                       } catch (IOException e) {
+                               // silent
+                       }
+                       // IOUtils.closeQuietly(unencrypted);
+                       // IOUtils.closeQuietly(in);
+                       // JcrUtils.closeQuietly(binary);
+                       // JcrUtils.logoutQuietly(session());
                }
        }
 
        @Override
        protected synchronized InputStream decrypt(String path) {
                Binary binary = null;
-               InputStream encrypted = null;
-               Reader reader = null;
+               // InputStream encrypted = null;
                try {
+                       session().refresh(true);
                        if (!session().nodeExists(path)) {
                                char[] password = ask();
-                               reader = new CharArrayReader(password);
-                               return new ByteArrayInputStream(IOUtils.toByteArray(reader));
+                               Reader reader = new CharArrayReader(password);
+                               return new ByteArrayInputStream(IOUtils.toByteArray(reader, StandardCharsets.UTF_8));
                        } else {
                                // should be called first for lazy initialisation
-                               SecretKey secretKey = getSecretKey();
-
+                               SecretKey secretKey = getSecretKey(null);
                                Cipher cipher = createCipher();
-
                                Node node = session().getNode(path);
-                               if (node.hasProperty(ARGEO_IV)) {
-                                       byte[] iv = JcrUtils.getBinaryAsBytes(node.getProperty(ARGEO_IV));
-                                       cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
-                               } else {
-                                       cipher.init(Cipher.DECRYPT_MODE, secretKey);
-                               }
-
-                               binary = node.getProperty(Property.JCR_DATA).getBinary();
-                               encrypted = binary.getStream();
-                               return new CipherInputStream(encrypted, cipher);
+                               return decrypt(secretKey, cipher, node);
                        }
                } catch (Exception e) {
                        throw new ArgeoJcrException("Cannot decrypt", e);
                } finally {
-                       IOUtils.closeQuietly(encrypted);
-                       IOUtils.closeQuietly(reader);
+                       // IOUtils.closeQuietly(encrypted);
+                       // IOUtils.closeQuietly(reader);
                        JcrUtils.closeQuietly(binary);
                        JcrUtils.logoutQuietly(session());
                }
        }
 
+       protected synchronized InputStream decrypt(SecretKey secretKey, Cipher cipher, Node node)
+                       throws RepositoryException, GeneralSecurityException {
+               if (node.hasProperty(ARGEO_IV)) {
+                       byte[] iv = JcrUtils.getBinaryAsBytes(node.getProperty(ARGEO_IV));
+                       cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
+               } else {
+                       cipher.init(Cipher.DECRYPT_MODE, secretKey);
+               }
+
+               Binary binary = node.getProperty(Property.JCR_DATA).getBinary();
+               InputStream encrypted = binary.getStream();
+               return new CipherInputStream(encrypted, cipher);
+       }
+
        protected Cipher createCipher() {
                try {
                        Node userHome = NodeUtils.getUserHome(session());
@@ -298,7 +355,25 @@ public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
        }
 
        public synchronized void changePassword(char[] oldPassword, char[] newPassword) {
-               // TODO decrypt with old pw / encrypt with new pw all argeo:encrypted
+               // TODO make it XA compatible
+               SecretKey oldSecretKey = getSecretKey(oldPassword);
+               SecretKey newSecretKey = getSecretKey(newPassword);
+               Session session = session();
+               try {
+                       NodeIterator encryptedNodes = session.getWorkspace().getQueryManager()
+                                       .createQuery("select * from [argeo:encrypted]", Query.JCR_SQL2).execute().getNodes();
+                       while (encryptedNodes.hasNext()) {
+                               Node node = encryptedNodes.nextNode();
+                               InputStream in = decrypt(oldSecretKey, createCipher(), node);
+                               encrypt(newSecretKey, createCipher(), node, in);
+                               if (log.isDebugEnabled())
+                                       log.debug("Converted keyring encrypted value of " + node.getPath());
+                       }
+               } catch (RepositoryException | GeneralSecurityException e) {
+                       throw new CmsException("Cannot change JCR keyring password", e);
+               } finally {
+                       JcrUtils.logoutQuietly(session);
+               }
        }
 
        // public synchronized void setSession(Session session) {
@@ -309,16 +384,16 @@ public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
                this.iterationCountFactor = iterationCountFactor;
        }
 
-       public void setSecreteKeyLength(Long keyLength) {
-               this.secreteKeyLength = keyLength;
+       public void setSecretKeyLength(Long keyLength) {
+               this.secretKeyLength = keyLength;
        }
 
-       public void setSecreteKeyFactoryName(String secreteKeyFactoryName) {
-               this.secreteKeyFactoryName = secreteKeyFactoryName;
+       public void setSecretKeyFactoryName(String secreteKeyFactoryName) {
+               this.secretKeyFactoryName = secreteKeyFactoryName;
        }
 
-       public void setSecreteKeyEncryption(String secreteKeyEncryption) {
-               this.secreteKeyEncryption = secreteKeyEncryption;
+       public void setSecretKeyEncryption(String secreteKeyEncryption) {
+               this.secretKeyEncryption = secreteKeyEncryption;
        }
 
        public void setCipherName(String cipherName) {
diff --git a/org.argeo.cms/src/org/argeo/cms/security/PasswordBasedEncryption.java b/org.argeo.cms/src/org/argeo/cms/security/PasswordBasedEncryption.java
deleted file mode 100644 (file)
index a74cb59..0000000
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed 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.argeo.cms.security;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.cms.CmsException;
-
-/** Simple password based encryption / decryption */
-public class PasswordBasedEncryption {
-       public final static Integer DEFAULT_ITERATION_COUNT = 1024;
-       /** Stronger with 256, but causes problem with Oracle JVM */
-       public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256;
-       public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128;
-       public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
-       public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
-       public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding";
-       public final static String DEFAULT_CHARSET = "UTF-8";
-
-       private Integer iterationCount = DEFAULT_ITERATION_COUNT;
-       private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
-       private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
-       private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
-       private String cipherName = DEFAULT_CIPHER_NAME;
-
-       private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B,
-                       (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3,
-                       (byte) 0x03 };
-       private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B,
-                       (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3,
-                       (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32,
-                       (byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
-
-       private Key key;
-       private Cipher ecipher;
-       private Cipher dcipher;
-
-       private String securityProviderName = null;
-
-       /**
-        * This is up to the caller to clear the passed array. Neither copy of nor
-        * reference to the passed array is kept
-        */
-       public PasswordBasedEncryption(char[] password) {
-               this(password, DEFAULT_SALT_8, DEFAULT_IV_16);
-       }
-
-       /**
-        * This is up to the caller to clear the passed array. Neither copies of nor
-        * references to the passed arrays are kept
-        */
-       public PasswordBasedEncryption(char[] password, byte[] passwordSalt,
-                       byte[] initializationVector) {
-               try {
-                       initKeyAndCiphers(password, passwordSalt, initializationVector);
-               } catch (InvalidKeyException e) {
-                       Integer previousSecreteKeyLength = secreteKeyLength;
-                       secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED;
-                       System.err.println("'" + e.getMessage() + "', will use "
-                                       + secreteKeyLength + " secrete key length instead of "
-                                       + previousSecreteKeyLength);
-                       try {
-                               initKeyAndCiphers(password, passwordSalt, initializationVector);
-                       } catch (Exception e1) {
-                               throw new CmsException(
-                                               "Cannot get secret key (with restricted length)", e1);
-                       }
-               } catch (Exception e) {
-                       throw new CmsException("Cannot get secret key", e);
-               }
-       }
-
-       protected void initKeyAndCiphers(char[] password, byte[] passwordSalt,
-                       byte[] initializationVector) throws GeneralSecurityException {
-               byte[] salt = new byte[8];
-               System.arraycopy(passwordSalt, 0, salt, 0, salt.length);
-               // for (int i = 0; i < password.length && i < salt.length; i++)
-               // salt[i] = (byte) password[i];
-               byte[] iv = new byte[16];
-               System.arraycopy(initializationVector, 0, iv, 0, iv.length);
-
-               SecretKeyFactory keyFac = SecretKeyFactory
-                               .getInstance(getSecretKeyFactoryName());
-               PBEKeySpec keySpec = new PBEKeySpec(password, salt,
-                               getIterationCount(), getKeyLength());
-               String secKeyEncryption = getSecretKeyEncryption();
-               if (secKeyEncryption != null) {
-                       SecretKey tmp = keyFac.generateSecret(keySpec);
-                       key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption());
-               } else {
-                       key = keyFac.generateSecret(keySpec);
-               }
-               if (securityProviderName != null)
-                       ecipher = Cipher.getInstance(getCipherName(), securityProviderName);
-               else
-                       ecipher = Cipher.getInstance(getCipherName());
-               ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
-               dcipher = Cipher.getInstance(getCipherName());
-               dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
-       }
-
-       public void encrypt(InputStream decryptedIn, OutputStream encryptedOut)
-                       throws IOException {
-               try {
-                       CipherOutputStream out = new CipherOutputStream(encryptedOut,
-                                       ecipher);
-                       IOUtils.copy(decryptedIn, out);
-                       IOUtils.closeQuietly(out);
-               } catch (IOException e) {
-                       throw e;
-               } catch (Exception e) {
-                       throw new CmsException("Cannot encrypt", e);
-               } finally {
-                       IOUtils.closeQuietly(decryptedIn);
-               }
-       }
-
-       public void decrypt(InputStream encryptedIn, OutputStream decryptedOut)
-                       throws IOException {
-               try {
-                       CipherInputStream decryptedIn = new CipherInputStream(encryptedIn,
-                                       dcipher);
-                       IOUtils.copy(decryptedIn, decryptedOut);
-               } catch (IOException e) {
-                       throw e;
-               } catch (Exception e) {
-                       throw new CmsException("Cannot decrypt", e);
-               } finally {
-                       IOUtils.closeQuietly(encryptedIn);
-               }
-       }
-
-       public byte[] encryptString(String str) {
-               ByteArrayOutputStream out = null;
-               ByteArrayInputStream in = null;
-               try {
-                       out = new ByteArrayOutputStream();
-                       in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET));
-                       encrypt(in, out);
-                       return out.toByteArray();
-               } catch (Exception e) {
-                       throw new CmsException("Cannot encrypt", e);
-               } finally {
-                       IOUtils.closeQuietly(out);
-               }
-       }
-
-       /** Closes the input stream */
-       public String decryptAsString(InputStream in) {
-               ByteArrayOutputStream out = null;
-               try {
-                       out = new ByteArrayOutputStream();
-                       decrypt(in, out);
-                       return new String(out.toByteArray(), DEFAULT_CHARSET);
-               } catch (Exception e) {
-                       throw new CmsException("Cannot decrypt", e);
-               } finally {
-                       IOUtils.closeQuietly(out);
-               }
-       }
-
-       protected Key getKey() {
-               return key;
-       }
-
-       protected Cipher getEcipher() {
-               return ecipher;
-       }
-
-       protected Cipher getDcipher() {
-               return dcipher;
-       }
-
-       protected Integer getIterationCount() {
-               return iterationCount;
-       }
-
-       protected Integer getKeyLength() {
-               return secreteKeyLength;
-       }
-
-       protected String getSecretKeyFactoryName() {
-               return secreteKeyFactoryName;
-       }
-
-       protected String getSecretKeyEncryption() {
-               return secreteKeyEncryption;
-       }
-
-       protected String getCipherName() {
-               return cipherName;
-       }
-
-       public void setIterationCount(Integer iterationCount) {
-               this.iterationCount = iterationCount;
-       }
-
-       public void setSecreteKeyLength(Integer keyLength) {
-               this.secreteKeyLength = keyLength;
-       }
-
-       public void setSecreteKeyFactoryName(String secreteKeyFactoryName) {
-               this.secreteKeyFactoryName = secreteKeyFactoryName;
-       }
-
-       public void setSecreteKeyEncryption(String secreteKeyEncryption) {
-               this.secreteKeyEncryption = secreteKeyEncryption;
-       }
-
-       public void setCipherName(String cipherName) {
-               this.cipherName = cipherName;
-       }
-
-       public void setSecurityProviderName(String securityProviderName) {
-               this.securityProviderName = securityProviderName;
-       }
-}
index 2b4c284f62034cfc51a3795907e0069a8a3df764..22afe0065e2e0d220f4e8e917da35b75cc00fd67 100644 (file)
@@ -1,22 +1,6 @@
 package org.argeo.node;
 
 public interface NodeConstants {
-       /*
-        * PIDs
-        */
-       String NODE_STATE_PID = "org.argeo.node.state";
-       String NODE_DEPLOYMENT_PID = "org.argeo.node.deployment";
-       String NODE_INSTANCE_PID = "org.argeo.node.instance";
-
-       String NODE_KEYRING_PID = "org.argeo.node.keyring";
-       String NODE_FS_PROVIDER_PID = "org.argeo.node.fsProvider";
-
-       /*
-        * FACTORY PIDs
-        */
-       String NODE_REPOS_FACTORY_PID = "org.argeo.node.repos";
-       String NODE_USER_ADMIN_PID = "org.argeo.node.userAdmin";
-
        /*
         * DN ATTRIBUTES (RFC 4514)
         */
@@ -73,6 +57,7 @@ public interface NodeConstants {
        String LOGIN_CONTEXT_ANONYMOUS = "ANONYMOUS";
        String LOGIN_CONTEXT_DATA_ADMIN = "DATA_ADMIN";
        String LOGIN_CONTEXT_SINGLE_USER = "SINGLE_USER";
+       String LOGIN_CONTEXT_KEYRING = "KEYRING";
 
        /*
         * PATHS
@@ -108,4 +93,20 @@ public interface NodeConstants {
        // HTTP
        String HTTP_PORT = "org.osgi.service.http.port";
        String HTTP_PORT_SECURE = "org.osgi.service.http.port.secure";
+
+       /*
+        * PIDs
+        */
+       String NODE_STATE_PID = "org.argeo.node.state";
+       String NODE_DEPLOYMENT_PID = "org.argeo.node.deployment";
+       String NODE_INSTANCE_PID = "org.argeo.node.instance";
+
+       String NODE_KEYRING_PID = "org.argeo.node.keyring";
+       String NODE_FS_PROVIDER_PID = "org.argeo.node.fsProvider";
+
+       /*
+        * FACTORY PIDs
+        */
+       String NODE_REPOS_FACTORY_PID = "org.argeo.node.repos";
+       String NODE_USER_ADMIN_PID = "org.argeo.node.userAdmin";
 }
index 026fcb06d259b76f8eeb9604b23f5bfa9531dff1..dd34022772894814c073156c559d003c36cdbc92 100644 (file)
@@ -19,5 +19,7 @@ package org.argeo.node.security;
  * Marker interface for an advanced keyring based on cryptography.
  */
 public interface CryptoKeyring extends Keyring {
+       public void changePassword(char[] oldPassword, char[] newPassword);
 
+       public void unlock(char[] password);
 }
index 467d9a8aab16ab1518202d2b23509b7c934cdb6a..fe054c3cc249b3d6768e58141292ed97bbd94815 100644 (file)
@@ -23,8 +23,6 @@ import java.io.InputStream;
  * change.</b>
  */
 public interface Keyring {
-       public void changePassword(char[] oldPassword, char[] newPassword);
-
        /**
         * Returns the confidential information as chars. Must ask for it if it is
         * not stored.
diff --git a/org.argeo.util/src/org/argeo/util/PasswordEncryption.java b/org.argeo.util/src/org/argeo/util/PasswordEncryption.java
new file mode 100644 (file)
index 0000000..7269689
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed 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.argeo.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class PasswordEncryption {
+       public final static Integer DEFAULT_ITERATION_COUNT = 1024;
+       /** Stronger with 256, but causes problem with Oracle JVM */
+       public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256;
+       public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128;
+       public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
+       public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
+       public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding";
+       public final static String DEFAULT_CHARSET = "UTF-8";
+
+       private Integer iterationCount = DEFAULT_ITERATION_COUNT;
+       private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
+       private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
+       private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
+       private String cipherName = DEFAULT_CIPHER_NAME;
+
+       private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
+                       (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
+       private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
+                       (byte) 0x35, (byte) 0xE3, (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
+                       (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
+
+       private Key key;
+       private Cipher ecipher;
+       private Cipher dcipher;
+
+       private String securityProviderName = null;
+
+       /**
+        * This is up to the caller to clear the passed array. Neither copy of nor
+        * reference to the passed array is kept
+        */
+       public PasswordEncryption(char[] password) {
+               this(password, DEFAULT_SALT_8, DEFAULT_IV_16);
+       }
+
+       /**
+        * This is up to the caller to clear the passed array. Neither copies of nor
+        * references to the passed arrays are kept
+        */
+       public PasswordEncryption(char[] password, byte[] passwordSalt, byte[] initializationVector) {
+               try {
+                       initKeyAndCiphers(password, passwordSalt, initializationVector);
+               } catch (InvalidKeyException e) {
+                       Integer previousSecreteKeyLength = secreteKeyLength;
+                       secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED;
+                       System.err.println("'" + e.getMessage() + "', will use " + secreteKeyLength
+                                       + " secrete key length instead of " + previousSecreteKeyLength);
+                       try {
+                               initKeyAndCiphers(password, passwordSalt, initializationVector);
+                       } catch (Exception e1) {
+                               throw new UtilsException("Cannot get secret key (with restricted length)", e1);
+                       }
+               } catch (Exception e) {
+                       throw new UtilsException("Cannot get secret key", e);
+               }
+       }
+
+       protected void initKeyAndCiphers(char[] password, byte[] passwordSalt, byte[] initializationVector)
+                       throws GeneralSecurityException {
+               byte[] salt = new byte[8];
+               System.arraycopy(passwordSalt, 0, salt, 0, salt.length);
+               // for (int i = 0; i < password.length && i < salt.length; i++)
+               // salt[i] = (byte) password[i];
+               byte[] iv = new byte[16];
+               System.arraycopy(initializationVector, 0, iv, 0, iv.length);
+
+               SecretKeyFactory keyFac = SecretKeyFactory.getInstance(getSecretKeyFactoryName());
+               PBEKeySpec keySpec = new PBEKeySpec(password, salt, getIterationCount(), getKeyLength());
+               String secKeyEncryption = getSecretKeyEncryption();
+               if (secKeyEncryption != null) {
+                       SecretKey tmp = keyFac.generateSecret(keySpec);
+                       key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption());
+               } else {
+                       key = keyFac.generateSecret(keySpec);
+               }
+               if (securityProviderName != null)
+                       ecipher = Cipher.getInstance(getCipherName(), securityProviderName);
+               else
+                       ecipher = Cipher.getInstance(getCipherName());
+               ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
+               dcipher = Cipher.getInstance(getCipherName());
+               dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
+       }
+
+       public void encrypt(InputStream decryptedIn, OutputStream encryptedOut) throws IOException {
+               try {
+                       CipherOutputStream out = new CipherOutputStream(encryptedOut, ecipher);
+                       StreamUtils.copy(decryptedIn, out);
+                       StreamUtils.closeQuietly(out);
+               } catch (IOException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new UtilsException("Cannot encrypt", e);
+               } finally {
+                       StreamUtils.closeQuietly(decryptedIn);
+               }
+       }
+
+       public void decrypt(InputStream encryptedIn, OutputStream decryptedOut) throws IOException {
+               try {
+                       CipherInputStream decryptedIn = new CipherInputStream(encryptedIn, dcipher);
+                       StreamUtils.copy(decryptedIn, decryptedOut);
+               } catch (IOException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new UtilsException("Cannot decrypt", e);
+               } finally {
+                       StreamUtils.closeQuietly(encryptedIn);
+               }
+       }
+
+       public byte[] encryptString(String str) {
+               ByteArrayOutputStream out = null;
+               ByteArrayInputStream in = null;
+               try {
+                       out = new ByteArrayOutputStream();
+                       in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET));
+                       encrypt(in, out);
+                       return out.toByteArray();
+               } catch (Exception e) {
+                       throw new UtilsException("Cannot encrypt", e);
+               } finally {
+                       StreamUtils.closeQuietly(out);
+               }
+       }
+
+       /** Closes the input stream */
+       public String decryptAsString(InputStream in) {
+               ByteArrayOutputStream out = null;
+               try {
+                       out = new ByteArrayOutputStream();
+                       decrypt(in, out);
+                       return new String(out.toByteArray(), DEFAULT_CHARSET);
+               } catch (Exception e) {
+                       throw new UtilsException("Cannot decrypt", e);
+               } finally {
+                       StreamUtils.closeQuietly(out);
+               }
+       }
+
+       protected Key getKey() {
+               return key;
+       }
+
+       protected Cipher getEcipher() {
+               return ecipher;
+       }
+
+       protected Cipher getDcipher() {
+               return dcipher;
+       }
+
+       protected Integer getIterationCount() {
+               return iterationCount;
+       }
+
+       protected Integer getKeyLength() {
+               return secreteKeyLength;
+       }
+
+       protected String getSecretKeyFactoryName() {
+               return secreteKeyFactoryName;
+       }
+
+       protected String getSecretKeyEncryption() {
+               return secreteKeyEncryption;
+       }
+
+       protected String getCipherName() {
+               return cipherName;
+       }
+
+       public void setIterationCount(Integer iterationCount) {
+               this.iterationCount = iterationCount;
+       }
+
+       public void setSecreteKeyLength(Integer keyLength) {
+               this.secreteKeyLength = keyLength;
+       }
+
+       public void setSecreteKeyFactoryName(String secreteKeyFactoryName) {
+               this.secreteKeyFactoryName = secreteKeyFactoryName;
+       }
+
+       public void setSecreteKeyEncryption(String secreteKeyEncryption) {
+               this.secreteKeyEncryption = secreteKeyEncryption;
+       }
+
+       public void setCipherName(String cipherName) {
+               this.cipherName = cipherName;
+       }
+
+       public void setSecurityProviderName(String securityProviderName) {
+               this.securityProviderName = securityProviderName;
+       }
+}