/* * Apple A6 (S5L8950X) SHA-1 accelerator used by the SecureROM. * * The ROM supplies already padded 64-byte SHA-1 blocks through the input * window. QEMU's hash API expects the original message, so this model keeps * the submitted blocks, validates/removes the standard SHA-1 padding, and * exposes the resulting five digest words through the hardware registers. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" #include "crypto/hash.h" #include "hw/core/cpu.h" #include "hw/misc/s5l8950x-sha1.h" #include "hw/core/sysbus.h" #include "qemu/log.h" #include "qemu/module.h" #include "qemu/units.h" #define A6_SHA1_MMIO_SIZE 0x100 #define A6_SHA1_RESET 0x008 #define A6_SHA1_COMMAND 0x00c #define A6_SHA1_STATUS 0x010 #define A6_SHA1_DIGEST_BASE 0x040 #define A6_SHA1_DIGEST_SIZE QCRYPTO_HASH_DIGEST_LEN_SHA1 #define A6_SHA1_INPUT_BASE 0x080 #define A6_SHA1_BLOCK_SIZE 64 #define A6_SHA1_MAX_MESSAGE (16 * MiB) struct S5L8950XSHA1State { SysBusDevice parent_obj; MemoryRegion iomem; uint32_t reset; uint32_t command; uint32_t status; uint8_t input[A6_SHA1_BLOCK_SIZE]; uint8_t digest[A6_SHA1_DIGEST_SIZE]; GByteArray *padded; bool digest_valid; }; static bool s5l8950x_sha1_finalize(S5L8950XSHA1State *s) { uint64_t bit_length; uint64_t message_length; uint8_t *digest = s->digest; size_t digest_length = sizeof(s->digest); if (!s->padded->len || s->padded->len % A6_SHA1_BLOCK_SIZE || s->padded->len < 9) { return false; } bit_length = ldq_be_p(s->padded->data + s->padded->len - 8); if (bit_length & 7) { return false; } message_length = bit_length / 8; if (message_length > A6_SHA1_MAX_MESSAGE || message_length >= s->padded->len - 8 || s->padded->data[message_length] != 0x80) { return false; } for (size_t i = message_length + 1; i < s->padded->len - 8; i++) { if (s->padded->data[i]) { return false; } } if (qcrypto_hash_bytes(QCRYPTO_HASH_ALGO_SHA1, s->padded->data, message_length, &digest, &digest_length, NULL) < 0 || digest_length != sizeof(s->digest)) { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x.sha1: SHA-1 calculation failed\n"); return false; } s->digest_valid = true; qemu_log_mask(LOG_UNIMP, "s5l8950x.sha1: PC=0x%" VADDR_PRIx " hashed %" PRIu64 " guest bytes\n", current_cpu ? current_cpu->cc->get_pc(current_cpu) : 0, message_length); return true; } static void s5l8950x_sha1_submit(S5L8950XSHA1State *s) { if (s->padded->len > A6_SHA1_MAX_MESSAGE + 2 * A6_SHA1_BLOCK_SIZE) { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x.sha1: guest message is too large\n"); return; } g_byte_array_append(s->padded, s->input, sizeof(s->input)); s->digest_valid = false; } static uint64_t s5l8950x_sha1_read(void *opaque, hwaddr offset, unsigned size) { S5L8950XSHA1State *s = opaque; if (offset >= A6_SHA1_INPUT_BASE && offset <= A6_SHA1_MMIO_SIZE - sizeof(uint32_t)) { return ldl_le_p(s->input + offset - A6_SHA1_INPUT_BASE); } if (offset >= A6_SHA1_DIGEST_BASE && offset < A6_SHA1_DIGEST_BASE + A6_SHA1_DIGEST_SIZE) { if (!s->digest_valid) { s5l8950x_sha1_finalize(s); } return ldl_le_p(s->digest + offset - A6_SHA1_DIGEST_BASE); } switch (offset) { case A6_SHA1_RESET: return s->reset; case A6_SHA1_COMMAND: return s->command; case A6_SHA1_STATUS: return s->status; default: return 0; } } static void s5l8950x_sha1_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { S5L8950XSHA1State *s = opaque; if (offset >= A6_SHA1_INPUT_BASE && offset <= A6_SHA1_MMIO_SIZE - sizeof(uint32_t)) { stl_le_p(s->input + offset - A6_SHA1_INPUT_BASE, value); return; } switch (offset) { case A6_SHA1_RESET: s->reset = value; if (value & 0x10) { g_byte_array_set_size(s->padded, 0); memset(s->input, 0, sizeof(s->input)); memset(s->digest, 0, sizeof(s->digest)); s->digest_valid = false; } break; case A6_SHA1_COMMAND: s->command = value; if (value & 1) { s5l8950x_sha1_submit(s); } /* The SecureROM waits for bit zero to clear after each block. */ s->command &= ~1u; break; case A6_SHA1_STATUS: s->status = value; break; default: break; } } static const MemoryRegionOps s5l8950x_sha1_ops = { .read = s5l8950x_sha1_read, .write = s5l8950x_sha1_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid.min_access_size = 4, .valid.max_access_size = 4, }; static void s5l8950x_sha1_reset(DeviceState *dev) { S5L8950XSHA1State *s = S5L8950X_SHA1(dev); s->reset = 0; s->command = 0; s->status = 0; s->digest_valid = false; memset(s->input, 0, sizeof(s->input)); memset(s->digest, 0, sizeof(s->digest)); if (s->padded) { g_byte_array_set_size(s->padded, 0); } } static void s5l8950x_sha1_init(Object *object) { S5L8950XSHA1State *s = S5L8950X_SHA1(object); SysBusDevice *sbd = SYS_BUS_DEVICE(object); s->padded = g_byte_array_new(); memory_region_init_io(&s->iomem, object, &s5l8950x_sha1_ops, s, TYPE_S5L8950X_SHA1, A6_SHA1_MMIO_SIZE); sysbus_init_mmio(sbd, &s->iomem); } static void s5l8950x_sha1_finalize_instance(Object *object) { S5L8950XSHA1State *s = S5L8950X_SHA1(object); g_clear_pointer(&s->padded, g_byte_array_unref); } static void s5l8950x_sha1_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_legacy_reset(dc, s5l8950x_sha1_reset); } static const TypeInfo s5l8950x_sha1_type_info = { .name = TYPE_S5L8950X_SHA1, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(S5L8950XSHA1State), .instance_init = s5l8950x_sha1_init, .instance_finalize = s5l8950x_sha1_finalize_instance, .class_init = s5l8950x_sha1_class_init, }; static void s5l8950x_sha1_register_types(void) { type_register_static(&s5l8950x_sha1_type_info); } type_init(s5l8950x_sha1_register_types)