//
//  RNDecryptor
//
//  Copyright (c) 2012 Rob Napier
//
//  This code is licensed under the MIT License:
//
//  Permission is hereby granted, free of charge, to any person obtaining a 
//  copy of this software and associated documentation files (the "Software"),
//  to deal in the Software without restriction, including without limitation
//  the rights to use, copy, modify, merge, publish, distribute, sublicense,
//  and/or sell copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following conditions:
//  
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//


#import "RNCryptor+Private.h"
#import "RNDecryptor.h"
#import "RNCryptorEngine.h"

static const NSUInteger kPreambleSize = 2;

@interface NSData (RNCryptor_ConsistentCompare)

/** Compare two NSData in time proportional to the compared data (otherData)
 *
 * isEqual:-based comparisons stop comparing at the first difference. This can be used by attackers, in some situations,
 * to determine a secret value by considering the time required to compare the values.
 *
 * It is slightly better to call this as [secret rnc_isEqualInConsistentTime:attackersData] rather than the reverse,
 * but it is not a major issue either way. In the first case, the time required is proportional to the attacker's data,
 * which provides the attacker no information about the length of secret. In the second case, the time is proportional
 * to the length of secret, which leaks a small amount of informaiont, but in a way that does not varry in proportion to
 * the attacker's data.
 *
 * @param otherData data to compare
 * @returns YES if values are equal
 */
- (BOOL)rnc_isEqualInConsistentTime:(NSData *)otherData;

@end

@implementation NSData (RNCryptor_ConstantCompare)

- (BOOL)rnc_isEqualInConsistentTime:(NSData *)otherData {
  // The point of this routine is XOR the bytes of each data and accumulate the results with OR.
  // If any bytes are different, then the OR will accumulate some non-0 value.

  uint8_t result = otherData.length - self.length;  // Start with 0 (equal) only if our lengths are equal

  const uint8_t *myBytes = [self bytes];
  const NSUInteger myLength = [self length];
  const uint8_t *otherBytes = [otherData bytes];
  const NSUInteger otherLength = [otherData length];

  for (NSUInteger i = 0; i < otherLength; ++i) {
    // Use mod to wrap around ourselves if they are longer than we are.
    // Remember, we already broke equality if our lengths are different.
    result |= myBytes[i % myLength] ^ otherBytes[i];
  }

  return result == 0;
}

@end


@interface RNDecryptor ()
@property (nonatomic, readonly, strong) NSMutableData *inData;
@property (nonatomic, readwrite, copy) NSData *encryptionKey;
@property (nonatomic, readwrite, copy) NSData *HMACKey;
@property (nonatomic, readwrite, copy) NSString *password;
@property (nonatomic, readwrite, assign) BOOL hasV1HMAC;

@property (nonatomic, readwrite, assign) RNCryptorSettings settings;

@end

@implementation RNDecryptor
{
  CCHmacContext _HMACContext;
  NSMutableData *__inData;
}
@synthesize encryptionKey = _encryptionKey;
@synthesize HMACKey = _HMACKey;
@synthesize password = _password;
@synthesize settings = _settings;

+ (NSData *)decryptData:(NSData *)theCipherText withSettings:(RNCryptorSettings)settings password:(NSString *)aPassword error:(NSError **)anError
{
  RNDecryptor *cryptor = [[self alloc] initWithPassword:aPassword
                                                handler:^(RNCryptor *c, NSData *d) {}];
  cryptor.settings = settings;
  return [self synchronousResultForCryptor:cryptor data:theCipherText error:anError];
}

+ (NSData *)decryptData:(NSData *)theCipherText withSettings:(RNCryptorSettings)settings encryptionKey:(NSData *)encryptionKey HMACKey:(NSData *)HMACKey error:(NSError **)anError
{
  RNDecryptor *cryptor = [[self alloc] initWithEncryptionKey:encryptionKey
                                                     HMACKey:HMACKey
                                                     handler:^(RNCryptor *c, NSData *d) {}];
  cryptor.settings = settings;
  return [self synchronousResultForCryptor:cryptor data:theCipherText error:anError];
}

