yourkarma/JWT

View on GitHub
Sources/JWT/Algorithms/RSFamily/JWTAlgorithmRSBase.m

Summary

Maintainability
Test Coverage
//
//  JWTAlgorithmRSBase.m
//  JWT
//
//  Created by Lobanov Dmitry on 13.03.16.
//  Copyright © 2016 Karma. All rights reserved.
//

#import "JWTAlgorithmRSBase.h"
#import "JWTBase64Coder.h"
#import "JWTCryptoSecurity.h"
#import "JWTCryptoKeyExtractor.h"
#import "JWTCryptoKey.h"
#import "JWTAlgorithmFactory.h"
#import <CommonCrypto/CommonCrypto.h>

#import "JWTAlgorithmErrorDescription+Subclass.h"
NSString *const JWTAlgorithmRSFamilyErrorDomain = @"io.jwt.jwa.rs";

@implementation JWTAlgorithmRSFamilyErrorDescription
#pragma mark - Subclass
+ (NSString *)errorDomain {
    return JWTAlgorithmRSFamilyErrorDomain;
}
+ (NSInteger)defaultErrorCode {
    return JWTAlgorithmRSFamilyErrorUnexpected;
}
+ (NSInteger)externalErrorCode {
    return JWTAlgorithmRSFamilyErrorInternalSecurityAPI;
}
+ (NSDictionary *)codesAndUserDescriptions {
    static NSDictionary *dictionary = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        dictionary = @{
                       @(JWTAlgorithmRSFamilyErrorIncorrectHashComputation) : @"RS algorithm incorrect hash computation!",
                       @(JWTAlgorithmRSFamilyErrorIncorrectKeySize) : @"RS algorithm incorrect key size. Apple have not sent any response about it.",
                       @(JWTAlgorithmRSFamilyErrorInternalSecurityAPI) : @"RS algorithm internal security framework error.",
                       @(JWTAlgorithmRSFamilyErrorUnexpected) : @"RS algorithm unexpected error!"
                       };
    });
    return dictionary;
}
+ (NSDictionary *)codesAndDescriptions {
    static NSDictionary *dictionary = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        dictionary = @{
                       @(JWTAlgorithmRSFamilyErrorIncorrectHashComputation) : @"JWTAlgorithmRSFamilyErrorIncorrectHashComputation",
                       @(JWTAlgorithmRSFamilyErrorIncorrectKeySize) : @"JWTAlgorithmRSFamilyErrorIncorrectKeySize",
                       @(JWTAlgorithmRSFamilyErrorInternalSecurityAPI) : @"JWTAlgorithmRSFamilyErrorInternalSecurityAPI",
                       @(JWTAlgorithmRSFamilyErrorUnexpected) : @"JWTAlgorithmRSFamilyErrorUnexpected"
                       };
    });
    return dictionary;
}
@end

/*
*    * Possible inheritence *
*
*
*             RSBase (Public + Create-category)
*            /      \
*           /        \
*  RSBaseMac          RSBaseIOS
*           \  ifdef /
*            \      /
*         RSFamilyMember
*                |
*         RSFamilyMemberMutable
*
*/

NSString *const JWTAlgorithmNameRS256 = @"RS256";
NSString *const JWTAlgorithmNameRS384 = @"RS384";
NSString *const JWTAlgorithmNameRS512 = @"RS512";

@interface JWTAlgorithmRSBase()
@property (nonatomic, readonly) id <JWTCryptoKeyExtractorProtocol> keyExtractor;
@end

@implementation JWTAlgorithmRSBase

#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
    // create new.
    id <JWTRSAlgorithm> algorithm = (id<JWTRSAlgorithm>)[JWTAlgorithmFactory algorithmByName:[self name]];
    algorithm.privateKeyCertificatePassphrase = self.privateKeyCertificatePassphrase;
    algorithm.keyExtractorType = self.keyExtractorType;
    algorithm.signKey = self.signKey;
    algorithm.verifyKey = self.verifyKey;
    return algorithm;
}

