1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/crypto_module_password_dialog_nss.h"
6
7#include <pk11pub.h>
8
9#include "base/bind.h"
10#include "base/logging.h"
11#include "content/public/browser/browser_thread.h"
12#include "net/base/crypto_module.h"
13#include "net/cert/x509_certificate.h"
14
15using content::BrowserThread;
16
17namespace {
18
19bool ShouldShowDialog(const net::CryptoModule* module) {
20  // The wincx arg is unused since we don't call PK11_SetIsLoggedInFunc.
21  return (PK11_NeedLogin(module->os_module_handle()) &&
22          !PK11_IsLoggedIn(module->os_module_handle(), NULL /* wincx */));
23}
24
25// Basically an asynchronous implementation of NSS's PK11_DoPassword.
26// Note: This currently handles only the simple case.  See the TODOs in
27// GotPassword for what is yet unimplemented.
28class SlotUnlocker {
29 public:
30  SlotUnlocker(const net::CryptoModuleList& modules,
31               chrome::CryptoModulePasswordReason reason,
32               const net::HostPortPair& server,
33               gfx::NativeWindow parent,
34               const base::Closure& callback);
35
36  void Start();
37
38 private:
39  void GotPassword(const std::string& password);
40  void Done();
41
42  size_t current_;
43  net::CryptoModuleList modules_;
44  chrome::CryptoModulePasswordReason reason_;
45  net::HostPortPair server_;
46  gfx::NativeWindow parent_;
47  base::Closure callback_;
48  PRBool retry_;
49};
50
51SlotUnlocker::SlotUnlocker(const net::CryptoModuleList& modules,
52                           chrome::CryptoModulePasswordReason reason,
53                           const net::HostPortPair& server,
54                           gfx::NativeWindow parent,
55                           const base::Closure& callback)
56    : current_(0),
57      modules_(modules),
58      reason_(reason),
59      server_(server),
60      parent_(parent),
61      callback_(callback),
62      retry_(PR_FALSE) {
63  DCHECK_CURRENTLY_ON(BrowserThread::UI);
64}
65
66void SlotUnlocker::Start() {
67  DCHECK_CURRENTLY_ON(BrowserThread::UI);
68
69  for (; current_ < modules_.size(); ++current_) {
70    if (ShouldShowDialog(modules_[current_].get())) {
71      ShowCryptoModulePasswordDialog(
72          modules_[current_]->GetTokenName(),
73          retry_,
74          reason_,
75          server_.host(),
76          parent_,
77          base::Bind(&SlotUnlocker::GotPassword, base::Unretained(this)));
78      return;
79    }
80  }
81  Done();
82}
83
84void SlotUnlocker::GotPassword(const std::string& password) {
85  // TODO(mattm): PK11_DoPassword has something about PK11_Global.verifyPass.
86  // Do we need it?
87  // http://mxr.mozilla.org/mozilla/source/security/nss/lib/pk11wrap/pk11auth.c#577
88
89  if (password.empty()) {
90    // User cancelled entering password.  Oh well.
91    ++current_;
92    Start();
93    return;
94  }
95
96  // TODO(mattm): handle protectedAuthPath
97  SECStatus rv = PK11_CheckUserPassword(modules_[current_]->os_module_handle(),
98                                        password.c_str());
99  if (rv == SECWouldBlock) {
100    // Incorrect password.  Try again.
101    retry_ = PR_TRUE;
102    Start();
103    return;
104  }
105
106  // TODO(mattm): PK11_DoPassword calls nssTrustDomain_UpdateCachedTokenCerts on
107  // non-friendly slots.  How important is that?
108
109  // Correct password (SECSuccess) or too many attempts/other failure
110  // (SECFailure).  Either way we're done with this slot.
111  ++current_;
112  Start();
113}
114
115void SlotUnlocker::Done() {
116  DCHECK_EQ(current_, modules_.size());
117  callback_.Run();
118  delete this;
119}
120
121}  // namespace
122
123namespace chrome {
124
125void UnlockSlotsIfNecessary(const net::CryptoModuleList& modules,
126                            chrome::CryptoModulePasswordReason reason,
127                            const net::HostPortPair& server,
128                            gfx::NativeWindow parent,
129                            const base::Closure& callback) {
130  DCHECK_CURRENTLY_ON(BrowserThread::UI);
131  for (size_t i = 0; i < modules.size(); ++i) {
132    if (ShouldShowDialog(modules[i].get())) {
133      (new SlotUnlocker(modules, reason, server, parent, callback))->Start();
134      return;
135    }
136  }
137  callback.Run();
138}
139
140void UnlockCertSlotIfNecessary(net::X509Certificate* cert,
141                               chrome::CryptoModulePasswordReason reason,
142                               const net::HostPortPair& server,
143                               gfx::NativeWindow parent,
144                               const base::Closure& callback) {
145  net::CryptoModuleList modules;
146  modules.push_back(net::CryptoModule::CreateFromHandle(
147      cert->os_cert_handle()->slot));
148  UnlockSlotsIfNecessary(modules, reason, server, parent, callback);
149}
150
151}  // namespace chrome
152