Repository: mina-sshd
Updated Branches:
  refs/heads/master 330d17c81 -> e12434d0d


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java
new file mode 100644
index 0000000..6421dc0
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntry.java
@@ -0,0 +1,335 @@
+/*
+ * 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.sshd.server.config.keys;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StreamCorruptedException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.NoCloseReader;
+import org.apache.sshd.server.PublickeyAuthenticator;
+
+/**
+ * Represents an entry in the user's {@code authorized_keys} file according
+ * to the <A 
HREF="http://en.wikibooks.org/wiki/OpenSSH/Client_Configuration_Files#.7E.2F.ssh.2Fauthorized_keys";>OpenSSH
 format</A>.
+ * <B>Note:</B> {@code equals/hashCode} check only the key type and data - the
+ * comment and/or login options are not considered part of equality
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class AuthorizedKeyEntry extends PublicKeyEntry {
+    private static final long serialVersionUID = -9007505285002809156L;
+
+    private String  comment;
+    // for options that have no value, "true" is used
+    private Map<String,String> 
loginOptions=Collections.<String,String>emptyMap();
+
+    public AuthorizedKeyEntry() {
+        super();
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String value) {
+        this.comment = value;
+    }
+
+    public Map<String,String> getLoginOptions() {
+        return loginOptions;
+    }
+
+    public void setLoginOptions(Map<String,String> value) {
+        if ((this.loginOptions=value) == null) {
+            this.loginOptions = Collections.<String,String>emptyMap();
+        }
+    }
+
+    @Override
+    public String toString() {
+        String   entry = super.toString();
+        String   kc = getComment();
+        Map<?,?> ko=getLoginOptions();
+        return (GenericUtils.isEmpty(ko) ? "" : ko.toString() + " ")
+                + entry
+                + (GenericUtils.isEmpty(kc) ? "" : " " + kc)
+                ;
+    }
+    
+    public static final PublickeyAuthenticator 
fromAuthorizedEntries(Collection<? extends AuthorizedKeyEntry> entries) throws 
IOException, GeneralSecurityException  {
+        Collection<PublicKey> keys = resolveAuthorizedKeys(entries); 
+        if (GenericUtils.isEmpty(keys)) {
+            return 
PublickeyAuthenticator.RejectAllPublickeyAuthenticator.INSTANCE;
+        } else {
+            return new 
PublickeyAuthenticator.KeySetPublickeyAuthenticator(keys);
+        }
+    }
+    
+    public static final List<PublicKey> resolveAuthorizedKeys(Collection<? 
extends AuthorizedKeyEntry> entries) throws IOException, 
GeneralSecurityException  {
+        if (GenericUtils.isEmpty(entries)) {
+            return Collections.emptyList();
+        }
+
+        List<PublicKey> keys = new ArrayList<PublicKey>(entries.size());
+        for (AuthorizedKeyEntry e : entries) {
+            PublicKey k = e.resolvePublicKey();
+            keys.add(k);
+        }
+        
+        return keys;
+    }
+
+    /**
+     * Standard OpenSSH authorized keys file name
+     */
+    public static final String  STD_AUTHORIZED_KEYS_FILENAME="authorized_keys";
+    private static final class LazyDefaultAuthorizedKeysFileHolder {
+        private static final File   keysFile=new 
File(PublicKeyEntry.getDefaultKeysFolder(), STD_AUTHORIZED_KEYS_FILENAME);
+    }
+
+    /**
+     * @return The default {@link File} location of the OpenSSH authorized 
keys file
+     */
+    @SuppressWarnings("synthetic-access")
+    public static final File getDefaultAuthorizedKeysFile() {
+        return LazyDefaultAuthorizedKeysFileHolder.keysFile;
+    }
+    /**
+     * Reads read the contents of the default OpenSSH 
<code>authorized_keys</code> file
+     * @return A {@link Collection} of all the {@link AuthorizedKeyEntry}-ies 
found there -
+     * or empty if file does not exist
+     * @throws IOException If failed to read keys from file
+     */
+    public static final Collection<AuthorizedKeyEntry> 
readDefaultAuthorizedKeys() throws IOException {
+        File    keysFile=getDefaultAuthorizedKeysFile();
+        if (keysFile.exists()) {
+            return readAuthorizedKeys(keysFile);
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Reads read the contents of an <code>authorized_keys</code> file
+     * @param url The {@link URL} to read from
+     * @return A {@link Collection} of all the {@link AuthorizedKeyEntry}-ies 
found there
+     * @throws IOException If failed to read or parse the entries
+     * @see #readAuthorizedKeys(InputStream, boolean)
+     */
+    public static final Collection<AuthorizedKeyEntry> readAuthorizedKeys(URL 
url) throws IOException {
+        return readAuthorizedKeys(url.openStream(), true);
+    }
+
+    /**
+     * Reads read the contents of an <code>authorized_keys</code> file
+     * @param file The {@link File} to read from
+     * @return A {@link Collection} of all the {@link AuthorizedKeyEntry}-ies 
found there
+     * @throws IOException If failed to read or parse the entries
+     * @see #readAuthorizedKeys(InputStream, boolean)
+     */
+    public static final Collection<AuthorizedKeyEntry> readAuthorizedKeys(File 
file) throws IOException {
+        return readAuthorizedKeys(new FileInputStream(file), true);
+    }
+
+    /**
+     * Reads read the contents of an <code>authorized_keys</code> file
+     * @param path {@link Path} to read from
+     * @param options The {@link OpenOption}s to use - if unspecified then 
appropriate
+     * defaults assumed 
+     * @return A {@link Collection} of all the {@link AuthorizedKeyEntry}-ies 
found there
+     * @throws IOException If failed to read or parse the entries
+     * @see #readAuthorizedKeys(InputStream, boolean)
+     * @see Files#newInputStream(Path, OpenOption...)
+     */
+    public static final Collection<AuthorizedKeyEntry> readAuthorizedKeys(Path 
path, OpenOption ... options) throws IOException {
+        return readAuthorizedKeys(Files.newInputStream(path, options), true);
+    }
+
+    /**
+     * Reads read the contents of an <code>authorized_keys</code> file
+     * @param filePath The file path to read from
+     * @return A {@link Collection} of all the {@link AuthorizedKeyEntry}-ies 
found there
+     * @throws IOException If failed to read or parse the entries
+     * @see #readAuthorizedKeys(InputStream, boolean)
+     */
+    public static final Collection<AuthorizedKeyEntry> 
readAuthorizedKeys(String filePath) throws IOException {
+        return readAuthorizedKeys(new FileInputStream(filePath), true);
+    }
+
+    /**
+     * Reads read the contents of an <code>authorized_keys</code> file
+     * @param in The {@link InputStream}
+     * @param okToClose <code>true</code> if method may close the input stream
+     * regardless of whether successful or failed
+     * @return A {@link Collection} of all the {@link AuthorizedKeyEntry}-ies 
found there
+     * @throws IOException If failed to read or parse the entries
+     * @see #readAuthorizedKeys(Reader, boolean)
+     */
+    public static final Collection<AuthorizedKeyEntry> 
readAuthorizedKeys(InputStream in, boolean okToClose) throws IOException {
+        try(Reader  rdr=new 
InputStreamReader(NoCloseInputStream.resolveInputStream(in, okToClose), 
StandardCharsets.UTF_8)) {
+            return readAuthorizedKeys(rdr, true);
+        }
+    }
+
+    /**
+     * Reads read the contents of an <code>authorized_keys</code> file
+     * @param rdr The {@link Reader}
+     * @param okToClose <code>true</code> if method may close the input stream
+     * regardless of whether successful or failed
+     * @return A {@link Collection} of all the {@link AuthorizedKeyEntry}-ies 
found there
+     * @throws IOException If failed to read or parse the entries
+     * @see #readAuthorizedKeys(BufferedReader)
+     */
+    public static final Collection<AuthorizedKeyEntry> 
readAuthorizedKeys(Reader rdr, boolean okToClose) throws IOException {
+        try(BufferedReader  buf=new 
BufferedReader(NoCloseReader.resolveReader(rdr, okToClose))) {
+            return readAuthorizedKeys(buf);
+        }
+    }
+
+    /**
+     * @param rdr The {@link BufferedReader} to use to read the contents of
+     * an <code>authorized_keys</code> file
+     * @return A {@link Collection} of all the {@link AuthorizedKeyEntry}-ies 
found there
+     * @throws IOException If failed to read or parse the entries
+     * @see #parseAuthorizedKeyEntry(String)
+     */
+    public static final Collection<AuthorizedKeyEntry> 
readAuthorizedKeys(BufferedReader rdr) throws IOException {
+        Collection<AuthorizedKeyEntry>  entries=null;
+
+        for (String line=rdr.readLine(); line != null; line=rdr.readLine()) {
+            final AuthorizedKeyEntry  entry;
+            try {
+                if ((entry=parseAuthorizedKeyEntry(line.trim())) == null) {
+                    continue;
+                }
+            } catch(IllegalArgumentException e) {
+                throw new StreamCorruptedException(e.getMessage());
+            }
+
+            if (entries == null) {
+                entries = new LinkedList<AuthorizedKeyEntry>();
+            }
+
+            entries.add(entry);
+        }
+
+        if (entries == null) {
+            return Collections.emptyList();
+        } else {
+            return entries;
+        }
+    }
+
+    /**
+     * @param line Original line from an <code>authorized_keys</code> file
+     * @return {@link AuthorizedKeyEntry} or <code>null</code> if the line is
+     * <code>null</code>/empty or a comment line
+     * @throws IllegalArgumentException If failed to parse/decode the line
+     * @see #COMMENT_CHAR
+     */
+    public static final AuthorizedKeyEntry parseAuthorizedKeyEntry(String 
line) throws IllegalArgumentException {
+        if (GenericUtils.isEmpty(line) || (line.charAt(0) == COMMENT_CHAR) /* 
comment ? */) {
+            return null;
+        }
+
+        int startPos=line.indexOf(' ');
+        if (startPos <= 0) {
+            throw new IllegalArgumentException("Bad format (no key data 
delimiter): " + line);
+        }
+
+        int endPos=line.indexOf(' ', startPos + 1);
+        if (endPos <= startPos) {
+            endPos = line.length();
+        }
+
+        String keyType = line.substring(0, startPos);
+        PublicKeyEntryDecoder<? extends PublicKey> decoder = 
KeyUtils.getPublicKeyEntryDecoder(keyType);
+        final AuthorizedKeyEntry    entry;
+        if (decoder == null) {  // assume this is due to the fact that it 
starts with login options
+            if ((entry=parseAuthorizedKeyEntry(line.substring(startPos + 
1).trim())) == null) {
+                throw new IllegalArgumentException("Bad format (no key data 
after login options): " + line);
+            }
+
+            entry.setLoginOptions(parseLoginOptions(keyType));
+        } else {
+            String encData = (endPos < (line.length() - 1)) ? 
line.substring(0, endPos).trim() : line;
+            String comment = (endPos < (line.length() - 1)) ? 
line.substring(endPos + 1).trim() : null;
+            entry = parsePublicKeyEntry(new AuthorizedKeyEntry(), encData);
+            entry.setComment(comment);
+        }
+
+        return entry;
+    }
+    
+    public static final Map<String,String> parseLoginOptions(String options) {
+        // TODO add support if quoted values contain ','
+        String[]    pairs=GenericUtils.split(options, ',');
+        if (GenericUtils.isEmpty(pairs)) {
+            return Collections.emptyMap();
+        }
+        
+        Map<String,String>  optsMap=new TreeMap<String, 
String>(String.CASE_INSENSITIVE_ORDER);
+        for (String p : pairs) {
+            p = GenericUtils.trimToEmpty(p);
+            if (GenericUtils.isEmpty(p)) {
+                continue;
+            }
+            
+            int             pos=p.indexOf('=');
+            String          name=(pos < 0) ? p : 
GenericUtils.trimToEmpty(p.substring(0, pos));
+            CharSequence    value=(pos < 0) ? null : 
GenericUtils.trimToEmpty(p.substring(pos + 1));
+            value = GenericUtils.stripQuotes(value);
+            if (value == null) {
+                value = Boolean.TRUE.toString();
+            }
+            
+            String  prev=optsMap.put(name, value.toString());
+            if (prev != null) {
+                throw new IllegalArgumentException("Multiple values for key=" 
+ name + ": old=" + prev + ", new=" + value);
+            }
+        }
+        
+        return optsMap;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java
new file mode 100644
index 0000000..177686e
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticator.java
@@ -0,0 +1,126 @@
+/*
+ * 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.sshd.server.config.keys;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.IoUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.ModifiableFileWatcher;
+import org.apache.sshd.server.PublickeyAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * Uses the authorized keys file to implement {@link PublickeyAuthenticator}
+ * while automatically re-loading the keys if the file has changed when a
+ * new authentication request is received. <B>Note:</B> by default, the only
+ * validation of the username is that it is not {@code null}/empty - see
+ * {@link #isValidUsername(String, ServerSession)}
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class AuthorizedKeysAuthenticator extends ModifiableFileWatcher 
implements PublickeyAuthenticator {
+    private final AtomicReference<PublickeyAuthenticator> delegateHolder =  // 
assumes initially reject-all
+            new 
AtomicReference<PublickeyAuthenticator>(PublickeyAuthenticator.RejectAllPublickeyAuthenticator.INSTANCE);
+
+    public AuthorizedKeysAuthenticator(File file) {
+        this(ValidateUtils.checkNotNull(file, "No file to watch", 
GenericUtils.EMPTY_OBJECT_ARRAY).toPath());
+    }
+
+    public AuthorizedKeysAuthenticator(Path file) {
+        this(file, IoUtils.getLinkOptions(false));
+    }
+
+    public AuthorizedKeysAuthenticator(Path file, LinkOption... options) {
+        super(file, options);
+    }
+
+    @Override
+    public boolean authenticate(String username, PublicKey key, ServerSession 
session) {
+        if (!isValidUsername(username, session)) {
+            if (log.isDebugEnabled()) {
+                log.debug("authenticate(" + username + ")[" + session + "][" + 
key.getAlgorithm() + "] invalid user name - file = " + getPath());
+            }
+            return false;
+        }
+
+        try {
+            PublickeyAuthenticator delegate =
+                    
ValidateUtils.checkNotNull(resolvePublickeyAuthenticator(username, session), 
"No delegate", GenericUtils.EMPTY_OBJECT_ARRAY);
+            boolean accepted = delegate.authenticate(username, key, session);
+            if (log.isDebugEnabled()) {
+                log.debug("authenticate(" + username + ")[" + session + "][" + 
key.getAlgorithm() + "] accepted " + accepted + " from " + getPath());
+            }
+            
+            return accepted;
+        } catch(Exception e) {
+            if (log.isDebugEnabled()) {
+                log.debug("authenticate(" + username + ")[" + session + "][" + 
getPath() + "]"
+                        + " failed (" + e.getClass().getSimpleName() + ")"
+                        + " to resolve delegate: " + e.getMessage());
+            }
+            
+            return false;
+        }
+    }
+
+    protected boolean isValidUsername(String username, ServerSession session) {
+        if (GenericUtils.isEmpty(username)) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    protected PublickeyAuthenticator resolvePublickeyAuthenticator(String 
username, ServerSession session) throws IOException, GeneralSecurityException {
+        if (checkReloadRequired()) {
+            /* Start fresh - NOTE: if there is any error then we want to 
reject all attempts
+             * since we don't want to remain with the previous data - safer 
that way
+             */
+            
delegateHolder.set(PublickeyAuthenticator.RejectAllPublickeyAuthenticator.INSTANCE);
+
+            Path path = getPath();
+            if (exists()) {
+                Collection<AuthorizedKeyEntry> entries = 
reloadAuthorizedKeys(path, username, session);
+                if (GenericUtils.size(entries) > 0) {
+                    
delegateHolder.set(AuthorizedKeyEntry.fromAuthorizedEntries(entries));
+                }
+            } else {
+                log.info("resolvePublickeyAuthenticator(" + username + ")[" + 
session + "] no authorized keys file at " + path);
+            }
+        }
+        
+        return delegateHolder.get();
+    }
+    
+    protected Collection<AuthorizedKeyEntry> reloadAuthorizedKeys(Path path, 
String username, ServerSession session) throws IOException {
+        Collection<AuthorizedKeyEntry> entries = 
AuthorizedKeyEntry.readAuthorizedKeys(path, options);
+        log.info("reloadAuthorizedKeys(" + username + ")[" + session + "] 
loaded " + GenericUtils.size(entries) + " keys from " + path);
+        updateReloadAttributes();
+        return entries;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
new file mode 100644
index 0000000..6bad436
--- /dev/null
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticator.java
@@ -0,0 +1,161 @@
+/*
+ * 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.sshd.server.config.keys;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystemException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.IoUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.server.session.ServerSession;
+
+/**
+ * Monitors the {@code ~/.ssh/authorized_keys} file of the user currently 
running
+ * the server, re-loading it if necessary. It also (optionally) enforces the 
same
+ * permissions regime as {@code OpenSSH} does for the file permissions. By 
default
+ * also compares the current username with the authenticated one.
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class DefaultAuthorizedKeysAuthenticator extends 
AuthorizedKeysAuthenticator {
+    /**
+     * The {@link Set} of {@link PosixFilePermission} <U>not</U> allowed if 
strict
+     * permissions are enforced
+     */
+    public static final Set<PosixFilePermission> 
STRICTLY_PROHIBITED_FILE_PERMISSION =
+            Collections.unmodifiableSet(
+                    EnumSet.of(PosixFilePermission.GROUP_READ, 
PosixFilePermission.GROUP_WRITE, PosixFilePermission.GROUP_EXECUTE,
+                               PosixFilePermission.OTHERS_READ, 
PosixFilePermission.OTHERS_WRITE, PosixFilePermission.OTHERS_EXECUTE));
+
+    /**
+     * The default instance that enforces the same permissions regime as 
{@code OpenSSH}
+     */
+    public static final DefaultAuthorizedKeysAuthenticator INSTANCE = new 
DefaultAuthorizedKeysAuthenticator(true);
+
+    private final boolean strict;
+    private final String user;
+
+    /**
+     * @param strict If {@code true} then makes sure that the containing folder
+     * has 0700 access and the file 0600. <B>Note:</B> for <I>Windows</I> it
+     * does not check these permissions 
+     */
+    public DefaultAuthorizedKeysAuthenticator(boolean strict) {
+        this(System.getProperty("user.name"), strict);
+    }
+
+    public DefaultAuthorizedKeysAuthenticator(String user, boolean strict) {
+        this(user, AuthorizedKeyEntry.getDefaultAuthorizedKeysFile(), strict);
+    }
+
+    public DefaultAuthorizedKeysAuthenticator(File file, boolean strict) {
+        this(ValidateUtils.checkNotNull(file, "No file provided", 
GenericUtils.EMPTY_OBJECT_ARRAY).toPath(), strict, 
IoUtils.getLinkOptions(false));
+    }
+
+    public DefaultAuthorizedKeysAuthenticator(String user, File file, boolean 
strict) {
+        this(user, ValidateUtils.checkNotNull(file, "No file provided", 
GenericUtils.EMPTY_OBJECT_ARRAY).toPath(), strict, 
IoUtils.getLinkOptions(false));
+    }
+
+    public DefaultAuthorizedKeysAuthenticator(Path path, boolean strict, 
LinkOption ... options) {
+        this(System.getProperty("user.name"), path, strict, options);
+    }
+
+    public DefaultAuthorizedKeysAuthenticator(String user, Path path, boolean 
strict, LinkOption ... options) {
+        super(path, options);
+        this.user = ValidateUtils.checkNotNullAndNotEmpty(user, "No username 
provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+        this.strict = strict;
+    }
+
+    public final String getUsername() {
+        return user;
+    }
+
+    public final boolean isStrict() {
+        return strict;
+    }
+
+    @Override
+    protected boolean isValidUsername(String username, ServerSession session) {
+        if (!super.isValidUsername(username, session)) {
+            return false;
+        }
+        
+        String expected = getUsername();
+        if (username.equals(expected)) {
+            return true;
+        } else {
+            return false;   // debug breakpoint
+        }
+    }
+
+    @Override
+    protected Collection<AuthorizedKeyEntry> reloadAuthorizedKeys(Path path, 
String username, ServerSession session) throws IOException {
+        if (isStrict()) {
+            if (log.isDebugEnabled()) {
+                log.info("reloadAuthorizedKeys(" + username + ")[" + session + 
"] check permissions of " + path);
+            }
+            
+            Collection<PosixFilePermission> perms = 
IoUtils.getPermissions(path);
+            // this is true for Windows as well
+            if (perms.contains(PosixFilePermission.OTHERS_EXECUTE)) {
+                throw new FileSystemException(path.toString(), 
path.toString(), "File is not allowed to have e(x)ecute permission");
+            }
+
+            if (OsUtils.isUNIX()) {
+                validateFilePath(path, perms, 
STRICTLY_PROHIBITED_FILE_PERMISSION);
+
+                Path parent=path.getParent();
+                validateFilePath(parent, IoUtils.getPermissions(parent), 
STRICTLY_PROHIBITED_FILE_PERMISSION);
+            }
+        }
+
+        return super.reloadAuthorizedKeys(path, username, session);
+    }
+    
+    /**
+     * @param path The {@link Path} to be validated
+     * @param perms The current {@link PosixFilePermission}s
+     * @param excluded The permissions <U>not</U> allowed to exist
+     * @return The original path
+     * @throws IOException If an excluded permission appears in the current 
ones
+     */
+    protected Path validateFilePath(Path path, Collection<PosixFilePermission> 
perms, Collection<PosixFilePermission> excluded) throws IOException {
+        if (GenericUtils.isEmpty(perms) || GenericUtils.isEmpty(excluded)) {
+            return path;
+        }
+
+        for (PosixFilePermission p : excluded) {
+            if (perms.contains(p)) {
+                throw new FileSystemException(path.toString(), 
path.toString(), "File is not allowed to have permission=" + p);
+            }
+        }
+        
+        return path;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java 
b/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java
index e0ce922..61fa2c1 100644
--- a/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/SinglePublicKeyAuthTest.java
@@ -24,8 +24,8 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.util.KeyUtils;
 import org.apache.sshd.server.Command;
 import org.apache.sshd.server.CommandFactory;
 import org.apache.sshd.server.PublickeyAuthenticator;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java 
b/sshd-core/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
index 1b594f5..1983f9b 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
@@ -54,4 +54,63 @@ public class GenericUtilsTest extends BaseTestSupport {
             }
         }
     }
+
+    @Test
+    public void testStripQuotes() {
+        String expected = getCurrentTestName();
+        assertSame("Unexpected un-quoted stripping", expected, 
GenericUtils.stripQuotes(expected));
+
+        StringBuilder sb = new StringBuilder(2 + 
expected.length()).append('|').append(expected).append('|');
+        for (int index=0; index < GenericUtils.QUOTES.length(); index++) {
+            char delim = GenericUtils.QUOTES.charAt(index);
+            sb.setCharAt(0, delim);
+            sb.setCharAt(sb.length() - 1, delim);
+            
+            CharSequence actual = GenericUtils.stripQuotes(sb);
+            assertEquals("Mismatched result for delim (" + delim + ")", 
expected, actual.toString());
+        }
+    }
+    
+    @Test
+    public void testStripOnlyFirstLayerQuotes() {
+        StringBuilder sb = new 
StringBuilder().append("||").append(getCurrentTestName()).append("||");
+        char[] delims = { '\'', '"', '"', '\'' };
+        for (int index=0; index < delims.length; index += 2) {
+            char topDelim = delims[index], innerDelim = delims[index + 1];
+            sb.setCharAt(0, topDelim);
+            sb.setCharAt(1, innerDelim);
+            sb.setCharAt(sb.length() - 2, innerDelim);
+            sb.setCharAt(sb.length() - 1, topDelim);
+            
+            CharSequence expected = sb.subSequence(1, sb.length() - 1);
+            CharSequence actual = GenericUtils.stripQuotes(sb);
+            assertEquals("Mismatched result for delim (" + topDelim + "/" + 
innerDelim + ")", expected.toString(), actual.toString());
+        }
+    }
+
+    @Test
+    public void testStripDelimiters() {
+        String expected = getCurrentTestName();
+        final char delim = '|';
+        assertSame("Unexpected un-delimited stripping", expected, 
GenericUtils.stripDelimiters(expected, delim));
+        
+        CharSequence actual = GenericUtils.stripDelimiters(
+                new StringBuilder(2 + 
expected.length()).append(delim).append(expected).append(delim), delim);
+        assertEquals("Mismatched stripped values", expected, 
actual.toString());
+    }
+    
+    @Test
+    public void testStripDelimitersOnlyIfOnBothEnds() {
+        final char delim = '$';
+        StringBuilder expected=new 
StringBuilder().append(delim).append(getCurrentTestName()).append(delim);
+        for (int index : new int[] { 0, expected.length() - 1 }) {
+            // restore original delimiters
+            expected.setCharAt(0, delim);
+            expected.setCharAt(expected.length() - 1, delim);
+            // trash one end
+            expected.setCharAt(index, (char) (delim + 1));
+            
+            assertSame("Mismatched result for delim at index=" + index, 
expected, GenericUtils.stripDelimiters(expected, delim));
+        }            
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthAgent.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthAgent.java 
b/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthAgent.java
index efcd610..51a31f0 100644
--- a/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthAgent.java
+++ b/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthAgent.java
@@ -25,7 +25,7 @@ import java.util.Iterator;
 import org.apache.sshd.agent.SshAgent;
 import org.apache.sshd.client.session.ClientSessionImpl;
 import org.apache.sshd.common.SshConstants;
-import org.apache.sshd.common.util.KeyUtils;
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthPublicKey.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthPublicKey.java 
b/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthPublicKey.java
index a8b7e21..ab0dd66 100644
--- a/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthPublicKey.java
+++ b/sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuthPublicKey.java
@@ -25,7 +25,7 @@ import org.apache.sshd.client.session.ClientSessionImpl;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.Signature;
 import org.apache.sshd.common.SshConstants;
-import org.apache.sshd.common.util.KeyUtils;
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntryTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntryTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntryTest.java
new file mode 100644
index 0000000..008e682
--- /dev/null
+++ 
b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeyEntryTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.sshd.server.config.keys;
+
+import java.io.File;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.security.PublicKey;
+import java.util.Collection;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.IoUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.server.PublickeyAuthenticator;
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class AuthorizedKeyEntryTest extends BaseTestSupport {
+    public AuthorizedKeyEntryTest() {
+        super();
+    }
+
+    @Test
+    public void testReadAuthorizedKeysFile() throws Exception {
+        URL url = 
getClass().getResource(AuthorizedKeyEntry.STD_AUTHORIZED_KEYS_FILENAME);
+        assertNotNull("Missing " + 
AuthorizedKeyEntry.STD_AUTHORIZED_KEYS_FILENAME + " resource", url);
+        
+        runAuthorizedKeysTests(AuthorizedKeyEntry.readAuthorizedKeys(url));
+    }
+    
+    @Test
+    @Ignore("It might cause some exceptions if user's file contains 
unsupported keys")
+    public void testReadDefaultAuthorizedKeysFile() throws Exception {
+        File file = AuthorizedKeyEntry.getDefaultAuthorizedKeysFile();
+        assertNotNull("No default location", file);
+
+        Path path = file.toPath();
+        LinkOption[] options = IoUtils.getLinkOptions(false);
+        if (!Files.exists(path, options)) {
+            System.out.append(getCurrentTestName()).append(": verify 
non-existing ").println(path);
+            Collection<AuthorizedKeyEntry> entries = 
AuthorizedKeyEntry.readDefaultAuthorizedKeys();
+            assertTrue("Non-empty keys even though file not found: " + 
entries, GenericUtils.isEmpty(entries));
+        } else {
+            assertFalse("Not a file: " + path, Files.isDirectory(path, 
options));
+            
runAuthorizedKeysTests(AuthorizedKeyEntry.readDefaultAuthorizedKeys());
+        }
+    }
+
+    private void runAuthorizedKeysTests(Collection<AuthorizedKeyEntry> 
entries) throws Exception {
+        testReadAuthorizedKeys(entries);
+        testAuthorizedKeysAuth(entries);
+    }
+
+    private static Collection<AuthorizedKeyEntry> 
testReadAuthorizedKeys(Collection<AuthorizedKeyEntry> entries) throws Exception 
{
+        assertFalse("No entries read", GenericUtils.isEmpty(entries));
+        
+        Exception err = null;
+        for (AuthorizedKeyEntry entry : entries) {
+            try {
+                ValidateUtils.checkNotNull(entry.resolvePublicKey(), "No 
public key resolved from %s", entry);
+            } catch(Exception e) {
+                System.err.append("Failed 
(").append(e.getClass().getSimpleName()).append(')')
+                          .append(" to resolve key of 
entry=").append(entry.toString())
+                          .append(": ").println(e.getMessage());
+                err = e;
+            }
+        }
+
+        if (err != null) {
+            throw err;
+        }
+        
+        return entries;
+    }
+    
+    private PublickeyAuthenticator 
testAuthorizedKeysAuth(Collection<AuthorizedKeyEntry> entries) throws Exception 
{
+        Collection<PublicKey>  keySet = 
AuthorizedKeyEntry.resolveAuthorizedKeys(entries);
+        PublickeyAuthenticator auth = 
AuthorizedKeyEntry.fromAuthorizedEntries(entries);
+        for (PublicKey key : keySet) {
+            assertTrue("Failed to authenticate with key=" + 
key.getAlgorithm(), auth.authenticate(getCurrentTestName(), key, null));
+        }
+        
+        return auth;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java
new file mode 100644
index 0000000..262d013
--- /dev/null
+++ 
b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/AuthorizedKeysAuthenticatorTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.sshd.server.config.keys;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Writer;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.server.PublickeyAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class AuthorizedKeysAuthenticatorTest extends BaseTestSupport {
+    public AuthorizedKeysAuthenticatorTest() {
+        super();
+    }
+
+    @Test
+    public void testAutomaticReload() throws Exception {
+        final Path file=new File(new File(detectTargetFolder(), 
TEMP_SUBFOLDER_NAME), getCurrentTestName()).toPath();
+        if (Files.exists(file)) {
+            Files.delete(file);
+        }
+
+        final AtomicInteger reloadCount = new AtomicInteger(0);
+        PublickeyAuthenticator  auth = new AuthorizedKeysAuthenticator(file) {
+                @Override
+                protected Collection<AuthorizedKeyEntry> 
reloadAuthorizedKeys(Path path, String username, ServerSession session) throws 
IOException {
+                    assertSame("Mismatched reload path", file, path);
+                    reloadCount.incrementAndGet();
+                    return super.reloadAuthorizedKeys(path, username, session);
+                }
+            };
+        assertFalse("Unexpected authentication success for missing file " + 
file, auth.authenticate(getCurrentTestName(), Mockito.mock(PublicKey.class), 
null));
+
+        URL url = 
getClass().getResource(AuthorizedKeyEntry.STD_AUTHORIZED_KEYS_FILENAME);
+        assertNotNull("Missing " + 
AuthorizedKeyEntry.STD_AUTHORIZED_KEYS_FILENAME + " resource", url);
+
+        List<String> lines = new ArrayList<String>();
+        try(BufferedReader rdr = new BufferedReader(new 
InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
+            for (String l = rdr.readLine(); l != null; l = rdr.readLine()) {
+                l = GenericUtils.trimToEmpty(l);
+                // filter out empty and comment lines
+                if (GenericUtils.isEmpty(l) || (l.charAt(0) == 
PublicKeyEntry.COMMENT_CHAR)) {
+                    continue;
+                } else {
+                    lines.add(l);
+                }
+            }
+        }
+
+        assertHierarchyTargetFolderExists(file.getParent());
+        
+        final String EOL = System.getProperty("line.separator");
+        Random rnd = new Random(System.nanoTime());
+        List<String> removed = new ArrayList<String>(lines.size());
+        for ( ; ; ) {
+            try(Writer w = Files.newBufferedWriter(file)) {
+                for (String l : lines) {
+                    w.append(l).append(EOL);
+                }
+            }
+
+            Collection<AuthorizedKeyEntry> entries = 
AuthorizedKeyEntry.readAuthorizedKeys(file);
+            Collection<PublicKey>  keySet = 
AuthorizedKeyEntry.resolveAuthorizedKeys(entries);
+
+            reloadCount.set(0);
+            for (PublicKey k : keySet) {
+                assertTrue("Failed to authenticate with key=" + 
k.getAlgorithm() + " on file=" + file, auth.authenticate(getCurrentTestName(), 
k, null));
+                // we expect EXACTLY ONE re-load call since we did not modify 
the file during the authentication
+                assertEquals("Unexpected extra calls to keys re-loading", 1, 
reloadCount.get());
+            }
+
+            if (lines.isEmpty()) {
+                break;
+            }
+
+            int nextSize = rnd.nextInt(lines.size());
+            while (lines.size() > nextSize) {
+                String l = lines.remove(0);
+                removed.add(l);
+            }
+        }
+
+        assertTrue("File no longer exists: " + file, Files.exists(file));
+        assertFalse("Unexpected authentication success for empty file " + 
file, auth.authenticate(getCurrentTestName(), Mockito.mock(PublicKey.class), 
null));
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java
new file mode 100644
index 0000000..ca700d7
--- /dev/null
+++ 
b/sshd-core/src/test/java/org/apache/sshd/server/config/keys/DefaultAuthorizedKeysAuthenticatorTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.sshd.server.config.keys;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.PublicKey;
+import java.util.Collection;
+
+import org.apache.sshd.common.util.IoUtils;
+import org.apache.sshd.server.PublickeyAuthenticator;
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class DefaultAuthorizedKeysAuthenticatorTest extends BaseTestSupport {
+    public DefaultAuthorizedKeysAuthenticatorTest() {
+        super();
+    }
+
+    @Test
+    public void testUsernameValidation() throws Exception {
+        Path file=new File(new File(detectTargetFolder(), 
TEMP_SUBFOLDER_NAME), getCurrentTestName()).toPath();
+        URL url = 
getClass().getResource(AuthorizedKeyEntry.STD_AUTHORIZED_KEYS_FILENAME);
+        assertNotNull("Missing " + 
AuthorizedKeyEntry.STD_AUTHORIZED_KEYS_FILENAME + " resource", url);
+
+        try(InputStream input = url.openStream();
+            OutputStream output = Files.newOutputStream(file)) {
+            IoUtils.copy(input, output);
+        }
+        
+        Collection<AuthorizedKeyEntry> entries = 
AuthorizedKeyEntry.readAuthorizedKeys(file);
+        Collection<PublicKey>  keySet = 
AuthorizedKeyEntry.resolveAuthorizedKeys(entries);
+        PublickeyAuthenticator auth = new 
DefaultAuthorizedKeysAuthenticator(file, false);
+        String thisUser = System.getProperty("user.name");
+        for (String username : new String[] { null, "", thisUser, 
getClass().getName() + "#" + getCurrentTestName() }) {
+            boolean expected = thisUser.equals(username);
+            for (PublicKey key : keySet) {
+                boolean actual = auth.authenticate(username, key, null);
+                assertEquals("Mismatched authentication results for user='" + 
username + "'", expected, actual);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/test/resources/org/apache/sshd/server/config/keys/authorized_keys
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/resources/org/apache/sshd/server/config/keys/authorized_keys
 
b/sshd-core/src/test/resources/org/apache/sshd/server/config/keys/authorized_keys
new file mode 100644
index 0000000..3a12f32
--- /dev/null
+++ 
b/sshd-core/src/test/resources/org/apache/sshd/server/config/keys/authorized_keys
@@ -0,0 +1,16 @@
+ssh-rsa 
AAAAB3NzaC1yc2EAAAABIwAAAQEA2KFr3GqL/3yXY2bAwRGGDxl/qLuE9qdx20+DMh5oAZPpwprlUnlxLm+ikimwn65Z0KeUyfofYKt+vc3rl1k2mDqyG8DqHeH0C+uFBbom0fthX7PRiQr2T9SOzSodjowZuBHlWIfgtcZI0bygX+GlKaAq00l4yCoe1xUTCRd2ZVyNuB1nozcFI+sUzdeKfaxvuyvbccG4tOx06HDryNdxW2e99bsAhLAg7d8xciOeb4PCAI1USg83dt0wVZE9VJbnRnoZ2y/DaQCJtBJ8t8uNLVdggakydDzQuglyd4dYRxeU7t4TEw6wsfXPB0kqdecd0Llspjx0ciEY/BbycdiApw==
 lgoldstein@LGOLDSTEIN-WIN7
+ssh-rsa 
AAAAB3NzaC1yc2EAAAABIwAAAQEAoiPvv8awFmA1iIwNlWD/29gfNyiTnkpjCVtFOqPGM8YTGF2G5FRrAwJyehdJu8qFJSUaNMzrjz4qlP4OyP4qcd16TE3FkIwd22+sXo0K9oEbOF307FVtBGrqnp+m5aFPQIZNhX86Rd9m0zrE4eSkZQ0qOjhp0Q60+G2lleaHSYvdc0IOcOJsWI43+ytlzJ2gKoVwPtttsXVtycjt2ZmD99V/lk3G7sdXQGL5S+lxn7rMtxamOSy+VR1eVu2ZagOCp2XZM1eFNWIRCH0KbRJh2mDrk08pIN9yCh2q/5BF+oh/CQyS8W8754MJuQ+0U0qHBH/wNtogIomedxpW8hG1jQ==
 [email protected]
+
+# some empty lines
+
+
+# no comment for key
+ssh-rsa 
AAAAB3NzaC1yc2EAAAABJQAAAIBVdEJBWozLd41Huv2LZOglgE3HpRfcxvKBUB/poMAfRY6nMoStxaVCc/dPzpzE6nOAbX+tuRLL611H3Ooby9quSlKsSfx4/Dk76tPokY7T6eLTmoJP6S3c9OMdNFLQ31UKbW4RlBRFYFFMMQGr3PMzXRKDpQhl0Bvts+N5qQ2eaQ==
+
+# dummy login options
+command="/usr/bin/tinyfugue",environment="PATH=/bin:/usr/bin/:/opt/gtm/bin" 
ssh-rsa 
AAAAB3NzaC1yc2EAAAABJQAAAIBVdEJBWozLd41Huv2LZOglgE3HpRfcxvKBUB/poMAfRY6nMoStxaVCc/dPzpzE6nOAbX+tuRLL611H3Ooby9quSlKsSfx4/Dk76tPokY7T6eLTmoJP6S3c9OMdNFLQ31UKbW4RlBRFYFFMMQGr3PMzXRKDpQhl0Bvts+N5qQ2eaQ==
+hosts="git.eng.vmware.com" ssh-rsa 
AAAAB3NzaC1yc2EAAAABIwAAAQEAvQK9DhBSStbi2CaZNOo5vHy9Nga/hLCnO19tL6i5U/5Nhh5Y8W+tL+AA8hqD/doLBnyaEv2xjfzECwKlStc9HWx6EcJ+9B1rA4+5HztuUEWxuozNnkvcScjTBBqEd7fPPt0INI+pSZRYa2InEBBUUHTt1YaDEXamM/j4RVKovEH7Efgq9VUti148ZG90/w9V5ZT1o6yhuOw9UbME/eHIbS2E9P/Gy33OhkAgTLyOfCZAdJiYvcvFXqNWSKVFx3H5hSolh9ppxVFVFj2hW6QtvgYTphLU0ccHOWTBd/UToG4Xd9GjgSoD1pAI9e0NwBOsUfiqSzO99wqpzs6erfwelQ==
+
+# ECDSA keys
+ecdsa-sha2-nistp256 
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCbZVVpqEHGLNWMqMeyU1VbWb91XteoamVcgpy4yxNVbZffb5IDdbo1ons/y9KAhcub6LZeLrvXzVUZbXCZiUkg=
+host=10.23.222.240 ecdsa-sha2-nistp256 
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCMOm8SqJPJqvSa5Q6r/pGhkp3aBc4Evf9wF8DjhSy13m+wwQwQCENQ8V+5bpI58Z0jjB8O3lmuOLils+Nx9AFc=

Reply via email to