xref: /openbsd/libexec/login_token/token.c (revision 1b7fb171)
1 /*	$OpenBSD: token.c,v 1.19 2015/10/05 17:31:17 millert Exp $	*/
2 
3 /*-
4  * Copyright (c) 1995 Migration Associates Corp. All Rights Reserved
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *      This product includes software developed by Berkeley Software Design,
17  *      Inc.
18  * 4. The name of Berkeley Software Design, Inc.  may not be used to endorse
19  *    or promote products derived from this software without specific prior
20  *    written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  *	BSDI $From: token.c,v 1.2 1996/08/28 22:07:55 prb Exp $
35  */
36 
37 /*
38  * DES functions for one-way encrypting Authentication Tokens.
39  * All knowledge of DES is confined to this file.
40  */
41 
42 #include <sys/types.h>
43 #include <sys/time.h>
44 #include <sys/resource.h>
45 
46 #include <ctype.h>
47 #include <stdio.h>
48 #include <syslog.h>
49 #include <limits.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53 #include <openssl/des.h>
54 
55 #include "token.h"
56 #include "tokendb.h"
57 
58 /*
59  * Define a union of various types of arguments to DES functions.
60  * All native DES types are modulo 8 bytes in length. Cipher text
61  * needs a trailing null byte.
62  */
63 
64 typedef	union {
65 	DES_cblock	cb;
66 	char		ct[9];
67 	uint32_t	ul[2];
68 } TOKEN_CBlock;
69 
70 /*
71  * Static definition of random number challenge for token.
72  * Challenge length is 8 bytes, left-justified with trailing null byte.
73  */
74 
75 static	TOKEN_CBlock tokennumber;
76 
77 /*
78  * Static function prototypes
79  */
80 
81 static	void	tokenseed(TOKEN_CBlock *);
82 static	void	lcase(char *);
83 static	void	h2d(char *);
84 static	void	h2cb(char *, TOKEN_CBlock *);
85 static	void	cb2h(TOKEN_CBlock, char *);
86 
87 /*
88  * Generate random DES cipherblock seed. Feedback key into
89  * new_random_key to strengthen.
90  */
91 
92 static void
tokenseed(TOKEN_CBlock * cb)93 tokenseed(TOKEN_CBlock *cb)
94 {
95 	cb->ul[0] = arc4random();
96 	cb->ul[1] = arc4random();
97 }
98 
99 /*
100  * Send a random challenge string to the token. The challenge
101  * is always base 10 as there are no alpha keys on the keyboard.
102  */
103 
104 void
tokenchallenge(char * user,char * challenge,int size,char * card_type)105 tokenchallenge(char *user, char *challenge, int size, char *card_type)
106 {
107 	TOKENDB_Rec tr;
108 	TOKEN_CBlock cb;
109 	DES_key_schedule ks;
110 	int r, c;
111 
112 	r = 1;	/* no reduced input mode by default! */
113 
114 	if ((tt->modes & TOKEN_RIM) &&
115 	    tokendb_getrec(user, &tr) == 0 &&
116 	    (tr.mode & TOKEN_RIM)) {
117 		c = 0;
118 		while ((r = tokendb_lockrec(user, &tr, TOKEN_LOCKED)) == 1) {
119 			if (c++ >= 60)
120 				break;
121 			sleep(1);
122 		}
123 		tr.flags &= ~TOKEN_LOCKED;
124 		if (r == 0 && tr.rim[0]) {
125 			h2cb(tr.secret, &cb);
126 			DES_fixup_key_parity(&cb.cb);
127 			DES_key_sched(&cb.cb, &ks);
128 			DES_ecb_encrypt(&tr.rim, &cb.cb, &ks, DES_ENCRYPT);
129 			memcpy(tr.rim, cb.cb, 8);
130 			for (r = 0; r < 8; ++r) {
131 				if ((tr.rim[r] &= 0xf) > 9)
132 					tr.rim[r] -= 10;
133 				tr.rim[r] |= 0x30;
134 			}
135 			r = 0;		/* reset it back */
136 			memcpy(tokennumber.ct, tr.rim, 8);
137 			tokennumber.ct[8] = 0;
138 			tokendb_putrec(user, &tr);
139 		}
140 	}
141 	if (r != 0 || tr.rim[0] == '\0') {
142 		memset(tokennumber.ct, 0, sizeof(tokennumber.ct));
143 		snprintf(tokennumber.ct, sizeof(tokennumber.ct), "%8.8u",
144 		    arc4random());
145 		if (r == 0) {
146 			memcpy(tr.rim, tokennumber.ct, 8);
147 			tokendb_putrec(user, &tr);
148 		}
149 	}
150 
151 	snprintf(challenge, size, "%s Challenge \"%s\"\r\n%s Response: ",
152 	    card_type, tokennumber.ct, card_type);
153 }
154 
155 /*
156  * Verify response from user against token's predicted cipher
157  * of the random number challenge.
158  */
159 
160 int
tokenverify(char * username,char * challenge,char * response)161 tokenverify(char *username, char *challenge, char *response)
162 {
163 	char	*state;
164 	TOKENDB_Rec tokenrec;
165 	TOKEN_CBlock tmp;
166 	TOKEN_CBlock cmp_text;
167 	TOKEN_CBlock user_seed;
168 	TOKEN_CBlock cipher_text;
169 	DES_key_schedule key_schedule;
170 
171 
172 	memset(cmp_text.ct, 0, sizeof(cmp_text.ct));
173 	memset(user_seed.ct, 0, sizeof(user_seed.ct));
174 	memset(cipher_text.ct, 0, sizeof(cipher_text.ct));
175 	memset(tokennumber.ct, 0, sizeof(tokennumber.ct));
176 
177 	(void)strtok(challenge, "\"");
178 	state = strtok(NULL, "\"");
179 	tmp.ul[0] = strtoul(state, NULL, 10);
180 	snprintf(tokennumber.ct, sizeof(tokennumber.ct), "%8.8u",tmp.ul[0]);
181 
182 	/*
183 	 * Retrieve the db record for the user. Nuke it as soon as
184 	 * we have translated out the user's shared secret just in
185 	 * case we (somehow) get core dumped...
186 	 */
187 
188 	if (tokendb_getrec(username, &tokenrec))
189 		return (-1);
190 
191 	h2cb(tokenrec.secret, &user_seed);
192 	explicit_bzero(&tokenrec.secret, sizeof(tokenrec.secret));
193 
194 	if (!(tokenrec.flags & TOKEN_ENABLED))
195 		return (-1);
196 
197 	/*
198 	 * Compute the anticipated response in hex. Nuke the user's
199 	 * shared secret asap.
200 	 */
201 
202 	DES_fixup_key_parity(&user_seed.cb);
203 	DES_key_sched(&user_seed.cb, &key_schedule);
204 	explicit_bzero(user_seed.ct, sizeof(user_seed.ct));
205 	DES_ecb_encrypt(&tokennumber.cb, &cipher_text.cb, &key_schedule,
206 	    DES_ENCRYPT);
207 	explicit_bzero(&key_schedule, sizeof(key_schedule));
208 
209 	/*
210 	 * The token thinks it's descended from VAXen.  Deal with i386
211 	 * endian-ness of binary cipher prior to generating ascii from first
212 	 * 32 bits.
213 	 */
214 
215 	HTONL(cipher_text.ul[0]);
216 	snprintf(cmp_text.ct, sizeof(cmp_text.ct), "%8.8x", cipher_text.ul[0]);
217 
218 	if (tokenrec.mode & TOKEN_PHONEMODE) {
219 		/*
220 		 * If we are a CRYPTOCard, we need to see if we are in
221 		 * "telephone number mode".  If so, transmogrify the fourth
222 		 * digit of the cipher.  Lower case response just in case
223 		 * it's * hex.  Compare hex cipher with anticipated response
224 		 * from token.
225 		 */
226 
227 		lcase(response);
228 
229 		if (response[3] == '-')
230 			cmp_text.ct[3] = '-';
231 	}
232 
233 	if ((tokenrec.mode & TOKEN_HEXMODE) && !strcmp(response, cmp_text.ct))
234 		return (0);
235 
236 	/*
237 	 * No match against the computed hex cipher.  The token could be
238 	 * in decimal mode.  Pervert the string to magic decimal equivalent.
239 	 */
240 
241 	h2d(cmp_text.ct);
242 
243 	if ((tokenrec.mode & TOKEN_DECMODE) && !strcmp(response, cmp_text.ct))
244 		return (0);
245 
246 	return (-1);
247 }
248 
249 /*
250  * Initialize a new user record in the token database.
251  */
252 
253 int
tokenuserinit(int flags,char * username,unsigned char * usecret,unsigned mode)254 tokenuserinit(int flags, char *username, unsigned char *usecret, unsigned mode)
255 {
256 	TOKENDB_Rec tokenrec;
257 	TOKEN_CBlock secret;
258 	TOKEN_CBlock nulls;
259 	TOKEN_CBlock checksum;
260 	TOKEN_CBlock checktxt;
261 	DES_key_schedule key_schedule;
262 
263 	memset(&secret, 0, sizeof(secret));
264 
265 	/*
266 	 * If no user secret passed in, create one
267 	 */
268 
269 	if ( (flags & TOKEN_GENSECRET) )
270 		tokenseed(&secret);
271 	else
272 		memcpy(&secret, usecret, sizeof(DES_cblock));
273 
274 	DES_fixup_key_parity(&secret.cb);
275 
276 	/*
277 	 * Check if the db record already exists.  If there's no
278 	 * force-init flag and it exists, go away. Else,
279 	 * create the user's db record and put to the db.
280 	 */
281 
282 
283 	if (!(flags & TOKEN_FORCEINIT) &&
284 	    tokendb_getrec(username, &tokenrec) == 0)
285 		return (1);
286 
287 	memset(&tokenrec, 0, sizeof(tokenrec));
288 	strlcpy(tokenrec.uname, username, sizeof(tokenrec.uname));
289 	cb2h(secret, tokenrec.secret);
290 	tokenrec.mode = 0;
291 	tokenrec.flags = TOKEN_ENABLED | TOKEN_USEMODES;
292 	tokenrec.mode = mode;
293 	memset(tokenrec.reserved_char1, 0, sizeof(tokenrec.reserved_char1));
294 	memset(tokenrec.reserved_char2, 0, sizeof(tokenrec.reserved_char2));
295 
296 	if (tokendb_putrec(username, &tokenrec))
297 		return (-1);
298 
299 	/*
300 	 * Check if the shared secret was generated here. If so, we
301 	 * need to inform the user about it in order that it can be
302 	 * programmed into the token. See tokenverify() (above) for
303 	 * discussion of cipher generation.
304 	 */
305 
306 	if (!(flags & TOKEN_GENSECRET)) {
307 		explicit_bzero(&secret, sizeof(secret));
308 		return (0);
309 	}
310 
311 	printf("Shared secret for %s\'s token: "
312 	    "%03o %03o %03o %03o %03o %03o %03o %03o\n",
313 	    username, secret.cb[0], secret.cb[1], secret.cb[2], secret.cb[3],
314 	    secret.cb[4], secret.cb[5], secret.cb[6], secret.cb[7]);
315 
316 	DES_key_sched(&secret.cb, &key_schedule);
317 	explicit_bzero(&secret, sizeof(secret));
318 	memset(&nulls, 0, sizeof(nulls));
319 	DES_ecb_encrypt(&nulls.cb, &checksum.cb, &key_schedule, DES_ENCRYPT);
320 	explicit_bzero(&key_schedule, sizeof(key_schedule));
321 	HTONL(checksum.ul[0]);
322 	snprintf(checktxt.ct, sizeof(checktxt.ct), "%8.8x", checksum.ul[0]);
323 	printf("Hex Checksum: \"%s\"", checktxt.ct);
324 
325 	h2d(checktxt.ct);
326 	printf("\tDecimal Checksum: \"%s\"\n", checktxt.ct);
327 
328 	return (0);
329 }
330 
331 /*
332  * Magically transform a hex character string into a decimal character
333  * string as defined by the token card vendor. The string should have
334  * been lowercased by now.
335  */
336 
337 static	void
h2d(char * cp)338 h2d(char *cp)
339 {
340 	int	i;
341 
342 	for (i=0; i<sizeof(DES_cblock); i++, cp++) {
343 		if (*cp >= 'a' && *cp <= 'f')
344 			*cp = tt->map[*cp - 'a'];
345 	}
346 }
347 
348 /*
349  * Translate an hex 16 byte ascii representation of an unsigned
350  * integer to a DES_cblock.
351  */
352 
353 static	void
h2cb(char * hp,TOKEN_CBlock * cb)354 h2cb(char *hp, TOKEN_CBlock *cb)
355 {
356 	char	scratch[9];
357 
358 	strlcpy(scratch, hp, sizeof(scratch));
359 	cb->ul[0] = strtoul(scratch, NULL, 16);
360 
361 	strlcpy(scratch, hp + 8, sizeof(scratch));
362 	cb->ul[1] = strtoul(scratch, NULL, 16);
363 }
364 
365 /*
366  * Translate a DES_cblock to an 16 byte ascii hex representation.
367  */
368 
369 static	void
cb2h(TOKEN_CBlock cb,char * hp)370 cb2h(TOKEN_CBlock cb, char* hp)
371 {
372 	char	scratch[17];
373 
374 	snprintf(scratch,   9, "%8.8x", cb.ul[0]);
375 	snprintf(scratch+8, 9, "%8.8x", cb.ul[1]);
376 	memcpy(hp, scratch, 16);
377 }
378 
379 /*
380  * Lowercase possible hex response
381  */
382 
383 static	void
lcase(char * cp)384 lcase(char *cp)
385 {
386 	while (*cp) {
387 		if (isupper((unsigned char)*cp))
388 			*cp = tolower((unsigned char)*cp);
389 		cp++;
390 	}
391 }
392