Skip to content

WIP #668

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

WIP #668

Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 12 additions & 4 deletions src/core/account/account-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,22 +261,30 @@ export function makeAccountApi(ai: ApiInput, accountId: string): EdgeAccount {
const duressAppId = activeAppId.endsWith('.duress')
? activeAppId
: activeAppId + '.duress'
// Ensure the duress account exists:
if (forDuressAccount) {
// Fakes for duress mode:
if (this.isDuressAccount) {
// Fake duress mode setup if this is a duress account:
if (this.isDuressAccount) {
if (forDuressAccount) {
fakeDuressModeSetup = opts.enableLogin ?? opts.pin != null
ai.props.dispatch({ type: 'UPDATE_NEXT' })
return ''
}
// Fake disable PIN login:
// if (opts.enableLogin === false) {
// // await fakeDisablePin(ai, accountId, opts)
// return ''
// }
}
// Ensure the duress account exists:
if (forDuressAccount) {
await ensureAccountExists(
ai,
accountState().stashTree,
accountState().sessionKey,
duressAppId
)
}
await changePin(ai, accountId, { ...opts, forDuressAccount })
await changePin(ai, accountId, opts)
const login = forDuressAccount
? searchTree(accountState().login, stash => stash.appId === duressAppId)
: accountState().login
Expand Down
18 changes: 13 additions & 5 deletions src/core/context/context-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,9 @@ export function makeContextApi(ai: ApiInput): EdgeContext {

return await makeAccount(ai, sessionKey, 'keyLogin', {
...opts,
// We must require that the duress account exists:
duressMode: inDuressMode && duressStash != null
// We must require that the duress account is active.
// Duress account is active if it exists and has a PIN key:
duressMode: inDuressMode && duressStash?.pin2Key != null
})
},

Expand Down Expand Up @@ -223,11 +224,12 @@ export function makeContextApi(ai: ApiInput): EdgeContext {
stash,
stash => stash.appId === duressAppId
)
// We may still be in duress mode but not log in into a duress account
// We may still be in duress mode but do not log-in to a duress account
// if it does not exist. It's important that we do not disable duress
// mode from this routine to make sure other accounts with duress mode
// still are protected.
if (duressStash != null) {
// Duress account is active if it exists and has a PIN key:
if (duressStash?.pin2Key != null) {
const duressSessionKey = decryptChildKey(
stash,
sessionKey,
Expand Down Expand Up @@ -283,6 +285,12 @@ export function makeContextApi(ai: ApiInput): EdgeContext {
stashTree: LoginStash,
duressStash: LoginStash
): Promise<EdgeAccount> {
if (duressStash.fakePinDisabled === true) {
throw new PinDisabledError(
'PIN login is not enabled for this account on this device'
)
}

// Try login with duress account
const sessionKey = await loginPin2(
ai,
Expand Down Expand Up @@ -310,7 +318,7 @@ export function makeContextApi(ai: ApiInput): EdgeContext {
}

// No duress account configured, so just login to the main account:
if (duressStash == null) {
if (duressStash?.pin2Key == null) {
// It's important that we don't disable duress mode here because
// we want to protect account that have duress mode enabled and only
// allow those accounts to suspend duress mode.
Expand Down
23 changes: 12 additions & 11 deletions src/core/login/login-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,18 @@ export const login = buildReducer<LoginState, RootAction, RootState>({
stash != null &&
(stash.passwordAuthBox != null || stash.loginAuthBox != null)

// This allows us to lie about PIN being enabled or disabled while in
// duress mode!
const pin2Stash = clientInfo.duressEnabled
? // Use the duress stash's PIN settings, but fallback for accounts
// that don't have duress mode setup
findDuressStash(stashTree, appId) ?? findPin2Stash(stashTree, appId)
: // If we're not in duress mode, then do a normal search
findPin2Stash(stashTree, appId)
// If we have found a pin2Stash, or the duress stash we found has
// a pin2Key defined:
const pinLoginEnabled = pin2Stash?.pin2Key != null
// Only look at the duress stash if we're in duress mode:
const duressStash = clientInfo.duressEnabled
? findDuressStash(stashTree, appId)
: undefined
// Only fake pin disabled if the duress stash is present and has a
// pin2Key:
const fakePinDisabled =
duressStash?.pin2Key != null && duressStash?.fakePinDisabled === true
const pin2Stash = findPin2Stash(stashTree, appId)
// Disable PIN login if we're faking it from the duress stash, or we
// don't have a pin2Key on the account's pin2Stash:
const pinLoginEnabled = !fakePinDisabled && pin2Stash?.pin2Key != null

return {
keyLoginEnabled,
Expand Down
1 change: 1 addition & 0 deletions src/core/login/login-stash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export interface LoginStash {
// PIN v2 login:
pin2Key?: Uint8Array
pin2TextBox?: EdgeBox
fakePinDisabled?: boolean

// Recovery v2 login:
recovery2Key?: Uint8Array
Expand Down
12 changes: 8 additions & 4 deletions src/core/login/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,10 +415,14 @@ export async function applyKit(
const { serverMethod = 'POST', serverPath } = kit

const { stashTree } = getStashById(ai, kit.loginId)
const childKey = decryptChildKey(stashTree, sessionKey, kit.loginId)
const request = makeAuthJson(stashTree, childKey)
request.data = kit.server
await loginFetch(ai, serverMethod, serverPath, request)

// Don't make server-side changes if the server path is faked:
if (serverPath !== '') {
const childKey = decryptChildKey(stashTree, sessionKey, kit.loginId)
const request = makeAuthJson(stashTree, childKey)
request.data = kit.server
await loginFetch(ai, serverMethod, serverPath, request)
}

const newStashTree = updateTree<LoginStash, LoginStash>(
stashTree,
Expand Down
76 changes: 59 additions & 17 deletions src/core/login/pin2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,23 +96,17 @@ export async function changePin(
// Deleting PIN logins while in duress account should delete PIN locally for
// all nodes:
if (isDuressAccount && !forDuressAccount) {
if (enableLogin) {
if (pin != null) {
await applyKits(
ai,
sessionKey,
makeChangePin2Kits(ai, loginTree, username, pin, enableLogin, true)
)
}
} else {
if (pin != null) {
// Change and disable PIN for duress app for real:
await applyKits(
ai,
sessionKey,
makeChangePin2Kits(ai, loginTree, username, pin, enableLogin, true)
)
}
await applyKits(
ai,
sessionKey,
makeFakeDisablePinKits(ai, loginTree, username, enableLogin, true)
)
if (pin != null) {
await applyKits(
ai,
sessionKey,
makeChangePin2Kits(ai, loginTree, username, pin, true, true)
)
}
return
}
Expand Down Expand Up @@ -239,6 +233,35 @@ export function makeChangePin2Kits(
return out
}

export function makeFakeDisablePinKits(
ai: ApiInput,
loginTree: LoginTree,
username: string | undefined,
enableLogin: boolean,
forDuressAccount: boolean
): LoginKit[] {
const out: LoginKit[] = []

// Only include pin change if the app id matches the duress account flag:
if (forDuressAccount === loginTree.appId.endsWith('.duress')) {
out.push(makeFakeDisablePinKit(loginTree, enableLogin))
}

for (const child of loginTree.children) {
out.push(
...makeFakeDisablePinKits(
ai,
child,
username,
enableLogin,
forDuressAccount
)
)
}

return out
}

/**
* Used when changing the username.
* This won't return anything if the PIN is missing.
Expand Down Expand Up @@ -312,6 +335,25 @@ export function makeChangePin2Kit(
}
}

/**
* Creates the data needed to attach a PIN to a login.
*/
export function makeFakeDisablePinKit(
login: LoginTree,
enableLogin: boolean
): LoginKit {
const { loginId } = login

return {
loginId,
server: undefined,
serverPath: '',
stash: {
fakePinDisabled: !enableLogin
}
}
}

/**
* Creates the data needed to delete a PIN from a tree of logins.
* @param loginTree - The login tree to create the kits for.
Expand Down
4 changes: 2 additions & 2 deletions test/core/account/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ describe('account', function () {
).deep.include.members([{ username: 'js test 0', pinLoginEnabled: false }])
})

it('disable pin while in duress account is not temporary', async function () {
it('disable pin while in duress account is temporary', async function () {
const world = await makeFakeEdgeWorld([fakeUser], quiet)
const context = await world.makeEdgeContext(contextOptions)

Expand Down Expand Up @@ -400,7 +400,7 @@ describe('account', function () {
username
}))
).deep.include.members([
{ username: fakeUser.username.toLowerCase(), pinLoginEnabled: false }
{ username: fakeUser.username.toLowerCase(), pinLoginEnabled: true }
])

await topicAccount.changePin({ enableLogin: true })
Expand Down
Loading