X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/blobdiff_plain/b82bce58fb8380d1889636c7978816c146982cc7..487b3235fdc9ce13644a8b8c2c7b637f8304c2be:/sbin/hammer2/crypto.c diff --git a/sbin/hammer2/crypto.c b/sbin/hammer2/crypto.c index 248764d04a..fd5ac6f112 100644 --- a/sbin/hammer2/crypto.c +++ b/sbin/hammer2/crypto.c @@ -4,6 +4,7 @@ * This code is derived from software contributed to The DragonFly Project * by Matthew Dillon * by Venkatesh Srinivas + * by Alex Hornung * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -34,6 +35,264 @@ */ #include "hammer2.h" +#include + +/* + * Setup crypto for pthreads + */ +static pthread_mutex_t *crypto_locks; +int crypto_count; + +static int hammer2_crypto_gcm_init(hammer2_ioq_t *, char *, int, char *, int, int); +static int hammer2_crypto_gcm_encrypt_chunk(hammer2_ioq_t *, char *, char *, int, int *); +static int hammer2_crypto_gcm_decrypt_chunk(hammer2_ioq_t *, char *, char *, int, int *); + +/* + * NOTE: the order of this table needs to match the HAMMER2_CRYPTO_ALGO_*_IDX + * defines in network.h. + */ +static struct crypto_algo crypto_algos[] = { + { + .name = "aes-256-gcm", + .keylen = HAMMER2_CRYPTO_GCM_KEY_SIZE, + .taglen = HAMMER2_CRYPTO_GCM_TAG_SIZE, + .init = hammer2_crypto_gcm_init, + .enc_chunk = hammer2_crypto_gcm_encrypt_chunk, + .dec_chunk = hammer2_crypto_gcm_decrypt_chunk + }, + { NULL, 0, 0, NULL, NULL, NULL } +}; + +static +unsigned long +hammer2_crypto_id_callback(void) +{ + return ((unsigned long)(uintptr_t)pthread_self()); +} + +static +void +hammer2_crypto_locking_callback(int mode, int type, + const char *file __unused, int line __unused) +{ + assert(type >= 0 && type < crypto_count); + if (mode & CRYPTO_LOCK) { + pthread_mutex_lock(&crypto_locks[type]); + } else { + pthread_mutex_unlock(&crypto_locks[type]); + } +} + +void +hammer2_crypto_setup(void) +{ + crypto_count = CRYPTO_num_locks(); + crypto_locks = calloc(crypto_count, sizeof(crypto_locks[0])); + CRYPTO_set_id_callback(hammer2_crypto_id_callback); + CRYPTO_set_locking_callback(hammer2_crypto_locking_callback); +} + +static +int +hammer2_crypto_gcm_init(hammer2_ioq_t *ioq, char *key, int klen, + char *iv_fixed, int ivlen, int enc) +{ + int i, ok; + + if (klen < HAMMER2_CRYPTO_GCM_KEY_SIZE || + ivlen < HAMMER2_CRYPTO_GCM_IV_FIXED_SIZE) { + if (DebugOpt) + fprintf(stderr, "Not enough key or iv material\n"); + return -1; + } + + printf("%s key: ", enc ? "Encryption" : "Decryption"); + for (i = 0; i < HAMMER2_CRYPTO_GCM_KEY_SIZE; ++i) + printf("%02x", (unsigned char)key[i]); + printf("\n"); + + printf("%s iv: ", enc ? "Encryption" : "Decryption"); + for (i = 0; i < HAMMER2_CRYPTO_GCM_IV_FIXED_SIZE; ++i) + printf("%02x", (unsigned char)iv_fixed[i]); + printf(" (fixed part only)\n"); + + EVP_CIPHER_CTX_init(&ioq->ctx); + + if (enc) + ok = EVP_EncryptInit_ex(&ioq->ctx, EVP_aes_256_gcm(), NULL, + key, NULL); + else + ok = EVP_DecryptInit_ex(&ioq->ctx, EVP_aes_256_gcm(), NULL, + key, NULL); + if (!ok) + goto fail; + + /* + * According to the original Galois/Counter Mode of Operation (GCM) + * proposal, only IVs that are exactly 96 bits get used without any + * further processing. Other IV sizes cause the GHASH() operation + * to be applied to the IV, which is more costly. + * + * The NIST SP 800-38D also recommends using a 96 bit IV for the same + * reasons. We actually follow the deterministic construction + * recommended in NIST SP 800-38D with a 64 bit invocation field as an + * integer counter and a random, session-specific fixed field. + * + * This means that we can essentially use the same session key and + * IV fixed field for up to 2^64 invocations of the authenticated + * encryption or decryption. + * + * With a chunk size of 64 bytes, this adds up to 1 zettabyte of + * traffic. + */ + ok = EVP_CIPHER_CTX_ctrl(&ioq->ctx, EVP_CTRL_GCM_SET_IVLEN, + HAMMER2_CRYPTO_GCM_IV_SIZE, NULL); + if (!ok) + goto fail; + + memset(ioq->iv, 0, HAMMER2_CRYPTO_GCM_IV_SIZE); + memcpy(ioq->iv, iv_fixed, HAMMER2_CRYPTO_GCM_IV_FIXED_SIZE); + + /* + * Strictly speaking, padding is irrelevant with a counter mode + * encryption. + * + * However, setting padding to 0, even if using a counter mode such + * as GCM, will cause an error in _finish if the pt/ct size is not + * a multiple of the cipher block size. + */ + EVP_CIPHER_CTX_set_padding(&ioq->ctx, 0); + + return 0; + +fail: + if (DebugOpt) + fprintf(stderr, "Error during _gcm_init\n"); + return -1; +} + +static +int +_gcm_iv_increment(char *iv) +{ + /* + * Deterministic construction according to NIST SP 800-38D, with + * 64 bit invocation field as integer counter. + * + * In other words, our 96 bit IV consists of a 32 bit fixed field + * unique to the session and a 64 bit integer counter. + */ + + uint64_t *c = (uint64_t *)(&iv[HAMMER2_CRYPTO_GCM_IV_FIXED_SIZE]); + + /* Increment invocation field integer counter */ + *c = htobe64(be64toh(*c)+1); + + /* + * Detect wrap-around, which means it is time to renegotiate + * the session to get a new key and/or fixed field. + */ + return (c == 0) ? 0 : 1; +} + +static +int +hammer2_crypto_gcm_encrypt_chunk(hammer2_ioq_t *ioq, char *ct, char *pt, + int in_size, int *out_size) +{ + int ok; + int u_len, f_len; + + *out_size = 0; + + /* Re-initialize with new IV (but without redoing the key schedule) */ + ok = EVP_EncryptInit_ex(&ioq->ctx, NULL, NULL, NULL, ioq->iv); + if (!ok) + goto fail; + + ok = EVP_EncryptUpdate(&ioq->ctx, ct, &u_len, pt, in_size); + if (!ok) + goto fail; + + ok = EVP_EncryptFinal(&ioq->ctx, ct + u_len, &f_len); + if (!ok) + goto fail; + + /* Retrieve auth tag */ + ok = EVP_CIPHER_CTX_ctrl(&ioq->ctx, EVP_CTRL_GCM_GET_TAG, + HAMMER2_CRYPTO_GCM_TAG_SIZE, + ct + u_len + f_len); + if (!ok) + goto fail; + + ok = _gcm_iv_increment(ioq->iv); + if (!ok) { + ioq->error = HAMMER2_IOQ_ERROR_IVWRAP; + goto fail_out; + } + + *out_size = u_len + f_len + HAMMER2_CRYPTO_GCM_TAG_SIZE; + + return 0; + +fail: + ioq->error = HAMMER2_IOQ_ERROR_ALGO; +fail_out: + if (DebugOpt) + fprintf(stderr, "error during encrypt_chunk\n"); + return -1; +} + +static +int +hammer2_crypto_gcm_decrypt_chunk(hammer2_ioq_t *ioq, char *ct, char *pt, + int out_size, int *consume_size) +{ + int ok; + int u_len, f_len; + + *consume_size = 0; + + /* Re-initialize with new IV (but without redoing the key schedule) */ + ok = EVP_DecryptInit_ex(&ioq->ctx, NULL, NULL, NULL, ioq->iv); + if (!ok) { + ioq->error = HAMMER2_IOQ_ERROR_ALGO; + goto fail_out; + } + + ok = EVP_CIPHER_CTX_ctrl(&ioq->ctx, EVP_CTRL_GCM_SET_TAG, + HAMMER2_CRYPTO_GCM_TAG_SIZE, + ct + out_size); + if (!ok) { + ioq->error = HAMMER2_IOQ_ERROR_ALGO; + goto fail_out; + } + + ok = EVP_DecryptUpdate(&ioq->ctx, pt, &u_len, ct, out_size); + if (!ok) + goto fail; + + ok = EVP_DecryptFinal(&ioq->ctx, pt + u_len, &f_len); + if (!ok) + goto fail; + + ok = _gcm_iv_increment(ioq->iv); + if (!ok) { + ioq->error = HAMMER2_IOQ_ERROR_IVWRAP; + goto fail_out; + } + + *consume_size = u_len + f_len + HAMMER2_CRYPTO_GCM_TAG_SIZE; + + return 0; + +fail: + ioq->error = HAMMER2_IOQ_ERROR_MACFAIL; +fail_out: + if (DebugOpt) + fprintf(stderr, "error during decrypt_chunk (likely authentication error)\n"); + return -1; +} /* * Synchronously negotiate crypto for a new session. This must occur @@ -100,6 +359,7 @@ hammer2_crypto_negotiate(hammer2_iocom_t *iocom) size_t blkmask; ssize_t n; int fd; + int error; /* * Get the peer IP address for the connection as a string. @@ -374,34 +634,24 @@ keyxchgfail: goto keyxchgfail; /* - * Calculate the session key and initialize the iv[]. + * Use separate session keys and session fixed IVs for receive and + * transmit. */ - assert(HAMMER2_AES_KEY_SIZE * 2 == sizeof(handrx.sess)); - for (i = 0; i < HAMMER2_AES_KEY_SIZE; ++i) { - iocom->sess[i] = handrx.sess[i] ^ handtx.sess[i]; - iocom->ioq_rx.iv[i] = handrx.sess[HAMMER2_AES_KEY_SIZE + i] ^ - handtx.sess[HAMMER2_AES_KEY_SIZE + i]; - iocom->ioq_tx.iv[i] = handrx.sess[HAMMER2_AES_KEY_SIZE + i] ^ - handtx.sess[HAMMER2_AES_KEY_SIZE + i]; - } - printf("sess: "); - for (i = 0; i < HAMMER2_AES_KEY_SIZE; ++i) - printf("%02x", (unsigned char)iocom->sess[i]); - printf("\n"); - printf("iv: "); - for (i = 0; i < HAMMER2_AES_KEY_SIZE; ++i) - printf("%02x", (unsigned char)iocom->ioq_rx.iv[i]); - printf("\n"); - - EVP_CIPHER_CTX_init(&iocom->ioq_rx.ctx); - EVP_DecryptInit_ex(&iocom->ioq_rx.ctx, HAMMER2_AES_TYPE_EVP, NULL, - iocom->sess, iocom->ioq_rx.iv); - EVP_CIPHER_CTX_set_padding(&iocom->ioq_rx.ctx, 0); + error = crypto_algos[HAMMER2_CRYPTO_ALGO].init(&iocom->ioq_rx, handrx.sess, + crypto_algos[HAMMER2_CRYPTO_ALGO].keylen, + handrx.sess + crypto_algos[HAMMER2_CRYPTO_ALGO].keylen, + sizeof(handrx.sess) - crypto_algos[HAMMER2_CRYPTO_ALGO].keylen, + 0 /* decryption */); + if (error) + goto keyxchgfail; - EVP_CIPHER_CTX_init(&iocom->ioq_tx.ctx); - EVP_EncryptInit_ex(&iocom->ioq_tx.ctx, HAMMER2_AES_TYPE_EVP, NULL, - iocom->sess, iocom->ioq_tx.iv); - EVP_CIPHER_CTX_set_padding(&iocom->ioq_tx.ctx, 0); + error = crypto_algos[HAMMER2_CRYPTO_ALGO].init(&iocom->ioq_tx, handtx.sess, + crypto_algos[HAMMER2_CRYPTO_ALGO].keylen, + handtx.sess + crypto_algos[HAMMER2_CRYPTO_ALGO].keylen, + sizeof(handtx.sess) - crypto_algos[HAMMER2_CRYPTO_ALGO].keylen, + 1 /* encryption */); + if (error) + goto keyxchgfail; iocom->flags |= HAMMER2_IOCOMF_CRYPTED; @@ -422,117 +672,96 @@ done: * Decrypt pending data in the ioq's fifo. The data is decrypted in-place. */ void -hammer2_crypto_decrypt(hammer2_iocom_t *iocom, hammer2_ioq_t *ioq) +hammer2_crypto_decrypt(hammer2_iocom_t *iocom __unused, hammer2_ioq_t *ioq) { int p_len; - int n; - int i; + int used; + int error; char buf[512]; - if ((iocom->flags & HAMMER2_IOCOMF_CRYPTED) == 0) - return; - p_len = ioq->fifo_end - ioq->fifo_cdx; - p_len &= ~HAMMER2_AES_KEY_MASK; + /* + * fifo_beg to fifo_cdx is data already decrypted. + * fifo_cdn to fifo_end is data not yet decrypted. + */ + p_len = ioq->fifo_end - ioq->fifo_cdn; /* data not yet decrypted */ + if (p_len == 0) return; - for (i = 0; i < p_len; i += n) { - n = (p_len - i > (int)sizeof(buf)) ? - (int)sizeof(buf) : p_len - i; - bcopy(ioq->buf + ioq->fifo_cdx + i, buf, n); - EVP_DecryptUpdate(&ioq->ctx, - ioq->buf + ioq->fifo_cdx + i, &n, - buf, n); + + while (p_len >= crypto_algos[HAMMER2_CRYPTO_ALGO].taglen + + HAMMER2_CRYPTO_CHUNK_SIZE) { + bcopy(ioq->buf + ioq->fifo_cdn, buf, + crypto_algos[HAMMER2_CRYPTO_ALGO].taglen + + HAMMER2_CRYPTO_CHUNK_SIZE); + error = crypto_algos[HAMMER2_CRYPTO_ALGO].dec_chunk( + ioq, buf, + ioq->buf + ioq->fifo_cdx, + HAMMER2_CRYPTO_CHUNK_SIZE, + &used); +#ifdef CRYPTO_DEBUG + printf("dec: p_len: %d, used: %d, fifo_cdn: %ju, fifo_cdx: %ju\n", + p_len, used, ioq->fifo_cdn, ioq->fifo_cdx); +#endif + p_len -= used; + ioq->fifo_cdn += used; + ioq->fifo_cdx += HAMMER2_CRYPTO_CHUNK_SIZE; +#ifdef CRYPTO_DEBUG + printf("dec: p_len: %d, used: %d, fifo_cdn: %ju, fifo_cdx: %ju\n", + p_len, used, ioq->fifo_cdn, ioq->fifo_cdx); +#endif } - ioq->fifo_cdx += p_len; } /* - * Decrypt data in the message's auxilary buffer. The data is decrypted - * in-place. + * *nactp is set to the number of ORIGINAL bytes consumed by the encrypter. + * The FIFO may contain more data. */ -void -hammer2_crypto_decrypt_aux(hammer2_iocom_t *iocom, hammer2_ioq_t *ioq, - hammer2_msg_t *msg, int already) -{ - int p_len; - int n; - int i; - char buf[512]; - - if ((iocom->flags & HAMMER2_IOCOMF_CRYPTED) == 0) - return; - p_len = msg->aux_size; - assert((p_len & HAMMER2_AES_KEY_MASK) == 0); - if (p_len == 0) - return; - i = already; - while (i < p_len) { - n = (p_len - i > (int)sizeof(buf)) ? - (int)sizeof(buf) : p_len - i; - bcopy(msg->aux_data + i, buf, n); - EVP_DecryptUpdate(&ioq->ctx, - msg->aux_data + i, &n, - buf, n); - i += n; - } -#if 0 - EVP_DecryptUpdate(&iocom->ioq_rx.ctx, - msg->aux_data, &p_len, - msg->aux_data, p_len); -#endif -} - int -hammer2_crypto_encrypt(hammer2_iocom_t *iocom, hammer2_ioq_t *ioq, - struct iovec *iov, int n) +hammer2_crypto_encrypt(hammer2_iocom_t *iocom __unused, hammer2_ioq_t *ioq, + struct iovec *iov, int n, size_t *nactp) { - int p_len; + int p_len, used, ct_used; int i; - int already; - int nmax; + int error; + size_t nmax; - if ((iocom->flags & HAMMER2_IOCOMF_CRYPTED) == 0) - return (n); - nmax = sizeof(ioq->buf) - ioq->fifo_cdx; /* max new bytes */ - already = ioq->fifo_cdx - ioq->fifo_beg; /* already encrypted */ + nmax = sizeof(ioq->buf) - ioq->fifo_end; /* max new bytes */ - for (i = 0; i < n; ++i) { + *nactp = 0; + for (i = 0; i < n && nmax; ++i) { + used = 0; p_len = iov[i].iov_len; - if (p_len <= already) { - already -= p_len; - continue; + assert((p_len & HAMMER2_MSG_ALIGNMASK) == 0); + + while (p_len >= HAMMER2_CRYPTO_CHUNK_SIZE && + nmax >= HAMMER2_CRYPTO_CHUNK_SIZE + + (size_t)crypto_algos[HAMMER2_CRYPTO_ALGO].taglen) { + error = crypto_algos[HAMMER2_CRYPTO_ALGO].enc_chunk( + ioq, + ioq->buf + ioq->fifo_cdx, + (char *)iov[i].iov_base + used, + HAMMER2_CRYPTO_CHUNK_SIZE, &ct_used); +#ifdef CRYPTO_DEBUG + printf("nactp: %ju, p_len: %d, ct_used: %d, used: %d, nmax: %ju\n", + *nactp, p_len, ct_used, used, nmax); +#endif + + *nactp += (size_t)HAMMER2_CRYPTO_CHUNK_SIZE; /* plaintext count */ + used += HAMMER2_CRYPTO_CHUNK_SIZE; + p_len -= HAMMER2_CRYPTO_CHUNK_SIZE; + + ioq->fifo_cdx += (size_t)ct_used; /* crypted count */ + ioq->fifo_cdn += (size_t)ct_used; /* crypted count */ + ioq->fifo_end += (size_t)ct_used; + nmax -= (size_t)ct_used; +#ifdef CRYPTO_DEBUG + printf("nactp: %ju, p_len: %d, ct_used: %d, used: %d, nmax: %ju\n", + *nactp, p_len, ct_used, used, nmax); +#endif } - p_len -= already; - if (p_len > nmax) - p_len = nmax; - EVP_EncryptUpdate(&ioq->ctx, - ioq->buf + ioq->fifo_cdx, &p_len, - (char *)iov[i].iov_base + already, p_len); - ioq->fifo_cdx += p_len; - ioq->fifo_end += p_len; - nmax -= p_len; - if (nmax == 0) - break; - already = 0; } iov[0].iov_base = ioq->buf + ioq->fifo_beg; iov[0].iov_len = ioq->fifo_cdx - ioq->fifo_beg; return (1); } - -void -hammer2_crypto_encrypt_wrote(hammer2_iocom_t *iocom, hammer2_ioq_t *ioq, - int nact) -{ - if ((iocom->flags & HAMMER2_IOCOMF_CRYPTED) == 0) - return; - if (nact == 0) - return; - ioq->fifo_beg += nact; - if (ioq->fifo_beg == ioq->fifo_end) { - ioq->fifo_beg = 0; - ioq->fifo_cdx = 0; - ioq->fifo_end = 0; - } -}