+ (NSData *)decryptData:(NSData *)theCipherText withPassword:(NSString *)aPassword error:(NSError **)anError
{
  RNDecryptor *cryptor = [[self alloc] initWithPassword:aPassword
                                                handler:^(RNCryptor *c, NSData *d) {}];
  return [self synchronousResultForCryptor:cryptor data:theCipherText error:anError];
}

+ (NSData *)decryptData:(NSData *)theCipherText withEncryptionKey:(NSData *)encryptionKey HMACKey:(NSData *)HMACKey error:(NSError **)anError;
{
  RNDecryptor *cryptor = [[self alloc] initWithEncryptionKey:encryptionKey
                                                     HMACKey:HMACKey
                                                     handler:^(RNCryptor *c, NSData *d) {}];
  return [self synchronousResultForCryptor:cryptor data:theCipherText error:anError];
}

- (RNDecryptor *)initWithEncryptionKey:(NSData *)anEncryptionKey HMACKey:(NSData *)anHMACKey handler:(RNCryptorHandler)aHandler
{
  self = [super initWithHandler:aHandler];
  if (self) {
    _encryptionKey = [anEncryptionKey copy];
    _HMACKey = [anHMACKey copy];
    _settings = kRNCryptorAES256Settings;
  }

  return self;
}

- (RNDecryptor *)initWithPassword:(NSString *)aPassword handler:(RNCryptorHandler)aHandler
{
  NSParameterAssert(aPassword != nil);

  self = [self initWithEncryptionKey:nil HMACKey:nil handler:aHandler];
  if (self) {
    _password = [aPassword copy];
    _settings = kRNCryptorAES256Settings;
  }
  return self;
}

- (NSMutableData *)inData
{
  if (!__inData) {
    __inData = [NSMutableData data];
  }
  return __inData;
}

- (void)decryptData:(NSData *)data
{
  dispatch_async(self.queue, ^{
    if (self.hasHMAC) {
      CCHmacUpdate(&_HMACContext, data.bytes, data.length);
    }

    NSError *error = nil;
    NSData *decryptedData = [self.engine addData:data error:&error];

    if (!decryptedData) {
      [self cleanupAndNotifyWithError:error];
      return;
    }

    [self.outData appendData:decryptedData];

    dispatch_sync(self.responseQueue, ^{
      self.handler(self, self.outData);
    });
    [self.outData setLength:0];
  });
}

- (void)addData:(NSData *)theData
{
  if (self.isFinished) {
    return;
  }

  [self.inData appendData:theData];
  if (!self.engine) {
    [self consumeHeaderFromData:self.inData];
  }
  if (self.engine) {
    NSUInteger HMACLength = self.HMACLength;
    if (self.inData.length > HMACLength) {
      NSData *data = [self.inData _RNConsumeToIndex:self.inData.length - HMACLength];
      [self decryptData:data];
    }
  }
}

- (BOOL)updateOptionsForPreamble:(NSData *)preamble
{
  const uint8_t *bytes = [preamble bytes];

  // See http://robnapier.net/blog/rncryptor-hmac-vulnerability-827 for information on the v1 bad HMAC
#ifdef RNCRYPTOR_ALLOW_V1_BAD_HMAC
  if (bytes[0] == 1) {
    self.options = bytes[1];
    self.hasV1HMAC = YES;
    return YES;
  }
#endif

  if (bytes[0] == 2) {
    self.options = bytes[1];

    RNCryptorSettings settings = self.settings;
    settings.keySettings.hasV2Password = YES;
    settings.HMACKeySettings.hasV2Password = YES;
    self.settings = settings;
    return YES;
  }

  if (bytes[0] == kRNCryptorFileVersion) {
    self.options = bytes[1];
    return YES;
  }

  return NO;
}

