1 |
|
|
/* $OpenBSD: ssh-keysign.c,v 1.52 2016/02/15 09:47:49 dtucker Exp $ */ |
2 |
|
|
/* |
3 |
|
|
* Copyright (c) 2002 Markus Friedl. All rights reserved. |
4 |
|
|
* |
5 |
|
|
* Redistribution and use in source and binary forms, with or without |
6 |
|
|
* modification, are permitted provided that the following conditions |
7 |
|
|
* are met: |
8 |
|
|
* 1. Redistributions of source code must retain the above copyright |
9 |
|
|
* notice, this list of conditions and the following disclaimer. |
10 |
|
|
* 2. Redistributions in binary form must reproduce the above copyright |
11 |
|
|
* notice, this list of conditions and the following disclaimer in the |
12 |
|
|
* documentation and/or other materials provided with the distribution. |
13 |
|
|
* |
14 |
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
15 |
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
16 |
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
17 |
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
18 |
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
19 |
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
20 |
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
21 |
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 |
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
23 |
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 |
|
|
*/ |
25 |
|
|
|
26 |
|
|
#include <sys/types.h> |
27 |
|
|
|
28 |
|
|
#include <openssl/evp.h> |
29 |
|
|
#include <openssl/rsa.h> |
30 |
|
|
|
31 |
|
|
#include <fcntl.h> |
32 |
|
|
#include <paths.h> |
33 |
|
|
#include <pwd.h> |
34 |
|
|
#include <stdlib.h> |
35 |
|
|
#include <string.h> |
36 |
|
|
#include <unistd.h> |
37 |
|
|
#include <errno.h> |
38 |
|
|
|
39 |
|
|
#include "xmalloc.h" |
40 |
|
|
#include "log.h" |
41 |
|
|
#include "sshkey.h" |
42 |
|
|
#include "ssh.h" |
43 |
|
|
#include "ssh2.h" |
44 |
|
|
#include "misc.h" |
45 |
|
|
#include "sshbuf.h" |
46 |
|
|
#include "authfile.h" |
47 |
|
|
#include "msg.h" |
48 |
|
|
#include "canohost.h" |
49 |
|
|
#include "pathnames.h" |
50 |
|
|
#include "readconf.h" |
51 |
|
|
#include "uidswap.h" |
52 |
|
|
#include "sshkey.h" |
53 |
|
|
#include "ssherr.h" |
54 |
|
|
|
55 |
|
|
extern char *__progname; |
56 |
|
|
|
57 |
|
|
/* XXX readconf.c needs these */ |
58 |
|
|
uid_t original_real_uid; |
59 |
|
|
|
60 |
|
|
static int |
61 |
|
|
valid_request(struct passwd *pw, char *host, struct sshkey **ret, |
62 |
|
|
u_char *data, size_t datalen) |
63 |
|
|
{ |
64 |
|
|
struct sshbuf *b; |
65 |
|
|
struct sshkey *key = NULL; |
66 |
|
|
u_char type, *pkblob; |
67 |
|
|
char *p; |
68 |
|
|
size_t blen, len; |
69 |
|
|
char *pkalg, *luser; |
70 |
|
|
int r, pktype, fail; |
71 |
|
|
|
72 |
|
|
if (ret != NULL) |
73 |
|
|
*ret = NULL; |
74 |
|
|
fail = 0; |
75 |
|
|
|
76 |
|
|
if ((b = sshbuf_from(data, datalen)) == NULL) |
77 |
|
|
fatal("%s: sshbuf_from failed", __func__); |
78 |
|
|
|
79 |
|
|
/* session id, currently limited to SHA1 (20 bytes) or SHA256 (32) */ |
80 |
|
|
if ((r = sshbuf_get_string(b, NULL, &len)) != 0) |
81 |
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
82 |
|
|
if (len != 20 && len != 32) |
83 |
|
|
fail++; |
84 |
|
|
|
85 |
|
|
if ((r = sshbuf_get_u8(b, &type)) != 0) |
86 |
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
87 |
|
|
if (type != SSH2_MSG_USERAUTH_REQUEST) |
88 |
|
|
fail++; |
89 |
|
|
|
90 |
|
|
/* server user */ |
91 |
|
|
if ((r = sshbuf_skip_string(b)) != 0) |
92 |
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
93 |
|
|
|
94 |
|
|
/* service */ |
95 |
|
|
if ((r = sshbuf_get_cstring(b, &p, NULL)) != 0) |
96 |
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
97 |
|
|
if (strcmp("ssh-connection", p) != 0) |
98 |
|
|
fail++; |
99 |
|
|
free(p); |
100 |
|
|
|
101 |
|
|
/* method */ |
102 |
|
|
if ((r = sshbuf_get_cstring(b, &p, NULL)) != 0) |
103 |
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
104 |
|
|
if (strcmp("hostbased", p) != 0) |
105 |
|
|
fail++; |
106 |
|
|
free(p); |
107 |
|
|
|
108 |
|
|
/* pubkey */ |
109 |
|
|
if ((r = sshbuf_get_cstring(b, &pkalg, NULL)) != 0 || |
110 |
|
|
(r = sshbuf_get_string(b, &pkblob, &blen)) != 0) |
111 |
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
112 |
|
|
|
113 |
|
|
pktype = sshkey_type_from_name(pkalg); |
114 |
|
|
if (pktype == KEY_UNSPEC) |
115 |
|
|
fail++; |
116 |
|
|
else if ((r = sshkey_from_blob(pkblob, blen, &key)) != 0) { |
117 |
|
|
error("%s: bad key blob: %s", __func__, ssh_err(r)); |
118 |
|
|
fail++; |
119 |
|
|
} else if (key->type != pktype) |
120 |
|
|
fail++; |
121 |
|
|
free(pkalg); |
122 |
|
|
free(pkblob); |
123 |
|
|
|
124 |
|
|
/* client host name, handle trailing dot */ |
125 |
|
|
if ((r = sshbuf_get_cstring(b, &p, &len)) != 0) |
126 |
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
127 |
|
|
debug2("%s: check expect chost %s got %s", __func__, host, p); |
128 |
|
|
if (strlen(host) != len - 1) |
129 |
|
|
fail++; |
130 |
|
|
else if (p[len - 1] != '.') |
131 |
|
|
fail++; |
132 |
|
|
else if (strncasecmp(host, p, len - 1) != 0) |
133 |
|
|
fail++; |
134 |
|
|
free(p); |
135 |
|
|
|
136 |
|
|
/* local user */ |
137 |
|
|
if ((r = sshbuf_get_cstring(b, &luser, NULL)) != 0) |
138 |
|
|
fatal("%s: buffer error: %s", __func__, ssh_err(r)); |
139 |
|
|
|
140 |
|
|
if (strcmp(pw->pw_name, luser) != 0) |
141 |
|
|
fail++; |
142 |
|
|
free(luser); |
143 |
|
|
|
144 |
|
|
/* end of message */ |
145 |
|
|
if (sshbuf_len(b) != 0) |
146 |
|
|
fail++; |
147 |
|
|
sshbuf_free(b); |
148 |
|
|
|
149 |
|
|
debug3("%s: fail %d", __func__, fail); |
150 |
|
|
|
151 |
|
|
if (fail && key != NULL) |
152 |
|
|
sshkey_free(key); |
153 |
|
|
else if (ret != NULL) |
154 |
|
|
*ret = key; |
155 |
|
|
|
156 |
|
|
return (fail ? -1 : 0); |
157 |
|
|
} |
158 |
|
|
|
159 |
|
|
int |
160 |
|
|
main(int argc, char **argv) |
161 |
|
|
{ |
162 |
|
|
struct sshbuf *b; |
163 |
|
|
Options options; |
164 |
|
|
#define NUM_KEYTYPES 4 |
165 |
|
|
struct sshkey *keys[NUM_KEYTYPES], *key = NULL; |
166 |
|
|
struct passwd *pw; |
167 |
|
|
int r, key_fd[NUM_KEYTYPES], i, found, version = 2, fd; |
168 |
|
|
u_char *signature, *data, rver; |
169 |
|
|
char *host, *fp; |
170 |
|
|
size_t slen, dlen; |
171 |
|
|
|
172 |
|
|
ssh_malloc_init(); /* must be called before any mallocs */ |
173 |
|
|
if (pledge("stdio rpath getpw dns id flock cpath wpath", NULL) != 0) |
174 |
|
|
fatal("%s: pledge: %s", __progname, strerror(errno)); |
175 |
|
|
|
176 |
|
|
/* Ensure that stdin and stdout are connected */ |
177 |
|
|
if ((fd = open(_PATH_DEVNULL, O_RDWR)) < 2) |
178 |
|
|
exit(1); |
179 |
|
|
/* Leave /dev/null fd iff it is attached to stderr */ |
180 |
|
|
if (fd > 2) |
181 |
|
|
close(fd); |
182 |
|
|
|
183 |
|
|
i = 0; |
184 |
|
|
/* XXX This really needs to read sshd_config for the paths */ |
185 |
|
|
key_fd[i++] = open(_PATH_HOST_DSA_KEY_FILE, O_RDONLY); |
186 |
|
|
key_fd[i++] = open(_PATH_HOST_ECDSA_KEY_FILE, O_RDONLY); |
187 |
|
|
key_fd[i++] = open(_PATH_HOST_ED25519_KEY_FILE, O_RDONLY); |
188 |
|
|
key_fd[i++] = open(_PATH_HOST_RSA_KEY_FILE, O_RDONLY); |
189 |
|
|
|
190 |
|
|
original_real_uid = getuid(); /* XXX readconf.c needs this */ |
191 |
|
|
if ((pw = getpwuid(original_real_uid)) == NULL) |
192 |
|
|
fatal("getpwuid failed"); |
193 |
|
|
pw = pwcopy(pw); |
194 |
|
|
|
195 |
|
|
permanently_set_uid(pw); |
196 |
|
|
|
197 |
|
|
#ifdef DEBUG_SSH_KEYSIGN |
198 |
|
|
log_init("ssh-keysign", SYSLOG_LEVEL_DEBUG3, SYSLOG_FACILITY_AUTH, 0); |
199 |
|
|
#endif |
200 |
|
|
|
201 |
|
|
/* verify that ssh-keysign is enabled by the admin */ |
202 |
|
|
initialize_options(&options); |
203 |
|
|
(void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, "", "", &options, 0); |
204 |
|
|
fill_default_options(&options); |
205 |
|
|
if (options.enable_ssh_keysign != 1) |
206 |
|
|
fatal("ssh-keysign not enabled in %s", |
207 |
|
|
_PATH_HOST_CONFIG_FILE); |
208 |
|
|
|
209 |
|
|
for (i = found = 0; i < NUM_KEYTYPES; i++) { |
210 |
|
|
if (key_fd[i] != -1) |
211 |
|
|
found = 1; |
212 |
|
|
} |
213 |
|
|
if (found == 0) |
214 |
|
|
fatal("could not open any host key"); |
215 |
|
|
|
216 |
|
|
OpenSSL_add_all_algorithms(); |
217 |
|
|
|
218 |
|
|
found = 0; |
219 |
|
|
for (i = 0; i < NUM_KEYTYPES; i++) { |
220 |
|
|
keys[i] = NULL; |
221 |
|
|
if (key_fd[i] == -1) |
222 |
|
|
continue; |
223 |
|
|
r = sshkey_load_private_type_fd(key_fd[i], KEY_UNSPEC, |
224 |
|
|
NULL, &key, NULL); |
225 |
|
|
close(key_fd[i]); |
226 |
|
|
if (r != 0) |
227 |
|
|
debug("parse key %d: %s", i, ssh_err(r)); |
228 |
|
|
else if (key != NULL) { |
229 |
|
|
keys[i] = key; |
230 |
|
|
found = 1; |
231 |
|
|
} |
232 |
|
|
} |
233 |
|
|
if (!found) |
234 |
|
|
fatal("no hostkey found"); |
235 |
|
|
|
236 |
|
|
if (pledge("stdio dns flock rpath cpath wpath", NULL) != 0) |
237 |
|
|
fatal("%s: pledge: %s", __progname, strerror(errno)); |
238 |
|
|
|
239 |
|
|
if ((b = sshbuf_new()) == NULL) |
240 |
|
|
fatal("%s: sshbuf_new failed", __progname); |
241 |
|
|
if (ssh_msg_recv(STDIN_FILENO, b) < 0) |
242 |
|
|
fatal("ssh_msg_recv failed"); |
243 |
|
|
if ((r = sshbuf_get_u8(b, &rver)) != 0) |
244 |
|
|
fatal("%s: buffer error: %s", __progname, ssh_err(r)); |
245 |
|
|
if (rver != version) |
246 |
|
|
fatal("bad version: received %d, expected %d", rver, version); |
247 |
|
|
if ((r = sshbuf_get_u32(b, (u_int *)&fd)) != 0) |
248 |
|
|
fatal("%s: buffer error: %s", __progname, ssh_err(r)); |
249 |
|
|
if (fd < 0 || fd == STDIN_FILENO || fd == STDOUT_FILENO) |
250 |
|
|
fatal("bad fd"); |
251 |
|
|
if ((host = get_local_name(fd)) == NULL) |
252 |
|
|
fatal("cannot get local name for fd"); |
253 |
|
|
|
254 |
|
|
if ((r = sshbuf_get_string(b, &data, &dlen)) != 0) |
255 |
|
|
fatal("%s: buffer error: %s", __progname, ssh_err(r)); |
256 |
|
|
if (valid_request(pw, host, &key, data, dlen) < 0) |
257 |
|
|
fatal("not a valid request"); |
258 |
|
|
free(host); |
259 |
|
|
|
260 |
|
|
found = 0; |
261 |
|
|
for (i = 0; i < NUM_KEYTYPES; i++) { |
262 |
|
|
if (keys[i] != NULL && |
263 |
|
|
sshkey_equal_public(key, keys[i])) { |
264 |
|
|
found = 1; |
265 |
|
|
break; |
266 |
|
|
} |
267 |
|
|
} |
268 |
|
|
if (!found) { |
269 |
|
|
if ((fp = sshkey_fingerprint(key, options.fingerprint_hash, |
270 |
|
|
SSH_FP_DEFAULT)) == NULL) |
271 |
|
|
fatal("%s: sshkey_fingerprint failed", __progname); |
272 |
|
|
fatal("no matching hostkey found for key %s %s", |
273 |
|
|
sshkey_type(key), fp ? fp : ""); |
274 |
|
|
} |
275 |
|
|
|
276 |
|
|
if ((r = sshkey_sign(keys[i], &signature, &slen, data, dlen, NULL, 0)) |
277 |
|
|
!= 0) |
278 |
|
|
fatal("sshkey_sign failed: %s", ssh_err(r)); |
279 |
|
|
free(data); |
280 |
|
|
|
281 |
|
|
/* send reply */ |
282 |
|
|
sshbuf_reset(b); |
283 |
|
|
if ((r = sshbuf_put_string(b, signature, slen)) != 0) |
284 |
|
|
fatal("%s: buffer error: %s", __progname, ssh_err(r)); |
285 |
|
|
if (ssh_msg_send(STDOUT_FILENO, version, b) == -1) |
286 |
|
|
fatal("ssh_msg_send failed"); |
287 |
|
|
|
288 |
|
|
return (0); |
289 |
|
|
} |