Як я зробив взаємний підпис договорів між двома телефонами без сервера, без інтернету і без центру сертифікації

💡 Усі статті, обговорення, новини про Mobile — в одному місці. Приєднуйтесь до Mobile спільноти!

Уся сучасна інфраструктура електронного підпису тримається на одному припущенні: у вас є інтернет... Мене звати Дмитро, і я зробив застосунок, який дозволяє двом людям підписати документ, маючи лише камери телефонів і нуль байтів інтернет-трафіку.

Я Дійкун Дмитро, розробник. Роблю Pakto — застосунок для підписання документів між двома сторонами, де підпис створюється апаратним ключем пристрою під біометрією. Соло: протокол, криптографія, клієнт під iOS і Android, бекенд, верифікатор. До цього випустив дві невеликі офлайн-штуки — гру Data Human і трекер звичок HabitGrid, обидві без реклами й аналітики; звідти й тягнеться інтерес до того, скільки всього можна зробити взагалі без сервера.

Найцікавіше не сам застосунок, а протокол під ним: взаємний підпис двома телефонами без інтернету, без сервера і без центру сертифікації. Єдиний канал зв’язку між пристроями — екран і камера.

Застосунок вже є в App Store і Google Play. Зараз готую препринт для ePrint (IACR) із формальним аналізом протоколу, а тут покажу технічний розбір: як розривається циклічна залежність у взаємному підписанні, звідки береться довіра до ключа без CA, як повний токен апаратної атестації їде через QR-канал, і що з цього реально доводиться, а що є інженерним компромісом.

Далі — як це влаштовано і де боліло.

Уся сучасна інфраструктура електронного підпису тримається на припущенні, якого ми зазвичай не помічаємо: у момент підписання є зв’язок. Треба сходити до ЦСК за сертифікатом, перевірити список відкликаних, отримати мітку часу, звернутися до хмари. Заберіть мережу — і схема не працює.

Мене зацікавило питання: а що як мережі немає в принципі? Не «поганий інтернет», а взагалі нічого. Двоє людей стоять поруч із телефонами і мають підписати документ так, щоб через рік третя сторона могла перевірити обидва підписи й переконатися, що кожен ключ живе у справжньому апаратному модулі пристрою.

Так з’явився Pakto — протокол і застосунок, де єдиний канал зв’язку між пристроями — це екран і камера.

Три обмеження одночасно

Задача виглядає простою, доки не випишеш обмеження разом:

  1. Немає мережі в момент підписання.
  2. Немає сервера, через який проходять підписані дані.
  3. Немає центру сертифікації, який поручився б за публічний ключ будь-якої зі сторін.

Канал — оптичний: один телефон показує анімований код, інший читає камерою. Односторонній у кожен момент, з втратами кадрів, з дуже обмеженою пропускною здатністю.

Кожне обмеження окремо вирішується. Разом вони створюють дві справжні проблеми.

Проблема перша: циклічна залежність

У наївній схемі взаємного підписання кожна сторона хоче підписати «фінальний документ». Але фінальний документ містить особу і підпис іншої сторони — яких ще не існує.

А не може підписати, доки не відомий Б. Б не може бути прив’язаний, доки А не підписав. Класична циклічність.

Рішення — двоетапний геш-якір.

Нехай H : {0,1}* → {0,1}²⁵⁶ — колізійно стійка геш-функція (SHA-256), ⟨RS⟩ — байт-роздільник 0×1E. Спочатку А обчислює перший якір над змістом документа й тільки своїми полями — без Б, без підписів, без публічних ключів:

h₁ = H("v81" ‖ ⟨RS⟩ ‖ C)

де C = enc(C⃗) — канонічна серіалізація вектора полів C⃗. Цей якір А може порахувати ще до того, як дізнається, хто буде другою стороною. Коли Б приєднується, другий якір визначається як детермінована функція від першого та полів Б:

h₂ = H(h₁ ‖ ⟨RS⟩ ‖ nickB ‖ ⟨RS⟩ ‖ avatarB)

Особа Б прив’язується до вже існуючого зобов’язання А — не руйнуючи перший підпис.

