James Moger
2014-03-14 8982e6e0738c6991b9a4b864423bd4f75383c7f4
commit | author | age
b53611 1 /*
JM 2  * Copyright 2014 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 package com.gitblit.transport.ssh;
17
18 import java.io.File;
19 import java.io.IOException;
20 import java.security.PublicKey;
21 import java.text.MessageFormat;
22 import java.util.ArrayList;
23 import java.util.List;
24
25 import org.apache.commons.codec.binary.Base64;
26 import org.apache.sshd.common.util.Buffer;
27 import org.eclipse.jgit.lib.Constants;
28
29 import com.gitblit.Keys;
30 import com.gitblit.manager.IRuntimeManager;
31 import com.google.common.base.Charsets;
8384e0 32 import com.google.common.base.Joiner;
b53611 33 import com.google.common.io.Files;
JM 34
35 /**
36  * Manages SSH keys on the filesystem.
8384e0 37  *
b53611 38  * @author James Moger
JM 39  *
40  */
41 public class FileKeyManager implements IKeyManager {
42
43     protected final IRuntimeManager runtimeManager;
8384e0 44
b53611 45     public FileKeyManager(IRuntimeManager runtimeManager) {
JM 46         this.runtimeManager = runtimeManager;
47     }
8384e0 48
b53611 49     @Override
JM 50     public String toString() {
51         File dir = runtimeManager.getFileOrFolder(Keys.git.sshKeysFolder, "${baseFolder}/ssh");
52         return MessageFormat.format("{0} ({1})", getClass().getSimpleName(), dir);
53     }
8384e0 54
b53611 55     @Override
JM 56     public FileKeyManager start() {
57         return this;
58     }
8384e0 59
b53611 60     @Override
JM 61     public boolean isReady() {
62         return true;
63     }
8384e0 64
b53611 65     @Override
JM 66     public FileKeyManager stop() {
67         return this;
68     }
69
70     @Override
71     public List<PublicKey> getKeys(String username) {
72         try {
73             File keys = getKeystore(username);
74             if (!keys.exists()) {
75                 return null;
76             }
77             if (keys.exists()) {
78                 List<PublicKey> list = new ArrayList<PublicKey>();
8384e0 79                 for (String entry : Files.readLines(keys, Charsets.ISO_8859_1)) {
b53611 80                     if (entry.trim().length() == 0) {
JM 81                         // skip blanks
82                         continue;
83                     }
84                     if (entry.charAt(0) == '#') {
85                         // skip comments
86                         continue;
87                     }
88                     final String[] parts = entry.split(" ");
89                     final byte[] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1]));
90                     list.add(new Buffer(bin).getRawPublicKey());
91                 }
8384e0 92
b53611 93                 if (list.isEmpty()) {
JM 94                     return null;
95                 }
96                 return list;
97             }
98         } catch (IOException e) {
99             throw new RuntimeException("Canot read ssh keys", e);
100         }
101         return null;
102     }
103
8384e0 104     /**
JM 105      * Adds a unique key to the keystore.  This function determines uniqueness
106      * by disregarding the comment/description field during key comparisons.
107      */
b53611 108     @Override
JM 109     public boolean addKey(String username, String data) {
110         try {
8384e0 111             String newKey = stripCommentFromKey(data);
JM 112
113             List<String> lines = new ArrayList<String>();
114             File keystore = getKeystore(username);
115             if (keystore.exists()) {
116                 for (String entry : Files.readLines(keystore, Charsets.ISO_8859_1)) {
117                     String line = entry.trim();
118                     if (line.length() == 0) {
119                         // keep blanks
120                         lines.add(entry);
121                         continue;
122                     }
123                     if (line.charAt(0) == '#') {
124                         // keep comments
125                         lines.add(entry);
126                         continue;
127                     }
128
129                     // only add keys that do not match the new key
130                     String oldKey = stripCommentFromKey(line);
131                     if (!newKey.equals(oldKey)) {
132                         lines.add(entry);
133                     }
134                 }
135             }
136
137             // add new key
138             lines.add(data);
139
140             // write keystore
141             String content = Joiner.on("\n").join(lines).trim().concat("\n");
142             Files.write(content, keystore, Charsets.ISO_8859_1);
b53611 143             return true;
JM 144         } catch (IOException e) {
145             throw new RuntimeException("Cannot add ssh key", e);
146         }
147     }
8384e0 148
JM 149     /**
150      * Removes a key from the keystore.
151      */
b53611 152     @Override
JM 153     public boolean removeKey(String username, String data) {
154         try {
8384e0 155             String rmKey = stripCommentFromKey(data);
JM 156
b53611 157             File keystore = getKeystore(username);
JM 158             if (keystore.exists()) {
8384e0 159                 List<String> lines = new ArrayList<String>();
JM 160                 for (String entry : Files.readLines(keystore, Charsets.ISO_8859_1)) {
161                     String line = entry.trim();
162                     if (line.length() == 0) {
b53611 163                         // keep blanks
8384e0 164                         lines.add(entry);
b53611 165                         continue;
JM 166                     }
8384e0 167                     if (line.charAt(0) == '#') {
b53611 168                         // keep comments
8384e0 169                         lines.add(entry);
b53611 170                         continue;
JM 171                     }
8384e0 172
JM 173                     // only include keys that are NOT rmKey
174                     String oldKey = stripCommentFromKey(line);
175                     if (!rmKey.equals(oldKey)) {
176                         lines.add(entry);
b53611 177                     }
8384e0 178                 }
JM 179                 if (lines.isEmpty()) {
180                     keystore.delete();
181                 } else {
182                     // write keystore
183                     String content = Joiner.on("\n").join(lines).trim().concat("\n");
184                     Files.write(content, keystore, Charsets.ISO_8859_1);
b53611 185                 }
JM 186                 return true;
187             }
188         } catch (IOException e) {
189             throw new RuntimeException("Cannot remove ssh key", e);
190         }
191         return false;
192     }
5eafd9 193
DO 194     @Override
8ee7c8 195     public boolean removeAllKeys(String username) {
8384e0 196         return getKeystore(username).delete();
5eafd9 197     }
DO 198
b53611 199     protected File getKeystore(String username) {
JM 200         File dir = runtimeManager.getFileOrFolder(Keys.git.sshKeysFolder, "${baseFolder}/ssh");
201         dir.mkdirs();
202         File keys = new File(dir, username + ".keys");
203         return keys;
204     }
8384e0 205
JM 206     /* Strips the comment from the key data and eliminates whitespace diffs */
207     protected String stripCommentFromKey(String data) {
208         String [] cols = data.split(" ");
209         String key = Joiner.on(" ").join(cols[0], cols[1]);
210         return key;
211     }
b53611 212 }