Проверка получений локально

Выполните проверку получения сразу после того, как Ваше приложение будет запущено, прежде, чем вывести на экран любой пользовательский интерфейс или породить любые дочерние процессы. Реализуйте эту регистрацию main функция, перед NSApplicationMain функция вызвана. В то время как приложение работает, для дополнительной безопасности можно периодически повторять эту проверку.

Найдите и проанализируйте получение

Когда приложение установлено от App Store, оно содержит получение приложения, криптографически подписывающееся, гарантируя, что только Apple может создать подлинные расписки. Получение сохранено в комплекте приложений. Вызовите appStoreReceiptURL метод NSBundle класс для определения местоположения получения.

Получение является двоичным файлом со структурой, показанной на рисунке 1-1.

  Структура рисунка 1-1 получения

Наиболее удаленная часть (маркированное Получение в числе) является контейнером № 7 PKCS, как определено RFC 2315, с его полезной нагрузкой, закодированной с помощью ASN.1 (Абстрактная синтаксическая нотация Одна), как определено ITU-T X.690. Полезная нагрузка составлена из ряда атрибутов получения. Каждый атрибут получения содержит тип, версию и значение.

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

Для получения информации о ключах, найденных в получении, посмотрите Поля Получения.

Для генерации кода сохраните описание полезной нагрузки, показанное в Перечислении 1-1 файлу и, в Терминале, выполните следующую команду:

asn1c -fnative-types filename

После asn1c инструмент заканчивает генерировать файлы в текущем каталоге, добавьте файлы, которые это генерировало к Вашему проекту XCode.

  Определение перечисления 1-1 ASN.1 формата полезной нагрузки

ReceiptModule DEFINITIONS ::=
BEGIN
 
ReceiptAttribute ::= SEQUENCE {
    type    INTEGER,
    version INTEGER,
    value   OCTET STRING
}
 
Payload ::= SET OF ReceiptAttribute
 
END

Вычислите хеш GUID

В OS X используйте метод, описанный в, Заставляют GUID в OS X выбирать GUID компьютера.

В iOS используйте значение, возвращенное identifierForVendor свойство UIDevice как GUID компьютера.

Для вычислений хеша сначала свяжите значение GUID с непрозрачным значением (атрибут типа 4) и идентификатор пакета. Используйте необработанные байты после получения, не выполняя строковой интерпретации UTF-8 или нормализации. Тогда вычислите хеш SHA 1 этого связанного ряда байтов.

Проверьте получение

Для проверки получения выполните следующие тесты в порядке:

  1. Найдите получение.

    Если никакое получение не присутствует, сбои проверки.

  2. Проверьте, что получение должным образом подписывается Apple.

    Если это не подписывается Apple, сбоями проверки.

  3. Проверьте, что идентификатор пакета в получении соответствует трудно кодированную константу, содержащую CFBundleIdentifier значение Вы ожидаете в Info.plist файл.

    Если они не соответствуют, сбои проверки.

  4. Проверьте, что строка идентификатора версии в получении соответствует трудно кодированную константу, содержащую CFBundleShortVersionString значение Вы ожидаете в Info.plist файл.

    Если они не соответствуют, сбои проверки.

  5. Вычислите хеш GUID, как описано в, Вычисляют Хеш GUID.

    Если результат не соответствует хеш в получении, сбоях проверки.

Если вся тестовая передача, передачи проверки.

Если Ваши поддержки приложений Программа Закупки Объема, проверьте дату истечения срока получения.

Реагируйте на отказ проверки получения

Проверка может перестать работать по ряду причин. Например, когда пользователи копируют Ваше приложение от одного Mac до другого, GUID больше не соответствует, заставляя проверку получения перестать работать.

Выход, если сбои проверки в OS X

Если сбои проверки в OS X, вызвать exit с состоянием 173. Этот статус выхода уведомляет систему, что Ваше приложение решило, что его получение недопустимо. В этой точке система пытается получить подлинную расписку и может запросить учетные данные iTunes пользователя.

Если система успешно получает подлинную расписку, она повторно запускает приложение. Иначе, это выводит на экран сообщение об ошибке пользователю, объясняя проблему.

Не выводите на экран сообщение об ошибке пользователю, если проверка перестала работать. Система ответственна за попытку получить подлинную расписку или информирование пользователя, что получение не допустимо.

Обновите Получение Если Сбои Проверки в iOS

Если сбои проверки в iOS, используйте SKReceiptRefreshRequest класс для обновления получения.

Не пытайтесь завершить приложение. В Вашей опции можно дать пользователю льготный период или ограничить функциональность в приложении.

Установите минимальную версию системы для приложений Mac

Включайте LSMinimumSystemVersion ключ со значением 10.6.6 или больше в Вашем приложении Info.plist файл. Если проверка получения перестала работать на версиях OS X ранее, чем 10.6.6, Ваши выходы приложения сразу после запуска без объяснения пользователю. Более ранние версии OS X не интерпретируют статус выхода 173, таким образом, они не пытаются получить подлинную расписку или вывести на экран любое сообщение об ошибке.

