diff --git a/ChangeLog b/ChangeLog index d58220b12..bf067ed7f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2012-01-29 Vladimir Serbinenko + + Cryptodisk write support. + + * grub-core/disk/cryptodisk.c (grub_crypto_pcbc_encrypt): New function. + (grub_cryptodisk_decrypt): Moved logic to ... + (grub_cryptodisk_endecrypt): ...this. New argument "encrypt". + (grub_cryptodisk_write): Implement. + * grub-core/kern/emu/hostdisk.c (nwrite): Rename to ... + (grub_util_fd_write): ... this. Make global. + * include/grub/emu/hostdisk.h (grub_util_fd_write): New proto. + 2012-01-29 Vladimir Serbinenko * include/grub/list.h (grub_list_remove): Don't crash if element is diff --git a/grub-core/disk/cryptodisk.c b/grub-core/disk/cryptodisk.c index 2852b65c4..8be2e21cf 100644 --- a/grub-core/disk/cryptodisk.c +++ b/grub-core/disk/cryptodisk.c @@ -128,6 +128,29 @@ grub_crypto_pcbc_decrypt (grub_crypto_cipher_handle_t cipher, return GPG_ERR_NO_ERROR; } +static gcry_err_code_t +grub_crypto_pcbc_encrypt (grub_crypto_cipher_handle_t cipher, + void *out, void *in, grub_size_t size, + void *iv) +{ + grub_uint8_t *inptr, *outptr, *end; + grub_uint8_t ivt[cipher->cipher->blocksize]; + if (!cipher->cipher->decrypt) + return GPG_ERR_NOT_SUPPORTED; + if (size % cipher->cipher->blocksize != 0) + return GPG_ERR_INV_ARG; + end = (grub_uint8_t *) in + size; + for (inptr = in, outptr = out; inptr < end; + inptr += cipher->cipher->blocksize, outptr += cipher->cipher->blocksize) + { + grub_memcpy (ivt, inptr, cipher->cipher->blocksize); + grub_crypto_xor (outptr, outptr, iv, cipher->cipher->blocksize); + cipher->cipher->encrypt (cipher->ctx, outptr, inptr); + grub_crypto_xor (iv, ivt, outptr, cipher->cipher->blocksize); + } + return GPG_ERR_NO_ERROR; +} + struct lrw_sector { grub_uint8_t low[GRUB_CRYPTODISK_GF_BYTES]; @@ -189,17 +212,18 @@ lrw_xor (const struct lrw_sector *sec, dev->lrw_precalc, sec->low_byte * GRUB_CRYPTODISK_GF_BYTES); } -gcry_err_code_t -grub_cryptodisk_decrypt (struct grub_cryptodisk *dev, - grub_uint8_t * data, grub_size_t len, - grub_disk_addr_t sector) +static gcry_err_code_t +grub_cryptodisk_endecrypt (struct grub_cryptodisk *dev, + grub_uint8_t * data, grub_size_t len, + grub_disk_addr_t sector, int encrypt) { grub_size_t i; gcry_err_code_t err; /* The only mode without IV. */ if (dev->mode == GRUB_CRYPTODISK_MODE_ECB && !dev->rekey) - return grub_crypto_ecb_decrypt (dev->cipher, data, data, len); + return (encrypt ? grub_crypto_ecb_encrypt (dev->cipher, data, data, len) + : grub_crypto_ecb_decrypt (dev->cipher, data, data, len)); for (i = 0; i < len; i += (1U << dev->log_sector_size)) { @@ -269,15 +293,23 @@ grub_cryptodisk_decrypt (struct grub_cryptodisk *dev, switch (dev->mode) { case GRUB_CRYPTODISK_MODE_CBC: - err = grub_crypto_cbc_decrypt (dev->cipher, data + i, data + i, - (1U << dev->log_sector_size), iv); + if (encrypt) + err = grub_crypto_cbc_encrypt (dev->cipher, data + i, data + i, + (1U << dev->log_sector_size), iv); + else + err = grub_crypto_cbc_decrypt (dev->cipher, data + i, data + i, + (1U << dev->log_sector_size), iv); if (err) return err; break; case GRUB_CRYPTODISK_MODE_PCBC: - err = grub_crypto_pcbc_decrypt (dev->cipher, data + i, data + i, - (1U << dev->log_sector_size), iv); + if (encrypt) + err = grub_crypto_pcbc_encrypt (dev->cipher, data + i, data + i, + (1U << dev->log_sector_size), iv); + else + err = grub_crypto_pcbc_decrypt (dev->cipher, data + i, data + i, + (1U << dev->log_sector_size), iv); if (err) return err; break; @@ -294,9 +326,14 @@ grub_cryptodisk_decrypt (struct grub_cryptodisk *dev, { grub_crypto_xor (data + i + j, data + i + j, iv, dev->cipher->cipher->blocksize); - err = grub_crypto_ecb_decrypt (dev->cipher, data + i + j, - data + i + j, - dev->cipher->cipher->blocksize); + if (encrypt) + err = grub_crypto_ecb_encrypt (dev->cipher, data + i + j, + data + i + j, + dev->cipher->cipher->blocksize); + else + err = grub_crypto_ecb_decrypt (dev->cipher, data + i + j, + data + i + j, + dev->cipher->cipher->blocksize); if (err) return err; grub_crypto_xor (data + i + j, data + i + j, iv, @@ -312,17 +349,26 @@ grub_cryptodisk_decrypt (struct grub_cryptodisk *dev, generate_lrw_sector (&sec, dev, (grub_uint8_t *) iv); lrw_xor (&sec, dev, data + i); - err = grub_crypto_ecb_decrypt (dev->cipher, data + i, - data + i, - (1U << dev->log_sector_size)); + if (encrypt) + err = grub_crypto_ecb_encrypt (dev->cipher, data + i, + data + i, + (1U << dev->log_sector_size)); + else + err = grub_crypto_ecb_decrypt (dev->cipher, data + i, + data + i, + (1U << dev->log_sector_size)); if (err) return err; lrw_xor (&sec, dev, data + i); } break; case GRUB_CRYPTODISK_MODE_ECB: - grub_crypto_ecb_decrypt (dev->cipher, data + i, data + i, - (1U << dev->log_sector_size)); + if (encrypt) + grub_crypto_ecb_encrypt (dev->cipher, data + i, data + i, + (1U << dev->log_sector_size)); + else + grub_crypto_ecb_decrypt (dev->cipher, data + i, data + i, + (1U << dev->log_sector_size)); break; default: return GPG_ERR_NOT_IMPLEMENTED; @@ -332,6 +378,14 @@ grub_cryptodisk_decrypt (struct grub_cryptodisk *dev, return GPG_ERR_NO_ERROR; } +gcry_err_code_t +grub_cryptodisk_decrypt (struct grub_cryptodisk *dev, + grub_uint8_t * data, grub_size_t len, + grub_disk_addr_t sector) +{ + return grub_cryptodisk_endecrypt (dev, data, len, sector, 0); +} + gcry_err_code_t grub_cryptodisk_setkey (grub_cryptodisk_t dev, grub_uint8_t *key, grub_size_t keysize) { @@ -526,19 +580,61 @@ grub_cryptodisk_read (grub_disk_t disk, grub_disk_addr_t sector, grub_dprintf ("cryptodisk", "grub_disk_read failed with error %d\n", err); return err; } - gcry_err = grub_cryptodisk_decrypt (dev, (grub_uint8_t *) buf, - size << disk->log_sector_size, - sector); + gcry_err = grub_cryptodisk_endecrypt (dev, (grub_uint8_t *) buf, + size << disk->log_sector_size, + sector, 0); return grub_crypto_gcry_error (gcry_err); } static grub_err_t -grub_cryptodisk_write (grub_disk_t disk __attribute ((unused)), - grub_disk_addr_t sector __attribute ((unused)), - grub_size_t size __attribute ((unused)), - const char *buf __attribute ((unused))) +grub_cryptodisk_write (grub_disk_t disk, grub_disk_addr_t sector, + grub_size_t size, const char *buf) { - return GRUB_ERR_NOT_IMPLEMENTED_YET; + grub_cryptodisk_t dev = (grub_cryptodisk_t) disk->data; + gcry_err_code_t gcry_err; + char *tmp; + grub_err_t err; + +#ifdef GRUB_UTIL + if (dev->cheat) + { + err = grub_util_fd_seek (dev->cheat_fd, dev->cheat, + sector << disk->log_sector_size); + if (err) + return err; + if (grub_util_fd_write (dev->cheat_fd, buf, size << disk->log_sector_size) + != (ssize_t) (size << disk->log_sector_size)) + return grub_error (GRUB_ERR_READ_ERROR, "cannot read from `%s'", + dev->cheat); + return GRUB_ERR_NONE; + } +#endif + + tmp = grub_malloc (size << disk->log_sector_size); + if (!tmp) + return grub_errno; + grub_memcpy (tmp, buf, size << disk->log_sector_size); + + grub_dprintf ("cryptodisk", + "Writing %" PRIuGRUB_SIZE " sectors to sector 0x%" + PRIxGRUB_UINT64_T " with offset of %" PRIuGRUB_UINT64_T "\n", + size, sector, dev->offset); + + gcry_err = grub_cryptodisk_endecrypt (dev, (grub_uint8_t *) tmp, + size << disk->log_sector_size, + sector, 1); + if (gcry_err) + { + grub_free (tmp); + return grub_crypto_gcry_error (gcry_err); + } + + err = grub_disk_write (dev->source_disk, + (sector << (disk->log_sector_size + - GRUB_DISK_SECTOR_BITS)) + dev->offset, + 0, size << disk->log_sector_size, tmp); + grub_free (tmp); + return err; } #ifdef GRUB_UTIL diff --git a/grub-core/kern/emu/hostdisk.c b/grub-core/kern/emu/hostdisk.c index 470bdcd16..1a39e9c56 100644 --- a/grub-core/kern/emu/hostdisk.c +++ b/grub-core/kern/emu/hostdisk.c @@ -958,8 +958,8 @@ grub_util_fd_read (int fd, char *buf, size_t len) /* Write LEN bytes from BUF to FD. Return less than or equal to zero if an error occurs, otherwise return LEN. */ -static ssize_t -nwrite (int fd, const char *buf, size_t len) +ssize_t +grub_util_fd_write (int fd, const char *buf, size_t len) { ssize_t size = len; @@ -1062,7 +1062,7 @@ grub_util_biosdisk_write (grub_disk_t disk, grub_disk_addr_t sector, if (fd < 0) return grub_errno; - if (nwrite (fd, buf, size << disk->log_sector_size) + if (grub_util_fd_write (fd, buf, size << disk->log_sector_size) != (ssize_t) (size << disk->log_sector_size)) grub_error (GRUB_ERR_WRITE_ERROR, "cannot write to `%s'", map[disk->id].device); diff --git a/include/grub/emu/hostdisk.h b/include/grub/emu/hostdisk.h index 8f97dce6d..e8df694a6 100644 --- a/include/grub/emu/hostdisk.h +++ b/include/grub/emu/hostdisk.h @@ -37,6 +37,7 @@ void grub_util_pull_device (const char *osname); grub_err_t grub_util_fd_seek (int fd, const char *name, grub_uint64_t sector); ssize_t grub_util_fd_read (int fd, char *buf, size_t len); +ssize_t grub_util_fd_write (int fd, const char *buf, size_t len); grub_err_t grub_cryptodisk_cheat_mount (const char *sourcedev, const char *cheat); void grub_util_cryptodisk_print_uuid (grub_disk_t disk);