diff --git a/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/SnapchatKit.xcscheme b/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/SnapchatKit.xcscheme index 230b6c3..1804021 100644 --- a/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/SnapchatKit.xcscheme +++ b/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/SnapchatKit.xcscheme @@ -23,33 +23,42 @@ - - + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -48,15 +48,18 @@ ReferencedContainer = "container:SnapchatKit.xcodeproj"> + + @@ -72,10 +75,10 @@ diff --git a/Pod/Classes/Categories/NSString+SnapchatKit.h b/Pod/Classes/Categories/NSString+SnapchatKit.h index c67da4a..ca75a54 100755 --- a/Pod/Classes/Categories/NSString+SnapchatKit.h +++ b/Pod/Classes/Categories/NSString+SnapchatKit.h @@ -21,7 +21,8 @@ + (NSString *)hashSC:(NSData *)first and:(NSData *)second; + (NSString *)hashSCString:(NSString *)first and:(NSString *)second; -+ (NSString *)hashHMac:(NSString *)data key:(NSString *)key; ++ (NSData *)hashHMac:(NSString *)data key:(NSString *)key; ++ (NSString *)hashHMacToString:(NSString *)data key:(NSString *)key; - (NSString *)MD5Hash; diff --git a/Pod/Classes/Categories/NSString+SnapchatKit.m b/Pod/Classes/Categories/NSString+SnapchatKit.m index 7115020..5ab7554 100755 --- a/Pod/Classes/Categories/NSString+SnapchatKit.m +++ b/Pod/Classes/Categories/NSString+SnapchatKit.m @@ -71,15 +71,17 @@ + (NSString *)hashSC:(NSData *)a and:(NSData *)b { return hash; } -+ (NSString *)hashHMac:(NSString *)data key:(NSString *)key { ++ (NSString *)hashHMacToString:(NSString *)data key:(NSString *)key { + return [[self hashHMac:data key:key] base64EncodedStringWithOptions:0]; +} + ++ (NSData *)hashHMac:(NSString *)data key:(NSString *)key { const char *cKey = [key cStringUsingEncoding:NSUTF8StringEncoding]; const char *cData = [data cStringUsingEncoding:NSUTF8StringEncoding]; unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH]; CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC); - NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)]; - - return [HMAC base64EncodedStringWithOptions:0]; + return [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)]; } - (NSString *)MD5Hash { diff --git a/Pod/Classes/Networking/SKClient.h b/Pod/Classes/Networking/SKClient.h index 85359a2..93a8470 100755 --- a/Pod/Classes/Networking/SKClient.h +++ b/Pod/Classes/Networking/SKClient.h @@ -12,6 +12,8 @@ #import "SKSession.h" +extern NSString *SKMakeCapserSignature(NSDictionary *params, NSString *secret); + /** Used to restructure JSON or modify an error returned from nearly any API call before being passed back to the application code. */ @protocol SKMiddleMan /** @discussion This method is passed the JSON and any error generated by \c -[SKClient handleError:data:response:completion:]. @@ -36,6 +38,31 @@ /** The maxium sized to load videos in. */ @property (nonatomic) CGSize maxVideoSize; +/** The username of the currently signed in (or not yet singed in) user. @note Always lowercase. */ +@property (nonatomic, readonly) NSString *username; +/** The \c SKSession object representing the current Snapchat session. + @discussion Many of the categories on \c SKClient will automatically update this property; rarely is it necessary to attempt to update it yourself. */ +@property (nonatomic) SKSession *currentSession; + +// Data used to sign in + +/** Used internally to sign in. Passed in either of the \c -restoreSessionWithUsername:snapchatAuthToken: methods as the \c snapchatAuthToken: parameter. */ +@property (nonatomic, readonly) NSString *authToken; +/** Used internally to sign in. Passed as the \c googleAuthToken: parameter to \c -restoreSessionWithUsername:snapchatAuthToken:googleAuthToken: method. */ +@property (nonatomic, readonly) NSString *googleAuthToken; +/** Used internally. */ +@property (nonatomic, readonly) NSString *deviceToken1i; +/** Used internally. */ +@property (nonatomic, readonly) NSString *deviceToken1v; +/** Used internally to sign in and trick Snapchat into thinking we're using the first party client. */ +@property (nonatomic, readonly) NSString *googleAttestation; + +/** Required to sign in properly. See https://clients.casper.io to get your own. */ +@property (nonatomic) NSString *casperAPIKey; +/** Required to sign in properly. See https://clients.casper.io to get your own. */ +@property (nonatomic) NSString *casperAPISecret; + + #pragma mark Signing in /** Signs into Snapchat. @discussion A valid GMail account is necessary to trick Snapchat into thinking we're using the first party client. Your data is only ever sent to Google, Scout's honor. @@ -123,24 +150,6 @@ The first step in creating a new Snapchat account. Registers an email, password, /** Completion is GUARANTEED to have one and only one non-nil parameter. */ - (void)handleError:(NSError *)error data:(NSData *)data response:(NSURLResponse *)response completion:(ResponseBlock)completion; -/** The username of the currently signed in (or not yet singed in) user. @note Always lowercase. */ -@property (nonatomic, readonly) NSString *username; -/** The \c SKSession object representing the current Snapchat session. - @discussion Many of the categories on \c SKClient will automatically update this property; rarely is it necessary to attempt to update it yourself. */ -@property (nonatomic) SKSession *currentSession; - -// Data used to sign in -/** Used internally to sign in. Passed in either of the \c -restoreSessionWithUsername:snapchatAuthToken: methods as the \c snapchatAuthToken: parameter. */ -@property (nonatomic, readonly) NSString *authToken; -/** Used internally to sign in. Passed as the \c googleAuthToken: parameter to \c -restoreSessionWithUsername:snapchatAuthToken:googleAuthToken: method. */ -@property (nonatomic, readonly) NSString *googleAuthToken; -/** Used internally. */ -@property (nonatomic, readonly) NSString *deviceToken1i; -/** Used internally. */ -@property (nonatomic, readonly) NSString *deviceToken1v; -/** Used internally to sign in and trick Snapchat into thinking we're using the first party client. */ -@property (nonatomic, readonly) NSString *googleAttestation; - @end /** Used to assert that we are signed in before making certain requests. */ diff --git a/Pod/Classes/Networking/SKClient.m b/Pod/Classes/Networking/SKClient.m index 35208ef..8275d55 100755 --- a/Pod/Classes/Networking/SKClient.m +++ b/Pod/Classes/Networking/SKClient.m @@ -40,8 +40,19 @@ BOOL SKHasActiveConnection() { return @{@"googleAuthToken": gauth, @"attestation": attest, @"pushToken": ptoken?:@"e", @"clientAuthToken": clientAuthToken, @"dt": deviceTokens, @"ts": timestamp}; } -NSString * const kAttestationURLString = @"https://www.googleapis.com/androidcheck/v1/attestations/attest?alt=JSON&key=AIzaSyDqVnJBjE5ymo--oBJt3On7HQx9xNm1RHA"; -NSString * const kAttestationBase64Request = @"ClMKABIUY29tLnNuYXBjaGF0LmFuZHJvaWQaIC8cqvyh7TDQtOOIY+76vqDoFXEfpM95uCJRmoJZ2VpYIgAojKq/AzIECgASADoECAEQAUD4kP+pBRIA"; +NSString *SKMakeCapserSignature(NSDictionary *params, NSString *secret) { + assert(params.allKeys.count); assert(secret); + NSArray *sortedKeys = [params.allKeys sortedArrayUsingSelector:@selector(compare:options:)]; + NSMutableString *signature = [NSMutableString string]; + + for (NSString *key in sortedKeys) { + [signature appendString:key]; + [signature appendString:params[key]]; + } + + return [@"v1:" stringByAppendingString:[NSString hashHMac:signature key:secret].hexadecimalString]; +} + @implementation SKClient @@ -314,9 +325,12 @@ - (void)getGoogleCloudMessagingIdentifier:(StringBlock)callback { /** Google attestation. */ - (void)getAttestation:(NSString *)username password:(NSString *)password ts:(NSString *)timestamp callback:(StringBlock)callback { + NSAssert(self.casperAPIKey, @"You must have a valid API key from https://clients.casper.io to sign in."); + NSAssert(self.casperAPISecret, @"You must have a valid API secret from https://clients.casper.io to sign in."); + NSString *hashString = [[NSString stringWithFormat:@"%@|%@|%@|%@", username, password, timestamp, SKEPAccount.login].sha256HashRaw base64EncodedStringWithOptions:0]; // Get binary data - NSData *binaryData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://api.casper.io/droidguard/create/binary"]]; + NSData *binaryData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://api.casper.io/snapchat/attestation/create"]]; __block NSError *jsonError = nil; __block NSDictionary *json = [NSJSONSerialization JSONObjectWithData:binaryData options:0 error:&jsonError]; @@ -334,11 +348,15 @@ - (void)getAttestation:(NSString *)username password:(NSString *)password ts:(NS // Send it to Casper.io to build the protobuf NSString *bytecodeProtobuf = [data base64EncodedStringWithOptions:0]; - NSDictionary *query = @{@"bytecode_proto": bytecodeProtobuf, + NSDictionary *query = @{@"protobuf": bytecodeProtobuf, @"nonce": hashString, - @"apk_digest": SKAttestation.digest9_14_2}; + @"snapchat_version": SKConsts.snapchatVersion}; request.URL = [NSURL URLWithString:SKAttestation.protobufPOSTURL]; request.HTTPBody = [[NSString queryStringWithParams:query URLEscapeValues:YES] dataUsingEncoding:NSUTF8StringEncoding]; + + // Set headers + [request setValue:self.casperAPIKey forHTTPHeaderField:SKHeaders.casperAPIKey]; + [request setValue:SKMakeCapserSignature(query, self.casperAPISecret) forHTTPHeaderField:SKHeaders.casperSignature]; [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:SKHeaders.contentType]; [[session dataTaskWithRequest:request completionHandler:^(NSData *data2, NSURLResponse *response2, NSError *error2) { @@ -387,11 +405,21 @@ - (void)getAttestation:(NSString *)username password:(NSString *)password ts:(NS - (void)getClientAuthToken:(NSString *)username password:(NSString *)password timestamp:(NSString *)ts callback:(StringBlock)callback { NSParameterAssert(username); NSParameterAssert(password); NSParameterAssert(ts); + NSAssert(self.casperAPIKey, @"You must have a valid API key from https://clients.casper.io to sign in."); + NSAssert(self.casperAPISecret, @"You must have a valid API secret from https://clients.casper.io to sign in."); - NSURL *url = [NSURL URLWithString:@"https://api.casper.io/security/login/signrequest/"]; + // Params + NSDictionary *query = @{@"username": username, @"password": password, @"timestamp": ts, @"snapchat_version": SKConsts.snapchatVersion}; + + // Build request + NSURL *url = [NSURL URLWithString:@"https://api.casper.io/snapchat/clientauth/signrequest"]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; request.HTTPMethod = @"POST"; - request.HTTPBody = [[NSString queryStringWithParams:@{@"username": username, @"password": password, @"timestamp": ts}] dataUsingEncoding:NSUTF8StringEncoding]; + request.HTTPBody = [[NSString queryStringWithParams:query] dataUsingEncoding:NSUTF8StringEncoding]; + + // Set headers + [request setValue:self.casperAPIKey forHTTPHeaderField:SKHeaders.casperAPIKey]; + [request setValue:SKMakeCapserSignature(query, self.casperAPISecret) forHTTPHeaderField:SKHeaders.casperSignature]; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; [[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { @@ -468,7 +496,7 @@ - (void)signInWithData:(NSDictionary *)loginData username:(NSString *)username p NSString *req_token = [NSString hashSCString:SKConsts.staticToken and:timestamp]; NSString *string = [NSString stringWithFormat:@"%@|%@|%@|%@", username, password, timestamp, req_token]; - NSString *deviceSig = [[NSString hashHMac:string key:self.deviceToken1v] substringWithRange:NSMakeRange(0, 20)]; + NSString *deviceSig = [[[NSString hashHMac:string key:self.deviceToken1v] base64EncodedStringWithOptions:0] substringWithRange:NSMakeRange(0, 20)]; NSDictionary *post = @{@"username": username, @"password": password, diff --git a/Pod/Classes/SnapchatKit-Constants.h b/Pod/Classes/SnapchatKit-Constants.h index 9b9e5bd..7fc8f62 100755 --- a/Pod/Classes/SnapchatKit-Constants.h +++ b/Pod/Classes/SnapchatKit-Constants.h @@ -140,6 +140,7 @@ SK_NAMESPACE(SKConsts, { NSNSString *boundary; NSNSString *deviceToken1i; NSNSString *deviceToken1v; + NSNSString *snapchatVersion; }); #pragma mark Header fields / values @@ -151,6 +152,8 @@ SK_NAMESPACE(SKHeaders, { NSNSString *acceptLocale; NSNSString *clientAuth; NSNSString *clientAuthToken; + NSNSString *casperAPIKey; + NSNSString *casperSignature; struct { NSNSString *language; NSNSString *locale; diff --git a/Pod/Classes/SnapchatKit-Constants.m b/Pod/Classes/SnapchatKit-Constants.m index 0665348..7e41cd2 100755 --- a/Pod/Classes/SnapchatKit-Constants.m +++ b/Pod/Classes/SnapchatKit-Constants.m @@ -101,7 +101,7 @@ BOOL SKMediaKindIsVideo(SKMediaKind mediaKind) { .certificateDigest = @"Lxyq/KHtMNC044hj7vq+oOgVcR+kz3m4IlGaglnZWlg=", .GMSVersion = 7329038, .protobufBytecodeURL = @"https://www.googleapis.com/androidantiabuse/v1/x/create?alt=PROTO&key=AIzaSyBofcZsgLSS7BOnBjZPEkk4rYwzOIz-lTI", - .protobufPOSTURL = @"https://api.casper.io/droidguard/attest/binary", + .protobufPOSTURL = @"https://api.casper.io/snapchat/attestation/attest", .attestationURL = @"https://www.googleapis.com/androidcheck/v1/attestations/attest?alt=JSON&key=AIzaSyDqVnJBjE5ymo--oBJt3On7HQx9xNm1RHA", .digest9_8 = @"vXCcGhQ7RfL1LUiE3F6vcNORNo7IFSOvuDBunK87mEI=", .digest9_9 = @"Yk9Wqmx7TrTatldWI+5PWbQjGA8Gi8ZoO8X9OUAw1hg=", @@ -118,7 +118,7 @@ BOOL SKMediaKindIsVideo(SKMediaKind mediaKind) { #pragma mark Misc SK_NAMESPACE_IMP(SKConsts) { .baseURL = @"https://feelinsonice-hrd.appspot.com", - .userAgent = @"Snapchat/9.14.2.0 (SM-N9005; Android 5.0.2; gzip)", //Snapchat/9.10.0.0 (HTC One; Android 4.4.2#302626.7#19; gzip)"; + .userAgent = @"Snapchat/9.16.1.0 (SM-N9005; Android 5.0.2; gzip)", //Snapchat/9.16.1.0 (HTC One; Android 4.4.2#302626.7#19; gzip)"; .eventsURL = @"https://sc-analytics.appspot.com/post_events", .analyticsURL = @"https://sc-analytics.appspot.com/analytics/bz", .secret = @"iEk21fuwZApXlz93750dmW22pw389dPwOk", @@ -128,6 +128,7 @@ BOOL SKMediaKindIsVideo(SKMediaKind mediaKind) { .boundary = @"Boundary+0xAbCdEfGbOuNdArY", .deviceToken1i = @"dtoken1i", .deviceToken1v = @"dtoken1v", + .snapchatVersion = @"9.16.1.0" }; #pragma mark Header fields / values @@ -139,6 +140,8 @@ BOOL SKMediaKindIsVideo(SKMediaKind mediaKind) { .acceptLocale = @"Accept-Locale", .clientAuth = @"X-Snapchat-Client-Auth", .clientAuthToken = @"X-Snapchat-Client-Auth-Token", + .casperAPIKey = @"X-Casper-API-Key", + .casperSignature = @"X-Casper-Signature", .values = { .language = @"en", .locale = @"en_US",