github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/clients/ios-objc/photobackup/LACamliClient/LACamliUtil.m (about) 1 // 2 // LACamliUtil.m 3 // photobackup 4 // 5 // Created by Nick O'Neill on 11/29/13. 6 // Copyright (c) 2013 The Camlistore Authors. All rights reserved. 7 // 8 9 #import "LACamliUtil.h" 10 #import <CommonCrypto/CommonDigest.h> 11 12 @implementation LACamliUtil 13 14 static NSString *const serviceName = @"org.camlistore.credentials"; 15 16 // h/t AFNetworking 17 + (NSString *)base64EncodedStringFromString:(NSString *)string 18 { 19 NSData *data = [NSData dataWithBytes:[string UTF8String] length:[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]; 20 NSUInteger length = [data length]; 21 NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; 22 23 uint8_t *input = (uint8_t *)[data bytes]; 24 uint8_t *output = (uint8_t *)[mutableData mutableBytes]; 25 26 for (NSUInteger i = 0; i < length; i += 3) { 27 NSUInteger value = 0; 28 for (NSUInteger j = i; j < (i + 3); j++) { 29 value <<= 8; 30 if (j < length) { 31 value |= (0xFF & input[j]); 32 } 33 } 34 35 static uint8_t const kAFBase64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 36 37 NSUInteger idx = (i / 3) * 4; 38 output[idx + 0] = kAFBase64EncodingTable[(value >> 18) & 0x3F]; 39 output[idx + 1] = kAFBase64EncodingTable[(value >> 12) & 0x3F]; 40 output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '='; 41 output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '='; 42 } 43 44 return [[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding]; 45 } 46 47 #pragma mark - keychain stuff 48 49 + (NSString *)passwordForUsername:(NSString *)username 50 { 51 if (!username) { 52 LALog(@"no username"); 53 return nil; 54 } 55 56 // construct query 57 NSDictionary *passwordQuery = @{(__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword, 58 (__bridge NSString *)kSecAttrAccount: username, 59 (__bridge NSString *)kSecAttrService: serviceName, 60 (__bridge NSString *)kSecReturnData: (id)kCFBooleanTrue}; 61 62 63 NSData *results = nil; 64 CFTypeRef resultRef = (__bridge CFTypeRef)results; 65 66 // actually request password 67 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)passwordQuery, &resultRef); 68 69 results = (__bridge NSData *)resultRef; 70 71 if (status == noErr) { 72 LALog(@"returning valid password"); 73 return [[NSString alloc] initWithData:results encoding:NSUTF8StringEncoding]; 74 } 75 76 return nil; 77 } 78 79 + (BOOL)savePassword:(NSString *)password forUsername:(NSString *)username 80 { 81 if (!password || !username) { 82 LALog(@"no password or username"); 83 return NO; 84 } 85 86 OSStatus status = noErr; 87 88 if ([self passwordForUsername:username]) { 89 LALog(@"update"); 90 // update keychain item 91 92 NSDictionary *updateQuery = @{(__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword, 93 (__bridge NSString *)kSecAttrAccount: username, 94 (__bridge NSString *)kSecAttrService: serviceName 95 }; 96 97 NSDictionary *updateAttribute = @{(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding]}; 98 99 status = SecItemUpdate((__bridge CFDictionaryRef)updateQuery, (__bridge CFDictionaryRef)updateAttribute); 100 } else { 101 LALog(@"new"); 102 // add a new keychain item 103 104 NSDictionary *addQuery = @{(__bridge NSString *)kSecClass: (__bridge NSString *)kSecClassGenericPassword, 105 (__bridge NSString *)kSecAttrAccount: username, 106 (__bridge NSString *)kSecAttrService: serviceName, 107 (__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding] 108 }; 109 110 status = SecItemAdd((__bridge CFDictionaryRef)addQuery, nil); 111 } 112 113 if (status != noErr) { 114 return NO; 115 } 116 117 return YES; 118 } 119 120 #pragma mark - hashes 121 122 + (NSString *)blobRef:(NSData *)data 123 { 124 uint8_t digest[CC_SHA1_DIGEST_LENGTH]; 125 126 CC_SHA1(data.bytes, data.length, digest); 127 128 NSMutableString* output = [NSMutableString stringWithCapacity:(CC_SHA1_DIGEST_LENGTH * 2) + 5]; 129 [output appendString:@"sha1-"]; 130 131 for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) { 132 [output appendFormat:@"%02x", digest[i]]; 133 } 134 135 return output; 136 } 137 138 #pragma mark - dates 139 140 + (NSString *)rfc3339StringFromDate:(NSDate *)date 141 { 142 NSDateFormatter *rfc3339DateFormatter = [[NSDateFormatter alloc] init]; 143 144 NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 145 146 [rfc3339DateFormatter setLocale:enUSPOSIXLocale]; 147 [rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"]; 148 [rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; 149 150 return [rfc3339DateFormatter stringFromDate:date]; 151 } 152 153 #pragma mark - yucky logging hack 154 155 + (void)logText:(NSArray *)logs 156 { 157 NSMutableString *logString = [NSMutableString string]; 158 159 for (NSString *log in logs) { 160 [logString appendString:log]; 161 } 162 163 [[NSNotificationCenter defaultCenter] postNotificationName:@"logtext" object:@{@"text": logString}]; 164 } 165 166 + (void)statusText:(NSArray *)statuses 167 { 168 NSMutableString *statusString = [NSMutableString string]; 169 170 for (NSString *status in statuses) { 171 [statusString appendString:status]; 172 } 173 174 [[NSNotificationCenter defaultCenter] postNotificationName:@"statusText" object:@{@"text": statusString}]; 175 } 176 177 + (void)errorText:(NSArray *)errors 178 { 179 NSMutableString *errorString = [NSMutableString string]; 180 181 for (NSString *error in errors) { 182 [errorString appendString:error]; 183 } 184 185 [[NSNotificationCenter defaultCenter] postNotificationName:@"errorText" object:@{@"text": errorString}]; 186 } 187 188 @end