1 |
efrain |
1 |
// Copyright 2017 Google
|
|
|
2 |
//
|
|
|
3 |
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
4 |
// you may not use this file except in compliance with the License.
|
|
|
5 |
// You may obtain a copy of the License at
|
|
|
6 |
//
|
|
|
7 |
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
8 |
//
|
|
|
9 |
// Unless required by applicable law or agreed to in writing, software
|
|
|
10 |
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
11 |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
12 |
// See the License for the specific language governing permissions and
|
|
|
13 |
// limitations under the License.
|
|
|
14 |
|
|
|
15 |
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h"
|
|
|
16 |
|
|
|
17 |
#import <Foundation/Foundation.h>
|
|
|
18 |
#import <dlfcn.h>
|
|
|
19 |
#import <mach-o/dyld.h>
|
|
|
20 |
#import <sys/sysctl.h>
|
|
|
21 |
#import <sys/utsname.h>
|
|
|
22 |
#import <objc/runtime.h>
|
|
|
23 |
|
|
|
24 |
#if TARGET_OS_IOS
|
|
|
25 |
#import <UIKit/UIKit.h>
|
|
|
26 |
#endif
|
|
|
27 |
|
|
|
28 |
/// The encryption info struct and constants are missing from the iPhoneSimulator SDK, but not from
|
|
|
29 |
/// the iPhoneOS or Mac OS X SDKs. Since one doesn't ever ship a Simulator binary, we'll just
|
|
|
30 |
/// provide the definitions here.
|
|
|
31 |
#if TARGET_OS_SIMULATOR && !defined(LC_ENCRYPTION_INFO)
|
|
|
32 |
#define LC_ENCRYPTION_INFO 0x21
|
|
|
33 |
struct encryption_info_command {
|
|
|
34 |
uint32_t cmd;
|
|
|
35 |
uint32_t cmdsize;
|
|
|
36 |
uint32_t cryptoff;
|
|
|
37 |
uint32_t cryptsize;
|
|
|
38 |
uint32_t cryptid;
|
|
|
39 |
};
|
|
|
40 |
#endif
|
|
|
41 |
|
|
|
42 |
@implementation GULAppEnvironmentUtil
|
|
|
43 |
|
|
|
44 |
/// A key for the Info.plist to enable or disable checking if the App Store is running in a sandbox.
|
|
|
45 |
/// This will affect your data integrity when using Firebase Analytics, as it will disable some
|
|
|
46 |
/// necessary checks.
|
|
|
47 |
static NSString *const kFIRAppStoreReceiptURLCheckEnabledKey =
|
|
|
48 |
@"FirebaseAppStoreReceiptURLCheckEnabled";
|
|
|
49 |
|
|
|
50 |
/// The file name of the sandbox receipt. This is available on iOS >= 8.0
|
|
|
51 |
static NSString *const kFIRAIdentitySandboxReceiptFileName = @"sandboxReceipt";
|
|
|
52 |
|
|
|
53 |
/// The following copyright from Landon J. Fuller applies to the isAppEncrypted function.
|
|
|
54 |
///
|
|
|
55 |
/// Copyright (c) 2017 Landon J. Fuller <landon@landonf.org>
|
|
|
56 |
/// All rights reserved.
|
|
|
57 |
///
|
|
|
58 |
/// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
|
|
59 |
/// and associated documentation files (the "Software"), to deal in the Software without
|
|
|
60 |
/// restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
|
|
61 |
/// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
|
|
62 |
/// Software is furnished to do so, subject to the following conditions:
|
|
|
63 |
///
|
|
|
64 |
/// The above copyright notice and this permission notice shall be included in all copies or
|
|
|
65 |
/// substantial portions of the Software.
|
|
|
66 |
///
|
|
|
67 |
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
|
|
68 |
/// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
69 |
/// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
|
70 |
/// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
71 |
/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
72 |
///
|
|
|
73 |
/// Comment from <a href="http://iphonedevwiki.net/index.php/Crack_prevention">iPhone Dev Wiki
|
|
|
74 |
/// Crack Prevention</a>:
|
|
|
75 |
/// App Store binaries are signed by both their developer and Apple. This encrypts the binary so
|
|
|
76 |
/// that decryption keys are needed in order to make the binary readable. When iOS executes the
|
|
|
77 |
/// binary, the decryption keys are used to decrypt the binary into a readable state where it is
|
|
|
78 |
/// then loaded into memory and executed. iOS can tell the encryption status of a binary via the
|
|
|
79 |
/// cryptid structure member of LC_ENCRYPTION_INFO MachO load command. If cryptid is a non-zero
|
|
|
80 |
/// value then the binary is encrypted.
|
|
|
81 |
///
|
|
|
82 |
/// 'Cracking' works by letting the kernel decrypt the binary then siphoning the decrypted data into
|
|
|
83 |
/// a new binary file, resigning, and repackaging. This will only work on jailbroken devices as
|
|
|
84 |
/// codesignature validation has been removed. Resigning takes place because while the codesignature
|
|
|
85 |
/// doesn't have to be valid thanks to the jailbreak, it does have to be in place unless you have
|
|
|
86 |
/// AppSync or similar to disable codesignature checks.
|
|
|
87 |
///
|
|
|
88 |
/// More information at <a href="http://landonf.org/2009/02/index.html">Landon Fuller's blog</a>
|
|
|
89 |
static BOOL IsAppEncrypted() {
|
|
|
90 |
const struct mach_header *executableHeader = NULL;
|
|
|
91 |
for (uint32_t i = 0; i < _dyld_image_count(); i++) {
|
|
|
92 |
const struct mach_header *header = _dyld_get_image_header(i);
|
|
|
93 |
if (header && header->filetype == MH_EXECUTE) {
|
|
|
94 |
executableHeader = header;
|
|
|
95 |
break;
|
|
|
96 |
}
|
|
|
97 |
}
|
|
|
98 |
|
|
|
99 |
if (!executableHeader) {
|
|
|
100 |
return NO;
|
|
|
101 |
}
|
|
|
102 |
|
|
|
103 |
BOOL is64bit = (executableHeader->magic == MH_MAGIC_64);
|
|
|
104 |
uintptr_t cursor = (uintptr_t)executableHeader +
|
|
|
105 |
(is64bit ? sizeof(struct mach_header_64) : sizeof(struct mach_header));
|
|
|
106 |
const struct segment_command *segmentCommand = NULL;
|
|
|
107 |
uint32_t i = 0;
|
|
|
108 |
|
|
|
109 |
while (i++ < executableHeader->ncmds) {
|
|
|
110 |
segmentCommand = (struct segment_command *)cursor;
|
|
|
111 |
|
|
|
112 |
if (!segmentCommand) {
|
|
|
113 |
continue;
|
|
|
114 |
}
|
|
|
115 |
|
|
|
116 |
if ((!is64bit && segmentCommand->cmd == LC_ENCRYPTION_INFO) ||
|
|
|
117 |
(is64bit && segmentCommand->cmd == LC_ENCRYPTION_INFO_64)) {
|
|
|
118 |
if (is64bit) {
|
|
|
119 |
struct encryption_info_command_64 *cryptCmd =
|
|
|
120 |
(struct encryption_info_command_64 *)segmentCommand;
|
|
|
121 |
return cryptCmd && cryptCmd->cryptid != 0;
|
|
|
122 |
} else {
|
|
|
123 |
struct encryption_info_command *cryptCmd = (struct encryption_info_command *)segmentCommand;
|
|
|
124 |
return cryptCmd && cryptCmd->cryptid != 0;
|
|
|
125 |
}
|
|
|
126 |
}
|
|
|
127 |
cursor += segmentCommand->cmdsize;
|
|
|
128 |
}
|
|
|
129 |
|
|
|
130 |
return NO;
|
|
|
131 |
}
|
|
|
132 |
|
|
|
133 |
static BOOL HasSCInfoFolder() {
|
|
|
134 |
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
|
|
|
135 |
NSString *bundlePath = [NSBundle mainBundle].bundlePath;
|
|
|
136 |
NSString *scInfoPath = [bundlePath stringByAppendingPathComponent:@"SC_Info"];
|
|
|
137 |
return [[NSFileManager defaultManager] fileExistsAtPath:scInfoPath];
|
|
|
138 |
#elif TARGET_OS_OSX
|
|
|
139 |
return NO;
|
|
|
140 |
#endif
|
|
|
141 |
}
|
|
|
142 |
|
|
|
143 |
static BOOL HasEmbeddedMobileProvision() {
|
|
|
144 |
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
|
|
|
145 |
return [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"].length > 0;
|
|
|
146 |
#elif TARGET_OS_OSX
|
|
|
147 |
return NO;
|
|
|
148 |
#endif
|
|
|
149 |
}
|
|
|
150 |
|
|
|
151 |
+ (BOOL)isFromAppStore {
|
|
|
152 |
static dispatch_once_t isEncryptedOnce;
|
|
|
153 |
static BOOL isEncrypted = NO;
|
|
|
154 |
|
|
|
155 |
dispatch_once(&isEncryptedOnce, ^{
|
|
|
156 |
isEncrypted = IsAppEncrypted();
|
|
|
157 |
});
|
|
|
158 |
|
|
|
159 |
if ([GULAppEnvironmentUtil isSimulator]) {
|
|
|
160 |
return NO;
|
|
|
161 |
}
|
|
|
162 |
|
|
|
163 |
// If an app contain the sandboxReceipt file, it means its coming from TestFlight
|
|
|
164 |
// This must be checked before the SCInfo Folder check below since TestFlight apps may
|
|
|
165 |
// also have an SCInfo folder.
|
|
|
166 |
if ([GULAppEnvironmentUtil isAppStoreReceiptSandbox]) {
|
|
|
167 |
return NO;
|
|
|
168 |
}
|
|
|
169 |
|
|
|
170 |
if (HasSCInfoFolder()) {
|
|
|
171 |
// When iTunes downloads a .ipa, it also gets a customized .sinf file which is added to the
|
|
|
172 |
// main SC_Info directory.
|
|
|
173 |
return YES;
|
|
|
174 |
}
|
|
|
175 |
|
|
|
176 |
// For iOS >= 8.0, iTunesMetadata.plist is moved outside of the sandbox. Any attempt to read
|
|
|
177 |
// the iTunesMetadata.plist outside of the sandbox will be rejected by Apple.
|
|
|
178 |
// If the app does not contain the embedded.mobileprovision which is stripped out by Apple when
|
|
|
179 |
// the app is submitted to store, then it is highly likely that it is from Apple Store.
|
|
|
180 |
return isEncrypted && !HasEmbeddedMobileProvision();
|
|
|
181 |
}
|
|
|
182 |
|
|
|
183 |
+ (BOOL)isAppStoreReceiptSandbox {
|
|
|
184 |
// Since checking the App Store's receipt URL can be memory intensive, check the option in the
|
|
|
185 |
// Info.plist if developers opted out of this check.
|
|
|
186 |
id enableSandboxCheck =
|
|
|
187 |
[[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRAppStoreReceiptURLCheckEnabledKey];
|
|
|
188 |
if (enableSandboxCheck && [enableSandboxCheck isKindOfClass:[NSNumber class]] &&
|
|
|
189 |
![enableSandboxCheck boolValue]) {
|
|
|
190 |
return NO;
|
|
|
191 |
}
|
|
|
192 |
|
|
|
193 |
NSURL *appStoreReceiptURL = [NSBundle mainBundle].appStoreReceiptURL;
|
|
|
194 |
NSString *appStoreReceiptFileName = appStoreReceiptURL.lastPathComponent;
|
|
|
195 |
return [appStoreReceiptFileName isEqualToString:kFIRAIdentitySandboxReceiptFileName];
|
|
|
196 |
}
|
|
|
197 |
|
|
|
198 |
+ (BOOL)isSimulator {
|
|
|
199 |
#if TARGET_OS_SIMULATOR
|
|
|
200 |
return YES;
|
|
|
201 |
#elif TARGET_OS_MACCATALYST
|
|
|
202 |
return NO;
|
|
|
203 |
#elif TARGET_OS_IOS || TARGET_OS_TV
|
|
|
204 |
NSString *platform = [GULAppEnvironmentUtil deviceModel];
|
|
|
205 |
return [platform isEqual:@"x86_64"] || [platform isEqual:@"i386"];
|
|
|
206 |
#elif TARGET_OS_OSX
|
|
|
207 |
return NO;
|
|
|
208 |
#endif
|
|
|
209 |
return NO;
|
|
|
210 |
}
|
|
|
211 |
|
|
|
212 |
+ (NSString *)deviceModel {
|
|
|
213 |
static dispatch_once_t once;
|
|
|
214 |
static NSString *deviceModel;
|
|
|
215 |
|
|
|
216 |
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
|
|
|
217 |
dispatch_once(&once, ^{
|
|
|
218 |
// The `uname` function only returns x86_64 for Macs. Use `sysctlbyname` instead, but fall back
|
|
|
219 |
// to the `uname` function if it fails.
|
|
|
220 |
size_t size;
|
|
|
221 |
sysctlbyname("hw.model", NULL, &size, NULL, 0);
|
|
|
222 |
if (size > 0) {
|
|
|
223 |
char *machine = malloc(size);
|
|
|
224 |
sysctlbyname("hw.model", machine, &size, NULL, 0);
|
|
|
225 |
deviceModel = [NSString stringWithCString:machine encoding:NSUTF8StringEncoding];
|
|
|
226 |
free(machine);
|
|
|
227 |
} else {
|
|
|
228 |
struct utsname systemInfo;
|
|
|
229 |
if (uname(&systemInfo) == 0) {
|
|
|
230 |
deviceModel = [NSString stringWithUTF8String:systemInfo.machine];
|
|
|
231 |
}
|
|
|
232 |
}
|
|
|
233 |
});
|
|
|
234 |
#else
|
|
|
235 |
dispatch_once(&once, ^{
|
|
|
236 |
struct utsname systemInfo;
|
|
|
237 |
if (uname(&systemInfo) == 0) {
|
|
|
238 |
deviceModel = [NSString stringWithUTF8String:systemInfo.machine];
|
|
|
239 |
}
|
|
|
240 |
});
|
|
|
241 |
#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
|
|
|
242 |
return deviceModel;
|
|
|
243 |
}
|
|
|
244 |
|
|
|
245 |
+ (NSString *)systemVersion {
|
|
|
246 |
#if TARGET_OS_IOS
|
|
|
247 |
return [UIDevice currentDevice].systemVersion;
|
|
|
248 |
#elif TARGET_OS_OSX || TARGET_OS_TV || TARGET_OS_WATCH
|
|
|
249 |
// Assemble the systemVersion, excluding the patch version if it's 0.
|
|
|
250 |
NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion;
|
|
|
251 |
NSMutableString *versionString = [[NSMutableString alloc]
|
|
|
252 |
initWithFormat:@"%ld.%ld", (long)osVersion.majorVersion, (long)osVersion.minorVersion];
|
|
|
253 |
if (osVersion.patchVersion != 0) {
|
|
|
254 |
[versionString appendFormat:@".%ld", (long)osVersion.patchVersion];
|
|
|
255 |
}
|
|
|
256 |
return versionString;
|
|
|
257 |
#endif
|
|
|
258 |
}
|
|
|
259 |
|
|
|
260 |
+ (BOOL)isAppExtension {
|
|
|
261 |
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
|
|
|
262 |
// Documented by <a href="https://goo.gl/RRB2Up">Apple</a>
|
|
|
263 |
BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"];
|
|
|
264 |
return appExtension;
|
|
|
265 |
#elif TARGET_OS_OSX
|
|
|
266 |
return NO;
|
|
|
267 |
#endif
|
|
|
268 |
}
|
|
|
269 |
|
|
|
270 |
+ (BOOL)isIOS7OrHigher {
|
|
|
271 |
return YES;
|
|
|
272 |
}
|
|
|
273 |
|
|
|
274 |
+ (BOOL)hasSwiftRuntime {
|
|
|
275 |
// The class
|
|
|
276 |
// [Swift._SwiftObject](https://github.com/apple/swift/blob/5eac3e2818eb340b11232aff83edfbd1c307fa03/stdlib/public/runtime/SwiftObject.h#L35)
|
|
|
277 |
// is a part of Swift runtime, so it should be present if Swift runtime is available.
|
|
|
278 |
|
|
|
279 |
BOOL hasSwiftRuntime =
|
|
|
280 |
objc_lookUpClass("Swift._SwiftObject") != nil ||
|
|
|
281 |
// Swift object class name before
|
|
|
282 |
// https://github.com/apple/swift/commit/9637b4a6e11ddca72f5f6dbe528efc7c92f14d01
|
|
|
283 |
objc_getClass("_TtCs12_SwiftObject") != nil;
|
|
|
284 |
|
|
|
285 |
return hasSwiftRuntime;
|
|
|
286 |
}
|
|
|
287 |
|
|
|
288 |
+ (NSString *)applePlatform {
|
|
|
289 |
NSString *applePlatform = @"unknown";
|
|
|
290 |
|
|
|
291 |
// When a Catalyst app is run on macOS then both `TARGET_OS_MACCATALYST` and `TARGET_OS_IOS` are
|
|
|
292 |
// `true`, which means the condition list is order-sensitive.
|
|
|
293 |
#if TARGET_OS_MACCATALYST
|
|
|
294 |
applePlatform = @"maccatalyst";
|
|
|
295 |
#elif TARGET_OS_IOS
|
|
|
296 |
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
|
|
|
297 |
if (@available(iOS 14.0, *)) {
|
|
|
298 |
// Early iOS 14 betas do not include isiOSAppOnMac (#6969)
|
|
|
299 |
applePlatform = ([[NSProcessInfo processInfo] respondsToSelector:@selector(isiOSAppOnMac)] &&
|
|
|
300 |
[NSProcessInfo processInfo].isiOSAppOnMac) ? @"ios_on_mac" : @"ios";
|
|
|
301 |
} else {
|
|
|
302 |
applePlatform = @"ios";
|
|
|
303 |
}
|
|
|
304 |
#else // defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
|
|
|
305 |
applePlatform = @"ios";
|
|
|
306 |
#endif // defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
|
|
|
307 |
|
|
|
308 |
#elif TARGET_OS_TV
|
|
|
309 |
applePlatform = @"tvos";
|
|
|
310 |
#elif TARGET_OS_OSX
|
|
|
311 |
applePlatform = @"macos";
|
|
|
312 |
#elif TARGET_OS_WATCH
|
|
|
313 |
applePlatform = @"watchos";
|
|
|
314 |
#endif // TARGET_OS_MACCATALYST
|
|
|
315 |
|
|
|
316 |
return applePlatform;
|
|
|
317 |
}
|
|
|
318 |
|
|
|
319 |
+ (NSString *)deploymentType {
|
|
|
320 |
#if SWIFT_PACKAGE
|
|
|
321 |
NSString *deploymentType = @"swiftpm";
|
|
|
322 |
#elif FIREBASE_BUILD_CARTHAGE
|
|
|
323 |
NSString *deploymentType = @"carthage";
|
|
|
324 |
#elif FIREBASE_BUILD_ZIP_FILE
|
|
|
325 |
NSString *deploymentType = @"zip";
|
|
|
326 |
#else
|
|
|
327 |
NSString *deploymentType = @"cocoapods";
|
|
|
328 |
#endif
|
|
|
329 |
|
|
|
330 |
return deploymentType;
|
|
|
331 |
}
|
|
|
332 |
|
|
|
333 |
@end
|