Skip to content

Add context screen & integrated TOTP support #229

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 49 commits into from
Sep 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
cb43222
Swap login & password (closes #214)
erayd Jun 28, 2020
ea461a0
Add UI & hotkey triggers for 'details' action
erayd Jun 28, 2020
97e48a6
Unmount mithril context before displaying error message
erayd Jun 28, 2020
28622e4
Just use mithril for error messages
erayd Jun 28, 2020
307c8d3
Remove unnecessary junk from svg
erayd Jun 30, 2020
786ae6e
OTP processing
erayd Jun 30, 2020
a58ec69
Details interface
erayd Jun 30, 2020
b086961
Theming for progress bar colour
erayd Jun 30, 2020
93d37c8
Implement OTP options
erayd Jun 30, 2020
fa94cad
Remove copyOTP setting
erayd Jun 30, 2020
be969fa
Field background colours
erayd Jun 30, 2020
fdd35db
Increase margins
erayd Jun 30, 2020
2c7ecab
Disable snack borders
erayd Jun 30, 2020
55a3113
Re-enable borders
erayd Jun 30, 2020
9d77c41
Fix progress bar
erayd Jun 30, 2020
ade1c2b
Fix search markup in entry title
erayd Jun 30, 2020
3d79836
Don't change inline colours on hover
erayd Jun 30, 2020
3ffe447
Remove debug logging
erayd Jun 30, 2020
fc59607
Increase spacing, make border less visible, change progressbar color
maximbaz Jun 30, 2020
fe356a7
Fix label border
erayd Jun 30, 2020
7d63219
Fix for bottom spacing when token is visible
erayd Jun 30, 2020
4689165
Remove debug logging
erayd Jun 30, 2020
97b1ed5
We don't support HOTP
erayd Jun 30, 2020
618db05
Fix otpauth:// URI processing
erayd Jun 30, 2020
df961c2
Progress bar theming
erayd Jun 30, 2020
8bc7f38
Fix background
erayd Jun 30, 2020
19375d9
Set cursor
erayd Jun 30, 2020
685b12b
Remove outline
erayd Jun 30, 2020
61a8458
Fix border-radius on labels
erayd Jun 30, 2020
3dfdbc2
Don't use highlighted path
erayd Jun 30, 2020
610cdb9
Add Source Code Pro (font)
erayd Jun 30, 2020
66e5c61
Add update notice about OTP changes
erayd Jun 30, 2020
c580e93
Use login section
erayd Jun 30, 2020
f86061a
Add tooltip
erayd Jun 30, 2020
8a0153f
Add section border
erayd Jun 30, 2020
7e1ead5
Fix whitespace in secrets
erayd Jun 30, 2020
f69528f
Remove click handler
erayd Jun 30, 2020
2908bc4
Adjust margins
erayd Sep 1, 2020
080254e
Prettier changes
erayd Sep 1, 2020
8c38f22
Remove notification again
erayd Sep 1, 2020
3a33d4a
Update README.md
erayd Sep 1, 2020
2febfa8
Remove debugging border
erayd Sep 1, 2020
ca4b802
Remove details header border
maximbaz Sep 8, 2020
39f15f0
s/details/getDetails/g
erayd Sep 9, 2020
298f742
Remove extra space
erayd Sep 9, 2020
99282c1
Remove trailing period
erayd Sep 9, 2020
93a1147
Add missing type check
erayd Sep 9, 2020
98f13de
Move default type correction into ternary
erayd Sep 9, 2020
d0aace1
Use default cursor for snack labels
erayd Sep 9, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 23 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ In order to use Browserpass you must also install a [companion native messaging
- [Password store locations](#password-store-locations)
- [Options](#options)
- - [A note about autosubmit](#a-note-about-autosubmit)
- - [A note about OTP](#a-note-about-otp)
- [Usage data](#usage-data)
- [Security](#security)
- [Privacy](#privacy)
Expand All @@ -33,7 +34,6 @@ In order to use Browserpass you must also install a [companion native messaging
- [Error: Unable to fetch and parse login fields](#error-unable-to-fetch-and-parse-login-fields)
- [How to use the same username and password pair on multiple domains](#how-to-use-the-same-username-and-password-pair-on-multiple-domains)
- [Why Browserpass on Firefox does not work on Mozilla domains?](#why-browserpass-on-firefox-does-not-work-on-mozilla-domains)
- [Why is OTP not supported?](#why-is-otp-not-supported)
- [Building the extension](#building-the-extension)
- [Build locally](#build-locally)
- [Load an unpacked extension](#load-an-unpacked-extension)
Expand Down Expand Up @@ -217,29 +217,32 @@ Using the `Custom store locations` setting in the browser extension options, you

The list of available options:

| Name | Description |
| --------------------------------------------------------------- | ------------------------------------------------------------ |
| Automatically submit forms after filling (aka `autoSubmit`) | Make Browserpass automatically submit the login form for you |
| Default username (aka `username`) | Username to use when it's not defined in the password file |
| Custom gpg binary (aka `gpgPath`) | Path to a custom `gpg` binary to use |
| Custom store locations | List of password stores to use |
| Custom store locations - badge background color (aka `bgColor`) | Badge background color for a given password store in popup |
| Custom store locations - badge text color (aka `color`) | Badge text color for a given password store in popup |
| Ignore items (aka `ignore`) | Ignore all matching logins |
| Name | Description |
| --------------------------------------------------------------- | ------------------------------------------------------------- |
| Automatically submit forms after filling (aka `autoSubmit`) | Make Browserpass automatically submit the login form for you |
| Enable support for OTP tokens (aka `enableOTP`) | Generate TOTP codes if a TOTP seed is found in the pass entry |
| Default username (aka `username`) | Username to use when it's not defined in the password file |
| Custom gpg binary (aka `gpgPath`) | Path to a custom `gpg` binary to use |
| Custom store locations | List of password stores to use |
| Custom store locations - badge background color (aka `bgColor`) | Badge background color for a given password store in popup |
| Custom store locations - badge text color (aka `color`) | Badge text color for a given password store in popup |
| Ignore items (aka `ignore`) | Ignore all matching logins |

Browserpass allows configuring certain settings in different places places using the following priority, highest first:

1. Options defined in specific `*.gpg` files, only apply to these password entries:
- `autoSubmit`
1. Options defined in `.browserpass.json` file located in the root of a password store:
- `autoSubmit`
- `enableOTP`
- `gpgPath`
- `username`
- `bgColor`
- `color`
- `ignore`
1. Options defined in browser extension options:
- Automatically submit forms after filling (aka `autoSubmit`)
- Enable support for OTP tokens (aka `enableOTP`)
- Default username (aka `username`)
- Custom gpg binary (aka `gpgPath`)
- Custom store locations
Expand All @@ -252,6 +255,16 @@ While we provide autosubmit as an option for users, we do not recommend it. This

As the demand for autosubmit is extremely high, we have decided to provide it anyway - however it is disabled by default, and we recommend that users do not enable it.

### A note about OTP

Tools like `pass-otp` make it possible to use `pass` for generating OTP codes, however keeping both passwords and OTP URI in the same location diminishes the major benefit that OTP is supposed to provide: two factor authentication. The purpose of multi-factor authentication is to protect your account even when attackers gain access to your password store, but if your OTP seed is stored in the same place, all auth factors will be compromised at once. In particular, Browserpass has access to the entire contents of your password entries, so if it is ever compromised, all your accounts will be at risk, even though you signed up for 2FA.

Browserpass is opinionated, it does not promote `pass-otp` and by default does not generate OTP codes from OTP seeds in password entries, even though there are other password managers that provide such functionality out of the box.

There are valid scenarios for using `pass-otp` (e.g. it gives protection against intercepting your password during transmission), but users are strongly advised to very carefully consider whether `pass-otp` is really an appropriate solution - and if so, come up with their own ways of accessing OTP codes that conforms to their security requirements. For the majority of people `pass-otp` is not recommended; using any phone app like Authy will be a much better and more secure alternative, because this way attackers would have to not only break into your password store, but they would _also_ have to break into your phone.

If you still want the OTP support regardless, you may enable it in the Browserpass settings.

## Usage data

Browserpass keeps metadata of recently used credentials in local storage and Indexed DB of the background page. This is first and foremost internal data to make Browserpass function properly, used for example to implement the [Password matching and sorting](#password-matching-and-sorting) algorithm, but nevertheless you might find it useful to explore using your browser's devtools. For example, if you are considering to rotate all passwords that you used in the past month (e.g. if you just found out that you had a malicious app installed for several weeks), you can retrieve such list from Indexed DB quite easily (open an issue if you need help).
Expand Down Expand Up @@ -360,16 +373,6 @@ The full list of blocked domains at the time of writing is:
- sync.services.mozilla.com
- testpilot.firefox.com

### Why is OTP not supported?

Tools like `pass-otp` make it possible to use `pass` for generating OTP codes, however keeping both passwords and OTP URI in the same location diminishes the major benefit that OTP is supposed to provide: two factor authentication. The purpose of multi-factor authentication is to protect your account even when attackers gain access to your password store, but if your OTP seed is stored in the same place, all auth factors will be compromised at once. In particular, Browserpass has access to the entire contents of your password entries, so if it is ever compromised, all your accounts will be at risk, even though you signed up for 2FA.

Browserpass is opinionated, it does not promote `pass-otp` and intentionally does not support generating OTP codes from OTP URIs in password entiries, even though there are other password managers that provide such functionality.

There are valid scenarios for using `pass-otp` (e.g. it gives protection against intercepting your password during transmission), but users are strongly advised to very carefully consider whether `pass-otp` is really an appropriate solution - and if so, come up with their own ways of accessing OTP codes that conforms to their security requirements (for example by using dmenu/rofi scripts). For the majority of people `pass-otp` is not recommended; using any phone app like Authy will be a much better and more secure alternative, because this way attackers would have to not only break into your password store, but they would _also_ have to break into your phone.

If you still want the OTP support, it is provided via a separate extension [browserpass-otp](https://github.com/browserpass/browserpass-otp). That extension integrates with Browserpass to ensure a streamlined workflow, for example if the OTP extension is installed, it will be automatically triggered when Browserpass fills an entry and an OTP token is present.

## Building the extension

### Build locally
Expand Down
127 changes: 70 additions & 57 deletions src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ const helpers = require("./helpers");
// native application id
var appID = "com.github.browserpass.native";

// OTP extension id
var otpID = [
"afjjoildnccgmjbblnklbohcbjehjaph", // webstore releases
"jbnpmhhgnchcoljeobafpinmchnpdpin", // github releases
"fcmmcnalhjjejhpnlfnddimcdlmpkbdf", // local unpacked
"[email protected]", // firefox
];

// default settings
var defaultSettings = {
autoSubmit: false,
Expand All @@ -26,6 +18,7 @@ var defaultSettings = {
foreignFills: {},
username: null,
theme: "dark",
enableOTP: false,
};

var authListeners = {};
Expand Down Expand Up @@ -562,7 +555,6 @@ async function getFullSettings() {
try {
settings.tab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0];
let originInfo = new BrowserpassURL(settings.tab.url);
settings.host = originInfo.host; // TODO remove this after OTP extension is migrated
settings.origin = originInfo.origin;
} catch (e) {}

Expand Down Expand Up @@ -750,6 +742,28 @@ async function handleMessage(settings, message, sendResponse) {
});
}
break;
case "copyOTP":
if (settings.enableOTP) {
try {
if (!message.login.fields.otp) {
throw new Exception("No OTP seed available");
}
copyToClipboard(helpers.makeTOTP(message.login.fields.otp.params));
sendResponse({ status: "ok" });
} catch (e) {
sendResponse({
status: "error",
message: "Unable to copy OTP token",
});
}
} else {
sendResponse({ status: "error", message: "OTP support is disabled" });
}
break;

case "getDetails":
sendResponse({ status: "ok", login: message.login });
break;

case "launch":
case "launchInNewTab":
Expand Down Expand Up @@ -831,9 +845,13 @@ async function handleMessage(settings, message, sendResponse) {
break;
}

// trigger browserpass-otp
if (typeof message.login !== "undefined" && message.login.fields.hasOwnProperty("otp")) {
triggerOTPExtension(settings, message.action, message.login.fields.otp);
// copy OTP token after fill
if (
settings.enableOTP &&
typeof message.login !== "undefined" &&
message.login.fields.hasOwnProperty("otp")
) {
copyToClipboard(helpers.makeTOTP(message.login.fields.otp.params));
}
}

Expand Down Expand Up @@ -885,18 +903,17 @@ async function parseFields(settings, login) {
secret: ["secret", "password", "pass"],
login: ["login", "username", "user"],
openid: ["openid"],
otp: ["otp", "totp", "hotp"],
otp: ["otp", "totp"],
url: ["url", "uri", "website", "site", "link", "launch"],
};
login.settings = {
autoSubmit: { name: "autosubmit", type: "bool" },
};
var lines = login.raw.split(/[\r\n]+/).filter((line) => line.trim().length > 0);
lines.forEach(function (line) {
// check for uri-encoded otp
if (line.match(/^otpauth:\/\/.+/)) {
login.fields.otp = { key: null, data: line };
return;
// check for uri-encoded otp without line prefix
if (line.match(/^otpauth:\/\/.+/i)) {
line = `otp: ${line}`;
}

// split key / value & ignore non-k/v lines
Expand All @@ -918,11 +935,7 @@ async function parseFields(settings, login) {
Array.isArray(login.fields[key]) &&
login.fields[key].includes(parts[0].toLowerCase())
) {
if (key === "otp") {
login.fields[key] = { key: parts[0].toLowerCase(), data: parts[1] };
} else {
login.fields[key] = parts[1];
}
login.fields[key] = parts[1];
break;
}
}
Expand Down Expand Up @@ -962,6 +975,41 @@ async function parseFields(settings, login) {
delete login.settings[key];
}
}

// preprocess otp
if (settings.enableOTP && login.fields.hasOwnProperty("otp")) {
if (login.fields.otp.match(/^otpauth:\/\/.+/i)) {
// attempt to parse otp data as URI
try {
let url = new URL(login.fields.otp.toLowerCase());
let otpParts = url.pathname.split("/").filter((s) => s.trim());
login.fields.otp = {
raw: login.fields.otp,
params: {
type: otpParts[0] === "otp" ? "totp" : otpParts[0],
secret: url.searchParams.get("secret").toUpperCase(),
algorithm: url.searchParams.get("algorithm") || "sha1",
digits: parseInt(url.searchParams.get("digits") || "6"),
period: parseInt(url.searchParams.get("period") || "30"),
},
};
} catch (e) {
throw new Exception(`Unable to parse URI: ${otp.data}`, e);
}
} else {
// use default params for secret-only otp data
login.fields.otp = {
raw: login.fields.otp,
params: {
type: "totp",
secret: login.fields.otp.toUpperCase(),
algorithm: "sha1",
digits: 6,
period: 30,
},
};
}
}
}

/**
Expand Down Expand Up @@ -1046,41 +1094,6 @@ async function saveSettings(settings) {
}
}

/**
* Trigger OTP extension (browserpass-otp)
*
* @since 3.0.13
*
* @param object settings Settings object
* @param string action Browserpass action
* @param object otp OTP field data
* @return void
*/
function triggerOTPExtension(settings, action, otp) {
// trigger otp extension
for (let targetID of otpID) {
chrome.runtime
.sendMessage(targetID, {
version: chrome.runtime.getManifest().version,
action: action,
otp: otp,
settings: {
host: settings.host,
origin: settings.origin,
tab: settings.tab,
},
})
// Both response & error are noop functions, because we don't care about
// the response, and if there's an error it just means the otp extension
// is probably not installed. We can't detect that without requesting the
// management permission, so this is an acceptable workaround.
.then(
(noop) => null,
(noop) => null
);
}
}

/**
* Handle browser extension installation and updates
*
Expand Down
Loading