From: Mathieu Baudier Date: Fri, 9 Oct 2015 10:11:42 +0000 (+0000) Subject: Refactor security components to the Kernel X-Git-Tag: argeo-commons-2.1.30~109 X-Git-Url: http://git.argeo.org/?a=commitdiff_plain;h=0a7d938324d33848ac7dc4ef4007c73a714171ee;p=lgpl%2Fargeo-commons.git Refactor security components to the Kernel git-svn-id: https://svn.argeo.org/commons/trunk@8467 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- 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 index 000000000..5a65fcb11 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java @@ -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 roles() { + return roles(currentSubject()); + } + + public final static Set roles(Subject subject) { + Set roles = new HashSet(); + 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() { + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java index 102bb7702..8f95a3ef9 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java @@ -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 index 8ef16c44f..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitNode.java +++ /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 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 regProps = new Hashtable(); - 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 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 getConfigurationProperties( - JackrabbitNodeType type) { - // use Hashtable to ease integration with Properties - Hashtable defaults = new Hashtable(); - - // 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 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 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; - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java index 8486f8d7e..026b12116 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java @@ -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; * */ 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 loggerReg; + private ServiceRegistration tmReg; + private ServiceRegistration utReg; + // private ServiceRegistration tsrReg; + private ServiceRegistration repositoryReg; + private ServiceRegistration repositoryFactoryReg; + private ServiceRegistration 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() { - @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 sr = bundleContext + // HTTP + ServiceReference 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 regProps = new Hashtable(); + 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 props = new HashMap(); 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 st = new ServiceTracker( - 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 st = new + // ServiceTracker( + // 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); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelThread.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelThread.java index e58cbeef1..4b4b8026f 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelThread.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelThread.java @@ -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() { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java index edcf719d7..bc74cf638 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java @@ -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 index 000000000..1264b2452 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java @@ -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 everythingListeners = Collections + .synchronizedList(new ArrayList()); + private final List allUsersListeners = Collections + .synchronizedList(new ArrayList()); + private final Map> userListeners = Collections + .synchronizedMap(new HashMap>()); + + private BlockingQueue events; + private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); + + private Integer maxLastEventsCount = 10 * 1000; + + /** Marker to prevent stack overflow */ + private ThreadLocal dispatching = new ThreadLocal() { + + @Override + protected Boolean initialValue() { + return false; + } + }; + + public void init() { + try { + events = new LinkedBlockingQueue(); + + // 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 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 lst = Collections + .synchronizedList(new ArrayList()); + userListeners.put(username, lst); + } + userListeners.get(username).add(listener); + List 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 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 everythingIt = everythingListeners + .iterator(); + while (everythingIt.hasNext()) + dispatchEvent(everythingIt.next(), event); + + if (event.getUsername() != null) { + Iterator allUsersIt = allUsersListeners + .iterator(); + while (allUsersIt.hasNext()) + dispatchEvent(allUsersIt.next(), event); + + if (userListeners.containsKey(event.getUsername())) { + Iterator 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 lastEvents = new LinkedList(); + + 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 getLastEvents(String username, + Integer maxCount) { + LinkedList evts = new LinkedList(); + ListIterator 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 index 000000000..a139289be --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeRepository.java @@ -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 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 getConfigurationProperties( + JackrabbitNodeType type) { + // use Hashtable to ease integration with Properties + Hashtable defaults = new Hashtable(); + + // 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 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 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; + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java index b436ac8d1..b43a9fdf5 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java @@ -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); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java index 75cd44491..7408b1c1f 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java @@ -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 userAdmins = new HashMap(); + // 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 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 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 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 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 index 000000000..d8b39cdbc --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java @@ -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); + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg index 4f647cf8a..90c68bd21 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg @@ -21,7 +21,7 @@ KERNEL { }; KEYRING { - org.argeo.security.crypto.KeyringLoginModule required; + org.argeo.util.security.KeyringLoginModule required; }; SINGLE_USER { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java b/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java index edb572681..30d9b7291 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/transaction/SimpleTransactionManager.java @@ -122,7 +122,7 @@ public class SimpleTransactionManager implements TransactionManager, return transaction; } - public TransactionSynchronizationRegistry getTransactionSynchronizationRegistry() { + public TransactionSynchronizationRegistry getTsr() { return syncRegistry; } diff --git a/org.argeo.cms/src/org/argeo/cms/util/CmsUtils.java b/org.argeo.cms/src/org/argeo/cms/util/CmsUtils.java index d8cd171e2..439f48af1 100644 --- a/org.argeo.cms/src/org/argeo/cms/util/CmsUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/util/CmsUtils.java @@ -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; } diff --git a/org.argeo.cms/src/org/argeo/cms/util/UserMenu.java b/org.argeo.cms/src/org/argeo/cms/util/UserMenu.java index fc651e65b..801a2611b 100644 --- a/org.argeo.cms/src/org/argeo/cms/util/UserMenu.java +++ b/org.argeo.cms/src/org/argeo/cms/util/UserMenu.java @@ -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(); diff --git a/org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java b/org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java index f4c5f0063..704f90980 100644 --- a/org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java +++ b/org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java @@ -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)) diff --git a/org.argeo.eclipse.ui.workbench/META-INF/spring/osgi.xml b/org.argeo.eclipse.ui.workbench/META-INF/spring/osgi.xml index 255462be4..38f733291 100644 --- a/org.argeo.eclipse.ui.workbench/META-INF/spring/osgi.xml +++ b/org.argeo.eclipse.ui.workbench/META-INF/spring/osgi.xml @@ -18,6 +18,6 @@ filter="(argeo.jcr.repository.alias=node)" /> - + \ 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 index 02f48c8a8..000000000 --- a/org.argeo.security.core/META-INF/spring/logger.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 index 5e1433c2f..000000000 --- a/org.argeo.security.core/META-INF/spring/osgi.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/org.argeo.security.core/bnd.bnd b/org.argeo.security.core/bnd.bnd index d3d44a195..d31b9e77a 100644 --- a/org.argeo.security.core/bnd.bnd +++ b/org.argeo.security.core/bnd.bnd @@ -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 index 6973f5704..000000000 --- a/org.argeo.security.core/ext/test/org/argeo/security/crypto/PasswordBasedEncryptionTest.java +++ /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 index e1f7899a5..000000000 --- a/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java +++ /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 roles() { - Set roles = Collections.synchronizedSet(new HashSet()); - 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 index daa1ebd12..000000000 --- a/org.argeo.security.core/src/org/argeo/security/crypto/AbstractKeyring.java +++ /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 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 index d25eccd22..000000000 --- a/org.argeo.security.core/src/org/argeo/security/crypto/CryptoKeyring.java +++ /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 index 34b7d4015..000000000 --- a/org.argeo.security.core/src/org/argeo/security/crypto/KeyringLoginModule.java +++ /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 sharedState, Map options) { - this.subject = subject; - if (subject == null) { - subject = Subject.getSubject(AccessController.getContext()); - } - this.callbackHandler = callbackHandler; - } - - public boolean login() throws LoginException { - Set 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 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 index e96436664..000000000 --- a/org.argeo.security.core/src/org/argeo/security/crypto/PBEKeySpecCallback.java +++ /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 index aec25ac17..000000000 --- a/org.argeo.security.core/src/org/argeo/security/crypto/PasswordBasedEncryption.java +++ /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 index f66d3f99c..000000000 --- a/org.argeo.security.core/src/org/argeo/security/crypto/PkiUtils.java +++ /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 index 1b9f24426..000000000 --- a/org.argeo.security.core/src/org/argeo/security/jcr/JcrKeyring.java +++ /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 notYetSavedKeyring = new ThreadLocal() { - - @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 index 1da985703..000000000 --- a/org.argeo.security.core/src/org/argeo/security/log4j/SecureLogger.java +++ /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 everythingListeners = Collections - .synchronizedList(new ArrayList()); - private final List allUsersListeners = Collections - .synchronizedList(new ArrayList()); - private final Map> userListeners = Collections - .synchronizedMap(new HashMap>()); - - private BlockingQueue events; - private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); - - private Integer maxLastEventsCount = 10 * 1000; - - /** Marker to prevent stack overflow */ - private ThreadLocal dispatching = new ThreadLocal() { - - @Override - protected Boolean initialValue() { - return false; - } - }; - - public void init() { - try { - events = new LinkedBlockingQueue(); - - // 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 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 lst = Collections - .synchronizedList(new ArrayList()); - userListeners.put(username, lst); - } - userListeners.get(username).add(listener); - List 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 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 everythingIt = everythingListeners - .iterator(); - while (everythingIt.hasNext()) - dispatchEvent(everythingIt.next(), event); - - if (event.getUsername() != null) { - Iterator allUsersIt = allUsersListeners - .iterator(); - while (allUsersIt.hasNext()) - dispatchEvent(allUsersIt.next(), event); - - if (userListeners.containsKey(event.getUsername())) { - Iterator 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 lastEvents = new LinkedList(); - - 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 getLastEvents(String username, - Integer maxCount) { - LinkedList evts = new LinkedList(); - ListIterator 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.security.ui/META-INF/spring/keyring.xml b/org.argeo.security.ui/META-INF/spring/keyring.xml index 429e4902d..74c4ad6cf 100644 --- a/org.argeo.security.ui/META-INF/spring/keyring.xml +++ b/org.argeo.security.ui/META-INF/spring/keyring.xml @@ -18,7 +18,7 @@ - + diff --git a/org.argeo.security.ui/META-INF/spring/osgi.xml b/org.argeo.security.ui/META-INF/spring/osgi.xml index 01e21244b..cb2cbfb2f 100644 --- a/org.argeo.security.ui/META-INF/spring/osgi.xml +++ b/org.argeo.security.ui/META-INF/spring/osgi.xml @@ -17,6 +17,6 @@ - \ No newline at end of file diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/RolesSourceProvider.java b/org.argeo.security.ui/src/org/argeo/security/ui/RolesSourceProvider.java index a81dc200a..42e6f10e7 100644 --- a/org.argeo.security.ui/src/org/argeo/security/ui/RolesSourceProvider.java +++ b/org.argeo.security.ui/src/org/argeo/security/ui/RolesSourceProvider.java @@ -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 index 7086de0af..000000000 --- a/org.argeo.security.ui/src/org/argeo/security/ui/internal/CurrentUser.java +++ /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 roles() { - return SecurityUtils.roles(); - } -} diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/views/UserProfile.java b/org.argeo.security.ui/src/org/argeo/security/ui/views/UserProfile.java index 83438e8aa..49f5bea5a 100644 --- a/org.argeo.security.ui/src/org/argeo/security/ui/views/UserProfile.java +++ b/org.argeo.security.ui/src/org/argeo/security/ui/views/UserProfile.java @@ -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()); diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/DefaultRepositoryRegister.java b/org.argeo.server.jcr/src/org/argeo/jcr/DefaultRepositoryRegister.java index f13c84e3b..a0d972cc2 100644 --- a/org.argeo.server.jcr/src/org/argeo/jcr/DefaultRepositoryRegister.java +++ b/org.argeo.server.jcr/src/org/argeo/jcr/DefaultRepositoryRegister.java @@ -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 map = new TreeMap( 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 index 000000000..77e969509 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/security/JcrKeyring.java @@ -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 notYetSavedKeyring = new ThreadLocal() { + + @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.util/build.properties b/org.argeo.util/build.properties index 52f323cda..9a8006a7c 100644 --- a/org.argeo.util/build.properties +++ b/org.argeo.util/build.properties @@ -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 index 000000000..42630cfee --- /dev/null +++ b/org.argeo.util/ext/test/org/argeo/util/security/PasswordBasedEncryptionTest.java @@ -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 index 000000000..28763f82b --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/security/AbstractKeyring.java @@ -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 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 index 000000000..53ce862f0 --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/security/CryptoKeyring.java @@ -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 index 000000000..11a834c25 --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/security/KeyringLoginModule.java @@ -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 sharedState, Map options) { + this.subject = subject; + if (subject == null) { + subject = Subject.getSubject(AccessController.getContext()); + } + this.callbackHandler = callbackHandler; + } + + public boolean login() throws LoginException { + Set 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 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 index 000000000..7b3a673bb --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/security/PBEKeySpecCallback.java @@ -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 index 000000000..d7866a810 --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/security/PasswordBasedEncryption.java @@ -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; + } +}