summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/nsSmartCardMonitor.cpp
blob: 3423d4f5f5f1dba5609a0091d02822d21a8fbf9f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsSmartCardMonitor.h"

#include "ScopedNSSTypes.h"
#include "mozilla/Services.h"
#include "mozilla/Unused.h"
#include "nsIObserverService.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "nspr.h"
#include "pk11func.h"

using namespace mozilla;

//
// The SmartCard monitoring thread should start up for each module we load
// that has removable tokens. This code calls an NSS function which waits
// until there is a change in the token state. NSS uses the
// C_WaitForSlotEvent() call in PKCS #11 if the module implements the call,
// otherwise NSS will poll the token in a loop with a delay of 'latency'
// between polls. Note that the C_WaitForSlotEvent() may wake up on any type
// of token event, so it's necessary to filter these events down to just the
// insertion and removal events we are looking for.
//
// Once the event is found, it is dispatched to the main thread to notify
// any window where window.crypto.enableSmartCardEvents is true.
// Additionally, all observers of the topics "smartcard-insert" and
// "smartcard-remove" are notified by the observer service of the appropriate
// event.
//

class nsTokenEventRunnable : public nsIRunnable {
public:
  nsTokenEventRunnable(const nsAString& aType, const nsAString& aTokenName)
    : mType(aType)
    , mTokenName(aTokenName)
  {
  }

  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIRUNNABLE

private:
  virtual ~nsTokenEventRunnable() {}

  nsString mType;
  nsString mTokenName;
};

NS_IMPL_ISUPPORTS(nsTokenEventRunnable, nsIRunnable)

NS_IMETHODIMP
nsTokenEventRunnable::Run()
{
  MOZ_ASSERT(NS_IsMainThread());

  nsCOMPtr<nsIObserverService> observerService =
    mozilla::services::GetObserverService();
  if (!observerService) {
    return NS_ERROR_FAILURE;
  }
  // This conversion is safe because mType can only be "smartcard-insert"
  // or "smartcard-remove".
  NS_ConvertUTF16toUTF8 eventTypeUTF8(mType);
  return observerService->NotifyObservers(nullptr, eventTypeUTF8.get(),
                                          mTokenName.get());
}

// self linking and removing double linked entry
// adopts the thread it is passed.
class SmartCardThreadEntry
{
public:
  friend class SmartCardThreadList;
  SmartCardThreadEntry(SmartCardMonitoringThread *thread,
                       SmartCardThreadEntry *next,
                       SmartCardThreadEntry *prev,
                       SmartCardThreadEntry **head)
    : next(next)
    , prev(prev)
    , head(head)
    , thread(thread)
  {
    if (prev) {
      prev->next = this;
    } else {
      *head = this;
    }
    if (next) {
      next->prev = this;
    }
  }

  ~SmartCardThreadEntry()
  {
    if (prev) {
      prev->next = next;
    } else {
      *head = next;
    }
    if (next) {
      next->prev = prev;
    }
    // NOTE: automatically stops the thread
    delete thread;
  }

private:
  SmartCardThreadEntry *next;
  SmartCardThreadEntry *prev;
  SmartCardThreadEntry **head;
  SmartCardMonitoringThread *thread;
};

//
// SmartCardThreadList is a class to help manage the running threads.
// That way new threads could be started and old ones terminated as we
// load and unload modules.
//
SmartCardThreadList::SmartCardThreadList() : head(0)
{
}

SmartCardThreadList::~SmartCardThreadList()
{
  // the head is self linking and unlinking, the following
  // loop removes all entries on the list.
  // it will also stop the thread if it happens to be running
  while (head) {
    delete head;
  }
}

void
SmartCardThreadList::Remove(SECMODModule *aModule)
{
  for (SmartCardThreadEntry* current = head; current;
       current = current->next) {
    if (current->thread->GetModule() == aModule) {
      // NOTE: automatically stops the thread and dequeues it from the list
      delete current;
      return;
    }
  }
}

