xref: /openbsd-src/usr.bin/ssh/ssh-add.c (revision 47911bd667ac77dc523b8a13ef40b012dbffa741)
1 /*
2  * Author: Tatu Ylonen <ylo@cs.hut.fi>
3  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
4  *                    All rights reserved
5  * Adds an identity to the authentication server, or removes an identity.
6  *
7  * As far as I am concerned, the code I have written for this software
8  * can be used freely for any purpose.  Any derived versions of this
9  * software must be clearly marked as such, and if the derived work is
10  * incompatible with the protocol description in the RFC file, it must be
11  * called by a name other than "ssh" or "Secure Shell".
12  *
13  * SSH2 implementation,
14  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
15  *
16  * Redistribution and use in source and binary forms, with or without
17  * modification, are permitted provided that the following conditions
18  * are met:
19  * 1. Redistributions of source code must retain the above copyright
20  *    notice, this list of conditions and the following disclaimer.
21  * 2. Redistributions in binary form must reproduce the above copyright
22  *    notice, this list of conditions and the following disclaimer in the
23  *    documentation and/or other materials provided with the distribution.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
26  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
28  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
29  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
34  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 #include "includes.h"
38 RCSID("$OpenBSD: ssh-add.c,v 1.64 2002/11/21 23:03:51 deraadt Exp $");
39 
40 #include <openssl/evp.h>
41 
42 #include "ssh.h"
43 #include "rsa.h"
44 #include "log.h"
45 #include "xmalloc.h"
46 #include "key.h"
47 #include "authfd.h"
48 #include "authfile.h"
49 #include "pathnames.h"
50 #include "readpass.h"
51 #include "misc.h"
52 
53 /* argv0 */
54 extern char *__progname;
55 
56 /* Default files to add */
57 static char *default_files[] = {
58 	_PATH_SSH_CLIENT_ID_RSA,
59 	_PATH_SSH_CLIENT_ID_DSA,
60 	_PATH_SSH_CLIENT_IDENTITY,
61 	NULL
62 };
63 
64 /* Default lifetime (0 == forever) */
65 static int lifetime = 0;
66 
67 /* we keep a cache of one passphrases */
68 static char *pass = NULL;
69 static void
70 clear_pass(void)
71 {
72 	if (pass) {
73 		memset(pass, 0, strlen(pass));
74 		xfree(pass);
75 		pass = NULL;
76 	}
77 }
78 
79 static int
80 delete_file(AuthenticationConnection *ac, const char *filename)
81 {
82 	Key *public;
83 	char *comment = NULL;
84 	int ret = -1;
85 
86 	public = key_load_public(filename, &comment);
87 	if (public == NULL) {
88 		printf("Bad key file %s\n", filename);
89 		return -1;
90 	}
91 	if (ssh_remove_identity(ac, public)) {
92 		fprintf(stderr, "Identity removed: %s (%s)\n", filename, comment);
93 		ret = 0;
94 	} else
95 		fprintf(stderr, "Could not remove identity: %s\n", filename);
96 
97 	key_free(public);
98 	xfree(comment);
99 
100 	return ret;
101 }
102 
103 /* Send a request to remove all identities. */
104 static int
105 delete_all(AuthenticationConnection *ac)
106 {
107 	int ret = -1;
108 
109 	if (ssh_remove_all_identities(ac, 1))
110 		ret = 0;
111 	/* ignore error-code for ssh2 */
112 	ssh_remove_all_identities(ac, 2);
113 
114 	if (ret == 0)
115 		fprintf(stderr, "All identities removed.\n");
116 	else
117 		fprintf(stderr, "Failed to remove all identities.\n");
118 
119 	return ret;
120 }
121 
122 static int
123 add_file(AuthenticationConnection *ac, const char *filename)
124 {
125 	struct stat st;
126 	Key *private;
127 	char *comment = NULL;
128 	char msg[1024];
129 	int ret = -1;
130 
131 	if (stat(filename, &st) < 0) {
132 		perror(filename);
133 		return -1;
134 	}
135 	/* At first, try empty passphrase */
136 	private = key_load_private(filename, "", &comment);
137 	if (comment == NULL)
138 		comment = xstrdup(filename);
139 	/* try last */
140 	if (private == NULL && pass != NULL)
141 		private = key_load_private(filename, pass, NULL);
142 	if (private == NULL) {
143 		/* clear passphrase since it did not work */
144 		clear_pass();
145 		snprintf(msg, sizeof msg, "Enter passphrase for %.200s: ",
146 		   comment);
147 		for (;;) {
148 			pass = read_passphrase(msg, RP_ALLOW_STDIN);
149 			if (strcmp(pass, "") == 0) {
150 				clear_pass();
151 				xfree(comment);
152 				return -1;
153 			}
154 			private = key_load_private(filename, pass, &comment);
155 			if (private != NULL)
156 				break;
157 			clear_pass();
158 			strlcpy(msg, "Bad passphrase, try again: ", sizeof msg);
159 		}
160 	}
161 
162 	if (ssh_add_identity_constrained(ac, private, comment, lifetime)) {
163 		fprintf(stderr, "Identity added: %s (%s)\n", filename, comment);
164 		ret = 0;
165 		if (lifetime != 0)
166 			fprintf(stderr,
167 			    "Lifetime set to %d seconds\n", lifetime);
168 	} else if (ssh_add_identity(ac, private, comment)) {
169 		fprintf(stderr, "Identity added: %s (%s)\n", filename, comment);
170 		ret = 0;
171 	} else {
172 		fprintf(stderr, "Could not add identity: %s\n", filename);
173 	}
174 
175 	xfree(comment);
176 	key_free(private);
177 
178 	return ret;
179 }
180 
181 static int
182 update_card(AuthenticationConnection *ac, int add, const char *id)
183 {
184 	char *pin;
185 
186 	pin = read_passphrase("Enter passphrase for smartcard: ", RP_ALLOW_STDIN);
187 	if (pin == NULL)
188 		return -1;
189 
190 	if (ssh_update_card(ac, add, id, pin)) {
191 		fprintf(stderr, "Card %s: %s\n",
192 		    add ? "added" : "removed", id);
193 		return 0;
194 	} else {
195 		fprintf(stderr, "Could not %s card: %s\n",
196 		    add ? "add" : "remove", id);
197 		return -1;
198 	}
199 }
200 
201 static int
202 list_identities(AuthenticationConnection *ac, int do_fp)
203 {
204 	Key *key;
205 	char *comment, *fp;
206 	int had_identities = 0;
207 	int version;
208 
209 	for (version = 1; version <= 2; version++) {
210 		for (key = ssh_get_first_identity(ac, &comment, version);
211 		    key != NULL;
212 		    key = ssh_get_next_identity(ac, &comment, version)) {
213 			had_identities = 1;
214 			if (do_fp) {
215 				fp = key_fingerprint(key, SSH_FP_MD5,
216 				    SSH_FP_HEX);
217 				printf("%d %s %s (%s)\n",
218 				    key_size(key), fp, comment, key_type(key));
219 				xfree(fp);
220 			} else {
221 				if (!key_write(key, stdout))
222 					fprintf(stderr, "key_write failed");
223 				fprintf(stdout, " %s\n", comment);
224 			}
225 			key_free(key);
226 			xfree(comment);
227 		}
228 	}
229 	if (!had_identities) {
230 		printf("The agent has no identities.\n");
231 		return -1;
232 	}
233 	return 0;
234 }
235 
236 static int
237 lock_agent(AuthenticationConnection *ac, int lock)
238 {
239 	char prompt[100], *p1, *p2;
240 	int passok = 1, ret = -1;
241 
242 	strlcpy(prompt, "Enter lock password: ", sizeof(prompt));
243 	p1 = read_passphrase(prompt, RP_ALLOW_STDIN);
244 	if (lock) {
245 		strlcpy(prompt, "Again: ", sizeof prompt);
246 		p2 = read_passphrase(prompt, RP_ALLOW_STDIN);
247 		if (strcmp(p1, p2) != 0) {
248 			fprintf(stderr, "Passwords do not match.\n");
249 			passok = 0;
250 		}
251 		memset(p2, 0, strlen(p2));
252 		xfree(p2);
253 	}
254 	if (passok && ssh_lock_agent(ac, lock, p1)) {
255 		fprintf(stderr, "Agent %slocked.\n", lock ? "" : "un");
256 		ret = 0;
257 	} else
258 		fprintf(stderr, "Failed to %slock agent.\n", lock ? "" : "un");
259 	memset(p1, 0, strlen(p1));
260 	xfree(p1);
261 	return (ret);
262 }
263 
264 static int
265 do_file(AuthenticationConnection *ac, int deleting, char *file)
266 {
267 	if (deleting) {
268 		if (delete_file(ac, file) == -1)
269 			return -1;
270 	} else {
271 		if (add_file(ac, file) == -1)
272 			return -1;
273 	}
274 	return 0;
275 }
276 
277 static void
278 usage(void)
279 {
280 	fprintf(stderr, "Usage: %s [options]\n", __progname);
281 	fprintf(stderr, "Options:\n");
282 	fprintf(stderr, "  -l          List fingerprints of all identities.\n");
283 	fprintf(stderr, "  -L          List public key parameters of all identities.\n");
284 	fprintf(stderr, "  -d          Delete identity.\n");
285 	fprintf(stderr, "  -D          Delete all identities.\n");
286 	fprintf(stderr, "  -x          Lock agent.\n");
287 	fprintf(stderr, "  -X          Unlock agent.\n");
288 	fprintf(stderr, "  -t life     Set lifetime (in seconds) when adding identities.\n");
289 #ifdef SMARTCARD
290 	fprintf(stderr, "  -s reader   Add key in smartcard reader.\n");
291 	fprintf(stderr, "  -e reader   Remove key in smartcard reader.\n");
292 #endif
293 }
294 
295 int
296 main(int argc, char **argv)
297 {
298 	extern char *optarg;
299 	extern int optind;
300 	AuthenticationConnection *ac = NULL;
301 	char *sc_reader_id = NULL;
302 	int i, ch, deleting = 0, ret = 0;
303 
304 	SSLeay_add_all_algorithms();
305 
306 	/* At first, get a connection to the authentication agent. */
307 	ac = ssh_get_authentication_connection();
308 	if (ac == NULL) {
309 		fprintf(stderr, "Could not open a connection to your authentication agent.\n");
310 		exit(2);
311 	}
312 	while ((ch = getopt(argc, argv, "lLdDxXe:s:t:")) != -1) {
313 		switch (ch) {
314 		case 'l':
315 		case 'L':
316 			if (list_identities(ac, ch == 'l' ? 1 : 0) == -1)
317 				ret = 1;
318 			goto done;
319 			break;
320 		case 'x':
321 		case 'X':
322 			if (lock_agent(ac, ch == 'x' ? 1 : 0) == -1)
323 				ret = 1;
324 			goto done;
325 			break;
326 		case 'd':
327 			deleting = 1;
328 			break;
329 		case 'D':
330 			if (delete_all(ac) == -1)
331 				ret = 1;
332 			goto done;
333 			break;
334 		case 's':
335 			sc_reader_id = optarg;
336 			break;
337 		case 'e':
338 			deleting = 1;
339 			sc_reader_id = optarg;
340 			break;
341 		case 't':
342 			if ((lifetime = convtime(optarg)) == -1) {
343 				fprintf(stderr, "Invalid lifetime\n");
344 				ret = 1;
345 				goto done;
346 			}
347 			break;
348 		default:
349 			usage();
350 			ret = 1;
351 			goto done;
352 		}
353 	}
354 	argc -= optind;
355 	argv += optind;
356 	if (sc_reader_id != NULL) {
357 		if (update_card(ac, !deleting, sc_reader_id) == -1)
358 			ret = 1;
359 		goto done;
360 	}
361 	if (argc == 0) {
362 		char buf[MAXPATHLEN];
363 		struct passwd *pw;
364 		struct stat st;
365 		int count = 0;
366 
367 		if ((pw = getpwuid(getuid())) == NULL) {
368 			fprintf(stderr, "No user found with uid %u\n",
369 			    (u_int)getuid());
370 			ret = 1;
371 			goto done;
372 		}
373 
374 		for(i = 0; default_files[i]; i++) {
375 			snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir,
376 			    default_files[i]);
377 			if (stat(buf, &st) < 0)
378 				continue;
379 			if (do_file(ac, deleting, buf) == -1)
380 				ret = 1;
381 			else
382 				count++;
383 		}
384 		if (count == 0)
385 			ret = 1;
386 	} else {
387 		for(i = 0; i < argc; i++) {
388 			if (do_file(ac, deleting, argv[i]) == -1)
389 				ret = 1;
390 		}
391 	}
392 	clear_pass();
393 
394 done:
395 	ssh_close_authentication_connection(ac);
396 	return ret;
397 }
398