You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by fm...@apache.org on 2013/11/01 11:42:17 UTC

svn commit: r1537892 - in /felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet: ConfigurationSupport.java OsgiManager.java OsgiManagerHttpContext.java Password.java

Author: fmeschbe
Date: Fri Nov  1 10:42:16 2013
New Revision: 1537892

URL: http://svn.apache.org/r1537892
Log:
FELIX-4299 Support encoded passwords and make sure the default
password is written in encoded form in the code such that when
storing such default configuration, no plain text password is written

Added:
    felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Password.java   (with props)
Modified:
    felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/ConfigurationSupport.java
    felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
    felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java

Modified: felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/ConfigurationSupport.java
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/ConfigurationSupport.java?rev=1537892&r1=1537891&r2=1537892&view=diff
==============================================================================
--- felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/ConfigurationSupport.java (original)
+++ felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/ConfigurationSupport.java Fri Nov  1 10:42:16 2013
@@ -20,6 +20,12 @@ package org.apache.felix.webconsole.inte
 
 
 import java.util.Dictionary;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
 import org.osgi.service.cm.ManagedService;
 
 
@@ -37,8 +43,65 @@ class ConfigurationSupport implements Ma
 
     //---------- ManagedService
 
-    public void updated( Dictionary config )
+    public void updated( Dictionary config ) throws ConfigurationException
+    {
+        // validate hashed password
+        if ( isPasswordHashed( config ) )
+        {
+            osgiManager.updateConfiguration( config );
+        }
+        else
+        {
+            // hash the password, update config and wait for the
+            // updated configuration to be supplied later
+            final BundleContext bc = this.osgiManager.getBundleContext();
+            final ServiceReference ref = bc.getServiceReference( ConfigurationAdmin.class.getName() );
+            if ( ref != null )
+            {
+                final ConfigurationAdmin ca = ( ConfigurationAdmin ) bc.getService( ref );
+                if ( ca != null )
+                {
+                    try
+                    {
+                        Configuration cfg = ca.getConfiguration( this.osgiManager.getConfigurationPid() );
+                        Dictionary newConfig = cfg.getProperties();
+                        if ( newConfig != null )
+                        {
+                            // assumption: config is not null and as a non-null password String property
+                            final String pwd = ( String ) config.get( OsgiManager.PROP_PASSWORD );
+                            final String hashedPassword = Password.hashPassword( pwd );
+                            newConfig.put( OsgiManager.PROP_PASSWORD, hashedPassword );
+                            cfg.update( newConfig );
+                        }
+                    }
+                    catch ( Exception e )
+                    {
+                        // IOException from getting/updated config
+                        // IllegalStateException from hashing password
+                        throw new ConfigurationException( OsgiManager.PROP_PASSWORD, "Cannot update password property",
+                            e );
+                    }
+                    finally
+                    {
+                        bc.ungetService( ref );
+                    }
+                }
+            }
+        }
+    }
+
+
+    private boolean isPasswordHashed( final Dictionary config )
     {
-        osgiManager.updateConfiguration( config );
+        // assume hashed (default) password if no config
+        if ( config == null )
+        {
+            return true;
+        }
+
+        // assume hashed (default) password if no password property
+        final Object pwd = config.get( OsgiManager.PROP_PASSWORD );
+        return (pwd instanceof String) && Password.isPasswordHashed((String) pwd);
     }
+
 }
\ No newline at end of file

Modified: felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java?rev=1537892&r1=1537891&r2=1537892&view=diff
==============================================================================
--- felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java (original)
+++ felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java Fri Nov  1 10:42:16 2013
@@ -159,7 +159,7 @@ public class OsgiManager extends Generic
 
     static final String DEFAULT_USER_NAME = "admin"; //$NON-NLS-1$
 
-    static final String DEFAULT_PASSWORD = "admin"; //$NON-NLS-1$
+    static final String DEFAULT_PASSWORD = "{sha-256}jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="; //$NON-NLS-1$
 
     static final String DEFAULT_CATEGORY = "Main"; //$NON-NLS-1$
 

