fix(auth)!: a session alone never gets a code checked
The third reading found a stolen session guessing TOTP codes without end: nine wrong codes for new recovery codes, then removing an unconfirmed authenticator of its own, a success that cleared the session's count. As Proton has it, every signed-in change of how an account is secured now takes the owner's proof (OwnerProof): the password's SRP proof for a change's challenge and a TOTP or recovery code. Adding an authenticator once one is confirmed, removing a confirmed one, new recovery codes, a new phrase, a new password and deleting take it; only the first authenticator is confirmed with its own code alone, and removing an unconfirmed one takes nothing. A proof given where none is taken is refused, and a confirmed authenticator's code is never checked for a session alone, so no success passes a proof unchecked. A session's proofs count for it alone, before they are checked, cleared only by one accepted; the tenth wrong in a row ends the session. They never count toward the account's waits nor wait for them, so whoever knows the password no longer holds a signed-in owner out of changing it (the second finding), and a new password forgets those waits. Beginning a change counts nothing against the account (the third finding): 30 an hour per session, holding back that session alone. Deleting takes a TOTP code wherever an authenticator is confirmed, a code by mail where no second factor works (the fourth finding), which SignInWays now tells (secondFactorWorks). Signed-in calls no longer take the address: nothing there mails. wrongExistingCode goes, a wrong existing code being a wrong proof. The app follows in the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
70b434bb84
commit
a9459bb875
1 changed files with 1461 additions and 1026 deletions
|
|
@ -533,16 +533,20 @@ void main() {
|
|||
authenticator: spare.authenticator,
|
||||
code: codeOf(spare),
|
||||
),
|
||||
refused(PasswordRefusal.wrongExistingCode),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
now = now.add(totpPeriod);
|
||||
Future<OwnerProof> ownerProof() async => device.ownerProof(
|
||||
await remote.beginPasswordChange(session, person: annaOnTodo),
|
||||
totpCode: codeOf(enrolment),
|
||||
);
|
||||
expect(
|
||||
await remote.confirmAuthenticator(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
authenticator: spare.authenticator,
|
||||
code: codeOf(spare),
|
||||
existingCode: codeOf(enrolment),
|
||||
proof: await ownerProof(),
|
||||
),
|
||||
isEmpty,
|
||||
);
|
||||
|
|
@ -551,7 +555,7 @@ void main() {
|
|||
session,
|
||||
person: annaOnTodo,
|
||||
authenticator: spare.authenticator,
|
||||
code: codeOf(enrolment),
|
||||
proof: await ownerProof(),
|
||||
);
|
||||
final ways = await remote.signInWaysOf(session, annaOnTodo);
|
||||
expect(ways.password && ways.authenticator, isTrue);
|
||||
|
|
@ -601,7 +605,7 @@ void main() {
|
|||
await remote.newRecoveryCodes(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
code: codeOf(enrolment, at: now),
|
||||
proof: await ownerProof(),
|
||||
),
|
||||
hasLength(recoveryCodeCount),
|
||||
);
|
||||
|
|
@ -749,19 +753,15 @@ void main() {
|
|||
session,
|
||||
person: annaOnTodo,
|
||||
);
|
||||
final answer = device.answer(challenge);
|
||||
await remote.changePassword(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
challenge: challenge.challenge,
|
||||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(enrolment),
|
||||
proof: device.ownerProof(
|
||||
challenge,
|
||||
totpCode: codeOf(enrolment),
|
||||
),
|
||||
password: device.newPassword(),
|
||||
keepSession: annasPhone,
|
||||
host: testHost,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
expect(members.sessionsTold.single, (
|
||||
_todo,
|
||||
|
|
@ -780,21 +780,17 @@ void main() {
|
|||
session,
|
||||
person: annaOnTodo,
|
||||
);
|
||||
final answer = device.answer(challenge);
|
||||
await remote.setRecoveryKeys(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
challenge: challenge.challenge,
|
||||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(enrolment),
|
||||
proof: device.ownerProof(
|
||||
challenge,
|
||||
totpCode: codeOf(enrolment),
|
||||
),
|
||||
recoveryKeys: fresh,
|
||||
recoveryProofKey: ByteData.sublistView(
|
||||
device.recoveryProofKey,
|
||||
),
|
||||
host: testHost,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue