Skip to content

Commit

Permalink
add verification and change output
Browse files Browse the repository at this point in the history
- add parameters for verification, verbosity and such
- changed output to output "hash *file"
- add SHA256SUMS verification via -c FILE
- add visual studio solution
- changed argument parsing
- add exit codes for every success and failure
- rewrote documentation
- add version
-
  • Loading branch information
cwansart committed Oct 2, 2023
1 parent 4274ca9 commit 8350cc0
Show file tree
Hide file tree
Showing 18 changed files with 1,364 additions and 424 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
*.obj
*.exe
*.exe
x64/
.vs/
23 changes: 0 additions & 23 deletions .vscode/c_cpp_properties.json

This file was deleted.

7 changes: 0 additions & 7 deletions .vscode/settings.json

This file was deleted.

32 changes: 0 additions & 32 deletions Makefile

This file was deleted.

32 changes: 0 additions & 32 deletions Makefile.tests

This file was deleted.

83 changes: 67 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,86 @@
# sha256sum for Windows

Since `certutil` in CMD is not ideal for use in a Dockerfile and I prefer not to rely on random authors on the web, I've
created my own `sha256sum.exe` tool.
The built-in certutil utility in CMD is not ideal for use in a Dockerfile, primarily because it prints out multiple lines and lacks a -c option for file verification. To avoid relying on third-party solutions from unknown authors, I've created my own sha256sum.exe utility.

## Requirements

The *Makefile* is designed for use with MSVC. Specifically, I've developed it using VS 2017 version 14.16.27023.
The _Makefile_ is designed for use with MSVC. Specifically, I've developed it using VS 2022 version v17.7.4.

## Building sha256sum.exe

1. Open the *x64 Native Tools Command Prompt for VS 2017*.
1. Open the _x64 Native Tools Command Prompt for VS 2022_.
2. Navigate to the folder containing the Makefile.
3. Run the `nmake` command.
3. Run the `msbuild` command.

## How to Run
Alternatively, you can open the Solution file in the Visual Studio IDE.

Either download the pre-compiled `sha256sum.exe` or build it yourself. To run the tool, execute it with a file
parameter. For example:
## Usage

```bash
sha256sum [OPTION]... [FILE]...
```

| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------- |
| -c, --check <FILE> | read checksums from the FILE and check them, input must be UTF-8 encoded |
| -b, --binary | read in binary mode, this is default |
| -t, --text | read in text mode, fails because WinAPI's ReadFile/CreateFile only reads in binary mode |
| -q, --quiet | don't print OK, just FAILED if checks fail |
| -s, --status | don't print anything, just return status code |
| -w, --warn | shows SHA256SUMS errors |
| -v, --version | shows program's version |

### Examples

Here's how to use the utility:

```bash
sha256sum.exe myText.txt
sha256sum.exe *.txt
sha256sum.exe hello.txt world.txt
sha256sum.exe *.txt > SHA256SUMS
sha256sum.exe -c SHA256SUMS
```

## Design Decisions
### Exit Codes

