Keychain сервисы в OS X

В этом разделе описываются и иллюстрируются основные функции Keychain Services в OS X. Для использования Keychain Services в iOS, см. "Keychain сервисы в iOS".

Функции, описанные в этом разделе позволяют:

  • Добавлять пароль в keychain
  • Искать пароль в keychain
  • Получать данные и атрибуты из ячейки keychain
  • Менять данные и атрибуты в ячейке keychain
  • Разблокировать keychain
  • Блокировать keychain
  • Подавлять автоматическое отображение создаваемых keychain или открытие диалогов keychain
  • Добавлять доверенные приложения в список контроля доступа к keychain элементам

Добавление простых сервисов Keychain в ваше приложение

Большинство приложений должны использовать Keychain Services только, чтобы добавить новый пароль в keychain или получить пароль, когда это необходимо. Keychain Services предоставляет следующие пары функций для решения этих задач:

  • SecKeychainAddInternetPassword и SecKeychainFindInternetPassword (для интернет паролей).
  • SecKeychainAddGenericPassword и SecKeychainFindGenericPassword (для общих паролей).

Вы можете использовать интернет-пароли для доступа к серверам и веб-сайтам в интернете, а общие пароли для любой другой защищенной паролем службы (например, базы данных или приложения расписаний). AppleShare пароли (то есть, Keychain элементы с классом кода kSecAppleSharePasswordItemClass) хранятся в виде общих паролей.

Примечание: AppleShare пароли, созданные с помощью функции KCAddAppleSharePassword Keychain менеджера хранятся в виде Интернет-паролей. Вы можете использовать функцию SecKeychainAddInternetPassword для сохранения пароля AppleShare, но она появится в утилите "Связка ключей" в качестве интернет-пароля. Чтобы создать элемент Keychain с классом кода kSecAppleSharePasswordItemClass, необходимо использовать API низкого уровня.

"Find" функции выполняют поиск и в случае успеха возвращают данные или атрибуты, под которые выделяется буферная память, соответственно после использования этих функций необходимо освободить эту память с помощью функции SecKeychainItemFreeContent. В случае если поиск не увенчался успехом, буферы в памяти не выделяются и освобождающую функцию вызывать ненадо.

Рисунок ниже показывает алгоритм, как приложение может использовать эти функции, чтобы получить доступ к FTP интернет-серверу.

Пример сохранения пароля при подключении к базе данных:


- (void) tryConnect:(NSString*)username
{
    NSString* password = @"";
    const char* uname = [username UTF8String]; // имя пользователя
    UInt32 len1 = (UInt32)strlen(uname); // длинна имени пользователя
    UInt32 pswLen = 0; 
    void* pswData = NULL;
    
    OSStatus osstatus = SecKeychainFindGenericPassword(NULL, // keychain по умолчанию
     10, // длинна имени сервиса
     "youAppName", // имя сервиса
     len1, //длинна имени аккаунта
     uname, // имя аккаунта
     &pswLen, // длинна пароля, возвращаемое значение
     &pswData, // данные пароля, возвращаемое значение
     NULL);
     
   if(osstatus == noErr){ // если пароль был сохранен
       password = [[NSString alloc] initWithBytes:pswData length:pswLen encoding:NSUTF8StringEncoding];
       SecKeychainItemFreeContent(NULL, pswData); // освобождаем выделенный буфер
   }
     
   // вызываем диалог для ввода пароля и заполняем поле для пароля значением password
     
   // если соединение прошло успешно, сохраняем пароль
   if(connected){
       pswData = (void*)[password UTF8String];
       pswLen = (UInt32)strlen((const char*)pswData);
         
       osstatus = SecKeychainAddGenericPassword(NULL, 
           10, // длинна имени сервиса
           "youAppName", // имя сервиса
           len1, //длинна имени аккаунта
           uname, // имя аккаунта
           pswLen, // длинна пароля
           pswData, // данные пароля
           NULL);
   }
}
Внимание! Чтобы линкер не плевался ошибками необходимо включить в состав проекта Security.framework
security.framework

 

