first and last commit
This commit is contained in:
commit
1bf4efd552
2612 files changed
+636500
No files matched your search
@@ -0,0 +1,544 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2014 Apple Inc. All rights reserved.
|
||||
*
|
||||
* This document is the property of Apple Inc.
|
||||
* It is considered confidential and proprietary.
|
||||
*
|
||||
* This document may not be reproduced or transmitted in any form,
|
||||
* in whole or in part, without the express written permission of
|
||||
* Apple Inc.
|
||||
*/
|
||||
#include <lib/DERApTicket.h>
|
||||
#include <lib/mib.h>
|
||||
#include <lib/ticket.h>
|
||||
#include <lib/pki.h>
|
||||
#include <libDER/asn1Types.h>
|
||||
#include <libDER/DER_Decode.h>
|
||||
#include <libDER/DER_Keys.h>
|
||||
#include <libDER/oids.h>
|
||||
#include <drivers/sha1.h>
|
||||
#include <arch.h>
|
||||
#include <platform.h>
|
||||
#include <AssertMacros.h>
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
extern size_t sprint_hex(const uint8_t *data, unsigned data_len, char *buf, unsigned buf_len);
|
||||
|
||||
static DERReturn DERApTicketValidateCertExtension( const DERItem *contents );
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
const DERItemSpec DERApTicketItemSpecs[] =
|
||||
{
|
||||
{ DER_OFFSET(DERApTicket, signatureAlgorithm),
|
||||
ASN1_CONSTR_SEQUENCE,
|
||||
DER_DEC_NO_OPTS },
|
||||
{ DER_OFFSET(DERApTicket, body),
|
||||
ASN1_CONSTR_SET,
|
||||
DER_DEC_NO_OPTS | DER_DEC_SAVE_DER },
|
||||
{ DER_OFFSET(DERApTicket, signature),
|
||||
ASN1_OCTET_STRING,
|
||||
DER_DEC_NO_OPTS },
|
||||
{ DER_OFFSET(DERApTicket, certificates),
|
||||
ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 1,
|
||||
DER_DEC_NO_OPTS }
|
||||
};
|
||||
|
||||
const DERSize DERNumApTicketItemSpecs = sizeof(DERApTicketItemSpecs) / sizeof(DERItemSpec);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
const DERItemSpec DERApTicketCertExtensionItemSpecs[] =
|
||||
{
|
||||
{ DER_OFFSET(DERApTicketCertExtension, version),
|
||||
ASN1_CONTEXT_SPECIFIC | 0,
|
||||
DER_DEC_OPTIONAL },
|
||||
{ DER_OFFSET(DERApTicketCertExtension, override),
|
||||
ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 1,
|
||||
DER_DEC_NO_OPTS },
|
||||
{ DER_OFFSET(DERApTicketCertExtension, append),
|
||||
ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 2,
|
||||
DER_DEC_OPTIONAL },
|
||||
{ DER_OFFSET(DERApTicketCertExtension, remove),
|
||||
ASN1_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED | 3,
|
||||
DER_DEC_OPTIONAL }
|
||||
};
|
||||
|
||||
const DERSize DERNumApTicketCertExtensionItemSpecs = sizeof(DERApTicketCertExtensionItemSpecs) / sizeof(DERItemSpec);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static DERReturn
|
||||
GetBuildStringTagForRunningInstance( DERTag *outTag, bool isRestore )
|
||||
{
|
||||
#if !TICKET_UNITTEST
|
||||
uint32_t app_prod = mib_get_u32(kMIBTargetApplicationProduct);
|
||||
#else
|
||||
uint32_t app_prod = MIB_APP_PROD_IBOOT;
|
||||
#endif
|
||||
|
||||
switch (app_prod) {
|
||||
case MIB_APP_PROD_LLB:
|
||||
/* <rdar://problem/9383890> LLB should not attempt to self-verify against a restore ticket */
|
||||
if ( !isRestore ) {
|
||||
*outTag = 6;
|
||||
return DR_Success;
|
||||
}
|
||||
return -1;
|
||||
|
||||
case MIB_APP_PROD_IBSS:
|
||||
*outTag = 20;
|
||||
return DR_Success;
|
||||
|
||||
case MIB_APP_PROD_IBEC:
|
||||
/* <rdar://problem/9384142> iBEC Should self-verify */
|
||||
*outTag = 22;
|
||||
return DR_Success;
|
||||
|
||||
default:
|
||||
/* iBoot has no build string tag */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static DERReturn
|
||||
DERFindItemWithTag( const DERItem *container, DERTag tag, DERItem *outItem )
|
||||
{
|
||||
DERReturn derstat = -1;
|
||||
DERSequence sequence;
|
||||
DERTag containerTag;
|
||||
DERDecodedInfo info;
|
||||
// char debugBuffer[128];
|
||||
|
||||
derstat = DERDecodeSeqInit( container, &containerTag, &sequence );
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("can't parse top-level container") );
|
||||
|
||||
int i = 0;
|
||||
while ( true ) {
|
||||
derstat = DERDecodeSeqNext( &sequence, &info );
|
||||
require( derstat == DR_Success, exit );
|
||||
|
||||
// sprint_hex(info.content.data, info.content.length, debugBuffer, sizeof(debugBuffer));
|
||||
// TICKETLOG( "tag %u length=%u: %s", info.tag & ~ASN1_CONTEXT_SPECIFIC, info.content.length, debugBuffer );
|
||||
|
||||
if ( info.tag == tag ) {
|
||||
break;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
*outItem = info.content;
|
||||
derstat = DR_Success;
|
||||
|
||||
exit:
|
||||
return derstat;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static DERReturn
|
||||
DERApTicketFindAndValidateTag( const DERItem *container, DERTag tag, void *requiredValue, unsigned requiredLength )
|
||||
{
|
||||
DERReturn derstat = -1;
|
||||
DERItem foundItem;
|
||||
|
||||
derstat = DERFindItemWithTag( container, tag, &foundItem );
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("missing tag (%u)", tag) );
|
||||
|
||||
require_action( foundItem.length == requiredLength, exit, derstat = -1;
|
||||
TICKETLOG("tag (%llu) value has incorrect length (%u bytes, expected %u)", (uint64_t) tag, foundItem.length, requiredLength) );
|
||||
|
||||
if ( memcmp(foundItem.data, requiredValue, requiredLength) != 0 ) {
|
||||
TICKETLOG( "tag (%llu) value is invalid", (uint64_t) tag );
|
||||
derstat = -1;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
derstat = DR_Success;
|
||||
|
||||
exit:
|
||||
return derstat;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
DERReturn
|
||||
DERApTicketCopyTagData( const DERItem *container, DERTag tag, uint8_t *buffer, DERSize *ioLength )
|
||||
{
|
||||
DERReturn derstat = -1;
|
||||
DERItem item;
|
||||
|
||||
derstat = DERFindItemWithTag( container, tag | ASN1_CONTEXT_SPECIFIC, &item );
|
||||
require( derstat == DR_Success, exit );
|
||||
|
||||
require_action( *ioLength >= item.length, exit, derstat = -1;
|
||||
TICKETLOG("item data length (%u) is larger than provided buffer length (%u)", item.length, *ioLength) );
|
||||
|
||||
memcpy( buffer, item.data, item.length );
|
||||
*ioLength = item.length;
|
||||
|
||||
derstat = DR_Success;
|
||||
|
||||
exit:
|
||||
return derstat;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Decode the AP ticket and verify the signature.
|
||||
* If the ticket structure is malformed or the signature is invalid, returns error.
|
||||
*/
|
||||
DERReturn
|
||||
DERApTicketDecode( const DERItem *contents, DERApTicket *outTicket, DERSize *outNumUsedBytes )
|
||||
{
|
||||
DERReturn derstat = -1;
|
||||
int error;
|
||||
DERDecodedInfo ticketInfo;
|
||||
DERAlgorithmId algId;
|
||||
DERItem appleSpec;
|
||||
uint8_t bodyHash[CCSHA1_OUTPUT_SIZE];
|
||||
|
||||
bzero( &appleSpec, sizeof(appleSpec) );
|
||||
|
||||
/*
|
||||
* Validate the ticket structure and cert chain
|
||||
*/
|
||||
derstat = DERDecodeItem( contents, &ticketInfo );
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("malformed ticket") );
|
||||
|
||||
derstat = DERParseSequenceContent( &(ticketInfo.content), DERNumApTicketItemSpecs, DERApTicketItemSpecs, outTicket, 0 );
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("malformed ticket") );
|
||||
|
||||
derstat = DERParseSequenceContent( &(outTicket->signatureAlgorithm), DERNumAlgorithmIdItemSpecs, DERAlgorithmIdItemSpecs, &algId, sizeof(algId) );
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("malformed ticket") );
|
||||
|
||||
if ( !DEROidCompare( &algId.oid, &oidSha1Rsa) || (algId.params.data != NULL)) {
|
||||
TICKETLOG( "mismatched algorithm" );
|
||||
derstat = -1;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
sha1_calculate( outTicket->body.data, outTicket->body.length, bodyHash );
|
||||
|
||||
error = verify_signature_with_chain(
|
||||
outTicket->certificates.data, outTicket->certificates.length,
|
||||
outTicket->signature.data, outTicket->signature.length,
|
||||
bodyHash, sizeof(bodyHash),
|
||||
NULL, NULL,
|
||||
(void **) &(appleSpec.data), (size_t *) &(appleSpec.length) );
|
||||
|
||||
if ( error != 0 ) {
|
||||
TICKETLOG( "failed signature validation" );
|
||||
derstat = -1;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
// TICKETLOG( "cert extension: 0x%08x, length=%u", appleSpec.data, appleSpec.length );
|
||||
// hexdump( appleSpec.data, appleSpec.length );
|
||||
|
||||
require_action( appleSpec.data != NULL, exit, derstat = -1; TICKETLOG("missing cert extension") );
|
||||
|
||||
derstat = DERApTicketValidateCertExtension( &appleSpec );
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("incompatible cert") );
|
||||
|
||||
derstat = DR_Success;
|
||||
|
||||
exit:
|
||||
if ( derstat == DR_Success ) {
|
||||
/*
|
||||
* "ticketInfo" describes a pointer and length to content within "contents->data".
|
||||
* The difference of that pointer and "contents->data" is the number of bytes used to describe the content.
|
||||
* The overall ticket length is the content length summed with the number of bytes used to describe it.
|
||||
*/
|
||||
*outNumUsedBytes = ticketInfo.content.length + (ticketInfo.content.data - contents->data);
|
||||
}
|
||||
else {
|
||||
*outNumUsedBytes = 0;
|
||||
}
|
||||
|
||||
return derstat;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Validate the build string
|
||||
*
|
||||
* The root ticket includes "build string" entries for LLB and iBSS that indicate the build
|
||||
* submission tag (e.g., iBoot-1194) of the iBoot project that was submitted to the build and
|
||||
* was included in its nomination.
|
||||
*
|
||||
* This build string is also stamped into the binary and is accessible via build_tag_string.
|
||||
*
|
||||
* To guard against "mix and match" attacks, the executing instance's build string is compared
|
||||
* against the one in the root ticket; that is, the executing firmware is bound to the ticket
|
||||
* being validated.
|
||||
*/
|
||||
static DERReturn
|
||||
_DERApTicketValidateBuildString( const DERApTicket *ticket, bool isRestore )
|
||||
{
|
||||
DERReturn derstat = -1;
|
||||
DERTag tmp;
|
||||
DERItem tmpItem;
|
||||
|
||||
if ( GetBuildStringTagForRunningInstance(&tmp, isRestore) == DR_Success ) {
|
||||
unsigned i, length;
|
||||
char ticketBuildString[128];
|
||||
|
||||
derstat = DERFindItemWithTag( &(ticket->body), tmp | ASN1_CONTEXT_SPECIFIC, &tmpItem );
|
||||
if ( derstat != DR_Success ) {
|
||||
TICKETLOG( "missing build string %u", tmp );
|
||||
derstat = -1;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
require_action( tmpItem.length < sizeof(ticketBuildString), exit, derstat = -1; TICKETLOG("malformed build string") );
|
||||
|
||||
/*
|
||||
* Remove any trailing build string turds.
|
||||
* <rdar://problem/9694009> Manifest "BuildString" entries have an undesired suffix
|
||||
*/
|
||||
length = strlcpy( ticketBuildString, (const char *) tmpItem.data, sizeof(ticketBuildString) );
|
||||
for ( i = 0; i < length; ++i ) {
|
||||
if ( ticketBuildString[i] == '~' ) {
|
||||
ticketBuildString[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( strncmp(build_tag_string, ticketBuildString, sizeof(ticketBuildString)) != 0 ) {
|
||||
TICKETLOG( "mismatched build string\n ticket: %s\n current: %s", ticketBuildString, build_tag_string );
|
||||
derstat = -1;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
derstat = DR_Success;
|
||||
goto exit;
|
||||
|
||||
exit:
|
||||
return derstat;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static DERReturn
|
||||
DERApTicketValidateProductionMode( const DERItem *container )
|
||||
{
|
||||
DERReturn derstat = -1;
|
||||
uint32_t tmp32 = platform_get_raw_production_mode();
|
||||
#if !TICKET_UNITTEST
|
||||
bool acceptGlobalTicket = mib_get_bool(kMIBTargetAcceptGlobalTicket);
|
||||
#else
|
||||
bool acceptGlobalTicket = false;
|
||||
#endif
|
||||
|
||||
if (acceptGlobalTicket) {
|
||||
DERItem tmpItem;
|
||||
derstat = DERFindItemWithTag( container, 4 | ASN1_CONTEXT_SPECIFIC, &tmpItem );
|
||||
require( derstat == DR_Success, exit );
|
||||
}
|
||||
if (!acceptGlobalTicket || tmp32)
|
||||
{
|
||||
derstat = DERApTicketFindAndValidateTag( container, 4 | ASN1_CONTEXT_SPECIFIC, &tmp32, sizeof(tmp32) );
|
||||
require( derstat == DR_Success, exit );
|
||||
}
|
||||
|
||||
derstat = DR_Success;
|
||||
|
||||
exit:
|
||||
return derstat;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static DERReturn
|
||||
DERApTicketValidateCertExtension( const DERItem *contents )
|
||||
{
|
||||
DERReturn derstat = -1;
|
||||
uint32_t tmp32;
|
||||
DERItem tmpItem;
|
||||
DERDecodedInfo extensionInfo;
|
||||
DERApTicketCertExtension extension;
|
||||
|
||||
bzero( &extension, sizeof(extension) );
|
||||
|
||||
derstat = DERDecodeItem( contents, &extensionInfo );
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("malformed cert extension") );
|
||||
|
||||
derstat = DERParseSequenceContent( &(extensionInfo.content), DERNumApTicketCertExtensionItemSpecs, DERApTicketCertExtensionItemSpecs, &extension, 0 );
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("malformed cert extension") );
|
||||
|
||||
/* We only know how to handle version 0 (in which the version tag isn't provided) */
|
||||
require_action( extension.version.data == NULL, exit, derstat = -1; TICKETLOG("unsupported extension version") );
|
||||
|
||||
/* These are provided for future expansion. For now, forbid them.*/
|
||||
require_action( extension.append.data == NULL, exit, derstat = -1; TICKETLOG("unsupported extension") );
|
||||
require_action( extension.remove.data == NULL, exit, derstat = -1; TICKETLOG("unsupported extension") );
|
||||
|
||||
/* An override is required */
|
||||
require_action( extension.override.data != NULL, exit, derstat = -1; TICKETLOG("incomplete extension") );
|
||||
|
||||
/* Validate the chipid (tag=2) */
|
||||
tmp32 = platform_get_chip_id();
|
||||
derstat = DERApTicketFindAndValidateTag( &(extension.override), 2 | ASN1_CONTEXT_SPECIFIC, &tmp32, sizeof(tmp32) );
|
||||
require( derstat == DR_Success, exit );
|
||||
|
||||
/* Validate the production mode (tag=4) */
|
||||
derstat = DERApTicketValidateProductionMode( &(extension.override) );
|
||||
require_action( derstat == DR_Success, exit, derstat = -1; TICKETLOG("invalid production mode") );
|
||||
|
||||
/* Validate the security domain (tag=5) */
|
||||
tmp32 = platform_get_security_domain();
|
||||
derstat = DERApTicketFindAndValidateTag( &(extension.override), 5 | ASN1_CONTEXT_SPECIFIC, &tmp32, sizeof(tmp32) );
|
||||
require( derstat == DR_Success, exit );
|
||||
|
||||
/* Validate the certificate epoch (tag=235) */
|
||||
derstat = DERFindItemWithTag( &(extension.override), 235 | ASN1_CONTEXT_SPECIFIC, &tmpItem );
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("missing cert epoch") );
|
||||
require_action( tmpItem.length == sizeof(tmp32), exit, derstat = -1; TICKETLOG("malformed cert epoch") );
|
||||
memcpy( &tmp32, tmpItem.data, sizeof(tmp32) );
|
||||
require_action( platform_get_hardware_epoch() <= tmp32, exit, derstat = -1; TICKETLOG("expired cert epoch") );
|
||||
|
||||
derstat = DR_Success;
|
||||
|
||||
exit:
|
||||
return derstat;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
DERReturn
|
||||
DERApTicketValidate( const DERApTicket *ticket, bool isRestore )
|
||||
{
|
||||
DERReturn derstat = -1;
|
||||
DERItem tmpItem;
|
||||
uint32_t tmp32;
|
||||
uint64_t tmp64;
|
||||
uint8_t nonceHash[CCSHA1_OUTPUT_SIZE];
|
||||
#if !TICKET_UNITTEST
|
||||
bool acceptGlobalTicket = mib_get_u32(kMIBTargetAcceptGlobalTicket);
|
||||
#else
|
||||
bool acceptGlobalTicket = false;
|
||||
#endif
|
||||
|
||||
/* Validate the ecid (tag=1) */
|
||||
derstat = DERFindItemWithTag( &(ticket->body), 1 | ASN1_CONTEXT_SPECIFIC, &tmpItem );
|
||||
if (!acceptGlobalTicket) {
|
||||
require( derstat == DR_Success, exit );
|
||||
}
|
||||
|
||||
if (derstat == DR_Success )
|
||||
{
|
||||
tmp64 = platform_get_ecid_id();
|
||||
derstat = DERApTicketFindAndValidateTag( &(ticket->body), 1 | ASN1_CONTEXT_SPECIFIC, &tmp64, sizeof(tmp64) );
|
||||
require( derstat == DR_Success, exit );
|
||||
}
|
||||
|
||||
/* Validate the chipid (tag=2) */
|
||||
tmp32 = platform_get_chip_id();
|
||||
derstat = DERApTicketFindAndValidateTag( &(ticket->body), 2 | ASN1_CONTEXT_SPECIFIC, &tmp32, sizeof(tmp32) );
|
||||
require( derstat == DR_Success, exit );
|
||||
|
||||
/* Validate the boardid (tag=3) */
|
||||
tmp32 = platform_get_board_id();
|
||||
derstat = DERApTicketFindAndValidateTag( &(ticket->body), 3 | ASN1_CONTEXT_SPECIFIC, &tmp32, sizeof(tmp32) );
|
||||
require( derstat == DR_Success, exit );
|
||||
|
||||
/* Validate the production mode (tag=4) */
|
||||
derstat = DERApTicketValidateProductionMode( &(ticket->body) );
|
||||
require( derstat == DR_Success, exit );
|
||||
|
||||
/* Validate the security domain (tag=5) */
|
||||
tmp32 = platform_get_security_domain();
|
||||
derstat = DERApTicketFindAndValidateTag( &(ticket->body), 5 | ASN1_CONTEXT_SPECIFIC, &tmp32, sizeof(tmp32) );
|
||||
require( derstat == DR_Success, exit );
|
||||
|
||||
/* Validate the build tag */
|
||||
derstat = _DERApTicketValidateBuildString( ticket, isRestore );
|
||||
require( derstat == DR_Success, exit );
|
||||
|
||||
/*
|
||||
* Restore validation is a superset of normal boot validation
|
||||
*/
|
||||
if ( isRestore ) {
|
||||
|
||||
/*
|
||||
* Validate the nonce (tag=18)
|
||||
*/
|
||||
derstat = DERFindItemWithTag( &(ticket->body), 18 | ASN1_CONTEXT_SPECIFIC, &tmpItem );
|
||||
if ( derstat == DR_Success ) {
|
||||
if ( tmpItem.length != sizeof(nonceHash) ) {
|
||||
TICKETLOG( "malformed nonce" );
|
||||
derstat = -1;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
tmp64 = platform_get_nonce();
|
||||
sha1_calculate( &tmp64, sizeof(tmp64), nonceHash );
|
||||
|
||||
if ( memcmp(nonceHash, tmpItem.data, sizeof(nonceHash)) != 0 ) {
|
||||
#if TICKETLOG_ENABLED
|
||||
char hash1[2 * CCSHA1_OUTPUT_SIZE + 1];
|
||||
char hash2[2 * CCSHA1_OUTPUT_SIZE + 1];
|
||||
sprint_hex( tmpItem.data, tmpItem.length, hash1, sizeof(hash1) );
|
||||
sprint_hex( nonceHash, sizeof(nonceHash), hash2, sizeof(hash2) );
|
||||
#endif
|
||||
|
||||
TICKETLOG( "mismatched nonce\n ticket: %s\n current: %s", hash1, hash2 );
|
||||
derstat = -1;
|
||||
goto exit;
|
||||
}
|
||||
} else {
|
||||
if (!acceptGlobalTicket) {
|
||||
TICKETLOG( "missing nonce" );
|
||||
derstat = -1;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
derstat = DR_Success;
|
||||
|
||||
exit:
|
||||
return derstat;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
DERReturn
|
||||
DERApTicketParseLengthFromBuffer( const uint8_t *buffer, size_t length, size_t *outTicketLength )
|
||||
{
|
||||
DERReturn derstat = -1;
|
||||
DERItem ticketItem;
|
||||
DERDecodedInfo ticketInfo;
|
||||
|
||||
require( buffer != NULL, exit );
|
||||
require( outTicketLength != NULL, exit );
|
||||
|
||||
/* Parse just enough of the buffer to determine the ticket length */
|
||||
ticketItem.data = (DERByte *) buffer;
|
||||
ticketItem.length = (DERSize) length;
|
||||
|
||||
derstat = DERDecodeItem( &ticketItem, &ticketInfo );
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("failed to decode buffer") );
|
||||
|
||||
/*
|
||||
* "ticketInfo" describes a pointer and length to content within "buffer".
|
||||
* The difference of that pointer and "buffer" is the number of bytes used to describe the content.
|
||||
* The overall ticket length is the content length summed with the number of bytes used to describe it.
|
||||
*/
|
||||
*outTicketLength = (size_t) ticketInfo.content.length + (ticketInfo.content.data - buffer);
|
||||
|
||||
derstat = DR_Success;
|
||||
|
||||
exit:
|
||||
return derstat;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
@@ -0,0 +1,132 @@
|
||||
#include <unittest.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <platform.h>
|
||||
#include <lib/blockdev.h>
|
||||
#include <lib/DERApTicket.h>
|
||||
#include <lib/mib.h>
|
||||
#include <lib/ticket.h>
|
||||
#include <lib/pki.h>
|
||||
#include <arch.h>
|
||||
#include <AssertMacros.h>
|
||||
|
||||
struct blockdev *create_mock_blockdev(const char *name, const char *filename, uint32_t block_size);
|
||||
|
||||
int do_printenv(int argc, void *args);
|
||||
|
||||
int fuzz_main(const char *filename)
|
||||
{
|
||||
void *in_buffer;
|
||||
size_t in_size;
|
||||
struct stat st;
|
||||
FILE *f;
|
||||
int result;
|
||||
struct blockdev *bdev;
|
||||
|
||||
// Get input size
|
||||
result = stat(filename, &st);
|
||||
TEST_ASSERT_EQ(result, 0);
|
||||
in_size = st.st_size;
|
||||
|
||||
// Read it
|
||||
f = fopen(filename, "r");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
in_buffer = malloc(in_size);
|
||||
TEST_ASSERT_NOT_NULL(in_buffer);
|
||||
result = fread(in_buffer, 1, in_size, f);
|
||||
TEST_ASSERT_EQ(result, in_size);
|
||||
|
||||
bdev = create_mem_blockdev("nand", in_buffer, in_size, 1);
|
||||
TEST_ASSERT_NOT_NULL(bdev);
|
||||
|
||||
image_search_bdev(bdev, 0, 0);
|
||||
|
||||
ticket_load();
|
||||
|
||||
free(in_buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// mocks
|
||||
|
||||
uint32_t platform_get_board_id(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t platform_get_chip_id(void)
|
||||
{
|
||||
return 0x8747;
|
||||
}
|
||||
|
||||
uint64_t platform_get_ecid_id(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t platform_get_nonce(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t platform_get_security_domain(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool platform_get_raw_production_mode(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t platform_get_hardware_epoch(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t platform_get_security_epoch(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *build_tag_string = "iBoot-1234";
|
||||
|
||||
MIB_CONSTANT_PTR(kMIBBuildTag, kOIDTypeString|kOIDDataIndirect, (void *)&build_tag_string);
|
||||
|
||||
struct mib_description mib_desc[] = {};
|
||||
|
||||
int fs_load_file(const char *path, addr_t addr, size_t *maxsize)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool security_validate_image(security_image_t image_validity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool platform_get_ecid_image_personalization_required(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void security_set_production_override(bool override)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
env_blacklist(const char *name, bool write)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
env_blacklist_nvram(const char *name)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# Copyright (C) 2014 Apple Inc. All rights reserved.
|
||||
#
|
||||
# This document is the property of Apple Inc.
|
||||
# It is considered confidential and proprietary.
|
||||
#
|
||||
# This document may not be reproduced or transmitted in any form,
|
||||
# in whole or in part, without the express written permission of
|
||||
# Apple Inc.
|
||||
#
|
||||
|
||||
LIBTICKET_DIR := $(GET_LOCAL_DIR)
|
||||
LIBTICKET_BUILD := $(call TOLIBDIR,$(LIBTICKET_DIR)/LIBTICKET.a)
|
||||
COMMONLIBS += LIBTICKET
|
||||
|
||||
# Only process the remainder of the makefile if we are building libraries
|
||||
#
|
||||
ifeq ($(MAKEPHASE),libraries)
|
||||
|
||||
GLOBAL_INCLUDES += $(SRCROOT)/lib/pki
|
||||
|
||||
LIBTICKET_OBJS := $(LIBTICKET_DIR)/ticket.o \
|
||||
$(LIBTICKET_DIR)/DERApTicket.o
|
||||
|
||||
LIBTICKET_OBJS := $(call TOLIBOBJDIR,$(LIBTICKET_OBJS))
|
||||
|
||||
ALL_DEPS += $(LIBTICKET_OBJS:%o=%d)
|
||||
|
||||
$(LIBTICKET_BUILD): $(LIBTICKET_OBJS)
|
||||
|
||||
endif
|
||||
@@ -0,0 +1,17 @@
|
||||
# Copyright (C) 2010-2011, 2014 Apple Inc. All rights reserved.
|
||||
#
|
||||
# This document is the property of Apple Inc.
|
||||
# It is considered confidential and proprietary.
|
||||
#
|
||||
# This document may not be reproduced or transmitted in any form,
|
||||
# in whole or in part, without the express written permission of
|
||||
# Apple Inc.
|
||||
#
|
||||
|
||||
OPTIONS += WITH_TICKET=1
|
||||
LIBRARY_MODULES += lib/ticket
|
||||
MODULES += lib/blockdev \
|
||||
lib/env \
|
||||
lib/image/image3 \
|
||||
lib/pki
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Copyright (C) 2014 Apple Inc. All rights reserved.
|
||||
#
|
||||
# This document is the property of Apple Inc.
|
||||
# It is considered confidential and proprietary.
|
||||
#
|
||||
# This document may not be reproduced or transmitted in any form,
|
||||
# in whole or in part, without the express written permission of
|
||||
# Apple Inc.
|
||||
#
|
||||
|
||||
LOCAL_DIR := $(GET_LOCAL_DIR)
|
||||
|
||||
TEST_NAME := ticket-fuzz
|
||||
|
||||
TEST_OBJS := \
|
||||
$(LOCAL_DIR)/ticket.o \
|
||||
$(LOCAL_DIR)/DERApTicket.o \
|
||||
lib/image/image.o \
|
||||
lib/image/image3/Image3.o \
|
||||
lib/image/image3/image3_wrapper.o \
|
||||
lib/pki/chain-validation.o \
|
||||
lib/pki/libDER/DER_CertCrl.o \
|
||||
lib/pki/libDER/DER_Decode.o \
|
||||
lib/pki/libDER/DER_Digest.o \
|
||||
lib/pki/libDER/DER_Encode.o \
|
||||
lib/pki/libDER/DER_Keys.o \
|
||||
lib/pki/libDER/oids.o \
|
||||
lib/pki/libGiants/giantExternal.o \
|
||||
lib/pki/libGiants/giantIntegers.o \
|
||||
lib/pki/libGiants/giantMemutils.o \
|
||||
lib/pki/libGiants/giantPort_C.o \
|
||||
lib/pki/libGiants/giantMod.o \
|
||||
lib/pki/libgRSA/rsaGiantKey.o \
|
||||
lib/pki/libgRSA/libgRSA.o \
|
||||
lib/pki/libgRSA/rsaPadding.o \
|
||||
lib/pki/libgRSA/libgRSA_priv.o \
|
||||
lib/pki/libgRSA/libgRSA_DER.o \
|
||||
lib/pki/libgRSA/rsaRawKey.o \
|
||||
drivers/sha1/sha1.o \
|
||||
drivers/sha1/mozilla_sha.o
|
||||
|
||||
TEST_SUPPORT_OBJS += \
|
||||
lib/blockdev/blockdev.o \
|
||||
lib/blockdev/mem_blockdev.o \
|
||||
lib/cksum/crc32.o \
|
||||
lib/env/env.o \
|
||||
lib/mib/mib.o \
|
||||
lib/libc/misc.o \
|
||||
lib/libc/log2.o \
|
||||
tests/fuzz-main.o \
|
||||
$(LOCAL_DIR)/fuzz.o
|
||||
|
||||
TEST_INCLUDES += \
|
||||
lib/pki
|
||||
|
||||
TEST_CFLAGS += \
|
||||
-DTICKET_UNITTEST=1 \
|
||||
-DWITH_PKI=1 \
|
||||
-DPKI_CHECK_ANCHOR_BY_SHA1=0 \
|
||||
-DPKI_APPLE_ROOT_CA=1 \
|
||||
-DPKI_CHECK_KEY_IDS=1 \
|
||||
-DWITH_IMAGE3=1 \
|
||||
-DIMAGE_MAX_COUNT=1 \
|
||||
-DCPU_CACHELINE_SIZE=64 \
|
||||
-DIMAGE3_NO_CRC=1
|
||||
|
||||
@@ -0,0 +1,557 @@
|
||||
/*
|
||||
* Copyright (C) 2011-2014 Apple Inc. All rights reserved.
|
||||
*
|
||||
* This document is the property of Apple Inc.
|
||||
* It is considered confidential and proprietary.
|
||||
*
|
||||
* This document may not be reproduced or transmitted in any form,
|
||||
* in whole or in part, without the express written permission of
|
||||
* Apple Inc.
|
||||
*/
|
||||
#include <drivers/sha1.h>
|
||||
#include <lib/fs.h>
|
||||
#include <lib/blockdev.h>
|
||||
#include <lib/env.h>
|
||||
#include <lib/image.h>
|
||||
#include <lib/mib.h>
|
||||
#include <platform.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/menu.h>
|
||||
#include <AssertMacros.h>
|
||||
#include <lib/ticket.h>
|
||||
#include <lib/DERApTicket.h>
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* <rdar://problem/9447707> Disable TICKET_LOOSE_VERIFICATION for the release build */
|
||||
#if !RELEASE_BUILD
|
||||
#define TICKET_LOOSE_VERIFICATION 1
|
||||
#endif
|
||||
|
||||
#define MAX_TICKET_LENGTH 8192
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static uint8_t *gRootTicketBuffer = NULL;
|
||||
static size_t gRootTicketLength = 0;
|
||||
static DERApTicket *gRootTicket = NULL;
|
||||
static uint8_t gRootTicketHashBuffer[CCSHA1_OUTPUT_SIZE];
|
||||
static bool gRootTicketIsRestore = false;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
size_t sprint_hex(const uint8_t *data, unsigned data_len, char *buf, unsigned buf_len)
|
||||
{
|
||||
static const char kAsciiHexChars[] = "0123456789ABCDEF";
|
||||
unsigned i, j;
|
||||
|
||||
/* ensure the output buffer is large enough to contain the hex string */
|
||||
if (buf_len < (data_len * 2) + 1) {
|
||||
j = -1;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
j = 0;
|
||||
for ( i = 0; i < data_len; ++i ) {
|
||||
uint8_t octet = data[i];
|
||||
buf[j++] = kAsciiHexChars[((octet & 0xF0) >> 4)];
|
||||
buf[j++] = kAsciiHexChars[(octet & 0x0F)];
|
||||
}
|
||||
|
||||
buf[j] = '\0';
|
||||
|
||||
exit:
|
||||
return j;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
uint32_t type;
|
||||
uint8_t boot_mode;
|
||||
uint8_t hash_tag;
|
||||
uint8_t trust_tag;
|
||||
uint8_t build_tag;
|
||||
} image3_ticket_tags;
|
||||
|
||||
#define kTicketBootRestore (0x1U)
|
||||
#define kTicketBootNormal (0x2U)
|
||||
#define kTicketBootAny (kTicketBootRestore | kTicketBootNormal)
|
||||
|
||||
static const image3_ticket_tags kImage3TicketTags[] = {
|
||||
/* type boot_mode hash_tag trust_tag build_tag */
|
||||
{ IMAGE_TYPE_DIAG, kTicketBootAny, 11, 52, 0, },
|
||||
{ IMAGE_TYPE_LLB, kTicketBootNormal, 228, 231, 6, },
|
||||
{ IMAGE_TYPE_IBOOT, kTicketBootAny, 7, 48, 0, },
|
||||
{ IMAGE_TYPE_DEVTREE, kTicketBootNormal, 9, 50, 0, },
|
||||
{ IMAGE_TYPE_KERNELCACHE, kTicketBootNormal, 10, 51, 0, },
|
||||
{ IMAGE_TYPE_LOGO, kTicketBootNormal, 8, 49, 0, },
|
||||
{ IMAGE_TYPE_NEEDSERVICE, kTicketBootNormal, 17, 58, 0, },
|
||||
{ IMAGE_TYPE_GLYPHCHRG, kTicketBootNormal, 12, 53, 0, },
|
||||
{ IMAGE_TYPE_GLYPHPLUGIN, kTicketBootNormal, 13, 54, 0, },
|
||||
{ IMAGE_TYPE_BATTERYCHARGING0, kTicketBootNormal, 78, 84, 0, },
|
||||
{ IMAGE_TYPE_BATTERYCHARGING1, kTicketBootNormal, 79, 85, 0, },
|
||||
{ IMAGE_TYPE_BATTERYLOW0, kTicketBootNormal, 14, 55, 0, },
|
||||
{ IMAGE_TYPE_BATTERYLOW1, kTicketBootNormal, 15, 56, 0, },
|
||||
{ IMAGE_TYPE_BATTERYFULL, kTicketBootNormal, 80, 86, 0, },
|
||||
{ IMAGE_TYPE_RECMODE, kTicketBootAny, 16, 57, 0, },
|
||||
{ IMAGE_TYPE_IBSS, kTicketBootRestore, 229, 232, 20, },
|
||||
{ IMAGE_TYPE_IBEC, kTicketBootRestore, 230, 233, 22, },
|
||||
{ IMAGE_TYPE_LOGO, kTicketBootRestore, 23, 59, 0, },
|
||||
{ IMAGE_TYPE_DEVTREE, kTicketBootRestore, 24, 60, 0, },
|
||||
{ IMAGE_TYPE_KERNELCACHE, kTicketBootRestore, 25, 61, 0, },
|
||||
{ IMAGE_TYPE_RAMDISK, kTicketBootRestore, 26, 62, 0, },
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
static const image3_ticket_tags *
|
||||
ticket_get_image3_tags( uint32_t type )
|
||||
{
|
||||
/*
|
||||
* Infer whether this is a normal boot or a restore.
|
||||
*/
|
||||
unsigned boot_mode;
|
||||
uint32_t product = mib_get_u32(kMIBTargetApplicationProduct);
|
||||
|
||||
if ((product == MIB_APP_PROD_IBOOT) || (product == MIB_APP_PROD_LLB)) {
|
||||
boot_mode = (type == IMAGE_TYPE_IBEC) ? kTicketBootRestore : kTicketBootNormal;
|
||||
} else {
|
||||
boot_mode = kTicketBootRestore;
|
||||
}
|
||||
|
||||
TICKETLOG( " boot mode: %s", (boot_mode == kTicketBootRestore) ? "restore" : "normal" );
|
||||
|
||||
unsigned i;
|
||||
unsigned count = sizeof(kImage3TicketTags) / sizeof(kImage3TicketTags[0]);
|
||||
const image3_ticket_tags *found = NULL;
|
||||
|
||||
for ( i = 0; i < count; ++i ) {
|
||||
if ( kImage3TicketTags[i].type == type && kImage3TicketTags[i].boot_mode & boot_mode ) {
|
||||
found = &kImage3TicketTags[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
bool
|
||||
ticket_validation_required()
|
||||
{
|
||||
bool required;
|
||||
|
||||
/*
|
||||
* <rdar://problem/9391858> Revised iBoot ticket policy
|
||||
* <rdar://problem/9414797> Don't allow replay of old iBECs on customer units
|
||||
* <rdar://problem/9790456> Modify ticket validation policy for K66
|
||||
*/
|
||||
|
||||
#if RELEASE_BUILD
|
||||
required = !( platform_get_chip_id() == 0x8930 && platform_get_board_id() == 0x10 );
|
||||
#else
|
||||
required = false;
|
||||
#endif
|
||||
|
||||
return required;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
bool
|
||||
ticket_copy_tag_data( DERTag tag, uint8_t *buffer, size_t *ioLength )
|
||||
{
|
||||
int status = -1;
|
||||
DERReturn derstat;
|
||||
DERSize dataLength = (DERSize) *ioLength;
|
||||
|
||||
require_action( gRootTicket != NULL, exit, TICKETLOG("no ticket") );
|
||||
|
||||
derstat = DERApTicketCopyTagData( &(gRootTicket->body), tag, buffer, &dataLength );
|
||||
require( derstat == DR_Success, exit );
|
||||
|
||||
*ioLength = (size_t) dataLength;
|
||||
|
||||
status = 0;
|
||||
|
||||
exit:
|
||||
return (status == 0);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
bool
|
||||
ticket_validate_image3( uint32_t type, uint32_t expectedType, uint8_t *hashBuffer, size_t hashBufferSize, bool *outIsUntrusted )
|
||||
{
|
||||
bool valid = false;
|
||||
const image3_ticket_tags *ticketTags;
|
||||
uint8_t ticketEntryHashBuffer[CCSHA1_OUTPUT_SIZE];
|
||||
size_t ticketEntryHashSize = sizeof(ticketEntryHashBuffer);
|
||||
uint32_t trustedEncoding;
|
||||
size_t trustedEncodingSize = sizeof(trustedEncoding);
|
||||
char hashLog[2 * CCSHA1_OUTPUT_SIZE + 1];
|
||||
|
||||
TICKETLOG( "examining image: type=0x%08x ('%c%c%c%c')", type, (type >> 24) & 0xFF, (type >> 16) & 0xFF, (type >> 8) & 0xFF, type & 0xFF );
|
||||
|
||||
if ( expectedType != IMAGE_TYPE_ANY && expectedType != type ) {
|
||||
TICKETLOG( " expected image: type=0x%08x ('%c%c%c%c')", expectedType, (expectedType >> 24) & 0xFF, (expectedType >> 16) & 0xFF, (expectedType >> 8) & 0xFF, expectedType & 0xFF );
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ticketTags = ticket_get_image3_tags(type);
|
||||
if (ticketTags == NULL) {
|
||||
TICKETLOG("this image isn't supported by the ticket model");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ticketTags->boot_mode == kTicketBootRestore && gRootTicket != NULL && !gRootTicketIsRestore) {
|
||||
TICKETLOG("a restore ticket is required");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!ticket_copy_tag_data(ticketTags->hash_tag, ticketEntryHashBuffer, &ticketEntryHashSize)) {
|
||||
TICKETLOG("this tag (%d) isn't in the ticket", ticketTags->hash_tag);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ticketEntryHashSize != sizeof(ticketEntryHashBuffer)) {
|
||||
TICKETLOG("unrecognized ticket encoding for hash tag (%d)", ticketTags->hash_tag);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
sprint_hex(ticketEntryHashBuffer, sizeof(ticketEntryHashBuffer), hashLog, sizeof(hashLog));
|
||||
TICKETLOG(" ticket hash: %s", hashLog);
|
||||
|
||||
sprint_hex(hashBuffer, hashBufferSize, hashLog, sizeof(hashLog));
|
||||
TICKETLOG(" image hash: %s", hashLog);
|
||||
|
||||
/* compare hash_buffer to the hash of the image3 */
|
||||
if (memcmp(hashBuffer, ticketEntryHashBuffer, sizeof(ticketEntryHashBuffer)) != 0) {
|
||||
TICKETLOG("this image doesn't match the one specified by ticket tag (%d)", ticketTags->hash_tag);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!ticket_copy_tag_data(ticketTags->trust_tag, (void *) &trustedEncoding, &trustedEncodingSize)) {
|
||||
TICKETLOG("ticket lacks trust tag (%d) for this image; assuming untrusted", ticketTags->trust_tag);
|
||||
*outIsUntrusted = true;
|
||||
}
|
||||
else {
|
||||
if (trustedEncodingSize != sizeof(trustedEncoding)) {
|
||||
TICKETLOG("unrecognized ticket encoding for trust tag (%d)", ticketTags->trust_tag);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
TICKETLOG(" explicit trust: %s", trustedEncoding ? "yes" : "no");
|
||||
*outIsUntrusted = trustedEncoding ? false : true;
|
||||
}
|
||||
|
||||
valid = true;
|
||||
|
||||
exit:
|
||||
return valid;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
int
|
||||
ticket_set( const uint8_t *buffer, size_t length, bool isRestore, size_t *outTicketLength )
|
||||
{
|
||||
int status = -1;
|
||||
DERReturn derstat;
|
||||
DERItem ticketItem;
|
||||
size_t ticketLength;
|
||||
uint8_t *ticketBuffer = NULL;
|
||||
DERApTicket *apTicket = NULL;
|
||||
|
||||
require( buffer != NULL, exit );
|
||||
require( length > 0, exit );
|
||||
|
||||
/* Parse just enough of the buffer to determine the ticket length */
|
||||
derstat = DERApTicketParseLengthFromBuffer( buffer, length, &ticketLength );
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("can't parse ticket length") );
|
||||
require_action( ticketLength > 0 && ticketLength < MAX_TICKET_LENGTH, exit, TICKETLOG("unreasonable ticket length: %d bytes", ticketLength) );
|
||||
|
||||
/* Copy the ticket data to a separate buffer */
|
||||
ticketBuffer = (uint8_t *) malloc( ticketLength );
|
||||
require_action( ticketBuffer != NULL, exit, TICKETLOG("failed to alloc ticket buffer") );
|
||||
memcpy( ticketBuffer, buffer, ticketLength );
|
||||
|
||||
/* Allocate a ticket structure */
|
||||
apTicket = (DERApTicket *) calloc( 1, sizeof(DERApTicket) );
|
||||
require_action( apTicket != NULL, exit, TICKETLOG("failed to alloc ticket structure") );
|
||||
|
||||
/* Decode */
|
||||
ticketItem.data = (DERByte *) ticketBuffer;
|
||||
ticketItem.length = (DERSize) ticketLength;
|
||||
derstat = DERApTicketDecode( &ticketItem, apTicket, (DERSize *) &ticketLength );
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("malformed ticket") );
|
||||
|
||||
/* Validate */
|
||||
derstat = DERApTicketValidate( apTicket, isRestore );
|
||||
|
||||
#if TICKET_LOOSE_VERIFICATION
|
||||
derstat = DR_Success;
|
||||
#endif
|
||||
|
||||
require_action( derstat == DR_Success, exit, TICKETLOG("ticket rejected") );
|
||||
|
||||
/* Destroy any older ticket */
|
||||
if ( gRootTicketBuffer != NULL ) {
|
||||
free( gRootTicketBuffer );
|
||||
gRootTicketBuffer = NULL;
|
||||
gRootTicketLength = 0;
|
||||
}
|
||||
|
||||
if ( gRootTicket != NULL ) {
|
||||
free( gRootTicket );
|
||||
gRootTicket = NULL;
|
||||
}
|
||||
|
||||
/* Cache this ticket */
|
||||
gRootTicketBuffer = ticketBuffer;
|
||||
gRootTicketLength = ticketLength;
|
||||
gRootTicket = apTicket;
|
||||
gRootTicketIsRestore = isRestore;
|
||||
|
||||
/* Cache the new ticket digest */
|
||||
sha1_calculate( gRootTicketBuffer, gRootTicketLength, gRootTicketHashBuffer );
|
||||
|
||||
/* Debug logging */
|
||||
{
|
||||
char hashStr[2 * CCSHA1_OUTPUT_SIZE + 1];
|
||||
sprint_hex(gRootTicketHashBuffer, sizeof(gRootTicketHashBuffer), hashStr, sizeof(hashStr));
|
||||
TICKETLOG( "cached ticket (%u bytes) hash: %s", ticketLength, hashStr );
|
||||
}
|
||||
|
||||
if ( outTicketLength != NULL ) {
|
||||
*outTicketLength = ticketLength;
|
||||
}
|
||||
|
||||
/* success */
|
||||
status = 0;
|
||||
|
||||
exit:
|
||||
if ( status != 0 ) {
|
||||
if ( ticketBuffer != NULL ) {
|
||||
free( ticketBuffer );
|
||||
}
|
||||
|
||||
if ( apTicket != NULL ) {
|
||||
free( apTicket );
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* "ticket" command handler
|
||||
* This command allows the host to specify a temporary RAM-based ticket.
|
||||
*/
|
||||
int
|
||||
do_ticket( int argc, struct cmd_arg *argv )
|
||||
{
|
||||
int status = -1;
|
||||
size_t ticketLength;
|
||||
size_t bufferLength;
|
||||
|
||||
bufferLength = env_get_uint( "filesize", 0 );
|
||||
|
||||
require_action( bufferLength > 0, exit, status = -1; TICKETLOG("filesize invalid or not set") );
|
||||
require_action( bufferLength < MAX_TICKET_LENGTH, exit, status = -1; TICKETLOG("filesize too large") );
|
||||
check( MAX_TICKET_LENGTH <= mib_get_size(kMIBTargetDefaultLoadSize) );
|
||||
|
||||
status = ticket_set( (uint8_t *) mib_get_addr(kMIBTargetDefaultLoadAddress), bufferLength, true, &ticketLength );
|
||||
require( status == 0, exit );
|
||||
require_action( ticketLength == bufferLength, exit, status = -1; TICKETLOG("garbage following ticket") );
|
||||
|
||||
/* success */
|
||||
status = 0;
|
||||
|
||||
exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
#if DEBUG_BUILD
|
||||
int
|
||||
do_ticket_dump( int argc, struct cmd_arg *argv )
|
||||
{
|
||||
int status = -1;
|
||||
|
||||
if ( gRootTicketBuffer == NULL ) {
|
||||
printf( "no ticket\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
hexdump( gRootTicketBuffer, gRootTicketLength );
|
||||
|
||||
status = 0;
|
||||
|
||||
exit:
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
bool
|
||||
ticket_get_hash( uint8_t *buffer, size_t length )
|
||||
{
|
||||
bool copied;
|
||||
|
||||
if ( gRootTicketBuffer != NULL && length >= sizeof(gRootTicketHashBuffer) ) {
|
||||
memcpy( buffer, gRootTicketHashBuffer, sizeof(gRootTicketHashBuffer) );
|
||||
copied = true;
|
||||
}
|
||||
else {
|
||||
copied = false;
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
/* ========================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
* Load a ticket from a file.
|
||||
*/
|
||||
int
|
||||
ticket_load_file( const char *filePath, uintptr_t bufferAddress, size_t bufferLength )
|
||||
{
|
||||
int status = -1;
|
||||
|
||||
size_t fileLength = bufferLength;
|
||||
size_t ticketLength;
|
||||
|
||||
status = fs_load_file(filePath, bufferAddress, &fileLength);
|
||||
require_action( status == 0, exit, status = -1; TICKETLOG("failed to load ticket file: %s", filePath) );
|
||||
|
||||
status = ticket_set( (uint8_t *) bufferAddress, fileLength, true, &ticketLength );
|
||||
require_action( status == 0, exit, status = -1; TICKETLOG("unable to set ticket") );
|
||||
|
||||
require_action( ticketLength == fileLength, exit, status = -1; TICKETLOG("garbage following ticket") );
|
||||
|
||||
status = 0;
|
||||
TICKETLOG("successfully loaded ticket file: %s", filePath);
|
||||
|
||||
exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
int
|
||||
do_ticket_file( int argc, struct cmd_arg *args )
|
||||
{
|
||||
int status = -1;
|
||||
|
||||
require_action( argc == 2, exit, printf("%s <path>\n", args[0].str) );
|
||||
|
||||
status = ticket_load_file( args[1].str,
|
||||
mib_get_addr(kMIBTargetDefaultLoadAddress),
|
||||
mib_get_size(kMIBTargetDefaultLoadSize) );
|
||||
require( status == 0, exit );
|
||||
|
||||
status = 0;
|
||||
|
||||
exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
int
|
||||
ticket_load()
|
||||
{
|
||||
int status = -1;
|
||||
|
||||
struct image_info *imageInfo;
|
||||
uint8_t *dataBuffer;
|
||||
size_t dataLength;
|
||||
size_t ticketLength;
|
||||
size_t objectLength;
|
||||
uint8_t *objectBuffer = NULL;
|
||||
bool restoreTicket;
|
||||
|
||||
/* Find the ticket image */
|
||||
imageInfo = image_find( 'SCAB' );
|
||||
require_action( imageInfo != NULL, exit, TICKETLOG("no ticket found") );
|
||||
require_action( imageInfo->imagePrivateMagic == IMAGE3_IMAGE_INFO_MAGIC, exit, TICKETLOG("ticket is not image3") );
|
||||
|
||||
/*
|
||||
* Allocate a buffer for the image and load it into the buffer
|
||||
* without attempting to validate it.
|
||||
*/
|
||||
objectLength = imageInfo->imageAllocation;
|
||||
objectBuffer = (uint8_t *) malloc( objectLength );
|
||||
imageInfo->imageOptions |= IMAGE_OPTION_JUST_LOAD;
|
||||
|
||||
/*
|
||||
* Accept any type (pass NULL)
|
||||
*/
|
||||
if ( image_load(imageInfo, NULL, 0, NULL, (void **)&objectBuffer, &objectLength) ) {
|
||||
TICKETLOG( "failed to load ticket image3" );
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/*
|
||||
* The ticket is always supplied in an image3 object with tags in
|
||||
* the order TYPE, DATA, resulting in an offset from the buffer to
|
||||
* the actual ticket data of 0x40 bytes.
|
||||
*
|
||||
* We don't try to introspect or validate the image here -
|
||||
* ticket_set below will reject the data if it is not a
|
||||
* well-formed ticket.
|
||||
*/
|
||||
if (objectLength < 0x40)
|
||||
goto exit;
|
||||
dataBuffer = objectBuffer + 0x40;
|
||||
dataLength = objectLength - 0x40;
|
||||
if (dataLength > MAX_TICKET_LENGTH)
|
||||
goto exit;
|
||||
|
||||
/* If the ticket comes from a memory blockdev,
|
||||
use the stricter restore ticket validation */
|
||||
restoreTicket = (imageInfo->imageOptions & IMAGE_OPTION_MEMORY) != 0;
|
||||
|
||||
/*
|
||||
* Parse the ticket data.
|
||||
*/
|
||||
status = ticket_set( dataBuffer, dataLength, restoreTicket, &ticketLength );
|
||||
|
||||
exit:
|
||||
|
||||
if ( objectBuffer != NULL ) {
|
||||
free( objectBuffer );
|
||||
}
|
||||
|
||||
TICKETLOG( "exiting" );
|
||||
return status;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
int
|
||||
do_ticket_load( int argc, struct cmd_arg *argv )
|
||||
{
|
||||
int status = -1;
|
||||
|
||||
status = ticket_load();
|
||||
require( status == 0, exit );
|
||||
|
||||
/* success */
|
||||
status = 0;
|
||||
|
||||
exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
Reference in new issue
Block a user