І ось практичний наслідок, заради якого все й робилось. Наївна схема потребувала б трьох раундів обміну: А показує документ → Б додає себе → А досогласовує фінальний результат і переподписує. Оскільки h₂ є детермінованою функцією від h₁, підпис А лишається валідним після приєднання Б — переподписувати нічого не треба.

Тому обмін укладається у два QR-переходи:

  1. А → Б: А підписує, показує код із документом і підписом А. Б сканує.
  2. Б → А: Б підписує власним ключем, показує фінальний код. А сканує — договір готовий.

Для оптичного каналу це не косметика. Кожен раунд — це фізично показати анімований код і зчитати його камерою, з втратою кадрів і перезапусками. Прибрати один раунд із трьох означає прибрати третину часу й третину шансів, що щось піде не так.

Зчеплення підписів і чому воно несуче

Два підписані повідомлення:

mA = "A:" ‖ id ‖ ":" ‖ h₁ mB = "B:" ‖ id ‖ ":" ‖ h₂ ‖ ":" ‖ pkA ‖ ":" ‖ σA

де σA = Sign(skA, mA), σB = Sign(skB, mB), і обидва підписи створюються всередині апаратного модуля під біометричним контролем.

Зверніть увагу на mB: він містить і публічний ключ А, і сам підпис А.

Тепер формально. Означимо гру co-signature inseparability: супротивник має доступ до оракула O_B, який на запит (id, C, idB, pkA, σA) повертає σB і записує (id, h₂, pkA, σA) у множину Q. Супротивник виграє, якщо видасть запис, який верифікується, але кортеж (id*, h₂*, pkA*, σA*) ∉ Q — тобто підпис Б валідний під комбінацією, яку Б ніколи не засвідчував.

Теорема. Якщо схема підпису EUF-CMA-стійка, то для будь-якого PPT-супротивника Adv^insep ≤ Adv^euf-cma.

Редукція будується так: підставляємо челендж-ключ як pkB, відповідаємо на запити оракула через власний оракул підпису, а на виході супротивника формуємо m*B і віддаємо як підробку. Валідність підробки тримається на одному кроці — треба показати, що m*B не було серед запитаних повідомлень. І тут вирішує однозначна декодованість шаблону: id — UUID без двокрапок, h₂ — hex фіксованої довжини, pkA — два base64-токени відомої довжини, σA- фінальний base64-токен; алфавіти base64 і hex не містять ":". Отже рядок m*Bоднозначно визначає кортеж, і збіг повідомлень означав би збіг кортежів — протиріччя з умовою виграшу.

Найцікавіше в цьому доведенні — що воно ламається, якщо прибрати поля. Редукція використала лише те, що mB ін’єктивно зобов’язується до (id, h₂, pkA, σA). Викиньте pkA або σA з mB — і супротивник зможе пред’явити той самий σB під іншим σA*, не виходячи із засвідченої множини. Крок із протиріччям не проходить. Тобто ланцюг усередині mB — це рівно те, що купує нероз’ємність, а не «про всяк випадок».

Ін’єктивність кодування: лема, без якої нічого не доводиться

Усі доведення спираються на те, що канонічний кодувальник enc ін’єктивний: різні вектори полів завжди дають різні байти. Інакше два різні документи могли б дати один h₁, і зобов’язання А перестало б бути зобов’язанням.

Лема. Зафіксуємо схему арності n (упорядкований список слотів, кожен простий або складений із двох під-полів). Кодувальник застосовує екрануючу мапу f до кожного значення (переписує кожен байт ⟨RS⟩ всередині значення на ⟨US⟩), склеює n токенів через ⟨RS⟩, а під-поля складеного слота — через ⟨US⟩. Тоді на допустимій області enc ін’єктивний.

Доведення через декодованість. Розіб’ємо s = enc(C⃗) по байту ⟨RS⟩. За побудовою ⟨RS⟩ трапляється в s лише між токенами верхнього рівня: усередині кожного значення кодувальник уже переписав ⟨RS⟩ ↦ ⟨US⟩. Отже розбиття дає рівно n токенів f(v₁), …, f(vₙ) у порядку, без неоднозначності щодо їх кількості чи меж — арність фіксована й публічна.

