У нас есть 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 с идентификатором пакета и версией.
- Я вижу двоичный файл демона в списке «Всегда разрешать доступ этим приложениям» на вкладке «Контроль доступа» элемента пароля в Keychain Access.app.
- Если я вручную переключаюсь на «Разрешить всем приложениям доступ к этому элементу» в Keychain Access, он работает. Однако это несколько лишает смысла сохранение пароля в связке ключей.
- Мы пробовали поиграть с параметрами
SecKeychainAddInternetPassword
, но, похоже, это не имело никакого значения. - Мы пробовали явно разблокировать связку ключей с помощью
SecKeychainUnlock()
, но, как предполагает документация, это кажется излишним. - Удаление элемента в
Keychain Access.app
приводит к тому, чтоSecKeychainFindInternetPassword()
даетerrSecItemNotFound
, как и следовало ожидать. Таким образом, он определенно может найти сохраненный элемент, но ему просто не разрешено его читать.
Документацию связки ключей не совсем легко читать, а отчасти она довольно тавтологична. («Чтобы сделать Y, вам нужно сделать Y», не говоря уже о том, почему вы хотите сделать Y.) Тем не менее, я думаю, что я прошел через это и понял большую часть из этого. Различные аспекты нашей конкретной настройки не рассматриваются подробно (доступ от демона), но кажется довольно очевидным, что доступ к элементу, ранее сохраненному тем же приложением, не требует какой-либо специальной авторизации или аутентификации. Это прямо противоречит тому поведению, которое мы наблюдаем.
Любые идеи?