1 /*
2 * Support for RSA/DSA key blacklisting based on partial fingerprints,
3 * developed under Openwall Project for Owl - http://www.openwall.com/Owl/
4 *
5 * Copyright (c) 2008 Dmitry V. Levin <ldv at cvs.openwall.com>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 *
19 * The blacklist encoding was designed by Solar Designer and Dmitry V. Levin.
20 * No intellectual property rights to the encoding scheme are claimed.
21 *
22 * This effort was supported by CivicActions - http://www.civicactions.com
23 *
24 * The file size to encode 294,903 of 48-bit fingerprints is just 1.3 MB,
25 * which corresponds to less than 4.5 bytes per fingerprint.
26 */
27
28 #include "mod_sftp.h"
29 #include "blacklist.h"
30 #include "keys.h"
31
32 struct blacklist_header {
33 /* format version identifier */
34 char version[8];
35
36 /* index size, in bits */
37 uint8_t index_size;
38
39 /* offset size, in bits */
40 uint8_t offset_size;
41
42 /* record size, in bits */
43 uint8_t record_bits;
44
45 /* number of records */
46 uint8_t records[3];
47
48 /* offset shift */
49 uint8_t shift[2];
50
51 };
52
53 /* Set a maximum number of records we expect to find in the blacklist file.
54 * The blacklist.dat file shipped with mod_sftp contains 294903 records.
55 */
56 #define SFTP_BLACKLIST_MAX_RECORDS 300000
57
58 static const char *blacklist_path = PR_CONFIG_DIR "/blacklist.dat";
59
60 static const char *trace_channel = "ssh2";
61
c2u(uint8_t c)62 static unsigned c2u(uint8_t c) {
63 return (c >= 'a') ? (c - 'a' + 10) : (c - '0');
64 }
65
validate_blacklist(int fd,unsigned int * bytes,unsigned int * records,unsigned int * shift)66 static int validate_blacklist(int fd, unsigned int *bytes,
67 unsigned int *records, unsigned int *shift) {
68
69 size_t expected;
70 struct stat st;
71 struct blacklist_header hdr;
72
73 if (fstat(fd, &st)) {
74 pr_trace_msg(trace_channel, 3, "error checking SFTPKeyBlacklist '%s': %s",
75 blacklist_path, strerror(errno));
76 return -1;
77 }
78
79 if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
80 pr_trace_msg(trace_channel, 3,
81 "error reading header of SFTPKeyBlacklist '%s': %s", blacklist_path,
82 strerror(errno));
83 return -1;
84 }
85
86 /* Check the header format and version */
87 if (memcmp(hdr.version, "SSH-FP", 6) != 0) {
88 pr_trace_msg(trace_channel, 2,
89 "SFTPKeyBlacklist '%s' has unknown format", blacklist_path);
90 return -1;
91 }
92
93 if (hdr.index_size != 16 ||
94 hdr.offset_size != 16 ||
95 memcmp(hdr.version, "SSH-FP00", 8) != 0) {
96 pr_trace_msg(trace_channel, 2,
97 "SFTPKeyBlacklist '%s' has unsupported format", blacklist_path);
98 return -1;
99 }
100
101 *bytes = (hdr.record_bits >> 3) - 2;
102
103 *records = (((hdr.records[0] << 8) + hdr.records[1]) << 8) + hdr.records[2];
104 if (*records > SFTP_BLACKLIST_MAX_RECORDS) {
105 pr_trace_msg(trace_channel, 2,
106 "SFTPKeyBlacklist '%s' contains %u records > max %u records",
107 blacklist_path, *records, (unsigned int) SFTP_BLACKLIST_MAX_RECORDS);
108 *records = SFTP_BLACKLIST_MAX_RECORDS;
109 }
110
111 *shift = (hdr.shift[0] << 8) + hdr.shift[1];
112
113 expected = sizeof(hdr) + 0x20000 + ((size_t) (*records) * (*bytes));
114 if (st.st_size != (off_t) expected) {
115 pr_trace_msg(trace_channel, 4,
116 "unexpected SFTPKeyBlacklist '%s' file size: expected %lu, found %lu",
117 blacklist_path, (unsigned long) expected, (unsigned long) st.st_size);
118 return -1;
119 }
120
121 return 0;
122 }
123
expected_offset(uint16_t idx,uint16_t shift,unsigned int records)124 static int expected_offset(uint16_t idx, uint16_t shift,
125 unsigned int records) {
126 return (int) (((idx * (long long) records) >> 16) - shift);
127 }
128
129 /* Returns -1 if there was an error, 1 if the fingerprint was found, and
130 * 0 otherwise.
131 */
check_fp(int fd,const char * fp_str)132 static int check_fp(int fd, const char *fp_str) {
133 register unsigned int i;
134 unsigned int bytes, num, records, shift;
135 off_t offset;
136 int off_start, off_end, res;
137 uint16_t idx;
138
139 /* Max number of bytes stored in record_bits, minus two bytes used for
140 * index.
141 */
142 uint8_t buf[(0xff >> 3) - 2];
143
144 res = validate_blacklist(fd, &bytes, &records, &shift);
145 if (res < 0)
146 return res;
147
148 idx = (((((c2u(fp_str[0]) << 4) | c2u(fp_str[1])) << 4) |
149 c2u(fp_str[2])) << 4) | c2u(fp_str[3]);
150
151 offset = sizeof(struct blacklist_header) + (idx * 2);
152 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
153 pr_trace_msg(trace_channel, 3, "error seeking to offset %" PR_LU
154 " in SFTPKeyBlacklist '%s': %s", (pr_off_t) offset, blacklist_path,
155 strerror(errno));
156 return -1;
157 }
158
159 if (read(fd, buf, 4) != 4) {
160 pr_trace_msg(trace_channel, 3, "error reading SFTPKeyBlacklist '%s': %s",
161 blacklist_path, strerror(errno));
162 return -1;
163 }
164
165 off_start = (buf[0] << 8) + buf[1] + expected_offset(idx, shift, records);
166
167 if (off_start < 0 ||
168 (unsigned int) off_start > records) {
169 pr_trace_msg(trace_channel, 4,
170 "SFTPKeyBlacklist '%s' has offset start overflow [%d] for index %#x",
171 blacklist_path, off_start, idx);
172 return -1;
173 }
174
175 if (idx < 0xffff) {
176 off_end = (buf[2] << 8) + buf[3] +
177 expected_offset(idx + 1, shift, records);
178
179 if (off_end < off_start ||
180 (unsigned int) off_end > records) {
181 pr_trace_msg(trace_channel, 4,
182 "SFTPKeyBlacklist '%s' has offset end overflow [%d] for index %#x",
183 blacklist_path, off_start, idx);
184 return -1;
185 }
186
187 } else {
188 off_end = records;
189 }
190
191 offset = sizeof(struct blacklist_header) + 0x20000 + (off_start * bytes);
192 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
193 pr_trace_msg(trace_channel, 3, "error seeking to offset %" PR_LU
194 " in SFTPKeyBlacklist '%s': %s", (pr_off_t) offset, blacklist_path,
195 strerror(errno));
196 return -1;
197 }
198
199 num = off_end - off_start;
200
201 for (i = 0; i < num; ++i) {
202 register unsigned int j;
203
204 if (read(fd, buf, bytes) != bytes) {
205 pr_trace_msg(trace_channel, 2, "error reading SFTPKeyBlacklist '%s': %s",
206 blacklist_path, strerror(errno));
207 return -1;
208 }
209
210 for (j = 0; j < bytes; ++j) {
211 if (((c2u(fp_str[4 + j * 2]) << 4) | c2u(fp_str[5 + j * 2])) != buf[j])
212 break;
213 }
214
215 if (j >= bytes) {
216 pr_trace_msg(trace_channel, 6,
217 "fingerprint '%s' blacklisted (offset %u, number %u)", fp_str,
218 off_start, i);
219 (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
220 "public key is blacklisted");
221 return 1;
222 }
223 }
224
225 pr_trace_msg(trace_channel, 12,
226 "fingerprint '%s' not blacklisted (offset %u, number %u)", fp_str,
227 off_start, num);
228 return 0;
229 }
230
sftp_blacklist_reject_key(pool * p,unsigned char * key_data,uint32_t key_datalen)231 int sftp_blacklist_reject_key(pool *p, unsigned char *key_data,
232 uint32_t key_datalen) {
233 int fd, res;
234 const char *fp;
235 char *digest_name = "none", *hex, *ptr;
236 size_t hex_len, hex_maxlen;
237
238 if (key_data == NULL ||
239 key_datalen == 0) {
240 return FALSE;
241 }
242
243 if (blacklist_path == NULL) {
244 /* No key blacklist configured, nothing to do. */
245 return FALSE;
246 }
247
248 #ifdef OPENSSL_FIPS
249 if (FIPS_mode()) {
250 /* Use SHA1 fingerprints when in FIPS mode, since FIPS does not allow the
251 * MD5 algorithm.
252 */
253 digest_name = "SHA1";
254 fp = sftp_keys_get_fingerprint(p, key_data, key_datalen,
255 SFTP_KEYS_FP_DIGEST_SHA1);
256
257 /* SHA1 digests are 20 bytes (40 bytes when hex-encoded). */
258 hex_maxlen = 40;
259
260 } else {
261 #endif /* OPENSSL_FIPS */
262 digest_name = "MD5";
263 fp = sftp_keys_get_fingerprint(p, key_data, key_datalen,
264 SFTP_KEYS_FP_DIGEST_MD5);
265
266 /* MD5 digests are 16 bytes (32 bytes when hex-encoded). */
267 hex_maxlen = 32;
268 #ifdef OPENSSL_FIPS
269 }
270 #endif /* OPENSSL_FIPS */
271
272 /* If we can't obtain a fingerprint for any reason, assume the key is OK. */
273 if (fp == NULL) {
274 (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
275 "unable to obtain %s fingerprint for checking against blacklist: %s",
276 digest_name, strerror(errno));
277 return FALSE;
278 }
279
280 pr_trace_msg(trace_channel, 5,
281 "checking key %s fingerprint against SFTPKeyBlacklist '%s'",
282 digest_name, blacklist_path);
283
284 /* Get a version of the fingerprint sans the colon delimiters. */
285 hex = pstrdup(p, fp);
286 for (ptr = hex; *fp; ++fp) {
287 pr_signals_handle();
288
289 if (*fp != ':')
290 *ptr++ = *fp;
291 }
292 *ptr = '\0';
293
294 hex_len = strlen(hex);
295 if (hex_len != hex_maxlen ||
296 hex_len != strspn(hex, "0123456789abcdef")) {
297 pr_trace_msg(trace_channel, 3, "invalid %s fingerprint: '%s'", digest_name,
298 hex);
299 return FALSE;
300 }
301
302 /* XXX Will this fd need to be cached, for handling keys after the
303 * process has chrooted?
304 */
305 fd = open(blacklist_path, O_RDONLY);
306 if (fd < 0) {
307 (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
308 "unable to open SFTPKeyBlacklist '%s': %s", blacklist_path,
309 strerror(errno));
310 return FALSE;
311 }
312
313 res = check_fp(fd, hex);
314 close(fd);
315
316 if (res == 1)
317 return TRUE;
318
319 return FALSE;
320 }
321
sftp_blacklist_set_file(const char * path)322 int sftp_blacklist_set_file(const char *path) {
323 if (path == NULL) {
324 blacklist_path = NULL;
325 }
326
327 blacklist_path = pstrdup(sftp_pool, path);
328 return 0;
329 }
330