Skip to content

Instantly share code, notes, and snippets.

@ai
Last active June 27, 2020 17:03
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ai/11d2dcc35f415121a5c0980bb5dbe4dd to your computer and use it in GitHub Desktop.
Save ai/11d2dcc35f415121a5c0980bb5dbe4dd to your computer and use it in GitHub Desktop.

Идея механизма авторизации с нулевым знанием

Современный механизм авторизации через почту/пароль поощряет пользователя на опасные привычки и в безопасной версии (с 2FA) сложен в использовании.

ИТ-миру нужно больше обсуждения механизмов авторизации, чтобы прийти к более удобный и безопасным вариантам.

Это предложение содержит необычный формат авторизации для узкого круга задач. Какие ошибки в нём могуть быть в плане безопасности и удобства использования?

Задача

RSS-читалка, которая работает полностью локально (local first) использую облако только для синхронизации данных между клиентами:

  1. Будет использоваться ежедневно, часто и с ноутбука, и с телефона.
  2. Данные (список подписок) потерять не хочется, но они не критичные.
  3. Будет использоваться пользователями с разным техническим уровнем.

Требования

  1. Сервер не должен ничего знать о пользователе.
  2. Сервер и клиент будет опенсорсными, чтобы можно было проверить код (на 100% доверять можно только коду клиента).

Регистрация

На лэндинге будет две кнопки: «Войти» и «Попробовать без регистрации». При выборе Попробовать, клиенту присвоится случайный userId и загрузиться обычный UI, который будет сохранять данные локально.

В верхнем меню сразу будет предупреждение, что надо бы зарегистрироваться, чтобы иметь доступ с разных устройств.

По клику на Регистрация, мы генерируем ключ авторизации и ключ шифрования. Получаем длинный общий ключ ${ userId }-${ accessSecret }-${ encryptionSecret }.

Мы спрашиваем пользователя, есть ли у него менеджер паролей (в сноске рассказывая что это такое и зачем ими нужно пользоваться).

  1. Если менеджера паролей нет, то мы спрашиваем у пользователя адрес эл. почты. Туда высылаем письмо с рекомендацией записать куда-то ключ, а письмо не удалять, а заархивировать (так как восстановления паролей у нас нет). Сервер никуда не сохраняет почту.
  2. Если есть менеджер паролей мы показываем ключ и предлагаем сохранить в него ключ (объясняя, что никакого востановления паролей у нас нет).

После этого, userId и encryptionSecret сохраняется в localStorage, а accessSecret ставиться как HttpOnly-кука (и больше н видна из JS). userId и accessSecret сохраняются на сервере, но encyptionSecret сервер никогда не знает.

Вход

Когда пользователь хочет войти в свой аккаунт на другом клиенте, он жмёт «Войти» и вводит свой ключ взяв его из менеджера паролей или найдя эл. письмо с регистрации.

Клиент подключается к серверу используя userId и accessSecret и загружает зашифрованные данные, которые расшировывает локально используя encyptionSecret. Если надо сохранить что-то на сервер, то данные снова шифруются и загружаются в облако.

Восстановление ключа

Если пользователь забыл ключ, то ему предлагается два выхода:

  1. Поискать в почтовом клиенте письмо с ключом (используя особое слово, которое есть только в тексте письма с регистрации).
  2. Взять клиент, который уже залогинен, и восстановить ключ в нём. Пользователь может опционально поставить отпечаток пальца для доступа к encryptionSecret. Сервер скажет accessSecret только добавив факт восстановления в лог.

Однако, если пользователь удалил письмо и потерял все залогиненные клиенты, то данные будут потеряны. Мы предполагаем, что список подписок, не такая критическая информация и нормально её потерять, совершив несколько ошибок.

Возможные векторы атак

  1. Общий-для-всех-сайтов пароль пользователя утёк через взлом другого сайта. На наш метод эта атака не действует, так как мы генерируем ключ сами.
  2. Взломали наш сервер. На нашем сервере нет encyptionSecret, данные полностью анонимны и зашифрованы.
  3. XSS-атака веб-клиента. У JS не будет доступа к accessSecret.
  4. Перехват данных между клиентом и сервером. Данные не расшифровать без encyptionSecret.
  5. Взлом машины клиента. Тут мы можем надеятся только на изоляцию приложений в ОС, но полный ключ, скорее всего, можно будет восстановить.
  6. Устройство пользователя украдено. Пользователь через другой клиент может сменить accessSecret и encryptionSecret, сбросив доступ всех клиентов.
  7. Член семьи получает доступ, пока пользователь спит. После восстановления ключ, клиент ещё пару дней показывает предупреждение, что ключ был восстановлен.

Проблемы с UX

  1. Система непрывычная. Поэтому мы сначала даём попробовать приложение, чтобы человек шёл на сложную регистрацию уже понимая пользу от продукта.
  2. Сложно восстановить приложение в случае потери ключа. Пару способов мы даём, если пользователю не повезло, то в потеря подписок не критичная.

Вопрос

Какие ещё векторы атаки или проблемы в UX вы видете?

@touzoku
Copy link

touzoku commented May 21, 2020

  1. Данные шифровать не нужно, если ключ шифрования лежит там же, где и данные — в localStorage. Это иллюзия безопасности
  2. Показываешь клиенту мнемонику (достаточно 12 слов из словаря в 2048 слов) и сохраняешь ее менеджере паролей или еще где.
  3. Из мнемоники выводишь публичный ключ и приватный ключ. Публичный ключ — это userId, приватный ключ — это твой authentication key и из него же выведешь сессионный ключ шифрования.
  4. Синхронизируешь девайсы через PeerJS, брокер получает только публичный ключ и сессионный публичный ключ шифрования от первого девайса по схеме Diffie-Hellman.

При такой схеме брокер нужен только для установки соединения, и, при желании, его можно хостить самому (надо сделать дешевую в хостинге serverless версию).

@ai
Copy link
Author

ai commented May 21, 2020

@touzoku ключ нужен не для хранения в localStorage, а при загрузке в облако.

@touzoku
Copy link

touzoku commented May 21, 2020

Зачем вообще облако? Если только для синхронизации, можно ключ в моменте генерировать просто

@glukki
Copy link

glukki commented May 25, 2020

@touzoku сервер — затем, чтобы залогиниться с другого девайса, когда ни одного предыдущего уже нет в сети, и получить все данные аккаунта

@glukki
Copy link

glukki commented May 25, 2020

Кажется, если userId нигде не светится другим пользователям, он может покрыть задачи accessSecret, если будет достаточно длинным:

  • для чтения — надо знать длинный userId;
  • для записи — то же самое;
  • для «сброса» accessSecret — просто сгенерировать новый userId, а на место данных старого userId — записать что-то вроде «данные были сброшены».
    Тогда отдельный accessSecret не будет нужен.

@ai
Copy link
Author

ai commented May 25, 2020

Кажется, если userId нигде не светится другим пользователям, он может покрыть задачи accessSecret, если будет достаточно длинным

Не-секретный userId может быть полезен для других фич

@levenkov
Copy link

безопкасным -> безопасным

@levenkov
Copy link

levenkov commented May 26, 2020

но 100% дловерять -> на 100% доверять

@glukki
Copy link

glukki commented Jun 26, 2020

Apple выкатывает в Safari 14 аутентификацию на сайтах по Face ID/Touch ID. Оказывается, по сути то же самое, есть в Chrome и Firefox. Но на сегодняшний день работает вроде только в Windows, когда включен вход по Hello. Вообще, это Web Authentication API, набор апи для работы с такими способами аутентификации, как внешний ключ, отпечаток пальца, лицо. Кажется, это ровно то, что тебе нужно!

Описание: https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API
API для работы:

Презентация Apple: https://developer.apple.com/videos/play/wwdc2020/10670/

Хмм, хотя, экспорта приватных ключей тут вроде нет. То есть, на новом устройстве так просто не залогиниться.
Ну, все равно оставлю здесь это сообщений, вдруг пригодится 🤷‍♂️

@ai
Copy link
Author

ai commented Jun 26, 2020

@glukki с WebAuthn есть популярное заблуждение. Да, можно использовать аппаратный ключ для логина. Но такой ключ стоит 10-50$ и есть у единиц (у меня есть).

Лицо/отпечаток использовать для логина нельзя. Лицо/отпечаток храниться в браузере и никуда не передаётся. В итоге по этому отпечатку ты можешь войти только на том же устройстве. Нельзя по этому же отпечатку войти на телефоне или другом ноутбуке.

Поддержка отпечатка/лица в WebAuthn служит не целью входа (когда связываешь новое устройство с твоим аккаунтом), а для пере-логина — подтверждения, что за ноутбуком до сих пор сидит тот же человек. Например, банковское приложение может проверять что это тот же человек. Или при обращении в опасным настройкам можно убедиться что это тот же пользователь.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment