/*
* hash.c
* ------
* This program uses the coretest_hashes subsystem to produce a
* cryptographic hash of a file or input stream. It is a generalization
* of the hash_tester.c test program.
*
* Authors: Joachim Strömbergson, Paul Selkirk
* Copyright (c) 2014-2015, NORDUnet A/S All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of the NORDUnet nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <assert.h>
#include "tc_i2c.h"
char *usage =
"Usage: %s [-d] [-v] [-q] [-i I2C_device] [-a I2C_addr] [algorithm [file]]\n"
"algorithms: sha-1, sha-256, sha-512/224, sha-512/256, sha-384, sha-512\n";
int debug = 0;
int quiet = 0;
int verbose = 0;
/* memory segments for core families */
#define SEGMENT_OFFSET_GLOBALS 0x0000
#define SEGMENT_OFFSET_HASHES 0x2000
#define SEGMENT_OFFSET_RNGS 0x4000
#define SEGMENT_OFFSET_CIPHERS 0x6000
#define CORE_SIZE 0x100
/* addresses and codes common to all cores */
#define ADDR_NAME0 0x00
#define ADDR_NAME1 0x01
#define ADDR_VERSION 0x02
/* At segment 0, we have board-level register and communication channel registers */
#define BOARD_ADDR_BASE SEGMENT_OFFSET_GLOBALS + (0x00 * CORE_SIZE)
#define BOARD_ADDR_NAME0 BOARD_ADDR_BASE + ADDR_NAME0
#define BOARD_ADDR_NAME1 BOARD_ADDR_BASE + ADDR_NAME1
#define BOARD_ADDR_VERSION BOARD_ADDR_BASE + ADDR_VERSION
#define BOARD_ADDR_DUMMY BOARD_ADDR_BASE + 0xFF
#define COMM_ADDR_BASE SEGMENT_OFFSET_GLOBALS + (0x01 * CORE_SIZE)
#define COMM_ADDR_NAME0 COMM_ADDR_BASE + ADDR_NAME0
#define COMM_ADDR_NAME1 COMM_ADDR_BASE + ADDR_NAME1
#define COMM_ADDR_VERSION COMM_ADDR_BASE + ADDR_VERSION
/* addresses and codes common to all hash cores */
#define ADDR_CTRL 0x08
#define CTRL_INIT_CMD 1
#define CTRL_NEXT_CMD 2
#define ADDR_STATUS 0x09
#define STATUS_READY_BIT 1
#define STATUS_VALID_BIT 2
#define ADDR_BLOCK 0x10
#define ADDR_DIGEST 0x20
/* addresses and codes for the specific hash cores */
#define SHA1_ADDR_BASE SEGMENT_OFFSET_HASHES + (0*CORE_SIZE)
#define SHA1_ADDR_NAME0 SHA1_ADDR_BASE + ADDR_NAME0
#define SHA1_ADDR_NAME1 SHA1_ADDR_BASE + ADDR_NAME1
#define SHA1_ADDR_VERSION SHA1_ADDR_BASE + ADDR_VERSION
#define SHA1_ADDR_CTRL SHA1_ADDR_BASE + ADDR_CTRL
#define SHA1_ADDR_STATUS SHA1_ADDR_BASE + ADDR_STATUS
#define SHA1_ADDR_BLOCK SHA1_ADDR_BASE + ADDR_BLOCK
#define SHA1_ADDR_DIGEST SHA1_ADDR_BASE + ADDR_DIGEST
#define SHA1_BLOCK_LEN 512 / 8
#define SHA1_DIGEST_LEN 160 / 8
#define SHA256_ADDR_BASE SEGMENT_OFFSET_HASHES + (1*CORE_SIZE)
#define SHA256_ADDR_NAME0 SHA256_ADDR_BASE + ADDR_NAME0
#define SHA256_ADDR_NAME1 SHA256_ADDR_BASE + ADDR_NAME1
#define SHA256_ADDR_VERSION SHA256_ADDR_BASE + ADDR_VERSION
#define SHA256_ADDR_CTRL SHA256_ADDR_BASE + ADDR_CTRL
#define SHA256_ADDR_STATUS SHA256_ADDR_BASE + ADDR_STATUS
#define SHA256_ADDR_BLOCK SHA256_ADDR_BASE + ADDR_BLOCK
#define SHA256_ADDR_DIGEST SHA256_ADDR_BASE + ADDR_DIGEST
#define SHA256_BLOCK_LEN 512 / 8
#define SHA256_DIGEST_LEN 256 / 8
#define SHA512_ADDR_BASE SEGMENT_OFFSET_HASHES + (2*CORE_SIZE)
#define SHA512_ADDR_NAME0 SHA512_ADDR_BASE + ADDR_NAME0
#define SHA512_ADDR_NAME1 SHA512_ADDR_BASE + ADDR_NAME1
#define SHA512_ADDR_VERSION SHA512_ADDR_BASE + ADDR_VERSION
#define SHA512_ADDR_CTRL SHA512_ADDR_BASE + ADDR_CTRL
#define SHA512_ADDR_STATUS SHA512_ADDR_BASE + ADDR_STATUS
#define SHA512_ADDR_BLOCK SHA512_ADDR_BASE + ADDR_BLOCK
#define SHA512_ADDR_DIGEST SHA512_ADDR_BASE + 0x40
#define SHA512_BLOCK_LEN 1024 / 8
#define SHA512_224_DIGEST_LEN 224 / 8
#define SHA512_256_DIGEST_LEN 256 / 8
#define SHA384_DIGEST_LEN 384 / 8
#define SHA512_DIGEST_LEN 512 / 8
#define MODE_SHA_512_224 0 << 2
#define MODE_SHA_512_256 1 << 2
#define MODE_SHA_384 2 << 2
#define MODE_SHA_512 3 << 2
/* ---------------- algorithm lookup code ---------------- */
struct ctrl {
char *name;
off_t block_addr;
int block_len;
off_t digest_addr;
int digest_len;
int mode;
} ctrl[] = {
{ "sha-1", SHA1_ADDR_BLOCK, SHA1_BLOCK_LEN,
SHA1_ADDR_DIGEST, SHA1_DIGEST_LEN, 0 },
{ "sha-256", SHA256_ADDR_BLOCK, SHA256_BLOCK_LEN,
SHA256_ADDR_DIGEST, SHA256_DIGEST_LEN, 0 },
{ "sha-512/224", SHA512_ADDR_BLOCK, SHA512_BLOCK_LEN,
SHA512_ADDR_DIGEST, SHA512_224_DIGEST_LEN, MODE_SHA_512_224 },
{ "sha-512/256", SHA512_ADDR_BLOCK, SHA512_BLOCK_LEN,
SHA512_ADDR_DIGEST, SHA512_256_DIGEST_LEN, MODE_SHA_512_256 },
{ "sha-384", SHA512_ADDR_BLOCK, SHA512_BLOCK_LEN,
SHA512_ADDR_DIGEST, SHA384_DIGEST_LEN, MODE_SHA_384 },
{ "sha-512", SHA512_ADDR_BLOCK, SHA512_BLOCK_LEN,
SHA512_ADDR_DIGEST, SHA512_DIGEST_LEN, MODE_SHA_512 },
{ NULL, 0, 0, 0 }
};
/* return the control structure for the given algorithm */
struct ctrl *find_algo(char *algo)
{
int i;
for (i = 0; ctrl[i].name != NULL; ++i)
if (strcmp(ctrl[i].name, algo) == 0)
return &ctrl[i];
fprintf(stderr, "algorithm \"%s\" not found\n\n", algo);
fprintf(stderr, usage, "hash");
return NULL;
}
/* ---------------- test-case low-level code ---------------- */
int tc_init(off_t offset, int mode)
{
uint8_t buf[4] = { 0, 0, 0, CTRL_INIT_CMD + mode };
return tc_write(offset, buf, 4);
}
int tc_next(off_t offset, int mode)
{
uint8_t buf[4] = { 0, 0, 0, CTRL_NEXT_CMD + mode };
return tc_write(offset, buf, 4);
}
int tc_wait_ready(off_t offset)
{
int limit = 10;
return tc_wait(offset, STATUS_READY_BIT, &limit);
}
int tc_wait_valid(off_t offset)
{
int limit = 10;
return tc_wait(offset, STATUS_VALID_BIT, &limit);
}
/* ---------------- hash ---------------- */
int transmit(off_t offset, uint8_t *block, int blen, int mode, int first)
{
off_t base = offset & ~(0xff);
if (tc_write(offset, block, blen) != 0)
return EXIT_FAILURE;
if (first) {
if (tc_init(base + ADDR_CTRL, mode) != 0)
return EXIT_FAILURE;
}
else {
if (tc_next(base + ADDR_CTRL, mode) != 0)
return EXIT_FAILURE;
}
return tc_wait_ready(base + ADDR_STATUS);
}
int pad_transmit(off_t offset, uint8_t *block, uint8_t flen, uint8_t blen,
uint8_t mode, long long tlen, int first)
{
assert(flen < blen);
block[flen++] = 0x80;
memset(block + flen, 0, blen - flen);
if (blen - flen < ((blen == 64) ? 8 : 16)) {
if (transmit(offset, block, blen, mode, first) != 0)
return EXIT_FAILURE;
first = 0;
memset(block, 0, blen);
}
/* properly the length is 128 bits for sha-512, but we can't
* actually count above 64 bits
*/
((uint32_t *)block)[blen/4 - 2] = htonl((tlen >> 32) & 0xffff);
((uint32_t *)block)[blen/4 - 1] = htonl(tlen & 0xffff);
return transmit(offset, block, blen, mode, first);
}
/* return number of digest bytes read */
int hash(char *algo, char *file, uint8_t *digest)
{
uint8_t block[SHA512_BLOCK_LEN];
struct ctrl *ctrl;
int in_fd = 0; /* stdin */
off_t baddr, daddr;
int blen, dlen, mode;
int nblk, nread, first;
int ret = -1;
struct timeval start, stop, difftime;
ctrl = find_algo(algo);
if (ctrl == NULL)
return -1;
baddr = ctrl->block_addr;
blen = ctrl->block_len;
daddr = ctrl->digest_addr;
dlen = ctrl->digest_len;
mode = ctrl->mode;
if (strcmp(file, "-") != 0) {
in_fd = open(file, O_RDONLY);
if (in_fd < 0) {
perror("open");
return -1;
}
}
if (verbose) {
if (gettimeofday(&start, NULL) < 0) {
perror("gettimeofday");
goto out;
}
}
for (nblk = 0, first = 1; ; ++nblk, first = 0) {
nread = read(in_fd, block, blen);
if (nread < 0) {
/* read error */
perror("read");
goto out;
}
else if (nread < blen) {
/* partial read = last block */
if (pad_transmit(baddr, block, nread, blen, mode,
(nblk * blen + nread) * 8, first) != 0)
goto out;
break;
}
else {
/* full block read */
if (transmit(baddr, block, blen, mode, first) != 0)
goto out;
}
}
/* Strictly speaking we should query "valid" status before reading digest,
* but transmit() waits for "ready" status before returning, and the SHA
* cores always assert valid before ready.
*/
if (tc_read(daddr, digest, dlen) != 0) {
perror("i2c read failed");
goto out;
}
if (verbose) {
if (gettimeofday(&stop, NULL) < 0) {
perror("gettimeofday");
goto out;
}
timersub(&stop, &start, &difftime);
printf("%d blocks written in %d.%03d sec (%.3f blocks/sec)\n",
nblk, (int)difftime.tv_sec, (int)difftime.tv_usec/1000,
(float)nblk / ((float)difftime.tv_sec + ((float)difftime.tv_usec)/1000000));
}
ret = dlen;
out:
if (in_fd != 0)
close(in_fd);
return ret;
}
/* ---------------- main ---------------- */
int main(int argc, char *argv[])
{
char *dev = I2C_dev;
int addr = I2C_addr;
int i, opt;
char *algo = "sha-1";
char *file = "-";
uint8_t digest[512/8];
int dlen;
while ((opt = getopt(argc, argv, "h?dvqi:a:")) != -1) {
switch (opt) {
case 'h':
case '?':
printf(usage, argv[0]);
return EXIT_SUCCESS;
case 'd':
debug = 1;
break;
case 'v':
verbose = 1;
break;
case 'q':
quiet = 1;
break;
case 'i':
dev = optarg;
break;
case 'a':
addr = (int)strtol(optarg, NULL, 0);
if ((addr < 0x03) || (addr > 0x77)) {
fprintf(stderr, "addr must be between 0x03 and 0x77\n");
return EXIT_FAILURE;
}
break;
default:
fprintf(stderr, usage, argv[0]);
return EXIT_FAILURE;
}
}
if (optind < argc) {
algo = argv[optind];
++optind;
}
else {
if (!quiet)
printf("defaulting to algorithm \"%s\"\n", algo);
}
if (optind < argc) {
file = argv[optind];
++optind;
}
else {
if (!quiet)
printf("reading from stdin\n");
}
if (i2c_open(dev, addr) != 0)
return EXIT_FAILURE;
dlen = hash(algo, file, digest);
if (dlen < 0)
return EXIT_FAILURE;
for (i = 0; i < dlen; ++i) {
printf("%02x", digest[i]);
if (i % 16 == 15)
printf("\n");
else if (i % 4 == 3)
printf(" ");
}
if (dlen % 16 != 0)
printf("\n");
return EXIT_SUCCESS;
}