Создание пользовательского Keychain элемента

В этом разделе рассказывается, как создать элемент Keychain, если вы хотите все контролировать на более низком уровне, чем это обеспечивается функциями SecKeychainAddInternetPassword или SecKeychainAddGenericPassword. Например, вы могли бы хотеть чтобы приложение "Связка ключей" отображало пользовательскую метку для вашего элемента Keychain или вы, возможно, захотите указать более одного доверенного приложения, которые могут иметь доступ к элементу. В частности, в этом разделе показано использование функции SecKeychainItemCreateFromContent для создания элемента Keychain и функцию SecAccessCreate для создания списка доступа.

Если Вы используете функцию SecKeychainAddInternetPassword или SecKeychainAddGenericPassword, создается метка для keychain элемента автоматически, для интернет пароля используется URL без схемы, двоеточия и слешей, например для http://www.macbug.ru создастся элемент keychain с меткой www.macbug.ru. Для общего пароля имя элемента keychain берется из параметра serviceName. Если Вы захотите использовать специфичное имя для элемента keychain, его можно задать в атрибуте kSecLabelItemAttr при вызове SecKeychainItemCreateFromContent.

Функции SecKeychainAddInternetPassword и SecKeychainAddGenericPassword создают для вас первоначальный список доступа. Этот список доступа по умолчанию включает в себя только одну доверенную программу (то есть, одно приложение, которое может получить доступ к keychain элементу всякий раз, когда keychain разблокирован), а именно приложение, создавшее элемент keychain. Функция SecKeychainItemCreateFromContent принимает список доступа в качестве входных данных. Тем не менее, если вы передаете NULL, эта функция создает список доступа, состоящий из одного приложения, вызывающего функцию. Таким образом, необходимо вызвать функцию SecAccessCreate перед вызовом функции SecKeychainItemCreateFromContent если вы хотите список доступа с более чем одним доверенным приложением.

Создание элемента keychain с пользовательскими атрибутами
#import <Foundation/Foundation.h>
 
#include <Security/Security.h>
 
SecAccessRef createAccess(NSString *accessLabel)
{
    OSStatus err;
    SecAccessRef access=nil;
    NSArray *trustedApplications=nil;
 
     // Создадим список исключений доверенных программ, т. е. 
     // приложений, которым разрешен доступ к элементу keychain без 
     // запроса подтверждения пользователя:
    SecTrustedApplicationRef myself, someOther;
 
    //Создание доверенных ссылок приложений
    err = SecTrustedApplicationCreateFromPath(NULL, &myself);
    err = err ?: SecTrustedApplicationCreateFromPath("/Applications/Mail.app",
                                                            &someOther);
 
    if (err == noErr) {
        trustedApplications = [NSArray arrayWithObjects:(__bridge_transfer id)myself,
                                                    (__bridge_transfer id)someOther, nil];
    }
 
    //Создание объектов доступа:
    err = err ?: SecAccessCreate((__bridge CFStringRef)accessLabel,
                            (__bridge CFArrayRef)trustedApplications, &access);
    if (err) return nil;
 
    return access;
}
 
 
OSStatus addInternetPassword(NSString *password, NSString *account,
                    NSString *server, NSString *itemLabel, NSString *path,
                    SecProtocolType protocol, int port)
{
    OSStatus err;
    SecKeychainItemRef item = nil;
    const char *pathUTF8 = [path UTF8String];
    const char *serverUTF8 = [server UTF8String];
    const char *accountUTF8 = [account UTF8String];
    const char *passwordUTF8 = [password UTF8String];
    const char *itemLabelUTF8 = [itemLabel UTF8String];
 
    //Создаtv первоначальные параметры контроля доступа для элемента:
    SecAccessRef access = createAccess(itemLabel);
 
    //Ниже приводится низко-уровневый эквивалент
   // SecKeychainAddInternetPassword функции:
 
    assert(strlen(itemLabelUTF8) <= 0xffffffff);
    assert(strlen(accountUTF8) <= 0xffffffff);
    assert(strlen(serverUTF8) <= 0xffffffff);
    assert(strlen(pathUTF8) <= 0xffffffff);
 
    //Настройте вектор атрибутов (каждый атрибут включает
    // {tag, length, pointer}):
    SecKeychainAttribute attrs[] = {
        { kSecLabelItemAttr, (UInt32)strlen(itemLabelUTF8), (char *)itemLabelUTF8 },
        { kSecAccountItemAttr, (UInt32)strlen(accountUTF8), (char *)accountUTF8 },
        { kSecServerItemAttr, (UInt32)strlen(serverUTF8), (char *)serverUTF8 },
        { kSecPortItemAttr, sizeof(int), (int *)&port },
        { kSecProtocolItemAttr, sizeof(SecProtocolType),
                                        (SecProtocolType *)&protocol },
        { kSecPathItemAttr, (UInt32)strlen(pathUTF8), (char *)pathUTF8 }
    };
    SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]),
                                            attrs };
 
 
    assert(strlen(passwordUTF8) <= 0xffffffff);
 
    err = SecKeychainItemCreateFromContent(
        kSecInternetPasswordItemClass,
        &attributes,
        (UInt32)strlen(passwordUTF8),
        passwordUTF8,
        NULL, // use the default keychain
        access,
        &item);
 
    if (access) CFRelease(access);
    if (item) CFRelease(item);
 
    return err;
}
 
 
int tryAdd(void)
{
         //Добавим в качестве примера пароля для keychain:
    return addInternetPassword(@"sample password", @"sample account",
            @"samplehost.apple.com", @"sampleName", @"cgi-bin/bogus/testpath",
                                                kSecProtocolTypeHTTP, 8080);
}