@synthesize privateKeyCertificatePassphrase;
@synthesize keyExtractorType;
@synthesize signKey;
@synthesize verifyKey;
- (id<JWTCryptoKeyExtractorProtocol>) keyExtractor {
    return [JWTCryptoKeyExtractor createWithType:self.keyExtractorType];
}
#pragma mark - Override
- (size_t)ccSHANumberDigestLength {
    @throw [[NSException alloc] initWithName:NSInternalInconsistencyException reason:@"ccSHANumberDigestLength property should be overriden" userInfo:nil];
}

- (uint32_t)secPaddingPKCS1SHANumber {
    @throw [[NSException alloc] initWithName:NSInternalInconsistencyException reason:@"secPaddingPKCS1SHANumber property should be overriden" userInfo:nil];
}

- (unsigned char *)CC_SHANumberWithData:(const void *)data withLength:(CC_LONG)len withHashBytes:(unsigned char *)hashBytes {
    return nil;
}

- (NSString *)name {
    return @"RSBase";
}

#pragma mark - JWTAlgorithm
- (NSDictionary *)verifyKeyExtractorParameters {
    return @{[JWTCryptoKey parametersKeyBuilder] : [JWTCryptoKeyBuilder new].keyTypeRSA};
}
- (NSDictionary *)signKeyExtractorParameters {
    NSMutableDictionary *mutableDictionary = [[self verifyKeyExtractorParameters] mutableCopy];
    mutableDictionary[[JWTCryptoKeyExtractor parametersKeyCertificatePassphrase]] = self.privateKeyCertificatePassphrase ?: [NSNull null];
    return [mutableDictionary copy];
}

- (BOOL)removeKeyItem:(id<JWTCryptoKeyProtocol>)item error:(NSError *__autoreleasing *)error {
    NSError *theError = nil;
    [JWTCryptoSecurity removeKeyByTag:item.tag error:&theError];
    if (error) {
        *error = theError;
    }
    return theError == nil;
}

- (NSData *)signHash:(NSData *)hash key:(NSData *)key error:(NSError *__autoreleasing *)error {
    id<JWTCryptoKeyExtractorProtocol> theExtractor = self.keyExtractor ?: [JWTCryptoKeyExtractor privateKeyInP12];
    NSError *extractKeyError = nil;
    id <JWTCryptoKeyProtocol> keyItem = self.signKey ?: [theExtractor keyFromData:key parameters:[self signKeyExtractorParameters] error:&extractKeyError];
    
    if (extractKeyError || keyItem == nil) {
        // tell about error
        if (extractKeyError && error) {
            *error = extractKeyError;
        }
        NSError *removeError = nil;
        [self removeKeyItem:keyItem error:&removeError];
        return nil;
    }
    else {
        NSError *signError = nil;
        NSData *result = [self signData:hash key:keyItem.key error:&signError];
        if (signError && error) {
            *error = signError;
        }
        return result;
    }
}
- (BOOL)verifyHash:(NSData *)hash signature:(NSData *)signature key:(NSData *)key error:(NSError *__autoreleasing *)error {
    id<JWTCryptoKeyExtractorProtocol> theExtractor = self.keyExtractor ?: [JWTCryptoKeyExtractor publicKeyWithCertificate];
    NSError *extractKeyError = nil;
    id<JWTCryptoKeyProtocol> keyItem = self.verifyKey ?: [theExtractor keyFromData:key parameters:[self verifyKeyExtractorParameters] error:&extractKeyError];
    if (extractKeyError || keyItem == nil) {
        if (extractKeyError && error) {
            *error = extractKeyError;
        }
        NSError *removeError = nil;
        [self removeKeyItem:keyItem error:&removeError];
        return NO;
    }
    else {
        NSError *verifyError = nil;
        BOOL verified = [self verifyData:hash signature:signature key:keyItem.key error:&verifyError];
        if (verifyError && error) {
            *error = verifyError;
        }
        NSError *removeError = nil;
        [self removeKeyItem:keyItem error:&removeError];
        return verified;
    }
}

