ably-cocoa/Source/ARTCrypto.m

350 lines
10 KiB
Objective-C

#import "ARTCrypto+Private.h"
#import <CommonCrypto/CommonCrypto.h>
#define ART_CBC_BLOCK_LENGTH (16)
@interface ARTCipherParams ()
- (BOOL)ccAlgorithm:(CCAlgorithm *)algorithm error:(NSError **)error;
@end
@interface ARTCrypto ()
@property (nonatomic, strong) ARTLog * logger;
@end
@interface ARTCbcCipher ()
@property CCAlgorithm algorithm;
@end
@implementation NSString (ARTCipherKeyCompatible)
- (NSData *)toData {
NSString *key = self;
key = [key stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
key = [key stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
return [[NSData alloc] initWithBase64EncodedString:key options:0];
}
@end
@implementation NSData (ARTCipherKeyCompatible)
- (NSData *)toData {
return self;
}
@end
@implementation ARTCipherParams
- (instancetype)initWithAlgorithm:(NSString *)algorithm key:(id<ARTCipherKeyCompatible>)key {
NSData *keyData = [key toData];
return [self initWithAlgorithm:algorithm key:keyData iv:nil];
}
- (instancetype)initWithAlgorithm:(NSString *)algorithm key:(id<ARTCipherKeyCompatible>)key iv:(NSData *)iv {
self = [super init];
if (self) {
_algorithm = algorithm;
_key = [key toData];
_keyLength = [_key length] * 8;
_iv = iv;
CCAlgorithm ccAlgorithm;
NSError *error = nil;
if (![self ccAlgorithm:&ccAlgorithm error:&error]) {
[ARTException raise:NSInvalidArgumentException format:@"%@", error.userInfo[NSLocalizedFailureReasonErrorKey]];
}
}
return self;
}
- (NSString *)getMode {
return @"CBC";
}
- (BOOL)ccAlgorithm:(CCAlgorithm *)algorithm error:(NSError **)error {
NSString *errorMsg;
if (NSOrderedSame == [self.algorithm compare:@"AES" options:NSCaseInsensitiveSearch]) {
if (self.iv != nil && [self.iv length] != ART_CBC_BLOCK_LENGTH) {
errorMsg = [NSString stringWithFormat:@"iv length expected to be %d, got %d instead", ART_CBC_BLOCK_LENGTH, (int)[self.iv length]];
} else if (self.keyLength != 128 && self.keyLength != 256) {
errorMsg = [NSString stringWithFormat:@"invalid key length for AES algorithm: %d", (int)self.keyLength];
} else {
*algorithm = kCCAlgorithmAES;
}
} else if (NSOrderedSame == [self.algorithm compare:@"DES" options:NSCaseInsensitiveSearch]) {
*algorithm = kCCAlgorithmDES;
} else if (NSOrderedSame == [self.algorithm compare:@"3DES" options:NSCaseInsensitiveSearch]) {
*algorithm = kCCAlgorithm3DES;
} else if (NSOrderedSame == [self.algorithm compare:@"CAST" options:NSCaseInsensitiveSearch]) {
*algorithm = kCCAlgorithmCAST;
} else if (NSOrderedSame == [self.algorithm compare:@"RC4" options:NSCaseInsensitiveSearch]) {
*algorithm = kCCAlgorithmRC4;
} else if (NSOrderedSame == [self.algorithm compare:@"RC2" options:NSCaseInsensitiveSearch]) {
*algorithm = kCCAlgorithmRC2;
} else {
errorMsg = [NSString stringWithFormat:@"unknown algorithm: %@", self.algorithm];
}
if (errorMsg) {
[self.logger error:@"ARTCrypto.ccAlgorithm: %@", errorMsg];
if (error) *error = [NSError errorWithDomain:ARTAblyErrorDomain code:0 userInfo:@{NSLocalizedFailureReasonErrorKey: errorMsg}];
return NO;
}
return YES;
}
- (ARTCipherParams *)toCipherParams {
return self;
}
@end
@implementation NSDictionary (ARTCipherParamsCompatible)
- (ARTCipherParams *)toCipherParams {
return [ARTCrypto getDefaultParams:self];
}
@end
@implementation ARTCbcCipher
- (id)initWithCipherParams:(ARTCipherParams *)cipherParams {
self = [super init];
if (self) {
_keySpec = cipherParams.key;
_iv = cipherParams.iv;
_blockLength = ART_CBC_BLOCK_LENGTH;
if (![cipherParams ccAlgorithm:&_algorithm error:nil]) {
return nil;
}
}
return self;
}
-(size_t) keyLength {
return [self.keySpec length] *8;
}
+ (instancetype)cbcCipherWithParams:(ARTCipherParams *)cipherParams {
return [[self alloc] initWithCipherParams:cipherParams];
}
- (ARTStatus *)encrypt:(NSData *)plaintext output:(NSData *__autoreleasing *)output {
NSData *iv = self.iv != nil ? self.iv : [ARTCrypto generateSecureRandomData:self.blockLength];
NSData *ciphertext = nil;
// The maximum cipher text is plaintext length + block length. We are also prepending this with the IV so need 2 block lengths in addition to the plaintext length.
size_t outputBufLen = [plaintext length] + self.blockLength * 2;
void *buf = malloc(outputBufLen);
if (!buf) {
[self.logger error:@"ARTCrypto error encrypting"];
return [ARTStatus state:ARTStateError];
}
// Copy the iv first
memcpy(buf, [iv bytes], self.blockLength);
void *ciphertextBuf = ((char *)buf) + self.blockLength;
size_t ciphertextBufLen = outputBufLen - self.blockLength;
const void *key = [self.keySpec bytes];
size_t keyLen = [self.keySpec length];
const void *ivBytes = [iv bytes];
const void *dataIn = [plaintext bytes];
size_t dataInLen = [plaintext length];
size_t bytesWritten = 0;
CCCryptorStatus status = CCCrypt(kCCEncrypt, self.algorithm, kCCOptionPKCS7Padding, key, keyLen, ivBytes, dataIn, dataInLen, ciphertextBuf, ciphertextBufLen, &bytesWritten);
if (status) {
[self.logger error:@"ARTCrypto error encrypting. Status is %d", status];
free(ciphertextBuf);
return [ARTStatus state: ARTStateError];
}
ciphertext = [NSData dataWithBytesNoCopy:buf length:(bytesWritten + self.blockLength) freeWhenDone:YES];
if (nil == ciphertext) {
[self.logger error:@"ARTCrypto error encrypting. cipher text is nil"];
free(buf);
return [ARTStatus state:ARTStateError];
}
*output = ciphertext;
return [ARTStatus state:ARTStateOk];
}
- (ARTStatus *)decrypt:(NSData *)ciphertext output:(NSData *__autoreleasing *)output {
// The first *blockLength* bytes are the iv
if ([ciphertext length] < self.blockLength) {
return [ARTStatus state: ARTStateInvalidArgs];;
}
NSData *ivData = [ciphertext subdataWithRange:NSMakeRange(0, self.blockLength)];
NSData *actualCiphertext = [ciphertext subdataWithRange:NSMakeRange(self.blockLength, [ciphertext length] - self.blockLength)];
CCOptions options = 0;
const void *key = [self.keySpec bytes];
size_t keyLength = [self.keySpec length];
const void *iv = [ivData bytes];
const void *dataIn = [actualCiphertext bytes];
size_t dataInLength = [actualCiphertext length];
// The output will never be more than the input + block length
size_t outputLength = dataInLength + self.blockLength;
void *buf = malloc(outputLength);
size_t bytesWritten = 0;
if (!buf) {
[self.logger error:@"ARTCrypto error decrypting."];
return [ARTStatus state:ARTStateError];
}
// Decrypt without padding because CCCrypt does not return an error code
// if the decrypted value is not padded correctly
CCCryptorStatus status = CCCrypt(kCCDecrypt, self.algorithm, options, key, keyLength, iv, dataIn, dataInLength, buf, outputLength, &bytesWritten);
if (status) {
[self.logger error:@"ARTCrypto error decrypting. Status is %d", status];
free(buf);
return [ARTStatus state:ARTStateError];
}
// Check that the decrypted value is padded correctly and determine the unpadded length
const char *cbuf = (char *)buf;
int paddingLength = cbuf[bytesWritten - 1];
if (0 == paddingLength || paddingLength > bytesWritten) { free(buf);
return [ARTStatus state:ARTStateCryptoBadPadding];
}
for (size_t i=(bytesWritten - 1); i>(bytesWritten - paddingLength); --i) {
if (paddingLength != cbuf[i-1]) {
free(buf);
return [ARTStatus state:ARTStateCryptoBadPadding];
}
}
size_t unpaddedLength = bytesWritten - paddingLength;
NSData *plaintext = [NSData dataWithBytesNoCopy:buf length:unpaddedLength freeWhenDone:YES];
if (!plaintext) {
[self.logger error:@"ARTCrypto error decrypting. plain text is nil"];
free(buf);
}
*output = plaintext;
return [ARTStatus state:ARTStateOk];
}
- (NSString *)cipherName {
NSString *algo = nil;
switch (self.algorithm) {
case kCCAlgorithmAES:
algo = @"aes";
break;
case kCCAlgorithmDES:
algo = @"des";
break;
case kCCAlgorithm3DES:
algo = @"3des";
break;
case kCCAlgorithmCAST:
algo = @"cast";
break;
case kCCAlgorithmRC4:
algo = @"rc4";
break;
case kCCAlgorithmRC2:
algo = @"rc2";
break;
default:
NSAssert(NO, @"Invalid algorithm");
return nil;
}
return [NSString stringWithFormat:@"%@-cbc", algo];
}
@end
@implementation ARTCrypto
+ (NSString *)defaultAlgorithm {
return @"AES";
}
+ (int)defaultKeyLength {
return 256;
}
+ (int)defaultBlockLength {
return 128;
}
+ (NSMutableData *)generateSecureRandomData:(size_t)length {
void *buf = malloc(length);
if (!buf) {
return nil;
}
int rc = SecRandomCopyBytes(kSecRandomDefault, length, buf);
if (rc != 0) {
free(buf);
return nil;
}
NSMutableData *outputData = [NSMutableData dataWithBytesNoCopy:buf length:length freeWhenDone:YES];
if (!outputData) {
free(buf);
}
return outputData;
}
+ (NSData *)generateHashSHA256:(NSData *)data {
u_int8_t digest[CC_SHA256_DIGEST_LENGTH * sizeof(u_int8_t)];
memset(digest, 0x0, CC_SHA256_DIGEST_LENGTH);
CC_SHA256([data bytes], (CC_LONG)[data length], digest);
NSData *hash = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
return hash;
}
+ (ARTCipherParams *)getDefaultParams:(NSDictionary *)values {
NSString *algorithm = values[@"algorithm"];
if (algorithm == nil) {
algorithm = [ARTCrypto defaultAlgorithm];
}
NSString *key = values[@"key"];
if (key == nil) {
[ARTException raise:NSInvalidArgumentException format:@"missing key parameter"];
}
return [[ARTCipherParams alloc] initWithAlgorithm:algorithm key:key];
}
+ (NSData *)generateRandomKey {
return [self generateRandomKey:[self defaultKeyLength]];
}
+ (NSData *)generateRandomKey:(NSUInteger)length {
return [self generateSecureRandomData:length / 8];
}
+ (id<ARTChannelCipher>)cipherWithParams:(ARTCipherParams *)params {
return [ARTCbcCipher cbcCipherWithParams:params];
}
@end