b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni/*
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni * The contents of this file are subject to the terms of the Common Development and
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni * Distribution License (the License). You may not use this file except in compliance with the
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni * License.
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni *
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni * specific language governing permission and limitations under the License.
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni *
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni * When distributing Covered Software, include this CDDL Header Notice in each file and include
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni * Header, with the fields enclosed by brackets [] replaced by your own identifying
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni * information: "Portions copyright [year] [name of copyright owner]".
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni *
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni * Copyright 2016 ForgeRock AS.
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni */
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings#include <CommonCrypto/CommonHMAC.h>
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni#import "FRAMessageUtils.h"
c6441b7e8f8ff32eeac89153bf584b8945543e84Diego Colantoni#import "FRAQRUtils.h"
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
9ebd9a731316dfd624ce3bcc4ea6519d10899936Ken Stubbings/*! The Communication mechanism Content Type. */
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantonistatic NSString * const JSON_CONTENT_TYPE = @"application/json";
9ebd9a731316dfd624ce3bcc4ea6519d10899936Ken Stubbings/*! The Communication mechanism key. */
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantonistatic NSString * const CONTENT_TYPE_HEADER = @"Content-Type";
cb0f7f3d422b5b0eebe075af32a90a58072c6b5fDiego Colantoni/*! The Set-Cookie header field. */
cb0f7f3d422b5b0eebe075af32a90a58072c6b5fDiego Colantonistatic NSString * const SET_COOKIE_HEADER = @"Set-Cookie";
cb0f7f3d422b5b0eebe075af32a90a58072c6b5fDiego Colantoni/*! The Accept-API-Version header field. */
cb0f7f3d422b5b0eebe075af32a90a58072c6b5fDiego Colantonistatic NSString * const ACCEPT_API_VERSION_HEADER = @"Accept-API-Version";
cb0f7f3d422b5b0eebe075af32a90a58072c6b5fDiego Colantoni/*! The Accept-API-Version header value. */
cb0f7f3d422b5b0eebe075af32a90a58072c6b5fDiego Colantonistatic NSString * const ACCEPT_API_VERSION_HEADER_VALUE = @"resource=1.0, protocol=1.0";
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni@implementation FRAMessageUtils
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings+ (void)respondWithEndpoint:(NSString *)endpoint
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings base64Secret:(NSString *)base64Secret
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings messageId:(NSString *)messageId
5d37db6a3aca50ba14cba8909d3ae44f7d43e407Ken Stubbings loadBalancerCookieData:(NSString *)loadBalancerCookieData
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings data:(NSDictionary *)data
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings handler:(void (^)(NSInteger, NSError *))handler {
5d37db6a3aca50ba14cba8909d3ae44f7d43e407Ken Stubbings [self respondWithEndpoint:endpoint base64Secret:base64Secret messageId:messageId loadBalancerCookieData:loadBalancerCookieData data:data protocol:nil handler:handler];
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni}
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings+ (void)respondWithEndpoint:(NSString *)endpoint
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings base64Secret:(NSString *)base64Secret
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings messageId:(NSString *)messageId
5d37db6a3aca50ba14cba8909d3ae44f7d43e407Ken Stubbings loadBalancerCookieData:(NSString *)loadBalancerCookieData
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings data:(NSDictionary *)data
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings protocol:(Class) protocol
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings handler:(void (^)(NSInteger, NSError *))handler {
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni NSURL *URL = [NSURL URLWithString:endpoint];
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni NSDictionary *payload = [self createPayloadWithMessageId:messageId base64Secret:base64Secret data:data];
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni AFHTTPSessionManager *manager = [self createHTTPSessionManager:protocol];
9ebd9a731316dfd624ce3bcc4ea6519d10899936Ken Stubbings [manager setRequestSerializer:[AFJSONRequestSerializer serializer]];
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni [manager.requestSerializer setValue:JSON_CONTENT_TYPE forHTTPHeaderField:CONTENT_TYPE_HEADER];
cb0f7f3d422b5b0eebe075af32a90a58072c6b5fDiego Colantoni [manager.requestSerializer setValue:loadBalancerCookieData forHTTPHeaderField:SET_COOKIE_HEADER];
cb0f7f3d422b5b0eebe075af32a90a58072c6b5fDiego Colantoni [manager.requestSerializer setValue:ACCEPT_API_VERSION_HEADER_VALUE forHTTPHeaderField:ACCEPT_API_VERSION_HEADER];
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni [manager POST:URL.absoluteString
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni parameters:payload
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni progress:nil
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni success:^(NSURLSessionTask *task, id responseObject) {
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni handler([(NSHTTPURLResponse*)task.response statusCode], nil);
9ebd9a731316dfd624ce3bcc4ea6519d10899936Ken Stubbings } failure:^(NSURLSessionTask *task, NSError *error) {
9ebd9a731316dfd624ce3bcc4ea6519d10899936Ken Stubbings NSLog(@"Error code = %li", [(NSHTTPURLResponse*)task.response statusCode]);
9ebd9a731316dfd624ce3bcc4ea6519d10899936Ken Stubbings
9ebd9a731316dfd624ce3bcc4ea6519d10899936Ken Stubbings handler([(NSHTTPURLResponse*)task.response statusCode], error);
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni }];
9ebd9a731316dfd624ce3bcc4ea6519d10899936Ken Stubbings
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni}
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings+ (AFHTTPSessionManager *)createHTTPSessionManager:(Class)protocol {
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni if(protocol) {
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni NSMutableArray * protocolsArray = [sessionConfiguration.protocolClasses mutableCopy];
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni [protocolsArray insertObject:protocol atIndex:0];
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni sessionConfiguration.protocolClasses = protocolsArray;
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni return [[AFHTTPSessionManager alloc] initWithSessionConfiguration:sessionConfiguration];
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni }
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni return [AFHTTPSessionManager manager];
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni}
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings+ (NSDictionary *)createPayloadWithMessageId:(NSString *)messageId
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni base64Secret:(NSString *)base64Secret
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni data:(NSDictionary *)data {
9ebd9a731316dfd624ce3bcc4ea6519d10899936Ken Stubbings NSString *jwtData = [self generateJwtWithPayload:data base64Secret:base64Secret];
9ebd9a731316dfd624ce3bcc4ea6519d10899936Ken Stubbings NSDictionary *topLevelData = @{@"messageId":messageId, @"jwt":jwtData};
9ebd9a731316dfd624ce3bcc4ea6519d10899936Ken Stubbings
9ebd9a731316dfd624ce3bcc4ea6519d10899936Ken Stubbings return topLevelData;
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni}
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings+ (NSString *)generateJwtWithPayload:(NSDictionary *)payload base64Secret:(NSString *)base64Secret {
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni id<JWTAlgorithm> algorithm = [JWTAlgorithmFactory algorithmByName:@"HS256"];
465ea459a87d4605e145d8f45b6a9c104b696e3bCraig McDonnell NSData *secretBytes = [FRAQRUtils decodeURL:base64Secret];
2708b42676edf3d2e8f85b3c22b9e3be3cf43eb8Diego Colantoni
2708b42676edf3d2e8f85b3c22b9e3be3cf43eb8Diego Colantoni return (secretBytes) ? [JWTBuilder encodePayload:payload].secretData(secretBytes).algorithm(algorithm).encode : @"";
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni}
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni
6a2ae9c7fb4d2c40d75cab0edaf940f22c18224fDiego Colantoni+ (NSDictionary *)extractJTWBodyFromString:(NSString *)message error:(NSError *__autoreleasing*)error {
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings NSArray* strings = [message componentsSeparatedByString:@"."];
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings NSString* payloadString = strings[1];
c0a2f8c6e84ddf9a597f19e5f161382b0e2cf81bDiego Colantoni payloadString = [FRAQRUtils pad:payloadString];
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings NSData *payloadBytes = [[NSData alloc] initWithBase64EncodedString:payloadString options:0];
6a2ae9c7fb4d2c40d75cab0edaf940f22c18224fDiego Colantoni
6a2ae9c7fb4d2c40d75cab0edaf940f22c18224fDiego Colantoni return [NSJSONSerialization JSONObjectWithData:payloadBytes
6a2ae9c7fb4d2c40d75cab0edaf940f22c18224fDiego Colantoni options:NSJSONReadingMutableContainers
6a2ae9c7fb4d2c40d75cab0edaf940f22c18224fDiego Colantoni error:error];
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings}
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings
c0a2f8c6e84ddf9a597f19e5f161382b0e2cf81bDiego Colantoni+ (NSString *)generateChallengeResponse:(NSString *)challenge secret:(NSString *)secret {
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings
465ea459a87d4605e145d8f45b6a9c104b696e3bCraig McDonnell NSData *saltData = [FRAQRUtils decodeURL:secret];
465ea459a87d4605e145d8f45b6a9c104b696e3bCraig McDonnell NSData *paramData = [FRAQRUtils decodeBase64:challenge];
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings NSMutableData * data = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings CCHmac(kCCHmacAlgSHA256, saltData.bytes, saltData.length, paramData.bytes, paramData.length, data.mutableBytes);
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings NSString * hashedResponseString = [data base64EncodedStringWithOptions:0];
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings return hashedResponseString;
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings}
a1e92b2783be4bfeb0c7e267223cc7779a6f324cKen Stubbings
b3a21de0a0e0a5dea71bece7e5c0356700136fbcDiego Colantoni@end