1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include "pkcs11.h"
6
7 #ifndef DEVM_H
8 #include "devm.h"
9 #endif /* DEVM_H */
10
11 #ifndef CKHELPER_H
12 #include "ckhelper.h"
13 #endif /* CKHELPER_H */
14
15 #include "pkim.h"
16 #include "dev3hack.h"
17 #include "pk11func.h"
18
19 /* measured in seconds */
20 #define NSSSLOT_TOKEN_DELAY_TIME 1
21
22 /* this should track global and per-transaction login information */
23
24 #define NSSSLOT_IS_FRIENDLY(slot) \
25 (slot->base.flags & NSSSLOT_FLAGS_FRIENDLY)
26
27 /* measured as interval */
28 static PRIntervalTime s_token_delay_time = 0;
29
30 NSS_IMPLEMENT PRStatus
nssSlot_Destroy(NSSSlot * slot)31 nssSlot_Destroy(
32 NSSSlot *slot)
33 {
34 if (slot) {
35 if (PR_ATOMIC_DECREMENT(&slot->base.refCount) == 0) {
36 PK11_FreeSlot(slot->pk11slot);
37 PZ_DestroyLock(slot->base.lock);
38 PZ_DestroyCondVar(slot->isPresentCondition);
39 PZ_DestroyLock(slot->isPresentLock);
40 return nssArena_Destroy(slot->base.arena);
41 }
42 }
43 return PR_SUCCESS;
44 }
45
46 void
nssSlot_EnterMonitor(NSSSlot * slot)47 nssSlot_EnterMonitor(NSSSlot *slot)
48 {
49 if (slot->lock) {
50 PZ_Lock(slot->lock);
51 }
52 }
53
54 void
nssSlot_ExitMonitor(NSSSlot * slot)55 nssSlot_ExitMonitor(NSSSlot *slot)
56 {
57 if (slot->lock) {
58 PZ_Unlock(slot->lock);
59 }
60 }
61
62 NSS_IMPLEMENT void
NSSSlot_Destroy(NSSSlot * slot)63 NSSSlot_Destroy(
64 NSSSlot *slot)
65 {
66 (void)nssSlot_Destroy(slot);
67 }
68
69 NSS_IMPLEMENT NSSSlot *
nssSlot_AddRef(NSSSlot * slot)70 nssSlot_AddRef(
71 NSSSlot *slot)
72 {
73 PR_ATOMIC_INCREMENT(&slot->base.refCount);
74 return slot;
75 }
76
77 NSS_IMPLEMENT NSSUTF8 *
nssSlot_GetName(NSSSlot * slot)78 nssSlot_GetName(
79 NSSSlot *slot)
80 {
81 return slot->base.name;
82 }
83
84 NSS_IMPLEMENT void
nssSlot_ResetDelay(NSSSlot * slot)85 nssSlot_ResetDelay(
86 NSSSlot *slot)
87 {
88 PZ_Lock(slot->isPresentLock);
89 slot->lastTokenPingState = nssSlotLastPingState_Reset;
90 PZ_Unlock(slot->isPresentLock);
91 }
92
93 static PRBool
token_status_checked(const NSSSlot * slot)94 token_status_checked(const NSSSlot *slot)
95 {
96 PRIntervalTime time;
97 int lastPingState = slot->lastTokenPingState;
98 /* When called from the same thread, that means
99 * nssSlot_IsTokenPresent() is called recursively through
100 * nssSlot_Refresh(). Return immediately in that case. */
101 if (slot->isPresentThread == PR_GetCurrentThread()) {
102 return PR_TRUE;
103 }
104 /* Set the delay time for checking the token presence */
105 if (s_token_delay_time == 0) {
106 s_token_delay_time = PR_SecondsToInterval(NSSSLOT_TOKEN_DELAY_TIME);
107 }
108 time = PR_IntervalNow();
109 if ((lastPingState == nssSlotLastPingState_Valid) && ((time - slot->lastTokenPingTime) < s_token_delay_time)) {
110 return PR_TRUE;
111 }
112 return PR_FALSE;
113 }
114
115 NSS_IMPLEMENT PRBool
nssSlot_IsTokenPresent(NSSSlot * slot)116 nssSlot_IsTokenPresent(
117 NSSSlot *slot)
118 {
119 CK_RV ckrv;
120 PRStatus nssrv;
121 NSSToken *nssToken = NULL;
122 /* XXX */
123 nssSession *session;
124 CK_SLOT_INFO slotInfo;
125 void *epv;
126 PRBool isPresent = PR_FALSE;
127 PRBool doUpdateCachedCerts = PR_FALSE;
128
129 /* permanent slots are always present unless they're disabled */
130 if (nssSlot_IsPermanent(slot)) {
131 return !PK11_IsDisabled(slot->pk11slot);
132 }
133
134 /* avoid repeated calls to check token status within set interval */
135 PZ_Lock(slot->isPresentLock);
136 if (token_status_checked(slot)) {
137 CK_FLAGS ckFlags = slot->ckFlags;
138 PZ_Unlock(slot->isPresentLock);
139 return ((ckFlags & CKF_TOKEN_PRESENT) != 0);
140 }
141 PZ_Unlock(slot->isPresentLock);
142
143 /* First obtain the slot epv before we set up the condition
144 * variable, so we can just return if we couldn't get it. */
145 epv = slot->epv;
146 if (!epv) {
147 return PR_FALSE;
148 }
149
150 /* set up condition so only one thread is active in this part of the code at a time */
151 PZ_Lock(slot->isPresentLock);
152 while (slot->isPresentThread) {
153 PR_WaitCondVar(slot->isPresentCondition, PR_INTERVAL_NO_TIMEOUT);
154 }
155 /* if we were one of multiple threads here, the first thread will have
156 * given us the answer, no need to make more queries of the token. */
157 if (token_status_checked(slot)) {
158 CK_FLAGS ckFlags = slot->ckFlags;
159 PZ_Unlock(slot->isPresentLock);
160 return ((ckFlags & CKF_TOKEN_PRESENT) != 0);
161 }
162 /* this is the winning thread, block all others until we've determined
163 * if the token is present and that it needs initialization. */
164 slot->lastTokenPingState = nssSlotLastPingState_Update;
165 slot->isPresentThread = PR_GetCurrentThread();
166
167 PZ_Unlock(slot->isPresentLock);
168
169 nssToken = PK11Slot_GetNSSToken(slot->pk11slot);
170 if (!nssToken) {
171 isPresent = PR_FALSE;
172 goto done;
173 }
174
175 nssSlot_EnterMonitor(slot);
176 ckrv = CKAPI(epv)->C_GetSlotInfo(slot->slotID, &slotInfo);
177 nssSlot_ExitMonitor(slot);
178 if (ckrv != CKR_OK) {
179 nssToken->base.name[0] = 0; /* XXX */
180 isPresent = PR_FALSE;
181 goto done;
182 }
183 slot->ckFlags = slotInfo.flags;
184 /* check for the presence of the token */
185 if ((slot->ckFlags & CKF_TOKEN_PRESENT) == 0) {
186 session = nssToken_GetDefaultSession(nssToken);
187 if (session) {
188 nssSession_EnterMonitor(session);
189 /* token is not present */
190 if (session->handle != CK_INVALID_HANDLE) {
191 /* session is valid, close and invalidate it */
192 CKAPI(epv)
193 ->C_CloseSession(session->handle);
194 session->handle = CK_INVALID_HANDLE;
195 }
196 nssSession_ExitMonitor(session);
197 }
198 if (nssToken->base.name[0] != 0) {
199 /* notify the high-level cache that the token is removed */
200 nssToken->base.name[0] = 0; /* XXX */
201 nssToken_NotifyCertsNotVisible(nssToken);
202 }
203 nssToken->base.name[0] = 0; /* XXX */
204 /* clear the token cache */
205 nssToken_Remove(nssToken);
206 isPresent = PR_FALSE;
207 goto done;
208 }
209 /* token is present, use the session info to determine if the card
210 * has been removed and reinserted.
211 */
212 session = nssToken_GetDefaultSession(nssToken);
213 if (session) {
214 PRBool tokenRemoved;
215 nssSession_EnterMonitor(session);
216 if (session->handle != CK_INVALID_HANDLE) {
217 CK_SESSION_INFO sessionInfo;
218 ckrv = CKAPI(epv)->C_GetSessionInfo(session->handle, &sessionInfo);
219 if (ckrv != CKR_OK) {
220 /* session is screwy, close and invalidate it */
221 CKAPI(epv)
222 ->C_CloseSession(session->handle);
223 session->handle = CK_INVALID_HANDLE;
224 }
225 }
226 tokenRemoved = (session->handle == CK_INVALID_HANDLE);
227 nssSession_ExitMonitor(session);
228 /* token not removed, finished */
229 if (!tokenRemoved) {
230 isPresent = PR_TRUE;
231 goto done;
232 }
233 }
234 /* the token has been removed, and reinserted, or the slot contains
235 * a token it doesn't recognize. invalidate all the old
236 * information we had on this token, if we can't refresh, clear
237 * the present flag */
238 nssToken_NotifyCertsNotVisible(nssToken);
239 nssToken_Remove(nssToken);
240 if (nssToken->base.name[0] == 0) {
241 doUpdateCachedCerts = PR_TRUE;
242 }
243 if (PK11_InitToken(slot->pk11slot, PR_FALSE) != SECSuccess) {
244 isPresent = PR_FALSE;
245 goto done;
246 }
247 if (doUpdateCachedCerts) {
248 nssTrustDomain_UpdateCachedTokenCerts(nssToken->trustDomain,
249 nssToken);
250 }
251 nssrv = nssToken_Refresh(nssToken);
252 if (nssrv != PR_SUCCESS) {
253 nssToken->base.name[0] = 0; /* XXX */
254 slot->ckFlags &= ~CKF_TOKEN_PRESENT;
255 isPresent = PR_FALSE;
256 goto done;
257 }
258 isPresent = PR_TRUE;
259 done:
260 if (nssToken) {
261 (void)nssToken_Destroy(nssToken);
262 }
263 /* Once we've set up the condition variable,
264 * Before returning, it's necessary to:
265 * 1) Set the lastTokenPingTime so that any other threads waiting on this
266 * initialization and any future calls within the initialization window
267 * return the just-computed status.
268 * 2) Indicate we're complete, waking up all other threads that may still
269 * be waiting on initialization can progress.
270 */
271 PZ_Lock(slot->isPresentLock);
272 /* don't update the time if we were reset while we were
273 * getting the token state */
274 if (slot->lastTokenPingState == nssSlotLastPingState_Update) {
275 slot->lastTokenPingTime = PR_IntervalNow();
276 slot->lastTokenPingState = nssSlotLastPingState_Valid;
277 }
278 slot->isPresentThread = NULL;
279 PR_NotifyAllCondVar(slot->isPresentCondition);
280 PZ_Unlock(slot->isPresentLock);
281 return isPresent;
282 }
283
284 NSS_IMPLEMENT void *
nssSlot_GetCryptokiEPV(NSSSlot * slot)285 nssSlot_GetCryptokiEPV(
286 NSSSlot *slot)
287 {
288 return slot->epv;
289 }
290
291 NSS_IMPLEMENT NSSToken *
nssSlot_GetToken(NSSSlot * slot)292 nssSlot_GetToken(
293 NSSSlot *slot)
294 {
295 NSSToken *rvToken = NULL;
296
297 if (nssSlot_IsTokenPresent(slot)) {
298 rvToken = PK11Slot_GetNSSToken(slot->pk11slot);
299 }
300
301 return rvToken;
302 }
303
304 NSS_IMPLEMENT PRStatus
nssSession_EnterMonitor(nssSession * s)305 nssSession_EnterMonitor(
306 nssSession *s)
307 {
308 if (s->lock)
309 PZ_Lock(s->lock);
310 return PR_SUCCESS;
311 }
312
313 NSS_IMPLEMENT PRStatus
nssSession_ExitMonitor(nssSession * s)314 nssSession_ExitMonitor(
315 nssSession *s)
316 {
317 return (s->lock) ? PZ_Unlock(s->lock) : PR_SUCCESS;
318 }
319
320 NSS_EXTERN PRBool
nssSession_IsReadWrite(nssSession * s)321 nssSession_IsReadWrite(
322 nssSession *s)
323 {
324 return s->isRW;
325 }
326