Pull to refresh

Comments 8

А вариант exec(sha256sum argv[0]) с перехватом stdout в таком варианте не прокатит? Как самый простой вариант?

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

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

App Attest + Notarization Ticket + SecCode API

1. DCAppAttestService (macOS 14+)

Публичный API, не требует отключения SIP. Привязывает ключ в Secure Enclave к конкретному App ID (Team ID + Bundle ID) и выдаёт сертификатную цепочку, подписанную Apple

let service = DCAppAttestService.shared
let keyId = try await service.generateKey()
let attestation = try await service.attestKey(keyId, clientDataHash: SHA256(challenge))

2. Notarization Ticket как доказательство хеша бинарника

При нотаризации Apple выдаёт тикет, который:

  • криптографически подписан Apple,

  • содержит cdHash нотаризованного бинарника,

  • стаплируется в bundle (xcrun stapler staple).

Тикет можно извлечь из bundle и отправить удалённой стороне, она верифицирует подпись Apple и извлекает хеш. Публичный ключ Apple для проверки тикетов встроен в macOS и задокументирован. Notarization Ticket нужно уметь парсить (формат Apple Binary Property List внутри .dmg-style структуры). Формат не документирован официально, но хорошо изучен если поискать.

3. SecCode API для получения cdHash запущенного процесса

var code: SecCode?
SecCodeCopySelf([], &code)  // code текущего процесса

var info: CFDictionary?
SecCodeCopySigningInformation(code as! SecStaticCode, 
                              SecCSFlags(rawValue: kSecCSSigningInformation), 
                              &info)
let cdHash = (info as! [String: Any])[kSecCodeInfoCdHash as String]
// cdHash — SHA-1 или SHA-256 кодовой подписи, тот же, что в notarization ticket

Это системный вызов, а не self-report приложения - ядро macOS возвращает данные из code signature structure, и подделать их без отключения SIP нельзя

Вся цепочка выглядит как-то так

[Remote Server]                         [MyBox App]
      |                                      |
      |-- 1. challenge_nonce --------------->|
      |                                      |
      |                         2. attestKey(keyId, SHA256(nonce))
      |                            → Apple attestation cert chain
      |                                      |
      |                         3. SecCodeCopySelf → cdHash
      |                            Extract notarization ticket from bundle
      |                                      |
      |                         4. payload = {nonce, cdHash, ticket, timestamp}
      |                            assertion = generateAssertion(keyId, SHA256(payload))
      |                                      |
      |<-- 5. {attestation, payload, assertion}--|
      |                                      
      | 6. Verify attestation chain (Apple CA)
      | 7. Verify assertion signature (attested pubkey)
      | 8. Verify notarization ticket (Apple signature)
      | 9. ticket.cdHash == payload.cdHash == expected_hash

Увы, DCAppAttestService хоть формально и доступен, но DCAppAttestService.isSupported для возвращал false по нашим тестам. Не работает решение, хотя мы копали в эту сторону.

Удалённую аттестацию приложения на macOS можно попытаться собрать из стандартных механизмов системы, не отключая SIP и не используя приватные API.
Идея такая - связать Secure Enclave, cdHash запущенного бинарника, Apple notarization ticket и nonce сервера. Устройство подписывает эти данные ключом из Secure Enclave, сервер проверяет подпись, ticket Apple и совпадение хеша бинарника.

Общая схема

Нужно доказать серверу три факта:

  • сообщение подписано реальным устройством

  • подпись относится к конкретному запуску приложения

  • бинарник соответствует нотариализованной версии Apple

Для этого используются четыре элемента:

  • ключ устройства в Secure Enclave

  • cdHash запущенного приложения

  • notarization ticket Apple

  • nonce, присланный сервером

1. Ключ устройства (Secure Enclave)

При первом запуске приложение создаёт ключ ECDSA P-256 внутри Secure Enclave.

Через:

  • Security.framework

  • или DeviceCheck / App Attest

Ключ:

  • неэкспортируемый

  • привязан к устройству

  • используется только для подписи

Серверу отправляется только публичный ключ.

2. Получение хеша запущенного приложения

В рантайме приложение может получить собственный cdHash (CodeDirectory hash) через публичный SecCode API.

var code: SecCode?
SecCodeCopySelf([], &code)

var info: CFDictionary?
SecCodeCopySigningInformation(code!, SecCSFlags(kSecCSSigningInformation), &info)

let dict = info as! [String: Any]
let cdHashes = dict[kSecCodeInfoCdHashes as String] as! [Data]
let cdHash = cdHashes.first!

Это тот же хеш, который macOS использует при проверке подписи приложения.

3. Подтверждение Apple (notarization)

Бинарник проходит обычную цепочку:

codesign
notarization
stapler

После этого внутри .app появляется notarization ticket, подписанный Apple и содержащий cdHash.

Проверка локально:

xcrun stapler validate MyBox.app

4. Протокол аттестации

Сервер отправляет устройству случайный challenge:

nonce

Приложение формирует payload:

payload = nonce || cdHash || ticket || timestamp

И подписывает его ключом Secure Enclave:

signature = ECDSA_sign(payload)

Ответ серверу:

{
  "payload": "...",
  "signature": "...",
  "cdHash": "...",
  "ticket": "...",
  "devicePublicKey": "..."
}

5. Проверка на сервере

Сервер выполняет три проверки.

1. Ticket Apple

  • проверяется подпись Apple

  • извлекается cdHash

2. Совпадение хеша

cdHash_ticket == cdHash_client

3. Подпись устройства

Verify(devicePublicKey, payload, signature)

Также проверяются nonce и timestamp (защита от replay).

Результат

Если все проверки проходят, сервер знает:

  • сообщение подписано Secure Enclave конкретного Mac

  • подпись относится к реальному запуску приложения

  • бинарник соответствует нотариализованной версии Apple

Ограничение

Нужен Secure Enclave - старые Intel-Mac без T2 такой сценарий не поддерживают.

Но эта реализация подвержена следующим рискам ИБ:

Реализация даёт гарантию - сервер может быть уверен, что:

  • подпись сделана ключом Secure Enclave;

  • бинарник соответствует нотариализованной версии;

  • сообщение свежее.

Но не может на 100% доказать, что:

  • код не модифицирован в памяти;

  • нет инжектированного кода;

  • устройство — настоящий Mac;

  • клиент не проксируется.

у вас внутри совсем-совсем ничего не протестует, когда вы копипастите ИИ слоп?

Завершили поиск ответа, все предложенные решения собрали в материал: https://habr.com/ru/articles/1009826/comments/

И итоговая гипотеза, которую команда будет тестировать, тоже там :)

Sign up to leave a comment.

Articles