Multiple user referentials working with IPA.
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / security / AbstractKeyring.java
index 7f4e960cda0a9ce70e27dc411ccfddad4c848ab1..3de2e1451d89a5a7b5cafb16d2cbe54194d2e2aa 100644 (file)
@@ -1,18 +1,3 @@
-/*
- * 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;
@@ -24,8 +9,6 @@ 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;
@@ -41,17 +24,15 @@ import javax.security.auth.callback.UnsupportedCallbackException;
 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";
@@ -82,22 +63,25 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
        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();
+               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();
                                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);
                        }
@@ -105,7 +89,7 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
                } 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;
                }
        }
@@ -119,48 +103,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);
-                       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) {
@@ -175,31 +166,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
@@ -213,7 +205,7 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
                        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);
                }
 
        }
@@ -223,7 +215,7 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
                        // 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))
@@ -266,4 +258,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);
+               }
+
+       }
 }