1 /*
2 * Dropbear - a SSH2 server
3 *
4 * Copyright (c) 2002,2003 Matt Johnston
5 * All rights reserved.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE. */
24
25 /* Process a pubkey auth request */
26
27 #include "includes.h"
28 #include "session.h"
29 #include "dbutil.h"
30 #include "buffer.h"
31 #include "signkey.h"
32 #include "auth.h"
33 #include "ssh.h"
34 #include "packet.h"
35 #include "algo.h"
36
37 #ifdef ENABLE_SVR_PUBKEY_AUTH
38
39 #define MIN_AUTHKEYS_LINE 10 /* "ssh-rsa AB" - short but doesn't matter */
40 #define MAX_AUTHKEYS_LINE 4200 /* max length of a line in authkeys */
41
42 static int checkpubkey(unsigned char* algo, unsigned int algolen,
43 unsigned char* keyblob, unsigned int keybloblen);
44 static int checkpubkeyperms();
45 static void send_msg_userauth_pk_ok(unsigned char* algo, unsigned int algolen,
46 unsigned char* keyblob, unsigned int keybloblen);
47 static int checkfileperm(char * filename);
48
49 /* process a pubkey auth request, sending success or failure message as
50 * appropriate */
svr_auth_pubkey()51 void svr_auth_pubkey() {
52
53 unsigned char testkey; /* whether we're just checking if a key is usable */
54 unsigned char* algo = NULL; /* pubkey algo */
55 unsigned int algolen;
56 unsigned char* keyblob = NULL;
57 unsigned int keybloblen;
58 buffer * signbuf = NULL;
59 sign_key * key = NULL;
60 char* fp = NULL;
61 int type = -1;
62
63 TRACE(("enter pubkeyauth"))
64
65 /* 0 indicates user just wants to check if key can be used, 1 is an
66 * actual attempt*/
67 testkey = (buf_getbool(ses.payload) == 0);
68
69 algo = buf_getstring(ses.payload, &algolen);
70 keybloblen = buf_getint(ses.payload);
71 keyblob = buf_getptr(ses.payload, keybloblen);
72
73 /* check if the key is valid */
74 if (checkpubkey(algo, algolen, keyblob, keybloblen) == DROPBEAR_FAILURE) {
75 send_msg_userauth_failure(0, 0);
76 goto out;
77 }
78
79 /* let them know that the key is ok to use */
80 if (testkey) {
81 send_msg_userauth_pk_ok(algo, algolen, keyblob, keybloblen);
82 goto out;
83 }
84
85 /* now we can actually verify the signature */
86
87 /* get the key */
88 key = new_sign_key();
89 type = DROPBEAR_SIGNKEY_ANY;
90 if (buf_get_pub_key(ses.payload, key, &type) == DROPBEAR_FAILURE) {
91 send_msg_userauth_failure(0, 1);
92 goto out;
93 }
94
95 /* create the data which has been signed - this a string containing
96 * session_id, concatenated with the payload packet up to the signature */
97 signbuf = buf_new(ses.payload->pos + 4 + SHA1_HASH_SIZE);
98 buf_putstring(signbuf, ses.session_id, SHA1_HASH_SIZE);
99 buf_putbytes(signbuf, ses.payload->data, ses.payload->pos);
100 buf_setpos(signbuf, 0);
101
102 /* ... and finally verify the signature */
103 fp = sign_key_fingerprint(keyblob, keybloblen);
104 if (buf_verify(ses.payload, key, buf_getptr(signbuf, signbuf->len),
105 signbuf->len) == DROPBEAR_SUCCESS) {
106 dropbear_log(LOG_NOTICE,
107 "pubkey auth succeeded for '%s' with key %s from %s",
108 ses.authstate.printableuser, fp, svr_ses.addrstring);
109 send_msg_userauth_success();
110 } else {
111 dropbear_log(LOG_WARNING,
112 "pubkey auth bad signature for '%s' with key %s from %s",
113 ses.authstate.printableuser, fp, svr_ses.addrstring);
114 send_msg_userauth_failure(0, 1);
115 }
116 m_free(fp);
117
118 out:
119 /* cleanup stuff */
120 if (signbuf) {
121 buf_free(signbuf);
122 }
123 if (algo) {
124 m_free(algo);
125 }
126 if (key) {
127 sign_key_free(key);
128 key = NULL;
129 }
130 TRACE(("leave pubkeyauth"))
131 }
132
133 /* Reply that the key is valid for auth, this is sent when the user sends
134 * a straight copy of their pubkey to test, to avoid having to perform
135 * expensive signing operations with a worthless key */
send_msg_userauth_pk_ok(unsigned char * algo,unsigned int algolen,unsigned char * keyblob,unsigned int keybloblen)136 static void send_msg_userauth_pk_ok(unsigned char* algo, unsigned int algolen,
137 unsigned char* keyblob, unsigned int keybloblen) {
138
139 TRACE(("enter send_msg_userauth_pk_ok"))
140 CHECKCLEARTOWRITE();
141
142 buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_PK_OK);
143 buf_putstring(ses.writepayload, algo, algolen);
144 buf_putstring(ses.writepayload, keyblob, keybloblen);
145
146 encrypt_packet();
147 TRACE(("leave send_msg_userauth_pk_ok"))
148
149 }
150
151 /* Checks whether a specified publickey (and associated algorithm) is an
152 * acceptable key for authentication */
153 /* Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */
checkpubkey(unsigned char * algo,unsigned int algolen,unsigned char * keyblob,unsigned int keybloblen)154 static int checkpubkey(unsigned char* algo, unsigned int algolen,
155 unsigned char* keyblob, unsigned int keybloblen) {
156
157 FILE * authfile = NULL;
158 char * filename = NULL;
159 int ret = DROPBEAR_FAILURE;
160 buffer * line = NULL;
161 unsigned int len, pos;
162
163 TRACE(("enter checkpubkey"))
164
165 /* check that we can use the algo */
166 if (have_algo(algo, algolen, sshhostkey) == DROPBEAR_FAILURE) {
167 dropbear_log(LOG_WARNING,
168 "pubkey auth attempt with unknown algo for '%s' from %s",
169 ses.authstate.printableuser, svr_ses.addrstring);
170 goto out;
171 }
172
173 /* check file permissions, also whether file exists */
174 if (checkpubkeyperms() == DROPBEAR_FAILURE) {
175 TRACE(("bad authorized_keys permissions, or file doesn't exist"))
176 goto out;
177 }
178
179 /* we don't need to check pw and pw_dir for validity, since
180 * its been done in checkpubkeyperms. */
181 len = strlen(ses.authstate.pw->pw_dir);
182 /* allocate max required pathname storage,
183 * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
184 filename = m_malloc(len + 22);
185 snprintf(filename, len + 22, "%s/.ssh/authorized_keys",
186 ses.authstate.pw->pw_dir);
187
188 /* open the file */
189 authfile = fopen(filename, "r");
190 if (authfile == NULL) {
191 goto out;
192 }
193 TRACE(("checkpubkey: opened authorized_keys OK"))
194
195 line = buf_new(MAX_AUTHKEYS_LINE);
196
197 /* iterate through the lines */
198 do {
199
200 if (buf_getline(line, authfile) == DROPBEAR_FAILURE) {
201 /* EOF reached */
202 TRACE(("checkpubkey: authorized_keys EOF reached"))
203 break;
204 }
205
206 if (line->len < MIN_AUTHKEYS_LINE) {
207 TRACE(("checkpubkey: line too short"))
208 continue; /* line is too short for it to be a valid key */
209 }
210
211 /* check the key type - this also stops us from using keys
212 * which have options with them */
213 if (strncmp(buf_getptr(line, algolen), algo, algolen) != 0) {
214 continue;
215 }
216 buf_incrpos(line, algolen);
217
218 /* check for space (' ') character */
219 if (buf_getbyte(line) != ' ') {
220 TRACE(("checkpubkey: space character expected, isn't there"))
221 continue;
222 }
223
224 /* truncate the line at the space after the base64 data */
225 pos = line->pos;
226 for (len = 0; line->pos < line->len; len++) {
227 if (buf_getbyte(line) == ' ') break;
228 }
229 buf_setpos(line, pos);
230 buf_setlen(line, line->pos + len);
231
232 TRACE(("checkpubkey: line pos = %d len = %d", line->pos, line->len))
233
234 ret = cmp_base64_key(keyblob, keybloblen, algo, algolen, line, NULL);
235 if (ret == DROPBEAR_SUCCESS) {
236 break;
237 }
238
239 /* We continue to the next line otherwise */
240
241 } while (1);
242
243 out:
244 if (authfile) {
245 fclose(authfile);
246 }
247 if (line) {
248 buf_free(line);
249 }
250 m_free(filename);
251 TRACE(("leave checkpubkey: ret=%d", ret))
252 return ret;
253 }
254
255
256 /* Returns DROPBEAR_SUCCESS if file permissions for pubkeys are ok,
257 * DROPBEAR_FAILURE otherwise.
258 * Checks that the user's homedir, ~/.ssh, and
259 * ~/.ssh/authorized_keys are all owned by either root or the user, and are
260 * g-w, o-w */
checkpubkeyperms()261 static int checkpubkeyperms() {
262
263 char* filename = NULL;
264 int ret = DROPBEAR_FAILURE;
265 unsigned int len;
266
267 TRACE(("enter checkpubkeyperms"))
268
269 if (ses.authstate.pw->pw_dir == NULL) {
270 goto out;
271 }
272
273 if ((len = strlen(ses.authstate.pw->pw_dir)) == 0) {
274 goto out;
275 }
276
277 /* allocate max required pathname storage,
278 * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
279 filename = m_malloc(len + 22);
280 strncpy(filename, ses.authstate.pw->pw_dir, len+1);
281
282 /* check ~ */
283 if (checkfileperm(filename) != DROPBEAR_SUCCESS) {
284 goto out;
285 }
286
287 /* check ~/.ssh */
288 strncat(filename, "/.ssh", 5); /* strlen("/.ssh") == 5 */
289 if (checkfileperm(filename) != DROPBEAR_SUCCESS) {
290 goto out;
291 }
292
293 /* now check ~/.ssh/authorized_keys */
294 strncat(filename, "/authorized_keys", 16);
295 if (checkfileperm(filename) != DROPBEAR_SUCCESS) {
296 goto out;
297 }
298
299 /* file looks ok, return success */
300 ret = DROPBEAR_SUCCESS;
301
302 out:
303 m_free(filename);
304
305 TRACE(("leave checkpubkeyperms"))
306 return ret;
307 }
308
309 /* Checks that a file is owned by the user or root, and isn't writable by
310 * group or other */
311 /* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
checkfileperm(char * filename)312 static int checkfileperm(char * filename) {
313 struct stat filestat;
314 int badperm = 0;
315
316 TRACE(("enter checkfileperm(%s)", filename))
317
318 if (stat(filename, &filestat) != 0) {
319 TRACE(("leave checkfileperm: stat() != 0"))
320 return DROPBEAR_FAILURE;
321 }
322 /* check ownership - user or root only*/
323 if (filestat.st_uid != ses.authstate.pw->pw_uid
324 && filestat.st_uid != 0) {
325 badperm = 1;
326 TRACE(("wrong ownership"))
327 }
328 /* check permissions - don't want group or others +w */
329 if (filestat.st_mode & (S_IWGRP | S_IWOTH)) {
330 badperm = 1;
331 TRACE(("wrong perms"))
332 }
333 if (badperm) {
334 if (!ses.authstate.perm_warn) {
335 ses.authstate.perm_warn = 1;
336 dropbear_log(LOG_INFO, "%s must be owned by user or root, and not writable by others", filename);
337 }
338 TRACE(("leave checkfileperm: failure perms/owner"))
339 return DROPBEAR_FAILURE;
340 }
341
342 TRACE(("leave checkfileperm: success"))
343 return DROPBEAR_SUCCESS;
344 }
345
346
347 #endif
348