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