Два різні вектори відрізняються хоча б в одній компоненті i. Якщо f(vᵢ) ≠ f(wᵢ) — відновлені кортежі різні, отже різні й кодування. Лишається випадок vᵢ ≠ wᵢ, але f(vᵢ) = f(wᵢ): оскільки f переписує лише ⟨RS⟩ ↦ ⟨US⟩ і в іншому є тотожністю, це вимагає значень, які відрізняються виключно байтами ⟨RS⟩-проти-⟨US⟩ на тих самих позиціях. На допустимій області схема забороняє сирий ⟨RS⟩ всередині значень, тому там f — тотожність, і f(vᵢ) = f(wᵢ) ⇒ vᵢ = wᵢ. Протиріччя. ∎

Чесна заувага про область. Ін’єктивність тримається на допустимій області — де значення не містять сирого ⟨RS⟩, який хтось очікував побачити як дані. На практиці хешовані поля — це UUID, base64/hex-дайджести, ISO-8601 таймстемпи й короткі людські рядки; жодне з них легітимно не містить керуючих байтів 0×1E/0×1F.

Якщо хочеться безумовного твердження без застережень — треба переходити на length-prefixed (TLV) кодування, де кожне поле кодується як <довжина_в_байтах>:<значення>. Довжина робить розбір однозначним незалежно від вмісту. Саме туди я й перейшов у наступній версії схеми; ціна — несумісність із раніше підписаними документами, тому обидві схеми живуть паралельно з версійним префіксом.

Стійкість до підміни після підписання

Ще одна властивість, яку варто довести, а не постулювати: жодне поле, зобов’язане першим підписом, не можна змінити після підписання.

Теорема. Якщо схема підпису EUF-CMA-стійка і H колізійно стійка, то Adv^tamper ≤ Adv^euf-cma + Adv^cr.

Доведення розпадається на дві взаємно виключні події для m*A:

  • E₁: m*A ніколи не було в оракулі. Тоді це свіже повідомлення, яке верифікується — готова EUF-CMA-підробка.
  • E₂: m*A збігається з якимось запитаним mA. Шаблон "A:" id ":" h₁ однозначно декодований, отже збіг повідомлень форсує h₁* = h₁, тобто H("v81" ‖ ⟨RS⟩ ‖ C*) = H("v81" ‖ ⟨RS⟩ ‖ C). Але вектори різні за умовою виграшу, а за лемою ін’єктивності різні вектори дають різні рядки. Різні входи, однакові образи — колізія H.

Події вичерпні й неперетинні, сума меж дає теорему. ∎

Практичний зміст: змініть суму, сторону, дату чи геолокацію в підписаному документі — і верифікатор поверне помилку, бо перерахований h₁ не зійдеться з тим, над яким стоїть підпис.

Проблема друга: довіра до ключа без CA

Без мережі й без CA отримувач не може завантажити й перевірити ланцюг сертифікатів для ключа відправника. Звідки впевненість, що на тому боці справжній апаратний ключ, а не програмний на емуляторі?

Заміна сертифікатної довіри — транспортована апаратна атестація.

Ключ ECDSA P-256 генерується всередині захищеного апаратного модуля (Secure Enclave / StrongBox) і ніколи його не покидає. Платформа видає токен атестації τ, який криптографічно засвідчує: цей публічний ключ згенеровано в справжньому апаратному модулі.

Зазвичай такий токен перевіряють на сервері. Тут сервера немає — тому повний токен їде оптичним каналом, а в підписаний якір входить лише його геш із контекстним префіксом:

deviceTokenHashA = H("pakto_device_A:" ‖ τA)

Контекстний префікс тут не декоративний: він розділяє домени, щоб геш токена А не міг бути підставлений як геш токена Б.

І ось де починається інженерія: повний об’єкт атестації не влазить в один статичний QR-код. Тому транспорт зроблено на безрейтовому (fountain) коді.

Fountain-код на оптичному каналі

Структура передачі (поля змісту, h₁, σA, pkA, повний τA, id схеми, id сесії) стискається й ріжеться на k базових фрагментів по ~675 байт. Базові кадри несуть один фрагмент; fountain-кадри несуть XOR псевдовипадкової підмножини:

