2 * Dropbear - a SSH2 server
4 * Copyright (c) 2002,2003 Matt Johnston
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:
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
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
25 * This file incorporates work covered by the following copyright and
28 * Copyright (c) 2000 Markus Friedl. All rights reserved.
30 * Redistribution and use in source and binary forms, with or without
31 * modification, are permitted provided that the following conditions
33 * 1. Redistributions of source code must retain the above copyright
34 * notice, this list of conditions and the following disclaimer.
35 * 2. Redistributions in binary form must reproduce the above copyright
36 * notice, this list of conditions and the following disclaimer in the
37 * documentation and/or other materials provided with the distribution.
39 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
40 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
41 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
42 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
43 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
44 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
45 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
46 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
47 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
48 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50 * This copyright and permission notice applies to the code parsing public keys
51 * options string which can also be found in OpenSSH auth2-pubkey.c file
52 * (user_key_allowed2). It has been adapted to work with buffers.
56 /* Process a pubkey auth request */
68 #ifdef ENABLE_SVR_PUBKEY_AUTH
70 #define MIN_AUTHKEYS_LINE 10 /* "ssh-rsa AB" - short but doesn't matter */
71 #define MAX_AUTHKEYS_LINE 4200 /* max length of a line in authkeys */
73 static int checkpubkey(unsigned char* algo
, unsigned int algolen
,
74 unsigned char* keyblob
, unsigned int keybloblen
);
75 static int checkpubkeyperms();
76 static void send_msg_userauth_pk_ok(unsigned char* algo
, unsigned int algolen
,
77 unsigned char* keyblob
, unsigned int keybloblen
);
78 static int checkfileperm(char * filename
);
80 /* process a pubkey auth request, sending success or failure message as
82 void svr_auth_pubkey() {
84 unsigned char testkey
; /* whether we're just checking if a key is usable */
85 unsigned char* algo
= NULL
; /* pubkey algo */
87 unsigned char* keyblob
= NULL
;
88 unsigned int keybloblen
;
89 buffer
* signbuf
= NULL
;
90 sign_key
* key
= NULL
;
94 TRACE(("enter pubkeyauth"))
96 /* 0 indicates user just wants to check if key can be used, 1 is an
98 testkey
= (buf_getbool(ses
.payload
) == 0);
100 algo
= buf_getstring(ses
.payload
, &algolen
);
101 keybloblen
= buf_getint(ses
.payload
);
102 keyblob
= buf_getptr(ses
.payload
, keybloblen
);
104 /* check if the key is valid */
105 if (checkpubkey(algo
, algolen
, keyblob
, keybloblen
) == DROPBEAR_FAILURE
) {
106 send_msg_userauth_failure(0, 0);
110 /* let them know that the key is ok to use */
112 send_msg_userauth_pk_ok(algo
, algolen
, keyblob
, keybloblen
);
116 /* now we can actually verify the signature */
119 key
= new_sign_key();
120 type
= DROPBEAR_SIGNKEY_ANY
;
121 if (buf_get_pub_key(ses
.payload
, key
, &type
) == DROPBEAR_FAILURE
) {
122 send_msg_userauth_failure(0, 1);
126 /* create the data which has been signed - this a string containing
127 * session_id, concatenated with the payload packet up to the signature */
128 signbuf
= buf_new(ses
.payload
->pos
+ 4 + SHA1_HASH_SIZE
);
129 buf_putstring(signbuf
, ses
.session_id
, SHA1_HASH_SIZE
);
130 buf_putbytes(signbuf
, ses
.payload
->data
, ses
.payload
->pos
);
131 buf_setpos(signbuf
, 0);
133 /* ... and finally verify the signature */
134 fp
= sign_key_fingerprint(keyblob
, keybloblen
);
135 if (buf_verify(ses
.payload
, key
, buf_getptr(signbuf
, signbuf
->len
),
136 signbuf
->len
) == DROPBEAR_SUCCESS
) {
137 dropbear_log(LOG_NOTICE
,
138 "Pubkey auth succeeded for '%s' with key %s from %s",
139 ses
.authstate
.pw_name
, fp
, svr_ses
.addrstring
);
140 send_msg_userauth_success();
142 dropbear_log(LOG_WARNING
,
143 "Pubkey auth bad signature for '%s' with key %s from %s",
144 ses
.authstate
.pw_name
, fp
, svr_ses
.addrstring
);
145 send_msg_userauth_failure(0, 1);
161 TRACE(("leave pubkeyauth"))
164 /* Reply that the key is valid for auth, this is sent when the user sends
165 * a straight copy of their pubkey to test, to avoid having to perform
166 * expensive signing operations with a worthless key */
167 static void send_msg_userauth_pk_ok(unsigned char* algo
, unsigned int algolen
,
168 unsigned char* keyblob
, unsigned int keybloblen
) {
170 TRACE(("enter send_msg_userauth_pk_ok"))
173 buf_putbyte(ses
.writepayload
, SSH_MSG_USERAUTH_PK_OK
);
174 buf_putstring(ses
.writepayload
, algo
, algolen
);
175 buf_putstring(ses
.writepayload
, keyblob
, keybloblen
);
178 TRACE(("leave send_msg_userauth_pk_ok"))
182 /* Checks whether a specified publickey (and associated algorithm) is an
183 * acceptable key for authentication */
184 /* Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */
185 static int checkpubkey(unsigned char* algo
, unsigned int algolen
,
186 unsigned char* keyblob
, unsigned int keybloblen
) {
188 FILE * authfile
= NULL
;
189 char * filename
= NULL
;
190 int ret
= DROPBEAR_FAILURE
;
191 buffer
* line
= NULL
;
192 unsigned int len
, pos
;
193 buffer
* options_buf
= NULL
;
196 TRACE(("enter checkpubkey"))
198 /* check that we can use the algo */
199 if (have_algo(algo
, algolen
, sshhostkey
) == DROPBEAR_FAILURE
) {
200 dropbear_log(LOG_WARNING
,
201 "Pubkey auth attempt with unknown algo for '%s' from %s",
202 ses
.authstate
.pw_name
, svr_ses
.addrstring
);
206 /* check file permissions, also whether file exists */
207 if (checkpubkeyperms() == DROPBEAR_FAILURE
) {
208 TRACE(("bad authorized_keys permissions, or file doesn't exist"))
212 /* we don't need to check pw and pw_dir for validity, since
213 * its been done in checkpubkeyperms. */
214 len
= strlen(ses
.authstate
.pw_dir
);
215 /* allocate max required pathname storage,
216 * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
217 filename
= m_malloc(len
+ 22);
218 snprintf(filename
, len
+ 22, "%s/.ssh/authorized_keys",
219 ses
.authstate
.pw_dir
);
222 authfile
= fopen(filename
, "r");
223 if (authfile
== NULL
) {
226 TRACE(("checkpubkey: opened authorized_keys OK"))
228 line
= buf_new(MAX_AUTHKEYS_LINE
);
231 /* iterate through the lines */
233 /* new line : potentially new options */
235 buf_free(options_buf
);
239 if (buf_getline(line
, authfile
) == DROPBEAR_FAILURE
) {
241 TRACE(("checkpubkey: authorized_keys EOF reached"))
246 if (line
->len
< MIN_AUTHKEYS_LINE
) {
247 TRACE(("checkpubkey: line too short"))
248 continue; /* line is too short for it to be a valid key */
251 /* check the key type - will fail if there are options */
254 if (strncmp(buf_getptr(line
, algolen
), algo
, algolen
) != 0) {
256 char *options_start
= NULL
;
260 /* skip over any comments or leading whitespace */
261 while (line
->pos
< line
->len
) {
262 const char c
= buf_getbyte(line
);
263 if (c
== ' ' || c
== '\t') {
265 } else if (c
== '#') {
269 buf_incrpos(line
, -1);
277 /* remember start of options */
278 options_start
= buf_getptr(line
, 1);
283 /* figure out where the options are */
284 while (line
->pos
< line
->len
) {
285 const char c
= buf_getbyte(line
);
286 if (!quoted
&& (c
== ' ' || c
== '\t')) {
289 escape
= (!escape
&& c
== '\\');
290 if (!escape
&& c
== '"') {
295 options_buf
= buf_new(options_len
);
296 buf_putbytes(options_buf
, options_start
, options_len
);
298 /* compare the algorithm */
299 if (line
->pos
+ algolen
> line
->len
) {
302 if (strncmp(buf_getptr(line
, algolen
), algo
, algolen
) != 0) {
306 buf_incrpos(line
, algolen
);
308 /* check for space (' ') character */
309 if (buf_getbyte(line
) != ' ') {
310 TRACE(("checkpubkey: space character expected, isn't there"))
314 /* truncate the line at the space after the base64 data */
316 for (len
= 0; line
->pos
< line
->len
; len
++) {
317 if (buf_getbyte(line
) == ' ') break;
319 buf_setpos(line
, pos
);
320 buf_setlen(line
, line
->pos
+ len
);
322 TRACE(("checkpubkey: line pos = %d len = %d", line
->pos
, line
->len
))
324 ret
= cmp_base64_key(keyblob
, keybloblen
, algo
, algolen
, line
, NULL
);
326 if (ret
== DROPBEAR_SUCCESS
&& options_buf
) {
327 ret
= svr_add_pubkey_options(options_buf
, line_num
, filename
);
330 if (ret
== DROPBEAR_SUCCESS
) {
334 /* We continue to the next line otherwise */
347 buf_free(options_buf
);
349 TRACE(("leave checkpubkey: ret=%d", ret
))
354 /* Returns DROPBEAR_SUCCESS if file permissions for pubkeys are ok,
355 * DROPBEAR_FAILURE otherwise.
356 * Checks that the user's homedir, ~/.ssh, and
357 * ~/.ssh/authorized_keys are all owned by either root or the user, and are
359 static int checkpubkeyperms() {
361 char* filename
= NULL
;
362 int ret
= DROPBEAR_FAILURE
;
365 TRACE(("enter checkpubkeyperms"))
367 if (ses
.authstate
.pw_dir
== NULL
) {
371 if ((len
= strlen(ses
.authstate
.pw_dir
)) == 0) {
375 /* allocate max required pathname storage,
376 * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
377 filename
= m_malloc(len
+ 22);
378 strncpy(filename
, ses
.authstate
.pw_dir
, len
+1);
381 if (checkfileperm(filename
) != DROPBEAR_SUCCESS
) {
386 strncat(filename
, "/.ssh", 5); /* strlen("/.ssh") == 5 */
387 if (checkfileperm(filename
) != DROPBEAR_SUCCESS
) {
391 /* now check ~/.ssh/authorized_keys */
392 strncat(filename
, "/authorized_keys", 16);
393 if (checkfileperm(filename
) != DROPBEAR_SUCCESS
) {
397 /* file looks ok, return success */
398 ret
= DROPBEAR_SUCCESS
;
403 TRACE(("leave checkpubkeyperms"))
407 /* Checks that a file is owned by the user or root, and isn't writable by
409 /* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
410 static int checkfileperm(char * filename
) {
411 struct stat filestat
;
414 TRACE(("enter checkfileperm(%s)", filename
))
416 if (stat(filename
, &filestat
) != 0) {
417 TRACE(("leave checkfileperm: stat() != 0"))
418 return DROPBEAR_FAILURE
;
420 /* check ownership - user or root only*/
421 if (filestat
.st_uid
!= ses
.authstate
.pw_uid
422 && filestat
.st_uid
!= 0) {
424 TRACE(("wrong ownership"))
426 /* check permissions - don't want group or others +w */
427 if (filestat
.st_mode
& (S_IWGRP
| S_IWOTH
)) {
429 TRACE(("wrong perms"))
432 if (!ses
.authstate
.perm_warn
) {
433 ses
.authstate
.perm_warn
= 1;
434 dropbear_log(LOG_INFO
, "%s must be owned by user or root, and not writable by others", filename
);
436 TRACE(("leave checkfileperm: failure perms/owner"))
437 return DROPBEAR_FAILURE
;
440 TRACE(("leave checkfileperm: success"))
441 return DROPBEAR_SUCCESS
;