OAuth refresh token error - iphone

I'm using OAuth 2.0 to sign into a website. so each time when i try to log into the server i got a response that refer to an expiration date, access token and refresh token. the problem is the token is expired before the given time that i got from the server. so i figure out that there is an interval between the time of the server and the time of the iPhone. when i looked at the code of the SDK facebook there is no logic to handle this issue, it's a simple comparison. so my question is this issue from server side i mean the implementation of OAuth is incorrect or it's an issue from the client side?
Facebook Code:
- (BOOL)isSessionValid {
return (self.accessToken != nil && self.expirationDate != nil
&& NSOrderedDescending == [self.expirationDate compare:[NSDate date]]);
}
My code :
// Get the remaining period of the token to expire and subtract 30 sec
int delay = ([expirationDate timeIntervalSinceDate:serverDateTaken] - 30);
// Save the new Expiring date
objectOAuth.expiresIn = [[[NSDate date] dateByAddingTimeInterval:delay] description];
+ (BOOL)isSessionValid
{
// Get expiration date
OAuth *authParam = [Connection getSessionParameters];
// Formating
static NSString *timeZone = #"UTC";
NSDate *expirationDate = [Connection getDateFromString:authParam.expiresIn withTimeZone:timeZone];
// get the remaining period
int diff = [expirationDate timeIntervalSinceNow];
// Check if it's expired
return (diff <= 0);
}

Unfortunately there isn't really any good mechanism to check the time skew between the client and the server. I would stick with the simple check since there is less code that needs to be maintained and debugged. And no matter how much you check, you will still need to handle getting back a TOKEN EXPIRED error.

Related

Better ways to implement more secure Play Scala framework session via cookie

I really like the idea to keep session data on the users browser but don't like the fact that session cookies are not very secure in play framework. If someones steals the cookie, he/she could use it to permanently access the site since cookie signature is not expiring and cookie expiration doesn't help here because it doesn't stop from reusing the cookie if someone has stolen it.
I've added time stamp to expire the session after 1hr and every 5min to update the time stamp if user is still using the site so the cookie signature is rolling and expiring.
I am pretty new to scala and play framework so any suggestions or better ways to achieve the same would be much appreciated.
trait Secured {
def withAuth(f: => String => Request[AnyContent] => Result) = {
Security.Authenticated(username, onUnauthorized) { user =>
Action(request => {
val sessionRolloverPeriod = 300
val sessionExpiryTime = 3600
val sessionCreationTime: Int = request.session("ts").toInt
val currentTime = System.currentTimeMillis() / 1000L
if(currentTime <= (sessionCreationTime + sessionExpiryTime)) {
if(currentTime >= (sessionCreationTime + sessionRolloverPeriod)) {
f(user)(request).withSession(request.session + ("ts" -> (System.currentTimeMillis() / 1000L).toString))
} else {
f(user)(request)
}
} else {
Results.Redirect(routes.Auth.login()).withNewSession
}
}
)
}
}
}
Cookies produced every 5min:
The cookies produced every 5min:
Cookie:PS="a6bdf9df798c24a8836c2b2222ec1ea4a4251f301-username=admin&ts=1381180064"
Cookie:PS="D7edg7df709b54B1537c2b9862dc2eaff40001c90-username=admin&ts=1381180380"
Seems reasonable to me, I probably put it serverside though, give the client a "session-id" and delete the session when a user logs out. Doing it all client side means there is no way to invalidate the session if it has been stolen except to wait for the timeout.

How to pin the Public key of a certificate on iOS

