From 75ebd391b88884581b1139c87c98bb687941a8fe Mon Sep 17 00:00:00 2001 From: David Ostrovsky <david@ostrovsky.org> Date: Thu, 10 Apr 2014 18:58:08 -0400 Subject: [PATCH] Prevent double authentication for the same public key --- src/main/java/com/gitblit/transport/ssh/FileKeyManager.java | 156 ++++++++++++++++++++++++++++++++++++++++++---------- 1 files changed, 126 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java index 87912ca..ae0bc9c 100644 --- a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java @@ -21,6 +21,8 @@ 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; @@ -29,55 +31,76 @@ import com.gitblit.Keys; import com.gitblit.manager.IRuntimeManager; import com.google.common.base.Charsets; +import com.google.common.base.Joiner; import com.google.common.io.Files; /** * Manages SSH keys on the filesystem. - * + * * @author James Moger * */ -public class FileKeyManager implements IKeyManager { +public class FileKeyManager extends IKeyManager { protected final IRuntimeManager runtimeManager; - + + protected final Map<File, Long> lastModifieds; + public FileKeyManager(IRuntimeManager runtimeManager) { this.runtimeManager = runtimeManager; + this.lastModifieds = new ConcurrentHashMap<File, Long>(); } - + @Override public String toString() { File dir = runtimeManager.getFileOrFolder(Keys.git.sshKeysFolder, "${baseFolder}/ssh"); return MessageFormat.format("{0} ({1})", getClass().getSimpleName(), dir); } - + @Override public FileKeyManager start() { return this; } - + @Override public boolean isReady() { return true; } - + @Override public FileKeyManager stop() { return this; } @Override - public List<PublicKey> getKeys(String username) { + protected boolean isStale(String username) { + File keystore = getKeystore(username); + if (!keystore.exists()) { + // keystore may have been deleted + return true; + } + + if (lastModifieds.containsKey(keystore)) { + // compare modification times + long lastModified = lastModifieds.get(keystore); + return lastModified != keystore.lastModified(); + } + + // assume stale + return true; + } + + @Override + protected List<PublicKey> getKeysImpl(String username) { try { - File keys = getKeystore(username); - if (!keys.exists()) { + log.info("loading keystore for {}", username); + File keystore = getKeystore(username); + if (!keystore.exists()) { return null; } - if (keys.exists()) { - String str = Files.toString(keys, Charsets.ISO_8859_1); - String [] entries = str.split("\n"); + if (keystore.exists()) { List<PublicKey> list = new ArrayList<PublicKey>(); - for (String entry : entries) { + for (String entry : Files.readLines(keystore, Charsets.ISO_8859_1)) { if (entry.trim().length() == 0) { // skip blanks continue; @@ -90,10 +113,12 @@ final byte[] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1])); list.add(new Buffer(bin).getRawPublicKey()); } - + if (list.isEmpty()) { return null; } + + lastModifieds.put(keystore, keystore.lastModified()); return list; } } catch (IOException e) { @@ -102,41 +127,94 @@ return null; } + /** + * Adds a unique key to the keystore. This function determines uniqueness + * by disregarding the comment/description field during key comparisons. + */ @Override public boolean addKey(String username, String data) { try { - File keys = getKeystore(username); - Files.append(data + '\n', keys, Charsets.ISO_8859_1); + String newKey = stripCommentFromKey(data); + + List<String> lines = new ArrayList<String>(); + File keystore = getKeystore(username); + if (keystore.exists()) { + for (String entry : Files.readLines(keystore, Charsets.ISO_8859_1)) { + String line = entry.trim(); + if (line.length() == 0) { + // keep blanks + lines.add(entry); + continue; + } + if (line.charAt(0) == '#') { + // keep comments + lines.add(entry); + continue; + } + + // only add keys that do not match the new key + String oldKey = stripCommentFromKey(line); + if (!newKey.equals(oldKey)) { + lines.add(entry); + } + } + } + + // add new key + lines.add(data); + + // write keystore + String content = Joiner.on("\n").join(lines).trim().concat("\n"); + Files.write(content, keystore, Charsets.ISO_8859_1); + + lastModifieds.remove(keystore); + keyCache.invalidate(username); return true; } catch (IOException e) { throw new RuntimeException("Cannot add ssh key", e); } } - + + /** + * Removes a key from the keystore. + */ @Override public boolean removeKey(String username, String data) { try { + String rmKey = stripCommentFromKey(data); + File keystore = getKeystore(username); if (keystore.exists()) { - String str = Files.toString(keystore, Charsets.ISO_8859_1); - List<String> keep = new ArrayList<String>(); - String [] entries = str.split("\n"); - for (String entry : entries) { - if (entry.trim().length() == 0) { + List<String> lines = new ArrayList<String>(); + for (String entry : Files.readLines(keystore, Charsets.ISO_8859_1)) { + String line = entry.trim(); + if (line.length() == 0) { // keep blanks - keep.add(entry); + lines.add(entry); continue; } - if (entry.charAt(0) == '#') { + if (line.charAt(0) == '#') { // keep comments - keep.add(entry); + lines.add(entry); continue; } - final String[] parts = entry.split(" "); - if (!parts[1].equals(data)) { - keep.add(entry); + + // only include keys that are NOT rmKey + String oldKey = stripCommentFromKey(line); + if (!rmKey.equals(oldKey)) { + lines.add(entry); } } + if (lines.isEmpty()) { + keystore.delete(); + } else { + // write keystore + String content = Joiner.on("\n").join(lines).trim().concat("\n"); + Files.write(content, keystore, Charsets.ISO_8859_1); + } + + lastModifieds.remove(keystore); + keyCache.invalidate(username); return true; } } catch (IOException e) { @@ -144,11 +222,29 @@ } return false; } - + + @Override + public boolean removeAllKeys(String username) { + File keystore = getKeystore(username); + if (keystore.delete()) { + lastModifieds.remove(keystore); + keyCache.invalidate(username); + return true; + } + return false; + } + protected File getKeystore(String username) { File dir = runtimeManager.getFileOrFolder(Keys.git.sshKeysFolder, "${baseFolder}/ssh"); dir.mkdirs(); File keys = new File(dir, username + ".keys"); return keys; } + + /* Strips the comment from the key data and eliminates whitespace diffs */ + protected String stripCommentFromKey(String data) { + String [] cols = data.split(" "); + String key = Joiner.on(" ").join(cols[0], cols[1]); + return key; + } } -- Gitblit v1.9.1