Не локализуйте свой номер версии

Если Ваше приложение локализуется, CFBundleShortVersionString ключ не должен появляться ни в одном Вашем приложении InfoPlist.strings файлы. Нелокализованное значение от Вашего Info.plist файл хранится в получении — пытающийся локализовать значение для этого ключа, может заставить проверку получения перестать работать.

Защитите свою проверку проверки

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

Тест во время процесса разработки

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

  1. Удостоверьтесь, что у Вас есть доступ в Интернет, таким образом, можно соединиться с серверами Apple.

  2. Запустите свое приложение путем двойного щелчка по нему (или в некотором роде заставьте Launch Services запускать его).

После запуска приложения следующее происходит:

С этим установленным получением разработки можно запустить приложение любым методом — например, с gdb или отладчик XCode.

Проверьте покупки в приложении

Для проверки закупки в приложении приложение выполняет следующие тесты в порядке:

  1. Проанализируйте и проверьте получение приложения, как описано в предыдущих разделах.

    Если получение не допустимо, ни одни из покупок в приложении не допустимы.

  2. Проанализируйте получения закупки в приложении (значения для атрибутов типа 17).

    Каждое получение закупки в приложении состоит из ряда атрибутов, как получение приложения делает. Структура для этих получений определяется в Перечислении 1-2. Анализируя получение, можно генерировать часть кода из описания ASN.1 с помощью asn1c инструмент. Проигнорируйте все атрибуты с типами, не появляющимися в таблице — они резервируются для использования системой, и их содержание может измениться в любое время.

    Для получения информации о полях в получении посмотрите Поля Получения.

  3. Исследуйте идентификатор продукта на каждую закупку в приложении, подтверждают получение и добавляют соответствующую функциональность или содержание в Вашем приложении. Для получения информации о том, как вычислить активный период подписки, посмотрите Работу с Подписками.

Если проверка получения закупки в приложении перестала работать, Ваше приложение просто не добавляет функциональность или содержание.

  Определение перечисления 1-2 ASN.1 закупки в приложении подтверждает получение формата

InAppAttribute ::= SEQUENCE {
    type                   INTEGER,
    version                INTEGER,
    value                  OCTET STRING
}
 
InAppReceipt ::= SET OF InAppAttribute

Когда закупка повторно загружается, атрибуты для исходного идентификатора транзакции и исходной даты транзакции используются. Повторно загруженной закупке дают новый идентификатор транзакции, но она содержит идентификатор и дату исходной закупки.

Подсказки по реализации

Этот раздел содержит несколько листингов кода для Вашей ссылки, поскольку Вы реализуете проверку получения.

Получите GUID в OS X

В OS X следуйте за моделью в Перечислении 1-3 (или даже используйте это точное тот же код), так, чтобы метод, который Вы используете для получения GUID в коде доступа, был точно тем же как методом, используемым, когда создавалось получение приложения.

Перечисление 1-3  Получает GUID компьютера

#import <IOKit/IOKitLib.h>
#import <Foundation/Foundation.h>
 
// Returns a CFData object, containing the computer's GUID.
CFDataRef copy_mac_address(void)
{
    kern_return_t             kernResult;
    mach_port_t               master_port;
    CFMutableDictionaryRef    matchingDict;
    io_iterator_t             iterator;
    io_object_t               service;
    CFDataRef                 macAddress = nil;
 
    kernResult = IOMasterPort(MACH_PORT_NULL, &master_port);
    if (kernResult != KERN_SUCCESS) {
        printf("IOMasterPort returned %d\n", kernResult);
        return nil;
    }
 
    matchingDict = IOBSDNameMatching(master_port, 0, "en0");
    if (!matchingDict) {
        printf("IOBSDNameMatching returned empty dictionary\n");
        return nil;
    }
 
    kernResult = IOServiceGetMatchingServices(master_port, matchingDict, &iterator);
    if (kernResult != KERN_SUCCESS) {
        printf("IOServiceGetMatchingServices returned %d\n", kernResult);
        return nil;
    }
 
    while((service = IOIteratorNext(iterator)) != 0) {
        io_object_t parentService;
 
        kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane,
                &parentService);
        if (kernResult == KERN_SUCCESS) {
            if (macAddress) CFRelease(macAddress);
 
            macAddress = (CFDataRef) IORegistryEntryCreateCFProperty(parentService,
                    CFSTR("IOMACAddress"), kCFAllocatorDefault, 0);
            IOObjectRelease(parentService);
        } else {
            printf("IORegistryEntryGetParentEntry returned %d\n", kernResult);
        }
 
        IOObjectRelease(service);
    }
    IOObjectRelease(iterator);
 
    return macAddress;
}

Проанализируйте получение и заверьте его подпись

Используйте следующие листинги кода в качестве схемы одной возможной реализации использования проверки получения OpenSSL и asn1c. Эти списки предоставлены для руководства Вас, поскольку Вы пишете свой собственный код, путем выделения соответствующего APIs и структур данных, для не использования в качестве решения копии-и-вставки.

