+++ /dev/null
-/*
- * 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);
- }
-
- }
-}
+++ /dev/null
-/*
- * 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);
-}
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * 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;
- }
-}
--- /dev/null
+/*
+ * 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);
+}
+++ /dev/null
-/*
- * 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()));
- }
-}
};
KEYRING {
- org.argeo.util.crypto.KeyringLoginModule required;
+ org.argeo.security.crypto.KeyringLoginModule required;
};
};
KEYRING {
- org.argeo.util.crypto.KeyringLoginModule required;
+ org.argeo.security.crypto.KeyringLoginModule required;
};
</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>
--- /dev/null
+/*
+ * 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);
+ }
+
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+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
--- /dev/null
+/*
+ * 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()));
+ }
+}
<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"
<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>
<!-- 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>
-<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>
<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>
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;
*
*/
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
// 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;
if (repositoriesNode != null)
repositoriesNode.dispose();
repositoriesNode = new RepositoriesNode("Repositories",
- repositoryRegister, repositoryFactory, null, jcrKeyring);
+ repositoryRegister, repositoryFactory, null, userSession,
+ keyring);
}
}
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;
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(
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;
@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
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",
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);
public void remove() {
try {
- Node remoteNode = jcrKeyring.getSession().getNode(remoteNodePath);
+ Node remoteNode = userSession.getNode(remoteNodePath);
remoteNode.remove();
remoteNode.getSession().save();
} catch (RepositoryException e) {
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
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;
}
/**
}
// remote
- if (jcrKeyring != null) {
+ if (keyring != null) {
try {
- addRemoteRepositories(jcrKeyring);
+ addRemoteRepositories(keyring);
} catch (RepositoryException e) {
throw new ArgeoException(
"Cannot browse remote repositories", e);
}
}
- 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();
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 "
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;
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;
* 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;
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);
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
});
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, "/",
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;
+ }
+
}
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("/"))
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-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() {
- }
-}