| Code | Name | Description |
| ---- | --------------------------------------------- | -------------------------------------------------------------------------- |
| 1 | MAIN_FAILED_TO_FIND_FILES | WinAPI's FindFirstFile was not able to find files wth given FILE arguments |
| 2 | PARSE_ARGS_MISSING_PARAMETER | too few arguments, at least 1 is required |
| 3 | PARSE_ARGS_MISSING_SHASUMS_FILE | -c argument found but missing following sum file, `-c <FILE>` |
| 4 | PARSE_ARGS_ALLOCATE_ERROR | memory allocation failed for file list, memory low? |
| 5 | CALC_HASH_FAILED_TO_OPEN_FILE | failed to open FILE, check permissions, if file exists |
| 6 | CALC_HASH_FAILED_TO_OPEN_ALG_HANDLE | failed to open algorithm handle[1] |
| 7 | CALC_HASH_FAILED_TO_ALLOCATE_HASH_BUFFER_SIZE | failed to calculate hash buffer size[1] |
| 8 | CALC_HASH_FAILED_TO_ALLOCATE_HASH_OBJECT | failed to allocate hash object[1] |
| 9 | CALC_HASH_FAILED_TO_CALC_HASH_LENGTH | failed to calculate hash length[1] |
| 10 | CALC_HASH_FAILED_TO_ALLOCATE_HASH_BUFFER | failed to allocate hash buffer[1] |
| 11 | CALC_HASH_FAILED_TO_CREATE_HASH | failed to create hash[1] |
| 12 | CALC_HASH_FAILED_TO_READ | failed to read from given FILE |
| 13 | CALC_HASH_FAILED_TO_HASH | failed to hash[1] |
| 14 | CALC_HASH_FAILED_TO_FINISH_HASH | failed to finish hash[1] |
| 15 | CALC_HASH_FAILED_TO_ALLOCATE_FILE_HASH | failed to allocate memory for file hash, check your memory |
| 16 | PARSE_LINE_INVALID_HASH_TOKEN | invalid hash token may happen if the line does not contain spaces |
| 17 | PARSE_LINE_INVALID_HASH_LENGTH | fails when the token is not 64 characters long |
| 18 | PARSE_LINE_INAVLID_FILE | fails if the file does not have a second string after the space(s) |
| 19 | CHECK_SUMS_FAILED_TO_OPEN_SUM_FILE | failed to open -c FILE |
| 20 | CHECK_SUMS_LINE_TOO_LONG | line in sum file is longer exceeds internal buffer (1024) |
| 21 | CHECK_SUMS_FAILED_TO_ALLOCATE_WIDE_BUFFER1 | line buffer allocation for UTF-16 failed |
| 22 | CHECK_SUMS_FAILED_TO_ALLOCATE_FILE_HASH1 | file hash object allocation failed |
| 23 | CHECK_SUMS_FAILED_TO_ALLOCATE_WIDE_BUFFER2 | line buffer allocation for UTF-16 failed[2] |
| 24 | CHECK_SUMS_FAILED_TO_ALLOCATE_FILE_HASH2 | file hash object allocation failed[2] |
| 25 | CHECK_SUMS_FAILED_TO_READ | failed to read from -c FILE |
| 26 | CHECK_SUM_CHECKSUM_FAILED | checksum verification failed |

[1] This should never occur. sha256sum.exe uses Microsoft's Cryptography API: Next Generation (CNG) with fixed values. If this happens the system is probably missing the CNG.

I made several decisions regarding the APIs used in the code. My initial attempt involved using OpenSSL's EVP functions,
but I encountered issues with static linking. Using a shared library resulted in an executable that was only about
120 KB in size, but it had a dependency on `libcrypto-3-x64.dll`, which is around 5-6 MB.
[2] There is a duplication for parsing the file when the end of file is not reached yet and for the remaining characters that are still in the read buffer.

## Design Decisions

I also tried writing `sha256sum.exe` in Go, which resulted in a file size of around 1.2 MB. While this size is
acceptable, I wanted to minimize the file size for use in a nanoserver environment.
I made several decisions regarding the APIs used in the code:

The current implementation uses the *Cryptography API: Next Generation* (CNG) and the Windows API for file reading. It
doesn't depend on any third-party libraries not already included in Windows, resulting in a very compact executable.
- **OpenSSL's EVP Functions**: My initial attempt involved using these, but I encountered issues with static linking.
- **Go Implementation**: Resulted in a larger file size than desired.
- **CNG and Windows API**: The current implementation uses these native APIs to minimize dependencies and file size.
8 changes: 8 additions & 0 deletions SHA256SUMS
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
e3247fa228bcdd1eaf815cb664761bd3ccd72b247105a1118a169d1e6e75e754 *.gitignore
88b177b001f949aca6e29cf9d481cf241c5ecd46ff39bfa709ee085d99facf77 *args.c
147a3761456127bcb8ada2c34728a301d01acd316946aad7d605c3a9ce37e6f2 *LICENSE
64c524482575b98db15fbfdaae6723fbaba0660d50986e77f237503c31377a0b *main.c
78e6e19baff8e8b2a2624d4219d08a8a781c2deb595da04cc7b14a4e2bc29c21 *README.md
08cb7d72f4915b23ae173d410e8d89e94d07f57fb550aa325da8ffb5c166558c *sha256.c
96c0c25c4958bdba7e26cd76fe09a05f8ee974730e1a3c0a011be33971374268 *sha256sum.h
f9b427d603ff904a4aeb6ebed684ba6ea59734a79da0d90a9ab8363b24d59140 *tests.c
151 changes: 119 additions & 32 deletions args.c
Original file line number Diff line number Diff line change
@@ -1,40 +1,127 @@
#include "args.h"
#include "sha256sum.h"