seed = seq LCG: x ← (1664525·x + 1013904223) mod 2³² degree d = (seq mod k) + 1 frame = ⊕ {fragment[j] : j ∈ subset(seed, d)}

Декодер «відлущує» фрагменти (peeling): кадр ступеня 1 одразу дає фрагмент; відомі фрагменти XOR-яться з кадрами вищого ступеня, знижуючи їх ступінь, і процес повторюється. Завершення підтверджується коротким чексумом — префіксом H(payload).

Ключова властивість: код безрейтовий, тому зворотний канал не потрібен. Не треба просити «повтори кадр 17» — просто крутіть анімацію, доки декодер не збереться. Для односторонньої оптики це критично: зворотного каналу фізично немає, доки інший телефон не почне показувати свій код.

Одразу зазначу чесно: конкретне правило ступеня — інженерний вибір, а не оптимальна конструкція. Оптимальність LT/Raptor доводиться для інших розподілів ступенів; я не претендую на те, що ця схема оптимальна, лише на те, що вона працює на потрібних розмірах.

Практика: те, чого немає в специфікаціях

Apple Secure Enclave видає HIGH-S підписи. Якщо ваш верифікатор строго відхиляє high-S за BIP-62-подібним правилом — ви зламаєте всі підписи з iOS-пристроїв. Довелось нормалізувати, а не відхиляти:

if s > n/2:  s ← n − s

Строгий DER-декодер обов’язковий. Наївний парсер приймає «хвіст» після SEQUENCE, неканонічне кодування довжини, r/s поза діапазоном — це класична DER-malleability: той самий підпис у кількох байтових представленнях. Декодер має відхиляти все, крім єдиного канонічного кодування.

Перевірка точки на кривій. Публічний ключ треба перевіряти на належність P-256 (n·P = O), інакше відкривається invalid-curve attack.

Паритет реалізацій. Ядро протоколу реалізоване двічі — Dart (клієнт) і Python (незалежний верифікатор), збіг підтверджено байт-у-байт. Це не естетика: якщо верифікатор пишеться незалежно іншою мовою й дає ті самі геші, значить доказ не залежить від моєї реалізації.

Чого протокол не робить

Тут намагаюсь бути акуратним, бо спокуса перепродати велика.

Відсутність CA — це компроміс, а не безкоштовний виграш. Довіра до ключа повністю тримається на корені атестації виробника. Зламається він — зламається походження ключа. Ми не позбулись довіреної сторони, ми замінили ЦСК на виробника заліза.

Атестація підтверджує пристрій, а не людину. Криптографічний зв’язок — це зв’язок із ключем у конкретному апаратному модулі. Прив’язка «ключ ↔ фізична особа» потребує окремого кроку і є шаром застосунку, а не властивістю офлайн-ядра.

Сила атестації залежить від платформи. StrongBox, TEE й чистий software — різні рівні гарантій; Android фрагментований.

Поза моделлю загроз: примус користувача, malware з повним контролем над рутованим пристроєм, який може сам натиснути біометричний запит.

Зовнішнього незалежного аудиту всього коду ще не було. Це наступний крок, і я вважаю його обов’язковим до будь-якого серйозного застосування.

Математична стаття

Готую препринт для ePrint (IACR) із формальним аналізом трьох властивостей — anchor binding, co-signature inseparability і attestation-bound key provenance — з редукціями до стандартних припущень (колізійна стійкість H, EUF-CMA-стійкість схеми підпису, апаратний модуль змодельовано як ідеальний підписуючий оракул).

Частина доведень наведена вище в скороченому вигляді. Найбільше часу забрало не написати конструкцію, а точно окреслити область, на якій твердження справедливі: лема про ін’єктивність тримається на допустимих входах, і чесно назвати цю умову виявилось важливіше, ніж сформулювати саму лему.

Коли препринт буде готовий — опублікую.

Спробувати

Застосунок працює на iOS і Android: pakto.pro. Опис можливостей і сценаріїв — на сторінці функцій.

Найпростіший спосіб перевірити, чи справді воно офлайн: увімкніть авіарежим на обох телефонах і підпишіть документ. Нічого не зміниться — обмін іде через камеру.

