123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- //
- // 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 "RNDecryptor.h"
- #import "RNCryptor+Private.h"
- #import "RNCryptorEngine.h"
- #import <CommonCrypto/CommonHMAC.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.
- const uint8_t *myBytes = [self bytes];
- const NSUInteger myLength = [self length];
- const uint8_t *otherBytes = [otherData bytes];
- const NSUInteger otherLength = [otherData length];
- uint8_t result = otherLength != myLength; // Start with 0 (equal) only if our lengths are equal
- 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
|