#pragma mark - Private ( Override-part depends on platform )
- (BOOL)verifyData:(NSData *)plainData signature:(NSData *)signature key:(SecKeyRef)publicKey error:(NSError *__autoreleasing*)error {
    return NO;
}

- (NSData *)signData:(NSData *)plainData key:(SecKeyRef)privateKey error:(NSError *__autoreleasing*)error {
    return nil;
}
@end

#if TARGET_OS_MAC && TARGET_OS_IPHONE
@interface JWTAlgorithmRSBaseIOS : JWTAlgorithmRSBase @end
@implementation JWTAlgorithmRSBaseIOS
- (BOOL)verifyData:(NSData *)plainData signature:(NSData *)signature key:(SecKeyRef)publicKey error:(NSError *__autoreleasing *)error {
    size_t signedHashBytesSize = SecKeyGetBlockSize(publicKey);
    const void* signedHashBytes = [signature bytes];

    size_t hashBytesSize = self.ccSHANumberDigestLength;
    uint8_t* hashBytes = malloc(hashBytesSize);
    if (![self CC_SHANumberWithData:[plainData bytes] withLength:(CC_LONG)[plainData length] withHashBytes:hashBytes]) {
        if (error) {
            *error = [JWTAlgorithmRSFamilyErrorDescription errorWithCode:JWTAlgorithmRSFamilyErrorIncorrectHashComputation];
        }
        return false;
    }

    OSStatus status = SecKeyRawVerify(publicKey,
                                      self.secPaddingPKCS1SHANumber,
                                      hashBytes,
                                      hashBytesSize,
                                      signedHashBytes,
                                      signedHashBytesSize);

    return status == errSecSuccess;
}

- (NSData *)signData:(NSData *)plainData key:(SecKeyRef)privateKey error:(NSError *__autoreleasing *)error {
    size_t signedHashBytesSize = SecKeyGetBlockSize(privateKey);
    uint8_t* signedHashBytes = malloc(signedHashBytesSize);
    memset(signedHashBytes, 0x0, signedHashBytesSize);

    size_t hashBytesSize = self.ccSHANumberDigestLength;
    uint8_t* hashBytes = malloc(hashBytesSize);

    // ([plainData bytes], (CC_LONG)[plainData length], hashBytes)
    unsigned char *str = [self CC_SHANumberWithData:[plainData bytes] withLength:(CC_LONG)[plainData length] withHashBytes:hashBytes];

    if (!str) {
        if (error) {
            *error = [JWTAlgorithmRSFamilyErrorDescription errorWithCode:JWTAlgorithmRSFamilyErrorIncorrectHashComputation];
        }
        return nil;
    }

    SecKeyRawSign(privateKey,
                  self.secPaddingPKCS1SHANumber,
                  hashBytes,
                  hashBytesSize,
                  signedHashBytes,
                  &signedHashBytesSize);

    NSData* signedHash = [NSData dataWithBytes:signedHashBytes
                                        length:(NSUInteger)signedHashBytesSize];

    if (hashBytes) {
        free(hashBytes);
    }

    if (signedHashBytes) {
        free(signedHashBytes);
    }

    return signedHash;
}
@end
#endif

#if TARGET_OS_MAC && !TARGET_OS_IPHONE
@interface JWTAlgorithmRSBaseMac : JWTAlgorithmRSBase
- (BOOL)checkKeyConsistency:(SecKeyRef)key;
@end

@implementation JWTAlgorithmRSBaseMac

- (BOOL)checkKeyConsistency:(SecKeyRef)key {
    size_t keyLength = SecKeyGetBlockSize(key);
    size_t hashBytesSize = self.ccSHANumberDigestLength;
    Byte bitsPerByte = /*???*/sizeof(Byte) * CHAR_BIT;
    return keyLength == hashBytesSize * bitsPerByte;
}