// adopts the thread passed to it. Starts the thread as well
nsresult
SmartCardThreadList::Add(SmartCardMonitoringThread* thread)
{
  SmartCardThreadEntry* current = new SmartCardThreadEntry(thread, head,
                                                           nullptr, &head);
  // OK to forget current here, it's on the list.
  Unused << current;

  return thread->Start();
}


// We really should have a Unity PL Hash function...
static PLHashNumber
unity(const void* key) { return PLHashNumber(NS_PTR_TO_INT32(key)); }

SmartCardMonitoringThread::SmartCardMonitoringThread(SECMODModule* module_)
  : mThread(nullptr)
{
  mModule = SECMOD_ReferenceModule(module_);
  // simple hash functions, most modules have less than 3 slots, so 10 buckets
  // should be plenty
  mHash = PL_NewHashTable(10, unity, PL_CompareValues, PL_CompareStrings,
                          nullptr, 0);
}

//
// when we shutdown the thread, be sure to stop it first. If not, it just might
// crash when the mModule it is looking at disappears.
//
SmartCardMonitoringThread::~SmartCardMonitoringThread()
{
  Stop();
  SECMOD_DestroyModule(mModule);
  if (mHash) {
    PL_HashTableDestroy(mHash);
  }
}

nsresult
SmartCardMonitoringThread::Start()
{
  if (!mThread) {
    mThread = PR_CreateThread(PR_SYSTEM_THREAD, LaunchExecute, this,
                              PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
                              PR_JOINABLE_THREAD, 0);
  }
  return mThread ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}

//
// Should only stop if we are through with the module.
// CancelWait has the side effect of losing all the keys and
// current operations on the module!. (See the comment in
// SECMOD_CancelWait for why this is so..).
//
void SmartCardMonitoringThread::Stop()
{
  SECStatus rv;

  rv = SECMOD_CancelWait(mModule);
  if (rv != SECSuccess) {
    // we didn't wake up the Wait, so don't try to join the thread
    // otherwise we will hang forever...
    return;
  }

  // confused about the memory model here? NSPR owns the memory for
  // threads. non-joinable threads are freed when the thread dies.
  // joinable threads are freed after the call to PR_JoinThread.
  // That means if SECMOD_CancelWait fails, we'll leak the mThread
  // structure. this is considered preferable to hanging (which is
  // what will happen if we try to join a thread that blocked).
  if (mThread) {
    PR_JoinThread(mThread);
    mThread = 0;
  }
}

//
// remember the name and series of a token in a particular slot.
// This is important because the name is no longer available when
// the token is removed. If listeners depended on this information,
// They would be out of luck. It also is a handy way of making sure
// we don't generate spurious insertion and removal events as the slot
// cycles through various states.
//
void
SmartCardMonitoringThread::SetTokenName(CK_SLOT_ID slotid,
                                       const char* tokenName, uint32_t series)
{
  if (mHash) {
    if (tokenName) {
      int len = strlen(tokenName) + 1;
      /* this must match the allocator used in
       * PLHashAllocOps.freeEntry DefaultFreeEntry */
      char* entry = (char*)PR_Malloc(len + sizeof(uint32_t));

      if (entry) {
        memcpy(entry, &series, sizeof(uint32_t));
        memcpy(&entry[sizeof(uint32_t)], tokenName, len);

        PL_HashTableAdd(mHash, (void*)(uintptr_t)slotid, entry); /* adopt */
        return;
      }
    } else {
      // if tokenName was not provided, remove the old one (implicit delete)
      PL_HashTableRemove(mHash, (void*)(uintptr_t)slotid);
    }
  }
}

// retrieve the name saved above
const char*
SmartCardMonitoringThread::GetTokenName(CK_SLOT_ID slotid)
{
  const char* tokenName = nullptr;
  const char* entry;

  if (mHash) {
    entry = (const char*)PL_HashTableLookupConst(mHash,
                                                 (void*)(uintptr_t)slotid);
    if (entry) {
      tokenName = &entry[sizeof(uint32_t)];
    }
  }
  return tokenName;
}

