Я реализую простое приложение PoC в React Native для iOS и пытаюсь подтвердить аутентификатор с помощью библиотеки react-native-passkey
, однако мне сложно проверить ответ на подтверждение, который я получаю от моего клиента на мой сервер Nest.JS, который использует зависимость @simplewebauthn/server
для генерации ответы. Похоже, проблема заключается в несоответствии предоставленного поля запроса clientDataJSON и поля, сгенерированного на сервере.
Вот задача, которую я создаю:
i-1E5Fhg0so0Xd3CInFzHG7EVQfWqazTgf-m26flrsQ
А вот проблема с данными клиента, которую я извлек, сначала преобразовав строку base64 в строку utf8 и проанализировав ее с помощью JSON.parse
:
{
type: 'webauthn.create',
challenge: 'aS0xRTVGaGcwc28wWGQzQ0luRnpIRzdFVlFmV3FhelRnZi1tMjZmbHJzUQ',
origin: <redacted>
}
Исходник специально отредактирован. Это действительный домен, который используется моим экземпляром EC2 и имеет все конечные точки, необходимые для правильных потоков аттестации/утверждения.
Я читал в спецификации W3C , что Apple перед его подписанием встраивает в вызов какой-то хеш клиентских данных. Я не уверен, что делать с этими деталями спецификации, поскольку они слишком высокотехнологичны, чтобы мой тупица мог их понять.
Итак, мой вопрос будет заключаться в том, какие байты мне нужно объединить с моим вызовом, чтобы он выглядел так же, как тот, который возвращает мое приложение для iOS?
Я попытался получить конечную точку на своем сервере, передать эту информацию аутентификатору iOS и вернуть его ответ обратно на сервер. Я ожидал, что запрос, возвращенный в данных клиента, будет соответствовать запросу, сгенерированному сервером.
В комментариях к правильному ответу на этот конкретный вопрос я завел разговор о трудностях проверки подписи утверждения.
Вот новый вопрос по этому поводу.
В вашем ответе у вас есть
{
type: 'webauthn.create',
challenge: 'aS0xRTVGaGcwc28wWGQzQ0luRnpIRzdFVlFmV3FhelRnZi1tMjZmbHJzUQ',
origin: <redacted>
}
И здесь aS0xRTVGaGcwc28wWGQzQ0luRnpIRzdFVlFmV3FhelRnZi1tMjZmbHJzUQ
будет закодирован в Base64.
Если я декодирую его, я получаю i-1E5Fhg0so0Xd3CInFzHG7EVQfWqazTgf-m26flrsQ
, который совпадает с тем, что вы вставили как i-1E5Fhg0so0Xd3CInFzHG7EVQfWqazTgf-m26flrsQ
, и вы говорите, что сгенерировали.
Итак, согласно комментарию, проблема также заключается в том, как проверить заявление об анонимной аттестации Apple, поэтому включите это как часть ответа.
Согласно Webauthn Apple проверяется немного иначе, чем ваша стандартная криптографическая подпись, или в соответствии со списком шагов.
Итак, из списка мы видим, что нам нужно получить AuthenticatorData, который является частью ответа, и clientDataHash, который является частью запроса. Оба доступны через поля в Webauthn согласно вашему комментарию. Таким образом, для вас concat будет правильным, и вы затем проверите его хеш-значение по сравнению с частью credCert x5c в attStmt ответа, как описано в шагах 3 и 4.
@Константин, подпись делается на объединении authenticatorData
и clientDataHash
, а не только на вызове. Это делает так, что как запрос, так и ответ обеспечивают большую безопасность. clientDataHash
— это SHA-256
имеющихся у вас данных о клиенте, а authenticatorData
— это часть ответа, который вы получаете от getAssertion. Я не использовал две библиотеки, которые вы используете, но предполагаю, что они обе поддерживают это «из коробки» без проверки вручную.
Похоже, что это не так, поскольку как вызов (оператор равенства в коде библиотеки), так и проверка подписи терпят неудачу. Как именно мне объединить AuthenticatorData и clientDataHash? Должен ли я просто декодировать оба из base64 в массив двоичных данных и просто добавить clientDataHash в конецuthuthentatorData? И нужно ли мне также хэшировать данные клиента с помощью SHA-256?
Это как бы превращается в другой вопрос о том, как проверить подпись с помощью ключей доступа, и я не уверен, что смогу дать полный ответ в комментариях. Я также вижу, что тип запроса — webauthn.create, а не webauthn.get, и мой комментарий был основан на get, так что это моя вина. Итак, в этом случае вы будете действовать по ссылке, указанной в вашем вопросе. Объединение authData и client будет выполняться на необработанных двоичных данных, поэтому вы хотите, чтобы данные не были в формате base64, а затем объединили два массива. Да, clientData должна быть хеширована, но это делается библиотеками перед отправкой.
Не могли бы вы помочь мне с моим кодом? Я использую встроенный криптомодуль NodeJS, и его метод проверки возвращает false. const clientDataHash = createHash('sha256').update(response.response.clientDataJSON).digest(); const authenticatorData = Buffer.from(response.response.authenticatorData, 'base64'); const signature = Buffer.from(response.response.signature, 'base64'); const concat = Buffer.concat([authenticatorData, clientDataHash]); const result = verify('sha256', concat, publicKey, signature); console.info('custom verify', result);
Я добавил некоторые подробности в ответ о проверке Apple.
Что ж, вот самое интересное: оператор подтверждения, возвращаемый клиентом, имеет формат «нет», и даже несмотря на то, что оператор является действительным CBOR (я могу успешно декодировать его из CBOR), базовый объект кажется пустым. Итак, я не знаю, что делать с credCert и x5c. Может ли credCert быть просто открытым ключом, содержащимся в данных аутентификатора?
Если формат аттестации — none, то для создания нечего проверять. Вы можете контролировать это с помощью запроса, установив переменную attestation
на direct
в опциях вызова .create
. При этом вам будет предоставлено подтверждение того, на что способен используемый вами аутентификатор. Однако не всегда вам нужно получать аттестацию или проверку при создании.
Хорошо, сейчас похоже, что задачи не меняются ни для запросов get
, ни для create
. Но проверка подписи по-прежнему не удалась, и поскольку формат аттестации отличается от формата Apple, я не уверен, как воссоздать исходные данные, которые использовались для подписи, и даже если бы я воссоздал данные Apple, это не удалось бы. Я застрял, и позвольте мне сказать вам, что это отстойное чувство.
Ну, на самом деле проверять нечего, чтобы проверять создание с аттестацией None. Я бы также предложил задать новый вопрос на тему получения аттестации, поскольку в комментариях довольно сложно передать всю необходимую информацию и правильно ответить.
Хорошо, я думаю, что просто отмечу ваш ответ как правильный, поскольку он ответил на мой вопрос, и добавлю ссылку на мою проблему с аттестацией в этот пост.
Я создал новый вопрос и отредактировал его со ссылкой, спасибо за ваш вклад, надеюсь увидеть вас там тоже!
Да, похоже, это действительно так! Однако сейчас я борюсь с другой проблемой: я не могу проверить подпись. Какой вызов мне следует использовать в качестве основы для проверки: тот, который сгенерирован на сервере или возвращен клиентом?