Замість висновку

Я не стверджую, що це замінює PKI. Кваліфікований підпис із ЦСК вирішує іншу задачу й вирішує її добре — там, де є мережа й акредитований провайдер.

Мені було цікаво інше: що залишається від доказовості, коли забрати всю інфраструктуру. Виявилось — залишається досить багато, якщо акуратно розібратися з циклічністю, ін’єктивністю кодування та тим, звідки береться довіра до ключа.

Буду радий критиці, особливо від людей із криптографічним бекграундом. Найцінніше зараз — знайти дірку, якої я не бачу.

P.S. Звісно, за кадром залишилося чимало детальних рішень — наприклад, як реалізована верифікація атестації ключа на сервері та її збереження безпосередньо в апаратному модулі. Якщо такий глибокий технічний розбір вам цікавий — напишіть у коментарях, і я із задоволенням детально розпишу ці рішення в наступному матеріалі!

👍ПодобаєтьсяСподобалось5
До обраногоВ обраному4
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Два рандоминих чувака в лісі з договорами без чорнил та з телефонами — O tempora, o mores!

Довірена мітка часу. Що підтверджує дійсність заявленого часу операції?

Ахах, так точно, питання тут теж )

в принципі на сайті на демо видно секур тайм, але це ж онлайн

Але ж юз кейс про офлайн, ну і як в офлайн з тим бути справді фіг його зна

Питання по суті, відповім чесно: у повному офлайні довіреної мітки часу немає.

Час у мене працює на трьох рівнях, і головне — джерело часу записується прямо в підписаний документ, тому збрехати про нього заднім числом не вийде. Коли є зв’язок, час бере сервер і повертає підписану квитанцію з HMAC, або тягнеться TSA по RFC 3161 — оце ви й бачите на демо як secure time, тут усе доказово. Якщо мережі в момент підписання нема, але була недавно, береться зсув від останньої NTP-синхронізації з поправкою на дрейф годинника — гірше за TSA, але хоч щось. А якщо офлайн повний і нічого з цього недоступне — тоді час береться з годинника пристрою, і документ несе прапорець LOCAL:WARNING. Тобто верифікатор бачить не «час підтверджено», а прямо «час локальний, довіра знижена».

Чому це все одно не діра: рядок джерела часу входить у геш і покривається підписом. Підмінити потім LOCAL:WARNING на нормальну TSA-мітку не можна — підпис розсиплеться. Сторона не може непомітно збрехати, що в неї був точний час, коли його не було. Максимум — чесно зафіксувати, що час локальний, і це видно кожному, хто відкриє документ.

На практиці виходить так: цілісність, авторство і апаратне походження підписів в офлайні доказові повністю, без зірочок. А от точний момент часу — рівно настільки, наскільки було доступне нормальне джерело, і цей рівень чесно стоїть у документі. Якщо комусь критична саме довірена мітка з точністю до хвилини — значить, для цього кейсу потрібен онлайн, і протокол це не ховає, а показує прапорцем. Для більшості офлайн-ситуацій, де двоє просто фіксують домовленість у полі, локального часу з чесним позначенням досить. Де вирішують хвилини — там видно, що треба мережа.

Коротше, офлайн-час у мене це не «вдаємо, що знаємо точний час», а «беремо найкраще доступне джерело і чесно пишемо, що це було». TSA в офлайні нема і бути не може.

Як що подробять токен атестації, то що?

Зараз буде БАЗА, на якій і тримається Pakto:

Токен апаратної атестації підписаний ключем виробника (Apple або Google), ланцюг довіри якого сходиться до кореневого сертифіката виробника. Це не рядок, який застосунок формує сам, це криптографічне свідчення виробника над фактом «цей конкретний ключ згенеровано всередині справжнього захищеного модуля».

Щоб перевірити токен, верифікатор будує ланцюг сертифікатів до кореневого сертифіката виробника, який публічно відомий і вшитий у верифікатор заздалегідь. Підробити токен означало б підробити підпис Apple/Google — тобто мати їхній кореневий приватний ключ. Це не «зламати застосунок Pakto», це «зламати саму Apple».

Якби хтось таке зміг, впала б уся екосистема iOS, а не один застосунок.

