150 lines
4.9 KiB
Objective-C
150 lines
4.9 KiB
Objective-C
//
|
|
// MBIMAuthToken.m
|
|
// MBIMAuthToken
|
|
//
|
|
// Created by James Magahern on 7/6/21.
|
|
// Copyright © 2021 James Magahern. All rights reserved.
|
|
//
|
|
|
|
#import "MBIMAuthToken.h"
|
|
#import <CommonCrypto/CommonHMAC.h>
|
|
|
|
#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<NSString *> *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
|