From 4d7df9c37f04ae65e8dc3097262b055c70008172 Mon Sep 17 00:00:00 2001 From: edtubbs Date: Wed, 24 Jan 2024 14:53:28 -0600 Subject: [PATCH] [feat] added key management enclaves openenclave, optee: added key manager enclaves openenclave, optee: added command line interfaces ci: updated x86 target for openenclave ci: added aarch64 target for op-tee ci: added x86 target for nixos doc: added enclave.md depends: added libyubikey, libusb and libykpiv sha: added sha1 and hmac for authentication with yubikey tests: added sha1 and hmac address: added wrapper for address from account pub key example: added wrapper test: added wrapper to bip44_test [feat] added yubikey for storage config, cmake, seal, tests: added yubikey support seal: added encrypted blobs to software encryption such, spvnode, wallet: updated software encryption tests: added encrypted blobs doc: added yubikey.md [feat] added NanoPC-T6 enclave ci: added NanoPC-T6 support for op-tee doc: updated enclave.md optee: added rk3588-nanopi6-common.dtsi.patch optee: added nanopi6.h.patch --- .github/workflows/ci.yml | 249 +++- .gitignore | 6 + CMakeLists.txt | 50 +- Makefile.am | 1 + configure.ac | 28 + contrib/examples/example.c | 35 +- depends/packages/libusb.mk | 22 + depends/packages/libyubikey.mk | 22 + depends/packages/packages.mk | 4 +- depends/packages/ykpers.mk | 29 + depends/patches/ykpers/ykpers-args.patch | 13 + doc/enclaves.md | 698 ++++++++++ doc/yubikey.md | 38 + include/dogecoin/address.h | 5 +- include/dogecoin/dogecoin.h | 3 +- include/dogecoin/libdogecoin.h | 52 +- include/dogecoin/seal.h | 48 +- include/dogecoin/sha2.h | 29 + src/address.c | 38 +- src/bip39.c | 8 +- src/cli/spvnode.c | 1 + src/cli/such.c | 14 +- src/openenclave/CMakeLists.txt | 24 +- src/openenclave/enclave/CMakeLists.txt | 13 +- src/openenclave/enclave/Makefile | 6 +- src/openenclave/enclave/enc.c | 781 ++++++++++- src/openenclave/host/CMakeLists.txt | 7 +- src/openenclave/host/host.c | 619 ++++++++- src/openenclave/libdogecoin.edl | 18 +- src/optee/Android.mk | 17 + src/optee/CMakeLists.txt | 13 + src/optee/Makefile | 15 + src/optee/common.mk.patch | 18 + src/optee/host/Makefile | 28 + src/optee/host/main.c | 1002 ++++++++++++++ src/optee/nanopi6.h.patch | 24 + src/optee/qemu.conf.patch | 7 + src/optee/rk3588-nanopi6-common.dtsi.patch | 27 + src/optee/ta/Android.mk | 4 + src/optee/ta/Makefile | 13 + src/optee/ta/include/libdogecoin_ta.h | 166 +++ src/optee/ta/libdogecoin_ta.c | 1365 ++++++++++++++++++++ src/optee/ta/sub.mk | 4 + src/optee/ta/user_ta_header_defines.h | 48 + src/random.c | 9 +- src/seal.c | 1184 +++++++++++++---- src/sha2.c | 490 +++++++ src/utils.c | 70 +- src/wallet.c | 6 +- test/bip44_tests.c | 7 +- test/sha1_tests.c | 96 ++ test/tpm_tests.c | 88 +- test/unittester.c | 6 + 53 files changed, 7099 insertions(+), 469 deletions(-) create mode 100644 depends/packages/libusb.mk create mode 100644 depends/packages/libyubikey.mk create mode 100644 depends/packages/ykpers.mk create mode 100644 depends/patches/ykpers/ykpers-args.patch create mode 100644 doc/enclaves.md create mode 100644 doc/yubikey.md create mode 100644 src/optee/Android.mk create mode 100644 src/optee/CMakeLists.txt create mode 100644 src/optee/Makefile create mode 100644 src/optee/common.mk.patch create mode 100644 src/optee/host/Makefile create mode 100644 src/optee/host/main.c create mode 100644 src/optee/nanopi6.h.patch create mode 100644 src/optee/qemu.conf.patch create mode 100644 src/optee/rk3588-nanopi6-common.dtsi.patch create mode 100644 src/optee/ta/Android.mk create mode 100644 src/optee/ta/Makefile create mode 100644 src/optee/ta/include/libdogecoin_ta.h create mode 100644 src/optee/ta/libdogecoin_ta.c create mode 100644 src/optee/ta/sub.mk create mode 100644 src/optee/ta/user_ta_header_defines.h create mode 100644 test/sha1_tests.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0fd0e444..b30d51a87 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,9 @@ jobs: name: - armhf-linux - aarch64-linux + - aarch64-linux-optee - aarch64-android + - x86_64-nixos - x86_64-linux-dbg - x86_64-linux-openenclave - x86_64-macos @@ -53,6 +55,15 @@ jobs: config-opts: "LIBS='-levent_pthreads' --enable-static --disable-shared --enable-test-passwd" run-tests: true goal: install + - name: aarch64-linux-optee + host: aarch64-linux-gnu + os: ubuntu-20.04 + run-tests: true + run-container: true + packages: g++-aarch64-linux-gnu qemu-user-static qemu-user + dep-opts: "CROSS_COMPILE='yes' SPEED=slow V=1" + config-opts: "LIBS=-levent_pthreads --enable-static --disable-shared --enable-test-passwd --enable-optee CFLAGS=-U_FORTIFY_SOURCE" + goal: install - name: aarch64-android host: aarch64-linux-android os: ubuntu-20.04 @@ -63,6 +74,15 @@ jobs: goal: install android-ndk: android-ndk-r25c-linux android-ndk-shasum: "769ee342ea75f80619d985c2da990c48b3d8eaf45f48783a2d48870d04b46108" + - name: x86_64-nixos + host: x86_64-pc-linux-gnu + os: ubuntu-20.04 + packages: docker.io + dep-opts: "SPEED=slow V=1" + config-opts: "--enable-static --disable-shared --enable-test-passwd" + run-tests: true + run-container: true + goal: install - name: x86_64-linux-dbg host: x86_64-pc-linux-gnu os: ubuntu-20.04 @@ -75,9 +95,10 @@ jobs: host: x86_64-pc-linux-gnu os: ubuntu-20.04 run-tests: true + run-container: true packages: python3-dev python3-dbg python dep-opts: "DEBUG=1 SPEED=slow V=1" - config-opts: "--enable-debug --enable-openenclave --enable-test-passwd CFLAGS=-U_FORTIFY_SOURCE" + config-opts: "--enable-openenclave --enable-test-passwd CFLAGS=-U_FORTIFY_SOURCE" goal: install - name: x86_64-macos host: x86_64-apple-darwin15 @@ -177,7 +198,7 @@ jobs: source ~/.bashrc else sudo apt-get update - DEBIAN_FRONTEND=noninteractive sudo apt-get install -y autoconf automake libtool-bin libevent-dev build-essential curl python3 valgrind ${{ matrix.packages }} + DEBIAN_FRONTEND=noninteractive sudo apt-get install -y autoconf automake libtool-bin build-essential curl python3 valgrind ${{ matrix.packages }} fi fi shell: bash @@ -263,15 +284,15 @@ jobs: run: | build_dir=./build/libdogecoin-${{ github.sha }}-${{ matrix.name }} mkdir -p $build_dir/bin $build_dir/docs $build_dir/examples $build_dir/include $build_dir/lib $build_dir/test/ $build_dir/test/wordlist - if [ "${{ matrix.host }}" == "x86_64-pc-windows-msvc" ]; then + if ([ "${{ matrix.host }}" == "x86_64-pc-windows-msvc" ]); then cmake -B $build_dir cmake --build $build_dir else make -j"$(getconf _NPROCESSORS_ONLN)" SPEED=slow V=1 if ([ "${{ matrix.name }}" == "x86_64-win" ] || [ "${{ matrix.name }}" == "i686-win" ]); then - cp spvnode.exe such.exe sendtx.exe $build_dir/bin/ + cp spvnode.exe such.exe sendtx.exe $build_dir/bin/ else - cp spvnode such sendtx $build_dir/bin/ + cp spvnode such sendtx $build_dir/bin/ fi cp doc/*.md $build_dir/docs/ cp contrib/examples/example.c $build_dir/examples/ @@ -294,41 +315,15 @@ jobs: "armhf-linux") qemu-arm -E LD_LIBRARY_PATH=/usr/arm-linux-gnueabihf/lib/ /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 ./tests ;; - "aarch64-linux") + "aarch64-linux" | "aarch64-linux-optee") qemu-aarch64 -E LD_LIBRARY_PATH=/usr/aarch64-linux-gnu/lib/ /usr/aarch64-linux-gnu/lib/ld-linux-aarch64.so.1 ./tests ;; - "x86_64-linux-dbg") + "x86_64-linux-dbg" | "x86_64-linux-openenclave" | "x86_64-nixos") make check -j"$(getconf _NPROCESSORS_ONLN)" V=1 python3 tooltests.py sudo ./rpctest/fetch.py --host x86_64-linux-gnu sudo rm /usr/local/bin/dogecoind ;; - "x86_64-linux-openenclave") - make check -j"$(getconf _NPROCESSORS_ONLN)" V=1 - python3 tooltests.py - sudo make install - - echo 'deb [arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu focal main' | sudo tee /etc/apt/sources.list.d/intel-sgx.list - wget -qO - https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | sudo apt-key add - - - echo "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-11 main" | sudo tee /etc/apt/sources.list.d/llvm-toolchain-focal-11.list - wget -qO - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - - - echo "deb [arch=amd64] https://packages.microsoft.com/ubuntu/20.04/prod focal main" | sudo tee /etc/apt/sources.list.d/msprod.list - wget -qO - https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - - - sudo apt update - - sudo apt -y install clang-11 libssl-dev gdb libsgx-enclave-common libsgx-quote-ex libprotobuf17 libsgx-dcap-ql libsgx-dcap-ql-dev az-dcap-client open-enclave - sudo apt -y install dkms - source /opt/openenclave/share/openenclave/openenclaverc - cd src/openenclave - mkdir build - cd build - cmake .. - make - make simulate - ;; "aarch64-android"): wget https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip unzip commandlinetools-linux-6858069_latest.zip @@ -366,12 +361,198 @@ jobs: ;; esac + - name: run container + if: ${{ matrix.run-container }} + shell: bash + run: | + case "${{ matrix.name }}" in + "x86_64-linux-openenclave" | "aarch64-linux-optee" | "x86_64-nixos"): + if ([ "${{ matrix.name }}" == "aarch64-linux-optee" ]); then + make install + docker pull jforissier/optee_os_ci:qemu_check + docker run -v "$(pwd):/src" -w /src jforissier/optee_os_ci:qemu_check /bin/bash -c "\ + # Set up the environment and build the OP-TEE SDK + set -e && \ + apt update && \ + apt -y upgrade && \ + apt -y install netcat libusb-1.0-0-dev swig python2 python3-dev python3-setuptools e2tools && \ + curl https://storage.googleapis.com/git-repo-downloads/repo > /bin/repo && chmod a+x /bin/repo && \ + mkdir -p optee && \ + cd optee && \ + repo init -u https://github.com/edtubbs/manifest.git -m nanopc-t6.xml -b nanopc-t6 && \ + export FORCE_UNSAFE_CONFIGURE=1 && \ + repo sync -j\"$(getconf _NPROCESSORS_ONLN)\" && \ + patch -F 4 /src/optee/build/common.mk < /src/src/optee/common.mk.patch && \ + patch /src/optee/build/kconfigs/qemu.conf < /src/src/optee/qemu.conf.patch && \ + patch /src/optee/linux/arch/arm64/boot/dts/rockchip/rk3588-nanopi6-common.dtsi < /src/src/optee/rk3588-nanopi6-common.dtsi.patch && \ + patch /src/optee/u-boot/include/configs/nanopi6.h < /src/src/optee/nanopi6.h.patch && \ + cd build && \ + make toolchains -j\"$(getconf _NPROCESSORS_ONLN)\" && \ + export CFG_TEE_CORE_LOG_LEVEL=0 && \ + export CFG_ATTESTATION_PTA=y && \ + export CFG_ATTESTATION_PTA_KEY_SIZE=1024 && \ + export CFG_WITH_USER_TA=y && \ + + # Generate subkeys + openssl genrsa -out /src/optee/optee_test/ta/top_level_subkey.pem && \ + openssl genrsa -out /src/optee/optee_test/ta/mid_level_subkey.pem && \ + openssl genrsa -out /src/optee/optee_test/ta/identity_subkey2.pem && \ + + # Sign the top-level subkey with the root key + /src/optee/optee_os/scripts/sign_encrypt.py sign-subkey \ + --uuid f04fa996-148a-453c-b037-1dcfbad120a6 \ + --key /src/optee/optee_os/keys/default_ta.pem --in /src/optee/optee_test/ta/top_level_subkey.pem \ + --out /src/optee/optee_test/ta/top_level_subkey.bin --max-depth 4 --name-size 64 \ + --subkey-version 1 && \ + + # Generate UUID for the mid-level subkey + /src/optee/optee_os/scripts/sign_encrypt.py subkey-uuid --in /src/optee/optee_test/ta/top_level_subkey.bin \ + --name mid_level_subkey && \ + + # Sign the mid-level subkey with the top-level subkey + /src/optee/optee_os/scripts/sign_encrypt.py sign-subkey \ + --uuid 1a5948c5-1aa0-518c-86f4-be6f6a057b16 \ + --key /src/optee/optee_test/ta/top_level_subkey.pem --subkey /src/optee/optee_test/ta/top_level_subkey.bin \ + --name-size 64 --subkey-version 1 \ + --name mid_level_subkey \ + --in /src/optee/optee_test/ta/mid_level_subkey.pem --out /src/optee/optee_test/ta/mid_level_subkey.bin && \ + + # Generate UUID for subkey1_ta + /src/optee/optee_os/scripts/sign_encrypt.py subkey-uuid --in /src/optee/optee_test/ta/mid_level_subkey.bin \ + --name subkey1_ta && \ + + # Sign the identity subkey2 with the root key + /src/optee/optee_os/scripts/sign_encrypt.py sign-subkey \ + --uuid a720ccbb-51da-417d-b82e-e5445d474a7a \ + --key /src/optee/optee_os/keys/default_ta.pem --in /src/optee/optee_test/ta/identity_subkey2.pem \ + --out /src/optee/optee_test/ta/identity_subkey2.bin --max-depth 0 --name-size 0 \ + --subkey-version 1 && \ + + # Build and test the OP-TEE OS and client + make -j\"$(getconf _NPROCESSORS_ONLN)\" && \ + cd /src && \ + git clone https://github.com/OP-TEE/optee_client.git && \ + cd optee_client && \ + mkdir -p build && \ + cd build && \ + export PATH=/src/optee/toolchains/aarch64/bin:$PATH && \ + export CC=aarch64-linux-gnu-gcc && \ + cmake .. -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc -DCMAKE_INSTALL_PREFIX=/src/optee/toolchains/aarch64 && \ + make -j\"$(getconf _NPROCESSORS_ONLN)\" VERBOSE=1 && \ + make install" + docker run --privileged -v "$(pwd):/src" -w /src jforissier/optee_os_ci:qemu_check /bin/bash -c "\ + export PATH=/src/optee/toolchains/aarch64/bin:$PATH && \ + export CC=aarch64-linux-gnu-gcc && \ + + # Run the libdogecoin TA + cd /src/src/optee/host && \ + make -j"$(getconf _NPROCESSORS_ONLN)" \ + CROSS_COMPILE=aarch64-linux-gnu- \ + LDFLAGS=\"-L/src/optee/toolchains/aarch64/lib -L/src/depends/aarch64-linux-gnu/lib -ldogecoin -lunistring\" \ + CFLAGS=\"-I/src/optee/toolchains/aarch64/include -I/src/src/optee/ta/include -I/src/depends/aarch64-linux-gnu/include -I/src/depends/aarch64-linux-gnu/include/ykpers-1 -I/src/depends/aarch64-linux-gnu/include/dogecoin\" && \ + + # Build the Trusted Application + cd ../ta && \ + make -j"$(getconf _NPROCESSORS_ONLN)" \ + CROSS_COMPILE=aarch64-linux-gnu- \ + LDFLAGS=\"-L/src/depends/aarch64-linux-gnu/lib -ldogecoin -lunistring\" \ + CFLAGS=\"-I/src/depends/aarch64-linux-gnu/include -I/src/depends/aarch64-linux-gnu/include/dogecoin\" \ + PLATFORM=vexpress-qemu_armv8a \ + TA_DEV_KIT_DIR=/src/optee/optee_os/out/arm/export-ta_arm64 && \ + + # Create symbolic links and prepare image + mkdir -p /src/optee/out/bin && \ + cd /src/optee/out/bin && \ + ln -sf ../../linux/arch/arm64/boot/Image Image && \ + ln -sf ../../trusted-firmware-a/build/qemu/release/bl1.bin bl1.bin && \ + ln -sf ../../trusted-firmware-a/build/qemu/release/bl2.bin bl2.bin && \ + ln -sf ../../trusted-firmware-a/build/qemu/release/bl31.bin bl31.bin && \ + ln -sf ../../optee_os/out/arm/core/tee-header_v2.bin bl32.bin && \ + ln -sf ../../optee_os/out/arm/core/tee-pager_v2.bin bl32_extra1.bin && \ + ln -sf ../../optee_os/out/arm/core/tee-pageable_v2.bin bl32_extra2.bin && \ + ln -sf ../../edk2/Build/ArmVirtQemuKernel-AARCH64/RELEASE_GCC5/FV/QEMU_EFI.fd bl33.bin && \ + ln -sf ../../out-br/images/rootfs.cpio.gz rootfs.cpio.gz && \ + dd if=/dev/zero of=/src/optee/out/bin/libdogecoin.img bs=1M count=32 && \ + mkfs.ext4 /src/optee/out/bin/libdogecoin.img && \ + mkdir -p /src/optee/out-br/mnt && \ + mount -o loop /src/optee/out/bin/libdogecoin.img /src/optee/out-br/mnt && \ + cp /src/src/optee/ta/*.ta /src/optee/out-br/mnt && \ + cp /src/src/optee/host/optee_libdogecoin /src/optee/out-br/mnt && \ + cp /src/spvnode /src/optee/out-br/mnt && \ + cp /src/sendtx /src/optee/out-br/mnt && \ + cp /src/such /src/optee/out-br/mnt && \ + cp /src/tests /src/optee/out-br/mnt && \ + cp /src/bench /src/optee/out-br/mnt && \ + mkdir -p /src/optee/out-br/mnt/data/tee && \ + umount /src/optee/out-br/mnt && \ + exit" + elif ([ "${{ matrix.name }}" == "x86_64-linux-openenclave" ]); then + make install && \ + mkdir -p src/openenclave/build && \ + docker run -v $PWD:/src -w /src ubuntu:20.04 bash -c "\ + # Install dependencies + export DEBIAN_FRONTEND=noninteractive && \ + apt-get update && \ + apt-get install -y wget gnupg2 cmake && \ + cd /src/src/openenclave && \ + echo 'deb [arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu focal main' | tee /etc/apt/sources.list.d/intel-sgx.list && \ + wget -qO - https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | apt-key add - && \ + echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-11 main' | tee /etc/apt/sources.list.d/llvm-toolchain-focal-11.list && \ + wget -qO - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ + echo 'deb [arch=amd64] https://packages.microsoft.com/ubuntu/20.04/prod focal main' | tee /etc/apt/sources.list.d/msprod.list && \ + wget -qO - https://packages.microsoft.com/keys/microsoft.asc | apt-key add - && \ + apt update && \ + apt -y install clang-11 libssl-dev gdb libsgx-enclave-common libsgx-quote-ex libprotobuf17 libsgx-dcap-ql libsgx-dcap-ql-dev az-dcap-client open-enclave && \ + apt -y install dkms && \ + + # Build OpenEnclave + source /opt/openenclave/share/openenclave/openenclaverc && \ + cd build && cmake .. && make && make simulate" + elif ([ "${{ matrix.name }}" == "x86_64-nixos" ]); then + docker run -v "$(pwd)":/src -w /src --rm -i ghcr.io/nixos/nix:latest /bin/sh -c "\ + nix-shell -p gnumake gcc stdenv autoconf automake libtool 'pkg-config' openssl python3 libevent libunistring \ + --run ' \ + ./autogen.sh && \ + ./configure --prefix=/src/depends/${{ matrix.host }} ${{ matrix.config-opts }} HOST=${{ matrix.host }} || ( cat config.log && false) && \ + make && \ + make check'" + fi + ;; + esac + - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: libdogecoin-${{ github.sha }}-${{ matrix.name }} + path: ${{ github.workspace }}/build/libdogecoin-${{ github.sha }}-${{ matrix.name }} + + - name: Upload OP-TEE artifacts + if: matrix.name == 'aarch64-linux-optee' + uses: actions/upload-artifact@v4 + with: + name: libdogecoin-${{ github.sha }}-optee-artifacts + path: | + ${{ github.workspace }}/optee/linux/arch/arm64/boot/Image + ${{ github.workspace }}/optee/trusted-firmware-a/build/qemu/release/bl1.bin + ${{ github.workspace }}/optee/trusted-firmware-a/build/qemu/release/bl2.bin + ${{ github.workspace }}/optee/trusted-firmware-a/build/qemu/release/bl31.bin + ${{ github.workspace }}/optee/optee_os/out/arm/core/tee-header_v2.bin + ${{ github.workspace }}/optee/optee_os/out/arm/core/tee-pager_v2.bin + ${{ github.workspace }}/optee/optee_os/out/arm/core/tee-pageable_v2.bin + ${{ github.workspace }}/optee/edk2/Build/ArmVirtQemuKernel-AARCH64/RELEASE_GCC5/FV/QEMU_EFI.fd + ${{ github.workspace }}/optee/out-br/images/rootfs.cpio.gz + ${{ github.workspace }}/src/optee/ta/*.ta + ${{ github.workspace }}/src/optee/host/optee_libdogecoin + ${{ github.workspace }}/optee/out/* + + - name: Upload OpenEnclave artifacts + if: matrix.name == 'x86_64-linux-openenclave' + uses: actions/upload-artifact@v4 + with: + name: libdogecoin-${{ github.sha }}-openenclave-artifacts path: | - ${{ github.workspace }}/build/libdogecoin-${{ github.sha }}-${{ matrix.name }} + ${{ github.workspace }}/src/openenclave/build/host/host + ${{ github.workspace }}/src/openenclave/build/enclave/enclave.signed sign-x86_64-win: needs: build diff --git a/.gitignore b/.gitignore index d4f57942d..413721a5f 100644 --- a/.gitignore +++ b/.gitignore @@ -156,3 +156,9 @@ qrtest.jpg # Store store/* .store/* + +# OPTEE +!/src/optee/Makefile +!/src/optee/host/Makefile +!/src/optee/ta/Makefile +/src/optee/build diff --git a/CMakeLists.txt b/CMakeLists.txt index fc972323b..ebcb1c6eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,8 +30,9 @@ SET(USE_AVX2 FALSE CACHE BOOL "enable intel avx2") SET(USE_SSE FALSE CACHE BOOL "enable intel sse") SET(USE_SSE2 FALSE CACHE BOOL "enable scrypt sse2") SET(USE_TPM2 TRUE CACHE BOOL "enable tpm2") -SET(USE_OPENENCLAVE FALSE CACHE BOOL "enable openenclave") -SET(TEST_PASSWD FALSE CACHE BOOL "enable test password") +SET(USE_OPENENCLAVE TRUE CACHE BOOL "enable openenclave") +SET(USE_YUBIKEY FALSE CACHE BOOL "enable yubikey") +SET(TEST_PASSWD TRUE CACHE BOOL "enable test password") string(RANDOM LENGTH 12 ALPHABET abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 PASSWD_STR) SET(RANDOM_DEVICE "/dev/urandom" CACHE STRING "set the device to read random data from") @@ -74,7 +75,26 @@ IF(WITH_NET) FIND_LIBRARY(LIBEVENT NAMES event event_core event_extras event_pthreads HINTS "${PROJECT_SOURCE_DIR}/src/libevent/build/lib/${CMAKE_BUILD_TYPE}" REQUIRED) FIND_LIBRARY(WINPTHREADS NAMES pthreadVC2 pthreadwin32 pthreads pthread HINTS "${PROJECT_SOURCE_DIR}/contrib/winpthreads/include/lib/x64/" REQUIRED) ELSE() - FIND_LIBRARY(LIBEVENT event REQUIRED) + # build libevent + execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/src/libevent/build) + execute_process(COMMAND cmake .. + -DCMAKE_C_FLAGS=-U_FORTIFY_SOURCE + -DEVENT__DISABLE_OPENSSL=ON + -DEVENT__DISABLE_SAMPLES=ON + -DEVENT__LIBRARY_TYPE=STATIC + -DEVENT__DISABLE_TESTS=ON + -DEVENT__DISABLE_THREAD_SUPPORT=OFF + -DEVENT__DISABLE_MM_REPLACEMENT=ON + -DEVENT__DISABLE_REGRESS=ON + -DEVENT__DISABLE_BENCHMARK=ON + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/libevent/build) + execute_process(COMMAND cmake --build . + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/libevent/build) + file(COPY "${PROJECT_SOURCE_DIR}/src/libevent/build/include" DESTINATION "${PROJECT_SOURCE_DIR}") + file(COPY "${PROJECT_SOURCE_DIR}/src/libevent/include/event2" DESTINATION "${PROJECT_SOURCE_DIR}/include") + find_path(LIBEVENT_INCLUDE_DIR event.h PATHS "${PROJECT_SOURCE_DIR}/include/event2" REQUIRED) + find_path(LIBEVENT_INCLUDE_DIR event-config.h PATHS "${PROJECT_SOURCE_DIR}/include/event2" REQUIRED) + FIND_LIBRARY(LIBEVENT NAMES event event_core event_extras event_pthreads HINTS "${PROJECT_SOURCE_DIR}/src/libevent/build/lib/${CMAKE_BUILD_TYPE}" REQUIRED) FIND_LIBRARY(LIBUNISTRING unistring REQUIRED) ENDIF() ENDIF() @@ -159,6 +179,10 @@ ENDIF() IF(USE_OPENENCLAVE) ADD_DEFINITIONS(-DUSE_OPENENCLAVE=1) ENDIF() +IF(USE_YUBIKEY) + FIND_LIBRARY(LIBYKPIV NAMES ykpiv REQUIRED HINTS /usr/lib/x86_64-linux-gnu) + ADD_DEFINITIONS(-DUSE_YUBIKEY=1) +ENDIF() IF(TEST_PASSWD) ADD_DEFINITIONS(-DTEST_PASSWD=1) ADD_DEFINITIONS(-DPASSWD_STR="${PASSWD_STR}") @@ -184,6 +208,7 @@ MESSAGE(STATUS " ${PASSWD_STR}") ENDIF() MESSAGE(STATUS "") MESSAGE(STATUS " openenclave = ${USE_OPENENCLAVE}") +MESSAGE(STATUS " yubikey = ${USE_YUBIKEY}") MESSAGE(STATUS "") message(STATUS " library type = ${library_type}") MESSAGE(STATUS "") @@ -325,6 +350,7 @@ IF(USE_TESTS) test/rmd160_tests.c test/scrypt_tests.c test/serialize_tests.c + test/sha1_tests.c test/sha2_tests.c test/signmsg_tests.c test/tpm_tests.c @@ -335,10 +361,16 @@ IF(USE_TESTS) test/utils_tests.c test/vector_tests.c ) +IF (USE_YUBIKEY) + SET(LIBS ${LIBDOGECOIN_NAME} ${LIBUNISTRING} ${LIBYKPIV}) +ELSE() + SET(LIBS ${LIBDOGECOIN_NAME} ${LIBUNISTRING}) +ENDIF() + IF (WIN32 AND USE_TPM2) - TARGET_LINK_LIBRARIES(tests ${LIBDOGECOIN_NAME} ${LIBUNISTRING} tbs ncrypt crypt32) + TARGET_LINK_LIBRARIES(tests ${LIBS} tbs ncrypt crypt32) ELSE() - TARGET_LINK_LIBRARIES(tests ${LIBDOGECOIN_NAME} ${LIBUNISTRING}) + TARGET_LINK_LIBRARIES(tests ${LIBS}) ENDIF() ADD_TEST(NAME ${LIBDOGECOIN_NAME}_tests COMMAND tests) # move test/wordlist to build @@ -405,9 +437,9 @@ IF(WITH_NET) ) IF(WIN32 AND USE_TPM2) - TARGET_LINK_LIBRARIES(${LIBDOGECOIN_NAME} ${LIBEVENT} ${LIBEVENT_PTHREADS} ${LIBUNISTRING} tbs ncrypt crypt32) + TARGET_LINK_LIBRARIES(${LIBS} ${LIBEVENT} ${LIBEVENT_PTHREADS} tbs ncrypt crypt32) ELSE() - TARGET_LINK_LIBRARIES(${LIBDOGECOIN_NAME} ${LIBEVENT} ${LIBEVENT_PTHREADS} ${LIBUNISTRING}) + TARGET_LINK_LIBRARIES(${LIBS} ${LIBEVENT} ${LIBEVENT_PTHREADS}) ENDIF() IF(USE_TESTS) TARGET_SOURCES(tests ${visibility} @@ -428,9 +460,9 @@ IF(WITH_TOOLS) ADD_EXECUTABLE(such src/cli/such.c) INSTALL(TARGETS such RUNTIME) IF (WIN32 AND USE_TPM2) - TARGET_LINK_LIBRARIES(such ${LIBDOGECOIN_NAME} ${LIBUNISTRING} tbs ncrypt crypt32) + TARGET_LINK_LIBRARIES(such ${LIBS} tbs ncrypt crypt32) ELSE() - TARGET_LINK_LIBRARIES(such ${LIBDOGECOIN_NAME} ${LIBUNISTRING}) + TARGET_LINK_LIBRARIES(such ${LIBS}) ENDIF() TARGET_INCLUDE_DIRECTORIES(such ${visibility} src) diff --git a/Makefile.am b/Makefile.am index 306196a73..182434424 100644 --- a/Makefile.am +++ b/Makefile.am @@ -205,6 +205,7 @@ tests_SOURCES = \ test/rmd160_tests.c \ test/scrypt_tests.c \ test/serialize_tests.c \ + test/sha1_tests.c \ test/sha2_tests.c \ test/signmsg_tests.c \ test/tpm_tests.c \ diff --git a/configure.ac b/configure.ac index 8d50f9c3f..d87ccaf05 100644 --- a/configure.ac +++ b/configure.ac @@ -154,6 +154,18 @@ AC_ARG_ENABLE([openenclave], [use_openenclave=$enableval], [use_openenclave=no]) +AC_ARG_ENABLE([optee], + [AS_HELP_STRING([--enable-optee], + [Build with optee (default is no)])], + [use_optee=$enableval], + [use_optee=no]) + +AC_ARG_ENABLE([yubikey], + [AS_HELP_STRING([--enable-yubikey], + [Build with yubikey (default is no)])], + [use_yubikey=$enableval], + [use_yubikey=no]) + AC_ARG_ENABLE(tests, AS_HELP_STRING([--enable-tests],[compile tests (default is yes)]), [use_tests=$enableval], @@ -256,6 +268,16 @@ if test "x$use_openenclave" = xyes; then AC_DEFINE(USE_OPENENCLAVE, 1, [Define this symbol if openenclave works]) fi +if test "x$use_optee" = xyes; then + AC_DEFINE_UNQUOTED([USE_OPTEE],[1],[Define this symbol if optee works]) +fi + +if test "x$use_yubikey" = xyes; then + AC_CHECK_LIB([ykpiv],[main],LIBYKPIV=-lykpiv,AC_MSG_ERROR(libpkpiv missing)) + LIBS="$LIBS $LIBYKPIV" + AC_DEFINE(USE_YUBIKEY, 1, [Define this symbol if Yubikey works]) +fi + AC_CONFIG_HEADERS([config/libdogecoin-config.h]) AC_CONFIG_FILES([Makefile libdogecoin.pc]) AC_SUBST(LIBTOOL_APP_LDFLAGS) @@ -265,6 +287,7 @@ AC_SUBST(EVENT_CORE_LIBS) AC_SUBST(EVENT_EXTRA_LIBS) AC_SUBST(EVENT_PTHREADS_LIBS) AC_SUBST(LIBUNISTRING) +AC_SUBST(LIBYKPIV) AC_SUBST(NASMFLAGS) AM_CONDITIONAL([USE_TESTS], [test x"$use_tests" != x"no"]) AM_CONDITIONAL([WITH_BENCH], [test "x$with_bench" = "xyes"]) @@ -276,6 +299,8 @@ AM_CONDITIONAL([USE_AVX2], [test "x$use_intel_avx2" = "xyes"]) AM_CONDITIONAL([USE_SSE], [test "x$use_intel_sse" = "xyes"]) AM_CONDITIONAL([USE_SSE2], [test "x$use_scrypt_sse2" = "xyes"]) AM_CONDITIONAL([USE_OPENENCLAVE], [test "x$use_openenclave" = "xyes"]) +AM_CONDITIONAL([USE_OPTEE], [test "x$use_optee" = "xyes"]) +AM_CONDITIONAL([USE_YUBIKEY], [test "x$use_yubikey" = "xyes"]) AC_SUBST(LIB_VERSION_CURRENT, _LIB_VERSION_CURRENT) AC_SUBST(LIB_VERSION_REVISION, _LIB_VERSION_REVISION) AC_SUBST(LIB_VERSION_AGE, _LIB_VERSION_AGE) @@ -304,6 +329,8 @@ echo " $PASSWD_STR" fi echo echo " openenclave = $use_openenclave" +echo " optee = $use_optee" +echo " Yubikey = $use_yubikey" echo echo " host = $host" echo " target os = $TARGET_OS" @@ -314,4 +341,5 @@ echo " CFLAGS = $CFLAGS" echo " CXX = $CXX" echo " CXXFLAGS = $CXXFLAGS" echo " LDFLAGS = $LDFLAGS" +echo " LIBS = $LIBS" echo diff --git a/contrib/examples/example.c b/contrib/examples/example.c index 6a041b754..a0f23732d 100644 --- a/contrib/examples/example.c +++ b/contrib/examples/example.c @@ -259,13 +259,27 @@ int main() { printf("Master public key: %s\n", master_public_key); // Derive an extended normal (non-hardened) public key - char extkeypath[KEYPATHMAXLEN] = "m/0/0/0/0/0"; + char extkeypath[KEYPATHMAXLEN] = "m/44'/3'/0'"; char extpubkey[HDKEYLEN] = {0}; - getHDNodeAndExtKeyByPath(master_public_key, extkeypath, extpubkey, false); - printf("Keypath: %s\nExtended public key: %s\n", extkeypath, extpubkey); - if (strcmp(extpubkey, "dgub8wcWPRxhthgZYftisbirNJ5Ae3navJCCEfd6SzyL5SK44GC4tok3BGkNWbhrM4KeJ8o9ZAkXiVdLTnUyzz89ah1izJjWTo5pv7eboGtzktJ") != 0) { + dogecoin_hdnode* node_bypath = getHDNodeAndExtKeyByPath(masterkey, extkeypath, extpubkey, false); + printf("(Account) Extended public key: %s\n", extpubkey); + if (strcmp(extpubkey, "dgub8rUhDtD3YFGZTUphBfpBbzvFxSMKQXYLzg87Me2ta78r2SdVLmypBUkkxrrn9RTnchsyiJSkHZyLWxD13ibBiXtuFWktBoDaGaZjQUBLNLs") != 0) { printf("extpubkey does not match!\n"); } + dogecoin_hdnode_free(node_bypath); + + // Derive an address from the extended public key + char derived_address2[P2PKHLEN]; + if (getDerivedHDAddressFromAcctPubKey(extpubkey, 0, BIP44_CHANGE_EXTERNAL, derived_address2, false) == 0) { + printf("(Account) Extended public key: %s\n", extpubkey); + if (strcmp(extpubkey, "dgub8rUhDtD3YFGZTUphBfpBbzvFxSMKQXYLzg87Me2ta78r2SdVLmypBUkkxrrn9RTnchsyiJSkHZyLWxD13ibBiXtuFWktBoDaGaZjQUBLNLs") != 0) { + printf("extpubkey does not match!\n"); + } + printf("Derived address 0: %s\n", derived_address2); + } else { + printf("Error occurred.\n"); + return -1; + } // BASIC TRANSACTION FORMATION EXAMPLE printf("\n\nBEGIN TRANSACTION FORMATION AND SIGNING:\n\n"); @@ -273,8 +287,8 @@ int main() { char *external_p2pkh_addr = "nbGfXLskPh7eM1iG5zz5EfDkkNTo9TRmde"; char *hash_2_doge = "b4455e7b7b7acb51fb6feba7a2702c42a5100f61f61abafa31851ed6ae076074"; char *hash_10_doge = "42113bdc65fc2943cf0359ea1a24ced0b6b0b5290db4c63a3329c6601c4616e2"; - char myscriptpubkey [PUBKEYHASHLEN]; - dogecoin_p2pkh_address_to_pubkey_hash (str, myscriptpubkey); + char myscriptpubkey [PUBKEYHASHLEN]; + dogecoin_p2pkh_address_to_pubkey_hash (str, myscriptpubkey); // build transaction int idx = start_transaction(); @@ -314,6 +328,15 @@ int main() { return -1; } + printf("Transaction hex: %s\n", get_raw_transaction(idx)); + printf("Transaction hex length: %ld\n", strlen(get_raw_transaction(idx))); + printf("Transaction unsigned hex: %s\n", get_raw_transaction(idx2)); + printf("Transaction unsigned hex length: %ld\n", strlen(get_raw_transaction(idx2))); + printf("str: %s\n", str); + printf("my script pubkey: %s\n", myscriptpubkey); + printf("my script pubkey length: %ld\n", strlen(myscriptpubkey)); + printf("privkeywif: %s\n", wifstr); + // sign transaction if (sign_transaction(idx, myscriptpubkey, wifstr)) { printf("\nAll transaction inputs signed successfully. \nFinal transaction hex: %s\n.", get_raw_transaction(idx)); diff --git a/depends/packages/libusb.mk b/depends/packages/libusb.mk new file mode 100644 index 000000000..08d578661 --- /dev/null +++ b/depends/packages/libusb.mk @@ -0,0 +1,22 @@ +package=libusb +$(package)_version=1.0.27 +$(package)_download_path=https://github.com/libusb/libusb/releases/download/v1.0.27 +$(package)_file_name=$(package)-$($(package)_version).tar.bz2 +$(package)_sha256_hash=ffaa41d741a8a3bee244ac8e54a72ea05bf2879663c098c82fc5757853441575 + +define $(package)_set_vars + $(package)_config_opts=--disable-shared --enable-static --disable-udev + $(package)_config_opts_mingw32=--enable-threads=windows +endef + +define $(package)_config_cmds + $($(package)_autoconf) CFLAGS="-fPIC" +endef + +define $(package)_build_cmds + $(MAKE) +endef + +define $(package)_stage_cmds + $(MAKE) DESTDIR=$($(package)_staging_dir) install +endef diff --git a/depends/packages/libyubikey.mk b/depends/packages/libyubikey.mk new file mode 100644 index 000000000..d9aa84610 --- /dev/null +++ b/depends/packages/libyubikey.mk @@ -0,0 +1,22 @@ +package=libyubikey +$(package)_version=1.13 +$(package)_download_path=https://developers.yubico.com/yubico-c/Releases +$(package)_file_name=libyubikey-$($(package)_version).tar.gz +$(package)_sha256_hash=04edd0eb09cb665a05d808c58e1985f25bb7c5254d2849f36a0658ffc51c3401 + +define $(package)_set_vars + $(package)_config_opts=--disable-shared --enable-static + $(package)_config_opts_mingw32=--enable-threads=windows +endef + +define $(package)_config_cmds + $($(package)_autoconf) CFLAGS="-fPIC" +endef + +define $(package)_build_cmds + $(MAKE) +endef + +define $(package)_stage_cmds + $(MAKE) DESTDIR=$($(package)_staging_dir) install +endef diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 9c952f709..0767f2862 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -1,11 +1,11 @@ -packages:=libevent libunistring +packages:=libevent libunistring libyubikey libusb ykpers native_packages := native_ccache wallet_packages= upnp_packages= -darwin_native_packages = +darwin_native_packages = ifneq ($(build_os),darwin) darwin_native_packages += native_cctools native_libtapi diff --git a/depends/packages/ykpers.mk b/depends/packages/ykpers.mk new file mode 100644 index 000000000..89cd885c0 --- /dev/null +++ b/depends/packages/ykpers.mk @@ -0,0 +1,29 @@ +package=ykpers +$(package)_version=1.20.0 +$(package)_download_path=https://developers.yubico.com/yubikey-personalization/Releases +$(package)_file_name=ykpers-$($(package)_version).tar.gz +$(package)_sha256_hash=0ec84d0ea862f45a7d85a1a3afe5e60b8da42df211bb7d27a50f486e31a79b93 +$(package)_dependencies=libyubikey libusb +$(package)_patches=ykpers-args.patch + +define $(package)_set_vars + $(package)_config_opts=--disable-shared --enable-static --with-backend=libusb-1.0 + $(package)_config_opts_mingw32=--enable-threads=windows + $(package)_ldflags_darwin=-framework CoreFoundation -framework IOKit -framework Security +endef + +define $(package)_preprocess_cmds + patch -p1 < $($(package)_patch_dir)/ykpers-args.patch +endef + +define $(package)_config_cmds + $($(package)_autoconf) CFLAGS="-fPIC" LIBUSB_CFLAGS="-I$(host_prefix)/include/libusb-1.0" LIBUSB_LIBS="-L$(host_prefix)/lib -lusb-1.0" +endef + +define $(package)_build_cmds + $(MAKE) +endef + +define $(package)_stage_cmds + $(MAKE) DESTDIR=$($(package)_staging_dir) install +endef diff --git a/depends/patches/ykpers/ykpers-args.patch b/depends/patches/ykpers/ykpers-args.patch new file mode 100644 index 000000000..9bb97187a --- /dev/null +++ b/depends/patches/ykpers/ykpers-args.patch @@ -0,0 +1,13 @@ +--- a/ykpers-args.h ++++ b/ykpers-args.h +@@ -33,8 +33,8 @@ + + #include "ykpers.h" + +-const char *usage; +-const char *optstring; ++extern const char *usage; ++extern const char *optstring; + + int args_to_config(int argc, char **argv, YKP_CONFIG *cfg, char *oathid, + size_t oathid_len, const char **infname, diff --git a/doc/enclaves.md b/doc/enclaves.md new file mode 100644 index 000000000..848ea4385 --- /dev/null +++ b/doc/enclaves.md @@ -0,0 +1,698 @@ +# Secure Enclaves Integration with libdogecoin + +## Introduction + +This document discusses our research on enhancing the security of **libdogecoin** key management operations using **Intel SGX** and **ARM TrustZone** as secure enclaves. By performing key management within secure enclaves, we significantly reduce the risk of key exposure, thereby increasing the overall security of Dogecoin transactions. This document includes an overview of the research, build instructions for the key management enclaves, and a step-by-step tutorial on using the host command line interface to interact with the enclaves in both **OP-TEE** and **OpenEnclave** environments. Additionally, we outline critical vulnerabilities in Trusted Execution Environments (TEEs) and provide recommendations for mitigating these risks. + +The integration of secure enclaves in key management operations presents an innovative security solution, but it also introduces technical complexity and performance trade-offs that should be carefully considered. + +## What are Secure Enclaves? + +Secure enclaves are isolated environments that provide a secure space for sensitive operations. These enclaves are isolated from the rest of the system, ensuring that sensitive data and operations are protected from unauthorized access. **Secure enclaves** within a processor can be implemented using different technologies such as **TEEs**, hardware security modules (**HSMs**), and virtualization-based solutions. To ensure a robust defense, secure enclaves must guarantee **confidentiality**, **integrity**, and **availability** for sensitive operations. However, enclaves are not a silver bullet and must be integrated within a broader security strategy. + +### Key Concepts + +In this section, we define some key concepts central to secure enclaves and their implementation. + +- **Host**: The normal world that interacts with the enclave. +- **Enclave**: The secure world within the CPU where sensitive operations are performed. +- **Trusted Execution Environment (TEE)**: A hardware-based secure environment that protects sensitive operations from external interference and tampering. +- **ECALLS**: Enclave calls used in **OpenEnclave** that allow the host to interact with the secure enclave. +- **SMCs**: Secure Monitor Calls that manage communication between the normal and secure worlds in **ARM TrustZone**. +- **Remote Attestation**: The process of verifying the integrity and authenticity of an enclave to a remote party. + +By performing key operations such as **seedphrase generation**, **public key derivation**, and **message and transaction signing** within these enclaves, we can greatly reduce the risk of private key exposure, even in the event of a host system compromise. + +## What are Trusted Execution Environments (TEEs)? + +TEEs are secure environments within a processor that provide a trusted execution domain for sensitive operations. TEEs ensure that sensitive data and operations are protected from unauthorized access and tampering. TEEs can be implemented using hardware-based security technologies such as **Intel SGX**, **ARM TrustZone**, or other vendor-specific technologies. While TEEs offer significant advantages in terms of isolating critical operations, they also come with their own set of challenges, particularly in terms of addressing vulnerabilities and maintaining performance. + +### Contrast Between Intel SGX and ARM TrustZone + +| **Criteria** | **Intel SGX (OpenEnclave)** | **ARM TrustZone (OP-TEE)** | +|----------------------------|-------------------------------------------------|-----------------------------------------------| +| **Target Platform** | Primarily x86 server-class CPUs | Primarily ARM mobile and embedded devices | +| **Security Model** | Memory encryption and isolated execution | Secure world execution isolated from normal world | +| **Key Vulnerabilities** | Foreshadow, Plundervolt, SGAxe | CVE-2021-34387, CVE-2020-16273 | +| **Performance Overhead** | Higher, especially for I/O and large memory use | Lower, better for embedded and mobile devices | +| **Use Cases** | Server applications, enterprise security | IoT, mobile devices, embedded systems | + +## Hardware and Software + +### Intel SGX and OpenEnclave + +Intel SGX (Software Guard Extensions) is a hardware-based security technology that creates isolated TEEs within some Intel CPUs. OpenEnclave is an open-source SDK that enables developers to build host and enclave applications that run on Intel SGX. SGX works by partitioning secure memory regions within the CPU that are isolated from the rest of the system, ensuring that sensitive data is protected from unauthorized access. These memory regions are encrypted and authenticated, providing a secure environment for cryptographic operations. + +OpenEnclave offers an interface for developing applications that run within SGX enclaves, enabling developers to build secure applications that protect sensitive data and operations. The host interacts with the enclave using ECALLS, allowing the enclave to perform cryptographic operations securely. Remote attestation is used to verify the integrity of the enclave to remote parties, ensuring that the enclave has not been tampered with. + +### ARM TrustZone and OP-TEE + +ARM TrustZone is another hardware-based security technology that creates TEEs within ARM CPUs. OP-TEE (Open Portable Trusted Execution Environment) is an open-source software framework that facilitates the development of applications running within ARM TrustZone enclaves. TrustZone creates a secure world and a normal world within the CPU, isolating sensitive operations from the rest of the system. The processor switches between the secure and normal worlds, ensuring that sensitive operations are performed securely. Memory is partitioned between the secure and normal worlds using page tables, protecting sensitive data from unauthorized access. + +OP-TEE provides a secure world OS for cryptographic operations in TrustZone firmware, ensuring that private keys and sensitive data are protected from unauthorized access. The host interacts with the secure world using SMCs, allowing the secure enclave to perform cryptographic operations securely. OP-TEE is widely used in IoT devices, mobile phones, and embedded systems to protect sensitive data and operations. + +## Key Management Enclaves + +### Visual Representation (Simplified) + +```text ++-----------------------------------------+ +-----------------------------------------+ +| Host | | Host | +| | | | +| +-----------------------------------+ | | +-----------------------------------+ | +| | command line interface | | | | command line interface | | +| | | | | | | | +| | +-----------------------------+ | | | | +-----------------------------+ | | +| | | libdogecoin | | | | | | libdogecoin | | | +| +-----------------------------------+ | | +-----------------------------------+ | +| | OpenEnclave Host | | | | OP-TEE Client | | ++-----------------------------------------+ +-----------------------------------------+ +| Linux OS | | Linux OS | ++-----------------------------------------+ +-----------------------------------------+ +| Enclave ^ | | Enclave ^ | +| ECALLS | | SMCs | +| | | | | | +| Messages and Transactions | | Messages and Transactions | +| Public Keys and Addresses | | Public Keys and Addresses | +| | | | | | +| v | | v | +| +-----------------------------------+ | | +-----------------------------------+ | +| | Key Management | | | | Key Management | | +| | - Seedphrase Generation | | | | - Seedphrase Generation | | +| | - Public Key Generation | | | | - Public Key Generation | | +| | - Address Generation | | | | - Address Generation | | +| | - Sign Message | | | | - Sign Message | | +| | - Sign Transaction | | | | - Sign Transaction | | +| | | | | | | | +| | +-----------------------------+ | | | | +-----------------------------+ | | +| | | libdogecoin | | | | | | libdogecoin | | | ++-----------------------------------------+ +-----------------------------------------+ +| OpenEnclave | | OP-TEE OS + Trusted Firmware | ++-----------------------------------------+ +-----------------------------------------+ +| Intel SGX | | ARMv8 TrustZone | ++-----------------------------------------+ +-----------------------------------------+ +``` + +### Key Management Operations + +The secure enclave handles the following operations: +- **Seedphrase Generation**: Protecting the creation of seedphrases for wallet recovery. +- **Public Key Generation**: Deriving public keys in the enclave, ensuring private keys are never exposed. +- **Address Generation**: Generating secure Dogecoin addresses within the enclave. +- **Sign Message/Transaction**: Ensuring the integrity and authenticity of signed messages and transactions. + +### Limitations and Assumptions + +While secure enclaves provide powerful protection, they are **not infallible**. The **Foreshadow** vulnerability is a good example of how attackers can bypass enclave protections. Furthermore, the security model assumes that the host environment is insecure, relying entirely on the enclave for protection. Developers should adopt a **defense-in-depth** strategy, using **secure coding practices** and **regular security audits** to complement enclave security. + +### Secure Storage + +Mnemonic seedphrases are securely encrypted by the enclave to prevent unauthorized access. Encrypted data should still be backed up using the **rule of three**: store copies in three distinct locations to ensure recovery. + +### Time-Based One-Time Password (TOTP) Authentication + +TOTP authentication using a **YubiKey** further enhances security by ensuring that access to sensitive enclave operations is restricted to authorized users. This two-factor authentication approach increases security during enclave operations, preventing unauthorized key usage even if the host environment is compromised. + +## Future Research + +To further improve the security and functionality of Dogecoin’s ecosystem, we recommend exploring **Remote Attestation** to validate the integrity of enclaves in distributed systems. Integrating **proof-of-work (PoW)** operations within secure enclaves could also allow for **private mempools** or **payment channels**, enhancing the Dogecoin network's security and efficiency. + +Additionally, alternative secure technologies like **AMD SEV** and **RISC-V** should be explored to broaden the options available for TEE-based applications. + +## References + +- [Intel SGX](https://software.intel.com/en-us/sgx) +- [ARM TrustZone](https://www.arm.com/solutions/security-on-arm/trustzone) +- [OpenEnclave](https://www.openenclave.io/) +- [OP-TEE](https://optee.readthedocs.io/en/latest/) + +## TEE Vulnerabilities + +It is crucial to keep systems up-to-date with the latest security fixes and patches to mitigate the risks associated with any vulnerabilities. Regularly applying security patches and following best practices for secure enclave development can help protect sensitive data and operations. Below are some recent vulnerabilities that have been discovered in Intel SGX, ARM TrustZone, OpenEnclave, and OP-TEE. It is essential to stay informed about these vulnerabilities and take appropriate measures to address them. + +**Intel SGX:** +Several vulnerabilities have been discovered in Intel SGX: + +1. **Foreshadow (CVE-2018-3615):** This vulnerability allows attackers to access sensitive information stored in SGX enclaves. Details can be found in the [Intel Security Advisory](https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00161.html). + +2. **Plundervolt (CVE-2019-11157):** This vulnerability exploits the CPU's undervolting features to breach SGX enclave protections. More information is available in the [Intel Security Advisory](https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00289.html). + +3. **SGAxe (CVE-2020-0551):** This vulnerability allows side-channel attacks to extract sensitive information from SGX enclaves. Refer to the [Intel Security Advisory](https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00334.html) for further details. + +**ARM TrustZone:** +While many vulnerabilities related to ARM TrustZone tend to be vendor-specific or related to specific implementations, the core TrustZone architecture itself has also been associated with certain security concerns. Below are examples of both types: + +1. **CVE-2021-34387:** This vulnerability in the ARM TrustZone technology, on which Trusty is based, allows unauthorized write access to kernel code and data that is mapped as read-only, affecting the DRAM reserved for TrustZone. Further details can be found [here](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-34387). + +2. **CVE-2021-33478:** This issue affects Broadcom MediaxChange firmware, potentially allowing an unauthenticated attacker to achieve arbitrary code execution in the TrustZone TEE of an affected device. More information is available [here](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-33478). + +3. **CVE-2021-29415:** This vulnerability involves the ARM TrustZone CryptoCell 310 in Nordic Semiconductor nRF52840, allowing adversaries to recover private ECC keys during ECDSA operations. Details can be found [here](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-29415). + +4. **CVE-2020-16273:** This vulnerability in ARMv8-M processors' software stack selection mechanism can be exploited by a stack-underflow attack, allowing changes to the stack pointer from a non-secure application. More information is available [here](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-16273). + +5. **CVE-2022-47549:** This issue in OP-TEE, which is closely associated with ARM TrustZone, allows bypassing signature verification and installing malicious trusted applications via electromagnetic fault injections. More details are available [here](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-47549). + +It's important to recognize that while TrustZone provides a secure environment, its effectiveness is heavily dependent on how it is implemented and integrated with other system components. Architectural issues or specific use-case vulnerabilities may arise, emphasizing the need for rigorous security practices. + +**OpenEnclave:** +For vulnerabilities and security advisories related to OpenEnclave, refer to their [GitHub security page](https://github.com/openenclave/openenclave/security). + +**OP-TEE:** +For vulnerabilities and security advisories related to OP-TEE, refer to their [GitHub security page](https://github.com/OP-TEE/optee_os/security). + +### Best Practices for Mitigation +- Keep software up-to-date by regularly applying security patches. +- Implement defense-in-depth strategies such as isolating critical operations in separate enclaves. +- Consider using multuple secure technologies (e.g., TEEs, HSMs) to minimize reliance on any single point of failure. + +## Disclaimer + +Enclaves are not a silver bullet. As a risk mitigation measure, enclaves can provide significant benefits in protecting sensitive data and operations. We encourage developers to explore secure enclaves and their potential benefits, but caution that they should be used as part of a comprehensive security strategy that includes other security measures such as secure coding practices and audits. Enclaves are not immune to vulnerabilities, and it is essential to stay informed about the latest security advisories and best practices for secure enclave development. + +## Build Instructions + +### Building OP-TEE SDK and Client + +**Dependencies** + +- Ubuntu 20.04 or later +- Docker +- Libdogecoin + +```sh +sudo apt-get install docker.io + +mkdir -p /doge +cd /doge +git clone https://github.com/dogecoinfoundation/libdogecoin.git +cd libdogecoin +``` + +The SDK has several components and requires over 10GB of disk space to build. The build process can take over 30 minutes on a modern machine. Docker is used to build the SDK and client in a clean environment. + +### Building OP-TEE SDK and Client (NanoPC-T6) + +This command builds the latest SDK and client for NanoPC-T6 (nanopc-t6.xml). When complete, the image will be located in `/doge/libdogecoin/optee/out/nanopc-t6.img`. Burn this image to an SD card to boot the NanoPC-T6. Connect an Ethernet cable to the NanoPC-T6 and power it on. The default IP address is `192.168.1.1`. Login as root via ssh (e.g. `ssh root@192.168.1.1`). + +```sh +docker pull jforissier/optee_os_ci:qemu_check +docker run -v "$(pwd):/src" -w /src jforissier/optee_os_ci:qemu_check /bin/bash -c "\ + # Set up the environment and build the OP-TEE SDK + set -e && \ + apt update && \ + apt -y upgrade && \ + apt -y install netcat libusb-1.0-0-dev swig python2 python3-dev python3-setuptools e2tools && \ + curl https://storage.googleapis.com/git-repo-downloads/repo > /bin/repo && chmod a+x /bin/repo && \ + mkdir -p optee && \ + cd optee && \ + # repo init -u https://github.com/OP-TEE/manifest.git -m qemu_v8.xml -b 4.0.0 + repo init -u https://github.com/edtubbs/manifest.git -m nanopc-t6.xml -b nanopc-t6 && \ + export FORCE_UNSAFE_CONFIGURE=1 && \ + repo sync -j 4 && \ + patch -F 4 /src/optee/build/common.mk < /src/src/optee/common.mk.patch && \ + patch /src/optee/build/kconfigs/qemu.conf < /src/src/optee/qemu.conf.patch && \ + patch /src/optee/linux/arch/arm64/boot/dts/rockchip/rk3588-nanopi6-common.dtsi < /src/src/optee/rk3588-nanopi6-common.dtsi.patch && \ + patch /src/optee/u-boot/include/configs/nanopi6.h < /src/src/optee/nanopi6.h.patch && \ + cd build && \ + make toolchains -j 4 && \ + export CFG_TEE_CORE_LOG_LEVEL=0 && \ + export CFG_ATTESTATION_PTA=y && \ + export CFG_ATTESTATION_PTA_KEY_SIZE=1024 && \ + export CFG_WITH_USER_TA=y && \ + + # Generate RSA private key and overwrite the default TA key + openssl genpkey -algorithm RSA -out rsa_private.pem -pkeyopt rsa_keygen_bits:2048 && \ + mv rsa_private.pem /src/optee/optee_os/keys/default_ta.pem && \ + + # Generate subkeys + openssl genrsa -out /src/optee/optee_test/ta/top_level_subkey.pem && \ + openssl genrsa -out /src/optee/optee_test/ta/mid_level_subkey.pem && \ + openssl genrsa -out /src/optee/optee_test/ta/identity_subkey2.pem && \ + + # Sign the top-level subkey with the root key + /src/optee/optee_os/scripts/sign_encrypt.py sign-subkey \ + --uuid f04fa996-148a-453c-b037-1dcfbad120a6 \ + --key /src/optee/optee_os/keys/default_ta.pem --in /src/optee/optee_test/ta/top_level_subkey.pem \ + --out /src/optee/optee_test/ta/top_level_subkey.bin --max-depth 4 --name-size 64 \ + --subkey-version 1 && \ + + # Generate UUID for the mid-level subkey + /src/optee/optee_os/scripts/sign_encrypt.py subkey-uuid --in /src/optee/optee_test/ta/top_level_subkey.bin \ + --name mid_level_subkey && \ + + # Sign the mid-level subkey with the top-level subkey + /src/optee/optee_os/scripts/sign_encrypt.py sign-subkey \ + --uuid 1a5948c5-1aa0-518c-86f4-be6f6a057b16 \ + --key /src/optee/optee_test/ta/top_level_subkey.pem --subkey /src/optee/optee_test/ta/top_level_subkey.bin \ + --name-size 64 --subkey-version 1 \ + --name mid_level_subkey \ + --in /src/optee/optee_test/ta/mid_level_subkey.pem --out /src/optee/optee_test/ta/mid_level_subkey.bin && \ + + # Generate UUID for subkey1_ta + /src/optee/optee_os/scripts/sign_encrypt.py subkey-uuid --in /src/optee/optee_test/ta/mid_level_subkey.bin \ + --name subkey1_ta && \ + + # Sign the identity subkey2 with the root key + /src/optee/optee_os/scripts/sign_encrypt.py sign-subkey \ + --uuid a720ccbb-51da-417d-b82e-e5445d474a7a \ + --key /src/optee/optee_os/keys/default_ta.pem --in /src/optee/optee_test/ta/identity_subkey2.pem \ + --out /src/optee/optee_test/ta/identity_subkey2.bin --max-depth 0 --name-size 0 \ + --subkey-version 1 && \ + + # Build and test the OP-TEE OS and client + # make -j 4 check + make -j 4 && \ + cd /src && \ + git clone https://github.com/OP-TEE/optee_client.git && \ + cd optee_client && \ + mkdir -p build && \ + cd build && \ + export PATH=/src/optee/toolchains/aarch64/bin:$PATH && \ + export CC=aarch64-linux-gnu-gcc && \ + cmake .. -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc -DCMAKE_INSTALL_PREFIX=/src/optee/toolchains/aarch64 && \ + make -j 4 VERBOSE=1 && \ + make install" +``` + +### Building OP-TEE SDK and Client (QEMU ARMv8) + +This command builds the SDK (version 3.22.0) and client for ARMv8 QEMU emulation (qemu_v8.xml). For other platforms, change the manifest file in the `repo init` command accordingly. Replace `3.22.0` with the desired version and `qemu_v8.xml` with the desired platform. Refer to the [OP-TEE documentation](https://optee.readthedocs.io/en/latest/building/index.html) for more information. + +An RSA private key is generated and overwrites the default Trusted Application (TA) key. This key is used to sign the enclave binaries during development. In the Continuous Integration (CI) environment, an Actions secret is used. Subkeys are generated for testing purposes but are not used to sign the enclave binaries. + +```sh +docker pull jforissier/optee_os_ci:qemu_check +docker run -v "$(pwd):/src" -w /src jforissier/optee_os_ci:qemu_check /bin/bash -c "\ + # Set up the environment and build the OP-TEE SDK + set -e && \ + apt update && \ + apt -y upgrade && \ + apt -y install netcat libusb-1.0-0-dev swig python2 python3-dev python3-setuptools e2tools && \ + curl https://storage.googleapis.com/git-repo-downloads/repo > /bin/repo && chmod a+x /bin/repo && \ + mkdir -p optee && \ + cd optee && \ + repo init -u https://github.com/OP-TEE/manifest.git -m qemu_v8.xml -b 4.0.0 + export FORCE_UNSAFE_CONFIGURE=1 && \ + repo sync -j 4 && \ + patch -F 4 /src/optee/build/common.mk < /src/src/optee/common.mk.patch && \ + patch /src/optee/build/kconfigs/qemu.conf < /src/src/optee/qemu.conf.patch && \ + cd build && \ + make toolchains -j 4 && \ + export CFG_TEE_CORE_LOG_LEVEL=0 && \ + export CFG_ATTESTATION_PTA=y && \ + export CFG_ATTESTATION_PTA_KEY_SIZE=1024 && \ + export CFG_WITH_USER_TA=y && \ + + # Generate RSA private key and overwrite the default TA key + openssl genpkey -algorithm RSA -out rsa_private.pem -pkeyopt rsa_keygen_bits:2048 && \ + mv rsa_private.pem /src/optee/optee_os/keys/default_ta.pem && \ + + # Generate subkeys + openssl genrsa -out /src/optee/optee_test/ta/top_level_subkey.pem && \ + openssl genrsa -out /src/optee/optee_test/ta/mid_level_subkey.pem && \ + openssl genrsa -out /src/optee/optee_test/ta/identity_subkey2.pem && \ + + # Sign the top-level subkey with the root key + /src/optee/optee_os/scripts/sign_encrypt.py sign-subkey \ + --uuid f04fa996-148a-453c-b037-1dcfbad120a6 \ + --key /src/optee/optee_os/keys/default_ta.pem --in /src/optee/optee_test/ta/top_level_subkey.pem \ + --out /src/optee/optee_test/ta/top_level_subkey.bin --max-depth 4 --name-size 64 \ + --subkey-version 1 && \ + + # Generate UUID for the mid-level subkey + /src/optee/optee_os/scripts/sign_encrypt.py subkey-uuid --in /src/optee/optee_test/ta/top_level_subkey.bin \ + --name mid_level_subkey && \ + + # Sign the mid-level subkey with the top-level subkey + /src/optee/optee_os/scripts/sign_encrypt.py sign-subkey \ + --uuid 1a5948c5-1aa0-518c-86f4-be6f6a057b16 \ + --key /src/optee/optee_test/ta/top_level_subkey.pem --subkey /src/optee/optee_test/ta/top_level_subkey.bin \ + --name-size 64 --subkey-version 1 \ + --name mid_level_subkey \ + --in /src/optee/optee_test/ta/mid_level_subkey.pem --out /src/optee/optee_test/ta/mid_level_subkey.bin && \ + + # Generate UUID for subkey1_ta + /src/optee/optee_os/scripts/sign_encrypt.py subkey-uuid --in /src/optee/optee_test/ta/mid_level_subkey.bin \ + --name subkey1_ta && \ + + # Sign the identity subkey2 with the root key + /src/optee/optee_os/scripts/sign_encrypt.py sign-subkey \ + --uuid a720ccbb-51da-417d-b82e-e5445d474a7a \ + --key /src/optee/optee_os/keys/default_ta.pem --in /src/optee/optee_test/ta/identity_subkey2.pem \ + --out /src/optee/optee_test/ta/identity_subkey2.bin --max-depth 0 --name-size 0 \ + --subkey-version 1 && \ + + # Build and test the OP-TEE OS and client + make -j 4 check + cd /src && \ + git clone https://github.com/OP-TEE/optee_client.git && \ + cd optee_client && \ + mkdir -p build && \ + cd build && \ + export PATH=/src/optee/toolchains/aarch64/bin:$PATH && \ + export CC=aarch64-linux-gnu-gcc && \ + cmake .. -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc -DCMAKE_INSTALL_PREFIX=/src/optee/toolchains/aarch64 && \ + make -j 4 VERBOSE=1 && \ + make install" +``` + +### Building OP-TEE Libdogecoin Key Manager Enclave (QEMU ARMv8 or NanoPC-T6) + +```sh +docker run --privileged -v "$(pwd):/src" -w /src jforissier/optee_os_ci:qemu_check /bin/bash -c "\ + # Set up the environment and build libdogecoin + apt-get update && \ + apt-get install -y autoconf automake libtool-bin build-essential curl python3 valgrind g++-aarch64-linux-gnu qemu-user-static qemu-user && \ + + make clean && \ + make -j 4 -C depends HOST=aarch64-linux-gnu && \ + ./autogen.sh && \ + ./configure --prefix=/src/depends/aarch64-linux-gnu LIBS=-levent_pthreads --enable-static --disable-shared --enable-test-passwd --enable-optee CFLAGS=-U_FORTIFY_SOURCE HOST=aarch64-linux-gnu && \ + make -j 4 && \ + make install && \ + + export PATH=/src/optee/toolchains/aarch64/bin:$PATH && \ + export CC=aarch64-linux-gnu-gcc && \ + + # Build the Host + cd /src/src/optee/host && \ + make -j 4 \ + CROSS_COMPILE=aarch64-linux-gnu- \ + LDFLAGS=\"-L/src/optee/toolchains/aarch64/lib -L/src/depends/aarch64-linux-gnu/lib -ldogecoin -lunistring\" \ + CFLAGS=\"-I/src/optee/toolchains/aarch64/include -I/src/src/optee/ta/include -I/src/depends/aarch64-linux-gnu/include -I/src/depends/aarch64-linux-gnu/include/ykpers-1 -I/src/depends/aarch64-linux-gnu/include/dogecoin\" && \ + + # Build the Enclave + cd ../ta && \ + make -j 4 \ + CROSS_COMPILE=aarch64-linux-gnu- \ + LDFLAGS=\"-L/src/depends/aarch64-linux-gnu/lib -ldogecoin -lunistring\" \ + CFLAGS=\"-I/src/depends/aarch64-linux-gnu/include -I/src/depends/aarch64-linux-gnu/include/dogecoin\" \ + PLATFORM=vexpress-qemu_armv8a \ + TA_DEV_KIT_DIR=/src/optee/optee_os/out/arm/export-ta_arm64 && \ + + # Create symbolic links and prepare image + mkdir -p /src/optee/out/bin && \ + cd /src/optee/out/bin && \ + ln -sf ../../linux/arch/arm64/boot/Image Image && \ + ln -sf ../../trusted-firmware-a/build/qemu/release/bl1.bin bl1.bin && \ + ln -sf ../../trusted-firmware-a/build/qemu/release/bl2.bin bl2.bin && \ + ln -sf ../../trusted-firmware-a/build/qemu/release/bl31.bin bl31.bin && \ + ln -sf ../../optee_os/out/arm/core/tee-header_v2.bin bl32.bin && \ + ln -sf ../../optee_os/out/arm/core/tee-pager_v2.bin bl32_extra1.bin && \ + ln -sf ../../optee_os/out/arm/core/tee-pageable_v2.bin bl32_extra2.bin && \ + ln -sf ../../edk2/Build/ArmVirtQemuKernel-AARCH64/RELEASE_GCC5/FV/QEMU_EFI.fd bl33.bin && \ + ln -sf ../../out-br/images/rootfs.cpio.gz rootfs.cpio.gz && \ + dd if=/dev/zero of=/src/optee/out/bin/libdogecoin.img bs=1M count=32 && \ + mkfs.ext4 /src/optee/out/bin/libdogecoin.img && \ + mkdir -p /src/optee/out-br/mnt && \ + mount -o loop /src/optee/out/bin/libdogecoin.img /src/optee/out-br/mnt && \ + cp /src/src/optee/ta/*.ta /src/optee/out-br/mnt && \ + cp /src/src/optee/host/optee_libdogecoin /src/optee/out-br/mnt && \ + cp /src/spvnode /src/optee/out-br/mnt && \ + cp /src/sendtx /src/optee/out-br/mnt && \ + cp /src/such /src/optee/out-br/mnt && \ + cp /src/tests /src/optee/out-br/mnt && \ + cp /src/bench /src/optee/out-br/mnt && \ + mkdir -p /src/optee/out-br/mnt/data/tee && \ + umount /src/optee/out-br/mnt && \ + exit" +``` + +### Running OP-TEE Libdogecoin Key Manager Enclave (on NanoPC-T6) + +Use scp to copy the /doge/libdogecoin/optee/out/bin/libdogecoin.img to the NanoPC-T6 (e.g. `scp /doge/libdogecoin/optee/out/bin/libdogecoin.img root@192.168.1.1:/root/`). Then, SSH into the NanoPC-T6 and run the following commands: + +```sh +mkdir /media/libdogecoin +mount /root/libdogecoin.img /media/libdogecoin +cd /media/libdogecoin +cp /media/libdogecoin/62d95dc0-7fc2-4cb3-a7f3-c13ae4e633c4.ta /lib/optee_armtz/ +./optee_libdogecoin -c generate_mnemonic +``` + +### Running OP-TEE Libdogecoin Key Manager Enclave (in QEMU ARMv8) + +```sh +docker run --device=/dev/bus/usb/001/003 -it -v "$(pwd):/src" -w /src jforissier/optee_os_ci:qemu_check /bin/bash -c "\ + chmod 777 /src/optee/qemu/build/aarch64-softmmu/qemu-system-aarch64 && \ + cd /src/optee/out/bin && \ + /src/optee/qemu/build/aarch64-softmmu/qemu-system-aarch64 \ + -L /src/optee/qemu/pc-bios \ + -nographic \ + -serial mon:stdio \ + -serial file:/src/optee/serial1.log \ + -smp 2 \ + -machine virt,secure=on,mte=off,gic-version=3 \ + -cpu max,pauth-impdef=on \ + -d unimp \ + -semihosting-config enable=on,target=native \ + -m 1057 \ + -bios bl1.bin \ + -initrd rootfs.cpio.gz \ + -kernel Image \ + -no-acpi \ + -drive file=libdogecoin.img,format=raw,id=libdogecoin,if=none \ + -device virtio-blk-device,drive=libdogecoin \ + -append 'console=ttyAMA0,38400 keep_bootcon root=/dev/vda2' \ + -usb \ + -device pci-ohci,id=ohci \ + -device usb-host,vendorid=0x1050,productid=0x0407" +``` + +`--device=/dev/bus/usb/001/003` is used to pass the YubiKey device to the container. Replace the device path with the appropriate path for your system. + +The QEMU ARMv8 emulator will boot the OP-TEE OS and Trusted Firmware, and the libdogecoin TA will be loaded into the enclave. The host application can then interact with the enclave to perform key management operations. + +In the QEMU terminal, run the following commands to start the libdogecoin TA: + +```sh +# Mount the drive and copy the TA, host application, and data if needed +mkdir /mnt/libdogecoin && \ +mount /dev/vda /mnt/libdogecoin && \ +cp /mnt/libdogecoin/62d95dc0-7fc2-4cb3-a7f3-c13ae4e633c4.ta /lib/optee_armtz/ && \ +cp /mnt/libdogecoin/optee_libdogecoin /usr/bin/ && \ +if [ "$(ls -A /mnt/libdogecoin/data/tee/)" ]; then + cp /mnt/libdogecoin/data/tee/* /data/tee/ && \ + chown tee:tee /data/tee/*; \ +fi +cd /usr/bin/ && \ +chmod 777 optee_libdogecoin +chmod 644 /lib/optee_armtz/62d95dc0-7fc2-4cb3-a7f3-c13ae4e633c4.ta + +# Run the OP-TEE Libdogecoin Key Manager Enclave (see tutorial for commands) +optee_libdogecoin -c generate_mnemonic + +# When finished, copy the data back to the drive +cp -r /data/tee/* /mnt/libdogecoin/data/tee/ + +# Unmount the drive and power off the system +umount /mnt/libdogecoin +poweroff +``` + +### Building OpenEnclave Libdogecoin Key Manager Enclave + +**Dependencies** + +- Ubuntu 20.04 or later +- Docker +- Libdogecoin + +```sh +sudo apt-get install docker.io + +mkdir -p /doge +cd /doge +git clone https://github.com/dogecoinfoundation/libdogecoin.git +cd libdogecoin +``` + +This command uses package installs for the OpenEnclave SDK and Docker to build the OpenEnclave Libdogecoin Key Manager Enclave. Docker is used to build the enclave in a clean environment. Refer to the [OpenEnclave documentation](https://github.com/openenclave/openenclave/blob/master/docs/GettingStartedDocs/Contributors/BuildingInADockerContainer.md) for more information. + +```sh +docker run -v $PWD:/src -w /src ubuntu:20.04 bash -c "\ + # Set up the environment and build libdogecoin + export DEBIAN_FRONTEND=noninteractive && \ + apt-get update && \ + apt-get install -y autoconf automake libtool-bin build-essential curl python3 valgrind python3-dev python3-dbg pkg-config && \ + cd /src && \ + make -j 4 -C depends HOST=x86_64-pc-linux-gnu && \ + make clean && \ + ./autogen.sh && \ + ./configure --prefix=/src/depends/x86_64-pc-linux-gnu --enable-openenclave --enable-test-passwd CFLAGS=-U_FORTIFY_SOURCE && \ + make && \ + make install && \ + + # Set up the OpenEnclave environment and build the enclave + apt-get install -y wget gnupg2 cmake && \ + cd /src/src/openenclave && \ + echo 'deb [arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu focal main' | tee /etc/apt/sources.list.d/intel-sgx.list && \ + wget -qO - https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | apt-key add - && \ + echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-11 main' | tee /etc/apt/sources.list.d/llvm-toolchain-focal-11.list && \ + wget -qO - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ + echo 'deb [arch=amd64] https://packages.microsoft.com/ubuntu/20.04/prod focal main' | tee /etc/apt/sources.list.d/msprod.list && \ + wget -qO - https://packages.microsoft.com/keys/microsoft.asc | apt-key add - && \ + apt update && \ + apt -y install clang-11 libssl-dev gdb libsgx-enclave-common libsgx-quote-ex libprotobuf17 libsgx-dcap-ql libsgx-dcap-ql-dev az-dcap-client open-enclave && \ + apt -y install dkms && \ + source /opt/openenclave/share/openenclave/openenclaverc && \ + mkdir -p build && cd build && cmake .. && make && make simulate" +``` + +## Host Command Line Tutorials + +In this section, we provide a step-by-step tutorial on using the host command line interface to interact with the key management enclaves in both environments. This tutorial will cover the basic operations including generating mnemonic seedphrases, generating public keys, generating addresses, signing messages, and signing transactions. + +### Prerequisites + +Before proceeding, ensure you have successfully built and set up the OP-TEE and OpenEnclave environments as described in the previous sections. + +### OP-TEE Host Command Line Tutorial + +#### Generating a Mnemonic Seedphrase + +Generate a mnemonic seedphrase for backup and recovery purposes. This is the first step in creating a new wallet. It will only be displayed once, so make sure to back it up. + +Enter the shared secret for TOTP when prompted. The shared secret must be 40 hex characters (*e.g., `f38243e0e3e97a5c8aa5cc481a815add6c119648`*). The shared secret will be set on the YubiKey (if present) by the host application, and the mnemonic will be generated and displayed. If OTP Slot 1 is already programmed, the host application will prompt you to overwrite it. + +This command will generate a mnemonic seedphrase and display it on the screen. All flags are optional, and the user will be prompted for input if not provided. + +```sh +optee_libdogecoin -c generate_mnemonic -n -s -e -p or -u -f +``` + +The `-n` flag is used to provide the mnemonic input for recovery purposes if needed. Replace `` with the mnemonic seedphrase you want to use for recovery. If no mnemonic input is provided, the enclave will generate a random mnemonic seedphrase. + +The `-s` flag is used to provide the shared secret for TOTP authentication from command line instead of prompting the user. Replace `` with the shared secret you want to use for TOTP authentication. + +The `-e` flag is used to provide the entropy size for the mnemonic seedphrase. Replace `` with the desired entropy size in bits. If no entropy size is provided, the default value of `"256"` bits (24 words) will be used. + +The `-p` flag is used to provide the password for the mnemonic seedphrase. Replace `` with the password you want to use for the mnemonic seedphrase. + +The `-u` flag is used to prompt the user for input. If the `-u` flag is provided, the user will be prompted for a password. + +The `-f` flag is used to provide additional flags for the mnemonic seedphrase. Replace `` with the desired flags for the mnemonic seedphrase. The `"delegate"` flag is used to delegate account keys to a third party. + +#### Generating an Extended Public Key + +Generate an extended public key using the account and change level. The change level can be set to 0 for external addresses and 1 for internal addresses. The account number is the BIP-44 account. The public key will be derived from the seedphrase stored within the enclave. + +The TOTP code will be retrieved from the YubiKey (if present) and used as the authentication token for this operation. + +The `-a` flag is used to provide the auth token if a Yubikey is not present. Use a tool like `oathtool` to generate TOTP (*e.g., `oathtool --totp "f38243e0e3e97a5c8aa5cc481a815add6c119648"`). + +```sh +optee_libdogecoin -c generate_extended_public_key -o -l -a -p or -u +``` + +#### Generating an Address + +Generate a Dogecoin address using the account, address index, and change level. + +```sh +optee_libdogecoin -c generate_address -o -l -i -a -p or -u +``` + +Replace ``, ``, and `` with appropriate values. The change level can be set to 0 for external addresses and 1 for internal addresses. The account number is the BIP-44 account. The address index is the index of the address within the account. The address will be derived from the seedphrase stored within the enclave. + +#### Signing a Message + +Sign a message using the private key stored within the enclave. The message will be signed using the private key derived from the seedphrase stored within the enclave. + +```sh +optee_libdogecoin -c sign_message -o -l -i -m -a -p or -u +``` + +Replace `` with the message you want to sign. + +#### Signing a Transaction + +Sign a raw transaction using the private key stored within the enclave. A raw transaction is a hexadecimal string representing the transaction data. Currently, if no transaction data is provided, an example transaction will be signed for demonstration purposes. The private key will be derived from the seedphrase stored within the enclave. + +```sh +optee_libdogecoin -c sign_transaction -o -l -i -t -a -p or -u +``` + +Replace `` with the raw transaction data. + +#### Delegate Key + +Delegate account keys to a third party. This operation allows a third party to manage the keys for a specific account. The third party will be able to export the account keys on behalf of the user using the delegate password. + +```sh +optee_libdogecoin -c delegate_key -o -d or -u -a -p +``` + +Replace `` with the password for the delegate account. + +#### Export Delegate Key + +Export the delegated account keys using the delegate password. This operation allows the third party to export the account keys on behalf of the user. + +```sh +optee_libdogecoin -c export_delegate_key -o -d or -u +``` + +Replace `` with the password for the delegate account. + +### OpenEnclave Host Command Line Tutorial + +Note: In OpenEnclave, `--simulate` is used to run the enclave in simulation mode. This is useful for testing without SGX hardware, but it is not secure. For production use, remove `--simulate` to run on real SGX hardware. + +#### Generating a Mnemonic Seedphrase + +Generate a mnemonic seedphrase for backup and recovery purposes. This is the first step in creating a new wallet. It will only be displayed once, so make sure to back it up. + +Enter the shared secret for TOTP when prompted. The shared secret must be 40 hex characters (*e.g., `f38243e0e3e97a5c8aa5cc481a815add6c119648`*). The shared secret will be set on the YubiKey (if present) by the host application, and the mnemonic will be generated and displayed. If OTP Slot 1 is already programmed, the host application will prompt you to overwrite it. + +This command will generate a mnemonic seedphrase and display it on the screen. All flags are optional, and the user will be prompted for input if not provided. + +```sh +/doge/libdogecoin/src/openenclave/build/host/host /doge/libdogecoin/src/openenclave/build/enclave/enclave.signed --simulate -c generate_mnemonic -n -s -e +``` + +#### Generating an Extended Public Key + +Generate an extended public key using the account and change level. The change level can be set to 0 for external addresses and 1 for internal addresses. The account number is the BIP-44 account. + +The TOTP code will be retrieved from the YubiKey (if present) and used as the authentication token for this operation. + +The `-a` flag is used to provide the auth token if a Yubikey is not present. Use a tool like `oathtool` to generate TOTP (*e.g., `oathtool --totp "f38243e0e3e97a5c8aa5cc481a815add6c119648"`). + +```sh +/doge/libdogecoin/src/openenclave/build/host/host /doge/libdogecoin/src/openenclave/build/enclave/enclave.signed --simulate -c generate_extended_public_key -o -l +``` + +#### Generating an Address + +Generate a Dogecoin address using the account, address index, and change level. + +```sh +/doge/libdogecoin/src/openenclave/build/host/host /doge/libdogecoin/src/openenclave/build/enclave/enclave.signed --simulate -c generate_address -o -l -i +``` + +Replace ``, ``, and `` with appropriate values. + +#### Signing a Message + +Sign a message using the private key stored within the enclave. + +```sh +/doge/libdogecoin/src/openenclave/build/host/host /doge/libdogecoin/src/openenclave/build/enclave/enclave.signed --simulate -c sign_message -o -l -i -m "This is just a test message" +``` + +#### Signing a Transaction + +Sign a raw transaction using the private key stored within the enclave. A raw transaction is a hexadecimal string representing the transaction data. Currently, if no transaction data is provided, an example transaction will be signed for demonstration purposes. + +```sh +/doge/libdogecoin/src/openenclave/build/host/host /doge/libdogecoin/src/openenclave/build/enclave/enclave.signed --simulate -c sign_transaction -o -l -i -t +``` + +Replace ``, ``, and `` with appropriate values. The change level can be set to 0 for external addresses and 1 for internal addresses. The account number is the BIP-44 account. The address index is the index of the address within the account. + +Replace `` with the raw transaction data. + +## Conclusion + +In this document, we explored the integration of libdogecoin with secure enclaves in Trusted Execution Environments (TEEs). We discussed the benefits of using Intel SGX and ARM TrustZone to secure key management operations, including mnemonic seedphrase generation, public key generation, address generation, message signing, and transaction signing. We provided a step-by-step tutorial on using the host command line interface to interact with the key management enclaves in both OP-TEE and OpenEnclave environments. We also outlined critical vulnerabilities in TEEs and provided recommendations for mitigating these risks. We hope this document serves as a valuable resource for developers looking to enhance the security of Dogecoin transactions using secure enclaves. diff --git a/doc/yubikey.md b/doc/yubikey.md new file mode 100644 index 000000000..52b47f653 --- /dev/null +++ b/doc/yubikey.md @@ -0,0 +1,38 @@ +### YubiKey Storage of Encrypted Keys + +The YubiKey is a hardware security key that provides strong two-factor authentication and secure cryptographic operations. By integrating the YubiKey with libdogecoin, users can enhance the security of their wallets and transactions. While the integration is tested with YubiKey 5 NFC, it also works with other YubiKey models that support PIV (Personal Identity Verification). + +YubiKey supports numerous cryptographic operations; for libdogecoin, we are primarily interested in the PIV application. The PIV application provides a secure way to store private keys. The YubiKey acts as secure key storage, protecting the private keys from unauthorized access. + +We have integrated the YubiKey with the `seal` module, specifically for encrypted key storage. The `seal` module is responsible for encrypting and decrypting the private keys stored in the YubiKey. By using the YubiKey, users can securely store and retrieve their private keys during wallet operations. + +The process involves multi-factor authentication (PIN and YubiKey) to unlock the encrypted keys, followed by the decryption of BIP39 mnemonics. The seed, master key, or mnemonic is first encrypted with software and then stored on the YubiKey. During the storage process, the user enters a management password, and to retrieve the key, the user enters the YubiKey PIN. + +Its recommeded that the user download the YubiKey Manager to manage the YubiKey. The YubiKey Manager is a graphical user interface that allows users to change the PIN, management key, and other settings. The YubiKey Manager is available for Windows, macOS, and Linux from the [Yubico website](https://www.yubico.com/support/download/yubikey-manager/). + +### Dependencies +- `libykpiv` - The YubiKey C library for interacting with the YubiKey. +- `libykpiv-dev` - The development headers for the YubiKey C library. +- `pcscd` - The PC/SC smart card daemon for managing smart card readers. +- `libpcsclite-dev` - The development headers for the PC/SC smart card library. + +### Installation +#### Linux +```sh +sudo apt-get update +sudo apt-get install libykpiv libykpiv-dev pcscd libpcsclite-dev +``` + +### Example C Code +```c +// Encrypt a BIP32 seed with software and store it on YubiKey +u_assert_true(dogecoin_encrypt_seed_with_sw_to_yubikey(seed, sizeof(SEED), TEST_FILE, true, test_password)); +debug_print("Seed to YubiKey: %s\n", utils_uint8_to_hex(seed, sizeof(SEED))); +debug_print("Encrypted seed: %s\n", utils_uint8_to_hex(file, filesize)); + +// Decrypt a BIP32 seed with software after retrieving it from YubiKey +uint8_t decrypted_seed[4096] = {0}; +u_assert_true(dogecoin_decrypt_seed_with_sw_from_yubikey(decrypted_seed, TEST_FILE, test_password)); +debug_print("Decrypted seed: %s\n", utils_uint8_to_hex(decrypted_seed, decrypted_size)); +u_assert_true(memcmp(seed, decrypted_seed, sizeof(SEED)) == 0); +``` \ No newline at end of file diff --git a/include/dogecoin/address.h b/include/dogecoin/address.h index 9e3e33bc9..508b53644 100644 --- a/include/dogecoin/address.h +++ b/include/dogecoin/address.h @@ -3,7 +3,7 @@ Copyright (c) 2022 bluezr Copyright (c) 2023 edtubbs - Copyright (c) 2023 The Dogecoin Foundation + Copyright (c) 2023-2024 The Dogecoin Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -95,6 +95,9 @@ LIBDOGECOIN_API int getDerivedHDAddressFromEncryptedMnemonic(const uint32_t acco /* generates a new dogecoin address from a encrypted master (HD) key and a slip44 key path */ LIBDOGECOIN_API int getDerivedHDAddressFromEncryptedHDNode(const uint32_t account, const uint32_t index, const CHANGE_LEVEL change_level, char* p2pkh_pubkey, const bool is_testnet, const int file_num); +/* generates a new dogecoin address from an account extended public key and a slip44 key path */ +LIBDOGECOIN_API int getDerivedHDAddressFromAcctPubKey(const char* ext_pubkey, const uint32_t index, const CHANGE_LEVEL change_level, char* p2pkh_pubkey, const bool is_testnet); + LIBDOGECOIN_END_DECL #endif // __LIBDOGECOIN_ADDRESS_H__ diff --git a/include/dogecoin/dogecoin.h b/include/dogecoin/dogecoin.h index 656c84e30..484587a3e 100644 --- a/include/dogecoin/dogecoin.h +++ b/include/dogecoin/dogecoin.h @@ -3,7 +3,7 @@ The MIT License (MIT) Copyright (c) 2023 bluezr Copyright (c) 2023 edtubbs - Copyright (c) 2023 The Dogecoin Foundation + Copyright (c) 2023-2024 The Dogecoin Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -134,6 +134,7 @@ typedef uint8_t dogecoin_bool; //!serialize, c/c++ save bool #define PRIVKEYHEXLEN DOGECOIN_ECKEY_PKEY_LENGTH * 2 + 1 #define PUBKEYHEXLEN 67 #define PUBKEYHASHLEN 41 +#define SCRIPTPUBKEYLEN 51 // 40 + 6 + 4 + 1 #define KEYPATHMAXLEN 256 /* Constants for transaction */ diff --git a/include/dogecoin/libdogecoin.h b/include/dogecoin/libdogecoin.h index 30dc1edb5..26686cbf3 100644 --- a/include/dogecoin/libdogecoin.h +++ b/include/dogecoin/libdogecoin.h @@ -100,6 +100,9 @@ void dogecoin_ecc_stop(void); //#define PUBKEYHASHLEN 40 //should be 40 for pubkeyhash. Internally this is cited as 41 for strings that represent it because +stringterm. #define PUBKEYHASHLEN 41 +//#define SCRIPTPUBKEYLEN 50 //should be 50 for pubkeyhash. Internally this is cited as 51 for strings that represent it because +stringterm. +#define SCRIPTPUBKEYLEN 51 + //#define KEYPATHMAXLEN 255 // Maximum length of key path string. Internally this is cited as 256 for strings that represent it because +stringterm. #define KEYPATHMAXLEN 256 @@ -288,6 +291,7 @@ dogecoin_bool deriveBIP44ExtendedPublicKey( /* utilities */ uint8_t* utils_hex_to_uint8(const char* str); char* utils_uint8_to_hex(const uint8_t* bin, size_t l); +char* getpass(const char *prompt); /* Advanced API functions for mnemonic seedphrase generation -------------------------------------------------------------------------- @@ -329,6 +333,10 @@ int getDerivedHDAddressFromMnemonic(const uint32_t account, const uint32_t index #define MAX_FILES 1000 #define TEST_FILE 999 +/* Encrypted BLOB */ +#define MAX_ENCRYPTED_BLOB_SIZE 2048 +typedef uint8_t ENCRYPTED_BLOB[MAX_ENCRYPTED_BLOB_SIZE]; + /* Encrypt a BIP32 seed with the TPM */ dogecoin_bool dogecoin_encrypt_seed_with_tpm (const SEED seed, const size_t size, const int file_num, const dogecoin_bool overwrite); @@ -350,6 +358,27 @@ dogecoin_bool dogecoin_decrypt_hdnode_with_tpm(dogecoin_hdnode* out, const int f /* Generate a 256-bit random english mnemonic with the TPM */ dogecoin_bool generateRandomEnglishMnemonicTPM(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite); +/* Encrypt a BIP32 seed with software */ +dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, const size_t size, const int file_num, const dogecoin_bool overwrite, const char* test_password, ENCRYPTED_BLOB* encrypted_blob_out, size_t* encrypted_blob_size); + +/* Decrypt a BIP32 seed with software */ +dogecoin_bool dogecoin_decrypt_seed_with_sw (SEED seed, const int file_num, const char* test_password, ENCRYPTED_BLOB encrypted_blob); + +/* Generate a BIP39 mnemonic and encrypt it with software */ +dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite, const char* lang, const char* space, const char* words, const char* test_password, ENCRYPTED_BLOB* encrypted_blob_out, size_t* encrypted_blob_size); + +/* Decrypt a BIP39 mnemonic with software */ +dogecoin_bool dogecoin_decrypt_mnemonic_with_sw(MNEMONIC mnemonic, const int file_num, const char* test_password, ENCRYPTED_BLOB encrypted_blob); + +/* Generate a BIP32 HD node and encrypt it with software */ +dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, const int file_num, const dogecoin_bool overwrite, const char* test_password, ENCRYPTED_BLOB* encrypted_blob_out, size_t* encrypted_blob_size); + +/* Decrypt a BIP32 HD node object with software */ +dogecoin_bool dogecoin_decrypt_hdnode_with_sw(dogecoin_hdnode* out, const int file_num, const char* test_password, ENCRYPTED_BLOB encrypted_blob); + +/* Generate a 256-bit random english mnemonic with software */ +dogecoin_bool generateRandomEnglishMnemonicSW(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite, uint8_t** encrypted_mnemonic_out, size_t* encrypted_mnemonic_size); + /* generates a new dogecoin address from an encrypted seed and a slip44 key path */ int getDerivedHDAddressFromEncryptedSeed(const uint32_t account, const uint32_t index, const CHANGE_LEVEL change_level, char* p2pkh_pubkey, const dogecoin_bool is_testnet, const int file_num); @@ -359,6 +388,9 @@ int getDerivedHDAddressFromEncryptedMnemonic(const uint32_t account, const uint3 /* generates a new dogecoin address from an encrypted HD node and a slip44 key path */ int getDerivedHDAddressFromEncryptedHDNode(const uint32_t account, const uint32_t index, const CHANGE_LEVEL change_level, char* p2pkh_pubkey, const bool is_testnet, const int file_num); +/* generates a new dogecoin address from an encrypted seed and a custom key path */ +int getDerivedHDAddressFromAcctPubKey(const char* ext_pubkey, const uint32_t index, const CHANGE_LEVEL change_level, char* p2pkh_pubkey, const bool is_testnet); + /* Transaction creation functions - builds a dogecoin transaction ---------------------------------------------------------------- */ @@ -440,6 +472,7 @@ uint64_t coins_to_koinu_str(char* coins); */ char* dogecoin_char_vla(size_t size); void dogecoin_free(void* ptr); +volatile void* dogecoin_mem_zero(volatile void* dst, size_t len); /* Advanced API for signing arbitrary messages @@ -524,5 +557,20 @@ char* dogecoin_get_utxo_txid_str(char* address, unsigned int index); uint8_t* dogecoin_get_utxo_txid(char* address, unsigned int index); uint64_t dogecoin_get_balance(char* address); char* dogecoin_get_balance_str(char* address); -uint64_t dogecoin_get_balance(char* address); -char* dogecoin_get_balance_str(char* address); + +/* Random API +-------------------------------------------------------------------------- +*/ + +dogecoin_bool dogecoin_random_bytes(uint8_t* buf, uint32_t len, const uint8_t update_seed); + +/* Crypto API +-------------------------------------------------------------------------- +*/ + +#define SHA1_DIGEST_LENGTH 20 +#define SHA256_DIGEST_LENGTH 32 + +void hmac_sha1(const uint8_t* key, const size_t keylen, const uint8_t* msg, const size_t msglen, uint8_t* hmac); + +void sha256_raw(const uint8_t*, size_t, uint8_t[SHA256_DIGEST_LENGTH]); diff --git a/include/dogecoin/seal.h b/include/dogecoin/seal.h index bcb6dea99..c7f8baf9e 100644 --- a/include/dogecoin/seal.h +++ b/include/dogecoin/seal.h @@ -1,6 +1,6 @@ /********************************************************************** * Copyright (c) 2023 edtubbs * - * Copyright (c) 2023 The Dogecoin Foundation * + * Copyright (c) 2023-2024 The Dogecoin Foundation * * Distributed under the MIT software license, see the accompanying * * file COPYING or http://www.opensource.org/licenses/mit-license.php.* **********************************************************************/ @@ -33,17 +33,33 @@ LIBDOGECOIN_API /* define test file number */ #define TEST_FILE 999 +/* define the maximum size of an Encrypted BLOB */ +#define MAX_ENCRYPTED_BLOB_SIZE 2048 + +/* + * Typedefs + */ + +/* Encrypted BLOB */ +typedef uint8_t ENCRYPTED_BLOB[MAX_ENCRYPTED_BLOB_SIZE]; + /* Encrypt a BIP32 seed with the TPM */ -LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_tpm (const SEED seed, const size_t size, const int file_num, const dogecoin_bool overwrite); +LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_tpm(const SEED seed, const size_t size, const int file_num, const dogecoin_bool overwrite); /* Decrypt a BIP32 seed with the TPM */ -LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_tpm (SEED seed, const int file_num); +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_tpm(SEED seed, const int file_num); /* Encrypt a BIP32 seed with software */ -LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw (const SEED seed, const size_t size, const int file_num, const dogecoin_bool overwrite, const char* test_password); +LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, const size_t size, const int file_num, const dogecoin_bool overwrite, const char* test_password, ENCRYPTED_BLOB* encrypted_blob_out, size_t* encrypted_blob_size); /* Decrypt a BIP32 seed with software */ -LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_sw (SEED seed, const int file_num, const char* test_password); +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_sw(SEED seed, const int file_num, const char* test_password, ENCRYPTED_BLOB encrypted_blob); + +/* Encrypt a BIP32 seed with software and store it on YubiKey */ +LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw_to_yubikey(const SEED seed, const size_t size, const int file_num, const dogecoin_bool overwrite, const char* test_password); + +/* Decrypt a BIP32 seed with software after retrieving it from YubiKey */ +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_sw_from_yubikey(SEED seed, const int file_num, const char* test_password); /* Generate a BIP39 mnemonic and encrypt it with the TPM */ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_tpm(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite, const char* lang, const char* space, const char* words); @@ -52,10 +68,16 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_tpm(MNEMON LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_mnemonic_with_tpm(MNEMONIC mnemonic, const int file_num); /* Generate a BIP39 mnemonic and encrypt it with software */ -LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite, const char* lang, const char* space, const char* words, const char* test_password); +LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite, const char* lang, const char* space, const char* words, const char* test_password, ENCRYPTED_BLOB* encrypted_blob_out, size_t* encrypted_blob_size); /* Decrypt a BIP39 mnemonic with software */ -LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_mnemonic_with_sw(MNEMONIC mnemonic, const int file_num, const char* test_password); +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_mnemonic_with_sw(MNEMONIC mnemonic, const int file_num, const char* test_password, ENCRYPTED_BLOB encrypted_blob); + +/* Generate a BIP39 mnemonic, encrypt it with software, and store it on YubiKey */ +LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw_to_yubikey(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite, const char* lang, const char* space, const char* words, const char* test_password); + +/* Decrypt a BIP39 mnemonic with software after retrieving it from YubiKey */ +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_mnemonic_with_sw_from_yubikey(MNEMONIC mnemonic, const int file_num, const char* test_password); /* Generate a BIP32 HD node and encrypt it with the TPM */ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_hdnode_encrypt_with_tpm(dogecoin_hdnode* out, const int file_num, const dogecoin_bool overwrite); @@ -64,10 +86,16 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_hdnode_encrypt_with_tpm(dogecoin LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_hdnode_with_tpm(dogecoin_hdnode* out, const int file_num); /* Generate a BIP32 HD node and encrypt it with software */ -LIBDOGECOIN_API dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, const int file_num, const dogecoin_bool overwrite, const char* test_password); +LIBDOGECOIN_API dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, const int file_num, const dogecoin_bool overwrite, const char* test_password, ENCRYPTED_BLOB* encrypted_blob_out, size_t* encrypted_blob_size); /* Decrypt a BIP32 HD node object with software */ -LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_hdnode_with_sw(dogecoin_hdnode* out, const int file_num, const char* test_password); +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_hdnode_with_sw(dogecoin_hdnode* out, const int file_num, const char* test_password, ENCRYPTED_BLOB encrypted_blob); + +/* Generate a BIP32 HD node, encrypt it with software, and store it on YubiKey */ +LIBDOGECOIN_API dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw_to_yubikey(dogecoin_hdnode* out, const int file_num, const dogecoin_bool overwrite, const char* test_password); + +/* Decrypt a BIP32 HD node with software after retrieving it from YubiKey */ +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_hdnode_with_sw_from_yubikey(dogecoin_hdnode* out, const int file_num, const char* test_password); /* List all encryption keys in the TPM */ LIBDOGECOIN_API dogecoin_bool dogecoin_list_encryption_keys_in_tpm(wchar_t* names[], size_t* count); @@ -76,7 +104,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_list_encryption_keys_in_tpm(wchar_t* name LIBDOGECOIN_API dogecoin_bool generateRandomEnglishMnemonicTPM(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite); /* Generate a 256-bit random english mnemonic with software */ -LIBDOGECOIN_API dogecoin_bool generateRandomEnglishMnemonicSW(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite); +LIBDOGECOIN_API dogecoin_bool generateRandomEnglishMnemonicSW(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite, ENCRYPTED_BLOB* encrypted_blob, size_t* encrypted_blob_size); LIBDOGECOIN_END_DECL diff --git a/include/dogecoin/sha2.h b/include/dogecoin/sha2.h index 15b79eea0..9fa92d3da 100644 --- a/include/dogecoin/sha2.h +++ b/include/dogecoin/sha2.h @@ -42,6 +42,9 @@ LIBDOGECOIN_BEGIN_DECL +#define SHA1_BLOCK_LENGTH 64 +#define SHA1_DIGEST_LENGTH 20 +#define SHA1_DIGEST_STRING_LENGTH (SHA1_DIGEST_LENGTH * 2 + 1) #define SHA256_BLOCK_LENGTH 64 #define SHA256_DIGEST_LENGTH 32 #define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1) @@ -49,6 +52,12 @@ LIBDOGECOIN_BEGIN_DECL #define SHA512_DIGEST_LENGTH 64 #define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1) + +typedef struct _sha1_context { + uint32_t state[5]; + uint64_t bitcount; + uint32_t buffer[SHA1_BLOCK_LENGTH/sizeof(uint32_t)]; +} sha1_context; typedef struct _sha256_context { uint32_t state[8]; uint64_t bitcount; @@ -60,6 +69,15 @@ typedef struct _sha512_context { uint8_t buffer[SHA512_BLOCK_LENGTH]; } sha512_context; + +LIBDOGECOIN_API void sha1_Transform(const uint32_t* state_in, const uint32_t* data, uint32_t* state_out); +LIBDOGECOIN_API void sha1_Init(sha1_context *); +LIBDOGECOIN_API void sha1_Update(sha1_context*, const uint8_t*, size_t); +LIBDOGECOIN_API void sha1_Final(sha1_context*, uint8_t[SHA1_DIGEST_LENGTH]); +LIBDOGECOIN_API char* sha1_End(sha1_context*, char[SHA1_DIGEST_STRING_LENGTH]); +LIBDOGECOIN_API void sha1_Raw(const uint8_t*, size_t, uint8_t[SHA1_DIGEST_LENGTH]); +LIBDOGECOIN_API char* sha1_Data(const uint8_t*, size_t, char[SHA1_DIGEST_STRING_LENGTH]); + LIBDOGECOIN_API void sha256_init(sha256_context*); LIBDOGECOIN_API void sha256_write(sha256_context*, const uint8_t*, size_t); LIBDOGECOIN_API void sha256_finalize(sha256_context*, uint8_t[SHA256_DIGEST_LENGTH]); @@ -71,6 +89,11 @@ LIBDOGECOIN_API void sha512_write(sha512_context*, const uint8_t*, size_t); LIBDOGECOIN_API void sha512_finalize(sha512_context*, uint8_t[SHA512_DIGEST_LENGTH]); LIBDOGECOIN_API void sha512_raw(const uint8_t*, size_t, uint8_t[SHA512_DIGEST_LENGTH]); +typedef struct _hmac_sha1_context { + uint8_t o_key_pad[SHA1_BLOCK_LENGTH]; + sha1_context ctx; +} hmac_sha1_context; + typedef struct _hmac_sha256_context { uint8_t o_key_pad[SHA256_BLOCK_LENGTH]; sha256_context ctx; @@ -81,6 +104,12 @@ typedef struct _hmac_sha512_context { sha512_context ctx; } hmac_sha512_context; +LIBDOGECOIN_API void hmac_sha1_init(hmac_sha1_context* hctx, const uint8_t* key, const uint32_t keylen); +LIBDOGECOIN_API void hmac_sha1_update(hmac_sha1_context* hctx, const uint8_t* msg, const uint32_t msglen); +LIBDOGECOIN_API void hmac_sha1_final(hmac_sha1_context* hctx, uint8_t* hmac); +LIBDOGECOIN_API void hmac_sha1(const uint8_t* key, const size_t keylen, const uint8_t* msg, const size_t msglen, uint8_t* hmac); +LIBDOGECOIN_API void hmac_sha1_prepare(const uint8_t *key, const uint32_t keylen, uint32_t *opad_digest, uint32_t *ipad_digest); + void hmac_sha256_prepare(const uint8_t *key, const uint32_t keylen, uint32_t *opad_digest, uint32_t *ipad_digest); LIBDOGECOIN_API void hmac_sha256_init(hmac_sha256_context* hctx, const uint8_t* key, const uint32_t keylen); diff --git a/src/address.c b/src/address.c index 55731ae7d..38f5d0ba1 100644 --- a/src/address.c +++ b/src/address.c @@ -4,7 +4,7 @@ Copyright (c) 2023 bluezr Copyright (c) 2023 edtubbs - Copyright (c) 2023 The Dogecoin Foundation + Copyright (c) 2023-2024 The Dogecoin Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -873,3 +873,39 @@ int getDerivedHDAddressFromEncryptedHDNode(const uint32_t account, const uint32_ return 0; } + +int getDerivedHDAddressFromAcctPubKey(const char* account_ext_pubkey, const uint32_t index, const CHANGE_LEVEL change_level, char* p2pkh_pubkey, const bool is_testnet) { + if (!account_ext_pubkey) { + debug_print("%s", "no extended key\n"); + return false; + } + + /* Initialize variables */ + dogecoin_hdnode derived_node; + + /* determine if mainnet or testnet/regtest */ + const dogecoin_chainparams* chain = is_testnet ? &dogecoin_chainparams_test : &dogecoin_chainparams_main; + + /* create account node */ + dogecoin_hdnode* account_node = dogecoin_hdnode_new(); + if (dogecoin_hdnode_deserialize(account_ext_pubkey, chain, account_node) == false) { + dogecoin_hdnode_free(account_node); + return false; + } + + /* derive child key */ + char path[KEYPATHMAXLEN]; + sprintf(path, "m/%s/%u", change_level, index); + if (dogecoin_hd_generate_key(&derived_node, path, account_node->public_key, account_node->depth, account_node->chain_code, true) == false) { + dogecoin_hdnode_free(account_node); + return false; + } + + /* get address */ + dogecoin_hdnode_get_p2pkh_address(&derived_node, chain, p2pkh_pubkey, P2PKHLEN); + + /* free memory */ + dogecoin_hdnode_free(account_node); + + return 0; +} diff --git a/src/bip39.c b/src/bip39.c index 4f7281e35..c63e5a73d 100644 --- a/src/bip39.c +++ b/src/bip39.c @@ -461,6 +461,7 @@ int get_root_seed(const char *pass, const char *passphrase, SEED seed) { */ int get_custom_words(const char *filepath, char* wordlist[]) { +#ifndef USE_OPTEE /* OPTEE does not support file I/O */ int i = 0; FILE * fp; char word[1024]; @@ -501,6 +502,11 @@ int get_custom_words(const char *filepath, char* wordlist[]) { } return 0; +#else + (void)filepath; + (void)wordlist; + return -1; +#endif } /* @@ -724,7 +730,7 @@ int dogecoin_generate_mnemonic (const ENTROPY_SIZE entropy_size, const char* lan if (entropy_size != NULL) { /* load custom word file into memory if path is valid */ - if (filepath != NULL) { + if (filepath != NULL) { if (get_custom_words (filepath, (char **) wordlist) == -1) { /* Free memory for custom words */ diff --git a/src/cli/spvnode.c b/src/cli/spvnode.c index c02f11b54..aece7b381 100644 --- a/src/cli/spvnode.c +++ b/src/cli/spvnode.c @@ -506,6 +506,7 @@ int main(int argc, char* argv[]) { printf("done\n"); printf("Discover peers...\n"); dogecoin_spv_client_discover_peers(client, ips); + printf("Connecting to the p2p network...\n"); dogecoin_spv_client_runloop(client); dogecoin_spv_client_free(client); diff --git a/src/cli/such.c b/src/cli/such.c index 5b615b476..c07aa9ef6 100644 --- a/src/cli/such.c +++ b/src/cli/such.c @@ -885,7 +885,7 @@ int main(int argc, char* argv[]) else { /* generate and encrypt a new hd master key with software */ - if (!dogecoin_generate_hdnode_encrypt_with_sw(&node, file_num, overwrite, NULL)) { + if (!dogecoin_generate_hdnode_encrypt_with_sw(&node, file_num, overwrite, NULL, NULL, NULL)) { printf("bip32_extended_master_key (-y , -j (use_tpm) and -w (overwrite), all optional),\n"); return showError("Failed to generate master key in sofware"); } @@ -1162,7 +1162,7 @@ int main(int argc, char* argv[]) else { /* generate mnemonic with software */ - if (generateRandomEnglishMnemonicSW(mnemonic, file_num, overwrite) == false) { + if (generateRandomEnglishMnemonicSW(mnemonic, file_num, overwrite, NULL, NULL) == false) { printf("generate_mnemonic -y , -j (use_tpm), -w (overwrite), -b (silent),\n"); return showError("Failed to generate/encrypt mnemonic in software"); } @@ -1231,7 +1231,7 @@ int main(int argc, char* argv[]) else { /* decrypt master key from software */ - if (dogecoin_decrypt_hdnode_with_sw (&node, file_num, NULL) == false) { + if (dogecoin_decrypt_hdnode_with_sw (&node, file_num, NULL, NULL) == false) { printf("decrypt_master_key (requires -y , -j (use_tpm) optional),\n"); return showError("failed to decrypt master key with software\n"); } @@ -1279,7 +1279,7 @@ int main(int argc, char* argv[]) else { /* decrypt mnemonic from software */ - if (dogecoin_decrypt_mnemonic_with_sw (mnemonic, file_num, NULL) == false) { + if (dogecoin_decrypt_mnemonic_with_sw (mnemonic, file_num, NULL, NULL) == false) { printf("decrypt_mnemonic (requires -y , -j (use_tpm) optional),\n"); return showError("failed to decrypt mnemonic with software\n"); } @@ -1322,7 +1322,7 @@ int main(int argc, char* argv[]) else { /* get seed from software */ - if (dogecoin_decrypt_seed_with_sw (seed, file_num, NULL) == false) { + if (dogecoin_decrypt_seed_with_sw (seed, file_num, NULL, NULL) == false) { printf("seed_to_master_key (requires -y , -j (use_tpm) optional),\n"); return showError("failed to decrypt seed with software\n"); } @@ -1364,7 +1364,7 @@ int main(int argc, char* argv[]) else { /* get mnemonic from software */ - if (dogecoin_decrypt_mnemonic_with_sw (mnemonic, file_num, NULL) == false) { + if (dogecoin_decrypt_mnemonic_with_sw (mnemonic, file_num, NULL, NULL) == false) { printf("mnemonic_to_key (requires -y , -j (use_tpm) optional),\n"); return showError("failed to decrypt mnemonic with software\n"); } @@ -1443,7 +1443,7 @@ int main(int argc, char* argv[]) else { /* get mnemonic from software */ - if (dogecoin_decrypt_mnemonic_with_sw (mnemonic, file_num, NULL) == false) { + if (dogecoin_decrypt_mnemonic_with_sw (mnemonic, file_num, NULL, NULL) == false) { printf("mnemonic_to_addresses (requires -y , -j (use_tpm), -o , -g , -i and -a, all optional),\n"); return showError("failed to decrypt mnemonic with software\n"); } diff --git a/src/openenclave/CMakeLists.txt b/src/openenclave/CMakeLists.txt index f1b70f78e..0ded9e0e8 100644 --- a/src/openenclave/CMakeLists.txt +++ b/src/openenclave/CMakeLists.txt @@ -36,18 +36,22 @@ set(OE_CRYPTO_LIB add_subdirectory(enclave) add_subdirectory(host) -# Generate key -add_custom_command( - OUTPUT private.pem public.pem - COMMAND openssl genrsa -out private.pem -3 3072 - COMMAND openssl rsa -in private.pem -pubout -out public.pem) +if (NOT DEFINED ENV{GITHUB_ACTIONS}) + # Generate key + add_custom_command( + OUTPUT private.pem public.pem + COMMAND openssl genrsa -out private.pem -3 3072 + COMMAND openssl rsa -in private.pem -pubout -out public.pem) +endif () # Sign enclave add_custom_command( OUTPUT enclave/enclave.signed DEPENDS enclave enclave/libdogecoin.conf private.pem COMMAND openenclave::oesign sign -e $ -c - ${CMAKE_SOURCE_DIR}/enclave/libdogecoin.conf -k private.pem) + ${CMAKE_SOURCE_DIR}/enclave/libdogecoin.conf -k private.pem + COMMENT "Signing enclave" +) add_custom_target(sign ALL DEPENDS enclave/enclave.signed) @@ -55,10 +59,12 @@ if ((NOT DEFINED ENV{OE_SIMULATION}) OR (NOT $ENV{OE_SIMULATION})) add_custom_target( run DEPENDS host sign - COMMAND host ${CMAKE_BINARY_DIR}/enclave/enclave.signed) -endif () + COMMAND host ${CMAKE_BINARY_DIR}/enclave/enclave.signed + ) +endif() add_custom_target( simulate DEPENDS host sign - COMMAND host ${CMAKE_BINARY_DIR}/enclave/enclave.signed --simulate) + COMMAND host ${CMAKE_BINARY_DIR}/enclave/enclave.signed --simulate +) diff --git a/src/openenclave/enclave/CMakeLists.txt b/src/openenclave/enclave/CMakeLists.txt index a6a91c4d3..91a6448db 100644 --- a/src/openenclave/enclave/CMakeLists.txt +++ b/src/openenclave/enclave/CMakeLists.txt @@ -19,10 +19,10 @@ endif () target_compile_definitions(enclave PUBLIC OE_API_VERSION=2) # Need for the generated file libdogecoin_t.h -target_include_directories(enclave PRIVATE ${CMAKE_CURRENT_BINARY_DIR} /usr/local/include/dogecoin ${CMAKE_SOURCE_DIR}/../../depends/x86_64-pc-linux-gnu/include/dogecoin) +target_include_directories(enclave PRIVATE /opt/openenclave/include ${CMAKE_CURRENT_BINARY_DIR} /usr/local/include/dogecoin ${CMAKE_SOURCE_DIR}/../../src/libevent/build/include ${CMAKE_SOURCE_DIR}/../../depends/x86_64-pc-linux-gnu/include/ ${CMAKE_SOURCE_DIR}/../../depends/x86_64-pc-linux-gnu/include/dogecoin) # Add search paths to find the enclave libraries. -target_link_directories(enclave PRIVATE /lib/x86_64-linux-gnu/ /lib64/ /usr/local/lib /usr/lib/x86_64-linux-gnu ${CMAKE_SOURCE_DIR}/../../depends/x86_64-pc-linux-gnu/lib) +target_link_directories(enclave PRIVATE ${CMAKE_SOURCE_DIR}../../ ${CMAKE_SOURCE_DIR}/../../src/libevent/build/lib ${CMAKE_SOURCE_DIR}/../../depends/x86_64-pc-linux-gnu/lib /usr/lib/x86_64-linux-gnu) if (LVI_MITIGATION MATCHES ControlFlow) # Helper to enable compiler options for LVI mitigation. @@ -30,12 +30,13 @@ if (LVI_MITIGATION MATCHES ControlFlow) # Link against LVI-mitigated libraries. target_link_libraries( enclave openenclave::oeenclave-lvi-cfg - openenclave::oecrypto${OE_CRYPTO_LIB}-lvi-cfg openenclave::oelibc-lvi-cfg - "libdogecoin.a" "libevent_core.a" "libunistring.a") + openenclave::oecrypto${OE_CRYPTO_LIB}-lvi-cfg + "libdogecoin.a" "libevent_core.a" "libunistring.a" + openenclave::oelibc-lvi-cfg) else () target_link_libraries( enclave openenclave::oeenclave openenclave::oecrypto${OE_CRYPTO_LIB} - openenclave::oelibc - "libdogecoin.a" "libevent_core.a" "libunistring.a") + "libdogecoin.a" "libevent_core.a" "libunistring.a" + openenclave::oelibc) endif () diff --git a/src/openenclave/enclave/Makefile b/src/openenclave/enclave/Makefile index 101ca0877..b512bbc8c 100644 --- a/src/openenclave/enclave/Makefile +++ b/src/openenclave/enclave/Makefile @@ -42,5 +42,7 @@ clean: rm -f enc.o enc enclave.signed private.pem public.pem libdogecoin_t.o libdogecoin_t.h libdogecoin_t.c libdogecoin_args.h keys: - openssl genrsa -out private.pem -3 3072 - openssl rsa -in private.pem -pubout -out public.pem + if [ ! -f private.pem ]; then \ + openssl genrsa -out private.pem -3 3072; \ + openssl rsa -in private.pem -pubout -out public.pem; \ + fi diff --git a/src/openenclave/enclave/enc.c b/src/openenclave/enclave/enc.c index f9cf67de4..a007479ed 100644 --- a/src/openenclave/enclave/enc.c +++ b/src/openenclave/enclave/enc.c @@ -1,3 +1,26 @@ +/** + * Copyright (c) 2024 edtubbs + * Copyright (c) 2024 The Dogecoin Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + // Copyright (c) Open Enclave SDK contributors. // Licensed under the MIT License. @@ -11,9 +34,14 @@ // Include the libdogecoin headher #include "libdogecoin.h" +#include + // Define the enclave's random number generator void set_rng(oe_result_t (*ptr)(void *, size_t)); +#define AUTH_TOKEN_LEN 6 // 6-digit TOTP +uint32_t get_totp(const char* shared_secret, uint64_t timestamp); + // This is the function that the host calls. It prints // a message in the enclave before calling back out to // the host to print a message from there too. @@ -67,25 +95,28 @@ void enclave_libdogecoin() printf("\nFinal signed transaction hex: %s\n\n", get_raw_transaction(idx)); dogecoin_ecc_stop(); - } //#define PRIVKEYWIFLEN 51 //WIF length for uncompressed keys is 51 and should start with Q. This can be 52 also for compressed keys. 53 internally to lib (+stringterm) -#define PRIVKEYWIFLEN 53 //Function takes 53 but needs to be fixed to take 51. +//#define PRIVKEYWIFLEN 53 //Function takes 53 but needs to be fixed to take 51. -//#define MASTERKEYLEN 111 //should be chaincode + privkey; starts with dgpv51eADS3spNJh8 or dgpv51eADS3spNJh9 (112 internally including stringterm? often 128. check this.) -#define MASTERKEYLEN 128 // Function expects 128 but needs to be fixed to take 111. +//#define HDKEYLEN 111 //should be chaincode + privkey; starts with dgpv51eADS3spNJh8 or dgpv51eADS3spNJh9 (112 internally including stringterm? often 128. check this.) +//#define HDKEYLEN 128 // Function expects 128 but needs to be fixed to take 111. -//#define PUBKEYLEN 34 //our mainnet addresses are 34 chars if p2pkh and start with D. Internally this is cited as 35 for strings that represent it because +stringterm. -#define PUBKEYLEN 35 // Function expects 35, but needs to be fixed to take 34. +//#define P2PKHLEN 34 //our mainnet addresses are 34 chars if p2pkh and start with D. Internally this is cited as 35 for strings that represent it because +stringterm. +//#define P2PKHLEN 35 // Function expects 35, but needs to be fixed to take 34. // Lets try running a function for cli // start with some calls the cli apps make // then we'll try and integrate host and cli -void enclave_libdogecoin_generate_mnemonic() +void enclave_libdogecoin_run_example() { MNEMONIC mnemonic = { 0 }; dogecoin_bool result = false; + + // Set the Open Enclave random number generator in libdogecoin + set_rng (&oe_random); + dogecoin_ecc_start(); generateRandomEnglishMnemonic("256", mnemonic); @@ -96,10 +127,10 @@ void enclave_libdogecoin_generate_mnemonic() // create variables char wif_privkey[PRIVKEYWIFLEN]; - char p2pkh_pubkey[PUBKEYLEN]; - char wif_master_privkey[MASTERKEYLEN]; - char p2pkh_master_pubkey[PUBKEYLEN]; - char p2pkh_child_pubkey[PUBKEYLEN]; + char p2pkh_pubkey[P2PKHLEN]; + char hd_master_privkey[HDKEYLEN]; + char p2pkh_master_pubkey[P2PKHLEN]; + char p2pkh_child_pubkey[P2PKHLEN]; // keypair generation if (generatePrivPubKeypair(wif_privkey, p2pkh_pubkey, 0)) { @@ -110,8 +141,8 @@ void enclave_libdogecoin_generate_mnemonic() oe_result_str(OE_FAILURE); } - if (generateHDMasterPubKeypair(wif_master_privkey, p2pkh_master_pubkey, 0)) { - printf("Mainnet master keypair 2:\n===============================\nPrivate: %s\nPublic: %s\n\n", wif_master_privkey, p2pkh_master_pubkey); + if (generateHDMasterPubKeypair(hd_master_privkey, p2pkh_master_pubkey, 0)) { + printf("Mainnet master keypair 2:\n===============================\nPrivate: %s\nPublic: %s\n\n", hd_master_privkey, p2pkh_master_pubkey); } else { printf("Error occurred 2.\n"); @@ -119,8 +150,8 @@ void enclave_libdogecoin_generate_mnemonic() } - if (generateDerivedHDPubkey((const char*)wif_master_privkey, (char*)p2pkh_child_pubkey)) { - printf("Mainnet master derived keypair 3:\n===============================\nPrivate: %s\nPublic: %s\n\n", wif_master_privkey, p2pkh_child_pubkey); + if (generateDerivedHDPubkey((const char*)hd_master_privkey, (char*)p2pkh_child_pubkey)) { + printf("Mainnet master derived keypair 3:\n===============================\nPrivate: %s\nPublic: %s\n\n", hd_master_privkey, p2pkh_child_pubkey); } else { printf("Error occurred 3.\n"); @@ -137,19 +168,19 @@ void enclave_libdogecoin_generate_mnemonic() oe_result_str(OE_FAILURE); } - if (verifyHDMasterPubKeypair(wif_master_privkey, p2pkh_master_pubkey, 0)) { - printf("Keypair (%s, %s) is valid for mainnet 5.\n\n", wif_master_privkey, p2pkh_master_pubkey); + if (verifyHDMasterPubKeypair(hd_master_privkey, p2pkh_master_pubkey, 0)) { + printf("Keypair (%s, %s) is valid for mainnet 5.\n\n", hd_master_privkey, p2pkh_master_pubkey); } else { - printf("Keypair (%s, %s) is not valid for mainnet 5.\n", wif_master_privkey, p2pkh_master_pubkey); + printf("Keypair (%s, %s) is not valid for mainnet 5.\n", hd_master_privkey, p2pkh_master_pubkey); oe_result_str(OE_FAILURE); } - if (verifyHDMasterPubKeypair(wif_master_privkey, p2pkh_child_pubkey, 0)) { - printf("Keypair (%s, %s) is valid for mainnet 6.\n\n", wif_master_privkey, p2pkh_child_pubkey); + if (verifyHDMasterPubKeypair(hd_master_privkey, p2pkh_child_pubkey, 0)) { + printf("Keypair (%s, %s) is valid for mainnet 6.\n\n", hd_master_privkey, p2pkh_child_pubkey); } else { - printf("Keypair (%s, %s) is not valid for mainnet 6.\n", wif_master_privkey, p2pkh_child_pubkey); + printf("Keypair (%s, %s) is not valid for mainnet 6.\n", hd_master_privkey, p2pkh_child_pubkey); oe_result_str(OE_FAILURE); } printf("\n"); @@ -346,3 +377,711 @@ void enclave_libdogecoin_generate_mnemonic() dogecoin_ecc_stop(); } + +// Secure Key Manager +#include "libdogecoin_t.h" // Include the trusted libdogecoin header +#include // Include the Open Enclave seal API +#include +#include + +// TOTP Time step in seconds +#define TOTP_TIME_STEP 30 + +// TOTP Shared secret size in bytes +#define TOTP_SECRET_SIZE 20 + +// Wrap sealing function for simulation mode +oe_result_t oe_seal_wrap( + const oe_uuid_t* plugin_id, + const oe_seal_setting_t* settings, + size_t settings_count, + const uint8_t* plaintext, + size_t plaintext_size, + const uint8_t* additional_data, + size_t additional_data_size, + uint8_t** blob, + size_t* blob_size) +{ + // Try the real sealing operation first + oe_result_t result = oe_seal( + plugin_id, + settings, + settings_count, + plaintext, + plaintext_size, + additional_data, + additional_data_size, + blob, + blob_size); + + if (result == OE_OK) + { + return result; + } + else + { + fprintf(stdout, "Simulating sealing operation\n"); + // Check if only the size is requested + if (blob == NULL) + { + *blob_size = plaintext_size; + return OE_OK; + } + + // Normal sealing operation (mocked behavior) + if (blob_size == NULL) + return OE_INVALID_PARAMETER; + + *blob_size = plaintext_size; + *blob = (uint8_t*)malloc(*blob_size); + if (*blob == NULL) + return OE_OUT_OF_MEMORY; + + memcpy(*blob, plaintext, plaintext_size); + return OE_OK; + } +} + +oe_result_t oe_unseal_wrap( + const uint8_t* sealed_blob, + size_t sealed_blob_size, + const uint8_t* additional_data, + size_t additional_data_size, + uint8_t** plaintext, + size_t* plaintext_size) +{ + // Check for invalid parameters + if (sealed_blob == NULL || plaintext == NULL || plaintext_size == NULL) + { + return OE_INVALID_PARAMETER; + } + + // Try to the real unsealing operation first + oe_result_t result = oe_unseal( + sealed_blob, + sealed_blob_size, + additional_data, + additional_data_size, + plaintext, + plaintext_size); + + if (result == OE_OK) + { + return result; + } + else + { + fprintf(stdout, "Simulating unsealing operation\n"); + // Allocate memory for the plaintext + *plaintext = (uint8_t*)malloc(sealed_blob_size); + if (*plaintext == NULL) + { + return OE_OUT_OF_MEMORY; + } + + // Copy the sealed data to the plaintext buffer + memcpy(*plaintext, sealed_blob, sealed_blob_size); + *plaintext_size = sealed_blob_size; + + return OE_OK; + } +} + +uint32_t get_totp(const char* shared_secret, uint64_t timestamp) { + uint8_t hmac[SHA1_DIGEST_LENGTH]; + uint8_t time_bytes[8]; + uint32_t totp; + + // Convert the timestamp to a byte array + for (int i = 7; i >= 0; i--) { + time_bytes[i] = timestamp & 0xFF; + timestamp >>= 8; + } + + // Compute the HMAC-SHA1 of the time_bytes using the shared_secret + hmac_sha1((const uint8_t*)utils_hex_to_uint8(shared_secret), TOTP_SECRET_SIZE, time_bytes, sizeof(time_bytes), hmac); + + // Dynamic truncation to get a 4-byte string (31 bits) + int offset = hmac[SHA1_DIGEST_LENGTH - 1] & 0x0F; // Corrected the index to 19 + totp = (hmac[offset] & 0x7F) << 24 + | (hmac[offset + 1] & 0xFF) << 16 + | (hmac[offset + 2] & 0xFF) << 8 + | (hmac[offset + 3] & 0xFF); + + // Return TOTP modulo 10^6 + return totp % 1000000; +} + +void enclave_libdogecoin_attest(uint8_t* report, size_t len) +{ + oe_result_t result; + uint8_t* report_buffer = NULL; + size_t report_size = 0; + + // Generate the report + result = oe_get_report( + OE_REPORT_FLAGS_REMOTE_ATTESTATION, + NULL, 0, + NULL, 0, + &report_buffer, &report_size); + + if (result != OE_OK) + { + printf("Failed to get report: %s\n", oe_result_str(result)); + return; + } + + if (report_size > len) + { + printf("Report buffer size is too small\n"); + dogecoin_free(report_buffer); + return; + } + + // Copy the report to the output buffer + memcpy(report, report_buffer, report_size); + + // Free the report buffer + dogecoin_free(report_buffer); +} + +void enclave_libdogecoin_generate_encrypted_seed(uint8_t** encrypted_blob, size_t* len) { + oe_result_t result; + SEED seed; + + // Generate a new seed + if (!dogecoin_random_bytes(seed, sizeof(seed), 1)) { + fprintf(stderr, "Failed to generate random bytes\n"); + // Handle error + *encrypted_blob = NULL; + *len = 0; + } + + // Initialize the seal key info + const oe_seal_setting_t settings[] = {OE_SEAL_SET_POLICY(OE_SEAL_POLICY_UNIQUE)}; + result = oe_seal_wrap( + NULL, + settings, + sizeof(settings) / sizeof(*settings), + (const uint8_t*) seed, // Data to seal + sizeof(seed), // Size of data + NULL, // No additional data + 0, // No additional data size + encrypted_blob, // Output: Pointer to the encrypted blob + len); // Output: Size of the encrypted blob + + if (result != OE_OK) { + fprintf(stderr, "Sealing failed with %d\n", result); + // Handle error + *encrypted_blob = NULL; + *len = 0; + } +} + +// ECALL to generate and encrypt a new master keypair +void enclave_libdogecoin_generate_master_key(uint8_t** encrypted_blob, size_t* len) { + oe_result_t result; + + char hd_master_privkey[HDKEYLEN]; + char p2pkh_master_pubkey[P2PKHLEN]; + + // Set the Open Enclave random number generator in libdogecoin + set_rng (&oe_random); + + dogecoin_ecc_start(); + + if (generateHDMasterPubKeypair(hd_master_privkey, p2pkh_master_pubkey, 0)) { + printf("Mainnet master keypair \n===============================\nPrivate: %s\nPublic: %s\n\n", hd_master_privkey, p2pkh_master_pubkey); + } + else { + printf("Error occurred.\n"); + // Handle error + *encrypted_blob = NULL; + *len = 0; + } + + dogecoin_ecc_stop(); + + // Initialize the seal key info + const oe_seal_setting_t settings[] = {OE_SEAL_SET_POLICY(OE_SEAL_POLICY_UNIQUE)}; + result = oe_seal_wrap( + NULL, + settings, + sizeof(settings) / sizeof(*settings), + (const uint8_t*) hd_master_privkey, // Data to seal + sizeof(hd_master_privkey), // Size of data + NULL, // No additional data + 0, // No additional data size + encrypted_blob, // Output: Pointer to the encrypted blob + len); // Output: Size of the encrypted blob + + if (result != OE_OK) { + fprintf(stderr, "Sealing failed with %d\n", result); + // Handle error + *encrypted_blob = NULL; + *len = 0; + } +} + +// ECALL to generate and encrypt a new mnemonic +void enclave_libdogecoin_generate_mnemonic(uint8_t** encrypted_blob, size_t* len, char* mnemonic, const char* shared_secret, const MNEMONIC mnemonic_input, const ENTROPY_SIZE entropy_size) { + // Validate the input parameters + if (encrypted_blob == NULL || len == NULL || mnemonic == NULL || shared_secret == NULL) { + fprintf(stderr, "Invalid input parameters\n"); + return; + } + + oe_result_t result; + + // Set the Open Enclave random number generator in libdogecoin + set_rng(&oe_random); + + dogecoin_ecc_start(); + + if (mnemonic_input != NULL) { + // Use the provided mnemonic + strncpy(mnemonic, mnemonic_input, strlen(mnemonic_input)); + mnemonic[strlen(mnemonic_input)] = '\0'; + } else { + if (entropy_size == NULL) { + // Generate a new mnemonic with 256 bits of entropy + int mnemonicResult = generateRandomEnglishMnemonic("256", mnemonic); + if (mnemonicResult != 0) { + fprintf(stderr, "Failed to generate mnemonic\n"); + // Handle error + *encrypted_blob = NULL; + *len = 0; + return; + } + } else { + // Generate a new mnemonic with the specified entropy size + int mnemonicResult = generateRandomEnglishMnemonic(entropy_size, mnemonic); + if (mnemonicResult != 0) { + fprintf(stderr, "Failed to generate mnemonic\n"); + // Handle error + *encrypted_blob = NULL; + *len = 0; + return; + } + } + } + + // Calculate the total length + size_t total_length = strlen(mnemonic) + strlen(shared_secret) + 2; // +1 for the comma separator, +1 for the null terminator + + // Allocate the array + uint8_t* mnemonic_and_shared_secret = (uint8_t*)malloc(total_length); + + // Check if the allocation was successful + if (mnemonic_and_shared_secret == NULL) { + fprintf(stderr, "Memory allocation failed\n"); + return; + } + + // Concatenate the mnemonic and shared_secret with a comma separator + snprintf((char*)mnemonic_and_shared_secret, total_length, "%s,%s", mnemonic, shared_secret); + + dogecoin_ecc_stop(); + + // Initialize the seal key info + const oe_seal_setting_t settings[1] = {OE_SEAL_SET_POLICY(OE_SEAL_POLICY_UNIQUE)}; + + // Determine the size of the encrypted blob + size_t encrypted_blob_size = 0; + result = oe_seal_wrap( + NULL, // No specific plugin_id + settings, // Seal settings + sizeof(settings) / sizeof(settings[0]), // Number of settings + mnemonic_and_shared_secret, // Data to seal + total_length, // Size of data + NULL, // No additional data + 0, // No additional data size + NULL, // Output: Pointer to the encrypted blob (NULL to get the size) + &encrypted_blob_size); // Output: Size of the encrypted blob + + if (result != OE_OK) { + fprintf(stderr, "Failed to get encrypted blob size: %d\n", result); + free(mnemonic_and_shared_secret); + return; + } + + // Allocate memory for the encrypted blob + *encrypted_blob = (uint8_t*)malloc(encrypted_blob_size); + if (*encrypted_blob == NULL) { + fprintf(stderr, "Memory allocation failed for encrypted blob\n"); + free(mnemonic_and_shared_secret); + return; + } + + // Perform the actual sealing + result = oe_seal_wrap( + NULL, // No specific plugin_id + settings, // Seal settings + sizeof(settings) / sizeof(settings[0]), // Number of settings + mnemonic_and_shared_secret, // Data to seal + total_length, // Size of data + NULL, // No additional data + 0, // No additional data size + encrypted_blob, // Output: Pointer to the encrypted blob + &encrypted_blob_size); // Output: Size of the encrypted blob + + // Print out the encrypted blob and length + if (result == OE_OK) { + *len = encrypted_blob_size; + } else { + fprintf(stderr, "Sealing failed with %d\n", result); + // Handle error + free(*encrypted_blob); + *encrypted_blob = NULL; + *len = 0; + } + + // Free the allocated memory + free(mnemonic_and_shared_secret); +} + +void enclave_libdogecoin_generate_extended_public_key(uint8_t* encrypted_blob, size_t len, char* pubkey, uint32_t* account, const char* change_level, const uint32_t auth_token) +{ + // Validate the input parameters + if (encrypted_blob == NULL || pubkey == NULL) { + fprintf(stderr, "Invalid input parameters\n"); + return; + } + + uint8_t* persistent_data = NULL; + size_t persistent_data_size = 0; + char* mnemonic = NULL; + char* shared_secret = NULL; + + // Unseal the data + oe_result_t result = oe_unseal_wrap( + encrypted_blob, + len, + NULL, + 0, + &persistent_data, + &persistent_data_size); + + if (result != OE_OK) { + printf("Failed to unseal encrypted blob: %s\n", oe_result_str(result)); + return; + } + + // Split the persistent data into mnemonic and shared secret + mnemonic = strtok(persistent_data, ","); + shared_secret = strtok(NULL, ","); + + // Verify TOTP using the shared secret + struct timeval tv; + if (gettimeofday(&tv, NULL) != 0) { + fprintf(stderr, "Failed to get time\n"); + return; + } + uint32_t current_time = tv.tv_sec; + uint32_t totp = get_totp((const char*) shared_secret, current_time / TOTP_TIME_STEP); + + char totp_str[AUTH_TOKEN_LEN + 1]; // +1 for the null terminator + snprintf(totp_str, sizeof(totp_str), "%06u", totp); + + char auth_token_str[AUTH_TOKEN_LEN + 1]; // +1 for the null terminator + snprintf(auth_token_str, sizeof(auth_token_str), "%06u", auth_token); + + if (strcmp(totp_str, auth_token_str) != 0) { + printf("TOTP verification failed\n"); + return; + } + + SEED seed; + dogecoin_seed_from_mnemonic((const char*)mnemonic, NULL, seed); + + dogecoin_ecc_start(); + + char master_key[HDKEYLEN]; + getHDRootKeyFromSeed(seed, sizeof(seed), false, master_key); + + char derived_pubkey[HDKEYLEN]; + char keypath[BIP44_KEY_PATH_MAX_LENGTH + 1]; + deriveBIP44ExtendedPublicKey(master_key, account, change_level, NULL, NULL, derived_pubkey, keypath); + + strncpy(pubkey, derived_pubkey, strlen(derived_pubkey)); + pubkey[strlen(derived_pubkey)] = '\0'; + + dogecoin_ecc_stop(); + + dogecoin_free(persistent_data); +} + +void enclave_libdogecoin_generate_address(uint8_t* encrypted_blob, size_t len, char* addresses, uint32_t account, uint32_t address_index, const char* change_level, uint32_t num_addresses, const uint32_t auth_token) +{ + // Validate the input parameters + if (encrypted_blob == NULL || addresses == NULL) { + fprintf(stderr, "Invalid input parameters\n"); + return; + } + + uint8_t* persistent_data = NULL; + size_t persistent_data_size = 0; + char* mnemonic = NULL; + char* shared_secret = NULL; + + // Unseal the data + oe_result_t result = oe_unseal_wrap( + encrypted_blob, + len, + NULL, + 0, + &persistent_data, + &persistent_data_size); + + if (result != OE_OK) { + printf("Failed to unseal encrypted blob: %s\n", oe_result_str(result)); + return; + } + + // Split the persistent data into mnemonic and shared secret + mnemonic = strtok(persistent_data, ","); + shared_secret = strtok(NULL, ","); + + // Verify TOTP using the shared secret + struct timeval tv; + if (gettimeofday(&tv, NULL) != 0) { + fprintf(stderr, "Failed to get time\n"); + return; + } + uint32_t current_time = tv.tv_sec; + uint32_t totp = get_totp((const char*) shared_secret, current_time / TOTP_TIME_STEP); + + char totp_str[AUTH_TOKEN_LEN + 1]; // +1 for the null terminator + snprintf(totp_str, sizeof(totp_str), "%06u", totp); + + char auth_token_str[AUTH_TOKEN_LEN + 1]; // +1 for the null terminator + snprintf(auth_token_str, sizeof(auth_token_str), "%06u", auth_token); + + if (strcmp(totp_str, auth_token_str) != 0) { + printf("TOTP verification failed\n"); + return; + } + + SEED seed; + dogecoin_seed_from_mnemonic((const char*)mnemonic, NULL, seed); + + dogecoin_ecc_start(); + + char master_key[HDKEYLEN]; + getHDRootKeyFromSeed(seed, sizeof(seed), false, master_key); + + char address_p2pkh[P2PKHLEN * num_addresses]; + memset(address_p2pkh, 0, sizeof(address_p2pkh)); + size_t offset = 0; + + for (uint32_t i = 0; i < num_addresses; i++) + { + char derived_pubkey[HDKEYLEN]; + char keypath[BIP44_KEY_PATH_MAX_LENGTH + 1]; + deriveBIP44ExtendedPublicKey(master_key, &account, NULL, NULL, NULL, derived_pubkey, keypath); + getDerivedHDAddressFromAcctPubKey(derived_pubkey, address_index, change_level, address_p2pkh + offset, false); + offset += strlen(address_p2pkh + offset); + if (i < num_addresses - 1) + { + address_p2pkh[offset++] = '\n'; + } + address_index++; + } + + strncpy(addresses, address_p2pkh, strlen(address_p2pkh)); + addresses[strlen(address_p2pkh)] = '\0'; + + dogecoin_ecc_stop(); + + dogecoin_free(persistent_data); +} + +void enclave_libdogecoin_sign_message(uint8_t* encrypted_blob, size_t len, const char* message, char* signature, uint32_t account, uint32_t address_index, const char* change_level, const uint32_t auth_token) +{ + // Validate the input parameters + if (encrypted_blob == NULL || message == NULL || signature == NULL) { + fprintf(stderr, "Invalid input parameters\n"); + return; + } + + uint8_t* persistent_data = NULL; + size_t persistent_data_size = 0; + char* mnemonic = NULL; + char* shared_secret = NULL; + + // Unseal the data + oe_result_t result = oe_unseal_wrap( + encrypted_blob, + len, + NULL, + 0, + &persistent_data, + &persistent_data_size); + + if (result != OE_OK) { + printf("Failed to unseal encrypted blob: %s\n", oe_result_str(result)); + return; + } + + // Split the persistent data into mnemonic and shared secret + mnemonic = strtok(persistent_data, ","); + shared_secret = strtok(NULL, ","); + + // Verify TOTP using the shared secret + struct timeval tv; + if (gettimeofday(&tv, NULL) != 0) { + fprintf(stderr, "Failed to get time\n"); + return; + } + uint32_t current_time = tv.tv_sec; + uint32_t totp = get_totp((const char*) shared_secret, current_time / TOTP_TIME_STEP); + + char totp_str[AUTH_TOKEN_LEN + 1]; // +1 for the null terminator + snprintf(totp_str, sizeof(totp_str), "%06u", totp); + + char auth_token_str[AUTH_TOKEN_LEN + 1]; // +1 for the null terminator + snprintf(auth_token_str, sizeof(auth_token_str), "%06u", auth_token); + + if (strcmp(totp_str, auth_token_str) != 0) { + printf("TOTP verification failed\n"); + return; + } + + SEED seed; + dogecoin_seed_from_mnemonic((const char*)mnemonic, NULL, seed); + + // Set the Open Enclave random number generator in libdogecoin + set_rng (&oe_random); + + dogecoin_ecc_start(); + + char master_key[HDKEYLEN]; + getHDRootKeyFromSeed(seed, sizeof(seed), false, master_key); + + char derived_path[BIP44_KEY_PATH_MAX_LENGTH + 1]; + char outaddress[P2PKHLEN]; + char privkeywif[PRIVKEYWIFLEN]; + size_t wiflen = PRIVKEYWIFLEN; + const dogecoin_chainparams* chain = chain_from_b58_prefix(master_key); + sprintf(derived_path, SLIP44_KEY_PATH "%s'/%d'/%s/%d", BIP44_COIN_TYPE, account, change_level, address_index); + + dogecoin_hdnode* hdnode = getHDNodeAndExtKeyByPath(master_key, derived_path, outaddress, true); + dogecoin_privkey_encode_wif((const dogecoin_key*)hdnode->private_key, chain, privkeywif, &wiflen); + + char* sig = sign_message(privkeywif, (char*) message); + strncpy(signature, sig, strlen(sig)); + signature[strlen(sig)] = '\0'; + + dogecoin_ecc_stop(); + + dogecoin_free(sig); + dogecoin_free(persistent_data); +} + +void enclave_libdogecoin_sign_transaction(uint8_t* encrypted_blob, size_t len, const char* raw_tx, char* signed_tx, uint32_t account, uint32_t address_index, const char* change_level, const uint32_t auth_token) +{ + // Validate the input parameters + if (encrypted_blob == NULL || raw_tx == NULL || signed_tx == NULL) { + fprintf(stderr, "Invalid input parameters\n"); + return; + } + + uint8_t* persistent_data = NULL; + size_t persistent_data_size = 0; + char* mnemonic = NULL; + char* shared_secret = NULL; + + // Unseal the data + oe_result_t result = oe_unseal_wrap( + encrypted_blob, + len, + NULL, + 0, + &persistent_data, + &persistent_data_size); + + if (result != OE_OK) { + printf("Failed to unseal encrypted blob: %s\n", oe_result_str(result)); + return; + } + + // Split the persistent data into mnemonic and shared secret + mnemonic = strtok(persistent_data, ","); + shared_secret = strtok(NULL, ","); + + // Verify TOTP using the shared secret + struct timeval tv; + if (gettimeofday(&tv, NULL) != 0) { + fprintf(stderr, "Failed to get time\n"); + return; + } + uint32_t current_time = tv.tv_sec; + uint32_t totp = get_totp((const char*) shared_secret, current_time / TOTP_TIME_STEP); + + char totp_str[AUTH_TOKEN_LEN + 1]; // +1 for the null terminator + snprintf(totp_str, sizeof(totp_str), "%06u", totp); + + char auth_token_str[AUTH_TOKEN_LEN + 1]; // +1 for the null terminator + snprintf(auth_token_str, sizeof(auth_token_str), "%06u", auth_token); + + if (strcmp(totp_str, auth_token_str) != 0) { + printf("TOTP verification failed\n"); + return; + } + + SEED seed; + dogecoin_seed_from_mnemonic((const char*)mnemonic, NULL, seed); + + // Set the Open Enclave random number generator in libdogecoin + set_rng (&oe_random); + + dogecoin_ecc_start(); + + char master_key[HDKEYLEN]; + getHDRootKeyFromSeed(seed, sizeof(seed), false, master_key); + + char derived_path[BIP44_KEY_PATH_MAX_LENGTH + 1]; + char outaddress[P2PKHLEN]; + char privkeywif[PRIVKEYWIFLEN]; + size_t wiflen = PRIVKEYWIFLEN; + const dogecoin_chainparams* chain = chain_from_b58_prefix(master_key); + sprintf(derived_path, SLIP44_KEY_PATH "%s'/%d'/%s/%d", BIP44_COIN_TYPE, account, change_level, address_index); + dogecoin_hdnode* hdnode = getHDNodeAndExtKeyByPath(master_key, derived_path, outaddress, true); + dogecoin_privkey_encode_wif((const dogecoin_key*)hdnode->private_key, chain, privkeywif, &wiflen); + + // Store the raw transaction + int idx = store_raw_transaction((char*)raw_tx); + if (idx < 0) { + printf("Failed to store raw transaction\n"); + dogecoin_ecc_stop(); + dogecoin_free(persistent_data); + return; + } + + // Derive the script pub key + char* myscriptpubkey = dogecoin_private_key_wif_to_pubkey_hash(privkeywif); + if (!sign_transaction(idx, myscriptpubkey, privkeywif)) { + printf("Failed to sign transaction\n"); + dogecoin_free(myscriptpubkey); + dogecoin_ecc_stop(); + dogecoin_free(persistent_data); + return; + } + + // Get the signed transaction + char* signed_tx_str = get_raw_transaction(idx); + if (!signed_tx_str) { + printf("Failed to get signed transaction\n"); + dogecoin_free(myscriptpubkey); + dogecoin_ecc_stop(); + dogecoin_free(persistent_data); + return; + } + + strncpy(signed_tx, signed_tx_str, strlen(signed_tx_str)); + signed_tx[strlen(signed_tx_str)] = '\0'; + + dogecoin_free(myscriptpubkey); + dogecoin_ecc_stop(); + dogecoin_free(persistent_data); +} diff --git a/src/openenclave/host/CMakeLists.txt b/src/openenclave/host/CMakeLists.txt index 8278ffb0a..8c18e22cf 100644 --- a/src/openenclave/host/CMakeLists.txt +++ b/src/openenclave/host/CMakeLists.txt @@ -18,6 +18,9 @@ endif () target_include_directories( host PRIVATE # Needed for the generated file libdogecoin_u.h - ${CMAKE_CURRENT_BINARY_DIR}) + ${CMAKE_CURRENT_BINARY_DIR} /usr/local/include /usr/local/include/dogecoin /usr/include/ykpers-1 ${CMAKE_SOURCE_DIR}/../../depends/x86_64-pc-linux-gnu/include/ ${CMAKE_SOURCE_DIR}/../../depends/x86_64-pc-linux-gnu/include/dogecoin/ ${CMAKE_SOURCE_DIR}/../../depends/x86_64-pc-linux-gnu/include/ykpers-1 ${CMAKE_SOURCE_DIR}/../../src/libevent/build/include) -target_link_libraries(host openenclave::oehost) +# Add search paths to find the enclave libraries. +target_link_directories(host PRIVATE ${CMAKE_SOURCE_DIR}../../ ${CMAKE_SOURCE_DIR}/../../src/libevent/build/lib ${CMAKE_SOURCE_DIR}/../../depends/x86_64-pc-linux-gnu/lib) + +target_link_libraries(host openenclave::oehost "libdogecoin.a" "libevent.a" "libunistring.a" "libykpers-1.so" "libyubikey.so" "libusb-1.0.so") diff --git a/src/openenclave/host/host.c b/src/openenclave/host/host.c index d5bcb8f8a..330bea72b 100644 --- a/src/openenclave/host/host.c +++ b/src/openenclave/host/host.c @@ -1,15 +1,65 @@ -// Copyright (c) Open Enclave SDK contributors. -// Licensed under the MIT License. +/** + * Copyright (c) 2024 edtubbs + * Copyright (c) 2024 The Dogecoin Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include // Include the untrusted libdogecoin header that is generated -// during the build. This file is generated by calling the -// sdk tool oeedger8r against the libdogecoin.edl file. #include "libdogecoin_u.h" -bool check_simulate_opt(int* argc, const char* argv[]) +// Include the libdogecoin header +#include "libdogecoin.h" + +#define TIME_STEP 30 +#define NUM_ADDRESSES 1 +#define TOTP_SECRET_HEX_SIZE 41 // hex, 40 characters + null + +// Base names for the items +#define BASE_NAME_MNEMONIC "dogecoin_mnemonic_" +#define BASE_NAME_SEED "dogecoin_seed_" +#define BASE_NAME_MASTER "dogecoin_master_" + +// Suffices for the items +#define SUFFIX_TEE "_tee" + +// Directory path for storage +#define CRYPTO_DIR_PATH "./.store/" + +// Full file path and names for Unix-like systems (with TEE encryption method suffix) +#define MNEMONIC_TEE_FILE_NAME CRYPTO_DIR_PATH BASE_NAME_MNEMONIC SUFFIX_TEE +#define SEED_TEE_FILE_NAME CRYPTO_DIR_PATH BASE_NAME_SEED SUFFIX_TEE +#define MASTER_TEE_FILE_NAME CRYPTO_DIR_PATH BASE_NAME_MASTER SUFFIX_TEE + +bool check_simulate_opt(int* argc, char* argv[]) { for (int i = 0; i < *argc; i++) { @@ -24,79 +74,558 @@ bool check_simulate_opt(int* argc, const char* argv[]) return false; } -// This is the function that the enclave will call back into to -// print a message. +// This is the function that the enclave will call back into to print a message. void host_libdogecoin() { fprintf(stdout, "Enclave called into host to print: Libdogecoin!\n"); } -int main(int argc, const char* argv[]) +static struct option long_options[] = +{ + {"command", required_argument, NULL, 'c'}, + {"account_int", required_argument, NULL, 'o'}, + {"input_index", required_argument, NULL, 'i'}, + {"change_level", required_argument, NULL, 'l'}, + {"message", required_argument, NULL, 'm'}, + {"transaction", required_argument, NULL, 't'}, + {"mnemonic_input", required_argument, NULL, 'n'}, + {"shared_secret", required_argument, NULL, 's'}, + {"entropy_size", required_argument, NULL, 'e'}, + {NULL, 0, NULL, 0} +}; + +static void print_usage() +{ + printf("Usage: host -c (-o|-account_int ) (-i|-input_index ) (-l|-change_level ) \ +(-m|-message ) (-t|-transaction ) (-n|-mnemonic_input ) (-s|-shared_secret ) \ +(-e|-entropy_size )\n"); + printf("Available commands:\n"); + printf(" generate_mnemonic (optional -n -s -e )\n"); + printf(" generate_extended_public_key (requires -o -i -l \n"); + printf(" generate_address (requires -o -i -l )\n"); + printf(" sign_message (requires -o -i -l -m )\n"); + printf(" sign_transaction (requires -o -i -l -t )\n"); +} + +void write_encrypted_file(const char* filename, const uint8_t* data, size_t data_size) +{ + FILE* file = fopen(filename, "wb"); + if (file == NULL) + { + fprintf(stderr, "Failed to open file %s for writing\n", filename); + return; + } + size_t written = fwrite(data, 1, data_size, file); + if (written != data_size) + { + fprintf(stderr, "Failed to write data to file %s\n", filename); + } + fclose(file); +} + +void read_encrypted_file(const char* filename, uint8_t* data, size_t* data_size) +{ + FILE* file = fopen(filename, "rb"); + if (file == NULL) + { + fprintf(stderr, "Failed to open file %s for reading\n", filename); + *data_size = 0; + return; + } + *data_size = fread(data, 1, *data_size, file); + if (*data_size == 0) + { + fprintf(stderr, "Failed to read data from file %s\n", filename); + } + fclose(file); +} + +void set_totp_secret(YK_KEY *yk, const char *secret) { + YKP_CONFIG *cfg = ykp_alloc(); + YK_STATUS *st = ykds_alloc(); + + if (!cfg || !st) { + fprintf(stderr, "Failed to allocate YubiKey structures\n"); + ykp_free_config(cfg); + ykds_free(st); + return; + } + + if (!yk_get_status(yk, st)) { + fprintf(stderr, "Failed to get YubiKey status: %s\n", yk_strerror(yk_errno)); + ykp_free_config(cfg); + ykds_free(st); + return; + } + + ykp_configure_version(cfg, st); + + // Set flags for HMAC challenge-response + struct config_st *core_config = (struct config_st *) ykp_core_config(cfg); + core_config->tktFlags |= TKTFLAG_CHAL_RESP; + core_config->cfgFlags |= CFGFLAG_CHAL_HMAC; + core_config->cfgFlags |= CFGFLAG_HMAC_LT64; + core_config->cfgFlags &= ~CFGFLAG_CHAL_BTN_TRIG; // Disable button press + core_config->extFlags |= EXTFLAG_SERIAL_API_VISIBLE; + + if (!ykp_configure_command(cfg, SLOT_CONFIG)) { + fprintf(stderr, "Internal error: couldn't configure command\n"); + ykp_free_config(cfg); + ykds_free(st); + return; + } + + printf("Configuring shared secret...\n"); + + // Ensure the secret size is correct + if (sizeof(secret) > TOTP_SECRET_HEX_SIZE) { + fprintf(stderr, "Secret too long\n"); + ykp_free_config(cfg); + ykds_free(st); + return; + } + + if (ykp_HMAC_key_from_hex(cfg, secret) != 0) { + fprintf(stderr, "Internal error: couldn't configure key\n"); + ykp_free_config(cfg); + ykds_free(st); + return; + } + + // Write to YubiKey + printf("Writing configuration to YubiKey...\n"); + if (!yk_write_command(yk, ykp_core_config(cfg), ykp_command(cfg), NULL)) { + fprintf(stderr, "Failed to write command: %s\n", yk_strerror(yk_errno)); + ykp_free_config(cfg); + ykds_free(st); + return; + } + + printf("Shared secret set successfully\n"); + + ykp_free_config(cfg); + ykds_free(st); +} + +uint32_t get_totp_from_yubikey(YK_KEY *yk) { + unsigned int t_counter = (unsigned int)time(NULL) / 30; + unsigned char challenge[8]; + for (int i = 7; i >= 0; i--) { + challenge[i] = t_counter & 0xFF; + t_counter >>= 8; + } + + if (!yk) { + fprintf(stderr, "Failed to get yubikey\n"); + return 0; + } + + unsigned char response[SHA1_MAX_BLOCK_SIZE]; + if (!yk_challenge_response(yk, SLOT_CHAL_HMAC1, true, sizeof(challenge), challenge, sizeof(response), response)) { + fprintf(stderr, "Failed to generate TOTP code\n"); + return 0; + } + + unsigned int offset = response[19] & 0xf; + unsigned int bin_code = (response[offset] & 0x7f) << 24 | + (response[offset + 1] & 0xff) << 16 | + (response[offset + 2] & 0xff) << 8 | + (response[offset + 3] & 0xff); + + return bin_code % 1000000; +} + +int main(int argc, char* argv[]) { oe_result_t result; int ret = 1; oe_enclave_t* enclave = NULL; uint32_t flags = OE_ENCLAVE_FLAG_DEBUG; - if (check_simulate_opt(&argc, argv)) + + // Parse the enclave image path and --simulate flag first + if (argc < 2) { - flags |= OE_ENCLAVE_FLAG_SIMULATE; + fprintf(stderr, "Usage: %s enclave_image_path [ --simulate ] -c [options]\n", argv[0]); + return ret; } - if (argc != 2) + const char* enclave_path = argv[1]; + int remaining_argc = argc - 1; + char** remaining_argv = &argv[1]; + + if (check_simulate_opt(&remaining_argc, remaining_argv)) { - fprintf( - stderr, "Usage: %s enclave_image_path [ --simulate ]\n", argv[0]); - goto exit; + flags |= OE_ENCLAVE_FLAG_SIMULATE; } // Create the enclave - result = oe_create_libdogecoin_enclave( - argv[1], OE_ENCLAVE_TYPE_AUTO, flags, NULL, 0, &enclave); + result = oe_create_libdogecoin_enclave(enclave_path, OE_ENCLAVE_TYPE_AUTO, flags, NULL, 0, &enclave); if (result != OE_OK) { - fprintf( - stderr, - "oe_create_libdogecoin_enclave(): result=%u (%s)\n", - result, - oe_result_str(result)); - goto exit; + fprintf(stderr, "oe_create_libdogecoin_enclave(): result=%u (%s)\n", result, oe_result_str(result)); + return ret; } - // Call into the enclave - result = enclave_libdogecoin(enclave); - if (result != OE_OK) + // Variables for CLI options + int opt = 0; + int long_index = 0; + char* cmd = 0; + uint32_t* account = NULL; + uint32_t* input_index = NULL; + const char* change_level = NULL; + char* message = "This is a test message"; + char* transaction = NULL; + char* shared_secret = NULL; + char* mnemonic_in = NULL; + char* entropy_size = NULL; + + // Allocate memory for encrypted blobs + uint8_t* encrypted_blob = malloc(MAX_ENCRYPTED_BLOB_SIZE); + size_t blob_size = MAX_ENCRYPTED_BLOB_SIZE; + + // Parse remaining CLI options + while ((opt = getopt_long_only(remaining_argc, remaining_argv, "c:o:i:l:m:t:n:s:e:", long_options, &long_index)) != -1) + { + switch (opt) + { + case 'c': + cmd = optarg; + break; + case 'o': + account = (uint32_t*)malloc(sizeof(uint32_t)); + *account = (uint32_t)strtol(optarg, NULL, 10); + break; + case 'i': + input_index = (uint32_t*)malloc(sizeof(uint32_t)); + *input_index = (uint32_t)strtol(optarg, NULL, 10); + break; + case 'l': + change_level = optarg; + break; + case 'm': + message = optarg; + break; + case 't': + transaction = optarg; + break; + case 'n': + mnemonic_in = optarg; + break; + case 's': + shared_secret = optarg; + break; + case 'e': + entropy_size = optarg; + break; + default: + print_usage(); + exit(EXIT_SUCCESS); + } + } + + if (!cmd) { - fprintf( - stderr, - "calling into enclave_libdogecoin failed: result=%u (%s)\n", - result, - oe_result_str(result)); - goto exit; + print_usage(); + exit(EXIT_SUCCESS); + } + + if (!yk_init()) { + fprintf(stderr, "Failed to initialize YubiKey\n"); } - // This is where we can call the cli code - // above may be added to any cli app - // app can make any calls into the enclave + YK_KEY *yk = yk_open_first_key(); + if (!yk) { + fprintf(stderr, "Failed to open YubiKey\n"); + } - // lets try to generate a bip39 mnemonic - result = enclave_libdogecoin_generate_mnemonic(enclave); - if (result != OE_OK) + if (strcmp(cmd, "run_example") == 0) { - fprintf( - stderr, - "calling into enclave_libdogecoin failed: result=%u (%s)\n", - result, - oe_result_str(result)); - goto exit; + printf("- Run the example\n"); + result = enclave_libdogecoin_run_example(enclave); + if (result != OE_OK) + { + fprintf(stderr, "Failed to run the example: result=%u (%s)\n", result, oe_result_str(result)); + } } + else if (strcmp(cmd, "generate_master_key") == 0) + { + printf("- Generate a master key\n"); + result = enclave_libdogecoin_generate_master_key(enclave, &encrypted_blob, &blob_size); + if (result != OE_OK) + { + fprintf(stderr, "Failed to generate a master key: result=%u (%s)\n", result, oe_result_str(result)); + } + else + { + write_encrypted_file(MASTER_TEE_FILE_NAME, encrypted_blob, blob_size); + } + } + else if (strcmp(cmd, "generate_mnemonic") == 0) + { + printf("- Generate and encrypt a mnemonic\n"); + if (!shared_secret) { + shared_secret = getpass("Enter shared secret (hex, 40 characters): "); + if (!shared_secret) { + fprintf(stderr, "Failed to read shared secret\n"); + goto exit; + } + } - ret = 0; + // Check if there is an existing configuration in slot 1 + YK_STATUS *status = ykds_alloc(); + if (yk && !yk_get_status(yk, status)) { + fprintf(stderr, "Failed to get YubiKey status\n"); + } + + // Check if slot 1 has a configuration + if (yk && (ykds_touch_level(status) & CONFIG1_VALID) == CONFIG1_VALID) { + char response; + printf("Slot 1 already has a configuration. Do you want to overwrite it? (y/N): "); + scanf(" %c", &response); + if (response != 'y' && response != 'Y') { + printf("Aborted by user\n"); + ykds_free(status); + goto exit; + } + } + ykds_free(status); + + if (yk) { + set_totp_secret(yk, shared_secret); + } + + MNEMONIC mnemonic = {0}; + result = enclave_libdogecoin_generate_mnemonic(enclave, &encrypted_blob, &blob_size, mnemonic, shared_secret, mnemonic_in, entropy_size); + if (result != OE_OK) + { + fprintf(stderr, "Failed to generate and encrypt a mnemonic: result=%u (%s)\n", result, oe_result_str(result)); + } + else + { + printf("Generated Mnemonic: %s\n", mnemonic); + if (mkdir(CRYPTO_DIR_PATH, 0777) == -1 && errno != EEXIST) + { + fprintf(stderr, "ERROR: Failed to create directory\n"); + return false; + } + write_encrypted_file(MNEMONIC_TEE_FILE_NAME, encrypted_blob, blob_size); + } + dogecoin_mem_zero(mnemonic, strlen(mnemonic)); + } + else if (strcmp(cmd, "generate_extended_public_key") == 0) + { + printf("- Generate a public key\n"); + uint8_t pubkeyhex[128]; + uint32_t auth_token = get_totp_from_yubikey(yk); + printf("Auth token: %u\n", auth_token); + read_encrypted_file(MNEMONIC_TEE_FILE_NAME, encrypted_blob, &blob_size); + if (blob_size == 0) + { + fprintf(stderr, "Failed to read encrypted mnemonic from file\n"); + ret = 0; + goto exit; + } + // verify account and change_level are set + if (!account || !change_level) + { + fprintf(stderr, "Account and change level must be set\n"); + ret = 0; + goto exit; + } + result = enclave_libdogecoin_generate_extended_public_key(enclave, encrypted_blob, blob_size, (char*)pubkeyhex, account, change_level, auth_token); + if (strlen(pubkeyhex) == 0) + { + fprintf(stderr, "Failed to generate public key: result=%u (%s)\n", result, oe_result_str(result)); + } + else + { + printf("Generated Public Key: %s\n", pubkeyhex); + } + } + else if (strcmp(cmd, "generate_address") == 0) + { + printf("- Generate address\n"); + char addresses[P2PKHLEN * NUM_ADDRESSES]; + uint32_t auth_token = get_totp_from_yubikey(yk); + printf("Auth token: %u\n", auth_token); + read_encrypted_file(MNEMONIC_TEE_FILE_NAME, encrypted_blob, &blob_size); + if (blob_size == 0) + { + fprintf(stderr, "Failed to read encrypted mnemonic from file\n"); + ret = 0; + goto exit; + } + // verify account, input_index and change_level are set + if (!account || !input_index || !change_level) + { + fprintf(stderr, "Account, input index and change level must be set\n"); + ret = 0; + goto exit; + } + result = enclave_libdogecoin_generate_address(enclave, encrypted_blob, blob_size, addresses, *account, *input_index, change_level, NUM_ADDRESSES, auth_token); + if (result != OE_OK) + { + fprintf(stderr, "Failed to generate addresses: result=%u (%s)\n", result, oe_result_str(result)); + } + else + { + printf("Generated Address: %s\n", addresses); + } + } + else if (strcmp(cmd, "sign_message") == 0) + { + printf("- Sign a message\n"); + uint8_t signature[2048] = {0}; + uint32_t auth_token = get_totp_from_yubikey(yk); + printf("Auth token: %u\n", auth_token); + read_encrypted_file(MNEMONIC_TEE_FILE_NAME, encrypted_blob, &blob_size); + if (blob_size == 0) + { + fprintf(stderr, "Failed to read encrypted mnemonic from file\n"); + ret = 0; + goto exit; + } + // verify account, input_index and change_level are set + if (!account || !input_index || !change_level) + { + fprintf(stderr, "Account, input index and change level must be set\n"); + ret = 0; + goto exit; + } + printf ("Signing message: %s\n", message); + result = enclave_libdogecoin_sign_message(enclave, encrypted_blob, blob_size, message, (char*)signature, *account, *input_index, change_level, auth_token); + if (result != OE_OK) + { + fprintf(stderr, "Failed to sign the message: result=%u (%s)\n", result, oe_result_str(result)); + } + else + { + printf("Signature: %s\n", signature); + } + } + else if (strcmp(cmd, "sign_transaction") == 0) + { + printf("- Sign a transaction\n"); + char raw_tx[1024]; + char signed_tx[4096]; + uint32_t auth_token = get_totp_from_yubikey(yk); + printf("Auth token: %u\n", auth_token); + read_encrypted_file(MNEMONIC_TEE_FILE_NAME, encrypted_blob, &blob_size); + if (blob_size == 0) + { + fprintf(stderr, "Failed to read encrypted mnemonic from file\n"); + ret = 0; + goto exit; + } + + // verify account, input_index and change_level are set + if (!account || !input_index || !change_level) + { + fprintf(stderr, "Account, input index and change level must be set\n"); + ret = 0; + goto exit; + } + + // Example transaction creation process + char *external_p2pkh_addr = "nbGfXLskPh7eM1iG5zz5EfDkkNTo9TRmde"; + char *hash_2_doge = "b4455e7b7b7acb51fb6feba7a2702c42a5100f61f61abafa31851ed6ae076074"; + char *hash_10_doge = "42113bdc65fc2943cf0359ea1a24ced0b6b0b5290db4c63a3329c6601c4616e2"; + + int idx = start_transaction(); + printf("Empty transaction created at index %d.\n", idx); + + if (add_utxo(idx, hash_2_doge, 1)) + { + printf("Input of value 2 dogecoin added to the transaction.\n"); + } + else + { + printf("Error occurred while adding input of value 2 dogecoin.\n"); + ret = 0; + goto exit; + } + + if (add_utxo(idx, hash_10_doge, 1)) + { + printf("Input of value 10 dogecoin added to the transaction.\n"); + } + else + { + printf("Error occurred while adding input of value 10 dogecoin.\n"); + ret = 0; + goto exit; + } + + if (add_output(idx, external_p2pkh_addr, "5.0")) + { + printf("Output of value 5 dogecoin added to the transaction.\n"); + } + else + { + printf("Error occurred while adding output of value 5 dogecoin.\n"); + ret = 0; + goto exit; + } + + int idx2 = store_raw_transaction(finalize_transaction(idx, external_p2pkh_addr, "0.00226", "12", "D5AkTLEwB4eCNcFoZN9pj1TxgkhQiVzt3T")); + if (idx2 > 0) + { + printf("Change returned to address %s and finalized unsigned transaction saved at index %d.\n", "D5AkTLEwB4eCNcFoZN9pj1TxgkhQiVzt3T", idx2); + } + else + { + printf("Error occurred while storing finalized unsigned transaction.\n"); + ret = 0; + goto exit; + } + + const char* raw_tx_hex = get_raw_transaction(idx); + strncpy(raw_tx, raw_tx_hex, sizeof(raw_tx) - 1); + raw_tx[sizeof(raw_tx) - 1] = '\0'; + printf("Raw transaction created: %s\n", raw_tx); + printf("Raw transaction length: %zu\n", strlen(raw_tx)); + + result = enclave_libdogecoin_sign_transaction(enclave, encrypted_blob, blob_size, transaction != NULL ? transaction : raw_tx, signed_tx, *account, *input_index, change_level, auth_token); + if (result != OE_OK) + { + fprintf(stderr, "Failed to sign the transaction: result=%u (%s)\n", result, oe_result_str(result)); + } + else + { + printf("Signed Transaction: %s\n", signed_tx); + } + } + else + { + print_usage(); + ret = 0; + } exit: - // Clean up the enclave if we created one if (enclave) + { oe_terminate_enclave(enclave); + } + if (mnemonic_in) { + dogecoin_mem_zero(mnemonic_in, strlen(mnemonic_in)); + } + if (shared_secret) { + dogecoin_mem_zero(shared_secret, strlen(shared_secret)); + } + if (account) + { + free(account); + } + if (input_index) + { + free(input_index); + } + if (yk) + { + yk_close_key(yk); + } + yk_release(); return ret; } diff --git a/src/openenclave/libdogecoin.edl b/src/openenclave/libdogecoin.edl index 7c7d8cb15..eafb7cb6a 100644 --- a/src/openenclave/libdogecoin.edl +++ b/src/openenclave/libdogecoin.edl @@ -4,15 +4,27 @@ enclave { from "openenclave/edl/syscall.edl" import *; from "platform.edl" import *; + from "openenclave/edl/logging.edl" import oe_write_ocall; + from "openenclave/edl/fcntl.edl" import *; + from "openenclave/edl/socket.edl" import *; + from "openenclave/edl/utsname.edl" import *; + from "openenclave/edl/epoll.edl" import *; trusted { public void enclave_libdogecoin(); - public void enclave_libdogecoin_generate_mnemonic(); + public void enclave_libdogecoin_attest(uint8_t* report, size_t len); + public void enclave_libdogecoin_run_example(); + public void enclave_libdogecoin_generate_encrypted_seed(uint8_t** encrypted_blob, size_t* len); + public void enclave_libdogecoin_generate_master_key(uint8_t** encrypted_blob, size_t* len); + + public void enclave_libdogecoin_generate_mnemonic(uint8_t** encrypted_blob, size_t* len, char* mnemonic, const char* shared_secret, const char* mnemonic_in, const char* entropy_size); + public void enclave_libdogecoin_generate_extended_public_key(uint8_t* encrypted_blob, size_t len, char* pubkey, uint32_t* account, const char* change_level, const uint32_t auth_token); + public void enclave_libdogecoin_generate_address(uint8_t* encrypted_blob, size_t len, char* addresses, uint32_t account, uint32_t address_index, const char* change_level, uint32_t num_addresses, const uint32_t auth_token); + public void enclave_libdogecoin_sign_message(uint8_t* encrypted_blob, size_t len, const char* message, char* signature, uint32_t account, uint32_t address_index, const char* change_level, const uint32_t auth_token); + public void enclave_libdogecoin_sign_transaction(uint8_t* encrypted_blob, size_t len, const char* raw_tx, char* signed_tx, uint32_t account, uint32_t address_index, const char* change_level, const uint32_t auth_token); }; untrusted { void host_libdogecoin(); }; }; - - diff --git a/src/optee/Android.mk b/src/optee/Android.mk new file mode 100644 index 000000000..3c1d042ae --- /dev/null +++ b/src/optee/Android.mk @@ -0,0 +1,17 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_CFLAGS += -DANDROID_BUILD +LOCAL_CFLAGS += -Wall + +LOCAL_SRC_FILES += host/main.c + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/ta/include + +LOCAL_SHARED_LIBRARIES := libteec +LOCAL_MODULE := optee_libdogecoin +LOCAL_VENDOR_MODULE := true +LOCAL_MODULE_TAGS := optional +include $(BUILD_EXECUTABLE) + +include $(LOCAL_PATH)/ta/Android.mk diff --git a/src/optee/CMakeLists.txt b/src/optee/CMakeLists.txt new file mode 100644 index 000000000..2940e9eb8 --- /dev/null +++ b/src/optee/CMakeLists.txt @@ -0,0 +1,13 @@ +project (optee_libdogecoin C) + +set (SRC host/main.c) + +add_executable (${PROJECT_NAME} ${SRC}) + +target_include_directories(${PROJECT_NAME} + PRIVATE ta/include + PRIVATE include) + +target_link_libraries (${PROJECT_NAME} PRIVATE teec) + +install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/optee/Makefile b/src/optee/Makefile new file mode 100644 index 000000000..dfa4f8b76 --- /dev/null +++ b/src/optee/Makefile @@ -0,0 +1,15 @@ +export V ?= 0 + +# If _HOST or _TA specific compilers are not specified, then use CROSS_COMPILE +HOST_CROSS_COMPILE ?= $(CROSS_COMPILE) +TA_CROSS_COMPILE ?= $(CROSS_COMPILE) + +.PHONY: all +all: + $(MAKE) -C host CROSS_COMPILE="$(HOST_CROSS_COMPILE)" --no-builtin-variables + $(MAKE) -C ta CROSS_COMPILE="$(TA_CROSS_COMPILE)" LDFLAGS="" + +.PHONY: clean +clean: + $(MAKE) -C host clean + $(MAKE) -C ta clean diff --git a/src/optee/common.mk.patch b/src/optee/common.mk.patch new file mode 100644 index 000000000..125fb7f7d --- /dev/null +++ b/src/optee/common.mk.patch @@ -0,0 +1,18 @@ +--- a common.mk ++++ b common.mk +@@ -274,4 +274,6 @@ + BR2_PER_PACKAGE_DIRECTORIES ?= y ++BR2_PACKAGE_LIBUSB ?= y ++BR2_PACKAGE_USBUTILS ?= y + BR2_PACKAGE_LIBOPENSSL ?= y + BR2_PACKAGE_MMC_UTILS ?= y + BR2_PACKAGE_OPENSSL ?= y +@@ -442,7 +444,7 @@ + # QEMU / QEMUv8 + ################################################################################ + QEMU_CONFIGURE_PARAMS_COMMON = --cc="$(CCACHE)gcc" --extra-cflags="-Wno-error" \ +- --disable-docs ++ --disable-docs --enable-libusb + QEMU_EXTRA_ARGS +=\ + -object rng-random,filename=/dev/urandom,id=rng0 \ + -device virtio-rng-pci,rng=rng0,max-bytes=1024,period=1000 diff --git a/src/optee/host/Makefile b/src/optee/host/Makefile new file mode 100644 index 000000000..38c8e72af --- /dev/null +++ b/src/optee/host/Makefile @@ -0,0 +1,28 @@ +CC ?= $(CROSS_COMPILE)gcc +LD ?= $(CROSS_COMPILE)ld +AR ?= $(CROSS_COMPILE)ar +NM ?= $(CROSS_COMPILE)nm +OBJCOPY ?= $(CROSS_COMPILE)objcopy +OBJDUMP ?= $(CROSS_COMPILE)objdump +READELF ?= $(CROSS_COMPILE)readelf + +OBJS = main.o + +CFLAGS += -Wall -I../ta/include -I./include +CFLAGS += -I$(TEEC_EXPORT)/include +LDADD += -ldogecoin -lykpers-1 -lyubikey -lusb-1.0 -lteec -L$(TEEC_EXPORT)/lib + +BINARY = optee_libdogecoin + +.PHONY: all +all: $(BINARY) + +$(BINARY): $(OBJS) + $(CC) $(LDFLAGS) -o $@ $< $(LDADD) + +.PHONY: clean +clean: + rm -f $(OBJS) $(BINARY) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ diff --git a/src/optee/host/main.c b/src/optee/host/main.c new file mode 100644 index 000000000..d96b7b371 --- /dev/null +++ b/src/optee/host/main.c @@ -0,0 +1,1002 @@ +/** + * Copyright (c) 2024 edtubbs + * Copyright (c) 2024 The Dogecoin Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* OP-TEE TEE client API (built by optee_client) */ +#include + +/* TA API: UUID and command IDs */ +#include + +#include + +#include + +/* TEE resources */ +struct test_ctx { + TEEC_Context ctx; + TEEC_Session sess; +}; + +void prepare_tee_session(struct test_ctx *ctx) +{ + TEEC_UUID uuid = TA_LIBDOGECOIN_UUID; + uint32_t origin; + TEEC_Result res; + + /* Initialize a context connecting us to the TEE */ + res = TEEC_InitializeContext(NULL, &ctx->ctx); + if (res != TEEC_SUCCESS) + errx(1, "TEEC_InitializeContext failed with code 0x%x", res); + + /* Open a session with the TA */ + res = TEEC_OpenSession(&ctx->ctx, &ctx->sess, &uuid, + TEEC_LOGIN_PUBLIC, NULL, NULL, &origin); + if (res != TEEC_SUCCESS) + errx(1, "TEEC_Opensession failed with code 0x%x origin 0x%x", + res, origin); +} + +void terminate_tee_session(struct test_ctx *ctx) +{ + TEEC_CloseSession(&ctx->sess); + TEEC_FinalizeContext(&ctx->ctx); +} + +TEEC_Result generate_seed(struct test_ctx *ctx) +{ + TEEC_Operation op; /* Operation structure used for the RPC */ + uint32_t origin; /* Origin of the command response */ + TEEC_Result res; /* Return value of the TEE invocation */ + SEED seed; /* Seed generated by the TA */ + + /* Prepare the operation */ + memset(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT, + TEEC_NONE, TEEC_NONE, TEEC_NONE); + op.params[0].tmpref.buffer = seed; + op.params[0].tmpref.size = sizeof(seed); + + /* Invoke the command */ + res = TEEC_InvokeCommand(&ctx->sess, + TA_LIBDOGECOIN_CMD_GENERATE_SEED, + &op, &origin); + + /* Check the return value */ + if (res == TEEC_ERROR_SHORT_BUFFER) { + /* Resize the buffer and retry */ + op.params[0].tmpref.buffer = malloc(op.params[0].tmpref.size); + if (!op.params[0].tmpref.buffer) + return TEEC_ERROR_OUT_OF_MEMORY; + + res = TEEC_InvokeCommand(&ctx->sess, + TA_LIBDOGECOIN_CMD_GENERATE_SEED, + &op, &origin); + if (res == TEEC_SUCCESS) { + /* Retrieve the output data */ + memcpy(seed, op.params[0].tmpref.buffer, sizeof(seed)); + printf("Seed generated: %s\n", utils_uint8_to_hex(seed, sizeof(seed))); + } else { + printf("Command GENERATE_SEED failed: 0x%x / %u\n", res, origin); + } + + free(op.params[0].tmpref.buffer); + } else if (res == TEEC_SUCCESS) { + /* Retrieve the output data */ + memcpy(seed, op.params[0].tmpref.buffer, sizeof(seed)); + printf("Seed generated: %s\n", utils_uint8_to_hex(seed, sizeof(seed))); + } else { + printf("Command GENERATE_SEED failed: 0x%x / %u\n", res, origin); + } + + return res; +} + +TEEC_Result generate_master_key(struct test_ctx *ctx) +{ + TEEC_Operation op; // Operation structure used for the RPC + uint32_t origin; // Origin of the command response + TEEC_Result res; // Return value of the TEE invocation + char hd_master_privkey[HDKEYLEN]; // Master key generated by the TA + + // Prepare the operation + memset(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT, + TEEC_NONE, TEEC_NONE, TEEC_NONE); + op.params[0].tmpref.buffer = hd_master_privkey; + op.params[0].tmpref.size = HDKEYLEN; + + // Invoke the command + res = TEEC_InvokeCommand(&ctx->sess, + TA_LIBDOGECOIN_CMD_GENERATE_MASTERKEY, + &op, &origin); + + // Check the return value + if (res == TEEC_ERROR_SHORT_BUFFER) { + // Resize the buffer and retry + op.params[0].tmpref.buffer = malloc(op.params[0].tmpref.size); + if (!op.params[0].tmpref.buffer) + return TEEC_ERROR_OUT_OF_MEMORY; + + res = TEEC_InvokeCommand(&ctx->sess, + TA_LIBDOGECOIN_CMD_GENERATE_MASTERKEY, + &op, &origin); + if (res == TEEC_SUCCESS) { + // Retrieve the output data + memcpy(hd_master_privkey, op.params[0].tmpref.buffer, HDKEYLEN); + printf("Master key generated: %s\n", hd_master_privkey); + } else { + printf("Command GENERATE_MASTERKEY failed: 0x%x / %u\n", res, origin); + } + + free(op.params[0].tmpref.buffer); + } else if (res == TEEC_SUCCESS) { + // Retrieve the output data + memcpy(hd_master_privkey, op.params[0].tmpref.buffer, HDKEYLEN); + printf("Master key generated: %s\n", hd_master_privkey); + } else { + printf("Command GENERATE_MASTERKEY failed: 0x%x / %u\n", res, origin); + } + + return res; +} + +// Maximum size of managed credentials (shared secret, password, flags) +#define MAX_MANAGED_CREDS_SIZE 1024 + +TEEC_Result generate_mnemonic(struct test_ctx *ctx, const char *shared_secret, const char *password, const char* flags, const MNEMONIC mnemonic_in, const ENTROPY_SIZE entropy_size) +{ + TEEC_Operation op; // Operation structure used for the RPC + uint32_t origin; // Origin of the command response + TEEC_Result res; // Return value of the TEE invocation + MNEMONIC mnemonic = {0}; // Mnemonic generated by the TA + char managed_creds[MAX_MANAGED_CREDS_SIZE] = {0}; // Buffer for managed credentials + + // Prepare the managed credentials as ",," + snprintf(managed_creds, sizeof(managed_creds), "%s,%s,%s", + shared_secret ? shared_secret : "none", + password ? password : "none", + flags ? flags : "none"); + + // Prepare the operation + memset(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT, + TEEC_MEMREF_TEMP_INPUT, TEEC_MEMREF_TEMP_INPUT, TEEC_MEMREF_TEMP_INPUT); + op.params[0].tmpref.buffer = mnemonic; + op.params[0].tmpref.size = sizeof(mnemonic); + op.params[1].tmpref.buffer = (void *)managed_creds; + op.params[1].tmpref.size = strlen(managed_creds) + 1; // Ensure null-terminator is included + if (mnemonic_in) { + op.params[2].tmpref.buffer = (void *)mnemonic_in; + op.params[2].tmpref.size = strlen(mnemonic_in) + 1; // Ensure null-terminator is included + } + if (entropy_size) { + op.params[3].tmpref.buffer = (void *)entropy_size; + op.params[3].tmpref.size = strlen(entropy_size) + 1; // Ensure null-terminator is included + } + + // Invoke the command + res = TEEC_InvokeCommand(&ctx->sess, + TA_LIBDOGECOIN_CMD_GENERATE_MNEMONIC, + &op, &origin); + + // Check the return value + if (res == TEEC_ERROR_SHORT_BUFFER) { + // Resize the buffer and retry + op.params[0].tmpref.buffer = malloc(op.params[0].tmpref.size); + if (!op.params[0].tmpref.buffer) + return TEEC_ERROR_OUT_OF_MEMORY; + + res = TEEC_InvokeCommand(&ctx->sess, + TA_LIBDOGECOIN_CMD_GENERATE_MNEMONIC, + &op, &origin); + if (res == TEEC_SUCCESS) { + // Retrieve the output data + memcpy(mnemonic, op.params[0].tmpref.buffer, sizeof(mnemonic)); + printf("Mnemonic generated: %s\n", mnemonic); + } else { + printf("Command GENERATE_MNEMONIC failed: 0x%x / %u\n", res, origin); + } + + free(op.params[0].tmpref.buffer); + } else if (res == TEEC_SUCCESS) { + // Retrieve the output data + memcpy(mnemonic, op.params[0].tmpref.buffer, sizeof(mnemonic)); + printf("Mnemonic generated: %s\n", mnemonic); + } else { + printf("Command GENERATE_MNEMONIC failed: 0x%x / %u\n", res, origin); + } + + // Erase the mnemonic and creds + dogecoin_mem_zero(mnemonic, strlen(mnemonic)); + dogecoin_mem_zero(managed_creds, strlen(managed_creds)); + + return res; +} + +TEEC_Result generate_address_ta(struct test_ctx *ctx, const char* account, const char* address_index, const char* change_level, uint32_t auth_token, const char* password, char *address, size_t *address_len) +{ + TEEC_Operation op; + uint32_t origin; + TEEC_Result res; + + // Construct key path from account, change_level, and address_index + char key_path[KEYPATHMAXLEN]; + snprintf(key_path, sizeof(key_path), "m/44'/3'/%s'/%s/%s", account, change_level, address_index); + + memset(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT, TEEC_MEMREF_TEMP_INPUT, TEEC_VALUE_INPUT, TEEC_MEMREF_TEMP_INPUT); + + op.params[0].tmpref.buffer = address; + op.params[0].tmpref.size = *address_len; + op.params[1].tmpref.buffer = key_path; + op.params[1].tmpref.size = strlen(key_path) + 1; + op.params[2].value.a = auth_token; + op.params[3].tmpref.buffer = (void *)password; + op.params[3].tmpref.size = password ? strlen(password) + 1 : 0; + + res = TEEC_InvokeCommand(&ctx->sess, TA_LIBDOGECOIN_CMD_GENERATE_ADDRESS, &op, &origin); + + if (res == TEEC_ERROR_SHORT_BUFFER) { + *address_len = op.params[0].tmpref.size; + op.params[0].tmpref.buffer = malloc(*address_len); + if (!op.params[0].tmpref.buffer) + return TEEC_ERROR_OUT_OF_MEMORY; + + res = TEEC_InvokeCommand(&ctx->sess, TA_LIBDOGECOIN_CMD_GENERATE_ADDRESS, &op, &origin); + if (res == TEEC_SUCCESS) { + *address_len = op.params[0].tmpref.size; + memcpy(address, op.params[0].tmpref.buffer, *address_len); + } else { + printf("Command GENERATE_ADDRESS failed: 0x%x / %u\n", res, origin); + } + + free(op.params[0].tmpref.buffer); + } else if (res == TEEC_SUCCESS) { + *address_len = op.params[0].tmpref.size; + } else { + printf("Command GENERATE_ADDRESS failed: 0x%x / %u\n", res, origin); + } + + return res; +} + +TEEC_Result generate_extended_public_key_ta(struct test_ctx *ctx, const char* account, const char* change_level, uint32_t auth_token, const char* password, char *pubkey, size_t *pubkey_len) +{ + TEEC_Operation op; + uint32_t origin; + TEEC_Result res; + + // Construct key path from account and change_level + char key_path[KEYPATHMAXLEN]; + snprintf(key_path, sizeof(key_path), "m/44'/3'/%s'/%s", account, change_level); + + memset(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT, TEEC_MEMREF_TEMP_INPUT, TEEC_VALUE_INPUT, TEEC_MEMREF_TEMP_INPUT); + + op.params[0].tmpref.buffer = pubkey; + op.params[0].tmpref.size = *pubkey_len; + op.params[1].tmpref.buffer = key_path; + op.params[1].tmpref.size = strlen(key_path) + 1; + op.params[2].value.a = auth_token; + op.params[3].tmpref.buffer = (void *)password; + op.params[3].tmpref.size = password ? strlen(password) + 1 : 0; + + res = TEEC_InvokeCommand(&ctx->sess, TA_LIBDOGECOIN_CMD_GENERATE_EXTENDED_PUBLIC_KEY, &op, &origin); + + if (res == TEEC_ERROR_SHORT_BUFFER) { + *pubkey_len = op.params[0].tmpref.size; + op.params[0].tmpref.buffer = malloc(*pubkey_len); + if (!op.params[0].tmpref.buffer) + return TEEC_ERROR_OUT_OF_MEMORY; + + res = TEEC_InvokeCommand(&ctx->sess, TA_LIBDOGECOIN_CMD_GENERATE_EXTENDED_PUBLIC_KEY, &op, &origin); + if (res == TEEC_SUCCESS) { + *pubkey_len = op.params[0].tmpref.size; + memcpy(pubkey, op.params[0].tmpref.buffer, *pubkey_len); + } else { + printf("Command GENERATE_EXTENDED_PUBLIC_KEY failed: 0x%x / %u\n", res, origin); + } + + free(op.params[0].tmpref.buffer); + } else if (res == TEEC_SUCCESS) { + *pubkey_len = op.params[0].tmpref.size; + } else { + printf("Command GENERATE_EXTENDED_PUBLIC_KEY failed: 0x%x / %u\n", res, origin); + } + + return res; +} + +TEEC_Result sign_message_ta(struct test_ctx *ctx, const char* account, const char* address_index, const char* change_level, uint32_t auth_token, const char* password, char *message, char *signature, size_t *signature_len) +{ + TEEC_Operation op; + uint32_t origin; + TEEC_Result res; + + // Construct key path from account, change_level, and address_index + char key_path[KEYPATHMAXLEN]; + snprintf(key_path, sizeof(key_path), "m/44'/3'/%s'/%s/%s,%s", account, change_level, address_index, password); + + memset(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, + TEEC_MEMREF_TEMP_OUTPUT, + TEEC_MEMREF_TEMP_INPUT, TEEC_VALUE_INPUT); + + op.params[0].tmpref.buffer = message; + op.params[0].tmpref.size = message ? strlen(message) + 1 : 0; // Ensure null-terminator is included + op.params[1].tmpref.buffer = signature; + op.params[1].tmpref.size = *signature_len; + op.params[2].tmpref.buffer = key_path; + op.params[2].tmpref.size = strlen(key_path) + 1; + op.params[3].value.a = auth_token; + op.params[3].value.b = password ? strlen(password) + 1 : 0; + + res = TEEC_InvokeCommand(&ctx->sess, TA_LIBDOGECOIN_CMD_SIGN_MESSAGE, &op, &origin); + + if (res == TEEC_ERROR_SHORT_BUFFER) { + // Resize the buffer and retry + *signature_len = op.params[1].tmpref.size; + op.params[1].tmpref.buffer = malloc(*signature_len); + if (!op.params[1].tmpref.buffer) + return TEEC_ERROR_OUT_OF_MEMORY; + + res = TEEC_InvokeCommand(&ctx->sess, TA_LIBDOGECOIN_CMD_SIGN_MESSAGE, &op, &origin); + if (res == TEEC_SUCCESS) { + *signature_len = op.params[1].tmpref.size; + memcpy(signature, op.params[1].tmpref.buffer, *signature_len); + } else { + printf("Command SIGN_MESSAGE failed: 0x%x / %u\n", res, origin); + } + + free(op.params[1].tmpref.buffer); + } else if (res == TEEC_SUCCESS) { + *signature_len = op.params[1].tmpref.size; + } else { + printf("Command SIGN_MESSAGE failed: 0x%x / %u\n", res, origin); + } + + return res; +} + +TEEC_Result sign_transaction_ta(struct test_ctx *ctx, const char* account, const char* address_index, const char* change_level, uint32_t auth_token, const char* password, char *raw_tx, size_t raw_tx_len, char *signed_tx, size_t *signed_tx_len) +{ + TEEC_Operation op; + uint32_t origin; + TEEC_Result res; + + // Construct key path from account, change_level, and address_index + char key_path[KEYPATHMAXLEN]; + snprintf(key_path, sizeof(key_path), "m/44'/3'/%s'/%s/%s,%s", account, change_level, address_index, password); + + memset(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, + TEEC_MEMREF_TEMP_OUTPUT, + TEEC_MEMREF_TEMP_INPUT, TEEC_VALUE_INPUT); + + op.params[0].tmpref.buffer = raw_tx; + op.params[0].tmpref.size = raw_tx_len; + op.params[1].tmpref.buffer = signed_tx; + op.params[1].tmpref.size = *signed_tx_len; + op.params[2].tmpref.buffer = key_path; + op.params[2].tmpref.size = strlen(key_path) + 1; + op.params[3].value.a = auth_token; + op.params[3].value.b = password ? strlen(password) + 1 : 0; + + res = TEEC_InvokeCommand(&ctx->sess, TA_LIBDOGECOIN_CMD_SIGN_TRANSACTION, &op, &origin); + + if (res == TEEC_ERROR_SHORT_BUFFER) { + // Resize the buffer and retry + *signed_tx_len = op.params[1].tmpref.size; + op.params[1].tmpref.buffer = malloc(*signed_tx_len); + if (!op.params[1].tmpref.buffer) + return TEEC_ERROR_OUT_OF_MEMORY; + + res = TEEC_InvokeCommand(&ctx->sess, TA_LIBDOGECOIN_CMD_SIGN_TRANSACTION, &op, &origin); + if (res == TEEC_SUCCESS) { + *signed_tx_len = op.params[1].tmpref.size; + memcpy(signed_tx, op.params[1].tmpref.buffer, *signed_tx_len); + } else { + printf("Command SIGN_TRANSACTION failed: 0x%x / %u\n", res, origin); + } + + free(op.params[1].tmpref.buffer); + } else if (res == TEEC_SUCCESS) { + *signed_tx_len = op.params[1].tmpref.size; + } else { + printf("Command SIGN_TRANSACTION failed: 0x%x / %u\n", res, origin); + } + + return res; +} + +TEEC_Result delegate_key_ta(struct test_ctx *ctx, const char* account, uint32_t auth_token, const char* delegate_password, const char *password, char *delegate_key) +{ + TEEC_Operation op; + uint32_t origin; + TEEC_Result res; + + char delegate_creds[MAX_MANAGED_CREDS_SIZE] = {0}; // Buffer for delegated credentials + + // Prepare the delegated credentials as "," + snprintf(delegate_creds, sizeof(delegate_creds), "%s,%s", + delegate_password && strlen(delegate_password) > 0 ? delegate_password : "none", + password && strlen(password) > 0 ? password : "none"); + + // Construct key path from account, change_level, and address_index + char key_path[KEYPATHMAXLEN]; + snprintf(key_path, sizeof(key_path), "m/44'/3'/%s'", account); + + // Prepare the operation + memset(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT, TEEC_MEMREF_TEMP_INPUT, TEEC_VALUE_INPUT, TEEC_MEMREF_TEMP_INPUT); + + op.params[0].tmpref.buffer = delegate_key; + op.params[0].tmpref.size = HDKEYLEN; + op.params[1].tmpref.buffer = (void *)key_path; + op.params[1].tmpref.size = strlen(key_path) + 1; + op.params[2].value.a = auth_token; + op.params[3].tmpref.buffer = (void *)delegate_creds; + op.params[3].tmpref.size = strlen(delegate_creds) + 1; + + // Invoke the command + res = TEEC_InvokeCommand(&ctx->sess, TA_LIBDOGECOIN_CMD_DELEGATE_KEY, &op, &origin); + + if (res != TEEC_SUCCESS) { + printf("Command DELEGATE_KEY failed: 0x%x / %u\n", res, origin); + } + + // Erase the creds + dogecoin_mem_zero(delegate_creds, strlen(delegate_creds)); + + return res; +} + +TEEC_Result export_delegate_key_ta(struct test_ctx *ctx, const char* account, const char *password, char *exported_key) { + TEEC_Operation op; + uint32_t origin; + TEEC_Result res; + + // Prepare the operation + memset(&op, 0, sizeof(op)); + op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_OUTPUT, TEEC_MEMREF_TEMP_INPUT, TEEC_NONE, TEEC_MEMREF_TEMP_INPUT); + + // Construct key path from account, change_level, and address_index + char key_path[KEYPATHMAXLEN]; + snprintf(key_path, sizeof(key_path), "m/44'/3'/%s'", account); + + op.params[0].tmpref.buffer = exported_key; + op.params[0].tmpref.size = HDKEYLEN; + op.params[1].tmpref.buffer = (void *)key_path; + op.params[1].tmpref.size = strlen(key_path) + 1; + op.params[3].tmpref.buffer = (void *)password; + op.params[3].tmpref.size = password ? strlen(password) + 1 : 0; + + // Invoke the command + res = TEEC_InvokeCommand(&ctx->sess, TA_LIBDOGECOIN_CMD_EXPORT_DELEGATED_KEY, &op, &origin); + + if (res != TEEC_SUCCESS) { + printf("Command EXPORT_DELEGATED_KEY failed: 0x%x / %u\n", res, origin); + } + + return res; +} + +#define TOTP_SECRET_HEX_SIZE 41 // hex, 40 characters + null + +static struct option long_options[] = { + {"command", required_argument, NULL, 'c'}, + {"account", required_argument, NULL, 'o'}, + {"change_level", required_argument, NULL, 'l'}, + {"address_index", required_argument, NULL, 'i'}, + {"message", required_argument, NULL, 'm'}, + {"transaction", required_argument, NULL, 't'}, + {"mnemonic_input", required_argument, NULL, 'n'}, + {"shared_secret", required_argument, NULL, 's'}, + {"entropy_size", required_argument, NULL, 'e'}, + {"password", required_argument, NULL, 'p'}, + {"delegate_password", required_argument, NULL, 'd'}, + {"auth_token", required_argument, NULL, 'a'}, + {"prompt", no_argument, NULL, 'u'}, + {"flags", required_argument, NULL, 'f'}, + {NULL, 0, NULL, 0} +}; + +static void print_usage() +{ + printf("Usage: such -c (-o ) (-l ) (-i ) (-m ) (-t ) \ +(-n ) (-s ) (-e ) (-a ) (-p/-d or -u ) (-f )\n"); + printf("Available commands:\n"); + printf(" generate_mnemonic (optional -n -s -e -p or -u -f )\n"); + printf(" generate_address (requires -o -l -i , optional -a -p or -u )\n"); + printf(" generate_extended_public_key (requires -o -l , optional -a -p or -u )\n"); + printf(" sign_message (requires -o -l -i -m , optional -a -p or -u )\n"); + printf(" sign_transaction (requires -o -l -i -t , optional -a -p or -u )\n"); + printf(" delegate_key (requires -o -d or -u , optional -a -p )\n"); + printf(" export_delegate_key (requires -o -d or -u )\n"); +} + +#define TIME_STEP 30 + +void set_totp_secret(YK_KEY *yk, const char *secret, const uint8_t slot) { + YKP_CONFIG *cfg = ykp_alloc(); + YK_STATUS *st = ykds_alloc(); + + if (!cfg || !st) { + fprintf(stderr, "Failed to allocate YubiKey structures\n"); + ykp_free_config(cfg); + ykds_free(st); + return; + } + + if (!yk_get_status(yk, st)) { + fprintf(stderr, "Failed to get YubiKey status: %s\n", yk_strerror(yk_errno)); + ykp_free_config(cfg); + ykds_free(st); + return; + } + + ykp_configure_version(cfg, st); + + // Set flags for HMAC challenge-response + struct config_st *core_config = (struct config_st *) ykp_core_config(cfg); + core_config->tktFlags |= TKTFLAG_CHAL_RESP; + core_config->cfgFlags |= CFGFLAG_CHAL_HMAC; + core_config->cfgFlags |= CFGFLAG_HMAC_LT64; + core_config->cfgFlags &= ~CFGFLAG_CHAL_BTN_TRIG; // Disable button press + core_config->extFlags |= EXTFLAG_SERIAL_API_VISIBLE; + + if (!ykp_configure_command(cfg, slot)) { + fprintf(stderr, "Internal error: couldn't configure command\n"); + ykp_free_config(cfg); + ykds_free(st); + return; + } + + printf("Configuring shared secret...\n"); + + // Ensure the secret size is correct + if (sizeof(secret) > TOTP_SECRET_HEX_SIZE) { + fprintf(stderr, "Secret too long\n"); + ykp_free_config(cfg); + ykds_free(st); + return; + } + + if (ykp_HMAC_key_from_hex(cfg, secret) != 0) { + fprintf(stderr, "Internal error: couldn't configure key\n"); + ykp_free_config(cfg); + ykds_free(st); + return; + } + + // Write to YubiKey + printf("Writing configuration to YubiKey...\n"); + if (!yk_write_command(yk, ykp_core_config(cfg), ykp_command(cfg), NULL)) { + fprintf(stderr, "Failed to write command: %s\n", yk_strerror(yk_errno)); + ykp_free_config(cfg); + ykds_free(st); + return; + } + + printf("Shared secret set successfully\n"); + + ykp_free_config(cfg); + ykds_free(st); +} + +uint32_t get_totp_from_yubikey(YK_KEY *yk, uint8_t yk_cmd) { + unsigned int t_counter = (unsigned int)time(NULL) / 30; + unsigned char challenge[8]; + for (int i = 7; i >= 0; i--) { + challenge[i] = t_counter & 0xFF; + t_counter >>= 8; + } + + if (!yk) { + fprintf(stderr, "Failed to get yubikey\n"); + return 0; + } + + unsigned char response[SHA1_MAX_BLOCK_SIZE]; + if (!yk_challenge_response(yk, yk_cmd, true, sizeof(challenge), challenge, sizeof(response), response)) { + fprintf(stderr, "Failed to generate TOTP code\n"); + return 0; + } + + unsigned int offset = response[19] & 0xf; + unsigned int bin_code = (response[offset] & 0x7f) << 24 | + (response[offset + 1] & 0xff) << 16 | + (response[offset + 2] & 0xff) << 8 | + (response[offset + 3] & 0xff); + + return bin_code % 1000000; +} + +int main(int argc, const char* argv[]) +{ + struct test_ctx ctx; + int opt = 0; + int long_index = 0; + char* cmd = 0; + uint32_t auth_token = 0; + const char* account = NULL; + const char* address_index = NULL; + const char* change_level = NULL; + char* message = NULL; + char* transaction = NULL; + uint8_t* shared_secret = NULL; + char* password = NULL; + char* mnemonic = NULL; + char* entropy_size = NULL; + char* delegate_password = NULL; + dogecoin_bool prompt = false; + char* flags = ""; + + while ((opt = getopt_long_only(argc, argv, "c:o:l:i:m:t:n:s:e:p:d:a:f:u", long_options, &long_index)) != -1) { + switch (opt) { + case 'c': + cmd = optarg; + break; + case 'o': + account = optarg; + break; + case 'i': + address_index = optarg; + break; + case 'l': + change_level = optarg; + break; + case 'm': + message = optarg; + break; + case 't': + transaction = optarg; + break; + case 'n': + mnemonic = optarg; + break; + case 's': + shared_secret = optarg; + break; + case 'p': + password = optarg; + break; + case 'd': + delegate_password = optarg; + break; + case 'u': + prompt = true; + break; + case 'e': + entropy_size = optarg; + break; + case 'a': + auth_token = (uint32_t)strtol(optarg, NULL, 10); + break; + case 'f': + flags = optarg; + break; + default: + print_usage(); + exit(EXIT_SUCCESS); + } + } + + if (!cmd) { + /* exit if no command was provided */ + print_usage(); + exit(EXIT_SUCCESS); + } + + printf("Prepare session with the TA\n"); + prepare_tee_session(&ctx); + + if (!yk_init()) { + fprintf(stderr, "Failed to initialize YubiKey\n"); + } + + YK_KEY *yk = yk_open_first_key(); + if (!yk) { + fprintf(stderr, "Failed to open YubiKey\n"); + } + + if (strcmp(cmd, "generate_seed") == 0) { + printf("- Generate a seed\n"); + TEEC_Result res = generate_seed(&ctx); + if (res != TEEC_SUCCESS) + errx(1, "Failed to generate a seed"); + else + printf("Seed generated\n"); + } else if (strcmp(cmd, "generate_master_key") == 0) { + printf("- Generate a master key\n"); + TEEC_Result res = generate_master_key(&ctx); + if (res != TEEC_SUCCESS) + errx(1, "Failed to generate a master key"); + else + printf("Master key generated\n"); + } else if (strcmp(cmd, "generate_mnemonic") == 0) { + printf("- Generate and encrypt a mnemonic\n"); + + if (!shared_secret) { + shared_secret = getpass("Enter shared secret (hex, 40 characters): "); + if (!shared_secret) { + fprintf(stderr, "Failed to read shared secret\n"); + goto exit; + } + } + + if (!password && prompt) { + password = getpass("Enter management password (or press enter for none): "); + if (!password) { + fprintf(stderr, "Failed to read password\n"); + goto exit; + } + } + + // Check if there is an existing configuration in slot 1 + YK_STATUS *status = ykds_alloc(); + if (yk) { + if (!yk_get_status(yk, status)) { + fprintf(stderr, "Failed to get YubiKey status\n"); + } + } + + // Check if slot 1 has a configuration + if (yk && (ykds_touch_level(status) & CONFIG1_VALID) == CONFIG1_VALID) { + char response; + printf("Slot 1 already has a configuration. Do you want to overwrite it? (y/N): "); + scanf(" %c", &response); + if (response != 'y' && response != 'Y') { + printf("Aborted by user\n"); + ykds_free(status); + goto exit; + } + } + + ykds_free(status); + + if (yk) { + set_totp_secret(yk, shared_secret, SLOT_CONFIG); + } + + TEEC_Result res = generate_mnemonic(&ctx, shared_secret, password, flags, mnemonic, entropy_size); + if (res != TEEC_SUCCESS) + errx(1, "Failed to generate a mnemonic"); + } else if (strcmp(cmd, "generate_address") == 0) { + printf("- Generate an address\n"); + char address[35]; + size_t address_len = sizeof(address); + + if (!password && prompt) { + password = getpass("Enter management password (or press enter for none): "); + if (!password) { + fprintf(stderr, "Failed to read password\n"); + goto exit; + } + } + + if (auth_token == 0) { + auth_token = get_totp_from_yubikey(yk, SLOT_CHAL_HMAC1); + } + printf("Auth token: %u\n", auth_token); + + TEEC_Result res = generate_address_ta(&ctx, account, address_index, change_level, auth_token, password, address, &address_len); + if (res != TEEC_SUCCESS) + errx(1, "Failed to generate address"); + else + printf("Address generated: %s\n", address); + } else if (strcmp(cmd, "generate_extended_public_key") == 0) { + printf("- Generate a public extended key\n"); + char pubkey[HDKEYLEN]; + size_t pubkey_len = sizeof(pubkey); + + if (!password && prompt) { + password = getpass("Enter management password (or press enter for none): "); + if (!password) { + fprintf(stderr, "Failed to read password\n"); + goto exit; + } + } + + if (auth_token == 0) { + auth_token = get_totp_from_yubikey(yk, SLOT_CHAL_HMAC1); + } + printf("Auth token: %u\n", auth_token); + + TEEC_Result res = generate_extended_public_key_ta(&ctx, account, change_level, auth_token, password, pubkey, &pubkey_len); + if (res != TEEC_SUCCESS) + errx(1, "Failed to generate public extended key"); + else + printf("Public extended key generated: %s\n", pubkey); + } else if (strcmp(cmd, "sign_message") == 0) { + printf("- Sign a message\n"); + char signature[2048] = {0}; + size_t signature_len = sizeof(signature); + + if (!password && prompt) { + password = getpass("Enter management password (or press enter for none): "); + if (!password) { + fprintf(stderr, "Failed to read password\n"); + goto exit; + } + } + + if (auth_token == 0) { + auth_token = get_totp_from_yubikey(yk, SLOT_CHAL_HMAC1); + } + printf("Auth token: %u\n", auth_token); + + TEEC_Result res = sign_message_ta(&ctx, account, address_index, change_level, auth_token, password, message ? message : "This is a test message.", signature, &signature_len); + if (res != TEEC_SUCCESS) + errx(1, "Failed to sign the message"); + else + printf("Message signed: %s\n", signature); + } else if (strcmp(cmd, "sign_transaction") == 0) { + printf("- Sign a transaction\n"); + char raw_tx[1024]; + char signed_tx[4096]; + size_t signed_tx_len = sizeof(signed_tx); + + // Example transaction creation process + char *external_p2pkh_addr = "nbGfXLskPh7eM1iG5zz5EfDkkNTo9TRmde"; + char *hash_2_doge = "b4455e7b7b7acb51fb6feba7a2702c42a5100f61f61abafa31851ed6ae076074"; + char *hash_10_doge = "42113bdc65fc2943cf0359ea1a24ced0b6b0b5290db4c63a3329c6601c4616e2"; + + int idx = start_transaction(); + printf("Empty transaction created at index %d.\n", idx); + + if (add_utxo(idx, hash_2_doge, 1)) { + printf("Input of value 2 dogecoin added to the transaction.\n"); + } else { + printf("Error occurred while adding input of value 2 dogecoin.\n"); + return TEEC_ERROR_GENERIC; + } + + if (add_utxo(idx, hash_10_doge, 1)) { + printf("Input of value 10 dogecoin added to the transaction.\n"); + } else { + printf("Error occurred while adding input of value 10 dogecoin.\n"); + return TEEC_ERROR_GENERIC; + } + + if (add_output(idx, external_p2pkh_addr, "5.0")) { + printf("Output of value 5 dogecoin added to the transaction.\n"); + } else { + printf("Error occurred while adding output of value 5 dogecoin.\n"); + return TEEC_ERROR_GENERIC; + } + + int idx2 = store_raw_transaction(finalize_transaction(idx, external_p2pkh_addr, "0.00226", "12", "D5AkTLEwB4eCNcFoZN9pj1TxgkhQiVzt3T")); + if (idx2 > 0) { + printf("Change returned to address %s and finalized unsigned transaction saved at index %d.\n", "D5AkTLEwB4eCNcFoZN9pj1TxgkhQiVzt3T", idx2); + } else { + printf("Error occurred while storing finalized unsigned transaction.\n"); + return TEEC_ERROR_GENERIC; + } + + const char* raw_tx_hex = get_raw_transaction(idx); + strncpy(raw_tx, raw_tx_hex, sizeof(raw_tx) - 1); + raw_tx[sizeof(raw_tx) - 1] = '\0'; + printf("Raw transaction created: %s\n", raw_tx); + printf("Raw transaction length: %zu\n", strlen(raw_tx)); + + if (!password && prompt) { + password = getpass("Enter management password (or press enter for none): "); + if (!password) { + fprintf(stderr, "Failed to read password\n"); + goto exit; + } + } + + if (auth_token == 0) { + auth_token = get_totp_from_yubikey(yk, SLOT_CHAL_HMAC1); + } + printf("Auth token: %u\n", auth_token); + + TEEC_Result res = sign_transaction_ta(&ctx, account, address_index, change_level, auth_token, password, (transaction != NULL) ? transaction : raw_tx, strlen((transaction != NULL) ? transaction : raw_tx) + 1, signed_tx, &signed_tx_len); + if (res != TEEC_SUCCESS) + errx(1, "Failed to sign the transaction"); + else + printf("Transaction signed: %s\n", signed_tx); + } else if (strcmp(cmd, "delegate_key") == 0) { + printf("- Delegate a key\n"); + char delegate_key[HDKEYLEN]; + + if (!delegate_password && prompt) { + delegate_password = getpass("Enter delegate password: "); + if (!delegate_password) { + fprintf(stderr, "Failed to read delegate password\n"); + goto exit; + } + } + + if (!password && prompt) { + password = getpass("Enter management password (or press enter for none): "); + if (!password) { + fprintf(stderr, "Failed to read password\n"); + goto exit; + } + } + + if (auth_token == 0) { + auth_token = get_totp_from_yubikey(yk, SLOT_CHAL_HMAC1); + } + printf("Auth token: %u\n", auth_token); + + TEEC_Result res = delegate_key_ta(&ctx, account, auth_token, delegate_password, password, delegate_key); + if (res != TEEC_SUCCESS) + errx(1, "Failed to delegate the key"); + else + printf("Delegated key generated: %s\n", delegate_key); + } else if (strcmp(cmd, "export_delegate_key") == 0) { + printf("- Export a delegated key\n"); + char exported_key[HDKEYLEN]; + + if (!delegate_password && prompt) { + delegate_password = getpass("Enter delegate password: "); + if (!delegate_password) { + fprintf(stderr, "Failed to read delegate password\n"); + goto exit; + } + } + + TEEC_Result res = export_delegate_key_ta(&ctx, account, delegate_password, exported_key); + if (res != TEEC_SUCCESS) + errx(1, "Failed to export the delegated key"); + else + printf("Delegated key exported: %s\n", exported_key); + } else { + print_usage(); + errx(1, "Unknown command"); + } + + printf("\nWe're done, close and release TEE resources\n"); + terminate_tee_session(&ctx); +exit: + if (mnemonic) { + dogecoin_mem_zero(mnemonic, strlen(mnemonic)); + } + if (shared_secret) { + dogecoin_mem_zero(shared_secret, strlen(shared_secret)); + } + if (password) { + dogecoin_mem_zero(password, strlen(password)); + } + if (delegate_password) { + dogecoin_mem_zero(delegate_password, strlen(delegate_password)); + } + if (yk) { + yk_close_key(yk); + } + yk_release(); + return 0; +} diff --git a/src/optee/nanopi6.h.patch b/src/optee/nanopi6.h.patch new file mode 100644 index 000000000..babf55653 --- /dev/null +++ b/src/optee/nanopi6.h.patch @@ -0,0 +1,24 @@ +--- a/nanopi6.h ++++ b/nanopi6.h +@@ -31,20 +31,10 @@ + "stderr=serial,vidconsole\0" + + #define RKIMG_DET_BOOTDEV \ +- "rkimg_bootdev=" \ +- "if mmc dev 1 && rkimgtest mmc 1; then " \ +- "setenv devtype mmc; setenv devnum 1; echo Boot from SDcard;" \ +- "elif mmc dev 0; then " \ +- "setenv devtype mmc; setenv devnum 0;" \ +- "elif rksfc dev 1; then " \ +- "setenv devtype spinor; setenv devnum 1;" \ +- "fi; \0" ++ "bootcmd=setenv bootdev unknown; setenv kernel_addr_gz 0xa2080000; setenv fdt_addr_r 0x08300000; setenv kernel /boot/Image.gz; setenv fdtfile rk3588-nanopi6-rev01.dtb; for d in 1 0; do test ${bootdev} = unknown && echo .. Looking for ${kernel} in mmc ${d}:5 && test -e mmc ${d}:5 ${kernel} && setenv bootdev ${d} && echo .. Found; done; if test ${bootdev} = unknown; then echo .. Kernel not found; else echo .. Loading kernel; ext2load mmc ${bootdev}:5 ${kernel_addr_gz} ${kernel}; unzip ${kernel_addr_gz} ${kernel_addr_r}; echo .. Loading DTB: mmc ${bootdev}:5 ${fdtfile}; ext2load mmc ${bootdev}:5 ${fdt_addr_r} /boot/${fdtfile}; echo .. Booting kernel; booti ${kernel_addr_r} - ${fdt_addr_r}; fi; \0" + + #define RKIMG_BOOTCOMMAND \ +- "boot_fit;" \ +- "boot_android ${devtype} ${devnum};" \ +- "bootrkp;" \ +- "run distro_bootcmd;" ++ "run bootcmd;" + + #define CONFIG_BOOTCOMMAND RKIMG_BOOTCOMMAND diff --git a/src/optee/qemu.conf.patch b/src/optee/qemu.conf.patch new file mode 100644 index 000000000..083ee3b83 --- /dev/null +++ b/src/optee/qemu.conf.patch @@ -0,0 +1,7 @@ +--- a qemu.conf ++++ b qemu.conf +@@ -13,3 +13,4 @@ + CONFIG_TRUSTED_KEYS=y + CONFIG_ENCRYPTED_KEYS=y + CONFIG_ARM_FFA_TRANSPORT=y ++CONFIG_USB_HID=n diff --git a/src/optee/rk3588-nanopi6-common.dtsi.patch b/src/optee/rk3588-nanopi6-common.dtsi.patch new file mode 100644 index 000000000..4dc030353 --- /dev/null +++ b/src/optee/rk3588-nanopi6-common.dtsi.patch @@ -0,0 +1,27 @@ +--- a/rk3588-nanopi6-common.dtsi ++++ b/rk3588-nanopi6-common.dtsi +@@ -124,6 +124,24 @@ + test-power { + status = "okay"; + }; ++ ++ firmware { ++ optee { ++ compatible = "linaro,optee-tz"; ++ method = "smc"; ++ }; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ optee@8400000 { ++ reg = <0x0 0x8400000 0x0 0x2000000>; ++ no-map; ++ }; ++ }; + }; + + &av1d_mmu { diff --git a/src/optee/ta/Android.mk b/src/optee/ta/Android.mk new file mode 100644 index 000000000..54469e3c3 --- /dev/null +++ b/src/optee/ta/Android.mk @@ -0,0 +1,4 @@ +LOCAL_PATH := $(call my-dir) + +local_module := 62d95dc0-7fc2-4cb3-a7f3-c13ae4e633c4.ta +include $(BUILD_OPTEE_MK) diff --git a/src/optee/ta/Makefile b/src/optee/ta/Makefile new file mode 100644 index 000000000..eba3fa620 --- /dev/null +++ b/src/optee/ta/Makefile @@ -0,0 +1,13 @@ +CFG_TEE_TA_LOG_LEVEL ?= 2 +CFG_TA_OPTEE_CORE_API_COMPAT_1_1=y + +# The UUID for the Trusted Application +BINARY=62d95dc0-7fc2-4cb3-a7f3-c13ae4e633c4 + +-include $(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk + +ifeq ($(wildcard $(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk), ) +clean: + @echo 'Note: $$(TA_DEV_KIT_DIR)/mk/ta_dev_kit.mk not found, cannot clean TA' + @echo 'Note: TA_DEV_KIT_DIR=$(TA_DEV_KIT_DIR)' +endif diff --git a/src/optee/ta/include/libdogecoin_ta.h b/src/optee/ta/include/libdogecoin_ta.h new file mode 100644 index 000000000..fd77b4185 --- /dev/null +++ b/src/optee/ta/include/libdogecoin_ta.h @@ -0,0 +1,166 @@ +/** + * Copyright (c) 2024 edtubbs + * Copyright (c) 2024 The Dogecoin Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Copyright (c) 2017, Linaro Limited + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __LIBDOGECOIN_TA_H__ +#define __LIBDOGECOIN_TA_H__ + +/* UUID of the trusted application */ +/* 62d95dc0-7fc2-4cb3-a7f3-c13ae4e633c4 */ +#define TA_LIBDOGECOIN_UUID \ + { 0x62d95dc0, 0x7fc2, 0x4cb3, \ + { 0xa7, 0xf3, 0xc1, 0x3a, 0xe4, 0xe6, 0x33, 0xc4} } +/* + * TA_LIBDOGECOIN_CMD_READ_RAW - Create and fill a secure storage file + * param[0] (memref) ID used the identify the persistent object + * param[1] (memref) Raw data dumped from the persistent object + * param[2] unused + * param[3] unused + */ +#define TA_LIBDOGECOIN_CMD_READ_RAW 0 + +/* + * TA_LIBDOGECOIN_CMD_WRITE_RAW - Create and fill a secure storage file + * param[0] (memref) ID used the identify the persistent object + * param[1] (memref) Raw data to be written in the persistent object + * param[2] unused + * param[3] unused + */ +#define TA_LIBDOGECOIN_CMD_WRITE_RAW 1 + +/* + * TA_LIBDOGECOIN_CMD_DELETE - Delete a persistent object + * param[0] (memref) ID used the identify the persistent object + * param[1] unused + * param[2] unused + * param[3] unused + */ +#define TA_LIBDOGECOIN_CMD_DELETE 2 + +/* + * TA_LIBDOGECOIN_CMD_GENERATE_SEED - Generate a seed + * param[0] (memref) Seed + * param[1] unused + * param[2] unused + * param[3] unused + */ +#define TA_LIBDOGECOIN_CMD_GENERATE_SEED 3 + +/* + * TA_LIBDOGECOIN_CMD_GENERATE_MNEMONIC - Generate a mnemonic + * param[0] (memref) Mnemonic + * param[1] (memref) Managed credentials (shared secret, password, flags) + * param[2] (memref) Mnemonic input + * param[3] (memref) Entropy size + */ +#define TA_LIBDOGECOIN_CMD_GENERATE_MNEMONIC 4 + +/* + * TA_LIBDOGECOIN_CMD_GENERATE_MASTERKEY - Generate a master key + * param[0] (memref) Master key + * param[1] unused + * param[2] unused + * param[3] unused + */ +#define TA_LIBDOGECOIN_CMD_GENERATE_MASTERKEY 5 + +/* + * TA_LIBDOGECOIN_CMD_SIGN_MESSAGE - Sign a message + * param[0] (memref) Message to be signed + * param[1] (memref) Signature + * param[2] (memref) Key path (account, change level, address index), password (optional) + * param[3] (value) Auth token, password size + */ +#define TA_LIBDOGECOIN_CMD_SIGN_MESSAGE 6 + +/* + * TA_LIBDOGECOIN_CMD_SIGN_TRANSACTION - Sign a transaction + * param[0] (memref) Raw transaction to be signed + * param[1] (memref) Signed transaction + * param[2] (memref) Key path (account, change level, address index), password (optional) + * param[3] (value) Auth token, password size + */ +#define TA_LIBDOGECOIN_CMD_SIGN_TRANSACTION 7 + +/* + * TA_LIBDOGECOIN_CMD_GENERATE_EXTENDED_PUBLIC_KEY - Get an extended public key + * param[0] (memref) Extended public key + * param[1] (memref) Key path (account, change level) + * param[2] (value) Auth token + * param[3] (memref) Password (optional) + */ +#define TA_LIBDOGECOIN_CMD_GENERATE_EXTENDED_PUBLIC_KEY 8 + +/* + * TA_LIBDOGECOIN_CMD_GENERATE_ADDRESS - Get an address + * param[0] (memref) Address + * param[1] (memref) Key path (account, change level, address index) + * param[2] (value) Auth token + * param[3] (memref) Password (optional) + */ +#define TA_LIBDOGECOIN_CMD_GENERATE_ADDRESS 9 + +/* + * TA_LIBDOGECOIN_CMD_DELEGATE_KEY - Delegate a key + * param[0] (memref) Key + * param[1] (memref) Account number (part of the key path) + * param[2] (value) Auth token + * param[3] (memref) Delegate password, password (optional) + */ +#define TA_LIBDOGECOIN_CMD_DELEGATE_KEY 10 + +/* + * TA_LIBDOGECOIN_CMD_EXPORT_DELEGATED_KEY - Export a delegated key + * param[0] (memref) Key + * param[1] (memref) Account number (part of the key path) + * param[2] (unused) + * param[3] (memref) Delegate password + */ +#define TA_LIBDOGECOIN_CMD_EXPORT_DELEGATED_KEY 11 + +#endif /* __LIBDOGECOIN_TA_H__ */ diff --git a/src/optee/ta/libdogecoin_ta.c b/src/optee/ta/libdogecoin_ta.c new file mode 100644 index 000000000..2a4542766 --- /dev/null +++ b/src/optee/ta/libdogecoin_ta.c @@ -0,0 +1,1365 @@ +/** + * Copyright (c) 2024 edtubbs + * Copyright (c) 2024 The Dogecoin Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Copyright (c) 2017, Linaro Limited + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#define MAX_AUTH_TOKEN_SIZE 64 + +// Function prototypes for stubs +void exit(int status); +unsigned long long strtoull(const char *nptr, char **endptr, int base); +int fflush(void *stream); +char *fgets(char *str, int n, void *stream); +void __assert_fail(const char *assertion, const char *file, unsigned int line, const char *function); +void assert(int expression); +int *__errno_location(void); +size_t fread(void *ptr, size_t size, size_t nmemb, void *stream); +void hmac_sha256(const uint8_t* key, const size_t keylen, const uint8_t* msg, const size_t msglen, uint8_t* hmac); +char* strcat(char *dest, const char *src); +char* strncat(char *dest, const char *src, size_t n); +char* strlcat(char *dst, const char *src, size_t size); +long int strtol (const char* str, char** endptr, int base); +int fprintf(void *stream, const char *format, ...); +int fopen(const char *path, const char *mode); +int fclose(void *stream); + +// Function prototypes for the TA +void set_rng(void (*ptr)(void *, uint32_t)); + +#define AUTH_TOKEN_LEN 6 // 6-digit TOTP +uint32_t get_totp(const char* shared_secret, uint64_t timestamp); + +// Stubs for functions that are not supported by OP-TEE +void exit(int status) +{ + EMSG("exit called with status %d", status); + TEE_Panic(status); + while(1); // Add an infinite loop to ensure the function does not return +} + +unsigned long long strtoull(const char *nptr, char **endptr, int base) +{ + const char *pch = nptr; + unsigned long long idx = 0; + + while (*pch >= '0' && *pch <= '9') { + idx = (idx * base) + (*pch - '0'); + pch++; + } + + if (endptr) { + *endptr = (char *)pch; + } + + return idx; +} + +int fflush(void *stream) { + // OP-TEE does not support fflush. This is a stub. + (void)stream; // Avoid unused parameter warning + EMSG("fflush not supported"); + return 0; +} + +void *stdin = NULL; // OP-TEE does not support stdin. This is a stub. + +char *fgets(char *str, int n, void *stream) { + // OP-TEE does not support fgets. This is a stub. + (void)n; // Avoid unused parameter warning + (void)stream; // Avoid unused parameter warning + EMSG("fgets not supported"); + return str; +} + +void __assert_fail(const char *assertion, const char *file, unsigned int line, const char *function) +{ + EMSG("Assertion failed: %s (%s: %s: %u)\n", assertion, file, function, line); + TEE_Panic(0); +} + +void assert(int expression) +{ + if (!expression) { + EMSG("Assertion failed\n"); + TEE_Panic(0); + } +} + +int *__errno_location(void) +{ + static int errno_val = 0; + EMSG("errno_location called"); + return &errno_val; +} + +size_t fread(void *ptr, size_t size, size_t nmemb, void *stream) +{ + // OP-TEE does not support fread. This is a stub. + (void)ptr; // Avoid unused parameter warning + (void)size; // Avoid unused parameter warning + (void)stream; // Avoid unused parameter warning + EMSG("fread not supported"); + return nmemb; +} + +char* strcat(char *dest, const char *src) +{ + // Call strlcat + strlcat(dest, src, strlen(dest) + strlen(src) + 1); + return dest; +} + +char* strncat(char *dest, const char *src, size_t n) +{ + // Call strlcat + strlcat(dest, src, strlen(dest) + n + 1); + return dest; +} + +long int strtol (const char* str, char** endptr, int base) +{ + // Call strtoul + return (long int)strtoul(str, endptr, base); +} + +int fprintf(void *stream, const char *format, ...) +{ + (void)stream; // ignore the stream parameter as EMSG doesn't use it + + va_list ap; + va_start(ap, format); + char buf[256]; // buffer for the formatted message + vsnprintf(buf, sizeof(buf), format, ap); + EMSG("%s", buf); + va_end(ap); + + return 0; // EMSG doesn't return a value, so return 0 +} + +int fopen(const char *path, const char *mode) +{ + // OP-TEE does not support fopen. This is a stub. + (void)path; // Avoid unused parameter warning + (void)mode; // Avoid unused parameter warning + EMSG("Cannot open file %s", path); + EMSG("fopen not supported"); + return 0; +} + +int fclose(void *stream) +{ + // OP-TEE does not support fclose. This is a stub. + (void)stream; // Avoid unused parameter warning + EMSG("fclose not supported"); + return 0; +} + +// TOTP Time step in seconds +#define TOTP_TIME_STEP 30 + +// TOTP Shared secret size in bytes +#define TOTP_SECRET_SIZE 20 + +// Maximum size of managed credentials (shared secret, password) +#define MAX_MANAGED_CREDS_SIZE 1024 + +uint32_t get_totp(const char* shared_secret, uint64_t timestamp) { + uint8_t hmac[SHA1_DIGEST_LENGTH]; + uint8_t time_bytes[8]; + uint32_t totp; + + // Convert the timestamp to a byte array + for (int i = 7; i >= 0; i--) { + time_bytes[i] = timestamp & 0xFF; + timestamp >>= 8; + } + + // Compute the HMAC-SHA1 of the time_bytes using the shared_secret + hmac_sha1((const uint8_t*)utils_hex_to_uint8(shared_secret), TOTP_SECRET_SIZE, time_bytes, sizeof(time_bytes), hmac); + + // Dynamic truncation to get a 4-byte string (31 bits) + int offset = hmac[SHA1_DIGEST_LENGTH - 1] & 0x0F; // Corrected the index to 19 + totp = (hmac[offset] & 0x7F) << 24 + | (hmac[offset + 1] & 0xFF) << 16 + | (hmac[offset + 2] & 0xFF) << 8 + | (hmac[offset + 3] & 0xFF); + + // Return TOTP modulo 10^6 + return totp % 1000000; +} + +/* + * Generate and store a random seed in secure storage + */ +static TEE_Result generate_and_store_seed(uint32_t param_types, TEE_Param params[4]) { + const uint32_t exp_param_types = + TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + + if (param_types != exp_param_types) + return TEE_ERROR_BAD_PARAMETERS; + + TEE_ObjectHandle object; + TEE_Result res; + SEED seed; + uint32_t obj_data_flag = TEE_DATA_FLAG_ACCESS_WRITE | TEE_DATA_FLAG_ACCESS_READ | + TEE_DATA_FLAG_ACCESS_WRITE_META | TEE_DATA_FLAG_OVERWRITE; + + // Generate a random seed + TEE_GenerateRandom(seed, sizeof(seed)); + + // Create a persistent object to store the seed + res = TEE_CreatePersistentObject(TEE_STORAGE_PRIVATE, + "seed_object", sizeof("seed_object"), + obj_data_flag, + TEE_HANDLE_NULL, NULL, 0, &object); + if (res != TEE_SUCCESS) { + EMSG("Failed to create persistent seed object, res=0x%08x", res); + return res; + } + + // Write the seed into the persistent object + res = TEE_WriteObjectData(object, seed, sizeof(seed)); + if (res != TEE_SUCCESS) { + EMSG("Failed to write seed into persistent object, res=0x%08x", res); + TEE_CloseAndDeletePersistentObject1(object); + return res; + } + + TEE_CloseObject(object); + + // Check if the provided buffer is large enough + if (params[0].memref.size < sizeof(seed)) { + params[0].memref.size = sizeof(seed); + return TEE_ERROR_SHORT_BUFFER; + } + + // Optionally, return the seed to the caller + TEE_MemMove(params[0].memref.buffer, seed, sizeof(seed)); + params[0].memref.size = sizeof(seed); + + return TEE_SUCCESS; +} + +static TEE_Result generate_and_store_master_key(uint32_t param_types, TEE_Param params[4]) { + const uint32_t exp_param_types = + TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT, TEE_PARAM_TYPE_NONE, TEE_PARAM_TYPE_NONE, TEE_PARAM_TYPE_NONE); + + if (param_types != exp_param_types) + return TEE_ERROR_BAD_PARAMETERS; + + TEE_ObjectHandle object; + TEE_Result res; + uint32_t obj_data_flag = TEE_DATA_FLAG_ACCESS_WRITE | TEE_DATA_FLAG_ACCESS_READ | + TEE_DATA_FLAG_ACCESS_WRITE_META | TEE_DATA_FLAG_OVERWRITE; + + // Initialize the random number generator + set_rng(&TEE_GenerateRandom); + + dogecoin_ecc_start(); + + char hd_master_privkey[HDKEYLEN]; + char p2pkh_master_pubkey[P2PKHLEN]; + if (!generateHDMasterPubKeypair(hd_master_privkey, p2pkh_master_pubkey, 0)) { + dogecoin_ecc_stop(); + EMSG("Failed to generate master keypair"); + return TEE_ERROR_GENERIC; + } + + dogecoin_ecc_stop(); + + // Create a persistent object to store the serialized HDNode + res = TEE_CreatePersistentObject(TEE_STORAGE_PRIVATE, "hd_master_privkey", sizeof("hd_master_privkey"), + obj_data_flag, TEE_HANDLE_NULL, NULL, 0, &object); + if (res != TEE_SUCCESS) { + EMSG("Failed to create persistent master key object, res=0x%08x", res); + return res; + } + + // Write the serialized HDNode into the persistent object + res = TEE_WriteObjectData(object, hd_master_privkey, HDKEYLEN); + if (res != TEE_SUCCESS) { + EMSG("Failed to write master key into persistent object, res=0x%08x", res); + TEE_CloseAndDeletePersistentObject1(object); + return res; + } + + TEE_CloseObject(object); + + EMSG("Successfully wrote master key into persistent object"); + + // Check if the provided buffer is large enough + if (params[0].memref.size < HDKEYLEN) { + params[0].memref.size = HDKEYLEN; + return TEE_ERROR_SHORT_BUFFER; + } + + // Optionally, return the master key to the caller + TEE_MemMove(params[0].memref.buffer, hd_master_privkey, HDKEYLEN); + params[0].memref.size = HDKEYLEN; + + return TEE_SUCCESS; +} + +static TEE_Result generate_and_store_mnemonic(uint32_t param_types, TEE_Param params[4]) { + const uint32_t exp_param_types = + TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT, TEE_PARAM_TYPE_MEMREF_INPUT, TEE_PARAM_TYPE_MEMREF_INPUT, TEE_PARAM_TYPE_MEMREF_INPUT); + + if (param_types != exp_param_types) { + EMSG("Bad parameter types: 0x%08x", param_types); + return TEE_ERROR_BAD_PARAMETERS; + } + + TEE_ObjectHandle object; + TEE_Result res; + uint32_t obj_data_flag = TEE_DATA_FLAG_ACCESS_WRITE | TEE_DATA_FLAG_ACCESS_READ | + TEE_DATA_FLAG_ACCESS_WRITE_META | TEE_DATA_FLAG_OVERWRITE; + + MNEMONIC mnemonic = {0}; + SEED seed = {0}; + char managed_creds[MAX_MANAGED_CREDS_SIZE] = {0}; + ENTROPY_SIZE entropy_size = {0}; + + EMSG("Starting mnemonic generation"); + + // Initialize the random number generator + set_rng(&TEE_GenerateRandom); + + dogecoin_ecc_start(); + + if (params[2].memref.size > 0) { + // Copy the mnemonic input from params + TEE_MemMove(mnemonic, params[2].memref.buffer, params[2].memref.size); + if (dogecoin_seed_from_mnemonic((const char*)mnemonic, NULL, seed) != 0) { + EMSG("Failed to generate seed from mnemonic"); + dogecoin_ecc_stop(); + return TEE_ERROR_GENERIC; + } + } else { + if (params[3].memref.size > 0) { + // Copy the entropy size from params + TEE_MemMove(entropy_size, params[3].memref.buffer, params[3].memref.size); + + // Generate the BIP-39 mnemonic + int mnemonicResult = generateRandomEnglishMnemonic(entropy_size, mnemonic); + if (mnemonicResult == -1) { + EMSG("Failed to generate mnemonic"); + dogecoin_ecc_stop(); + return TEE_ERROR_GENERIC; + } + } else { + int mnemonicResult = generateRandomEnglishMnemonic("256", mnemonic); + if (mnemonicResult == -1) { + EMSG("Failed to generate mnemonic"); + dogecoin_ecc_stop(); + return TEE_ERROR_GENERIC; + } + } + } + + if (params[1].memref.size > 0 && params[1].memref.size <= MAX_MANAGED_CREDS_SIZE) { + // Copy the managed credentials from params + TEE_MemMove(managed_creds, params[1].memref.buffer, params[1].memref.size); + } else { + EMSG("Managed credentials not provided or invalid"); + dogecoin_ecc_stop(); + return TEE_ERROR_BAD_PARAMETERS; + } + + // Concatenate the mnemonic and managed credentials + char mnemonic_and_creds[MAX_MNEMONIC_SIZE + MAX_MANAGED_CREDS_SIZE] = {0}; + snprintf(mnemonic_and_creds, sizeof(mnemonic_and_creds), "%s,%s", mnemonic, managed_creds); + + dogecoin_ecc_stop(); + + // Create a persistent object to store the mnemonic and managed credentials + res = TEE_CreatePersistentObject(TEE_STORAGE_PRIVATE, "mnemonic", sizeof("mnemonic"), + obj_data_flag, TEE_HANDLE_NULL, NULL, 0, &object); + if (res != TEE_SUCCESS) { + EMSG("Failed to create persistent mnemonic object, res=0x%08x", res); + return res; + } + + // Write the mnemonic and managed credentials into the persistent object + res = TEE_WriteObjectData(object, mnemonic_and_creds, strlen(mnemonic_and_creds)); + if (res != TEE_SUCCESS) { + TEE_CloseAndDeletePersistentObject1(object); + EMSG("Failed to write mnemonic and managed credentials into persistent object, res=0x%08x", res); + return res; + } + + // Hash and log the mnemonic and managed credentials + uint8_t loghash[SHA256_DIGEST_LENGTH]; + + // Compute and log the hash of the mnemonic and managed credentials + sha256_raw((const uint8_t*) mnemonic_and_creds, sizeof(mnemonic_and_creds), loghash); + EMSG("%s", utils_uint8_to_hex((const uint8_t*) loghash, SHA256_DIGEST_LENGTH)); + + TEE_CloseObject(object); + + // Check if the provided buffer is large enough + if (params[0].memref.size < strlen(mnemonic)) { + params[0].memref.size = strlen(mnemonic); + EMSG("Provided buffer is too small for mnemonic"); + return TEE_ERROR_SHORT_BUFFER; + } + + // Return the mnemonic to the caller + TEE_MemMove(params[0].memref.buffer, mnemonic, strlen(mnemonic)); + params[0].memref.size = strlen(mnemonic); + + return TEE_SUCCESS; +} + +static TEE_Result generate_extended_public_key(uint32_t param_types, TEE_Param params[4]) { + const uint32_t exp_param_types = + TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT, TEE_PARAM_TYPE_MEMREF_INPUT, TEE_PARAM_TYPE_VALUE_INPUT, TEE_PARAM_TYPE_MEMREF_INPUT); + + if (param_types != exp_param_types) { + EMSG("Bad parameter types: 0x%08x", param_types); + return TEE_ERROR_BAD_PARAMETERS; + } + + const char* key_path = (const char*)params[1].memref.buffer; + uint32_t auth_token = params[2].value.a; + const char* password = (const char*)params[3].memref.buffer; // Optional password + size_t password_size = password ? strlen(password) + 1 : 0; + + TEE_ObjectHandle object; + TEE_Result res; + char pubkey[HDKEYLEN]; + uint32_t read_bytes; + + char persistent_data[MAX_MNEMONIC_SIZE + MAX_MANAGED_CREDS_SIZE]; + + EMSG("Generating extended public key"); + + res = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE, "mnemonic", sizeof("mnemonic"), + TEE_DATA_FLAG_ACCESS_READ, &object); + if (res != TEE_SUCCESS) { + EMSG("Failed to open persistent mnemonic object, res=0x%08x", res); + return res; + } + + res = TEE_ReadObjectData(object, persistent_data, sizeof(persistent_data), &read_bytes); + if (res != TEE_SUCCESS) { + EMSG("Failed to read mnemonic and managed credentials from persistent object, res=0x%08x", res); + TEE_CloseObject(object); + return res; + } + + TEE_CloseObject(object); + + // Split the persistent data into mnemonic and managed credentials + char* saveptr; + char* mnemonic = strtok_r(persistent_data, ",", &saveptr); + char* shared_secret = strtok_r(NULL, ",", &saveptr); + char* stored_password = strtok_r(NULL, ",", &saveptr); + + // Verify that the password is part of the managed credentials, if supplied + if ((password_size > 0 && password && stored_password && strcmp(password, stored_password)) || + (password_size == 0 && stored_password && strcmp(stored_password, "none") != 0) || + (password_size > 0 && !password && stored_password && strcmp(stored_password, "none") != 0)) { + EMSG("Password verification failed"); + return TEE_ERROR_SECURITY; + } + + // Verify TOTP using the managed credentials + TEE_Time current_time; + TEE_GetREETime(¤t_time); + uint32_t totp = get_totp(shared_secret, current_time.seconds / TOTP_TIME_STEP); + + char totp_str[AUTH_TOKEN_LEN + 1]; + snprintf(totp_str, sizeof(totp_str), "%06u", totp); + + char auth_token_str[AUTH_TOKEN_LEN + 1]; + snprintf(auth_token_str, sizeof(auth_token_str), "%06u", auth_token); + + if (strcmp(totp_str, auth_token_str) != 0) { + EMSG("TOTP verification failed"); + return TEE_ERROR_SECURITY; + } + + SEED seed; + dogecoin_seed_from_mnemonic((const char*)mnemonic, password, seed); + + set_rng(&TEE_GenerateRandom); + + dogecoin_ecc_start(); + + char master_key[HDKEYLEN]; + char key_path_out[KEYPATHMAXLEN]; + getHDRootKeyFromSeed(seed, sizeof(seed), false, master_key); + + deriveBIP44ExtendedPublicKey(master_key, NULL, NULL, NULL, key_path, pubkey, key_path_out); + + dogecoin_ecc_stop(); + + if (params[0].memref.size < strlen(pubkey) + 1) { + params[0].memref.size = strlen(pubkey) + 1; + EMSG("Buffer too small for extended public key"); + return TEE_ERROR_SHORT_BUFFER; + } + + TEE_MemMove(params[0].memref.buffer, pubkey, strlen(pubkey) + 1); + params[0].memref.size = strlen(pubkey) + 1; + + EMSG("Extended public key generated successfully"); + + return TEE_SUCCESS; +} + +static TEE_Result generate_address(uint32_t param_types, TEE_Param params[4]) { + const uint32_t exp_param_types = + TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT, TEE_PARAM_TYPE_MEMREF_INPUT, TEE_PARAM_TYPE_VALUE_INPUT, TEE_PARAM_TYPE_MEMREF_INPUT); + + if (param_types != exp_param_types) { + EMSG("Bad parameter types: 0x%08x", param_types); + return TEE_ERROR_BAD_PARAMETERS; + } + + const char* key_path = (const char*)params[1].memref.buffer; + uint32_t auth_token = params[2].value.a; + const char* password = (const char*)params[3].memref.buffer; // Optional password + size_t password_size = password ? strlen(password) + 1 : 0; + + TEE_ObjectHandle object; + TEE_Result res; + char address[P2PKHLEN]; + uint32_t read_bytes; + + char persistent_data[MAX_MNEMONIC_SIZE + MAX_MANAGED_CREDS_SIZE]; + + EMSG("Generating address"); + + res = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE, "mnemonic", sizeof("mnemonic"), + TEE_DATA_FLAG_ACCESS_READ, &object); + if (res != TEE_SUCCESS) { + EMSG("Failed to open persistent mnemonic object, res=0x%08x", res); + return res; + } + + res = TEE_ReadObjectData(object, persistent_data, sizeof(persistent_data), &read_bytes); + if (res != TEE_SUCCESS) { + EMSG("Failed to read mnemonic and managed credentials from persistent object, res=0x%08x", res); + TEE_CloseObject(object); + return res; + } + + TEE_CloseObject(object); + + // Split the persistent data into mnemonic and managed credentials + char* saveptr; + char* mnemonic = strtok_r(persistent_data, ",", &saveptr); + char* shared_secret = strtok_r(NULL, ",", &saveptr); + char* stored_password = strtok_r(NULL, ",", &saveptr); + + // Verify that the password is part of the managed credentials, if supplied + if ((password_size > 0 && password && stored_password && strcmp(password, stored_password)) || + (password_size == 0 && stored_password && strcmp(stored_password, "none") != 0) || + (password_size > 0 && !password && stored_password && strcmp(stored_password, "none") != 0)) { + EMSG("Password verification failed"); + return TEE_ERROR_SECURITY; + } + + // Verify TOTP using the managed credentials + TEE_Time current_time; + TEE_GetREETime(¤t_time); + uint32_t totp = get_totp(shared_secret, current_time.seconds / TOTP_TIME_STEP); + + char totp_str[AUTH_TOKEN_LEN + 1]; + snprintf(totp_str, sizeof(totp_str), "%06u", totp); + + char auth_token_str[AUTH_TOKEN_LEN + 1]; + snprintf(auth_token_str, sizeof(auth_token_str), "%06u", auth_token); + + if (strcmp(totp_str, auth_token_str) != 0) { + EMSG("TOTP verification failed"); + return TEE_ERROR_SECURITY; + } + + SEED seed; + dogecoin_seed_from_mnemonic((const char*)mnemonic, password, seed); + + set_rng(&TEE_GenerateRandom); + + dogecoin_ecc_start(); + + char master_key[HDKEYLEN]; + getHDRootKeyFromSeed(seed, sizeof(seed), false, master_key); + + getDerivedHDAddressByPath(master_key, key_path, address, false); + + + dogecoin_ecc_stop(); + + if (params[0].memref.size < strlen(address) + 1) { + params[0].memref.size = strlen(address) + 1; + EMSG("Buffer too small for address"); + return TEE_ERROR_SHORT_BUFFER; + } + + TEE_MemMove(params[0].memref.buffer, address, strlen(address) + 1); + params[0].memref.size = strlen(address) + 1; + + EMSG("Address generated successfully"); + + return TEE_SUCCESS; +} + +static TEE_Result sign_message_with_private_key(uint32_t param_types, TEE_Param params[4]) { + const uint32_t exp_param_types = + TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT, TEE_PARAM_TYPE_MEMREF_OUTPUT, TEE_PARAM_TYPE_MEMREF_INPUT, TEE_PARAM_TYPE_VALUE_INPUT); + + if (param_types != exp_param_types) { + EMSG("Bad parameter types: 0x%08x", param_types); + return TEE_ERROR_BAD_PARAMETERS; + } + + char *saveptr; + char *key_path = strtok_r(params[2].memref.buffer, ",", &saveptr); + size_t password_size = params[3].value.b; + char *password = password_size > 0 ? strtok_r(NULL, ",", &saveptr) : NULL; + char *message = (char*)params[0].memref.buffer; + uint32_t auth_token = params[3].value.a; + + TEE_ObjectHandle object; + TEE_Result res; + char *sig = NULL; + uint32_t read_bytes; + + char persistent_data[MAX_MNEMONIC_SIZE + MAX_MANAGED_CREDS_SIZE]; + + EMSG("Signing message"); + + res = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE, "mnemonic", sizeof("mnemonic"), + TEE_DATA_FLAG_ACCESS_READ, &object); + if (res != TEE_SUCCESS) { + EMSG("Failed to open persistent mnemonic object, res=0x%08x", res); + return res; + } + + res = TEE_ReadObjectData(object, persistent_data, sizeof(persistent_data), &read_bytes); + if (res != TEE_SUCCESS) { + EMSG("Failed to read mnemonic and managed credentials from persistent object, res=0x%08x", res); + TEE_CloseObject(object); + return res; + } + + TEE_CloseObject(object); + + // Split the persistent data into mnemonic and managed credentials + char* saveptr2; + char* mnemonic = strtok_r(persistent_data, ",", &saveptr2); + char* shared_secret = strtok_r(NULL, ",", &saveptr2); + char* stored_password = strtok_r(NULL, ",", &saveptr2); + + // Verify that the password is part of the managed credentials, if supplied + if ((password_size > 0 && password && stored_password && strcmp(password, stored_password)) || + (password_size == 0 && stored_password && strcmp(stored_password, "none") != 0) || + (password_size > 0 && !password && stored_password && strcmp(stored_password, "none") != 0)) { + EMSG("Password verification failed"); + return TEE_ERROR_SECURITY; + } + + // Verify TOTP using the managed credentials + TEE_Time current_time; + TEE_GetREETime(¤t_time); + uint32_t totp = get_totp(shared_secret, current_time.seconds / TOTP_TIME_STEP); + + char totp_str[AUTH_TOKEN_LEN + 1]; + snprintf(totp_str, sizeof(totp_str), "%06u", totp); + + char auth_token_str[AUTH_TOKEN_LEN + 1]; + snprintf(auth_token_str, sizeof(auth_token_str), "%06u", auth_token); + + if (strcmp(totp_str, auth_token_str) != 0) { + EMSG("TOTP verification failed"); + return TEE_ERROR_SECURITY; + } + + SEED seed; + dogecoin_seed_from_mnemonic((const char*)mnemonic, password, seed); + + set_rng(&TEE_GenerateRandom); + + dogecoin_ecc_start(); + + char master_key[HDKEYLEN]; + getHDRootKeyFromSeed(seed, sizeof(seed), false, master_key); + + char outaddress[P2PKHLEN]; + char privkeywif[PRIVKEYWIFLEN]; + size_t wiflen = PRIVKEYWIFLEN; + const dogecoin_chainparams* chain = chain_from_b58_prefix(master_key); + + dogecoin_hdnode* hdnode = getHDNodeAndExtKeyByPath(master_key, key_path, outaddress, true); + dogecoin_privkey_encode_wif((const dogecoin_key*)hdnode->private_key, chain, privkeywif, &wiflen); + + sig = sign_message(privkeywif, message); + if (!sig) { + EMSG("Failed to sign message with private key"); + dogecoin_ecc_stop(); + return TEE_ERROR_GENERIC; + } + + if (params[1].memref.size < strlen(sig) + 1) { + params[1].memref.size = strlen(sig) + 1; + dogecoin_free(sig); + dogecoin_ecc_stop(); + return TEE_ERROR_SHORT_BUFFER; + } + + TEE_MemMove(params[1].memref.buffer, sig, strlen(sig) + 1); + params[1].memref.size = strlen(sig) + 1; + + dogecoin_free(sig); + dogecoin_hdnode_free(hdnode); + dogecoin_ecc_stop(); + + EMSG("Message signed successfully"); + + return TEE_SUCCESS; +} + +static TEE_Result sign_transaction_with_private_key(uint32_t param_types, TEE_Param params[4]) { + const uint32_t exp_param_types = + TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT, TEE_PARAM_TYPE_MEMREF_OUTPUT, TEE_PARAM_TYPE_MEMREF_INPUT, TEE_PARAM_TYPE_VALUE_INPUT); + + if (param_types != exp_param_types) { + EMSG("Bad parameter types: 0x%08x", param_types); + return TEE_ERROR_BAD_PARAMETERS; + } + + char *saveptr; + char *key_path = strtok_r(params[2].memref.buffer, ",", &saveptr); + size_t password_size = params[3].value.b; + char *password = password_size > 0 ? strtok_r(NULL, ",", &saveptr) : NULL; + char *raw_tx = (char*)params[0].memref.buffer; + uint32_t auth_token = params[3].value.a; + + TEE_ObjectHandle object; + TEE_Result res; + char *myscriptpubkey = NULL; + char *signed_tx = NULL; + uint32_t read_bytes; + + char persistent_data[MAX_MNEMONIC_SIZE + MAX_MANAGED_CREDS_SIZE]; + + EMSG("Signing transaction"); + + res = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE, "mnemonic", sizeof("mnemonic"), + TEE_DATA_FLAG_ACCESS_READ, &object); + if (res != TEE_SUCCESS) { + EMSG("Failed to open persistent mnemonic object, res=0x%08x", res); + return res; + } + + res = TEE_ReadObjectData(object, persistent_data, sizeof(persistent_data), &read_bytes); + if (res != TEE_SUCCESS) { + EMSG("Failed to read mnemonic and managed credentials from persistent object, res=0x%08x", res); + TEE_CloseObject(object); + return res; + } + + TEE_CloseObject(object); + + // Split the persistent data into mnemonic and managed credentials + char* saveptr2; + char* mnemonic = strtok_r(persistent_data, ",", &saveptr2); + char* shared_secret = strtok_r(NULL, ",", &saveptr2); + char* stored_password = strtok_r(NULL, ",", &saveptr2); + + // Verify that the password is part of the managed credentials, if supplied + if ((password_size > 0 && password && stored_password && strcmp(password, stored_password)) || + (password_size == 0 && stored_password && strcmp(stored_password, "none") != 0) || + (password_size > 0 && !password && stored_password && strcmp(stored_password, "none") != 0)) { + EMSG("Password verification failed"); + return TEE_ERROR_SECURITY; + } + + // Verify TOTP using the managed credentials + TEE_Time current_time; + TEE_GetREETime(¤t_time); + uint32_t totp = get_totp(shared_secret, current_time.seconds / TOTP_TIME_STEP); + + char totp_str[AUTH_TOKEN_LEN + 1]; + snprintf(totp_str, sizeof(totp_str), "%06u", totp); + + char auth_token_str[AUTH_TOKEN_LEN + 1]; + snprintf(auth_token_str, sizeof(auth_token_str), "%06u", auth_token); + + if (strcmp(totp_str, auth_token_str) != 0) { + EMSG("TOTP verification failed"); + return TEE_ERROR_SECURITY; + } + + SEED seed; + dogecoin_seed_from_mnemonic((const char*)mnemonic, password, seed); + + set_rng(&TEE_GenerateRandom); + + dogecoin_ecc_start(); + + char master_key[HDKEYLEN]; + getHDRootKeyFromSeed(seed, sizeof(seed), false, master_key); + + char outaddress[P2PKHLEN]; + char privkeywif[PRIVKEYWIFLEN]; + size_t wiflen = PRIVKEYWIFLEN; + const dogecoin_chainparams* chain = chain_from_b58_prefix(master_key); + dogecoin_hdnode* hdnode = getHDNodeAndExtKeyByPath(master_key, key_path, outaddress, true); + dogecoin_privkey_encode_wif((const dogecoin_key*)hdnode->private_key, chain, privkeywif, &wiflen); + + int idx = store_raw_transaction(raw_tx); + if (idx < 0) { + EMSG("Failed to store raw transaction"); + dogecoin_ecc_stop(); + return TEE_ERROR_GENERIC; + } + + myscriptpubkey = dogecoin_private_key_wif_to_pubkey_hash(privkeywif); + if (!sign_transaction(idx, myscriptpubkey, privkeywif)) { + EMSG("Failed to sign transaction"); + dogecoin_free(myscriptpubkey); + dogecoin_ecc_stop(); + return TEE_ERROR_GENERIC; + } + + signed_tx = get_raw_transaction(idx); + if (!signed_tx) { + EMSG("Failed to get signed transaction"); + dogecoin_free(myscriptpubkey); + dogecoin_ecc_stop(); + return TEE_ERROR_GENERIC; + } + + if (params[1].memref.size < strlen(signed_tx) + 1) { + params[1].memref.size = strlen(signed_tx) + 1; + dogecoin_free(signed_tx); + dogecoin_free(myscriptpubkey); + dogecoin_ecc_stop(); + return TEE_ERROR_SHORT_BUFFER; + } + + TEE_MemMove(params[1].memref.buffer, signed_tx, strlen(signed_tx) + 1); + params[1].memref.size = strlen(signed_tx) + 1; + + dogecoin_free(myscriptpubkey); + dogecoin_hdnode_free(hdnode); + dogecoin_ecc_stop(); + + EMSG("Transaction signed successfully"); + + return TEE_SUCCESS; +} + +static TEE_Result delegate_key(uint32_t param_types, TEE_Param params[4]) { + const uint32_t exp_param_types = + TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT, TEE_PARAM_TYPE_MEMREF_INPUT, TEE_PARAM_TYPE_VALUE_INPUT, TEE_PARAM_TYPE_MEMREF_INPUT); + + if (param_types != exp_param_types) { + EMSG("Bad parameter types: 0x%08x", param_types); + return TEE_ERROR_BAD_PARAMETERS; + } + + TEE_Result res; + TEE_ObjectHandle object; + uint32_t obj_data_flag = TEE_DATA_FLAG_ACCESS_WRITE | TEE_DATA_FLAG_ACCESS_READ | + TEE_DATA_FLAG_ACCESS_WRITE_META | TEE_DATA_FLAG_OVERWRITE; + + char *delegate_key = params[0].memref.buffer; + const char *account = (const char *)params[1].memref.buffer; + uint32_t auth_token = params[2].value.a; + char *delegate_creds = params[3].memref.buffer; + + // Open the mnemonic persistent object + char persistent_data[MAX_MNEMONIC_SIZE + MAX_MANAGED_CREDS_SIZE]; + uint32_t read_bytes; + + EMSG("Delegating key"); + + res = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE, "mnemonic", sizeof("mnemonic"), + TEE_DATA_FLAG_ACCESS_READ, &object); + if (res != TEE_SUCCESS) { + EMSG("Failed to open persistent mnemonic object, res=0x%08x", res); + return res; + } + + res = TEE_ReadObjectData(object, persistent_data, sizeof(persistent_data), &read_bytes); + if (res != TEE_SUCCESS) { + EMSG("Failed to read mnemonic and managed credentials from persistent object, res=0x%08x", res); + TEE_CloseObject(object); + return res; + } + + TEE_CloseObject(object); + + // Split the persistent data into shared_secret, stored_password, and flags + char* saveptr; + char* mnemonic = strtok_r(persistent_data, ",", &saveptr); + char* shared_secret = strtok_r(NULL, ",", &saveptr); + char* stored_password = strtok_r(NULL, ",", &saveptr); + char* flags = strtok_r(NULL, ",", &saveptr); + + // Split the delegate credentials into delegate_password and password + char* saveptr2; + char* delegate_password = strtok_r(delegate_creds, ",", &saveptr2); + char* password = strtok_r(NULL, ",", &saveptr2); + + // Verify flag for delegate key creation + if (!flags || strcmp(flags, "delegate") != 0) { + EMSG("Delegate key creation flag not set"); + return TEE_ERROR_SECURITY; + } + + // Verify that the password is part of the managed credentials, if supplied + if (strcmp(password, stored_password) != 0) { + EMSG("Password verification failed"); + return TEE_ERROR_SECURITY; + } + + // Verify delegate password was provided + if (strcmp(delegate_password, "none") == 0) { + EMSG("Delegate password not provided"); + return TEE_ERROR_BAD_PARAMETERS; + } + + // Verify TOTP using the managed credentials + TEE_Time current_time; + TEE_GetREETime(¤t_time); + uint32_t totp = get_totp(shared_secret, current_time.seconds / TOTP_TIME_STEP); + + char totp_str[AUTH_TOKEN_LEN + 1]; + snprintf(totp_str, sizeof(totp_str), "%06u", totp); + + char auth_token_str[AUTH_TOKEN_LEN + 1]; + snprintf(auth_token_str, sizeof(auth_token_str), "%06u", auth_token); + + if (strcmp(totp_str, auth_token_str) != 0) { + EMSG("TOTP verification failed"); + return TEE_ERROR_SECURITY; + } + + // Store the delegate credentials and mnemonic with the account number as the ID + char delegate_object_data[MAX_MNEMONIC_SIZE + MAX_MANAGED_CREDS_SIZE]; + snprintf(delegate_object_data, sizeof(delegate_object_data), "%s,%s", mnemonic, delegate_creds); + + char delegate_object_id[64]; + snprintf(delegate_object_id, sizeof(delegate_object_id), "delegate_%s", account); + + res = TEE_CreatePersistentObject(TEE_STORAGE_PRIVATE, delegate_object_id, strlen(delegate_object_id), + obj_data_flag, TEE_HANDLE_NULL, delegate_object_data, strlen(delegate_object_data), &object); + if (res != TEE_SUCCESS) { + EMSG("Failed to create persistent delegate object, res=0x%08x", res); + return res; + } + + TEE_CloseObject(object); + + // Derive the private child key + char key_path[BIP44_KEY_PATH_MAX_LENGTH + 1] = ""; + SEED seed; + dogecoin_seed_from_mnemonic((const char*)mnemonic, delegate_password, seed); + + set_rng(&TEE_GenerateRandom); + + dogecoin_ecc_start(); + + char master_key[HDKEYLEN]; + char privkey[HDKEYLEN]; + getHDRootKeyFromSeed(seed, sizeof(seed), false, master_key); + + deriveBIP44ExtendedKey(master_key, NULL, NULL, NULL, account, privkey, key_path); + strncpy(delegate_key, privkey, HDKEYLEN); + + dogecoin_ecc_stop(); + + EMSG("Delegate key generated and stored successfully"); + + return TEE_SUCCESS; +} + +static TEE_Result export_delegate_key(uint32_t param_types, TEE_Param params[4]) { + const uint32_t exp_param_types = + TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT, TEE_PARAM_TYPE_MEMREF_INPUT, TEE_PARAM_TYPE_NONE, TEE_PARAM_TYPE_MEMREF_INPUT); + + if (param_types != exp_param_types) { + EMSG("Bad parameter types: 0x%08x", param_types); + return TEE_ERROR_BAD_PARAMETERS; + } + + TEE_Result res; + TEE_ObjectHandle object; + uint32_t read_bytes; + const char *key_path = (const char *)params[1].memref.buffer; + const char *password = (const char *)params[3].memref.buffer; // Optional password + char *exported_key = params[0].memref.buffer; + + // Open the delegate persistent object + char delegate_object_id[64]; + snprintf(delegate_object_id, sizeof(delegate_object_id), "delegate_%s", key_path); + + EMSG("Exporting delegate key"); + + res = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE, delegate_object_id, strlen(delegate_object_id), + TEE_DATA_FLAG_ACCESS_READ, &object); + if (res != TEE_SUCCESS) { + EMSG("Failed to open persistent delegate object, res=0x%08x", res); + return res; + } + + char delegate_object_data[MAX_MNEMONIC_SIZE + MAX_MANAGED_CREDS_SIZE]; + res = TEE_ReadObjectData(object, delegate_object_data, sizeof(delegate_object_data), &read_bytes); + if (res != TEE_SUCCESS) { + EMSG("Failed to read delegate credentials and mnemonic from persistent object, res=0x%08x", res); + TEE_CloseObject(object); + return res; + } + + TEE_CloseObject(object); + + // Split the delegate object data into mnemonic and stored delegate credentials + char* saveptr; + char* mnemonic = strtok_r(delegate_object_data, ",", &saveptr); + char* stored_delegate_password = strtok_r(NULL, ",", &saveptr); + + // Verify that the password is the same as the stored delegate password + if ((password && stored_delegate_password && strcmp(password, stored_delegate_password)) || + (!password && stored_delegate_password) || + (password && !stored_delegate_password)) { + EMSG("Password verification failed"); + return TEE_ERROR_SECURITY; + } + + SEED seed; + dogecoin_seed_from_mnemonic((const char*)mnemonic, password, seed); + + set_rng(&TEE_GenerateRandom); + + dogecoin_ecc_start(); + + char master_key[HDKEYLEN]; + char privkey[HDKEYLEN]; + char key_path_out[KEYPATHMAXLEN]; + getHDRootKeyFromSeed(seed, sizeof(seed), false, master_key); + + deriveBIP44ExtendedKey(master_key, NULL, NULL, NULL, key_path, privkey, key_path_out); + strncpy(exported_key, privkey, HDKEYLEN); + + dogecoin_ecc_stop(); + + EMSG("Delegate key exported successfully"); + + return TEE_SUCCESS; +} + +static TEE_Result delete_object(uint32_t param_types, TEE_Param params[4]) +{ + const uint32_t exp_param_types = + TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + TEE_ObjectHandle object; + TEE_Result res; + char *obj_id; + size_t obj_id_sz; + + /* + * Safely get the invocation parameters + */ + if (param_types != exp_param_types) + return TEE_ERROR_BAD_PARAMETERS; + + obj_id_sz = params[0].memref.size; + obj_id = TEE_Malloc(obj_id_sz, 0); + if (!obj_id) + return TEE_ERROR_OUT_OF_MEMORY; + + TEE_MemMove(obj_id, params[0].memref.buffer, obj_id_sz); + + /* + * Check object exists and delete it + */ + res = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE, + obj_id, obj_id_sz, + TEE_DATA_FLAG_ACCESS_READ | + TEE_DATA_FLAG_ACCESS_WRITE_META, /* we must be allowed to delete it */ + &object); + if (res != TEE_SUCCESS) { + EMSG("Failed to open persistent object, res=0x%08x", res); + TEE_Free(obj_id); + return res; + } + + TEE_CloseAndDeletePersistentObject1(object); + TEE_Free(obj_id); + + return res; +} + +static TEE_Result create_raw_object(uint32_t param_types, TEE_Param params[4]) +{ + const uint32_t exp_param_types = + TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT, + TEE_PARAM_TYPE_MEMREF_INPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + TEE_ObjectHandle object; + TEE_Result res; + char *obj_id; + size_t obj_id_sz; + char *data; + size_t data_sz; + uint32_t obj_data_flag; + + /* + * Safely get the invocation parameters + */ + if (param_types != exp_param_types) + return TEE_ERROR_BAD_PARAMETERS; + + obj_id_sz = params[0].memref.size; + obj_id = TEE_Malloc(obj_id_sz, 0); + if (!obj_id) + return TEE_ERROR_OUT_OF_MEMORY; + + TEE_MemMove(obj_id, params[0].memref.buffer, obj_id_sz); + + data_sz = params[1].memref.size; + data = TEE_Malloc(data_sz, 0); + if (!data) + return TEE_ERROR_OUT_OF_MEMORY; + TEE_MemMove(data, params[1].memref.buffer, data_sz); + + /* + * Create object in secure storage and fill with data + */ + obj_data_flag = TEE_DATA_FLAG_ACCESS_READ | /* we can later read the oject */ + TEE_DATA_FLAG_ACCESS_WRITE | /* we can later write into the object */ + TEE_DATA_FLAG_ACCESS_WRITE_META | /* we can later destroy or rename the object */ + TEE_DATA_FLAG_OVERWRITE; /* destroy existing object of same ID */ + + res = TEE_CreatePersistentObject(TEE_STORAGE_PRIVATE, + obj_id, obj_id_sz, + obj_data_flag, + TEE_HANDLE_NULL, + NULL, 0, /* we may not fill it right now */ + &object); + if (res != TEE_SUCCESS) { + EMSG("TEE_CreatePersistentObject failed 0x%08x", res); + TEE_Free(obj_id); + TEE_Free(data); + return res; + } + + res = TEE_WriteObjectData(object, data, data_sz); + if (res != TEE_SUCCESS) { + EMSG("TEE_WriteObjectData failed 0x%08x", res); + TEE_CloseAndDeletePersistentObject1(object); + } else { + TEE_CloseObject(object); + } + TEE_Free(obj_id); + TEE_Free(data); + return res; +} + +static TEE_Result read_raw_object(uint32_t param_types, TEE_Param params[4]) +{ + const uint32_t exp_param_types = + TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT, + TEE_PARAM_TYPE_MEMREF_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + TEE_ObjectHandle object; + TEE_ObjectInfo object_info; + TEE_Result res; + uint32_t read_bytes; + char *obj_id; + size_t obj_id_sz; + char *data; + size_t data_sz; + + /* + * Safely get the invocation parameters + */ + if (param_types != exp_param_types) + return TEE_ERROR_BAD_PARAMETERS; + + obj_id_sz = params[0].memref.size; + obj_id = TEE_Malloc(obj_id_sz, 0); + if (!obj_id) + return TEE_ERROR_OUT_OF_MEMORY; + + TEE_MemMove(obj_id, params[0].memref.buffer, obj_id_sz); + + data_sz = params[1].memref.size; + data = TEE_Malloc(data_sz, 0); + if (!data) + return TEE_ERROR_OUT_OF_MEMORY; + + /* + * Check the object exist and can be dumped into output buffer + * then dump it. + */ + res = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE, + obj_id, obj_id_sz, + TEE_DATA_FLAG_ACCESS_READ | + TEE_DATA_FLAG_SHARE_READ, + &object); + if (res != TEE_SUCCESS) { + EMSG("Failed to open persistent object, res=0x%08x", res); + TEE_Free(obj_id); + TEE_Free(data); + return res; + } + + res = TEE_GetObjectInfo1(object, &object_info); + if (res != TEE_SUCCESS) { + EMSG("Failed to create persistent object, res=0x%08x", res); + goto exit; + } + + if (object_info.dataSize > data_sz) { + /* + * Provided buffer is too short. + * Return the expected size together with status "short buffer" + */ + params[1].memref.size = object_info.dataSize; + res = TEE_ERROR_SHORT_BUFFER; + goto exit; + } + + res = TEE_ReadObjectData(object, data, object_info.dataSize, + &read_bytes); + if (res == TEE_SUCCESS) + TEE_MemMove(params[1].memref.buffer, data, read_bytes); + if (res != TEE_SUCCESS || read_bytes != object_info.dataSize) { + EMSG("TEE_ReadObjectData failed 0x%08x, read %" PRIu32 " over %u", + res, read_bytes, object_info.dataSize); + goto exit; + } + + /* Return the number of byte effectively filled */ + params[1].memref.size = read_bytes; +exit: + TEE_CloseObject(object); + TEE_Free(obj_id); + TEE_Free(data); + return res; +} + +TEE_Result TA_CreateEntryPoint(void) +{ + /* Nothing to do */ + return TEE_SUCCESS; +} + +void TA_DestroyEntryPoint(void) +{ + /* Nothing to do */ +} + +TEE_Result TA_OpenSessionEntryPoint(uint32_t __unused param_types, + TEE_Param __unused params[4], + void __unused **session) +{ + /* Nothing to do */ + return TEE_SUCCESS; +} + +void TA_CloseSessionEntryPoint(void __unused *session) +{ + /* Nothing to do */ +} + +TEE_Result TA_InvokeCommandEntryPoint(void __unused *session, + uint32_t command, + uint32_t param_types, + TEE_Param params[4]) +{ + switch (command) { + case TA_LIBDOGECOIN_CMD_WRITE_RAW: + return create_raw_object(param_types, params); + case TA_LIBDOGECOIN_CMD_READ_RAW: + return read_raw_object(param_types, params); + case TA_LIBDOGECOIN_CMD_DELETE: + return delete_object(param_types, params); + case TA_LIBDOGECOIN_CMD_GENERATE_SEED: + return generate_and_store_seed(param_types, params); + case TA_LIBDOGECOIN_CMD_GENERATE_MNEMONIC: + return generate_and_store_mnemonic(param_types, params); + case TA_LIBDOGECOIN_CMD_GENERATE_MASTERKEY: + return generate_and_store_master_key(param_types, params); + case TA_LIBDOGECOIN_CMD_GENERATE_EXTENDED_PUBLIC_KEY: + return generate_extended_public_key(param_types, params); + case TA_LIBDOGECOIN_CMD_GENERATE_ADDRESS: + return generate_address(param_types, params); + case TA_LIBDOGECOIN_CMD_SIGN_MESSAGE: + return sign_message_with_private_key(param_types, params); + case TA_LIBDOGECOIN_CMD_SIGN_TRANSACTION: + return sign_transaction_with_private_key(param_types, params); + case TA_LIBDOGECOIN_CMD_DELEGATE_KEY: + return delegate_key(param_types, params); + case TA_LIBDOGECOIN_CMD_EXPORT_DELEGATED_KEY: + return export_delegate_key(param_types, params); + default: + EMSG("Command ID 0x%x is not supported", command); + return TEE_ERROR_NOT_SUPPORTED; + } +} diff --git a/src/optee/ta/sub.mk b/src/optee/ta/sub.mk new file mode 100644 index 000000000..8cdf73166 --- /dev/null +++ b/src/optee/ta/sub.mk @@ -0,0 +1,4 @@ +global-incdirs-y += include +srcs-y += libdogecoin_ta.c +libnames += dogecoin utils unistring yubikey usb-1.0 ykpers-1 +libdirs += /src/depends/aarch64-linux-gnu/lib diff --git a/src/optee/ta/user_ta_header_defines.h b/src/optee/ta/user_ta_header_defines.h new file mode 100644 index 000000000..730cb51c0 --- /dev/null +++ b/src/optee/ta/user_ta_header_defines.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017, Linaro Limited + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * The name of this file must not be modified + */ + +#ifndef USER_TA_HEADER_DEFINES_H +#define USER_TA_HEADER_DEFINES_H + +#include + +#define TA_UUID TA_LIBDOGECOIN_UUID + +#define TA_FLAGS (TA_FLAG_EXEC_DDR | TA_FLAG_SINGLE_INSTANCE) +#define TA_STACK_SIZE (32 * 1024) +#define TA_DATA_SIZE (256 * 1024) + +#define TA_CURRENT_TA_EXT_PROPERTIES \ + { "gp.ta.description", USER_TA_PROP_TYPE_STRING, \ + "Libdogecoin secure key management" }, \ + { "gp.ta.version", USER_TA_PROP_TYPE_U32, &(const uint32_t){ 0x0010 } } + +#endif /*USER_TA_HEADER_DEFINES_H*/ diff --git a/src/random.c b/src/random.c index 25b8742c7..29e5bbcc1 100644 --- a/src/random.c +++ b/src/random.c @@ -203,10 +203,11 @@ dogecoin_bool dogecoin_random_bytes_internal(uint8_t* buf, uint32_t len, const u errno = ENOSYS; return -1; #else -#ifdef USE_OPENENCLAVE +#if USE_OPENENCLAVE || USE_OPTEE if (rng_ptr != NULL) if (rng_ptr(buf, len) == 0) return true; + #endif (void)update_seed; //unused @@ -221,7 +222,7 @@ dogecoin_bool dogecoin_random_bytes_internal(uint8_t* buf, uint32_t len, const u } #endif -void random_seed(struct fast_random_context* this) +void random_seed(struct fast_random_context* this) { dogecoin_random_init(); uint256 seed; @@ -240,7 +241,7 @@ void fill_byte_buffer(struct fast_random_context* this) this->bytebuf_size = sizeof(this->bytebuf); } -uint256* rand256(struct fast_random_context* this) +uint256* rand256(struct fast_random_context* this) { if (this->bytebuf_size < 32) { fill_byte_buffer(this); @@ -252,7 +253,7 @@ uint256* rand256(struct fast_random_context* this) } /** Generate a random 64-bit integer. */ -uint64_t rand64(struct fast_random_context* this) +uint64_t rand64(struct fast_random_context* this) { if (this->requires_seed) random_seed(this); unsigned char buf[8]; diff --git a/src/seal.c b/src/seal.c index 5e372c317..79f3af832 100644 --- a/src/seal.c +++ b/src/seal.c @@ -59,6 +59,10 @@ #define WINVER 0x0600 #endif +#ifdef USE_YUBIKEY +#include +#endif + /* * Defines */ @@ -115,6 +119,11 @@ #define SEED_SW_FILE_NAME CRYPTO_DIR_PATH BASE_NAME_SEED FILE_NUM_FORMAT SUFFIX_SW #define MASTER_SW_FILE_NAME CRYPTO_DIR_PATH BASE_NAME_MASTER FILE_NUM_FORMAT SUFFIX_SW +// Custom tags for encrypted seeds, mnemonics, and HD nodes +#define SEED_DATA_TAG(file_num) (0x005F1000 + file_num) +#define MNEMONIC_DATA_TAG(file_num) (0x005F2000 + file_num) +#define HDNODE_DATA_TAG(file_num) (0x005F3000 + file_num) + /** * @brief Validates a file number * @@ -127,7 +136,7 @@ dogecoin_bool fileValid (const int file_num) { // Check if the file number is valid - if (file_num < DEFAULT_FILE || file_num > TEST_FILE) + if (file_num < NO_FILE || file_num > TEST_FILE) { return false; } @@ -452,12 +461,13 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_tpm(SEED seed, const in * @param size The size of the seed * @param file_num The file number to encrypt the seed for * @param overwrite Whether or not to overwrite an existing seed - * @param test_password The password to use for testing + * @param encrypted_blob_out The encrypted blob will be stored here + * @param encrypted_blob_size The size of the encrypted blob * @return Returns true if the seed is encrypted successfully, false otherwise. */ -LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, const size_t size, const int file_num, const dogecoin_bool overwrite, const char* test_password) +LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, const size_t size, const int file_num, const dogecoin_bool overwrite, const char* test_password, ENCRYPTED_BLOB* encrypted_blob_out, size_t* encrypted_blob_size) { - +#ifndef USE_OPTEE // OPTEE has no filesystem or console // Validate the input parameters if (seed == NULL) { @@ -473,39 +483,43 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, con } // File operations -#ifdef _WIN32 - if (_wmkdir(CRYPTO_DIR_PATH_W) == -1 && errno != EEXIST) - { - fprintf(stderr, "ERROR: Failed to create directory\n"); - return false; - } - wchar_t fullpath[FILE_PATH_MAX_LEN] = {0}; - swprintf(fullpath, sizeof(fullpath), SEED_SW_FILE_NAME_WIN, file_num); - if (!overwrite && _waccess(fullpath, F_OK) != -1) - { - fprintf(stderr, "ERROR: File already exists. Use overwrite flag to replace it.\n"); - return false; - } - FILE* fp = _wfopen(fullpath, overwrite ? L"wb+" : L"wb"); -#else - if (mkdir(CRYPTO_DIR_PATH, 0777) == -1 && errno != EEXIST) - { - fprintf(stderr, "ERROR: Failed to create directory\n"); - return false; - } - char fullpath[FILE_PATH_MAX_LEN] = {0}; - snprintf(fullpath, sizeof(fullpath), SEED_SW_FILE_NAME, file_num); - if (!overwrite && access(fullpath, F_OK) != -1) + FILE *fp = NULL; + if (file_num != NO_FILE) { - fprintf(stderr, "ERROR: File already exists. Use overwrite flag to replace it.\n"); - return false; - } - FILE* fp = fopen(fullpath, overwrite ? "wb+" : "wb"); -#endif - if (!fp) - { - fprintf(stderr, "ERROR: Failed to open file for writing.\n"); - return false; + #ifdef _WIN32 + if (_wmkdir(CRYPTO_DIR_PATH_W) == -1 && errno != EEXIST) + { + fprintf(stderr, "ERROR: Failed to create directory\n"); + return false; + } + wchar_t fullpath[FILE_PATH_MAX_LEN] = {0}; + swprintf(fullpath, sizeof(fullpath), SEED_SW_FILE_NAME_WIN, file_num); + if (!overwrite && _waccess(fullpath, F_OK) != -1) + { + fprintf(stderr, "ERROR: File already exists. Use overwrite flag to replace it.\n"); + return false; + } + fp = _wfopen(fullpath, overwrite ? L"wb+" : L"wb"); + #else + if (mkdir(CRYPTO_DIR_PATH, 0777) == -1 && errno != EEXIST) + { + fprintf(stderr, "ERROR: Failed to create directory\n"); + return false; + } + char fullpath[FILE_PATH_MAX_LEN] = {0}; + snprintf(fullpath, sizeof(fullpath), SEED_SW_FILE_NAME, file_num); + if (!overwrite && access(fullpath, F_OK) != -1) + { + fprintf(stderr, "ERROR: File already exists. Use overwrite flag to replace it.\n"); + return false; + } + fp = fopen(fullpath, overwrite ? "wb+" : "wb"); + #endif + if (!fp) + { + fprintf(stderr, "ERROR: Failed to open file for writing.\n"); + return false; + } } // Prompt for the password @@ -524,14 +538,14 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, con if (password == NULL) { fprintf(stderr, "ERROR: Failed to read password.\n"); - fclose(fp); + fp ? fclose(fp) : 0; return false; } if (strlen(password) == 0) { fprintf(stderr, "ERROR: Password cannot be empty.\n"); dogecoin_free(password); - fclose(fp); + fp ? fclose(fp) : 0; return false; } @@ -550,7 +564,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, con { fprintf(stderr, "ERROR: Failed to read password.\n"); dogecoin_free(password); - fclose(fp); + fp ? fclose(fp) : 0; return false; } if (strcmp(password, confirm_password) != 0) @@ -560,7 +574,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, con dogecoin_mem_zero(confirm_password, strlen(confirm_password)); dogecoin_free(password); dogecoin_free(confirm_password); - fclose(fp); + fp ? fclose(fp) : 0; return false; } // Clear the confirm password @@ -575,7 +589,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, con fprintf(stderr, "ERROR: Failed to generate random bytes.\n"); dogecoin_mem_zero(password, strlen(password)); dogecoin_free(password); - fclose(fp); + fp ? fclose(fp) : 0; return false; } @@ -600,7 +614,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, con if (!dogecoin_random_bytes(iv, sizeof(iv), 1)) { fprintf(stderr, "ERROR: Failed to generate random bytes.\n"); - fclose(fp); + fp ? fclose(fp) : 0; return false; } @@ -611,7 +625,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, con if (!encrypted_seed) { fprintf(stderr, "ERROR: Memory allocation failed.\n"); - fclose(fp); + fp ? fclose(fp) : 0; return false; } @@ -620,22 +634,48 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, con { fprintf(stderr, "ERROR: AES encryption failed.\n"); dogecoin_free(encrypted_seed); - fclose(fp); + fp ? fclose(fp) : 0; return false; } // Write the IV, salt, verification key hash, and encrypted seed to the file - fwrite(iv, 1, sizeof(iv), fp); - fwrite(salt_encryption, 1, SALT_SIZE, fp); - fwrite(salt_verification, 1, SALT_SIZE, fp); - fwrite(verification_key_hash, 1, sizeof(verification_key_hash), fp); - fwrite(encrypted_seed, 1, encrypted_size, fp); + if (fp != NULL) + { + fwrite(iv, 1, sizeof(iv), fp); + fwrite(salt_encryption, 1, SALT_SIZE, fp); + fwrite(salt_verification, 1, SALT_SIZE, fp); + fwrite(verification_key_hash, 1, sizeof(verification_key_hash), fp); + fwrite(encrypted_seed, 1, encrypted_size, fp); + fp ? fclose(fp) : 0; + } + else if (encrypted_blob_out != NULL && encrypted_blob_size != NULL) + { + memcpy(*encrypted_blob_out, iv, sizeof(iv)); + memcpy(*encrypted_blob_out + sizeof(iv), salt_encryption, SALT_SIZE); + memcpy(*encrypted_blob_out + sizeof(iv) + SALT_SIZE, salt_verification, SALT_SIZE); + memcpy(*encrypted_blob_out + sizeof(iv) + SALT_SIZE + SALT_SIZE, verification_key_hash, sizeof(verification_key_hash)); + memcpy(*encrypted_blob_out + sizeof(iv) + SALT_SIZE + SALT_SIZE + sizeof(verification_key_hash), encrypted_seed, encrypted_size); + *encrypted_blob_size = sizeof(iv) + SALT_SIZE + SALT_SIZE + sizeof(verification_key_hash) + encrypted_size; + } + else if (encrypted_blob_size != NULL) + { + *encrypted_blob_size = sizeof(iv) + SALT_SIZE + SALT_SIZE + sizeof(verification_key_hash) + encrypted_size; + } - // Close the file and free memory - fclose(fp); + // Free the encrypted seed dogecoin_free(encrypted_seed); return true; +#else + (void) seed; + (void) size; + (void) file_num; + (void) overwrite; + (void) test_password; + (void) encrypted_blob_out; + (void) encrypted_blob_size; + return false; +#endif } /** @@ -646,11 +686,13 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw(const SEED seed, con * @param seed Decrypted seed will be stored here * @param file_num The file number for the encrypted seed * @param test_password The password to use for testing + * @param encrypted_blob The encrypted blob to decrypt + * @param encrypted_blob_size The size of the encrypted blob * @return Returns true if the seed is decrypted successfully, false otherwise. */ -LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_sw(SEED seed, const int file_num, const char* test_password) +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_sw(SEED seed, const int file_num, const char* test_password, ENCRYPTED_BLOB encrypted_blob) { - +#ifndef USE_OPTEE // Software decryption is not supported in the OP-TEE environment // Validate the input parameters if (seed == NULL) { @@ -700,7 +742,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_sw(SEED seed, const int snprintf(fullpath, sizeof(fullpath), SEED_SW_FILE_NAME, file_num); FILE* fp = fopen(fullpath, "rb"); #endif - if (!fp) + if (!fp && encrypted_blob == NULL) { fprintf(stderr, "ERROR: Failed to open file for reading.\n"); dogecoin_mem_zero(password, strlen(password)); @@ -708,38 +750,60 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_sw(SEED seed, const int return false; } - // Read the IV from the file + // Read the IV from the file or blob uint8_t iv[AES_IV_SIZE]; - if (fread(iv, 1, sizeof(iv), fp) != sizeof(iv)) + if (fp != NULL) { - fprintf(stderr, "ERROR: Failed to read IV from file.\n"); - fclose(fp); - dogecoin_mem_zero(password, strlen(password)); - dogecoin_free(password); - return false; + if (fread(iv, 1, sizeof(iv), fp) != sizeof(iv)) + { + fprintf(stderr, "ERROR: Failed to read IV from file.\n"); + fclose(fp); + dogecoin_mem_zero(password, strlen(password)); + dogecoin_free(password); + return false; + } + } + else + { + memcpy(iv, encrypted_blob, sizeof(iv)); } - // Read the encryption and verification salts from the file + // Read the encryption and verification salts from the file or blob uint8_t salt_encryption[SALT_SIZE], salt_verification[SALT_SIZE]; - if (fread(salt_encryption, 1, SALT_SIZE, fp) != SALT_SIZE || - fread(salt_verification, 1, SALT_SIZE, fp) != SALT_SIZE) + if (fp != NULL) { - fprintf(stderr, "ERROR: Failed to read salts from file.\n"); - fclose(fp); - dogecoin_mem_zero(password, strlen(password)); - dogecoin_free(password); - return false; + if (fread(salt_encryption, 1, SALT_SIZE, fp) != SALT_SIZE || + fread(salt_verification, 1, SALT_SIZE, fp) != SALT_SIZE) + { + fprintf(stderr, "ERROR: Failed to read salts from file.\n"); + fclose(fp); + dogecoin_mem_zero(password, strlen(password)); + dogecoin_free(password); + return false; + } + } + else + { + memcpy(salt_encryption, encrypted_blob + sizeof(iv), SALT_SIZE); + memcpy(salt_verification, encrypted_blob + sizeof(iv) + SALT_SIZE, SALT_SIZE); } - // Read the verification key hash from the file + // Read the verification key hash from the file or blob uint8_t stored_verification_key_hash[SHA512_DIGEST_LENGTH]; - if (fread(stored_verification_key_hash, 1, sizeof(stored_verification_key_hash), fp) != sizeof(stored_verification_key_hash)) + if (fp != NULL) { - fprintf(stderr, "ERROR: Failed to read verification key hash from file.\n"); - fclose(fp); - dogecoin_mem_zero(password, strlen(password)); - dogecoin_free(password); - return false; + if (fread(stored_verification_key_hash, 1, sizeof(stored_verification_key_hash), fp) != sizeof(stored_verification_key_hash)) + { + fprintf(stderr, "ERROR: Failed to read verification key hash from file.\n"); + fclose(fp); + dogecoin_mem_zero(password, strlen(password)); + dogecoin_free(password); + return false; + } + } + else + { + memcpy(stored_verification_key_hash, encrypted_blob + sizeof(iv) + SALT_SIZE + SALT_SIZE, sizeof(stored_verification_key_hash)); } // Derive the verification key from the password and verification salt using PBKDF2 @@ -768,7 +832,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_sw(SEED seed, const int dogecoin_mem_zero(password, strlen(password)); dogecoin_free(password); - // Read the encrypted seed from the file + // Read the encrypted seed from the file or blob size_t encrypted_size = ENCRYPTED_SEED_SIZE; uint8_t* encrypted_seed = malloc(encrypted_size); if (!encrypted_seed) @@ -778,15 +842,22 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_sw(SEED seed, const int return false; } - if (fread(encrypted_seed, 1, encrypted_size, fp) != encrypted_size) + if (fp != NULL) { - fprintf(stderr, "ERROR: Failed to read encrypted seed from file.\n"); + if (fread(encrypted_seed, 1, encrypted_size, fp) != encrypted_size) + { + fprintf(stderr, "ERROR: Failed to read encrypted seed from file.\n"); + fclose(fp); + dogecoin_free(encrypted_seed); + return false; + } + fclose(fp); - dogecoin_free(encrypted_seed); - return false; } - - fclose(fp); + else + { + memcpy(encrypted_seed, encrypted_blob + sizeof(iv) + SALT_SIZE + SALT_SIZE + sizeof(stored_verification_key_hash), encrypted_size); + } // Decrypt the seed using AES dogecoin_bool padding_used = false; @@ -801,8 +872,16 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_sw(SEED seed, const int } return true; +#else + (void) seed; + (void) file_num; + (void) test_password; + (void) encrypted_blob; + return false; +#endif } + /** * @brief Generate a HD node object with the TPM * @@ -1176,11 +1255,13 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_hdnode_with_tpm(dogecoin_hdnode* * @param file_num The file number of the encrypted mnemonic * @param overwrite Whether or not to overwrite the existing HD node object * @param test_password The password to use for testing + * @param encrypted_blob_out The encrypted HD node will be stored here + * @param encrypted_blob_size The size of the encrypted HD node will be stored here * @return Returns true if the HD node is generated and encrypted successfully, false otherwise. */ -dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, const int file_num, dogecoin_bool overwrite, const char* test_password) +LIBDOGECOIN_API dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, const int file_num, dogecoin_bool overwrite, const char* test_password, ENCRYPTED_BLOB* encrypted_blob_out, size_t* encrypted_blob_size) { - +#ifndef USE_OPTEE // OPTEE has no filesystem or console // Validate the input parameters if (out == NULL) { @@ -1196,39 +1277,43 @@ dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, con } // File operations -#ifdef _WIN32 - if (_wmkdir(CRYPTO_DIR_PATH_W) == -1 && errno != EEXIST) - { - fprintf(stderr, "ERROR: Failed to create directory\n"); - return false; - } - wchar_t fullpath[FILE_PATH_MAX_LEN] = {0}; - swprintf(fullpath, sizeof(fullpath), MASTER_SW_FILE_NAME_WIN, file_num); - if (!overwrite && _waccess(fullpath, F_OK) != -1) + FILE *fp = NULL; + if (file_num != NO_FILE) { - fprintf(stderr, "ERROR: File already exists. Use overwrite flag to replace it.\n"); - return false; - } - FILE* fp = _wfopen(fullpath, overwrite ? L"wb+" : L"wb"); +#ifdef _WIN32 + if (_wmkdir(CRYPTO_DIR_PATH_W) == -1 && errno != EEXIST) + { + fprintf(stderr, "ERROR: Failed to create directory\n"); + return false; + } + wchar_t fullpath[FILE_PATH_MAX_LEN] = {0}; + swprintf(fullpath, sizeof(fullpath), MASTER_SW_FILE_NAME_WIN, file_num); + if (!overwrite && _waccess(fullpath, F_OK) != -1) + { + fprintf(stderr, "ERROR: File already exists. Use overwrite flag to replace it.\n"); + return false; + } + fp = _wfopen(fullpath, overwrite ? L"wb+" : L"wb"); #else - if (mkdir(CRYPTO_DIR_PATH, 0777) == -1 && errno != EEXIST) - { - fprintf(stderr, "ERROR: Failed to create directory\n"); - return false; - } - char fullpath[FILE_PATH_MAX_LEN] = {0}; - snprintf(fullpath, sizeof(fullpath), MASTER_SW_FILE_NAME, file_num); - if (!overwrite && access(fullpath, F_OK) != -1) - { - fprintf(stderr, "ERROR: File already exists. Use overwrite flag to replace it.\n"); - return false; - } - FILE* fp = fopen(fullpath, overwrite ? "wb+" : "wb"); + if (mkdir(CRYPTO_DIR_PATH, 0777) == -1 && errno != EEXIST) + { + fprintf(stderr, "ERROR: Failed to create directory\n"); + return false; + } + char fullpath[FILE_PATH_MAX_LEN] = {0}; + snprintf(fullpath, sizeof(fullpath), MASTER_SW_FILE_NAME, file_num); + if (!overwrite && access(fullpath, F_OK) != -1) + { + fprintf(stderr, "ERROR: File already exists. Use overwrite flag to replace it.\n"); + return false; + } + fp = fopen(fullpath, overwrite ? "wb+" : "wb"); #endif - if (!fp) - { - fprintf(stderr, "ERROR: Failed to open file for writing.\n"); - return false; + if (!fp) + { + fprintf(stderr, "ERROR: Failed to open file for writing.\n"); + return false; + } } // Prompt for the password @@ -1247,18 +1332,17 @@ dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, con if (password == NULL) { fprintf(stderr, "ERROR: Failed to read password.\n"); - fclose(fp); + fp ? fclose(fp) : 0; return false; } if (strlen(password) == 0) { fprintf(stderr, "ERROR: Password cannot be empty.\n"); dogecoin_free(password); - fclose(fp); + fp ? fclose(fp) : 0; return false; } - // Confirm the password char* confirm_password = NULL; #ifdef TEST_PASSWD @@ -1274,7 +1358,7 @@ dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, con { fprintf(stderr, "ERROR: Failed to read password.\n"); dogecoin_free(password); - fclose(fp); + fp ? fclose(fp) : 0; return false; } if (strcmp(password, confirm_password) != 0) @@ -1284,7 +1368,7 @@ dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, con dogecoin_mem_zero(confirm_password, strlen(confirm_password)); dogecoin_free(password); dogecoin_free(confirm_password); - fclose(fp); + fp ? fclose(fp) : 0; return false; } dogecoin_mem_zero(confirm_password, strlen(confirm_password)); @@ -1298,7 +1382,7 @@ dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, con fprintf(stderr, "ERROR: Failed to generate random bytes.\n"); dogecoin_mem_zero(password, strlen(password)); dogecoin_free(password); - fclose(fp); + fp ? fclose(fp) : 0; return false; } @@ -1323,7 +1407,7 @@ dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, con if (!dogecoin_random_bytes(iv, sizeof(iv), 1)) { fprintf(stderr, "ERROR: Failed to generate random bytes.\n"); - fclose(fp); + fp ? fclose(fp) : 0; return false; } @@ -1332,7 +1416,7 @@ dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, con if (!dogecoin_random_bytes(seed, sizeof(seed), 1)) { fprintf(stderr, "ERROR: Failed to generate random bytes.\n"); - fclose(fp); + fp ? fclose(fp) : 0; return false; } dogecoin_hdnode_from_seed(seed, sizeof(seed), out); @@ -1344,7 +1428,7 @@ dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, con if (!encrypted_data) { fprintf(stderr, "ERROR: Memory allocation failed.\n"); - fclose(fp); + fp ? fclose(fp) : 0; return false; } @@ -1353,22 +1437,43 @@ dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, con { fprintf(stderr, "ERROR: AES encryption failed.\n"); dogecoin_free(encrypted_data); - fclose(fp); + fp ? fclose(fp) : 0; return false; } // Write the IV, salts, verification key hash, and encrypted HD node to the file - fwrite(iv, 1, sizeof(iv), fp); - fwrite(salt_encryption, 1, SALT_SIZE, fp); - fwrite(salt_verification, 1, SALT_SIZE, fp); - fwrite(verification_key_hash, 1, sizeof(verification_key_hash), fp); - fwrite(encrypted_data, 1, encrypted_actual_size, fp); + if (fp != NULL) + { + fwrite(iv, 1, sizeof(iv), fp); + fwrite(salt_encryption, 1, SALT_SIZE, fp); + fwrite(salt_verification, 1, SALT_SIZE, fp); + fwrite(verification_key_hash, 1, sizeof(verification_key_hash), fp); + fwrite(encrypted_data, 1, encrypted_actual_size, fp); + fclose(fp); + } + else if (encrypted_blob_out != NULL && encrypted_blob_size != NULL) + { + memcpy(*encrypted_blob_out, iv, sizeof(iv)); + memcpy(*encrypted_blob_out + sizeof(iv), salt_encryption, SALT_SIZE); + memcpy(*encrypted_blob_out + sizeof(iv) + SALT_SIZE, salt_verification, SALT_SIZE); + memcpy(*encrypted_blob_out + sizeof(iv) + SALT_SIZE + SALT_SIZE, verification_key_hash, sizeof(verification_key_hash)); + memcpy(*encrypted_blob_out + sizeof(iv) + SALT_SIZE + SALT_SIZE + sizeof(verification_key_hash), encrypted_data, encrypted_actual_size); + *encrypted_blob_size = sizeof(iv) + SALT_SIZE + SALT_SIZE + sizeof(verification_key_hash) + encrypted_actual_size; + } - // Close the file and free memory - fclose(fp); + // Free the encrypted data dogecoin_free(encrypted_data); return true; +#else + (void) out; + (void) file_num; + (void) overwrite; + (void) test_password; + (void) encrypted_blob_out; + (void) encrypted_blob_size; + return false; +#endif } /** @@ -1379,11 +1484,13 @@ dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw(dogecoin_hdnode* out, con * @param out The decrypted HD node will be stored here * @param file_num The file number for the encrypted HD node * @param test_password The password to use for testing + * @param encrypted_blob The encrypted blob containing the HD node + * @param encrypted_blob_size The size of the encrypted blob * @return Returns true if the HD node is decrypted successfully, false otherwise. */ -dogecoin_bool dogecoin_decrypt_hdnode_with_sw(dogecoin_hdnode* out, const int file_num, const char* test_password) +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_hdnode_with_sw(dogecoin_hdnode* out, const int file_num, const char* test_password, ENCRYPTED_BLOB encrypted_blob) { - +#ifndef USE_OPTEE // OPTEE has no filesystem or console // Validate the input parameters if (out == NULL) { @@ -1424,55 +1531,81 @@ dogecoin_bool dogecoin_decrypt_hdnode_with_sw(dogecoin_hdnode* out, const int fi } // Open the file for reading + FILE *fp = NULL; + if (file_num != NO_FILE) + { #ifdef _WIN32 - wchar_t fullpath[FILE_PATH_MAX_LEN] = {0}; - swprintf(fullpath, sizeof(fullpath), MASTER_SW_FILE_NAME_WIN, file_num); - FILE* fp = _wfopen(fullpath, L"rb"); + wchar_t fullpath[FILE_PATH_MAX_LEN] = {0}; + swprintf(fullpath, sizeof(fullpath), MASTER_SW_FILE_NAME_WIN, file_num); + fp = _wfopen(fullpath, L"rb"); #else - char fullpath[FILE_PATH_MAX_LEN] = {0}; - snprintf(fullpath, sizeof(fullpath), MASTER_SW_FILE_NAME, file_num); - FILE* fp = fopen(fullpath, "rb"); + char fullpath[FILE_PATH_MAX_LEN] = {0}; + snprintf(fullpath, sizeof(fullpath), MASTER_SW_FILE_NAME, file_num); + fp = fopen(fullpath, "rb"); #endif - if (!fp) - { - fprintf(stderr, "ERROR: Failed to open file for reading.\n"); - dogecoin_mem_zero(password, strlen(password)); - dogecoin_free(password); - return false; + if (!fp && encrypted_blob == NULL) + { + fprintf(stderr, "ERROR: Failed to open file for reading.\n"); + dogecoin_mem_zero(password, strlen(password)); + dogecoin_free(password); + return false; + } } - // Read the IV from the file + // Read the IV from the file or blob uint8_t iv[AES_IV_SIZE]; - if (fread(iv, 1, sizeof(iv), fp) != sizeof(iv)) + if (fp != NULL) { - fprintf(stderr, "ERROR: Failed to read IV from file.\n"); - fclose(fp); - dogecoin_mem_zero(password, strlen(password)); - dogecoin_free(password); - return false; + if (fread(iv, 1, sizeof(iv), fp) != sizeof(iv)) + { + fprintf(stderr, "ERROR: Failed to read IV from file.\n"); + fclose(fp); + dogecoin_mem_zero(password, strlen(password)); + dogecoin_free(password); + return false; + } + } + else + { + memcpy(iv, encrypted_blob, sizeof(iv)); } - // Read the encryption and verification salts from the file + // Read the encryption and verification salts from the file or blob uint8_t salt_encryption[SALT_SIZE], salt_verification[SALT_SIZE]; - if (fread(salt_encryption, 1, SALT_SIZE, fp) != SALT_SIZE || - fread(salt_verification, 1, SALT_SIZE, fp) != SALT_SIZE) + if (fp != NULL) { - fprintf(stderr, "ERROR: Failed to read salts from file.\n"); - fclose(fp); - dogecoin_mem_zero(password, strlen(password)); - dogecoin_free(password); - return false; + if (fread(salt_encryption, 1, SALT_SIZE, fp) != SALT_SIZE || + fread(salt_verification, 1, SALT_SIZE, fp) != SALT_SIZE) + { + fprintf(stderr, "ERROR: Failed to read salts from file.\n"); + fclose(fp); + dogecoin_mem_zero(password, strlen(password)); + dogecoin_free(password); + return false; + } + } + else + { + memcpy(salt_encryption, encrypted_blob + sizeof(iv), SALT_SIZE); + memcpy(salt_verification, encrypted_blob + sizeof(iv) + SALT_SIZE, SALT_SIZE); } - // Read the verification key hash from the file + // Read the verification key hash from the file or blob uint8_t stored_verification_key_hash[SHA512_DIGEST_LENGTH]; - if (fread(stored_verification_key_hash, 1, sizeof(stored_verification_key_hash), fp) != sizeof(stored_verification_key_hash)) + if (fp != NULL) { - fprintf(stderr, "ERROR: Failed to read verification key hash from file.\n"); - fclose(fp); - dogecoin_mem_zero(password, strlen(password)); - dogecoin_free(password); - return false; + if (fread(stored_verification_key_hash, 1, sizeof(stored_verification_key_hash), fp) != sizeof(stored_verification_key_hash)) + { + fprintf(stderr, "ERROR: Failed to read verification key hash from file.\n"); + fclose(fp); + dogecoin_mem_zero(password, strlen(password)); + dogecoin_free(password); + return false; + } + } + else + { + memcpy(stored_verification_key_hash, encrypted_blob + sizeof(iv) + SALT_SIZE + SALT_SIZE, sizeof(stored_verification_key_hash)); } // Derive the verification key from the password and verification salt using PBKDF2 @@ -1501,7 +1634,7 @@ dogecoin_bool dogecoin_decrypt_hdnode_with_sw(dogecoin_hdnode* out, const int fi dogecoin_mem_zero(password, strlen(password)); dogecoin_free(password); - // Read the encrypted HD node from the file + // Read the encrypted HD node from the file or blob size_t encrypted_size = sizeof(dogecoin_hdnode); uint8_t* encrypted_data = malloc(encrypted_size); if (!encrypted_data) @@ -1511,15 +1644,22 @@ dogecoin_bool dogecoin_decrypt_hdnode_with_sw(dogecoin_hdnode* out, const int fi return false; } - if (fread(encrypted_data, 1, encrypted_size, fp) != encrypted_size) + if (fp != NULL) { - fprintf(stderr, "ERROR: Failed to read encrypted HD node from file.\n"); + if (fread(encrypted_data, 1, encrypted_size, fp) != encrypted_size) + { + fprintf(stderr, "ERROR: Failed to read encrypted HD node from file.\n"); + fclose(fp); + dogecoin_free(encrypted_data); + return false; + } + fclose(fp); - dogecoin_free(encrypted_data); - return false; } - - fclose(fp); + else + { + memcpy(encrypted_data, encrypted_blob + sizeof(iv) + SALT_SIZE + SALT_SIZE + sizeof(stored_verification_key_hash), encrypted_size); + } // Decrypt the HD node with software decryption (AES) dogecoin_bool padding_used = false; @@ -1533,6 +1673,13 @@ dogecoin_bool dogecoin_decrypt_hdnode_with_sw(dogecoin_hdnode* out, const int fi } return true; +#else + (void) out; + (void) file_num; + (void) test_password; + (void) encrypted_blob; + return false; +#endif } /** @@ -1920,11 +2067,13 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_mnemonic_with_tpm(MNEMONIC mnemon * @param space The mnemonic space to use * @param words The mnemonic words to use * @param test_password The password to use for testing + * @param encrypted_blob_out The encrypted mnemonic will be stored here + * @param encrypted_blob_size The size of the encrypted mnemonic will be stored here * @return Returns true if the mnemonic is generated and encrypted successfully, false otherwise. */ -LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite, const char* lang, const char* space, const char* words, const char* test_password) +LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite, const char* lang, const char* space, const char* words, const char* test_password, ENCRYPTED_BLOB* encrypted_blob_out, size_t* encrypted_blob_size) { - +#ifndef USE_OPTEE // OPTEE has no filesystem or console // Validate the input parameters if (mnemonic == NULL) { @@ -1940,39 +2089,43 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONI } // File operations -#ifdef _WIN32 - if (_wmkdir(CRYPTO_DIR_PATH_W) == -1 && errno != EEXIST) + FILE *fp = NULL; + if (file_num != NO_FILE) { - fprintf(stderr, "ERROR: Failed to create directory\n"); - return false; - } - wchar_t fullpath[FILE_PATH_MAX_LEN] = {0}; - swprintf(fullpath, sizeof(fullpath), MNEMONIC_SW_FILE_NAME_WIN, file_num); - if (!overwrite && _waccess(fullpath, F_OK) != -1) - { - fprintf(stderr, "ERROR: File already exists. Use overwrite flag to replace it.\n"); - return false; - } - FILE* fp = _wfopen(fullpath, overwrite ? L"wb+" : L"wb"); +#ifdef _WIN32 + if (_wmkdir(CRYPTO_DIR_PATH_W) == -1 && errno != EEXIST) + { + fprintf(stderr, "ERROR: Failed to create directory\n"); + return false; + } + wchar_t fullpath[FILE_PATH_MAX_LEN] = {0}; + swprintf(fullpath, sizeof(fullpath), MNEMONIC_SW_FILE_NAME_WIN, file_num); + if (!overwrite && _waccess(fullpath, F_OK) != -1) + { + fprintf(stderr, "ERROR: File already exists. Use overwrite flag to replace it.\n"); + return false; + } + fp = _wfopen(fullpath, overwrite ? L"wb+" : L"wb"); #else - if (mkdir(CRYPTO_DIR_PATH, 0777) == -1 && errno != EEXIST) - { - fprintf(stderr, "ERROR: Failed to create directory\n"); - return false; - } - char fullpath[FILE_PATH_MAX_LEN] = {0}; - snprintf(fullpath, sizeof(fullpath), MNEMONIC_SW_FILE_NAME, file_num); - if (!overwrite && access(fullpath, F_OK) != -1) - { - fprintf(stderr, "ERROR: File already exists. Use overwrite flag to replace it.\n"); - return false; - } - FILE* fp = fopen(fullpath, overwrite ? "wb+" : "wb"); + if (mkdir(CRYPTO_DIR_PATH, 0777) == -1 && errno != EEXIST) + { + fprintf(stderr, "ERROR: Failed to create directory\n"); + return false; + } + char fullpath[FILE_PATH_MAX_LEN] = {0}; + snprintf(fullpath, sizeof(fullpath), MNEMONIC_SW_FILE_NAME, file_num); + if (!overwrite && access(fullpath, F_OK) != -1) + { + fprintf(stderr, "ERROR: File already exists. Use overwrite flag to replace it.\n"); + return false; + } + fp = fopen(fullpath, overwrite ? "wb+" : "wb"); #endif - if (!fp) - { - fprintf(stderr, "ERROR: Failed to open file for writing.\n"); - return false; + if (!fp) + { + fprintf(stderr, "ERROR: Failed to open file for writing.\n"); + return false; + } } // Prompt for the password @@ -1987,18 +2140,18 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONI #else (void) test_password; #endif - password = getpass("Enter password for mnenonic encryption: \n"); + password = getpass("Enter password for mnemonic encryption: \n"); if (password == NULL) { fprintf(stderr, "ERROR: Failed to read password.\n"); - fclose(fp); + fp ? fclose(fp) : 0; return false; } if (strlen(password) == 0) { fprintf(stderr, "ERROR: Password cannot be empty.\n"); dogecoin_free(password); - fclose(fp); + fp ? fclose(fp) : 0; return false; } @@ -2017,7 +2170,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONI { fprintf(stderr, "ERROR: Failed to read password.\n"); dogecoin_free(password); - fclose(fp); + fp ? fclose(fp) : 0; return false; } if (strcmp(password, confirm_password) != 0) @@ -2027,7 +2180,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONI dogecoin_mem_zero(confirm_password, strlen(confirm_password)); dogecoin_free(password); dogecoin_free(confirm_password); - fclose(fp); + fp ? fclose(fp) : 0; return false; } dogecoin_mem_zero(confirm_password, strlen(confirm_password)); @@ -2041,17 +2194,17 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONI fprintf(stderr, "ERROR: Failed to generate random bytes.\n"); dogecoin_mem_zero(password, strlen(password)); dogecoin_free(password); - fclose(fp); + fp ? fclose(fp) : 0; return false; } // Derive encryption key uint8_t encryption_key[AES_KEY_SIZE]; - pbkdf2_hmac_sha256((const uint8_t*)password, strlen(password), salt_encryption, SALT_SIZE, PBKDF2_ITERATIONS, encryption_key, sizeof(encryption_key)); + pbkdf2_hmac_sha256((const uint8_t*)password, strlen(password), salt_encryption, SALT_SIZE, PBKDF2_ITERATIONS, encryption_key, AES_KEY_SIZE); - // Derive verification key + // Derive a separate key for verification uint8_t verification_key[AES_KEY_SIZE]; - pbkdf2_hmac_sha256((const uint8_t*)password, strlen(password), salt_verification, SALT_SIZE, PBKDF2_ITERATIONS, verification_key, sizeof(verification_key)); + pbkdf2_hmac_sha256((const uint8_t*)password, strlen(password), salt_verification, SALT_SIZE, PBKDF2_ITERATIONS, verification_key, AES_KEY_SIZE); // Hash the verification key uint8_t verification_key_hash[SHA512_DIGEST_LENGTH]; @@ -2067,7 +2220,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONI if (mnemonicResult == -1) { fprintf(stderr, "ERROR: Failed to generate mnemonic\n"); - fclose(fp); + fp ? fclose(fp) : 0; return false; } @@ -2076,7 +2229,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONI if (!dogecoin_random_bytes(iv, sizeof(iv), 1)) { fprintf(stderr, "ERROR: Failed to generate random bytes.\n"); - fclose(fp); + fp ? fclose(fp) : 0; return false; } @@ -2086,7 +2239,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONI if (!encrypted_data) { fprintf(stderr, "ERROR: Memory allocation failed.\n"); - fclose(fp); + fp ? fclose(fp) : 0; return false; } memset(encrypted_data, 0, encrypted_size); @@ -2096,37 +2249,63 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw(MNEMONI { fprintf(stderr, "ERROR: AES encryption failed.\n"); dogecoin_free(encrypted_data); - fclose(fp); + fp ? fclose(fp) : 0; return false; } // Write the IV, salts, verification key hash, and encrypted mnemonic to the file - fwrite(iv, 1, sizeof(iv), fp); - fwrite(salt_encryption, 1, SALT_SIZE, fp); - fwrite(salt_verification, 1, SALT_SIZE, fp); - fwrite(verification_key_hash, 1, sizeof(verification_key_hash), fp); - fwrite(encrypted_data, 1, encrypted_actual_size, fp); + if (fp != NULL) + { + fwrite(iv, 1, sizeof(iv), fp); + fwrite(salt_encryption, 1, SALT_SIZE, fp); + fwrite(salt_verification, 1, SALT_SIZE, fp); + fwrite(verification_key_hash, 1, sizeof(verification_key_hash), fp); + fwrite(encrypted_data, 1, encrypted_actual_size, fp); + fclose(fp); + } + else if (encrypted_blob_out != NULL && encrypted_blob_size != NULL) + { + memcpy(*encrypted_blob_out, iv, sizeof(iv)); + memcpy(*encrypted_blob_out + sizeof(iv), salt_encryption, SALT_SIZE); + memcpy(*encrypted_blob_out + sizeof(iv) + SALT_SIZE, salt_verification, SALT_SIZE); + memcpy(*encrypted_blob_out + sizeof(iv) + SALT_SIZE + SALT_SIZE, verification_key_hash, sizeof(verification_key_hash)); + memcpy(*encrypted_blob_out + sizeof(iv) + SALT_SIZE + SALT_SIZE + sizeof(verification_key_hash), encrypted_data, encrypted_actual_size); + *encrypted_blob_size = sizeof(iv) + SALT_SIZE + SALT_SIZE + sizeof(verification_key_hash) + encrypted_actual_size; + } - fclose(fp); + // Free the encrypted data dogecoin_free(encrypted_data); return true; +#else + (void) mnemonic; + (void) file_num; + (void) overwrite; + (void) lang; + (void) space; + (void) words; + (void) test_password; + (void) encrypted_blob_out; + (void) encrypted_blob_size; + return false; +#endif } /** - * @brief Decrypts a BIP-39 mnemonic with software decryption + * @brief Decrypt a BIP-39 mnemonic with software decryption * - * Decrypts a BIP-39 mnemonic previously encrypted with software-based encryption. + * Decrypt a BIP-39 mnemonic previously encrypted with software-based encryption. * * @param mnemonic The decrypted mnemonic will be stored here * @param file_num The file number for the encrypted mnemonic * @param test_password The password to use for testing - * - * @return True if the mnemonic was successfully decrypted, false otherwise + * @param encrypted_blob The encrypted blob containing the mnemonic + * @param encrypted_blob_size The size of the encrypted blob + * @return Returns true if the mnemonic is decrypted successfully, false otherwise. */ -LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_mnemonic_with_sw(MNEMONIC mnemonic, const int file_num, const char* test_password) +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_mnemonic_with_sw(MNEMONIC mnemonic, const int file_num, const char* test_password, ENCRYPTED_BLOB encrypted_blob) { - +#ifndef USE_OPTEE // OPTEE has no filesystem or console // Validate the input parameters if (mnemonic == NULL) { @@ -2167,45 +2346,65 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_mnemonic_with_sw(MNEMONIC mnemoni } // Open the file for reading + FILE *fp = NULL; + if (file_num != NO_FILE) + { #ifdef _WIN32 - wchar_t fullpath[FILE_PATH_MAX_LEN] = {0}; - swprintf(fullpath, sizeof(fullpath), MNEMONIC_SW_FILE_NAME_WIN, file_num); - FILE* fp = _wfopen(fullpath, L"rb"); + wchar_t fullpath[FILE_PATH_MAX_LEN] = {0}; + swprintf(fullpath, sizeof(fullpath), MNEMONIC_SW_FILE_NAME_WIN, file_num); + fp = _wfopen(fullpath, L"rb"); #else - char fullpath[FILE_PATH_MAX_LEN] = {0}; - snprintf(fullpath, sizeof(fullpath), MNEMONIC_SW_FILE_NAME, file_num); - FILE* fp = fopen(fullpath, "rb"); + char fullpath[FILE_PATH_MAX_LEN] = {0}; + snprintf(fullpath, sizeof(fullpath), MNEMONIC_SW_FILE_NAME, file_num); + fp = fopen(fullpath, "rb"); #endif - if (!fp) - { - fprintf(stderr, "ERROR: Failed to open file for reading.\n"); - dogecoin_mem_zero(password, strlen(password)); - dogecoin_free(password); - return false; + if (!fp && encrypted_blob == NULL) + { + fprintf(stderr, "ERROR: Failed to open file for reading.\n"); + dogecoin_mem_zero(password, strlen(password)); + dogecoin_free(password); + return false; + } } - // Read the IV, encryption salt, and verification salt from the file + // Read the IV, encryption salt, and verification salt from the file or blob uint8_t iv[AES_IV_SIZE], salt_encryption[SALT_SIZE], salt_verification[SALT_SIZE]; - if (fread(iv, 1, sizeof(iv), fp) != sizeof(iv) || - fread(salt_encryption, 1, SALT_SIZE, fp) != SALT_SIZE || - fread(salt_verification, 1, SALT_SIZE, fp) != SALT_SIZE) + if (fp != NULL) { - fprintf(stderr, "ERROR: Failed to read data from file.\n"); - fclose(fp); - dogecoin_mem_zero(password, strlen(password)); - dogecoin_free(password); - return false; + if (fread(iv, 1, sizeof(iv), fp) != sizeof(iv) || + fread(salt_encryption, 1, SALT_SIZE, fp) != SALT_SIZE || + fread(salt_verification, 1, SALT_SIZE, fp) != SALT_SIZE) + { + fprintf(stderr, "ERROR: Failed to read data from file.\n"); + fclose(fp); + dogecoin_mem_zero(password, strlen(password)); + dogecoin_free(password); + return false; + } + } + else + { + memcpy(iv, encrypted_blob, sizeof(iv)); + memcpy(salt_encryption, encrypted_blob + sizeof(iv), SALT_SIZE); + memcpy(salt_verification, encrypted_blob + sizeof(iv) + SALT_SIZE, SALT_SIZE); } - // Read the verification key hash from the file + // Read the verification key hash from the file or blob uint8_t stored_verification_key_hash[SHA512_DIGEST_LENGTH]; - if (fread(stored_verification_key_hash, 1, sizeof(stored_verification_key_hash), fp) != sizeof(stored_verification_key_hash)) + if (fp != NULL) { - fprintf(stderr, "ERROR: Failed to read verification key hash from file.\n"); - fclose(fp); - dogecoin_mem_zero(password, strlen(password)); - dogecoin_free(password); - return false; + if (fread(stored_verification_key_hash, 1, sizeof(stored_verification_key_hash), fp) != sizeof(stored_verification_key_hash)) + { + fprintf(stderr, "ERROR: Failed to read verification key hash from file.\n"); + fclose(fp); + dogecoin_mem_zero(password, strlen(password)); + dogecoin_free(password); + return false; + } + } + else + { + memcpy(stored_verification_key_hash, encrypted_blob + sizeof(iv) + SALT_SIZE + SALT_SIZE, sizeof(stored_verification_key_hash)); } // Derive the verification key from the password and verification salt using PBKDF2 @@ -2234,7 +2433,7 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_mnemonic_with_sw(MNEMONIC mnemoni dogecoin_mem_zero(password, strlen(password)); dogecoin_free(password); - // Read the encrypted mnemonic from the file + // Read the encrypted mnemonic from the file or blob size_t encrypted_size = ENCRYPTED_MNEMONIC_SIZE; uint8_t* encrypted_data = malloc(encrypted_size); if (!encrypted_data) @@ -2244,15 +2443,22 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_mnemonic_with_sw(MNEMONIC mnemoni return false; } - if (fread(encrypted_data, 1, encrypted_size, fp) != encrypted_size) + if (fp != NULL) { - fprintf(stderr, "ERROR: Failed to read encrypted mnemonic from file.\n"); + if (fread(encrypted_data, 1, encrypted_size, fp) != encrypted_size) + { + fprintf(stderr, "ERROR: Failed to read encrypted mnemonic from file.\n"); + fclose(fp); + dogecoin_free(encrypted_data); + return false; + } + fclose(fp); - dogecoin_free(encrypted_data); - return false; } - - fclose(fp); + else + { + memcpy(encrypted_data, encrypted_blob + sizeof(iv) + SALT_SIZE + SALT_SIZE + sizeof(stored_verification_key_hash), encrypted_size); + } // Decrypt the mnemonic with AES dogecoin_bool padding_used = false; @@ -2266,6 +2472,13 @@ LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_mnemonic_with_sw(MNEMONIC mnemoni } return true; +#else + (void) mnemonic; + (void) file_num; + (void) test_password; + (void) encrypted_blob; + return false; +#endif } /** @@ -2375,12 +2588,445 @@ LIBDOGECOIN_API dogecoin_bool generateRandomEnglishMnemonicTPM(MNEMONIC mnemonic * @param mnemonic The generated mnemonic will be stored here * @param file_num The file number of the encrypted mnemonic * @param overwrite If true, overwrite the existing mnemonic + * @param encrypted_blob The encrypted blob will be stored here + * @param encrypted_blob_size The size of the encrypted blob will be stored here * * @return True if the mnemonic was successfully generated, false otherwise */ -LIBDOGECOIN_API dogecoin_bool generateRandomEnglishMnemonicSW(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite) +LIBDOGECOIN_API dogecoin_bool generateRandomEnglishMnemonicSW(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite, ENCRYPTED_BLOB* encrypted_blob, size_t* encrypted_blob_size) { // Generate an English mnemonic with software encryption - return dogecoin_generate_mnemonic_encrypt_with_sw(mnemonic, file_num, overwrite, "eng", " ", NULL, NULL); + return dogecoin_generate_mnemonic_encrypt_with_sw(mnemonic, file_num, overwrite, "eng", " ", NULL, NULL, encrypted_blob, encrypted_blob_size); +} + +#ifdef USE_YUBIKEY + +/** + * @brief Encrypt a seed with software encryption and write it to a YubiKey + * + * Encrypts a seed with software encryption and writes it to a YubiKey + * + * @param seed The seed to encrypt + * @param size The size of the seed + * @param file_num The file number of the encrypted seed + * @param overwrite If true, overwrite the existing encrypted seed + * @param test_password The password to use for testing + * + * @return True if the seed was successfully encrypted and written to the YubiKey, false otherwise + */ +LIBDOGECOIN_API dogecoin_bool dogecoin_encrypt_seed_with_sw_to_yubikey(const SEED seed, const size_t size, const int file_num, const dogecoin_bool overwrite, const char* test_password) +{ + ENCRYPTED_BLOB encrypted_blob; + size_t encrypted_blob_size = 0; + dogecoin_bool result = dogecoin_encrypt_seed_with_sw(seed, size, NO_FILE, overwrite, test_password, &encrypted_blob, &encrypted_blob_size); + if (!result) + { + return false; + } + + ykpiv_state *state = NULL; + + // Initialize and connect the YubiKey + if (ykpiv_init(&state, true) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to initialize YubiKey.\n"); + return false; + } + if (ykpiv_connect(state, NULL) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to connect to YubiKey.\n"); + ykpiv_done(state); + return false; + } + + // Prompt for the management key + char* mgm_key = getpass("Enter YubiKey management key: \n"); + if (mgm_key == NULL) + { + fprintf(stderr, "ERROR: Failed to read management key.\n"); + ykpiv_done(state); + return false; + } + + // Decode the management key from hex into binary + unsigned char binary_mgm_key[24]; + size_t binary_mgm_key_len = sizeof(binary_mgm_key); + if (ykpiv_hex_decode(mgm_key, strlen(mgm_key), binary_mgm_key, &binary_mgm_key_len) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to decode management key.\n"); + dogecoin_mem_zero(mgm_key, strlen(mgm_key)); + dogecoin_free(mgm_key); + ykpiv_done(state); + return false; + } + + // Authenticate with the YubiKey using the management key + if (ykpiv_authenticate(state, binary_mgm_key) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to authenticate with YubiKey.\n"); + dogecoin_mem_zero(mgm_key, strlen(mgm_key)); + dogecoin_mem_zero(binary_mgm_key, sizeof(binary_mgm_key)); + dogecoin_free(mgm_key); + ykpiv_done(state); + return false; + } + + dogecoin_mem_zero(mgm_key, strlen(mgm_key)); + dogecoin_free(mgm_key); + + // Write the encrypted blob directly to the YubiKey using the defined tag + if (ykpiv_save_object(state, SEED_DATA_TAG(file_num), encrypted_blob, encrypted_blob_size) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to save encrypted seed to YubiKey.\n"); + ykpiv_done(state); + return false; + } + + ykpiv_done(state); // Clean up YubiKey state after use + return true; +} + +/** + * @brief Decrypt a seed with software decryption from a YubiKey + * + * Decrypts a seed with software decryption from a YubiKey + * + * @param seed The decrypted seed will be stored here + * @param file_num The file number of the encrypted seed + * @param test_password The password to use for testing + * + * @return True if the seed was successfully decrypted, false otherwise + */ +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_seed_with_sw_from_yubikey(SEED seed, const int file_num, const char* test_password) +{ + ykpiv_state *state = NULL; + ENCRYPTED_BLOB encrypted_blob; + unsigned long encrypted_blob_size = sizeof(encrypted_blob); + + // Initialize and connect the YubiKey + if (ykpiv_init(&state, true) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to initialize YubiKey.\n"); + return false; + } + if (ykpiv_connect(state, NULL) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to connect to YubiKey.\n"); + ykpiv_done(state); + return false; + } + + // Verify the PIN to enable reading from the PIN-protected slot + char* pin = getpass("Enter YubiKey PIN: \n"); + int tries; + if (ykpiv_verify(state, pin, &tries) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Incorrect PIN. Tries left: %d\n", tries); + ykpiv_done(state); + dogecoin_free(pin); + return false; + } + + dogecoin_free(pin); + + // Retrieve the encrypted blob from the YubiKey + if (ykpiv_fetch_object(state, SEED_DATA_TAG(file_num), encrypted_blob, &encrypted_blob_size) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to retrieve encrypted seed from YubiKey.\n"); + ykpiv_done(state); + return false; + } + + ykpiv_done(state); // Clean up YubiKey state after use + + // Decrypt the seed using the software decryption function + return dogecoin_decrypt_seed_with_sw(seed, NO_FILE, test_password, encrypted_blob); } + +/** + * @brief Encrypt a BIP-39 mnemonic with software encryption and write it to a YubiKey + * + * Encrypts a BIP-39 mnemonic with software encryption and writes it to a YubiKey + * + * @param mnemonic The mnemonic to encrypt + * @param file_num The file number of the encrypted mnemonic + * @param overwrite If true, overwrite the existing encrypted mnemonic + * @param test_password The password to use for testing + * + * @return True if the mnemonic was successfully encrypted and written to the YubiKey, false otherwise + */ +LIBDOGECOIN_API dogecoin_bool dogecoin_generate_hdnode_encrypt_with_sw_to_yubikey(dogecoin_hdnode* hdnode, const int file_num, const dogecoin_bool overwrite, const char* test_password) +{ + ENCRYPTED_BLOB encrypted_blob; + size_t encrypted_blob_size = 0; + dogecoin_bool result = dogecoin_generate_hdnode_encrypt_with_sw(hdnode, NO_FILE, overwrite, test_password, &encrypted_blob, &encrypted_blob_size); + if (!result) + { + return false; + } + + ykpiv_state *state = NULL; + + // Initialize and connect the YubiKey + if (ykpiv_init(&state, true) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to initialize YubiKey.\n"); + return false; + } + if (ykpiv_connect(state, NULL) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to connect to YubiKey.\n"); + ykpiv_done(state); + return false; + } + + // Prompt for the management key + char* mgm_key = getpass("Enter YubiKey management key: \n"); + if (mgm_key == NULL) + { + fprintf(stderr, "ERROR: Failed to read management key.\n"); + ykpiv_done(state); + return false; + } + + // Decode the management key from hex into binary + unsigned char binary_mgm_key[24]; + size_t binary_mgm_key_len = sizeof(binary_mgm_key); + if (ykpiv_hex_decode(mgm_key, strlen(mgm_key), binary_mgm_key, &binary_mgm_key_len) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to decode management key.\n"); + dogecoin_mem_zero(mgm_key, strlen(mgm_key)); + dogecoin_free(mgm_key); + ykpiv_done(state); + return false; + } + + // Authenticate with the YubiKey using the management key + if (ykpiv_authenticate(state, binary_mgm_key) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to authenticate with YubiKey.\n"); + dogecoin_mem_zero(mgm_key, strlen(mgm_key)); + dogecoin_mem_zero(binary_mgm_key, sizeof(binary_mgm_key)); + dogecoin_free(mgm_key); + ykpiv_done(state); + return false; + } + + dogecoin_mem_zero(mgm_key, strlen(mgm_key)); + dogecoin_free(mgm_key); + + // Write the encrypted blob directly to the YubiKey using the defined tag + if (ykpiv_save_object(state, HDNODE_DATA_TAG(file_num), encrypted_blob, encrypted_blob_size) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to save encrypted HD node to YubiKey.\n"); + ykpiv_done(state); + return false; + } + + ykpiv_done(state); // Clean up YubiKey state after use + return true; +} + +/** + * @brief Decrypt a BIP-39 mnemonic with software decryption from a YubiKey + * + * Decrypts a BIP-39 mnemonic with software decryption from a YubiKey + * + * @param mnemonic The decrypted mnemonic will be stored here + * @param file_num The file number of the encrypted mnemonic + * @param test_password The password to use for testing + * + * @return True if the mnemonic is decrypted successfully, false otherwise + */ +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_hdnode_with_sw_from_yubikey(dogecoin_hdnode* hdnode, const int file_num, const char* test_password) +{ + ykpiv_state *state = NULL; + ENCRYPTED_BLOB encrypted_blob; + unsigned long encrypted_blob_size = sizeof(encrypted_blob); + + // Initialize and connect the YubiKey + if (ykpiv_init(&state, true) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to initialize YubiKey.\n"); + return false; + } + if (ykpiv_connect(state, NULL) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to connect to YubiKey.\n"); + ykpiv_done(state); + return false; + } + + // Verify the PIN to enable reading from the PIN-protected slot + char* pin = getpass("Enter YubiKey PIN: \n"); + int tries; + if (ykpiv_verify(state, pin, &tries) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Incorrect PIN. Tries left: %d\n", tries); + ykpiv_done(state); + dogecoin_free(pin); + return false; + } + + dogecoin_free(pin); + + // Retrieve the encrypted blob from the YubiKey + if (ykpiv_fetch_object(state, HDNODE_DATA_TAG(file_num), encrypted_blob, &encrypted_blob_size) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to retrieve encrypted HD node from YubiKey.\n"); + ykpiv_done(state); + return false; + } + + ykpiv_done(state); // Clean up YubiKey state after use + + // Decrypt the HD node using the software decryption function + return dogecoin_decrypt_hdnode_with_sw(hdnode, NO_FILE, test_password, encrypted_blob); +} + +/** + * @brief Encrypt a BIP-39 mnemonic with software encryption and write it to a YubiKey + * + * Encrypts a BIP-39 mnemonic with software encryption and writes it to a YubiKey + * + * @param mnemonic The mnemonic to encrypt + * @param file_num The file number of the encrypted mnemonic + * @param overwrite If true, overwrite the existing encrypted mnemonic + * @param lang The language of the mnemonic + * @param space The space between words in the mnemonic + * @param words The word list for the mnemonic + * @param test_password The password to use for testing + * + * @return True if the mnemonic was successfully encrypted and written to the YubiKey, false otherwise + */ +LIBDOGECOIN_API dogecoin_bool dogecoin_generate_mnemonic_encrypt_with_sw_to_yubikey(MNEMONIC mnemonic, const int file_num, const dogecoin_bool overwrite, const char* lang, const char* space, const char* words, const char* test_password) +{ + ENCRYPTED_BLOB encrypted_blob; + size_t encrypted_blob_size = 0; + dogecoin_bool result = dogecoin_generate_mnemonic_encrypt_with_sw(mnemonic, NO_FILE, overwrite, lang, space, words, test_password, &encrypted_blob, &encrypted_blob_size); + if (!result) + { + return false; + } + + ykpiv_state *state = NULL; + + // Initialize and connect the YubiKey + if (ykpiv_init(&state, true) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to initialize YubiKey.\n"); + return false; + } + if (ykpiv_connect(state, NULL) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to connect to YubiKey.\n"); + ykpiv_done(state); + return false; + } + + // Prompt for the management key + char* mgm_key = getpass("Enter YubiKey management key: \n"); + if (mgm_key == NULL) + { + fprintf(stderr, "ERROR: Failed to read management key.\n"); + ykpiv_done(state); + return false; + } + + // Decode the management key from hex into binary + unsigned char binary_mgm_key[24]; + size_t binary_mgm_key_len = sizeof(binary_mgm_key); + if (ykpiv_hex_decode(mgm_key, strlen(mgm_key), binary_mgm_key, &binary_mgm_key_len) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to decode management key.\n"); + dogecoin_mem_zero(mgm_key, strlen(mgm_key)); + dogecoin_free(mgm_key); + ykpiv_done(state); + return false; + } + + // Authenticate with the YubiKey using the management key + if (ykpiv_authenticate(state, binary_mgm_key) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to authenticate with YubiKey.\n"); + dogecoin_mem_zero(mgm_key, strlen(mgm_key)); + dogecoin_mem_zero(binary_mgm_key, sizeof(binary_mgm_key)); + dogecoin_free(mgm_key); + ykpiv_done(state); + return false; + } + + dogecoin_mem_zero(mgm_key, strlen(mgm_key)); + dogecoin_free(mgm_key); + + // Write the encrypted blob directly to the YubiKey using the defined tag + if (ykpiv_save_object(state, MNEMONIC_DATA_TAG(file_num), encrypted_blob, encrypted_blob_size) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to save encrypted mnemonic to YubiKey.\n"); + ykpiv_done(state); + return false; + } + + ykpiv_done(state); // Clean up YubiKey state after use + return true; +} + +/** + * @brief Decrypt a BIP-39 mnemonic with software decryption from a YubiKey + * + * Decrypts a BIP-39 mnemonic with software decryption from a YubiKey + * + * @param mnemonic The decrypted mnemonic will be stored here + * @param file_num The file number of the encrypted mnemonic + * @param test_password The password to use for testing + * + * @return True if the mnemonic is decrypted successfully, false otherwise + */ +LIBDOGECOIN_API dogecoin_bool dogecoin_decrypt_mnemonic_with_sw_from_yubikey(MNEMONIC mnemonic, const int file_num, const char* test_password) +{ + ykpiv_state *state = NULL; + ENCRYPTED_BLOB encrypted_blob; + unsigned long encrypted_blob_size = sizeof(encrypted_blob); + + // Initialize and connect the YubiKey + if (ykpiv_init(&state, true) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to initialize YubiKey.\n"); + return false; + } + if (ykpiv_connect(state, NULL) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to connect to YubiKey.\n"); + ykpiv_done(state); + return false; + } + + // Verify the PIN to enable reading from the PIN-protected slot + char* pin = getpass("Enter YubiKey PIN: \n"); + int tries; + if (ykpiv_verify(state, pin, &tries) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Incorrect PIN. Tries left: %d\n", tries); + ykpiv_done(state); + dogecoin_free(pin); + return false; + } + + dogecoin_free(pin); + + // Retrieve the encrypted blob from the YubiKey + if (ykpiv_fetch_object(state, MNEMONIC_DATA_TAG(file_num), encrypted_blob, &encrypted_blob_size) != YKPIV_OK) + { + fprintf(stderr, "ERROR: Failed to retrieve encrypted mnemonic from YubiKey.\n"); + ykpiv_done(state); + return false; + } + + ykpiv_done(state); // Clean up YubiKey state after use + + // Decrypt the mnemonic using the software decryption function + return dogecoin_decrypt_mnemonic_with_sw(mnemonic, NO_FILE, test_password, encrypted_blob); +} + +#endif diff --git a/src/sha2.c b/src/sha2.c index cd5af3ce5..148d2df05 100644 --- a/src/sha2.c +++ b/src/sha2.c @@ -107,6 +107,7 @@ typedef uint64_t sha2_word64; /* Exactly 8 bytes */ /*** SHA-256/384/512 Various Length Definitions ***********************/ /* NOTE: Most of these are in sha2.h */ +#define SHA1_SHORT_BLOCK_LENGTH (SHA1_BLOCK_LENGTH - 8) #define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8) #define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16) @@ -158,11 +159,16 @@ typedef uint64_t sha2_word64; /* Exactly 8 bytes */ #define S32(b, x) (((x) >> (b)) | ((x) << (32 - (b)))) /* 64-bit Rotate-right (used in SHA-384 and SHA-512): */ #define S64(b, x) (((x) >> (b)) | ((x) << (64 - (b)))) +/* 32-bit Rotate-left (used in SHA-1): */ +#define ROTL32(b,x) (((x) << (b)) | ((x) >> (32 - (b)))) /* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */ #define hyperbolic_cosign(x, y, z) (((x) & (y)) ^ ((~(x)) & (z))) #define majority(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +/* Function used in SHA-1: */ +#define Parity(x,y,z) ((x) ^ (y) ^ (z)) + /* Four of six logical functions used in SHA-256: */ #define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x))) #define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x))) @@ -200,6 +206,22 @@ extern void sha512_block_sse(const void *, void *); extern void sha512_block_avx(const void *, void *); /*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/ + +/* Hash constant words K for SHA-1: */ +#define K1_0_TO_19 0x5a827999UL +#define K1_20_TO_39 0x6ed9eba1UL +#define K1_40_TO_59 0x8f1bbcdcUL +#define K1_60_TO_79 0xca62c1d6UL + +/* Initial hash value H for SHA-1: */ +const sha2_word32 sha1_initial_hash_value[SHA1_DIGEST_LENGTH / sizeof(sha2_word32)] = { + 0x67452301UL, + 0xefcdab89UL, + 0x98badcfeUL, + 0x10325476UL, + 0xc3d2e1f0UL +}; + /* Hash constant words K for SHA-256: */ #if !(defined(USE_AVX2) || defined(USE_SSE)) static const sha2_word32 K256[64] = { @@ -376,6 +398,398 @@ static const sha2_word64 sha512_initial_hash_value[8] = { 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL}; +/* + * Constant used by SHA256/384/512_End() functions for converting the + * digest to a readable hexadecimal character string: + */ +static const char *sha2_hex_digits = "0123456789abcdef"; + +/*** SHA-1: ***********************************************************/ +void sha1_Init(sha1_context* context) { + MEMCPY_BCOPY(context->state, sha1_initial_hash_value, SHA1_DIGEST_LENGTH); + MEMSET_BZERO(context->buffer, SHA1_BLOCK_LENGTH); + context->bitcount = 0; +} + +#ifdef SHA2_UNROLL_TRANSFORM + +/* Unrolled SHA-1 round macros: */ + +#define ROUND1_0_TO_15(a,b,c,d,e) \ + (e) = ROTL32(5, (a)) + hyperbolic_cosign((b), (c), (d)) + (e) + \ + K1_0_TO_19 + ( W1[j] = *data++ ); \ + (b) = ROTL32(30, (b)); \ + j++; + +#define ROUND1_16_TO_19(a,b,c,d,e) \ + T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; \ + (e) = ROTL32(5, a) + hyperbolic_cosign(b,c,d) + e + K1_0_TO_19 + ( W1[j&0x0f] = ROTL32(1, T1) ); \ + (b) = ROTL32(30, b); \ + j++; + +#define ROUND1_20_TO_39(a,b,c,d,e) \ + T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; \ + (e) = ROTL32(5, a) + Parity(b,c,d) + e + K1_20_TO_39 + ( W1[j&0x0f] = ROTL32(1, T1) ); \ + (b) = ROTL32(30, b); \ + j++; + +#define ROUND1_40_TO_59(a,b,c,d,e) \ + T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; \ + (e) = ROTL32(5, a) + majority(b,c,d) + e + K1_40_TO_59 + ( W1[j&0x0f] = ROTL32(1, T1) ); \ + (b) = ROTL32(30, b); \ + j++; + +#define ROUND1_60_TO_79(a,b,c,d,e) \ + T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; \ + (e) = ROTL32(5, a) + Parity(b,c,d) + e + K1_60_TO_79 + ( W1[j&0x0f] = ROTL32(1, T1) ); \ + (b) = ROTL32(30, b); \ + j++; + +void sha1_Transform(const sha2_word32* state_in, const sha2_word32* data, sha2_word32* state_out) { + sha2_word32 a = 0, b = 0, c = 0, d = 0, e = 0; + sha2_word32 T1 = 0; + sha2_word32 W1[16] = {0}; + int j = 0; + + /* Initialize registers with the prev. intermediate value */ + a = state_in[0]; + b = state_in[1]; + c = state_in[2]; + d = state_in[3]; + e = state_in[4]; + + j = 0; + + /* Rounds 0 to 15 unrolled: */ + ROUND1_0_TO_15(a,b,c,d,e); + ROUND1_0_TO_15(e,a,b,c,d); + ROUND1_0_TO_15(d,e,a,b,c); + ROUND1_0_TO_15(c,d,e,a,b); + ROUND1_0_TO_15(b,c,d,e,a); + ROUND1_0_TO_15(a,b,c,d,e); + ROUND1_0_TO_15(e,a,b,c,d); + ROUND1_0_TO_15(d,e,a,b,c); + ROUND1_0_TO_15(c,d,e,a,b); + ROUND1_0_TO_15(b,c,d,e,a); + ROUND1_0_TO_15(a,b,c,d,e); + ROUND1_0_TO_15(e,a,b,c,d); + ROUND1_0_TO_15(d,e,a,b,c); + ROUND1_0_TO_15(c,d,e,a,b); + ROUND1_0_TO_15(b,c,d,e,a); + ROUND1_0_TO_15(a,b,c,d,e); + + /* Rounds 16 to 19 unrolled: */ + ROUND1_16_TO_19(e,a,b,c,d); + ROUND1_16_TO_19(d,e,a,b,c); + ROUND1_16_TO_19(c,d,e,a,b); + ROUND1_16_TO_19(b,c,d,e,a); + + /* Rounds 20 to 39 unrolled: */ + ROUND1_20_TO_39(a,b,c,d,e); + ROUND1_20_TO_39(e,a,b,c,d); + ROUND1_20_TO_39(d,e,a,b,c); + ROUND1_20_TO_39(c,d,e,a,b); + ROUND1_20_TO_39(b,c,d,e,a); + ROUND1_20_TO_39(a,b,c,d,e); + ROUND1_20_TO_39(e,a,b,c,d); + ROUND1_20_TO_39(d,e,a,b,c); + ROUND1_20_TO_39(c,d,e,a,b); + ROUND1_20_TO_39(b,c,d,e,a); + ROUND1_20_TO_39(a,b,c,d,e); + ROUND1_20_TO_39(e,a,b,c,d); + ROUND1_20_TO_39(d,e,a,b,c); + ROUND1_20_TO_39(c,d,e,a,b); + ROUND1_20_TO_39(b,c,d,e,a); + ROUND1_20_TO_39(a,b,c,d,e); + ROUND1_20_TO_39(e,a,b,c,d); + ROUND1_20_TO_39(d,e,a,b,c); + ROUND1_20_TO_39(c,d,e,a,b); + ROUND1_20_TO_39(b,c,d,e,a); + + /* Rounds 40 to 59 unrolled: */ + ROUND1_40_TO_59(a,b,c,d,e); + ROUND1_40_TO_59(e,a,b,c,d); + ROUND1_40_TO_59(d,e,a,b,c); + ROUND1_40_TO_59(c,d,e,a,b); + ROUND1_40_TO_59(b,c,d,e,a); + ROUND1_40_TO_59(a,b,c,d,e); + ROUND1_40_TO_59(e,a,b,c,d); + ROUND1_40_TO_59(d,e,a,b,c); + ROUND1_40_TO_59(c,d,e,a,b); + ROUND1_40_TO_59(b,c,d,e,a); + ROUND1_40_TO_59(a,b,c,d,e); + ROUND1_40_TO_59(e,a,b,c,d); + ROUND1_40_TO_59(d,e,a,b,c); + ROUND1_40_TO_59(c,d,e,a,b); + ROUND1_40_TO_59(b,c,d,e,a); + ROUND1_40_TO_59(a,b,c,d,e); + ROUND1_40_TO_59(e,a,b,c,d); + ROUND1_40_TO_59(d,e,a,b,c); + ROUND1_40_TO_59(c,d,e,a,b); + ROUND1_40_TO_59(b,c,d,e,a); + + /* Rounds 60 to 79 unrolled: */ + ROUND1_60_TO_79(a,b,c,d,e); + ROUND1_60_TO_79(e,a,b,c,d); + ROUND1_60_TO_79(d,e,a,b,c); + ROUND1_60_TO_79(c,d,e,a,b); + ROUND1_60_TO_79(b,c,d,e,a); + ROUND1_60_TO_79(a,b,c,d,e); + ROUND1_60_TO_79(e,a,b,c,d); + ROUND1_60_TO_79(d,e,a,b,c); + ROUND1_60_TO_79(c,d,e,a,b); + ROUND1_60_TO_79(b,c,d,e,a); + ROUND1_60_TO_79(a,b,c,d,e); + ROUND1_60_TO_79(e,a,b,c,d); + ROUND1_60_TO_79(d,e,a,b,c); + ROUND1_60_TO_79(c,d,e,a,b); + ROUND1_60_TO_79(b,c,d,e,a); + ROUND1_60_TO_79(a,b,c,d,e); + ROUND1_60_TO_79(e,a,b,c,d); + ROUND1_60_TO_79(d,e,a,b,c); + ROUND1_60_TO_79(c,d,e,a,b); + ROUND1_60_TO_79(b,c,d,e,a); + + /* Compute the current intermediate hash value */ + state_out[0] = state_in[0] + a; + state_out[1] = state_in[1] + b; + state_out[2] = state_in[2] + c; + state_out[3] = state_in[3] + d; + state_out[4] = state_in[4] + e; + + /* Clean up */ + a = b = c = d = e = T1 = 0; +} + +#else /* SHA2_UNROLL_TRANSFORM */ + +void sha1_Transform(const sha2_word32* state_in, const sha2_word32* data, sha2_word32* state_out) { + sha2_word32 a = 0, b = 0, c = 0, d = 0, e = 0; + sha2_word32 T1 = 0; + sha2_word32 W1[16] = {0}; + int j = 0; + + /* Initialize registers with the prev. intermediate value */ + a = state_in[0]; + b = state_in[1]; + c = state_in[2]; + d = state_in[3]; + e = state_in[4]; + j = 0; + do { + T1 = ROTL32(5, a) + hyperbolic_cosign(b, c, d) + e + K1_0_TO_19 + (W1[j] = *data++); + e = d; + d = c; + c = ROTL32(30, b); + b = a; + a = T1; + j++; + } while (j < 16); + + do { + T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; + T1 = ROTL32(5, a) + hyperbolic_cosign(b,c,d) + e + K1_0_TO_19 + (W1[j&0x0f] = ROTL32(1, T1)); + e = d; + d = c; + c = ROTL32(30, b); + b = a; + a = T1; + j++; + } while (j < 20); + + do { + T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; + T1 = ROTL32(5, a) + Parity(b,c,d) + e + K1_20_TO_39 + (W1[j&0x0f] = ROTL32(1, T1)); + e = d; + d = c; + c = ROTL32(30, b); + b = a; + a = T1; + j++; + } while (j < 40); + + do { + T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; + T1 = ROTL32(5, a) + majority(b,c,d) + e + K1_40_TO_59 + (W1[j&0x0f] = ROTL32(1, T1)); + e = d; + d = c; + c = ROTL32(30, b); + b = a; + a = T1; + j++; + } while (j < 60); + + do { + T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; + T1 = ROTL32(5, a) + Parity(b,c,d) + e + K1_60_TO_79 + (W1[j&0x0f] = ROTL32(1, T1)); + e = d; + d = c; + c = ROTL32(30, b); + b = a; + a = T1; + j++; + } while (j < 80); + + + /* Compute the current intermediate hash value */ + state_out[0] = state_in[0] + a; + state_out[1] = state_in[1] + b; + state_out[2] = state_in[2] + c; + state_out[3] = state_in[3] + d; + state_out[4] = state_in[4] + e; + + /* Clean up */ + a = b = c = d = e = T1 = 0; +} + +#endif /* SHA2_UNROLL_TRANSFORM */ + +void sha1_Update(sha1_context* context, const sha2_byte *data, size_t len) { + unsigned int freespace = 0, usedspace = 0; + + if (len == 0) { + /* Calling with no data is valid - we do nothing */ + return; + } + + usedspace = (context->bitcount >> 3) % SHA1_BLOCK_LENGTH; + if (usedspace > 0) { + /* Calculate how much free space is available in the buffer */ + freespace = SHA1_BLOCK_LENGTH - usedspace; + + if (len >= freespace) { + /* Fill the buffer completely and process it */ + MEMCPY_BCOPY(((uint8_t*)context->buffer) + usedspace, data, freespace); + context->bitcount += freespace << 3; + len -= freespace; + data += freespace; +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + for (int j = 0; j < 16; j++) { + REVERSE32(context->buffer[j],context->buffer[j]); + } +#endif + sha1_Transform(context->state, context->buffer, context->state); + } else { + /* The buffer is not yet full */ + MEMCPY_BCOPY(((uint8_t*)context->buffer) + usedspace, data, len); + context->bitcount += len << 3; + /* Clean up: */ + usedspace = freespace = 0; + return; + } + } + while (len >= SHA1_BLOCK_LENGTH) { + /* Process as many complete blocks as we can */ + MEMCPY_BCOPY(context->buffer, data, SHA1_BLOCK_LENGTH); +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + for (int j = 0; j < 16; j++) { + REVERSE32(context->buffer[j],context->buffer[j]); + } +#endif + sha1_Transform(context->state, context->buffer, context->state); + context->bitcount += SHA1_BLOCK_LENGTH << 3; + len -= SHA1_BLOCK_LENGTH; + data += SHA1_BLOCK_LENGTH; + } + if (len > 0) { + /* There's left-overs, so save 'em */ + MEMCPY_BCOPY(context->buffer, data, len); + context->bitcount += len << 3; + } + /* Clean up: */ + usedspace = freespace = 0; +} + +void sha1_Final(sha1_context* context, sha2_byte digest[SHA1_DIGEST_LENGTH]) { + unsigned int usedspace = 0; + + /* If no digest buffer is passed, we don't bother doing this: */ + if (digest != (sha2_byte*)0) { + usedspace = (context->bitcount >> 3) % SHA1_BLOCK_LENGTH; + /* Begin padding with a 1 bit: */ + ((uint8_t*)context->buffer)[usedspace++] = 0x80; + + if (usedspace > SHA1_SHORT_BLOCK_LENGTH) { + MEMSET_BZERO(((uint8_t*)context->buffer) + usedspace, SHA1_BLOCK_LENGTH - usedspace); + +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + for (int j = 0; j < 16; j++) { + REVERSE32(context->buffer[j],context->buffer[j]); + } +#endif + /* Do second-to-last transform: */ + sha1_Transform(context->state, context->buffer, context->state); + + /* And prepare the last transform: */ + usedspace = 0; + } + /* Set-up for the last transform: */ + MEMSET_BZERO(((uint8_t*)context->buffer) + usedspace, SHA1_SHORT_BLOCK_LENGTH - usedspace); + +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + for (int j = 0; j < 14; j++) { + REVERSE32(context->buffer[j],context->buffer[j]); + } +#endif + /* Set the bit count: */ + context->buffer[14] = context->bitcount >> 32; + context->buffer[15] = context->bitcount & 0xffffffff; + + /* Final transform: */ + sha1_Transform(context->state, context->buffer, context->state); + +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert FROM host byte order */ + for (int j = 0; j < 5; j++) { + REVERSE32(context->state[j],context->state[j]); + } +#endif + MEMCPY_BCOPY(digest, context->state, SHA1_DIGEST_LENGTH); + } + + /* Clean up state data: */ + MEMSET_BZERO(context, sizeof(sha1_context)); + usedspace = 0; +} + +char *sha1_End(sha1_context* context, char buffer[SHA1_DIGEST_STRING_LENGTH]) { + sha2_byte digest[SHA1_DIGEST_LENGTH] = {0}, *d = digest; + int i = 0; + + if (buffer != (char*)0) { + sha1_Final(context, digest); + + for (i = 0; i < SHA1_DIGEST_LENGTH; i++) { + *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; + *buffer++ = sha2_hex_digits[*d & 0x0f]; + d++; + } + *buffer = (char)0; + } else { + MEMSET_BZERO(context, sizeof(sha1_context)); + } + MEMSET_BZERO(digest, SHA1_DIGEST_LENGTH); + return buffer; +} + +void sha1_Raw(const sha2_byte* data, size_t len, uint8_t digest[SHA1_DIGEST_LENGTH]) { + sha1_context context = {0}; + sha1_Init(&context); + sha1_Update(&context, data, len); + sha1_Final(&context, digest); +} + +char* sha1_Data(const sha2_byte* data, size_t len, char digest[SHA1_DIGEST_STRING_LENGTH]) { + sha1_context context = {0}; + + sha1_Init(&context); + sha1_Update(&context, data, len); + return sha1_End(&context, digest); +} /*** SHA-256: *********************************************************/ void sha256_init(sha256_context* context) @@ -968,6 +1382,82 @@ void sha512_raw(const sha2_byte* data, size_t len, uint8_t digest[SHA512_DIGEST_ /*** HMAC_SHA-*: *********************************************************/ +void hmac_sha1_Init(hmac_sha1_context *hctx, const uint8_t *key, const uint32_t keylen) +{ + static CONFIDENTIAL uint8_t i_key_pad[SHA1_BLOCK_LENGTH]; + memset(i_key_pad, 0, SHA1_BLOCK_LENGTH); + if (keylen > SHA1_BLOCK_LENGTH) { + sha1_Raw(key, keylen, i_key_pad); + } else { + memcpy(i_key_pad, key, keylen); + } + for (int i = 0; i < SHA1_BLOCK_LENGTH; i++) { + hctx->o_key_pad[i] = i_key_pad[i] ^ 0x5c; + i_key_pad[i] ^= 0x36; + } + sha1_Init(&(hctx->ctx)); + sha1_Update(&(hctx->ctx), i_key_pad, SHA1_BLOCK_LENGTH); + MEMSET_BZERO(i_key_pad, sizeof(i_key_pad)); +} + +void hmac_sha1_Update(hmac_sha1_context *hctx, const uint8_t *msg, const uint32_t msglen) +{ + sha1_Update(&(hctx->ctx), msg, msglen); +} + +void hmac_sha1_Final(hmac_sha1_context *hctx, uint8_t *hmac) +{ + sha1_Final(&(hctx->ctx), hmac); + sha1_Init(&(hctx->ctx)); + sha1_Update(&(hctx->ctx), hctx->o_key_pad, SHA1_BLOCK_LENGTH); + sha1_Update(&(hctx->ctx), hmac, SHA1_DIGEST_LENGTH); + sha1_Final(&(hctx->ctx), hmac); + MEMSET_BZERO(hctx, sizeof(hmac_sha1_context)); +} + +void hmac_sha1(const uint8_t *key, const size_t keylen, const uint8_t *msg, const size_t msglen, uint8_t *hmac) +{ + static CONFIDENTIAL hmac_sha1_context hctx; + hmac_sha1_Init(&hctx, key, keylen); + hmac_sha1_Update(&hctx, msg, msglen); + hmac_sha1_Final(&hctx, hmac); +} + +void hmac_sha1_prepare(const uint8_t *key, const uint32_t keylen, uint32_t *opad_digest, uint32_t *ipad_digest) +{ + static CONFIDENTIAL uint32_t key_pad[SHA1_BLOCK_LENGTH/sizeof(uint32_t)]; + + MEMSET_BZERO(key_pad, sizeof(key_pad)); + if (keylen > SHA1_BLOCK_LENGTH) { + static CONFIDENTIAL sha1_context context; + sha1_Init(&context); + sha1_Update(&context, key, keylen); + sha1_Final(&context, (uint8_t*)key_pad); + } else { + memcpy(key_pad, key, keylen); + } + + /* compute o_key_pad and its digest */ + for (int i = 0; i < SHA1_BLOCK_LENGTH/(int)sizeof(uint32_t); i++) { + uint32_t data; +#if BYTE_ORDER == LITTLE_ENDIAN + REVERSE32(key_pad[i], data); +#else + data = key_pad[i]; +#endif + key_pad[i] = data ^ 0x5c5c5c5c; + } + sha1_Transform(sha1_initial_hash_value, key_pad, opad_digest); + + /* convert o_key_pad to i_key_pad and compute its digest */ + for (int i = 0; i < SHA1_BLOCK_LENGTH/(int)sizeof(uint32_t); i++) { + key_pad[i] = key_pad[i] ^ 0x5c5c5c5c ^ 0x36363636; + } + sha1_Transform(sha1_initial_hash_value, key_pad, ipad_digest); + MEMSET_BZERO(key_pad, sizeof(key_pad)); +} +//============================ + void hmac_sha256_prepare(const uint8_t *key, const uint32_t keylen, uint32_t *opad_digest, uint32_t *ipad_digest) { static CONFIDENTIAL uint32_t key_pad[SHA256_BLOCK_LENGTH / sizeof(uint32_t)]; diff --git a/src/utils.c b/src/utils.c index 5cb3b34ba..612ae13dd 100644 --- a/src/utils.c +++ b/src/utils.c @@ -307,13 +307,13 @@ void utils_uint256_sethex(char* psz, uint8_t* out) { dogecoin_mem_zero(out, sizeof(uint256)); - // skip leading spaces - while (isspace(*psz)) { + // skip leading space + while ((unsigned int)*psz == ' ' || (unsigned int)(*psz - 0x09) < 5u) { psz++; } // skip 0x - if (psz[0] == '0' && tolower(psz[1]) == 'x') { + if (psz[0] == '0' && (psz[1] == 'x' || psz[1] == 'X')) { psz += 2; } @@ -345,7 +345,7 @@ unsigned char* parse_hex(const char* psz) unsigned char* input = dogecoin_uchar_vla(strlen(psz)); while (true) { - while (isspace(*psz)) + while (psz[0] == '0' && (psz[1] == 'x' || psz[1] == 'X')) psz++; signed char c = utils_hex_digit(*psz++); if (c == (signed char)-1) @@ -465,7 +465,9 @@ void* safe_malloc(size_t size) */ void dogecoin_cheap_random_bytes(uint8_t* buf, size_t len) { +#ifndef USE_OPTEE // OPTEE has its own secure random number generator srand(time(NULL)); // insecure +#endif for (size_t i = 0; i < len; i++) { buf[i] = rand(); // weak non secure cryptographic rng } @@ -481,6 +483,7 @@ void* safe_malloc(size_t size) */ void dogecoin_get_default_datadir(cstring* path_out) { +#ifndef USE_OPTEE // OPTEE has no filesystem or console // Windows < Vista: C:\Documents and Settings\Username\Application Data\Bitcoin // Windows >= Vista: C:\Users\Username\AppData\Roaming\Bitcoin // Mac: ~/Library/Application Support/Bitcoin @@ -506,6 +509,9 @@ void dogecoin_get_default_datadir(cstring* path_out) char* posix_home = "/.dogecoin"; cstr_append_buf(path_out, posix_home, strlen(posix_home)); #endif +#endif +#else + (void)path_out; #endif } @@ -520,6 +526,7 @@ void dogecoin_get_default_datadir(cstring* path_out) */ void dogecoin_file_commit(FILE* file) { +#ifndef USE_OPTEE // OPTEE has no filesystem or console fflush(file); // harmless if redundantly called #ifdef WIN32 HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); @@ -528,10 +535,14 @@ void dogecoin_file_commit(FILE* file) fdatasync(fileno(file)); #elif defined(__APPLE__) && defined(F_FULLFSYNC) fcntl(fileno(file), F_FULLFSYNC, 0); +#endif +#else + (void)file; #endif } void print_header(char* filepath) { +#ifndef USE_OPTEE // OPTEE has no filesystem or console if (!filepath) return; char* filename = filepath; FILE* fptr = NULL; @@ -544,14 +555,21 @@ void print_header(char* filepath) { print_image(fptr); fclose(fptr); +#else + (void)filepath; +#endif } void print_image(FILE* fptr) { +#ifndef USE_OPTEE // OPTEE has no filesystem or console char read_string[MAX_LEN]; while (fgets(read_string, sizeof(read_string), fptr) != NULL) printf("%s", read_string); +#else + (void)fptr; +#endif } void print_bits(size_t const size, void const* ptr) @@ -621,15 +639,21 @@ void slice(const char *str, char *result, size_t start, size_t end) } void remove_substr(char *string, char *sub) { +#ifndef USE_OPTEE // OPTEE has no filesystem or console char *match; int len = strlen(sub); while ((match = strstr(string, sub))) { *match = '\0'; strcat(string, match+len); } +#else + (void)string; + (void)sub; +#endif } void replace_last_after_delim(const char *str, char* delim, char* replacement) { +#ifndef USE_OPTEE // OPTEE has no filesystem or console char* tmp = strdup((char*)str); char* new = tmp; char *strptr = strtok(new, delim); @@ -643,6 +667,11 @@ void replace_last_after_delim(const char *str, char* delim, char* replacement) { append((char*)str, replacement); } dogecoin_free(tmp); +#else + (void)str; + (void)delim; + (void)replacement; +#endif } /** @@ -687,8 +716,6 @@ const char* get_build() { */ char *getpass(const char *prompt) { char buffer[MAX_LEN] = {0}; // Initialize to zero - -#ifndef USE_OPENENCLAVE #ifdef _WIN32 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); DWORD mode, count; @@ -712,7 +739,16 @@ char *getpass(const char *prompt) { return NULL; buffer[count] = '\0'; // Ensure null-termination +#elif defined(USE_OPENENCLAVE) || defined(USE_OPTEE) + printf("%s", prompt); + fflush(stdout); + if (!fgets(buffer, sizeof(buffer), stdin)) + return NULL; + + ssize_t nread = strlen(buffer); + if (nread > 0 && buffer[nread-1] == '\n') + buffer[nread-1] = '\0'; // Remove newline character #else struct termios old, new; ssize_t nread; @@ -739,18 +775,6 @@ char *getpass(const char *prompt) { if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old) != 0) return NULL; -#endif - -#else // USE_OPENENCLAVE - printf("%s", prompt); - fflush(stdout); - - if (!fgets(buffer, sizeof(buffer), stdin)) - return NULL; - - ssize_t nread = strlen(buffer); - if (nread > 0 && buffer[nread-1] == '\n') - buffer[nread-1] = '\0'; // Remove newline character #endif return strdup(buffer); } @@ -804,9 +828,10 @@ int integer_length(int x) { int file_copy(char src [], char dest []) { +#ifndef USE_OPTEE // OPTEE has no filesystem or console int c; FILE *stream_read; - FILE *stream_write; + FILE *stream_write; stream_read = fopen (src, "r"); if (stream_read == NULL) @@ -816,13 +841,18 @@ int file_copy(char src [], char dest []) { fclose (stream_read); return -2; - } + } while ((c = fgetc(stream_read)) != EOF) fputc (c, stream_write); fclose (stream_read); fclose (stream_write); return 0; +#else + (void)src; + (void)dest; + return -1; +#endif } unsigned char base64_char[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; diff --git a/src/wallet.c b/src/wallet.c index 98b3128d7..6e1c83f67 100644 --- a/src/wallet.c +++ b/src/wallet.c @@ -446,7 +446,7 @@ dogecoin_wallet* dogecoin_wallet_init(const dogecoin_chainparams* chain, const c } // decrypt encrypted mnemonic with software if (!tpmSuccess) { - if (!dogecoin_decrypt_mnemonic_with_sw(mnemonic, file_num, NULL)) { + if (!dogecoin_decrypt_mnemonic_with_sw(mnemonic, file_num, NULL, NULL)) { showError("Decrypting mnemonic from software failed\n"); dogecoin_wallet_free(wallet); return NULL; @@ -473,7 +473,7 @@ dogecoin_wallet* dogecoin_wallet_init(const dogecoin_chainparams* chain, const c } // decrypt encrypted master key with software if (!tpmSuccess) { - if (!dogecoin_decrypt_hdnode_with_sw(&node, file_num, NULL)) { + if (!dogecoin_decrypt_hdnode_with_sw(&node, file_num, NULL, NULL)) { showError("Decrypting master key from software failed\n"); dogecoin_wallet_free(wallet); return NULL; @@ -505,7 +505,7 @@ dogecoin_wallet* dogecoin_wallet_init(const dogecoin_chainparams* chain, const c if (fgets(buffer, sizeof(buffer), stdin) != NULL) { bool overwrite = (buffer[0] == 'Y' || buffer[0] == 'y'); // encrypt seed for storage with software - if (dogecoin_encrypt_seed_with_sw(seed, sizeof(seed), file_id, overwrite, NULL) == false) { + if (dogecoin_encrypt_seed_with_sw(seed, sizeof(seed), file_id, overwrite, NULL, NULL, NULL)) { dogecoin_wallet_free(wallet); return NULL; } diff --git a/test/bip44_tests.c b/test/bip44_tests.c index 47800b8af..c64e00673 100644 --- a/test/bip44_tests.c +++ b/test/bip44_tests.c @@ -25,10 +25,10 @@ void test_bip44() dogecoin_hdnode bip44_change_key; dogecoin_hdnode bip44_address_key; uint32_t account = BIP44_FIRST_ACCOUNT_NODE; - size_t size; char keypath[BIP44_KEY_PATH_MAX_LENGTH + 1] = ""; /* generate mnemonic(s) */ + size_t size; char *words = NULL; char *entropy = "00000000000000000000000000000000"; char* entropy_out = NULL; @@ -59,7 +59,6 @@ void test_bip44() dogecoin_seed_from_mnemonic (words, "", seed); u_assert_mem_eq(seed, test_seed, MAX_SEED_SIZE); - char* seed_hex; seed_hex = utils_uint8_to_hex(seed, MAX_SEED_SIZE); debug_print ("%s\n", seed_hex); @@ -187,7 +186,7 @@ void test_bip44() char changepubkey[HDKEYLEN]; u_assert_true(getHDRootKeyFromSeed(test_seed, MAX_SEED_SIZE, true, masterkey)); - u_assert_true(deriveBIP44ExtendedKey(masterkey, NULL, NULL, NULL, NULL, accountkey, keypath)); + u_assert_true(deriveBIP44ExtendedKey(masterkey, &account, NULL, NULL, NULL, accountkey, keypath)); u_assert_true(deriveBIP44ExtendedKey(masterkey, &account, BIP44_CHANGE_EXTERNAL, NULL, NULL, bip32key, keypath)); u_assert_str_eq(bip32key, "tprv8hi9XJvkuKfu6oRGUsAnPAnQNUKcEjwrLbS2w2hTSPKrFj5YYS3Ax7UDDrZZHd4PSnPLW5whNxAXTW5bBrSNiSD1LUeg9n4j5sdGRJsZZwP"); @@ -196,7 +195,7 @@ void test_bip44() debug_print("deriveBIP44ExtendedKey: %s\n", bip32key); /* test deriveBIP44ExtendedPublicKey */ - u_assert_true(deriveBIP44ExtendedKey(masterkey, NULL, NULL, NULL, NULL, accountkey, keypath)); + u_assert_true(deriveBIP44ExtendedKey(masterkey, &account, NULL, NULL, NULL, accountkey, keypath)); u_assert_true(deriveBIP44ExtendedKey(masterkey, &account, BIP44_CHANGE_EXTERNAL, NULL, NULL, bip32key, keypath)); u_assert_true(deriveBIP44ExtendedPublicKey(masterkey, &account, NULL, NULL, NULL, accountPubkey, keypath)); u_assert_true(deriveBIP44ExtendedPublicKey(masterkey, &account, BIP44_CHANGE_EXTERNAL, NULL, NULL, changepubkey, keypath)); diff --git a/test/sha1_tests.c b/test/sha1_tests.c new file mode 100644 index 000000000..89a20cf25 --- /dev/null +++ b/test/sha1_tests.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include + +/** + * NIST Test Vectors for SHA-1 and HMAC-SHA1 + * Reference: https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/secure-hashing + * RFC 2202: https://datatracker.ietf.org/doc/html/rfc2202 + */ + +struct sha1_test_v_short { + int len; + const char* msg; + const char* digest_hex; +}; + +struct sha_hmac_test_v { + const uint8_t* key; + size_t key_len; + const uint8_t* msg; + size_t msg_len; + const char* digest_hex; +}; + +static const struct sha1_test_v_short nist_sha1_test_vectors_short[] = { + {0, "", "da39a3ee5e6b4b0d3255bfef95601890afd80709"}, + {3, "abc", "a9993e364706816aba3e25717850c26c9cd0d89d"}, + {56, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "84983e441c3bd26ebaae4aa1f95129e5e54670f1"}, + {112, "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" + "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", "a49b2446a02c645bf419f995b67091253a04a259"}, + {0, NULL, NULL} +}; + +static const struct sha_hmac_test_v sha_hmac_test_vectors[] = { + {(const uint8_t*)"\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", 20, + (const uint8_t*)"Hi There", 8, + "b617318655057264e28bc0b6fb378c8ef146be00"}, + {(const uint8_t*)"Jefe", 4, + (const uint8_t*)"what do ya want for nothing?", 28, + "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"}, + {(const uint8_t*)"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", 20, + (const uint8_t*)"\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd", 50, + "125d7342b9ac11cd91a39af48aa17b4f63f175d3"}, + {(const uint8_t*)"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", 25, + (const uint8_t*)"\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd", 50, + "4c9007f4026250c6bc8414f9bf50c86c2d7235da"}, + {(const uint8_t*)"\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c", 20, + (const uint8_t*)"Test With Truncation", 20, + "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04"}, + {(const uint8_t*)"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", 80, + (const uint8_t*)"Test Using Larger Than Block-Size Key - Hash Key First", 54, + "aa4ae5e15272d00e95705637ce8a3b55ed402112"}, + {(const uint8_t*)"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", 80, + (const uint8_t*)"Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", 73, + "e8e99d0f45237d786d6bbaa7965c7808bbff1a91"}, + {NULL, 0, NULL, 0, NULL} +}; + +void test_sha1() { + sha1_context context; + uint8_t digest[SHA1_DIGEST_LENGTH]; + unsigned int i; + + for (i = 0; nist_sha1_test_vectors_short[i].msg != NULL; ++i) { + sha1_Init(&context); + sha1_Update(&context, (const uint8_t*)nist_sha1_test_vectors_short[i].msg, nist_sha1_test_vectors_short[i].len); + sha1_Final(&context, digest); + + uint8_t expected_digest[SHA1_DIGEST_LENGTH]; + size_t expected_digest_len = sizeof(expected_digest); + utils_hex_to_bin(nist_sha1_test_vectors_short[i].digest_hex, expected_digest, SHA1_DIGEST_LENGTH * 2, &expected_digest_len); + + assert(memcmp(digest, expected_digest, SHA1_DIGEST_LENGTH) == 0); + debug_print("SHA1 Test %d passed.\n", i + 1); + } +} + +void test_sha1_hmac() { + uint8_t hmac[SHA1_DIGEST_LENGTH]; + unsigned int i; + + for (i = 0; sha_hmac_test_vectors[i].key != NULL; ++i) { + hmac_sha1(sha_hmac_test_vectors[i].key, sha_hmac_test_vectors[i].key_len, + sha_hmac_test_vectors[i].msg, sha_hmac_test_vectors[i].msg_len, hmac); + + uint8_t expected_hmac[SHA1_DIGEST_LENGTH]; + size_t expected_hmac_len = sizeof(expected_hmac); + utils_hex_to_bin(sha_hmac_test_vectors[i].digest_hex, expected_hmac, SHA1_DIGEST_LENGTH * 2, &expected_hmac_len); + + assert(memcmp(hmac, expected_hmac, SHA1_DIGEST_LENGTH) == 0); + debug_print("HMAC-SHA1 Test %d passed.\n", i + 1); + } +} diff --git a/test/tpm_tests.c b/test/tpm_tests.c index 20ece030b..08dd8cbed 100644 --- a/test/tpm_tests.c +++ b/test/tpm_tests.c @@ -1,7 +1,7 @@ /********************************************************************** * Copyright (c) 2015 Jonas Schnelli * * Copyright (c) 2023 edtubbs * - * Copyright (c) 2022-2024 The Dogecoin Foundation * + * Copyright (c) 2022-2024 The Dogecoin Foundation * * Distributed under the MIT software license, see the accompanying * * file COPYING or http://www.opensource.org/licenses/mit-license.php.* **********************************************************************/ @@ -44,11 +44,11 @@ void test_tpm() #endif // Encrypt a random seed with software - u_assert_true (dogecoin_encrypt_seed_with_sw (seed, sizeof(SEED), TEST_FILE, true, test_password)); + u_assert_true (dogecoin_encrypt_seed_with_sw (seed, sizeof(SEED), TEST_FILE, true, test_password, NULL, NULL)); debug_print ("Seed: %s\n", utils_uint8_to_hex (seed, sizeof (SEED))); // Decrypt the seed with software - u_assert_true (dogecoin_decrypt_seed_with_sw (decrypted_seed, TEST_FILE, test_password)); + u_assert_true (dogecoin_decrypt_seed_with_sw (decrypted_seed, TEST_FILE, test_password, NULL)); debug_print ("Decrypted seed: %s\n", utils_uint8_to_hex (decrypted_seed, sizeof (SEED))); // Compare the seed and the decrypted seed @@ -58,11 +58,11 @@ void test_tpm() dogecoin_hdnode node, decrypted_node; // Generate a random HD node with software - u_assert_true (dogecoin_generate_hdnode_encrypt_with_sw (&node, TEST_FILE, true, test_password)); + u_assert_true (dogecoin_generate_hdnode_encrypt_with_sw (&node, TEST_FILE, true, test_password, NULL, 0)); debug_print ("HD node: %s\n", utils_uint8_to_hex ((uint8_t *) &node, sizeof (dogecoin_hdnode))); // Decrypt the HD node with software - u_assert_true (dogecoin_decrypt_hdnode_with_sw (&decrypted_node, TEST_FILE, test_password)); + u_assert_true (dogecoin_decrypt_hdnode_with_sw (&decrypted_node, TEST_FILE, test_password, NULL)); debug_print ("Decrypted HD node: %s\n", utils_uint8_to_hex ((uint8_t *) &decrypted_node, sizeof (dogecoin_hdnode))); // Compare the HD node and the decrypted HD node @@ -73,16 +73,90 @@ void test_tpm() MNEMONIC decrypted_mnemonic = {0}; // Generate a random mnemonic with software - u_assert_true (dogecoin_generate_mnemonic_encrypt_with_sw(mnemonic, TEST_FILE, true, "eng", " ", NULL, test_password)); + u_assert_true (dogecoin_generate_mnemonic_encrypt_with_sw(mnemonic, TEST_FILE, true, "eng", " ", NULL, test_password, NULL, NULL)); debug_print("Mnemonic: %s\n", mnemonic); // Decrypt the mnemonic with software - u_assert_true (dogecoin_decrypt_mnemonic_with_sw(decrypted_mnemonic, TEST_FILE, test_password)); + u_assert_true (dogecoin_decrypt_mnemonic_with_sw(decrypted_mnemonic, TEST_FILE, test_password, NULL)); debug_print("Decrypted mnemonic: %s\n", decrypted_mnemonic); // Compare the mnemonic and the decrypted mnemonic u_assert_mem_eq (mnemonic, decrypted_mnemonic, sizeof (MNEMONIC)); + // Test encrypting and decrypting a seed with an encrypted blob + ENCRYPTED_BLOB encrypted_blob; + size_t encrypted_blob_size; + + // Encrypt a random seed with software into a blob + u_assert_true(dogecoin_encrypt_seed_with_sw(seed, sizeof(SEED), NO_FILE, true, test_password, &encrypted_blob, &encrypted_blob_size)); + debug_print("Encrypted seed blob: %s\n", utils_uint8_to_hex(encrypted_blob, encrypted_blob_size)); + + // Decrypt the seed with software from a blob + u_assert_true(dogecoin_decrypt_seed_with_sw(decrypted_seed, NO_FILE, test_password, encrypted_blob)); + debug_print("Decrypted seed from blob: %s\n", utils_uint8_to_hex(decrypted_seed, sizeof(SEED))); + + // Compare the seed and the decrypted seed + u_assert_mem_eq(seed, decrypted_seed, sizeof(SEED)); + + // Test encrypting and decrypting an HD node with an encrypted blob + u_assert_true(dogecoin_generate_hdnode_encrypt_with_sw(&node, NO_FILE, true, test_password, &encrypted_blob, &encrypted_blob_size)); + debug_print("Encrypted HD node blob: %s\n", utils_uint8_to_hex(encrypted_blob, encrypted_blob_size)); + + // Decrypt the HD node with software from a blob + u_assert_true(dogecoin_decrypt_hdnode_with_sw(&decrypted_node, NO_FILE, test_password, encrypted_blob)); + debug_print("Decrypted HD node from blob: %s\n", utils_uint8_to_hex((uint8_t*)&decrypted_node, sizeof(dogecoin_hdnode))); + + // Compare the HD node and the decrypted HD node + u_assert_mem_eq(&node, &decrypted_node, sizeof(dogecoin_hdnode)); + + // Test encrypting and decrypting a mnemonic with an encrypted blob + u_assert_true(dogecoin_generate_mnemonic_encrypt_with_sw(mnemonic, NO_FILE, true, "eng", " ", NULL, test_password, &encrypted_blob, &encrypted_blob_size)); + debug_print("Encrypted mnemonic blob: %s\n", utils_uint8_to_hex(encrypted_blob, encrypted_blob_size)); + + // Decrypt the mnemonic with software from a blob + u_assert_true(dogecoin_decrypt_mnemonic_with_sw(decrypted_mnemonic, NO_FILE, test_password, encrypted_blob)); + debug_print("Decrypted mnemonic from blob: %s\n", decrypted_mnemonic); + + // Compare the mnemonic and the decrypted mnemonic + u_assert_mem_eq(mnemonic, decrypted_mnemonic, sizeof(MNEMONIC)); + +#ifdef USE_YUBIKEY + + // Encrypt a random seed with YubiKey + u_assert_true (dogecoin_encrypt_seed_with_sw_to_yubikey(seed, sizeof(SEED), TEST_FILE, true, test_password)); + debug_print ("Seed to YubiKey: %s\n", utils_uint8_to_hex (seed, sizeof (SEED))); + + // Decrypt the seed with YubiKey + u_assert_true (dogecoin_decrypt_seed_with_sw_from_yubikey(decrypted_seed, TEST_FILE, test_password)); + debug_print ("Decrypted seed from YubiKey: %s\n", utils_uint8_to_hex (decrypted_seed, sizeof (SEED))); + + // Compare the seed and the decrypted seed + u_assert_mem_eq (seed, decrypted_seed, sizeof (SEED)); + + // Generate a random HD node with YubiKey + u_assert_true (dogecoin_generate_hdnode_encrypt_with_sw_to_yubikey(&node, TEST_FILE, true, test_password)); + debug_print ("HD node to YubiKey: %s\n", utils_uint8_to_hex ((uint8_t *) &node, sizeof (dogecoin_hdnode))); + + // Decrypt the HD node with YubiKey + u_assert_true (dogecoin_decrypt_hdnode_with_sw_from_yubikey(&decrypted_node, TEST_FILE, test_password)); + debug_print ("Decrypted HD node from YubiKey: %s\n", utils_uint8_to_hex ((uint8_t *) &decrypted_node, sizeof (dogecoin_hdnode))); + + // Compare the HD node and the decrypted HD node + u_assert_mem_eq (&node, &decrypted_node, sizeof (dogecoin_hdnode)); + + // Generate a random mnemonic with YubiKey + u_assert_true (dogecoin_generate_mnemonic_encrypt_with_sw_to_yubikey(mnemonic, TEST_FILE, true, "eng", " ", NULL, test_password)); + debug_print("Mnemonic to YubiKey: %s\n", mnemonic); + + // Decrypt the mnemonic with YubiKey + u_assert_true (dogecoin_decrypt_mnemonic_with_sw_from_yubikey(decrypted_mnemonic, TEST_FILE, test_password)); + debug_print("Decrypted mnemonic from YubiKey: %s\n", decrypted_mnemonic); + + // Compare the mnemonic and the decrypted mnemonic + u_assert_mem_eq (mnemonic, decrypted_mnemonic, sizeof (MNEMONIC)); + +#endif + #if defined (_WIN64) && !defined(__MINGW64__) && defined(USE_TPM2) // Create TBS context (TPM2) diff --git a/test/unittester.c b/test/unittester.c index 4889a2a67..6a25a23c8 100644 --- a/test/unittester.c +++ b/test/unittester.c @@ -59,6 +59,8 @@ extern void test_random(); extern void test_rmd160(); extern void test_scrypt(); extern void test_serialize(); +extern void test_sha1(); +extern void test_sha1_hmac(); extern void test_sha_256(); extern void test_sha_512(); extern void test_sha_hmac(); @@ -140,12 +142,16 @@ int main() u_run_test(test_rmd160); u_run_test(test_scrypt); u_run_test(test_serialize); + u_run_test(test_sha1); + u_run_test(test_sha1_hmac); u_run_test(test_sha_256); u_run_test(test_sha_512); u_run_test(test_sha_hmac); u_run_test(test_signmsg); u_run_test(test_signmsg_ext); +#ifndef USE_OPTEE // TPM is not supported in OPTEE u_run_test(test_tpm); +#endif u_run_test(test_transaction); u_run_test(test_tx_serialization); u_run_test(test_invalid_tx_deser);