From f639d966cb5e7026cb30e6b25be55fb681feb896 Mon Sep 17 00:00:00 2001 From: Florian Zschocke Date: Fri, 25 Nov 2016 18:21:27 +0100 Subject: Retrieve public SSH keys from LDAP. 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 | 12 + .../java/com/gitblit/auth/LdapAuthProvider.java | 11 +- src/main/java/com/gitblit/ldap/LdapConnection.java | 110 +++- .../com/gitblit/transport/ssh/LdapKeyManager.java | 397 +++++++++++ .../java/com/gitblit/tests/LdapConnectionTest.java | 32 + .../gitblit/tests/LdapPublicKeyManagerTest.java | 723 +++++++++++++++++++++ 6 files changed, 1248 insertions(+), 37 deletions(-) create mode 100644 src/main/java/com/gitblit/transport/ssh/LdapKeyManager.java create mode 100644 src/test/java/com/gitblit/tests/LdapPublicKeyManagerTest.java diff --git a/src/main/distrib/data/defaults.properties b/src/main/distrib/data/defaults.properties index 16be8476..1fe5b345 100644 --- a/src/main/distrib/data/defaults.properties +++ b/src/main/distrib/data/defaults.properties @@ -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 diff --git a/src/main/java/com/gitblit/auth/LdapAuthProvider.java b/src/main/java/com/gitblit/auth/LdapAuthProvider.java index 8a326cdc..7ea8f113 100644 --- a/src/main/java/com/gitblit/auth/LdapAuthProvider.java +++ b/src/main/java/com/gitblit/auth/LdapAuthProvider.java @@ -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. * diff --git a/src/main/java/com/gitblit/ldap/LdapConnection.java b/src/main/java/com/gitblit/ldap/LdapConnection.java index b7f07a1e..14fedf10 100644 --- a/src/main/java/com/gitblit/ldap/LdapConnection.java +++ b/src/main/java/com/gitblit/ldap/LdapConnection.java @@ -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 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 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 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 index 00000000..9612a96b --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/LdapKeyManager.java @@ -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 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 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 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 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> loginOptionsMulti = Collections.emptyMap(); + + + List getLoginOptionValues(String option) { + return loginOptionsMulti.get(option); + } + + + + /** + * @param line Original line from an authorized_keys 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> 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 opts = optsMap.get(name); + if (opts == null) { + opts = new ArrayList(); + optsMap.put(name, opts); + } + opts.add(value.toString()); + } while(m.find()); + + loginOptionsMulti = optsMap; + } + } + +} diff --git a/src/test/java/com/gitblit/tests/LdapConnectionTest.java b/src/test/java/com/gitblit/tests/LdapConnectionTest.java index f8d2fed0..3da54777 100644 --- a/src/test/java/com/gitblit/tests/LdapConnectionTest.java +++ b/src/test/java/com/gitblit/tests/LdapConnectionTest.java @@ -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 index 00000000..c426254f --- /dev/null +++ b/src/test/java/com/gitblit/tests/LdapPublicKeyManagerTest.java @@ -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 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 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 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 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 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 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 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(); + } + +} -- cgit v1.2.3