From 1e569c68a1baa2a7433c938cb5f9df994e49c014 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Fri, 3 Oct 2025 01:49:44 +0100 Subject: [PATCH] Add SHA-1 checksum calculation support --- CMakeLists.txt | 4 +- include/aaruformat/context.h | 3 + include/aaruformat/decls.h | 6 ++ include/sha1.h | 53 ++++++++++++ src/close.c | 7 +- src/create.c | 5 ++ src/sha1.c | 161 +++++++++++++++++++++++++++++++++++ src/write.c | 5 ++ tests/CMakeLists.txt | 2 +- tests/sha1.cpp | 96 +++++++++++++++++++++ 10 files changed, 339 insertions(+), 3 deletions(-) create mode 100644 include/sha1.h create mode 100644 src/sha1.c create mode 100644 tests/sha1.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d38c946..5c2ea58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,7 +127,9 @@ add_library(aaruformat SHARED include/aaruformat/consts.h include/aaruformat/enu src/ddt/hash_map.c include/aaruformat/hash_map.h src/md5.c - include/md5.h) + include/md5.h + src/sha1.c + include/sha1.h) include_directories(include include/aaruformat 3rdparty/uthash/include 3rdparty/uthash/src) diff --git a/include/aaruformat/context.h b/include/aaruformat/context.h index cbf87de..889bbbb 100644 --- a/include/aaruformat/context.h +++ b/include/aaruformat/context.h @@ -23,6 +23,7 @@ #include "hash_map.h" #include "lru.h" #include "md5.h" +#include "sha1.h" #include "structs.h" #include "utarray.h" @@ -215,6 +216,8 @@ typedef struct aaruformatContext uint64_t last_written_block; ///< Last written block number (write path). bool calculating_md5; ///< True if whole-image MD5 being calculated on-the-fly. md5_ctx md5_context; ///< Opaque MD5 context for streaming updates + bool calculating_sha1; ///< True if whole-image SHA-1 being calculated on-the-fly. + sha1_ctx sha1_context; ///< Opaque SHA-1 context for streaming updates } aaruformatContext; /** \struct DumpHardwareEntriesWithData diff --git a/include/aaruformat/decls.h b/include/aaruformat/decls.h index c7bbe0c..39db3da 100644 --- a/include/aaruformat/decls.h +++ b/include/aaruformat/decls.h @@ -21,6 +21,7 @@ #include "crc64.h" #include "md5.h" +#include "sha1.h" #include "simd.h" #include "spamsum.h" #ifdef __cplusplus @@ -163,6 +164,11 @@ AARU_EXPORT void AARU_CALL aaruf_md5_update(md5_ctx *ctx, const void *data, unsi AARU_EXPORT void AARU_CALL aaruf_md5_final(md5_ctx *ctx, unsigned char *result); AARU_EXPORT void AARU_CALL aaruf_md5_buffer(const void *data, unsigned long size, unsigned char *result); +AARU_EXPORT void AARU_CALL aaruf_sha1_init(sha1_ctx *ctx); +AARU_EXPORT void AARU_CALL aaruf_sha1_update(sha1_ctx *ctx, const void *data, unsigned long size); +AARU_EXPORT void AARU_CALL aaruf_sha1_final(sha1_ctx *ctx, unsigned char *result); +AARU_EXPORT void AARU_CALL aaruf_sha1_buffer(const void *data, unsigned long size, unsigned char *result); + #if defined(__x86_64__) || defined(__amd64) || defined(_M_AMD64) || defined(_M_X64) || defined(__I386__) || \ defined(__i386__) || defined(__THW_INTEL) || defined(_M_IX86) diff --git a/include/sha1.h b/include/sha1.h new file mode 100644 index 0000000..0863d94 --- /dev/null +++ b/include/sha1.h @@ -0,0 +1,53 @@ +/* + * Public domain or MIT-licensed SHA-1 implementation for libaaruformat. + * + * Based on a clean-room implementation referencing the FIPS PUB 180-1 specification. + * + * This code is released into the public domain. If that is not recognized + * in your jurisdiction, you may treat it under the terms of the MIT license: + * + * MIT License + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef LIBAARUFORMAT_SHA1_H +#define LIBAARUFORMAT_SHA1_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifndef SHA1_DIGEST_LENGTH +#define SHA1_DIGEST_LENGTH 20 +#endif + + typedef struct + { + uint32_t state[5]; /* A,B,C,D,E */ + uint64_t count; /* Bits processed */ + uint8_t buffer[64]; + } sha1_ctx; + +#ifdef __cplusplus +} +#endif + +#endif /* LIBAARUFORMAT_SHA1_H */ diff --git a/src/close.c b/src/close.c index 7ba479a..ce65d83 100644 --- a/src/close.c +++ b/src/close.c @@ -506,6 +506,11 @@ int aaruf_close(void *context) ctx->checksums.hasMd5 = true; aaruf_md5_final(&ctx->md5_context, ctx->checksums.md5); } + if(ctx->calculating_sha1) + { + ctx->checksums.hasSha1 = true; + aaruf_sha1_final(&ctx->sha1_context, ctx->checksums.sha1); + } // Write the checksums block bool has_checksums = @@ -562,7 +567,7 @@ int aaruf_close(void *context) sha256_entry.type = Sha256; fwrite(&sha256_entry, sizeof(ChecksumEntry), 1, ctx->imageStream); fwrite(&ctx->checksums.sha256, SHA256_DIGEST_LENGTH, 1, ctx->imageStream); - checksum_header.length += sizeof(ChecksumEntry) + SHA1_DIGEST_LENGTH; + checksum_header.length += sizeof(ChecksumEntry) + SHA256_DIGEST_LENGTH; checksum_header.entries++; } diff --git a/src/create.c b/src/create.c index 0e072d7..cd57e0f 100644 --- a/src/create.c +++ b/src/create.c @@ -315,6 +315,11 @@ void *aaruf_create(const char *filepath, const uint32_t media_type, const uint32 ctx->calculating_md5 = true; aaruf_md5_init(&ctx->md5_context); } + if(parsed_options.sha1) + { + ctx->calculating_sha1 = true; + aaruf_sha1_init(&ctx->sha1_context); + } // Is writing ctx->isWriting = true; diff --git a/src/sha1.c b/src/sha1.c new file mode 100644 index 0000000..ec690ff --- /dev/null +++ b/src/sha1.c @@ -0,0 +1,161 @@ +/* + * Public domain / MIT SHA-1 implementation for libaaruformat. + * + * This implementation is derived from the FIPS PUB 180-1 specification and + * other public domain reference implementations; released as public domain. + * If public domain dedication is not recognized, you may use it under the + * MIT license terms (see header in sha1.h). + */ +#include +#include +#include +#include + +#include "decls.h" +#include "sha1.h" + +#ifndef AARU_LOCAL +#define AARU_LOCAL static +#endif + +/* Rotate-left 32-bit */ +#if defined(_MSC_VER) +#define ROTL32(v, n) _rotl(v, n) +#else +#define ROTL32(v, n) (((v) << (n)) | ((v) >> (32 - (n)))) +#endif + +/* SHA-1 logical functions */ +#define F0(b, c, d) (((b) & (c)) | (~(b) & (d))) +#define F1(b, c, d) ((b) ^ (c) ^ (d)) +#define F2(b, c, d) (((b) & (c)) | ((b) & (d)) | ((c) & (d))) +#define F3(b, c, d) ((b) ^ (c) ^ (d)) + +AARU_EXPORT void AARU_CALL aaruf_sha1_init(sha1_ctx *ctx) +{ + ctx->state[0] = 0x67452301UL; + ctx->state[1] = 0xEFCDAB89UL; + ctx->state[2] = 0x98BADCFEUL; + ctx->state[3] = 0x10325476UL; + ctx->state[4] = 0xC3D2E1F0UL; + ctx->count = 0ULL; + memset(ctx->buffer, 0, sizeof(ctx->buffer)); +} + +AARU_LOCAL void sha1_transform(uint32_t state[5], const uint8_t block[64]) +{ + uint32_t W[80]; + + /* Prepare message schedule */ + for(int t = 0; t < 16; t++) + { + W[t] = ((uint32_t)block[t * 4] << 24) | ((uint32_t)block[t * 4 + 1] << 16) | ((uint32_t)block[t * 4 + 2] << 8) | + ((uint32_t)block[t * 4 + 3]); + } + for(int t = 16; t < 80; t++) { W[t] = ROTL32(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1); } + + uint32_t a = state[0]; + uint32_t b = state[1]; + uint32_t c = state[2]; + uint32_t d = state[3]; + uint32_t e = state[4]; + + for(int t = 0; t < 80; t++) + { + uint32_t temp; + if(t < 20) + temp = ROTL32(a, 5) + F0(b, c, d) + e + W[t] + 0x5A827999UL; + else if(t < 40) + temp = ROTL32(a, 5) + F1(b, c, d) + e + W[t] + 0x6ED9EBA1UL; + else if(t < 60) + temp = ROTL32(a, 5) + F2(b, c, d) + e + W[t] + 0x8F1BBCDCUL; + else + temp = ROTL32(a, 5) + F3(b, c, d) + e + W[t] + 0xCA62C1D6UL; + + e = d; + d = c; + c = ROTL32(b, 30); + b = a; + a = temp; + } + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; +} + +AARU_EXPORT void AARU_CALL aaruf_sha1_update(sha1_ctx *ctx, const void *data_ptr, unsigned long len) +{ + const uint8_t *data = (const uint8_t *)data_ptr; + if(len == 0) return; + + /* Number of bytes mod 64 currently in buffer */ + uint32_t buffer_bytes = (uint32_t)((ctx->count >> 3) & 0x3F); + + /* Update bit count */ + ctx->count += (uint64_t)len << 3; + + uint32_t free_bytes = 64 - buffer_bytes; + + if(len >= free_bytes) + { + /* Fill buffer and compress */ + memcpy(ctx->buffer + buffer_bytes, data, free_bytes); + sha1_transform(ctx->state, ctx->buffer); + data += free_bytes; + len -= free_bytes; + + /* Process direct blocks */ + while(len >= 64) + { + sha1_transform(ctx->state, data); + data += 64; + len -= 64; + } + buffer_bytes = 0; + } + + /* Buffer the remaining */ + if(len > 0) memcpy(ctx->buffer + buffer_bytes, data, len); +} + +AARU_EXPORT void AARU_CALL aaruf_sha1_final(sha1_ctx *ctx, unsigned char *digest) +{ + uint8_t pad[64]; + memset(pad, 0, sizeof(pad)); + pad[0] = 0x80; + + uint8_t length_be[8]; + uint64_t bits = ctx->count; + for(int i = 0; i < 8; i++) { length_be[7 - i] = (uint8_t)(bits >> (i * 8)); } + + /* Current bytes mod 64 */ + uint32_t buffer_bytes = (uint32_t)((ctx->count >> 3) & 0x3F); + + uint32_t pad_len = (buffer_bytes < 56) ? (56 - buffer_bytes) : (120 - buffer_bytes); + + aaruf_sha1_update(ctx, pad, pad_len); + aaruf_sha1_update(ctx, length_be, 8); + + /* Output digest big-endian */ + for(int i = 0; i < 5; i++) + { + digest[i * 4] = (uint8_t)(ctx->state[i] >> 24); + digest[i * 4 + 1] = (uint8_t)(ctx->state[i] >> 16); + digest[i * 4 + 2] = (uint8_t)(ctx->state[i] >> 8); + digest[i * 4 + 3] = (uint8_t)(ctx->state[i]); + } + + /* Wipe context (except state in case reused) */ + memset(ctx->buffer, 0, sizeof(ctx->buffer)); +} + +AARU_EXPORT void AARU_CALL aaruf_sha1_buffer(const void *data, unsigned long size, unsigned char *result) +{ + sha1_ctx ctx; + aaruf_sha1_init(&ctx); + aaruf_sha1_update(&ctx, data, size); + aaruf_sha1_final(&ctx, result); +} diff --git a/src/write.c b/src/write.c index d449872..f72e930 100644 --- a/src/write.c +++ b/src/write.c @@ -154,6 +154,8 @@ int32_t aaruf_write_sector(void *context, uint64_t sector_address, bool negative // Disable MD5 calculation if(ctx->calculating_md5) ctx->calculating_md5 = false; + // Disable SHA1 calculation + if(ctx->calculating_sha1) ctx->calculating_sha1 = false; } else ctx->last_written_block = sector_address; @@ -162,6 +164,9 @@ int32_t aaruf_write_sector(void *context, uint64_t sector_address, bool negative // Calculate MD5 on-the-fly if requested and sector is within user sectors (not negative or overflow) if(ctx->calculating_md5 && !negative && sector_address <= ctx->imageInfo.Sectors) aaruf_md5_update(&ctx->md5_context, data, length); + // Calculate SHA1 on-the-fly if requested and sector is within user sectors (not negative or overflow) + if(ctx->calculating_sha1 && !negative && sector_address <= ctx->imageInfo.Sectors) + aaruf_sha1_update(&ctx->sha1_context, data, length); // TODO: If optical disc check track diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3a452ee..7686874 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,7 +28,7 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/data.bin # 'Google_Tests_run' is the target name # 'test1.cpp tests2.cpp' are source files with tests -add_executable(tests_run crc64.cpp spamsum.cpp crc32.c crc32.h flac.cpp lzma.cpp sha256.cpp md5.cpp) +add_executable(tests_run crc64.cpp spamsum.cpp crc32.c crc32.h flac.cpp lzma.cpp sha256.cpp md5.cpp sha1.cpp) # Link libraries including OpenSSL for SHA256 test target_link_libraries(tests_run PRIVATE gtest gtest_main aaruformat) diff --git a/tests/sha1.cpp b/tests/sha1.cpp new file mode 100644 index 0000000..0d7efc3 --- /dev/null +++ b/tests/sha1.cpp @@ -0,0 +1,96 @@ +// SHA-1 tests (public domain / MIT implementation integration) +// Uses standard FIPS 180-1 test vectors. + +#include +#include +#include +#include +#include +#include + +#include "decls.h" +#include "gtest/gtest.h" +#include "sha1.h" + +static const unsigned char sha1_empty[SHA1_DIGEST_LENGTH] = {0xDA, 0x39, 0xA3, 0xEE, 0x5E, 0x6B, 0x4B, + 0x0D, 0x32, 0x55, 0xBF, 0xEF, 0x95, 0x60, + 0x18, 0x90, 0xAF, 0xD8, 0x07, 0x09}; + +static const unsigned char sha1_abc[SHA1_DIGEST_LENGTH] = {0xA9, 0x99, 0x3E, 0x36, 0x47, 0x06, 0x81, 0x6A, 0xBA, 0x3E, + 0x25, 0x71, 0x78, 0x50, 0xC2, 0x6C, 0x9C, 0xD0, 0xD8, 0x9D}; + +static const unsigned char sha1_random[SHA1_DIGEST_LENGTH] = {0x72, 0x0d, 0x3b, 0x71, 0x7d, 0xe0, 0xc7, + 0x4c, 0x77, 0xdd, 0x9c, 0xaa, 0x9e, 0xba, + 0x50, 0x60, 0xdc, 0xbd, 0x28, 0x8d}; + +#define EXPECT_ARRAY_EQ(expected, actual, len) \ + for(size_t i_ = 0; i_ < (len); ++i_) \ + EXPECT_EQ(((const unsigned char *)(expected))[i_], ((const unsigned char *)(actual))[i_]) + +TEST(SHA1, EmptyBuffer) +{ + unsigned char out[SHA1_DIGEST_LENGTH]; + aaruf_sha1_buffer("", 0, out); + EXPECT_ARRAY_EQ(sha1_empty, out, SHA1_DIGEST_LENGTH); +} + +TEST(SHA1, ABCSingleBuffer) +{ + unsigned char out[SHA1_DIGEST_LENGTH]; + const char *msg = "abc"; + aaruf_sha1_buffer(msg, 3, out); + EXPECT_ARRAY_EQ(sha1_abc, out, SHA1_DIGEST_LENGTH); +} + +TEST(SHA1, ABCStreaming) +{ + unsigned char out[SHA1_DIGEST_LENGTH]; + sha1_ctx ctx; + aaruf_sha1_init(&ctx); + const char *msg = "abc"; + for(size_t i = 0; i < 3; i++) aaruf_sha1_update(&ctx, msg + i, 1); + aaruf_sha1_final(&ctx, out); + EXPECT_ARRAY_EQ(sha1_abc, out, SHA1_DIGEST_LENGTH); +} + +TEST(SHA1, RandomFileOneShot) +{ + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/random", path); + FILE *file = fopen(filename, "rb"); + ASSERT_NE(file, nullptr); + fseek(file, 0, SEEK_END); + long sz = ftell(file); + ASSERT_GT(sz, 0); + fseek(file, 0, SEEK_SET); + unsigned char *buf = (unsigned char *)malloc((size_t)sz); + ASSERT_NE(buf, nullptr); + size_t rd = fread(buf, 1, (size_t)sz, file); + fclose(file); + ASSERT_EQ(rd, (size_t)sz); + unsigned char out[SHA1_DIGEST_LENGTH]; + aaruf_sha1_buffer(buf, (unsigned long)sz, out); + free(buf); + EXPECT_ARRAY_EQ(sha1_random, out, SHA1_DIGEST_LENGTH); +} + +TEST(SHA1, RandomFileStreaming) +{ + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/random", path); + FILE *file = fopen(filename, "rb"); + ASSERT_NE(file, nullptr); + sha1_ctx ctx; + aaruf_sha1_init(&ctx); + unsigned char chunk[8192]; + size_t rd; + while((rd = fread(chunk, 1, sizeof(chunk), file)) > 0) aaruf_sha1_update(&ctx, chunk, (unsigned long)rd); + fclose(file); + unsigned char out[SHA1_DIGEST_LENGTH]; + aaruf_sha1_final(&ctx, out); + EXPECT_ARRAY_EQ(sha1_random, out, SHA1_DIGEST_LENGTH); +}