aboutsummaryrefslogblamecommitdiff
path: root/src/sw/hash.c
blob: 29110eb3d80fc0ceb114ba124466b27d7c05df7b (plain) (tree)
















































                                                                        
                   




































                                                                             
                                                  

                                    
                                     
                                    
                                     


                                    
                                     
                                    
                                     




                                    
                                      
                                    







                                     




















                                                                             
                                                                                  
                                                                             
                                                                                  
                                                                             
                                                                          
                                                                             
                                                                          




















                                                            
























































































































































































































                                                                                      
                                 










                                                                       
                                 








                                                      





















































                                                                        


                                                 
                                    



                                                  
                 

                                         
























                                             




                                                    

                     



                                                                    
                         
                  

              

                                                                      

                         


                                  
                 















                                                                                           
               























































































                                                                        
/* 
 * 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, SUNET
 * 
 * Redistribution and use in source and binary forms, with or 
 * without modification, are permitted provided that the following 
 * conditions are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer. 
 * 
 * 2. 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. 
 * 
 * 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 OWNER 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 <errno.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <assert.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";

/* I2C configuration */
#define I2C_dev  "/dev/i2c-2"
#define I2C_addr 0x0f

/* command codes */
#define SOC       0x55
#define EOC       0xaa
#define READ_CMD  0x10
#define WRITE_CMD 0x11
#define RESET_CMD 0x01

/* response codes */
#define SOR       0xaa
#define EOR       0x55
#define READ_OK   0x7f
#define WRITE_OK  0x7e
#define RESET_OK  0x7d
#define UNKNOWN   0xfe
#define ERROR     0xfd

/* addresses and codes common to all hash cores */
#define ADDR_NAME0              0x00
#define ADDR_NAME1              0x01
#define ADDR_VERSION            0x02
#define ADDR_CTRL               0x08
#define CTRL_INIT_CMD           1
#define CTRL_NEXT_CMD           2
#define ADDR_STATUS             0x09
#define STATUS_READY_BIT        0
#define STATUS_VALID_BIT        1

/* addresses and codes for the specific hash cores */
/* block and digest lengths are number of bytes */
#define SHA1_ADDR_PREFIX        0x10
#define SHA1_ADDR_BLOCK         0x10
#define SHA1_BLOCK_LEN          512/8
#define SHA1_ADDR_DIGEST        0x20
#define SHA1_DIGEST_LEN         160/8

#define SHA256_ADDR_PREFIX      0x20
#define SHA256_ADDR_BLOCK       0x10
#define SHA256_BLOCK_LEN        512/8
#define SHA256_ADDR_DIGEST      0x20
#define SHA256_DIGEST_LEN       256/8

#define SHA512_ADDR_PREFIX      0x30
#define SHA512_CTRL_MODE_LOW    2
#define SHA512_CTRL_MODE_HIGH   3
#define SHA512_ADDR_BLOCK       0x10
#define SHA512_BLOCK_LEN        1024/8
#define SHA512_ADDR_DIGEST      0x40
#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_SHA512_224         0
#define MODE_SHA512_256         1
#define MODE_SHA384             2
#define MODE_SHA512             3

int i2cfd;
int debug = 0;
int verbose = 0;

/* ---------------- algorithm lookup code ---------------- */

