--- /dev/null
+/*
+ * 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;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+}