]> source.dussan.org Git - gitblit.git/commitdiff
Retrieve public SSH keys from LDAP.
authorFlorian Zschocke <florian.zschocke@devolo.de>
Fri, 25 Nov 2016 17:21:27 +0000 (18:21 +0100)
committerFlorian Zschocke <florian.zschocke@devolo.de>
Tue, 29 Nov 2016 11:01:42 +0000 (12:01 +0100)
Add new class `LdapPublicKeyManager` which retrieves public SSH keys
from LDAP.

The attribute can be configured with the new configuration option
`realm.ldap.sshPublicKey`. The setting can be a simple attribute name,
like `sshPublicKey`, or an attribute name and a prefix for the value,
like `altSecurityIdentities:SshKey`, in which case attributes are selected
that have the name `altSecurityIdentities` and whose values start with
`SshKey:`.

src/main/distrib/data/defaults.properties
src/main/java/com/gitblit/auth/LdapAuthProvider.java
src/main/java/com/gitblit/ldap/LdapConnection.java
src/main/java/com/gitblit/transport/ssh/LdapKeyManager.java [new file with mode: 0644]
src/test/java/com/gitblit/tests/LdapConnectionTest.java
src/test/java/com/gitblit/tests/LdapPublicKeyManagerTest.java [new file with mode: 0644]

index 16be84763162dfec312b7f8d68da7ff832cb333c..1fe5b345e94c1b1dce8e5db285ab5e337bf3723d 100644 (file)
@@ -1935,6 +1935,18 @@ realm.ldap.email = email
 # SINCE 1.0.0
 realm.ldap.uid = uid
 
+# Attribute on the USER record that indicates their public SSH key.
+# Leave blank when public SSH keys shall not be retrieved from LDAP.
+#
+# This may be a simple attribute or an attribute and a value prefix. Examples:
+#  sshPublicKey - Use the attribute 'sshPublicKey' on the user record.
+#  altSecurityIdentities:SshKey - Use the attribute 'altSecurityIdentities'
+#                                 on the user record, for which the record value
+#                                 starts with 'SshKey:', followed by the SSH key entry.
+#
+# SINCE 1.9.0
+realm.ldap.sshPublicKey =
+
 # Defines whether to synchronize all LDAP users and teams into the user service
 # This requires either anonymous LDAP access or that a specific account is set
 # in realm.ldap.username and realm.ldap.password, that has permission to read
