secondfactorwrap

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: AGPL-3.0 Imports: 7 Imported by: 0

Documentation

Overview

Package secondfactorwrap — переобёртка секретов второго фактора под ПЕРВЫЙ ключ перечня `authn.second-factor-encryption-key-hex` (kaname#259 п.3).

Зачем

Перечень ключей обёртки даёт ЗАМЕЩЕНИЕ ключа — новый ставится первым, все открывают, — но не ВЫВОД: прежний ключ открывает каждый секрет, записанный им, и снять его из перечня значит отдать недоступность (503) каждому, чей секрет им обёрнут. Проход переносит каждый такой секрет под первый ключ; после него прежний ключ не читает ни одной строки и снимается.

Как проход держит свои свойства

  • БЕЗ ПОТЕРИ: секрет не меняется, меняется только обёртка — приложение человека даёт те же коды, принятый шаг строки остаётся в силе;
  • КАЖДАЯ ЗАМЕНА — CAS ПО ПРОЧИТАННОМУ: материал строки сменяется, только пока он побайтово тот, что проход прочитал (ban #10). Служба, заведшая фактор заново или снявшая его, пока проход держал прежнее значение, выигрывает: переобёрнутый ПРЕЖНИЙ секрет поверх нового не ложится, снятая строка не воскресает. Промах замены — перечитывание строки;
  • СРЫВ НА СЕРЕДИНЕ БЕЗОПАСЕН: у каждой строки своя замена, зафиксированная отдельно, поэтому в любой момент каждая строка — либо под первым ключом, либо под прежним, который ещё назван в перечне. Повтор довершает;
  • ПОВТОР ИДЕМПОТЕНТЕН: строка, уже обёрнутая первым ключом, не пишется.

Исходы — по типу, а не по значению

`Run` возвращает счёт (`Report`) и отказ. Отказ — «не смог спросить»: хранилище не ответило, сделанное до него названо счётом и зафиксировано. Счёт без отказа различает «каждый секрет под первым ключом» (`Settled`) и «проход прошёл, но не всё переехало»: секрет, которого не открывает ни один ключ перечня, и строка, не устоявшаяся за бюджет замен, — остаток, и прежние ключи при нём не снимаются.

Index

Constants

View Source
const SwapAttempts = 3

SwapAttempts — бюджет замен одной строки.

Выведен из аргумента о прогрессе, а не выбран: промах замены доказывает, что конкурент ЗАФИКСИРОВАЛ новое значение строки (замена ждёт его замка и судит условие по зафиксированному). Значение, положенное службой с тем же первым ключом, при перечитывании уже под ним — строка устаивается второй попыткой. Третья покрывает реплику, у которой первым ещё стоял прежний ключ: её значение переобёртывается ещё раз. Строка, которую меняют перед КАЖДОЙ заменой, дольше не держит проход: она — остаток «не устоялось», и повтор команды пройдёт её снова.

Variables

This section is empty.

Functions

This section is empty.

Types

type Report

type Report struct {
	// Rows — строк `totp`, увиденных проходом.
	Rows int
	// Rewrapped — переобёрнуто этим проходом.
	Rewrapped int
	// Current — уже были под первым ключом: писать было нечего.
	Current int
	// Unreadable — не открываются ни одним ключом перечня: не тронуты.
	Unreadable int
	// Vanished — строку сняли, пока проход держал её значение.
	Vanished int
	// Unsettled — строку меняли перед каждой заменой дольше бюджета.
	Unsettled int
}

Report — счёт прохода. Каждая строка, увиденная проходом, попадает ровно в одну клетку: Rows = Rewrapped + Current + Unreadable + Vanished + Unsettled.

func (Report) Settled

func (r Report) Settled() bool

Settled — каждая увиденная строка, которая существует, под первым ключом. Только при этом исходе прежние ключи можно снимать из перечня.

type Rewrapper

type Rewrapper interface {
	Rewrap(stored domain.LoginVerifier) (domain.LoginVerifier, bool, error)
}

Rewrapper — переобёртка хранимого секрета под первый ключ перечня (порт по существующему типу `totpverify.Verifier`): (stored, true) — уже под ним; (новое, false) — переобёрнут; `totpverify.ErrSecretUnreadable` — не открывается перечнем.

type Store

type Store interface {
	// TOTPSecretsAfter — строки вида `totp` ОБОИХ состояний (подтверждённые и
	// заведения — секрет под ключом обёртки есть у тех и у других) с
	// человеком строго после after, по возрастанию человека, не больше limit.
	TOTPSecretsAfter(ctx context.Context, after domain.UserID, limit int) ([]domain.LoginMethod, error)
	// SwapTOTPSecret — замена материала строки `totp` человека на next ТОЛЬКО
	// если он побайтово равен prev; меняется один материал. false — материал
	// уже другой либо строки нет.
	SwapTOTPSecret(ctx context.Context, user domain.UserID, prev, next domain.LoginVerifier) (bool, error)
	// Get — строка человека данного вида; NOT_FOUND — строки нет.
	Get(ctx context.Context, user domain.UserID, kind domain.LoginMethodKind) (domain.LoginMethod, error)
}

Store — хранилище строк второго фактора, над которым идёт проход. Исполняет его адаптер способа входа (`internal/repo/kaname/pg`), единственный файл, которому дано назвать таблицу секрета.

type UseCase

type UseCase struct {
	// contains filtered or unexported fields
}

UseCase — проход переобёртки.

func New

func New(store Store, rewrapper Rewrapper, page int, callTimeout time.Duration) (*UseCase, error)

New собирает проход. page — строк за одно чтение; callTimeout — предел КАЖДОГО обращения к хранилищу: вызывающий (команда оператора) срока на весь проход не ставит — работа пропорциональна числу людей, — поэтому зависшая связь ограничивается здесь, на каждом вызове.

func (*UseCase) Run

func (u *UseCase) Run(ctx context.Context) (Report, error)

Run проходит все строки `totp` страницами по возрастанию человека.

Отказ хранилища останавливает проход и возвращается вместе со счётом сделанного: каждая замена до него зафиксирована, и повтор продолжит. Строки, заведённые после того, как курсор их миновал, пишет служба своим первым ключом; проход их не видит, и это не остаток — отсюда совет повторного прогона у команды.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL