Refactor security components to the Kernel
authorMathieu Baudier <mbaudier@argeo.org>
Fri, 9 Oct 2015 10:11:42 +0000 (10:11 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Fri, 9 Oct 2015 10:11:42 +0000 (10:11 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@8467 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

44 files changed:
org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitNode.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelThread.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeRepository.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg
org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java
org.argeo.cms/src/org/argeo/cms/util/CmsUtils.java
org.argeo.cms/src/org/argeo/cms/util/UserMenu.java
org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java
org.argeo.eclipse.ui.workbench/META-INF/spring/osgi.xml
org.argeo.security.core/META-INF/spring/logger.xml [deleted file]
org.argeo.security.core/META-INF/spring/osgi.xml [deleted file]
org.argeo.security.core/bnd.bnd
org.argeo.security.core/ext/test/org/argeo/security/crypto/PasswordBasedEncryptionTest.java [deleted file]
org.argeo.security.core/src/org/argeo/security/SecurityUtils.java [deleted file]
org.argeo.security.core/src/org/argeo/security/crypto/AbstractKeyring.java [deleted file]
org.argeo.security.core/src/org/argeo/security/crypto/CryptoKeyring.java [deleted file]
org.argeo.security.core/src/org/argeo/security/crypto/KeyringLoginModule.java [deleted file]
org.argeo.security.core/src/org/argeo/security/crypto/PBEKeySpecCallback.java [deleted file]
org.argeo.security.core/src/org/argeo/security/crypto/PasswordBasedEncryption.java [deleted file]
org.argeo.security.core/src/org/argeo/security/crypto/PkiUtils.java [deleted file]
org.argeo.security.core/src/org/argeo/security/jcr/JcrKeyring.java [deleted file]
org.argeo.security.core/src/org/argeo/security/log4j/SecureLogger.java [deleted file]
org.argeo.security.ui/META-INF/spring/keyring.xml
org.argeo.security.ui/META-INF/spring/osgi.xml
org.argeo.security.ui/src/org/argeo/security/ui/RolesSourceProvider.java
org.argeo.security.ui/src/org/argeo/security/ui/internal/CurrentUser.java [deleted file]
org.argeo.security.ui/src/org/argeo/security/ui/views/UserProfile.java
org.argeo.server.jcr/src/org/argeo/jcr/DefaultRepositoryRegister.java
org.argeo.server.jcr/src/org/argeo/jcr/security/JcrKeyring.java [new file with mode: 0644]
org.argeo.util/build.properties
org.argeo.util/ext/test/org/argeo/util/security/PasswordBasedEncryptionTest.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/security/AbstractKeyring.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/security/CryptoKeyring.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/security/KeyringLoginModule.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/security/PBEKeySpecCallback.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/security/PasswordBasedEncryption.java [new file with mode: 0644]

diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java b/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java
new file mode 100644 (file)
index 0000000..5a65fcb
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * 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.auth;
+
+import java.security.AccessController;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.cms.CmsException;
+import org.argeo.cms.CmsView;
+import org.argeo.cms.util.CmsUtils;
+import org.osgi.service.useradmin.Authorization;
+
+/** Static utilities */
+public final class CurrentUser {
+       /**
+        * @return the authenticated username or null if not authenticated /
+        *         anonymous
+        */
+       public static String getUsername() {
+               return getUsername(currentSubject());
+       }
+
+       public static String getDisplayName() {
+               return getDisplayName(currentSubject());
+       }
+
+       private static Subject currentSubject() {
+               Subject subject = Subject.getSubject(AccessController.getContext());
+               if (subject != null)
+                       return subject;
+               if (subject == null) {
+                       CmsView cmsView = CmsUtils.getCmsView();
+                       if (cmsView != null)
+                               return cmsView.getSubject();
+               }
+               throw new CmsException("Cannot find related subject");
+       }
+
+       public final static String getUsername(Subject subject) {
+               // Subject subject = Subject.getSubject(AccessController.getContext());
+               // if (subject == null)
+               // return null;
+               if (subject.getPrincipals(X500Principal.class).size() != 1)
+                       return null;
+               Principal principal = subject.getPrincipals(X500Principal.class)
+                               .iterator().next();
+               return principal.getName();
+
+       }
+
+       public final static String getDisplayName(Subject subject) {
+               return getAuthorization(subject).toString();
+       }
+
+       private static Authorization getAuthorization(Subject subject) {
+               return subject.getPrivateCredentials(Authorization.class).iterator()
+                               .next();
+       }
+
+       public final static Set<String> roles() {
+               return roles(currentSubject());
+       }
+
+       public final static Set<String> roles(Subject subject) {
+               Set<String> roles = new HashSet<String>();
+               X500Principal userPrincipal = subject
+                               .getPrincipals(X500Principal.class).iterator().next();
+               roles.add(userPrincipal.getName());
+               for (Principal group : subject.getPrincipals(Group.class)) {
+                       roles.add(group.getName());
+               }
+               return roles;
+       }
+
+       private CurrentUser() {
+       }
+}
index 102bb77026783b80a317a8365730d1a4a2858c50..8f95a3ef993077cc8071828f360079898f582058 100644 (file)
@@ -65,7 +65,7 @@ class DataHttp implements KernelConstants, ArgeoJcrConstants {
        // WebDav / JCR remoting
        private OpenInViewSessionProvider sessionProvider;
 
-       DataHttp(HttpService httpService, JackrabbitNode node) {
+       DataHttp(HttpService httpService, NodeRepository node) {
                this.httpService = httpService;
                sessionProvider = new OpenInViewSessionProvider();
                registerRepositoryServlets(ALIAS_NODE, node);
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitNode.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitNode.java
deleted file mode 100644 (file)
index 8ef16c4..0000000
+++ /dev/null
@@ -1,200 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import static org.argeo.cms.internal.kernel.JackrabbitNodeType.h2;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.Properties;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.core.RepositoryContext;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.apache.jackrabbit.core.cache.CacheManager;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
-import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
-import org.argeo.ArgeoException;
-import org.argeo.cms.CmsException;
-import org.argeo.jackrabbit.JackrabbitWrapper;
-import org.argeo.jcr.ArgeoJcrConstants;
-import org.argeo.jcr.DefaultRepositoryRegister;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-import org.xml.sax.InputSource;
-
-/** Jacrabbit based data layer */
-class JackrabbitNode extends JackrabbitWrapper implements KernelConstants,
-               ArgeoJcrConstants {
-       private static Log log = LogFactory.getLog(JackrabbitNode.class);
-
-       private RepositoryContext repositoryContext;
-
-       private ServiceRegistration<Repository> repositoryReg;
-
-       public JackrabbitNode(BundleContext bundleContext) {
-               setBundleContext(bundleContext);
-               JackrabbitNodeType type = JackrabbitNodeType.valueOf(prop(REPO_TYPE,
-                               h2.name()));
-               try {
-                       repositoryContext = createNode(type);
-                       setCndFiles(Arrays.asList(DEFAULT_CNDS));
-                       prepareDataModel();
-               } catch (Exception e) {
-                       throw new ArgeoException(
-                                       "Cannot create Jackrabbit repository of type " + type, e);
-               }
-       }
-
-       void publish(DefaultRepositoryRegister repositoryRegister) {
-               Hashtable<String, String> regProps = new Hashtable<String, String>();
-               regProps.put(JCR_REPOSITORY_ALIAS, ALIAS_NODE);
-               repositoryReg = getBundleContext().registerService(Repository.class,
-                               this, regProps);
-               repositoryRegister.register(this, regProps);
-       }
-
-       public void destroy() {
-               repositoryReg.unregister();
-               ((RepositoryImpl) getRepository()).shutdown();
-       }
-
-       RepositoryStatisticsImpl getRepositoryStatistics() {
-               return repositoryContext.getRepositoryStatistics();
-       }
-
-       private RepositoryConfig getConfiguration(JackrabbitNodeType type,
-                       Hashtable<String, Object> vars) throws RepositoryException {
-               ClassLoader cl = getClass().getClassLoader();
-               InputStream in = null;
-               try {
-                       final String base = "/org/argeo/cms/internal/kernel";
-                       switch (type) {
-                       case h2:
-                               in = cl.getResourceAsStream(base + "/repository-h2.xml");
-                               break;
-                       case postgresql:
-                               in = cl.getResourceAsStream(base + "/repository-postgresql.xml");
-                               break;
-                       case memory:
-                               in = cl.getResourceAsStream(base + "/repository-memory.xml");
-                               break;
-                       default:
-                               throw new CmsException("Unsupported node type " + type);
-                       }
-
-                       if (in == null)
-                               throw new CmsException("Repository configuration not found");
-                       InputSource config = new InputSource(in);
-                       Properties jackrabbitProps = new Properties();
-                       jackrabbitProps.putAll(vars);
-                       RepositoryConfig repositoryConfig = RepositoryConfig.create(config,
-                                       jackrabbitProps);
-                       return repositoryConfig;
-               } finally {
-                       IOUtils.closeQuietly(in);
-               }
-       }
-
-       private Hashtable<String, Object> getConfigurationProperties(
-                       JackrabbitNodeType type) {
-               // use Hashtable to ease integration with Properties
-               Hashtable<String, Object> defaults = new Hashtable<String, Object>();
-
-               // home
-               File osgiInstanceDir = KernelUtils.getOsgiInstanceDir();
-               File homeDir = new File(osgiInstanceDir, "node");
-               // home cannot be overridden
-               defaults.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
-                               homeDir.getAbsolutePath());
-
-               // common
-               setProp(defaults, REPO_DEFAULT_WORKSPACE, "main");
-               setProp(defaults, REPO_MAX_POOL_SIZE, "10");
-               // Jackrabbit defaults
-               setProp(defaults, REPO_BUNDLE_CACHE_MB, "8");
-               // See http://wiki.apache.org/jackrabbit/Search
-               setProp(defaults, REPO_EXTRACTOR_POOL_SIZE, "0");
-               setProp(defaults, REPO_SEARCH_CACHE_SIZE, "1000");
-               setProp(defaults, REPO_MAX_VOLATILE_INDEX_SIZE, "1048576");
-
-               // specific
-               String dburl;
-               switch (type) {
-               case h2:
-                       dburl = "jdbc:h2:" + homeDir.getPath() + "/h2/repository";
-                       setProp(defaults, REPO_DBURL, dburl);
-                       setProp(defaults, REPO_DBUSER, "sa");
-                       setProp(defaults, REPO_DBPASSWORD, "");
-                       break;
-               case postgresql:
-                       dburl = "jdbc:postgresql://localhost/demo";
-                       setProp(defaults, REPO_DBURL, dburl);
-                       setProp(defaults, REPO_DBUSER, "argeo");
-                       setProp(defaults, REPO_DBPASSWORD, "argeo");
-                       break;
-               case memory:
-                       break;
-               default:
-                       throw new CmsException("Unsupported node type " + type);
-               }
-               return defaults;
-       }
-
-       private void setProp(Dictionary<String, Object> props, String key,
-                       String defaultValue) {
-               String value = prop(key, defaultValue);
-               props.put(key, value);
-       }
-
-       private String prop(String key, String defaultValue) {
-               // TODO use OSGi CM instead of Framework/System properties
-               return KernelUtils.getFrameworkProp(key, defaultValue);
-       }
-
-       private RepositoryContext createNode(JackrabbitNodeType type)
-                       throws RepositoryException {
-               Hashtable<String, Object> vars = getConfigurationProperties(type);
-               RepositoryConfig repositoryConfig = getConfiguration(type, vars);
-               RepositoryContext repositoryContext = createJackrabbitRepository(repositoryConfig);
-               RepositoryImpl repository = repositoryContext.getRepository();
-
-               // cache
-               String maxCacheMbStr = prop(REPO_MAX_CACHE_MB, null);
-               if (maxCacheMbStr != null) {
-                       Integer maxCacheMB = Integer.parseInt(maxCacheMbStr);
-                       CacheManager cacheManager = repository.getCacheManager();
-                       cacheManager.setMaxMemory(maxCacheMB * 1024l * 1024l);
-                       cacheManager.setMaxMemoryPerCache((maxCacheMB / 4) * 1024l * 1024l);
-               }
-
-               // wrap the repository
-               setRepository(repository);
-               return repositoryContext;
-       }
-
-       private RepositoryContext createJackrabbitRepository(
-                       RepositoryConfig repositoryConfig) throws RepositoryException {
-               File homeDirectory = null;
-               long begin = System.currentTimeMillis();
-               //
-               // Actual repository creation
-               //
-               RepositoryContext repositoryContext = RepositoryContext
-                               .create(repositoryConfig);
-
-               double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
-               if (log.isTraceEnabled())
-                       log.trace("Created Jackrabbit repository in " + duration
-                                       + " s, home: " + homeDirectory);
-
-               return repositoryContext;
-       }
-}
index 8486f8d7ea6e31d6fc86b1a090c4d322cebaa192..026b12116159b9667f2c96464f767754bd5d1f3e 100644 (file)
@@ -1,22 +1,25 @@
 package org.argeo.cms.internal.kernel;
 
+import static org.argeo.jcr.ArgeoJcrConstants.ALIAS_NODE;
+import static org.argeo.jcr.ArgeoJcrConstants.JCR_REPOSITORY_ALIAS;
+
 import java.lang.management.ManagementFactory;
 import java.security.PrivilegedAction;
 import java.util.HashMap;
+import java.util.Hashtable;
 import java.util.Map;
 
 import javax.jcr.Repository;
 import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
 import javax.security.auth.Subject;
 import javax.transaction.TransactionManager;
-import javax.transaction.TransactionSynchronizationRegistry;
 import javax.transaction.UserTransaction;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.jackrabbit.util.TransientFileFactory;
 import org.argeo.ArgeoException;
+import org.argeo.ArgeoLogger;
 import org.argeo.cms.CmsException;
 import org.argeo.cms.internal.transaction.SimpleTransactionManager;
 import org.argeo.jackrabbit.OsgiJackrabbitRepositoryFactory;
@@ -26,8 +29,8 @@ import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceEvent;
 import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.util.tracker.ServiceTracker;
 
 /**
  * Argeo CMS Kernel. Responsible for :
@@ -41,17 +44,32 @@ import org.osgi.util.tracker.ServiceTracker;
  * </ul>
  */
 final class Kernel implements ServiceListener {
-       private final static Log log = LogFactory.getLog(Kernel.class);
-
-       private final BundleContext bundleContext = Activator.getBundleContext();
-       private final NodeSecurity nodeSecurity;
-
-       ThreadGroup threadGroup = new ThreadGroup(Kernel.class.getSimpleName());
-       JackrabbitNode node;
-       private NodeUserAdmin userAdmin;
+       /*
+        * REGISTERED SERVICES
+        */
+       private ServiceRegistration<ArgeoLogger> loggerReg;
+       private ServiceRegistration<TransactionManager> tmReg;
+       private ServiceRegistration<UserTransaction> utReg;
+       // private ServiceRegistration<TransactionSynchronizationRegistry> tsrReg;
+       private ServiceRegistration<Repository> repositoryReg;
+       private ServiceRegistration<RepositoryFactory> repositoryFactoryReg;
+       private ServiceRegistration<UserAdmin> userAdminReg;
+
+       /*
+        * SERVICES IMPLEMENTATIONS
+        */
+       private NodeLogger logger;
        private SimpleTransactionManager transactionManager;
        private OsgiJackrabbitRepositoryFactory repositoryFactory;
-       private DataHttp nodeHttp;
+       NodeRepository repository;
+       private NodeUserAdmin userAdmin;
+
+       // Members
+       private final static Log log = LogFactory.getLog(Kernel.class);
+       ThreadGroup threadGroup = new ThreadGroup(Kernel.class.getSimpleName());
+       private final BundleContext bc = Activator.getBundleContext();
+       private final NodeSecurity nodeSecurity;
+       private DataHttp dataHttp;
        private KernelThread kernelThread;
 
        public Kernel() {
@@ -61,13 +79,11 @@ final class Kernel implements ServiceListener {
        final void init() {
                Subject.doAs(nodeSecurity.getKernelSubject(),
                                new PrivilegedAction<Void>() {
-
                                        @Override
                                        public Void run() {
                                                doInit();
                                                return null;
                                        }
-
                                });
        }
 
@@ -79,26 +95,15 @@ final class Kernel implements ServiceListener {
                long begin = System.currentTimeMillis();
 
                try {
-                       // Transaction
+                       // Initialise services
+                       logger = new NodeLogger();
                        transactionManager = new SimpleTransactionManager();
-
-                       // Jackrabbit node
-                       node = new JackrabbitNode(bundleContext);
-
-                       // JCR repository factory
+                       repository = new NodeRepository(bc);
                        repositoryFactory = new OsgiJackrabbitRepositoryFactory();
+                       userAdmin = new NodeUserAdmin(transactionManager, repository);
 
-                       // Authentication
-                       Session adminSession = node.login();
-                       userAdmin = new NodeUserAdmin(adminSession);
-                       userAdmin.setTransactionManager(transactionManager);
-                       bundleContext.registerService(UserAdmin.class, userAdmin,
-                                       userAdmin.currentState());
-
-                       // Equinox dependency
-                       // ExtendedHttpService httpService = waitForHttpService();
-                       // nodeHttp = new NodeHttp(httpService, node);
-                       ServiceReference<ExtendedHttpService> sr = bundleContext
+                       // HTTP
+                       ServiceReference<ExtendedHttpService> sr = bc
                                        .getServiceReference(ExtendedHttpService.class);
                        if (sr != null)
                                addHttpService(sr);
@@ -109,19 +114,7 @@ final class Kernel implements ServiceListener {
                        kernelThread.start();
 
                        // Publish services to OSGi
-                       bundleContext.registerService(TransactionManager.class,
-                                       transactionManager, null);
-                       bundleContext.registerService(UserTransaction.class,
-                                       transactionManager, null);
-                       bundleContext.registerService(
-                                       TransactionSynchronizationRegistry.class,
-                                       transactionManager.getTransactionSynchronizationRegistry(),
-                                       null);
-                       node.publish(repositoryFactory);
-                       bundleContext.registerService(RepositoryFactory.class,
-                                       repositoryFactory, null);
-
-                       bundleContext.addServiceListener(Kernel.this);
+                       publish();
                } catch (Exception e) {
                        log.error("Cannot initialize Argeo CMS", e);
                        throw new ArgeoException("Cannot initialize", e);
@@ -138,19 +131,45 @@ final class Kernel implements ServiceListener {
                directorsCut(initDuration);
        }
 
+       private void publish() {
+               // Listen to service publication (also ours)
+               bc.addServiceListener(Kernel.this);
+               
+               // Logging
+               loggerReg = bc.registerService(ArgeoLogger.class, logger, null);
+               // Transaction
+               tmReg = bc.registerService(TransactionManager.class,
+                               transactionManager, null);
+               utReg = bc.registerService(UserTransaction.class, transactionManager,
+                               null);
+               // tsrReg = bc.registerService(TransactionSynchronizationRegistry.class,
+               // transactionManager.getTsr(), null);
+               // User admin
+               userAdminReg = bc.registerService(UserAdmin.class, userAdmin,
+                               userAdmin.currentState());
+               // JCR
+               Hashtable<String, String> regProps = new Hashtable<String, String>();
+               regProps.put(JCR_REPOSITORY_ALIAS, ALIAS_NODE);
+               repositoryReg = bc.registerService(Repository.class, repository,
+                               regProps);
+               repositoryFactoryReg = bc.registerService(RepositoryFactory.class,
+                               repositoryFactory, null);
+       }
+
        void destroy() {
                long begin = System.currentTimeMillis();
+               unpublish();
 
                kernelThread.destroyAndJoin();
 
-               if (nodeHttp != null)
-                       nodeHttp.destroy();
+               if (dataHttp != null)
+                       dataHttp.destroy();
                if (userAdmin != null)
                        userAdmin.destroy();
-               if (node != null)
-                       node.destroy();
+               if (repository != null)
+                       repository.destroy();
 
-               bundleContext.removeServiceListener(this);
+               bc.removeServiceListener(this);
 
                // Clean hanging threads from Jackrabbit
                TransientFileFactory.shutdown();
@@ -164,39 +183,47 @@ final class Kernel implements ServiceListener {
                                + (duration % 1000) + "s ##");
        }
 
+       private void unpublish() {
+               userAdminReg.unregister();
+               repositoryFactoryReg.unregister();
+               repositoryReg.unregister();
+               tmReg.unregister();
+               utReg.unregister();
+               loggerReg.unregister();
+       }
+
        @Override
        public void serviceChanged(ServiceEvent event) {
                ServiceReference<?> sr = event.getServiceReference();
-               Object service = bundleContext.getService(sr);
+               Object service = bc.getService(sr);
                if (service instanceof Repository) {
                        Object jcrRepoAlias = sr
                                        .getProperty(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS);
                        if (jcrRepoAlias != null) {// JCR repository
                                String alias = jcrRepoAlias.toString();
-                               Repository repository = (Repository) bundleContext
-                                               .getService(sr);
+                               Repository repository = (Repository) bc.getService(sr);
                                Map<String, Object> props = new HashMap<String, Object>();
                                for (String key : sr.getPropertyKeys())
                                        props.put(key, sr.getProperty(key));
                                if (ServiceEvent.REGISTERED == event.getType()) {
                                        try {
                                                repositoryFactory.register(repository, props);
-                                               nodeHttp.registerRepositoryServlets(alias, repository);
+                                               dataHttp.registerRepositoryServlets(alias, repository);
                                        } catch (Exception e) {
                                                throw new CmsException(
                                                                "Could not publish JCR repository " + alias, e);
                                        }
                                } else if (ServiceEvent.UNREGISTERING == event.getType()) {
                                        repositoryFactory.unregister(repository, props);
-                                       nodeHttp.unregisterRepositoryServlets(alias);
+                                       dataHttp.unregisterRepositoryServlets(alias);
                                }
                        }
                } else if (service instanceof ExtendedHttpService) {
                        if (ServiceEvent.REGISTERED == event.getType()) {
                                addHttpService(sr);
                        } else if (ServiceEvent.UNREGISTERING == event.getType()) {
-                               nodeHttp.destroy();
-                               nodeHttp = null;
+                               dataHttp.destroy();
+                               dataHttp = null;
                        }
                }
        }
@@ -204,33 +231,34 @@ final class Kernel implements ServiceListener {
        private void addHttpService(ServiceReference<?> sr) {
                // for (String key : sr.getPropertyKeys())
                // log.debug(key + "=" + sr.getProperty(key));
-               ExtendedHttpService httpService = (ExtendedHttpService) bundleContext
+               ExtendedHttpService httpService = (ExtendedHttpService) bc
                                .getService(sr);
                // TODO find constants
                Object httpPort = sr.getProperty("http.port");
                Object httpsPort = sr.getProperty("https.port");
-               nodeHttp = new DataHttp(httpService, node);
+               dataHttp = new DataHttp(httpService, repository);
                if (log.isDebugEnabled())
                        log.debug("HTTP " + httpPort
                                        + (httpsPort != null ? " - HTTPS " + httpsPort : ""));
        }
 
-       private ExtendedHttpService waitForHttpService() {
-               final ServiceTracker<ExtendedHttpService, ExtendedHttpService> st = new ServiceTracker<ExtendedHttpService, ExtendedHttpService>(
-                               bundleContext, ExtendedHttpService.class, null);
-               st.open();
-               ExtendedHttpService httpService;
-               try {
-                       httpService = st.waitForService(1000);
-               } catch (InterruptedException e) {
-                       httpService = null;
-               }
-
-               if (httpService == null)
-                       throw new CmsException("Could not find "
-                                       + ExtendedHttpService.class + " service.");
-               return httpService;
-       }
+       // private ExtendedHttpService waitForHttpService() {
+       // final ServiceTracker<ExtendedHttpService, ExtendedHttpService> st = new
+       // ServiceTracker<ExtendedHttpService, ExtendedHttpService>(
+       // bc, ExtendedHttpService.class, null);
+       // st.open();
+       // ExtendedHttpService httpService;
+       // try {
+       // httpService = st.waitForService(1000);
+       // } catch (InterruptedException e) {
+       // httpService = null;
+       // }
+       //
+       // if (httpService == null)
+       // throw new CmsException("Could not find "
+       // + ExtendedHttpService.class + " service.");
+       // return httpService;
+       // }
 
        final private static void directorsCut(long initDuration) {
                // final long ms = 128l + (long) (Math.random() * 128d);
index e58cbeef1959654589c80b44699bceca27e13be5..4b4b8026f983830d6e307b36d332ed9a9544e70a 100644 (file)
@@ -35,7 +35,7 @@ class KernelThread extends Thread {
        public KernelThread(Kernel kernel) {
                super(kernel.threadGroup, kernel.getClass().getSimpleName());
                this.kernel = kernel;
-               this.repoStats = kernel.node.getRepositoryStatistics();
+               this.repoStats = kernel.repository.getRepositoryStatistics();
        }
 
        private void doSmallestPeriod() {
index edcf719d7b3170edbfc7a6c61e40ca915e2ad5ad..bc74cf638722c3d8409e511f865ea28b34f0b442 100644 (file)
@@ -65,7 +65,7 @@ class NodeHttp implements KernelConstants, ArgeoJcrConstants {
        // WebDav / JCR remoting
        private OpenInViewSessionProvider sessionProvider;
 
-       NodeHttp(ExtendedHttpService httpService, JackrabbitNode node) {
+       NodeHttp(ExtendedHttpService httpService, NodeRepository node) {
                // this.bundleContext = bundleContext;
                // this.authenticationManager = authenticationManager;
 
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java
new file mode 100644 (file)
index 0000000..1264b24
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+ * 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.internal.kernel;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.spi.LoggingEvent;
+import org.argeo.ArgeoException;
+import org.argeo.ArgeoLogListener;
+import org.argeo.ArgeoLogger;
+import org.argeo.cms.auth.CurrentUser;
+
+/** Not meant to be used directly in standard log4j config */
+class NodeLogger implements ArgeoLogger {
+
+       private Boolean disabled = false;
+
+       private String level = null;
+
+       private Level log4jLevel = null;
+       // private Layout layout;
+
+       private Properties configuration;
+
+       private AppenderImpl appender;
+
+       private final List<ArgeoLogListener> everythingListeners = Collections
+                       .synchronizedList(new ArrayList<ArgeoLogListener>());
+       private final List<ArgeoLogListener> allUsersListeners = Collections
+                       .synchronizedList(new ArrayList<ArgeoLogListener>());
+       private final Map<String, List<ArgeoLogListener>> userListeners = Collections
+                       .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
+
+       private BlockingQueue<LogEvent> events;
+       private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
+
+       private Integer maxLastEventsCount = 10 * 1000;
+
+       /** Marker to prevent stack overflow */
+       private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
+
+               @Override
+               protected Boolean initialValue() {
+                       return false;
+               }
+       };
+
+       public void init() {
+               try {
+                       events = new LinkedBlockingQueue<LogEvent>();
+
+                       // if (layout != null)
+                       // setLayout(layout);
+                       // else
+                       // setLayout(new PatternLayout(pattern));
+                       appender = new AppenderImpl();
+                       reloadConfiguration();
+                       Logger.getRootLogger().addAppender(appender);
+
+                       logDispatcherThread = new LogDispatcherThread();
+                       logDispatcherThread.start();
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot initialize log4j");
+               }
+       }
+
+       public void destroy() throws Exception {
+               Logger.getRootLogger().removeAppender(appender);
+               allUsersListeners.clear();
+               for (List<ArgeoLogListener> lst : userListeners.values())
+                       lst.clear();
+               userListeners.clear();
+
+               events.clear();
+               events = null;
+               logDispatcherThread.interrupt();
+       }
+
+       // public void setLayout(Layout layout) {
+       // this.layout = layout;
+       // }
+
+       public synchronized void register(ArgeoLogListener listener,
+                       Integer numberOfPreviousEvents) {
+               String username = CurrentUser.getUsername();
+               if (username == null)
+                       throw new ArgeoException(
+                                       "Only authenticated users can register a log listener");
+
+               if (!userListeners.containsKey(username)) {
+                       List<ArgeoLogListener> lst = Collections
+                                       .synchronizedList(new ArrayList<ArgeoLogListener>());
+                       userListeners.put(username, lst);
+               }
+               userListeners.get(username).add(listener);
+               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username,
+                               numberOfPreviousEvents);
+               for (LogEvent evt : lastEvents)
+                       dispatchEvent(listener, evt);
+       }
+
+       public synchronized void registerForAll(ArgeoLogListener listener,
+                       Integer numberOfPreviousEvents, boolean everything) {
+               if (everything)
+                       everythingListeners.add(listener);
+               else
+                       allUsersListeners.add(listener);
+               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null,
+                               numberOfPreviousEvents);
+               for (LogEvent evt : lastEvents)
+                       if (everything || evt.getUsername() != null)
+                               dispatchEvent(listener, evt);
+       }
+
+       public synchronized void unregister(ArgeoLogListener listener) {
+               String username = CurrentUser.getUsername();
+               if (username == null)// FIXME
+                       return;
+               if (!userListeners.containsKey(username))
+                       throw new ArgeoException("No user listeners " + listener
+                                       + " registered for user " + username);
+               if (!userListeners.get(username).contains(listener))
+                       throw new ArgeoException("No user listeners " + listener
+                                       + " registered for user " + username);
+               userListeners.get(username).remove(listener);
+               if (userListeners.get(username).isEmpty())
+                       userListeners.remove(username);
+
+       }
+
+       public synchronized void unregisterForAll(ArgeoLogListener listener) {
+               everythingListeners.remove(listener);
+               allUsersListeners.remove(listener);
+       }
+
+       /** For development purpose, since using regular logging is not easy here */
+       static void stdOut(Object obj) {
+               System.out.println(obj);
+       }
+
+       // public void setPattern(String pattern) {
+       // this.pattern = pattern;
+       // }
+
+       public void setDisabled(Boolean disabled) {
+               this.disabled = disabled;
+       }
+
+       public void setLevel(String level) {
+               this.level = level;
+       }
+
+       public void setConfiguration(Properties configuration) {
+               this.configuration = configuration;
+       }
+
+       public void updateConfiguration(Properties configuration) {
+               setConfiguration(configuration);
+               reloadConfiguration();
+       }
+
+       public Properties getConfiguration() {
+               return configuration;
+       }
+
+       /** Reloads configuration (if the configuration {@link Properties} is set) */
+       protected void reloadConfiguration() {
+               if (configuration != null) {
+                       LogManager.resetConfiguration();
+                       PropertyConfigurator.configure(configuration);
+               }
+       }
+
+       protected synchronized void processLoggingEvent(LogEvent event) {
+               if (disabled)
+                       return;
+
+               if (dispatching.get())
+                       return;
+
+               if (level != null && !level.trim().equals("")) {
+                       if (log4jLevel == null || !log4jLevel.toString().equals(level))
+                               try {
+                                       log4jLevel = Level.toLevel(level);
+                               } catch (Exception e) {
+                                       System.err
+                                                       .println("Log4j level could not be set for level '"
+                                                                       + level + "', resetting it to null.");
+                                       e.printStackTrace();
+                                       level = null;
+                               }
+
+                       if (log4jLevel != null
+                                       && !event.getLoggingEvent().getLevel()
+                                                       .isGreaterOrEqual(log4jLevel)) {
+                               return;
+                       }
+               }
+
+               try {
+                       // admin listeners
+                       Iterator<ArgeoLogListener> everythingIt = everythingListeners
+                                       .iterator();
+                       while (everythingIt.hasNext())
+                               dispatchEvent(everythingIt.next(), event);
+
+                       if (event.getUsername() != null) {
+                               Iterator<ArgeoLogListener> allUsersIt = allUsersListeners
+                                               .iterator();
+                               while (allUsersIt.hasNext())
+                                       dispatchEvent(allUsersIt.next(), event);
+
+                               if (userListeners.containsKey(event.getUsername())) {
+                                       Iterator<ArgeoLogListener> userIt = userListeners.get(
+                                                       event.getUsername()).iterator();
+                                       while (userIt.hasNext())
+                                               dispatchEvent(userIt.next(), event);
+                               }
+                       }
+               } catch (Exception e) {
+                       stdOut("Cannot process logging event");
+                       e.printStackTrace();
+               }
+       }
+
+       protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
+               LoggingEvent event = evt.getLoggingEvent();
+               logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event
+                               .getLevel().toString(), event.getLoggerName(), event
+                               .getThreadName(), event.getMessage(), event
+                               .getThrowableStrRep());
+       }
+
+       private class AppenderImpl extends AppenderSkeleton {
+               public boolean requiresLayout() {
+                       return false;
+               }
+
+               public void close() {
+               }
+
+               @Override
+               protected void append(LoggingEvent event) {
+                       if (events != null) {
+                               try {
+                                       String username = CurrentUser.getUsername();
+                                       events.put(new LogEvent(username, event));
+                               } catch (InterruptedException e) {
+                                       // silent
+                               }
+                       }
+               }
+
+       }
+
+       private class LogDispatcherThread extends Thread {
+               /** encapsulated in order to simplify concurrency management */
+               private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
+
+               public LogDispatcherThread() {
+                       super("Argeo Logging Dispatcher Thread");
+               }
+
+               public void run() {
+                       while (events != null) {
+                               try {
+                                       LogEvent loggingEvent = events.take();
+                                       processLoggingEvent(loggingEvent);
+                                       addLastEvent(loggingEvent);
+                               } catch (InterruptedException e) {
+                                       if (events == null)
+                                               return;
+                               }
+                       }
+               }
+
+               protected synchronized void addLastEvent(LogEvent loggingEvent) {
+                       if (lastEvents.size() >= maxLastEventsCount)
+                               lastEvents.poll();
+                       lastEvents.add(loggingEvent);
+               }
+
+               public synchronized List<LogEvent> getLastEvents(String username,
+                               Integer maxCount) {
+                       LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
+                       ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents
+                                       .size());
+                       int count = 0;
+                       while (it.hasPrevious() && (count < maxCount)) {
+                               LogEvent evt = it.previous();
+                               if (username == null || username.equals(evt.getUsername())) {
+                                       evts.push(evt);
+                                       count++;
+                               }
+                       }
+                       return evts;
+               }
+       }
+
+       private class LogEvent {
+               private final String username;
+               private final LoggingEvent loggingEvent;
+
+               public LogEvent(String username, LoggingEvent loggingEvent) {
+                       super();
+                       this.username = username;
+                       this.loggingEvent = loggingEvent;
+               }
+
+               @Override
+               public int hashCode() {
+                       return loggingEvent.hashCode();
+               }
+
+               @Override
+               public boolean equals(Object obj) {
+                       return loggingEvent.equals(obj);
+               }
+
+               @Override
+               public String toString() {
+                       return username + "@ " + loggingEvent.toString();
+               }
+
+               public String getUsername() {
+                       return username;
+               }
+
+               public LoggingEvent getLoggingEvent() {
+                       return loggingEvent;
+               }
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeRepository.java
new file mode 100644 (file)
index 0000000..a139289
--- /dev/null
@@ -0,0 +1,186 @@
+package org.argeo.cms.internal.kernel;
+
+import static org.argeo.cms.internal.kernel.JackrabbitNodeType.h2;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Properties;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.cache.CacheManager;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
+import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
+import org.argeo.ArgeoException;
+import org.argeo.cms.CmsException;
+import org.argeo.jackrabbit.JackrabbitWrapper;
+import org.argeo.jcr.ArgeoJcrConstants;
+import org.osgi.framework.BundleContext;
+import org.xml.sax.InputSource;
+
+/** Jacrabbit based data layer */
+class NodeRepository extends JackrabbitWrapper implements KernelConstants,
+               ArgeoJcrConstants {
+       private static Log log = LogFactory.getLog(NodeRepository.class);
+
+       private RepositoryContext repositoryContext;
+
+       public NodeRepository(BundleContext bundleContext) {
+               setBundleContext(bundleContext);
+               JackrabbitNodeType type = JackrabbitNodeType.valueOf(prop(REPO_TYPE,
+                               h2.name()));
+               try {
+                       repositoryContext = createNode(type);
+                       setCndFiles(Arrays.asList(DEFAULT_CNDS));
+                       prepareDataModel();
+               } catch (Exception e) {
+                       throw new ArgeoException(
+                                       "Cannot create Jackrabbit repository of type " + type, e);
+               }
+       }
+
+       public void destroy() {
+               ((RepositoryImpl) getRepository()).shutdown();
+       }
+
+       RepositoryStatisticsImpl getRepositoryStatistics() {
+               return repositoryContext.getRepositoryStatistics();
+       }
+
+       private RepositoryConfig getConfiguration(JackrabbitNodeType type,
+                       Hashtable<String, Object> vars) throws RepositoryException {
+               ClassLoader cl = getClass().getClassLoader();
+               InputStream in = null;
+               try {
+                       final String base = "/org/argeo/cms/internal/kernel";
+                       switch (type) {
+                       case h2:
+                               in = cl.getResourceAsStream(base + "/repository-h2.xml");
+                               break;
+                       case postgresql:
+                               in = cl.getResourceAsStream(base + "/repository-postgresql.xml");
+                               break;
+                       case memory:
+                               in = cl.getResourceAsStream(base + "/repository-memory.xml");
+                               break;
+                       default:
+                               throw new CmsException("Unsupported node type " + type);
+                       }
+
+                       if (in == null)
+                               throw new CmsException("Repository configuration not found");
+                       InputSource config = new InputSource(in);
+                       Properties jackrabbitProps = new Properties();
+                       jackrabbitProps.putAll(vars);
+                       RepositoryConfig repositoryConfig = RepositoryConfig.create(config,
+                                       jackrabbitProps);
+                       return repositoryConfig;
+               } finally {
+                       IOUtils.closeQuietly(in);
+               }
+       }
+
+       private Hashtable<String, Object> getConfigurationProperties(
+                       JackrabbitNodeType type) {
+               // use Hashtable to ease integration with Properties
+               Hashtable<String, Object> defaults = new Hashtable<String, Object>();
+
+               // home
+               File osgiInstanceDir = KernelUtils.getOsgiInstanceDir();
+               File homeDir = new File(osgiInstanceDir, "node");
+               // home cannot be overridden
+               defaults.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
+                               homeDir.getAbsolutePath());
+
+               // common
+               setProp(defaults, REPO_DEFAULT_WORKSPACE, "main");
+               setProp(defaults, REPO_MAX_POOL_SIZE, "10");
+               // Jackrabbit defaults
+               setProp(defaults, REPO_BUNDLE_CACHE_MB, "8");
+               // See http://wiki.apache.org/jackrabbit/Search
+               setProp(defaults, REPO_EXTRACTOR_POOL_SIZE, "0");
+               setProp(defaults, REPO_SEARCH_CACHE_SIZE, "1000");
+               setProp(defaults, REPO_MAX_VOLATILE_INDEX_SIZE, "1048576");
+
+               // specific
+               String dburl;
+               switch (type) {
+               case h2:
+                       dburl = "jdbc:h2:" + homeDir.getPath() + "/h2/repository";
+                       setProp(defaults, REPO_DBURL, dburl);
+                       setProp(defaults, REPO_DBUSER, "sa");
+                       setProp(defaults, REPO_DBPASSWORD, "");
+                       break;
+               case postgresql:
+                       dburl = "jdbc:postgresql://localhost/demo";
+                       setProp(defaults, REPO_DBURL, dburl);
+                       setProp(defaults, REPO_DBUSER, "argeo");
+                       setProp(defaults, REPO_DBPASSWORD, "argeo");
+                       break;
+               case memory:
+                       break;
+               default:
+                       throw new CmsException("Unsupported node type " + type);
+               }
+               return defaults;
+       }
+
+       private void setProp(Dictionary<String, Object> props, String key,
+                       String defaultValue) {
+               String value = prop(key, defaultValue);
+               props.put(key, value);
+       }
+
+       private String prop(String key, String defaultValue) {
+               // TODO use OSGi CM instead of Framework/System properties
+               return KernelUtils.getFrameworkProp(key, defaultValue);
+       }
+
+       private RepositoryContext createNode(JackrabbitNodeType type)
+                       throws RepositoryException {
+               Hashtable<String, Object> vars = getConfigurationProperties(type);
+               RepositoryConfig repositoryConfig = getConfiguration(type, vars);
+               RepositoryContext repositoryContext = createJackrabbitRepository(repositoryConfig);
+               RepositoryImpl repository = repositoryContext.getRepository();
+
+               // cache
+               String maxCacheMbStr = prop(REPO_MAX_CACHE_MB, null);
+               if (maxCacheMbStr != null) {
+                       Integer maxCacheMB = Integer.parseInt(maxCacheMbStr);
+                       CacheManager cacheManager = repository.getCacheManager();
+                       cacheManager.setMaxMemory(maxCacheMB * 1024l * 1024l);
+                       cacheManager.setMaxMemoryPerCache((maxCacheMB / 4) * 1024l * 1024l);
+               }
+
+               // wrap the repository
+               setRepository(repository);
+               return repositoryContext;
+       }
+
+       private RepositoryContext createJackrabbitRepository(
+                       RepositoryConfig repositoryConfig) throws RepositoryException {
+               File homeDirectory = null;
+               long begin = System.currentTimeMillis();
+               //
+               // Actual repository creation
+               //
+               RepositoryContext repositoryContext = RepositoryContext
+                               .create(repositoryConfig);
+
+               double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
+               if (log.isTraceEnabled())
+                       log.trace("Created Jackrabbit repository in " + duration
+                                       + " s, home: " + homeDirectory);
+
+               return repositoryContext;
+       }
+}
index b436ac8d11e1e07262736f72d6ef0c78a0b02764..b43a9fdf5c393b5aef084b8eb9e5b3df35e808ee 100644 (file)
@@ -22,11 +22,14 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.cms.CmsException;
 import org.argeo.cms.auth.AuthConstants;
-import org.argeo.security.crypto.PkiUtils;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
-/** Authentication and user management. */
+/** Low-level kernel security */
 class NodeSecurity {
+       public final static int HARDENED = 3;
+       public final static int STAGING = 2;
+       public final static int DEV = 1;
+
        final static String SECURITY_PROVIDER = "BC";// Bouncy Castle
 
        private final static Log log;
@@ -45,6 +48,7 @@ class NodeSecurity {
        }
 
        private final Subject kernelSubject;
+       private int securityLevel = STAGING;
 
        public NodeSecurity() {
                // Configure JAAS first
@@ -53,10 +57,10 @@ class NodeSecurity {
                System.setProperty("java.security.auth.login.config",
                                url.toExternalForm());
 
-               this.kernelSubject = logKernel();
+               this.kernelSubject = logInKernel();
        }
 
-       private Subject logKernel() {
+       private Subject logInKernel() {
                final Subject kernelSubject = new Subject();
                createKeyStoreIfNeeded();
 
@@ -66,7 +70,8 @@ class NodeSecurity {
                        public void handle(Callback[] callbacks) throws IOException,
                                        UnsupportedCallbackException {
                                // alias
-                               ((NameCallback) callbacks[1]).setName(AuthConstants.ROLE_KERNEL);
+                               ((NameCallback) callbacks[1])
+                                               .setName(AuthConstants.ROLE_KERNEL);
                                // store pwd
                                ((PasswordCallback) callbacks[2]).setPassword("changeit"
                                                .toCharArray());
@@ -93,7 +98,7 @@ class NodeSecurity {
                                        KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject);
                        kernelLc.logout();
                } catch (LoginException e) {
-                       throw new CmsException("Cannot log in kernel", e);
+                       throw new CmsException("Cannot log out kernel", e);
                }
 
                Security.removeProvider(SECURITY_PROVIDER);
@@ -103,6 +108,21 @@ class NodeSecurity {
                return kernelSubject;
        }
 
+       public synchronized int getSecurityLevel() {
+               return securityLevel;
+       }
+
+       public void setSecurityLevel(int newValue) {
+               if (newValue != STAGING || newValue != DEV)
+                       throw new CmsException("Invalid value for security level "
+                                       + newValue);
+               if (newValue >= securityLevel)
+                       throw new CmsException(
+                                       "Impossible to increase security level (from "
+                                                       + securityLevel + " to " + newValue + ")");
+               securityLevel = newValue;
+       }
+
        private void createKeyStoreIfNeeded() {
                char[] ksPwd = "changeit".toCharArray();
                char[] keyPwd = Arrays.copyOf(ksPwd, ksPwd.length);
index 75cd44491cb20278714a5b96f138cda04978385d..7408b1c1f91d28977690dcf3e30c73d901e0629d 100644 (file)
@@ -1,5 +1,8 @@
 package org.argeo.cms.internal.kernel;
 
+import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp;
+import static org.argeo.cms.internal.kernel.KernelUtils.getOsgiInstanceDir;
+
 import java.io.File;
 import java.io.IOException;
 import java.net.URI;
@@ -14,6 +17,7 @@ import java.util.Map;
 import java.util.Set;
 
 import javax.jcr.Node;
+import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Value;
@@ -44,6 +48,10 @@ import org.osgi.service.useradmin.Role;
 import org.osgi.service.useradmin.User;
 import org.osgi.service.useradmin.UserAdmin;
 
+/**
+ * Aggregates multiple {@link UserDirectory} and integrates them with this node
+ * system roles.
+ */
 public class NodeUserAdmin implements UserAdmin {
        private final static Log log = LogFactory.getLog(NodeUserAdmin.class);
        final static LdapName ROLES_BASE;
@@ -56,110 +64,41 @@ public class NodeUserAdmin implements UserAdmin {
                }
        }
 
+       // DAOs
        private UserAdmin nodeRoles = null;
        private Map<LdapName, UserAdmin> userAdmins = new HashMap<LdapName, UserAdmin>();
 
+       // JCR
        /** The home base path. */
        private String homeBasePath = "/home";
        private String peopleBasePath = ArgeoJcrConstants.PEOPLE_BASE_PATH;
+       private Repository repository;
        private Session adminSession;
 
-       public NodeUserAdmin(Session adminSession) {
-               this.adminSession = adminSession;
-               File osgiInstanceDir = KernelUtils.getOsgiInstanceDir();
-               File nodeBaseDir = new File(osgiInstanceDir, "node");
-               nodeBaseDir.mkdirs();
-
-               String userAdminUri = KernelUtils
-                               .getFrameworkProp(KernelConstants.USERADMIN_URIS);
-               if (userAdminUri == null) {
-                       String demoBaseDn = "dc=example,dc=com";
-                       File businessRolesFile = new File(nodeBaseDir, demoBaseDn + ".ldif");
-                       if (!businessRolesFile.exists())
-                               try {
-                                       FileUtils.copyInputStreamToFile(getClass()
-                                                       .getResourceAsStream(demoBaseDn + ".ldif"),
-                                                       businessRolesFile);
-                               } catch (IOException e) {
-                                       throw new CmsException("Cannot copy demo resource", e);
-                               }
-                       userAdminUri = businessRolesFile.toURI().toString();
-               }
-
-               String[] uris = userAdminUri.split(" ");
-               for (String uri : uris) {
-                       URI u;
-                       try {
-                               u = new URI(uri);
-                               if (u.getPath() == null)
-                                       throw new CmsException("URI " + uri
-                                                       + " must have a path in order to determine base DN");
-                               if (u.getScheme() == null) {
-                                       if (uri.startsWith("/") || uri.startsWith("./")
-                                                       || uri.startsWith("../"))
-                                               u = new File(uri).getCanonicalFile().toURI();
-                                       else if (!uri.contains("/"))
-                                               u = new File(nodeBaseDir, uri).getCanonicalFile()
-                                                               .toURI();
-                                       else
-                                               throw new CmsException("Cannot interpret " + uri
-                                                               + " as an uri");
-                               } else if (u.getScheme().equals("file")) {
-                                       u = new File(u).getCanonicalFile().toURI();
-                               }
-                       } catch (Exception e) {
-                               throw new CmsException(
-                                               "Cannot interpret " + uri + " as an uri", e);
-                       }
-                       Dictionary<String, ?> properties = UserAdminConf.uriAsProperties(u
-                                       .toString());
-                       UserDirectory businessRoles;
-                       if (u.getScheme().startsWith("ldap")) {
-                               businessRoles = new LdapUserAdmin(properties);
-                       } else {
-                               businessRoles = new LdifUserAdmin(properties);
-                       }
-                       businessRoles.init();
-                       addUserAdmin(businessRoles.getBaseDn(), (UserAdmin) businessRoles);
-                       if (log.isDebugEnabled())
-                               log.debug("User directory " + businessRoles.getBaseDn() + " ["
-                                               + u.getScheme() + "] enabled.");
+       public NodeUserAdmin(TransactionManager transactionManager,
+                       Repository repository) {
+               this.repository = repository;
+               try {
+                       this.adminSession = this.repository.login();
+               } catch (RepositoryException e) {
+                       throw new CmsException("Cannot log-in", e);
                }
 
-               // Node roles
-               String nodeRolesUri = KernelUtils
-                               .getFrameworkProp(KernelConstants.ROLES_URI);
-               String baseNodeRoleDn = AuthConstants.ROLES_BASEDN;
-               if (nodeRolesUri == null) {
-                       File nodeRolesFile = new File(nodeBaseDir, baseNodeRoleDn + ".ldif");
-                       if (!nodeRolesFile.exists())
-                               try {
-                                       FileUtils.copyInputStreamToFile(getClass()
-                                                       .getResourceAsStream("demo.ldif"), nodeRolesFile);
-                               } catch (IOException e) {
-                                       throw new CmsException("Cannot copy demo resource", e);
-                               }
-                       nodeRolesUri = nodeRolesFile.toURI().toString();
-               }
+               // DAOs
+               File nodeBaseDir = new File(getOsgiInstanceDir(), "node");
+               nodeBaseDir.mkdirs();
+               String userAdminUri = getFrameworkProp(KernelConstants.USERADMIN_URIS);
+               initUserAdmins(userAdminUri, nodeBaseDir);
+               String nodeRolesUri = getFrameworkProp(KernelConstants.ROLES_URI);
+               initNodeRoles(nodeRolesUri, nodeBaseDir);
 
-               Dictionary<String, ?> nodeRolesProperties = UserAdminConf
-                               .uriAsProperties(nodeRolesUri);
-               if (!nodeRolesProperties.get(UserAdminConf.baseDn.property()).equals(
-                               baseNodeRoleDn)) {
-                       throw new CmsException("Invalid base dn for node roles");
-                       // TODO deal with "mounted" roles with a different baseDN
-               }
-               UserDirectory nodeRoles;
-               if (nodeRolesUri.startsWith("ldap")) {
-                       nodeRoles = new LdapUserAdmin(nodeRolesProperties);
-               } else {
-                       nodeRoles = new LdifUserAdmin(nodeRolesProperties);
+               // Transaction manager
+               ((UserDirectory) nodeRoles).setTransactionManager(transactionManager);
+               for (UserAdmin userAdmin : userAdmins.values()) {
+                       if (userAdmin instanceof UserDirectory)
+                               ((UserDirectory) userAdmin)
+                                               .setTransactionManager(transactionManager);
                }
-               nodeRoles.setExternalRoles(this);
-               nodeRoles.init();
-               addUserAdmin(baseNodeRoleDn, (UserAdmin) nodeRoles);
-               if (log.isTraceEnabled())
-                       log.trace("Node roles enabled.");
 
                // JCR
                initJcr(adminSession);
@@ -257,11 +196,6 @@ public class NodeUserAdmin implements UserAdmin {
        // USER ADMIN AGGREGATOR
        //
        public synchronized void addUserAdmin(String baseDn, UserAdmin userAdmin) {
-               if (baseDn.equals(AuthConstants.ROLES_BASEDN)) {
-                       nodeRoles = userAdmin;
-                       return;
-               }
-
                if (userAdmins.containsKey(baseDn))
                        throw new UserDirectoryException(
                                        "There is already a user admin for " + baseDn);
@@ -273,22 +207,6 @@ public class NodeUserAdmin implements UserAdmin {
                }
        }
 
-       public synchronized void removeUserAdmin(String baseDn) {
-               if (baseDn.equals(AuthConstants.ROLES_BASEDN))
-                       throw new UserDirectoryException("Node roles cannot be removed.");
-               LdapName base;
-               try {
-                       base = new LdapName(baseDn);
-               } catch (InvalidNameException e) {
-                       throw new UserDirectoryException("Badly formatted base DN "
-                                       + baseDn, e);
-               }
-               if (!userAdmins.containsKey(base))
-                       throw new UserDirectoryException("There is no user admin for "
-                                       + base);
-               userAdmins.remove(base);
-       }
-
        private UserAdmin findUserAdmin(String name) {
                try {
                        return findUserAdmin(new LdapName(name));
@@ -325,6 +243,106 @@ public class NodeUserAdmin implements UserAdmin {
                }
        }
 
+       private void initUserAdmins(String userAdminUri, File nodeBaseDir) {
+               if (userAdminUri == null) {
+                       String demoBaseDn = "dc=example,dc=com";
+                       File businessRolesFile = new File(nodeBaseDir, demoBaseDn + ".ldif");
+                       if (!businessRolesFile.exists())
+                               try {
+                                       FileUtils.copyInputStreamToFile(getClass()
+                                                       .getResourceAsStream(demoBaseDn + ".ldif"),
+                                                       businessRolesFile);
+                               } catch (IOException e) {
+                                       throw new CmsException("Cannot copy demo resource", e);
+                               }
+                       userAdminUri = businessRolesFile.toURI().toString();
+               }
+               String[] uris = userAdminUri.split(" ");
+               for (String uri : uris) {
+                       URI u;
+                       try {
+                               u = new URI(uri);
+                               if (u.getPath() == null)
+                                       throw new CmsException("URI " + uri
+                                                       + " must have a path in order to determine base DN");
+                               if (u.getScheme() == null) {
+                                       if (uri.startsWith("/") || uri.startsWith("./")
+                                                       || uri.startsWith("../"))
+                                               u = new File(uri).getCanonicalFile().toURI();
+                                       else if (!uri.contains("/"))
+                                               u = new File(nodeBaseDir, uri).getCanonicalFile()
+                                                               .toURI();
+                                       else
+                                               throw new CmsException("Cannot interpret " + uri
+                                                               + " as an uri");
+                               } else if (u.getScheme().equals("file")) {
+                                       u = new File(u).getCanonicalFile().toURI();
+                               }
+                       } catch (Exception e) {
+                               throw new CmsException(
+                                               "Cannot interpret " + uri + " as an uri", e);
+                       }
+                       Dictionary<String, ?> properties = UserAdminConf.uriAsProperties(u
+                                       .toString());
+                       UserDirectory businessRoles;
+                       if (u.getScheme().startsWith("ldap")) {
+                               businessRoles = new LdapUserAdmin(properties);
+                       } else {
+                               businessRoles = new LdifUserAdmin(properties);
+                       }
+                       businessRoles.init();
+                       String baseDn = businessRoles.getBaseDn();
+                       if (userAdmins.containsKey(baseDn))
+                               throw new UserDirectoryException(
+                                               "There is already a user admin for " + baseDn);
+                       try {
+                               userAdmins.put(new LdapName(baseDn), (UserAdmin) businessRoles);
+                       } catch (InvalidNameException e) {
+                               throw new UserDirectoryException("Badly formatted base DN "
+                                               + baseDn, e);
+                       }
+                       addUserAdmin(businessRoles.getBaseDn(), (UserAdmin) businessRoles);
+                       if (log.isDebugEnabled())
+                               log.debug("User directory " + businessRoles.getBaseDn() + " ["
+                                               + u.getScheme() + "] enabled.");
+               }
+
+       }
+
+       private void initNodeRoles(String nodeRolesUri, File nodeBaseDir) {
+               String baseNodeRoleDn = AuthConstants.ROLES_BASEDN;
+               if (nodeRolesUri == null) {
+                       File nodeRolesFile = new File(nodeBaseDir, baseNodeRoleDn + ".ldif");
+                       if (!nodeRolesFile.exists())
+                               try {
+                                       FileUtils.copyInputStreamToFile(getClass()
+                                                       .getResourceAsStream("demo.ldif"), nodeRolesFile);
+                               } catch (IOException e) {
+                                       throw new CmsException("Cannot copy demo resource", e);
+                               }
+                       nodeRolesUri = nodeRolesFile.toURI().toString();
+               }
+
+               Dictionary<String, ?> nodeRolesProperties = UserAdminConf
+                               .uriAsProperties(nodeRolesUri);
+               if (!nodeRolesProperties.get(UserAdminConf.baseDn.property()).equals(
+                               baseNodeRoleDn)) {
+                       throw new CmsException("Invalid base dn for node roles");
+                       // TODO deal with "mounted" roles with a different baseDN
+               }
+               if (nodeRolesUri.startsWith("ldap")) {
+                       nodeRoles = new LdapUserAdmin(nodeRolesProperties);
+               } else {
+                       nodeRoles = new LdifUserAdmin(nodeRolesProperties);
+               }
+               ((UserDirectory) nodeRoles).setExternalRoles(this);
+               ((UserDirectory) nodeRoles).init();
+               addUserAdmin(baseNodeRoleDn, (UserAdmin) nodeRoles);
+               if (log.isTraceEnabled())
+                       log.trace("Node roles enabled.");
+
+       }
+
        /*
         * JCR
         */
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java
new file mode 100644 (file)
index 0000000..d8b39cd
--- /dev/null
@@ -0,0 +1,95 @@
+package org.argeo.cms.internal.kernel;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.ArgeoException;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+/**
+ * Utilities around private keys and certificate, mostly wrapping BouncyCastle
+ * implementations.
+ */
+public class PkiUtils {
+       private final static String SECURITY_PROVIDER;
+       static {
+               // Security.addProvider(new BouncyCastleProvider());
+               SECURITY_PROVIDER = "BC";
+       }
+
+       public static X509Certificate generateSelfSignedCertificate(
+                       KeyStore keyStore, X500Principal x500Principal, char[] keyPassword) {
+               try {
+                       KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA",
+                                       SECURITY_PROVIDER);
+                       kpGen.initialize(1024, new SecureRandom());
+                       KeyPair pair = kpGen.generateKeyPair();
+                       Date notBefore = new Date(System.currentTimeMillis() - 10000);
+                       Date notAfter = new Date(
+                                       System.currentTimeMillis() + 24L * 3600 * 1000);
+                       BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
+                       X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
+                                       x500Principal, serial, notBefore, notAfter, x500Principal,
+                                       pair.getPublic());
+                       ContentSigner sigGen = new JcaContentSignerBuilder(
+                                       "SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER)
+                                       .build(pair.getPrivate());
+                       X509Certificate cert = new JcaX509CertificateConverter()
+                                       .setProvider(SECURITY_PROVIDER).getCertificate(
+                                                       certGen.build(sigGen));
+                       cert.checkValidity(new Date());
+                       cert.verify(cert.getPublicKey());
+
+                       keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(),
+                                       keyPassword, new Certificate[] { cert });
+                       return cert;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot generate self-signed certificate",
+                                       e);
+               }
+       }
+
+       public static KeyStore getKeyStore(File keyStoreFile,
+                       char[] keyStorePassword) {
+               try {
+                       KeyStore store = KeyStore.getInstance("PKCS12", SECURITY_PROVIDER);
+                       if (keyStoreFile.exists()) {
+                               try (FileInputStream fis = new FileInputStream(keyStoreFile)) {
+                                       store.load(fis, keyStorePassword);
+                               }
+                       } else {
+                               store.load(null);
+                       }
+                       return store;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot load keystore " + keyStoreFile, e);
+               }
+       }
+
+       public static void saveKeyStore(File keyStoreFile, char[] keyStorePassword,
+                       KeyStore keyStore) {
+               try {
+                       try (FileOutputStream fis = new FileOutputStream(keyStoreFile)) {
+                               keyStore.store(fis, keyStorePassword);
+                       }
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot save keystore " + keyStoreFile, e);
+               }
+       }
+
+}
index 4f647cf8a667a38f8f4f2a06a8bc2237310a2862..90c68bd21dcaa5254478557c58e59a76cb8b1575 100644 (file)
@@ -21,7 +21,7 @@ KERNEL {
 };
 
 KEYRING {
-    org.argeo.security.crypto.KeyringLoginModule required;
+    org.argeo.util.security.KeyringLoginModule required;
 };
 
 SINGLE_USER {
index edb57268119d9edd7a4e6b1474a4e6274c0bb571..30d9b72911b3e7151968802624f669841cb6d5b5 100644 (file)
@@ -122,7 +122,7 @@ public class SimpleTransactionManager implements TransactionManager,
                return transaction;
        }
 
-       public TransactionSynchronizationRegistry getTransactionSynchronizationRegistry() {
+       public TransactionSynchronizationRegistry getTsr() {
                return syncRegistry;
        }
 
index d8cd171e2d0441e1bcb0f62352314b3ea6cb95d2..439f48af195c082193796f7a29ee276375238a1f 100644 (file)
@@ -29,14 +29,17 @@ import org.eclipse.swt.widgets.Widget;
 
 /** Static utilities for the CMS framework. */
 public class CmsUtils implements CmsConstants {
-       /** The CMS view related to this display. */
+       /**
+        * The CMS view related to this display, or null if none is available from
+        * this call.
+        */
        public static CmsView getCmsView() {
                Display display = Display.getCurrent();
                if (display == null)
-                       throw new CmsException("No display available");
+                       return null;
                CmsView cmsView = (CmsView) display.getData(CmsView.KEY);
                if (cmsView == null)
-                       throw new CmsException("No CMS view available");
+                       return null;
                return cmsView;
        }
 
index fc651e65b3290f471dd7b3a7c7f5a285841088b1..801a2611bf9b861bebfdc7021bbe738acfffd7f9 100644 (file)
@@ -24,7 +24,7 @@ import org.argeo.cms.CmsMsg;
 import org.argeo.cms.CmsStyles;
 import org.argeo.cms.CmsView;
 import org.argeo.cms.auth.AuthConstants;
-import org.argeo.security.SecurityUtils;
+import org.argeo.cms.auth.CurrentUser;
 import org.eclipse.rap.rwt.RWT;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.MouseAdapter;
@@ -50,7 +50,7 @@ public class UserMenu extends Shell implements CmsStyles, CallbackHandler {
                super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
                setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU);
 
-               String username = SecurityUtils.getUsername(CmsUtils.getCmsView().getSubject());
+               String username = CurrentUser.getUsername(CmsUtils.getCmsView().getSubject());
                if (username.equalsIgnoreCase(AuthConstants.ROLE_ANONYMOUS)) {
                        username = null;
                        anonymousUi();
index f4c5f006305deeaff642958046c615239a169cdf..704f90980af02273efb39f43d57af113296cff18 100644 (file)
@@ -5,7 +5,7 @@ import javax.jcr.Node;
 import org.argeo.cms.CmsMsg;
 import org.argeo.cms.CmsStyles;
 import org.argeo.cms.auth.AuthConstants;
-import org.argeo.security.SecurityUtils;
+import org.argeo.cms.auth.CurrentUser;
 import org.eclipse.swt.events.DisposeEvent;
 import org.eclipse.swt.events.DisposeListener;
 import org.eclipse.swt.events.MouseEvent;
@@ -23,12 +23,12 @@ public class UserMenuLink extends MenuLink {
 
        @Override
        public Control createUi(Composite parent, Node context) {
-               String username = SecurityUtils.getUsername(CmsUtils.getCmsView()
+               String username = CurrentUser.getUsername(CmsUtils.getCmsView()
                                .getSubject());
                if (username.equalsIgnoreCase(AuthConstants.ROLE_ANONYMOUS))
                        setLabel(CmsMsg.login.lead());
                else {
-                       setLabel(SecurityUtils.getDisplayName(CmsUtils.getCmsView()
+                       setLabel(CurrentUser.getDisplayName(CmsUtils.getCmsView()
                                        .getSubject()));
                }
                Label link = (Label) ((Composite) super.createUi(parent, context))
index 255462be41f70ba029f698b1126817dd277619ba..38f73329130a53ed4a8d657f116c73c995fed2c7 100644 (file)
@@ -18,6 +18,6 @@
                filter="(argeo.jcr.repository.alias=node)" />\r
        <reference id="repositoryFactory" interface="javax.jcr.RepositoryFactory" />\r
 \r
-       <reference id="keyring" interface="org.argeo.security.crypto.CryptoKeyring" />\r
+       <reference id="keyring" interface="org.argeo.util.security.CryptoKeyring" />\r
 \r
 </beans:beans>
\ No newline at end of file
diff --git a/org.argeo.security.core/META-INF/spring/logger.xml b/org.argeo.security.core/META-INF/spring/logger.xml
deleted file mode 100644 (file)
index 02f48c8..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
-       xsi:schemaLocation="http://www.springframework.org/schema/beans
-        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
-
-       <!-- Log4j appender singleton -->
-       <bean id="secureLogger" class="org.argeo.security.log4j.SecureLogger"
-               init-method="init" destroy-method="destroy">
-<!--           <property name="configuration"> -->
-<!--                   <value><![CDATA[ -->
-<!-- log4j.rootLogger=WARN, console -->
-
-<!-- ## Levels -->
-<!-- log4j.logger.org.argeo=DEBUG -->
-<!-- log4j.logger.org.argeo.jackrabbit.remote.ExtendedDispatcherServlet=ERROR -->
-<!-- log4j.logger.org.springframework.web.servlet.PageNotFound=ERROR -->
-<!-- log4j.logger.org.argeo.server.webextender.TomcatDeployer=WARN -->
-
-<!-- log4j.logger.org.apache.coyote=INFO -->
-<!-- log4j.logger.org.apache.catalina.core.ContainerBase=INFO -->
-<!-- log4j.logger.org.apache.directory.server=ERROR -->
-<!-- log4j.logger.org.apache.jackrabbit.core.query.lucene=ERROR -->
-<!-- log4j.logger.org.apache.jackrabbit.core.config.ConfigurationErrorHandler=ERROR -->
-<!-- log4j.logger.org.apache.jackrabbit.core.util.db.DbUtility=FATAL -->
-<!-- log4j.logger.org.apache.activemq=INFO -->
-<!-- log4j.logger.org.apache.activemq.ActiveMQMessageConsumer=INFO -->
-<!-- log4j.logger.org.apache.activemq.ActiveMQMessageProducer=INFO -->
-
-<!-- log4j.appender.console=org.apache.log4j.ConsoleAppender -->
-<!-- log4j.appender.console.layout=org.apache.log4j.PatternLayout -->
-<!-- log4j.appender.console.layout.ConversionPattern=%d{yyMMdd HH:mm:ss} %-5p %m [%t] %c%n -->
-<!--                   ]]></value> -->
-<!--           </property> -->
-       </bean>
-</beans>
diff --git a/org.argeo.security.core/META-INF/spring/osgi.xml b/org.argeo.security.core/META-INF/spring/osgi.xml
deleted file mode 100644 (file)
index 5e1433c..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>\r
-<beans:beans xmlns="http://www.springframework.org/schema/osgi"\r
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"\r
-       xmlns:osgi="http://www.springframework.org/schema/osgi"\r
-       xsi:schemaLocation="http://www.springframework.org/schema/osgi  \r
-       http://www.springframework.org/schema/osgi/spring-osgi-1.1.xsd\r
-       http://www.springframework.org/schema/beans   \r
-       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">\r
-\r
-       <!-- SERVICES -->\r
-       <service ref="secureLogger" interface="org.argeo.ArgeoLogger" />\r
-</beans:beans>
\ No newline at end of file
index d3d44a195dba084a13e566c97dcdcd54f2ad6449..d31b9e77a034b868ffe24d11e2c7489b0210b4f5 100644 (file)
@@ -1,4 +1,3 @@
-Bundle-ActivationPolicy: lazy
 Import-Package:org.bouncycastle.*;resolution:=optional,\
 javax.jcr.security,\
 org.apache.commons.codec,\
diff --git a/org.argeo.security.core/ext/test/org/argeo/security/crypto/PasswordBasedEncryptionTest.java b/org.argeo.security.core/ext/test/org/argeo/security/crypto/PasswordBasedEncryptionTest.java
deleted file mode 100644 (file)
index 6973f57..0000000
+++ /dev/null
@@ -1,144 +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.security.crypto;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-
-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.PBEParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import javax.xml.bind.DatatypeConverter;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.StreamUtils;
-import org.argeo.security.crypto.PasswordBasedEncryption;
-
-public class PasswordBasedEncryptionTest extends TestCase {
-       private final static Log log = LogFactory
-                       .getLog(PasswordBasedEncryptionTest.class);
-
-       public void testEncryptDecrypt() {
-               final String password = "test long password since they are safer";
-               PasswordBasedEncryption pbeEnc = new PasswordBasedEncryption(
-                               password.toCharArray());
-               String message = "Hello World!";
-               log.info("Password:\t'" + password + "'");
-               log.info("Message:\t'" + message + "'");
-               byte[] encrypted = pbeEnc.encryptString(message);
-               log.info("Encrypted:\t'"
-                               + DatatypeConverter.printBase64Binary(encrypted) + "'");
-               PasswordBasedEncryption pbeDec = new PasswordBasedEncryption(
-                               password.toCharArray());
-               InputStream in = null;
-               in = new ByteArrayInputStream(encrypted);
-               String decrypted = pbeDec.decryptAsString(in);
-               log.info("Decrypted:\t'" + decrypted + "'");
-               StreamUtils.closeQuietly(in);
-               assertEquals(message, decrypted);
-       }
-
-       public void testPBEWithMD5AndDES() throws Exception {
-               String password = "test";
-               String message = "Hello World!";
-
-               byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c,
-                               (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 };
-
-               int count = 1024;
-
-               String cipherAlgorithm = "PBEWithMD5AndDES";
-               String secretKeyAlgorithm = "PBEWithMD5AndDES";
-               SecretKeyFactory keyFac = SecretKeyFactory
-                               .getInstance(secretKeyAlgorithm);
-               PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
-               PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count);
-               SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
-               Cipher ecipher = Cipher.getInstance(cipherAlgorithm);
-               ecipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
-               Cipher dcipher = Cipher.getInstance(cipherAlgorithm);
-               dcipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec);
-
-               byte[] encrypted = ecipher.doFinal(message.getBytes());
-               byte[] decrypted = dcipher.doFinal(encrypted);
-               assertEquals(message, new String(decrypted));
-
-       }
-
-       public void testPBEWithSHA1AndAES() throws Exception {
-               String password = "test";
-               String message = "Hello World!";
-
-               byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c,
-                               (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 };
-               byte[] iv = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c,
-                               (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99,
-                               (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c,
-                               (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 };
-
-               int count = 1024;
-               // int keyLength = 256;
-               int keyLength = 128;
-
-               String cipherAlgorithm = "AES/CBC/PKCS5Padding";
-               String secretKeyAlgorithm = "PBKDF2WithHmacSHA1";
-               SecretKeyFactory keyFac = SecretKeyFactory
-                               .getInstance(secretKeyAlgorithm);
-               PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt,
-                               count, keyLength);
-               SecretKey tmp = keyFac.generateSecret(pbeKeySpec);
-               SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
-               Cipher ecipher = Cipher.getInstance(cipherAlgorithm);
-               ecipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv));
-
-               // decrypt
-               keyFac = SecretKeyFactory.getInstance(secretKeyAlgorithm);
-               pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, count,
-                               keyLength);
-               tmp = keyFac.generateSecret(pbeKeySpec);
-               secret = new SecretKeySpec(tmp.getEncoded(), "AES");
-               // AlgorithmParameters params = ecipher.getParameters();
-               // byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
-               Cipher dcipher = Cipher.getInstance(cipherAlgorithm);
-               dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
-
-               byte[] encrypted = ecipher.doFinal(message.getBytes());
-               byte[] decrypted = dcipher.doFinal(encrypted);
-               assertEquals(message, new String(decrypted));
-
-               ByteArrayOutputStream out = new ByteArrayOutputStream();
-               CipherOutputStream cipherOut = new CipherOutputStream(out, ecipher);
-               cipherOut.write(message.getBytes());
-               StreamUtils.closeQuietly(cipherOut);
-               byte[] enc = out.toByteArray();
-
-               ByteArrayInputStream in = new ByteArrayInputStream(enc);
-               CipherInputStream cipherIn = new CipherInputStream(in, dcipher);
-               ByteArrayOutputStream dec = new ByteArrayOutputStream();
-               StreamUtils.copy(cipherIn, dec);
-               assertEquals(message, new String(dec.toByteArray()));
-       }
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java b/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java
deleted file mode 100644 (file)
index e1f7899..0000000
+++ /dev/null
@@ -1,86 +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.security;
-
-import java.security.AccessController;
-import java.security.Principal;
-import java.security.acl.Group;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-import javax.security.auth.x500.X500Principal;
-
-import org.argeo.ArgeoException;
-import org.osgi.service.useradmin.Authorization;
-
-/** Static utilities */
-public final class SecurityUtils {
-       private SecurityUtils() {
-       }
-
-       /** Whether the current thread has the admin role */
-       public static boolean hasCurrentThreadAuthority(String authority) {
-               return roles().contains(authority);
-       }
-
-       /**
-        * @return the authenticated username or null if not authenticated /
-        *         anonymous
-        */
-       public static String getCurrentThreadUsername() {
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               if (subject == null)
-                       return null;
-               return getUsername(subject);
-       }
-
-       public final static String getUsername(Subject subject) {
-               // Subject subject = Subject.getSubject(AccessController.getContext());
-               // if (subject == null)
-               // return null;
-               if (subject.getPrincipals(X500Principal.class).size() != 1)
-                       return null;
-               Principal principal = subject.getPrincipals(X500Principal.class)
-                               .iterator().next();
-               return principal.getName();
-
-       }
-
-       public final static String getDisplayName(Subject subject) {
-               return getAuthorization(subject).toString();
-       }
-
-       public final static Authorization getAuthorization(Subject subject) {
-               return subject.getPrivateCredentials(Authorization.class).iterator()
-                               .next();
-       }
-
-       public final static Set<String> roles() {
-               Set<String> roles = Collections.synchronizedSet(new HashSet<String>());
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               if (subject == null)
-                       throw new ArgeoException("Not authenticated.");
-               X500Principal userPrincipal = subject
-                               .getPrincipals(X500Principal.class).iterator().next();
-               roles.add(userPrincipal.getName());
-               for (Principal group : subject.getPrincipals(Group.class)) {
-                       roles.add(group.getName());
-               }
-               return roles;
-       }
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/crypto/AbstractKeyring.java b/org.argeo.security.core/src/org/argeo/security/crypto/AbstractKeyring.java
deleted file mode 100644 (file)
index daa1ebd..0000000
+++ /dev/null
@@ -1,282 +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.security.crypto;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.CharArrayWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-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;
-import java.util.Iterator;
-
-import javax.crypto.SecretKey;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.TextOutputCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.ArgeoException;
-import org.argeo.StreamUtils;
-import org.argeo.util.security.Keyring;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-
-/** username / password based keyring. TODO internationalize */
-public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
-       static {
-               Security.addProvider(new BouncyCastleProvider());
-       }
-
-       public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING";
-
-       private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT;
-       private CallbackHandler defaultCallbackHandler;
-
-       private String charset = "UTF-8";
-
-       /**
-        * Default provider is bouncy castle, in order to have consistent behaviour
-        * across implementations
-        */
-       private String securityProviderName = "BC";
-
-       /**
-        * Whether the keyring has already been created in the past with a master
-        * password
-        */
-       protected abstract Boolean isSetup();
-
-       /**
-        * Setup the keyring persistently, {@link #isSetup()} must return true
-        * afterwards
-        */
-       protected abstract void setup(char[] password);
-
-       /** Populates the key spec callback */
-       protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback);
-
-       protected abstract void encrypt(String path, InputStream unencrypted);
-
-       protected abstract InputStream decrypt(String path);
-
-       /** Triggers lazy initialization */
-       protected SecretKey getSecretKey() {
-               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();
-                       try {
-                               LoginContext loginContext = new LoginContext(loginContextName,
-                                               subject, callbackHandler);
-                               loginContext.login();
-                               // FIXME will login even if password is wrong
-                               iterator = subject.getPrivateCredentials(SecretKey.class)
-                                               .iterator();
-                               return iterator.next();
-                       } catch (LoginException e) {
-                               throw new ArgeoException("Keyring login failed", e);
-                       }
-
-               } else {
-                       SecretKey secretKey = iterator.next();
-                       if (iterator.hasNext())
-                               throw new ArgeoException(
-                                               "More than one secret key in private credentials");
-                       return secretKey;
-               }
-       }
-
-       public InputStream getAsStream(String path) {
-               return decrypt(path);
-       }
-
-       public void set(String path, InputStream in) {
-               encrypt(path, in);
-       }
-
-       public char[] getAsChars(String path) {
-               InputStream in = getAsStream(path);
-               CharArrayWriter writer = null;
-               Reader reader = null;
-               try {
-                       writer = new CharArrayWriter();
-                       reader = new InputStreamReader(in, charset);
-                       StreamUtils.copy(reader, writer);
-                       return writer.toCharArray();
-               } catch (IOException e) {
-                       throw new ArgeoException("Cannot decrypt to char array", e);
-               } finally {
-                       StreamUtils.closeQuietly(reader);
-                       StreamUtils.closeQuietly(in);
-                       StreamUtils.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);
-                       writer.write(arr);
-                       writer.flush();
-                       in = new ByteArrayInputStream(out.toByteArray());
-                       set(path, in);
-               } catch (IOException e) {
-                       throw new ArgeoException("Cannot encrypt to char array", e);
-               } finally {
-                       StreamUtils.closeQuietly(writer);
-                       StreamUtils.closeQuietly(out);
-                       StreamUtils.closeQuietly(in);
-               }
-       }
-
-       protected Provider getSecurityProvider() {
-               return Security.getProvider(securityProviderName);
-       }
-
-       public void setLoginContextName(String loginContextName) {
-               this.loginContextName = loginContextName;
-       }
-
-       public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) {
-               this.defaultCallbackHandler = defaultCallbackHandler;
-       }
-
-       public void setCharset(String charset) {
-               this.charset = charset;
-       }
-
-       public void setSecurityProviderName(String securityProviderName) {
-               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 ArgeoException("Cannot hash", e);
-               } finally {
-                       StreamUtils.closeQuietly(out);
-                       StreamUtils.closeQuietly(writer);
-               }
-
-       }
-
-       /**
-        * Convenience method using the underlying callback to ask for a password
-        * (typically used when the password is not saved in the keyring)
-        */
-       protected char[] ask() {
-               PasswordCallback passwordCb = new PasswordCallback("Password", false);
-               Callback[] dialogCbs = new Callback[] { passwordCb };
-               try {
-                       defaultCallbackHandler.handle(dialogCbs);
-                       char[] password = passwordCb.getPassword();
-                       return password;
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot ask for a password", e);
-               }
-
-       }
-
-       class KeyringCallbackHandler implements CallbackHandler {
-               public void handle(Callback[] callbacks) throws IOException,
-                               UnsupportedCallbackException {
-                       // checks
-                       if (callbacks.length != 2)
-                               throw new IllegalArgumentException(
-                                               "Keyring required 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];
-                       PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
-
-                       if (isSetup()) {
-                               Callback[] dialogCbs = new Callback[] { passwordCb };
-                               defaultCallbackHandler.handle(dialogCbs);
-                       } else {// setup keyring
-                               TextOutputCallback textCb1 = new TextOutputCallback(
-                                               TextOutputCallback.INFORMATION,
-                                               "Enter a master password which will protect your private data");
-                               TextOutputCallback textCb2 = new TextOutputCallback(
-                                               TextOutputCallback.INFORMATION,
-                                               "(for example your credentials to third-party services)");
-                               TextOutputCallback textCb3 = new TextOutputCallback(
-                                               TextOutputCallback.INFORMATION,
-                                               "Don't forget this password since the data cannot be read without it");
-                               PasswordCallback confirmPasswordCb = new PasswordCallback(
-                                               "Confirm password", false);
-                               // first try
-                               Callback[] dialogCbs = new Callback[] { textCb1, textCb2,
-                                               textCb3, passwordCb, confirmPasswordCb };
-                               defaultCallbackHandler.handle(dialogCbs);
-
-                               // if passwords different, retry (except if cancelled)
-                               while (passwordCb.getPassword() != null
-                                               && !Arrays.equals(passwordCb.getPassword(),
-                                                               confirmPasswordCb.getPassword())) {
-                                       TextOutputCallback textCb = new TextOutputCallback(
-                                                       TextOutputCallback.ERROR,
-                                                       "The passwords do not match");
-                                       dialogCbs = new Callback[] { textCb, passwordCb,
-                                                       confirmPasswordCb };
-                                       defaultCallbackHandler.handle(dialogCbs);
-                               }
-
-                               if (passwordCb.getPassword() != null) {// not cancelled
-                                       setup(passwordCb.getPassword());
-                               }
-                       }
-
-                       if (passwordCb.getPassword() != null)
-                               handleKeySpecCallback(pbeCb);
-               }
-
-       }
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/crypto/CryptoKeyring.java b/org.argeo.security.core/src/org/argeo/security/crypto/CryptoKeyring.java
deleted file mode 100644 (file)
index d25eccd..0000000
+++ /dev/null
@@ -1,27 +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.security.crypto;
-
-import org.argeo.util.security.Keyring;
-
-/**
- * Advanced keyring based on cryptography that can easily be centralized and
- * coordinated with {@link KeyringLoginModule} (since they ar ein the same
- * package)
- */
-public interface CryptoKeyring extends Keyring {
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/crypto/KeyringLoginModule.java b/org.argeo.security.core/src/org/argeo/security/crypto/KeyringLoginModule.java
deleted file mode 100644 (file)
index 34b7d40..0000000
+++ /dev/null
@@ -1,102 +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.security.crypto;
-
-import java.security.AccessController;
-import java.util.Map;
-import java.util.Set;
-
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-
-/** Adds a secret key to the private credentials */
-public class KeyringLoginModule implements LoginModule {
-       private Subject subject;
-       private CallbackHandler callbackHandler;
-       private SecretKey secretKey;
-
-       public void initialize(Subject subject, CallbackHandler callbackHandler,
-                       Map<String, ?> sharedState, Map<String, ?> options) {
-               this.subject = subject;
-               if (subject == null) {
-                       subject = Subject.getSubject(AccessController.getContext());
-               }
-               this.callbackHandler = callbackHandler;
-       }
-
-       public boolean login() throws LoginException {
-               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 };
-               try {
-                       callbackHandler.handle(callbacks);
-                       char[] password = pc.getPassword();
-
-                       SecretKeyFactory keyFac = SecretKeyFactory.getInstance(pbeCb
-                                       .getSecretKeyFactory());
-                       PBEKeySpec keySpec;
-                       if (pbeCb.getKeyLength() != null)
-                               keySpec = new PBEKeySpec(password, pbeCb.getSalt(),
-                                               pbeCb.getIterationCount(), pbeCb.getKeyLength());
-                       else
-                               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);
-                       } else {
-                               secretKey = keyFac.generateSecret(keySpec);
-                       }
-               } catch (Exception e) {
-                       LoginException le = new LoginException("Cannot login keyring");
-                       le.initCause(e);
-                       throw le;
-               }
-               return true;
-       }
-
-       public boolean commit() throws LoginException {
-               if (secretKey != null)
-                       subject.getPrivateCredentials().add(secretKey);
-               return true;
-       }
-
-       public boolean abort() throws LoginException {
-               return true;
-       }
-
-       public boolean logout() throws LoginException {
-               Set<PasswordBasedEncryption> pbes = subject
-                               .getPrivateCredentials(PasswordBasedEncryption.class);
-               pbes.clear();
-               return true;
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/crypto/PBEKeySpecCallback.java b/org.argeo.security.core/src/org/argeo/security/crypto/PBEKeySpecCallback.java
deleted file mode 100644 (file)
index e964366..0000000
+++ /dev/null
@@ -1,78 +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.security.crypto;
-
-import javax.crypto.spec.PBEKeySpec;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.PasswordCallback;
-
-/**
- * All information required to set up a {@link PBEKeySpec} bar the password
- * itself (use a {@link PasswordCallback})
- */
-public class PBEKeySpecCallback implements Callback {
-       private String secretKeyFactory;
-       private byte[] salt;
-       private Integer iterationCount;
-       /** Can be null for some algorithms */
-       private Integer keyLength;
-       /** Can be null, will trigger secret key encryption if not */
-       private String secretKeyEncryption;
-
-       private String encryptedPasswordHashCipher;
-       private byte[] encryptedPasswordHash;
-
-       public void set(String secretKeyFactory, byte[] salt,
-                       Integer iterationCount, Integer keyLength,
-                       String secretKeyEncryption) {
-               this.secretKeyFactory = secretKeyFactory;
-               this.salt = salt;
-               this.iterationCount = iterationCount;
-               this.keyLength = keyLength;
-               this.secretKeyEncryption = secretKeyEncryption;
-//             this.encryptedPasswordHashCipher = encryptedPasswordHashCipher;
-//             this.encryptedPasswordHash = encryptedPasswordHash;
-       }
-
-       public String getSecretKeyFactory() {
-               return secretKeyFactory;
-       }
-
-       public byte[] getSalt() {
-               return salt;
-       }
-
-       public Integer getIterationCount() {
-               return iterationCount;
-       }
-
-       public Integer getKeyLength() {
-               return keyLength;
-       }
-
-       public String getSecretKeyEncryption() {
-               return secretKeyEncryption;
-       }
-
-       public String getEncryptedPasswordHashCipher() {
-               return encryptedPasswordHashCipher;
-       }
-
-       public byte[] getEncryptedPasswordHash() {
-               return encryptedPasswordHash;
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/crypto/PasswordBasedEncryption.java b/org.argeo.security.core/src/org/argeo/security/crypto/PasswordBasedEncryption.java
deleted file mode 100644 (file)
index aec25ac..0000000
+++ /dev/null
@@ -1,259 +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.security.crypto;
-
-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 java.security.Security;
-
-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.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.ArgeoException;
-import org.argeo.StreamUtils;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-
-/** Simple password based encryption / decryption */
-public class PasswordBasedEncryption {
-       private final static Log log = LogFactory
-                       .getLog(PasswordBasedEncryption.class);
-
-       static {
-               Security.addProvider(new BouncyCastleProvider());
-       }
-
-       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;
-
-       /**
-        * Default provider is bouncy castle, in order to have consistent behaviour
-        * across implementations
-        */
-       private String securityProviderName = "BC";
-
-       /**
-        * 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;
-                       log.warn("'" + e.getMessage() + "', will use " + secreteKeyLength
-                                       + " secrete key length instead of "
-                                       + previousSecreteKeyLength);
-                       try {
-                               initKeyAndCiphers(password, passwordSalt, initializationVector);
-                       } catch (Exception e1) {
-                               throw new ArgeoException(
-                                               "Cannot get secret key (with restricted length)", e1);
-                       }
-               } catch (Exception e) {
-                       throw new ArgeoException("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);
-               }
-               ecipher = Cipher.getInstance(getCipherName(), securityProviderName);
-               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 ArgeoException("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 ArgeoException("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 ArgeoException("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 ArgeoException("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;
-       }
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/crypto/PkiUtils.java b/org.argeo.security.core/src/org/argeo/security/crypto/PkiUtils.java
deleted file mode 100644 (file)
index f66d3f9..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.argeo.security.crypto;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.SecureRandom;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.argeo.ArgeoException;
-import org.bouncycastle.cert.X509v3CertificateBuilder;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
-import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
-import org.bouncycastle.operator.ContentSigner;
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
-
-/**
- * Utilities around private keys and certificate, mostly wrapping BouncyCastle
- * implementations.
- */
-public class PkiUtils {
-       private final static String SECURITY_PROVIDER;
-       static {
-               // Security.addProvider(new BouncyCastleProvider());
-               SECURITY_PROVIDER = "BC";
-       }
-
-       public static X509Certificate generateSelfSignedCertificate(
-                       KeyStore keyStore, X500Principal x500Principal, char[] keyPassword) {
-               try {
-                       KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA",
-                                       SECURITY_PROVIDER);
-                       kpGen.initialize(1024, new SecureRandom());
-                       KeyPair pair = kpGen.generateKeyPair();
-                       Date notBefore = new Date(System.currentTimeMillis() - 10000);
-                       Date notAfter = new Date(
-                                       System.currentTimeMillis() + 24L * 3600 * 1000);
-                       BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
-                       X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
-                                       x500Principal, serial, notBefore, notAfter, x500Principal,
-                                       pair.getPublic());
-                       ContentSigner sigGen = new JcaContentSignerBuilder(
-                                       "SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER)
-                                       .build(pair.getPrivate());
-                       X509Certificate cert = new JcaX509CertificateConverter()
-                                       .setProvider(SECURITY_PROVIDER).getCertificate(
-                                                       certGen.build(sigGen));
-                       cert.checkValidity(new Date());
-                       cert.verify(cert.getPublicKey());
-
-                       keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(),
-                                       keyPassword, new Certificate[] { cert });
-                       return cert;
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot generate self-signed certificate",
-                                       e);
-               }
-       }
-
-       public static KeyStore getKeyStore(File keyStoreFile,
-                       char[] keyStorePassword) {
-               try {
-                       KeyStore store = KeyStore.getInstance("PKCS12", SECURITY_PROVIDER);
-                       if (keyStoreFile.exists()) {
-                               try (FileInputStream fis = new FileInputStream(keyStoreFile)) {
-                                       store.load(fis, keyStorePassword);
-                               }
-                       } else {
-                               store.load(null);
-                       }
-                       return store;
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot load keystore " + keyStoreFile, e);
-               }
-       }
-
-       public static void saveKeyStore(File keyStoreFile, char[] keyStorePassword,
-                       KeyStore keyStore) {
-               try {
-                       try (FileOutputStream fis = new FileOutputStream(keyStoreFile)) {
-                               keyStore.store(fis, keyStorePassword);
-                       }
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot save keystore " + keyStoreFile, e);
-               }
-       }
-
-}
diff --git a/org.argeo.security.core/src/org/argeo/security/jcr/JcrKeyring.java b/org.argeo.security.core/src/org/argeo/security/jcr/JcrKeyring.java
deleted file mode 100644 (file)
index 1b9f244..0000000
+++ /dev/null
@@ -1,294 +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.security.jcr;
-
-import java.io.ByteArrayInputStream;
-import java.io.CharArrayReader;
-import java.io.InputStream;
-import java.io.Reader;
-import java.security.SecureRandom;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.ArgeoTypes;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.UserJcrUtils;
-import org.argeo.security.crypto.AbstractKeyring;
-import org.argeo.security.crypto.PBEKeySpecCallback;
-
-/** JCR based implementation of a keyring */
-public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
-       /**
-        * 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";
-       public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
-       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 String cipherName = DEFAULT_CIPHER_NAME;
-
-       private Session session;
-
-       /**
-        * 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;
-               }
-       };
-
-       @Override
-       protected Boolean isSetup() {
-               try {
-                       if (notYetSavedKeyring.get() != null)
-                               return true;
-
-                       Node userHome = UserJcrUtils.getUserHome(session);
-                       return userHome.hasNode(ARGEO_KEYRING);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot check whether keyring is setup", e);
-               }
-       }
-
-       @Override
-       protected void setup(char[] password) {
-               Binary binary = null;
-               InputStream in = null;
-               try {
-                       Node userHome = UserJcrUtils.getUserHome(session);
-                       if (userHome.hasNode(ARGEO_KEYRING))
-                               throw new ArgeoException("Keyring already setup");
-                       Node 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();
-                       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);
-                       keyring.setProperty(ARGEO_SALT, binary);
-
-                       Integer iterationCount = username.length() * iterationCountFactor;
-                       keyring.setProperty(ARGEO_ITERATION_COUNT, iterationCount);
-
-                       // 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_CIPHER, cipherName);
-
-                       // encrypted password hash
-                       // IOUtils.closeQuietly(in);
-                       // JcrUtils.closeQuietly(binary);
-                       // byte[] btPass = hash(password, salt, iterationCount);
-                       // in = new ByteArrayInputStream(btPass);
-                       // binary = session.getValueFactory().createBinary(in);
-                       // keyring.setProperty(ARGEO_PASSWORD, binary);
-
-                       notYetSavedKeyring.set(keyring);
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot setup keyring", e);
-               } finally {
-                       JcrUtils.closeQuietly(binary);
-                       IOUtils.closeQuietly(in);
-                       // JcrUtils.discardQuietly(session);
-               }
-       }
-
-       @Override
-       protected void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) {
-               try {
-                       Node userHome = UserJcrUtils.getUserHome(session);
-                       Node keyring;
-                       if (userHome.hasNode(ARGEO_KEYRING))
-                               keyring = userHome.getNode(ARGEO_KEYRING);
-                       else if (notYetSavedKeyring.get() != null)
-                               keyring = notYetSavedKeyring.get();
-                       else
-                               throw new ArgeoException("Keyring not setup");
-
-                       pbeCallback.set(keyring.getProperty(ARGEO_SECRET_KEY_FACTORY)
-                                       .getString(), JcrUtils.getBinaryAsBytes(keyring
-                                       .getProperty(ARGEO_SALT)),
-                                       (int) keyring.getProperty(ARGEO_ITERATION_COUNT).getLong(),
-                                       (int) keyring.getProperty(ARGEO_KEY_LENGTH).getLong(),
-                                       keyring.getProperty(ARGEO_SECRET_KEY_ENCRYPTION)
-                                                       .getString());
-
-                       if (notYetSavedKeyring.get() != null)
-                               notYetSavedKeyring.remove();
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot handle key spec callback", e);
-               }
-       }
-
-       /** The parent node must already exist at this path. */
-       @Override
-       protected synchronized void encrypt(String path, InputStream unencrypted) {
-               // should be called first for lazy initialization
-               SecretKey secretKey = getSecretKey();
-
-               Binary binary = null;
-               InputStream in = null;
-               try {
-                       Cipher cipher = createCipher();
-                       Node node;
-                       if (!session.nodeExists(path)) {
-                               String parentPath = JcrUtils.parentPath(path);
-                               if (!session.nodeExists(parentPath))
-                                       throw new ArgeoException("No parent node of " + path);
-                               Node parentNode = session.getNode(parentPath);
-                               node = parentNode.addNode(JcrUtils.nodeNameFromPath(path));
-                       } else {
-                               node = session.getNode(path);
-                       }
-                       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);
-
-                       in = new CipherInputStream(unencrypted, cipher);
-                       binary = session.getValueFactory().createBinary(in);
-                       node.setProperty(Property.JCR_DATA, binary);
-                       session.save();
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot encrypt", e);
-               } finally {
-                       IOUtils.closeQuietly(unencrypted);
-                       IOUtils.closeQuietly(in);
-                       JcrUtils.closeQuietly(binary);
-               }
-       }
-
-       @Override
-       protected synchronized InputStream decrypt(String path) {
-               Binary binary = null;
-               InputStream encrypted = null;
-               Reader reader = null;
-               try {
-                       if (!session.nodeExists(path)) {
-                               char[] password = ask();
-                               reader = new CharArrayReader(password);
-                               return new ByteArrayInputStream(IOUtils.toByteArray(reader));
-                       } else {
-                               // should be called first for lazy initialisation
-                               SecretKey secretKey = getSecretKey();
-
-                               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);
-                       }
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot decrypt", e);
-               } finally {
-                       IOUtils.closeQuietly(encrypted);
-                       IOUtils.closeQuietly(reader);
-                       JcrUtils.closeQuietly(binary);
-               }
-       }
-
-       protected Cipher createCipher() {
-               try {
-                       Node userHome = UserJcrUtils.getUserHome(session);
-                       if (!userHome.hasNode(ARGEO_KEYRING))
-                               throw new ArgeoException("Keyring not setup");
-                       Node keyring = userHome.getNode(ARGEO_KEYRING);
-                       Cipher cipher = Cipher.getInstance(keyring
-                                       .getProperty(ARGEO_CIPHER).getString(),
-                                       getSecurityProvider());
-                       return cipher;
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot get cipher", e);
-               }
-       }
-
-       public synchronized void changePassword(char[] oldPassword,
-                       char[] newPassword) {
-               // TODO decrypt with old pw / encrypt with new pw all argeo:encrypted
-       }
-
-       public synchronized void setSession(Session session) {
-               this.session = session;
-       }
-
-       public void setIterationCountFactor(Integer iterationCountFactor) {
-               this.iterationCountFactor = iterationCountFactor;
-       }
-
-       public void setSecreteKeyLength(Long 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;
-       }
-
-}
\ No newline at end of file
diff --git a/org.argeo.security.core/src/org/argeo/security/log4j/SecureLogger.java b/org.argeo.security.core/src/org/argeo/security/log4j/SecureLogger.java
deleted file mode 100644 (file)
index 1da9857..0000000
+++ /dev/null
@@ -1,360 +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.security.log4j;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-import org.apache.log4j.AppenderSkeleton;
-import org.apache.log4j.Level;
-import org.apache.log4j.LogManager;
-import org.apache.log4j.Logger;
-import org.apache.log4j.PropertyConfigurator;
-import org.apache.log4j.spi.LoggingEvent;
-import org.argeo.ArgeoException;
-import org.argeo.ArgeoLogListener;
-import org.argeo.ArgeoLogger;
-import org.argeo.security.SecurityUtils;
-
-/** Not meant to be used directly in standard log4j config */
-public class SecureLogger implements ArgeoLogger {
-
-       private Boolean disabled = false;
-
-       private String level = null;
-
-       private Level log4jLevel = null;
-       // private Layout layout;
-
-       private Properties configuration;
-
-       private AppenderImpl appender;
-
-       private final List<ArgeoLogListener> everythingListeners = Collections
-                       .synchronizedList(new ArrayList<ArgeoLogListener>());
-       private final List<ArgeoLogListener> allUsersListeners = Collections
-                       .synchronizedList(new ArrayList<ArgeoLogListener>());
-       private final Map<String, List<ArgeoLogListener>> userListeners = Collections
-                       .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
-
-       private BlockingQueue<LogEvent> events;
-       private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
-
-       private Integer maxLastEventsCount = 10 * 1000;
-
-       /** Marker to prevent stack overflow */
-       private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
-
-               @Override
-               protected Boolean initialValue() {
-                       return false;
-               }
-       };
-
-       public void init() {
-               try {
-                       events = new LinkedBlockingQueue<LogEvent>();
-
-                       // if (layout != null)
-                       // setLayout(layout);
-                       // else
-                       // setLayout(new PatternLayout(pattern));
-                       appender = new AppenderImpl();
-                       reloadConfiguration();
-                       Logger.getRootLogger().addAppender(appender);
-
-                       logDispatcherThread = new LogDispatcherThread();
-                       logDispatcherThread.start();
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot initialize log4j");
-               }
-       }
-
-       public void destroy() throws Exception {
-               Logger.getRootLogger().removeAppender(appender);
-               allUsersListeners.clear();
-               for (List<ArgeoLogListener> lst : userListeners.values())
-                       lst.clear();
-               userListeners.clear();
-
-               events.clear();
-               events = null;
-               logDispatcherThread.interrupt();
-       }
-
-       // public void setLayout(Layout layout) {
-       // this.layout = layout;
-       // }
-
-       public synchronized void register(ArgeoLogListener listener,
-                       Integer numberOfPreviousEvents) {
-               String username = SecurityUtils.getCurrentThreadUsername();
-               if (username == null)
-                       throw new ArgeoException(
-                                       "Only authenticated users can register a log listener");
-
-               if (!userListeners.containsKey(username)) {
-                       List<ArgeoLogListener> lst = Collections
-                                       .synchronizedList(new ArrayList<ArgeoLogListener>());
-                       userListeners.put(username, lst);
-               }
-               userListeners.get(username).add(listener);
-               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username,
-                               numberOfPreviousEvents);
-               for (LogEvent evt : lastEvents)
-                       dispatchEvent(listener, evt);
-       }
-
-       public synchronized void registerForAll(ArgeoLogListener listener,
-                       Integer numberOfPreviousEvents, boolean everything) {
-               if (everything)
-                       everythingListeners.add(listener);
-               else
-                       allUsersListeners.add(listener);
-               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null,
-                               numberOfPreviousEvents);
-               for (LogEvent evt : lastEvents)
-                       if (everything || evt.getUsername() != null)
-                               dispatchEvent(listener, evt);
-       }
-
-       public synchronized void unregister(ArgeoLogListener listener) {
-               String username = SecurityUtils.getCurrentThreadUsername();
-               if (!userListeners.containsKey(username))
-                       throw new ArgeoException("No user listeners " + listener
-                                       + " registered for user " + username);
-               if (!userListeners.get(username).contains(listener))
-                       throw new ArgeoException("No user listeners " + listener
-                                       + " registered for user " + username);
-               userListeners.get(username).remove(listener);
-               if (userListeners.get(username).isEmpty())
-                       userListeners.remove(username);
-
-       }
-
-       public synchronized void unregisterForAll(ArgeoLogListener listener) {
-               everythingListeners.remove(listener);
-               allUsersListeners.remove(listener);
-       }
-
-       /** For development purpose, since using regular logging is not easy here */
-       static void stdOut(Object obj) {
-               System.out.println(obj);
-       }
-
-       // public void setPattern(String pattern) {
-       // this.pattern = pattern;
-       // }
-
-       public void setDisabled(Boolean disabled) {
-               this.disabled = disabled;
-       }
-
-       public void setLevel(String level) {
-               this.level = level;
-       }
-
-       public void setConfiguration(Properties configuration) {
-               this.configuration = configuration;
-       }
-
-       public void updateConfiguration(Properties configuration) {
-               setConfiguration(configuration);
-               reloadConfiguration();
-       }
-
-       public Properties getConfiguration() {
-               return configuration;
-       }
-
-       /** Reloads configuration (if the configuration {@link Properties} is set) */
-       protected void reloadConfiguration() {
-               if (configuration != null) {
-                       LogManager.resetConfiguration();
-                       PropertyConfigurator.configure(configuration);
-               }
-       }
-
-       protected synchronized void processLoggingEvent(LogEvent event) {
-               if (disabled)
-                       return;
-
-               if (dispatching.get())
-                       return;
-
-               if (level != null && !level.trim().equals("")) {
-                       if (log4jLevel == null || !log4jLevel.toString().equals(level))
-                               try {
-                                       log4jLevel = Level.toLevel(level);
-                               } catch (Exception e) {
-                                       System.err
-                                                       .println("Log4j level could not be set for level '"
-                                                                       + level + "', resetting it to null.");
-                                       e.printStackTrace();
-                                       level = null;
-                               }
-
-                       if (log4jLevel != null
-                                       && !event.getLoggingEvent().getLevel()
-                                                       .isGreaterOrEqual(log4jLevel)) {
-                               return;
-                       }
-               }
-
-               try {
-                       // admin listeners
-                       Iterator<ArgeoLogListener> everythingIt = everythingListeners
-                                       .iterator();
-                       while (everythingIt.hasNext())
-                               dispatchEvent(everythingIt.next(), event);
-
-                       if (event.getUsername() != null) {
-                               Iterator<ArgeoLogListener> allUsersIt = allUsersListeners
-                                               .iterator();
-                               while (allUsersIt.hasNext())
-                                       dispatchEvent(allUsersIt.next(), event);
-
-                               if (userListeners.containsKey(event.getUsername())) {
-                                       Iterator<ArgeoLogListener> userIt = userListeners.get(
-                                                       event.getUsername()).iterator();
-                                       while (userIt.hasNext())
-                                               dispatchEvent(userIt.next(), event);
-                               }
-                       }
-               } catch (Exception e) {
-                       stdOut("Cannot process logging event");
-                       e.printStackTrace();
-               }
-       }
-
-       protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
-               LoggingEvent event = evt.getLoggingEvent();
-               logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event
-                               .getLevel().toString(), event.getLoggerName(), event
-                               .getThreadName(), event.getMessage(), event
-                               .getThrowableStrRep());
-       }
-
-       private class AppenderImpl extends AppenderSkeleton {
-               public boolean requiresLayout() {
-                       return false;
-               }
-
-               public void close() {
-               }
-
-               @Override
-               protected void append(LoggingEvent event) {
-                       if (events != null) {
-                               try {
-                                       String username = SecurityUtils.getCurrentThreadUsername();
-                                       events.put(new LogEvent(username, event));
-                               } catch (InterruptedException e) {
-                                       // silent
-                               }
-                       }
-               }
-
-       }
-
-       private class LogDispatcherThread extends Thread {
-               /** encapsulated in order to simplify concurrency management */
-               private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
-
-               public LogDispatcherThread() {
-                       super("Argeo Logging Dispatcher Thread");
-               }
-
-               public void run() {
-                       while (events != null) {
-                               try {
-                                       LogEvent loggingEvent = events.take();
-                                       processLoggingEvent(loggingEvent);
-                                       addLastEvent(loggingEvent);
-                               } catch (InterruptedException e) {
-                                       if (events == null)
-                                               return;
-                               }
-                       }
-               }
-
-               protected synchronized void addLastEvent(LogEvent loggingEvent) {
-                       if (lastEvents.size() >= maxLastEventsCount)
-                               lastEvents.poll();
-                       lastEvents.add(loggingEvent);
-               }
-
-               public synchronized List<LogEvent> getLastEvents(String username,
-                               Integer maxCount) {
-                       LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
-                       ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents
-                                       .size());
-                       int count = 0;
-                       while (it.hasPrevious() && (count < maxCount)) {
-                               LogEvent evt = it.previous();
-                               if (username == null || username.equals(evt.getUsername())) {
-                                       evts.push(evt);
-                                       count++;
-                               }
-                       }
-                       return evts;
-               }
-       }
-
-       private class LogEvent {
-               private final String username;
-               private final LoggingEvent loggingEvent;
-
-               public LogEvent(String username, LoggingEvent loggingEvent) {
-                       super();
-                       this.username = username;
-                       this.loggingEvent = loggingEvent;
-               }
-
-               @Override
-               public int hashCode() {
-                       return loggingEvent.hashCode();
-               }
-
-               @Override
-               public boolean equals(Object obj) {
-                       return loggingEvent.equals(obj);
-               }
-
-               @Override
-               public String toString() {
-                       return username + "@ " + loggingEvent.toString();
-               }
-
-               public String getUsername() {
-                       return username;
-               }
-
-               public LoggingEvent getLoggingEvent() {
-                       return loggingEvent;
-               }
-
-       }
-}
index 429e4902dc397d3146c10bd224f487a5ecfc8789..74c4ad6cf459da86cd4ffcaa62346a369bcf9d37 100644 (file)
@@ -18,7 +18,7 @@
                <property name="repository" ref="nodeRepository" />
        </bean>
 
-       <bean id="keyring" class="org.argeo.security.jcr.JcrKeyring">
+       <bean id="keyring" class="org.argeo.jcr.security.JcrKeyring">
                <property name="session" ref="nodeSession" />
                <property name="defaultCallbackHandler" ref="defaultCallbackHandler" />
                <property name="secreteKeyLength" value="${argeo.keyring.secreteKeyLength}" />
index 01e21244b1261baa82cb40fd94beab8d789006e5..cb2cbfb2f03d7081ce75527f0b3e9c15c83def3a 100644 (file)
@@ -17,6 +17,6 @@
        <reference id="defaultCallbackHandler" interface="javax.security.auth.callback.CallbackHandler" />\r
 \r
        <!-- SERVICES -->\r
-       <service interface="org.argeo.security.crypto.CryptoKeyring"\r
+       <service interface="org.argeo.util.security.CryptoKeyring"\r
                ref="keyring" />\r
 </beans:beans>
\ No newline at end of file
index a81dc200a9d90aea17f227aba4e63de874ddf88c..42e6f10e7f9de4767914d021c616a70224fd3d50 100644 (file)
@@ -19,7 +19,7 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
-import org.argeo.security.ui.internal.CurrentUser;
+import org.argeo.cms.auth.CurrentUser;
 import org.eclipse.ui.AbstractSourceProvider;
 
 /**
diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/internal/CurrentUser.java b/org.argeo.security.ui/src/org/argeo/security/ui/internal/CurrentUser.java
deleted file mode 100644 (file)
index 7086de0..0000000
+++ /dev/null
@@ -1,34 +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.security.ui.internal;
-
-import java.util.Set;
-
-import org.argeo.security.SecurityUtils;
-
-/**
- * Retrieves information about the current user. Not an API, can change without
- * notice.
- */
-public class CurrentUser {
-       public final static String getUsername() {
-               return SecurityUtils.getCurrentThreadUsername();
-       }
-
-       public final static Set<String> roles() {
-               return SecurityUtils.roles();
-       }
-}
index 83438e8aae5130f481ee0c0dea3a9eaf02de80a4..49f5bea5a316f45fb5f7b895de4300f707d2d3a0 100644 (file)
@@ -17,9 +17,9 @@ package org.argeo.security.ui.views;
 
 import java.util.TreeSet;
 
+import org.argeo.cms.auth.CurrentUser;
 import org.argeo.eclipse.ui.EclipseUiUtils;
 import org.argeo.security.ui.SecurityUiPlugin;
-import org.argeo.security.ui.internal.CurrentUser;
 import org.eclipse.jface.viewers.IStructuredContentProvider;
 import org.eclipse.jface.viewers.LabelProvider;
 import org.eclipse.jface.viewers.TableViewer;
@@ -41,9 +41,9 @@ public class UserProfile extends ViewPart {
        public void createPartControl(Composite parent) {
                parent.setLayout(new GridLayout(2, false));
 
-//             Authentication authentication = CurrentUser.getAuthentication();
-//             EclipseUiUtils.createGridLL(parent, "Name", authentication
-//                             .getPrincipal().toString());
+               // Authentication authentication = CurrentUser.getAuthentication();
+               // EclipseUiUtils.createGridLL(parent, "Name", authentication
+               // .getPrincipal().toString());
                EclipseUiUtils.createGridLL(parent, "User ID",
                                CurrentUser.getUsername());
 
index f13c84e3b8b1ca8fb8d4e297c3467b5dd1362fa9..a0d972cc28f5a611ef39dc4da8f137b76b9715b8 100644 (file)
@@ -57,7 +57,6 @@ public class DefaultRepositoryRegister extends Observable implements
        /** Registers a service, typically called when OSGi services are bound. */
        @SuppressWarnings("rawtypes")
        public synchronized void register(Repository repository, Map properties) {
-               // TODO: also check bean name?
                String alias;
                if (properties == null || !properties.containsKey(JCR_REPOSITORY_ALIAS)) {
                        log.warn("Cannot register a repository if no "
@@ -86,7 +85,10 @@ public class DefaultRepositoryRegister extends Observable implements
                String alias = properties.get(JCR_REPOSITORY_ALIAS).toString();
                Map<String, Repository> map = new TreeMap<String, Repository>(
                                repositories);
-               map.put(alias, repository);
+               if (map.remove(alias) == null) {
+                       log.warn("No repository was registered with alias " + alias);
+                       return;
+               }
                repositories = Collections.unmodifiableMap(map);
                setChanged();
                notifyObservers(alias);
diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/security/JcrKeyring.java b/org.argeo.server.jcr/src/org/argeo/jcr/security/JcrKeyring.java
new file mode 100644 (file)
index 0000000..77e9695
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * 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.jcr.security;
+
+import java.io.ByteArrayInputStream;
+import java.io.CharArrayReader;
+import java.io.InputStream;
+import java.io.Reader;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.jcr.ArgeoTypes;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.UserJcrUtils;
+import org.argeo.util.security.AbstractKeyring;
+import org.argeo.util.security.PBEKeySpecCallback;
+
+/** JCR based implementation of a keyring */
+public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
+       /**
+        * 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";
+       public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
+       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 String cipherName = DEFAULT_CIPHER_NAME;
+
+       private Session session;
+
+       /**
+        * 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;
+               }
+       };
+
+       @Override
+       protected Boolean isSetup() {
+               try {
+                       if (notYetSavedKeyring.get() != null)
+                               return true;
+
+                       Node userHome = UserJcrUtils.getUserHome(session);
+                       return userHome.hasNode(ARGEO_KEYRING);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot check whether keyring is setup", e);
+               }
+       }
+
+       @Override
+       protected void setup(char[] password) {
+               Binary binary = null;
+               InputStream in = null;
+               try {
+                       Node userHome = UserJcrUtils.getUserHome(session);
+                       if (userHome.hasNode(ARGEO_KEYRING))
+                               throw new ArgeoException("Keyring already setup");
+                       Node 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();
+                       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);
+                       keyring.setProperty(ARGEO_SALT, binary);
+
+                       Integer iterationCount = username.length() * iterationCountFactor;
+                       keyring.setProperty(ARGEO_ITERATION_COUNT, iterationCount);
+
+                       // 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_CIPHER, cipherName);
+
+                       // encrypted password hash
+                       // IOUtils.closeQuietly(in);
+                       // JcrUtils.closeQuietly(binary);
+                       // byte[] btPass = hash(password, salt, iterationCount);
+                       // in = new ByteArrayInputStream(btPass);
+                       // binary = session.getValueFactory().createBinary(in);
+                       // keyring.setProperty(ARGEO_PASSWORD, binary);
+
+                       notYetSavedKeyring.set(keyring);
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot setup keyring", e);
+               } finally {
+                       JcrUtils.closeQuietly(binary);
+                       IOUtils.closeQuietly(in);
+                       // JcrUtils.discardQuietly(session);
+               }
+       }
+
+       @Override
+       protected void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) {
+               try {
+                       Node userHome = UserJcrUtils.getUserHome(session);
+                       Node keyring;
+                       if (userHome.hasNode(ARGEO_KEYRING))
+                               keyring = userHome.getNode(ARGEO_KEYRING);
+                       else if (notYetSavedKeyring.get() != null)
+                               keyring = notYetSavedKeyring.get();
+                       else
+                               throw new ArgeoException("Keyring not setup");
+
+                       pbeCallback.set(keyring.getProperty(ARGEO_SECRET_KEY_FACTORY)
+                                       .getString(), JcrUtils.getBinaryAsBytes(keyring
+                                       .getProperty(ARGEO_SALT)),
+                                       (int) keyring.getProperty(ARGEO_ITERATION_COUNT).getLong(),
+                                       (int) keyring.getProperty(ARGEO_KEY_LENGTH).getLong(),
+                                       keyring.getProperty(ARGEO_SECRET_KEY_ENCRYPTION)
+                                                       .getString());
+
+                       if (notYetSavedKeyring.get() != null)
+                               notYetSavedKeyring.remove();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot handle key spec callback", e);
+               }
+       }
+
+       /** The parent node must already exist at this path. */
+       @Override
+       protected synchronized void encrypt(String path, InputStream unencrypted) {
+               // should be called first for lazy initialization
+               SecretKey secretKey = getSecretKey();
+
+               Binary binary = null;
+               InputStream in = null;
+               try {
+                       Cipher cipher = createCipher();
+                       Node node;
+                       if (!session.nodeExists(path)) {
+                               String parentPath = JcrUtils.parentPath(path);
+                               if (!session.nodeExists(parentPath))
+                                       throw new ArgeoException("No parent node of " + path);
+                               Node parentNode = session.getNode(parentPath);
+                               node = parentNode.addNode(JcrUtils.nodeNameFromPath(path));
+                       } else {
+                               node = session.getNode(path);
+                       }
+                       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);
+
+                       in = new CipherInputStream(unencrypted, cipher);
+                       binary = session.getValueFactory().createBinary(in);
+                       node.setProperty(Property.JCR_DATA, binary);
+                       session.save();
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot encrypt", e);
+               } finally {
+                       IOUtils.closeQuietly(unencrypted);
+                       IOUtils.closeQuietly(in);
+                       JcrUtils.closeQuietly(binary);
+               }
+       }
+
+       @Override
+       protected synchronized InputStream decrypt(String path) {
+               Binary binary = null;
+               InputStream encrypted = null;
+               Reader reader = null;
+               try {
+                       if (!session.nodeExists(path)) {
+                               char[] password = ask();
+                               reader = new CharArrayReader(password);
+                               return new ByteArrayInputStream(IOUtils.toByteArray(reader));
+                       } else {
+                               // should be called first for lazy initialisation
+                               SecretKey secretKey = getSecretKey();
+
+                               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);
+                       }
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot decrypt", e);
+               } finally {
+                       IOUtils.closeQuietly(encrypted);
+                       IOUtils.closeQuietly(reader);
+                       JcrUtils.closeQuietly(binary);
+               }
+       }
+
+       protected Cipher createCipher() {
+               try {
+                       Node userHome = UserJcrUtils.getUserHome(session);
+                       if (!userHome.hasNode(ARGEO_KEYRING))
+                               throw new ArgeoException("Keyring not setup");
+                       Node keyring = userHome.getNode(ARGEO_KEYRING);
+                       Cipher cipher = Cipher.getInstance(keyring
+                                       .getProperty(ARGEO_CIPHER).getString(),
+                                       getSecurityProvider());
+                       return cipher;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot get cipher", e);
+               }
+       }
+
+       public synchronized void changePassword(char[] oldPassword,
+                       char[] newPassword) {
+               // TODO decrypt with old pw / encrypt with new pw all argeo:encrypted
+       }
+
+       public synchronized void setSession(Session session) {
+               this.session = session;
+       }
+
+       public void setIterationCountFactor(Integer iterationCountFactor) {
+               this.iterationCountFactor = iterationCountFactor;
+       }
+
+       public void setSecreteKeyLength(Long 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;
+       }
+
+}
\ No newline at end of file
index 52f323cdad902b37e48a118d71b8ea999343c0eb..9a8006a7cdd699e53796bc210e16172ddbf4d5a4 100644 (file)
@@ -1,4 +1,6 @@
 source.. = src/,\
            ext/test/
 output.. = bin/
-additional.bundles = org.junit
+additional.bundles = org.junit,\
+                     org.slf4j.commons.logging,\
+                     org.apache.log4j
diff --git a/org.argeo.util/ext/test/org/argeo/util/security/PasswordBasedEncryptionTest.java b/org.argeo.util/ext/test/org/argeo/util/security/PasswordBasedEncryptionTest.java
new file mode 100644 (file)
index 0000000..42630cf
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * 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.security;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+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.PBEParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.bind.DatatypeConverter;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.StreamUtils;
+import org.argeo.util.security.PasswordBasedEncryption;
+
+public class PasswordBasedEncryptionTest extends TestCase {
+       private final static Log log = LogFactory
+                       .getLog(PasswordBasedEncryptionTest.class);
+
+       public void testEncryptDecrypt() {
+               final String password = "test long password since they are safer";
+               PasswordBasedEncryption pbeEnc = new PasswordBasedEncryption(
+                               password.toCharArray());
+               String message = "Hello World!";
+               log.info("Password:\t'" + password + "'");
+               log.info("Message:\t'" + message + "'");
+               byte[] encrypted = pbeEnc.encryptString(message);
+               log.info("Encrypted:\t'"
+                               + DatatypeConverter.printBase64Binary(encrypted) + "'");
+               PasswordBasedEncryption pbeDec = new PasswordBasedEncryption(
+                               password.toCharArray());
+               InputStream in = null;
+               in = new ByteArrayInputStream(encrypted);
+               String decrypted = pbeDec.decryptAsString(in);
+               log.info("Decrypted:\t'" + decrypted + "'");
+               StreamUtils.closeQuietly(in);
+               assertEquals(message, decrypted);
+       }
+
+       public void testPBEWithMD5AndDES() throws Exception {
+               String password = "test";
+               String message = "Hello World!";
+
+               byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c,
+                               (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 };
+
+               int count = 1024;
+
+               String cipherAlgorithm = "PBEWithMD5AndDES";
+               String secretKeyAlgorithm = "PBEWithMD5AndDES";
+               SecretKeyFactory keyFac = SecretKeyFactory
+                               .getInstance(secretKeyAlgorithm);
+               PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
+               PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count);
+               SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
+               Cipher ecipher = Cipher.getInstance(cipherAlgorithm);
+               ecipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
+               Cipher dcipher = Cipher.getInstance(cipherAlgorithm);
+               dcipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec);
+
+               byte[] encrypted = ecipher.doFinal(message.getBytes());
+               byte[] decrypted = dcipher.doFinal(encrypted);
+               assertEquals(message, new String(decrypted));
+
+       }
+
+       public void testPBEWithSHA1AndAES() throws Exception {
+               String password = "test";
+               String message = "Hello World!";
+
+               byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c,
+                               (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 };
+               byte[] iv = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c,
+                               (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99,
+                               (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c,
+                               (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 };
+
+               int count = 1024;
+               // int keyLength = 256;
+               int keyLength = 128;
+
+               String cipherAlgorithm = "AES/CBC/PKCS5Padding";
+               String secretKeyAlgorithm = "PBKDF2WithHmacSHA1";
+               SecretKeyFactory keyFac = SecretKeyFactory
+                               .getInstance(secretKeyAlgorithm);
+               PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt,
+                               count, keyLength);
+               SecretKey tmp = keyFac.generateSecret(pbeKeySpec);
+               SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
+               Cipher ecipher = Cipher.getInstance(cipherAlgorithm);
+               ecipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv));
+
+               // decrypt
+               keyFac = SecretKeyFactory.getInstance(secretKeyAlgorithm);
+               pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, count,
+                               keyLength);
+               tmp = keyFac.generateSecret(pbeKeySpec);
+               secret = new SecretKeySpec(tmp.getEncoded(), "AES");
+               // AlgorithmParameters params = ecipher.getParameters();
+               // byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
+               Cipher dcipher = Cipher.getInstance(cipherAlgorithm);
+               dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
+
+               byte[] encrypted = ecipher.doFinal(message.getBytes());
+               byte[] decrypted = dcipher.doFinal(encrypted);
+               assertEquals(message, new String(decrypted));
+
+               ByteArrayOutputStream out = new ByteArrayOutputStream();
+               CipherOutputStream cipherOut = new CipherOutputStream(out, ecipher);
+               cipherOut.write(message.getBytes());
+               StreamUtils.closeQuietly(cipherOut);
+               byte[] enc = out.toByteArray();
+
+               ByteArrayInputStream in = new ByteArrayInputStream(enc);
+               CipherInputStream cipherIn = new CipherInputStream(in, dcipher);
+               ByteArrayOutputStream dec = new ByteArrayOutputStream();
+               StreamUtils.copy(cipherIn, dec);
+               assertEquals(message, new String(dec.toByteArray()));
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/security/AbstractKeyring.java b/org.argeo.util/src/org/argeo/util/security/AbstractKeyring.java
new file mode 100644 (file)
index 0000000..28763f8
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+ * 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.security;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+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;
+import java.util.Iterator;
+
+import javax.crypto.SecretKey;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.ArgeoException;
+import org.argeo.StreamUtils;
+
+/** username / password based keyring. TODO internationalize */
+public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
+       public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING";
+
+       private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT;
+       private CallbackHandler defaultCallbackHandler;
+
+       private String charset = "UTF-8";
+
+       /**
+        * Default provider is bouncy castle, in order to have consistent behaviour
+        * across implementations
+        */
+       private String securityProviderName = "BC";
+
+       /**
+        * Whether the keyring has already been created in the past with a master
+        * password
+        */
+       protected abstract Boolean isSetup();
+
+       /**
+        * Setup the keyring persistently, {@link #isSetup()} must return true
+        * afterwards
+        */
+       protected abstract void setup(char[] password);
+
+       /** Populates the key spec callback */
+       protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback);
+
+       protected abstract void encrypt(String path, InputStream unencrypted);
+
+       protected abstract InputStream decrypt(String path);
+
+       /** Triggers lazy initialization */
+       protected SecretKey getSecretKey() {
+               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();
+                       try {
+                               LoginContext loginContext = new LoginContext(loginContextName,
+                                               subject, callbackHandler);
+                               loginContext.login();
+                               // FIXME will login even if password is wrong
+                               iterator = subject.getPrivateCredentials(SecretKey.class)
+                                               .iterator();
+                               return iterator.next();
+                       } catch (LoginException e) {
+                               throw new ArgeoException("Keyring login failed", e);
+                       }
+
+               } else {
+                       SecretKey secretKey = iterator.next();
+                       if (iterator.hasNext())
+                               throw new ArgeoException(
+                                               "More than one secret key in private credentials");
+                       return secretKey;
+               }
+       }
+
+       public InputStream getAsStream(String path) {
+               return decrypt(path);
+       }
+
+       public void set(String path, InputStream in) {
+               encrypt(path, in);
+       }
+
+       public char[] getAsChars(String path) {
+               InputStream in = getAsStream(path);
+               CharArrayWriter writer = null;
+               Reader reader = null;
+               try {
+                       writer = new CharArrayWriter();
+                       reader = new InputStreamReader(in, charset);
+                       StreamUtils.copy(reader, writer);
+                       return writer.toCharArray();
+               } catch (IOException e) {
+                       throw new ArgeoException("Cannot decrypt to char array", e);
+               } finally {
+                       StreamUtils.closeQuietly(reader);
+                       StreamUtils.closeQuietly(in);
+                       StreamUtils.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);
+                       writer.write(arr);
+                       writer.flush();
+                       in = new ByteArrayInputStream(out.toByteArray());
+                       set(path, in);
+               } catch (IOException e) {
+                       throw new ArgeoException("Cannot encrypt to char array", e);
+               } finally {
+                       StreamUtils.closeQuietly(writer);
+                       StreamUtils.closeQuietly(out);
+                       StreamUtils.closeQuietly(in);
+               }
+       }
+
+       protected Provider getSecurityProvider() {
+               return Security.getProvider(securityProviderName);
+       }
+
+       public void setLoginContextName(String loginContextName) {
+               this.loginContextName = loginContextName;
+       }
+
+       public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) {
+               this.defaultCallbackHandler = defaultCallbackHandler;
+       }
+
+       public void setCharset(String charset) {
+               this.charset = charset;
+       }
+
+       public void setSecurityProviderName(String securityProviderName) {
+               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 ArgeoException("Cannot hash", e);
+               } finally {
+                       StreamUtils.closeQuietly(out);
+                       StreamUtils.closeQuietly(writer);
+               }
+
+       }
+
+       /**
+        * Convenience method using the underlying callback to ask for a password
+        * (typically used when the password is not saved in the keyring)
+        */
+       protected char[] ask() {
+               PasswordCallback passwordCb = new PasswordCallback("Password", false);
+               Callback[] dialogCbs = new Callback[] { passwordCb };
+               try {
+                       defaultCallbackHandler.handle(dialogCbs);
+                       char[] password = passwordCb.getPassword();
+                       return password;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot ask for a password", e);
+               }
+
+       }
+
+       class KeyringCallbackHandler implements CallbackHandler {
+               public void handle(Callback[] callbacks) throws IOException,
+                               UnsupportedCallbackException {
+                       // checks
+                       if (callbacks.length != 2)
+                               throw new IllegalArgumentException(
+                                               "Keyring required 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];
+                       PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
+
+                       if (isSetup()) {
+                               Callback[] dialogCbs = new Callback[] { passwordCb };
+                               defaultCallbackHandler.handle(dialogCbs);
+                       } else {// setup keyring
+                               TextOutputCallback textCb1 = new TextOutputCallback(
+                                               TextOutputCallback.INFORMATION,
+                                               "Enter a master password which will protect your private data");
+                               TextOutputCallback textCb2 = new TextOutputCallback(
+                                               TextOutputCallback.INFORMATION,
+                                               "(for example your credentials to third-party services)");
+                               TextOutputCallback textCb3 = new TextOutputCallback(
+                                               TextOutputCallback.INFORMATION,
+                                               "Don't forget this password since the data cannot be read without it");
+                               PasswordCallback confirmPasswordCb = new PasswordCallback(
+                                               "Confirm password", false);
+                               // first try
+                               Callback[] dialogCbs = new Callback[] { textCb1, textCb2,
+                                               textCb3, passwordCb, confirmPasswordCb };
+                               defaultCallbackHandler.handle(dialogCbs);
+
+                               // if passwords different, retry (except if cancelled)
+                               while (passwordCb.getPassword() != null
+                                               && !Arrays.equals(passwordCb.getPassword(),
+                                                               confirmPasswordCb.getPassword())) {
+                                       TextOutputCallback textCb = new TextOutputCallback(
+                                                       TextOutputCallback.ERROR,
+                                                       "The passwords do not match");
+                                       dialogCbs = new Callback[] { textCb, passwordCb,
+                                                       confirmPasswordCb };
+                                       defaultCallbackHandler.handle(dialogCbs);
+                               }
+
+                               if (passwordCb.getPassword() != null) {// not cancelled
+                                       setup(passwordCb.getPassword());
+                               }
+                       }
+
+                       if (passwordCb.getPassword() != null)
+                               handleKeySpecCallback(pbeCb);
+               }
+
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/security/CryptoKeyring.java b/org.argeo.util/src/org/argeo/util/security/CryptoKeyring.java
new file mode 100644 (file)
index 0000000..53ce862
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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.security;
+
+
+/**
+ * Advanced keyring based on cryptography that can easily be centralized and
+ * coordinated with {@link KeyringLoginModule} (since they ar ein the same
+ * package)
+ */
+public interface CryptoKeyring extends Keyring {
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/security/KeyringLoginModule.java b/org.argeo.util/src/org/argeo/util/security/KeyringLoginModule.java
new file mode 100644 (file)
index 0000000..11a834c
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.security;
+
+import java.security.AccessController;
+import java.util.Map;
+import java.util.Set;
+
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+/** Adds a secret key to the private credentials */
+public class KeyringLoginModule implements LoginModule {
+       private Subject subject;
+       private CallbackHandler callbackHandler;
+       private SecretKey secretKey;
+
+       public void initialize(Subject subject, CallbackHandler callbackHandler,
+                       Map<String, ?> sharedState, Map<String, ?> options) {
+               this.subject = subject;
+               if (subject == null) {
+                       subject = Subject.getSubject(AccessController.getContext());
+               }
+               this.callbackHandler = callbackHandler;
+       }
+
+       public boolean login() throws LoginException {
+               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 };
+               try {
+                       callbackHandler.handle(callbacks);
+                       char[] password = pc.getPassword();
+
+                       SecretKeyFactory keyFac = SecretKeyFactory.getInstance(pbeCb
+                                       .getSecretKeyFactory());
+                       PBEKeySpec keySpec;
+                       if (pbeCb.getKeyLength() != null)
+                               keySpec = new PBEKeySpec(password, pbeCb.getSalt(),
+                                               pbeCb.getIterationCount(), pbeCb.getKeyLength());
+                       else
+                               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);
+                       } else {
+                               secretKey = keyFac.generateSecret(keySpec);
+                       }
+               } catch (Exception e) {
+                       LoginException le = new LoginException("Cannot login keyring");
+                       le.initCause(e);
+                       throw le;
+               }
+               return true;
+       }
+
+       public boolean commit() throws LoginException {
+               if (secretKey != null)
+                       subject.getPrivateCredentials().add(secretKey);
+               return true;
+       }
+
+       public boolean abort() throws LoginException {
+               return true;
+       }
+
+       public boolean logout() throws LoginException {
+               Set<PasswordBasedEncryption> pbes = subject
+                               .getPrivateCredentials(PasswordBasedEncryption.class);
+               pbes.clear();
+               return true;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/security/PBEKeySpecCallback.java b/org.argeo.util/src/org/argeo/util/security/PBEKeySpecCallback.java
new file mode 100644 (file)
index 0000000..7b3a673
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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.security;
+
+import javax.crypto.spec.PBEKeySpec;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.PasswordCallback;
+
+/**
+ * All information required to set up a {@link PBEKeySpec} bar the password
+ * itself (use a {@link PasswordCallback})
+ */
+public class PBEKeySpecCallback implements Callback {
+       private String secretKeyFactory;
+       private byte[] salt;
+       private Integer iterationCount;
+       /** Can be null for some algorithms */
+       private Integer keyLength;
+       /** Can be null, will trigger secret key encryption if not */
+       private String secretKeyEncryption;
+
+       private String encryptedPasswordHashCipher;
+       private byte[] encryptedPasswordHash;
+
+       public void set(String secretKeyFactory, byte[] salt,
+                       Integer iterationCount, Integer keyLength,
+                       String secretKeyEncryption) {
+               this.secretKeyFactory = secretKeyFactory;
+               this.salt = salt;
+               this.iterationCount = iterationCount;
+               this.keyLength = keyLength;
+               this.secretKeyEncryption = secretKeyEncryption;
+//             this.encryptedPasswordHashCipher = encryptedPasswordHashCipher;
+//             this.encryptedPasswordHash = encryptedPasswordHash;
+       }
+
+       public String getSecretKeyFactory() {
+               return secretKeyFactory;
+       }
+
+       public byte[] getSalt() {
+               return salt;
+       }
+
+       public Integer getIterationCount() {
+               return iterationCount;
+       }
+
+       public Integer getKeyLength() {
+               return keyLength;
+       }
+
+       public String getSecretKeyEncryption() {
+               return secretKeyEncryption;
+       }
+
+       public String getEncryptedPasswordHashCipher() {
+               return encryptedPasswordHashCipher;
+       }
+
+       public byte[] getEncryptedPasswordHash() {
+               return encryptedPasswordHash;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/security/PasswordBasedEncryption.java b/org.argeo.util/src/org/argeo/util/security/PasswordBasedEncryption.java
new file mode 100644 (file)
index 0000000..d7866a8
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * 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.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.argeo.ArgeoException;
+import org.argeo.StreamUtils;
+
+/** 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 ArgeoException(
+                                               "Cannot get secret key (with restricted length)", e1);
+                       }
+               } catch (Exception e) {
+                       throw new ArgeoException("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 ArgeoException("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 ArgeoException("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 ArgeoException("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 ArgeoException("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;
+       }
+}