1/*
2 * Copyright (c) 2006-2007 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#include "filediskrep.h"
24#include "StaticCode.h"
25#include <security_utilities/macho++.h>
26#include <cstring>
27
28
29namespace Security {
30namespace CodeSigning {
31
32using namespace UnixPlusPlus;
33
34
35//
36// Everything's lazy in here
37//
38FileDiskRep::FileDiskRep(const char *path)
39	: SingleDiskRep(path)
40{
41	CODESIGN_DISKREP_CREATE_FILE(this, (char*)path);
42}
43
44
45//
46// Produce an extended attribute name from a canonical slot name
47//
48string FileDiskRep::attrName(const char *name)
49{
50	static const char prefix[] = "com.apple.cs.";
51	return string(prefix) + name;
52}
53
54
55//
56// Retrieve an extended attribute by name
57//
58CFDataRef FileDiskRep::getAttribute(const char *name)
59{
60	string aname = attrName(name);
61	try {
62		ssize_t length = fd().getAttrLength(aname);
63		if (length < 0)
64			return NULL;		// no such attribute
65		CFMallocData buffer(length);
66		fd().getAttr(aname, buffer, length);
67		return buffer;
68	} catch (const UnixError &err) {
69		// recover some errors that happen in (relatively) benign circumstances
70		switch (err.error) {
71		case ENOTSUP:	// no extended attributes on this filesystem
72		case EPERM:		// filesystem objects to name(?)
73			return NULL;
74		default:
75			throw;
76		}
77	}
78}
79
80
81//
82// Extract and return a component by slot number.
83// If we have a Mach-O binary, use embedded components.
84// Otherwise, look for and return the extended attribute, if any.
85//
86CFDataRef FileDiskRep::component(CodeDirectory::SpecialSlot slot)
87{
88	if (const char *name = CodeDirectory::canonicalSlotName(slot))
89		return getAttribute(name);
90	else
91		return NULL;
92}
93
94
95//
96// Generate a suggested set of internal requirements.
97// We don't really have to say much. However, if we encounter a file that
98// starts with the magic "#!" script marker, we do suggest that this should
99// be a valid host if we can reasonably make out what that is.
100//
101const Requirements *FileDiskRep::defaultRequirements(const Architecture *, const SigningContext &ctx)
102{
103	// read start of file
104	char buffer[256];
105	size_t length = fd().read(buffer, sizeof(buffer), 0);
106	if (length > 3 && buffer[0] == '#' && buffer[1] == '!' && buffer[2] == '/') {
107		// isolate (full) path element in #!/full/path -some -other -stuff
108		if (length == sizeof(buffer))
109			length--;
110		buffer[length] = '\0';
111		char *cmd = buffer + 2;
112		cmd[strcspn(cmd, " \t\n\r\f")] = '\0';
113		secdebug("filediskrep", "looks like a script for %s", cmd);
114		if (cmd[1])
115			try {
116				// find path on disk, get designated requirement (if signed)
117				string path = ctx.sdkPath(cmd);
118				if (RefPointer<DiskRep> rep = DiskRep::bestFileGuess(path))
119					if (SecPointer<SecStaticCode> code = new SecStaticCode(rep))
120						if (const Requirement *req = code->designatedRequirement()) {
121							CODESIGN_SIGN_DEP_INTERP(this, (char*)cmd, (void*)req);
122							// package up as host requirement and return that
123							Requirements::Maker maker;
124							maker.add(kSecHostRequirementType, req->clone());
125							return maker.make();
126						}
127			} catch (...) {
128				secdebug("filediskrep", "exception getting host requirement (ignored)");
129			}
130	}
131	return NULL;
132}
133
134
135string FileDiskRep::format()
136{
137	return "generic";
138}
139
140
141//
142// FileDiskRep::Writers
143//
144DiskRep::Writer *FileDiskRep::writer()
145{
146	return new Writer(this);
147}
148
149
150//
151// Write a component.
152// Note that this isn't concerned with Mach-O writing; this is handled at
153// a much higher level. If we're called, it's extended attribute time.
154//
155void FileDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data)
156{
157	try {
158		fd().setAttr(attrName(CodeDirectory::canonicalSlotName(slot)),
159			CFDataGetBytePtr(data), CFDataGetLength(data));
160	} catch (const UnixError &error) {
161		if (error.error == ERANGE)
162			MacOSError::throwMe(errSecCSCMSTooLarge);
163		throw;
164	}
165}
166
167
168//
169// Clear all signing data
170//
171void FileDiskRep::Writer::remove()
172{
173	for (CodeDirectory::SpecialSlot slot = 0; slot < cdSlotCount; slot++)
174		if (const char *name = CodeDirectory::canonicalSlotName(slot))
175			fd().removeAttr(attrName(name));
176	fd().removeAttr(attrName(kSecCS_SIGNATUREFILE));
177}
178
179
180//
181// We are NOT the preferred store for components because our approach
182// (extended attributes) suffers from some serious limitations.
183//
184bool FileDiskRep::Writer::preferredStore()
185{
186	return false;
187}
188
189
190} // end namespace CodeSigning
191} // end namespace Security
192