keywrap

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: 6 Imported by: 0

Documentation

Overview

Package keywrap — обёртка секретного материала для хранения в базе: приватной половины подписного ключа и секретов второго фактора.

Чей это ключ — ПРЕДМЕТОВ ДВА, и у каждого СВОЙ перечень ключей

Обёртка одна, ручек две, и это не расхождение, а два разных предмета:

  • `authn.jwks-encryption-key-hex` — приватная половина ПОДПИСНОГО ключа службы (приёмка §2.5, задача kacho#897): ключей мало, срок жизни — срок подписного ключа, радиус компрометации — подпись службы;
  • `authn.second-factor-encryption-key-hex` — СЕКРЕТЫ ВТОРОГО ФАКТОРА (Ф12 Р2, kacho#1281): секретов много, по одному на человека, срок жизни — срок фактора, радиус компрометации — вход людей; смена одного перечня не обязана останавливать другой.

Второй ручки ОБ ОДНОМ предмете по-прежнему не заводится: одна из двух неизбежно оказалась бы необязательной, и профиль развёртывания, задавший «не ту», выглядел бы настроенным. Ручка же, чьё имя уже её содержимого, — утверждение о настройке, которое лжёт с первого дня; поэтому у второго предмета своя. Механика у обеих одна (перечень: первый оборачивает, все открывают), и самоотчёт старта печатает оба числа ключей.

Почему AES-256-GCM

Форма ручки уже объявлена ровно 32 байтами, то есть размером ключа симметричного шифра. Изобретать второй размер незачем. GCM выбран потому, что даёт подлинность вместе с сокрытием: подменённая в базе строка не разворачивается, а не разворачивается в мусор, который потом окажется «ключом».

Ключей может быть НЕСКОЛЬКО, и это единственный путь смены

Ключ обёртки — секрет, а у секрета обязан быть путь смены: утёк, скомпрометирован носитель, сменился владелец, истёк срок политики. Одно значение такого пути не даёт вовсе: новое значение не открывает ни одной уже записанной приватной половины, и вернуть их нечем.

Поэтому обёртка принимает ПЕРЕЧЕНЬ: ПЕРВЫЙ ключ оборачивает, ВСЕ открывают (задача kacho#1065). Смена — это постановка нового ключа первым при сохранённом прежнем: записанного никто не трогает, простоя нет, а хранилище переходит на новый ключ само, по мере того как ключница порождает ключи и снимает старые.

Чего перечень НЕ даёт: ВЫВОДА ключа. Прежний ключ открывает написанное им до тех пор, пока в наборе есть хоть одна строка, обёрнутая им, — и утёкший ключ снимается из перечня не раньше.

Вывод даёт ПЕРЕОБЁРТКА уже записанного (`Rewrap`, kaname#259 п.3): тот же материал под первым ключом. У двух предметов она исполняется по-разному. Секреты второго фактора переобёртывает команда оператора `kaname second-factor-key rewrap` — проходом по хранилищу, каждая строка своей заменой по прочитанному значению, поэтому срыв на середине оставляет каждую строку читаемой перечнем (`cmd/kaname/second_factor_key_command.go`). Приватные половины подписных ключей прохода не имеют: ключница порождает ключи по сроку и снимает выведенные, и хранилище переезжает на новый ключ обёртки само.

Что этот пакет НЕ делает

Он не журналирует, не оборачивает ошибки текстом, содержащим материал, и не печатает ни одного байта ни на каком пути (§6.10). Текст отказа называет операцию, а не значение.

Index

Constants

View Source
const KeySize = 32

KeySize — размер ключа обёртки в байтах. Объявлен ЧИСЛОМ ровно здесь; страж старта конфигурации сверяется с этим числом, а не со своей копией.

Variables

View Source
var ErrNotWrapped = errors.New("keywrap: value is not a wrapped key")

ErrNotWrapped — предъявленное значение обёрткой не является (слишком коротко, чтобы нести хотя бы вектор инициализации).

View Source
var ErrUnwrap = errors.New("keywrap: unwrap failed")

ErrUnwrap — обёртка не снимается: ключ не тот, значение повреждено или подменено. Причина НЕ различается намеренно — различение подсказывало бы предъявителю, какая половина неверна.

Functions

This section is empty.

Types

type Wrapper

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

Wrapper оборачивает и разворачивает приватный материал.

Ключей может быть несколько: первый делает КАЖДУЮ новую обёртку, все годятся для снятия. Разделение полей выражает это по построению — «обернуть чем-то из перечня» здесь не выражается, а не запрещено правилом.

func New

func New(keys ...[]byte) (*Wrapper, error)

New строит обёртку на ПЕРЕЧНЕ ключей объявленного размера: первый оборачивает, все открывают.

Перечень из одного ключа — законный и самый частый случай; профиль развёртывания, назвавший одно значение, не меняется оттого, что смена ключа стала возможной.

Пустой перечень — ОТКАЗ. Обёртка без ключей не открыла бы ничего, а построенная выглядела бы собранной: «не сужаем» здесь читалось бы как «запрещаем».

Ключ негодного размера — ОТКАЗ на ЛЮБОЙ позиции, а не усечение и не растяжение: «привели к нужной длине» означает, что ошибка настройки становится рабочим режимом. Отказ называет ПОЗИЦИЮ — со списком из трёх значений оператор иначе не знает, какое чинить, — и никогда значение.

func (*Wrapper) KeyCount

func (w *Wrapper) KeyCount() int

KeyCount — сколько ключей названо.

Печатается при старте: перечень растёт с каждой сменой и не убывает сам, а «названо шесть ключей» иначе невидимо ниоткуда — то есть работу по выводу прежних ключей некому начать.

func (*Wrapper) Rewrap added in v0.5.0

func (w *Wrapper) Rewrap(wrapped []byte) ([]byte, bool, error)

Rewrap переносит значение, обёрнутое ЛЮБЫМ ключом перечня, под ПЕРВЫЙ ключ (kaname#259 п.3): это и есть путь ВЫВОДА прежнего ключа, которого перечень сам по себе не даёт.

Исходов три, и различаются они типом, а не значением:

  • (nil, true) — значение уже обёрнуто первым ключом: новой обёртки не порождается, писать нечего, и повтор прохода ничего не меняет;
  • (новая обёртка, false) — тот же материал под первым ключом;
  • отказ — значение не открывает ни один ключ перечня (ErrUnwrap) либо оно не обёртка вовсе (ErrNotWrapped). «Переобернуть» нечитаемое нечем, и новое значение из него было бы ложью.

Первым пробуется ПЕРВЫЙ ключ: у прохода, повторённого после переезда, каждое значение уже под ним, и проверка оканчивается одним снятием. Материал в памяти процесса после обёртки затирается: копия секрета не переживает вызова дольше, чем нужна.

func (*Wrapper) Unwrap

func (w *Wrapper) Unwrap(wrapped []byte) ([]byte, error)

Unwrap снимает обёртку ЛЮБЫМ из названных ключей, в порядке объявления.

Перебор не ослабляет проверку подлинности: каждый ключ проверяет метку целостности сам, поэтому подменённое значение не открывается НИ ОДНИМ — и перечень из десяти отвергает ровно то же, что перечень из одного.

Порядок выбран не для красоты: первым стоит текущий ключ, а самый частый читатель — подписывающий ключ, только что записанный им же. Перебор поэтому оканчивается на первом ключе на горячем пути, а длина перечня оплачивается лишь на строках, доставшихся от прежних ключей.

Текст отказа не несёт ни байта материала и не называет, чем именно значение негодно и какой ключ его не открыл: у сообщения об ошибке нет адресата, которому эта разница помогала бы законно.

func (*Wrapper) Wrap

func (w *Wrapper) Wrap(plain []byte) ([]byte, error)

Wrap оборачивает приватный материал. Вектор инициализации свой у каждого вызова и хранится префиксом — двух одинаковых обёрток одного ключа не бывает.

Jump to

Keyboard shortcuts

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