struct ctrl {
    char *name;
    uint8_t addr_prefix;
    uint8_t addr_block;
    uint8_t block_len;
    uint8_t addr_digest;
    uint8_t digest_len;
    uint8_t mode;
} ctrl[] = {
    { "sha-1",       SHA1_ADDR_PREFIX, SHA1_ADDR_BLOCK, SHA1_BLOCK_LEN,
                     SHA1_ADDR_DIGEST, SHA1_DIGEST_LEN, 0 },
    { "sha-256",     SHA256_ADDR_PREFIX, SHA256_ADDR_BLOCK, SHA256_BLOCK_LEN,
                     SHA256_ADDR_DIGEST, SHA256_DIGEST_LEN, 0 },
    { "sha-512/224", SHA512_ADDR_PREFIX, SHA512_ADDR_BLOCK, SHA512_BLOCK_LEN,
                     SHA512_ADDR_DIGEST, SHA512_224_DIGEST_LEN, MODE_SHA512_224 },
    { "sha-512/256", SHA512_ADDR_PREFIX, SHA512_ADDR_BLOCK, SHA512_BLOCK_LEN,
                     SHA512_ADDR_DIGEST, SHA512_256_DIGEST_LEN, MODE_SHA512_256 },
    { "sha-384",     SHA512_ADDR_PREFIX, SHA512_ADDR_BLOCK, SHA512_BLOCK_LEN,
                     SHA512_ADDR_DIGEST, SHA384_DIGEST_LEN, MODE_SHA384 },
    { "sha-512",     SHA512_ADDR_PREFIX, SHA512_ADDR_BLOCK, SHA512_BLOCK_LEN,
                     SHA512_ADDR_DIGEST, SHA512_DIGEST_LEN, MODE_SHA512 },
    { 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;
}

/* ---------------- I2C low-level code ---------------- */

int i2c_open(char *dev, int addr)
{
    i2cfd = open(dev, O_RDWR);
    if (i2cfd < 0) {
        fprintf(stderr, "Unable to open %s: ", dev);
        perror("");
        i2cfd = 0;
        return 1;
    }

    if (ioctl(i2cfd, I2C_SLAVE, addr) < 0) {
        fprintf(stderr, "Unable to set I2C slave device 0x%02x: ", addr);
        perror("");
        return 1;
    }

    return 0;
}

void i2c_close(void)
{
    close(i2cfd);
}

int i2c_write(uint8_t *buf, int len)
{
    if (debug) {
        int i;
        printf("write [");
        for (i = 0; i < len; ++i)
            printf(" %02x", buf[i]);
        printf(" ]\n");
    }

    if (write(i2cfd, buf, len) != len) {
        perror("i2c write failed");
        return 1;
    }

    return 0;
}

int i2c_read(uint8_t *b)
{
    /* read() on the i2c device only returns one byte at a time,
     * and tc_get_resp() needs to parse the response one byte at a time
     */
    if (read(i2cfd, b, 1) != 1) {
        perror("i2c read failed");
        return 1;
    }

    return 0;
}

/* ---------------- test-case low-level code ---------------- */

int tc_send_write_cmd(uint8_t addr0, uint8_t addr1, uint8_t *data)
{
    uint8_t buf[9] = { SOC, WRITE_CMD, addr0, addr1,
                       data[0], data[1], data[2], data[3], EOC };

    return i2c_write(buf, sizeof(buf));
}

int tc_send_read_cmd(uint8_t addr0, uint8_t addr1)
{
    uint8_t buf[5] = { SOC, READ_CMD, addr0, addr1, EOC };

    return i2c_write(buf, sizeof(buf));
}

int tc_get_resp(uint8_t *buf, int len)
{
    int i;

    for (i = 0; i < len; ++i) {
        if (i2c_read(&buf[i]) != 0)
            return 1;
        if ((i == 0) && (buf[i] != SOR)) {
            /* we've gotten out of sync, and there's probably nothing we can do */
            fprintf(stderr, "response byte 0: expected 0x%02x (SOR), got 0x%02x\n",
                    SOR, buf[0]);
            return 1;
        }
        else if (i == 1) {      /* response code */
            switch (buf[i]) {
            case READ_OK:
                len = 9;
                break;
            case WRITE_OK:
                len = 5;
                break;
            case RESET_OK:
                len = 3;
                break;
            case ERROR:
            case UNKNOWN:
                len = 4;
                break;
            default:
                /* we've gotten out of sync, and there's probably nothing we can do */
                fprintf(stderr, "unknown response code 0x%02x\n", buf[i]);
                return 1;
            }
        }
    }

    if (debug) {
        printf("read  [");
        for (i = 0; i < len; ++i)
            printf(" %02x", buf[i]);
        printf(" ]\n");
    }

    return 0;
}

int tc_compare(uint8_t *buf, uint8_t *expected, int len)
{
    int i;

    /* start at byte 1 because SOR has already been tested */
    for (i = 1; i < len; ++i) {
        if (buf[i] != expected[i]) {
            fprintf(stderr, "response byte %d: expected 0x%02x, got 0x%02x\n",
                    i, expected[i], buf[i]);
            return 1;
        }
    }

    return 0;
}

int tc_get_write_resp(uint8_t addr0, uint8_t addr1)
{
    uint8_t buf[5];
    uint8_t expected[5] = { SOR, WRITE_OK, addr0, addr1, EOR };

    return
        tc_get_resp(buf, sizeof(buf)) ||
        tc_compare(buf, expected, sizeof(expected));
}

int tc_get_read_resp(uint8_t addr0, uint8_t addr1, uint8_t *data)
{
    uint8_t buf[9];
    uint8_t expected[4] = { SOR, READ_OK, addr0, addr1 };

    if ((tc_get_resp(buf, sizeof(buf)) != 0) ||
        (tc_compare(buf, expected, 4) != 0) || buf[8] != EOR)
        return 1;

    data[0] = buf[4];
    data[1] = buf[5];
    data[2] = buf[6];
    data[3] = buf[7];

    return 0;
}

int tc_write(uint8_t addr0, uint8_t addr1, uint8_t *data)
{
    return (tc_send_write_cmd(addr0, addr1, data) ||
            tc_get_write_resp(addr0, addr1));
}

int tc_read(uint8_t addr0, uint8_t addr1, uint8_t *data)
{
    return (tc_send_read_cmd(addr0, addr1) ||
            tc_get_read_resp(addr0, addr1, data));
}

int tc_init(uint8_t addr0, uint8_t mode)
{
    uint8_t buf[4] = { 0, 0, 0, CTRL_INIT_CMD };

    if (addr0 == SHA512_ADDR_PREFIX)
        buf[3] += (mode << SHA512_CTRL_MODE_LOW);

    return tc_write(addr0, ADDR_CTRL, buf);
}

int tc_next(uint8_t addr0, uint8_t mode)
{
    uint8_t buf[4] = { 0, 0, 0, CTRL_NEXT_CMD };

    if (addr0 == SHA512_ADDR_PREFIX)
        buf[3] += (mode << SHA512_CTRL_MODE_LOW);

    return tc_write(addr0, ADDR_CTRL, buf);
}

int tc_wait(uint8_t addr0, uint8_t status)
{
    uint8_t buf[4];

    do {
        if (tc_read(addr0, ADDR_STATUS, &buf[0]) != 0)
            return 1;
    } while ((buf[3] & status) != status);

    return 0;
}

int tc_wait_ready(uint8_t addr0)
{
    return tc_wait(addr0, STATUS_READY_BIT);
}

int tc_wait_valid(uint8_t addr0)
{
    return tc_wait(addr0, STATUS_VALID_BIT);
}

int tc_write_block(uint8_t addr0, uint8_t addr1, uint8_t *buf, int len)
{
    int i;

    for (i = 0; i < len/4; ++i) {
        if (tc_write(addr0, addr1 + i, &buf[i*4]) != 0)
            return 1;
    }

    return 0;
}

int tc_read_digest(uint8_t addr0, uint8_t addr1, uint8_t *buf, int len)
{
    int i;

    for (i = 0; i < len/4; ++i) {
        if (tc_read(addr0, addr1 + i, &buf[i*4]) != 0)
            return 1;
    }

    return 0;
}

/* ---------------- hash ---------------- */

int transmit(uint8_t *block, uint8_t blen, uint8_t addr0, uint8_t baddr,
             uint8_t mode, int first)
{
    int i;

    if (debug) {
        printf("write [");
        for (i = 0; i < blen; ++i)
            printf(" %02x", block[i]);
        printf(" ]\n");
    }

    if (tc_write_block(addr0, baddr, block, blen) != 0) {
        return 1;
    }
    if (first) {
        if (tc_init(addr0, mode) != 0)
            return 1;
    }
    else {
        if (tc_next(addr0, mode) != 0)
            return 1;
    }
    if (tc_wait_ready(addr0) != 0)
        return 1;

    return 0;
}

int pad_transmit(uint8_t *block, uint8_t flen, uint8_t blen,
                 uint8_t addr0, uint8_t baddr, 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(block, blen, addr0, baddr, mode, first) != 0)
            return 1;
        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(block, blen, addr0, baddr, 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 */
    uint8_t addr0, baddr, blen, daddr, dlen, mode;
    int nblk, nread, first;
    int ret = -1;
    struct timeval start, stop, difftime;

    ctrl = find_algo(algo);
    if (ctrl == NULL)
        return -1;
    addr0 = ctrl->addr_prefix;
    baddr = ctrl->addr_block;
    blen = ctrl->block_len;
    daddr = ctrl->addr_digest;
    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(block, nread, blen, addr0, baddr, mode,
                             (nblk * blen + nread) * 8, first) != 0)
                goto out;
            break;
        }
        else {
            /* full block read */
            if (transmit(block, blen, addr0, baddr, mode, first) != 0)
                goto out;
        }
    }

    if (tc_wait_valid(addr0) != 0)
        goto out;
    if (tc_read_digest(addr0, 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, quiet = 0;
    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 0;
        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 1;
            }
            break;
        default:
            fprintf(stderr, usage, argv[0]);
            return 1;
        }
    }

    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 -1;

    dlen = hash(algo, file, digest);
    if (dlen < 0)
        return 1;

    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");

    i2c_close();

    return 0;
}