Связка ключей Xamarin.iOS зависает при попытке запросить ключ, хранящий NSData

У меня есть развернутое корпоративное приложение, которое использует OpenID Connect для аутентификации. После аутентификации я сохраняю объект AuthState в цепочке ключей iOS в целях безопасности и чтобы иметь возможность позже войти в систему с помощью только Face/Touch ID (учитывая, что токен обновления в AuthState все еще действителен). Объект AuthState выглядит так:

namespace OpenId.AppAuth
{
    [Register ("OIDAuthState", true)]
    public class AuthState : NSObject, INSCoding, INativeObject, IDisposable, INSSecureCoding
    {
        [CompilerGenerated]
        private static readonly IntPtr class_ptr = Class.GetHandle ("OIDAuthState");

        [CompilerGenerated]
        private object __mt_ErrorDelegate_var;

        [CompilerGenerated]
        private object __mt_StateChangeDelegate_var;

        public override IntPtr ClassHandle {
            get;
        }

        [CompilerGenerated]
        public virtual NSError AuthorizationError {
            [Export ("authorizationError")]
            get;
        }

        [CompilerGenerated]
        public virtual IAuthStateErrorDelegate ErrorDelegate {
            [Export ("errorDelegate", ArgumentSemantic.Weak)]
            get;
            [Export ("setErrorDelegate:", ArgumentSemantic.Weak)]
            set;
        }

        [CompilerGenerated]
        public virtual bool IsAuthorized {
            [Export ("isAuthorized")]
            get;
        }

        [CompilerGenerated]
        public virtual AuthorizationResponse LastAuthorizationResponse {
            [Export ("lastAuthorizationResponse")]
            get;
        }

        [CompilerGenerated]
        public virtual RegistrationResponse LastRegistrationResponse {
            [Export ("lastRegistrationResponse")]
            get;
        }

        [CompilerGenerated]
        public virtual TokenResponse LastTokenResponse {
            [Export ("lastTokenResponse")]
            get;
        }

        [CompilerGenerated]
        public virtual string RefreshToken {
            [Export ("refreshToken")]
            get;
        }

        [CompilerGenerated]
        public virtual string Scope {
            [Export ("scope")]
            get;
        }

        [CompilerGenerated]
        public virtual IAuthStateChangeDelegate StateChangeDelegate {
            [Export ("stateChangeDelegate", ArgumentSemantic.Weak)]
            get;
            [Export ("setStateChangeDelegate:", ArgumentSemantic.Weak)]
            set;
        }

        public static IAuthorizationFlowSession PresentAuthorizationRequest (AuthorizationRequest authorizationRequest, UIViewController presentingViewController, AuthStateAuthorizationCallback callback);

        [CompilerGenerated]
        [DesignatedInitializer]
        [EditorBrowsable (EditorBrowsableState.Advanced)]
        [Export ("initWithCoder:")]
        public AuthState (NSCoder coder)
            : base (NSObjectFlag.Empty);

        [CompilerGenerated]
        [EditorBrowsable (EditorBrowsableState.Advanced)]
        protected AuthState (NSObjectFlag t)
            : base (t);

        [CompilerGenerated]
        [EditorBrowsable (EditorBrowsableState.Advanced)]
        protected internal AuthState (IntPtr handle)
            : base (handle);

        [Export ("initWithAuthorizationResponse:")]
        [CompilerGenerated]
        public AuthState (AuthorizationResponse authorizationResponse)
            : base (NSObjectFlag.Empty);

        [Export ("initWithAuthorizationResponse:tokenResponse:")]
        [CompilerGenerated]
        public AuthState (AuthorizationResponse authorizationResponse, TokenResponse tokenResponse)
            : base (NSObjectFlag.Empty);

        [Export ("initWithRegistrationResponse:")]
        [CompilerGenerated]
        public AuthState (RegistrationResponse registrationResponse)
            : base (NSObjectFlag.Empty);

        [Export ("initWithAuthorizationResponse:tokenResponse:registrationResponse:")]
        [DesignatedInitializer]
        [CompilerGenerated]
        public AuthState (AuthorizationResponse authorizationResponse, TokenResponse tokenResponse, RegistrationResponse registrationResponse)
            : base (NSObjectFlag.Empty);

        [Export ("encodeWithCoder:")]
        [CompilerGenerated]
        [Preserve (Conditional = true)]
        public virtual void EncodeTo (NSCoder encoder);

        [Export ("performActionWithFreshTokens:")]
        [CompilerGenerated]
        public unsafe virtual void PerformWithFreshTokens ([BlockProxy (typeof(ObjCRuntime.Trampolines.NIDAuthStateAction))] AuthStateAction action);

        [Export ("performActionWithFreshTokens:additionalRefreshParameters:")]
        [CompilerGenerated]
        public unsafe virtual void PerformWithFreshTokens ([BlockProxy (typeof(ObjCRuntime.Trampolines.NIDAuthStateAction))] AuthStateAction action, NSDictionary<NSString, NSString> additionalParameters);

        [Export ("authStateByPresentingAuthorizationRequest:UICoordinator:callback:")]
        [CompilerGenerated]
        public unsafe static IAuthorizationFlowSession PresentAuthorizationRequest (AuthorizationRequest authorizationRequest, IAuthorizationUICoordinator UICoordinator, [BlockProxy (typeof(ObjCRuntime.Trampolines.NIDAuthStateAuthorizationCallback))] AuthStateAuthorizationCallback callback);

