| /* mz_strm_wzaes.c -- Stream for WinZip AES encryption |
| part of the minizip-ng project |
| |
| Copyright (C) Nathan Moinvaziri |
| https://github.com/zlib-ng/minizip-ng |
| Copyright (C) 1998-2010 Brian Gladman, Worcester, UK |
| |
| This program is distributed under the terms of the same license as zlib. |
| See the accompanying LICENSE file for the full text of the license. |
| */ |
| |
| #include "mz.h" |
| #include "mz_crypt.h" |
| #include "mz_strm.h" |
| #include "mz_strm_wzaes.h" |
| |
| /***************************************************************************/ |
| |
| #define MZ_AES_KEY_LENGTH(STRENGTH) (8 * (STRENGTH & 3) + 8) |
| #define MZ_AES_KEYING_ITERATIONS (1000) |
| #define MZ_AES_SALT_LENGTH(STRENGTH) (4 * (STRENGTH & 3) + 4) |
| #define MZ_AES_SALT_LENGTH_MAX (16) |
| #define MZ_AES_PW_LENGTH_MAX (128) |
| #define MZ_AES_PW_VERIFY_SIZE (2) |
| #define MZ_AES_AUTHCODE_SIZE (10) |
| |
| /***************************************************************************/ |
| |
| static mz_stream_vtbl mz_stream_wzaes_vtbl = { |
| mz_stream_wzaes_open, |
| mz_stream_wzaes_is_open, |
| mz_stream_wzaes_read, |
| mz_stream_wzaes_write, |
| mz_stream_wzaes_tell, |
| mz_stream_wzaes_seek, |
| mz_stream_wzaes_close, |
| mz_stream_wzaes_error, |
| mz_stream_wzaes_create, |
| mz_stream_wzaes_delete, |
| mz_stream_wzaes_get_prop_int64, |
| mz_stream_wzaes_set_prop_int64 |
| }; |
| |
| /***************************************************************************/ |
| |
| typedef struct mz_stream_wzaes_s { |
| mz_stream stream; |
| int32_t mode; |
| int32_t error; |
| int16_t initialized; |
| uint8_t buffer[UINT16_MAX]; |
| int64_t total_in; |
| int64_t max_total_in; |
| int64_t total_out; |
| uint8_t strength; |
| const char *password; |
| void *aes; |
| uint32_t crypt_pos; |
| uint8_t crypt_block[MZ_AES_BLOCK_SIZE]; |
| void *hmac; |
| uint8_t nonce[MZ_AES_BLOCK_SIZE]; |
| } mz_stream_wzaes; |
| |
| /***************************************************************************/ |
| |
| int32_t mz_stream_wzaes_open(void *stream, const char *path, int32_t mode) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| uint16_t salt_length = 0; |
| uint16_t password_length = 0; |
| uint16_t key_length = 0; |
| uint8_t kbuf[2 * MZ_AES_KEY_LENGTH_MAX + MZ_AES_PW_VERIFY_SIZE]; |
| uint8_t verify[MZ_AES_PW_VERIFY_SIZE]; |
| uint8_t verify_expected[MZ_AES_PW_VERIFY_SIZE]; |
| uint8_t salt_value[MZ_AES_SALT_LENGTH_MAX]; |
| const char *password = path; |
| |
| wzaes->total_in = 0; |
| wzaes->total_out = 0; |
| wzaes->initialized = 0; |
| |
| if (mz_stream_is_open(wzaes->stream.base) != MZ_OK) |
| return MZ_OPEN_ERROR; |
| |
| if (!password) |
| password = wzaes->password; |
| if (!password) |
| return MZ_PARAM_ERROR; |
| password_length = (uint16_t)strlen(password); |
| if (password_length > MZ_AES_PW_LENGTH_MAX) |
| return MZ_PARAM_ERROR; |
| |
| if (wzaes->strength < 1 || wzaes->strength > 3) |
| return MZ_PARAM_ERROR; |
| |
| key_length = MZ_AES_KEY_LENGTH(wzaes->strength); |
| salt_length = MZ_AES_SALT_LENGTH(wzaes->strength); |
| |
| if (mode & MZ_OPEN_MODE_WRITE) { |
| mz_crypt_rand(salt_value, salt_length); |
| } else if (mode & MZ_OPEN_MODE_READ) { |
| if (mz_stream_read(wzaes->stream.base, salt_value, salt_length) != salt_length) |
| return MZ_READ_ERROR; |
| } |
| |
| /* Derive the encryption and authentication keys and the password verifier */ |
| mz_crypt_pbkdf2((uint8_t *)password, password_length, salt_value, salt_length, |
| MZ_AES_KEYING_ITERATIONS, kbuf, 2 * key_length + MZ_AES_PW_VERIFY_SIZE); |
| |
| /* Initialize the buffer pos */ |
| wzaes->crypt_pos = MZ_AES_BLOCK_SIZE; |
| |
| /* Use fixed zeroed IV/nonce for CTR mode */ |
| memset(wzaes->nonce, 0, sizeof(wzaes->nonce)); |
| |
| /* Initialize for encryption using key 1 */ |
| mz_crypt_aes_reset(wzaes->aes); |
| mz_crypt_aes_set_encrypt_key(wzaes->aes, kbuf, key_length, NULL, 0); |
| |
| /* Initialize for authentication using key 2 */ |
| mz_crypt_hmac_reset(wzaes->hmac); |
| mz_crypt_hmac_set_algorithm(wzaes->hmac, MZ_HASH_SHA1); |
| mz_crypt_hmac_init(wzaes->hmac, kbuf + key_length, key_length); |
| |
| memcpy(verify, kbuf + (2 * key_length), MZ_AES_PW_VERIFY_SIZE); |
| |
| if (mode & MZ_OPEN_MODE_WRITE) { |
| if (mz_stream_write(wzaes->stream.base, salt_value, salt_length) != salt_length) |
| return MZ_WRITE_ERROR; |
| |
| wzaes->total_out += salt_length; |
| |
| if (mz_stream_write(wzaes->stream.base, verify, MZ_AES_PW_VERIFY_SIZE) != MZ_AES_PW_VERIFY_SIZE) |
| return MZ_WRITE_ERROR; |
| |
| wzaes->total_out += MZ_AES_PW_VERIFY_SIZE; |
| } else if (mode & MZ_OPEN_MODE_READ) { |
| wzaes->total_in += salt_length; |
| |
| if (mz_stream_read(wzaes->stream.base, verify_expected, MZ_AES_PW_VERIFY_SIZE) != MZ_AES_PW_VERIFY_SIZE) |
| return MZ_READ_ERROR; |
| |
| wzaes->total_in += MZ_AES_PW_VERIFY_SIZE; |
| |
| if (memcmp(verify_expected, verify, MZ_AES_PW_VERIFY_SIZE) != 0) |
| return MZ_PASSWORD_ERROR; |
| } |
| |
| wzaes->mode = mode; |
| wzaes->initialized = 1; |
| |
| return MZ_OK; |
| } |
| |
| int32_t mz_stream_wzaes_is_open(void *stream) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| if (!wzaes->initialized) |
| return MZ_OPEN_ERROR; |
| return MZ_OK; |
| } |
| |
| static int32_t mz_stream_wzaes_ctr_encrypt(void *stream, uint8_t *buf, int32_t size) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| uint32_t pos = wzaes->crypt_pos; |
| uint32_t i = 0; |
| int32_t err = MZ_OK; |
| |
| while (i < (uint32_t)size) { |
| if (pos == MZ_AES_BLOCK_SIZE) { |
| uint32_t j = 0; |
| |
| /* Increment encryption nonce */ |
| while (j < 8 && !++wzaes->nonce[j]) |
| j += 1; |
| |
| /* Encrypt the nonce using ECB mode to form next xor buffer */ |
| memcpy(wzaes->crypt_block, wzaes->nonce, MZ_AES_BLOCK_SIZE); |
| mz_crypt_aes_encrypt(wzaes->aes, NULL, 0, wzaes->crypt_block, sizeof(wzaes->crypt_block)); |
| pos = 0; |
| } |
| |
| buf[i++] ^= wzaes->crypt_block[pos++]; |
| } |
| |
| wzaes->crypt_pos = pos; |
| return err; |
| } |
| |
| int32_t mz_stream_wzaes_read(void *stream, void *buf, int32_t size) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| int64_t max_total_in = 0; |
| int32_t bytes_to_read = size; |
| int32_t read = 0; |
| |
| max_total_in = wzaes->max_total_in - MZ_AES_FOOTER_SIZE; |
| if ((int64_t)bytes_to_read > (max_total_in - wzaes->total_in)) |
| bytes_to_read = (int32_t)(max_total_in - wzaes->total_in); |
| |
| read = mz_stream_read(wzaes->stream.base, buf, bytes_to_read); |
| |
| if (read > 0) { |
| mz_crypt_hmac_update(wzaes->hmac, (uint8_t *)buf, read); |
| mz_stream_wzaes_ctr_encrypt(stream, (uint8_t *)buf, read); |
| |
| wzaes->total_in += read; |
| } |
| |
| return read; |
| } |
| |
| int32_t mz_stream_wzaes_write(void *stream, const void *buf, int32_t size) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| const uint8_t *buf_ptr = (const uint8_t *)buf; |
| int32_t bytes_to_write = sizeof(wzaes->buffer); |
| int32_t total_written = 0; |
| int32_t written = 0; |
| |
| if (size < 0) |
| return MZ_PARAM_ERROR; |
| |
| do { |
| if (bytes_to_write > (size - total_written)) |
| bytes_to_write = (size - total_written); |
| |
| memcpy(wzaes->buffer, buf_ptr, bytes_to_write); |
| buf_ptr += bytes_to_write; |
| |
| mz_stream_wzaes_ctr_encrypt(stream, (uint8_t *)wzaes->buffer, bytes_to_write); |
| mz_crypt_hmac_update(wzaes->hmac, wzaes->buffer, bytes_to_write); |
| |
| written = mz_stream_write(wzaes->stream.base, wzaes->buffer, bytes_to_write); |
| if (written < 0) |
| return written; |
| |
| total_written += written; |
| } while (total_written < size && written > 0); |
| |
| wzaes->total_out += total_written; |
| return total_written; |
| } |
| |
| int64_t mz_stream_wzaes_tell(void *stream) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| return mz_stream_tell(wzaes->stream.base); |
| } |
| |
| int32_t mz_stream_wzaes_seek(void *stream, int64_t offset, int32_t origin) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| return mz_stream_seek(wzaes->stream.base, offset, origin); |
| } |
| |
| int32_t mz_stream_wzaes_close(void *stream) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| uint8_t expected_hash[MZ_AES_AUTHCODE_SIZE]; |
| uint8_t computed_hash[MZ_HASH_SHA1_SIZE]; |
| |
| mz_crypt_hmac_end(wzaes->hmac, computed_hash, sizeof(computed_hash)); |
| |
| if (wzaes->mode & MZ_OPEN_MODE_WRITE) { |
| if (mz_stream_write(wzaes->stream.base, computed_hash, MZ_AES_AUTHCODE_SIZE) != MZ_AES_AUTHCODE_SIZE) |
| return MZ_WRITE_ERROR; |
| |
| wzaes->total_out += MZ_AES_AUTHCODE_SIZE; |
| } else if (wzaes->mode & MZ_OPEN_MODE_READ) { |
| if (mz_stream_read(wzaes->stream.base, expected_hash, MZ_AES_AUTHCODE_SIZE) != MZ_AES_AUTHCODE_SIZE) |
| return MZ_READ_ERROR; |
| |
| wzaes->total_in += MZ_AES_AUTHCODE_SIZE; |
| |
| /* If entire entry was not read this will fail */ |
| if (memcmp(computed_hash, expected_hash, MZ_AES_AUTHCODE_SIZE) != 0) |
| return MZ_CRC_ERROR; |
| } |
| |
| wzaes->initialized = 0; |
| return MZ_OK; |
| } |
| |
| int32_t mz_stream_wzaes_error(void *stream) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| return wzaes->error; |
| } |
| |
| void mz_stream_wzaes_set_password(void *stream, const char *password) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| wzaes->password = password; |
| } |
| |
| void mz_stream_wzaes_set_strength(void *stream, uint8_t strength) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| wzaes->strength = strength; |
| } |
| |
| int32_t mz_stream_wzaes_get_prop_int64(void *stream, int32_t prop, int64_t *value) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| switch (prop) { |
| case MZ_STREAM_PROP_TOTAL_IN: |
| *value = wzaes->total_in; |
| break; |
| case MZ_STREAM_PROP_TOTAL_OUT: |
| *value = wzaes->total_out; |
| break; |
| case MZ_STREAM_PROP_TOTAL_IN_MAX: |
| *value = wzaes->max_total_in; |
| break; |
| case MZ_STREAM_PROP_HEADER_SIZE: |
| *value = MZ_AES_SALT_LENGTH((int64_t)wzaes->strength) + MZ_AES_PW_VERIFY_SIZE; |
| break; |
| case MZ_STREAM_PROP_FOOTER_SIZE: |
| *value = MZ_AES_AUTHCODE_SIZE; |
| break; |
| default: |
| return MZ_EXIST_ERROR; |
| } |
| return MZ_OK; |
| } |
| |
| int32_t mz_stream_wzaes_set_prop_int64(void *stream, int32_t prop, int64_t value) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)stream; |
| switch (prop) { |
| case MZ_STREAM_PROP_TOTAL_IN_MAX: |
| wzaes->max_total_in = value; |
| break; |
| default: |
| return MZ_EXIST_ERROR; |
| } |
| return MZ_OK; |
| } |
| |
| void *mz_stream_wzaes_create(void) { |
| mz_stream_wzaes *wzaes = (mz_stream_wzaes *)calloc(1, sizeof(mz_stream_wzaes)); |
| if (wzaes) { |
| wzaes->stream.vtbl = &mz_stream_wzaes_vtbl; |
| wzaes->strength = MZ_AES_STRENGTH_256; |
| |
| wzaes->hmac = mz_crypt_hmac_create(); |
| if (!wzaes->hmac) { |
| free(wzaes); |
| return NULL; |
| } |
| wzaes->aes = mz_crypt_aes_create(); |
| if (!wzaes->aes) { |
| mz_crypt_hmac_delete(&wzaes->hmac); |
| free(wzaes); |
| return NULL; |
| } |
| } |
| return wzaes; |
| } |
| |
| void mz_stream_wzaes_delete(void **stream) { |
| mz_stream_wzaes *wzaes = NULL; |
| if (!stream) |
| return; |
| wzaes = (mz_stream_wzaes *)*stream; |
| if (wzaes) { |
| mz_crypt_aes_delete(&wzaes->aes); |
| mz_crypt_hmac_delete(&wzaes->hmac); |
| free(wzaes); |
| } |
| *stream = NULL; |
| } |
| |
| void *mz_stream_wzaes_get_interface(void) { |
| return (void *)&mz_stream_wzaes_vtbl; |
| } |