X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.cms%2Fsrc%2Forg%2Fargeo%2Fcms%2Fsecurity%2FAbstractKeyring.java;h=779406a1dabc043172eabc4ab58682794d727bda;hb=088c1b517a543e935d8ab65c3b2fd2d0269b551d;hp=091de9e6333d063caf6c0082290ea5f0d0062b9e;hpb=77a5498dd5d10d2442127022efd6501a7dbddbae;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.cms/src/org/argeo/cms/security/AbstractKeyring.java b/org.argeo.cms/src/org/argeo/cms/security/AbstractKeyring.java index 091de9e63..779406a1d 100644 --- a/org.argeo.cms/src/org/argeo/cms/security/AbstractKeyring.java +++ b/org.argeo.cms/src/org/argeo/cms/security/AbstractKeyring.java @@ -25,7 +25,6 @@ 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; @@ -43,15 +42,16 @@ import javax.security.auth.login.LoginException; import org.apache.commons.io.IOUtils; import org.argeo.cms.CmsException; +import org.argeo.node.NodeConstants; import org.argeo.node.security.CryptoKeyring; import org.argeo.node.security.Keyring; import org.argeo.node.security.PBEKeySpecCallback; /** 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"; @@ -82,30 +82,32 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring { protected abstract InputStream decrypt(String path); /** Triggers lazy initialization */ - protected SecretKey getSecretKey() { + protected SecretKey getSecretKey(char[] password) { Subject subject = Subject.getSubject(AccessController.getContext()); // we assume only one secrete key is available - Iterator iterator = subject.getPrivateCredentials( - SecretKey.class).iterator(); - if (!iterator.hasNext()) {// not initialized - CallbackHandler callbackHandler = new KeyringCallbackHandler(); + Iterator 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(NodeConstants.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); + } 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 CmsException("More than one secret key in private credentials"); return secretKey; } } @@ -119,48 +121,55 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring { } public char[] getAsChars(String path) { - InputStream in = getAsStream(path); - CharArrayWriter writer = null; - Reader reader = null; - try { - writer = new CharArrayWriter(); - reader = new InputStreamReader(in, charset); + // 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);) { IOUtils.copy(reader, writer); return writer.toCharArray(); } catch (IOException e) { throw new CmsException("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); } 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 CmsException("Could not unlock keyring"); } - public void setLoginContextName(String loginContextName) { - this.loginContextName = loginContextName; + protected Provider getSecurityProvider() { + return Security.getProvider(securityProviderName); } public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) { @@ -175,32 +184,32 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring { 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 @@ -220,12 +229,11 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring { } 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)) @@ -238,31 +246,23 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring { 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); } @@ -276,4 +276,30 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring { } } + + 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); + } + + } }