Files
firebase-ios-sdk/FirebaseAppDistribution/Sources/FIRAppDistributionMachO.m
T
ec670ae0bb Persist auth information to the iOS keychain (#5460)
* Add initial code hash generation implementation

* Adding auth state to iOS keychain

* Cleanup logging and error handling for initial auth persistence implementation (#5413)

* Cleanup logging and error handling for initial auth persistence implementation

* Update Nullability of error fields and reformat

* Move the error domain and enum into the AuthPersistence file

* Format Mach files

* Externalize handling of nullable error and greedily return on error

* Add initial code hash generation implementation

* Adding auth state to iOS keychain

* Cleanup logging and error handling for initial auth persistence implementation (#5413)

* Cleanup logging and error handling for initial auth persistence implementation

* Update Nullability of error fields and reformat

* Move the error domain and enum into the AuthPersistence file

* Format Mach files

* Externalize handling of nullable error and greedily return on error

* Add unittests for AppDistribution auth persistence

* Split out the keychain calls from control flow and error handling

* Add mocking and tests for auth persistence tests

* Remove debug logging

* Update styling

* Re-add resources to podspec and break out block for readability

Co-authored-by: Jeremy Durham <jeremydurham@google.com>
Co-authored-by: Pranav Rajgopal <pranavrajgopal@google.com>
2020-04-30 14:29:33 -04:00

157 lines
4.9 KiB
Objective-C

/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <CommonCrypto/CommonHMAC.h>
#include <mach-o/arch.h>
#import <mach-o/fat.h>
#import <mach-o/loader.h>
#import "FIRAppDistributionMachO+Private.h"
#import "FIRAppDistributionMachOSlice+Private.h"
@interface FIRAppDistributionMachO ()
@property(nonatomic, copy) NSFileHandle* file;
@property(nonatomic, copy) NSMutableArray* slices;
@end
@implementation FIRAppDistributionMachO
- (instancetype)initWithPath:(NSString*)path {
self = [super init];
if (self) {
_file = [NSFileHandle fileHandleForReadingAtPath:path];
_slices = [NSMutableArray new];
[self extractSlices];
}
return self;
}
- (void)extractSlices {
uint32_t magicValue;
struct fat_header fheader;
NSData* data = [self.file readDataOfLength:sizeof(fheader)];
[data getBytes:&fheader length:sizeof(fheader)];
magicValue = CFSwapInt32BigToHost(fheader.magic);
// Check to see if the file is a FAT binary (has multiple architectures)
if (magicValue == FAT_MAGIC) {
uint32_t archCount = CFSwapInt32BigToHost(fheader.nfat_arch);
NSUInteger archOffsets[archCount];
// Gather the offsets for each architecture
for (uint32_t i = 0; i < archCount; i++) {
struct fat_arch arch;
data = [self.file readDataOfLength:sizeof(arch)];
[data getBytes:&arch length:sizeof(arch)];
archOffsets[i] = CFSwapInt32BigToHost(arch.offset);
}
// Iterate the slices based on the offsets we extracted above
for (uint32_t i = 0; i < archCount; i++) {
FIRAppDistributionMachOSlice* slice = [self extractSliceAtOffset:archOffsets[i]];
if (slice) [_slices addObject:slice];
}
} else {
// If the binary is not FAT, we're dealing with a single architecture
FIRAppDistributionMachOSlice* slice = [self extractSliceAtOffset:0];
if (slice) [_slices addObject:slice];
}
}
- (FIRAppDistributionMachOSlice*)extractSliceAtOffset:(NSUInteger)offset {
[self.file seekToFileOffset:offset];
struct mach_header header;
NSData* data = [self.file readDataOfLength:sizeof(header)];
[data getBytes:&header length:sizeof(header)];
uint32_t magicValue = CFSwapInt32BigToHost(header.magic);
// If we didn't read a valid magic value, something is wrong
// Bail out immediately to prevent reading random data
if (magicValue != MH_CIGAM_64 && magicValue != MH_CIGAM) {
return nil;
}
// If the binary is 64-bit, read the reserved bit and discard it
if (magicValue == MH_CIGAM_64) {
uint32_t reserved;
[self.file readDataOfLength:sizeof(reserved)];
}
for (uint32_t i = 0; i < header.ncmds; i++) {
struct load_command lc;
data = [self.file readDataOfLength:sizeof(lc)];
[data getBytes:&lc length:sizeof(lc)];
if (lc.cmd != LC_UUID) {
// Move to the next load command
[self.file seekToFileOffset:[self.file offsetInFile] + lc.cmdsize - sizeof(lc)];
continue;
}
// Re-read the load command, but this time as a UUID command
// so we can easily fetch the UUID
[self.file seekToFileOffset:[self.file offsetInFile] - sizeof(lc)];
struct uuid_command uc;
data = [self.file readDataOfLength:sizeof(uc)];
[data getBytes:&uc length:sizeof(uc)];
NSUUID* uuid = [[NSUUID alloc] initWithUUIDBytes:uc.uuid];
const NXArchInfo* arch = NXGetArchInfoFromCpuType(header.cputype, header.cpusubtype);
return [[FIRAppDistributionMachOSlice alloc] initWithArch:arch uuid:uuid];
}
// If we got here, something is wrong. We iterated the load commands
// and didn't find a UUID load command. This means the binary is corrupt.
return nil;
}
- (NSString*)codeHash {
NSMutableString* prehashedString = [NSMutableString new];
NSArray* sortedSlices =
[_slices sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [[obj1 architectureName] compare:[obj2 architectureName]];
}];
for (FIRAppDistributionMachOSlice* slice in sortedSlices) {
[prehashedString appendString:[slice uuidString]];
}
return [self sha1:prehashedString];
}
- (NSString*)sha1:(NSString*)prehashedString {
NSData* data = [prehashedString dataUsingEncoding:NSUTF8StringEncoding];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, (int)data.length, digest);
NSMutableString* hashedString = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) [hashedString appendFormat:@"%02x", digest[i]];
return hashedString;
}
@end