деталі цікаві, перечитаю пізніше як слід

чим не вгодив gpg чи банальний dh для побудови довіри? На tls в принципі теж можна будувати довіру без ca, по хешу сертифіката

якщо там це було то сорі )

Пс

Канал — оптичний: один телефон показує анімований код, інший читає камерою.

як насчьот зловмисника який просканує той самий код?

Без мережі й без CA отримувач не може завантажити й перевірити ланцюг сертифікатів для ключа відправника

Ca робить не тільки цю функцію. Собсно ca теж не обов’язково завантажувати, його можна вшивати в пристрої теж, але revocation lists от потрібно перевіряти регулярно. Це також функція перевірки відкликаних сертифікатів

Що робити у вашому випадку, якщо один з девайсів скомпрометовано? Довіряти його підписам? Не довіряти? Перевіряти? Як взагалі знати що перевірений через три роки валідний підпис був з девайса який не був три роки тому скомпрометований?

Дякую за влучні питання, давайте розберемо їх по черзі.

Якщо коротко щодо GPG та DH: ці інструменти просто вирішують інші задачі.

Банальний DH — це протокол узгодження ключів, а не підпису. Він дає нам спільний секрет. Але якщо ми використаємо його для документа, виникне проблема доказовості. Оскільки секрет знаємо ми обидва, я не зможу довести третій стороні, що документ підписали саме ви, адже я міг згенерувати цей підпис сам. Тут потрібна саме асиметрична криптографія, щоб підпис міг перевірити будь-хто в майбутньому.

Щодо GPG, то це класний інструмент, але він не вирішує проблему апаратної атестації. GPG працює переважно з програмними ключами. А вся суть мого протоколу тримається на тому, що разом із підписом передається токен атестації від виробника заліза. Цей токен свідчить про апаратне походження ключа на пристроях із захищеним модулем — що ключ згенеровано всередині захищеного модуля телефона; для пристроїв без такого модуля застосовується програмний запасний варіант зі зниженим рівнем гарантії. GPG такого свідчення не дає. Крім того, в глухому офлайні з GPG довелося б вручну звіряти відбитки ключів, що повністю ламає досвід замість простого наведення камери ми б отримали зайву рутину.

Щодо TLS та хешів сертифікатів для довіри: проблема в тому, що хеш сертифіката доводить лише те, що ви спілкуєтеся з тим самим ключем. Але він нічого не каже про природу цього ключа. Звичайний самопідписаний сертифікат або його хеш може згенерувати будь-який скрипт на рутованому телефоні чи емуляторі, і ви цього ніколи не відрізните. У Pakto ж база довіри — це токен апаратної атестації, який доводить, що ключ на тому боці живе виключно всередині захищеного чипа, і що для підпису була застосована біометрія.

Щодо зловмисника, який просканує код: у QR-кодах передаються виключно публічні дані — текст документа, публічні ключі та самі криптографічні підписи. Приватний ключ ніколи не залишає апаратний модуль телефона. Тому, якщо хтось просканує код з-за вашої спини, він просто отримає копію вашого договору. Тобто це питання приватності, а не безпеки підпису: зловмисник не зможе змінити договір непомітно — будь-яка зміна руйнує підпис і виявляється під час перевірки. А приватність тут гарантується фізично — ви самі бачите, кому показуєте екран.

Щодо компрометації та відкликання (CRL) — це найцікавіше і дуже правильне зауваження. І це фундаментальний компроміс будь-якої системи, яка працює в повному офлайні. Якщо інтернету немає взагалі, ви фізично не можете перевірити список відкликаних сертифікатів у момент підписання.

Як це нівелюється: оскільки ключі апаратно ізольовані, їх не можна просто скопіювати як файл. Щоб скомпрометувати ключ, зловмиснику потрібен ваш фізичний пристрій плюс ваш палець або обличчя для біометрії. Якщо ж стається глобальний злам на рівні заліза (наприклад, знайдено критичну вразливість у чипах), виробник відкликає кореневий сертифікат самої атестації. Коли через три роки хтось перевірятиме ваш документ через верифікатор (який вже матиме інтернет), верифікатор підтягне актуальні CRL від Apple/Google і побачить, що сертифікат партії цього чипа був відкликаний. Але безпосередньо в момент підписання в лісі без зв’язку — так, ми приймаємо цей ризик як неминучу плату за повну автономність.