Изменение списка доступа к существующему элементу Keychain

По умолчанию список доступа элемента keychain содержит только приложение, создавшее элемент keychain. Предидущий листинг иллюстрирует создание интернет элемента keychain со списком доступа, который включает в себя два доверенных приложения. Следующий листинг покажет, как изменить существующий элемент keychain. Перечисление находит конкретный элемент keychain, извлекает объект доступа, в том числе список доверенных приложений, теги авторизации и другую информацию, добавляет доверенное приложение в список, воссоздает объект доступа и сохраняет новый объект доступа к этому элементу keychain. Хотя этот пример иллюстрирует изменение списка доверенных приложений, вы можете использовать аналогичную последовательность для изменения любой информации в объекте-например доступа, чтобы добавить или удалить тег авторизации.

Ниже приведенный листинг начинается с вызыва функции SecKeychainSearchCreateFromAttributes, создающей ссылку поиска. В этом примере, код ищет элемент keychain по его метке (как видно в приложении "Связка ключей"), вы можете искать другие атрибуты, такие как дата изменения. Используя эту ссылку поиска, данный пример вызывает SecKeychainSearchCopyNext чтобы найти элемент keychain.

Далее листинг вызывает SecKeychainItemCopyAccess для извлечения объекта доступа к этому элементу keychain. Объект доступа включает в себя одну или несколько записей ACL. Перечисление вызывает функцию SecAccessCopySelectedACLList и передает ей значение тега авторизации CSSM_ACL_AUTHORIZATION_DECRYPT. Этот тег авторизации является одним из тегов, обычно связанных со списком доступа, используемым для чувствительных операций и список отображается Keychain приложением доступа. Чтобы получить все записи ACL для объекта доступа, воспользуйтесь функцией SecAccessCopyACLList вместо этой.

