Refactor cryptography and keyring
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 11 Oct 2012 18:38:04 +0000 (18:38 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 11 Oct 2012 18:38:04 +0000 (18:38 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@5599 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

29 files changed:
base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/AbstractKeyring.java [deleted file]
base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/Keyring.java [deleted file]
base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/KeyringLoginModule.java [deleted file]
base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/PBEKeySpecCallback.java [deleted file]
base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/PasswordBasedEncryption.java [deleted file]
base/runtime/org.argeo.util/src/main/java/org/argeo/util/security/Keyring.java [new file with mode: 0644]
base/runtime/org.argeo.util/src/test/java/org/argeo/util/crypto/PasswordBasedEncryptionTest.java [deleted file]
security/plugins/org.argeo.security.ui.rap/META-INF/jaas_default.txt
security/plugins/org.argeo.security.ui.rcp/META-INF/jaas_default.txt
security/runtime/org.argeo.security.core/pom.xml
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/crypto/AbstractKeyring.java [new file with mode: 0644]
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/crypto/KeyringLoginModule.java [new file with mode: 0644]
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/crypto/PBEKeySpecCallback.java [new file with mode: 0644]
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/crypto/PasswordBasedEncryption.java [new file with mode: 0644]
security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrKeyring.java [new file with mode: 0644]
security/runtime/org.argeo.security.core/src/test/java/org/argeo/security/crypto/ListBCCapabilities.java [new file with mode: 0644]
security/runtime/org.argeo.security.core/src/test/java/org/argeo/security/crypto/PasswordBasedEncryptionTest.java [new file with mode: 0644]
server/plugins/org.argeo.jcr.ui.explorer/META-INF/spring/commands.xml
server/plugins/org.argeo.jcr.ui.explorer/META-INF/spring/jcr.xml
server/plugins/org.argeo.jcr.ui.explorer/META-INF/spring/views.xml
server/plugins/org.argeo.jcr.ui.explorer/pom.xml
server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/browser/NodeContentProvider.java
server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/commands/AddRemoteRepository.java
server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/model/RemoteRepositoryNode.java
server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/model/RepositoriesNode.java
server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/views/GenericJcrBrowser.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 [deleted file]
server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/SecurityJcrUtils.java [deleted file]

diff --git a/base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/AbstractKeyring.java b/base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/AbstractKeyring.java
deleted file mode 100644 (file)
index e424513..0000000
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Mathieu Baudier
- *
- * 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.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.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 {
-       public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING";
-
-       private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT;
-       private CallbackHandler defaultCallbackHandler;
-
-       private String charset = "UTF-8";
-
-       /**
-        * Whether the keyring has already been created in the past with a master
-        * password
-        */
-       protected abstract Boolean isSetup();
-
-       /**
-        * Setup the keyring persistently, {@link #isSetup()} must return true
-        * afterwards
-        */
-       protected abstract void setup(char[] password);
-
-       /** Populates the key spec callback */
-       protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback);
-
-       protected abstract void encrypt(String path, InputStream unencrypted);
-
-       protected abstract InputStream decrypt(String path);
-
-       /** Triggers lazy initialization */
-       protected SecretKey getSecretKey() {
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               // we assume only one secrete key is available
-               Iterator<SecretKey> iterator = subject.getPrivateCredentials(
-                               SecretKey.class).iterator();
-               if (!iterator.hasNext()) {// not initialized
-                       CallbackHandler callbackHandler = new KeyringCallbackHandler();
-                       try {
-                               LoginContext loginContext = new LoginContext(loginContextName,
-                                               subject, callbackHandler);
-                               loginContext.login();
-                               // FIXME will login even if password is wrong
-                               iterator = subject.getPrivateCredentials(SecretKey.class)
-                                               .iterator();
-                               return iterator.next();
-                       } catch (LoginException e) {
-                               throw new ArgeoException("Keyring login failed", e);
-                       }
-
-               } else {
-                       SecretKey secretKey = iterator.next();
-                       if (iterator.hasNext())
-                               throw new ArgeoException(
-                                               "More than one secret key in private credentials");
-                       return secretKey;
-               }
-       }
-
-       public InputStream getAsStream(String path) {
-               return decrypt(path);
-       }
-
-       public void set(String path, InputStream in) {
-               encrypt(path, in);
-       }
-
-       public char[] getAsChars(String path) {
-               InputStream in = getAsStream(path);
-               CharArrayWriter writer = null;
-               Reader reader = null;
-               try {
-                       writer = new CharArrayWriter();
-                       reader = new InputStreamReader(in, charset);
-                       StreamUtils.copy(reader, writer);
-                       return writer.toCharArray();
-               } catch (IOException e) {
-                       throw new ArgeoException("Cannot decrypt to char array", e);
-               } finally {
-                       StreamUtils.closeQuietly(reader);
-                       StreamUtils.closeQuietly(in);
-                       StreamUtils.closeQuietly(writer);
-               }
-       }
-
-       public void set(String path, char[] arr) {
-               ByteArrayOutputStream out = new ByteArrayOutputStream();
-               ByteArrayInputStream in = null;
-               Writer writer = null;
-               try {
-                       writer = new OutputStreamWriter(out, charset);
-                       writer.write(arr);
-                       writer.flush();
-                       in = new ByteArrayInputStream(out.toByteArray());
-                       set(path, in);
-               } catch (IOException e) {
-                       throw new ArgeoException("Cannot encrypt to char array", e);
-               } finally {
-                       StreamUtils.closeQuietly(writer);
-                       StreamUtils.closeQuietly(out);
-                       StreamUtils.closeQuietly(in);
-               }
-       }
-
-       public void setLoginContextName(String loginContextName) {
-               this.loginContextName = loginContextName;
-       }
-
-       public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) {
-               this.defaultCallbackHandler = defaultCallbackHandler;
-       }
-
-       public void setCharset(String charset) {
-               this.charset = charset;
-       }
-
-       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);
-               }
-
-       }
-
-       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/base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/Keyring.java b/base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/Keyring.java
deleted file mode 100644 (file)
index eb876df..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Mathieu Baudier
- *
- * 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.crypto;
-
-import java.io.InputStream;
-
-/**
- * Access to private (typically encrypted) data. The keyring is responsible for
- * retrieving the necessary credentials.
- */
-public interface Keyring {
-       public void changePassword(char[] oldPassword, char[] newPassword);
-
-       public char[] getAsChars(String path);
-
-       public InputStream getAsStream(String path);
-
-       public void set(String path, char[] arr);
-
-       public void set(String path, InputStream in);
-}
diff --git a/base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/KeyringLoginModule.java b/base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/KeyringLoginModule.java
deleted file mode 100644 (file)
index e8b6928..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Mathieu Baudier
- *
- * 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.crypto;
-
-import java.security.AccessController;
-import java.util.Map;
-import java.util.Set;
-
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-
-/** Adds a secret key to the private credentials */
-public class KeyringLoginModule implements LoginModule {
-       private Subject subject;
-       private CallbackHandler callbackHandler;
-       private SecretKey secretKey;
-
-       public void initialize(Subject subject, CallbackHandler callbackHandler,
-                       Map<String, ?> sharedState, Map<String, ?> options) {
-               this.subject = subject;
-               if (subject == null) {
-                       subject = Subject.getSubject(AccessController.getContext());
-               }
-               this.callbackHandler = callbackHandler;
-       }
-
-       public boolean login() throws LoginException {
-               Set<SecretKey> pbes = subject.getPrivateCredentials(SecretKey.class);
-               if (pbes.size() > 0)
-                       return true;
-               PasswordCallback pc = new PasswordCallback("Master password", false);
-               PBEKeySpecCallback pbeCb = new PBEKeySpecCallback();
-               Callback[] callbacks = { pc, pbeCb };
-               try {
-                       callbackHandler.handle(callbacks);
-                       char[] password = pc.getPassword();
-
-                       SecretKeyFactory keyFac = SecretKeyFactory.getInstance(pbeCb
-                                       .getSecretKeyFactory());
-                       PBEKeySpec keySpec;
-                       if (pbeCb.getKeyLength() != null)
-                               keySpec = new PBEKeySpec(password, pbeCb.getSalt(),
-                                               pbeCb.getIterationCount(), pbeCb.getKeyLength());
-                       else
-                               keySpec = new PBEKeySpec(password, pbeCb.getSalt(),
-                                               pbeCb.getIterationCount());
-
-                       String secKeyEncryption = pbeCb.getSecretKeyEncryption();
-                       if (secKeyEncryption != null) {
-                               SecretKey tmp = keyFac.generateSecret(keySpec);
-                               secretKey = new SecretKeySpec(tmp.getEncoded(),
-                                               secKeyEncryption);
-                       } else {
-                               secretKey = keyFac.generateSecret(keySpec);
-                       }
-               } catch (Exception e) {
-                       LoginException le = new LoginException("Cannot login keyring");
-                       le.initCause(e);
-                       throw le;
-               }
-               return true;
-       }
-
-       public boolean commit() throws LoginException {
-               if (secretKey != null)
-                       subject.getPrivateCredentials().add(secretKey);
-               return true;
-       }
-
-       public boolean abort() throws LoginException {
-               return true;
-       }
-
-       public boolean logout() throws LoginException {
-               Set<PasswordBasedEncryption> pbes = subject
-                               .getPrivateCredentials(PasswordBasedEncryption.class);
-               pbes.clear();
-               return true;
-       }
-
-}
diff --git a/base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/PBEKeySpecCallback.java b/base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/PBEKeySpecCallback.java
deleted file mode 100644 (file)
index 676b970..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Mathieu Baudier
- *
- * 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.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/base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/PasswordBasedEncryption.java b/base/runtime/org.argeo.util/src/main/java/org/argeo/util/crypto/PasswordBasedEncryption.java
deleted file mode 100644 (file)
index 62cc240..0000000
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Mathieu Baudier
- *
- * 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.crypto;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-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;
-       public final static Integer DEFAULT_KEY_LENGTH = 256;
-       public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
-       public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
-       public final static String DEFAULT_CIPHER = "AES/CBC/PKCS5Padding";
-       public final static String DEFAULT_CHARSET = "UTF-8";
-
-       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 final Key key;
-       private final Cipher ecipher;
-       private final Cipher dcipher;
-
-       /**
-        * 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 {
-                       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);
-                       // for (int i = 0; i < password.length && i < iv.length; i++)
-                       // iv[i] = (byte) password[i];
-
-                       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());
-                       ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
-                       // AlgorithmParameters params = ecipher.getParameters();
-                       // byte[] iv =
-                       // params.getParameterSpec(IvParameterSpec.class).getIV();
-                       dcipher = Cipher.getInstance(getCipherName());
-                       dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot get secret key", e);
-               }
-       }
-
-       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 DEFAULT_ITERATION_COUNT;
-       }
-
-       protected Integer getKeyLength() {
-               return DEFAULT_KEY_LENGTH;
-       }
-
-       protected String getSecretKeyFactoryName() {
-               return DEFAULT_SECRETE_KEY_FACTORY;
-       }
-
-       protected String getSecretKeyEncryption() {
-               return DEFAULT_SECRETE_KEY_ENCRYPTION;
-       }
-
-       protected String getCipherName() {
-               return DEFAULT_CIPHER;
-       }
-}
diff --git a/base/runtime/org.argeo.util/src/main/java/org/argeo/util/security/Keyring.java b/base/runtime/org.argeo.util/src/main/java/org/argeo/util/security/Keyring.java
new file mode 100644 (file)
index 0000000..552d2ef
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2007-2012 Mathieu Baudier
+ *
+ * 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.InputStream;
+
+/**
+ * Access to private (typically encrypted) data. The keyring is responsible for
+ * retrieving the necessary credentials. <b>Experimental. This API may
+ * change.</b>
+ */
+public interface Keyring {
+       public void changePassword(char[] oldPassword, char[] newPassword);
+
+       /**
+        * Returns the confidential information as chars. Must ask for it if it is
+        * not stored.
+        */
+       public char[] getAsChars(String path);
+
+       /**
+        * Returns the confidential information as a stream. Must ask for it if it
+        * is not stored.
+        */
+       public InputStream getAsStream(String path);
+
+       public void set(String path, char[] arr);
+
+       public void set(String path, InputStream in);
+}
diff --git a/base/runtime/org.argeo.util/src/test/java/org/argeo/util/crypto/PasswordBasedEncryptionTest.java b/base/runtime/org.argeo.util/src/test/java/org/argeo/util/crypto/PasswordBasedEncryptionTest.java
deleted file mode 100644 (file)
index a424445..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Mathieu Baudier
- *
- * 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.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.argeo.StreamUtils;
-
-public class PasswordBasedEncryptionTest extends TestCase {
-       public void testEncryptDecrypt() {
-               final String password = "test long password since they are safer";
-               PasswordBasedEncryption pbeEnc = new PasswordBasedEncryption(
-                               password.toCharArray());
-               String message = "Hello World!";
-               System.out.println("Password:\t'" + password + "'");
-               System.out.println("Message:\t'" + message + "'");
-               byte[] encrypted = pbeEnc.encryptString(message);
-               System.out.println("Encrypted:\t'"
-                               + DatatypeConverter.printBase64Binary(encrypted) + "'");
-               PasswordBasedEncryption pbeDec = new PasswordBasedEncryption(
-                               password.toCharArray());
-               InputStream in = null;
-               in = new ByteArrayInputStream(encrypted);
-               String decrypted = pbeDec.decryptAsString(in);
-               System.out.println("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()));
-       }
-}
index 3829f93bb6087b7bcc02eca0c73b45cbc478976c..c74797b9366007fb0cf56014c28780f1f9d996d9 100644 (file)
@@ -19,5 +19,5 @@ SPRING_SECURITY_CONTEXT {
 };
 
 KEYRING {
-    org.argeo.util.crypto.KeyringLoginModule required;
+    org.argeo.security.crypto.KeyringLoginModule required;
 };
index 124d7ad9f57c93ec0cb70e4f8bd860a5fa6bdc18..16d476dafff26fc3494de9c65b7693565e2270d7 100644 (file)
@@ -23,5 +23,5 @@ REMOTE {
 };
 
 KEYRING {
-    org.argeo.util.crypto.KeyringLoginModule required;
+    org.argeo.security.crypto.KeyringLoginModule required;
 };
index abb559ad784a1a35dca62338352e569a6104a339..527d052fde198228223a3c9e0aaab58619dd9cfa 100644 (file)
                </dependency>
 
                <!-- Crypto -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.dep.osgi</groupId> -->
-               <!-- <artifactId>org.argeo.dep.osgi.bouncycastle.jdk15</artifactId> -->
-               <!-- </dependency> -->
+               <dependency>
+                       <groupId>org.argeo.tp</groupId>
+                       <artifactId>bcprov</artifactId>
+               </dependency>
                <dependency>
                        <groupId>org.argeo.tp</groupId>
                        <artifactId>org.apache.commons.codec</artifactId>
diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/crypto/AbstractKeyring.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/crypto/AbstractKeyring.java
new file mode 100644 (file)
index 0000000..b69fff9
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2007-2012 Mathieu Baudier
+ *
+ * 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 {
+       static {
+               Security.addProvider(new BouncyCastleProvider());
+       }
+
+       public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING";
+
+       private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT;
+       private CallbackHandler defaultCallbackHandler;
+
+       private String charset = "UTF-8";
+
+       /**
+        * Default provider is bouncy castle, in order to have consistent behaviour
+        * across implementations
+        */
+       private String securityProviderName = "BC";
+
+       /**
+        * Whether the keyring has already been created in the past with a master
+        * password
+        */
+       protected abstract Boolean isSetup();
+
+       /**
+        * Setup the keyring persistently, {@link #isSetup()} must return true
+        * afterwards
+        */
+       protected abstract void setup(char[] password);
+
+       /** Populates the key spec callback */
+       protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback);
+
+       protected abstract void encrypt(String path, InputStream unencrypted);
+
+       protected abstract InputStream decrypt(String path);
+
+       /** Triggers lazy initialization */
+       protected SecretKey getSecretKey() {
+               Subject subject = Subject.getSubject(AccessController.getContext());
+               // we assume only one secrete key is available
+               Iterator<SecretKey> iterator = subject.getPrivateCredentials(
+                               SecretKey.class).iterator();
+               if (!iterator.hasNext()) {// not initialized
+                       CallbackHandler callbackHandler = new KeyringCallbackHandler();
+                       try {
+                               LoginContext loginContext = new LoginContext(loginContextName,
+                                               subject, callbackHandler);
+                               loginContext.login();
+                               // FIXME will login even if password is wrong
+                               iterator = subject.getPrivateCredentials(SecretKey.class)
+                                               .iterator();
+                               return iterator.next();
+                       } catch (LoginException e) {
+                               throw new ArgeoException("Keyring login failed", e);
+                       }
+
+               } else {
+                       SecretKey secretKey = iterator.next();
+                       if (iterator.hasNext())
+                               throw new ArgeoException(
+                                               "More than one secret key in private credentials");
+                       return secretKey;
+               }
+       }
+
+       public InputStream getAsStream(String path) {
+               return decrypt(path);
+       }
+
+       public void set(String path, InputStream in) {
+               encrypt(path, in);
+       }
+
+       public char[] getAsChars(String path) {
+               InputStream in = getAsStream(path);
+               CharArrayWriter writer = null;
+               Reader reader = null;
+               try {
+                       writer = new CharArrayWriter();
+                       reader = new InputStreamReader(in, charset);
+                       StreamUtils.copy(reader, writer);
+                       return writer.toCharArray();
+               } catch (IOException e) {
+                       throw new ArgeoException("Cannot decrypt to char array", e);
+               } finally {
+                       StreamUtils.closeQuietly(reader);
+                       StreamUtils.closeQuietly(in);
+                       StreamUtils.closeQuietly(writer);
+               }
+       }
+
+       public void set(String path, char[] arr) {
+               ByteArrayOutputStream out = new ByteArrayOutputStream();
+               ByteArrayInputStream in = null;
+               Writer writer = null;
+               try {
+                       writer = new OutputStreamWriter(out, charset);
+                       writer.write(arr);
+                       writer.flush();
+                       in = new ByteArrayInputStream(out.toByteArray());
+                       set(path, in);
+               } catch (IOException e) {
+                       throw new ArgeoException("Cannot encrypt to char array", e);
+               } finally {
+                       StreamUtils.closeQuietly(writer);
+                       StreamUtils.closeQuietly(out);
+                       StreamUtils.closeQuietly(in);
+               }
+       }
+
+       protected Provider getSecurityProvider() {
+               return Security.getProvider(securityProviderName);
+       }
+
+       public void setLoginContextName(String loginContextName) {
+               this.loginContextName = loginContextName;
+       }
+
+       public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) {
+               this.defaultCallbackHandler = defaultCallbackHandler;
+       }
+
+       public void setCharset(String charset) {
+               this.charset = charset;
+       }
+
+       public void setSecurityProviderName(String securityProviderName) {
+               this.securityProviderName = securityProviderName;
+       }
+
+       @Deprecated
+       protected static byte[] hash(char[] password, byte[] salt,
+                       Integer iterationCount) {
+               ByteArrayOutputStream out = null;
+               OutputStreamWriter writer = null;
+               try {
+                       out = new ByteArrayOutputStream();
+                       writer = new OutputStreamWriter(out, "UTF-8");
+                       writer.write(password);
+                       MessageDigest pwDigest = MessageDigest.getInstance("SHA-256");
+                       pwDigest.reset();
+                       pwDigest.update(salt);
+                       byte[] btPass = pwDigest.digest(out.toByteArray());
+                       for (int i = 0; i < iterationCount; i++) {
+                               pwDigest.reset();
+                               btPass = pwDigest.digest(btPass);
+                       }
+                       return btPass;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot hash", e);
+               } finally {
+                       StreamUtils.closeQuietly(out);
+                       StreamUtils.closeQuietly(writer);
+               }
+
+       }
+
+       /**
+        * Convenience method using the underlying callback to ask for a password
+        * (typically used when the password is not saved in the keyring)
+        */
+       protected char[] ask() {
+               PasswordCallback passwordCb = new PasswordCallback("Password", false);
+               Callback[] dialogCbs = new Callback[] { passwordCb };
+               try {
+                       defaultCallbackHandler.handle(dialogCbs);
+                       char[] password = passwordCb.getPassword();
+                       return password;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot ask for a password", e);
+               }
+
+       }
+
+       class KeyringCallbackHandler implements CallbackHandler {
+               public void handle(Callback[] callbacks) throws IOException,
+                               UnsupportedCallbackException {
+                       // checks
+                       if (callbacks.length != 2)
+                               throw new IllegalArgumentException(
+                                               "Keyring required 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
+                       if (!(callbacks[0] instanceof PasswordCallback))
+                               throw new UnsupportedCallbackException(callbacks[0]);
+                       if (!(callbacks[1] instanceof PBEKeySpecCallback))
+                               throw new UnsupportedCallbackException(callbacks[0]);
+
+                       PasswordCallback passwordCb = (PasswordCallback) callbacks[0];
+                       PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
+
+                       if (isSetup()) {
+                               Callback[] dialogCbs = new Callback[] { passwordCb };
+                               defaultCallbackHandler.handle(dialogCbs);
+                       } else {// setup keyring
+                               TextOutputCallback textCb1 = new TextOutputCallback(
+                                               TextOutputCallback.INFORMATION,
+                                               "Enter a master password which will protect your private data");
+                               TextOutputCallback textCb2 = new TextOutputCallback(
+                                               TextOutputCallback.INFORMATION,
+                                               "(for example your credentials to third-party services)");
+                               TextOutputCallback textCb3 = new TextOutputCallback(
+                                               TextOutputCallback.INFORMATION,
+                                               "Don't forget this password since the data cannot be read without it");
+                               PasswordCallback confirmPasswordCb = new PasswordCallback(
+                                               "Confirm password", false);
+                               // first try
+                               Callback[] dialogCbs = new Callback[] { textCb1, textCb2,
+                                               textCb3, passwordCb, confirmPasswordCb };
+                               defaultCallbackHandler.handle(dialogCbs);
+
+                               // if passwords different, retry (except if cancelled)
+                               while (passwordCb.getPassword() != null
+                                               && !Arrays.equals(passwordCb.getPassword(),
+                                                               confirmPasswordCb.getPassword())) {
+                                       TextOutputCallback textCb = new TextOutputCallback(
+                                                       TextOutputCallback.ERROR,
+                                                       "The passwords do not match");
+                                       dialogCbs = new Callback[] { textCb, passwordCb,
+                                                       confirmPasswordCb };
+                                       defaultCallbackHandler.handle(dialogCbs);
+                               }
+
+                               if (passwordCb.getPassword() != null) {// not cancelled
+                                       setup(passwordCb.getPassword());
+                               }
+                       }
+
+                       if (passwordCb.getPassword() != null)
+                               handleKeySpecCallback(pbeCb);
+               }
+
+       }
+}
diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/crypto/KeyringLoginModule.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/crypto/KeyringLoginModule.java
new file mode 100644 (file)
index 0000000..24e0f1e
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007-2012 Mathieu Baudier
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.security.crypto;
+
+import java.security.AccessController;
+import java.util.Map;
+import java.util.Set;
+
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+/** Adds a secret key to the private credentials */
+public class KeyringLoginModule implements LoginModule {
+       private Subject subject;
+       private CallbackHandler callbackHandler;
+       private SecretKey secretKey;
+
+       public void initialize(Subject subject, CallbackHandler callbackHandler,
+                       Map<String, ?> sharedState, Map<String, ?> options) {
+               this.subject = subject;
+               if (subject == null) {
+                       subject = Subject.getSubject(AccessController.getContext());
+               }
+               this.callbackHandler = callbackHandler;
+       }
+
+       public boolean login() throws LoginException {
+               Set<SecretKey> pbes = subject.getPrivateCredentials(SecretKey.class);
+               if (pbes.size() > 0)
+                       return true;
+               PasswordCallback pc = new PasswordCallback("Master password", false);
+               PBEKeySpecCallback pbeCb = new PBEKeySpecCallback();
+               Callback[] callbacks = { pc, pbeCb };
+               try {
+                       callbackHandler.handle(callbacks);
+                       char[] password = pc.getPassword();
+
+                       SecretKeyFactory keyFac = SecretKeyFactory.getInstance(pbeCb
+                                       .getSecretKeyFactory());
+                       PBEKeySpec keySpec;
+                       if (pbeCb.getKeyLength() != null)
+                               keySpec = new PBEKeySpec(password, pbeCb.getSalt(),
+                                               pbeCb.getIterationCount(), pbeCb.getKeyLength());
+                       else
+                               keySpec = new PBEKeySpec(password, pbeCb.getSalt(),
+                                               pbeCb.getIterationCount());
+
+                       String secKeyEncryption = pbeCb.getSecretKeyEncryption();
+                       if (secKeyEncryption != null) {
+                               SecretKey tmp = keyFac.generateSecret(keySpec);
+                               secretKey = new SecretKeySpec(tmp.getEncoded(),
+                                               secKeyEncryption);
+                       } else {
+                               secretKey = keyFac.generateSecret(keySpec);
+                       }
+               } catch (Exception e) {
+                       LoginException le = new LoginException("Cannot login keyring");
+                       le.initCause(e);
+                       throw le;
+               }
+               return true;
+       }
+
+       public boolean commit() throws LoginException {
+               if (secretKey != null)
+                       subject.getPrivateCredentials().add(secretKey);
+               return true;
+       }
+
+       public boolean abort() throws LoginException {
+               return true;
+       }
+
+       public boolean logout() throws LoginException {
+               Set<PasswordBasedEncryption> pbes = subject
+                               .getPrivateCredentials(PasswordBasedEncryption.class);
+               pbes.clear();
+               return true;
+       }
+
+}
diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/crypto/PBEKeySpecCallback.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/crypto/PBEKeySpecCallback.java
new file mode 100644 (file)
index 0000000..1112123
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007-2012 Mathieu Baudier
+ *
+ * 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/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/crypto/PasswordBasedEncryption.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/crypto/PasswordBasedEncryption.java
new file mode 100644 (file)
index 0000000..63cdc6c
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2007-2012 Mathieu Baudier
+ *
+ * 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.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.argeo.ArgeoException;
+import org.argeo.StreamUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/** Simple password based encryption / decryption */
+public class PasswordBasedEncryption {
+       static {
+               Security.addProvider(new BouncyCastleProvider());
+       }
+
+       public final static Integer DEFAULT_ITERATION_COUNT = 1024;
+       public final static Integer DEFAULT_KEY_LENGTH = 256;
+       public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
+       public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
+       public final static String DEFAULT_CIPHER = "AES/CBC/PKCS5Padding";
+       public final static String DEFAULT_CHARSET = "UTF-8";
+
+       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 final Key key;
+       private final Cipher ecipher;
+       private final 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 {
+                       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);
+                       // for (int i = 0; i < password.length && i < iv.length; i++)
+                       // iv[i] = (byte) password[i];
+
+                       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));
+                       // AlgorithmParameters params = ecipher.getParameters();
+                       // byte[] iv =
+                       // params.getParameterSpec(IvParameterSpec.class).getIV();
+                       dcipher = Cipher.getInstance(getCipherName());
+                       dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot get secret key", e);
+               }
+       }
+
+       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 DEFAULT_ITERATION_COUNT;
+       }
+
+       protected Integer getKeyLength() {
+               return DEFAULT_KEY_LENGTH;
+       }
+
+       protected String getSecretKeyFactoryName() {
+               return DEFAULT_SECRETE_KEY_FACTORY;
+       }
+
+       protected String getSecretKeyEncryption() {
+               return DEFAULT_SECRETE_KEY_ENCRYPTION;
+       }
+
+       protected String getCipherName() {
+               return DEFAULT_CIPHER;
+       }
+
+       public void setSecurityProviderName(String securityProviderName) {
+               this.securityProviderName = securityProviderName;
+       }
+}
diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrKeyring.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/JcrKeyring.java
new file mode 100644 (file)
index 0000000..04974bd
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2007-2012 Mathieu Baudier
+ *
+ * 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 {
+       private Session session;
+
+       /**
+        * When setup is called the session has not yet been saved and we don't want
+        * to save it since there maybe other data which would be inconsistent. So
+        * we keep a reference to this node which will then be used (an reset to
+        * null) when handling the PBE callback. We keep one per thread in case
+        * multiple users are accessing the same instance of a keyring.
+        */
+       private ThreadLocal<Node> notYetSavedKeyring = new ThreadLocal<Node>() {
+
+               @Override
+               protected Node initialValue() {
+                       return null;
+               }
+       };
+
+       @Override
+       protected Boolean isSetup() {
+               try {
+                       if (notYetSavedKeyring.get() != null)
+                               return true;
+
+                       Node userHome = UserJcrUtils.getUserHome(session);
+                       return userHome.hasNode(ARGEO_KEYRING);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot check whether keyring is setup", e);
+               }
+       }
+
+       @Override
+       protected void setup(char[] password) {
+               Binary binary = null;
+               InputStream in = null;
+               try {
+                       Node userHome = UserJcrUtils.getUserHome(session);
+                       if (userHome.hasNode(ARGEO_KEYRING))
+                               throw new ArgeoException("Keyring already setup");
+                       Node keyring = userHome.addNode(ARGEO_KEYRING);
+                       keyring.addMixin(ArgeoTypes.ARGEO_PBE_SPEC);
+
+                       // deterministic salt and iteration count based on username
+                       String username = session.getUserID();
+                       byte[] salt = new byte[8];
+                       byte[] usernameBytes = username.getBytes();
+                       for (int i = 0; i < salt.length; i++) {
+                               if (i < usernameBytes.length)
+                                       salt[i] = usernameBytes[i];
+                               else
+                                       salt[i] = 0;
+                       }
+                       in = new ByteArrayInputStream(salt);
+                       binary = session.getValueFactory().createBinary(in);
+                       keyring.setProperty(ARGEO_SALT, binary);
+
+                       Integer iterationCount = username.length() * 200;
+                       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");
+
+                       // 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;
+       }
+}
\ No newline at end of file
diff --git a/security/runtime/org.argeo.security.core/src/test/java/org/argeo/security/crypto/ListBCCapabilities.java b/security/runtime/org.argeo.security.core/src/test/java/org/argeo/security/crypto/ListBCCapabilities.java
new file mode 100644 (file)
index 0000000..a95fba2
--- /dev/null
@@ -0,0 +1,43 @@
+package org.argeo.security.crypto;\r
+\r
+import java.security.Provider;\r
+import java.security.Security;\r
+import java.util.Iterator;\r
+\r
+import org.bouncycastle.jce.provider.BouncyCastleProvider;\r
+\r
+/**\r
+ * List the available capabilities for ciphers, key agreement, macs, message\r
+ * digests, signatures and other objects in the BC provider.\r
+ */\r
+public class ListBCCapabilities {\r
+       public static void main(String[] args) {\r
+               Security.addProvider(new BouncyCastleProvider());\r
+\r
+               Provider[] providers = Security.getProviders();\r
+               for (Provider provider : providers) {\r
+                       System.out.println(provider.getName());\r
+                       System.out.println(" " + provider.getVersion());\r
+                       System.out.println(" " + provider.getInfo());\r
+               }\r
+               Provider provider = Security.getProvider("BC");\r
+               // Provider provider = Security.getProvider(null);\r
+\r
+               Iterator it = provider.keySet().iterator();\r
+\r
+               while (it.hasNext()) {\r
+                       String entry = (String) it.next();\r
+\r
+                       // this indicates the entry refers to another entry\r
+\r
+                       if (entry.startsWith("Alg.Alias.")) {\r
+                               entry = entry.substring("Alg.Alias.".length());\r
+                       }\r
+\r
+                       String factoryClass = entry.substring(0, entry.indexOf('.'));\r
+                       String name = entry.substring(factoryClass.length() + 1);\r
+\r
+                       System.out.println(factoryClass + ": " + name);\r
+               }\r
+       }\r
+}\r
diff --git a/security/runtime/org.argeo.security.core/src/test/java/org/argeo/security/crypto/PasswordBasedEncryptionTest.java b/security/runtime/org.argeo.security.core/src/test/java/org/argeo/security/crypto/PasswordBasedEncryptionTest.java
new file mode 100644 (file)
index 0000000..bea9318
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2007-2012 Mathieu Baudier
+ *
+ * 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.argeo.StreamUtils;
+import org.argeo.security.crypto.PasswordBasedEncryption;
+
+public class PasswordBasedEncryptionTest extends TestCase {
+       public void testEncryptDecrypt() {
+               final String password = "test long password since they are safer";
+               PasswordBasedEncryption pbeEnc = new PasswordBasedEncryption(
+                               password.toCharArray());
+               String message = "Hello World!";
+               System.out.println("Password:\t'" + password + "'");
+               System.out.println("Message:\t'" + message + "'");
+               byte[] encrypted = pbeEnc.encryptString(message);
+               System.out.println("Encrypted:\t'"
+                               + DatatypeConverter.printBase64Binary(encrypted) + "'");
+               PasswordBasedEncryption pbeDec = new PasswordBasedEncryption(
+                               password.toCharArray());
+               InputStream in = null;
+               in = new ByteArrayInputStream(encrypted);
+               String decrypted = pbeDec.decryptAsString(in);
+               System.out.println("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()));
+       }
+}
index e3bad2892c67863294363d95c962470213b05560..5e4a185c73e16b84dbd2d5f9dd53e5f49e65ffdd 100644 (file)
 
        <bean id="addRemoteRepository" class="org.argeo.jcr.ui.explorer.commands.AddRemoteRepository">
                <property name="repositoryFactory" ref="repositoryFactory" />
-               <property name="keyring" ref="jcrKeyring" />
+               <property name="nodeRepository" ref="nodeRepository" />
+               <property name="keyring" ref="keyring" />
        </bean>
 
-       <bean id="removeRemoteRepository" class="org.argeo.jcr.ui.explorer.commands.RemoveRemoteRepository">
+       <bean id="removeRemoteRepository"
+               class="org.argeo.jcr.ui.explorer.commands.RemoveRemoteRepository">
        </bean>
 
        <bean id="addFolderNode" class="org.argeo.jcr.ui.explorer.commands.AddFolderNode"
index e83fb10531a37b7dbf4f6ecb5e957789954c3a3e..243ed50255f0b528fb12952f384be7009a689fa6 100644 (file)
@@ -9,7 +9,7 @@
                <property name="repository" ref="nodeRepository" />
        </bean>
 
-       <bean id="jcrKeyring" class="org.argeo.jcr.security.JcrKeyring">
+       <bean id="keyring" class="org.argeo.security.jcr.JcrKeyring">
                <property name="session" ref="nodeSession" />
                <property name="defaultCallbackHandler" ref="defaultCallbackHandler" />
        </bean>
index 84520ef550f6a70b1112d4835fd75acdc142780f..14f1943d7e7afdeef22926285bf2300b00998a5c 100644 (file)
@@ -7,8 +7,9 @@
        <!-- Views -->
        <bean id="browserView" class="org.argeo.jcr.ui.explorer.views.GenericJcrBrowser"
                scope="prototype">
-               <property name="jcrKeyring" ref="jcrKeyring" />
                <property name="repositoryRegister" ref="repositoryRegister" />
                <property name="repositoryFactory" ref="repositoryFactory" />
+               <property name="nodeRepository" ref="nodeRepository" />
+               <property name="keyring" ref="keyring" />
        </bean>
 </beans>
index 3f92ce1ab04a1cefb80a9d7ff39fd2550ea45f3f..4fdd06434dad5ea23b34729cc596d8bbe5842746 100644 (file)
@@ -1,4 +1,5 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
                <groupId>org.argeo.commons.server</groupId>
@@ -25,7 +26,6 @@
                                                <Import-Package>
                                                        *,
                                                        org.argeo.eclipse.spring,
-                                                       org.argeo.util.crypto,
                                                </Import-Package>
                                                <Export-Package>org.argeo.jcr.ui.explorer.*</Export-Package>
                                        </instructions>
                </plugins>
        </build>
        <dependencies>
+               <dependency>
+                       <groupId>org.argeo.commons.base</groupId>
+                       <artifactId>org.argeo.util</artifactId>
+                       <version>1.1.4-SNAPSHOT</version>
+               </dependency>
                <dependency>
                        <groupId>org.argeo.commons.base</groupId>
                        <artifactId>org.argeo.eclipse.ui.jcr</artifactId>
index 34466d301f2faa60132383cdec3365216e9c472e..c438463d85d734ac80279ff26b1f2b6a376ea877 100644 (file)
@@ -27,10 +27,10 @@ import org.argeo.eclipse.ui.TreeParent;
 import org.argeo.jcr.ArgeoJcrConstants;
 import org.argeo.jcr.RepositoryRegister;
 import org.argeo.jcr.UserJcrUtils;
-import org.argeo.jcr.security.JcrKeyring;
 import org.argeo.jcr.ui.explorer.model.RepositoriesNode;
 import org.argeo.jcr.ui.explorer.model.SingleJcrNode;
 import org.argeo.jcr.ui.explorer.utils.TreeObjectsComparator;
+import org.argeo.util.security.Keyring;
 import org.eclipse.jface.viewers.ITreeContentProvider;
 import org.eclipse.jface.viewers.Viewer;
 
@@ -40,14 +40,14 @@ import org.eclipse.jface.viewers.Viewer;
  * 
  */
 public class NodeContentProvider implements ITreeContentProvider {
-       // private final static Log log =
-       // LogFactory.getLog(NodeContentProvider.class);
-
-       // Business Objects
        final private RepositoryRegister repositoryRegister;
        final private RepositoryFactory repositoryFactory;
+       /**
+        * A session of the logged in user on the default workspace of the node
+        * repository.
+        */
        final private Session userSession;
-       final private JcrKeyring jcrKeyring;
+       final private Keyring keyring;
        final private boolean sortChildren;
 
        // reference for cleaning
@@ -57,11 +57,11 @@ public class NodeContentProvider implements ITreeContentProvider {
        // Utils
        private TreeObjectsComparator itemComparator = new TreeObjectsComparator();
 
-       public NodeContentProvider(JcrKeyring jcrKeyring,
+       public NodeContentProvider(Session userSession, Keyring keyring,
                        RepositoryRegister repositoryRegister,
                        RepositoryFactory repositoryFactory, Boolean sortChildren) {
-               this.userSession = jcrKeyring != null ? jcrKeyring.getSession() : null;
-               this.jcrKeyring = jcrKeyring;
+               this.userSession = userSession;
+               this.keyring = keyring;
                this.repositoryRegister = repositoryRegister;
                this.repositoryFactory = repositoryFactory;
                this.sortChildren = sortChildren;
@@ -85,7 +85,8 @@ public class NodeContentProvider implements ITreeContentProvider {
                        if (repositoriesNode != null)
                                repositoriesNode.dispose();
                        repositoriesNode = new RepositoriesNode("Repositories",
-                                       repositoryRegister, repositoryFactory, null, jcrKeyring);
+                                       repositoryRegister, repositoryFactory, null, userSession,
+                                       keyring);
                }
        }
 
index b7d4e030a2439b9267f66158146e3517b69a2a09..6187f40ee3ffdaa66ab35b7e143573db2697c5fb 100644 (file)
@@ -31,8 +31,8 @@ import org.argeo.jcr.ArgeoNames;
 import org.argeo.jcr.ArgeoTypes;
 import org.argeo.jcr.JcrUtils;
 import org.argeo.jcr.UserJcrUtils;
-import org.argeo.jcr.security.JcrKeyring;
 import org.argeo.jcr.ui.explorer.JcrExplorerConstants;
+import org.argeo.util.security.Keyring;
 import org.eclipse.core.commands.AbstractHandler;
 import org.eclipse.core.commands.ExecutionEvent;
 import org.eclipse.core.commands.ExecutionException;
@@ -62,7 +62,8 @@ public class AddRemoteRepository extends AbstractHandler implements
                JcrExplorerConstants, ArgeoNames {
 
        private RepositoryFactory repositoryFactory;
-       private JcrKeyring keyring;
+       private Repository nodeRepository;
+       private Keyring keyring;
 
        public Object execute(ExecutionEvent event) throws ExecutionException {
                RemoteRepositoryLoginDialog dlg = new RemoteRepositoryLoginDialog(
@@ -76,10 +77,14 @@ public class AddRemoteRepository extends AbstractHandler implements
                this.repositoryFactory = repositoryFactory;
        }
 
-       public void setKeyring(JcrKeyring keyring) {
+       public void setKeyring(Keyring keyring) {
                this.keyring = keyring;
        }
 
+       public void setNodeRepository(Repository nodeRepository) {
+               this.nodeRepository = nodeRepository;
+       }
+
        class RemoteRepositoryLoginDialog extends TitleAreaDialog {
                private Text name;
                private Text uri;
@@ -158,7 +163,7 @@ public class AddRemoteRepository extends AbstractHandler implements
                @Override
                protected void okPressed() {
                        try {
-                               Session nodeSession = keyring.getSession();
+                               Session nodeSession = nodeRepository.login();
                                Node home = UserJcrUtils.getUserHome(nodeSession);
 
                                Node remote = home.hasNode(ARGEO_REMOTE) ? home
@@ -171,11 +176,13 @@ public class AddRemoteRepository extends AbstractHandler implements
                                                ArgeoTypes.ARGEO_REMOTE_REPOSITORY);
                                remoteRepository.setProperty(ARGEO_URI, uri.getText());
                                remoteRepository.setProperty(ARGEO_USER_ID, username.getText());
-                               Node pwd = remoteRepository.addNode(ARGEO_PASSWORD);
-                               pwd.getSession().save();
-                               if (saveInKeyring.getSelection())
-                                       keyring.set(pwd.getPath(), password.getText().toCharArray());
-                               keyring.getSession().save();
+                               nodeSession.save();
+                               if (saveInKeyring.getSelection()) {
+                                       String pwdPath = remoteRepository.getPath() + '/'
+                                                       + ARGEO_PASSWORD;
+                                       keyring.set(pwdPath, password.getText().toCharArray());
+                               }
+                               nodeSession.save();
                                MessageDialog.openInformation(
                                                getParentShell(),
                                                "Repository Added",
index 8f71ace0a51b6f002b057c7c82552b3a84425200..19ca3990b2a00540f2b61e516753427c3c822c63 100644 (file)
@@ -26,28 +26,37 @@ import javax.jcr.SimpleCredentials;
 import org.argeo.ArgeoException;
 import org.argeo.eclipse.ui.TreeParent;
 import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.security.JcrKeyring;
+import org.argeo.util.security.Keyring;
 
 /** Root of a remote repository */
 public class RemoteRepositoryNode extends RepositoryNode {
-       private JcrKeyring jcrKeyring;
-       private String remoteNodePath;
+       private final Keyring keyring;
+       /**
+        * A session of the logged in user on the default workspace of the node
+        * repository.
+        */
+       private final Session userSession;
+       private final String remoteNodePath;
 
        public RemoteRepositoryNode(String alias, Repository repository,
-                       TreeParent parent, JcrKeyring jcrKeyring, String remoteNodePath) {
+                       TreeParent parent, Session userSession, Keyring keyring,
+                       String remoteNodePath) {
                super(alias, repository, parent);
-               this.jcrKeyring = jcrKeyring;
+               this.keyring = keyring;
+               this.userSession = userSession;
                this.remoteNodePath = remoteNodePath;
        }
 
        @Override
        protected Session repositoryLogin(String workspaceName)
                        throws RepositoryException {
-               Node remoteNode = jcrKeyring.getSession().getNode(remoteNodePath);
-               String userID = remoteNode.getProperty(ArgeoNames.ARGEO_USER_ID)
+               Node remoteRepository = userSession.getNode(remoteNodePath);
+               String userID = remoteRepository.getProperty(ArgeoNames.ARGEO_USER_ID)
                                .getString();
-               char[] password = jcrKeyring.getAsChars(remoteNodePath + "/"
-                               + ArgeoNames.ARGEO_PASSWORD);
+               String pwdPath = remoteRepository.getPath() + '/'
+                               + ArgeoNames.ARGEO_PASSWORD;
+               char[] password = keyring.getAsChars(pwdPath);
+
                try {
                        SimpleCredentials credentials = new SimpleCredentials(userID,
                                        password);
@@ -59,7 +68,7 @@ public class RemoteRepositoryNode extends RepositoryNode {
 
        public void remove() {
                try {
-                       Node remoteNode = jcrKeyring.getSession().getNode(remoteNodePath);
+                       Node remoteNode = userSession.getNode(remoteNodePath);
                        remoteNode.remove();
                        remoteNode.getSession().save();
                } catch (RepositoryException e) {
index 52ed4c2ef4bb1f56b61350cd95b421139bcf6ff3..fe82932354cc1bcfa850f43f06946adca60085b3 100644 (file)
@@ -32,7 +32,7 @@ import org.argeo.jcr.ArgeoJcrConstants;
 import org.argeo.jcr.ArgeoNames;
 import org.argeo.jcr.RepositoryRegister;
 import org.argeo.jcr.UserJcrUtils;
-import org.argeo.jcr.security.JcrKeyring;
+import org.argeo.util.security.Keyring;
 
 /**
  * UI Tree component. Implements the Argeo abstraction of a
@@ -49,15 +49,21 @@ public class RepositoriesNode extends TreeParent implements ArgeoNames {
        private final RepositoryRegister repositoryRegister;
        private final RepositoryFactory repositoryFactory;
 
-       private final JcrKeyring jcrKeyring;
+       /**
+        * A session of the logged in user on the default workspace of the node
+        * repository.
+        */
+       private final Session userSession;
+       private final Keyring keyring;
 
        public RepositoriesNode(String name, RepositoryRegister repositoryRegister,
                        RepositoryFactory repositoryFactory, TreeParent parent,
-                       JcrKeyring jcrKeyring) {
+                       Session userSession, Keyring keyring) {
                super(name);
                this.repositoryRegister = repositoryRegister;
                this.repositoryFactory = repositoryFactory;
-               this.jcrKeyring = jcrKeyring;
+               this.userSession = userSession;
+               this.keyring = keyring;
        }
 
        /**
@@ -78,9 +84,9 @@ public class RepositoriesNode extends TreeParent implements ArgeoNames {
                        }
 
                        // remote
-                       if (jcrKeyring != null) {
+                       if (keyring != null) {
                                try {
-                                       addRemoteRepositories(jcrKeyring);
+                                       addRemoteRepositories(keyring);
                                } catch (RepositoryException e) {
                                        throw new ArgeoException(
                                                        "Cannot browse remote repositories", e);
@@ -90,9 +96,8 @@ public class RepositoriesNode extends TreeParent implements ArgeoNames {
                }
        }
 
-       protected void addRemoteRepositories(JcrKeyring jcrKeyring)
+       protected void addRemoteRepositories(Keyring jcrKeyring)
                        throws RepositoryException {
-               Session userSession = jcrKeyring.getSession();
                Node userHome = UserJcrUtils.getUserHome(userSession);
                if (userHome != null && userHome.hasNode(ARGEO_REMOTE)) {
                        NodeIterator it = userHome.getNode(ARGEO_REMOTE).getNodes();
@@ -107,8 +112,8 @@ public class RepositoriesNode extends TreeParent implements ArgeoNames {
                                        Repository repository = repositoryFactory
                                                        .getRepository(params);
                                        RemoteRepositoryNode remoteRepositoryNode = new RemoteRepositoryNode(
-                                                       remoteNode.getName(), repository, this, jcrKeyring,
-                                                       remoteNode.getPath());
+                                                       remoteNode.getName(), repository, this,
+                                                       userSession, jcrKeyring, remoteNode.getPath());
                                        super.addChild(remoteRepositoryNode);
                                } catch (Exception e) {
                                        ErrorFeedback.show("Cannot add remote repository "
index 9ae1b0b6302ab21c2141b0401be91e7569d66e3c..5557d810914f1c0d73af4ca48e3dfaa558915518 100644 (file)
@@ -19,22 +19,21 @@ import java.util.List;
 
 import javax.jcr.Property;
 import javax.jcr.PropertyType;
+import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
 import javax.jcr.Value;
 import javax.jcr.observation.Event;
 import javax.jcr.observation.EventListener;
 import javax.jcr.observation.ObservationManager;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
 import org.argeo.ArgeoException;
 import org.argeo.eclipse.ui.TreeParent;
 import org.argeo.eclipse.ui.jcr.AsyncUiEventListener;
 import org.argeo.eclipse.ui.jcr.utils.NodeViewerComparer;
 import org.argeo.eclipse.ui.jcr.views.AbstractJcrBrowser;
 import org.argeo.jcr.RepositoryRegister;
-import org.argeo.jcr.security.JcrKeyring;
 import org.argeo.jcr.ui.explorer.JcrExplorerPlugin;
 import org.argeo.jcr.ui.explorer.browser.NodeContentProvider;
 import org.argeo.jcr.ui.explorer.browser.NodeLabelProvider;
@@ -42,6 +41,7 @@ import org.argeo.jcr.ui.explorer.browser.PropertiesContentProvider;
 import org.argeo.jcr.ui.explorer.model.SingleJcrNode;
 import org.argeo.jcr.ui.explorer.utils.GenericNodeDoubleClickListener;
 import org.argeo.jcr.ui.explorer.utils.JcrUiUtils;
+import org.argeo.util.security.Keyring;
 import org.eclipse.jface.action.MenuManager;
 import org.eclipse.jface.viewers.ColumnLabelProvider;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
@@ -65,16 +65,20 @@ import org.eclipse.swt.widgets.Menu;
  * Basic View to display a sash form to browse a JCR compliant multirepository
  * environment
  */
-
 public class GenericJcrBrowser extends AbstractJcrBrowser {
-       private final static Log log = LogFactory.getLog(GenericJcrBrowser.class);
        public final static String ID = JcrExplorerPlugin.ID + ".browserView";
        private boolean sortChildNodes = false;
 
        /* DEPENDENCY INJECTION */
-       private JcrKeyring jcrKeyring;
+       private Keyring keyring;
        private RepositoryRegister repositoryRegister;
        private RepositoryFactory repositoryFactory;
+       private Repository nodeRepository;
+       /**
+        * A session of the logged in user on the default workspace of the node
+        * repository.
+        */
+       private Session userSession;
 
        // This page widgets
        private TreeViewer nodesViewer;
@@ -82,35 +86,8 @@ public class GenericJcrBrowser extends AbstractJcrBrowser {
        private TableViewer propertiesViewer;
        private EventListener resultsObserver;
 
-       // Manage documents
-       // private JcrFileProvider jcrFileProvider;
-       // private FileHandler fileHandler;
-
        @Override
        public void createPartControl(Composite parent) {
-
-               // look for session
-               // Session nodeSession = jcrKeyring != null ? jcrKeyring.getSession()
-               // : null;
-               // if (nodeSession == null) {
-               // Repository nodeRepository = JcrUtils.getRepositoryByAlias(
-               // repositoryRegister, ArgeoJcrConstants.ALIAS_NODE);
-               // if (nodeRepository != null)
-               // try {
-               // nodeSession = nodeRepository.login();
-               // // TODO : enhance that to enable multirepository listener.
-               // } catch (RepositoryException e1) {
-               // throw new ArgeoException("Cannot login to node repository");
-               // }
-               // }
-
-               // Instantiate the generic object that fits for
-               // both RCP & RAP
-               // Note that in RAP, it registers a service handler that provide the
-               // access to the files.
-               // jcrFileProvider = new JcrFileProvider();
-               // fileHandler = new FileHandler(jcrFileProvider);
-
                parent.setLayout(new FillLayout());
                SashForm sashForm = new SashForm(parent, SWT.VERTICAL);
                sashForm.setSashWidth(4);
@@ -121,7 +98,13 @@ public class GenericJcrBrowser extends AbstractJcrBrowser {
                GridLayout gl = new GridLayout(1, false);
                top.setLayout(gl);
 
-               nodeContentProvider = new NodeContentProvider(jcrKeyring,
+               try {
+                       this.userSession = this.nodeRepository.login();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot open user session", e);
+               }
+
+               nodeContentProvider = new NodeContentProvider(userSession, keyring,
                                repositoryRegister, repositoryFactory, sortChildNodes);
 
                // nodes viewer
@@ -181,10 +164,9 @@ public class GenericJcrBrowser extends AbstractJcrBrowser {
                                });
 
                resultsObserver = new TreeObserver(tmpNodeViewer.getTree().getDisplay());
-               if (jcrKeyring != null)
+               if (keyring != null)
                        try {
-                               log.debug("userID=" + jcrKeyring.getSession().getUserID());
-                               ObservationManager observationManager = jcrKeyring.getSession()
+                               ObservationManager observationManager = userSession
                                                .getWorkspace().getObservationManager();
                                observationManager.addEventListener(resultsObserver,
                                                Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED, "/",
@@ -335,12 +317,16 @@ public class GenericJcrBrowser extends AbstractJcrBrowser {
                this.repositoryRegister = repositoryRegister;
        }
 
-       public void setJcrKeyring(JcrKeyring jcrKeyring) {
-               this.jcrKeyring = jcrKeyring;
+       public void setKeyring(Keyring keyring) {
+               this.keyring = keyring;
        }
 
        public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
                this.repositoryFactory = repositoryFactory;
        }
 
+       public void setNodeRepository(Repository nodeRepository) {
+               this.nodeRepository = nodeRepository;
+       }
+
 }
index 1ace83fcdde604c6d86aff5d8c346da1161570e5..91eaceeb206c08982d0dfe00dbe9ace64ad593d3 100644 (file)
@@ -113,6 +113,20 @@ public class JcrUtils implements ArgeoJcrConstants {
                return node;
        }
 
+       /** Retrieves the node name from the provided path */
+       public static String nodeNameFromPath(String path) {
+               if (path.equals("/"))
+                       return "";
+               if (path.charAt(0) != '/')
+                       throw new ArgeoException("Path " + path + " must start with a '/'");
+               String pathT = path;
+               if (pathT.charAt(pathT.length() - 1) == '/')
+                       pathT = pathT.substring(0, pathT.length() - 2);
+
+               int index = pathT.lastIndexOf('/');
+               return pathT.substring(index + 1);
+       }
+
        /** Retrieves the parent path of the provided path */
        public static String parentPath(String path) {
                if (path.equals("/"))
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
deleted file mode 100644 (file)
index a35bbd2..0000000
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Mathieu Baudier
- *
- * 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.InputStream;
-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.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 = 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() * 200;
-                       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");
-
-                       // 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 node must already exist at this path. Session is saved. */
-       @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;
-               // ByteArrayOutputStream out = null;
-               // OutputStream encrypted = null;
-
-               try {
-                       Cipher cipher = createCipher();
-                       if (!session.nodeExists(path))
-                               throw new ArgeoException("No node at " + path);
-                       if (session.hasPendingChanges())
-                               session.save();
-                       Node 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));
-                       // AlgorithmParameters params = cipher.getParameters();
-                       // byte[] iv =
-                       // params.getParameterSpec(IvParameterSpec.class).getIV();
-                       // if (iv != null)
-                       JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv);
-
-                       // out = new ByteArrayOutputStream();
-                       // // encrypted = new CipherOutputStream(out, cipher);
-                       // IOUtils.copy(unencrypted, out);
-                       // byte[] unenc = out.toByteArray();
-                       // byte[] crypted = cipher.doFinal(unenc);
-
-                       // Cipher decipher = createCipher();
-                       // decipher.init(Cipher.DECRYPT_MODE, secretKey, new
-                       // IvParameterSpec(
-                       // iv));
-                       // byte[] decrypted = decipher.doFinal(crypted);
-                       // System.out.println("Password :'" + new String(decrypted) + "'");
-
-                       // JcrUtils.setBinaryAsBytes(node, Property.JCR_DATA, crypted);
-
-                       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(out);
-                       // IOUtils.closeQuietly(encrypted);
-                       IOUtils.closeQuietly(unencrypted);
-                       IOUtils.closeQuietly(in);
-                       JcrUtils.closeQuietly(binary);
-               }
-       }
-
-       @Override
-       protected synchronized 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);
-                       }
-
-                       // byte[] arr = JcrUtils.getBinaryAsBytes(node
-                       // .getProperty(Property.JCR_DATA));
-                       // byte[] arr2 = cipher.doFinal(arr);
-                       //
-                       // return new ByteArrayInputStream(arr2);
-
-                       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 = 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());
-                       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 Session getSession() {
-               return session;
-       }
-
-       public synchronized void setSession(Session session) {
-               this.session = session;
-       }
-
-}
diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/SecurityJcrUtils.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/SecurityJcrUtils.java
deleted file mode 100644 (file)
index 90a8d87..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-package org.argeo.jcr.security;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.version.VersionManager;
-
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoJcrConstants;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.ArgeoTypes;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.UserJcrUtils;
-
-/** Utilities related to Argeo security model in JCR */
-@Deprecated
-public class SecurityJcrUtils implements ArgeoJcrConstants {
-       /**
-        * Creates an Argeo user home, does nothing if it already exists. Session is
-        * NOT saved.
-        */
-       static Node createUserHomeIfNeeded(Session session, String username) {
-               try {
-                       String homePath = generateUserHomePath(username);
-                       if (session.itemExists(homePath))
-                               return session.getNode(homePath);
-                       else {
-                               Node userHome = JcrUtils.mkdirs(session, homePath);
-                               userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
-                               userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username);
-
-                               // JcrUtils.addPrivilege(session, homePath, username,
-                               // "jcr:all");
-                               return userHome;
-                       }
-               } catch (RepositoryException e) {
-                       JcrUtils.discardQuietly(session);
-                       throw new ArgeoException("Cannot create home for " + username
-                                       + " in workspace " + session.getWorkspace().getName(), e);
-               }
-       }
-
-       private static String generateUserHomePath(String username) {
-               String homeBasePath = UserJcrUtils.DEFAULT_HOME_BASE_PATH;
-               return homeBasePath + '/' + JcrUtils.firstCharsToPath(username, 2)
-                               + '/' + username;
-       }
-
-       /**
-        * Creates a user profile in the home of this user. Creates the home if
-        * needed, but throw an exception if a profile already exists. The session
-        * is not saved and the node is in a checkedOut state (that is, it requires
-        * a subsequent checkin after saving the session).
-        */
-       static Node createUserProfile(Session session, String username) {
-               try {
-                       Node userHome = createUserHomeIfNeeded(session, username);
-                       if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE))
-                               throw new ArgeoException(
-                                               "There is already a user profile under " + userHome);
-                       Node userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE);
-                       userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE);
-                       userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username);
-                       userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true);
-                       userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, true);
-                       userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, true);
-                       userProfile.setProperty(ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED,
-                                       true);
-                       return userProfile;
-               } catch (RepositoryException e) {
-                       JcrUtils.discardQuietly(session);
-                       throw new ArgeoException("Cannot create user profile for "
-                                       + username + " in workspace "
-                                       + session.getWorkspace().getName(), e);
-               }
-       }
-
-       /**
-        * Create user profile if needed, the session IS saved.
-        * 
-        * @return the user profile
-        */
-       static Node createUserProfileIfNeeded(Session securitySession,
-                       String username) {
-               try {
-                       Node userHome = createUserHomeIfNeeded(securitySession, username);
-                       Node userProfile = userHome.hasNode(ArgeoNames.ARGEO_PROFILE) ? userHome
-                                       .getNode(ArgeoNames.ARGEO_PROFILE) : createUserProfile(
-                                       securitySession, username);
-                       if (securitySession.hasPendingChanges())
-                               securitySession.save();
-                       VersionManager versionManager = securitySession.getWorkspace()
-                                       .getVersionManager();
-                       if (versionManager.isCheckedOut(userProfile.getPath()))
-                               versionManager.checkin(userProfile.getPath());
-                       return userProfile;
-               } catch (RepositoryException e) {
-                       JcrUtils.discardQuietly(securitySession);
-                       throw new ArgeoException("Cannot create user profile for "
-                                       + username + " in workspace "
-                                       + securitySession.getWorkspace().getName(), e);
-               }
-       }
-
-       /**
-        * @return null if not found *
-        */
-       static Node getUserProfile(Session session, String username) {
-               try {
-                       Node userHome = UserJcrUtils.getUserHome(session, username);
-                       if (userHome == null)
-                               return null;
-                       if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE))
-                               return userHome.getNode(ArgeoNames.ARGEO_PROFILE);
-                       else
-                               return null;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException(
-                                       "Cannot find profile for user " + username, e);
-               }
-       }
-
-       private SecurityJcrUtils() {
-       }
-}