        [Export ("setNeedsTokenRefresh")]
        [CompilerGenerated]
        public virtual void SetNeedsTokenRefresh ();

        [Export ("tokenRefreshRequest")]
        [CompilerGenerated]
        public virtual TokenRequest TokenRefreshRequest ();

        [Export ("tokenRefreshRequestWithAdditionalParameters:")]
        [CompilerGenerated]
        public virtual TokenRequest TokenRefreshRequest (NSDictionary<NSString, NSString> additionalParameters);

        [Export ("updateWithAuthorizationResponse:error:")]
        [CompilerGenerated]
        public virtual void Update (AuthorizationResponse authorizationResponse, NSError error);

        [Export ("updateWithTokenResponse:error:")]
        [CompilerGenerated]
        public virtual void Update (TokenResponse tokenResponse, NSError error);

        [Export ("updateWithAuthorizationError:")]
        [CompilerGenerated]
        public virtual void Update (NSError authorizationError);

        [Export ("updateWithRegistrationResponse:")]
        [CompilerGenerated]
        public virtual void UpdateWithRegistrationResponse (RegistrationResponse registrationResponse);

        [CompilerGenerated]
        protected override void Dispose (bool disposing);
    }
}

До iOS 12.1 обновления все работало отлично

ПРОБЛЕМА: после установки обновления ios 12.1 только при работе в режиме выпуска (работает при отладке в режиме разработки при подключении к отладчику VS for Mac) запрашивается существующий ключ в цепочке ключей, хранящей двоичное NSData представление этот объект AuthState зависает, и в конечном итоге приложение закрывается из-за того, что оно не отвечает более 10 секунд.

Кто-нибудь сталкивался с подобными проблемами? Было бы удивительно, если бы кто-нибудь мог пролить свет на то, что здесь может происходить, или указать мне правильное направление.

Дополнительная информация:

Как я получаю двоичное представление NSData объекта AppAuth:

NSData authStateData = NSKeyedArchiver.ArchivedDataWithRootObject(authState);

Как я сохраняю этот NSData в связку ключей:

    var secAccess = new SecAccessControl(SecAccessible.WhenUnlockedThisDeviceOnly, SecAccessControlCreateFlags.UserPresence);
    var secRecord = new SecRecord(SecKind.GenericPassword)
    {
        Account = "keystring",
        Service = "ServiceNameString",
        Label = "keystring",
        ValueData = authStateData,
        AccessControl = secAccess
    };
    var result = SecKeyChain.Add(secRecord);

Как я запрашиваю существующие данные в keychain:

var searchRecord = new SecRecord(SecKind.GenericPassword)
{
    Service = ServiceName,
    Label = key,
};
var match = SecKeyChain.QueryAsRecord(searchRecord, out SecStatusCode resultCode);

Явных ошибок в журнале устройства нет, проверял. Было ли в iOS 12.1 изменение, которое я пропустил и которое существенно повлияло на это?

ОБНОВЛЕНИЕ: я переработал код, чтобы хранить в цепочке ключей только пароль ключа шифрования, а не весь объект AuthState, и вместо этого хранить сериализованный AuthState в зашифрованном виде в локальном файле. Все еще наблюдается та же проблема, в режиме отладки на устройстве все работает, пишет и читает из цепочки для ключей просто отлично, при работе без подключенного отладчика на том же устройстве, та же сборка, пишет нормально, при чтении зависает после успешной проверки TouchID/FaceID, есть ли ошибка в Xamarin.iOS SDK, которая не догнала некоторые изменения в последней версии iOS, вызывающие это?


person Dmitry Samuylov    schedule 27.11.2018    source источник


Ответы (2)


Вы должны включить право на совместное использование связки ключей.

в Права.plist

введите здесь описание изображения

А вот похожий случай, который вы можете ссылаться .

person Lucas Zhang    schedule 29.11.2018
comment
Спасибо за ответ, но я уверен, что это не так. У меня нет проблем с сохранением данных в цепочке для ключей, и вплоть до 12.0 он отлично работал и при их извлечении. Кроме того, все тесты, которые я делаю, выполняются на физическом устройстве, независимо от того, работает ли я в режиме отладки или в режиме выпуска. Я уже пытался явно включить это право, но безрезультатно. - person Dmitry Samuylov; 29.11.2018

Я исправил проблему, с которой я столкнулся, похоже, это было какое-то состояние гонки, которое блокировало приложение, поскольку вызов сохранения в цепочке ключей выполнялся в основном потоке. Обычно этот вызов выполняется очень быстро, и в прошлом никогда не было проблем с блокировкой чего-либо, но что-то в обновлении iOS 12.1 изменило это. В любом случае, я просто явно запустил код, сохраняющий пароль шифрования в связку ключей в фоновом потоке, и это решило проблему:

Task.Run(() =>
{
    var keychain = new KeyChain();
    keychain.SetValueForKey("securedvalue", "securedvaluekey");
}).ConfigureAwait(false);
person Dmitry Samuylov    schedule 30.11.2018