- (NSData *)executeTransform:(SecTransformRef)transform withInput:(NSData *)input withDigestType:(CFStringRef)type withDigestLength:(NSNumber *)length withFalseResult:(CFTypeRef)falseResultRef {
    CFErrorRef errorRef = NULL;

    CFTypeRef resultRef = NULL;
    
    // inout
    NSData *resultData = nil;


    BOOL success = transform != NULL;
    //TODO: after import algorithm by pem, this code seems not working well.
    //error: Error Domain=com.apple.security.transforms.error Code=6 "Invalid digest algorithm for RSA signature, choose one of: SHA1, SHA2 (512bits, 348bits, 256bits, or 224 bits), MD2, or MD5"
    //TODO: add error inout parameter to this method.
    if (success) {
        // setup digest type
        success = SecTransformSetAttribute(transform, kSecDigestTypeAttribute, type, &errorRef);
    }

    if (success) {
        // digest length
        success = SecTransformSetAttribute(transform, kSecDigestLengthAttribute, (__bridge CFNumberRef)length, &errorRef);
    }

    if (success) {
        // set input
        success = SecTransformSetAttribute(transform, kSecTransformInputAttributeName, (__bridge CFDataRef)input, &errorRef);
    }

    if (success) {
        // execute
        resultRef = SecTransformExecute(transform, &errorRef);
        success = (resultRef != falseResultRef);
    }

    BOOL positiveResult = success; // resultRef != falseResultRef

    // error
    if (errorRef != NULL) {
        NSLog(@"%@ error: %@", self.debugDescription, (__bridge NSError *)errorRef);
    }
    else {
        if (positiveResult) {
            resultData = (__bridge NSData *)resultRef;
        }
    }

    if (resultRef != NULL) {
        CFRelease(resultRef);
    }
    
    if (errorRef != NULL) {
        CFRelease(errorRef);
    }

    return resultData;
}
- (BOOL)verifyData:(NSData *)plainData signature:(NSData *)signature key:(SecKeyRef)publicKey error:(NSError *__autoreleasing *)error {

    size_t signedHashBytesSize = SecKeyGetBlockSize(publicKey);
    //const void* signedHashBytes = [signature bytes];

    size_t hashBytesSize = self.ccSHANumberDigestLength;
    uint8_t* hashBytes = malloc(hashBytesSize);
    Byte bitsPerByte = /*???*/sizeof(Byte) * CHAR_BIT;
//    NSNumber *plainDataLength = @((CC_LONG)[plainData length]);
    
    if (![self checkKeyConsistency:publicKey]) {
        // error in log!
        // bug?
        NSLog(@"the size of key(%@) should be related to SHA length in bits(%@)", @(signedHashBytesSize), @(hashBytesSize * bitsPerByte));
        if (error) {
            *error = [JWTAlgorithmRSFamilyErrorDescription errorWithCode:JWTAlgorithmRSFamilyErrorIncorrectKeySize];
        }
        return NO;
    }
    
    if (![self CC_SHANumberWithData:[plainData bytes] withLength:(CC_LONG)[plainData length] withHashBytes:hashBytes]) {
        if (error) {
            *error = [JWTAlgorithmRSFamilyErrorDescription errorWithCode:JWTAlgorithmRSFamilyErrorIncorrectHashComputation];
        }
        return NO;
    }

//    NSString *formattedString = [NSString stringWithFormat:@"%@: %@ \n %@: %@ \n %@: %@", @"hashBytesSize", @(hashBytesSize), @"plainDataLength", plainDataLength, @"signedHashBytesSize", @(signedHashBytesSize)];
//
//    NSLog(@"%@ %@", self.debugDescription, formattedString);
    // verify for iOS
//    OSStatus status = SecKeyRawVerify(publicKey,
//                                      self.secPaddingPKCS1SHANumber,
//                                      hashBytes,
//                                      hashBytesSize,
//                                      signedHashBytes,
//                                      signedHashBytesSize);
//    return status == errSecSuccess;
    
    CFErrorRef errorRef = NULL;
    SecTransformRef transform = SecVerifyTransformCreate(publicKey, (__bridge CFDataRef)signature, &errorRef);

    // verification. false result is kCFBooleanFalse
    BOOL result = [self executeTransform:transform withInput:plainData withDigestType:kSecDigestSHA2 withDigestLength:@(signedHashBytesSize) withFalseResult:kCFBooleanFalse] != nil;

    if (transform != NULL) {
        // TODO: WTF?
        // somehow it stops there :/
        // Is it a race condition with removeSecKeyForTag?
        // don't know :(
        CFRelease(transform);
    }
    
    if (errorRef != NULL) {
        CFRelease(errorRef);
    }
    
    if (hashBytes != NULL) {
        free(hashBytes);
    }

    return result;
}

