Проверка получений локально
Выполните проверку получения сразу после того, как Ваше приложение будет запущено, прежде, чем вывести на экран любой пользовательский интерфейс или породить любые дочерние процессы. Реализуйте эту регистрацию main
функция, перед NSApplicationMain
функция вызвана. В то время как приложение работает, для дополнительной безопасности можно периодически повторять эту проверку.
Найдите и проанализируйте получение
Когда приложение установлено от App Store, оно содержит получение приложения, криптографически подписывающееся, гарантируя, что только Apple может создать подлинные расписки. Получение сохранено в комплекте приложений. Вызовите appStoreReceiptURL
метод NSBundle
класс для определения местоположения получения.
Получение является двоичным файлом со структурой, показанной на рисунке 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 этого связанного ряда байтов.
Проверьте получение
Для проверки получения выполните следующие тесты в порядке:
Найдите получение.
Если никакое получение не присутствует, сбои проверки.
Проверьте, что получение должным образом подписывается Apple.
Если это не подписывается Apple, сбоями проверки.
Проверьте, что идентификатор пакета в получении соответствует трудно кодированную константу, содержащую
CFBundleIdentifier
значение Вы ожидаете вInfo.plist
файл.Если они не соответствуют, сбои проверки.
Проверьте, что строка идентификатора версии в получении соответствует трудно кодированную константу, содержащую
CFBundleShortVersionString
значение Вы ожидаете вInfo.plist
файл.Если они не соответствуют, сбои проверки.
Вычислите хеш 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
файл хранится в получении — пытающийся локализовать значение для этого ключа, может заставить проверку получения перестать работать.
Защитите свою проверку проверки
Атакующий может попытаться обойти код доступа путем исправления двоичного файла приложения или изменения подпрограмм базовой операционной системы, от которых зависит код доступа. Упругость против этих типов атак требует множества кодирования методов, включая следующее:
Встройте код для криптографических проверок вместо того, чтобы использовать APIs, предоставленный системой.
Избегите простых конструкций кода, обеспечивающих тривиальную цель для исправления двоичного файла приложения.
Например, избегите писать код как следующее:
if (failedValidation) {
exit(173);
}
Методы устойчивости кода реализации, такие как путаница.
Если многократные приложения будут использовать тот же код для выполнения проверки, то эта общая подпись кода может быть предназначена инструментами то приложение патча двоичные файлы.
Гарантируйте это, даже если
exit
функционируйте не удается завершить Ваше приложение, Ваше приложение прекращает работать.
Тест во время процесса разработки
Для тестирования главного приложения во время процесса разработки Вам нужна подлинная расписка так, чтобы запустилось Ваше приложение. Для установки этого сделайте следующее:
Удостоверьтесь, что у Вас есть доступ в Интернет, таким образом, можно соединиться с серверами Apple.
Запустите свое приложение путем двойного щелчка по нему (или в некотором роде заставьте Launch Services запускать его).
После запуска приложения следующее происходит:
Вашему приложению не удается проверить его получение, потому что нет никакого настоящего получения, и оно выходит с состоянием
173
.Система интерпретирует статус выхода и пытается получить подлинную расписку. Принятие Вашего приложения, подписывая сертификат допустимо, система устанавливает подлинную расписку для приложения. Система может предложить Вам Ваши учетные данные iTunes.
Система повторно запускает Ваше приложение, и Ваше приложение успешно проверяет получение.
С этим установленным получением разработки можно запустить приложение любым методом — например, с gdb
или отладчик XCode.
Проверьте покупки в приложении
Для проверки закупки в приложении приложение выполняет следующие тесты в порядке:
Проанализируйте и проверьте получение приложения, как описано в предыдущих разделах.
Если получение не допустимо, ни одни из покупок в приложении не допустимы.
Проанализируйте получения закупки в приложении (значения для атрибутов типа 17).
Каждое получение закупки в приложении состоит из ряда атрибутов, как получение приложения делает. Структура для этих получений определяется в Перечислении 1-2. Анализируя получение, можно генерировать часть кода из описания ASN.1 с помощью
asn1c
инструмент. Проигнорируйте все атрибуты с типами, не появляющимися в таблице — они резервируются для использования системой, и их содержание может измениться в любое время.Для получения информации о полях в получении посмотрите Поля Получения.
Исследуйте идентификатор продукта на каждую закупку в приложении, подтверждают получение и добавляют соответствующую функциональность или содержание в Вашем приложении. Для получения информации о том, как вычислить активный период подписки, посмотрите Работу с Подписками.
Если проверка получения закупки в приложении перестала работать, Ваше приложение просто не добавляет функциональность или содержание.
Определение перечисления 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-4).
Проанализируйте полезную нагрузку (Перечисление 1-5).
Извлеките (Перечисление 1-6) атрибутов получения.
Вычислите хеш 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); |