iBoot/lib/pki/chain-validation.c

663 lines
21 KiB
C

/*
* Copyright (C) 2007-2008 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 <platform.h>
#include <libDER/DER_CertCrl.h>
#include <libDER/DER_Keys.h>
#include <libDER/DER_Digest.h>
#include <libDER/oids.h>
#include <libDER/asn1Types.h>
#ifndef WITH_HW_PKE
#include <libgRSA/libgRSA.h>
#include <libgRSA/libgRSA_DER.h>
#include <libgRSA/libgRSA_priv.h>
#include <libGiants/giantDebug.h>
#endif
#include <debug.h>
#if DEBUG_BUILD
#ifndef DEBUG_ASSERT_PRODUCTION_CODE
#define DEBUG_ASSERT_PRODUCTION_CODE 0
#endif
#endif
#include <AssertMacros.h>
#include <drivers/pke.h>
#include <drivers/sha1.h>
#include <lib/pki.h>
#if PKI_APPLE_ROOT_CA
#include <apple_ca.h>
#else
#include <fake_ca.h>
#endif
typedef struct {
DERItem subjectKeyID;
DERItem authorityKeyID;
DERItem appleSpecBlob;
DERItem appleSpecTicketBlob;
} DERExtensions;
static int extension_octet_string_value(DERExtension *extn, bool critical,
DERItem *value)
{
DERDecodedInfo extn_info;
#if 0
if (critical) {
/* this is more important to be enforced when issued */
bool critical;
require_noerr(DERParseBoolean(&extn->critical, false, &critical), out);
require(critical, out);
}
#endif
/* get value */
require_noerr(DERDecodeItem(&extn->extnValue, &extn_info), out);
require(extn_info.tag == ASN1_OCTET_STRING, out);
*value = extn_info.content;
return 0;
out:
return -1;
}
static int extension_sequence_item(DERExtension *extn, bool critical,
DERItem *value)
{
DERDecodedInfo extn_info;
#if 0
if (critical) {
/* this is more important to be enforced when issued */
bool critical;
require_noerr(DERParseBoolean(&extn->critical, false, &critical), out);
require(critical, out);
}
#endif
/* get the sequence */
require_noerr(DERDecodeItem(&extn->extnValue, &extn_info), out);
require(extn_info.tag == ASN1_CONSTR_SEQUENCE, out);
*value = extn->extnValue;
return 0;
out:
return -1;
}
/**
* Find extension with oidAppleSecureBootCertSpec oid and return octet string.
* It's not an error to not find it, but general parse errors are flagged.
*/
static int parse_extensions(DERTBSCert *tbsCert, DERExtensions *extensions)
{
DERSequence derSeq;
DERTag tag;
DERDecodedInfo currDecoded;
DERExtension extn;
bzero(extensions, sizeof(*extensions));
if (tbsCert->extensions.length) {
require_noerr(DERDecodeSeqInit(&tbsCert->extensions, &tag, &derSeq), out);
require(tag == ASN1_CONSTR_SEQUENCE, out);
while (DERDecodeSeqNext(&derSeq, &currDecoded) == DR_Success) {
require(currDecoded.tag == ASN1_CONSTR_SEQUENCE, out);
require_noerr(DERParseSequenceContent(&currDecoded.content,
DERNumExtensionItemSpecs, DERExtensionItemSpecs,
&extn, sizeof(extn)), out);
if (DEROidCompare(&oidAppleSecureBootTicketCertSpec, &extn.extnID)) {
require_noerr(extension_sequence_item(&extn, false,
&extensions->appleSpecTicketBlob), out);
dprintf(DEBUG_SPEW, "found apple spec (ticket) extension (%p, %u)\n",
extensions->appleSpecTicketBlob.data,
extensions->appleSpecTicketBlob.length);
}
else if (DEROidCompare(&oidAppleSecureBootCertSpec, &extn.extnID)) {
require_noerr(extension_octet_string_value(&extn, false,
&extensions->appleSpecBlob), out);
dprintf(DEBUG_SPEW, "found apple spec (image3) extension (%p, %d)\n",
extensions->appleSpecBlob.data,
extensions->appleSpecBlob.length);
}
#if PKI_CHECK_KEY_IDS
else if (DEROidCompare(&oidSubjectKeyIdentifier, &extn.extnID)) {
require_noerr(extension_octet_string_value(&extn, false,
&extensions->subjectKeyID), out);
dprintf(DEBUG_SPEW, "found subject key id extension (%p, %d)\n",
extensions->subjectKeyID.data,
extensions->subjectKeyID.length);
}
else if (DEROidCompare(&oidAuthorityKeyIdentifier, &extn.extnID)) {
DERAuthorityKeyIdentifier authority_key_id_seq;
require_noerr(DERParseSequence(&extn.extnValue,
DERNumAuthorityKeyIdentifierItemSpecs,
DERAuthorityKeyIdentifierItemSpecs,
&authority_key_id_seq,
sizeof(authority_key_id_seq)), out);
if (authority_key_id_seq.keyIdentifier.length) {
extensions->authorityKeyID =
authority_key_id_seq.keyIdentifier;
dprintf(DEBUG_SPEW, "found auth key id extension (%p, %d)\n",
extensions->authorityKeyID.data,
extensions->authorityKeyID.length);
}
}
#endif
}
}
return 0;
out:
return -1;
}
static inline int dercmp(const DERItem a, const DERItem b)
{
if (a.length != b.length)
return -1;
if (memcmp(a.data, b.data, a.length))
return -1;
return 0;
}
/* Take a byte string and reverse it: giants layout on little endian */
static int rsa_set_giant(uint8_t *destination, size_t *dest_len,
uint8_t *source, size_t length, bool should_trim)
{
size_t len = length;
uint8_t *end = source;
uint8_t *dst = destination;
if(should_trim) {
/* ASN.1 integers may start with a zero to avoid sign interpretation */
if( len && !(*end) ) {
end++;
len--;
}
}
/* If it won't fit or zero we won't continue */
if (!len || len > *dest_len) {
*dest_len = 0;
return -1;
}
/* Reverse bytes: bignums start with lsb, word size doesn't matter for
little endian */
uint8_t *src = source+length-1;
while (src >= end)
*dst++ = *src--;
*dest_len = len;
return 0;
}
#if WITH_HW_PKE
/* Verify signed encoded digest info with rsa pubKey against signature */
static int verify_pkcs1_sig(const DERItem *pubKey /* PKCS1 format */,
const DERItem *digest_info, const DERItem *sig)
{
DERItem keyItem = {(DERByte *)pubKey->data, pubKey->length};
DERRSAPubKeyPKCS1 decodedKey;
struct {
uint8_t pmod[256];
uint8_t base[256];
uint8_t expn[256];
uint8_t mods[256];
size_t pmod_length;
size_t base_length;
size_t expn_length;
size_t mods_length;
size_t key_length;
} request;
require_noerr(DERParseSequence(&keyItem,
DERNumRSAPubKeyPKCS1ItemSpecs, DERRSAPubKeyPKCS1ItemSpecs,
&decodedKey, sizeof(decodedKey)), out);
bzero(&request, sizeof(request));
request.pmod_length = sizeof(request.pmod);
request.base_length = sizeof(request.base);
request.expn_length = sizeof(request.expn);
request.mods_length = sizeof(request.mods);
//Modulus and expn can have a leading zero padded to indicate a positive num.
//So trim those zeros out.
require_noerr(rsa_set_giant(request.mods, &request.mods_length,
decodedKey.modulus.data, decodedKey.modulus.length, true), out);
require_noerr(rsa_set_giant(request.expn, &request.expn_length,
decodedKey.pubExponent.data, decodedKey.pubExponent.length, true), out);
require_noerr(rsa_set_giant(request.base, &request.base_length,
sig->data, sig->length, false), out);
request.key_length = request.mods_length * 8;
require(pke_do_exp(request.pmod, &request.pmod_length,
request.key_length,
request.base, request.base_length,
request.expn, request.expn_length,
request.mods, request.mods_length), out);
/* verify padding: 0x00, 0x01, 0xff**0xff, 0x00, {algid, hash}: */
/* minimal pad length; digest_info->length although passed in is fixed. */
require(request.pmod_length >= digest_info->length + 11, out);
uint8_t *ptr = request.pmod + request.pmod_length - 1;
uint8_t *end = request.pmod;
uint8_t result = 0;
/* start with 0 */
result |= *ptr-- ^ 0;
require(ptr>end, out);
/* block type 1 */
result |= *ptr-- ^ 1;
require(ptr>end, out);
/* all 0xff */
while (*ptr == 0xff) {
require(ptr>end, out);
ptr--;
}
/* terminated by 0 */
result |= *ptr-- ^ 0;
require(ptr>end, out);
/* digest_info->length bytes left */
require(end + digest_info->length - 1 == ptr, out);
uint8_t *hash = digest_info->data;
/* match SHA-1 hash */
while (ptr>=end) {
result |= *ptr-- ^ *hash++;
}
require(result == 0, out);
return 0;
out:
return -1;
}
#else /* !WITH_HW_PKE */
/*
* Verify a PKCS1 signature with an RSA key. Caller is responsible for
* EncodedDigestInfo formatting.
*/
static int verify_pkcs1_sig(
const DERItem *pubKey, /* PKCS1 format */
const DERItem *toVerify,
const DERItem *sig)
{
RSAPubGiantKey rsaPubKey;
RSAStatus rrtn;
GI_CHECK_SP("verifyPkcs1Sig");
/*
* Convert PKCS1 format key to native libgRSA key.
* Ideally we'd like to have the key in Apple Custom form,
* with the reciprocal, obtainable from cert,
* Someday maybe.
*/
GI_LOG_SP("calling RSA_DecodePubKey");
rrtn = RSA_DecodePubKey(pubKey->data, pubKey->length, &rsaPubKey);
require_noerr(rrtn, out);
GI_LOG_SP("calling RSA_SigVerify");
rrtn = RSA_SigVerify(&rsaPubKey, RP_PKCS1,
(const gi_uint8 *)toVerify->data, (gi_uint16)toVerify->length,
(const gi_uint8 *)sig->data, (gi_uint16)sig->length);
require_noerr(rrtn, out);
return 0;
out:
return -1;
}
#endif
/*
* Given data to sign or verify, take the SHA1 digest of it and encode the result in
* an EncodedDigestInfo. The result is DER_SHA1_DIGEST_INFO_LEN bytes, allocated by the
* caller.
*/
static int sha1DigestInfo(const DERItem *ptext, unsigned char *digestInfo)
/* DER_SHA1_DIGEST_INFO_LEN bytes RETURNED */
{
DERByte digest[DER_SHA1_DIGEST_LEN];
DERReturn resultLen = DER_SHA1_DIGEST_INFO_LEN;
/* take the SHA1 digest */
sha1_calculate(ptext->data, ptext->length, digest);
/* now roll it into an EncodedDigestInfo */
return DEREncodeSHA1DigestInfo(digest, DER_SHA1_DIGEST_LEN,
digestInfo, &resultLen);
}
/* decode an algorithm ID */
static int decodeAlgId(const DERItem *encodedAlgId, DERAlgorithmId *decodedAlgId)
{
return DERParseSequenceContent(encodedAlgId,
DERNumAlgorithmIdItemSpecs, DERAlgorithmIdItemSpecs,
decodedAlgId, sizeof(*decodedAlgId));
}
/*
* Obtain a PKCS1-format public key from a SubjectPubKeyInfo (tbs.subjectPubKey).
*/
static int decodePubKey(DERItem *pubKeyInfoContent, DERItem *pubKeyPkcs1)
{
DERSubjPubKeyInfo pubKeyInfo;
DERByte numUnused;
DERAlgorithmId algId;
/* sequence we're given: encoded DERSubjPubKeyInfo */
require_noerr(DERParseSequenceContent(pubKeyInfoContent,
DERNumSubjPubKeyInfoItemSpecs, DERSubjPubKeyInfoItemSpecs,
&pubKeyInfo, sizeof(pubKeyInfo)), out);
/* verify that this is an RSA key by decoding the AlgId */
require_noerr(decodeAlgId(&pubKeyInfo.algId, &algId), out);
require(DEROidCompare(&algId.oid, &oidRsa), out);
/*
* The contents of pubKeyInfo.pubKey is a bit string whose contents
* are a PKCS1 format RSA key.
*/
require_noerr(DERParseBitString(&pubKeyInfo.pubKey, pubKeyPkcs1, &numUnused), out);
return 0;
out:
return -1;
}
static int crack_chain(DERItem *certblob, DERItem chain[], DERShort chain_len)
{
DERItem der_iter = *certblob;
DERShort num = 0;
DERSize used;
DERDecodedInfo topDecode;
dprintf(DEBUG_SPEW, "PKI: break certificate chain into certs\n");
do {
require_noerr(DERDecodeItem(&der_iter, &topDecode), out);
used = topDecode.content.data + topDecode.content.length - der_iter.data;
require(used < (128 * 1024), out); //no single cert larger than 128k
require(der_iter.length >= used, out);
require(num < chain_len, out);
chain[num].length = used;
chain[num].data = der_iter.data;
num++;
der_iter.length -= used;
der_iter.data += used;
} while (der_iter.length > 0);
#if PKI_CHECK_ANCHOR_BY_SHA1
/* require 3 element long chain */
require(num == chain_len, out);
/* check SHA-1 of (whole) root certificate (Apple root CA) */
dprintf(DEBUG_SPEW, "PKI: check SHA-1 of root\n");
unsigned char sha1[DER_SHA1_DIGEST_LEN];
sha1_calculate(chain[0].data, chain[0].length, sha1);
require_noerr(memcmp(ROOT_CA_SHA1_HASH, sha1, DER_SHA1_DIGEST_LEN), out);
#else
/* make room for root CA */
if (num == chain_len - 1) {
DERShort index;
for (index = chain_len - 1; index > 0; index--)
chain[index] = chain[index - 1];
}
else
require(num == chain_len, out);
/* use embedded root ca; override if one was passed in */
dprintf(DEBUG_SPEW, "PKI: anchor chain to %s CA\n",
PKI_APPLE_ROOT_CA ? "Apple" : "fake");
chain[0].data = (u_int8_t*)&ROOT_CA_CERTIFICATE;
chain[0].length = sizeof(ROOT_CA_CERTIFICATE);
#endif /* PKI_CHECK_ANCHOR_BY_SHA1 */
return 0;
out:
return -1;
}
static int parse_chain(DERItem chain[], DERShort chain_len,
DERSignedCertCrl chainElements[], DERTBSCert tbsCerts[],
DERItem derPubKey[], DERExtensions extensions[])
{
DERShort num = 0;
/* top level decode */
for (num = 0; num < chain_len; num++) {
dprintf(DEBUG_SPEW, "PKI: parse cert %d of %d\n", num + 1, chain_len);
require_noerr(DERParseSequence(&chain[num],
DERNumSignedCertCrlItemSpecs, DERSignedCertCrlItemSpecs,
&chainElements[num], sizeof(*chainElements)), out);
require_noerr(DERParseSequence(&chainElements[num].tbs,
DERNumTBSCertItemSpecs, DERTBSCertItemSpecs,
&tbsCerts[num], sizeof(*tbsCerts)), out);
require_noerr(decodePubKey(&tbsCerts[num].subjectPubKey, &derPubKey[num]), out);
require_noerr(parse_extensions(&tbsCerts[num], &extensions[num]), out);
}
return 0;
out:
return -1;
}
static int verify_chain_signatures(
DERItem *issuerPubKey,
DERSignedCertCrl *signedCert)
{
DERAlgorithmId algId;
unsigned char digestInfo[DER_SHA1_DIGEST_INFO_LEN];
DERByte numUnused;
DERItem toVerify = { digestInfo, DER_SHA1_DIGEST_INFO_LEN };
DERItem sigBytes;
/* figure out which digest to use from the sig algId */
require_noerr(decodeAlgId(&signedCert->sigAlg, &algId), out);
/* get the encodedDigestInfo from the digest of the subject's TBSCert */
require(DEROidCompare(&algId.oid, &oidSha1Rsa), out);
/* generate digest info from plaintext */
require_noerr(sha1DigestInfo(&signedCert->tbs, digestInfo), out);
/* get contents of sig, a bit string, as raw bytes */
require_noerr(DERParseBitString(&signedCert->sig, &sigBytes, &numUnused), out);
require_noerr(verify_pkcs1_sig(issuerPubKey, &toVerify, &sigBytes), out);
return 0;
out:
return -1;
}
/* find first value for oid */
static int find_content_by_oid(const DERItem *rdnSetContent,
const DERItem *oid, DERItem *content)
{
DERReturn drtn;
DERSequence rdn;
DERDecodedInfo atvContent;
DERAttributeTypeAndValue atv;
require_noerr(DERDecodeSeqContentInit(rdnSetContent, &rdn), out);
while ((drtn = DERDecodeSeqNext(&rdn, &atvContent)) == DR_Success) {
require(atvContent.tag == ASN1_CONSTR_SEQUENCE, out);
require_noerr(DERParseSequenceContent(&atvContent.content,
DERNumAttributeTypeAndValueItemSpecs,
DERAttributeTypeAndValueItemSpecs,
&atv, sizeof(atv)), out);
if (DEROidCompare(oid, &atv.type)) {
*content = atv.value;
return 0;
}
}
return drtn;
out:
return -1;
}
static int parse_common_name(const DERItem *subject, const DERItem *oid, DERItem *content)
{
DERSequence derSeq;
DERDecodedInfo currDecoded;
require_noerr(DERDecodeSeqContentInit(subject, &derSeq), out);
while (DERDecodeSeqNext(&derSeq, &currDecoded) == DR_Success) {
require(currDecoded.tag == ASN1_CONSTR_SET, out);
/* only if we reached the end of a sequence do we keep looking */
int status = find_content_by_oid(&currDecoded.content, oid, content);
if (status != DR_EndOfSequence)
return status;
}
out:
return -1;
}
static int verify_signature_with_hash(DERItem *public_key_pkcs1_rsa,
unsigned char *sig_blob, unsigned sig_blob_len,
unsigned char *hash_blob, unsigned hash_blob_len)
{
unsigned char digest_data[DER_SHA1_DIGEST_INFO_LEN];
DERItem signature = { sig_blob, sig_blob_len };
DERItem digest_info = { digest_data, sizeof(digest_data) };
/* generate digest info blob from hash */
require(hash_blob_len == DER_SHA1_DIGEST_LEN, out);
require_noerr(DEREncodeSHA1DigestInfo(hash_blob, hash_blob_len,
digest_info.data, &digest_info.length), out);
/* verify signature */
return verify_pkcs1_sig(public_key_pkcs1_rsa, &digest_info, &signature);
out:
return -1;
}
int verify_signature_with_chain(void *chain_blob, size_t chain_blob_length,
void *sig_blob, size_t sig_blob_len,
void *hash_blob, size_t hash_blob_len,
void **img3_spec_blob, size_t *img3_spec_blob_len,
void **ticket_spec_blob, size_t *ticket_spec_blob_len)
{
DERShort index;
const uint8_t chain_len = 3;
DERItem chain = { chain_blob, chain_blob_length };
DERItem certs[chain_len];
DERSignedCertCrl signedCert[chain_len];
DERTBSCert tbsCert[chain_len];
DERItem pubKeyPkcs1[chain_len];
DERExtensions extensions[chain_len];
require_noerr(crack_chain(&chain, certs, chain_len), out);
require_noerr(parse_chain(certs, chain_len, signedCert, tbsCert,
pubKeyPkcs1, extensions), out);
/* check linkage */
index = 1;
while (index < chain_len) {
dprintf(DEBUG_SPEW, "PKI: verify cert %d was issued by %d\n", index, index-1);
require_noerr(dercmp(tbsCert[index-1].subject,
tbsCert[index].issuer), out);
#if PKI_CHECK_KEY_IDS
require_noerr(dercmp(extensions[index-1].subjectKeyID,
extensions[index].authorityKeyID), out);
#endif
require_noerr(verify_chain_signatures(&pubKeyPkcs1[index-1], &signedCert[index]), out);
index++;
}
/* check common name intermediary cert to restrict to secure boot sub CA */
DERItem commonName = { NULL, 0 };
require_noerr(parse_common_name(&tbsCert[1].subject, &oidCommonName, &commonName), out);
// first character (0x13) - ASN1_PRINTABLE_STRING
// second character (0x29) - Length (copied over a cert)
static const DERByte sboot_common_name[] = "\x13\x29""Apple Secure Boot Certification Authority";
static const DERItem sboot_item = {
.data = (DERByte *)sboot_common_name,
.length = 0x2b
};
dprintf(DEBUG_SPEW, "PKI: check intermediate cert for common name '%s'\n", sboot_common_name);
require(DEROidCompare(&sboot_item, &commonName), out);
dprintf(DEBUG_SPEW, "PKI: check payload hash with signature\n");
require_noerr(verify_signature_with_hash(&pubKeyPkcs1[chain_len-1],
sig_blob, sig_blob_len, hash_blob, hash_blob_len), out);
/* return pointer to leaf blob in cert chain if it exists */
if (extensions[chain_len-1].appleSpecTicketBlob.data &&
extensions[chain_len-1].appleSpecTicketBlob.length) {
if (ticket_spec_blob != NULL && ticket_spec_blob_len != NULL) {
*ticket_spec_blob = extensions[chain_len-1].appleSpecTicketBlob.data;
*ticket_spec_blob_len = extensions[chain_len-1].appleSpecTicketBlob.length;
dprintf(DEBUG_SPEW, "PKI: parse apple spec id extension (ticket) in leaf: (%p, %zu)\n", *ticket_spec_blob, *ticket_spec_blob_len);
}
}
else if (extensions[chain_len-1].appleSpecBlob.data &&
extensions[chain_len-1].appleSpecBlob.length) {
if (img3_spec_blob != NULL && img3_spec_blob_len != NULL) {
*img3_spec_blob = extensions[chain_len-1].appleSpecBlob.data;
*img3_spec_blob_len = extensions[chain_len-1].appleSpecBlob.length;
dprintf(DEBUG_SPEW, "PKI: parse apple spec id extension (image3) in leaf: (%p, %zu)\n", *img3_spec_blob, *img3_spec_blob_len);
}
}
#if NO_DEFAULT_PLATFORM_PROVIDE_SPEC_BLOB
else /* ask the platform to provide a leaf blob */
{
DERTBSCert *leaf = &tbsCert[chain_len-1];
DERItem leafCommonName = { NULL, 0 };
require_noerr(parse_common_name(&leaf->subject, &oidCommonName, &leafCommonName), out);
require(platform_provide_spec_blob(leafCommonName.data, leafCommonName.length,
leaf->serialNum.data, leaf->serialNum.length,
img3_spec_blob, img3_spec_blob_len), out);
}
#endif // NO_DEFAULT_PLATFORM_PROVIDE_SPEC_BLOB
return 0;
out:
return -1;
}