- (NSData *)signData:(NSData *)plainData key:(SecKeyRef)privateKey error:(NSError *__autoreleasing *)error {
    size_t signedHashBytesSize = SecKeyGetBlockSize(privateKey);
    //uint8_t* signedHashBytes = malloc(signedHashBytesSize);
    //memset(signedHashBytes, 0x0, signedHashBytesSize);

    size_t hashBytesSize = self.ccSHANumberDigestLength;
    uint8_t* hashBytes = malloc(hashBytesSize);

    /**
     for sha256
     CC_SHANumberWithData() is CC_SHA256()
     self.secPaddingPKCS1SHANumber = kSecPaddingPKCS1SHA256
     self.ccSHANumberDigestLength  = CC_SHA256_DIGEST_LENGTH
     */

    unsigned char *str = [self CC_SHANumberWithData:[plainData bytes] withLength:(CC_LONG)[plainData length] withHashBytes:hashBytes];

    if (!str) {
        if (error) {
            *error = [JWTAlgorithmRSFamilyErrorDescription errorWithCode:JWTAlgorithmRSFamilyErrorIncorrectHashComputation];
        }
        return nil;
    }

    CFErrorRef errorRef = NULL;

    SecTransformRef transform = SecSignTransformCreate(privateKey, &errorRef);

    NSData *resultData = nil;
    
    // signing: false result is NULL.
    resultData = [self executeTransform:transform withInput:plainData withDigestType:kSecDigestSHA2 withDigestLength:@(signedHashBytesSize) withFalseResult:NULL];

    if (transform != NULL) {
        CFRelease(transform);
    }
    
    if (errorRef != NULL) {
        CFRelease(errorRef);
    }
    
    if (hashBytes != NULL) {
        free(hashBytes);
    }

    return resultData;
}
@end
#endif


// MacOS OR iOS is Base
#if TARGET_OS_MAC && !TARGET_OS_IPHONE
@interface JWTAlgorithmRSFamilyMember : JWTAlgorithmRSBaseMac @end
#else
@interface JWTAlgorithmRSFamilyMember : JWTAlgorithmRSBaseIOS @end
#endif

@interface JWTAlgorithmRS256 : JWTAlgorithmRSFamilyMember @end
@interface JWTAlgorithmRS384 : JWTAlgorithmRSFamilyMember @end
@interface JWTAlgorithmRS512 : JWTAlgorithmRSFamilyMember @end

@implementation JWTAlgorithmRSFamilyMember
- (uint32_t)secPaddingPKCS1SHANumber {
    return 0;
}
@end

@implementation JWTAlgorithmRS256

- (size_t)ccSHANumberDigestLength {
    return CC_SHA256_DIGEST_LENGTH;
}

#if TARGET_OS_MAC && TARGET_OS_IPHONE
- (uint32_t)secPaddingPKCS1SHANumber {
    return kSecPaddingPKCS1SHA256;
}
#endif