Modified: felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java?rev=1537892&r1=1537891&r2=1537892&view=diff
==============================================================================
--- felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java (original)
+++ felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManagerHttpContext.java Fri Nov  1 10:42:16 2013
@@ -20,7 +20,6 @@ package org.apache.felix.webconsole.inte
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URL;
-
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -46,7 +45,7 @@ final class OsgiManagerHttpContext imple
 
     private final String username;
 
-    private final String password;
+    private final Password password;
 
     private final String realm;
 
@@ -56,7 +55,7 @@ final class OsgiManagerHttpContext imple
     {
         this.tracker = tracker;
         this.username = username;
-        this.password = password;
+        this.password = new Password(password);
         this.realm = realm;
         this.base = httpService.createDefaultHttpContext();
     }
@@ -124,13 +123,11 @@ final class OsgiManagerHttpContext imple
                 {
                     try
                     {
-                        String srcString = base64Decode( authInfo );
-                        int i = srcString.indexOf( ':' );
-                        String username = srcString.substring( 0, i );
-                        String password = srcString.substring( i + 1 );
+                        byte[][] userPass = base64Decode( authInfo );
+                        final String username = toString( userPass[0] );
 
                         // authenticate
-                        if ( authenticate( provider, username, password ) )
+                        if ( authenticate( provider, username, userPass[1] ) )
                         {
                             // as per the spec, set attributes
                             request.setAttribute( HttpContext.AUTHENTICATION_TYPE, HttpServletRequest.BASIC_AUTH );
@@ -189,27 +186,47 @@ final class OsgiManagerHttpContext imple
     }
 
 
-    private static String base64Decode( String srcString )
+    private static byte[][] base64Decode( String srcString )
     {
         byte[] transformed = Base64.decodeBase64( srcString );
+        for ( int i = 0; i < transformed.length; i++ )
+        {
+            if ( transformed[i] == ':' )
+            {
+                byte[] user = new byte[i];
+                byte[] pass = new byte[transformed.length - i - 1];
+                System.arraycopy( transformed, 0, user, 0, user.length );
+                System.arraycopy( transformed, i + 1, pass, 0, pass.length );
+                return new byte[][]
+                    { user, pass };
+            }
+        }
+
+        return new byte[][]
+            { transformed, new byte[0] };
+    }
+
+
+    private static String toString( final byte[] src )
+    {
         try
         {
-            return new String( transformed, "ISO-8859-1" );
+            return new String( src, "ISO-8859-1" );
         }
         catch ( UnsupportedEncodingException uee )
         {
-            return new String( transformed );
+            return new String( src );
         }
     }
 
 
-    private boolean authenticate( Object provider, String username, String password )
+    private boolean authenticate( Object provider, String username, byte[] password )
     {
         if ( provider != null )
         {
-            return ( ( WebConsoleSecurityProvider ) provider ).authenticate( username, password ) != null;
+            return ( ( WebConsoleSecurityProvider ) provider ).authenticate( username, toString( password ) ) != null;
         }
-        if ( this.username.equals( username ) && this.password.equals( password ) )
+        if ( this.username.equals( username ) && this.password.matches( password ) )
         {
             return true;
         }

Added: felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Password.java
URL: http://svn.apache.org/viewvc/felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Password.java?rev=1537892&view=auto
==============================================================================
--- felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Password.java (added)
+++ felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Password.java Fri Nov  1 10:42:16 2013
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.felix.webconsole.internal.servlet;
+
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+
+/**
+ * The <code>Password</code> class encapsulates encoding and decoding
+ * operations on plain text and hashed passwords.
+ * <p>
+ * Encoded hashed passwords are strings of the form
+ * <code>{hashAlgorithm}base64-encoded-password-hash</code> where
+ * <i>hashAlgorithm</i> is the name of the hash algorithm used to hash
+ * the password and <i>base64-encoded-password-hash</i> is the password
+ * hashed with the indicated hash algorithm and subsequently encoded in
+ * Base64.
+ */
+class Password
+{
+
+    // the default hash algorithm (part of the Java Platform since 1.4)
+    private static final String DEFAULT_HASH_ALGO = "SHA-256";
+
+    // the hash algorithm used to hash the password or null
+    // if the password is not hashed at all
+    private final String hashAlgo;
+
+    // the hashed or plain password
+    private final byte[] password;
+
+
+    /**
+     * Returns {@code true} if the given {@code textPassword} is hashed
+     * and encoded as described in the class comment.
+     *
+     * @param textPassword
+     * @return
+     * @throws NullPointerException if {@code textPassword} is {@code null}.
+     */
+    static boolean isPasswordHashed( final String textPassword )
+    {
+        return getEndOfHashAlgorithm( textPassword ) >= 0;
+    }
+
+
+    /**
+     * Returns the given plain {@code textPassword} as an encoded hashed
+     * password string as described in the class comment.
+     *
+     * @param textPassword
+     * @return
+     * @throws NullPointerException if {@code textPassword} is {@code null}.
+     */
+    static String hashPassword( final String textPassword )
+    {
+        final byte[] bytePassword = Base64.getBytesUtf8( textPassword );
+        return hashPassword( DEFAULT_HASH_ALGO, bytePassword );
+    }
+
+
+    Password( String textPassword )
+    {
+        this.hashAlgo = getPasswordHashAlgorithm( textPassword );
+        this.password = getPasswordBytes( textPassword );
+    }
+
+
+    /**
+     * Returns {@code true} if this password matches the password
+     * {@code toCompare}. If this password is hashed, the {@code toCompare}
+     * password is hashed, too, with the same hash algorithm before
+     * comparison.
+     *
+     * @param toCompare
+     * @return
+     * @throws NullPointerException if {@code toCompare} is {@code null}.
+     */
+    boolean matches( final byte[] toCompare )
+    {
+        return Arrays.equals( this.password, hashPassword( toCompare, this.hashAlgo ) );
+    }
+
+
+    /**
+     * Returns this password as a string hashed and encoded as described
+     * by the class comment. If this password has not been hashed originally,
+     * the default hash algorithm <i>SHA-256</i> is applied.
+     */
+    public String toString()
+    {
+        return hashPassword( this.hashAlgo, this.password );
+    }
+
+
+    private static String hashPassword( final String hashAlgorithm, final byte[] password )
+    {
+        final String actualHashAlgo = ( hashAlgorithm == null ) ? DEFAULT_HASH_ALGO : hashAlgorithm;
+        final byte[] hashedPassword = hashPassword( password, actualHashAlgo );
+        final StringBuffer buf = new StringBuffer( 2 + actualHashAlgo.length() + hashedPassword.length * 3 );
+        buf.append( '{' ).append( actualHashAlgo.toLowerCase() ).append( '}' );
+        buf.append( Base64.newStringUtf8( Base64.encodeBase64( hashedPassword ) ) );
+        return buf.toString();
+    }
+
+
+    private static String getPasswordHashAlgorithm( final String textPassword )
+    {
+        final int endHash = getEndOfHashAlgorithm( textPassword );
+        if ( endHash >= 0 )
+        {
+            return textPassword.substring( 1, endHash );
+        }
+
+        // password is plain text, hence no algorithm
+        return null;
+    }
+
+
+    private static byte[] getPasswordBytes( final String textPassword )
+    {
+        final int endHash = getEndOfHashAlgorithm( textPassword );
+        if ( endHash >= 0 )
+        {
+            final String encodedPassword = textPassword.substring( endHash + 1 );
+            return Base64.decodeBase64( encodedPassword );
+        }
+
+        return Base64.getBytesUtf8( textPassword );
+    }
+
+
+    private static int getEndOfHashAlgorithm( final String textPassword )
+    {
+        if ( textPassword.startsWith( "{" ) )
+        {
+            final int endHash = textPassword.indexOf( "}" );
+            if ( endHash > 0 )
+            {
+                return endHash;
+            }
+        }
+
+        return -1;
+    }
+
+
+    private static byte[] hashPassword( final byte[] pwd, final String hashAlg )
+    {
+        // no hashing if no hash algorithm
+        if ( hashAlg == null || hashAlg.length() == 0 )
+        {
+            return pwd;
+        }
+
+        try
+        {
+            final MessageDigest md = MessageDigest.getInstance( hashAlg );
+            return md.digest( pwd );
+        }
+        catch ( NoSuchAlgorithmException e )
+        {
+            throw new IllegalStateException( "Cannot hash the password: " + e );
+        }
+    }
+}

Propchange: felix/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/Password.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain