Termiyo

Безопасность

Как устроено хранилище

Без общих слов: вот сами конструкции, их параметры и почему выбрана каждая.

От чего мы защищаемся

Каждое решение ниже определяют три ситуации. Сервер синхронизации, который хранит ваши данные и никогда не должен уметь их прочитать. Украденный ноутбук, где файл базы в чужих руках. И атакующий с этим файлом офлайн, перебирающий догадки так быстро, как позволяет железо.

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

Второй фактор и то, зачем он нужен

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

Открыть хранилище на машине, которая у кого-то уже есть, этого пароля по-прежнему достаточно. Хранилище зашифровано здесь, на вашем диске, и никакого сервера, у которого можно что-то спросить, в этом нет — значит, коду там нечего охранять. Двухфакторная проверка также не завершает сеансы на устройствах, которые уже вошли, и не делает забытый пароль восстановимым.

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

Argon2id, а не быстрый хеш

Ваш мастер-пароль растягивается в 32-byte ключ с помощью Argon2id при 64 MiB памяти и трёх проходах — это `crypto_pwhash` из libsodium, который выполняет Argon2id в одну полосу. Память здесь и есть смысл: GPU может параллельно считать огромное число хеш-операций, но не может дать каждой по 64 MiB быстрой памяти. Это превращает широкую параллельную атаку обратно в узкую.

Для сравнения: вывод PBKDF2-SHA256 на тысяче итераций — всё ещё распространённое значение по умолчанию — стоит атакующему меньше миллисекунды на догадку и распараллеливается почти идеально. Argon2id с этими параметрами занимает около 130 ms на ноутбуке и сопротивляется именно такой параллельности.

Параметры записаны рядом с хранилищем, поэтому их повышение позже влияет на новые выводы, а существующие хранилища по-прежнему открываются.

Каждая запись запечатана отдельно

У каждого хранилища свой случайный 32-byte ключ, завёрнутый под мастер-ключом. Записи шифруются XChaCha20-Poly1305 со свежим 24-byte одноразовым значением, а собственная идентичность записи — её тип, её хранилище и её id — привязывается как associated data.

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

Поделиться, не передавая ключ

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

Отзыв доступа удаляет запечатанную копию и останавливает синхронизацию. До того, что у человека уже есть, он не дотягивается: кто уже прочитал данные — прочитал, и никакая система этого не отменит. Ключ хранилища отзыв тоже не меняет — Termiyo этого не умеет, — поэтому тот, кто синхронизировался, пока у него было «Может изменять», сохраняет ключ, который открывает всё, что позже сохранят в этом хранилище, где бы эти записи до него ни дошли; если снова поделиться с ним хранилищем, даже как «Только чтение», он получит как минимум его хосты. Вернуть секрет можно одним способом — сменить пароли и ключи на самих серверах.

Ключи хостов, закреплённые

Впервые увидев сервер, Termiyo показывает его ключ и отпечаток SHA-256 и закрепляет то, что вы приняли. Каждое переподключение сверяется с этим закреплением.

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

Где на самом деле лежат секреты

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

Всё, что показано в интерфейсе, сначала маскируется. Сохранение записи с замаскированным полем оставляет сохранённый секрет нетронутым.

Как проверить то, что вы скачали

Две разные проверки, и они отвечают на разные вопросы. SHA-256 рядом с каждой загрузкой на странице загрузки говорит, что байты у вас — это байты, которые собрали мы: `shasum -a 256 <file>` на macOS и Linux, `certutil -hashfile <file> SHA256` на Windows.

Подпись говорит, кто их собрал, и ваша операционная система проверяет её за вас, прежде чем запустить приложение. Вы можете проверить и сами, заранее. На macOS: `spctl -a -t open --context context:primary-signature -vv <file>.dmg` должна ответить "accepted" и "source=Notarized Developer ID".

Установщик Windows не подписан, поэтому в свойствах нет вкладки «Цифровые подписи». По отсутствию вкладки нельзя определить, был ли файл изменён. Сохраните установщик, не запуская его, затем выполните `certutil -hashfile "<file>" SHA256`, заменив `<file>` путём к сохранённому файлу. Прежде чем открывать установщик, сравните результат с SHA-256 рядом с этим же файлом на странице загрузки. Если хеши не совпадают, не запускайте файл. Совпадение хешей подтверждает, что байты соответствуют нашему опубликованному файлу; это не подпись издателя. В отличие от случая с Linux ниже, это наш пробел, а не свойство платформы: Windows проверяет подписи прекрасно, мы ещё не дали ему, что проверять. Эта страница будет писать так до тех пор, пока в скачанном установщике не появится подпись, которую вы сможете проверить сами.

На Linux эквивалента нет: ни .deb, ни AppImage не несут подписи, которую проверяет ваша система, поэтому хеш — это всё, что можно проверить. Это свойство платформы, а не наш выбор, и лучше это знать, чем предполагать обратное.

Что уходит с вашей машины

Две вещи, и обе перечислены. Termiyo отправляет короткий отчёт о себе не чаще одного раза в 24 часа — версия, платформа, процессор, способ установки, язык интерфейса, включена ли проверка обновлений и число дампов сбоев в его собственной папке — без идентификатора и без чего-либо из ваших сессий; он включён по умолчанию, и выключается одним щелчком, при первом запуске или в разделе «Настройки → Справка». Сбой пишет дамп в папку на вашем диске, а отправка такого дампа нам — отдельный переключатель, который остаётся выключенным, пока вы его не включите; раздел «Настройки → Справка» показывает её, объясняет, что может быть в дампе, и открывает её.

Отправка дампа нам — это переключатель на том же экране. На новой установке он выключен, после обновления остаётся выключенным, и пока он выключен ни один дамп не покидает машину: приложить его к сообщению об ошибке вручную по-прежнему единственный способ. Включите его: если включено «Проверять обновления при запуске Termiyo», следующий сбой отправит дамп по HTTPS вместе с именем, которое дала этому файлу ваша машина, версией, платформой, архитектурой процессора и идентификатором этой установки, который создаётся при первой отправке, и больше ни с чем; тот же экран перечисляет отправленное и содержит кнопку, которая просит наш сервер это удалить. Прежде здесь было написано, что отправки отчётов о сбоях не просто нет по умолчанию, а нет вообще: это было верно для каждой сборки до той, которая добавила адрес. Подробности — на странице о конфиденциальности. Когда проверка обновлений выключена, дамп, на отправку которого вы согласились, ждёт на этой машине и уходит при первом запуске после того, как проверку снова включат.

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

Здесь мы были неправы и исправляем это: тут было сказано, что с выключенной проверкой запуск не делает ни одного исходящего запроса, и предлагалось проверить это через tcpdump. Запуск, которого никто не касается, с выключенной проверкой теперь действительно молчит — это измерено на уровне сети, в Linux, — но когда открывается хранилище, начинается трафик, не связанный с настройкой обновлений, и эта страница называла только его часть. Первое — терминалы, которые вы оставили открытыми: SSH-вкладки, которые были открыты, когда Termiyo закрылся в прошлый раз, открываются снова сразу после открытия хранилища, и каждая заново полностью входит на свой хост, — так ваша работа возвращается туда, где вы её оставили, а вкладка, закрытая до выхода, сама не возвращается. Без выполненного входа, пока вы не ответили на предложение завести аккаунт, экран первого запуска спрашивает, отвечает ли termiyo.com, — чтобы знать, стоит ли вообще предлагать вам аккаунт, — один раз за запуск, при первой разблокировке; пока termiyo.com недоступен, предложение не показывается, поэтому следующий запуск спрашивает снова. С выполненным входом Termiyo спрашивает, подтверждён ли ваш адрес, — один раз за запуск и ещё раз, пока он не подтверждён, потому что панель, которая после этого просит код, перечитывает ответ, — синхронизируется при открытии хранилища и дальше каждые пять минут, при открытии формы хоста, группы или прокси спрашивает, в какое из ваших хранилищ можно сохранить запись, а если у вас нет своего ключа ИИ — спрашивает, сколько осталось от лимита ИИ вашей учётной записи, когда вы открываете панель ИИ или начинаете печатать в терминале. Пока открыт список хостов, каждый сохранённый хост SSH или telnet, до которого Termiyo может достучаться напрямую, проверяется каждые 15 секунд — соединение с его портом, которое закрывается, не отправив ни байта, — чтобы точка рядом с ним была актуальной; эти соединения идут к вашим серверам, а не к нам, и видны в их журналах. Хост, сохранённый по имени, а не по адресу, при каждой проверке добавляет ещё и DNS-запрос, и этот запрос уходит к резолверу, которым пользуется ваша машина. Если скрыть список хостов, эти проверки прекращаются, но не ещё одна такая же: пока на переднем плане вкладка с терминалом, подключённым к одному из этих хостов, строка состояния каждые 10 секунд проверяет этот хост ради задержки, которую показывает, — со списком или без, — пока в строке написано «Подключено», — а для хоста, сохранённого по имени, ещё и каждый раз спрашивает о нём ваш резолвер. А то, что вы настроили, делает то, для чего вы это настроили: резервная копия по расписанию копируется на указанное вами зеркало, вебхуки обращаются к своим адресам, а задания по расписанию и перенаправления портов подключаются к своим хостам — задание по расписанию по разу на каждый запуск, выходя из сеанса, когда его сниппет закончился. Так что утверждение оказалось неверным именно в той форме, которую мы называли единственной стоящей: в проверяемой.

Терминал, которым вы делитесь, — это второе место, где существует сессия, и это второе место — вкладка браузера. Байты идут с вашей машины на машину гостя, не проходя больше ни через что: их не несёт ни один ретранслятор, и это выбор — TURN-сервер видел бы трафик, поэтому Termiyo его не использует, а наш собственный сервер лишь знакомит вас двоих. Что мы используем, так это публичный STUN, Google и Cloudflare, — чтобы спросить, как ваш адрес выглядит снаружи; эти два сервиса узнают этот адрес — и больше ничего. Браузеру гостя не даётся никакого STUN-сервера вообще. Дальше цена: гость смотрит в браузере, поэтому расширения могут читать эту страницу, а браузер может сохранить её снимок для предпросмотра вкладки. Гость начинает в режиме только чтения и не может вводить, пока вы не разрешите, а доступ прекращается, когда вы его прекратите, или через четыре часа. Чем это не является, так это той же защитой, которую даёт само приложение, — и страница гостя говорит об этом на экране самого гостя, а не только здесь.

Чего мы утверждать не можем: сам файл SQLite не зашифрован, зашифрованы только записи внутри него. Тот, у кого есть файл, узнает, сколько у вас серверов, когда вы в последний раз касались каждого и какие дали им названия. Их адреса, имена пользователей, пароли и ключи зашифрованы. Лучше сказать это здесь, чем чтобы вы выяснили сами.

Нашли что-то?

Сообщения о безопасности — на info@termiyo.com. Мы подтвердим получение в течение двух рабочих дней.