1/*
2 * Copyright (c) 2006-2010 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24//
25// cdbuilder - constructor for CodeDirectories
26//
27#include "cdbuilder.h"
28#include <security_utilities/memutils.h>
29#include <cmath>
30
31using namespace UnixPlusPlus;
32using LowLevelMemoryUtilities::alignUp;
33
34
35namespace Security {
36namespace CodeSigning {
37
38
39//
40// Create an (empty) builder
41//
42CodeDirectory::Builder::Builder(HashAlgorithm digestAlgorithm)
43	: mFlags(0),
44	  mHashType(digestAlgorithm),
45	  mSpecialSlots(0),
46	  mCodeSlots(0),
47	  mScatter(NULL),
48	  mScatterSize(0),
49	  mDir(NULL)
50{
51	mDigestLength = (uint32_t)MakeHash<Builder>(this)->digestLength();
52	mSpecial = (unsigned char *)calloc(cdSlotMax, mDigestLength);
53}
54
55CodeDirectory::Builder::~Builder()
56{
57	::free(mSpecial);
58	::free(mScatter);
59}
60
61
62//
63// Set the source of the main executable (i.e. the code pages)
64//
65void CodeDirectory::Builder::executable(string path,
66	size_t pagesize, size_t offset, size_t length)
67{
68	mExec.close();			// any previously opened one
69	mExec.open(path);
70	mPageSize = pagesize;
71	mExecOffset = offset;
72	mExecLength = length;
73}
74
75void CodeDirectory::Builder::reopen(string path, size_t offset, size_t length)
76{
77	assert(mExec);					// already called executable()
78	mExec.close();
79	mExec.open(path);
80	mExecOffset = offset;
81	mExecLength = length;
82}
83
84
85//
86// Set the source for one special slot
87//
88void CodeDirectory::Builder::specialSlot(SpecialSlot slot, CFDataRef data)
89{
90	assert(slot <= cdSlotMax);
91	MakeHash<Builder> hash(this);
92	hash->update(CFDataGetBytePtr(data), CFDataGetLength(data));
93	hash->finish(specialSlot(slot));
94	if (slot >= mSpecialSlots)
95		mSpecialSlots = slot;
96}
97
98
99//
100// Allocate a Scatter vector
101//
102CodeDirectory::Scatter *CodeDirectory::Builder::scatter(unsigned count)
103{
104	mScatterSize = (count + 1) * sizeof(Scatter);
105	if (!(mScatter = (Scatter *)::realloc(mScatter, mScatterSize)))
106		UnixError::throwMe(ENOMEM);
107	::memset(mScatter, 0, mScatterSize);
108	return mScatter;
109}
110
111// This calculates the fixed size of the code directory
112// Because of <rdar://problem/16102695>, if the team ID
113// field is not used, we leave out the team ID offset
114// as well, to keep cd hashes consistent between
115// versions.
116const size_t CodeDirectory::Builder::fixedSize(const uint32_t version)
117{
118	size_t cdSize = sizeof(CodeDirectory);
119	if (version < supportsTeamID)
120		cdSize -= sizeof(mDir->teamIDOffset);
121
122	return cdSize;
123}
124
125//
126// Calculate the size we'll need for the CodeDirectory as described so far
127//
128size_t CodeDirectory::Builder::size(const uint32_t version)
129{
130	assert(mExec);			// must have called executable()
131	if (mExecLength == 0)
132		mExecLength = mExec.fileSize() - mExecOffset;
133
134	// how many code pages?
135	if (mPageSize == 0) {	// indefinite - one page
136		mCodeSlots = (mExecLength > 0);
137	} else {				// finite - calculate from file size
138		mCodeSlots = (mExecLength + mPageSize - 1) / mPageSize; // round up
139	}
140
141	size_t offset = fixedSize(version);
142
143	offset += mScatterSize;				// scatter vector
144	offset += mIdentifier.size() + 1;	// size of identifier (with null byte)
145	if (mTeamID.size())
146		offset += mTeamID.size() + 1;	// size of teamID (with null byte)
147	offset += (mCodeSlots + mSpecialSlots) * mDigestLength; // hash vector
148
149	return offset;
150}
151
152
153//
154// Take everything added to date and wrap it up in a shiny new CodeDirectory.
155//
156// Note that this only constructs a CodeDirectory; it does not touch any subsidiary
157// structures (resource tables, etc.), nor does it create any signature to secure
158// the CodeDirectory.
159// The returned CodeDirectory object is yours, and you may modify it as desired.
160// But the memory layout is set here, so the various sizes and counts should be good
161// when you call build().
162// It's up to us to order the dynamic fields as we wish; but note that we currently
163// don't pad them, and so they should be allocated in non-increasing order of required
164// alignment. Make sure to keep the code here in sync with the size-calculating code above.
165//
166CodeDirectory *CodeDirectory::Builder::build()
167{
168	assert(mExec);			// must have (successfully) called executable()
169	uint32_t version;
170
171	// size and allocate
172	size_t identLength = mIdentifier.size() + 1;
173	size_t teamIDLength = mTeamID.size() + 1;
174
175	// Determine the version
176	if (mTeamID.size()) {
177		version = currentVersion;
178	} else {
179		version = supportsScatter;
180	}
181
182	size_t total = size(version);
183	if (!(mDir = (CodeDirectory *)calloc(1, total)))	// initialize to zero
184		UnixError::throwMe(ENOMEM);
185
186	// fill header
187	mDir->initialize(total);
188	mDir->version = version;
189	mDir->flags = mFlags;
190	mDir->nSpecialSlots = (uint32_t)mSpecialSlots;
191	mDir->nCodeSlots = (uint32_t)mCodeSlots;
192	mDir->codeLimit = (uint32_t)mExecLength;
193	mDir->hashType = mHashType;
194	mDir->hashSize = mDigestLength;
195	if (mPageSize) {
196		int pglog;
197		assert(frexp(mPageSize, &pglog) == 0.5); // must be power of 2
198		frexp(mPageSize, &pglog);
199		assert(pglog < 256);
200		mDir->pageSize = pglog - 1;
201	} else
202		mDir->pageSize = 0;	// means infinite page size
203
204	// locate and fill flex fields
205	size_t offset = fixedSize(mDir->version);
206
207	if (mScatter) {
208		mDir->scatterOffset = (uint32_t)offset;
209		memcpy(mDir->scatterVector(), mScatter, mScatterSize);
210		offset += mScatterSize;
211	}
212
213	mDir->identOffset = (uint32_t)offset;
214	memcpy(mDir->identifier(), mIdentifier.c_str(), identLength);
215	offset += identLength;
216
217	if (mTeamID.size()) {
218		mDir->teamIDOffset = (uint32_t)offset;
219		memcpy(mDir->teamID(), mTeamID.c_str(), teamIDLength);
220		offset += teamIDLength;
221	}
222	// (add new flexibly-allocated fields here)
223
224	mDir->hashOffset = (uint32_t)(offset + mSpecialSlots * mDigestLength);
225	offset += (mSpecialSlots + mCodeSlots) * mDigestLength;
226	assert(offset == total);	// matches allocated size
227
228	// fill special slots
229	memset((*mDir)[(int)-mSpecialSlots], 0, mDigestLength * mSpecialSlots);
230	for (size_t slot = 1; slot <= mSpecialSlots; ++slot)
231		memcpy((*mDir)[(int)-slot], specialSlot((SpecialSlot)slot), mDigestLength);
232
233	// fill code slots
234	mExec.seek(mExecOffset);
235	size_t remaining = mExecLength;
236	for (unsigned int slot = 0; slot < mCodeSlots; ++slot) {
237		size_t thisPage = min(mPageSize, remaining);
238		MakeHash<Builder> hasher(this);
239		generateHash(hasher, mExec, (*mDir)[slot], thisPage);
240		remaining -= thisPage;
241	}
242
243	// all done. Pass ownership to caller
244	return mDir;
245}
246
247
248}	// CodeSigning
249}	// Security
250