From 5f670c22f476356b2703f3eea795036bc9e0e121 Mon Sep 17 00:00:00 2001 From: A-Ron Date: Sun, 10 Dec 2023 11:01:00 -0600 Subject: [PATCH] Rewrite of hs.hash to simplify adding new hashes (#3483) * Initial commit * oopsies * placate lint, doc tweaks --- Hammerspoon.xcodeproj/project.pbxproj | 20 + extensions/fs/fs.lua | 9 + extensions/fs/libfs.m | 269 +++++++++++++ extensions/hash/algorithms.h | 71 ++++ extensions/hash/algorithms.m | 305 +++++++++++++++ extensions/hash/byte_order.h | 209 ++++++++++ extensions/hash/hash.lua | 305 ++++++++++++++- extensions/hash/libhash.m | 531 ++++++++++++++++---------- extensions/hash/sha3.h | 74 ++++ extensions/hash/sha3.m | 429 +++++++++++++++++++++ 10 files changed, 2008 insertions(+), 214 deletions(-) create mode 100644 extensions/hash/algorithms.h create mode 100644 extensions/hash/algorithms.m create mode 100644 extensions/hash/byte_order.h create mode 100644 extensions/hash/sha3.h create mode 100644 extensions/hash/sha3.m diff --git a/Hammerspoon.xcodeproj/project.pbxproj b/Hammerspoon.xcodeproj/project.pbxproj index 6188bb234..2d4b2f735 100644 --- a/Hammerspoon.xcodeproj/project.pbxproj +++ b/Hammerspoon.xcodeproj/project.pbxproj @@ -599,6 +599,11 @@ D6A318AB1DDD836800D9B328 /* libnetwork_ping.m in Sources */ = {isa = PBXBuildFile; fileRef = D6A3189B1DDD830A00D9B328 /* libnetwork_ping.m */; }; D6A318AC1DDD836800D9B328 /* SimplePing.m in Sources */ = {isa = PBXBuildFile; fileRef = D6A3189D1DDD830A00D9B328 /* SimplePing.m */; }; D6A318AD1DDD837400D9B328 /* SimplePing.h in Headers */ = {isa = PBXBuildFile; fileRef = D6A3189C1DDD830A00D9B328 /* SimplePing.h */; }; + D6A6C1122A05F3500018FC9B /* byte_order.h in Headers */ = {isa = PBXBuildFile; fileRef = D6A6C10D2A05F3500018FC9B /* byte_order.h */; }; + D6A6C1132A05F3500018FC9B /* sha3.m in Sources */ = {isa = PBXBuildFile; fileRef = D6A6C10E2A05F3500018FC9B /* sha3.m */; }; + D6A6C1142A05F3500018FC9B /* sha3.h in Headers */ = {isa = PBXBuildFile; fileRef = D6A6C10F2A05F3500018FC9B /* sha3.h */; }; + D6A6C1152A05F3500018FC9B /* algorithms.m in Sources */ = {isa = PBXBuildFile; fileRef = D6A6C1102A05F3500018FC9B /* algorithms.m */; }; + D6A6C1162A05F3500018FC9B /* algorithms.h in Headers */ = {isa = PBXBuildFile; fileRef = D6A6C1112A05F3500018FC9B /* algorithms.h */; }; D6B9464E1D139C7D00177E0C /* webview.h in Headers */ = {isa = PBXBuildFile; fileRef = D64B03361BA9541E0006C468 /* webview.h */; }; D6B946531D139CB800177E0C /* libwebview_datastore.m in Sources */ = {isa = PBXBuildFile; fileRef = D6B946431D139C4900177E0C /* libwebview_datastore.m */; }; D6B9465A1D139CC500177E0C /* webview.h in Headers */ = {isa = PBXBuildFile; fileRef = D64B03361BA9541E0006C468 /* webview.h */; }; @@ -2101,6 +2106,11 @@ D6A3189C1DDD830A00D9B328 /* SimplePing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SimplePing.h; path = extensions/network/ping/SimplePing.h; sourceTree = ""; }; D6A3189D1DDD830A00D9B328 /* SimplePing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SimplePing.m; path = extensions/network/ping/SimplePing.m; sourceTree = ""; }; D6A318AA1DDD833100D9B328 /* libnetworkping.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libnetworkping.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; + D6A6C10D2A05F3500018FC9B /* byte_order.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = byte_order.h; path = extensions/hash/byte_order.h; sourceTree = ""; }; + D6A6C10E2A05F3500018FC9B /* sha3.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = sha3.m; path = extensions/hash/sha3.m; sourceTree = ""; }; + D6A6C10F2A05F3500018FC9B /* sha3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sha3.h; path = extensions/hash/sha3.h; sourceTree = ""; }; + D6A6C1102A05F3500018FC9B /* algorithms.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = algorithms.m; path = extensions/hash/algorithms.m; sourceTree = ""; }; + D6A6C1112A05F3500018FC9B /* algorithms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = algorithms.h; path = extensions/hash/algorithms.h; sourceTree = ""; }; D6B772981CCB544B00908A59 /* cgilua_compatibility_functions.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = cgilua_compatibility_functions.lua; path = extensions/httpserver/cgilua_compatibility_functions.lua; sourceTree = ""; }; D6B772991CCB544B00908A59 /* httpserver_hsminweb.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = httpserver_hsminweb.lua; path = extensions/httpserver/httpserver_hsminweb.lua; sourceTree = ""; }; D6B946431D139C4900177E0C /* libwebview_datastore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = libwebview_datastore.m; path = extensions/webview/libwebview_datastore.m; sourceTree = ""; }; @@ -3131,6 +3141,11 @@ 4F4CB3F41B73A3D9000EA9B6 /* hash */ = { isa = PBXGroup; children = ( + D6A6C1112A05F3500018FC9B /* algorithms.h */, + D6A6C1102A05F3500018FC9B /* algorithms.m */, + D6A6C10D2A05F3500018FC9B /* byte_order.h */, + D6A6C10F2A05F3500018FC9B /* sha3.h */, + D6A6C10E2A05F3500018FC9B /* sha3.m */, 4F4CB3F51B73A4EC000EA9B6 /* hash.lua */, 4F4CB3F61B73A4EC000EA9B6 /* libhash.m */, ); @@ -4500,6 +4515,9 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + D6A6C1162A05F3500018FC9B /* algorithms.h in Headers */, + D6A6C1122A05F3500018FC9B /* byte_order.h in Headers */, + D6A6C1142A05F3500018FC9B /* sha3.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -7284,6 +7302,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D6A6C1132A05F3500018FC9B /* sha3.m in Sources */, + D6A6C1152A05F3500018FC9B /* algorithms.m in Sources */, 4F4CB3F71B73A4EC000EA9B6 /* libhash.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/extensions/fs/fs.lua b/extensions/fs/fs.lua index 4a336096b..12d26b0df 100644 --- a/extensions/fs/fs.lua +++ b/extensions/fs/fs.lua @@ -150,4 +150,13 @@ module.symlinkAttributes = function(...) end end +--- hs.fs.defaultPathListExcludes -> table +--- Variable +--- A table containing the default list of patterns to ignore when using the [hs.fs.fileListForPath](#fileListForPath). +--- +--- By default this table contains the single entry `{ "^\..*$" }` which is a regular expression matching all files that begin with a period. +module.defaultPathListExcludes = { + "^\\..*$", +} + return module diff --git a/extensions/fs/libfs.m b/extensions/fs/libfs.m index d542e7779..6a99a8d16 100644 --- a/extensions/fs/libfs.m +++ b/extensions/fs/libfs.m @@ -51,6 +51,8 @@ #define STAT_FUNC stat #define LSTAT_FUNC lstat +static const char * const USERDATA_TAG = "hs.fs" ; + /* ** Utility functions */ @@ -1156,6 +1158,272 @@ static int fs_urlFromPath(lua_State *L) { return 1 ; } +/// hs.fs.fileListForPath(path, [options]) -> table, fileCount, dirCount +/// Function +/// Returns a table containing the paths to all of the files located at the specified path. +/// +/// Parameters: +/// * `path` - a string specifying the path to gather the files from. If this path specifies a file, then the return value is a table containing only this path. If the path specifies a directory, then the table contains the paths of all of the files found in the specified directory. +/// * `options` - an optional table with one or more key-value pairs determining how and what files are to be included in the table returned. +/// * The following keys are recognized: +/// * `subdirs` - a boolean, default false, indicating whether or not subdirectories should be descended into and examined for files as well. +/// * `followSymlinks` - a boolean, default false, indicating whether or not symbolic links should be followed +/// * `expandSymlinks` - a boolean, default false, specifying whether or not the real path of any files discovered after following a symbolic link should be included in the list (true) or whether the path added to the list should remain relative to the starting path (false). +/// * `relativePath` - a boolean, default false, specifying whether paths included in the result list should be relative to the starting path (true) or the full and complete path to the file (false). +/// * `ignore` - a table of strings, specifying regular expression matches for files to exclude from the result list. If not provided, this value will be inherited from the module's variable [hs.fs.defaultPathListExcludes](#defaultPathListExcludes) which, by defualt, is set to ignore all files beginning with a period (often called dot-files). To include all files, set this option equal to the empty table (i.e. `{}`). +/// * `except` - a table of strings, default empty, specifying regular expression matches for files that match an `ignore` rule, but should be included anyways. For example, if this option is set to `{ "^\\.gitignore$" }`, then a file named `.gitignore` would be included, even though it would normally be excluded by the default `ignore` ruleset. +/// +/// Returns: +/// * a table containing the paths to the files discovered at the specified path. the number of files found, and the number of directories examined. Only files will be included in the results table-- directory names are not included in the resulting list. The table will be sorted as per the Objective-C NSString's `compare:` method. +/// +/// Notes: +/// * `ignore` and `except` options require the use of actual regular expressions, not the simplified pattern matching used by Lua. More details about the proper syntax for the strings to use in the tables of these options can be found at https://unicode-org.github.io/icu/userguide/strings/regexp.html. +/// * note that this function only checks to see if the regular expression returns a match for each filename found (not the path, just the filename component of the path). Any captures are ignored. +static int fs_filesInPath(lua_State *L) { + LuaSkin *skin = [LuaSkin sharedWithState:L] ; + [skin checkArgs:LS_TSTRING, + LS_TTABLE | LS_TOPTIONAL, + LS_TBREAK] ; + + NSString *path = [skin toNSObjectAtIndex:1] ; + + BOOL subdirs = NO ; + BOOL followSymlinks = NO ; + BOOL expandSymlinks = NO ; + BOOL relativePath = NO ; +// BOOL objectWrapper = NO ; + NSArray *ignore = nil ; + NSArray *except = nil ; + + if (lua_type(L, 2) == LUA_TTABLE) { + lua_pushnil(L) ; + while (lua_next(L, 2) != 0) { + if (lua_type(L, -2) == LUA_TSTRING) { + const char *keyName = lua_tostring(L, -2) ; + if (!strcmp(keyName, "subdirs")) { + if (lua_type(L, -1) == LUA_TBOOLEAN) { + subdirs = (BOOL)(lua_toboolean(L, -1)) ; + } else { + return luaL_argerror(L, 2, "subdirs option expects boolean value") ; + } + } else if (!strcmp(keyName, "followSymlinks")) { + if (lua_type(L, -1) == LUA_TBOOLEAN) { + followSymlinks = (BOOL)(lua_toboolean(L, -1)) ; + } else { + return luaL_argerror(L, 2, "followSymlinks option expects boolean value") ; + } + } else if (!strcmp(keyName, "expandSymlinks")) { + if (lua_type(L, -1) == LUA_TBOOLEAN) { + expandSymlinks = (BOOL)(lua_toboolean(L, -1)) ; + } else { + return luaL_argerror(L, 2, "expandSymlinks option expects boolean value") ; + } + } else if (!strcmp(keyName, "relativePath")) { + if (lua_type(L, -1) == LUA_TBOOLEAN) { + relativePath = (BOOL)(lua_toboolean(L, -1)) ; + } else { + return luaL_argerror(L, 2, "relativePath option expects boolean value") ; + } +// The speedup hoped for by this wasn't as impressive as desired; leaving the code in, though, in case we +// decide we need it later anyways. Also see the return section at the bottom +// } else if (!strcmp(keyName, "objectWrapper")) { +// if (lua_type(L, -1) == LUA_TBOOLEAN) { +// objectWrapper = (BOOL)(lua_toboolean(L, -1)) ; +// } else { +// return luaL_argerror(L, 2, "objectWrapper option expects boolean value") ; +// } + } else if (!strcmp(keyName, "ignore")) { + ignore = [skin toNSObjectAtIndex:-1] ; + if ([ignore isKindOfClass:[NSArray class]]) { + for (NSString *entry in ignore) { + if ([entry isKindOfClass:[NSString class]]) continue ; + return luaL_argerror(L, 2, "ignore option table entries must be strings") ; + } + } else { + return luaL_argerror(L, 2, "ignore option expects table value") ; + } + } else if (!strcmp(keyName, "except")) { + except = [skin toNSObjectAtIndex:-1] ; + if ([except isKindOfClass:[NSArray class]]) { + for (NSString *entry in except) { + if ([entry isKindOfClass:[NSString class]]) continue ; + return luaL_argerror(L, 2, "except option table entries must be strings") ; + } + } else { + return luaL_argerror(L, 2, "except option expects table value") ; + } + } else { + return luaL_argerror(L, 2, [[NSString stringWithFormat:@"option %s not recognized", keyName] UTF8String]) ; + } + } else { + return luaL_argerror(L, 2, "option table keys must be strings") ; + } + lua_pop(L, 1); + } + } + + if (!except) except = [NSArray array] ; + + if (!ignore) { + [skin requireModule:USERDATA_TAG] ; // put our module on top of the stack + lua_getfield(L, -1, "defaultPathListExcludes") ; + ignore = [skin toNSObjectAtIndex:-1] ; + lua_pop(L, 2) ; + } + + NSMutableArray *excluders = [NSMutableArray arrayWithCapacity:ignore.count] ; + NSMutableArray *exceptions = [NSMutableArray arrayWithCapacity:except.count] ; + for (NSUInteger i = 0 ; i < ignore.count ; i++) { + NSError *error = nil ; + NSRegularExpression *p = [NSRegularExpression regularExpressionWithPattern:ignore[i] + options:NSRegularExpressionUseUnicodeWordBoundaries + error:&error] ; + if (!error) { + [excluders addObject:p] ; + } else { + return luaL_argerror(L, 2, [[NSString stringWithFormat:@"invalid regex (%@) at index %lu of ignore option", error.localizedDescription, i + 1] UTF8String]) ; + } + } + for (NSUInteger i = 0 ; i < except.count ; i++) { + NSError *error = nil ; + NSRegularExpression *p = [NSRegularExpression regularExpressionWithPattern:except[i] + options:NSRegularExpressionUseUnicodeWordBoundaries + error:&error] ; + if (!error) { + [exceptions addObject:p] ; + } else { + return luaL_argerror(L, 2, [[NSString stringWithFormat:@"invalid regex (%@) at index %lu of except option", error.localizedDescription, i + 1] UTF8String]) ; + } + } + + path = path.stringByExpandingTildeInPath.stringByResolvingSymlinksInPath ; + lua_Integer dirCount = 0 ; + + NSFileManager *fileManager = [NSFileManager defaultManager] ; + BOOL isDirectory ; + BOOL fileExists = [fileManager fileExistsAtPath:path isDirectory:&isDirectory] ; + + // take care of the easy cases: + if (!fileExists) { + return luaL_argerror(L, 1, "path does not specify a reachable file or directory") ; + } else if (!isDirectory) { + [skin pushNSObject:@[ path ]] ; + lua_pushinteger(L, 1) ; + lua_pushinteger(L, 0) ; + return 3 ; + } + + NSURL *startingURL = [NSURL fileURLWithPath:path isDirectory:YES] ; +// stdlib realpath() instead? + NSString *startingPath = nil ; + [startingURL getResourceValue:&startingPath forKey:NSURLPathKey error:nil] ; + + NSMutableArray *foundPaths = [NSMutableArray array] ; + NSMutableArray *seenDirectories = [NSMutableArray array] ; // to prevent loops when links is true + NSMutableArray *directories = [NSMutableArray arrayWithObject:@[ startingPath, startingPath ]] ; + + while(directories.count > 0) { + NSArray *currentPathArray = directories[0] ; + [directories removeObjectAtIndex:0] ; + dirCount++ ; + + NSString *thisDir = currentPathArray[0] ; + NSString *symbolicDir = currentPathArray[1] ; + + [seenDirectories addObject:thisDir] ; + + NSURL *thisDirURL = [NSURL fileURLWithPath:thisDir isDirectory:YES] ; + NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtURL:thisDirURL + includingPropertiesForKeys:@[ + NSURLIsRegularFileKey, + NSURLIsSymbolicLinkKey, + NSURLIsDirectoryKey, + NSURLPathKey + ] + options: NSDirectoryEnumerationSkipsSubdirectoryDescendants + errorHandler:nil] ; + for (__strong NSURL *fileURL in dirEnum) { + NSString *filePath = nil ; + [fileURL getResourceValue:&filePath forKey:NSURLPathKey error:nil] ; + + NSNumber *isSymbolicLink = nil ; + [fileURL getResourceValue:&isSymbolicLink forKey:NSURLIsSymbolicLinkKey error:nil] ; + + NSString *originalFilePath = [filePath copy] ; + NSString *fileName = originalFilePath.lastPathComponent ; + + if (isSymbolicLink.boolValue) { + if (followSymlinks) { + NSString *newPath = filePath.stringByResolvingSymlinksInPath ; + if ([fileManager fileExistsAtPath:newPath]) { + fileURL = [NSURL fileURLWithPath:newPath] ; + [fileURL getResourceValue:&filePath forKey:NSURLPathKey error:nil] ; + } else { + [LuaSkin logWarn:[NSString stringWithFormat:@"%s.pathList - error resolving symbolic link %@", USERDATA_TAG, newPath]] ; + continue ; + } + } else { + continue ; + } + } + + BOOL keepGoing = YES ; + + for (NSRegularExpression *test in excluders) { + NSUInteger matches = [test numberOfMatchesInString:fileName options:0 range:NSMakeRange(0, fileName.length)] ; + if (matches > 0) { + keepGoing = NO ; + break ; + } + } + + for (NSRegularExpression *test in exceptions) { + NSUInteger matches = [test numberOfMatchesInString:fileName options:0 range:NSMakeRange(0, fileName.length)] ; + if (matches > 0) { + keepGoing = YES ; + break ; + } + } + + if (!keepGoing) continue ; + + NSNumber *isRegularFile = nil ; + [fileURL getResourceValue:&isRegularFile forKey:NSURLIsRegularFileKey error:nil] ; + if (isRegularFile.boolValue) { + if (!expandSymlinks) { + filePath = [originalFilePath stringByReplacingOccurrencesOfString:thisDir + withString:symbolicDir + options:(NSAnchoredSearch | NSLiteralSearch) + range:NSMakeRange(0, originalFilePath.length)] ; + } + if (relativePath && [filePath hasPrefix:startingPath]) { + [foundPaths addObject:[filePath substringFromIndex:startingPath.length + 1]] ; // include / before rest of path + } else { + [foundPaths addObject:filePath] ; + } + } else if (subdirs) { + NSNumber *isThisDir = nil ; + [fileURL getResourceValue:&isThisDir forKey:NSURLIsDirectoryKey error:nil] ; + if (isThisDir.boolValue && ![seenDirectories containsObject:filePath]) { + [directories addObject:@[ filePath, [NSString stringWithFormat:@"%@/%@", symbolicDir, fileName] ]] ; + } + } + } + } + + // ensure consistent order + [foundPaths sortUsingSelector:@selector(compare:)] ; + +// if (objectWrapper) { +// [skin pushNSObject:foundPaths withOptions:LS_WithObjectWrapper | LS_OW_ReadWrite] ; +// } else { + [skin pushNSObject:foundPaths] ; +// } + lua_pushinteger(L, (lua_Integer)foundPaths.count) ; + lua_pushinteger(L, dirCount) ; + return 3; +} + static const struct luaL_Reg fslib[] = { {"attributes", file_info}, {"chdir", change_dir}, @@ -1183,6 +1451,7 @@ static int fs_urlFromPath(lua_State *L) { {"pathToBookmark", fs_pathToBookmark}, {"pathFromBookmark", fs_pathFromBookmark}, {"urlFromPath", fs_urlFromPath}, + {"fileListForPath", fs_filesInPath}, {NULL, NULL}, }; diff --git a/extensions/hash/algorithms.h b/extensions/hash/algorithms.h new file mode 100644 index 000000000..f517e4e14 --- /dev/null +++ b/extensions/hash/algorithms.h @@ -0,0 +1,71 @@ +#pragma once + +@import Cocoa ; + +typedef void *(*hashInit_t)(NSData *); +typedef void (*hashAppend_t)(void *, NSData *); +typedef NSData *(*hashFinish_t)(void *); + +typedef struct hashEntry_s { + const char *hashName ; + hashInit_t initFn ; + hashAppend_t appendFn ; + hashFinish_t finishFn ; +} hashEntry_t ; + +extern void *init_CRC32(NSData *key) ; +extern void append_CRC32(void *_context, NSData *data) ; +extern NSData *finish_CRC32(void *_context) ; + +extern void *init_MD2(NSData *key) ; +extern void append_MD2(void *_context, NSData *data) ; +extern NSData *finish_MD2(void *_context) ; + +extern void *init_MD4(NSData *key) ; +extern void append_MD4(void *_context, NSData *data) ; +extern NSData *finish_MD4(void *_context) ; + +extern void *init_MD5(NSData *key) ; +extern void append_MD5(void *_context, NSData *data) ; +extern NSData *finish_MD5(void *_context) ; + +extern void *init_SHA1(NSData *key) ; +extern void append_SHA1(void *_context, NSData *data) ; +extern NSData *finish_SHA1(void *_context) ; + +extern void *init_SHA224(NSData *key) ; +extern void append_SHA224(void *_context, NSData *data) ; +extern NSData *finish_SHA224(void *_context) ; + +extern void *init_SHA256(NSData *key) ; +extern void append_SHA256(void *_context, NSData *data) ; +extern NSData *finish_SHA256(void *_context) ; + +extern void *init_SHA384(NSData *key) ; +extern void append_SHA384(void *_context, NSData *data) ; +extern NSData *finish_SHA384(void *_context) ; + +extern void *init_SHA512(NSData *key) ; +extern void append_SHA512(void *_context, NSData *data) ; +extern NSData *finish_SHA512(void *_context) ; + +extern void *init_hmacMD5(NSData *key) ; +extern NSData *finish_hmacMD5(void *_context) ; + +extern void *init_hmacSHA1(NSData *key) ; +extern NSData *finish_hmacSHA1(void *_context) ; + +extern void *init_hmacSHA224(NSData *key) ; +extern NSData *finish_hmacSHA224(void *_context) ; + +extern void *init_hmacSHA256(NSData *key) ; +extern NSData *finish_hmacSHA256(void *_context) ; + +extern void *init_hmacSHA384(NSData *key) ; +extern NSData *finish_hmacSHA384(void *_context) ; + +extern void *init_hmacSHA512(NSData *key) ; +extern NSData *finish_hmacSHA512(void *_context) ; + +extern void append_hmac(void *_context, NSData *data) ; + diff --git a/extensions/hash/algorithms.m b/extensions/hash/algorithms.m new file mode 100644 index 000000000..7c98537ce --- /dev/null +++ b/extensions/hash/algorithms.m @@ -0,0 +1,305 @@ +@import Cocoa ; +@import CommonCrypto.CommonDigest ; +@import CommonCrypto.CommonHMAC ; +@import zlib ; + +#include "algorithms.h" + +#pragma mark - CRC32 + +void *init_CRC32(__unused NSData *_key) { + uLong *_context = malloc(sizeof(uLong)) ; + uLong crc = crc32_z(0L, Z_NULL, 0) ; + memcpy(_context, &crc, sizeof(uLong)) ; + return _context ; +} + +void append_CRC32(void *_context, NSData *data) { + uLong crc ; + memcpy(&crc, _context, sizeof(uLong)) ; + crc = crc32_z(crc, data.bytes, data.length) ; + memcpy(_context, &crc, sizeof(uLong)) ; +} + +NSData *finish_CRC32(void *_context) { + uLong crc ; + memcpy(&crc, _context, sizeof(uLong)) ; + + unsigned char asBytes[4] = { 0 }; + asBytes[0] = (crc >> 24) & 0xff ; + asBytes[1] = (crc >> 16) & 0xff ; + asBytes[2] = (crc >> 8) & 0xff ; + asBytes[3] = crc & 0xff ; + + free(_context) ; + _context = NULL ; + return [NSData dataWithBytes:asBytes length:4] ; +} + +#pragma mark - MD2 + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +void *init_MD2(__unused NSData *_key) { + CC_MD2_CTX *_context = malloc(sizeof(CC_MD2_CTX)) ; + CC_MD2_Init(_context) ; + return _context ; +} + +void append_MD2(void *_context, NSData *data) { + CC_MD2_Update((CC_MD2_CTX *)_context, data.bytes, (CC_LONG)data.length) ; +} + +NSData *finish_MD2(void *_context) { + unsigned char *md = malloc(CC_MD2_DIGEST_LENGTH) ; + CC_MD2_Final(md, (CC_MD2_CTX *)_context) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_MD2_DIGEST_LENGTH] ; +} + +#pragma mark - MD4 + +void *init_MD4(__unused NSData *_key) { + CC_MD4_CTX *_context = malloc(sizeof(CC_MD4_CTX)) ; + CC_MD4_Init(_context) ; + return _context ; +} + +void append_MD4(void *_context, NSData *data) { + CC_MD4_Update((CC_MD4_CTX *)_context, data.bytes, (CC_LONG)data.length) ; +} + +NSData *finish_MD4(void *_context) { + unsigned char *md = malloc(CC_MD4_DIGEST_LENGTH) ; + CC_MD4_Final(md, (CC_MD4_CTX *)_context) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_MD4_DIGEST_LENGTH] ; +} + +#pragma mark - MD5 + +void *init_MD5(__unused NSData *_key) { + CC_MD5_CTX *_context = malloc(sizeof(CC_MD5_CTX)) ; + CC_MD5_Init(_context) ; + return _context ; +} + +void append_MD5(void *_context, NSData *data) { + CC_MD5_Update((CC_MD5_CTX *)_context, data.bytes, (CC_LONG)data.length) ; +} + +NSData *finish_MD5(void *_context) { + unsigned char *md = malloc(CC_MD5_DIGEST_LENGTH) ; + CC_MD5_Final(md, (CC_MD5_CTX *)_context) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_MD5_DIGEST_LENGTH] ; +} + +#pragma clang diagnostic pop + +#pragma mark - SHA1 + +void *init_SHA1(__unused NSData *_key) { + CC_SHA1_CTX *_context = malloc(sizeof(CC_SHA1_CTX)) ; + CC_SHA1_Init(_context) ; + return _context ; +} + +void append_SHA1(void *_context, NSData *data) { + CC_SHA1_Update((CC_SHA1_CTX *)_context, data.bytes, (CC_LONG)data.length) ; +} + +NSData *finish_SHA1(void *_context) { + unsigned char *md = malloc(CC_SHA1_DIGEST_LENGTH) ; + CC_SHA1_Final(md, (CC_SHA1_CTX *)_context) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_SHA1_DIGEST_LENGTH] ; +} + +#pragma mark - SHA224 + +void *init_SHA224(__unused NSData *_key) { + CC_SHA256_CTX *_context = malloc(sizeof(CC_SHA256_CTX)) ; + CC_SHA224_Init(_context) ; + return _context ; +} + +void append_SHA224(void *_context, NSData *data) { + CC_SHA224_Update((CC_SHA256_CTX *)_context, data.bytes, (CC_LONG)data.length) ; +} + +NSData *finish_SHA224(void *_context) { + unsigned char *md = malloc(CC_SHA224_DIGEST_LENGTH) ; + CC_SHA224_Final(md, (CC_SHA256_CTX *)_context) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_SHA224_DIGEST_LENGTH] ; +} + +#pragma mark - SHA256 + +void *init_SHA256(__unused NSData *_key) { + CC_SHA256_CTX *_context = malloc(sizeof(CC_SHA256_CTX)) ; + CC_SHA256_Init(_context) ; + return _context ; +} + +void append_SHA256(void *_context, NSData *data) { + CC_SHA256_Update((CC_SHA256_CTX *)_context, data.bytes, (CC_LONG)data.length) ; +} + +NSData *finish_SHA256(void *_context) { + unsigned char *md = malloc(CC_SHA256_DIGEST_LENGTH) ; + CC_SHA256_Final(md, (CC_SHA256_CTX *)_context) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_SHA256_DIGEST_LENGTH] ; +} + +#pragma mark - SHA384 + +void *init_SHA384(__unused NSData *_key) { + CC_SHA512_CTX *_context = malloc(sizeof(CC_SHA512_CTX)) ; + CC_SHA384_Init(_context) ; + return _context ; +} + +void append_SHA384(void *_context, NSData *data) { + CC_SHA384_Update((CC_SHA512_CTX *)_context, data.bytes, (CC_LONG)data.length) ; +} + +NSData *finish_SHA384(void *_context) { + unsigned char *md = malloc(CC_SHA384_DIGEST_LENGTH) ; + CC_SHA384_Final(md, (CC_SHA512_CTX *)_context) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_SHA384_DIGEST_LENGTH] ; +} + +#pragma mark - SHA512 + +void *init_SHA512(__unused NSData *_key) { + CC_SHA512_CTX *_context = malloc(sizeof(CC_SHA512_CTX)) ; + CC_SHA512_Init(_context) ; + return _context ; +} + +void append_SHA512(void *_context, NSData *data) { + CC_SHA512_Update((CC_SHA512_CTX *)_context, data.bytes, (CC_LONG)data.length) ; +} + +NSData *finish_SHA512(void *_context) { + unsigned char *md = malloc(CC_SHA512_DIGEST_LENGTH) ; + CC_SHA512_Final(md, (CC_SHA512_CTX *)_context) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_SHA512_DIGEST_LENGTH] ; +} + +#pragma mark - hmacMD5 + +void *init_hmacMD5(NSData *_key) { + CCHmacContext *_context = malloc(sizeof(CCHmacContext)) ; + CCHmacInit(_context, kCCHmacAlgMD5, _key.bytes, _key.length) ; + return _context ; +} + +NSData *finish_hmacMD5(void *_context) { + unsigned char *md = malloc(CC_MD5_DIGEST_LENGTH) ; + CCHmacFinal((CCHmacContext *)_context, md) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_MD5_DIGEST_LENGTH] ; +} + +#pragma mark - hmacSHA1 + +void *init_hmacSHA1(NSData *_key) { + CCHmacContext *_context = malloc(sizeof(CCHmacContext)) ; + CCHmacInit(_context, kCCHmacAlgSHA1, _key.bytes, _key.length) ; + return _context ; +} + +NSData *finish_hmacSHA1(void *_context) { + unsigned char *md = malloc(CC_SHA1_DIGEST_LENGTH) ; + CCHmacFinal((CCHmacContext *)_context, md) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_SHA1_DIGEST_LENGTH] ; +} + +#pragma mark - hmacSHA224 + +void *init_hmacSHA224(NSData *_key) { + CCHmacContext *_context = malloc(sizeof(CCHmacContext)) ; + CCHmacInit(_context, kCCHmacAlgSHA224, _key.bytes, _key.length) ; + return _context ; +} + +NSData *finish_hmacSHA224(void *_context) { + unsigned char *md = malloc(CC_SHA224_DIGEST_LENGTH) ; + CCHmacFinal((CCHmacContext *)_context, md) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_SHA224_DIGEST_LENGTH] ; +} + +#pragma mark - hmacSHA256 + +void *init_hmacSHA256(NSData *_key) { + CCHmacContext *_context = malloc(sizeof(CCHmacContext)) ; + CCHmacInit(_context, kCCHmacAlgSHA256, _key.bytes, _key.length) ; + return _context ; +} + +NSData *finish_hmacSHA256(void *_context) { + unsigned char *md = malloc(CC_SHA256_DIGEST_LENGTH) ; + CCHmacFinal((CCHmacContext *)_context, md) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_SHA256_DIGEST_LENGTH] ; +} + +#pragma mark - hmacSHA384 + +void *init_hmacSHA384(NSData *_key) { + CCHmacContext *_context = malloc(sizeof(CCHmacContext)) ; + CCHmacInit(_context, kCCHmacAlgSHA384, _key.bytes, _key.length) ; + return _context ; +} + +NSData *finish_hmacSHA384(void *_context) { + unsigned char *md = malloc(CC_SHA384_DIGEST_LENGTH) ; + CCHmacFinal((CCHmacContext *)_context, md) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_SHA384_DIGEST_LENGTH] ; +} + +#pragma mark - hmacSHA512 + +void *init_hmacSHA512(NSData *_key) { + CCHmacContext *_context = malloc(sizeof(CCHmacContext)) ; + CCHmacInit(_context, kCCHmacAlgSHA512, _key.bytes, _key.length) ; + return _context ; +} + +NSData *finish_hmacSHA512(void *_context) { + unsigned char *md = malloc(CC_SHA512_DIGEST_LENGTH) ; + CCHmacFinal((CCHmacContext *)_context, md) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:CC_SHA512_DIGEST_LENGTH] ; +} + +#pragma mark - hmac common + +void append_hmac(void *_context, NSData *data) { + CCHmacUpdate((CCHmacContext *)_context, data.bytes, data.length) ; +} + diff --git a/extensions/hash/byte_order.h b/extensions/hash/byte_order.h new file mode 100644 index 000000000..3bca388a7 --- /dev/null +++ b/extensions/hash/byte_order.h @@ -0,0 +1,209 @@ +// Modified from code at https://github.com/rhash/RHash + +/* byte_order.h */ +#ifndef BYTE_ORDER_H +#define BYTE_ORDER_H +// #include "ustd.h" +@import Darwin.C.stdlib; + +#if defined(__GLIBC__) +# include +#endif +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__APPLE__) +@import Darwin.POSIX.sys.types; +#elif defined (__NetBSD__) || defined(__OpenBSD__) +# include +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +/* if x86 compatible cpu */ +#if defined(i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(__pentium__) || \ + defined(__pentiumpro__) || defined(__pentium4__) || \ + defined(__nocona__) || defined(prescott) || defined(__core2__) || \ + defined(__k6__) || defined(__k8__) || defined(__athlon__) || \ + defined(__amd64) || defined(__amd64__) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_IX86) || \ + defined(_M_AMD64) || defined(_M_IA64) || defined(_M_X64) +/* detect if x86-64 instruction set is supported */ +# if defined(_LP64) || defined(__LP64__) || defined(__x86_64) || \ + defined(__x86_64__) || defined(_M_AMD64) || defined(_M_X64) +# define CPU_X64 +# else +# define CPU_IA32 +# endif +#endif + +#define RHASH_BYTE_ORDER_LE 1234 +#define RHASH_BYTE_ORDER_BE 4321 + +#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +# define RHASH_BYTE_ORDER RHASH_BYTE_ORDER_LE +#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +# define RHASH_BYTE_ORDER RHASH_BYTE_ORDER_BE +#elif defined(_BYTE_ORDER) +# if defined(_LITTLE_ENDIAN) && (_BYTE_ORDER == _LITTLE_ENDIAN) +# define RHASH_BYTE_ORDER RHASH_BYTE_ORDER_LE +# elif defined(_BIG_ENDIAN) && (_BYTE_ORDER == _BIG_ENDIAN) +# define RHASH_BYTE_ORDER RHASH_BYTE_ORDER_BE +# endif +#elif defined(__sun) && defined(_LITTLE_ENDIAN) +# define RHASH_BYTE_ORDER RHASH_BYTE_ORDER_LE +#elif defined(__sun) && defined(_BIG_ENDIAN) +# define RHASH_BYTE_ORDER RHASH_BYTE_ORDER_BE +#endif + +/* try detecting endianness by CPU */ +#ifdef RHASH_BYTE_ORDER +#elif defined(CPU_IA32) || defined(CPU_X64) || defined(__ia64) || defined(__ia64__) || \ + defined(__alpha__) || defined(_M_ALPHA) || defined(vax) || defined(MIPSEL) || \ + defined(_ARM_) || defined(__arm__) || defined(_M_ARM64) || defined(_M_ARM64EC) +# define RHASH_BYTE_ORDER RHASH_BYTE_ORDER_LE +#elif defined(__sparc) || defined(__sparc__) || defined(sparc) || \ + defined(_ARCH_PPC) || defined(_ARCH_PPC64) || defined(_POWER) || \ + defined(__POWERPC__) || defined(POWERPC) || defined(__powerpc) || \ + defined(__powerpc__) || defined(__powerpc64__) || defined(__ppc__) || \ + defined(__hpux) || defined(_MIPSEB) || defined(mc68000) || \ + defined(__s390__) || defined(__s390x__) || defined(sel) || defined(__hppa__) +# define RHASH_BYTE_ORDER RHASH_BYTE_ORDER_BE +#else +# error "Can't detect CPU architechture" +#endif + +#define IS_BIG_ENDIAN (RHASH_BYTE_ORDER == RHASH_BYTE_ORDER_BE) +#define IS_LITTLE_ENDIAN (RHASH_BYTE_ORDER == RHASH_BYTE_ORDER_LE) + +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + +#define IS_ALIGNED_32(p) (0 == (3 & (uintptr_t)(p))) +#define IS_ALIGNED_64(p) (0 == (7 & (uintptr_t)(p))) + +#if defined(_MSC_VER) +#define ALIGN_ATTR(n) __declspec(align(n)) +#elif defined(__GNUC__) +#define ALIGN_ATTR(n) __attribute__((aligned (n))) +#else +#define ALIGN_ATTR(n) /* nothing */ +#endif + + +#if defined(_MSC_VER) || defined(__BORLANDC__) +#define I64(x) x##ui64 +#else +#define I64(x) x##ULL +#endif + +#if defined(_MSC_VER) +#define RHASH_INLINE __inline +#elif defined(__GNUC__) && !defined(__STRICT_ANSI__) +#define RHASH_INLINE inline +#elif defined(__GNUC__) +#define RHASH_INLINE __inline__ +#else +#define RHASH_INLINE +#endif + +/* define rhash_ctz - count traling zero bits */ +#if (defined(__GNUC__) && __GNUC__ >= 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) || \ + (defined(__clang__) && __has_builtin(__builtin_ctz)) +/* GCC >= 3.4 or clang */ +# define rhash_ctz(x) __builtin_ctz(x) +#else +unsigned rhash_ctz(unsigned); /* define as function */ +#endif + +void rhash_swap_copy_str_to_u32(void* to, int index, const void* from, size_t length); +void rhash_swap_copy_str_to_u64(void* to, int index, const void* from, size_t length); +void rhash_swap_copy_u64_to_str(void* to, const void* from, size_t length); +void rhash_u32_mem_swap(unsigned* p, int length_in_u32); + +/* bswap definitions */ +#if (defined(__GNUC__) && (__GNUC__ >= 4) && (__GNUC__ > 4 || __GNUC_MINOR__ >= 3)) || \ + (defined(__clang__) && __has_builtin(__builtin_bswap32) && __has_builtin(__builtin_bswap64)) +/* GCC >= 4.3 or clang */ +# define bswap_32(x) __builtin_bswap32(x) +# define bswap_64(x) __builtin_bswap64(x) +#elif (_MSC_VER > 1300) && (defined(CPU_IA32) || defined(CPU_X64)) /* MS VC */ +# define bswap_32(x) _byteswap_ulong((unsigned long)x) +# define bswap_64(x) _byteswap_uint64((__int64)x) +#else +/* fallback to generic bswap definition */ +static RHASH_INLINE uint32_t bswap_32(uint32_t x) +{ +# if defined(__GNUC__) && defined(CPU_IA32) && !defined(__i386__) && !defined(RHASH_NO_ASM) + __asm("bswap\t%0" : "=r" (x) : "0" (x)); /* gcc x86 version */ + return x; +# else + x = ((x << 8) & 0xFF00FF00u) | ((x >> 8) & 0x00FF00FFu); + return (x >> 16) | (x << 16); +# endif +} +static RHASH_INLINE uint64_t bswap_64(uint64_t x) +{ + union { + uint64_t ll; + uint32_t l[2]; + } w, r; + w.ll = x; + r.l[0] = bswap_32(w.l[1]); + r.l[1] = bswap_32(w.l[0]); + return r.ll; +} +#endif /* bswap definitions */ + +#if IS_BIG_ENDIAN +# define be2me_32(x) (x) +# define be2me_64(x) (x) +# define le2me_32(x) bswap_32(x) +# define le2me_64(x) bswap_64(x) + +# define be32_copy(to, index, from, length) memcpy((char*)(to) + (index), (from), (length)) +# define le32_copy(to, index, from, length) rhash_swap_copy_str_to_u32((to), (index), (from), (length)) +# define be64_copy(to, index, from, length) memcpy((char*)(to) + (index), (from), (length)) +# define le64_copy(to, index, from, length) rhash_swap_copy_str_to_u64((to), (index), (from), (length)) +# define me64_to_be_str(to, from, length) memcpy((to), (from), (length)) +# define me64_to_le_str(to, from, length) rhash_swap_copy_u64_to_str((to), (from), (length)) + +#else /* IS_BIG_ENDIAN */ +# define be2me_32(x) bswap_32(x) +# define be2me_64(x) bswap_64(x) +# define le2me_32(x) (x) +# define le2me_64(x) (x) + +# define be32_copy(to, index, from, length) rhash_swap_copy_str_to_u32((to), (index), (from), (length)) +# define le32_copy(to, index, from, length) memcpy((char*)(to) + (index), (from), (length)) +# define be64_copy(to, index, from, length) rhash_swap_copy_str_to_u64((to), (index), (from), (length)) +# define le64_copy(to, index, from, length) memcpy((char*)(to) + (index), (from), (length)) +# define me64_to_be_str(to, from, length) rhash_swap_copy_u64_to_str((to), (from), (length)) +# define me64_to_le_str(to, from, length) memcpy((to), (from), (length)) +#endif /* IS_BIG_ENDIAN */ + +/* ROTL/ROTR macros rotate a 32/64-bit word left/right by n bits */ +#define ROTL32(dword, n) ((dword) << (n) ^ ((dword) >> (32 - (n)))) +#define ROTR32(dword, n) ((dword) >> (n) ^ ((dword) << (32 - (n)))) +#define ROTL64(qword, n) ((qword) << (n) ^ ((qword) >> (64 - (n)))) +#define ROTR64(qword, n) ((qword) >> (n) ^ ((qword) << (64 - (n)))) + +#define CPU_FEATURE_SSE4_2 (52) + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) \ + && (defined(CPU_X64) || defined(CPU_IA32)) +# define HAS_INTEL_CPUID +int has_cpu_feature(unsigned feature_bit); +#else +# define has_cpu_feature(x) (0) +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* BYTE_ORDER_H */ diff --git a/extensions/hash/hash.lua b/extensions/hash/hash.lua index 06b4cf252..2605a940c 100644 --- a/extensions/hash/hash.lua +++ b/extensions/hash/hash.lua @@ -1,6 +1,305 @@ --- === hs.hash === --- ---- Various hashing algorithms +--- This module provides various hashing algorithms for use within Hammerspoon. +--- +--- The currently supported hash types can be viewed by examining the [hs.hash.types](#types) constant. +--- +--- In keeping with common hash library conventions to simplify the future addition of additional hash types, hash calculations in this module are handled in a three step manner, which is reflected in the constructor and methods defined for this module: +--- +--- * First, the hash context is initialized. For this module, this occurs when you create a new hash object with [hs.hash.new(name, [secret])](#new). +--- * Second, data is "input" or appended to the hash with the [hs.hash:append(data)](#append) method. This may be invoked one or more times for the hash object before finalizing it, and order *is* important: `hashObject:append(data1):append(data2)` is different than `hashObject:append(data2):append(data1)`. +--- * Finally, you finalize or finish the hash with [hs.hash:finish()](#finish), which generates the final hash value. You can then retrieve the hash value with [hs.hash:value()](#value). +--- +--- Most of the time, we only want to generate a hash value for a single data object; for this reason, meta-methods for this module allow you to use the following shortcut when computing a hash value: +--- +--- * `hs.hash.(data)` will return the hexadecimal version of the hash for the hash type `` where `` is one of the entries in [hs.hash.types](#types). This is syntacticly identical to `hs.hash.new():append(data):finish():value()`. +--- * `hs.hash.b(data)` will return the binary version of the hash for the hash type ''. This is syntacticly identical to `hs.hash.new():append(data):finish():value(true)`. +--- * In both cases above, if the hash name begins with `hmac`, the arguments should be `(secret, data)`, but otherwise act as described above. If additional shared key hash algorithms are added, this will be adjusted to continue to allow the shortcuts for the most common usage patterns. +--- +--- The SHA3 code is based on code from the https://github.com/rhash/RHash project. +--- https://github.com/krzyzanowskim/CryptoSwift may also prove useful for future additions. + +local USERDATA_TAG = "hs.hash" +local module = require(table.concat({ USERDATA_TAG:match("^([%w%._]+%.)([%w_]+)$") }, "lib")) +local fnutils = require("hs.fnutils") + +-- Public interface ------------------------------------------------------ + +--- hs.hash.SHA1(data) -> string +--- Deprecated +--- Calculates an SHA1 hash +--- +--- Parameters: +--- * data - A string containing some data to hash +--- +--- Returns: +--- * A string containing the hash of the supplied data, encoded as hexadecimal +--- +--- Notes: +--- * this function is provided for backwards compatibility with a previous version of this module and is functionally equivalent to: `hs.hash.new("SHA1"):append(data):finish():value()` + +--- hs.hash.SHA256(data) -> string +--- Deprecated +--- Calculates an SHA256 hash +--- +--- Parameters: +--- * data - A string containing some data to hash +--- +--- Returns: +--- * A string containing the hash of the supplied data, encoded as hexadecimal +--- +--- Notes: +--- * this function is provided for backwards compatibility with a previous version of this module and is functionally equivalent to: `hs.hash.new("SHA256"):append(data):finish():value()` + +--- hs.hash.SHA512(data) -> string +--- Deprecated +--- Calculates an SHA512 hash +--- +--- Parameters: +--- * data - A string containing some data to hash +--- +--- Returns: +--- * A string containing the hash of the supplied data, encoded as hexadecimal +--- +--- Notes: +--- * this function is provided for backwards compatibility with a previous version of this module and is functionally equivalent to: `hs.hash.new("SHA512"):append(data):finish():value()` + +--- hs.hash.MD5(data) -> string +--- Deprecated +--- Calculates an MD5 hash +--- +--- Parameters: +--- * data - A string containing some data to hash +--- +--- Returns: +--- * A string containing the hash of the supplied data, encoded as hexadecimal +--- +--- Notes: +--- * this function is provided for backwards compatibility with a previous version of this module and is functionally equivalent to: `hs.hash.new("MD5"):append(data):finish():value()` + +--- hs.hash.bSHA1(data) -> data +--- Deprecated +--- Calculates a binary SHA1 hash +--- +--- Parameters: +--- * data - A string containing some data to hash +--- +--- Returns: +--- * A string containing the binary hash of the supplied data +--- +--- Notes: +--- * this function is provided for backwards compatibility with a previous version of this module and is functionally equivalent to: `hs.hash.new("SHA1"):append(data):finish():value(true)` + +--- hs.hash.bSHA256(data) -> data +--- Deprecated +--- Calculates a binary SHA256 hash +--- +--- Parameters: +--- * data - A string containing some data to hash +--- +--- Returns: +--- * A string containing the binary hash of the supplied data +--- +--- Notes: +--- * this function is provided for backwards compatibility with a previous version of this module and is functionally equivalent to: `hs.hash.new("SHA256"):append(data):finish():value(true)` + +--- hs.hash.bSHA512(data) -> data +--- Deprecated +--- Calculates a binary SHA512 hash +--- +--- Parameters: +--- * data - A string containing some data to hash +--- +--- Returns: +--- * A string containing the binary hash of the supplied data +--- +--- Notes: +--- * this function is provided for backwards compatibility with a previous version of this module and is functionally equivalent to: `hs.hash.new("SHA512"):append(data):finish():value(true)` + +--- hs.hash.bMD5(data) -> data +--- Deprecated +--- Calculates a binary MD5 hash +--- +--- Parameters: +--- * data - A string containing some data to hash +--- +--- Returns: +--- * A string containing the binary hash of the supplied data +--- +--- Notes: +--- * this function is provided for backwards compatibility with a previous version of this module and is functionally equivalent to: `hs.hash.new("MD5"):append(data):finish():value(true)` + +--- hs.hash.hmacSHA1(key, data) -> string +--- Deprecated +--- Calculates an HMAC using a key and a SHA1 hash +--- +--- Parameters: +--- * key - A string containing a secret key to use +--- * data - A string containing the data to hash +--- +--- Returns: +--- * A string containing the hash of the supplied data +--- +--- Notes: +--- * this function is provided for backwards compatibility with a previous version of this module and is functionally equivalent to: `hs.hash.new("hmacSHA1", key):append(data):finish():value()` + +--- hs.hash.hmacSHA256(key, data) -> string +--- Deprecated +--- Calculates an HMAC using a key and a SHA256 hash +--- +--- Parameters: +--- * key - A string containing a secret key to use +--- * data - A string containing the data to hash +--- +--- Returns: +--- * A string containing the hash of the supplied data +--- +--- Notes: +--- * this function is provided for backwards compatibility with a previous version of this module and is functionally equivalent to: `hs.hash.new("hmacSHA256", key):append(data):finish():value()` + +--- hs.hash.hmacSHA512(key, data) -> string +--- Deprecated +--- Calculates an HMAC using a key and a SHA512 hash +--- +--- Parameters: +--- * key - A string containing a secret key to use +--- * data - A string containing the data to hash +--- +--- Returns: +--- * A string containing the hash of the supplied data +--- +--- Notes: +--- * this function is provided for backwards compatibility with a previous version of this module and is functionally equivalent to: `hs.hash.new("hmacSHA512", key):append(data):finish():value()` + +--- hs.hash.hmacMD5(key, data) -> string +--- Deprecated +--- Calculates an HMAC using a key and an MD5 hash +--- +--- Parameters: +--- * key - A string containing a secret key to use +--- * data - A string containing the data to hash +--- +--- Returns: +--- * A string containing the hash of the supplied data +--- +--- Notes: +--- * this function is provided for backwards compatibility with a previous version of this module and is functionally equivalent to: `hs.hash.new("hmacMD5", key):append(data):finish():value()` + +--- hs.hash.types +--- Constant +--- A tale containing the names of the hashing algorithms supported by this module. +--- +--- Notes: +--- At present, this module supports the following hash functions: +--- * `CRC32` - Technically a checksum, not a hash, but often used for similar purposes, like verifying file integrity. Produces a 32bit value. +--- * `MD5` - A message digest algorithm producing a 128bit hash value. MD5 is no longer consider secure for cryptographic purposes, but is still widely used to verify file integrity and other non cryptographic uses. +--- * `SHA1` - A message digest algorithm producing a 160bit hash value. SHA-1 is no longer consider secure for cryptographic purposes, but is still widely used to verify file integrity and other non cryptographic uses. +--- * `SHA256` - A cryptographic hash function that produces a 256bit hash value. While there has been some research into attack vectors on the SHA-2 family of algorithms, this is still considered sufficiently secure for many cryptographic purposes and for data validation and verification. +--- * `SHA512` - A cryptographic hash function that produces a 512bit hash value. While there has been some research into attack vectors on the SHA-2 family of algorithms, this is still considered sufficiently secure for many cryptographic purposes and for data validation and verification. +--- * `hmacMD5` - Combines the MD5 hash algorithm with a hash-based message authentication code, or pre-shared secret. +--- * `hmacSHA1` - Combines the SHA1 hash algorithm with a hash-based message authentication code, or pre-shared secret. +--- * `hmacSHA256` - Combines the SHA-2 256bit hash algorithm with a hash-based message authentication code, or pre-shared secret. +--- * `hmacSHA512` - Combines the SHA-2 512bit hash algorithm with a hash-based message authentication code, or pre-shared secret. +--- * `SHA3_224` - A SHA3 based cryptographic hash function that produces a 224bit hash value. The SHA3 family of algorithms use a different process than that which is used in the MD5, SHA1 and SHA2 families of algorithms and is considered the most cryptographically secure at present, though at the cost of additional computational complexity. +--- * `SHA3_256` - A SHA3 based cryptographic hash function that produces a 256bit hash value. The SHA3 family of algorithms use a different process than that which is used in the MD5, SHA1 and SHA2 families of algorithms and is considered the most cryptographically secure at present, though at the cost of additional computational complexity. +--- * `SHA3_384` - A SHA3 based cryptographic hash function that produces a 384bit hash value. The SHA3 family of algorithms use a different process than that which is used in the MD5, SHA1 and SHA2 families of algorithms and is considered the most cryptographically secure at present, though at the cost of additional computational complexity. +--- * `SHA3_512` - A SHA3 based cryptographic hash function that produces a 512bit hash value. The SHA3 family of algorithms use a different process than that which is used in the MD5, SHA1 and SHA2 families of algorithms and is considered the most cryptographically secure at present, though at the cost of additional computational complexity. +table.sort(module.types) +module.types = ls.makeConstantsTable(module.types) + +--- hs.hash.convertHexHashToBinary(input) -> string +--- Function +--- Converts a string containing a hash value as a string of hexadecimal digits into its binary equivalent. +--- +--- Parameters: +--- * input - a string containing the hash value you wish to convert into its binary equivalent. The string must be a sequence of hexadecimal digits with an even number of characters. +--- +--- Returns: +--- * a string containing the equivalent binary hash +--- +--- Notes: +--- * this is a convenience function for use when you already have a hash value that you wish to convert to its binary equivalent. Beyond checking that the input string contains only hexadecimal digits and is an even length, the value is not actually validated as the actual hash value for anything specific. +module.convertHashToBinary = function(...) + local args = table.pack(...) + local input = args[1] + assert(args.n == 1 and type(input) == "string" and #input % 2 == 0 and input:match("^%x+$"), "expected a string of hexidecimal digits") + local output = "" + for p in input:gmatch("%x%x") do output = output .. string.char(tonumber(p, 16)) end + return output +end + +--- hs.hash.convertBinaryHashToHex(input) -> string +--- Function +--- Converts a string containing a binary hash value to its equivalent hexadecimal digits. +--- +--- Parameters: +--- * input - a string containing the binary hash value you wish to convert into its equivalent hexadecimal digits. +--- +--- Returns: +--- * a string containing the equivalent hash as a string of hexadecimal digits +--- +--- Notes: +--- * this is a convenience function for use when you already have a binary hash value that you wish to convert to its hexadecimal equivalent -- the value is not actually validated as the actual hash value for anything specific. +module.convertBinaryHashToHEX = function(...) + local args = table.pack(...) + local input = args[1] + assert(args.n == 1 and type(input) == "string", "expected a string") + local output = "" + for p in input:gmatch(".") do output = output .. string.format("%02x", string.byte(p)) end + return output +end + +--- hs.hash.forFile(hash, [secret], path) -> string +--- Function +--- Calculates the specified hash value for the file at the given path. +--- +--- Parameters: +--- * `hash` - the name of the type of hash to calculate. This must be one of the string values found in the [hs.hash.types](#types) constant. +--- * `secret` - an optional string specifying the shared secret to prepare the hmac hash function with. For all other hash types this field is ignored. Leaving this parameter off when specifying an hmac hash function is equivalent to specifying an empty secret or a secret composed solely of null values. +--- * `path` - the path to the file to calculate the hash value for. +--- +--- Returns: +--- * a string containing the hexadecimal version of the calculated hash for the specified file. +--- +--- Notes: +--- * this is a convenience function that performs the equivalent of `hs.new.hash(hash, [secret]):appendFile(path):finish():value()`. +module.forFile = function(...) + local args = { ... } + local hashFn = args[1] + assert(type(hashFn) == "string" and fnutils.contains(module.types, hashFn), "hash type must be a string specifying one of the following -- " .. table.concat(module.types, ", ")) + + local key = hashFn:match("^hmac") and args[3] and args[2] or nil + local path = hashFn:match("^hmac") and args[3] or args[2] + + local object = hashFn:match("^hmac") and module.new(hashFn, key) or module.new(hashFn) + return object:appendFile(path):finish():value() +end + +-- Return Module Object -------------------------------------------------- -local hash = require "hs.libhash" -return hash +return setmetatable(module, { + __index = function(_, key) + local realKey = key:match("^b([%w_]+)$") or key + if fnutils.contains(module.types, realKey) then + return function(...) + local args = { ... } + local object + if realKey:match("^hmac") then + local secret = table.remove(args, 1) + object = module.new(realKey, secret) + else + object = module.new(realKey) + end + for _, v in ipairs(args) do object:append(v) end + return object:finish():value(not not key:match("^b")) + end + end + end, + __call = function(_, key, ...) + if fnutils.contains(module.types, key) then + return _[key](...) + else + error(3, "attempt to call a table value") + end + end, +}) diff --git a/extensions/hash/libhash.m b/extensions/hash/libhash.m index 5c85fe9b0..0373d984b 100644 --- a/extensions/hash/libhash.m +++ b/extensions/hash/libhash.m @@ -1,278 +1,387 @@ -#import -#import -#import -#import - -static int doHash(lua_State *L, CC_LONG length, unsigned char *(*hashFunc)(const void *, CC_LONG, unsigned char *)) { - unsigned char digest[length + 1]; - size_t sourceLength; - const char *source = luaL_checklstring(L, 1, &sourceLength); - NSMutableString *conversionSink = [NSMutableString string]; - - hashFunc(source, (CC_LONG)sourceLength, digest); - digest[length] = 0; - - for (unsigned int i = 0; i < length; i++) { - [conversionSink appendFormat:@"%02x", digest[i]]; +@import Cocoa ; +@import CommonCrypto.CommonDigest ; +@import CommonCrypto.CommonHMAC ; +@import zlib ; +@import LuaSkin ; + +// When adding a new hash type, you should only need to update a couple of areas... +// they are labeled with ADD_NEW_HASH_HERE + +// uncomment to include deprecated/less-common hash types (see hashLookupTable below) +// #define INCLUDE_HISTORICAL + +#include "algorithms.h" +#include "sha3.h" +// ADD_NEW_HASH_HERE -- assuming new hash code is in its own .m and .h files + +static const char * const USERDATA_TAG = "hs.hash" ; +static LSRefTable refTable = LUA_NOREF ; + +#define get_objectFromUserdata(objType, L, idx, tag) (objType*)*((void**)luaL_checkudata(L, idx, tag)) + +#pragma mark - Support Functions and Classes + +static const hashEntry_t hashLookupTable[] = { +#ifdef INCLUDE_HISTORICAL + { "MD2", init_MD2, append_MD2, finish_MD2 }, + { "MD4", init_MD4, append_MD4, finish_MD4 }, + { "SHA224", init_SHA224, append_SHA224, finish_SHA224 }, + { "SHA384", init_SHA384, append_SHA384, finish_SHA384 }, + { "hmacSHA224", init_hmacSHA224, append_hmac, finish_hmacSHA224 }, + { "hmacSHA384", init_hmacSHA384, append_hmac, finish_hmacSHA384 }, +#endif + + { "CRC32", init_CRC32, append_CRC32, finish_CRC32 }, + { "MD5", init_MD5, append_MD5, finish_MD5 }, + { "SHA1", init_SHA1, append_SHA1, finish_SHA1 }, + { "SHA256", init_SHA256, append_SHA256, finish_SHA256 }, + { "SHA512", init_SHA512, append_SHA512, finish_SHA512 }, + { "hmacMD5", init_hmacMD5, append_hmac, finish_hmacMD5 }, + { "hmacSHA1", init_hmacSHA1, append_hmac, finish_hmacSHA1 }, + { "hmacSHA256", init_hmacSHA256, append_hmac, finish_hmacSHA256 }, + { "hmacSHA512", init_hmacSHA512, append_hmac, finish_hmacSHA512 }, + + { "SHA3_224", init_SHA3_224, append_SHA3, finish_SHA3_224 }, + { "SHA3_256", init_SHA3_256, append_SHA3, finish_SHA3_256 }, + { "SHA3_384", init_SHA3_384, append_SHA3, finish_SHA3_384 }, + { "SHA3_512", init_SHA3_512, append_SHA3, finish_SHA3_512 }, +// ADD_NEW_HASH_HERE -- label(s) for Hammerspoon and functions for initializing, appending to, and finishing +} ; + +static const NSUInteger knownHashCount = sizeof(hashLookupTable) / sizeof(hashEntry_t) ; + +@interface HSHashObject : NSObject +@property int selfRefCount ; +@property NSUInteger hashType ; +@property NSData *secret ; +@property void *context ; +@property NSData *value ; +@end + +@implementation HSHashObject +- (instancetype)initHashType:(NSUInteger)hashType withSecret:(NSData *)secret { + self = [super init] ; + if (self) { + _selfRefCount = 0 ; + _hashType = hashType ; + _secret = secret ; + _context = (hashLookupTable[_hashType].initFn)(secret) ; + _value = nil ; } - - //NSLog(@"Hashed '%s' into '%@'", source, conversionSink); - - lua_pushstring(L, [conversionSink UTF8String]); - - return 1; + return self ; } -static int doHashHMAC(lua_State *L, CCHmacAlgorithm algorithm, CC_LONG resultLength) { - unsigned char digest[resultLength + 1]; - size_t keyLength; - size_t dataLength; - const char *key = luaL_checklstring(L, 1, &keyLength); - const char *data = luaL_checklstring(L, 2, &dataLength); - NSMutableString *conversionSink = [NSMutableString string]; - - CCHmac(algorithm, key, keyLength, data, dataLength, digest); - digest[resultLength] = 0; - - for (unsigned int i = 0; i < resultLength; i++) { - [conversionSink appendFormat:@"%02x", digest[i]]; - } - - //NSLog(@"HMAC Hashed '%s' with key '%s' into '%@'", data, key, conversionSink); - - lua_pushstring(L, [conversionSink UTF8String]); - - return 1; +- (void)append:(NSData *)data { + (hashLookupTable[_hashType].appendFn)(_context, data) ; } -/// hs.hash.SHA1(data) -> string -/// Function -/// Calculates an SHA1 hash -/// -/// Parameters: -/// * data - A string containing some data to hash -/// -/// Returns: -/// * A string containing the hash of the supplied data, encoded as hexadecimal -static int hash_sha1(lua_State *L) { - return doHash(L, CC_SHA1_DIGEST_LENGTH, CC_SHA1); +- (void)finish { + _value = (hashLookupTable[_hashType].finishFn)(_context) ; + _context = NULL ; // it was freed in the finish function } +@end -/// hs.hash.SHA256(data) -> string -/// Function -/// Calculates an SHA256 hash -/// -/// Parameters: -/// * data - A string containing some data to hash -/// -/// Returns: -/// * A string containing the hash of the supplied data, encoded as hexadecimal -static int hash_sha256(lua_State *L) { - return doHash(L, CC_SHA256_DIGEST_LENGTH, CC_SHA256); -} +#pragma mark - Module Functions -/// hs.hash.SHA512(data) -> string -/// Function -/// Calculates an SHA512 hash +/// hs.hash.new(hash, [secret]) -> hashObject +/// Constructor +/// Creates a new context for the specified hash function. /// /// Parameters: -/// * data - A string containing some data to hash +/// * `hash` - a string specifying the name of the hash function to use. This must be one of the string values found in the [hs.hash.types](#types) constant. +/// * `secret` - an optional string specifying the shared secret to prepare the hmac hash function with. For all other hash types this field is ignored. Leaving this parameter off when specifying an hmac hash function is equivalent to specifying an empty secret or a secret composed solely of null values. /// /// Returns: -/// * A string containing the hash of the supplied data, encoded as hexadecimal -static int hash_sha512(lua_State *L) { - return doHash(L, CC_SHA512_DIGEST_LENGTH, CC_SHA512); +/// * the new hash object +static int hash_new(lua_State *L) { + LuaSkin *skin = [LuaSkin sharedWithState:L] ; + [skin checkArgs:LS_TSTRING, LS_TSTRING | LS_TOPTIONAL, LS_TBREAK] ; + NSString *hashName = [skin toNSObjectAtIndex:1] ; + NSData *secret = nil ; + if (lua_gettop(L) == 2) secret = [skin toNSObjectAtIndex:2 withOptions:LS_NSLuaStringAsDataOnly] ; + + NSUInteger hashType = 0 ; + BOOL hashFound = NO ; + + for (NSUInteger i = 0 ; i < knownHashCount ; i++) { + NSString *label = @(hashLookupTable[i].hashName) ; + if ([hashName caseInsensitiveCompare:label] == NSOrderedSame) { + hashFound = YES ; + hashType = i ; + break ; + } + } + if (hashFound) { + HSHashObject *object = [[HSHashObject alloc] initHashType:hashType + withSecret:secret] ; + [skin pushNSObject:object] ; + } else { + return luaL_argerror(L, 1, "unrecognized hash type") ; + } + return 1 ; } -/// hs.hash.MD5(data) -> string -/// Function -/// Calculates an MD5 hash +#pragma mark - Module Methods + +/// hs.hash:append(data) -> hashObject | nil, error +/// Method +/// Adds the provided data to the input of the hash function currently in progress for the hashObject. /// /// Parameters: -/// * data - A string containing some data to hash +/// * `data` - a string containing the data to add to the hash functions input. /// /// Returns: -/// * A string containing the hash of the supplied data, encoded as hexadecimal -static int hash_md5(lua_State *L) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return doHash(L, CC_MD5_DIGEST_LENGTH, CC_MD5); -#pragma clang diagnostic pop +/// * the hash object, or if the hash has already been calculated (finished), nil and an error string +static int hash_append(lua_State *L) { + LuaSkin *skin = [LuaSkin sharedWithState:L] ; + [skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING, LS_TBREAK] ; + HSHashObject *object = [skin toNSObjectAtIndex:1] ; + NSData *data = [skin toNSObjectAtIndex:2 withOptions:LS_NSLuaStringAsDataOnly] ; + + if (!object.value) { + [object append:data] ; + } else { + lua_pushnil(L) ; + lua_pushstring(L, "hash calculation completed") ; + return 2 ; + } + + lua_pushvalue(L, 1) ; + return 1 ; } -/// hs.hash.bSHA1(data) -> data -/// Function -/// Calculates a binary SHA1 hash + +/// hs.hash:appendFile(path) -> hashObject | nil, error +/// Method +/// Adds the contents of the file at the specified path to the input of the hash function currently in progress for the hashObject. /// /// Parameters: -/// * data - A string containing some data to hash +/// * `path` - a string containing the path of the file to add to the hash functions input. /// /// Returns: -/// * A string containing the binary hash of the supplied data -static int hash_bsha1(lua_State *L) { - LuaSkin *skin = [LuaSkin sharedWithState:L]; - [skin checkArgs:LS_TSTRING, LS_TBREAK]; - - NSData *dataIn = [skin toNSObjectAtIndex:1 withOptions:LS_NSLuaStringAsDataOnly]; - - NSMutableData *macOut = [NSMutableData dataWithLength:CC_SHA1_DIGEST_LENGTH]; - CC_SHA1(dataIn.bytes, (CC_LONG)dataIn.length, macOut.mutableBytes); +/// * the hash object +static int hash_appendFile(lua_State *L) { + LuaSkin *skin = [LuaSkin sharedWithState:L] ; + [skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING, LS_TBREAK] ; + HSHashObject *object = [skin toNSObjectAtIndex:1] ; + NSString *path = [skin toNSObjectAtIndex:2] ; + + if (!object.value) { + path = path.stringByExpandingTildeInPath.stringByResolvingSymlinksInPath ; + NSError *error = nil ; + NSData *data = [NSData dataWithContentsOfFile:path options:NSDataReadingUncached error:&error] ; + if (!error) { + [object append:data] ; + } else { + lua_pushnil(L) ; + lua_pushfstring(L, "error reading contents of %s: %s", path.UTF8String, error.localizedDescription.UTF8String) ; + return 2 ; + } + } else { + lua_pushnil(L) ; + lua_pushstring(L, "hash calculation completed") ; + return 2 ; + } - [skin pushNSObject:macOut]; - return 1; + lua_pushvalue(L, 1) ; + return 1 ; } -/// hs.hash.bSHA256(data) -> data -/// Function -/// Calculates a binary SHA256 hash +/// hs.hash:finish() -> hashObject +/// Method +/// Finalizes the hash and computes the resulting value. /// /// Parameters: -/// * data - A string containing some data to hash +/// * None /// /// Returns: -/// * A string containing the binary hash of the supplied data -static int hash_bsha256(lua_State *L) { - LuaSkin *skin = [LuaSkin sharedWithState:L]; - [skin checkArgs:LS_TSTRING, LS_TBREAK]; - - NSData *dataIn = [skin toNSObjectAtIndex:1 withOptions:LS_NSLuaStringAsDataOnly]; +/// * the hash object +/// +/// Notes: +/// * a hash that has been finished can no longer have data appended to it. +static int hash_finish(lua_State *L) { + LuaSkin *skin = [LuaSkin sharedWithState:L] ; + [skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ; + HSHashObject *object = [skin toNSObjectAtIndex:1] ; - NSMutableData *macOut = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; - CC_SHA256(dataIn.bytes, (CC_LONG)dataIn.length, macOut.mutableBytes); + if (!object.value) [object finish] ; - [skin pushNSObject:macOut]; - return 1; + lua_pushvalue(L, 1) ; + return 1 ; } -/// hs.hash.bSHA512(data) -> data -/// Function -/// Calculates a binary SHA512 hash +/// hs.hash:value([binary]) -> string | nil +/// Method +/// Returns the value of a completed hash, or nil if it is still in progress. /// /// Parameters: -/// * data - A string containing some data to hash +/// * `binary` - an optional boolean, default false, specifying whether or not the value should be provided as raw binary bytes (true) or as a string of hexadecimal numbers (false). /// /// Returns: -/// * A string containing the binary hash of the supplied data -static int hash_bsha512(lua_State *L) { - LuaSkin *skin = [LuaSkin sharedWithState:L]; - [skin checkArgs:LS_TSTRING, LS_TBREAK]; - - NSData *dataIn = [skin toNSObjectAtIndex:1 withOptions:LS_NSLuaStringAsDataOnly]; - - NSMutableData *macOut = [NSMutableData dataWithLength:CC_SHA512_DIGEST_LENGTH]; - CC_SHA512(dataIn.bytes, (CC_LONG)dataIn.length, macOut.mutableBytes); - - [skin pushNSObject:macOut]; - return 1; +/// * a string containing the hash value or nil if the hash has not been finished. +static int hash_value(lua_State *L) { + LuaSkin *skin = [LuaSkin sharedWithState:L] ; + [skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK] ; + HSHashObject *object = [skin toNSObjectAtIndex:1] ; + BOOL inBinary = (lua_gettop(L) == 2) ? (BOOL)(lua_toboolean(L, 2)) : NO ; + + if (object.value) { + if (inBinary) { + [skin pushNSObject:object.value] ; + } else { + NSMutableString* asHex = [NSMutableString stringWithCapacity:(object.value.length * 2)] ; + [object.value enumerateByteRangesUsingBlock:^(const void *bytes, NSRange range, __unused BOOL *stop) { + for (NSUInteger i = 0; i < range.length; ++i) { + [asHex appendFormat:@"%02x", ((const uint8_t*)bytes)[i]]; + } + }]; + [skin pushNSObject:asHex] ; + } + } else { + lua_pushnil(L) ; + } + return 1 ; } -/// hs.hash.bMD5(data) -> data -/// Function -/// Calculates a binary MD5 hash +/// hs.hash:type() -> string +/// Method +/// Returns the name of the hash type the object refers to /// /// Parameters: -/// * data - A string containing some data to hash +/// * None /// /// Returns: -/// * A string containing the binary hash of the supplied data -static int hash_bmd5(lua_State *L) { - LuaSkin *skin = [LuaSkin sharedWithState:L]; - [skin checkArgs:LS_TSTRING, LS_TBREAK]; +/// * a string containing the hash type name. +static int hash_type(lua_State *L) { + LuaSkin *skin = [LuaSkin sharedWithState:L] ; + [skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ; + HSHashObject *object = [skin toNSObjectAtIndex:1] ; + lua_pushstring(L, hashLookupTable[object.hashType].hashName) ; + return 1 ; +} - NSData *dataIn = [skin toNSObjectAtIndex:1 withOptions:LS_NSLuaStringAsDataOnly]; +#pragma mark - Module Constants - NSMutableData *macOut = [NSMutableData dataWithLength:CC_MD5_DIGEST_LENGTH]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CC_MD5(dataIn.bytes, (CC_LONG)dataIn.length, macOut.mutableBytes); -#pragma clang diagnostic pop +// documented in hash.lua +static int hash_types(lua_State *L) { + lua_newtable(L) ; + for (NSUInteger i = 0 ; i < knownHashCount ; i++) { + lua_pushstring(L, hashLookupTable[i].hashName) ; + lua_rawseti(L, -2, luaL_len(L, -2) + 1) ; + } + return 1 ; +} - [skin pushNSObject:macOut]; +#pragma mark - Lua<->NSObject Conversion Functions +// These must not throw a lua error to ensure LuaSkin can safely be used from Objective-C +// delegates and blocks. + +static int pushHSHashObject(lua_State *L, id obj) { + HSHashObject *value = obj; + value.selfRefCount++ ; + void** valuePtr = lua_newuserdata(L, sizeof(HSHashObject *)); + *valuePtr = (__bridge_retained void *)value; + luaL_getmetatable(L, USERDATA_TAG); + lua_setmetatable(L, -2); return 1; } -/// hs.hash.hmacSHA1(key, data) -> string -/// Function -/// Calculates an HMAC using a key and a SHA1 hash -/// -/// Parameters: -/// * key - A string containing a secret key to use -/// * data - A string containing the data to hash -/// -/// Returns: -/// * A string containing the hash of the supplied data -static int hash_sha1_hmac(lua_State *L) { - return doHashHMAC(L, kCCHmacAlgSHA1, CC_SHA1_DIGEST_LENGTH); +static id toHSHashObjectFromLua(lua_State *L, int idx) { + LuaSkin *skin = [LuaSkin sharedWithState:L] ; + HSHashObject *value ; + if (luaL_testudata(L, idx, USERDATA_TAG)) { + value = get_objectFromUserdata(__bridge HSHashObject, L, idx, USERDATA_TAG) ; + } else { + [skin logError:[NSString stringWithFormat:@"expected %s object, found %s", USERDATA_TAG, + lua_typename(L, lua_type(L, idx))]] ; + } + return value ; } -/// hs.hash.hmacSHA256(key, data) -> string -/// Function -/// Calculates an HMAC using a key and a SHA256 hash -/// -/// Parameters: -/// * key - A string containing a secret key to use -/// * data - A string containing the data to hash -/// -/// Returns: -/// * A string containing the hash of the supplied data -static int hash_sha256_hmac(lua_State *L) { - return doHashHMAC(L, kCCHmacAlgSHA256, CC_SHA256_DIGEST_LENGTH); +#pragma mark - Hammerspoon/Lua Infrastructure + +static int userdata_tostring(lua_State* L) { + LuaSkin *skin = [LuaSkin sharedWithState:L] ; + HSHashObject *obj = [skin luaObjectAtIndex:1 toClass:"HSHashObject"] ; + NSString *title = [NSString stringWithFormat:@"%s", hashLookupTable[obj.hashType].hashName] ; + if (!obj.value) { + title = [NSString stringWithFormat:@"%@ ", title]; + } + [skin pushNSObject:[NSString stringWithFormat:@"%s: %@ (%p)", USERDATA_TAG, title, lua_topointer(L, 1)]] ; + return 1 ; } -/// hs.hash.hmacSHA512(key, data) -> string -/// Function -/// Calculates an HMAC using a key and a SHA512 hash -/// -/// Parameters: -/// * key - A string containing a secret key to use -/// * data - A string containing the data to hash -/// -/// Returns: -/// * A string containing the hash of the supplied data -static int hash_sha512_hmac(lua_State *L) { - return doHashHMAC(L, kCCHmacAlgSHA512, CC_SHA512_DIGEST_LENGTH); +static int userdata_eq(lua_State* L) { +// can't get here if at least one of us isn't a userdata type, and we only care if both types are ours, +// so use luaL_testudata before the macro causes a lua error + if (luaL_testudata(L, 1, USERDATA_TAG) && luaL_testudata(L, 2, USERDATA_TAG)) { + LuaSkin *skin = [LuaSkin sharedWithState:L] ; + HSHashObject *obj1 = [skin luaObjectAtIndex:1 toClass:"HSHashObject"] ; + HSHashObject *obj2 = [skin luaObjectAtIndex:2 toClass:"HSHashObject"] ; + lua_pushboolean(L, [obj1 isEqualTo:obj2]) ; + } else { + lua_pushboolean(L, NO) ; + } + return 1 ; } -/// hs.hash.hmacMD5(key, data) -> string -/// Function -/// Calculates an HMAC using a key and an MD5 hash -/// -/// Parameters: -/// * key - A string containing a secret key to use -/// * data - A string containing the data to hash -/// -/// Returns: -/// * A string containing the hash of the supplied data -static int hash_md5_hmac(lua_State *L) { - return doHashHMAC(L, kCCHmacAlgMD5, CC_MD5_DIGEST_LENGTH); +static int userdata_gc(lua_State* L) { + HSHashObject *obj = get_objectFromUserdata(__bridge_transfer HSHashObject, L, 1, USERDATA_TAG) ; + if (obj) { + obj. selfRefCount-- ; + if (obj.selfRefCount == 0) { + if (obj.context) [obj finish] ; + obj = nil ; + } + } + // Remove the Metatable so future use of the variable in Lua won't think its valid + lua_pushnil(L) ; + lua_setmetatable(L, 1) ; + return 0 ; } -static const luaL_Reg hashlib[] = { - {"SHA1", hash_sha1}, - {"SHA256", hash_sha256}, - {"SHA512", hash_sha512}, - {"MD5", hash_md5}, +// static int meta_gc(lua_State* __unused L) { +// return 0 ; +// } + +// Metatable for userdata objects +static const luaL_Reg userdata_metaLib[] = { + {"append", hash_append}, + {"appendFile", hash_appendFile}, + {"finish", hash_finish}, + {"value", hash_value}, + {"type", hash_type}, + + {"__tostring", userdata_tostring}, + {"__eq", userdata_eq}, + {"__gc", userdata_gc}, + {NULL, NULL} +}; - {"bSHA1", hash_bsha1}, - {"bSHA256", hash_bsha256}, - {"bSHA512", hash_bsha512}, - {"bMD5", hash_bmd5}, +// Functions for returned object when module loads +static luaL_Reg moduleLib[] = { + {"new", hash_new}, + {NULL, NULL} +}; - {"hmacSHA1", hash_sha1_hmac}, - {"hmacSHA256", hash_sha256_hmac}, - {"hmacSHA512", hash_sha512_hmac}, - {"hmacMD5", hash_md5_hmac}, +// // Metatable for module, if needed +// static const luaL_Reg module_metaLib[] = { +// {"__gc", meta_gc}, +// {NULL, NULL} +// }; - {NULL, NULL} -}; +int luaopen_hs_libhash(lua_State* L) { + LuaSkin *skin = [LuaSkin sharedWithState:L] ; + refTable = [skin registerLibraryWithObject:USERDATA_TAG + functions:moduleLib + metaFunctions:nil // or module_metaLib + objectFunctions:userdata_metaLib]; -/* NOTE: The substring "hs_hash_internal" in the following function's name - must match the require-path of this file, i.e. "hs.hash.internal". */ + hash_types(L) ; lua_setfield(L, -2, "types") ; -int luaopen_hs_libhash(lua_State *L) { - // Table for luaopen - LuaSkin *skin = [LuaSkin sharedWithState:L]; - [skin registerLibrary:"hs.hash" functions:hashlib metaFunctions:nil]; + [skin registerPushNSHelper:pushHSHashObject forClass:"HSHashObject"]; + [skin registerLuaObjectHelper:toHSHashObjectFromLua forClass:"HSHashObject" + withUserdataMapping:USERDATA_TAG]; return 1; } diff --git a/extensions/hash/sha3.h b/extensions/hash/sha3.h new file mode 100644 index 000000000..3da389950 --- /dev/null +++ b/extensions/hash/sha3.h @@ -0,0 +1,74 @@ +// Modified from code at https://github.com/rhash/RHash + +/* sha3.h */ +#ifndef RHASH_SHA3_H +#define RHASH_SHA3_H +// #include "ustd.h" + +@import Cocoa ; + +#ifdef __cplusplus +extern "C" { +#endif + +#define sha3_224_hash_size 28 +#define sha3_256_hash_size 32 +#define sha3_384_hash_size 48 +#define sha3_512_hash_size 64 +#define sha3_max_permutation_size 25 +#define sha3_max_rate_in_qwords 24 + +/** + * SHA3 Algorithm context. + */ +typedef struct sha3_ctx +{ + /* 1600 bits algorithm hashing state */ + uint64_t hash[sha3_max_permutation_size]; + /* 1536-bit buffer for leftovers */ + uint64_t message[sha3_max_rate_in_qwords]; + /* count of bytes in the message[] buffer */ + unsigned rest; + /* size of a message block processed at once */ + unsigned block_size; +} sha3_ctx; + +/* methods for calculating the hash function */ + +void rhash_sha3_224_init(sha3_ctx* ctx); +void rhash_sha3_256_init(sha3_ctx* ctx); +void rhash_sha3_384_init(sha3_ctx* ctx); +void rhash_sha3_512_init(sha3_ctx* ctx); +void rhash_sha3_update(sha3_ctx* ctx, const unsigned char* msg, size_t size); +void rhash_sha3_final(sha3_ctx* ctx, unsigned char* result); + +#ifdef USE_KECCAK +#define rhash_keccak_224_init rhash_sha3_224_init +#define rhash_keccak_256_init rhash_sha3_256_init +#define rhash_keccak_384_init rhash_sha3_384_init +#define rhash_keccak_512_init rhash_sha3_512_init +#define rhash_keccak_update rhash_sha3_update +void rhash_keccak_final(sha3_ctx* ctx, unsigned char* result); +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#pragma mark - Interface functions for Hammerspoon's hs.hash module + +extern void *init_SHA3_224(NSData *key) ; +extern NSData *finish_SHA3_224(void *_context) ; + +extern void *init_SHA3_256(NSData *key) ; +extern NSData *finish_SHA3_256(void *_context) ; + +extern void *init_SHA3_384(NSData *key) ; +extern NSData *finish_SHA3_384(void *_context) ; + +extern void *init_SHA3_512(NSData *key) ; +extern NSData *finish_SHA3_512(void *_context) ; + +extern void append_SHA3(void *_context, NSData *data) ; + +#endif /* RHASH_SHA3_H */ diff --git a/extensions/hash/sha3.m b/extensions/hash/sha3.m new file mode 100644 index 000000000..a7ac669d1 --- /dev/null +++ b/extensions/hash/sha3.m @@ -0,0 +1,429 @@ +// Modified from code at https://github.com/rhash/RHash + +/* sha3.c - an implementation of Secure Hash Algorithm 3 (Keccak). + * based on the + * The Keccak SHA-3 submission. Submission to NIST (Round 3), 2011 + * by Guido Bertoni, Joan Daemen, Michaƫl Peeters and Gilles Van Assche + * + * Copyright (c) 2013, Aleksey Kravchenko + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +// #include +#include "byte_order.h" +#include "sha3.h" + +/* constants */ +#define NumberOfRounds 24 + +/* SHA3 (Keccak) constants for 24 rounds */ +static uint64_t keccak_round_constants[NumberOfRounds] = { + I64(0x0000000000000001), I64(0x0000000000008082), I64(0x800000000000808A), I64(0x8000000080008000), + I64(0x000000000000808B), I64(0x0000000080000001), I64(0x8000000080008081), I64(0x8000000000008009), + I64(0x000000000000008A), I64(0x0000000000000088), I64(0x0000000080008009), I64(0x000000008000000A), + I64(0x000000008000808B), I64(0x800000000000008B), I64(0x8000000000008089), I64(0x8000000000008003), + I64(0x8000000000008002), I64(0x8000000000000080), I64(0x000000000000800A), I64(0x800000008000000A), + I64(0x8000000080008081), I64(0x8000000000008080), I64(0x0000000080000001), I64(0x8000000080008008) +}; + +/* Initializing a sha3 context for given number of output bits */ +static void rhash_keccak_init(sha3_ctx* ctx, unsigned bits) +{ + /* NB: The Keccak capacity parameter = bits * 2 */ + unsigned rate = 1600 - bits * 2; + + memset(ctx, 0, sizeof(sha3_ctx)); + ctx->block_size = rate / 8; + assert(rate <= 1600 && (rate % 64) == 0); +} + +/** + * Initialize context before calculating hash. + * + * @param ctx context to initialize + */ +void rhash_sha3_224_init(sha3_ctx* ctx) +{ + rhash_keccak_init(ctx, 224); +} + +/** + * Initialize context before calculating hash. + * + * @param ctx context to initialize + */ +void rhash_sha3_256_init(sha3_ctx* ctx) +{ + rhash_keccak_init(ctx, 256); +} + +/** + * Initialize context before calculating hash. + * + * @param ctx context to initialize + */ +void rhash_sha3_384_init(sha3_ctx* ctx) +{ + rhash_keccak_init(ctx, 384); +} + +/** + * Initialize context before calculating hash. + * + * @param ctx context to initialize + */ +void rhash_sha3_512_init(sha3_ctx* ctx) +{ + rhash_keccak_init(ctx, 512); +} + +#define XORED_A(i) A[(i)] ^ A[(i) + 5] ^ A[(i) + 10] ^ A[(i) + 15] ^ A[(i) + 20] +#define THETA_STEP(i) \ + A[(i)] ^= D[(i)]; \ + A[(i) + 5] ^= D[(i)]; \ + A[(i) + 10] ^= D[(i)]; \ + A[(i) + 15] ^= D[(i)]; \ + A[(i) + 20] ^= D[(i)] \ + +/* Keccak theta() transformation */ +static void keccak_theta(uint64_t* A) +{ + uint64_t D[5]; + D[0] = ROTL64(XORED_A(1), 1) ^ XORED_A(4); + D[1] = ROTL64(XORED_A(2), 1) ^ XORED_A(0); + D[2] = ROTL64(XORED_A(3), 1) ^ XORED_A(1); + D[3] = ROTL64(XORED_A(4), 1) ^ XORED_A(2); + D[4] = ROTL64(XORED_A(0), 1) ^ XORED_A(3); + THETA_STEP(0); + THETA_STEP(1); + THETA_STEP(2); + THETA_STEP(3); + THETA_STEP(4); +} + +/* Keccak pi() transformation */ +static void keccak_pi(uint64_t* A) +{ + uint64_t A1; + A1 = A[1]; + A[ 1] = A[ 6]; + A[ 6] = A[ 9]; + A[ 9] = A[22]; + A[22] = A[14]; + A[14] = A[20]; + A[20] = A[ 2]; + A[ 2] = A[12]; + A[12] = A[13]; + A[13] = A[19]; + A[19] = A[23]; + A[23] = A[15]; + A[15] = A[ 4]; + A[ 4] = A[24]; + A[24] = A[21]; + A[21] = A[ 8]; + A[ 8] = A[16]; + A[16] = A[ 5]; + A[ 5] = A[ 3]; + A[ 3] = A[18]; + A[18] = A[17]; + A[17] = A[11]; + A[11] = A[ 7]; + A[ 7] = A[10]; + A[10] = A1; + /* note: A[ 0] is left as is */ +} + +#define CHI_STEP(i) \ + A0 = A[0 + (i)]; \ + A1 = A[1 + (i)]; \ + A[0 + (i)] ^= ~A1 & A[2 + (i)]; \ + A[1 + (i)] ^= ~A[2 + (i)] & A[3 + (i)]; \ + A[2 + (i)] ^= ~A[3 + (i)] & A[4 + (i)]; \ + A[3 + (i)] ^= ~A[4 + (i)] & A0; \ + A[4 + (i)] ^= ~A0 & A1 \ + +/* Keccak chi() transformation */ +static void keccak_chi(uint64_t* A) +{ + uint64_t A0, A1; + CHI_STEP(0); + CHI_STEP(5); + CHI_STEP(10); + CHI_STEP(15); + CHI_STEP(20); +} + +static void rhash_sha3_permutation(uint64_t* state) +{ + int round; + for (round = 0; round < NumberOfRounds; round++) + { + keccak_theta(state); + + /* apply Keccak rho() transformation */ + state[ 1] = ROTL64(state[ 1], 1); + state[ 2] = ROTL64(state[ 2], 62); + state[ 3] = ROTL64(state[ 3], 28); + state[ 4] = ROTL64(state[ 4], 27); + state[ 5] = ROTL64(state[ 5], 36); + state[ 6] = ROTL64(state[ 6], 44); + state[ 7] = ROTL64(state[ 7], 6); + state[ 8] = ROTL64(state[ 8], 55); + state[ 9] = ROTL64(state[ 9], 20); + state[10] = ROTL64(state[10], 3); + state[11] = ROTL64(state[11], 10); + state[12] = ROTL64(state[12], 43); + state[13] = ROTL64(state[13], 25); + state[14] = ROTL64(state[14], 39); + state[15] = ROTL64(state[15], 41); + state[16] = ROTL64(state[16], 45); + state[17] = ROTL64(state[17], 15); + state[18] = ROTL64(state[18], 21); + state[19] = ROTL64(state[19], 8); + state[20] = ROTL64(state[20], 18); + state[21] = ROTL64(state[21], 2); + state[22] = ROTL64(state[22], 61); + state[23] = ROTL64(state[23], 56); + state[24] = ROTL64(state[24], 14); + + keccak_pi(state); + keccak_chi(state); + + /* apply iota(state, round) */ + *state ^= keccak_round_constants[round]; + } +} + +/** + * The core transformation. Process the specified block of data. + * + * @param hash the algorithm state + * @param block the message block to process + * @param block_size the size of the processed block in bytes + */ +static void rhash_sha3_process_block(uint64_t hash[25], const uint64_t* block, size_t block_size) +{ + /* expanded loop */ + hash[ 0] ^= le2me_64(block[ 0]); + hash[ 1] ^= le2me_64(block[ 1]); + hash[ 2] ^= le2me_64(block[ 2]); + hash[ 3] ^= le2me_64(block[ 3]); + hash[ 4] ^= le2me_64(block[ 4]); + hash[ 5] ^= le2me_64(block[ 5]); + hash[ 6] ^= le2me_64(block[ 6]); + hash[ 7] ^= le2me_64(block[ 7]); + hash[ 8] ^= le2me_64(block[ 8]); + /* if not sha3-512 */ + if (block_size > 72) { + hash[ 9] ^= le2me_64(block[ 9]); + hash[10] ^= le2me_64(block[10]); + hash[11] ^= le2me_64(block[11]); + hash[12] ^= le2me_64(block[12]); + /* if not sha3-384 */ + if (block_size > 104) { + hash[13] ^= le2me_64(block[13]); + hash[14] ^= le2me_64(block[14]); + hash[15] ^= le2me_64(block[15]); + hash[16] ^= le2me_64(block[16]); + /* if not sha3-256 */ + if (block_size > 136) { + hash[17] ^= le2me_64(block[17]); +#ifdef FULL_SHA3_FAMILY_SUPPORT + /* if not sha3-224 */ + if (block_size > 144) { + hash[18] ^= le2me_64(block[18]); + hash[19] ^= le2me_64(block[19]); + hash[20] ^= le2me_64(block[20]); + hash[21] ^= le2me_64(block[21]); + hash[22] ^= le2me_64(block[22]); + hash[23] ^= le2me_64(block[23]); + hash[24] ^= le2me_64(block[24]); + } +#endif + } + } + } + /* make a permutation of the hash */ + rhash_sha3_permutation(hash); +} + +#define SHA3_FINALIZED 0x80000000 + +/** + * Calculate message hash. + * Can be called repeatedly with chunks of the message to be hashed. + * + * @param ctx the algorithm context containing current hashing state + * @param msg message chunk + * @param size length of the message chunk + */ +void rhash_sha3_update(sha3_ctx* ctx, const unsigned char* msg, size_t size) +{ + size_t index = (size_t)ctx->rest; + size_t block_size = (size_t)ctx->block_size; + + if (ctx->rest & SHA3_FINALIZED) return; /* too late for additional input */ + ctx->rest = (unsigned)((ctx->rest + size) % block_size); + + /* fill partial block */ + if (index) { + size_t left = block_size - index; + memcpy((char*)ctx->message + index, msg, (size < left ? size : left)); + if (size < left) return; + + /* process partial block */ + rhash_sha3_process_block(ctx->hash, ctx->message, block_size); + msg += left; + size -= left; + } + while (size >= block_size) { + const uint64_t* aligned_message_block; + if (IS_ALIGNED_64(msg)) { + /* the most common case is processing of an already aligned message + without copying it */ + aligned_message_block = (const uint64_t*)((const void *)msg); + } else { + memcpy(ctx->message, msg, block_size); + aligned_message_block = ctx->message; + } + + rhash_sha3_process_block(ctx->hash, aligned_message_block, block_size); + msg += block_size; + size -= block_size; + } + if (size) { + memcpy(ctx->message, msg, size); /* save leftovers */ + } +} + +/** + * Store calculated hash into the given array. + * + * @param ctx the algorithm context containing current hashing state + * @param result calculated hash in binary form + */ +void rhash_sha3_final(sha3_ctx* ctx, unsigned char* result) +{ + size_t digest_length = 100 - ctx->block_size / 2; + const size_t block_size = ctx->block_size; + + if (!(ctx->rest & SHA3_FINALIZED)) + { + /* clear the rest of the data queue */ + memset((char*)ctx->message + ctx->rest, 0, block_size - ctx->rest); + ((char*)ctx->message)[ctx->rest] |= 0x06; + ((char*)ctx->message)[block_size - 1] |= 0x80; + + /* process final block */ + rhash_sha3_process_block(ctx->hash, ctx->message, block_size); + ctx->rest = SHA3_FINALIZED; /* mark context as finalized */ + } + + assert(block_size > digest_length); + if (result) me64_to_le_str(result, ctx->hash, digest_length); +} + +#ifdef USE_KECCAK +/** +* Store calculated hash into the given array. +* +* @param ctx the algorithm context containing current hashing state +* @param result calculated hash in binary form +*/ +void rhash_keccak_final(sha3_ctx* ctx, unsigned char* result) +{ + size_t digest_length = 100 - ctx->block_size / 2; + const size_t block_size = ctx->block_size; + + if (!(ctx->rest & SHA3_FINALIZED)) + { + /* clear the rest of the data queue */ + memset((char*)ctx->message + ctx->rest, 0, block_size - ctx->rest); + ((char*)ctx->message)[ctx->rest] |= 0x01; + ((char*)ctx->message)[block_size - 1] |= 0x80; + + /* process final block */ + rhash_sha3_process_block(ctx->hash, ctx->message, block_size); + ctx->rest = SHA3_FINALIZED; /* mark context as finalized */ + } + + assert(block_size > digest_length); + if (result) me64_to_le_str(result, ctx->hash, digest_length); +} +#endif /* USE_KECCAK */ + +#pragma mark - Interface functions for Hammerspoon's hs.hash module + +void *init_SHA3_224(__unused NSData *key) { + sha3_ctx *_context = malloc(sizeof(sha3_ctx)) ; + rhash_sha3_224_init(_context) ; + return _context ; +} + +NSData *finish_SHA3_224(void *_context) { + unsigned char *md = malloc(sha3_224_hash_size) ; + rhash_sha3_final((sha3_ctx *)_context, md) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:sha3_224_hash_size] ; +} + +void *init_SHA3_256(__unused NSData *key) { + sha3_ctx *_context = malloc(sizeof(sha3_ctx)) ; + rhash_sha3_256_init(_context) ; + return _context ; +} + +NSData *finish_SHA3_256(void *_context) { + unsigned char *md = malloc(sha3_256_hash_size) ; + rhash_sha3_final((sha3_ctx *)_context, md) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:sha3_256_hash_size] ; +} + +void *init_SHA3_384(__unused NSData *key) { + sha3_ctx *_context = malloc(sizeof(sha3_ctx)) ; + rhash_sha3_384_init(_context) ; + return _context ; +} + +NSData *finish_SHA3_384(void *_context) { + unsigned char *md = malloc(sha3_384_hash_size) ; + rhash_sha3_final((sha3_ctx *)_context, md) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:sha3_384_hash_size] ; +} + +void *init_SHA3_512(__unused NSData *key) { + sha3_ctx *_context = malloc(sizeof(sha3_ctx)) ; + rhash_sha3_512_init(_context) ; + return _context ; +} + +NSData *finish_SHA3_512(void *_context) { + unsigned char *md = malloc(sha3_512_hash_size) ; + rhash_sha3_final((sha3_ctx *)_context, md) ; + free(_context) ; + _context = NULL ; + return [NSData dataWithBytesNoCopy:md length:sha3_512_hash_size] ; +} + +#pragma mark * SHA3 Common + +void append_SHA3(void *_context, NSData *data) { + rhash_sha3_update((sha3_ctx *)_context, data.bytes, data.length) ; +} +