Функция SecAccessCopySelectedACLList возвращает объект CFArrayRef, содержащий все записи ACL, которые отвечают критериям отбора. В этом случае код подразумевает, что должна быть только одна запись ACL, которая использует CSSM_ACL_AUTHORIZATION_DECRYPT метку. Перечисление вызывает CFArrayGetValues функцию для создания объектов C массива SecACLRef из CFArrayRef а затем вызывает функцию SecACLCopySimpleContents, передавая ей первый элемент массива. Функция SecACLCopySimpleContents также извлекает CFArrayRef, содержащий список доверенных приложений, строку c описанием элемента keychain , и оперативный флаг селектора. Эти значения необходимы для того, чтобы воссоздать запись ACL после добавления доверенного приложения в список.

Далее, листинг использует функцию CFArrayGetValues​​, на этот раз, чтобы извлечь массив доверенных приложений из CFArrayRef. Перечисление вызывает функцию SecTrustedApplicationCreateFromPath чтобы создать новый доверенный объект приложения, добавляет новое приложение в список, и вызывает CFArrayCreate для создания нового CFArrayRef.

Перед добавлением новой записи ACL в объект доступа, список вызывает функцию SecACLGetAuthorizations чтобы получить список тегов авторизации от старого объекта доступа. Тогда, добыв всю информацию со старого входа ACL, код вызывает SecACLRemove функцию, чтобы удалить старую запись ACL из объекта доступа. Перечисление затем вызывает функцию SecACLCreateFromSimpleContents, чтобы создать новую запись ACL для объекта доступа. Эта функция автоматически добавляет запись к объекту доступа; для этой цели не существует отдельного вызова. Новая запись ACL имеет только список прав по умолчанию, однако, таким образом листинг вызывает функцию SecACLSetAuthorizations, предавая в список авторизации, извлеченное из старой записи ACL.

Объект доступа теперь завершен, с новой записью ACL, содержащей все доверенные приложения из старой записи плюс новой добавленной здесь. Остаются только два шага: ​​во-первых, список вызывает функцию SecKeychainItemSetAccess, чтобы заменить объект доступа в элементе keychain на новый объект доступа; затем листинг вызывает функцию CFRelease для каждого объекта core foundation, который больше не нужен, для того, чтобы освободить память.

