svr-authpubkey.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  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. * This file incorporates work covered by the following copyright and
  26. * permission notice:
  27. *
  28. * Copyright (c) 2000 Markus Friedl. All rights reserved.
  29. *
  30. * Redistribution and use in source and binary forms, with or without
  31. * modification, are permitted provided that the following conditions
  32. * are met:
  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.
  38. *
  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.
  49. *
  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.
  53. *
  54. */
  55. /* Process a pubkey auth request */
  56. #include "includes.h"
  57. #include "session.h"
  58. #include "dbutil.h"
  59. #include "buffer.h"
  60. #include "signkey.h"
  61. #include "auth.h"
  62. #include "ssh.h"
  63. #include "packet.h"
  64. #include "algo.h"
  65. #if DROPBEAR_SVR_PUBKEY_AUTH
  66. #define MIN_AUTHKEYS_LINE 10 /* "ssh-rsa AB" - short but doesn't matter */
  67. #define MAX_AUTHKEYS_LINE 4200 /* max length of a line in authkeys */
  68. static int checkpubkey(const char* keyalgo, unsigned int keyalgolen,
  69. const unsigned char* keyblob, unsigned int keybloblen);
  70. static int checkpubkeyperms(void);
  71. static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen,
  72. const unsigned char* keyblob, unsigned int keybloblen);
  73. static int checkfileperm(char * filename);
  74. /* process a pubkey auth request, sending success or failure message as
  75. * appropriate */
  76. void svr_auth_pubkey(int valid_user) {
  77. unsigned char testkey; /* whether we're just checking if a key is usable */
  78. char* sigalgo = NULL;
  79. unsigned int sigalgolen;
  80. const char* keyalgo;
  81. unsigned int keyalgolen;
  82. unsigned char* keyblob = NULL;
  83. unsigned int keybloblen;
  84. unsigned int sign_payload_length;
  85. buffer * signbuf = NULL;
  86. sign_key * key = NULL;
  87. char* fp = NULL;
  88. enum signature_type sigtype;
  89. enum signkey_type keytype;
  90. int auth_failure = 1;
  91. TRACE(("enter pubkeyauth"))
  92. /* 0 indicates user just wants to check if key can be used, 1 is an
  93. * actual attempt*/
  94. testkey = (buf_getbool(ses.payload) == 0);
  95. sigalgo = buf_getstring(ses.payload, &sigalgolen);
  96. keybloblen = buf_getint(ses.payload);
  97. keyblob = buf_getptr(ses.payload, keybloblen);
  98. if (!valid_user) {
  99. /* Return failure once we have read the contents of the packet
  100. required to validate a public key.
  101. Avoids blind user enumeration though it isn't possible to prevent
  102. testing for user existence if the public key is known */
  103. send_msg_userauth_failure(0, 0);
  104. goto out;
  105. }
  106. sigtype = signature_type_from_name(sigalgo, sigalgolen);
  107. if (sigtype == DROPBEAR_SIGNATURE_NONE) {
  108. send_msg_userauth_failure(0, 0);
  109. goto out;
  110. }
  111. keytype = signkey_type_from_signature(sigtype);
  112. keyalgo = signkey_name_from_type(keytype, &keyalgolen);
  113. #if DROPBEAR_PLUGIN
  114. if (svr_ses.plugin_instance != NULL) {
  115. char *options_buf;
  116. if (svr_ses.plugin_instance->checkpubkey(
  117. svr_ses.plugin_instance,
  118. &ses.plugin_session,
  119. keyalgo,
  120. keyalgolen,
  121. keyblob,
  122. keybloblen,
  123. ses.authstate.username) == DROPBEAR_SUCCESS) {
  124. /* Success */
  125. auth_failure = 0;
  126. /* Options provided? */
  127. options_buf = ses.plugin_session->get_options(ses.plugin_session);
  128. if (options_buf) {
  129. struct buf temp_buf = {
  130. .data = (unsigned char *)options_buf,
  131. .len = strlen(options_buf),
  132. .pos = 0,
  133. .size = 0
  134. };
  135. int ret = svr_add_pubkey_options(&temp_buf, 0, "N/A");
  136. if (ret == DROPBEAR_FAILURE) {
  137. /* Fail immediately as the plugin provided wrong options */
  138. send_msg_userauth_failure(0, 0);
  139. goto out;
  140. }
  141. }
  142. }
  143. }
  144. #endif
  145. /* check if the key is valid */
  146. if (auth_failure) {
  147. auth_failure = checkpubkey(keyalgo, keyalgolen, keyblob, keybloblen) == DROPBEAR_FAILURE;
  148. }
  149. if (auth_failure) {
  150. send_msg_userauth_failure(0, 0);
  151. goto out;
  152. }
  153. /* let them know that the key is ok to use */
  154. if (testkey) {
  155. send_msg_userauth_pk_ok(sigalgo, sigalgolen, keyblob, keybloblen);
  156. goto out;
  157. }
  158. /* now we can actually verify the signature */
  159. /* get the key */
  160. key = new_sign_key();
  161. if (buf_get_pub_key(ses.payload, key, &keytype) == DROPBEAR_FAILURE) {
  162. send_msg_userauth_failure(0, 1);
  163. goto out;
  164. }
  165. /* create the data which has been signed - this a string containing
  166. * session_id, concatenated with the payload packet up to the signature */
  167. assert(ses.payload_beginning <= ses.payload->pos);
  168. sign_payload_length = ses.payload->pos - ses.payload_beginning;
  169. signbuf = buf_new(ses.payload->pos + 4 + ses.session_id->len);
  170. buf_putbufstring(signbuf, ses.session_id);
  171. /* The entire contents of the payload prior. */
  172. buf_setpos(ses.payload, ses.payload_beginning);
  173. buf_putbytes(signbuf,
  174. buf_getptr(ses.payload, sign_payload_length),
  175. sign_payload_length);
  176. buf_incrpos(ses.payload, sign_payload_length);
  177. buf_setpos(signbuf, 0);
  178. /* ... and finally verify the signature */
  179. fp = sign_key_fingerprint(keyblob, keybloblen);
  180. if (buf_verify(ses.payload, key, sigtype, signbuf) == DROPBEAR_SUCCESS) {
  181. dropbear_log(LOG_NOTICE,
  182. "Pubkey auth succeeded for '%s' with %s key %s from %s",
  183. ses.authstate.pw_name,
  184. signkey_name_from_type(keytype, NULL), fp,
  185. svr_ses.addrstring);
  186. send_msg_userauth_success();
  187. #if DROPBEAR_PLUGIN
  188. if ((ses.plugin_session != NULL) && (svr_ses.plugin_instance->auth_success != NULL)) {
  189. /* Was authenticated through the external plugin. tell plugin that signature verification was ok */
  190. svr_ses.plugin_instance->auth_success(ses.plugin_session);
  191. }
  192. #endif
  193. } else {
  194. dropbear_log(LOG_WARNING,
  195. "Pubkey auth bad signature for '%s' with key %s from %s",
  196. ses.authstate.pw_name, fp, svr_ses.addrstring);
  197. send_msg_userauth_failure(0, 1);
  198. }
  199. m_free(fp);
  200. out:
  201. /* cleanup stuff */
  202. if (signbuf) {
  203. buf_free(signbuf);
  204. }
  205. if (sigalgo) {
  206. m_free(sigalgo);
  207. }
  208. if (key) {
  209. sign_key_free(key);
  210. key = NULL;
  211. }
  212. /* Retain pubkey options only if auth succeeded */
  213. if (!ses.authstate.authdone) {
  214. svr_pubkey_options_cleanup();
  215. }
  216. TRACE(("leave pubkeyauth"))
  217. }
  218. /* Reply that the key is valid for auth, this is sent when the user sends
  219. * a straight copy of their pubkey to test, to avoid having to perform
  220. * expensive signing operations with a worthless key */
  221. static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen,
  222. const unsigned char* keyblob, unsigned int keybloblen) {
  223. TRACE(("enter send_msg_userauth_pk_ok"))
  224. CHECKCLEARTOWRITE();
  225. buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_PK_OK);
  226. buf_putstring(ses.writepayload, sigalgo, sigalgolen);
  227. buf_putstring(ses.writepayload, (const char*)keyblob, keybloblen);
  228. encrypt_packet();
  229. TRACE(("leave send_msg_userauth_pk_ok"))
  230. }
  231. /* Content for SSH_PUBKEYINFO is optionally returned malloced in ret_info (will be
  232. freed if already set */
  233. static int checkpubkey_line(buffer* line, int line_num, const char* filename,
  234. const char* algo, unsigned int algolen,
  235. const unsigned char* keyblob, unsigned int keybloblen,
  236. char ** ret_info) {
  237. buffer *options_buf = NULL;
  238. char *info_str = NULL;
  239. unsigned int pos, len, infopos, infolen;
  240. int ret = DROPBEAR_FAILURE;
  241. if (line->len < MIN_AUTHKEYS_LINE || line->len > MAX_AUTHKEYS_LINE) {
  242. TRACE(("checkpubkey_line: bad line length %d", line->len))
  243. goto out;
  244. }
  245. if (memchr(line->data, 0x0, line->len) != NULL) {
  246. TRACE(("checkpubkey_line: bad line has null char"))
  247. goto out;
  248. }
  249. /* compare the algorithm. +3 so we have enough bytes to read a space and some base64 characters too. */
  250. if (line->pos + algolen+3 > line->len) {
  251. goto out;
  252. }
  253. /* check the key type */
  254. if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) {
  255. int is_comment = 0;
  256. unsigned char *options_start = NULL;
  257. int options_len = 0;
  258. int escape, quoted;
  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') {
  263. continue;
  264. } else if (c == '#') {
  265. is_comment = 1;
  266. break;
  267. }
  268. buf_decrpos(line, 1);
  269. break;
  270. }
  271. if (is_comment) {
  272. /* next line */
  273. goto out;
  274. }
  275. /* remember start of options */
  276. options_start = buf_getptr(line, 1);
  277. quoted = 0;
  278. escape = 0;
  279. options_len = 0;
  280. /* figure out where the options are */
  281. while (line->pos < line->len) {
  282. const char c = buf_getbyte(line);
  283. if (!quoted && (c == ' ' || c == '\t')) {
  284. break;
  285. }
  286. escape = (!escape && c == '\\');
  287. if (!escape && c == '"') {
  288. quoted = !quoted;
  289. }
  290. options_len++;
  291. }
  292. options_buf = buf_new(options_len);
  293. buf_putbytes(options_buf, options_start, options_len);
  294. /* compare the algorithm. +3 so we have enough bytes to read a space and some base64 characters too. */
  295. if (line->pos + algolen+3 > line->len) {
  296. goto out;
  297. }
  298. if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) {
  299. goto out;
  300. }
  301. }
  302. buf_incrpos(line, algolen);
  303. /* check for space (' ') character */
  304. if (buf_getbyte(line) != ' ') {
  305. TRACE(("checkpubkey_line: space character expected, isn't there"))
  306. goto out;
  307. }
  308. /* find the length of base64 data */
  309. pos = line->pos;
  310. for (len = 0; line->pos < line->len; len++) {
  311. if (buf_getbyte(line) == ' ') {
  312. break;
  313. }
  314. }
  315. /* find out the length of the public key info, stop at the first space */
  316. infopos = line->pos;
  317. for (infolen = 0; line->pos < line->len; infolen++) {
  318. const char c = buf_getbyte(line);
  319. if (c == ' ') {
  320. break;
  321. }
  322. /* We have an allowlist - authorized_keys lines can't be fully trusted,
  323. some shell scripts may do unsafe things with env var values */
  324. if (!(isalnum(c) || strchr(".,_-+@", c))) {
  325. TRACE(("Not setting SSH_PUBKEYINFO, special characters"))
  326. infolen = 0;
  327. break;
  328. }
  329. }
  330. if (infolen > 0) {
  331. info_str = m_malloc(infolen + 1);
  332. buf_setpos(line, infopos);
  333. strncpy(info_str, buf_getptr(line, infolen), infolen);
  334. }
  335. /* truncate to base64 data length */
  336. buf_setpos(line, pos);
  337. buf_setlen(line, line->pos + len);
  338. TRACE(("checkpubkey_line: line pos = %d len = %d", line->pos, line->len))
  339. ret = cmp_base64_key(keyblob, keybloblen, (const unsigned char *) algo, algolen, line, NULL);
  340. /* free pubkey_info if it is filled */
  341. if (ret_info && *ret_info) {
  342. m_free(*ret_info);
  343. *ret_info = NULL;
  344. }
  345. if (ret == DROPBEAR_SUCCESS) {
  346. if (options_buf) {
  347. ret = svr_add_pubkey_options(options_buf, line_num, filename);
  348. }
  349. if (ret_info) {
  350. /* take the (optional) public key information */
  351. *ret_info = info_str;
  352. info_str = NULL;
  353. }
  354. }
  355. out:
  356. if (options_buf) {
  357. buf_free(options_buf);
  358. }
  359. if (info_str) {
  360. m_free(info_str);
  361. }
  362. return ret;
  363. }
  364. /* Checks whether a specified publickey (and associated algorithm) is an
  365. * acceptable key for authentication */
  366. /* Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */
  367. static int checkpubkey(const char* keyalgo, unsigned int keyalgolen,
  368. const unsigned char* keyblob, unsigned int keybloblen) {
  369. FILE * authfile = NULL;
  370. char * filename = NULL;
  371. int ret = DROPBEAR_FAILURE;
  372. buffer * line = NULL;
  373. unsigned int len;
  374. int line_num;
  375. uid_t origuid;
  376. gid_t origgid;
  377. TRACE(("enter checkpubkey"))
  378. #if DROPBEAR_SVR_MULTIUSER
  379. /* access the file as the authenticating user. */
  380. origuid = getuid();
  381. origgid = getgid();
  382. if ((setegid(ses.authstate.pw_gid)) < 0 ||
  383. (seteuid(ses.authstate.pw_uid)) < 0) {
  384. dropbear_exit("Failed to set euid");
  385. }
  386. #endif
  387. /* check file permissions, also whether file exists */
  388. if (checkpubkeyperms() == DROPBEAR_FAILURE) {
  389. TRACE(("bad authorized_keys permissions, or file doesn't exist"))
  390. } else {
  391. /* we don't need to check pw and pw_dir for validity, since
  392. * its been done in checkpubkeyperms. */
  393. len = strlen(ses.authstate.pw_dir);
  394. /* allocate max required pathname storage,
  395. * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
  396. filename = m_malloc(len + 22);
  397. snprintf(filename, len + 22, "%s/.ssh/authorized_keys",
  398. ses.authstate.pw_dir);
  399. authfile = fopen(filename, "r");
  400. if (!authfile) {
  401. TRACE(("checkpubkey: failed opening %s: %s", filename, strerror(errno)))
  402. }
  403. }
  404. #if DROPBEAR_SVR_MULTIUSER
  405. if ((seteuid(origuid)) < 0 ||
  406. (setegid(origgid)) < 0) {
  407. dropbear_exit("Failed to revert euid");
  408. }
  409. #endif
  410. if (authfile == NULL) {
  411. goto out;
  412. }
  413. TRACE(("checkpubkey: opened authorized_keys OK"))
  414. line = buf_new(MAX_AUTHKEYS_LINE);
  415. line_num = 0;
  416. /* iterate through the lines */
  417. do {
  418. if (buf_getline(line, authfile) == DROPBEAR_FAILURE) {
  419. /* EOF reached */
  420. TRACE(("checkpubkey: authorized_keys EOF reached"))
  421. break;
  422. }
  423. line_num++;
  424. ret = checkpubkey_line(line, line_num, filename, keyalgo, keyalgolen,
  425. keyblob, keybloblen, &ses.authstate.pubkey_info);
  426. if (ret == DROPBEAR_SUCCESS) {
  427. break;
  428. }
  429. /* We continue to the next line otherwise */
  430. } while (1);
  431. out:
  432. if (authfile) {
  433. fclose(authfile);
  434. }
  435. if (line) {
  436. buf_free(line);
  437. }
  438. m_free(filename);
  439. TRACE(("leave checkpubkey: ret=%d", ret))
  440. return ret;
  441. }
  442. /* Returns DROPBEAR_SUCCESS if file permissions for pubkeys are ok,
  443. * DROPBEAR_FAILURE otherwise.
  444. * Checks that the user's homedir, ~/.ssh, and
  445. * ~/.ssh/authorized_keys are all owned by either root or the user, and are
  446. * g-w, o-w */
  447. static int checkpubkeyperms() {
  448. char* filename = NULL;
  449. int ret = DROPBEAR_FAILURE;
  450. unsigned int len;
  451. TRACE(("enter checkpubkeyperms"))
  452. if (ses.authstate.pw_dir == NULL) {
  453. goto out;
  454. }
  455. if ((len = strlen(ses.authstate.pw_dir)) == 0) {
  456. goto out;
  457. }
  458. /* allocate max required pathname storage,
  459. * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
  460. len += 22;
  461. filename = m_malloc(len);
  462. strlcpy(filename, ses.authstate.pw_dir, len);
  463. /* check ~ */
  464. if (checkfileperm(filename) != DROPBEAR_SUCCESS) {
  465. goto out;
  466. }
  467. /* check ~/.ssh */
  468. strlcat(filename, "/.ssh", len);
  469. if (checkfileperm(filename) != DROPBEAR_SUCCESS) {
  470. goto out;
  471. }
  472. /* now check ~/.ssh/authorized_keys */
  473. strlcat(filename, "/authorized_keys", len);
  474. if (checkfileperm(filename) != DROPBEAR_SUCCESS) {
  475. goto out;
  476. }
  477. /* file looks ok, return success */
  478. ret = DROPBEAR_SUCCESS;
  479. out:
  480. m_free(filename);
  481. TRACE(("leave checkpubkeyperms"))
  482. return ret;
  483. }
  484. /* Checks that a file is owned by the user or root, and isn't writable by
  485. * group or other */
  486. /* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
  487. static int checkfileperm(char * filename) {
  488. struct stat filestat;
  489. int badperm = 0;
  490. TRACE(("enter checkfileperm(%s)", filename))
  491. if (stat(filename, &filestat) != 0) {
  492. TRACE(("leave checkfileperm: stat() != 0"))
  493. return DROPBEAR_FAILURE;
  494. }
  495. /* check ownership - user or root only*/
  496. if (filestat.st_uid != ses.authstate.pw_uid
  497. && filestat.st_uid != 0) {
  498. badperm = 1;
  499. TRACE(("wrong ownership"))
  500. }
  501. /* check permissions - don't want group or others +w */
  502. if (filestat.st_mode & (S_IWGRP | S_IWOTH)) {
  503. badperm = 1;
  504. TRACE(("wrong perms"))
  505. }
  506. if (badperm) {
  507. if (!ses.authstate.perm_warn) {
  508. ses.authstate.perm_warn = 1;
  509. dropbear_log(LOG_INFO, "%s must be owned by user or root, and not writable by others", filename);
  510. }
  511. TRACE(("leave checkfileperm: failure perms/owner"))
  512. return DROPBEAR_FAILURE;
  513. }
  514. TRACE(("leave checkfileperm: success"))
  515. return DROPBEAR_SUCCESS;
  516. }
  517. #if DROPBEAR_FUZZ
  518. int fuzz_checkpubkey_line(buffer* line, int line_num, char* filename,
  519. const char* algo, unsigned int algolen,
  520. const unsigned char* keyblob, unsigned int keybloblen) {
  521. return checkpubkey_line(line, line_num, filename, algo, algolen, keyblob, keybloblen, NULL);
  522. }
  523. #endif
  524. #endif