errno_t parse_args(int argc, wchar_t *argv[], wchar_t **out_shasums_file)
void usage(__in LPWSTR prog, __in_opt LPWSTR message)
{
if (argc < 2)
{
usage(argv[0], L"missing parameter");
return ARGS_MISSING_PARAMETER;
}

for (int i = 0; i < argc; ++i)
{
if (wcscmp(argv[i], L"-c") == 0)
#ifndef _UNITTESTS
if (NULL != message)
{
// check if there is another argument after -c
if (i + 1 < argc)
{
*out_shasums_file = argv[i + 1];
++i; // skip next argument since we used it here
}
else
{
usage(argv[0], L"");
return ARGS_MISSING_SHASUMS_FILE;
}
wprintf(L"%ls\n", message);
}
}

return OK;
wprintf(L"Usage: %ls [-c sha256sums_file] [file...]\n", prog);
#endif
}

void usage(wchar_t *prog, wchar_t *message)
ErrorCode parse_args(__out Args* args, __in int argc, __in LPWSTR argv[])
{
if (NULL != message)
{
fwprintf(stderr, L"%ls\n", message);
}
ErrorCode status = SUCCESS;
FileList* current = NULL;

// default values
args->files = NULL;
args->sum_file = NULL;
args->quiet = FALSE;
args->status = FALSE;
args->warn = FALSE;
args->show_version = FALSE;
args->text_mode = FALSE;

// check if there are any argments given
if (argc < 2)
{
usage(argv[0], L"too few arguments");
status = PARSE_ARGS_MISSING_PARAMETER;
goto Cleanup;
}

for (int i = 1; i < argc; ++i)
{
// -v, --version
if (wcscmp(argv[i], L"-v") == 0 || wcscmp(argv[i], L"--version") == 0)
{
args->show_version = TRUE;
goto Cleanup;
}

// -t, --text
if (wcscmp(argv[i], L"-t") == 0 || wcscmp(argv[i], L"--text") == 0)
{
args->text_mode = TRUE;
goto Cleanup;
}

// -b, --binary
if (wcscmp(argv[i], L"-b") == 0 || wcscmp(argv[i], L"--binary") == 0)
{
// do nothing, since this only works with binary
continue;
}

// -q, --quiet
if (wcscmp(argv[i], L"-q") == 0 || wcscmp(argv[i], L"--quiet") == 0)
{
args->quiet = TRUE;
continue;
}

// -s, --status
if (wcscmp(argv[i], L"-s") == 0 || wcscmp(argv[i], L"--status") == 0)
{
args->status = TRUE;
continue;
}

// -w, --warn
if (wcscmp(argv[i], L"-w") == 0 || wcscmp(argv[i], L"--warn") == 0)
{
args->warn = TRUE;
continue;
}

// -c, --check <file>
// checks for -c or --check and checks the following argument
// fails when there is no other argument after -c
if (wcscmp(argv[i], L"-c") == 0 || wcscmp(argv[i], L"--check") == 0)
{
// check if there is another argument after -c
if (i + 1 < argc)
{
args->sum_file = argv[i + 1];
++i; // skip next argument since we used it here
continue;
}
else
{
usage(argv[0], L"missing SHA256SUMS file");
status = PARSE_ARGS_MISSING_SHASUMS_FILE;
goto Cleanup;
}
}
// if there are no argument handling left, we assume the rest are files
else
{
FileList* newFile = malloc(sizeof(FileList));
if (newFile == NULL)
{
wprintf(L"allocation for new file list item failed\n");
status = PARSE_ARGS_ALLOCATE_ERROR;
goto Cleanup;
}
newFile->file = argv[i];
newFile->next = NULL;

wprintf(L"Usage: %ls [-c sha256sums_file] [file...]\n", prog);
}
if (args->files == NULL)
{
args->files = newFile;
current = args->files;
}
else
{
current->next = newFile;
current = newFile;
}
}
}

Cleanup:
return status;
}
9 changes: 0 additions & 9 deletions args.h

This file was deleted.

Loading

0 comments on commit 8350cc0

Please sign in to comment.