While improving the security of an iOS application that we are developing, we found the need to PIN (the entire or parts of) the SSL certificate of server to prevent man-in-the-middle attacks.
Even though there are various approaches to do this, when you searching for thisI only found examples for pinning the entire certificate. Such practice poses a problem: As soon as the certificate is updated, your application will not be able to connect anymore.
If you choose to pin the public key instead of the entire certificate you will find yourself (I believe) in an equally secure situation, while being more resilient to certificate updates in the server.
But how do you do this?
In case you are in need of knowing how to extract this information from the certificate in your iOS code, here you have one way to do it.
First of all add the security framework.
#import <Security/Security.h>
The add the openssl libraries. You can download them from https://github.com/st3fan/ios-openssl
#import <openssl/x509.h>
The NSURLConnectionDelegate Protocol allows you to decide whether the connection should be able to respond to a protection space. In a nutshell, this is when you can have a look at the certificate that is coming from the server, and decide to allow the connection to proceed or to cancel. What you want to do here is compare the certificates public key with the one you've pinned. Now the question is, how do you get such public key? Have a look at the following code:
First get the certificate in X509 format (you will need the ssl libraries for this)
const unsigned char *certificateDataBytes = (const unsigned char *)[serverCertificateData bytes];
X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [serverCertificateData length]);
Now we will prepare to read the public key data
ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509);
NSString *publicKeyString = [[NSString alloc] init];
At this point you can iterate through the pubKey2 string and extract the bytes in HEX format into a string with the following loop
for (int i = 0; i < pubKey2->length; i++)
{
NSString *aString = [NSString stringWithFormat:#"%02x", pubKey2->data[i]];
publicKeyString = [publicKeyString stringByAppendingString:aString];
}
Print the public key to see it
NSLog(#"%#", publicKeyString);
The complete code
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
const unsigned char *certificateDataBytes = (const unsigned char *)[serverCertificateData bytes];
X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [serverCertificateData length]);
ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509);
NSString *publicKeyString = [[NSString alloc] init];
for (int i = 0; i < pubKey2->length; i++)
{
NSString *aString = [NSString stringWithFormat:#"%02x", pubKey2->data[i]];
publicKeyString = [publicKeyString stringByAppendingString:aString];
}
if ([publicKeyString isEqual:myPinnedPublicKeyString]){
NSLog(#"YES THEY ARE EQUAL, PROCEED");
return YES;
}else{
NSLog(#"Security Breach");
[connection cancel];
return NO;
}
}
As far as I can tell you cannot easily create the expected public key directly in iOS, you need to do it via a certificate.
So the steps needed are similar to pinning the certificate, but additionally you need to extract the public key from the actual certificate, and from a reference certificate (the expected public key).
What you need to do is:
Use a NSURLConnectionDelegate to retrieve the data, and implement willSendRequestForAuthenticationChallenge.
Include a reference certificate in DER format. In the example I've used a simple resource file.
Extract the public key presented by the server
Extract the public key from your reference certificate
Compare the two
If they match, continue with the regular checks (hostname, certificate signing, etc)
If they don't match, fail.
Some example code:
(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
// get the public key offered by the server
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
SecKeyRef actualKey = SecTrustCopyPublicKey(serverTrust);
// load the reference certificate
NSString *certFile = [[NSBundle mainBundle] pathForResource:#"ref-cert" ofType:#"der"];
NSData* certData = [NSData dataWithContentsOfFile:certFile];
SecCertificateRef expectedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
// extract the expected public key
SecKeyRef expectedKey = NULL;
SecCertificateRef certRefs[1] = { expectedCertificate };
CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, (void *) certRefs, 1, NULL);
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef expTrust = NULL;
OSStatus status = SecTrustCreateWithCertificates(certArray, policy, &expTrust);
if (status == errSecSuccess) {
expectedKey = SecTrustCopyPublicKey(expTrust);
}
CFRelease(expTrust);
CFRelease(policy);
CFRelease(certArray);
// check a match
if (actualKey != NULL && expectedKey != NULL && [(__bridge id) actualKey isEqual:(__bridge id)expectedKey]) {
// public keys match, continue with other checks
[challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
} else {
// public keys do not match
[challenge.sender cancelAuthenticationChallenge:challenge];
}
if(actualKey) {
CFRelease(actualKey);
}
if(expectedKey) {
CFRelease(expectedKey);
}
}
Disclaimer: this is example code only, and not thoroughly tested.
For a full implementation start with the certificate pinning example by OWASP.
And remember that certificate pinning can always be avoided using SSL Kill Switch and similar tools.
You can do public key SSL pinning using the SecTrustCopyPublicKey function of the Security.framework. See an example at connection:willSendRequestForAuthenticationChallenge: of the AFNetworking project.
If you need openSSL for iOS, use https://gist.github.com/foozmeat/5154962 It's based on st3fan/ios-openssl, which currently doesn't work.
You could use the PhoneGap (Build) plugin mentioned here: http://www.x-services.nl/certificate-pinning-plugin-for-phonegap-to-prevent-man-in-the-middle-attacks/734
The plugin supports multiple certificates, so the server and client don't need to be updated at the same time. If your fingerprint changes every (say) 2 year, then implement a mechanism for forcing the clients to update (add a version to your app and create a 'minimalRequiredVersion' API method on the server. Tell the client to update if the app version is too low (f.i. when the new certificate is activate).
If you use AFNetworking (more specifically, AFSecurityPolicy), and you choose the mode AFSSLPinningModePublicKey, it doesn't matter if your certificates change or not, as long as the public keys stay the same. Yes, it is true that AFSecurityPolicy doesn't provide a method for you to directly set your public keys; you can only set your certificates by calling setPinnedCertificates. However, if you look at the implementation of setPinnedCertificates, you'll see that the framework is extracting the public keys from the certificates and then comparing the keys.
In short, pass in the certificates, and don't worry about them changing in the future. The framework only cares about the public keys in those certificates.
The following code works for me.
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
[manager.securityPolicy setPinnedCertificates:myCertificate];
...for pinning the entire certificate. Such practice poses a problem...
Also, Google changes the certificate monthly (or so) but retains or re-certifies the public. So certificate pinning will result in a lot of spurious warnings, while public key pinning will pass key continuity tests.
I believe Google does it to keep CRLs, OCSP and Revocation Lists manageable, and I expect others will do it also. For my sites, I usually re-certify the keys so folks to ensure key continuity.
But how do you do this?
Certificate and Public Key Pinning. The article discusses the practice and offers sample code for OpenSSL, Android, iOS, and .Net. There is at least one problem with iOS incumbent to the framework discussed at iOS: Provide Meaningful Error from NSUrlConnection didReceiveAuthenticationChallenge (Certificate Failure).
Also, Peter Gutmann has a great treatment of key continuity and pinning in his book Engineering Security.
If you use AFNetworking, use AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];

Can't create reminder calendar in iOS

I'm trying to create a reminder calendar so I can add and delete reminders. It is actually working well on the devices I use (iPhone 5/4S/4) but on certain client devices which are still iPhones - I'm getting this error below about the account not supporting reminders.
Here is the workflow:
* Init the event store.
* Request permission (check its granted for Reminder types) (iOS6+) for lower we just init.
* Create a new calendar, local storage, type = Reminder
* Save calendar to get its Identifier.
Works most of the time, this appears on some devices -
Error Domain=EKErrorDomain Code=24 “That account does not support reminders.”
Permissions are granted and checked under Settings, Privacy, Reminders. I can't find anything in the docs about the conditions under which you'd get this error.
Thanks!
Not sure if you still need this but here is what i ran into.
First of all i'm pretty sure that reminders cannot be set on a calendar with a local source. I kept getting the "That account does not support reminders". Even after setting all non read only properties on the calendar before committing to the event store it still didn't work. The source needs to be calDav. Then I tried Devfly's response and it didn't work either but for a different reason. It kept fetching my gmail calendar which does not allow setting reminders (i think its actually read only for events and reminders). So i used the following code to get the actual iCloud source
for (EKSource *source in sources) {
NSLog(#"source %#",source.title);
if (source.sourceType == EKSourceTypeCalDAV && [source.title isEqualToString:#"iCloud"]) {
localSource = source;
break;
}
}
This setting this source on my new reminders calendar worked for me. hope it helps someone
First, just a check: you are creating a "new calendar" (a whole calendar), not just a "new reminder", right?
Second: are you using iOS6? Reminders are available (in EventKit) only starting from iOS6: link
As commented by Jesse Rusak, this happens because you are probably creating the new calendar inside an account/source that doesn't support reminders.
How do you create the new calendar?
Do you set the source property?
the first thing you can try, is to loop all sources until you find a suitable one.
EKSourceTypeLocal supports reminders. iCal too.
Here a list of EKSourceType
typedef enum {
EKSourceTypeLocal,
EKSourceTypeExchange,
EKSourceTypeCalDAV,
EKSourceTypeMobileMe,
EKSourceTypeSubscribed,
EKSourceTypeBirthdays
} EKSourceType;
Find a suitable one:
// find local source for example
EKSource *localSource = nil;
for (EKSource *source in store.sources)
{
if (source.sourceType == EKSourceTypeLocal) // or another source type that supports
{
localSource = source;
break;
}
}
Then, create the new calendar setting the right source
EKCalendar *cal;
if (identifier == nil)
{
cal = [EKCalendar calendarForEntityType:EKEntityTypeReminder eventStore:store];
cal.title = #"Demo calendar";
cal.source = localSource;
[store saveCalendar:cal commit:YES error:nil];
}
Try and let me know
What solved my problem is not saving the calendar to the local source, but instead to EKSourceTypeCalDAV (iCloud). It works, and it's synced across all devices.
The local store may not support reminders. This is reproducible if iCloud is enabled.
This is the most reliable solution I could find, without hard-coding any assumptions:
let calendar = EKCalendar(forEntityType: .Reminder, eventStore: eventStore)
if eventStore.sources.count == 0 { // reproducible after Reset Content and Settings
calendar.source = EKSource()
}
else {
calendar.source = eventStore.defaultCalendarForNewReminders().source
}
eventStore.saveCalendar(calendar, commit: true)

iOS Authorize.net integration issue

I am developing iOS app, in which I have integrated authorize.net payment gateways using auth.net iOS API.
I followed each and every steps which are mentioned in their doc. Now each time, when a transaction request is sent to auth.net server on live environment it gives me error (Code E00007) i.e. "User authentication failed due to invalid authentication values".
I checked Login ID and Transaction Key and those are correct.
Even I tried with Auth.net support person, but still it did not work out.
The test enviroment and request were successful. But on Live env. it gives me the problem
My code follows:
For device registration:
Here I have put XXXX.... instead of real transaction key and login id.
MobileDeviceRegistrationRequest *mobileDeviceRegistrationRequest =
[MobileDeviceRegistrationRequest mobileDeviceRegistrationRequest];
mobileDeviceRegistrationRequest.mobileDevice.mobileDeviceId =
[[[UIDevice currentDevice] uniqueIdentifier]
stringByReplacingOccurrencesOfString:#"-" withString:#"_"];
mobileDeviceRegistrationRequest.mobileDevice.mobileDescription = #"iPhone";
mobileDeviceRegistrationRequest.anetApiRequest.merchantAuthentication.name = #"XXXXXXXXX";
mobileDeviceRegistrationRequest.anetApiRequest.merchantAuthentication.password = #"XXXXXXXXXXXXXX";
[AuthNet authNetWithEnvironment:ENV_LIVE];
AuthNet *an = [AuthNet getInstance];
[an mobileDeviceRegistrationRequest:mobileDeviceRegistrationRequest];
After that the code for payment request:
MobileDeviceLoginRequest *mobileDeviceLoginRequest = [MobileDeviceLoginRequest mobileDeviceLoginRequest];
mobileDeviceLoginRequest.anetApiRequest.merchantAuthentication.name = #"XXXXX";
mobileDeviceLoginRequest.anetApiRequest.merchantAuthentication.password = #"XXXXXX";
mobileDeviceLoginRequest.anetApiRequest.merchantAuthentication.mobileDeviceId =
[[[UIDevice currentDevice] uniqueIdentifier] stringByReplacingOccurrencesOfString:#"-" withString:#"_"];
// Set up an AuthNet instance.
[AuthNet authNetWithEnvironment:ENV_LIVE];
AuthNet *an = [AuthNet getInstance];
[an setDelegate:self];
// Process a mobile device login request.
[an mobileDeviceLoginRequest:mobileDeviceLoginRequest];
But in the initial registering request I am receiving the failure with error code E00007. Any settings I missed?
please provide the transaction key
mobileDeviceLoginRequest.anetApiRequest.merchantAuthentication.transactionKey = #"XXXXXX";
it works for me

User not logged in error in Facebook SDK iOS

How can I fix this error in my program? Using Facebook SDK.
Error Domain=facebookErrDomain Code=10000 "The operation couldn’t be completed. (facebookErrDomain error 10000.)" UserInfo=0x5da9b10 {error=<CFBasicHash 0x5db09a0 [0x1087400]>{type = mutable dict, count = 2,
entries =>
2 : <CFString 0x5db7920 [0x1087400]>{contents = "type"} = <CFString 0x5d35420 [0x1087400]>{contents = "OAuthException"}
3 : <CFString 0x5d34970 [0x1087400]>{contents = "message"} = <CFString 0x5da96b0 [0x1087400]>{contents = "Error validating access token: The session is invalid because the user logged out."}
}
}
I think the solution enbr posted is not complete. I will explain why:
Access token errors are returned when the Facebook session expires and you continue using an old access token. Logout is not the only scenario in which sessions expire (e.g. change login password in the web). Hence, it is not enough to simply clean the user defaults after logout.
From the client side, we don't know if the access token we have is actually valid, until a response comes with such information. Therefore, you need to detect access token errors and restore the Facebook instance to a working state by cleaning the old access token and expiration date. This way, the user will need to login again to obtain a new access token.
So, IMO, what you need to do is:
Handle errors and detect when an access token error has occurred.
When an access token error occurs, logout automatically and clean user defaults to remove old access token (step 3).
As a result of logout, you need to clean user defaults for access token and expiration date (as enbr posted).
Here you have some code that can be used to detect access token errors:
-(BOOL)isAccessTokenError:(NSError *) error {
if ([[error domain] isEqualToString:#"facebookErrDomain"] && [error code] == 10000 ) {
NSDictionary *userInfo = [error userInfo];
NSDictionary *errorAsDictionary = [userInfo objectForKey:#"error"];
if ([[errorAsDictionary objectForKey:#"type"] isEqualToString:#"OAuthException"]) {
//Invalid access token
return YES;
}
}
if ([[error domain] isEqualToString:#"facebookErrDomain"] && ([error code] == 110 || [error code] == 190)) {
//Error accessing access token
return YES;
}
return NO;
}
The places to handle such errors are:
- (void)request:(FBRequest*)request didFailWithError:(NSError*)error;
- (void)dialog:(FBDialog*)dialog didFailWithError:(NSError *)error;
I hope this helps.
UPDATE:
I forgot to mention. If you are using the SSO feature (most likely), I think it is a very good idea to clean the facebook cookies before login. Sometimes, after an invalid access token error, it seems login won't bring back the Facebook object to a working state (valid access token) unless a "clean login" is performed. Not always work though.
Also, If you are not using the SSO feature, this used to fix the ghost login popup that appeared to automatically disappear again.
This is how I clean the cookies:
NSHTTPCookieStorage* cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray* facebookCookies = [cookies cookiesForURL:
[NSURL URLWithString:#"http://login.facebook.com"]];
for (NSHTTPCookie* cookie in facebookCookies) {
[cookies deleteCookie:cookie];
}
enter code here
I just recently came across this error also. Here is the solution. Place this code in your main FBSessionDelegate (probably your app delegate).
- (void)fbDidLogout {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObjectForKey:#"FBAccessTokenKey"];
[defaults removeObjectForKey:#"FBExpirationDateKey"];
[defaults synchronize];
}
Then delete and reinstall the app!