ну так, ви робите упор на апаратних ключах, але ніхто ж не заважає мати апаратні ключі з готовими бібліотеками, навіщо тоді особливі кастомні протоколи?

Якщо ідея робити це на пристроях великих виробників то по суті ви компроментуєте систему за дизайном, даючи довіру великим виробникам смартфонів які контролюють операційку.

Якщо ідея робити це в загальному, то потрібна власна платформа й власний чіп, ну і допустім, але тоді теж можна зберігати приватні ключі в умовному tpm і навіщо кастомні протокол тоді

Я не проти кастомних речей, але не зовсім розумію яку проблему вирішено тут, яку не можна вирішити без вже готових криптографічних засобів і потрібно кастомне

Про зламисника питання не в приватності а в його можливості підробити підпис, маючи якийсь доступ до «приватності» одного або й обох пристроїв (допустім камери спостереження чи два зловмисника за обома спинами) чи просто підписувати потім будьщо через replay

Відповім по суті.

Щодо «навіщо кастомний протокол, якщо є готові бібліотеки». Тут важливо розділити примітиви й протокол. Я не винаходив ні ECDSA, ні SHA, ні апаратну атестацію — усе це стандартні, перевірені засоби, і я беру їх готовими. Кастомним є не примітив, а спосіб їх скомпонувати: двоетапний геш-якір, що розв’язує циклічну залежність підпису (перша сторона фіксує документ, ще не знаючи, хто буде другою), і криптографічне зчеплення, за якого підпис Б невіддільний від конкретного підпису А — і все це в повному офлайні, з передаванням токена атестації оптичним каналом. Готова бібліотека дає вам «підписати рядок». Вона не дає вам «двоє незнайомих людей у лісі без мережі взаємно підписали договір так, що жоден не може ані змінити його заднім числом, ані відкріпити чужий підпис». Оце — задача рівня протоколу, а не виклику функції.

Щодо «довіри великим виробникам — це компрометація за дизайном». Тут ви маєте рацію, і я це не приховую: так, коренем довіри стає виробник заліза замість центру сертифікації. Але зверніть увагу — це не додатковий компроміс, а заміна одного кореня довіри на інший. У класичній моделі ви довіряєте ЦСК (який теж може помилитися, бути скомпрометованим чи відкликати сертифікат). Я міняю довіру до державного/комерційного ЦСК на довіру до апаратного кореня Apple/Google. Різниця в тому, що апаратний корінь ви вже й так довіряєте щодня — вашим Apple Pay, банківськими застосунками, паролями в keychain. Я не додаю нового об’єкта довіри, я спираюся на той, що вже є в кишені. Абсолютно бездовірної системи не існує — питання лише, який корінь практичніший.

Щодо власного чипа й TPM. Саме так — якщо будувати з нуля власну платформу, можна зберігати ключі в TPM, і протокол це передбачає (у формулі перелічено TPM, TEE, смарт-карту як рівноправні варіанти захищеного елемента). Але тоді ви впираєтеся в поширення: власний чип треба комусь роздати. Уся ідея — працювати на пристроях, які вже в людей у руках. Кастомний тут не носій ключа, а протокол взаємного офлайн-співпідписання поверх будь-якого з цих носіїв.

Щодо replay і двох зловмисників за спинами — це найважливіше питання. Тут захист не від приватності, а криптографічний:

Replay «підписати потім будь-що» не працює, бо кожен підпис прив’язаний до конкретного документа — і до його ідентифікатора, і до геш-якоря. Узяти готовий підпис і «переклеїти» на інший документ неможливо: зміниться прив’язка, і перевірка впаде. Доменне розділення ролей не дає видати підпис однієї сторони за іншу. Точну структуру підписаного повідомлення я тут не розписую — вона є у патентній заявці, але суть саме така: підпис невіддільний від конкретного документа й конкретної ролі.

