// // MBIMAuthToken.m // MBIMAuthToken // // Created by James Magahern on 7/6/21. // Copyright © 2021 James Magahern. All rights reserved. // #import "MBIMAuthToken.h" #import #define HOUR 3600 #define DAY (24*HOUR) static const NSTimeInterval ExpirationTime = 15 * DAY; static const char *SecretKey = "709E7CD8-4983-4D5F-B7BF-8B1C6341D2DB"; static NSString *const kUsernamePayloadKey = @"user"; static NSString *const kIssuerPayloadKey = @"iss"; static NSString *const kExpirationDatePayloadKey = @"exp"; static NSString *const kIssuerPayloadValue = @"kordophone"; @interface MBIMAuthToken () @property (nonatomic, copy) NSString *username; @property (nonatomic, copy) NSString *jwtToken; @property (nonatomic, copy) NSDate *expirationDate; // JWT Payload data @property (nonatomic, copy) NSString *headerString; @property (nonatomic, copy) NSString *payloadString; @property (nonatomic, copy) NSData *signatureData; @end @implementation MBIMAuthToken - (instancetype)initWithUsername:(NSString *)username { self = [super init]; if (self) { self.username = username; self.expirationDate = [NSDate dateWithTimeIntervalSinceNow:ExpirationTime]; } return self; } - (instancetype)initWithTokenString:(NSString *)tokenString { NSArray *components = [tokenString componentsSeparatedByString:@"."]; if (components.count != 3) { return nil; } NSString *header = components[0]; NSString *payload = components[1]; NSString *signature = components[2]; NSData *payloadData = [[NSData alloc] initWithBase64EncodedString:payload options:0]; NSDictionary *decodedPayload = [NSJSONSerialization JSONObjectWithData:payloadData options:0 error:nil]; if (!decodedPayload) { return nil; } if (![decodedPayload[kIssuerPayloadKey] isEqualToString:@"kordophone"]) { return nil; } self = [super init]; if (self) { _headerString = header; _payloadString = payload; _signatureData = [[NSData alloc] initWithBase64EncodedString:signature options:0]; _username = decodedPayload[kUsernamePayloadKey]; NSTimeInterval expirationDate = [decodedPayload[kExpirationDatePayloadKey] floatValue]; _expirationDate = [NSDate dateWithTimeIntervalSince1970:expirationDate]; } return self; } - (NSUInteger)hash { return (_username.hash ^ _expirationDate.hash); } - (NSString *)jwtToken { if (!_jwtToken) { NSDictionary *header = @{ @"alg" : @"HS256", @"typ" : @"jwt" }; NSData *headerData = [NSJSONSerialization dataWithJSONObject:header options:0 error:nil]; NSString *headerStr = [headerData base64EncodedStringWithOptions:0]; NSInteger expirationDate = [_expirationDate timeIntervalSince1970]; NSDictionary *payload = @{ kUsernamePayloadKey : _username, kIssuerPayloadKey : kIssuerPayloadValue, kExpirationDatePayloadKey : [NSString stringWithFormat:@"%ld", expirationDate] }; NSData *payloadData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:nil]; NSString *payloadStr = [payloadData base64EncodedStringWithOptions:0]; NSString *jwtDataStr = [NSString stringWithFormat:@"%@.%@", headerStr, payloadStr]; NSData *jwtData = [jwtDataStr dataUsingEncoding:NSASCIIStringEncoding]; unsigned char signature[CC_SHA256_DIGEST_LENGTH]; CCHmac(kCCHmacAlgSHA256, SecretKey, sizeof(SecretKey), jwtData.bytes, jwtData.length, signature); NSData *signatureData = [NSData dataWithBytes:signature length:CC_SHA256_DIGEST_LENGTH]; NSString *signatureStr = [signatureData base64EncodedStringWithOptions:0]; _jwtToken = [NSString stringWithFormat:@"%@.%@", jwtDataStr, signatureStr]; } return _jwtToken; } - (BOOL)isValid { // Verify expiration date BOOL expirationDateValid = [_expirationDate timeIntervalSinceNow] > 0; if (!expirationDateValid) { MBIMLogInfo(@"Auth token expired."); return NO; } // Verify signature NSString *verificationDataStr = [NSString stringWithFormat:@"%@.%@", _headerString, _payloadString]; NSData *verificationData = [verificationDataStr dataUsingEncoding:NSASCIIStringEncoding]; unsigned char computedSignature[CC_SHA256_DIGEST_LENGTH]; CCHmac(kCCHmacAlgSHA256, SecretKey, sizeof(SecretKey), verificationData.bytes, verificationData.length, computedSignature); NSData *computedSignatureData = [NSData dataWithBytes:computedSignature length:CC_SHA256_DIGEST_LENGTH]; if (![computedSignatureData isEqualToData:_signatureData]) { MBIMLogInfo(@"Auth token signature verification failed."); return NO; } return YES; } @end