index 8a326cdc4d01ade33616ec92e614e4ac1e9f00c8..7ea8f1137128f6394d10227c4b68deac3c196c23 100644 (file)
@@ -107,9 +107,9 @@ public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
                                }
 
                                try {
-                                       String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
                                        String uidAttribute = settings.getString(Keys.realm.ldap.uid, "uid");
-                                       String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
+                                       String accountBase = ldapConnection.getAccountBase();
+                                       String accountPattern = ldapConnection.getAccountPattern();
                                        accountPattern = StringUtils.replace(accountPattern, "${username}", "*");
 
                                        SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
@@ -275,11 +275,7 @@ public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
 
                        try {
                                // Find the logging in user's DN
-                               String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
-                               String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
-                               accountPattern = StringUtils.replace(accountPattern, "${username}", LdapConnection.escapeLDAPSearchFilter(simpleUsername));
-
-                               SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
+                               SearchResult result = ldapConnection.searchUser(simpleUsername);
                                if (result != null && result.getEntryCount() == 1) {
                                        SearchResultEntry loggingInUser = result.getSearchEntries().get(0);
                                        String loggingInUserDN = loggingInUser.getDN();
@@ -527,6 +523,7 @@ public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider {
 
 
 
+
        /**
         * Returns a simple username without any domain prefixes.
         *
index b7f07a1e68243080fd3547fb395fdeadeb9438d9..14fedf1009c44167f80b29923b78105b48335223 100644 (file)
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2016 gitblit.com.
+ *
+ * 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 com.gitblit.ldap;
 
 import java.net.URI;
@@ -69,6 +84,16 @@ public class LdapConnection implements AutoCloseable {
 
 
 
+       public static String getAccountBase(IStoredSettings settings) {
+               return settings.getString(Keys.realm.ldap.accountBase, "");
+       }
+
+       public static String getAccountPattern(IStoredSettings settings) {
+               return settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
+       }
+
+
+
        public LdapConnection(IStoredSettings settings) {
                this.settings = settings;
 
@@ -82,6 +107,16 @@ public class LdapConnection implements AutoCloseable {
 
 
 
+       public String getAccountBase() {
+               return getAccountBase(settings);
+       }
+
+       public String getAccountPattern() {
+               return getAccountPattern(settings);
+       }
+
+
+
        public boolean connect() {
                try {
                        URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server));
@@ -198,36 +233,6 @@ public class LdapConnection implements AutoCloseable {
 
 
 
-       public SearchResult search(SearchRequest request) {
-               try {
-                       return conn.search(request);
-               } catch (LDAPSearchException e) {
-                       logger.error("Problem Searching LDAP [{}]",  e.getResultCode());
-                       return e.getSearchResult();
-               }
-       }
-
-
-       public SearchResult search(String base, boolean dereferenceAliases, String filter, List<String> attributes) {
-               try {
-                       SearchRequest searchRequest = new SearchRequest(base, SearchScope.SUB, filter);
-                       if (dereferenceAliases) {
-                               searchRequest.setDerefPolicy(DereferencePolicy.SEARCHING);
-                       }
-                       if (attributes != null) {
-                               searchRequest.setAttributes(attributes);
-                       }
-                       SearchResult result = search(searchRequest);
-                       return result;
-
-               } catch (LDAPException e) {
-                       logger.error("Problem creating LDAP search", e);
-                       return null;
-               }
-       }
-
-
-
        public boolean isAuthenticated(String userDn, String password) {
                verifyCurrentBinding();
 
@@ -267,6 +272,51 @@ public class LdapConnection implements AutoCloseable {
 
 
 
+
+       public SearchResult search(SearchRequest request) {
+               try {
+                       return conn.search(request);
+               } catch (LDAPSearchException e) {
+                       logger.error("Problem Searching LDAP [{}]",  e.getResultCode());
+                       return e.getSearchResult();
+               }
+       }
+
+
+       public SearchResult search(String base, boolean dereferenceAliases, String filter, List<String> attributes) {
+               try {
+                       SearchRequest searchRequest = new SearchRequest(base, SearchScope.SUB, filter);
+                       if (dereferenceAliases) {
+                               searchRequest.setDerefPolicy(DereferencePolicy.SEARCHING);
+                       }
+                       if (attributes != null) {
+                               searchRequest.setAttributes(attributes);
+                       }
+                       SearchResult result = search(searchRequest);
+                       return result;
+
+               } catch (LDAPException e) {
+                       logger.error("Problem creating LDAP search", e);
+                       return null;
+               }
+       }
+
+
+       public SearchResult searchUser(String username, List<String> attributes) {
+
+               String accountPattern = getAccountPattern();
+               accountPattern = StringUtils.replace(accountPattern, "${username}", escapeLDAPSearchFilter(username));
+
+               return search(getAccountBase(), false, accountPattern, attributes);
+       }
+
+
+       public SearchResult searchUser(String username) {
+               return searchUser(username, null);
+       }
+
+
+
        private boolean verifyCurrentBinding() {
                BindRequest lastBind = conn.getLastBindRequest();
                if (lastBind == currentBindRequest) {
diff --git a/src/main/java/com/gitblit/transport/ssh/LdapKeyManager.java b/src/main/java/com/gitblit/transport/ssh/LdapKeyManager.java
new file mode 100644 (file)
index 0000000..9612a96
--- /dev/null
@@ -0,0 +1,397 @@
+/*
+ * Copyright 2016 gitblit.com.
+ *
+ * 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 com.gitblit.transport.ssh;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.server.config.keys.AuthorizedKeyEntry;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.ldap.LdapConnection;
+import com.gitblit.utils.StringUtils;
+import com.google.common.base.Joiner;
+import com.google.inject.Inject;
+import com.unboundid.ldap.sdk.BindResult;
+import com.unboundid.ldap.sdk.ResultCode;
+import com.unboundid.ldap.sdk.SearchResult;
+import com.unboundid.ldap.sdk.SearchResultEntry;
+
+/**
+ * LDAP public key manager
+ *
+ * Retrieves public keys from user's LDAP entries. Using this key manager,
+ * no SSH keys can be edited, i.e. added, removed, permissions changed, etc.
+ *
+ * @author Florian Zschocke
+ *
+ */
+public class LdapKeyManager extends IPublicKeyManager {
+
+       /**
+        * Pattern to find prefixes like 'SSHKey:' in key entries.
+        * These prefixes describe the type of an altSecurityIdentity.
+        * The pattern accepts anything but quote and colon up to the
+        * first colon at the start of a string.
+        */
+       private static final Pattern PREFIX_PATTERN = Pattern.compile("^([^\":]+):");
+       /**
+        * Pattern to find the string describing Gitblit permissions for a SSH key.
+        * The pattern matches on a string starting with 'gbPerm', matched case-insensitive,
+        * followed by '=' with optional whitespace around it, followed by a string of
+        * upper and lower case letters and '+' and '-' for the permission, which can optionally
+        * be enclosed in '"' or '\"' (only the leading quote is matched in the pattern).
+        * Only the group describing the permission is a capturing group.
+        */
+       private static final Pattern GB_PERM_PATTERN = Pattern.compile("(?i:gbPerm)\\s*=\\s*(?:\\\\\"|\")?\\s*([A-Za-z+-]+)");
+
+
+       private final IStoredSettings settings;
+
+
+
+       @Inject
+       public LdapKeyManager(IStoredSettings settings) {
+               this.settings = settings;
+       }
+
+
+       @Override
+       public String toString() {
+               return getClass().getSimpleName();
+       }
+
+       @Override
+       public LdapKeyManager start() {
+               log.info(toString());
+               return this;
+       }
+
+       @Override
+       public boolean isReady() {
+               return true;
+       }
+
+       @Override
+       public LdapKeyManager stop() {
+               return this;
+       }
+
+       @Override
+       protected boolean isStale(String username) {
+               // always return true so we gets keys from LDAP every time
+               return true;
+       }
+
+       @Override
+       protected List<SshKey> getKeysImpl(String username) {
+               try (LdapConnection conn = new LdapConnection(settings)) {
+                       if (conn.connect()) {
+                               log.info("loading ssh key for {} from LDAP directory", username);
+
+                               BindResult bindResult = conn.bind();
+                               if (bindResult == null) {
+                                       conn.close();
+                                       return null;
+                               }
+
+                               // Search the user entity
+
+                               // Support prefixing the key data, e.g. when using altSecurityIdentities in AD.
+                               String pubKeyAttribute = settings.getString(Keys.realm.ldap.sshPublicKey, "sshPublicKey");
+                               String pkaPrefix = null;
+                               int idx = pubKeyAttribute.indexOf(':');
+                               if (idx > 0) {
+                                       pkaPrefix = pubKeyAttribute.substring(idx +1);
+                                       pubKeyAttribute = pubKeyAttribute.substring(0, idx);
+                               }
+
+                               SearchResult result = conn.searchUser(getSimpleUsername(username), Arrays.asList(pubKeyAttribute));
+                               conn.close();
+
+                               if (result != null && result.getResultCode() == ResultCode.SUCCESS) {
+                                       if ( result.getEntryCount() > 1) {
+                                               log.info("Found more than one entry for user {} in LDAP. Cannot retrieve SSH key.", username);
+                                               return null;
+                                       } else if ( result.getEntryCount() < 1) {
+                                               log.info("Found no entry for user {} in LDAP. Cannot retrieve SSH key.", username);
+                                               return null;
+                                       }
+
+                                       // Retrieve the SSH key attributes
+                                       SearchResultEntry foundUser = result.getSearchEntries().get(0);
+                                       String[] attrs = foundUser.getAttributeValues(pubKeyAttribute);
+                                       if (attrs == null ||attrs.length == 0) {
+                                               log.info("found no keys for user {} under attribute {} in directory", username, pubKeyAttribute);
+                                               return null;
+                                       }
+
+
+                                       // Filter resulting list to match with required special prefix in entry
+                                       List<GbAuthorizedKeyEntry> authorizedKeys = new ArrayList<>(attrs.length);
+                                       Matcher m = PREFIX_PATTERN.matcher("");
+                                       for (int i = 0; i < attrs.length; ++i) {
+                                               // strip out line breaks
+                                               String keyEntry = Joiner.on("").join(attrs[i].replace("\r\n", "\n").split("\n"));
+                                               m.reset(keyEntry);
+                                               try {
+                                                       if (m.lookingAt()) { // Key is prefixed in LDAP
+                                                               if (pkaPrefix == null) {
+                                                                       continue;
+                                                               }
+                                                               String prefix = m.group(1).trim();
+                                                               if (! pkaPrefix.equalsIgnoreCase(prefix)) {
+                                                                       continue;
+                                                               }
+                                                               String s = keyEntry.substring(m.end()); // Strip prefix off
+                                                               authorizedKeys.add(GbAuthorizedKeyEntry.parseAuthorizedKeyEntry(s));
+
+                                                       } else { // Key is not prefixed in LDAP
+                                                               if (pkaPrefix != null) {
+                                                                       continue;
+                                                               }
+                                                               String s = keyEntry; // Strip prefix off
+                                                               authorizedKeys.add(GbAuthorizedKeyEntry.parseAuthorizedKeyEntry(s));
+                                                       }
+                                               } catch (IllegalArgumentException e) {
+                                                       log.info("Failed to parse key entry={}:", keyEntry, e.getMessage());
+                                               }
+                                       }
+
+                                       List<SshKey> keyList = new ArrayList<>(authorizedKeys.size());
+                                       for (GbAuthorizedKeyEntry keyEntry : authorizedKeys) {
+                                               try {
+                                                       SshKey key = new SshKey(keyEntry.resolvePublicKey());
+                                                       key.setComment(keyEntry.getComment());
+                                                       setKeyPermissions(key, keyEntry);
+                                                       keyList.add(key);
+                                               } catch (GeneralSecurityException | IOException e) {
+                                                       log.warn("Error resolving key entry for user {}. Entry={}", username, keyEntry, e);
+                                               }
+                                       }
+                                       return keyList;
+                               }
+                       }
+               }
+
+               return null;
+       }
+
+
+       @Override
+       public boolean addKey(String username, SshKey key) {
+               return false;
+       }
+
+       @Override
+       public boolean removeKey(String username, SshKey key) {
+               return false;
+       }
+
+       @Override
+       public boolean removeAllKeys(String username) {
+               return false;
+       }
+
+
+
+       private void setKeyPermissions(SshKey key, GbAuthorizedKeyEntry keyEntry) {
+               List<String> env = keyEntry.getLoginOptionValues("environment");
+               if (env != null && !env.isEmpty()) {
+                       // Walk over all entries and find one that sets 'gbPerm'. The last one wins.
+                       for (String envi : env) {
+                               Matcher m = GB_PERM_PATTERN.matcher(envi);
+                               if (m.find()) {
+                                       String perm = m.group(1).trim();
+                                       AccessPermission ap = AccessPermission.fromCode(perm);
+                                       if (ap == AccessPermission.NONE) {
+                                               ap = AccessPermission.valueOf(perm.toUpperCase());
+                                       }
+
+                                       if (ap != null && ap != AccessPermission.NONE) {
+                                               try {
+                                                       key.setPermission(ap);
+                                               } catch (IllegalArgumentException e) {
+                                                       log.warn("Incorrect permissions ({}) set for SSH key entry {}.", ap, envi, e);
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+
+       /**
+        * Returns a simple username without any domain prefixes.
+        *
+        * @param username
+        * @return a simple username
+        */
+       private String getSimpleUsername(String username) {
+               int lastSlash = username.lastIndexOf('\\');
+               if (lastSlash > -1) {
+                       username = username.substring(lastSlash + 1);
+               }
+
+               return username;
+       }
+
+
+       /**
+        * Extension of the AuthorizedKeyEntry from Mina SSHD with better option parsing.
+        *
+        * The class makes use of code from the two methods copied from the original
+        * Mina SSHD AuthorizedKeyEntry class. The code is rewritten to improve user login
+        * option support. Options are correctly parsed even if they have whitespace within
+        * double quotes. Options can occur multiple times, which is needed for example for
+        * the "environment" option. Thus for an option a list of strings is kept, holding
+        * multiple option values.
+        */
+       private static class GbAuthorizedKeyEntry extends AuthorizedKeyEntry {
+
+               private static final long serialVersionUID = 1L;
+               /**
+                * Pattern to extract the first part of the key entry without whitespace or only with quoted whitespace.
+                * The pattern essentially splits the line in two parts with two capturing groups. All other groups
+                * in the pattern are non-capturing. The first part is a continuous string that only includes double quoted
+                * whitespace and ends in whitespace. The second part is the rest of the line.
+                * The first part is at the beginning of the line, the lead-in. For a SSH key entry this can either be
+                * login options (see authorized keys file description) or the key type. Since options, other than the
+                * key type, can include whitespace and escaped double quotes within double quotes, the pattern takes
+                * care of that by searching for either "characters that are not whitespace and not double quotes"
+                * or "a double quote, followed by 'characters that are not a double quote or backslash, or a backslash
+                * and then a double quote, or a backslash', followed by a double quote".
+                */
+               private static final Pattern LEADIN_PATTERN = Pattern.compile("^((?:[^\\s\"]*|(?:\"(?:[^\"\\\\]|\\\\\"|\\\\)*\"))*\\s+)(.+)");
+               /**
+                * Pattern to split a comma separated list of options.
+                * Since an option could contain commas (as well as escaped double quotes) within double quotes
+                * in the option value, a simple split on comma is not enough. So the pattern searches for multiple
+                * occurrences of:
+                * characters that are not double quotes or a comma, or
+                * a double quote followed by: characters that are not a double quote or backslash, or
+                *                             a backslash and then a double quote, or
+                *                             a backslash,
+                *   followed by a double quote.
+                */
+               private static final Pattern OPTION_PATTERN = Pattern.compile("([^\",]+|(?:\"(?:[^\"\\\\]|\\\\\"|\\\\)*\"))+");
+
+               // for options that have no value, "true" is used
+               private Map<String, List<String>> loginOptionsMulti = Collections.emptyMap();
+
+
+               List<String> getLoginOptionValues(String option) {
+                       return loginOptionsMulti.get(option);
+               }
+
+
+
+               /**
+                * @param line Original line from an <code>authorized_keys</code> file
+                * @return {@link GbAuthorizedKeyEntry} or {@code null} if the line is
+                * {@code null}/empty or a comment line
+                * @throws IllegalArgumentException If failed to parse/decode the line
+                * @see #COMMENT_CHAR
+                */
+               public static GbAuthorizedKeyEntry parseAuthorizedKeyEntry(String line) throws IllegalArgumentException {
+                       line = GenericUtils.trimToEmpty(line);
+                       if (StringUtils.isEmpty(line) || (line.charAt(0) == COMMENT_CHAR) /* comment ? */) {
+                               return null;
+                       }
+
+                       Matcher m = LEADIN_PATTERN.matcher(line);
+                       if (! m.lookingAt()) {
+                               throw new IllegalArgumentException("Bad format (no key data delimiter): " + line);
+                       }
+
+                       String keyType = m.group(1).trim();
+                       final GbAuthorizedKeyEntry entry;
+                       if (KeyUtils.getPublicKeyEntryDecoder(keyType) == null) {  // assume this is due to the fact that it starts with login options
+                               entry = parseAuthorizedKeyEntry(m.group(2));
+                               if (entry == null) {
+                                       throw new IllegalArgumentException("Bad format (no key data after login options): " + line);
+                               }
+
+                               entry.parseAndSetLoginOptions(keyType);
+                       } else {
+                               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 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 GbAuthorizedKeyEntry(), encData);
+                               entry.setComment(comment);
+                       }
+
+                       return entry;
+               }
+
+               private void parseAndSetLoginOptions(String options) {
+                       Matcher m = OPTION_PATTERN.matcher(options);
+                       if (! m.find()) {
+                               loginOptionsMulti = Collections.emptyMap();
+                       }
+                       Map<String, List<String>> optsMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+                       do {
+                               String p = m.group();
+                               p = GenericUtils.trimToEmpty(p);
+                               if (StringUtils.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);
+
+                               // For options without value the value is set to TRUE.
+                               if (value == null) {
+                                       value = Boolean.TRUE.toString();
+                               }
+
+                               List<String> opts = optsMap.get(name);
+                               if (opts == null) {
+                                       opts = new ArrayList<String>();
+                                       optsMap.put(name, opts);
+                               }
+                               opts.add(value.toString());
+                       } while(m.find());
+
+                       loginOptionsMulti = optsMap;
+               }
+       }
+
+}
index f8d2fed0db9d6f346b939146f584cbd7c8426909..3da5477724b7a8b4ec4982cb8622ce28a80c18cb 100644 (file)
@@ -245,4 +245,36 @@ public class LdapConnectionTest extends LdapBasedUnitTest {
                }
        }
 
+
+       @Test
+       public void testSearchUser() throws LDAPException {
+               LdapConnection conn = new LdapConnection(settings);
+               try {
+                       assertTrue(conn.connect());
+                       BindResult br = conn.bind();
+                       assertNotNull(br);
+
+                       SearchResult result;
+                       SearchResultEntry entry;
+
+                       result = conn.searchUser("UserOne");
+                       assertNotNull(result);
+                       assertEquals(1, result.getEntryCount());
+                       entry = result.getSearchEntries().get(0);
+                       assertEquals("CN=UserOne,OU=US," + ACCOUNT_BASE, entry.getDN());
+
+                       result = conn.searchUser("UserFour", Arrays.asList("givenName", "surname"));
+                       assertNotNull(result);
+                       assertEquals(1, result.getEntryCount());
+                       entry = result.getSearchEntries().get(0);
+                       assertEquals("CN=UserFour,OU=Canada," + ACCOUNT_BASE, entry.getDN());
+                       assertEquals(2, entry.getAttributes().size());
+                       assertEquals("User", entry.getAttributeValue("givenName"));
+                       assertEquals("Four", entry.getAttributeValue("surname"));
+
+               } finally {
+                       conn.close();
+               }
+       }
+
 }
diff --git a/src/test/java/com/gitblit/tests/LdapPublicKeyManagerTest.java b/src/test/java/com/gitblit/tests/LdapPublicKeyManagerTest.java
new file mode 100644 (file)
index 0000000..c426254
--- /dev/null
@@ -0,0 +1,723 @@
+/*
+ * Copyright 2016 Florian Zschocke
+ * Copyright 2016 gitblit.com
+ *
+ * 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 com.gitblit.tests;
+
+import static org.junit.Assume.assumeTrue;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Signature;
+import java.security.spec.ECGenParameterSpec;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sshd.common.util.SecurityUtils;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import com.gitblit.Keys;
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.transport.ssh.LdapKeyManager;
+import com.gitblit.transport.ssh.SshKey;
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.ldap.sdk.Modification;
+import com.unboundid.ldap.sdk.ModificationType;
+
+/**
+ * Test LdapPublicKeyManager going against an in-memory UnboundID
+ * LDAP server.
+ *
+ * @author Florian Zschocke
+ *
+ */
+@RunWith(Parameterized.class)
+public class LdapPublicKeyManagerTest extends LdapBasedUnitTest {
+
+       private static Map<String,KeyPair> keyPairs = new HashMap<>(10);
+       private static KeyPairGenerator rsaGenerator;
+       private static KeyPairGenerator dsaGenerator;
+       private static KeyPairGenerator ecGenerator;
+
+
+
+       @BeforeClass
+       public static void init() throws GeneralSecurityException {
+               rsaGenerator = SecurityUtils.getKeyPairGenerator("RSA");
+               dsaGenerator = SecurityUtils.getKeyPairGenerator("DSA");
+               ecGenerator = SecurityUtils.getKeyPairGenerator("ECDSA");
+       }
+
+
+
+       @Test
+       public void testGetKeys() throws LDAPException {
+               String keyRsaOne = getRsaPubKey("UserOne@example.com");
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", keyRsaOne));
+
+               String keyRsaTwo = getRsaPubKey("UserTwo@example.com");
+               String keyDsaTwo = getDsaPubKey("UserTwo@example.com");
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", keyRsaTwo, keyDsaTwo));
+
+               String keyRsaThree = getRsaPubKey("UserThree@example.com");
+               String keyDsaThree = getDsaPubKey("UserThree@example.com");
+               String keyEcThree  = getEcPubKey("UserThree@example.com");
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", keyEcThree, keyRsaThree, keyDsaThree));
+
+               LdapKeyManager kmgr = new LdapKeyManager(settings);
+
+               List<SshKey> keys = kmgr.getKeys("UserOne");
+               assertNotNull(keys);
+               assertTrue(keys.size() == 1);
+               assertEquals(keyRsaOne, keys.get(0).getRawData());
+
+
+               keys = kmgr.getKeys("UserTwo");
+               assertNotNull(keys);
+               assertTrue(keys.size() == 2);
+               if (keyRsaTwo.equals(keys.get(0).getRawData())) {
+                       assertEquals(keyDsaTwo, keys.get(1).getRawData());
+               } else if (keyDsaTwo.equals(keys.get(0).getRawData())) {
+                       assertEquals(keyRsaTwo, keys.get(1).getRawData());
+               } else {
+                       fail("Mismatch in UserTwo keys.");
+               }
+
+
+               keys = kmgr.getKeys("UserThree");
+               assertNotNull(keys);
+               assertTrue(keys.size() == 3);
+               assertEquals(keyEcThree, keys.get(0).getRawData());
+               assertEquals(keyRsaThree, keys.get(1).getRawData());
+               assertEquals(keyDsaThree, keys.get(2).getRawData());
+
+               keys = kmgr.getKeys("UserFour");
+               assertNotNull(keys);
+               assertTrue(keys.size() == 0);
+       }
+
+
+       @Test
+       public void testGetKeysAttributeName() throws LDAPException {
+               settings.put(Keys.realm.ldap.sshPublicKey, "sshPublicKey");
+
+               String keyRsaOne = getRsaPubKey("UserOne@example.com");
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", keyRsaOne));
+
+               String keyDsaTwo = getDsaPubKey("UserTwo@example.com");
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "publicsshkey", keyDsaTwo));
+
+               String keyRsaThree = getRsaPubKey("UserThree@example.com");
+               String keyDsaThree = getDsaPubKey("UserThree@example.com");
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", keyRsaThree));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "publicsshkey", keyDsaThree));
+
+
+               LdapKeyManager kmgr = new LdapKeyManager(settings);
+
+               List<SshKey> keys = kmgr.getKeys("UserOne");
+               assertNotNull(keys);
+               assertEquals(1, keys.size());
+               assertEquals(keyRsaOne, keys.get(0).getRawData());
+
+               keys = kmgr.getKeys("UserTwo");
+               assertNotNull(keys);
+               assertEquals(0, keys.size());
+
+               keys = kmgr.getKeys("UserThree");
+               assertNotNull(keys);
+               assertEquals(1, keys.size());
+               assertEquals(keyRsaThree, keys.get(0).getRawData());
+
+               keys = kmgr.getKeys("UserFour");
+               assertNotNull(keys);
+               assertEquals(0, keys.size());
+
+
+               settings.put(Keys.realm.ldap.sshPublicKey, "publicsshkey");
+
+               keys = kmgr.getKeys("UserOne");
+               assertNotNull(keys);
+               assertEquals(0, keys.size());
+
+               keys = kmgr.getKeys("UserTwo");
+               assertNotNull(keys);
+               assertEquals(1, keys.size());
+               assertEquals(keyDsaTwo, keys.get(0).getRawData());
+
+               keys = kmgr.getKeys("UserThree");
+               assertNotNull(keys);
+               assertEquals(1, keys.size());
+               assertEquals(keyDsaThree, keys.get(0).getRawData());
+
+               keys = kmgr.getKeys("UserFour");
+               assertNotNull(keys);
+               assertEquals(0, keys.size());
+       }
+
+
+       @Test
+       public void testGetKeysPrefixed() throws LDAPException {
+               // This test is independent from authentication mode, so run only once.
+               assumeTrue(authMode == AuthMode.ANONYMOUS);
+
+               String keyRsaOne = getRsaPubKey("UserOne@example.com");
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", keyRsaOne));
+
+               String keyRsaTwo = getRsaPubKey("UserTwo@example.com");
+               String keyDsaTwo = getDsaPubKey("UserTwo@example.com");
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", keyRsaTwo));
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHKey: " + keyDsaTwo));
+
+               String keyRsaThree = getRsaPubKey("UserThree@example.com");
+               String keyDsaThree = getDsaPubKey("UserThree@example.com");
+               String keyEcThree =  getEcPubKey("UserThree@example.com");
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", " SshKey :\r\n" + keyRsaThree));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", " sshkey: " + keyDsaThree));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "ECDSAKey :\n " + keyEcThree));
+
+
+               LdapKeyManager kmgr = new LdapKeyManager(settings);
+
+               settings.put(Keys.realm.ldap.sshPublicKey, "altSecurityIdentities");
+
+               List<SshKey> keys = kmgr.getKeys("UserOne");
+               assertNotNull(keys);
+               assertEquals(0, keys.size());
+
+               keys = kmgr.getKeys("UserTwo");
+               assertNotNull(keys);
+               assertEquals(1, keys.size());
+               assertEquals(keyRsaTwo, keys.get(0).getRawData());
+
+               keys = kmgr.getKeys("UserThree");
+               assertNotNull(keys);
+               assertEquals(0, keys.size());
+
+               keys = kmgr.getKeys("UserFour");
+               assertNotNull(keys);
+               assertEquals(0, keys.size());
+
+
+
+               settings.put(Keys.realm.ldap.sshPublicKey, "altSecurityIdentities:SSHKey");
+
+               keys = kmgr.getKeys("UserOne");
+               assertNotNull(keys);
+               assertEquals(0, keys.size());
+
+               keys = kmgr.getKeys("UserTwo");
+               assertNotNull(keys);
+               assertEquals(1, keys.size());
+               assertEquals(keyDsaTwo, keys.get(0).getRawData());
+
+               keys = kmgr.getKeys("UserThree");
+               assertNotNull(keys);
+               assertEquals(2, keys.size());
+               assertEquals(keyRsaThree, keys.get(0).getRawData());
+               assertEquals(keyDsaThree, keys.get(1).getRawData());
+
+               keys = kmgr.getKeys("UserFour");
+               assertNotNull(keys);
+               assertEquals(0, keys.size());
+
+
+
+               settings.put(Keys.realm.ldap.sshPublicKey, "altSecurityIdentities:ECDSAKey");
+
+               keys = kmgr.getKeys("UserOne");
+               assertNotNull(keys);
+               assertEquals(0, keys.size());
+
+               keys = kmgr.getKeys("UserTwo");
+               assertNotNull(keys);
+               assertEquals(0, keys.size());
+
+               keys = kmgr.getKeys("UserThree");
+               assertNotNull(keys);
+               assertEquals(1, keys.size());
+               assertEquals(keyEcThree, keys.get(0).getRawData());
+
+               keys = kmgr.getKeys("UserFour");
+               assertNotNull(keys);
+               assertEquals(0, keys.size());
+       }
+
+
+       @Test
+       public void testGetKeysPermissions() throws LDAPException {
+               // This test is independent from authentication mode, so run only once.
+               assumeTrue(authMode == AuthMode.ANONYMOUS);
+
+               String keyRsaOne = getRsaPubKey("UserOne@example.com");
+               String keyRsaTwo = getRsaPubKey("");
+               String keyDsaTwo = getDsaPubKey("UserTwo at example.com");
+               String keyRsaThree = getRsaPubKey("UserThree@example.com");
+               String keyDsaThree = getDsaPubKey("READ key for user 'Three' @example.com");
+               String keyEcThree =  getEcPubKey("UserThree@example.com");
+
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", keyRsaOne));
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", "     " + keyRsaTwo));
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", "no-agent-forwarding " + keyDsaTwo));
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", " command=\"sh /etc/netstart tun0 \" " + keyRsaThree));
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", " command=\"netstat -nult\",environment=\"gb=\\\"What now\\\"\" " + keyDsaThree));
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "sshPublicKey", "environment=\"SSH=git\",command=\"netstat -nult\",environment=\"gbPerms=VIEW\" " + keyEcThree));
+
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", "environment=\"gbPerm=R\" " + keyRsaOne));
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", " restrict,environment=\"gbperm=V\"   " + keyRsaTwo));
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", "restrict,environment=\"GBPerm=RW\",pty " + keyDsaTwo));
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", " environment=\"gbPerm=CLONE\",environment=\"X=\\\" Y \\\"\" " + keyRsaThree));
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", " environment=\"A = B \",from=\"*.example.com,!pc.example.com\",environment=\"gbPerm=VIEW\" " + keyDsaThree));
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", "environment=\"SSH=git\",environment=\"gbPerm=PUSH\",environment=\"XYZ='Ali Baba'\" " + keyEcThree));
+
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", "environment=\"gbPerm=R\",environment=\"josh=\\\"mean\\\"\",tunnel=\"0\" " + keyRsaOne));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", " environment=\" gbPerm = V \"      " + keyRsaTwo));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", "command=\"sh echo \\\"Nope, not you!\\\" \",user-rc,environment=\"gbPerm=RW\" " + keyDsaTwo));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", "environment=\"gbPerm=VIEW\",command=\"sh /etc/netstart tun0 \",environment=\"gbPerm=CLONE\",no-pty " + keyRsaThree));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", "  command=\"netstat -nult\",environment=\"gbPerm=VIEW\" " + keyDsaThree));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "sshPublicKey", "environment=\"SSH=git\",command=\"netstat -nult\",environment=\"gbPerm=PUSH\" " + keyEcThree));
+
+
+               LdapKeyManager kmgr = new LdapKeyManager(settings);
+
+               List<SshKey> keys = kmgr.getKeys("UserOne");
+               assertNotNull(keys);
+               assertEquals(6, keys.size());
+               for (SshKey key : keys) {
+                       assertEquals(AccessPermission.PUSH, key.getPermission());
+               }
+
+               keys = kmgr.getKeys("UserTwo");
+               assertNotNull(keys);
+               assertEquals(6, keys.size());
+               int seen = 0;
+               for (SshKey key : keys) {
+                       if (keyRsaOne.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 0;
+                       }
+                       else if (keyRsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 1;
+                       }
+                       else if (keyDsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 2;
+                       }
+                       else if (keyRsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 3;
+                       }
+                       else if (keyDsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 4;
+                       }
+                       else if (keyEcThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 5;
+                       }
+               }
+               assertEquals(63, seen);
+
+               keys = kmgr.getKeys("UserThree");
+               assertNotNull(keys);
+               assertEquals(6, keys.size());
+               seen = 0;
+               for (SshKey key : keys) {
+                       if (keyRsaOne.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 0;
+                       }
+                       else if (keyRsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 1;
+                       }
+                       else if (keyDsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 2;
+                       }
+                       else if (keyRsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 3;
+                       }
+                       else if (keyDsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 4;
+                       }
+                       else if (keyEcThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 5;
+                       }
+               }
+               assertEquals(63, seen);
+       }
+
+
+       @Test
+       public void testGetKeysPrefixedPermissions() throws LDAPException {
+               // This test is independent from authentication mode, so run only once.
+               assumeTrue(authMode == AuthMode.ANONYMOUS);
+
+               String keyRsaOne = getRsaPubKey("UserOne@example.com");
+               String keyRsaTwo = getRsaPubKey("UserTwo at example.com");
+               String keyDsaTwo = getDsaPubKey("UserTwo@example.com");
+               String keyRsaThree = getRsaPubKey("example.com: user Three");
+               String keyDsaThree = getDsaPubKey("");
+               String keyEcThree =  getEcPubKey("  ");
+
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "altSecurityIdentities",          "permitopen=\"host:220\"" + keyRsaOne));
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "altSecurityIdentities", "sshkey:" + "        " + keyRsaTwo));
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHKEY :" + "no-agent-forwarding " + keyDsaTwo));
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + " command=\"sh /etc/netstart tun0 \" " + keyRsaThree));
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + " command=\"netstat -nult\",environment=\"gb=\\\"What now\\\"\" " + keyDsaThree));
+               getDS().modify(DN_USER_ONE, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + "environment=\"SSH=git\",command=\"netstat -nult\",environment=\"gbPerms=VIEW\" " + keyEcThree));
+
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHkey: " + "environment=\"gbPerm=R\" " + keyRsaOne));
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHKey : " + " restrict,environment=\"gbPerm=V\",permitopen=\"sshkey: 220\" " + keyRsaTwo));
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHkey: " + "permitopen=\"sshkey: 443\",restrict,environment=\"gbPerm=RW\",pty " + keyDsaTwo));
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + "environment=\"gbPerm=CLONE\",permitopen=\"pubkey: 29184\",environment=\"X=\\\" Y \\\"\" " + keyRsaThree));
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + " environment=\"A = B \",from=\"*.example.com,!pc.example.com\",environment=\"gbPerm=VIEW\" " + keyDsaThree));
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + "environment=\"SSH=git\",environment=\"gbPerm=PUSH\",environemnt=\"XYZ='Ali Baba'\" " + keyEcThree));
+
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHkey: " + "environment=\"gbPerm=R\",environment=\"josh=\\\"mean\\\"\",tunnel=\"0\" " + keyRsaOne));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHkey : " + " environment=\" gbPerm = V \"       " + keyRsaTwo));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "SSHkey: " + "command=\"sh echo \\\"Nope, not you! \\b (bell)\\\" \",user-rc,environment=\"gbPerm=RW\" " + keyDsaTwo));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + "environment=\"gbPerm=VIEW\",command=\"sh /etc/netstart tun0 \",environment=\"gbPerm=CLONE\",no-pty " + keyRsaThree));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + "    command=\"netstat -nult\",environment=\"gbPerm=VIEW\" " + keyDsaThree));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "pubkey: " + "environment=\"SSH=git\",command=\"netstat -nult\",environment=\"gbPerm=PUSH\" " + keyEcThree));
+
+               // Weird stuff, not to specification but shouldn't make it stumble.
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", "opttest: " + "permitopen=host:443,command=,environment=\"gbPerm=CLONE\",no-pty= " + keyRsaThree));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", " opttest: " + "  cmd=git,environment=\"gbPerm=\\\"VIEW\\\"\" " + keyDsaThree));
+               getDS().modify(DN_USER_THREE, new Modification(ModificationType.ADD, "altSecurityIdentities", " opttest:" + "environment=,command=netstat,environment=gbperm=push " + keyEcThree));
+
+
+               LdapKeyManager kmgr = new LdapKeyManager(settings);
+
+               settings.put(Keys.realm.ldap.sshPublicKey, "altSecurityIdentities:SSHkey");
+
+               List<SshKey> keys = kmgr.getKeys("UserOne");
+               assertNotNull(keys);
+               assertEquals(2, keys.size());
+               int seen = 0;
+               for (SshKey key : keys) {
+                       assertEquals(AccessPermission.PUSH, key.getPermission());
+                       if (keyRsaOne.equals(key.getRawData())) {
+                               seen += 1 << 0;
+                       }
+                       else if (keyRsaTwo.equals(key.getRawData())) {
+                               seen += 1 << 1;
+                       }
+                       else if (keyDsaTwo.equals(key.getRawData())) {
+                               seen += 1 << 2;
+                       }
+                       else if (keyRsaThree.equals(key.getRawData())) {
+                               seen += 1 << 3;
+                       }
+                       else if (keyDsaThree.equals(key.getRawData())) {
+                               seen += 1 << 4;
+                       }
+                       else if (keyEcThree.equals(key.getRawData())) {
+                               seen += 1 << 5;
+                       }
+               }
+               assertEquals(6, seen);
+
+               keys = kmgr.getKeys("UserTwo");
+               assertNotNull(keys);
+               assertEquals(3, keys.size());
+               seen = 0;
+               for (SshKey key : keys) {
+                       if (keyRsaOne.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 0;
+                       }
+                       else if (keyRsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 1;
+                       }
+                       else if (keyDsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 2;
+                       }
+                       else if (keyRsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 3;
+                       }
+                       else if (keyDsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 4;
+                       }
+                       else if (keyEcThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 5;
+                       }
+               }
+               assertEquals(7, seen);
+
+               keys = kmgr.getKeys("UserThree");
+               assertNotNull(keys);
+               assertEquals(3, keys.size());
+               seen = 0;
+               for (SshKey key : keys) {
+                       if (keyRsaOne.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 0;
+                       }
+                       else if (keyRsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 1;
+                       }
+                       else if (keyDsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 2;
+                       }
+                       else if (keyRsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 3;
+                       }
+                       else if (keyDsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 4;
+                       }
+                       else if (keyEcThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 5;
+                       }
+               }
+               assertEquals(7, seen);
+
+
+
+               settings.put(Keys.realm.ldap.sshPublicKey, "altSecurityIdentities:pubKey");
+
+               keys = kmgr.getKeys("UserOne");
+               assertNotNull(keys);
+               assertEquals(3, keys.size());
+               seen = 0;
+               for (SshKey key : keys) {
+                       assertEquals(AccessPermission.PUSH, key.getPermission());
+                       if (keyRsaOne.equals(key.getRawData())) {
+                               seen += 1 << 0;
+                       }
+                       else if (keyRsaTwo.equals(key.getRawData())) {
+                               seen += 1 << 1;
+                       }
+                       else if (keyDsaTwo.equals(key.getRawData())) {
+                               seen += 1 << 2;
+                       }
+                       else if (keyRsaThree.equals(key.getRawData())) {
+                               seen += 1 << 3;
+                       }
+                       else if (keyDsaThree.equals(key.getRawData())) {
+                               seen += 1 << 4;
+                       }
+                       else if (keyEcThree.equals(key.getRawData())) {
+                               seen += 1 << 5;
+                       }
+               }
+               assertEquals(56, seen);
+
+               keys = kmgr.getKeys("UserTwo");
+               assertNotNull(keys);
+               assertEquals(3, keys.size());
+               seen = 0;
+               for (SshKey key : keys) {
+                       if (keyRsaOne.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 0;
+                       }
+                       else if (keyRsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 1;
+                       }
+                       else if (keyDsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 2;
+                       }
+                       else if (keyRsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 3;
+                       }
+                       else if (keyDsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 4;
+                       }
+                       else if (keyEcThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 5;
+                       }
+               }
+               assertEquals(56, seen);
+
+               keys = kmgr.getKeys("UserThree");
+               assertNotNull(keys);
+               assertEquals(3, keys.size());
+               seen = 0;
+               for (SshKey key : keys) {
+                       if (keyRsaOne.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 0;
+                       }
+                       else if (keyRsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 1;
+                       }
+                       else if (keyDsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 2;
+                       }
+                       else if (keyRsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 3;
+                       }
+                       else if (keyDsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 4;
+                       }
+                       else if (keyEcThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 5;
+                       }
+               }
+               assertEquals(56, seen);
+
+
+               settings.put(Keys.realm.ldap.sshPublicKey, "altSecurityIdentities:opttest");
+               keys = kmgr.getKeys("UserThree");
+               assertNotNull(keys);
+               assertEquals(3, keys.size());
+               seen = 0;
+               for (SshKey key : keys) {
+                       if (keyRsaOne.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 0;
+                       }
+                       else if (keyRsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 1;
+                       }
+                       else if (keyDsaTwo.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 2;
+                       }
+                       else if (keyRsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.CLONE, key.getPermission());
+                               seen += 1 << 3;
+                       }
+                       else if (keyDsaThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.VIEW, key.getPermission());
+                               seen += 1 << 4;
+                       }
+                       else if (keyEcThree.equals(key.getRawData())) {
+                               assertEquals(AccessPermission.PUSH, key.getPermission());
+                               seen += 1 << 5;
+                       }
+               }
+               assertEquals(56, seen);
+
+       }
+
+
+       @Test
+       public void testKeyValidity() throws LDAPException, GeneralSecurityException {
+               LdapKeyManager kmgr = new LdapKeyManager(settings);
+
+               String comment = "UserTwo@example.com";
+               String keyDsaTwo = getDsaPubKey(comment);
+               getDS().modify(DN_USER_TWO, new Modification(ModificationType.ADD, "sshPublicKey", keyDsaTwo));
+
+
+               List<SshKey> keys = kmgr.getKeys("UserTwo");
+               assertNotNull(keys);
+               assertEquals(1, keys.size());
+               SshKey sshKey = keys.get(0);
+               assertEquals(keyDsaTwo, sshKey.getRawData());
+
+               Signature signature = SecurityUtils.getSignature("DSA");
+               signature.initSign(getDsaKeyPair(comment).getPrivate());
+               byte[] message = comment.getBytes();
+               signature.update(message);
+               byte[] sigBytes = signature.sign();
+
+               signature.initVerify(sshKey.getPublicKey());
+               signature.update(message);
+               assertTrue("Verify failed with retrieved SSH key.", signature.verify(sigBytes));
+       }
+
+
+
+
+
+
+
+
+       private KeyPair getDsaKeyPair(String comment) {
+               return getKeyPair("DSA", comment, dsaGenerator);
+       }
+
+       private KeyPair getKeyPair(String type, String comment, KeyPairGenerator generator) {
+               String kpkey = type + ":" + comment;
+               KeyPair kp = keyPairs.get(kpkey);
+               if (kp == null) {
+                       if ("EC".equals(type)) {
+                               ECGenParameterSpec ecSpec = new ECGenParameterSpec("P-384");
+                               try {
+                                       ecGenerator.initialize(ecSpec);
+                               } catch (InvalidAlgorithmParameterException e) {
+                                       kp = generator.generateKeyPair();
+                                       e.printStackTrace();
+                               }
+                               kp = ecGenerator.generateKeyPair();
+                       } else {
+                               kp = generator.generateKeyPair();
+                       }
+                       keyPairs.put(kpkey, kp);
+               }
+
+               return kp;
+       }
+
+
+       private String getRsaPubKey(String comment) {
+               return getPubKey("RSA", comment, rsaGenerator);
+       }
+
+       private String getDsaPubKey(String comment) {
+               return getPubKey("DSA", comment, dsaGenerator);
+       }
+
+       private String getEcPubKey(String comment) {
+               return getPubKey("EC", comment, ecGenerator);
+       }
+
+       private String getPubKey(String type, String comment, KeyPairGenerator generator) {
+               KeyPair kp = getKeyPair(type, comment, generator);
+               if (kp == null) {
+                       return null;
+               }
+
+               SshKey sk = new SshKey(kp.getPublic());
+               sk.setComment(comment);
+               return sk.getRawData();
+       }
+
+}