diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 9ff9d10a..2c672152 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -52,3 +52,4 @@ target_link_libraries(clock PUBLIC ui) luna_app(startui.cpp startui) luna_app(launch.cpp launch) luna_app(run.cpp run) +luna_app(sha256sum.cpp sha256sum) diff --git a/apps/sha256sum.cpp b/apps/sha256sum.cpp new file mode 100644 index 00000000..682cd786 --- /dev/null +++ b/apps/sha256sum.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +Result luna_main(int argc, char** argv) +{ + Vector files; + + os::ArgumentParser parser; + parser.add_description("Calculate SHA256 hashes for a series of files."_sv); + parser.add_system_program_info("sha256sum"_sv); + parser.set_vector_argument(files, "files"_sv, "-"_sv); + parser.parse(argc, argv); + + for (auto path : files) + { + auto file = TRY(os::File::open_input_file(path)); + auto data = TRY(file->read_all()); + + SHA256 sha; + + TRY(sha.append(data.data(), data.size())); + auto hash = TRY(sha.digest()); + + auto string = TRY(sha.hash_to_string(hash)); + os::println("%s %s", string.chars(), path.chars()); + } + + return 0; +} diff --git a/libluna/CMakeLists.txt b/libluna/CMakeLists.txt index 405d746d..7826cda1 100644 --- a/libluna/CMakeLists.txt +++ b/libluna/CMakeLists.txt @@ -16,6 +16,7 @@ set(FREESTANDING_SOURCES src/Bitmap.cpp src/Buffer.cpp src/Scanf.cpp + src/SHA.cpp src/Stack.cpp src/String.cpp src/StringBuilder.cpp diff --git a/libluna/include/luna/SHA.h b/libluna/include/luna/SHA.h new file mode 100644 index 00000000..78fde77d --- /dev/null +++ b/libluna/include/luna/SHA.h @@ -0,0 +1,40 @@ +/** + * @file SHA.h + * @author apio (cloudapio.eu) + * @brief SHA256 algorithm implementation. + * + * @copyright Copyright (c) 2024, the Luna authors. + * + */ + +#pragma once +#include +#include + +/** + * @brief A class to calculate a SHA256 hash. + * + */ +class SHA256 +{ + public: + /** + * @brief Add data to the hash. + * + * @param data The data to add. + * @param size The amount of bytes to add. + */ + Result append(const u8* data, usize size); + + /** + * @brief Calculate the final hash. + * + * @return Buffer The calculated hash. + */ + Result digest(); + + static Result hash_to_string(const Buffer& buffer); + + private: + Buffer m_message; +}; diff --git a/libluna/src/SHA.cpp b/libluna/src/SHA.cpp new file mode 100644 index 00000000..04bcd443 --- /dev/null +++ b/libluna/src/SHA.cpp @@ -0,0 +1,185 @@ +/** + * @file SHA.cpp + * @author apio (cloudapio.eu) + * @brief SHA256 algorithm implementation. + * + * @copyright Copyright (c) 2024, the Luna authors. + * + */ + +#include +#include +#include +#include +#include +#include + +// Taken from https://gist.github.com/hak8or/8794351 +#define ROTRIGHT(word, bits) (((word) >> (bits)) | ((word) << (32 - (bits)))) + +u32 k[64] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; + +u32 static H0 = 0x6a09e667; +u32 static H1 = 0xbb67ae85; +u32 static H2 = 0x3c6ef372; +u32 static H3 = 0xa54ff53a; +u32 static H4 = 0x510e527f; +u32 static H5 = 0x9b05688c; +u32 static H6 = 0x1f83d9ab; +u32 static H7 = 0x5be0cd19; + +Result SHA256::append(const u8* data, usize size) +{ + return m_message.append_data(data, size); +} + +// Thank you to https://sha256algorithm.com/ for showing me how the SHA256 algorithm works, and effectively, how to +// implement it! +Result SHA256::digest() +{ + usize message_block_length = m_message.size() + 1 + sizeof(u64); + usize message_block_chunks = ceil_div(message_block_length, 64ul); + usize num_zeros = message_block_chunks * 64 - message_block_length; + + // Prepare a message block and add the data into it. + Buffer message_block; + TRY(message_block.append_data(m_message.data(), m_message.size())); + + // Add one bit at the end. + u8 one = 0b10000000; + TRY(message_block.append_data(&one, 1)); + + // Fill with zeros. + u8* slice = TRY(message_block.slice_at_end(num_zeros)); + memset(slice, 0, num_zeros); + + // Add the length of the original message (in bits), this has to be big-endian. + usize message_length = m_message.size() * 8; + message_length = __builtin_bswap64(message_length); // FIXME: We're assuming we're on a little-endian platform. + TRY(message_block.append_data((const u8*)&message_length, sizeof(usize))); + + // The length of the message block should now be a multiple of 512 bits (64 bytes). + check(is_aligned<64>(message_block.size())); + + u32 a, b, c, d, e, f, g, h; // working variables + u32 h0, h1, h2, h3, h4, h5, h6, h7; // hash values + + // Initialize the hash values to their starting values. + h0 = H0; + h1 = H1; + h2 = H2; + h3 = H3; + h4 = H4; + h5 = H5; + h6 = H6; + h7 = H7; + + // For each 512-bit (64-byte) chunk, perform the following operations. + for (usize i = 0; i < message_block_chunks; i++) + { + // Create a message schedule of 64 dwords, and copy the current chunk into the first 16 dwords. + u32 message_schedule[64]; + + memcpy(message_schedule, &message_block.data()[i * 64], 64); + + for (int j = 0; j < 16; j++) + { + // SHA uses big-endian. FIXME: Don't assume little-endian. + message_schedule[j] = __builtin_bswap32(message_schedule[j]); + } + + // Fill the rest of the message schedule. + for (int j = 0; j < 48; j++) + { + u32 w0 = message_schedule[j]; + u32 w1 = message_schedule[j + 1]; + u32 w14 = message_schedule[j + 14]; + u32 w9 = message_schedule[j + 9]; + + u32 s0 = ROTRIGHT(w1, 7) ^ ROTRIGHT(w1, 18) ^ (w1 >> 3); + u32 s1 = ROTRIGHT(w14, 17) ^ ROTRIGHT(w14, 19) ^ (w14 >> 10); + + message_schedule[j + 16] = w0 + s0 + w9 + s1; + } + + // Initialize the working variables with the current hash values. + a = h0; + b = h1; + c = h2; + d = h3; + e = h4; + f = h5; + g = h6; + h = h7; + + // For each dword in the message schedule, update the working variables, using this algorithm and the + // corresponding dword from the constant k[] array. + for (int j = 0; j < 64; j++) + { + u32 temp1, temp2, choice, majority; + choice = (e & f) ^ ((~e) & g); + majority = (a & b) ^ (a & c) ^ (b & c); + temp1 = h + (ROTRIGHT(e, 6) ^ ROTRIGHT(e, 11) ^ ROTRIGHT(e, 25)) + choice + k[j] + message_schedule[j]; + temp2 = (ROTRIGHT(a, 2) ^ ROTRIGHT(a, 13) ^ ROTRIGHT(a, 22)) + majority; + + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + + // Add the working variables to the hash values. + h0 += a; + h1 += b; + h2 += c; + h3 += d; + h4 += e; + h5 += f; + h6 += g; + h7 += h; + } + + // FIXME: Again, this assumes little-endian. + h0 = __builtin_bswap32(h0); + h1 = __builtin_bswap32(h1); + h2 = __builtin_bswap32(h2); + h3 = __builtin_bswap32(h3); + h4 = __builtin_bswap32(h4); + h5 = __builtin_bswap32(h5); + h6 = __builtin_bswap32(h6); + h7 = __builtin_bswap32(h7); + + Buffer result; + TRY(result.append_data((const u8*)&h0, sizeof(u32))); + TRY(result.append_data((const u8*)&h1, sizeof(u32))); + TRY(result.append_data((const u8*)&h2, sizeof(u32))); + TRY(result.append_data((const u8*)&h3, sizeof(u32))); + TRY(result.append_data((const u8*)&h4, sizeof(u32))); + TRY(result.append_data((const u8*)&h5, sizeof(u32))); + TRY(result.append_data((const u8*)&h6, sizeof(u32))); + TRY(result.append_data((const u8*)&h7, sizeof(u32))); + + m_message.try_resize(0); // Don't need this anymore. + + return result; +} + +Result SHA256::hash_to_string(const Buffer& buffer) +{ + StringBuilder sb; + + for (usize i = 0; i < buffer.size(); i++) { sb.format("%02x", buffer.data()[i]); } + + return sb.string(); +}