JCR Keyring
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 29 Sep 2011 16:56:14 +0000 (16:56 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 29 Sep 2011 16:56:14 +0000 (16:56 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@4765 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

15 files changed:
security/plugins/org.argeo.security.equinox/META-INF/spring/loginModules.xml
security/plugins/org.argeo.security.equinox/pom.xml
security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/KeyringLoginModule.java [deleted file]
security/plugins/org.argeo.security.ui.rcp/META-INF/jaas_default.txt
security/plugins/org.argeo.security.ui.rcp/src/main/java/org/argeo/security/ui/rcp/SecureApplicationActivator.java
security/plugins/org.argeo.security.ui/plugin.xml
security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/SecurityUiPlugin.java
security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/DefaultLoginDialog.java
security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/internal/CurrentUser.java
security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/keyring/KeyringLoginModule.java [new file with mode: 0644]
server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoNames.java
server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoTypes.java
server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java
server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrKeyring.java [new file with mode: 0644]
server/runtime/org.argeo.server.jcr/src/main/resources/org/argeo/jcr/argeo.cnd

index d661b5f562ef00fb8a2fca80fac0f02fd897adae..f409ecd995fd6d1741867a02cd954c0574bfd928 100644 (file)
@@ -14,7 +14,7 @@
                <property name="authenticationManager" ref="authenticationManager" />
        </bean>
 
-       <bean id="keyringLoginModule" class="org.argeo.security.equinox.KeyringLoginModule"
+       <bean id="keyringLoginModule" class="org.argeo.crypto.KeyringLoginModule"
                scope="prototype">
        </bean>
 </beans>
index d3528a98047dc41498cd021d2473c081919d6620..31cd1da14a5524125a06ded6ed42cdbf9212ce40 100644 (file)
@@ -43,7 +43,8 @@
                                        <instructions>
                                                <Import-Package>*,
                                                        org.springframework.core,
-                                                       org.argeo.eclipse.spring
+                                                       org.argeo.eclipse.spring,
+                                                       org.argeo.util.crypto
                                                </Import-Package>
                                        </instructions>
                                </configuration>
diff --git a/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/KeyringLoginModule.java b/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/KeyringLoginModule.java
deleted file mode 100644 (file)
index 3de56cc..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.argeo.security.equinox;
-
-import java.util.Map;
-import java.util.Set;
-
-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;
-
-import org.apache.commons.logging.LogConfigurationException;
-import org.argeo.util.crypto.PasswordBasedEncryption;
-
-public class KeyringLoginModule implements LoginModule {
-       private Subject subject;
-       private CallbackHandler callbackHandler;
-       private PasswordBasedEncryption passwordBasedEncryption;
-
-       public void initialize(Subject subject, CallbackHandler callbackHandler,
-                       Map<String, ?> sharedState, Map<String, ?> options) {
-               this.subject = subject;
-               this.callbackHandler = callbackHandler;
-       }
-
-       public boolean login() throws LoginException {
-               Set<PasswordBasedEncryption> pbes = subject
-                               .getPrivateCredentials(PasswordBasedEncryption.class);
-               if (pbes.size() > 0)
-                       return true;
-               PasswordCallback pc = new PasswordCallback("Master password", false);
-               Callback[] callbacks = { pc };
-               try {
-                       callbackHandler.handle(callbacks);
-                       passwordBasedEncryption = new PasswordBasedEncryption(
-                                       pc.getPassword());
-               } catch (Exception e) {
-                       throw new LogConfigurationException(e);
-               }
-               return true;
-       }
-
-       public boolean commit() throws LoginException {
-               if (passwordBasedEncryption != null)
-                       subject.getPrivateCredentials(PasswordBasedEncryption.class).add(
-                                       passwordBasedEncryption);
-               return true;
-       }
-
-       public boolean abort() throws LoginException {
-               return true;
-       }
-
-       public boolean logout() throws LoginException {
-               Set<PasswordBasedEncryption> pbes = subject
-                               .getPrivateCredentials(PasswordBasedEncryption.class);
-               pbes.clear();
-               return true;
-       }
-
-}
index 96747d3ea73d1a7b2ad401a3d5c38a13a34f77d9..98e39b54f8b7cd54e48855e99b920faeba103f10 100644 (file)
@@ -16,3 +16,12 @@ WINDOWS {
     org.eclipse.equinox.security.auth.module.ExtensionLoginModule required
         extensionId="org.argeo.security.equinox.osSpringLoginModule";
 };
+
+KEYRING_OLD {
+    org.eclipse.equinox.security.auth.module.ExtensionLoginModule required
+        extensionId="org.argeo.security.equinox.keyringLoginModule";
+};
+
+KEYRING {
+    org.argeo.util.crypto.KeyringLoginModule required;
+};
index d5617d773c8c647509754e11ef393f0a75ebd361..1c8bd7c25e7a4c4be58896f0efb339f3c28abe67 100644 (file)
@@ -1,19 +1,11 @@
 package org.argeo.security.ui.rcp;
 
-import java.io.IOException;
 import java.net.URL;
 
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-import org.argeo.security.ui.dialogs.DefaultLoginDialog;
 import org.eclipse.equinox.security.auth.ILoginContext;
 import org.eclipse.equinox.security.auth.LoginContextFactory;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.ServiceRegistration;
 
 /** Activator able to create {@link ILoginContext} */
 public class SecureApplicationActivator implements BundleActivator {
@@ -23,25 +15,12 @@ public class SecureApplicationActivator implements BundleActivator {
        private static final String JAAS_CONFIG_FILE = "/META-INF/jaas_default.txt";
 
        private static BundleContext bundleContext;
-       private ServiceRegistration callbackHandlerRegistration;
 
        public void start(BundleContext bundleContext) throws Exception {
                SecureApplicationActivator.bundleContext = bundleContext;
-
-               CallbackHandler callbackHandler = new CallbackHandler() {
-
-                       public void handle(Callback[] callbacks) throws IOException,
-                                       UnsupportedCallbackException {
-                               DefaultLoginDialog dialog = new DefaultLoginDialog();
-                               dialog.handle(callbacks);
-                       }
-               };
-               callbackHandlerRegistration = bundleContext.registerService(
-                               CallbackHandler.class.getName(), callbackHandler, null);
        }
 
        public void stop(BundleContext context) throws Exception {
-               callbackHandlerRegistration.unregister();
        }
 
        static ILoginContext createLoginContext(String context) {
index a9ad7d848b8a1a5d08936d6fad4687bcbb21040c..59ccd3beec46e7b982bbb98e30270682bd721aac 100644 (file)
@@ -21,7 +21,7 @@
          point="org.eclipse.equinox.security.callbackHandlerMapping">
          <callbackHandlerMapping
             callbackHandlerId="org.argeo.security.ui.defaultLoginDialog"
-            configName="UNIX">
+            configName="NIX">
       </callbackHandlerMapping>
    </extension>
    <extension
index bc05495b4299c2e990e225ea3c2afb5828b1d6f1..ed07bbb5061f430f989dd3073d769e9ca852b82d 100644 (file)
@@ -1,8 +1,16 @@
 package org.argeo.security.ui;
 
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.argeo.security.ui.dialogs.DefaultLoginDialog;
 import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.ui.plugin.AbstractUIPlugin;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
 
 /**
  * The activator class controls the plug-in life cycle
@@ -12,36 +20,31 @@ public class SecurityUiPlugin extends AbstractUIPlugin {
        // The plug-in ID
        public static final String PLUGIN_ID = "org.argeo.security.ui"; //$NON-NLS-1$
 
-       // The shared instance
-       private static SecurityUiPlugin plugin;
+       public final static String CONTEXT_KEYRING = "KEYRING";
 
-       /**
-        * The constructor
-        */
-       public SecurityUiPlugin() {
-       }
+       private CallbackHandler defaultCallbackHandler;
+       private ServiceRegistration defaultCallbackHandlerReg;
+
+       private static SecurityUiPlugin plugin;
 
-       /*
-        * (non-Javadoc)
-        * 
-        * @see
-        * org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext
-        * )
-        */
        public void start(BundleContext context) throws Exception {
                super.start(context);
                plugin = this;
+
+               defaultCallbackHandler = new CallbackHandler() {
+                       public void handle(Callback[] callbacks) throws IOException,
+                                       UnsupportedCallbackException {
+                               DefaultLoginDialog dialog = new DefaultLoginDialog();
+                               dialog.handle(callbacks);
+                       }
+               };
+               defaultCallbackHandlerReg = context.registerService(
+                               CallbackHandler.class.getName(), defaultCallbackHandler, null);
        }
 
-       /*
-        * (non-Javadoc)
-        * 
-        * @see
-        * org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext
-        * )
-        */
        public void stop(BundleContext context) throws Exception {
                plugin = null;
+               defaultCallbackHandlerReg.unregister();
                super.stop(context);
        }
 
index 96f0535d93ee9181396dd950a610242ace68cfce..8c8554c6b77a0ef8ff2700acc4590f190617a4ba 100644 (file)
@@ -33,13 +33,20 @@ public class DefaultLoginDialog extends AbstractLoginDialog {
                return new Point(300, 180);
        }
 
+       @Override
+       protected Control createContents(Composite parent) {
+               Control control = super.createContents(parent);
+               parent.pack();
+               return control;
+       }
+
        protected Control createDialogArea(Composite parent) {
                Composite dialogarea = (Composite) super.createDialogArea(parent);
                Composite composite = new Composite(dialogarea, SWT.NONE);
                composite.setLayout(new GridLayout(2, false));
                composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
                createCallbackHandlers(composite);
-               parent.pack();
+               // parent.pack();
                return composite;
        }
 
@@ -48,7 +55,7 @@ public class DefaultLoginDialog extends AbstractLoginDialog {
                for (int i = 0; i < callbacks.length; i++) {
                        Callback callback = callbacks[i];
                        if (callback instanceof TextOutputCallback) {
-                               createTextoutputHandler(composite,
+                               createLabelTextoutputHandler(composite,
                                                (TextOutputCallback) callback);
                        } else if (callback instanceof NameCallback) {
                                createNameHandler(composite, (NameCallback) callback);
@@ -69,6 +76,7 @@ public class DefaultLoginDialog extends AbstractLoginDialog {
                passwordText.addModifyListener(new ModifyListener() {
 
                        public void modifyText(ModifyEvent event) {
+                               // FIXME use getTextChars() in Eclipse 3.7
                                callback.setPassword(passwordText.getText().toCharArray());
                        }
                });
@@ -89,8 +97,13 @@ public class DefaultLoginDialog extends AbstractLoginDialog {
                });
        }
 
-       private void createTextoutputHandler(Composite composite,
-                       TextOutputCallback callback) {
+       private void createLabelTextoutputHandler(Composite composite,
+                       final TextOutputCallback callback) {
+               Label label = new Label(composite, SWT.NONE);
+               label.setText(callback.getMessage());
+               GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
+               data.horizontalSpan = 2;
+               label.setLayoutData(data);
                // TODO: find a way to pass this information
                // int messageType = callback.getMessageType();
                // int dialogMessageType = IMessageProvider.NONE;
index ed50d1e3ccd521d864d5e1ac77a8ee5b4a6bdc82..b26df1018965d797b9beca95a5e28124a2280f56 100644 (file)
@@ -12,6 +12,10 @@ import org.argeo.ArgeoException;
 import org.springframework.security.Authentication;
 import org.springframework.security.GrantedAuthority;
 
+/**
+ * Retrieves information about the current user. Not an API, can change without
+ * notice.
+ */
 public class CurrentUser {
        public final static String getUsername() {
                Subject subject = getSubject();
@@ -38,11 +42,9 @@ public class CurrentUser {
        }
 
        public final static Subject getSubject() {
-
                Subject subject = Subject.getSubject(AccessController.getContext());
                if (subject == null)
                        throw new ArgeoException("Not authenticated.");
                return subject;
-
        }
 }
diff --git a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/keyring/KeyringLoginModule.java b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/keyring/KeyringLoginModule.java
new file mode 100644 (file)
index 0000000..ba06f88
--- /dev/null
@@ -0,0 +1,66 @@
+package org.argeo.security.ui.keyring;
+
+import java.security.AccessController;
+import java.util.Map;
+import java.util.Set;
+
+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;
+
+import org.apache.commons.logging.LogConfigurationException;
+import org.argeo.util.crypto.PasswordBasedEncryption;
+
+public class KeyringLoginModule implements LoginModule {
+       private Subject subject;
+       private CallbackHandler callbackHandler;
+       private PasswordBasedEncryption passwordBasedEncryption;
+
+       public void initialize(Subject subject, CallbackHandler callbackHandler,
+                       Map<String, ?> sharedState, Map<String, ?> options) {
+               this.subject = subject;
+               if (subject == null) {
+                       subject = Subject.getSubject(AccessController.getContext());
+               }
+               this.callbackHandler = callbackHandler;
+       }
+
+       public boolean login() throws LoginException {
+               Set<PasswordBasedEncryption> pbes = subject
+                               .getPrivateCredentials(PasswordBasedEncryption.class);
+               if (pbes.size() > 0)
+                       return true;
+               PasswordCallback pc = new PasswordCallback("Master password", false);
+               Callback[] callbacks = { pc };
+               try {
+                       callbackHandler.handle(callbacks);
+                       passwordBasedEncryption = new PasswordBasedEncryption(
+                                       pc.getPassword());
+               } catch (Exception e) {
+                       throw new LogConfigurationException(e);
+               }
+               return true;
+       }
+
+       public boolean commit() throws LoginException {
+               if (passwordBasedEncryption != null)
+                       subject.getPrivateCredentials(PasswordBasedEncryption.class).add(
+                                       passwordBasedEncryption);
+               return true;
+       }
+
+       public boolean abort() throws LoginException {
+               return true;
+       }
+
+       public boolean logout() throws LoginException {
+               Set<PasswordBasedEncryption> pbes = subject
+                               .getPrivateCredentials(PasswordBasedEncryption.class);
+               pbes.clear();
+               return true;
+       }
+
+}
index 86a909483635f9b71516afc7040f7c63ec9072db..2dc0c2eb4f23c540a7ca56bdb51103ce8fe3b454 100644 (file)
@@ -7,8 +7,12 @@ public interface ArgeoNames {
 
        public final static String ARGEO_URI = "argeo:uri";
        public final static String ARGEO_USER_ID = "argeo:userID";
+       public final static String ARGEO_PREFERENCES = "argeo:preferences";
        public final static String ARGEO_DATA_MODEL_VERSION = "argeo:dataModelVersion";
 
+       public final static String ARGEO_REMOTE = "argeo:remote";
+       public final static String ARGEO_PASSWORD = "argeo:password";
+
        // user profile
        public final static String ARGEO_PROFILE = "argeo:profile";
        public final static String ARGEO_FIRST_NAME = "argeo:firstName";
@@ -19,4 +23,13 @@ public interface ArgeoNames {
        // tabular
        public final static String ARGEO_IS_KEY = "argeo:isKey";
 
+       // crypto
+       public final static String ARGEO_IV = "argeo:iv";
+       public final static String ARGEO_SECRET_KEY_FACTORY = "argeo:secretKeyFactory";
+       public final static String ARGEO_SALT = "argeo:salt";
+       public final static String ARGEO_ITERATION_COUNT = "argeo:iterationCount";
+       public final static String ARGEO_KEY_LENGTH = "argeo:keyLength";
+       public final static String ARGEO_SECRET_KEY_ENCRYPTION = "argeo:secretKeyEncryption";
+       public final static String ARGEO_CIPHER = "argeo:cipher";
+       public final static String ARGEO_KEYRING = "argeo:keyring";
 }
index 8dfab71daedd91cc79fd487e052327f0c8bc8d66..7416759b8fae8f0bc265d7f0032694cdab4e19a2 100644 (file)
@@ -5,9 +5,15 @@ public interface ArgeoTypes {
        public final static String ARGEO_LINK = "argeo:link";
        public final static String ARGEO_USER_HOME = "argeo:userHome";
        public final static String ARGEO_USER_PROFILE = "argeo:userProfile";
+       public final static String ARGEO_REMOTE_REPOSITORY = "argeo:remoteRepository";
 
        // tabular
        public final static String ARGEO_TABLE = "argeo:table";
        public final static String ARGEO_COLUMN = "argeo:column";
        public final static String ARGEO_CSV = "argeo:csv";
+
+       // crypto
+       public final static String ARGEO_ENCRYPTED = "argeo:encrypted";
+       public final static String ARGEO_PBE_SPEC = "argeo:pbeSpec";
+
 }
index 6dfb4a017f57da6870d9ad0a59c81ea11d3aae76..760e6600969e9e5666468e47afe1cd230be7bc1b 100644 (file)
@@ -16,6 +16,9 @@
 
 package org.argeo.jcr;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.text.DateFormat;
@@ -52,6 +55,7 @@ import javax.jcr.query.qom.QueryObjectModelFactory;
 import javax.jcr.query.qom.Selector;
 import javax.jcr.query.qom.StaticOperand;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.ArgeoException;
@@ -660,6 +664,43 @@ public class JcrUtils implements ArgeoJcrConstants {
                binary.dispose();
        }
 
+       /** Retrieve a {@link Binary} as a byte array */
+       public static byte[] getBinaryAsBytes(Property property) {
+               ByteArrayOutputStream out = new ByteArrayOutputStream();
+               InputStream in = null;
+               Binary binary = null;
+               try {
+                       binary = property.getBinary();
+                       in = binary.getStream();
+                       IOUtils.copy(in, out);
+                       return out.toByteArray();
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot read binary " + property
+                                       + " as bytes", e);
+               } finally {
+                       IOUtils.closeQuietly(out);
+                       IOUtils.closeQuietly(in);
+                       closeQuietly(binary);
+               }
+       }
+
+       /** Writes a {@link Binary} from a byte array */
+       public static void setBinaryAsBytes(Node node, String property, byte[] bytes) {
+               InputStream in = null;
+               Binary binary = null;
+               try {
+                       in = new ByteArrayInputStream(bytes);
+                       binary = node.getSession().getValueFactory().createBinary(in);
+                       node.setProperty(property, binary);
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot read binary " + property
+                                       + " as bytes", e);
+               } finally {
+                       IOUtils.closeQuietly(in);
+                       closeQuietly(binary);
+               }
+       }
+
        /**
         * Creates depth from a string (typically a username) by adding levels based
         * on its first characters: "aBcD",2 => a/aB
@@ -995,6 +1036,7 @@ public class JcrUtils implements ArgeoJcrConstants {
                        NodeIterator ni = node.getNodes();
                        while (ni.hasNext())
                                curNodeSize += getNodeApproxSize(ni.nextNode());
+                       log.debug(node + ": " + curNodeSize);
                        return curNodeSize;
                } catch (RepositoryException re) {
                        throw new ArgeoException(
diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrKeyring.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrKeyring.java
new file mode 100644 (file)
index 0000000..7d9baad
--- /dev/null
@@ -0,0 +1,218 @@
+package org.argeo.jcr.security;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+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.util.crypto.AbstractKeyring;
+import org.argeo.util.crypto.PBEKeySpecCallback;
+
+/** JCR based implementation of a keyring */
+public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
+       private Session session;
+
+       /**
+        * When setup is called the session has not yet been saved and we don't want
+        * to save it since there maybe other data which would be inconsistent. So
+        * we keep a reference to this node which will then be used (an reset to
+        * null) when handling the PBE callback. We keep one per thread in case
+        * multiple users are accessing the same instance of a keyring.
+        */
+       private ThreadLocal<Node> notYetSavedKeyring = new ThreadLocal<Node>() {
+
+               @Override
+               protected Node initialValue() {
+                       return null;
+               }
+       };
+
+       @Override
+       protected Boolean isSetup() {
+               try {
+                       if (notYetSavedKeyring.get() != null)
+                               return true;
+
+                       Node userHome = JcrUtils.getUserHome(session);
+                       return userHome.hasNode(ARGEO_KEYRING);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot check whether keyring is setup", e);
+               }
+       }
+
+       @Override
+       protected void setup() {
+               Binary binary = null;
+               InputStream in = null;
+               try {
+                       Node userHome = JcrUtils.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);
+
+                       Long iterationCount = username.length() * 200l;
+                       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, "PBKDF2WithHmacSHA1");
+                       keyring.setProperty(ARGEO_KEY_LENGTH, 256l);
+                       keyring.setProperty(ARGEO_SECRET_KEY_ENCRYPTION, "AES");
+                       keyring.setProperty(ARGEO_CIPHER, "AES/CBC/PKCS5Padding");
+
+                       notYetSavedKeyring.set(keyring);
+               } catch (RepositoryException 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 = JcrUtils.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 node must already exist at this path */
+       @Override
+       protected 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();
+                       if (!session.nodeExists(path))
+                               throw new ArgeoException("No node at " + path);
+                       Node node = session.getNode(path);
+                       node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED);
+                       cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+                       byte[] iv = cipher.getIV();
+                       if (iv != null) {
+                               JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv);
+                       }
+                       in = new CipherInputStream(unencrypted, cipher);
+                       binary = session.getValueFactory().createBinary(in);
+                       node.setProperty(Property.JCR_DATA, binary);
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot encrypt", e);
+               } finally {
+                       IOUtils.closeQuietly(in);
+                       IOUtils.closeQuietly(unencrypted);
+                       JcrUtils.closeQuietly(binary);
+               }
+       }
+
+       @Override
+       protected InputStream decrypt(String path) {
+               // should be called first for lazy initialization
+               SecretKey secretKey = getSecretKey();
+
+               Binary binary = null;
+               InputStream encrypted = null;
+
+               try {
+                       Cipher cipher = createCipher();
+                       if (!session.nodeExists(path))
+                               throw new ArgeoException("No node at " + path);
+                       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);
+                       JcrUtils.closeQuietly(binary);
+               }
+       }
+
+       protected Cipher createCipher() {
+               try {
+                       Node userHome = JcrUtils.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());
+                       return cipher;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot get cipher", e);
+               }
+       }
+
+       public void changePassword(char[] oldPassword, char[] newPassword) {
+               // TODO Auto-generated method stub
+
+       }
+
+       public Session getSession() {
+               return session;
+       }
+
+       public void setSession(Session session) {
+               this.session = session;
+       }
+
+}
index 8fa59aceb452dcfc0f647995bc2d3e2f7039c10b..315e2c244daad1ab65af2f395dd1c1ff2a1d7651 100644 (file)
@@ -12,11 +12,18 @@ mixin
 mixin
 - argeo:userID (STRING) m
 + argeo:profile (argeo:userProfile)
++ argeo:keyring (argeo:pbeSpec)
++ argeo:preferences (nt:unstructured)
 
 [argeo:userProfile] > mix:created, mix:lastModified, mix:title, mix:versionable
 mixin
 - argeo:userID (STRING) m
 
+[argeo:remoteRepository] > nt:unstructured
+- argeo:uri (STRING)
+- argeo:userID (STRING)
++ argeo:password (argeo:encrypted)
+
 // TABULAR CONTENT
 [argeo:table] > nt:file
 + * (argeo:column) *
@@ -25,3 +32,22 @@ mixin
 - jcr:requiredType (STRING) = 'STRING'
 
 [argeo:csv] > nt:resource
+
+// CRYPTO
+[argeo:encrypted] > nt:base
+mixin
+// initialization vector used by some algorithms
+- argeo:iv (BINARY)
+
+[argeo:pbeKeySpec] > nt:base
+mixin
+- argeo:secretKeyFactory (STRING)
+- argeo:salt (BINARY)
+- argeo:iterationCount (LONG)
+- argeo:keyLength (LONG)
+- argeo:secretKeyEncryption (STRING)
+
+[argeo:pbeSpec] > argeo:pbeKeySpec
+mixin
+- argeo:cipher (STRING)
+