-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
package org.argeo.cms.security;
import java.io.ByteArrayInputStream;
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 javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
-import org.apache.commons.io.IOUtils;
-import org.argeo.cms.CmsException;
-import org.argeo.node.security.CryptoKeyring;
-import org.argeo.node.security.Keyring;
-import org.argeo.node.security.PBEKeySpecCallback;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.util.CurrentSubject;
+import org.argeo.util.StreamUtils;
/** username / password based keyring. TODO internationalize */
public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
- public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING";
+ // public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING";
- private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT;
+ // private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT;
private CallbackHandler defaultCallbackHandler;
private String charset = "UTF-8";
protected abstract InputStream decrypt(String path);
/** Triggers lazy initialization */
- protected SecretKey getSecretKey() {
- Subject subject = Subject.getSubject(AccessController.getContext());
+ protected SecretKey getSecretKey(char[] password) {
+ Subject subject = CurrentSubject.current();
+ if (subject == null)
+ throw new IllegalStateException("Current subject cannot be null");
// 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();
+ Iterator<SecretKey> iterator = subject.getPrivateCredentials(SecretKey.class).iterator();
+ if (!iterator.hasNext() || password != null) {// not initialized
+ CallbackHandler callbackHandler = password == null ? new KeyringCallbackHandler()
+ : new PasswordProvidedCallBackHandler(password);
+ ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
try {
- LoginContext loginContext = new LoginContext(loginContextName,
- subject, callbackHandler);
+ LoginContext loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_KEYRING, subject, callbackHandler);
loginContext.login();
// FIXME will login even if password is wrong
- iterator = subject.getPrivateCredentials(SecretKey.class)
- .iterator();
+ iterator = subject.getPrivateCredentials(SecretKey.class).iterator();
return iterator.next();
} catch (LoginException e) {
- throw new CmsException("Keyring login failed", e);
+ throw new IllegalStateException("Keyring login failed", e);
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentContextClassLoader);
}
} else {
SecretKey secretKey = iterator.next();
if (iterator.hasNext())
- throw new CmsException(
- "More than one secret key in private credentials");
+ throw new IllegalStateException("More than one secret key in private credentials");
return secretKey;
}
}
}
public char[] getAsChars(String path) {
- InputStream in = getAsStream(path);
- CharArrayWriter writer = null;
- Reader reader = null;
- try {
- writer = new CharArrayWriter();
- reader = new InputStreamReader(in, charset);
- IOUtils.copy(reader, writer);
+ // InputStream in = getAsStream(path);
+ // CharArrayWriter writer = null;
+ // Reader reader = null;
+ try (InputStream in = getAsStream(path);
+ CharArrayWriter writer = new CharArrayWriter();
+ Reader reader = new InputStreamReader(in, charset);) {
+ StreamUtils.copy(reader, writer);
return writer.toCharArray();
} catch (IOException e) {
- throw new CmsException("Cannot decrypt to char array", e);
+ throw new IllegalStateException("Cannot decrypt to char array", e);
} finally {
- IOUtils.closeQuietly(reader);
- IOUtils.closeQuietly(in);
- IOUtils.closeQuietly(writer);
+ // IOUtils.closeQuietly(reader);
+ // IOUtils.closeQuietly(in);
+ // IOUtils.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);
+ // ByteArrayOutputStream out = new ByteArrayOutputStream();
+ // ByteArrayInputStream in = null;
+ // Writer writer = null;
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Writer writer = new OutputStreamWriter(out, charset);) {
+ // writer = new OutputStreamWriter(out, charset);
writer.write(arr);
writer.flush();
- in = new ByteArrayInputStream(out.toByteArray());
- set(path, in);
+ // in = new ByteArrayInputStream(out.toByteArray());
+ try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());) {
+ set(path, in);
+ }
} catch (IOException e) {
- throw new CmsException("Cannot encrypt to char array", e);
+ throw new IllegalStateException("Cannot encrypt to char array", e);
} finally {
- IOUtils.closeQuietly(writer);
- IOUtils.closeQuietly(out);
- IOUtils.closeQuietly(in);
+ // IOUtils.closeQuietly(writer);
+ // IOUtils.closeQuietly(out);
+ // IOUtils.closeQuietly(in);
}
}
- protected Provider getSecurityProvider() {
- return Security.getProvider(securityProviderName);
+ public void unlock(char[] password) {
+ if (!isSetup())
+ setup(password);
+ SecretKey secretKey = getSecretKey(password);
+ if (secretKey == null)
+ throw new IllegalStateException("Could not unlock keyring");
}
- public void setLoginContextName(String loginContextName) {
- this.loginContextName = loginContextName;
+ protected Provider getSecurityProvider() {
+ return Security.getProvider(securityProviderName);
}
public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) {
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 CmsException("Cannot hash", e);
- } finally {
- IOUtils.closeQuietly(out);
- IOUtils.closeQuietly(writer);
- }
-
- }
+ // @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 CmsException("Cannot hash", e);
+ // } finally {
+ // IOUtils.closeQuietly(out);
+ // IOUtils.closeQuietly(writer);
+ // }
+ //
+ // }
/**
* Convenience method using the underlying callback to ask for a password
char[] password = passwordCb.getPassword();
return password;
} catch (Exception e) {
- throw new CmsException("Cannot ask for a password", e);
+ throw new IllegalStateException("Cannot ask for a password", e);
}
}
class KeyringCallbackHandler implements CallbackHandler {
- public void handle(Callback[] callbacks) throws IOException,
- UnsupportedCallbackException {
+ 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}");
+ "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
if (!(callbacks[0] instanceof PasswordCallback))
throw new UnsupportedCallbackException(callbacks[0]);
if (!(callbacks[1] instanceof PBEKeySpecCallback))
Callback[] dialogCbs = new Callback[] { passwordCb };
defaultCallbackHandler.handle(dialogCbs);
} else {// setup keyring
- TextOutputCallback textCb1 = new TextOutputCallback(
- TextOutputCallback.INFORMATION,
+ TextOutputCallback textCb1 = new TextOutputCallback(TextOutputCallback.INFORMATION,
"Enter a master password which will protect your private data");
- TextOutputCallback textCb2 = new TextOutputCallback(
- TextOutputCallback.INFORMATION,
+ TextOutputCallback textCb2 = new TextOutputCallback(TextOutputCallback.INFORMATION,
"(for example your credentials to third-party services)");
- TextOutputCallback textCb3 = new TextOutputCallback(
- TextOutputCallback.INFORMATION,
+ 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);
+ PasswordCallback confirmPasswordCb = new PasswordCallback("Confirm password", false);
// first try
- Callback[] dialogCbs = new Callback[] { textCb1, textCb2,
- textCb3, passwordCb, confirmPasswordCb };
+ 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,
+ && !Arrays.equals(passwordCb.getPassword(), confirmPasswordCb.getPassword())) {
+ TextOutputCallback textCb = new TextOutputCallback(TextOutputCallback.ERROR,
"The passwords do not match");
- dialogCbs = new Callback[] { textCb, passwordCb,
- confirmPasswordCb };
+ dialogCbs = new Callback[] { textCb, passwordCb, confirmPasswordCb };
defaultCallbackHandler.handle(dialogCbs);
}
}
}
+
+ class PasswordProvidedCallBackHandler implements CallbackHandler {
+ private final char[] password;
+
+ public PasswordProvidedCallBackHandler(char[] password) {
+ this.password = password;
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ // checks
+ if (callbacks.length != 2)
+ throw new IllegalArgumentException(
+ "Keyring requires 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];
+ passwordCb.setPassword(password);
+ PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
+ handleKeySpecCallback(pbeCb);
+ }
+
+ }
}