summaryrefslogtreecommitdiffstats
path: root/devtools/shared/security/prompt.js
blob: b03204cc3469ceb8f2e4f2cbad11486914c9874a (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
/* 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/. */

"use strict";

var DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
loader.lazyRequireGetter(
  this,
  "AuthenticationResult",
  "resource://devtools/shared/security/auth.js",
  true
);

const { LocalizationHelper } = require("resource://devtools/shared/l10n.js");
const L10N = new LocalizationHelper(
  "devtools/shared/locales/debugger.properties"
);

var Client = (exports.Client = {});
var Server = (exports.Server = {});

/**
 * During OOB_CERT authentication, a notification dialog like this is used to
 * to display a token which the user must transfer through some mechanism to the
 * server to authenticate the devices.
 *
 * This implementation presents the token as text for the user to transfer
 * manually.  For a mobile device, you should override this implementation with
 * something more convenient, such as displaying a QR code.
 *
 * @param host string
 *        The host name or IP address of the devtools server.
 * @param port number
 *        The port number of the devtools server.
 * @param authResult AuthenticationResult
 *        Authentication result sent from the server.
 * @param oob object (optional)
 *        The token data to be transferred during OOB_CERT step 8:
 *        * sha256: hash(ClientCert)
 *        * k     : K(random 128-bit number)
 * @return object containing:
 *         * close: Function to hide the notification
 */
Client.defaultSendOOB = ({ authResult, oob }) => {
  // Only show in the PENDING state
  if (authResult != AuthenticationResult.PENDING) {
    throw new Error("Expected PENDING result, got " + authResult);
  }
  const title = L10N.getStr("clientSendOOBTitle");
  const header = L10N.getStr("clientSendOOBHeader");
  const hashMsg = L10N.getFormatStr("clientSendOOBHash", oob.sha256);
  const token = oob.sha256.replace(/:/g, "").toLowerCase() + oob.k;
  const tokenMsg = L10N.getFormatStr("clientSendOOBToken", token);
  const msg = `${header}\n\n${hashMsg}\n${tokenMsg}`;
  const prompt = Services.prompt;
  const flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_CANCEL;

  // Listen for the window our prompt opens, so we can close it programatically
  let promptWindow;
  const windowListener = {
    onOpenWindow(xulWindow) {
      const win = xulWindow.docShell.domWindow;
      win.addEventListener(
        "load",
        function () {
          if (
            win.document.documentElement.getAttribute("id") != "commonDialog"
          ) {
            return;
          }
          // Found the window
          promptWindow = win;
          Services.wm.removeListener(windowListener);
        },
        { once: true }
      );
    },
    onCloseWindow() {},
  };
  Services.wm.addListener(windowListener);

  // nsIPrompt is typically a blocking API, so |executeSoon| to get around this
  DevToolsUtils.executeSoon(() => {
    prompt.confirmEx(null, title, msg, flags, null, null, null, null, {
      value: false,
    });
  });

  return {
    close() {
      if (!promptWindow) {
        return;
      }
      promptWindow.document.documentElement.acceptDialog();
      promptWindow = null;
    },
  };
};

/**
 * Prompt the user to accept or decline the incoming connection.  This is the
 * default implementation that products embedding the devtools server may
 * choose to override.  This can be overridden via |allowConnection| on the
 * socket's authenticator instance.
 *
 * @param session object
 *        The session object will contain at least the following fields:
 *        {
 *          authentication,
 *          client: {
 *            host,
 *            port
 *          },
 *          server: {
 *            host,
 *            port
 *          }
 *        }
 *        Specific authentication modes may include additional fields.  Check
 *        the different |allowConnection| methods in ./auth.js.
 * @return An AuthenticationResult value.
 *         A promise that will be resolved to the above is also allowed.
 */
Server.defaultAllowConnection = ({ client, server }) => {
  const title = L10N.getStr("remoteIncomingPromptTitle");
  const header = L10N.getStr("remoteIncomingPromptHeader");
  const clientEndpoint = `${client.host}:${client.port}`;
  const clientMsg = L10N.getFormatStr(
    "remoteIncomingPromptClientEndpoint",
    clientEndpoint
  );
  const serverEndpoint = `${server.host}:${server.port}`;
  const serverMsg = L10N.getFormatStr(
    "remoteIncomingPromptServerEndpoint",
    serverEndpoint
  );
  const footer = L10N.getStr("remoteIncomingPromptFooter");
  const msg = `${header}\n\n${clientMsg}\n${serverMsg}\n\n${footer}`;
  const disableButton = L10N.getStr("remoteIncomingPromptDisable");
  const prompt = Services.prompt;
  const flags =
    prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_OK +
    prompt.BUTTON_POS_1 * prompt.BUTTON_TITLE_CANCEL +
    prompt.BUTTON_POS_2 * prompt.BUTTON_TITLE_IS_STRING +
    prompt.BUTTON_POS_1_DEFAULT;
  const result = prompt.confirmEx(
    null,
    title,
    msg,
    flags,
    null,
    null,
    disableButton,
    null,
    { value: false }
  );
  if (result === 0) {
    return AuthenticationResult.ALLOW;
  }
  if (result === 2) {
    return AuthenticationResult.DISABLE_ALL;
  }
  return AuthenticationResult.DENY;
};

/**
 * During OOB_CERT authentication, the user must transfer some data through some
 * out of band mechanism from the client to the server to authenticate the
 * devices.
 *
 * This implementation prompts the user for a token as constructed by
 * |Client.defaultSendOOB| that the user needs to transfer manually.  For a
 * mobile device, you should override this implementation with something more
 * convenient, such as reading a QR code.
 *
 * @return An object containing:
 *         * sha256: hash(ClientCert)
 *         * k     : K(random 128-bit number)
 *         A promise that will be resolved to the above is also allowed.
 */
Server.defaultReceiveOOB = () => {
  const title = L10N.getStr("serverReceiveOOBTitle");
  const msg = L10N.getStr("serverReceiveOOBBody");
  let input = { value: null };
  const prompt = Services.prompt;
  const result = prompt.prompt(null, title, msg, input, null, { value: false });
  if (!result) {
    return null;
  }
  // Re-create original object from token
  input = input.value.trim();
  let sha256 = input.substring(0, 64);
  sha256 = sha256.replace(/\w{2}/g, "$&:").slice(0, -1).toUpperCase();
  const k = input.substring(64);
  return { sha256, k };
};