Mac Launch Daemon не может восстановить пароль из системной цепочки для ключей после сохранения его там
У нас есть Launch Daemon, который (по разным причинам) запускается от имени пользователя root и взаимодействует с компонентом сервера через сеть. Он должен пройти аутентификацию в службе, поэтому, когда он впервые получает пароль, мы сохраняем его в системной цепочке для ключей. При последующих запусках идея состоит в том, чтобы извлечь пароль из цепочки для ключей и использовать его для аутентификации в сетевой службе.
Это работало нормально, но в macOS 10.12 существующий код перестал работать, и мы были полностью озадачены тем, как это исправить. Это сводится к этому:
Независимо от того, сохраняем ли мы новый пароль или извлекаем старый, мы получаем ссылку на системную связку ключей, используя это:
SecKeychainCopyDomainDefault(kSecPreferencesDomainSystem, &system_keychain);
Мы также отключаем взаимодействие с пользователем для хорошей меры, хотя мы ожидаем, что оно уже отключено в контексте демона.
SecKeychainSetUserInteractionAllowed(false);
При сохранении нового пароля в связку ключей мы используем
OSStatus status = SecKeychainAddInternetPassword(
system_keychain,
urlLength, server_base_url,
0, NULL,
usernameLength, username,
0, NULL,
0,
kSecProtocolTypeAny, kSecAuthenticationTypeAny,
passwordLength, password,
NULL);
Это много работает. Об успехе сообщается, и я вижу элемент в "системной" цепочке для ключей в Keychain Access.app.
Получение его при последующих запусках нашего демона выполняется с помощью этой строки:
status = SecKeychainFindInternetPassword(
system_keychain,
urlLength, url,
0, NULL,
usernameLength, username,
0, NULL,
0,
kSecProtocolTypeAny, kSecAuthenticationTypeAny,
&passwordLength, &password_data,
NULL);
К сожалению, это начало возвращаться errSecAuthFailed
по неясным для нас причинам.
Несколько дополнительных деталей, которые мы проверили, и вещи, которые мы попробовали, но безрезультатно:
- Двоичный файл демона подписан сертификатом Developer Id.
- Двоичный файл демона содержит встроенный раздел Info.plist с идентификатором пакета и версией.
- Я вижу двоичный файл демона в списке "Всегда разрешать доступ этим приложениям" на вкладке "Контроль доступа" элемента пароля в связке ключей Access.app.
- Если я вручную переключусь на "Разрешить всем приложениям доступ к этому элементу" в Keychain Access, это будет работать. Это, однако, несколько лишает смысла сохранение пароля в цепочке для ключей.
- Мы попытались поиграться с параметрами, чтобы
SecKeychainAddInternetPassword
, но это, кажется, не имело никакого значения. - Мы попытались явно разблокировать брелок с помощью
SecKeychainUnlock()
Но, как следует из документации, это кажется излишним. - Удаление элемента в
Keychain Access.app
причиныSecKeychainFindInternetPassword()
уступатьerrSecItemNotFound
, как и следовало ожидать. Так что он может определенно найти сохраненный элемент, ему просто не разрешено его читать.
Документация по связке ключей не совсем легко читать, а по частям она довольно тавтологическая. ("Чтобы сделать Y, вам нужно сделать Y", не упоминая, почему вы хотите сделать Y.) Тем не менее, я думаю, что я прошел и понял большую часть этого. Различные аспекты нашей конкретной настройки не рассматриваются подробно (доступ из демона), но кажется довольно ясным, что доступ к элементу, ранее сохраненному тем же приложением, не требует специальной авторизации или аутентификации. Что находится в прямом противоречии с поведением, которое мы наблюдаем.
Есть идеи?
1 ответ
Потратив на это несколько часов в течение нескольких дней, мы наконец-то поняли, что происходит.
Сначала я попытался создать минимальный пример, который воспроизвел бы проблему. Это не с ошибкой errSecAuthFailed
и, таким образом, не воспроизвести проблему. Итак, вернемся к первоначальному демону, должно быть, что-то определенное в этом пошло не так.
Следующей идеей было проверить системный журнал на время, когда SecKeychainFindInternetPassword()
назывался. Это вызвало некоторые сообщения об ошибках:
securityd CSSM Exception: -2147411889 CSSMERR_CL_UNKNOWN_TAG
securityd MacOS error: -67063
securityd MacOS error: -67063
securityd code requirement check failed (-67063), client is not Apple-signed
securityd CSSM Exception: 32 CSSM_ERRCODE_OPERATION_AUTH_DENIED
OurDaemon subsystem: com.apple.securityd, category: security_exception, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 0, privacy_setting: 2, enable_private_data: 0
OurDaemon CSSM Exception: -2147416032 CSSMERR_CSP_OPERATION_AUTH_DENIED
Это предполагает, что проблема может быть с подписью кода. Странный. Проверка подписи кода двоичного файла с помощью codesign -vv
не вернул никаких проблем.
После поисков в Интернете различных частей сообщений об ошибках, я нашел -67063
соответствует errSecCSGuestInvalid
, Комментарий гласит: "Идентификация кода была признана недействительной".
Ладно, определенно какая-то ошибка кодирования, но что это значит и почему это произошло?
Охота вокруг еще немного, наконец, нашел объяснение, а также решение: http://lists.apple.com/archives/apple-cdsa/2010/Mar/msg00027.html
Это означает, что в какой-то момент с момента запуска программы с ней произошло что-то, что сделало ее недействительной.
а также
если вы запустите подписанную программу, затем замените ее (скажем, создав новую версию на месте:-), а затем запустите новую версию, ядро все равно будет хранить старую подпись, прикрепленную к vnode исполняемого файла. Если это ваша ситуация, то просто удалив исполняемый файл и воссоздав его, вы решите проблему навсегда (пока вы не перезапишите файл снова:-). Мы рекомендуем всегда заменять подписанный код (mv(1), а не cp(1) или его эквиваленты).
Это объяснило это. Я копировал новые версии демона на место, используя
sudo cp path/to/built/daemon /usr/local/libexec/
Очевидно, это перезаписывает файл на месте, а не создает новый vnode, записывает его, а затем переименовывает его поверх старого файла. Таким образом, решение либо cp
сначала во временный каталог, а затем mv
на место. Или удалите файл назначения перед использованием cp
,
Как только я это сделал, это сработало!