Index: src/OFCryptographicHash.h ================================================================== --- src/OFCryptographicHash.h +++ src/OFCryptographicHash.h @@ -98,10 +98,15 @@ * @param buffer The buffer which should be included into the calculation * @param length The length of the buffer */ - (void)updateWithBuffer: (const void *)buffer length: (size_t)length; +/** + * @brief Performs the final calculation of the cryptographic hash. + */ +- (void)calculate; + /** * @brief Resets all state so that a new hash can be calculated. * * @warning This invalidates any pointer previously returned by @ref digest. If * you are still interested in the previous digest, you need to memcpy Index: src/OFData+CryptographicHashing.m ================================================================== --- src/OFData+CryptographicHashing.m +++ src/OFData+CryptographicHashing.m @@ -39,10 +39,11 @@ const unsigned char *digest; char cString[digestSize * 2]; [hash updateWithBuffer: self->_items length: self->_count * self->_itemSize]; + [hash calculate]; digest = hash.digest; for (size_t i = 0; i < digestSize; i++) { uint8_t high, low; Index: src/OFHMAC.h ================================================================== --- src/OFHMAC.h +++ src/OFHMAC.h @@ -101,10 +101,15 @@ * @param buffer The buffer which should be included into the calculation * @param length The length of the buffer */ - (void)updateWithBuffer: (const void *)buffer length: (size_t)length; +/** + * @brief Performs the final calculation of the HMAC. + */ +- (void)calculate; + /** * @brief Resets the HMAC so that it can be calculated for a new message. * * @note This does not reset the key so that a new HMAC with the same key can * be calculated efficiently. If you want to reset both, use Index: src/OFHMAC.m ================================================================== --- src/OFHMAC.m +++ src/OFHMAC.m @@ -17,10 +17,11 @@ #import "OFHMAC.h" #import "OFSecureData.h" #import "OFHashAlreadyCalculatedException.h" +#import "OFHashNotCalculatedException.h" #import "OFInvalidArgumentException.h" @implementation OFHMAC @synthesize hashClass = _hashClass; @synthesize allowsSwappableMemory = _allowsSwappableMemory; @@ -82,10 +83,11 @@ if (length > blockSize) { id hash = [_hashClass hashWithAllowsSwappableMemory: _allowsSwappableMemory]; [hash updateWithBuffer: key length: length]; + [hash calculate]; length = hash.digestSize; if OF_UNLIKELY (length > blockSize) length = blockSize; @@ -138,21 +140,30 @@ exceptionWithObject: self]; [_innerHash updateWithBuffer: buffer length: length]; } -- (const unsigned char *)digest +- (void)calculate { + if (_calculated) + @throw [OFHashAlreadyCalculatedException + exceptionWithObject: self]; + if (_outerHash == nil || _innerHash == nil) @throw [OFInvalidArgumentException exception]; - if (_calculated) - return _outerHash.digest; - + [_innerHash calculate]; [_outerHash updateWithBuffer: _innerHash.digest length: _innerHash.digestSize]; + [_outerHash calculate]; _calculated = true; +} + +- (const unsigned char *)digest +{ + if (!_calculated) + @throw [OFHashNotCalculatedException exceptionWithObject: self]; return _outerHash.digest; } - (size_t)digestSize Index: src/OFMD5Hash.m ================================================================== --- src/OFMD5Hash.m +++ src/OFMD5Hash.m @@ -19,10 +19,11 @@ #import "OFMD5Hash.h" #import "OFSecureData.h" #import "OFHashAlreadyCalculatedException.h" +#import "OFHashNotCalculatedException.h" #import "OFOutOfRangeException.h" static const size_t digestSize = 16; static const size_t blockSize = 64; @@ -242,12 +243,21 @@ } } - (const unsigned char *)digest { + if (!_calculated) + @throw [OFHashNotCalculatedException exceptionWithObject: self]; + + return (const unsigned char *)_iVars->state; +} + +- (void)calculate +{ if (_calculated) - return (const unsigned char *)_iVars->state; + @throw [OFHashAlreadyCalculatedException + exceptionWithObject: self]; _iVars->buffer.bytes[_iVars->bufferLength] = 0x80; OFZeroMemory(_iVars->buffer.bytes + _iVars->bufferLength + 1, 64 - _iVars->bufferLength - 1); @@ -263,12 +273,10 @@ processBlock(_iVars->state, _iVars->buffer.words); OFZeroMemory(&_iVars->buffer, sizeof(_iVars->buffer)); byteSwapVectorIfBE(_iVars->state, 4); _calculated = true; - - return (const unsigned char *)_iVars->state; } - (void)reset { [self of_resetState]; Index: src/OFPBKDF2.m ================================================================== --- src/OFPBKDF2.m +++ src/OFPBKDF2.m @@ -71,17 +71,19 @@ memcpy(extendedSaltItems + param.saltLength, &i, 4); [param.HMAC reset]; [param.HMAC updateWithBuffer: extendedSaltItems length: param.saltLength + 4]; + [param.HMAC calculate]; memcpy(bufferItems, param.HMAC.digest, digestSize); memcpy(digestItems, param.HMAC.digest, digestSize); for (size_t j = 1; j < param.iterations; j++) { [param.HMAC reset]; [param.HMAC updateWithBuffer: digestItems length: digestSize]; + [param.HMAC calculate]; memcpy(digestItems, param.HMAC.digest, digestSize); for (size_t k = 0; k < digestSize; k++) bufferItems[k] ^= digestItems[k]; Index: src/OFRIPEMD160Hash.m ================================================================== --- src/OFRIPEMD160Hash.m +++ src/OFRIPEMD160Hash.m @@ -19,10 +19,11 @@ #import "OFRIPEMD160Hash.h" #import "OFSecureData.h" #import "OFHashAlreadyCalculatedException.h" +#import "OFHashNotCalculatedException.h" #import "OFOutOfRangeException.h" static const size_t digestSize = 20; static const size_t blockSize = 64; @@ -256,12 +257,21 @@ } } - (const unsigned char *)digest { + if (!_calculated) + @throw [OFHashNotCalculatedException exceptionWithObject: self]; + + return (const unsigned char *)_iVars->state; +} + +- (void)calculate +{ if (_calculated) - return (const unsigned char *)_iVars->state; + @throw [OFHashAlreadyCalculatedException + exceptionWithObject: self]; _iVars->buffer.bytes[_iVars->bufferLength] = 0x80; OFZeroMemory(_iVars->buffer.bytes + _iVars->bufferLength + 1, 64 - _iVars->bufferLength - 1); @@ -277,12 +287,10 @@ processBlock(_iVars->state, _iVars->buffer.words); OFZeroMemory(&_iVars->buffer, sizeof(_iVars->buffer)); byteSwapVectorIfBE(_iVars->state, 5); _calculated = true; - - return (const unsigned char *)_iVars->state; } - (void)reset { [self of_resetState]; Index: src/OFSHA1Hash.m ================================================================== --- src/OFSHA1Hash.m +++ src/OFSHA1Hash.m @@ -19,10 +19,11 @@ #import "OFSHA1Hash.h" #import "OFSecureData.h" #import "OFHashAlreadyCalculatedException.h" +#import "OFHashNotCalculatedException.h" #import "OFOutOfRangeException.h" static const size_t digestSize = 20; static const size_t blockSize = 64; @@ -216,12 +217,21 @@ } } - (const unsigned char *)digest { + if (!_calculated) + @throw [OFHashNotCalculatedException exceptionWithObject: self]; + + return (const unsigned char *)_iVars->state; +} + +- (void)calculate +{ if (_calculated) - return (const unsigned char *)_iVars->state; + @throw [OFHashAlreadyCalculatedException + exceptionWithObject: self]; _iVars->buffer.bytes[_iVars->bufferLength] = 0x80; OFZeroMemory(_iVars->buffer.bytes + _iVars->bufferLength + 1, 64 - _iVars->bufferLength - 1); @@ -237,12 +247,10 @@ processBlock(_iVars->state, _iVars->buffer.words); OFZeroMemory(&_iVars->buffer, sizeof(_iVars->buffer)); byteSwapVectorIfLE(_iVars->state, 5); _calculated = true; - - return (const unsigned char *)_iVars->state; } - (void)reset { [self of_resetState]; Index: src/OFSHA224Or256Hash.m ================================================================== --- src/OFSHA224Or256Hash.m +++ src/OFSHA224Or256Hash.m @@ -20,10 +20,11 @@ #import "OFSHA224Or256Hash.h" #import "OFSecureData.h" #import "OFHashAlreadyCalculatedException.h" +#import "OFHashNotCalculatedException.h" #import "OFOutOfRangeException.h" static const size_t blockSize = 64; @interface OFSHA224Or256Hash () @@ -232,12 +233,21 @@ } } - (const unsigned char *)digest { + if (!_calculated) + @throw [OFHashNotCalculatedException exceptionWithObject: self]; + + return (const unsigned char *)_iVars->state; +} + +- (void)calculate +{ if (_calculated) - return (const unsigned char *)_iVars->state; + @throw [OFHashAlreadyCalculatedException + exceptionWithObject: self]; _iVars->buffer.bytes[_iVars->bufferLength] = 0x80; OFZeroMemory(_iVars->buffer.bytes + _iVars->bufferLength + 1, 64 - _iVars->bufferLength - 1); @@ -253,12 +263,10 @@ processBlock(_iVars->state, _iVars->buffer.words); OFZeroMemory(&_iVars->buffer, sizeof(_iVars->buffer)); byteSwapVectorIfLE(_iVars->state, 8); _calculated = true; - - return (const unsigned char *)_iVars->state; } - (void)reset { [self of_resetState]; Index: src/OFSHA384Or512Hash.m ================================================================== --- src/OFSHA384Or512Hash.m +++ src/OFSHA384Or512Hash.m @@ -20,10 +20,11 @@ #import "OFSHA384Or512Hash.h" #import "OFSecureData.h" #import "OFHashAlreadyCalculatedException.h" +#import "OFHashNotCalculatedException.h" #import "OFOutOfRangeException.h" static const size_t blockSize = 128; @interface OFSHA384Or512Hash () @@ -245,12 +246,21 @@ } } - (const unsigned char *)digest { + if (!_calculated) + @throw [OFHashNotCalculatedException exceptionWithObject: self]; + + return (const unsigned char *)_iVars->state; +} + +- (void)calculate +{ if (_calculated) - return (const unsigned char *)_iVars->state; + @throw [OFHashAlreadyCalculatedException + exceptionWithObject: self]; _iVars->buffer.bytes[_iVars->bufferLength] = 0x80; OFZeroMemory(_iVars->buffer.bytes + _iVars->bufferLength + 1, 128 - _iVars->bufferLength - 1); @@ -264,12 +274,10 @@ processBlock(_iVars->state, _iVars->buffer.words); OFZeroMemory(&_iVars->buffer, sizeof(_iVars->buffer)); byteSwapVectorIfLE(_iVars->state, 8); _calculated = true; - - return (const unsigned char *)_iVars->state; } - (void)reset { [self of_resetState]; Index: src/OFString+CryptographicHashing.m ================================================================== --- src/OFString+CryptographicHashing.m +++ src/OFString+CryptographicHashing.m @@ -37,10 +37,11 @@ size_t digestSize = [class digestSize]; const unsigned char *digest; char cString[digestSize * 2]; [hash updateWithBuffer: self.UTF8String length: self.UTF8StringLength]; + [hash calculate]; digest = hash.digest; for (size_t i = 0; i < digestSize; i++) { uint8_t high, low; Index: src/exceptions/Makefile ================================================================== --- src/exceptions/Makefile +++ src/exceptions/Makefile @@ -11,10 +11,11 @@ OFCreateSymbolicLinkFailedException.m \ OFEnumerationMutationException.m \ OFException.m \ OFGetOptionFailedException.m \ OFHashAlreadyCalculatedException.m \ + OFHashNotCalculatedException.m \ OFInitializationFailedException.m \ OFInvalidArgumentException.m \ OFInvalidEncodingException.m \ OFInvalidFormatException.m \ OFInvalidJSONException.m \ ADDED src/exceptions/OFHashNotCalculatedException.h Index: src/exceptions/OFHashNotCalculatedException.h ================================================================== --- src/exceptions/OFHashNotCalculatedException.h +++ src/exceptions/OFHashNotCalculatedException.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2008-2022 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFException.h" + +OF_ASSUME_NONNULL_BEGIN + +/** + * @class OFHashNotCalculatedException OFHashNotCalculatedException.h \ + * ObjFW/OFHashNotCalculatedException.h + * + * @brief An exception indicating that the hash has not been calculated yet. + */ +@interface OFHashNotCalculatedException: OFException +{ + id _object; +} + +/** + * @brief The hash which has not been calculated yet. + */ +@property (readonly, nonatomic) id object; + ++ (instancetype)exception OF_UNAVAILABLE; + +/** + * @brief Creates a new, autoreleased hash not calculated exception. + * + * @param object The hash which has not been calculated yet + * @return A new, autoreleased hash not calculated exception + */ ++ (instancetype)exceptionWithObject: (id)object; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated hash not calculated exception. + * + * @param object The hash which has not been calculated yet + * @return An initialized hash not calculated exception + */ +- (instancetype)initWithObject: (id)object OF_DESIGNATED_INITIALIZER; +@end + +OF_ASSUME_NONNULL_END ADDED src/exceptions/OFHashNotCalculatedException.m Index: src/exceptions/OFHashNotCalculatedException.m ================================================================== --- src/exceptions/OFHashNotCalculatedException.m +++ src/exceptions/OFHashNotCalculatedException.m @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2008-2022 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFHashNotCalculatedException.h" +#import "OFString.h" + +@implementation OFHashNotCalculatedException +@synthesize object = _object; + ++ (instancetype)exception +{ + OF_UNRECOGNIZED_SELECTOR +} + ++ (instancetype)exceptionWithObject: (id)object +{ + return [[[self alloc] initWithObject: object] autorelease]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)initWithObject: (id)object +{ + self = [super init]; + + _object = [object retain]; + + return self; +} + +- (void)dealloc +{ + [_object release]; + + [super dealloc]; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: + @"The hash of type %@ has not been calculated yet!", + [_object class]]; +} +@end Index: tests/OFHMACTests.m ================================================================== --- tests/OFHMACTests.m +++ tests/OFHMACTests.m @@ -101,10 +101,17 @@ [HMACSHA384 updateWithBuffer: buffer length: length]; [HMACSHA512 updateWithBuffer: buffer length: length]; } [file close]; + TEST(@"-[calculate] with MD5", R([HMACMD5 calculate])) + TEST(@"-[calculate] with SHA-1", R([HMACSHA1 calculate])) + TEST(@"-[calculate] with RIPEMD-160", R([HMACRMD160 calculate])) + TEST(@"-[calculate] with SHA-256", R([HMACSHA256 calculate])) + TEST(@"-[calculate] with SHA-384", R([HMACSHA384 calculate])) + TEST(@"-[calculate] with SHA-512", R([HMACSHA512 calculate])) + TEST(@"-[digest] with MD5", memcmp(HMACMD5.digest, MD5Digest, HMACMD5.digestSize) == 0) TEST(@"-[digest] with SHA-1", memcmp(HMACSHA1.digest, SHA1Digest, HMACSHA1.digestSize) == 0) TEST(@"-[digest] with RIPEMD-160", Index: tests/OFMD5HashTests.m ================================================================== --- tests/OFMD5HashTests.m +++ tests/OFMD5HashTests.m @@ -40,10 +40,12 @@ [MD5 updateWithBuffer: buffer length: length]; } [file close]; TEST(@"-[copy]", (MD5Copy = [[MD5 copy] autorelease])) + + TEST(@"-[calculate]", R([MD5 calculate]) && R([MD5Copy calculate])) TEST(@"-[digest]", memcmp(MD5.digest, testFileMD5, 16) == 0 && memcmp(MD5Copy.digest, testFileMD5, 16) == 0) Index: tests/OFRIPEMD160HashTests.m ================================================================== --- tests/OFRIPEMD160HashTests.m +++ tests/OFRIPEMD160HashTests.m @@ -42,10 +42,13 @@ } [file close]; TEST(@"-[copy]", (RIPEMD160Copy = [[RIPEMD160 copy] autorelease])) + TEST(@"-[calculate]", + R([RIPEMD160 calculate]) && R([RIPEMD160Copy calculate])) + TEST(@"-[digest]", memcmp(RIPEMD160.digest, testFileRIPEMD160, 20) == 0 && memcmp(RIPEMD160Copy.digest, testFileRIPEMD160, 20) == 0) EXPECT_EXCEPTION(@"Detect invalid call of " Index: tests/OFSHA1HashTests.m ================================================================== --- tests/OFSHA1HashTests.m +++ tests/OFSHA1HashTests.m @@ -41,10 +41,12 @@ [SHA1 updateWithBuffer: buffer length: length]; } [file close]; TEST(@"-[copy]", (SHA1Copy = [[SHA1 copy] autorelease])) + + TEST(@"-[calculate]", R([SHA1 calculate]) && R([SHA1Copy calculate])) TEST(@"-[digest]", memcmp(SHA1.digest, testFileSHA1, 20) == 0 && memcmp(SHA1Copy.digest, testFileSHA1, 20) == 0) Index: tests/OFSHA224HashTests.m ================================================================== --- tests/OFSHA224HashTests.m +++ tests/OFSHA224HashTests.m @@ -42,10 +42,13 @@ } [file close]; TEST(@"-[copy]", (SHA224Copy = [[SHA224 copy] autorelease])) + TEST(@"-[calculate]", + R([SHA224 calculate]) && R([SHA224Copy calculate])) + TEST(@"-[digest]", memcmp(SHA224.digest, testFileSHA224, 28) == 0 && memcmp(SHA224Copy.digest, testFileSHA224, 28) == 0) EXPECT_EXCEPTION(@"Detect invalid call of " Index: tests/OFSHA256HashTests.m ================================================================== --- tests/OFSHA256HashTests.m +++ tests/OFSHA256HashTests.m @@ -42,10 +42,13 @@ } [file close]; TEST(@"-[copy]", (SHA256Copy = [[SHA256 copy] autorelease])) + TEST(@"-[calculate]", + R([SHA256 calculate]) && R([SHA256Copy calculate])) + TEST(@"-[digest]", memcmp(SHA256.digest, testFileSHA256, 32) == 0 && memcmp(SHA256Copy.digest, testFileSHA256, 32) == 0) EXPECT_EXCEPTION(@"Detect invalid call of " Index: tests/OFSHA384HashTests.m ================================================================== --- tests/OFSHA384HashTests.m +++ tests/OFSHA384HashTests.m @@ -43,10 +43,13 @@ } [file close]; TEST(@"-[copy]", (SHA384Copy = [[SHA384 copy] autorelease])) + TEST(@"-[calculate]", + R([SHA384 calculate]) && R([SHA384Copy calculate])) + TEST(@"-[digest]", memcmp(SHA384.digest, testFileSHA384, 48) == 0 && memcmp(SHA384Copy.digest, testFileSHA384, 48) == 0) EXPECT_EXCEPTION(@"Detect invalid call of " Index: tests/OFSHA512HashTests.m ================================================================== --- tests/OFSHA512HashTests.m +++ tests/OFSHA512HashTests.m @@ -44,10 +44,13 @@ } [file close]; TEST(@"-[copy]", (SHA512Copy = [[SHA512 copy] autorelease])) + TEST(@"-[calculate]", + R([SHA512 calculate]) && R([SHA512Copy calculate])) + TEST(@"-[digest]", memcmp(SHA512.digest, testFileSHA512, 64) == 0 && memcmp(SHA512Copy.digest, testFileSHA512, 64) == 0) EXPECT_EXCEPTION(@"Detect invalid call of " Index: utils/ofhash/OFHash.m ================================================================== --- utils/ofhash/OFHash.m +++ utils/ofhash/OFHash.m @@ -51,12 +51,15 @@ } static void printHash(OFString *algo, OFString *path, id hash) { - const unsigned char *digest = hash.digest; size_t digestSize = hash.digestSize; + const unsigned char *digest; + + [hash calculate]; + digest = hash.digest; [OFStdOut writeFormat: @"%@ ", algo]; for (size_t i = 0; i < digestSize; i++) [OFStdOut writeFormat: @"%02x", digest[i]];