Если Вы используете OpenSSL, статически соединяете Ваш двоичный файл против него. Динамическое подключение против OpenSSL осуждается и приводит к предупреждениям сборки.

Удостоверьтесь, что Ваш код делает следующий, как обрисовано в общих чертах в списках:

  1. Заверьте подпись (Перечисление 1-4).

  2. Проанализируйте полезную нагрузку (Перечисление 1-5).

  3. Извлеките (Перечисление 1-6) атрибутов получения.

  4. Вычислите хеш GUID (Перечисление 1-7).

Перечисление 1-4  Проверяет использование подписи OpenSSL

/* The PKCS #7 container (the receipt) and the output of the verification. */
BIO *b_p7;
PKCS7 *p7;
 
/* The Apple root certificate, as raw data and in its OpenSSL representation. */
BIO *b_x509;
X509 *Apple;
 
/* The root certificate for chain-of-trust verification. */
X509_STORE *store = X509_STORE_new();
 
/* ... Initialize both BIO variables using BIO_new_mem_buf() with a buffer and its size ... */
 
/* Initialize b_out as an output BIO to hold the receipt payload extracted during signature verification. */
BIO *b_out = BIO_new(BIO_s_mem());
 
/* Capture the content of the receipt file and populate the p7 variable with the PKCS #7 container. */
p7 = d2i_PKCS7_bio(b_p7, NULL);
 
/* ... Load the Apple root certificate into b_X509 ... */
 
/* Initialize b_x509 as an input BIO with a value of the Apple root certificate and load it into X509 data structure. Then add the Apple root certificate to the structure. */
Apple = d2i_X509_bio(b_x509, NULL);
X509_STORE_add_cert(store, Apple);
 
/* Verify the signature. If the verification is correct, b_out will contain the PKCS #7 payload and rc will be 1. */
int rc = PKCS7_verify(p7, NULL, store, NULL, b_out, 0);
 
/* For additional security, you may verify the fingerprint of the root certificate and verify the OIDs of the intermediate certificate and signing certificate. The OID in the certificate policies extension of the intermediate certificate is (1 2 840 113635 100 5 6 1), and the marker OID of the signing certificate is (1 2 840 113635 100 6 11 1). */

  Синтаксический анализ перечисления 1-5 полезная нагрузка с помощью asn1c

#include "Payload.h" /* This header file is generated by asn1c. */
 
/* The receipt payload and its size. */
void *pld = NULL;
size_t pld_sz;
 
/* Variables used to parse the payload. Both data types are declared in Payload.h. */
Payload_t *payload = NULL;
asn_dec_rval_t rval;
 
/* ... Load the payload from the receipt file into pld and set pld_sz to the payload size ... */
 
/* Parse the buffer using the decoder function generated by asn1c.  The payload variable will contain the receipt attributes. */
rval = asn_DEF_Payload.ber_decoder(NULL, &asn_DEF_Payload, (void **)&payload, pld, pld_sz, 0);

  Выдержка перечисления 1-6 атрибуты получения

/* Variables used to store the receipt attributes. */
OCTET_STRING_t *bundle_id = NULL;
OCTET_STRING_t *bundle_version = NULL;
OCTET_STRING_t *opaque = NULL;
OCTET_STRING_t *hash = NULL;
 
/* Iterate over the receipt attributes, saving the values needed to compute the GUID hash. */
size_t i;
for (i = 0; i < payload->list.count; i++) {
   ReceiptAttribute_t *entry;
 
   entry = payload->list.array[i];
 
   switch (entry->type) {
     case 2:
       bundle_id = &entry->value;
       break;
     case 3:
       bundle_version = &entry->value;
       break;
     case 4:
       opaque = &entry->value;
       break;
     case 5:
       hash = &entry->value;
       break;
    }
  }

Перечисление 1-7  Вычисляет хеш GUID

/* The GUID returned by copy_mac_address() is a CFDataRef.  Use CFDataGetBytePtr() and CFDataGetLength() to get a pointer to the bytes that make up the GUID and to get its length. */
UInt8 *guid = NULL;
size_t guid_sz;
 
/* Declare and initialize an EVP context for OpenSSL. */
EVP_MD_CTX evp_ctx;
EVP_MD_CTX_init(&evp_ctx);
 
/* A buffer for result of the hash computation. */
UInt8 digest[20];
 
/* Set up the EVP context to compute a SHA-1 digest. */
EVP_DigestInit_ex(&evp_ctx, EVP_sha1(), NULL);
 
/* Concatenate the pieces to be hashed.  They must be concatenated in this order. */
EVP_DigestUpdate(&evp_ctx, guid, guid_sz);
EVP_DigestUpdate(&evp_ctx, opaque->buf, opaque->size);
EVP_DigestUpdate(&evp_ctx, bundle_id->buf, bundle_id->size);
 
/* Compute the hash, saving the result into the digest variable. */
EVP_DigestFinal_ex(&evp_ctx, digest, NULL);