1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * This file contains the functions that are shared among
31  * the various services this tool will ultimately provide.
32  */
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <ctype.h>
38 #include <cryptoutil.h>
39 #include <security/cryptoki.h>
40 #include "common.h"
41 
42 /* Global PKCS#11 error value. */
43 int		pk11_errno = 0;
44 
45 /*
46  * Gets passphrase from user, caller needs to free when done.
47  */
48 int
49 get_password(char *prompt, char **password)
50 {
51 	char		*phrase;
52 
53 	/* Prompt user for password. */
54 	if ((phrase = getpassphrase(prompt)) == NULL)
55 		return (-1);
56 
57 	/* Duplicate passphrase in separate chunk of memory */
58 	if ((*password = strdup(phrase)) == NULL)
59 		return (-1);
60 
61 	return (strlen(phrase));
62 }
63 
64 /*
65  * Perform any PKCS#11 setup here.  Right now, this tool only
66  * requires C_Initialize().  Additional features planned for
67  * this tool will require more initialization and state info
68  * added here.
69  */
70 int
71 init_pk11(void)
72 {
73 	int		rv;
74 
75 	cryptodebug("inside init_pk11");
76 
77 	/* Initialize PKCS#11 library. */
78 	if ((rv = C_Initialize(NULL_PTR)) != CKR_OK &&
79 	    rv != CKR_CRYPTOKI_ALREADY_INITIALIZED) {
80 		pk11_errno = rv;
81 		return (PK_ERR_PK11INIT);
82 	}
83 
84 	return (PK_ERR_NONE);
85 }
86 
87 /*
88  * memcmp_pad_max() is a specialized version of memcmp() which
89  * compares two pieces of data up to a maximum length.  If the
90  * the two data match up the maximum length, they are considered
91  * matching.  Trailing blanks do not cause the match to fail if
92  * one of the data is shorted.
93  *
94  * Examples of matches:
95  *	"one"           |
96  *	"one      "     |
97  *	                ^maximum length
98  *
99  *	"Number One     |  X"	(X is beyond maximum length)
100  *	"Number One   " |
101  *	                ^maximum length
102  *
103  * Examples of mismatches:
104  *	" one"
105  *	"one"
106  *
107  *	"Number One    X|"
108  *	"Number One     |"
109  *	                ^maximum length
110  */
111 static int
112 memcmp_pad_max(void *d1, uint_t d1_len, void *d2, uint_t d2_len, uint_t max_sz)
113 {
114 
115 	uint_t		len, extra_len;
116 	char		*marker;
117 
118 	/* No point in comparing anything beyond max_sz */
119 	if (d1_len > max_sz)
120 		d1_len = max_sz;
121 	if (d2_len > max_sz)
122 		d2_len = max_sz;
123 
124 	/* Find shorter of the two data. */
125 	if (d1_len <= d2_len) {
126 		len = d1_len;
127 		extra_len = d2_len;
128 		marker = d2;
129 	} else {	/* d1_len > d2_len */
130 		len = d2_len;
131 		extra_len = d1_len;
132 		marker = d1;
133 	}
134 
135 	/* Have a match in the shortest length of data? */
136 	if (memcmp(d1, d2, len) != 0)
137 		/* CONSTCOND */
138 		return (!0);
139 
140 	/* If the rest of longer data is nulls or blanks, call it a match. */
141 	while (len < extra_len)
142 		if (!isspace(marker[len++]))
143 			/* CONSTCOND */
144 			return (!0);
145 	return (0);
146 }
147 
148 /*
149  * Locate a token slot whose token matches the label, manufacturer
150  * ID, and serial number given.  Token label must be specified,
151  * manufacturer ID and serial number are optional.
152  */
153 int
154 find_token_slot(char *token_name, char *manuf_id, char *serial_no,
155 		CK_SLOT_ID *slot_id, CK_FLAGS *pin_state)
156 {
157 	CK_SLOT_ID_PTR	slot_list;
158 	CK_TOKEN_INFO	token_info;
159 	CK_ULONG	slot_count = 0;
160 	int		rv;
161 	int		i;
162 	uint_t		len, max_sz;
163 	boolean_t	tok_match = B_FALSE,
164 			man_match = B_FALSE,
165 			ser_match = B_FALSE;
166 
167 	cryptodebug("inside find_token_slot");
168 
169 	/*
170 	 * Get the slot count first because we don't know how many
171 	 * slots there are and how many of those slots even have tokens.
172 	 * Don't specify an arbitrary buffer size for the slot list;
173 	 * it may be too small (see section 11.5 of PKCS#11 spec).
174 	 * Also select only those slots that have tokens in them,
175 	 * because this tool has no need to know about empty slots.
176 	 */
177 	if ((rv = C_GetSlotList(1, NULL_PTR, &slot_count)) != CKR_OK) {
178 		pk11_errno = rv;
179 		return (PK_ERR_PK11SLOTS);
180 	}
181 
182 	if (slot_count == 0)
183 		return (PK_ERR_NOSLOTS);	/* with tokens in them */
184 
185 	/* Allocate space for the slot list and get it. */
186 	if ((slot_list =
187 	    (CK_SLOT_ID_PTR) malloc(slot_count * sizeof (CK_SLOT_ID))) == NULL)
188 		return (PK_ERR_NOMEMORY);
189 
190 	if ((rv = C_GetSlotList(1, slot_list, &slot_count)) != CKR_OK) {
191 		/* NOTE:  can slot_count change from previous call??? */
192 		pk11_errno = rv;
193 		free(slot_list);
194 		return (PK_ERR_PK11SLOTS);
195 	}
196 
197 	/* Search for the token. */
198 	for (i = 0; i < slot_count; i++) {
199 		if ((rv =
200 		    C_GetTokenInfo(slot_list[i], &token_info)) != CKR_OK) {
201 			cryptodebug("slot %d has no token", i);
202 			continue;
203 		}
204 
205 		len = strlen(token_name);
206 		max_sz = sizeof (token_info.label);
207 		if (memcmp_pad_max(&(token_info.label), max_sz, token_name, len,
208 		    max_sz) == 0)
209 			tok_match = B_TRUE;
210 
211 		cryptodebug("slot %d:", i);
212 		cryptodebug("\tlabel = \"%.32s\"", token_info.label);
213 		cryptodebug("\tmanuf = \"%.32s\"", token_info.manufacturerID);
214 		cryptodebug("\tserno = \"%.16s\"", token_info.serialNumber);
215 		cryptodebug("\tmodel = \"%.16s\"", token_info.model);
216 
217 		cryptodebug("\tCKF_USER_PIN_INITIALIZED = %s",
218 		    (token_info.flags & CKF_USER_PIN_INITIALIZED) ?
219 		    "true" : "false");
220 		cryptodebug("\tCKF_USER_PIN_TO_BE_CHANGED = %s",
221 		    (token_info.flags & CKF_USER_PIN_TO_BE_CHANGED) ?
222 		    "true" : "false");
223 
224 		if (manuf_id) {
225 			len = strlen(manuf_id);
226 			max_sz = sizeof ((char *)(token_info.manufacturerID));
227 			if (memcmp_pad_max(&(token_info.manufacturerID), max_sz,
228 			    manuf_id, len, max_sz) == 0)
229 				man_match = B_TRUE;
230 		}
231 
232 		if (serial_no) {
233 			len = strlen(serial_no);
234 			max_sz = sizeof ((char *)(token_info.serialNumber));
235 			if (memcmp_pad_max(&(token_info.serialNumber), max_sz,
236 			    serial_no, len, max_sz) == 0)
237 				ser_match = B_TRUE;
238 		}
239 
240 		if (tok_match &&
241 		    (manuf_id ? B_TRUE : B_FALSE) == man_match &&
242 		    (serial_no ? B_TRUE : B_FALSE) == ser_match)
243 			break;	/* found it! */
244 	}
245 
246 	if (i == slot_count) {
247 		free(slot_list);
248 		return (PK_ERR_NOTFOUND);
249 	}
250 
251 	cryptodebug("matched token at slot %d", i);
252 	*slot_id = slot_list[i];
253 	*pin_state = (token_info.flags & CKF_USER_PIN_TO_BE_CHANGED);
254 	free(slot_list);
255 	return (PK_ERR_NONE);
256 }
257 
258 /*
259  * Log into the token in given slot and create a session for it.
260  */
261 int
262 login_token(CK_SLOT_ID slot_id, CK_UTF8CHAR_PTR pin, CK_ULONG pinlen,
263 	    CK_SESSION_HANDLE_PTR hdl)
264 {
265 	int		rv;
266 
267 	cryptodebug("inside login_token");
268 
269 	/* Create a read-write session so we can change the PIN. */
270 	if ((rv = C_OpenSession(slot_id, CKF_SERIAL_SESSION|CKF_RW_SESSION,
271 	    NULL, NULL, hdl)) != CKR_OK) {
272 		pk11_errno = rv;
273 		return (PK_ERR_PK11SESSION);
274 	}
275 
276 	/*
277 	 * If the token is newly created, there initial PIN will be "changme",
278 	 * and all subsequent PKCS#11 calls will fail with CKR_PIN_EXPIRED,
279 	 * but C_Login() will succeed.
280 	 */
281 	if ((rv = C_Login(*hdl, CKU_USER, pin, pinlen)) != CKR_OK) {
282 		pk11_errno = rv;
283 		(void) C_CloseSession(*hdl);
284 		cryptodebug("C_Login returns %s", pkcs11_strerror(rv));
285 		if (rv == CKR_USER_PIN_NOT_INITIALIZED)
286 			return (PK_ERR_CHANGEPIN);
287 		return (PK_ERR_PK11LOGIN);
288 	}
289 
290 	return (PK_ERR_NONE);
291 }
292 
293 /*
294  * Log out of the token and close the session.
295  */
296 void
297 logout_token(CK_SESSION_HANDLE hdl)
298 {
299 	cryptodebug("inside logout_token");
300 
301 	if (hdl) {
302 		(void) C_Logout(hdl);
303 		(void) C_CloseSession(hdl);
304 	}
305 	(void) C_Finalize(NULL);
306 }
307