- (unsigned char *)CC_SHANumberWithData:(const void *)data withLength:(CC_LONG)len withHashBytes:(unsigned char *)hashBytes {
    return CC_SHA256(data, len, hashBytes);
}

- (NSString *)name {
    return JWTAlgorithmNameRS256;
}

@end

@implementation JWTAlgorithmRS384

- (size_t)ccSHANumberDigestLength {
    return CC_SHA384_DIGEST_LENGTH;
}

#if TARGET_OS_MAC && TARGET_OS_IPHONE
- (uint32_t)secPaddingPKCS1SHANumber {
    return kSecPaddingPKCS1SHA384;
}
#endif

- (unsigned char *)CC_SHANumberWithData:(const void *)data withLength:(CC_LONG)len withHashBytes:(unsigned char *)hashBytes {
    return CC_SHA384(data, len, hashBytes);
}

- (NSString *)name {
    return JWTAlgorithmNameRS384;
}

@end

@implementation JWTAlgorithmRS512

- (size_t)ccSHANumberDigestLength {
    return CC_SHA512_DIGEST_LENGTH;
}

#if TARGET_OS_MAC && TARGET_OS_IPHONE
- (uint32_t)secPaddingPKCS1SHANumber {
    return kSecPaddingPKCS1SHA512;
}
#endif

- (unsigned char *)CC_SHANumberWithData:(const void *)data withLength:(CC_LONG)len withHashBytes:(unsigned char *)hashBytes {
    return CC_SHA512(data, len, hashBytes);
}

- (NSString *)name {
    return JWTAlgorithmNameRS512;
}

@end


@interface JWTAlgorithmRSFamilyMemberMutable : JWTAlgorithmRSFamilyMember

@property (assign, nonatomic, readwrite) size_t ccSHANumberDigestLength;
@property (assign, nonatomic, readwrite) uint32_t secPaddingPKCS1SHANumber;
@property (copy, nonatomic, readwrite) unsigned char * (^ccShaNumberWithData)(const void *data, CC_LONG len, unsigned char *hashBytes);
@property (copy, nonatomic, readwrite) NSString *name;
@end

@implementation JWTAlgorithmRSFamilyMemberMutable

@synthesize ccSHANumberDigestLength = _ccSHANumberDigestLength;
@synthesize secPaddingPKCS1SHANumber = _secPaddingPKCS1SHANumber;
@synthesize name = _name;

- (size_t)ccSHANumberDigestLength {
    return _ccSHANumberDigestLength;
}

- (uint32_t)secPaddingPKCS1SHANumber {
    return _secPaddingPKCS1SHANumber;
}

- (unsigned char *)CC_SHANumberWithData:(const void *)data withLength:(uint32_t)len withHashBytes:(unsigned char *)hashBytes {
    unsigned char *result = [super CC_SHANumberWithData:data withLength:len withHashBytes:hashBytes];
    if (!result && self.ccShaNumberWithData) {
        result = self.ccShaNumberWithData(data, len, hashBytes);
    }
    return result;
}

@end


@implementation JWTAlgorithmRSBase (Create)

+ (instancetype)algorithm256 {
    return [JWTAlgorithmRS256 new];
}

+ (instancetype)algorithm384 {
    return [JWTAlgorithmRS384 new];
}

+ (instancetype)algorithm512 {
    return [JWTAlgorithmRS512 new];
}

+ (instancetype)mutableAlgorithm {
    JWTAlgorithmRSFamilyMemberMutable *base = [JWTAlgorithmRSFamilyMemberMutable new];
    base.ccSHANumberDigestLength = CC_SHA256_DIGEST_LENGTH;

    //set to something ok
    //base.secPaddingPKCS1SHANumber = kSecPaddingPKCS1SHA256;
    base.ccShaNumberWithData = ^unsigned char *(const void *data, CC_LONG len, unsigned char *hashBytes){
        return CC_SHA256(data, len, hashBytes);
    };
    base.name = @"RS256";
    return base;
}

@end