#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <CoreServices/CoreServices.h>
 
 
//Получить ACL из CFArray:
SecACLRef GetACL (CFIndex numACLs, CFArrayRef ACLList,
                CFArrayRef *applicationList, CFStringRef *description,
                SecKeychainPromptSelector *promptSelector)
{
    OSStatus status;
// Поскольку мы ограничили наш поиск ACL, используемых для расшифровки, мы
// ожидаем только один ACL для этого элемента. Таким образом, мы извлекаем
// список приложений из первого ACL в массиве.
    const SecACLRef acl = (SecACLRef) CFArrayGetValueAtIndex(ACLList, 0);
    status = SecACLCopyContents (
        acl,                    //  ACL  из которого извлекается 
                                 // список доверенный приложений
        applicationList,        // список доверенный приложений
        description,            // строка описания
        promptSelector          // значение оперативного флага селектора
                                        );
 
    if (status == noErr) {
        return acl;
    } else {
        return NULL;
    }
}
 
 
int modifyTheACL(void)
{
    OSStatus status;
 
    SecKeychainSearchRef searchReference = NULL;
    SecKeychainItemRef itemRef = NULL;
 
    SecAccessRef itemAccess = NULL;
    SecACLRef oldACL = NULL, newACL = NULL;
 
    CFIndex arrayCount;
    CFRange arrayRange;
    SecTrustedApplicationRef trustedAppArray[10];
    SecKeychainPromptSelector promptSelector;
    CFStringRef description = NULL;
    CFArrayRef newTrustedAppArray = NULL;
 
    const char *path = "/Applications/Mail.app";       //path to
                                        // trusted app to add to ACL
    SecTrustedApplicationRef trustedApp = NULL;
 
    /* Construct a search dictionary to find the desired item. */
    const void *keys[] = {
    	kSecAttrLabel,
        kSecReturnRef, /* return the item. */
        NULL
    };
    const void *values[] = {
        /* kSecAttrLabel => */ CFSTR("www.TestItem.com"),
        /* kSecReturnRef => */ kCFBooleanTrue,
        NULL
    };
 
    CFDictionaryRef searchDict = CFDictionaryCreate(kCFAllocatorDefault,
        keys,
        values,
        sizeof(keys) / sizeof(keys[0]),
        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 
     CFArrayRef aclList = NULL;
     CFIndex numACLs = 0;
     CFArrayRef applicationList;
     CFTypeRef authorizationTag =
        kSecACLAuthorizationDecrypt;  // тэг авторизации для поиска
 
    status = SecItemCopyMatching(searchDict, (CFTypeRef *)&itemRef);
 
    if (status == noErr)
        {
        // Получаем ссылку на объект элемента keychain
        // результат SecAccessRef, должен быть освобожден после
        //  того, как мы его больше не используем.
        status = SecKeychainItemCopyAccess (itemRef, &itemAccess);
        // получаем массив из ACL для объекта доступа.
        // критерий поиска для ACL задан тэгом авторизации
        aclList = SecAccessCopyMatchingACLList(itemAccess,
                                     authorizationTag);
        numACLs = CFArrayGetCount (aclList);
        
        oldACL = GetACL (numACLs, aclList, &applicationList,
                                    &description, &promptSelector);
        if (oldACL) { CFRetain(oldACL); }
        arrayCount = CFArrayGetCount (applicationList);
 
        arrayRange.location = (CFIndex) 0;
        arrayRange.length = arrayCount;
        CFArrayGetValues (applicationList, arrayRange,
                                            (void *) trustedAppArray);
        // Создаем новый объект ссылку на новое доверенное приложение
        status = status ?: SecTrustedApplicationCreateFromPath (path, &trustedApp);
        if (status == noErr)   // функция терпит неудачу, если приложение не найдено
        {
            // Добавляем новое приложение в массив и создаем новый CFArray.
            trustedAppArray[arrayCount] = trustedApp;
            newTrustedAppArray = CFArrayCreate (NULL,
                                (void *)trustedAppArray, arrayCount+1,
                                                &kCFTypeArrayCallBacks);
            // получаем авторизации из старого ACL.
            CFArrayRef authorizations = SecACLCopyAuthorizations(oldACL);
 
            //Удаляем старый ACL из списка доверенных приложений.
            // Пользователь запрашивается на разрешение данной операции
            status = status ?: SecACLRemove (oldACL);
 
            // Создаем новый ACL с значениями старого ACL и новым 
            status = status ?: SecACLCreateWithSimpleContents (itemAccess,
                        newTrustedAppArray, description, promptSelector,
                                                            &newACL);
            
            status = status ?: SecACLUpdateAuthorizations (newACL, authorizations);
 
            // Заменим объект доступа в keychain элементае на
            // новый объект доступа. Пользователь запрашивается на разрешение данной операции
            status = status ?: SecKeychainItemSetAccess (itemRef, itemAccess);
 
            CFRelease(authorizations);
        }
        else {
            // Сообщение об ошибке приложение не найдено.
            // ...
        }
 
    // Освобождаем объекты нами выделенные или полученные
    if (searchReference)
        CFRelease(searchReference);     //SecKeychainSearchRef
    if (itemRef)
        CFRelease(itemRef);             //SecKeychainItemRef
    if (itemAccess)
        CFRelease(itemAccess);          //SecAccessRef
    if (oldACL)
        CFRelease(oldACL);              //SecACLRef
    if (newACL)
        CFRelease(newACL);              //SecACLRef
    if (description)
        CFRelease(description);         //CFStringRef
    if (newTrustedAppArray)
        CFRelease(newTrustedAppArray);  //CFArrayRef
    if (trustedApp)
        CFRelease(trustedApp);          //SecTrustedApplicationRef
    if (aclList)
        CFRelease(aclList);             //CFArrayRef
    if (applicationList)
        CFRelease(applicationList);     //CFArrayRef
    }
    return (status);
 
}
 
 
homeЗаметили ошибкукарта сайта 
   Made on a Mac