Mercurial > dropbear
comparison svr-authpubkey.c @ 284:eed26cff980b
propagate from branch 'au.asn.ucc.matt.ltm.dropbear' (head 6c790cad5a7fa866ad062cb3a0c279f7ba788583)
to branch 'au.asn.ucc.matt.dropbear' (head fff0894a0399405a9410ea1c6d118f342cf2aa64)
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Wed, 08 Mar 2006 13:23:49 +0000 |
parents | bf64e666f99b |
children | 7282370416a0 |
comparison
equal
deleted
inserted
replaced
283:bd240aa12ba7 | 284:eed26cff980b |
---|---|
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 */ | |
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 */ | |
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 */ | |
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); | |
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 */ | |
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 */ | |
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 |