// retrieve the series saved in SetTokenName above
uint32_t
SmartCardMonitoringThread::GetTokenSeries(CK_SLOT_ID slotid)
{
  uint32_t series = 0;
  const char* entry;

  if (mHash) {
    entry = (const char*)PL_HashTableLookupConst(mHash,
                                                 (void*)(uintptr_t)slotid);
    if (entry) {
      memcpy(&series, entry, sizeof(uint32_t));
    }
  }
  return series;
}

//
// helper function to pass the event off to nsNSSComponent.
//
void
SmartCardMonitoringThread::SendEvent(const nsAString& eventType,
                                     const char* tokenName)
{
  // The token name should be UTF8, but it's not clear that this is enforced
  // by NSS. To be safe, we explicitly check here before converting it to
  // UTF16. If it isn't UTF8, we just use an empty string with the idea that
  // consumers of these events should at least be notified that something
  // happened.
  nsAutoString tokenNameUTF16(NS_LITERAL_STRING(""));
  if (IsUTF8(nsDependentCString(tokenName))) {
    tokenNameUTF16.Assign(NS_ConvertUTF8toUTF16(tokenName));
  }
  nsCOMPtr<nsIRunnable> runnable(new nsTokenEventRunnable(eventType,
                                                          tokenNameUTF16));
  NS_DispatchToMainThread(runnable);
}

//
// This is the main loop.
//
void SmartCardMonitoringThread::Execute()
{
  const char* tokenName;

  //
  // populate token names for already inserted tokens.
  //
  PK11SlotList* sl = PK11_FindSlotsByNames(mModule->dllName, nullptr, nullptr,
                                           true);

  PK11SlotListElement* sle;
  if (sl) {
    for (sle = PK11_GetFirstSafe(sl); sle;
         sle = PK11_GetNextSafe(sl, sle, false)) {
      SetTokenName(PK11_GetSlotID(sle->slot), PK11_GetTokenName(sle->slot),
                   PK11_GetSlotSeries(sle->slot));
    }
    PK11_FreeSlotList(sl);
  }

  // loop starts..
  do {
    UniquePK11SlotInfo slot(
      SECMOD_WaitForAnyTokenEvent(mModule, 0, PR_SecondsToInterval(1)));
    if (!slot) {
      break;
    }

    // now we have a potential insertion or removal event, see if the slot
    // is present to determine which it is...
    if (PK11_IsPresent(slot.get())) {
      // insertion
      CK_SLOT_ID slotID = PK11_GetSlotID(slot.get());
      uint32_t series = PK11_GetSlotSeries(slot.get());

      // skip spurious insertion events...
      if (series != GetTokenSeries(slotID)) {
        // if there's a token name, then we have not yet issued a remove
        // event for the previous token, do so now...
        tokenName = GetTokenName(slotID);
        if (tokenName) {
          SendEvent(NS_LITERAL_STRING("smartcard-remove"), tokenName);
        }
        tokenName = PK11_GetTokenName(slot.get());
        // save the token name and series
        SetTokenName(slotID, tokenName, series);
        SendEvent(NS_LITERAL_STRING("smartcard-insert"), tokenName);
      }
    } else {
      // retrieve token name
      CK_SLOT_ID slotID = PK11_GetSlotID(slot.get());
      tokenName = GetTokenName(slotID);
      // if there's not a token name, then the software isn't expecting
      // a (or another) remove event.
      if (tokenName) {
        SendEvent(NS_LITERAL_STRING("smartcard-remove"), tokenName);
        // clear the token name (after we send it)
        SetTokenName(slotID, nullptr, 0);
      }
    }
  } while (1);
}

// accessor to help searching active Monitoring threads
const SECMODModule* SmartCardMonitoringThread::GetModule()
{
  return mModule;
}

// C-like calling sequence to glue into PR_CreateThread.
void SmartCardMonitoringThread::LaunchExecute(void* arg)
{
  PR_SetCurrentThreadName("SmartCard");

  ((SmartCardMonitoringThread*)arg)->Execute();
}