"gitblit keys ls" now defaults to showing an indexed list of fingerprints which almost matches the output of "sshadd -l". The indexes are useful specifying key(s) to remove using "gitblit keys rm <index>". This is an important improvement for key management.
import java.nio.charset.Charset;
import java.security.Principal;
-import java.security.PublicKey;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import com.gitblit.auth.WindowsAuthProvider;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
+import com.gitblit.transport.ssh.SshKey;
import com.gitblit.utils.Base64;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.StringUtils;
}
return this;
}
-
+
public void addAuthenticationProvider(AuthenticationProvider prov) {
authenticationProviders.add(prov);
}
* @return a user object or null
*/
@Override
- public UserModel authenticate(String username, PublicKey key) {
+ public UserModel authenticate(String username, SshKey key) {
if (username != null) {
if (!StringUtils.isEmpty(username)) {
UserModel user = userManager.getUserModel(username);
}
}
}
-
+
// could not authenticate locally or with a provider
return null;
}
-
+
/**
* Returns a UserModel if local authentication succeeds.
- *
+ *
* @param user
* @param password
* @return a UserModel if local authentication succeeds, null otherwise
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
-import java.security.PublicKey;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import com.gitblit.models.UserModel;
import com.gitblit.tickets.ITicketService;
import com.gitblit.transport.ssh.IPublicKeyManager;
+import com.gitblit.transport.ssh.SshKey;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.JsonUtils;
}
@Override
- public UserModel authenticate(String username, PublicKey key) {
+ public UserModel authenticate(String username, SshKey key) {
return authenticationManager.authenticate(username, key);
}
*/
package com.gitblit.manager;
-import java.security.PublicKey;
-
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
+import com.gitblit.transport.ssh.SshKey;
public interface IAuthenticationManager extends IManager {
UserModel authenticate(HttpServletRequest httpRequest);
/**
- * Authenticate a user based on a public key.
+ * Authenticate a user based on a ssh public key.
*
* @param username
* @param key
* @return a user object or null
*/
- UserModel authenticate(String username, PublicKey key);
+ UserModel authenticate(String username, SshKey key);
/**
* Authenticate a user based on HTTP request parameters.
SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY);
Preconditions.checkState(client.getUser() == null);
username = username.toLowerCase(Locale.US);
- List<PublicKey> keys = keyManager.getKeys(username);
+ List<SshKey> keys = keyManager.getKeys(username);
if (keys == null || keys.isEmpty()) {
log.info("{} has not added any public keys for ssh authentication",
username);
return false;
}
- for (PublicKey key : keys) {
+ for (SshKey key : keys) {
if (key.equals(suppliedKey)) {
UserModel user = authManager.authenticate(username, key);
if (user != null) {
import java.io.File;
import java.io.IOException;
-import java.security.PublicKey;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.sshd.common.util.Buffer;
-import org.eclipse.jgit.lib.Constants;
-
import com.gitblit.Keys;
import com.gitblit.manager.IRuntimeManager;
import com.google.common.base.Charsets;
}
@Override
- protected List<PublicKey> getKeysImpl(String username) {
+ protected List<SshKey> getKeysImpl(String username) {
try {
log.info("loading keystore for {}", username);
File keystore = getKeystore(username);
return null;
}
if (keystore.exists()) {
- List<PublicKey> list = new ArrayList<PublicKey>();
+ List<SshKey> list = new ArrayList<SshKey>();
for (String entry : Files.readLines(keystore, Charsets.ISO_8859_1)) {
if (entry.trim().length() == 0) {
// skip blanks
// skip comments
continue;
}
- final String[] parts = entry.split(" ");
- final byte[] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1]));
- list.add(new Buffer(bin).getRawPublicKey());
+ SshKey key = new SshKey(entry);
+ list.add(key);
}
if (list.isEmpty()) {
* by disregarding the comment/description field during key comparisons.
*/
@Override
- public boolean addKey(String username, String data) {
+ public boolean addKey(String username, SshKey key) {
try {
- String newKey = stripCommentFromKey(data);
+ String newKey = stripCommentFromKey(key.getRawData());
List<String> lines = new ArrayList<String>();
File keystore = getKeystore(username);
}
// add new key
- lines.add(data);
+ lines.add(key.getRawData());
// write keystore
String content = Joiner.on("\n").join(lines).trim().concat("\n");
}
/**
- * Removes a key from the keystore.
+ * Removes the specified key from the keystore.
*/
@Override
- public boolean removeKey(String username, String data) {
+ public boolean removeKey(String username, SshKey key) {
try {
- String rmKey = stripCommentFromKey(data);
+ String rmKey = stripCommentFromKey(key.getRawData());
File keystore = getKeystore(username);
if (keystore.exists()) {
/* Strips the comment from the key data and eliminates whitespace diffs */
protected String stripCommentFromKey(String data) {
- String [] cols = data.split(" ");
+ String [] cols = data.split(" ", 3);
String key = Joiner.on(" ").join(cols[0], cols[1]);
return key;
}
*/
package com.gitblit.transport.ssh;
-import java.security.PublicKey;
import java.text.MessageFormat;
import java.util.List;
import java.util.concurrent.ExecutionException;
import com.google.common.cache.LoadingCache;
/**
- * Parent class for public key managers.
+ * Parent class for ssh public key managers.
*
* @author James Moger
*
protected final Logger log = LoggerFactory.getLogger(getClass());
- protected final LoadingCache<String, List<PublicKey>> keyCache = CacheBuilder
+ protected final LoadingCache<String, List<SshKey>> keyCache = CacheBuilder
.newBuilder().
expireAfterAccess(15, TimeUnit.MINUTES).
maximumSize(100)
- .build(new CacheLoader<String, List<PublicKey>>() {
+ .build(new CacheLoader<String, List<SshKey>>() {
@Override
- public List<PublicKey> load(String username) {
+ public List<SshKey> load(String username) {
return getKeysImpl(username);
}
});
@Override
public abstract IPublicKeyManager stop();
- public final List<PublicKey> getKeys(String username) {
+ public final List<SshKey> getKeys(String username) {
try {
if (isStale(username)) {
keyCache.invalidate(username);
protected abstract boolean isStale(String username);
- protected abstract List<PublicKey> getKeysImpl(String username);
+ protected abstract List<SshKey> getKeysImpl(String username);
- public abstract boolean addKey(String username, String data);
+ public abstract boolean addKey(String username, SshKey key);
- public abstract boolean removeKey(String username, String data);
+ public abstract boolean removeKey(String username, SshKey key);
public abstract boolean removeAllKeys(String username);
}
*/
package com.gitblit.transport.ssh;
-import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
*/
public class MemoryKeyManager extends IPublicKeyManager {
- Map<String, List<PublicKey>> keys;
+ Map<String, List<SshKey>> keys;
public MemoryKeyManager() {
- keys = new HashMap<String, List<PublicKey>>();
+ keys = new HashMap<String, List<SshKey>>();
}
@Override
}
@Override
- protected List<PublicKey> getKeysImpl(String username) {
+ protected List<SshKey> getKeysImpl(String username) {
String id = username.toLowerCase();
if (keys.containsKey(id)) {
return keys.get(id);
}
@Override
- public boolean addKey(String username, String data) {
- return false;
+ public boolean addKey(String username, SshKey key) {
+ String id = username.toLowerCase();
+ if (!keys.containsKey(id)) {
+ keys.put(id, new ArrayList<SshKey>());
+ }
+ return keys.get(id).add(key);
}
@Override
- public boolean removeKey(String username, String data) {
- return false;
+ public boolean removeKey(String username, SshKey key) {
+ String id = username.toLowerCase();
+ if (!keys.containsKey(id)) {
+ return false;
+ }
+ return keys.get(id).remove(key);
}
@Override
keys.remove(id.toLowerCase());
return true;
}
-
- /* Test method for populating the memory key manager */
- public void addKey(String username, PublicKey key) {
- String id = username.toLowerCase();
- if (!keys.containsKey(id)) {
- keys.put(id, new ArrayList<PublicKey>());
- }
- keys.get(id).add(key);
- }
}
*/
package com.gitblit.transport.ssh;
-import java.security.PublicKey;
import java.util.List;
/**
}
@Override
- protected List<PublicKey> getKeysImpl(String username) {
+ protected List<SshKey> getKeysImpl(String username) {
return null;
}
@Override
- public boolean addKey(String username, String data) {
+ public boolean addKey(String username, SshKey key) {
return false;
}
@Override
- public boolean removeKey(String username, String data) {
+ public boolean removeKey(String username, SshKey key) {
return false;
}
--- /dev/null
+package com.gitblit.transport.ssh;
+
+import java.io.Serializable;
+import java.security.PublicKey;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.util.Buffer;
+import org.eclipse.jgit.lib.Constants;
+
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Class that encapsulates a public SSH key and it's metadata.
+ *
+ * @author James Moger
+ *
+ */
+public class SshKey implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private String rawData;
+
+ private PublicKey publicKey;
+
+ private String comment;
+
+ private String fingerprint;
+
+ public SshKey(String data) {
+ this.rawData = data;
+ }
+
+ public SshKey(PublicKey key) {
+ this.publicKey = key;
+ this.comment = "";
+ }
+
+ public PublicKey getPublicKey() {
+ if (publicKey == null && rawData != null) {
+ // instantiate the public key from the raw key data
+ final String[] parts = rawData.split(" ", 3);
+ if (comment == null && parts.length == 3) {
+ comment = parts[2];
+ }
+ final byte[] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1]));
+ try {
+ publicKey = new Buffer(bin).getRawPublicKey();
+ } catch (SshException e) {
+ e.printStackTrace();
+ }
+ }
+ return publicKey;
+ }
+
+ public String getAlgorithm() {
+ return getPublicKey().getAlgorithm();
+ }
+
+ public String getComment() {
+ if (comment == null && rawData != null) {
+ // extract comment from the raw data
+ final String[] parts = rawData.split(" ", 3);
+ if (parts.length == 3) {
+ comment = parts[2];
+ }
+ }
+ return comment;
+ }
+
+ public void setComment(String comment) {
+ this.comment = comment;
+ }
+
+ public String getRawData() {
+ if (rawData == null && publicKey != null) {
+ // build the raw data manually from the public key
+ Buffer buf = new Buffer();
+
+ // 1: identify the algorithm
+ buf.putRawPublicKey(publicKey);
+ String alg = buf.getString();
+
+ // 2: encode the key
+ buf.clear();
+ buf.putPublicKey(publicKey);
+ String b64 = Base64.encodeBase64String(buf.getBytes());
+
+ String c = getComment();
+ rawData = alg + " " + b64 + (StringUtils.isEmpty(c) ? "" : (" " + c));
+ }
+ return rawData;
+ }
+
+ public String getFingerprint() {
+ if (fingerprint == null) {
+ StringBuilder sb = new StringBuilder();
+ // TODO append the keysize
+ int keySize = 0;
+ if (keySize > 0) {
+ sb.append(keySize).append(' ');
+ }
+
+ // append the key hash as colon-separated pairs
+ String hash;
+ if (rawData != null) {
+ final String[] parts = rawData.split(" ", 3);
+ final byte [] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1]));
+ hash = StringUtils.getMD5(bin);
+ } else {
+ // TODO get hash from publickey
+ hash = "todo";
+ }
+ for (int i = 0; i < hash.length(); i += 2) {
+ sb.append(hash.charAt(i)).append(hash.charAt(i + 1)).append(':');
+ }
+ sb.setLength(sb.length() - 1);
+
+ // append the comment
+ String c = getComment();
+ if (!StringUtils.isEmpty(c)) {
+ sb.append(' ');
+ sb.append(c);
+ }
+
+ // append the algorithm
+ String alg = getAlgorithm();
+ if (!StringUtils.isEmpty(alg)) {
+ sb.append(" (").append(alg).append(")");
+ }
+ fingerprint = sb.toString();
+ }
+ return fingerprint;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof PublicKey) {
+ return getPublicKey().equals(o);
+ } else if (o instanceof SshKey) {
+ return getPublicKey().equals(((SshKey) o).getPublicKey());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return getPublicKey().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return getFingerprint();
+ }
+}
import java.util.List;
import com.gitblit.transport.ssh.IPublicKeyManager;
+import com.gitblit.transport.ssh.SshKey;
import com.gitblit.transport.ssh.commands.SshCommand;
import com.google.common.base.Charsets;
protected IPublicKeyManager getKeyManager() {
return getContext().getGitblit().getPublicKeyManager();
}
+
+ protected SshKey parseKey(String rawData) throws UnloggedFailure {
+ if (rawData.contains("PRIVATE")) {
+ throw new UnloggedFailure(1, "Please provide a PUBLIC key, not a PRIVATE key!");
+ }
+ SshKey key = new SshKey(rawData);
+ return key;
+ }
}
package com.gitblit.transport.ssh.gitblit;
import java.io.IOException;
-import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.sshd.common.util.Buffer;
import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.models.UserModel;
import com.gitblit.transport.ssh.IPublicKeyManager;
+import com.gitblit.transport.ssh.SshKey;
import com.gitblit.transport.ssh.commands.CommandMetaData;
import com.gitblit.transport.ssh.commands.DispatchCommand;
import com.gitblit.transport.ssh.commands.SshCommand;
String username = getContext().getClient().getUsername();
List<String> keys = readKeys(addKeys);
for (String key : keys) {
- getKeyManager().addKey(username, key);
+ SshKey sshKey = parseKey(key);
+ getKeyManager().addKey(username, sshKey);
log.info("added SSH public key for {}", username);
}
}
private final String ALL = "ALL";
- @Argument(metaVar = "<stdin>|<KEY>|ALL", usage = "the key to remove")
+ @Argument(metaVar = "-|<INDEX>|<KEY>|ALL", usage = "the key to remove", required = true)
private List<String> removeKeys = new ArrayList<String>();
@Override
public void run() throws IOException, UnloggedFailure {
String username = getContext().getClient().getUsername();
+ // remove a key that has been piped to the command
+ // or remove all keys
+
+ List<SshKey> currentKeys = getKeyManager().getKeys(username);
+ if (currentKeys == null || currentKeys.isEmpty()) {
+ throw new UnloggedFailure(1, "There are no registered keys!");
+ }
+
List<String> keys = readKeys(removeKeys);
if (keys.contains(ALL)) {
- getKeyManager().removeAllKeys(username);
- log.info("removed all SSH public keys from {}", username);
+ if (getKeyManager().removeAllKeys(username)) {
+ stdout.println("Removed all keys.");
+ log.info("removed all SSH public keys from {}", username);
+ } else {
+ log.warn("failed to remove all SSH public keys from {}", username);
+ }
} else {
for (String key : keys) {
- getKeyManager().removeKey(username, key);
- log.info("removed SSH public key from {}", username);
+ try {
+ // remove a key by it's index (1-based indexing)
+ int index = Integer.parseInt(key);
+ if (index > keys.size()) {
+ if (keys.size() == 1) {
+ throw new UnloggedFailure(1, "Invalid index specified. There is only 1 registered key.");
+ }
+ throw new UnloggedFailure(1, String.format("Invalid index specified. There are %d registered keys.", keys.size()));
+ }
+ SshKey sshKey = currentKeys.get(index - 1);
+ if (getKeyManager().removeKey(username, sshKey)) {
+ stdout.println(String.format("Removed %s", sshKey.getFingerprint()));
+ } else {
+ throw new UnloggedFailure(1, String.format("failed to remove #%s: %s", key, sshKey.getFingerprint()));
+ }
+ } catch (Exception e) {
+ // remove key by raw key data
+ SshKey sshKey = parseKey(key);
+ if (getKeyManager().removeKey(username, sshKey)) {
+ stdout.println(String.format("Removed %s", sshKey.getFingerprint()));
+ log.info("removed SSH public key {} from {}", sshKey.getFingerprint(), username);
+ } else {
+ log.warn("failed to remove SSH public key {} from {}", sshKey.getFingerprint(), username);
+ throw new UnloggedFailure(1, String.format("failed to remove %s", sshKey.getFingerprint()));
+ }
+ }
}
}
}
}
- @CommandMetaData(name = "list", aliases = { "ls" }, description = "List your public keys")
+ @CommandMetaData(name = "list", aliases = { "ls" }, description = "List your registered public keys")
public static class ListKeys extends SshCommand {
+ @Option(name = "-L", usage = "list complete public key parameters")
+ private boolean showRaw;
+
@Override
public void run() {
IPublicKeyManager keyManager = getContext().getGitblit().getPublicKeyManager();
String username = getContext().getClient().getUsername();
- List<PublicKey> keys = keyManager.getKeys(username);
- if (keys == null) {
- stdout.println(String.format("%s has not added any public keys for ssh authentication", username));
+ List<SshKey> keys = keyManager.getKeys(username);
+ if (keys == null || keys.isEmpty()) {
+ stdout.println("You have not registered any public keys for ssh authentication.");
return;
}
- for (PublicKey key : keys) {
- // two-steps - perhaps this could be improved
- Buffer buf = new Buffer();
-
- // 1: identify the algorithm
- buf.putRawPublicKey(key);
- String alg = buf.getString();
-
- // 2: encode the key
- buf.clear();
- buf.putPublicKey(key);
- String b64 = Base64.encodeBase64String(buf.getBytes());
-
- stdout.println(alg + " " + b64);
+ for (int i = 0; i < keys.size(); i++) {
+ if (showRaw) {
+ // output in the same format as authorized_keys
+ stdout.println(keys.get(i).getRawData());
+ } else {
+ // show 1-based index numbers with the fingerprint
+ // this is useful for comparing with "ssh-add -l"
+ stdout.println("#" + (i + 1) + ": " + keys.get(i).getFingerprint());
+ }
}
}
}
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
+import com.gitblit.transport.ssh.SshKey;
import com.gitblit.transport.ssh.commands.CommandMetaData;
/** Set a user's account settings. **/
}
}
- private void addSshKeys(List<String> sshKeys) throws UnloggedFailure,
+ private void addSshKeys(List<String> keys) throws UnloggedFailure,
IOException {
- for (String sshKey : sshKeys) {
+ for (String key : keys) {
+ SshKey sshKey = new SshKey(key);
getKeyManager().addKey(user, sshKey);
}
}
- private void deleteSshKeys(List<String> sshKeys) {
- if (sshKeys.contains(ALL)) {
+ private void deleteSshKeys(List<String> keys) {
+ if (keys.contains(ALL)) {
getKeyManager().removeAllKeys(user);
} else {
- for (String sshKey : sshKeys) {
+ for (String key : keys) {
+ SshKey sshKey = new SshKey(key);
getKeyManager().removeKey(user, sshKey);
}
}
import com.gitblit.Constants;
import com.gitblit.transport.ssh.IPublicKeyManager;
import com.gitblit.transport.ssh.MemoryKeyManager;
+import com.gitblit.transport.ssh.SshKey;
public class SshDaemonTest extends GitblitUnitTest {
@Before
public void prepare() {
MemoryKeyManager keyMgr = getKeyManager();
- keyMgr.addKey("admin", pair.getPublic());
+ keyMgr.addKey("admin", new SshKey(pair.getPublic()));
}
@After