- (void)consumeHeaderFromData:(NSMutableData *)data
{
  if (data.length < kPreambleSize) {
    return;
  }

  if (![self updateOptionsForPreamble:[data subdataWithRange:NSMakeRange(0, kPreambleSize)]]) {
    [self cleanupAndNotifyWithError:[NSError errorWithDomain:kRNCryptorErrorDomain
                                                        code:kRNCryptorUnknownHeader
                                                    userInfo:[NSDictionary dictionaryWithObject:@"Unknown header" /* DNL */
                                                                                         forKey:NSLocalizedDescriptionKey]]];
    return;
  }

  NSUInteger headerSize = kPreambleSize + self.settings.IVSize;
  if (self.options & kRNCryptorOptionHasPassword) {
    headerSize += self.settings.keySettings.saltSize + self.settings.HMACKeySettings.saltSize;
  }

  if (data.length < headerSize) {
    return;
  }

  NSData *header = [data subdataWithRange:NSMakeRange(0, headerSize)];  // We'll need this for the HMAC later

  [[data _RNConsumeToIndex:kPreambleSize] mutableCopy]; // Throw away the preamble

  NSError *error = nil;
  if (self.options & kRNCryptorOptionHasPassword) {
    NSAssert(!self.encryptionKey && !self.HMACKey, @"Both password and the key (%d) or HMACKey (%d) are set.", self.encryptionKey != nil, self.HMACKey != nil);

    NSData *encryptionKeySalt = [data _RNConsumeToIndex:self.settings.keySettings.saltSize];
    NSData *HMACKeySalt = [data _RNConsumeToIndex:self.settings.HMACKeySettings.saltSize];
    self.encryptionKey = [[self class] keyForPassword:self.password salt:encryptionKeySalt settings:self.settings.keySettings];
    self.HMACKey = [[self class] keyForPassword:self.password salt:HMACKeySalt settings:self.settings.HMACKeySettings];

    self.password = nil;  // Don't need this anymore.
  }

  NSData *IV = [data _RNConsumeToIndex:self.settings.IVSize];

  self.engine = [[RNCryptorEngine alloc] initWithOperation:kCCDecrypt settings:self.settings key:self.encryptionKey IV:IV error:&error];
  self.encryptionKey = nil; // Don't need this anymore
  if (!self.engine) {
    [self cleanupAndNotifyWithError:error];
    return;
  }

  if (self.HMACKey) {
    CCHmacInit(&_HMACContext, self.settings.HMACAlgorithm, self.HMACKey.bytes, self.HMACKey.length);
    self.HMACLength = self.settings.HMACLength;
    self.HMACKey = nil; // Don't need this anymore

    if (! self.hasV1HMAC) {
      CCHmacUpdate(&_HMACContext, [header bytes], [header length]);
    }
  }
}

- (void)finish
{
  if (self.isFinished) {
    return;
  }

  dispatch_async(self.queue, ^{
    NSError *error = nil;
    NSData *decryptedData = [self.engine finishWithError:&error];

    if (!decryptedData) {
      [self cleanupAndNotifyWithError:error];
      return;
    }
    [self.outData appendData:decryptedData];

    if (self.hasHMAC) {
      NSMutableData *HMACData = [NSMutableData dataWithLength:self.HMACLength];
      CCHmacFinal(&_HMACContext, [HMACData mutableBytes]);

      if (![HMACData rnc_isEqualInConsistentTime:self.inData]) {
        [self cleanupAndNotifyWithError:[NSError errorWithDomain:kRNCryptorErrorDomain
                                                            code:kRNCryptorHMACMismatch
                                                        userInfo:[NSDictionary dictionaryWithObject:@"HMAC Mismatch" /* DNL */
                                                                                             forKey:NSLocalizedDescriptionKey]]];
        return;
      }
    }

    [self cleanupAndNotifyWithError:nil];
  });
}

@end
