tool-openssl/dgst.cc (171 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
#include <fcntl.h>
#include <string.h>
#include <iostream>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include "internal.h"
// MD5 command currently only supports stdin
static const argument_t kArguments[] = {
{"-help", kBooleanArgument, "Display option summary"},
{"-hmac", kOptionalArgument,
"Create a hashed MAC with the corresponding key"},
{"", kOptionalArgument, ""}};
static bool dgst_file_op(const std::string &filename, const int fd,
const EVP_MD *digest) {
static const size_t kBufSize = 8192;
std::unique_ptr<uint8_t[]> buf(new uint8_t[kBufSize]);
bssl::ScopedEVP_MD_CTX ctx;
if (!EVP_DigestInit_ex(ctx.get(), digest, nullptr)) {
fprintf(stderr, "Failed to initialize EVP_MD_CTX.\n");
return false;
}
for (;;) {
size_t n;
if (!ReadFromFD(fd, &n, buf.get(), kBufSize)) {
fprintf(stderr, "Failed to read from %s: %s\n", filename.c_str(),
strerror(errno));
return false;
}
if (n == 0) {
break;
}
if (!EVP_DigestUpdate(ctx.get(), buf.get(), n)) {
fprintf(stderr, "Failed to update hash.\n");
return false;
}
}
uint8_t hash[EVP_MAX_MD_SIZE];
unsigned hash_len;
if (!EVP_DigestFinal_ex(ctx.get(), hash, &hash_len)) {
fprintf(stderr, "Failed to finish hash.\n");
return false;
}
// Print digest output. OpenSSL outputs the digest name with files, but not
// with stdin.
if (fd != 0) {
fprintf(stdout, "%s(%s)= ", EVP_MD_get0_name(digest), filename.c_str());
} else {
fprintf(stdout, "(%s)= ", filename.c_str());
};
for (size_t i = 0; i < hash_len; i++) {
fprintf(stdout, "%02x", hash[i]);
}
fprintf(stdout, "\n");
return true;
}
static bool hmac_file_op(const std::string &filename, const int fd,
const EVP_MD *digest, const char *hmac_key,
const size_t hmac_key_len) {
static const size_t kBufSize = 8192;
std::unique_ptr<uint8_t[]> buf(new uint8_t[kBufSize]);
bssl::ScopedHMAC_CTX ctx;
if (!HMAC_Init_ex(ctx.get(), hmac_key, hmac_key_len, digest, nullptr)) {
fprintf(stderr, "Failed to initialize HMAC_Init_ex.\n");
return false;
}
// Update |buf| from file continuously.
for (;;) {
size_t n;
if (!ReadFromFD(fd, &n, buf.get(), kBufSize)) {
fprintf(stderr, "Failed to read from %s: %s\n", filename.c_str(),
strerror(errno));
return false;
}
if (n == 0) {
break;
}
if (!HMAC_Update(ctx.get(), buf.get(), n)) {
fprintf(stderr, "Failed to update HMAC.\n");
return false;
}
}
const unsigned expected_mac_len = EVP_MD_size(digest);
std::unique_ptr<uint8_t[]> mac(new uint8_t[expected_mac_len]);
unsigned mac_len;
if (!HMAC_Final(ctx.get(), mac.get(), &mac_len)) {
fprintf(stderr, "Failed to finalize HMAC.\n");
return false;
}
// Print HMAC output. OpenSSL outputs the digest name with files, but not
// with stdin.
if (fd != 0) {
fprintf(stdout, "HMAC-%s(%s)= ", EVP_MD_get0_name(digest),
filename.c_str());
} else {
fprintf(stdout, "(%s)= ", filename.c_str());
};
for (size_t i = 0; i < expected_mac_len; i++) {
fprintf(stdout, "%02x", mac[i]);
}
fprintf(stdout, "\n");
return true;
}
static bool dgst_tool_op(const args_list_t &args, const EVP_MD *digest) {
std::vector<std::string> file_inputs;
// Default is SHA-256.
// TODO: Make this customizable when "-digest" is introduced.
if (digest == nullptr) {
digest = EVP_sha256();
}
// HMAC keys can be empty, but C++ std::string has no way to differentiate
// between null and empty.
const char *hmac_key = nullptr;
size_t hmac_key_len = 0;
auto it = args.begin();
while (it != args.end()) {
const std::string &arg = *it;
if (!arg.empty() && arg[0] != '-') {
// Any input without a '-' prefix is parsed as a file. This
// also marks the end of any option input.
while (it != args.end()) {
if (!(*it).empty()) {
file_inputs.push_back(*it);
}
it++;
}
break;
}
if (!arg.empty() && arg[0] == '-') {
const std::string option = arg.substr(1);
if (option == "help") {
PrintUsage(kArguments);
return false;
} else if (option == "hmac") {
// Read next argument as key string.
it++;
// HMAC allows for empty keys.
if (it != args.end()) {
hmac_key = (*it).c_str();
hmac_key_len = (*it).length();
} else {
fprintf(stderr,
"dgst: Option -hmac needs a value\n"
"dgst: Use -help for summary.\n");
return false;
}
} else {
fprintf(stderr, "Unknown option '%s'.\n", option.c_str());
return false;
}
} else {
// Empty input. OpenSSL continues processing the next file even when
// provided an invalid file.
fprintf(stderr, "Failed to read from empty input.");
}
// Increment while loop.
it++;
}
// Use stdin if no files are provided.
if (file_inputs.empty()) {
// 0 denotes stdin.
std::string file_name = "stdin";
int fd = 0;
if (hmac_key) {
if (!hmac_file_op(file_name, fd, digest, hmac_key, hmac_key_len)) {
return false;
}
} else {
if (!dgst_file_op(file_name, fd, digest)) {
return false;
}
}
return true;
}
// Do the dgst operation on all file inputs.
for (const auto &file_name : file_inputs) {
ScopedFD scoped_fd = OpenFD(file_name.c_str(), O_RDONLY | O_BINARY);
int fd = scoped_fd.get();
if (hmac_key) {
if (!hmac_file_op(file_name, fd, digest, hmac_key, hmac_key_len)) {
return false;
}
} else {
if (!dgst_file_op(file_name, fd, digest)) {
return false;
}
}
}
return true;
}
bool dgstTool(const args_list_t &args) { return dgst_tool_op(args, nullptr); }
bool md5Tool(const args_list_t &args) { return dgst_tool_op(args, EVP_md5()); }