| | |
| | | |
| | | 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 org.apache.commons.codec.binary.Base64; |
| | | import org.apache.sshd.common.util.Buffer; |
| | | import org.eclipse.jgit.lib.Constants; |
| | | import java.util.Map; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | import com.gitblit.Keys; |
| | | import com.gitblit.manager.IRuntimeManager; |
| | | import com.gitblit.utils.FileUtils; |
| | | import com.google.common.base.Charsets; |
| | | import com.google.common.base.Joiner; |
| | | import com.google.common.io.Files; |
| | | |
| | | /** |
| | | * Manages SSH keys on the filesystem. |
| | | * |
| | | * Manages public keys on the filesystem. |
| | | * |
| | | * @author James Moger |
| | | * |
| | | */ |
| | | public class FileKeyManager implements IKeyManager { |
| | | public class FileKeyManager extends IPublicKeyManager { |
| | | |
| | | 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() { |
| | | log.info(toString()); |
| | | 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<SshKey> 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"); |
| | | List<PublicKey> list = new ArrayList<PublicKey>(); |
| | | for (String entry : entries) { |
| | | if (keystore.exists()) { |
| | | List<SshKey> list = new ArrayList<SshKey>(); |
| | | for (String entry : Files.readLines(keystore, Charsets.ISO_8859_1)) { |
| | | if (entry.trim().length() == 0) { |
| | | // skip blanks |
| | | continue; |
| | |
| | | // 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()) { |
| | | return null; |
| | | } |
| | | |
| | | lastModifieds.put(keystore, keystore.lastModified()); |
| | | return list; |
| | | } |
| | | } catch (IOException e) { |
| | |
| | | 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) { |
| | | public boolean addKey(String username, SshKey key) { |
| | | try { |
| | | File keys = getKeystore(username); |
| | | Files.append(data + '\n', keys, Charsets.ISO_8859_1); |
| | | String newKey = stripCommentFromKey(key.getRawData()); |
| | | boolean replaced = false; |
| | | 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; |
| | | } |
| | | |
| | | String oldKey = stripCommentFromKey(line); |
| | | if (newKey.equals(oldKey)) { |
| | | // replace key |
| | | lines.add(key.getRawData()); |
| | | replaced = true; |
| | | } else { |
| | | // retain key |
| | | lines.add(entry); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (!replaced) { |
| | | // new key, append |
| | | lines.add(key.getRawData()); |
| | | } |
| | | |
| | | // 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 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(key.getRawData()); |
| | | |
| | | 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) { |
| | |
| | | } |
| | | |
| | | @Override |
| | | public boolean removeAllKey(String username) { |
| | | return FileUtils.delete(getKeystore(username)); |
| | | 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 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(" ", 3); |
| | | String key = Joiner.on(" ").join(cols[0], cols[1]); |
| | | return key; |
| | | } |
| | | } |