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
;
92 enum signkey_type type
= -1;
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 + ses
.session_id
->len
);
129 buf_putbufstring(signbuf
, ses
.session_id
);
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
, signbuf
) == DROPBEAR_SUCCESS
) {
136 dropbear_log(LOG_NOTICE
,
137 "Pubkey auth succeeded for '%s' with key %s from %s",
138 ses
.authstate
.pw_name
, fp
, svr_ses
.addrstring
);
139 send_msg_userauth_success();
141 dropbear_log(LOG_WARNING
,
142 "Pubkey auth bad signature for '%s' with key %s from %s",
143 ses
.authstate
.pw_name
, fp
, svr_ses
.addrstring
);
144 send_msg_userauth_failure(0, 1);
160 TRACE(("leave pubkeyauth"))
163 /* Reply that the key is valid for auth, this is sent when the user sends
164 * a straight copy of their pubkey to test, to avoid having to perform
165 * expensive signing operations with a worthless key */
166 static void send_msg_userauth_pk_ok(unsigned char* algo
, unsigned int algolen
,
167 unsigned char* keyblob
, unsigned int keybloblen
) {
169 TRACE(("enter send_msg_userauth_pk_ok"))
172 buf_putbyte(ses
.writepayload
, SSH_MSG_USERAUTH_PK_OK
);
173 buf_putstring(ses
.writepayload
, algo
, algolen
);
174 buf_putstring(ses
.writepayload
, keyblob
, keybloblen
);
177 TRACE(("leave send_msg_userauth_pk_ok"))
181 /* Checks whether a specified publickey (and associated algorithm) is an
182 * acceptable key for authentication */
183 /* Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */
184 static int checkpubkey(unsigned char* algo
, unsigned int algolen
,
185 unsigned char* keyblob
, unsigned int keybloblen
) {
187 FILE * authfile
= NULL
;
188 char * filename
= NULL
;
189 int ret
= DROPBEAR_FAILURE
;
190 buffer
* line
= NULL
;
191 unsigned int len
, pos
;
192 buffer
* options_buf
= NULL
;
195 TRACE(("enter checkpubkey"))
197 /* check that we can use the algo */
198 if (have_algo(algo
, algolen
, sshhostkey
) == DROPBEAR_FAILURE
) {
199 dropbear_log(LOG_WARNING
,
200 "Pubkey auth attempt with unknown algo for '%s' from %s",
201 ses
.authstate
.pw_name
, svr_ses
.addrstring
);
205 /* check file permissions, also whether file exists */
206 if (checkpubkeyperms() == DROPBEAR_FAILURE
) {
207 TRACE(("bad authorized_keys permissions, or file doesn't exist"))
211 /* we don't need to check pw and pw_dir for validity, since
212 * its been done in checkpubkeyperms. */
213 len
= strlen(ses
.authstate
.pw_dir
);
214 /* allocate max required pathname storage,
215 * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
216 filename
= m_malloc(len
+ 22);
217 snprintf(filename
, len
+ 22, "%s/.ssh/authorized_keys",
218 ses
.authstate
.pw_dir
);
221 authfile
= fopen(filename
, "r");
222 if (authfile
== NULL
) {
225 TRACE(("checkpubkey: opened authorized_keys OK"))
227 line
= buf_new(MAX_AUTHKEYS_LINE
);
230 /* iterate through the lines */
232 /* new line : potentially new options */
234 buf_free(options_buf
);
238 if (buf_getline(line
, authfile
) == DROPBEAR_FAILURE
) {
240 TRACE(("checkpubkey: authorized_keys EOF reached"))
245 if (line
->len
< MIN_AUTHKEYS_LINE
) {
246 TRACE(("checkpubkey: line too short"))
247 continue; /* line is too short for it to be a valid key */
250 /* check the key type - will fail if there are options */
253 if (strncmp(buf_getptr(line
, algolen
), algo
, algolen
) != 0) {
255 char *options_start
= NULL
;
259 /* skip over any comments or leading whitespace */
260 while (line
->pos
< line
->len
) {
261 const char c
= buf_getbyte(line
);
262 if (c
== ' ' || c
== '\t') {
264 } else if (c
== '#') {
268 buf_incrpos(line
, -1);
276 /* remember start of options */
277 options_start
= buf_getptr(line
, 1);
282 /* figure out where the options are */
283 while (line
->pos
< line
->len
) {
284 const char c
= buf_getbyte(line
);
285 if (!quoted
&& (c
== ' ' || c
== '\t')) {
288 escape
= (!escape
&& c
== '\\');
289 if (!escape
&& c
== '"') {
294 options_buf
= buf_new(options_len
);
295 buf_putbytes(options_buf
, options_start
, options_len
);
297 /* compare the algorithm. +3 so we have enough bytes to read a space and some base64 characters too. */
298 if (line
->pos
+ algolen
+3 > line
->len
) {
301 if (strncmp(buf_getptr(line
, algolen
), algo
, algolen
) != 0) {
305 buf_incrpos(line
, algolen
);
307 /* check for space (' ') character */
308 if (buf_getbyte(line
) != ' ') {
309 TRACE(("checkpubkey: space character expected, isn't there"))
313 /* truncate the line at the space after the base64 data */
315 for (len
= 0; line
->pos
< line
->len
; len
++) {
316 if (buf_getbyte(line
) == ' ') break;
318 buf_setpos(line
, pos
);
319 buf_setlen(line
, line
->pos
+ len
);
321 TRACE(("checkpubkey: line pos = %d len = %d", line
->pos
, line
->len
))
323 ret
= cmp_base64_key(keyblob
, keybloblen
, algo
, algolen
, line
, NULL
);
325 if (ret
== DROPBEAR_SUCCESS
&& options_buf
) {
326 ret
= svr_add_pubkey_options(options_buf
, line_num
, filename
);
329 if (ret
== DROPBEAR_SUCCESS
) {
333 /* We continue to the next line otherwise */
346 buf_free(options_buf
);
348 TRACE(("leave checkpubkey: ret=%d", ret
))
353 /* Returns DROPBEAR_SUCCESS if file permissions for pubkeys are ok,
354 * DROPBEAR_FAILURE otherwise.
355 * Checks that the user's homedir, ~/.ssh, and
356 * ~/.ssh/authorized_keys are all owned by either root or the user, and are
358 static int checkpubkeyperms() {
360 char* filename
= NULL
;
361 int ret
= DROPBEAR_FAILURE
;
364 TRACE(("enter checkpubkeyperms"))
366 if (ses
.authstate
.pw_dir
== NULL
) {
370 if ((len
= strlen(ses
.authstate
.pw_dir
)) == 0) {
374 /* allocate max required pathname storage,
375 * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
376 filename
= m_malloc(len
+ 22);
377 strncpy(filename
, ses
.authstate
.pw_dir
, len
+1);
380 if (checkfileperm(filename
) != DROPBEAR_SUCCESS
) {
385 strncat(filename
, "/.ssh", 5); /* strlen("/.ssh") == 5 */
386 if (checkfileperm(filename
) != DROPBEAR_SUCCESS
) {
390 /* now check ~/.ssh/authorized_keys */
391 strncat(filename
, "/authorized_keys", 16);
392 if (checkfileperm(filename
) != DROPBEAR_SUCCESS
) {
396 /* file looks ok, return success */
397 ret
= DROPBEAR_SUCCESS
;
402 TRACE(("leave checkpubkeyperms"))
406 /* Checks that a file is owned by the user or root, and isn't writable by
408 /* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
409 static int checkfileperm(char * filename
) {
410 struct stat filestat
;
413 TRACE(("enter checkfileperm(%s)", filename
))
415 if (stat(filename
, &filestat
) != 0) {
416 TRACE(("leave checkfileperm: stat() != 0"))
417 return DROPBEAR_FAILURE
;
419 /* check ownership - user or root only*/
420 if (filestat
.st_uid
!= ses
.authstate
.pw_uid
421 && filestat
.st_uid
!= 0) {
423 TRACE(("wrong ownership"))
425 /* check permissions - don't want group or others +w */
426 if (filestat
.st_mode
& (S_IWGRP
| S_IWOTH
)) {
428 TRACE(("wrong perms"))
431 if (!ses
.authstate
.perm_warn
) {
432 ses
.authstate
.perm_warn
= 1;
433 dropbear_log(LOG_INFO
, "%s must be owned by user or root, and not writable by others", filename
);
435 TRACE(("leave checkfileperm: failure perms/owner"))
436 return DROPBEAR_FAILURE
;
439 TRACE(("leave checkfileperm: success"))
440 return DROPBEAR_SUCCESS
;