[samples] Add sample hwcrypto app
Sample hwcrypto app illustrates implementing
HWRNG and HWKEY interfaces so it can be used
as a starting point for real implementation.
It MUST not be shipped as is.
Change-Id: I33be3b778ed392488bac81205771537c833b027b
diff --git a/hwcrypto/common.h b/hwcrypto/common.h
new file mode 100644
index 0000000..f40b518
--- /dev/null
+++ b/hwcrypto/common.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <compiler.h>
+#include <sys/types.h>
+#include <trusty_std.h>
+
+#define TLOGE(fmt, ...) \
+ fprintf(stderr, "%s: %d: " fmt, LOG_TAG, __LINE__, ## __VA_ARGS__)
+
+#if LOCAL_TRACE
+#define TLOGI(fmt, ...) \
+ fprintf(stderr, "%s: %d: " fmt, LOG_TAG, __LINE__, ## __VA_ARGS__)
+#else
+#define TLOGI(fmt, ...)
+#endif
+
+typedef void (*event_handler_proc_t) (const uevent_t *ev, void *ctx);
+
+typedef struct tipc_event_handler {
+ event_handler_proc_t proc;
+ void *priv;
+} tipc_event_handler_t;
+
+
+__BEGIN_CDECLS
+
+/*
+ * tipc helpers
+ */
+void tipc_handle_port_errors(const uevent_t *ev);
+void tipc_handle_chan_errors(const uevent_t *ev);
+
+int tipc_send_single_buf(handle_t chan, const void *buf, size_t len);
+int tipc_recv_single_buf(handle_t chan, void *buf, size_t len);
+
+int tipc_send_two_segments(handle_t chan, const void *hdr, size_t hdr_len,
+ const void *payload, size_t payload_len);
+
+int tipc_recv_two_segments(handle_t chan, void *hdr, size_t hdr_len,
+ void *payload, size_t payload_len);
+
+/*
+ * tipc services
+ */
+int hwrng_setup_service(void);
+int hwkey_setup_service(void);
+
+__END_CDECLS
+
diff --git a/hwcrypto/hwkey_srv.c b/hwcrypto/hwkey_srv.c
new file mode 100644
index 0000000..b0984ae
--- /dev/null
+++ b/hwcrypto/hwkey_srv.c
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <compiler.h>
+#include <err.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <trusty_std.h>
+#include <interface/hwkey/hwkey.h>
+
+#include "common.h"
+#include "uuids.h"
+#include "hwkey_srv_priv.h"
+
+#define LOCAL_TRACE 1
+#define LOG_TAG "hwkey_srv"
+
+#define HWKEY_MAX_PAYLOAD_SIZE 2048
+
+struct hwkey_chan_ctx {
+ tipc_event_handler_t evt_handler;
+ handle_t chan;
+ uuid_t uuid;
+};
+
+static void hwkey_port_handler(const uevent_t *ev, void *priv);
+static void hwkey_chan_handler(const uevent_t *ev, void *priv);
+
+static tipc_event_handler_t hwkey_port_evt_handler = {
+ .proc = hwkey_port_handler,
+};
+
+static uint8_t req_data[HWKEY_MAX_PAYLOAD_SIZE+1];
+static uint8_t key_data[HWKEY_MAX_PAYLOAD_SIZE];
+
+static uint key_slot_cnt;
+static const struct hwkey_keyslot *key_slots;
+
+
+#if WITH_HWCRYPTO_UNITTEST
+/*
+ * Support for hwcrypto unittest keys should be only enabled
+ * to test hwcrypto related APIs
+ */
+
+/* UUID of HWCRYPTO_UNITTEST application */
+static const uuid_t hwcrypto_unittest_uuid = HWCRYPTO_UNITTEST_APP_UUID;
+
+static uint8_t _unittest_key32[32] = "unittestkeyslotunittestkeyslotun";
+static uint32_t get_unittest_key32(const struct hwkey_keyslot *slot,
+ uint8_t *kbuf, size_t kbuf_len, size_t *klen)
+{
+ assert(kbuf);
+ assert(klen);
+ assert(kbuf_len >= sizeof(_unittest_key32));
+
+ /* just return predefined key */
+ memcpy(kbuf, _unittest_key32, sizeof(_unittest_key32));
+ *klen = sizeof(_unittest_key32);
+
+ return HWKEY_NO_ERROR;
+}
+
+static const struct hwkey_keyslot test_key_slots[] = {
+ {
+ .uuid = &hwcrypto_unittest_uuid,
+ .key_id = "com.android.trusty.hwcrypto.unittest.key32",
+ .handler = get_unittest_key32,
+ },
+};
+#endif /* WITH_HWCRYPTO_UNITTEST */
+
+/*
+ * Close specified hwkey context
+ */
+static void hwkey_ctx_close(struct hwkey_chan_ctx *ctx)
+{
+ close(ctx->chan);
+ free(ctx);
+}
+
+/*
+ * Send response message
+ */
+static int hwkey_send_rsp(struct hwkey_chan_ctx *ctx,
+ struct hwkey_msg *rsp_hdr,
+ uint8_t *rsp_data, size_t rsp_data_len)
+{
+ rsp_hdr->cmd |= HWKEY_RESP_BIT;
+ return tipc_send_two_segments(ctx->chan,
+ rsp_hdr, sizeof(*rsp_hdr),
+ rsp_data, rsp_data_len);
+}
+
+
+static uint32_t _handle_slots(struct hwkey_chan_ctx *ctx,
+ const char *slot_id,
+ const struct hwkey_keyslot *slots, uint slot_cnt,
+ uint8_t *kbuf, size_t kbuf_len, size_t *klen)
+{
+ if (!slots)
+ return HWKEY_ERR_NOT_FOUND;
+
+ for (uint i = 0; i < slot_cnt; i++, slots++) {
+
+ /* check key id */
+ if (strcmp(slots->key_id, slot_id))
+ continue;
+
+ /* Check if the caller is allowed to get that key */
+ if (memcmp(&ctx->uuid, slots->uuid, sizeof(uuid_t)) == 0) {
+ if (slots->handler) {
+ return slots->handler(slots, kbuf, kbuf_len, klen);
+ }
+ }
+ }
+ return HWKEY_ERR_NOT_FOUND;
+}
+
+
+/*
+ * Handle get key slot command
+ */
+static int hwkey_handle_get_keyslot_cmd(struct hwkey_chan_ctx *ctx,
+ struct hwkey_msg *hdr,
+ const char *slot_id)
+{
+ int rc;
+ size_t klen = 0;
+
+ hdr->status = _handle_slots(ctx, slot_id,
+ key_slots, key_slot_cnt,
+ key_data, sizeof(key_data), &klen);
+
+#if WITH_HWCRYPTO_UNITTEST
+ if (hdr->status == HWKEY_ERR_NOT_FOUND) {
+ /* also search test keys */
+ hdr->status = _handle_slots(ctx, slot_id,
+ test_key_slots, countof(test_key_slots),
+ key_data, sizeof(key_data), &klen);
+ }
+#endif
+
+ rc = hwkey_send_rsp(ctx, hdr, key_data, klen);
+ if (klen) {
+ /* sanitize key buffer */
+ memset(key_data, 0, klen);
+ }
+ return rc;
+}
+
+/*
+ * Handle Derive key cmd
+ */
+static int hwkey_handle_derive_key_cmd(struct hwkey_chan_ctx *ctx,
+ struct hwkey_msg *hdr,
+ const uint8_t *ikm_data, size_t ikm_len)
+{
+ int rc;
+ size_t key_len = sizeof(key_data);
+
+ /* check requested key derivation function */
+ if (hdr->arg1 == HWKEY_KDF_VERSION_BEST)
+ hdr->arg1 = HWKEY_KDF_VERSION_1; /* we only support V1 */
+
+ switch (hdr->arg1) {
+ case HWKEY_KDF_VERSION_1:
+ hdr->status = derive_key_v1(&ctx->uuid, ikm_data, ikm_len,
+ key_data, &key_len);
+ break;
+
+ default:
+ TLOGE("%u is unsupported KDF function\n", hdr->arg1);
+ key_len = 0;
+ hdr->status = HWKEY_ERR_NOT_IMPLEMENTED;
+ }
+
+ rc = hwkey_send_rsp(ctx, hdr, key_data, key_len);
+ if (key_len) {
+ /* sanitize key buffer */
+ memset(key_data, 0, key_len);
+ }
+ return rc;
+}
+
+/*
+ * Read and queue HWKEY request message
+ */
+static int hwkey_chan_handle_msg(struct hwkey_chan_ctx *ctx)
+{
+ int rc;
+ size_t req_data_len;
+ struct hwkey_msg hdr;
+
+ rc = tipc_recv_two_segments(ctx->chan, &hdr, sizeof(hdr),
+ req_data, sizeof(req_data) - 1);
+ if (rc < 0) {
+ TLOGE("failed (%d) to recv msg from chan %d\n", rc, ctx->chan);
+ return rc;
+ }
+
+ /* calculate payload length */
+ req_data_len = (size_t)rc - sizeof(hdr);
+
+ /* handle it */
+ switch (hdr.cmd) {
+ case HWKEY_GET_KEYSLOT:
+ req_data[req_data_len] = 0; /* force zero termination */
+ rc = hwkey_handle_get_keyslot_cmd(ctx, &hdr, (const char *)req_data);
+ break;
+
+ case HWKEY_DERIVE:
+ rc = hwkey_handle_derive_key_cmd(ctx, &hdr, req_data, req_data_len);
+ memset(req_data, 0, req_data_len); /* sanitize request buffer */
+ break;
+
+ default:
+ TLOGE("Unsupported request: %d\n", (int)hdr.cmd);
+ hdr.status = HWKEY_ERR_NOT_IMPLEMENTED;
+ rc = hwkey_send_rsp(ctx, &hdr, NULL, 0);
+ }
+
+ return rc;
+}
+
+/*
+ * HWKEY service channel event handler
+ */
+static void hwkey_chan_handler(const uevent_t *ev, void *priv)
+{
+ struct hwkey_chan_ctx *ctx = priv;
+
+ assert(ctx);
+ assert(ev->handle == ctx->chan);
+
+ tipc_handle_chan_errors(ev);
+
+ if (ev->event & IPC_HANDLE_POLL_HUP) {
+ /* closed by peer. */
+ hwkey_ctx_close(ctx);
+ return;
+ }
+
+ if (ev->event & IPC_HANDLE_POLL_MSG) {
+ int rc = hwkey_chan_handle_msg(ctx);
+ if (rc < 0) {
+ /* report an error and close channel */
+ TLOGE("failed (%d) to handle event on channel %d\n",
+ rc, ev->handle);
+ hwkey_ctx_close(ctx);
+ }
+ }
+}
+
+/*
+ * HWKEY service port event handler
+ */
+static void hwkey_port_handler(const uevent_t *ev, void *priv)
+{
+ uuid_t peer_uuid;
+
+ tipc_handle_port_errors(ev);
+
+ if (ev->event & IPC_HANDLE_POLL_READY) {
+ /* incoming connection: accept it */
+ int rc = accept(ev->handle, &peer_uuid);
+ if (rc < 0) {
+ TLOGE("failed (%d) to accept on port %d\n", rc, ev->handle);
+ return;
+ }
+
+ handle_t chan = (handle_t) rc;
+ struct hwkey_chan_ctx *ctx = calloc(1, sizeof(*ctx));
+ if (!ctx) {
+ TLOGE("failed (%d) to allocate context on chan %d\n", rc, chan);
+ close(chan);
+ return;
+ }
+
+ /* init channel state */
+ ctx->evt_handler.priv = ctx;
+ ctx->evt_handler.proc = hwkey_chan_handler;
+ ctx->chan = chan;
+ ctx->uuid = peer_uuid;
+
+ rc = set_cookie(chan, &ctx->evt_handler);
+ if (rc < 0) {
+ TLOGE("failed (%d) to set_cookie on chan %d\n",
+ rc, chan);
+ hwkey_ctx_close(ctx);
+ return;
+ }
+ }
+}
+
+/*
+ * Install Key slot provider
+ */
+void hwkey_install_keys(const struct hwkey_keyslot *keys, uint kcnt)
+{
+ assert(key_slots == NULL);
+ assert(key_slot_cnt == 0);
+ assert(keys && kcnt);
+
+ key_slots = keys;
+ key_slot_cnt = kcnt;
+}
+
+/*
+ * Initialize HWKEY service
+ */
+int hwkey_start_service(void)
+{
+ int rc;
+ handle_t port;
+
+ TLOGI("Start HWKEY service\n");
+
+ /* Initialize service */
+ rc = port_create(HWKEY_PORT, 1, sizeof(struct hwkey_msg) + HWKEY_MAX_PAYLOAD_SIZE,
+ IPC_PORT_ALLOW_TA_CONNECT);
+ if (rc < 0) {
+ TLOGE("Failed (%d) to create port %s\n", rc, HWKEY_PORT);
+ return rc;
+ }
+
+ port = (handle_t) rc;
+ rc = set_cookie(port, &hwkey_port_evt_handler);
+ if (rc) {
+ TLOGE("failed (%d) to set_cookie on port %d\n", rc, port);
+ close(port);
+ return rc;
+ }
+
+ return NO_ERROR;
+}
diff --git a/hwcrypto/hwkey_srv_fake_provider.c b/hwcrypto/hwkey_srv_fake_provider.c
new file mode 100644
index 0000000..c8c1a85
--- /dev/null
+++ b/hwcrypto/hwkey_srv_fake_provider.c
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <trusty_std.h>
+#include <interface/hwkey/hwkey.h>
+#include <openssl/cipher.h>
+#include <openssl/aes.h>
+#include <openssl/digest.h>
+#include <openssl/err.h>
+#include <openssl/hkdf.h>
+
+#include "common.h"
+#include "uuids.h"
+#include "hwkey_srv_priv.h"
+
+#define LOCAL_TRACE 1
+#define LOG_TAG "hwkey_fake_srv"
+
+#warning "Compiling FAKE HWKEY provider"
+
+/*
+ * This module is a sample only. For real device, this code
+ * needs to be rewritten to operate on real per device key that
+ * should come directly or indirectly from hardware.
+ */
+static uint8_t fake_device_key[32] = "this is a fake unique device key";
+
+/* This input vector is taken from RFC 5869 (Extract-and-Expand HKDF) */
+static const uint8_t IKM[] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b };
+
+static const uint8_t salt[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c };
+
+static const uint8_t info[] = { 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9 };
+
+/* Expected pseudorandom key */
+static const uint8_t exp_PRK[] = { 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
+ 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
+ 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
+ 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5 };
+
+/* Expected Output Key */
+static const uint8_t exp_OKM[42]= { 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a,
+ 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a,
+ 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c,
+ 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf,
+ 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18,
+ 0x58, 0x65 };
+
+static bool hkdf_self_test(void)
+{
+ int res;
+ uint8_t OKM[sizeof(exp_OKM)];
+
+ TLOGI("hkdf self test\n");
+
+ /* Check if OKM is OK */
+ memset(OKM, 0x55, sizeof(OKM));
+
+ res = HKDF(OKM, sizeof(OKM), EVP_sha256(),
+ IKM, sizeof(IKM), salt, sizeof(salt), info, sizeof(info));
+ if (!res) {
+ TLOGE("hkdf: failed 0x%x\n", ERR_get_error());
+ return false;
+ }
+
+ res = memcmp(OKM, exp_OKM, sizeof(OKM));
+ if (res) {
+ TLOGE("hkdf: data mismatch\n");
+ return false;
+ }
+
+ TLOGI("hkdf self test: PASSED\n");
+ return true;
+}
+
+/*
+ * Derive key V1 - HMAC SHA256 based Key derivation function
+ */
+uint32_t derive_key_v1(const uuid_t *uuid,
+ const uint8_t *ikm_data, size_t ikm_len,
+ uint8_t *key_buf, size_t *key_len)
+{
+ if (!ikm_len) {
+ *key_len = 0;
+ return HWKEY_ERR_BAD_LEN;
+ }
+
+ if (!HKDF(key_buf, ikm_len, EVP_sha256(),
+ (const uint8_t *)fake_device_key, sizeof(fake_device_key),
+ (const uint8_t *)uuid, sizeof(uuid),
+ ikm_data, ikm_len)) {
+ TLOGE("HDKF failed 0x%x\n", ERR_get_error());
+ *key_len = 0;
+ memset(key_buf, 0, ikm_len);
+ return HWKEY_ERR_GENERIC;
+ }
+
+ *key_len = ikm_len;
+
+ return HWKEY_NO_ERROR;
+}
+
+/*
+ * RPMB Key support
+ */
+#define RPMB_SS_AUTH_KEY_SIZE 32
+#define RPMB_SS_AUTH_KEY_ID "com.android.trusty.storage_auth.rpmb"
+
+/* Secure storage service app uuid */
+static const uuid_t ss_uuid = SECURE_STORAGE_SERVER_APP_UUID;
+
+static uint8_t rpmb_salt[RPMB_SS_AUTH_KEY_SIZE] = {
+ 0x42, 0x18, 0xa9, 0xf2, 0xf6, 0xb1, 0xf5, 0x35,
+ 0x06, 0x37, 0x9f, 0xba, 0xcc, 0x1a, 0xc9, 0x36,
+ 0xf4, 0x83, 0x04, 0xd4, 0xf1, 0x65, 0x91, 0x32,
+ 0xa6, 0xae, 0xda, 0x27, 0x4d, 0x21, 0xdb, 0x40
+};
+
+/*
+ * Generate RPMB Secure Storage Authentication key
+ */
+static uint32_t get_rpmb_ss_auth_key(const struct hwkey_keyslot *slot,
+ uint8_t *kbuf, size_t kbuf_len, size_t *klen)
+{
+ int rc;
+ int out_len;
+ EVP_CIPHER_CTX evp;
+
+ assert(kbuf);
+ assert(klen);
+
+ EVP_CIPHER_CTX_init(&evp);
+
+ rc = EVP_EncryptInit_ex(&evp, EVP_aes_256_cbc(), NULL, fake_device_key, NULL);
+ if (!rc)
+ goto evp_err;
+
+ rc = EVP_CIPHER_CTX_set_padding(&evp, 0);
+ if (!rc)
+ goto evp_err;
+
+ uint min_kbuf_len = RPMB_SS_AUTH_KEY_SIZE + EVP_CIPHER_CTX_key_length(&evp);
+ if (kbuf_len < min_kbuf_len) {
+ TLOGE("buffer too small: (%zd vs. %zd )\n", kbuf_len, min_kbuf_len);
+ goto other_err;
+ }
+
+ rc = EVP_EncryptUpdate(&evp, kbuf, &out_len, rpmb_salt, sizeof(rpmb_salt));
+ if (!rc)
+ goto evp_err;
+
+ if ((size_t)out_len != RPMB_SS_AUTH_KEY_SIZE) {
+ TLOGE("output length mismatch (%zd vs %zd)\n",
+ (size_t)out_len, sizeof(rpmb_salt));
+ goto other_err;
+ }
+
+ rc = EVP_EncryptFinal_ex(&evp, NULL, &out_len);
+ if (!rc)
+ goto evp_err;
+
+ *klen = RPMB_SS_AUTH_KEY_SIZE;
+
+ EVP_CIPHER_CTX_cleanup(&evp);
+ return HWKEY_NO_ERROR;
+
+evp_err:
+ TLOGE("EVP err 0x%x\n", ERR_get_error());
+other_err:
+ EVP_CIPHER_CTX_cleanup(&evp);
+ return HWKEY_ERR_GENERIC;
+}
+
+/*
+ * List of keys slots that hwkey service supports
+ */
+static const struct hwkey_keyslot _keys[] = {
+ {
+ .uuid = &ss_uuid,
+ .key_id = RPMB_SS_AUTH_KEY_ID,
+ .handler = get_rpmb_ss_auth_key,
+ },
+};
+
+/*
+ * Run Self test
+ */
+static bool hwkey_self_test(void)
+{
+ TLOGI("hwkey self test\n");
+
+ if (!hkdf_self_test())
+ return false;
+
+ TLOGI("hwkey self test: PASSED\n");
+ return true;
+}
+
+/*
+ * Initialize Fake HWKEY service provider
+ */
+void hwkey_init_srv_provider(void)
+{
+ int rc;
+
+ TLOGE("Init FAKE!!!! HWKEY service provider\n");
+ TLOGE("FAKE HWKEY service provider MUST be replaced with the REAL one\n");
+
+ /* run self test */
+ if (!hwkey_self_test()) {
+ TLOGE("hwkey_self_test failed\n");
+ abort();
+ }
+
+ /* install key handlers */
+ hwkey_install_keys(_keys, countof(_keys));
+
+ /* start service */
+ rc = hwkey_start_service();
+ if (rc != NO_ERROR ) {
+ TLOGE("failed (%d) to start HWKEY service\n", rc);
+ }
+}
diff --git a/hwcrypto/hwkey_srv_priv.h b/hwcrypto/hwkey_srv_priv.h
new file mode 100644
index 0000000..1836bb6
--- /dev/null
+++ b/hwcrypto/hwkey_srv_priv.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <compiler.h>
+#include <sys/types.h>
+#include <trusty_uuid.h>
+
+struct hwkey_keyslot {
+ const char *key_id;
+ const uuid_t *uuid;
+ const void *priv;
+ uint32_t (*handler)(const struct hwkey_keyslot *slot,
+ uint8_t *kbuf, size_t kbuf_len, size_t *klen);
+};
+
+__BEGIN_CDECLS
+
+void hwkey_init_srv_provider(void);
+
+void hwkey_install_keys(const struct hwkey_keyslot *keys, uint kcnt);
+
+int hwkey_start_service(void);
+
+uint32_t derive_key_v1(const uuid_t *uuid,
+ const uint8_t *ikm_data, size_t ikm_len,
+ uint8_t *key_data, size_t *key_len);
+
+__END_CDECLS
+
+
diff --git a/hwcrypto/hwrng_srv.c b/hwcrypto/hwrng_srv.c
new file mode 100644
index 0000000..9f6e4e9
--- /dev/null
+++ b/hwcrypto/hwrng_srv.c
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <list.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <trusty_std.h>
+#include <interface/hwrng/hwrng.h>
+
+#include "common.h"
+#include "hwrng_srv_priv.h"
+
+#define LOCAL_TRACE 1
+#define LOG_TAG "hwrng_srv"
+
+#define HWRNG_SRV_NAME HWRNG_PORT
+#define MAX_HWRNG_MSG_SIZE 4096
+
+struct hwrng_chan_ctx {
+ tipc_event_handler_t evt_handler;
+ struct list_node node;
+ handle_t chan;
+ size_t req_size;
+ bool send_blocked;
+};
+
+static void hwrng_port_handler(const uevent_t *ev, void *priv);
+static void hwrng_chan_handler(const uevent_t *ev, void *priv);
+
+static handle_t hwrng_port = INVALID_IPC_HANDLE;
+
+static tipc_event_handler_t hwrng_port_evt_handler = {
+ .proc = hwrng_port_handler,
+};
+
+static uint8_t rng_data[MAX_HWRNG_MSG_SIZE];
+
+static struct list_node hwrng_req_list = LIST_INITIAL_VALUE(hwrng_req_list);
+
+/****************************************************************************/
+
+/*
+ * Hexdump content of memory region
+ */
+static void _hexdump8(const void *ptr, size_t len)
+{
+ addr_t address = (addr_t)ptr;
+ size_t count;
+ size_t i;
+
+ for (count = 0 ; count < len; count += 16) {
+ fprintf(stderr, "0x%08lx: ", address);
+ for (i=0; i < MIN(len - count, 16); i++) {
+ fprintf(stderr, "0x%02hhx ", *(const uint8_t *)(address + i));
+ }
+ fprintf(stderr, "\n");
+ address += 16;
+ }
+}
+
+/*
+ * Close specified HWRNG service channel
+ */
+static void hwrng_close_chan(struct hwrng_chan_ctx *ctx)
+{
+ close(ctx->chan);
+
+ if (list_in_list(&ctx->node))
+ list_delete(&ctx->node);
+
+ free(ctx);
+}
+
+/*
+ * Handle HWRNG request queue
+ */
+static bool hwrng_handle_req_queue(void)
+{
+ struct hwrng_chan_ctx *ctx;
+ struct hwrng_chan_ctx *temp;
+
+ /* service channels */
+ bool need_more = false;
+
+ /* for all pending requests */
+ list_for_every_entry_safe(&hwrng_req_list, ctx, temp,
+ struct hwrng_chan_ctx, node) {
+
+ if (ctx->send_blocked)
+ continue; /* cant service it rignt now */
+
+ size_t len = ctx->req_size;
+
+ if (len > MAX_HWRNG_MSG_SIZE)
+ len = MAX_HWRNG_MSG_SIZE;
+
+ /* get rng data */
+ hwrng_dev_get_rng_data(rng_data, len);
+
+ /* send reply */
+ int rc = tipc_send_single_buf(ctx->chan, rng_data, len);
+ if (rc < 0) {
+ if (rc == ERR_NOT_ENOUGH_BUFFER) {
+ /* mark it as send_blocked */
+ ctx->send_blocked = true;
+ } else {
+ /* just close HWRNG request channel */
+ TLOGE("failed (%d) to send_reply\n", rc);
+ hwrng_close_chan(ctx);
+ }
+ continue;
+ }
+
+ ctx->req_size -= len;
+
+ if (ctx->req_size == 0) {
+ /* remove it from pending list */
+ list_delete(&ctx->node);
+ } else {
+ need_more = true;
+ }
+ }
+
+ return need_more;
+}
+
+/*
+ * Check if we can handle request queue
+ */
+static void hwrng_kick_req_queue(void)
+{
+ hwrng_handle_req_queue();
+}
+
+/*
+ * Read and queue HWRNG request message
+ */
+static int hwrng_chan_handle_msg(struct hwrng_chan_ctx *ctx)
+{
+ int rc;
+ struct hwrng_req req;
+
+ assert(ctx);
+
+ /* read request */
+ rc = tipc_recv_single_buf(ctx->chan, &req, sizeof(req));
+ if (rc != sizeof(req)) {
+ TLOGE("failed (%d) to receive msg for chan %d\n",
+ rc, ctx->chan);
+ return rc;
+ }
+
+ /* check if we already have request in progress */
+ if (list_in_list(&ctx->node)) {
+ /* extend it */
+ ctx->req_size += req.len;
+ } else {
+ /* queue it */
+ ctx->req_size = req.len;
+ list_add_tail(&hwrng_req_list, &ctx->node);
+ }
+
+ return 0;
+}
+
+/*
+ * Channel handler where HWRNG requests are coming from
+ */
+static void hwrng_chan_handler(const uevent_t *ev, void *priv)
+{
+ struct hwrng_chan_ctx *ctx = priv;
+
+ assert(ctx);
+ assert(ev->handle == ctx->chan);
+
+ tipc_handle_chan_errors(ev);
+
+ if (ev->event & IPC_HANDLE_POLL_HUP) {
+ hwrng_close_chan(ctx);
+ } else {
+ if (ev->event & IPC_HANDLE_POLL_SEND_UNBLOCKED) {
+ ctx->send_blocked = false;
+ }
+
+ if (ev->event & IPC_HANDLE_POLL_MSG) {
+ int rc = hwrng_chan_handle_msg(ctx);
+ if (rc) {
+ hwrng_close_chan(ctx);
+ }
+ }
+ }
+
+ /* kick state machine */
+ hwrng_kick_req_queue();
+}
+
+/*
+ * Port were HWRNG requests are coming from
+ */
+static void hwrng_port_handler(const uevent_t *ev, void *priv)
+{
+ uuid_t peer_uuid;
+
+ tipc_handle_port_errors(ev);
+
+ if (ev->event & IPC_HANDLE_POLL_READY) {
+ handle_t chan;
+
+ /* incoming connection: accept it */
+ int rc = accept(ev->handle, &peer_uuid);
+ if (rc < 0) {
+ TLOGE("failed (%d) to accept on port %d\n",
+ rc, ev->handle);
+ return;
+ }
+ chan = (handle_t) rc;
+
+ /* allocate state */
+ struct hwrng_chan_ctx *ctx = calloc(1, sizeof(*ctx));
+ if (!ctx) {
+ TLOGE("failed to alloc state for chan %d\n", chan);
+ close(chan);
+ return;
+ }
+
+ /* init channel state */
+ ctx->evt_handler.priv = ctx;
+ ctx->evt_handler.proc = hwrng_chan_handler;
+ ctx->chan = chan;
+
+ /* attach channel handler */
+ rc = set_cookie(chan, &ctx->evt_handler);
+ if (rc) {
+ TLOGE("failed (%d) to set_cookie on chan %d\n",
+ rc, chan);
+ free(ctx);
+ close(chan);
+ return;
+ }
+ }
+}
+
+/*
+ * Initialize HWRNG services
+ */
+int hwrng_start_service(void)
+{
+ int rc;
+
+ TLOGI("Start HWRNG service\n");
+
+ /* create HWRNG port */
+ rc = port_create(HWRNG_SRV_NAME, 1, MAX_HWRNG_MSG_SIZE,
+ IPC_PORT_ALLOW_TA_CONNECT);
+ if (rc < 0) {
+ TLOGE("Failed (%d) to create port '%s'\n", rc, HWRNG_SRV_NAME);
+ return rc;
+ } else {
+ hwrng_port = (handle_t)rc;
+ set_cookie(hwrng_port, &hwrng_port_evt_handler);
+ }
+
+ return NO_ERROR;
+}
diff --git a/hwcrypto/hwrng_srv_fake_provider.c b/hwcrypto/hwrng_srv_fake_provider.c
new file mode 100644
index 0000000..cc26858
--- /dev/null
+++ b/hwcrypto/hwrng_srv_fake_provider.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+#include "hwrng_srv_priv.h"
+
+#define LOCAL_TRACE 1
+#define LOG_TAG "hwrng_fake_srv"
+
+#warning "Compiling FAKE HWRNG provider"
+
+void hwrng_dev_get_rng_data(uint8_t *buf, size_t buf_len)
+{
+ memset(buf, 0, buf_len);
+}
+
+void hwrng_init_srv_provider(void)
+{
+ int rc;
+
+ TLOGE("Init FAKE!!!! HWRNG service provider\n");
+ TLOGE("FAKE HWRNG service provider MUST be replaced with the REAL one\n");
+
+ /* Nothing to initialize here, just start service */
+ rc = hwrng_start_service();
+ if (rc != NO_ERROR) {
+ TLOGE("failed (%d) to start HWRNG service\n", rc);
+ }
+}
+
diff --git a/hwcrypto/hwrng_srv_priv.h b/hwcrypto/hwrng_srv_priv.h
new file mode 100644
index 0000000..1fa3690
--- /dev/null
+++ b/hwcrypto/hwrng_srv_priv.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <compiler.h>
+
+__BEGIN_CDECLS
+
+void hwrng_init_srv_provider(void);
+int hwrng_start_service(void);
+
+void hwrng_dev_get_rng_data(uint8_t *buf, size_t buf_len);
+
+__END_CDECLS
diff --git a/hwcrypto/main.c b/hwcrypto/main.c
new file mode 100644
index 0000000..cf4e25b
--- /dev/null
+++ b/hwcrypto/main.c
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <assert.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <trusty_std.h>
+
+
+#define LOCAL_TRACE 1
+#define LOG_TAG "hwcrypto_srv"
+
+#include "common.h"
+#include "hwrng_srv_priv.h"
+#include "hwkey_srv_priv.h"
+
+
+/*
+ * Hexdump content of memory region
+ */
+void _hexdump8(const void *ptr, size_t len)
+{
+ addr_t address = (addr_t)ptr;
+ size_t count;
+ size_t i;
+
+ for (count = 0 ; count < len; count += 16) {
+ fprintf(stderr, "0x%08lx: ", address);
+ for (i=0; i < MIN(len - count, 16); i++) {
+ fprintf(stderr, "0x%02hhx ", *(const uint8_t *)(address + i));
+ }
+ fprintf(stderr, "\n");
+ address += 16;
+ }
+}
+
+
+/*
+ * Handle common unexpected port events
+ */
+void tipc_handle_port_errors(const uevent_t *ev)
+{
+ if ((ev->event & IPC_HANDLE_POLL_ERROR) ||
+ (ev->event & IPC_HANDLE_POLL_HUP) ||
+ (ev->event & IPC_HANDLE_POLL_MSG) ||
+ (ev->event & IPC_HANDLE_POLL_SEND_UNBLOCKED)) {
+ /* should never happen with port handles */
+ TLOGE("error event (0x%x) for port (%d)\n",
+ ev->event, ev->handle);
+ abort();
+ }
+}
+
+/*
+ * Handle common unexpected channel events
+ */
+void tipc_handle_chan_errors(const uevent_t *ev)
+{
+ if ((ev->event & IPC_HANDLE_POLL_ERROR) ||
+ (ev->event & IPC_HANDLE_POLL_READY)) {
+ /* close it as it is in an error state */
+ TLOGE("error event (0x%x) for chan (%d)\n",
+ ev->event, ev->handle);
+ abort();
+ }
+}
+
+/*
+ * Send single buf message
+ */
+int tipc_send_single_buf(handle_t chan, const void *buf, size_t len)
+{
+ iovec_t iov = {
+ .base = (void *)buf,
+ .len = len,
+ };
+ ipc_msg_t msg = {
+ .iov = &iov,
+ .num_iov = 1,
+
+ };
+ return send_msg(chan, &msg);
+}
+
+/*
+ * Receive single buf message
+ */
+int tipc_recv_single_buf(handle_t chan, void *buf, size_t len)
+{
+ int rc;
+ ipc_msg_info_t msg_inf;
+
+ rc = get_msg(chan, &msg_inf);
+ if (rc)
+ return rc;
+
+ if (msg_inf.len != len) {
+ /* unexpected msg size */
+ rc = ERR_BAD_LEN;
+ } else {
+ iovec_t iov = {
+ .base = buf,
+ .len = len,
+ };
+ ipc_msg_t msg = {
+ .iov = &iov,
+ .num_iov = 1,
+ };
+ rc = read_msg(chan, msg_inf.id, 0, &msg);
+ }
+
+ put_msg(chan, msg_inf.id);
+ return rc;
+}
+
+/*
+ * Send message consisting of two segments (header and payload)
+ */
+int tipc_send_two_segments(handle_t chan, const void *hdr, size_t hdr_len,
+ const void *payload, size_t payload_len)
+{
+ iovec_t iovs[2] = {
+ {
+ .base = (void *)hdr,
+ .len = hdr_len,
+ },
+ {
+ .base = (void *)payload,
+ .len = payload_len,
+ },
+ };
+ ipc_msg_t msg = {
+ .iov = iovs,
+ .num_iov = countof(iovs),
+ };
+ return send_msg(chan, &msg);
+}
+
+/*
+ * Receive message consisting of two segments (header and payload).
+ */
+int tipc_recv_two_segments(handle_t chan, void *hdr, size_t hdr_len,
+ void *payload, size_t payload_len)
+{
+ int rc;
+ ipc_msg_info_t msg_inf;
+
+ rc = get_msg(chan, &msg_inf);
+ if (rc)
+ return rc;
+
+ if (msg_inf.len < hdr_len) {
+ /* unexpected msg size */
+ rc = ERR_BAD_LEN;
+ } else {
+ iovec_t iovs[2] = {
+ {
+ .base = hdr,
+ .len = hdr_len,
+ },
+ {
+ .base = payload,
+ .len = payload_len,
+ }
+ };
+ ipc_msg_t msg = {
+ .iov = iovs,
+ .num_iov = countof(iovs),
+ };
+ rc = read_msg(chan, msg_inf.id, 0, &msg);
+ }
+
+ put_msg(chan, msg_inf.id);
+ return rc;
+}
+
+/*
+ * Dispatch event
+ */
+static void dispatch_event(const uevent_t *ev)
+{
+ assert(ev);
+
+ if (ev->event == IPC_HANDLE_POLL_NONE) {
+ /* not really an event, do nothing */
+ TLOGI("got an empty event\n");
+ return;
+ }
+
+ /* check if we have handler */
+ struct tipc_event_handler *handler = ev->cookie;
+ if (handler && handler->proc) {
+ /* invoke it */
+ handler->proc(ev, handler->priv);
+ return;
+ }
+
+ /* no handler? close it */
+ TLOGE("no handler for event (0x%x) with handle %d\n",
+ ev->event, ev->handle);
+
+ close(ev->handle);
+
+ return;
+}
+
+/*
+ * Main application event loop
+ */
+int main(void)
+{
+ int rc;
+ uevent_t event;
+
+ TLOGI("Initializing\n");
+
+ /* initialize service providers */
+ hwrng_init_srv_provider();
+ hwkey_init_srv_provider();
+
+ TLOGI("enter main event loop\n");
+
+ /* enter main event loop */
+ while (true) {
+ event.handle = INVALID_IPC_HANDLE;
+ event.event = 0;
+ event.cookie = NULL;
+
+ rc = wait_any(&event, -1);
+ if (rc < 0) {
+ TLOGE("wait_any failed (%d)\n", rc);
+ break;
+ }
+
+ if (rc == NO_ERROR) { /* got an event */
+ dispatch_event(&event);
+ }
+ }
+
+ return rc;
+}
diff --git a/hwcrypto/manifest.c b/hwcrypto/manifest.c
new file mode 100644
index 0000000..1f6c175
--- /dev/null
+++ b/hwcrypto/manifest.c
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <trusty_app_manifest.h>
+
+#include "uuids.h"
+
+trusty_app_manifest_t TRUSTY_APP_MANIFEST_ATTRS trusty_app_manifest =
+{
+ .uuid = GEN_HWCRYPTO_UUID,
+
+ .config_options =
+ /* optional configuration options here */
+ {
+ TRUSTY_APP_CONFIG_MIN_HEAP_SIZE(6 * 4096),
+ },
+};
diff --git a/hwcrypto/rules.mk b/hwcrypto/rules.mk
new file mode 100644
index 0000000..bcb1c0d
--- /dev/null
+++ b/hwcrypto/rules.mk
@@ -0,0 +1,42 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := \
+ $(LOCAL_DIR)/manifest.c \
+ $(LOCAL_DIR)/main.c \
+ $(LOCAL_DIR)/hwrng_srv.c \
+ $(LOCAL_DIR)/hwkey_srv.c \
+
+ifeq (true,$(call TOBOOL,$(WITH_FAKE_HWRNG)))
+MODULE_SRCS += $(LOCAL_DIR)/hwrng_srv_fake_provider.c
+endif
+
+ifeq (true,$(call TOBOOL,$(WITH_FAKE_HWKEY)))
+MODULE_SRCS += $(LOCAL_DIR)/hwkey_srv_fake_provider.c
+endif
+
+MODULE_DEPS := \
+ app/trusty \
+ lib/libc-trusty \
+ interface/hwrng \
+ interface/hwkey \
+ openssl \
+
+include make/module.mk
diff --git a/hwcrypto/uuids.h b/hwcrypto/uuids.h
new file mode 100644
index 0000000..c8866a9
--- /dev/null
+++ b/hwcrypto/uuids.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/* This App UUID: {23fe5938-ccd5-4a78-8baf-0f3d05ffc2df} */
+#define GEN_HWCRYPTO_UUID \
+ { 0x23fe5938, 0xccd5, 0x4a78, \
+ { 0x8b, 0xaf, 0x0f, 0x3d, 0x05, 0xff, 0xc2, 0xdf }}
+
+/* HWCRYPTO unittest App UUID */
+#define HWCRYPTO_UNITTEST_APP_UUID \
+ { 0xab742471, 0xd6e6, 0x4806, \
+ { 0x85, 0xf6, 0x05, 0x55, 0xb0, 0x24, 0xf4, 0xda }}
+
+/* Secure Storage Server App UUID */
+#define SECURE_STORAGE_SERVER_APP_UUID \
+ {0xcea8706d, 0x6cb4, 0x49f3,\
+ { 0xb9, 0x94, 0x29, 0xe0, 0xe4, 0x78, 0xbd, 0x29 }}