Два зловмисники за обома спинами, які сканують екрани — отримають копію договору, але не зможуть нічого підписати замість вас, бо підпис вимагає операції приватним ключем усередині захищеного модуля, а вона фізично неможлива без вашої біометрії в цей момент. Скан екрана дає публічні дані, не приватний ключ. Щоб підробити ваш підпис, потрібен не погляд на екран, а ваш палець на вашому пристрої.

Єдине, чого апаратна атестація справді не рятує, це якщо зловмисник має ваш розблокований пристрій і вашу біометрію в моменті. Але це вже не «зламати протокол», це «мати вас під фізичним контролем», і від цього не захищає жодна система, включно з ЦСК-підписом на тому ж телефоні.

кожен підпис прив’язаний до конкретного документа — і до його ідентифікатора, і до геш-якоря.

ну ок, але це ж і є типовий цифровий підпис в tls.

якщо це

самопідписаний сертифікат або його хеш може згенерувати будь-який скрипт на рутованому телефоні чи емуляторі, і ви цього ніколи не відрізните

то в принципі для для цього в tls є власні простори oid і з них ви можете робити власні розширення й перевіряти їх. Ну тобто тоді довіряйте не просто хешу, довіряйте хешу з певними вашими полями. Ну рутовий телефон так може й проблема, але в цій ситуації генерувати щось на рутованому телефоні хіба не ставити під сумнів будьяку довіру? В тч самопальну...

мені подобається оптичний канал і передача, ітд, в цьому щось є. На рахунок як будувати довіру мені все ж турбує чим tls не вгодив, але такоє, можна рибу готувати по різному, треба то й треба, довіряти спільно чомусь треба, чому б не виробникам смартфонів... але таким самим спільним трастом це може бути якийсь спільний ca мені здається, не обов’язково для цього онлайн бути. Це ж по суті недалеко від s/mime

В будьякому разі цікавенна стаття

О, тут ви фактично самі навели аргумент на мою користь.

Про OID у X.509 — згоден, втиснути можна, простір для того й є. Але сертифікат засвідчує одну штуку: «ключ належить оцьому імені». А мені треба інше — щоб два підписи зчепилися між собою, щоб підпис Б був невіддільний від конкретного підпису А. Це вже не про сертифікат, це протокол зверху, його однаково довелося б писати. Плюс ваш кастомний OID стандартний валідатор або зігнорить, або впреться на critical — тобто перевіряти це поле все одно вашим кодом. Я просто не тягну весь ASN.1/DER у QR заради одного поля. Ну і корінь довіри той самий: атестацію в тому OID ви однаково перевіряєте проти Apple/Google. X.509 дав би формат, а не довіру — я обгортку зняв, суть лишив.

А от про рутований телефон — це 50 на 50, ви тут б’єте не туди, куди думаєте. Ви кажете: на руті скрипт згенерує будь-що, отже під сумнівом усе, в тч самопальне. І це правда! Саме тому я й НЕ роблю самопальну довіру. Рут дає владу над ОС — але не дає приватного ключа з Secure Enclave (його там просто нема у вигляді, який вкрадеш) і не дає підробити підпис Apple над токеном атестації. Корінь не на телефоні, він на заводі. Тобто рут ламає хеші й самопідписані серти. — і не ламає апаратну атестацію. Виходить, рутований телефон це аргумент ПРОТИ хешів і ЗА атестацію.

Про S/MIME і спільний CA офлайн — тут ви праві, спільний траст таки потрібен, я не сперечаюся. І CA це вміє, і ланцюг офлайн перевіряється, все так. Питання суто прагматичне: CA хтось має підняти, видавати серти, відкликати, тримати CRL. А апаратний корінь Apple/Google уже лежить у кожного в кишені, і мені не треба адмініструвати нічого. Це не «CA гірший» — він норм. Це «навіщо піднімати новий спільний траст, коли робочий уже вшитий у телефон». S/MIME по духу реально поруч, ви влучно помітили — тільки воно про CA-модель, а я пробую взагалі без адмініструваного CA.

Загалом клас, що ви це все розкладаєте — рідко коли в коментарях така предметна розмова. І дякую, що оптичний канал зайшов, це та частина, за яку я найбільше вболівав )

Підписатись на коментарі