summaryrefslogtreecommitdiffstats
path: root/devtools/shared/security/prompt.js
blob: aad9b72111d4e3f244b351663d8803b9c41b889a (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
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { Ci } = require("chrome");
var Services = require("Services");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
loader.lazyRequireGetter(this, "DebuggerSocket",
  "devtools/shared/security/socket", true);
loader.lazyRequireGetter(this, "AuthenticationResult",
  "devtools/shared/security/auth", true);

const {LocalizationHelper} = require("devtools/shared/l10n");
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 debugger server.
 * @param port number
 *        The port number of the debugger server.
 * @param cert object (optional)
 *        The server's cert details.
 * @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);
  }
  let title = L10N.getStr("clientSendOOBTitle");
  let header = L10N.getStr("clientSendOOBHeader");
  let hashMsg = L10N.getFormatStr("clientSendOOBHash", oob.sha256);
  let token = oob.sha256.replace(/:/g, "").toLowerCase() + oob.k;
  let tokenMsg = L10N.getFormatStr("clientSendOOBToken", token);
  let msg = `${header}\n\n${hashMsg}\n${tokenMsg}`;
  let prompt = Services.prompt;
  let flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_CANCEL;

  // Listen for the window our prompt opens, so we can close it programatically
  let promptWindow;
  let windowListener = {
    onOpenWindow(xulWindow) {
      let win = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIDOMWindow);
      win.addEventListener("load", function listener() {
        win.removeEventListener("load", listener, false);
        if (win.document.documentElement.getAttribute("id") != "commonDialog") {
          return;
        }
        // Found the window
        promptWindow = win;
        Services.wm.removeListener(windowListener);
      }, false);
    },
    onCloseWindow() {},
    onWindowTitleChange() {}
  };
  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 debugger 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 }) => {
  let title = L10N.getStr("remoteIncomingPromptTitle");
  let header = L10N.getStr("remoteIncomingPromptHeader");
  let clientEndpoint = `${client.host}:${client.port}`;
  let clientMsg = L10N.getFormatStr("remoteIncomingPromptClientEndpoint", clientEndpoint);
  let serverEndpoint = `${server.host}:${server.port}`;
  let serverMsg = L10N.getFormatStr("remoteIncomingPromptServerEndpoint", serverEndpoint);
  let footer = L10N.getStr("remoteIncomingPromptFooter");
  let msg = `${header}\n\n${clientMsg}\n${serverMsg}\n\n${footer}`;
  let disableButton = L10N.getStr("remoteIncomingPromptDisable");
  let prompt = Services.prompt;
  let 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;
  let 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 = () => {
  let title = L10N.getStr("serverReceiveOOBTitle");
  let msg = L10N.getStr("serverReceiveOOBBody");
  let input = { value: null };
  let prompt = Services.prompt;
  let 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();
  let k = input.substring(64);
  return { sha256, k };
};