diff --git a/CMakeLists.txt b/CMakeLists.txt index 6150967..eb9b163 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,10 @@ LIST(APPEND CMAKE_MODULE_PATH "${PDO_SOURCE_ROOT}/contracts/wawaka") INCLUDE(contract-build) INCLUDE(wawaka_common) +# This switch is necessary in general. However, it is +# extremely helpful for reducing the size of large contracts +LIST(APPEND WASM_LINK_OPTIONS "-Wl,--strip-all") + LIST(APPEND WASM_LIBRARIES ${WW_COMMON_LIB}) LIST(APPEND WASM_INCLUDES ${WW_COMMON_INCLUDES}) diff --git a/exchange-contract/exchange/common/Common.h b/exchange-contract/exchange/common/Common.h index 53c1cfd..407704e 100644 --- a/exchange-contract/exchange/common/Common.h +++ b/exchange-contract/exchange/common/Common.h @@ -20,6 +20,21 @@ #include "Message.h" #include "Types.h" #include "Value.h" +#include "WasmExtensions.h" + +#define ERROR_IF_NULL(_ptr_, _message_, ...) \ + if ((_ptr_) == NULL) \ + { \ + CONTRACT_SAFE_LOG(3, _message_, ##__VA_ARGS__); \ + return false; \ + } + +#define ERROR_IF(_condition_, _message_, ...) \ + if (_condition_) \ + { \ + CONTRACT_SAFE_LOG(3, _message_, ##__VA_ARGS__); \ + return false; \ + } namespace ww { diff --git a/identity-contract/CMakeLists.txt b/identity-contract/CMakeLists.txt index 80c7bb7..76e08bb 100644 --- a/identity-contract/CMakeLists.txt +++ b/identity-contract/CMakeLists.txt @@ -15,24 +15,52 @@ INCLUDE(exchange-contract/methods) INCLUDE(methods.cmake) +# ----------------------------------------------------------------- +# ----------------------------------------------------------------- +SET(OPENSSL_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}) +SET(OPENSSL_VERSION 3.1.0) + +SET(OPENSSL_WASM_INCLUDE_DIR ${OPENSSL_BUILD_DIR}/precompiled/include) +SET(OPENSSL_WASM_LIB_DIR ${OPENSSL_BUILD_DIR}/precompiled/lib) +SET(OPENSSL_WASM_CRYPTO_LIB ${OPENSSL_WASM_LIB_DIR}/libcrypto.a) +SET(OPENSSL_WASM_SSL_LIB ${OPENSSL_WASM_LIB_DIR}/libssl.a) + +ADD_CUSTOM_COMMAND( + OUTPUT ${OPENSSL_WASM_CRYPTO_LIB} ${OPENSSL_WASM_SSL_LIB} ${OPENSSL_WASM_INCLUDE_DIR} + BYPRODUCTS ${OPENSSL_BUILD_DIR}/${OPENSSL_VERSION} + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/openssl/build.sh + ARGS -o ${OPENSSL_BUILD_DIR}/precompiled -p ${CMAKE_CURRENT_SOURCE_DIR}/openssl/patches -v ${OPENSSL_VERSION} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/openssl/build.sh ${CMAKE_CURRENT_SOURCE_DIR}/openssl/patches/*.patch + COMMENT "Building OpenSSL for WebAssembly" +) + +ADD_CUSTOM_TARGET( + openssl_wasm + DEPENDS ${OPENSSL_WASM_CRYPTO_LIB} ${OPENSSL_WASM_SSL_LIB} ${OPENSSL_WASM_INCLUDE_DIR} +) + # ----------------------------------------------------------------- ADD_LIBRARY(${IDENTITY_LIB} STATIC ${IDENTITY_SOURCES}) +SET_PROPERTY(TARGET ${IDENTITY_LIB} APPEND_STRING PROPERTY COMPILE_OPTIONS "${WASM_BUILD_OPTIONS}" "-Wno-deprecated") +SET_PROPERTY(TARGET ${IDENTITY_LIB} APPEND_STRING PROPERTY LINK_OPTIONS "${WASM_LINK_OPTIONS}") +SET_TARGET_PROPERTIES(${IDENTITY_LIB} PROPERTIES EXCLUDE_FROM_ALL TRUE) + TARGET_INCLUDE_DIRECTORIES(${IDENTITY_LIB} PUBLIC ${IDENTITY_INCLUDES}) TARGET_INCLUDE_DIRECTORIES(${IDENTITY_LIB} PUBLIC ${EXCHANGE_INCLUDES}) TARGET_INCLUDE_DIRECTORIES(${IDENTITY_LIB} PUBLIC ${WASM_INCLUDES}) +TARGET_INCLUDE_DIRECTORIES(${IDENTITY_LIB} PUBLIC ${OPENSSL_WASM_INCLUDE_DIR}) -SET_PROPERTY(TARGET ${IDENTITY_LIB} APPEND_STRING PROPERTY COMPILE_OPTIONS "${WASM_BUILD_OPTIONS}") -SET_PROPERTY(TARGET ${IDENTITY_LIB} APPEND_STRING PROPERTY LINK_OPTIONS "${WASM_LINK_OPTIONS}") -SET_TARGET_PROPERTIES(${IDENTITY_LIB} PROPERTIES EXCLUDE_FROM_ALL TRUE) +ADD_DEPENDENCIES(${IDENTITY_LIB} openssl_wasm) # ----------------------------------------------------------------- BUILD_CONTRACT(signature_authority contracts/signature_authority.cpp HEADERS ${EXCHANGE_INCLUDES} ${IDENTITY_INCLUDES} - LIBRARIES ${EXCHANGE_LIB} ${IDENTITY_LIB}) + LIBRARIES ${EXCHANGE_LIB} ${IDENTITY_LIB} ${OPENSSL_WASM_CRYPTO_LIB}) BUILD_CONTRACT(identity contracts/identity.cpp HEADERS ${EXCHANGE_INCLUDES} ${IDENTITY_INCLUDES} - LIBRARIES ${EXCHANGE_LIB} ${IDENTITY_LIB}) + LIBRARIES ${EXCHANGE_LIB} ${IDENTITY_LIB} ${OPENSSL_WASM_CRYPTO_LIB}) # ----------------------------------------------------------------- INCLUDE(Python) diff --git a/identity-contract/identity/common/BigNum.cpp b/identity-contract/identity/common/BigNum.cpp deleted file mode 100644 index b62d8a1..0000000 --- a/identity-contract/identity/common/BigNum.cpp +++ /dev/null @@ -1,256 +0,0 @@ -/* Copyright 2023 Intel Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -#include -#include -#include -#include -#include - -#include "BigNum.h" - -#define MAXIMUM_WORD_VALUE 256 - -// ----------------------------------------------------------------- -// Bit operations -// ----------------------------------------------------------------- -uint8_t ww::types::get_bit( - const ww::types::ByteArray& v, - const size_t index) -{ - uint8_t byte = v[index / 8]; - return ((byte >> (8 - (index % 8) - 1)) & 0x01); -} - -void ww::types::set_bit( - const size_t index, - const uint8_t value, - ww::types::ByteArray& v) -{ - uint8_t byte = v[index / 8]; - uint8_t mask = (0x01 << (8 - (index % 8) - 1)); - byte = value ? (byte | mask) : (byte & (~ mask)); - v[index / 8] = byte; -} - -// ----------------------------------------------------------------- -// cmp_big_numbers -// Compare two big numbers with return similar to other comparison -// operators: <0 if num1 < num2, 0 if num1 == num2, 1 if num1 > num2; -// -255 if something has gone wrong. -// ----------------------------------------------------------------- -int ww::types::cmp_big_numbers( - const ww::types::ByteArray& num1, - const ww::types::ByteArray& num2) -{ - // we don't really have an error and cant throw an - // exception - if (num1.size() != num2.size()) - return -255; - - const size_t SIZE = num1.size(); - - for (size_t i = 0; i < SIZE; i++) - { - if (num1[i] < num2[i]) - return -1; - else if (num1[i] > num2[i]) - return 1; - } - - return 0; -} - -// ----------------------------------------------------------------- -// add_big_numbers -// -// Add two equally sized big numbers, put the result in another -// big number, numbers are encoded in ByteArrays, numbers are all -// assumed to be positive and there is no overflow. -// ----------------------------------------------------------------- -bool ww::types::add_big_numbers( - const ww::types::ByteArray& num1, - const ww::types::ByteArray& num2, - ww::types::ByteArray& result) -{ - if (num1.size() != num2.size()) - return false; - - const size_t SIZE = num1.size(); - - result.resize(SIZE); - result.assign(SIZE, 0); - - uint8_t carry = 0; - - // least significant digit to most significant - for (size_t i = SIZE; i > 0; i--) - { - uint32_t sum = carry + num1[i-1] + num2[i-1]; - carry = sum / MAXIMUM_WORD_VALUE; - result[i-1] = sum % MAXIMUM_WORD_VALUE; - } - - return true; -} - -// ----------------------------------------------------------------- -// sub_big_numbers -// -// Subtract one big number from another and put the result into a -// third. Big numbers are assumed to be positive and the result must -// be positive (e.g. num1 > num2). -// ----------------------------------------------------------------- -bool ww::types::sub_big_numbers( - const ww::types::ByteArray& num1, - const ww::types::ByteArray& num2, - ww::types::ByteArray& result) -{ - if (num1.size() != num2.size()) - return false; - - const size_t SIZE = num1.size(); - - // num1 must be greater than num2 - if (cmp_big_numbers(num1, num2) < 0) - return false; - - result.resize(SIZE); - result.assign(SIZE, 0); - - uint8_t borrow = 0; - - // least significant digit to most significant - for (size_t i = SIZE; i > 0; i--) - { - if (num1[i-1] - borrow < num2[i-1]) - { - result[i-1] = MAXIMUM_WORD_VALUE + num1[i-1] - borrow - num2[i-1]; - borrow = 1; - } - else - { - result[i-1] = num1[i-1] - borrow - num2[i-1]; - borrow = 0; - } - } - - return true; -} - -// ----------------------------------------------------------------- -// mod_big_numbers -// -// Compute the remainder after dividing one big number from another. -// Put the result into a third. All numbers are assumed to be positive. -// ----------------------------------------------------------------- -bool ww::types::mod_big_numbers( - const ww::types::ByteArray& num, - const ww::types::ByteArray& mod, - ww::types::ByteArray& result) -{ - if (num.size() != mod.size()) - return false; - - const size_t SIZE = num.size(); - - result.resize(SIZE); - result.assign(SIZE, 0); - - // if num < mod, then the result is num - if (cmp_big_numbers(num, mod) < 0) - { - result.assign(num.begin(), num.end()); - return true; - } - - size_t msb_num = find_most_significant_bit(num); - size_t msb_mod = find_most_significant_bit(mod); - ww::types::ByteArray temp_num = num; - - // Multiply mod so that it is almost as big as num, basically - // this code fixes one binary digit at a time in the subtraction - for (size_t i = msb_mod - msb_num + 1; i > 0; i--) - { - ww::types::ByteArray temp_mod; - - shift_left_big_numbers(i-1, mod, temp_mod); - if (cmp_big_numbers(temp_mod,temp_num) < 0) - { - if (! sub_big_numbers(temp_num, temp_mod, result)) - return false; - - temp_num = result; - } - } - - return true; -} - -// ----------------------------------------------------------------- -// shift_left_big_numbers -// -// Shift bits from least significant to most significant, dropping -// any carry bits. Copy the result to a new array. -// ----------------------------------------------------------------- -bool ww::types::shift_left_big_numbers( - const size_t shift, - const ww::types::ByteArray& num, - ww::types::ByteArray& result) -{ - const size_t SIZE = num.size(); - - if (8 * SIZE <= shift) - return false; - - result.resize(SIZE); - result.assign(SIZE, 0); - - for (size_t i = 0; i < (8 * SIZE) - shift; i++) - { - uint8_t b = ww::types::get_bit(num, i+shift); - ww::types::set_bit(i, b, result); - } - - return true; -} - -// ----------------------------------------------------------------- -// find_most_significant_bit -// -// Utility function that helps to compute modulus -// ----------------------------------------------------------------- -size_t ww::types::find_most_significant_bit( - const ww::types::ByteArray& num) -{ - const size_t SIZE = num.size(); - - size_t w, b; - for (w = 0; w < SIZE; w++) - { - if (num[w] > 0) break; - } - - uint8_t word = num[w]; - for (b = 0; b < 8; b++) - { - if (word & 0x80) - break; - word = word << 1; - } - - return w * 8 + b; -} diff --git a/identity-contract/identity/common/BigNum.h b/identity-contract/identity/common/BigNum.h deleted file mode 100644 index b221608..0000000 --- a/identity-contract/identity/common/BigNum.h +++ /dev/null @@ -1,171 +0,0 @@ -/* Copyright 2023 Intel Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "Cryptography.h" -#include "Types.h" - -/* - This file contains a very simple implementation of big numbers. Specifically, - it defines addition, subtraction and modulus operations for positive integers - with fixed byte size. The numbers are encoded most significant digits at the - low index in the array. -*/ -namespace ww -{ -namespace types -{ - // Big number operations - int cmp_big_numbers(const std::vector& num1, const std::vector& num2); - bool add_big_numbers(const std::vector& num1, const std::vector& num2, std::vector& result); - bool sub_big_numbers(const std::vector& num1, const std::vector& num2, std::vector& result); - bool mod_big_numbers(const std::vector& num, const std::vector& mod, std::vector& result); - bool shift_left_big_numbers(const size_t shift, const std::vector& num, std::vector& result); - size_t find_most_significant_bit(const std::vector& num); - - // Bit operations - uint8_t get_bit(const std::vector& v, const size_t index); - void set_bit(const size_t index, const uint8_t value, std::vector& v); - - template - class BigNum : protected ww::types::ByteArray - { - private: - - public: - // Constructors - BigNum(void) { - resize(SIZE); - }; - - BigNum(std::initializer_list n) : BigNum() { - if (n.size() != SIZE) - return; // throw std::runtime_error("bad length"); - - std::copy_n(n.begin(), SIZE, this->begin()); - }; - - BigNum(const ww::types::ByteArray& n) : BigNum() - { - this->decode(n); - }; - - BigNum(const std::string& encoded) : BigNum() - { - this->decode(encoded); - } - - // assign from a raw byte array - bool decode(const ww::types::ByteArray& n) - { - if (n.size() != SIZE) - return false; - std::copy_n(n.begin(), SIZE, this->begin()); - return true; - } - - // copy to a raw byte array - bool encode(ww::types::ByteArray& n) const - { - n.resize(SIZE); - std::copy_n(this->begin(), SIZE, n.begin()); - return true; - } - - // assign from a base64 encoded string - bool decode(const std::string& encoded) - { - ww::types::ByteArray decoded; - if (! ww::crypto::b64_decode(encoded, decoded)) - return false; - return this->decode(decoded); - } - - // copy to a base64 encoded string - bool encode(std::string& encoded) const - { - ww::types::ByteArray n; - if (! this->encode(n)) - return false; - return ww::crypto::b64_encode(n, encoded); - } - - void reset(void) - { - std::fill(this->begin(), this->end(), 0); - }; - - // Arithmetic operators - BigNum operator+(const BigNum &b) const - { - BigNum result; - ww::types::add_big_numbers(*this, b, result); - return result; - }; - - BigNum operator-(const BigNum &b) const - { - BigNum result; - ww::types::sub_big_numbers(*this, b, result); - return result; - }; - - BigNum operator%(const BigNum &b) const - { - BigNum result; - ww::types::mod_big_numbers(*this, b, result); - return result; - }; - - // Comparison operators - bool operator<(const BigNum &b) const - { - return ww::types::cmp_big_numbers(*this, b) < 0; - }; - - bool operator<=(const BigNum &b) const - { - return ww::types::cmp_big_numbers(*this, b) <= 0; - }; - - bool operator>(const BigNum &b) const - { - return ww::types::cmp_big_numbers(*this, b) > 0; - }; - - bool operator>=(const BigNum &b) const - { - return ww::types::cmp_big_numbers(*this, b) >= 0; - }; - - bool operator==(const BigNum &b) const - { - return ww::types::cmp_big_numbers(*this, b) == 0; - }; - }; - - typedef BigNum<4> BigNum32; - typedef BigNum<32> BigNum256; - typedef BigNum<48> BigNum384; - typedef BigNum<64> BigNum512; -}; // types -} // ww diff --git a/identity-contract/identity/common/Missing.cpp b/identity-contract/identity/common/Missing.cpp new file mode 100644 index 0000000..5cad207 --- /dev/null +++ b/identity-contract/identity/common/Missing.cpp @@ -0,0 +1,49 @@ +/* Copyright 2023 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "Cryptography.h" +#include "WasmExtensions.h" + +// This file contains the definition of functions that are not +// available in the WASI libc but required for openssl. + +extern "C" char* getenv(const char* name) +{ + return NULL; +} + +// openssl > 3.00.0 requires atexit to be defined unless +// you specifically initialize the library with OPENSSL_init_crypto +extern "C" int atexit(void (*function)(void)) +{ + OPENSSL_cleanup(); + return 0; +} + +// this is a weird way of replacing getuid, getgid, getegid, getpid +// for the WASM implementations of SSL; see the openssl-wasm github +// repository for more details +extern "C" int getpagesize(void) +{ + return 4096; +} + +extern "C" void arc4random_buf(void* buffer, size_t size) +{ + ::random_identifier(size, (uint8_t*)buffer); +} diff --git a/identity-contract/identity/common/SigningContext.cpp b/identity-contract/identity/common/SigningContext.cpp index 62752fe..b64d9e3 100644 --- a/identity-contract/identity/common/SigningContext.cpp +++ b/identity-contract/identity/common/SigningContext.cpp @@ -17,16 +17,18 @@ #include #include +#include "Cryptography.h" #include "Types.h" #include "Value.h" -#include "WasmExtensions.h" - -#include "Cryptography.h" #include "exchange/common/Common.h" -#include "identity/common/BigNum.h" +#include "identity/crypto/Crypto.h" +#include "identity/crypto/PrivateKey.h" +#include "identity/crypto/PublicKey.h" #include "identity/common/SigningContext.h" +const std::string ww::identity::SigningContext::index_base = "PDO SigningContext:"; + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // Class: ww::identity::SigningContext // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @@ -136,101 +138,82 @@ bool ww::identity::SigningContext::verify_signature( // the main difference is that this implementation currently only performs // hardened derivations (private key) and it focuses on the secp384r1 // curve rather than the bitcoin focused secp256k1 curve. +// ----------------------------------------------------------------- +// ----------------------------------------------------------------- -#define CHUNK_HASH_FUNCTION ww::crypto::hash::sha256_hmac -#define EXTENDED_CHUNK_SIZE 16 +// ----------------------------------------------------------------- +// ----------------------------------------------------------------- +bool ww::identity::SigningContext::generate_keys( + const ww::types::ByteArray& root_chain_code, // array of random bytes, EXTENDED_KEY_SIZE + const std::vector& context_path, // array of strings, path to the current key + std::string& private_key, // PEM encoded ECDSA private and public keys + std::string& public_key) +{ + pdo_contracts::crypto::signing::PrivateKey extended_private_key; + ww::types::ByteArray extended_chain_code; -const std::string index_base("PDO SigningContext:"); + if (! ww::identity::SigningContext::generate_keys( + root_chain_code, context_path, extended_private_key, extended_chain_code)) + return false; -#ifdef DEBUG_BIGNUM -void DumpByteArray(const char* msg, const ww::types::ByteArray ba) -{ - std::string s; - ww::crypto::b64_encode(ba, s); - CONTRACT_SAFE_LOG(3, "[BA] %s: %s", msg, s.c_str()); -} + if (! extended_private_key.Serialize(private_key)) + return false; -void DumpBigNum(const char* msg, BIGNUM_TYPE bn) -{ - std::string s; - bn.encode(s); - CONTRACT_SAFE_LOG(3, "[BN] %s: %s", msg, s.c_str()); + pdo_contracts::crypto::signing::PublicKey extended_public_key(extended_private_key); + if (! extended_public_key.Serialize(public_key)) + return false; + + return true; } -#endif +// ----------------------------------------------------------------- // ----------------------------------------------------------------- bool ww::identity::SigningContext::generate_keys( - const ww::types::ByteArray& root_key, // base64 encoded representation of 48 byte random array - const std::vector& context_path, - std::string& private_key, // PEM encoded ECDSA private and public keys - std::string& public_key) + const ww::types::ByteArray& root_chain_code, // array of random bytes, EXTENDED_KEY_SIZE + const std::vector& context_path, // array of strings, path to the current key + pdo_contracts::crypto::signing::PrivateKey& private_key, + ww::types::ByteArray& chain_code) { - // Root key must contain EXTENDED_KEY_SIZE bytes - if (root_key.size() != EXTENDED_KEY_SIZE) - return false; + ERROR_IF(context_path.size() == 0, "Invalid empty context path"); + ERROR_IF(root_chain_code.size() != EXTENDED_KEY_SIZE, "Invalid root chain code size"); - // Create the initial extended key, this is a fixed value based on the - // index_base strings + // Root key --> fixed value derived from a common string ww::types::ByteArray base(index_base.begin(), index_base.end()); - ww::types::ByteArray hashed_base; - if (! HASH_FUNCTION(base, hashed_base)) - return false; + ww::types::ByteArray root_key; - // CURVE_ORDER is a base64 encoded number - const BIGNUM_TYPE curve_order(CURVE_ORDER); - BIGNUM_TYPE extended_key; - if (! extended_key.decode(hashed_base)) - return false; + int res = HASH_FUNCTION(base, root_key); + ERROR_IF(res <= 0, "Failed to hash base string"); - // Decode the root key, this is the chain code for the first iteration - ww::types::ByteArray extended_chain_code = root_key; + // And start processing the context path. The context path is a list of strings + ww::types::ByteArray parent_chain_code = root_chain_code; + pdo_contracts::crypto::signing::PrivateKey parent_key(CURVE_NID, root_key); std::vector::iterator path_element; - for ( path_element = context_path.begin(); path_element < context_path.end(); path_element++) + + ww::types::ByteArray child_chain_code; + pdo_contracts::crypto::signing::PrivateKey child_key(CURVE_NID); + + for (path_element = context_path.begin(); path_element < context_path.end(); path_element++) { - // For the purpose of the hashing, we are concatenating the index and the parent private key - size_t path_hash = std::hash{}(*path_element); - - ww::types::ByteArray ba_extended_key; - extended_key.encode(ba_extended_key); - - ww::types::ByteArray index; - index.push_back(0x00); // this is part of the BIP32 specification for extended keys - index.insert(index.end(), ba_extended_key.begin(), ba_extended_key.end()); - auto ptr = reinterpret_cast(&path_hash); - index.insert(index.end(), ptr, ptr + sizeof(size_t)); - - ww::types::ByteArray child_key_ba; - ww::types::ByteArray child_chain_code; - - for (int i = 0; i < EXTENDED_KEY_SIZE / EXTENDED_CHUNK_SIZE; i++) { - const size_t seg_start = i * EXTENDED_CHUNK_SIZE; - const size_t seg_end = (i + 1) * EXTENDED_CHUNK_SIZE; - ww::types::ByteArray chain_code_segment( - extended_chain_code.begin() + seg_start, extended_chain_code.begin() + seg_end); - - ww::types::ByteArray hmac; - CHUNK_HASH_FUNCTION(index, chain_code_segment, hmac); - if (hmac.size() != 2 * EXTENDED_CHUNK_SIZE) - return false; - - child_key_ba.insert(child_key_ba.end(), hmac.begin(), hmac.begin() + EXTENDED_CHUNK_SIZE); - child_chain_code.insert(child_chain_code.end(), hmac.begin() + EXTENDED_CHUNK_SIZE, hmac.end()); + if ((*path_element)[0] == '#') + { + //CONTRACT_SAFE_LOG(3, "generate hardened key for element %s", (*path_element).c_str()); + ERROR_IF(! parent_key.DeriveHardenedKey(parent_chain_code, *path_element, child_key, child_chain_code), + "Failed to generate hardened child keys"); + } + else + { + //CONTRACT_SAFE_LOG(3, "generate normal key for element %s", (*path_element).c_str()); + ERROR_IF(! parent_key.DeriveNormalKey(parent_chain_code, *path_element, child_key, child_chain_code), + "Failed to generate normal child keys"); } - // Add the extended key to the value created... - BIGNUM_TYPE child_key; - if (! child_key.decode(child_key_ba)) - return false; - - extended_key = (extended_key + child_key) % curve_order; - extended_chain_code = child_chain_code; + // Prepare for the next iteration + parent_key = child_key; + parent_chain_code = child_chain_code; } - // now convert the extended_key into an ECDSA key, for the moment the key - // generation function only understands byte arrays - ww::types::ByteArray extended_key_ba; - if (! extended_key.encode(extended_key_ba)) - return false; + private_key = parent_key; + chain_code = parent_chain_code; - return ww::crypto::ecdsa::generate_keys(extended_key_ba, private_key, public_key); + return true; } diff --git a/identity-contract/identity/common/SigningContext.h b/identity-contract/identity/common/SigningContext.h index 31feed3..6eb3ed9 100644 --- a/identity-contract/identity/common/SigningContext.h +++ b/identity-contract/identity/common/SigningContext.h @@ -21,6 +21,8 @@ #include "Value.h" #include "exchange/common/Common.h" +#include "identity/crypto/PrivateKey.h" + #define SIGNING_CONTEXT_SCHEMA \ "{" \ @@ -65,9 +67,12 @@ // https://en.wikipedia.org/wiki/P-384 #ifdef USE_SECP384R1 #define EXTENDED_KEY_SIZE 48 -#define BIGNUM_TYPE ww::types::BigNum384 -#define HASH_FUNCTION ww::crypto::hash::sha384_hash +#define HASH_FUNCTION pdo_contracts::crypto::SHA384Hash +#define CURVE_NID NID_secp384r1 #define CURVE_ORDER "//////////////////////////////////////////7/////AAAAAAAAAAD/////" + +// #define HASH_FUNCTION ww::crypto::hash::sha384_hash + #endif namespace ww @@ -86,6 +91,8 @@ namespace identity std::vector subcontexts_; // registered subcontexts public: + static const std::string index_base; + static bool sign_message( const ww::types::ByteArray& root_key, const std::vector& context_path, @@ -101,8 +108,14 @@ namespace identity static bool generate_keys( const ww::types::ByteArray& root_key, // base64 encoded representation of 48 byte random array const std::vector& context_path, - std::string& private_key, // PEM encoded ECDSA private and public keys - std::string& public_key); + std::string& private_key, // PEM encoded ECDSA private key + std::string& public_key); // PEM encoded ECDSA public key + + static bool generate_keys( + const ww::types::ByteArray& root_key, // base64 encoded representation of 48 byte random array + const std::vector& context_path, + pdo_contracts::crypto::signing::PrivateKey& private_key, // PEM encoded ECDSA private and public keys + ww::types::ByteArray& chain_code); // SerializeableObject virtual methods static bool verify_schema(const ww::value::Object& deserialized_object) diff --git a/identity-contract/identity/crypto/Crypto.h b/identity-contract/identity/crypto/Crypto.h new file mode 100644 index 0000000..be82252 --- /dev/null +++ b/identity-contract/identity/crypto/Crypto.h @@ -0,0 +1,71 @@ +/* Copyright 2025 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Types.h" + +extern "C" { +#include +#include +#include +#include +#include +#include +} + +#include + +typedef bool (*HashFunctionType)(const ww::types::ByteArray& message, ww::types::ByteArray& hash); + +namespace pdo_contracts +{ +namespace crypto +{ + // Typedefs for memory management + typedef std::unique_ptr BIGNUM_ptr; + typedef std::unique_ptr BIO_ptr; + typedef std::unique_ptr BN_CTX_ptr; + typedef std::unique_ptr ECDSA_SIG_ptr; + typedef std::unique_ptr EC_GROUP_ptr; + typedef std::unique_ptr EC_KEY_ptr; + typedef std::unique_ptr EC_POINT_ptr; + typedef std::unique_ptr CTX_ptr; + typedef std::unique_ptr EVP_MD_CTX_ptr; + typedef std::unique_ptr HMAC_CTX_ptr; + + typedef std::unique_ptr EVP_MAC_CTX_ptr; + + const unsigned int PBDK_Iterations = 10000; + + bool SHA256Hash( + const ww::types::ByteArray& message, ww::types::ByteArray& hash); + bool SHA256HMAC( + const ww::types::ByteArray& message, const ww::types::ByteArray& key, ww::types::ByteArray& hmac); + + bool SHA384Hash( + const ww::types::ByteArray& message, ww::types::ByteArray& hash); + bool SHA384HMAC( + const ww::types::ByteArray& message, const ww::types::ByteArray& key, ww::types::ByteArray& hmac); + + bool SHA512Hash( + const ww::types::ByteArray& message, ww::types::ByteArray& hash); + bool SHA512HMAC( + const ww::types::ByteArray& message, const ww::types::ByteArray& key, ww::types::ByteArray& hmac); + + bool SHA512PasswordBasedKeyDerivation( + const std::string& password, const ww::types::ByteArray& salt, ww::types::ByteArray& hmac); +} +} diff --git a/identity-contract/identity/crypto/Hash.cpp b/identity-contract/identity/crypto/Hash.cpp new file mode 100644 index 0000000..c927ea9 --- /dev/null +++ b/identity-contract/identity/crypto/Hash.cpp @@ -0,0 +1,205 @@ +/* Copyright 2022 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "Types.h" +#include "WasmExtensions.h" + +#include "exchange/common/Common.h" +#include "identity/crypto/Crypto.h" + +namespace pcrypto = pdo_contracts::crypto; + +// ----------------------------------------------------------------- +// Hash Functions +// ----------------------------------------------------------------- +static bool _ComputeHash_( + const EVP_MD *hashfunc(void), + const ww::types::ByteArray& message, + ww::types::ByteArray& hash) +{ + // **** NOTE **** + // There does not appear to be anything wrong with this code, but it + // fails consistently on DigestInit. The alternatives below work but + // use deprecated functions. This needs to be revisited. + + pcrypto::EVP_MD_CTX_ptr evp_md_ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); + ERROR_IF_NULL(evp_md_ctx.get(), "invalid hash context"); + + const EVP_MD *md = hashfunc(); + hash.resize(EVP_MD_size(md)); + + int ret; + + ret = EVP_DigestInit_ex(evp_md_ctx.get(), md, nullptr); + ERROR_IF(ret == 0, "hash digest init failed"); + + ret = EVP_DigestUpdate(evp_md_ctx.get(), message.data(), message.size()); + ERROR_IF(ret == 0, "hash update failed"); + + ret = EVP_DigestFinal_ex(evp_md_ctx.get(), hash.data(), NULL); + ERROR_IF(ret == 0, "hash final failed"); + + return true; +} + +bool pcrypto::SHA256Hash(const ww::types::ByteArray& message, ww::types::ByteArray& hash) +{ +#if USE_EVP_HASH_FUNCTIONS + return _ComputeHash_(EVP_sha256, message, hash); +#else + hash.resize(SHA256_DIGEST_LENGTH); + + SHA256_CTX ctx; + ERROR_IF(SHA256_Init(&ctx) <= 0, "sha56 init failed"); + ERROR_IF(SHA256_Update(&ctx, message.data(), message.size()) <= 0, "sha256 update failed"); + ERROR_IF(SHA256_Final(hash.data(), &ctx) <= 0, "sha256 final failed"); + + return true; +#endif +} + +bool pcrypto::SHA384Hash(const ww::types::ByteArray& message, ww::types::ByteArray& hash) +{ +#if USE_EVP_HASH_FUNCTIONS + return _ComputeHash_(EVP_sha384, message, hash); +#else + hash.resize(SHA384_DIGEST_LENGTH); + + SHA512_CTX ctx; + ERROR_IF(SHA384_Init(&ctx) <= 0, "sha384 init failed"); + ERROR_IF(SHA384_Update(&ctx, message.data(), message.size()) <= 0, "sha384 update failed"); + ERROR_IF(SHA384_Final(hash.data(), &ctx) <= 0, "sha384 final failed"); + + return true; +#endif +} + +bool pcrypto::SHA512Hash(const ww::types::ByteArray& message, ww::types::ByteArray& hash) +{ +#if USE_EVP_HASH_FUNCTIONS + return _ComputeHash_(EVP_sha512, message, hash); +#else + hash.resize(SHA384_DIGEST_LENGTH); + + SHA512_CTX ctx; + ERROR_IF(SHA512_Init(&ctx) <= 0, "sha512 init failed"); + ERROR_IF(SHA512_Update(&ctx, message.data(), message.size()) <= 0, "sha512 update failed"); + ERROR_IF(SHA512_Final(hash.data(), &ctx) <= 0, "sha512 final failed"); + + return true; +#endif +} + +// ----------------------------------------------------------------- +// HMAC Functions +// ----------------------------------------------------------------- +static bool _ComputeHMAC_( + const std::string& digest_name, + const ww::types::ByteArray& message, + const ww::types::ByteArray& key, + ww::types::ByteArray& hmac) +{ + int res; + + OSSL_LIB_CTX *library_context = OSSL_LIB_CTX_new(); + + EVP_MAC* mac = EVP_MAC_fetch(library_context, "HMAC", nullptr); + ERROR_IF_NULL(mac, "failed to fetch MAC algorithm"); + + pcrypto::EVP_MAC_CTX_ptr ctx(EVP_MAC_CTX_new(mac), EVP_MAC_CTX_free); + ERROR_IF_NULL(ctx.get(), "failed create the MAC context"); + + // 3. Set the MAC parameters (key and digest) + OSSL_PARAM params[3]; + params[0] = OSSL_PARAM_construct_utf8_string( + OSSL_MAC_PARAM_DIGEST, const_cast(digest_name.c_str()), digest_name.size()); + params[1] = OSSL_PARAM_construct_end(); + + res = EVP_MAC_init(ctx.get(), key.data(), key.size(), params); + ERROR_IF(res <= 0, "Failed to initialize MAC context"); + + res = EVP_MAC_update(ctx.get(), (const unsigned char*)message.data(), message.size()); + ERROR_IF(res <= 0, "Failed to update MAC context"); + + size_t outlen = EVP_MAC_CTX_get_mac_size(ctx.get()); + unsigned char* mac_value = (unsigned char*)OPENSSL_malloc(outlen); + ERROR_IF_NULL(mac_value, "Failed to allocate memory for mac value"); + + size_t mac_len = 0; + res = EVP_MAC_final(ctx.get(), mac_value, &mac_len, outlen); + ERROR_IF(res <= 0, "Failed to finalize MAC context"); + + hmac.assign(mac_value, mac_value + mac_len); + + return true; +} + +bool pcrypto::SHA256HMAC( + const ww::types::ByteArray& message, + const ww::types::ByteArray& key, + ww::types::ByteArray& hmac) +{ + return _ComputeHMAC_("SHA256", message, key, hmac); +} + +bool pcrypto::SHA384HMAC( + const ww::types::ByteArray& message, + const ww::types::ByteArray& key, + ww::types::ByteArray& hmac) +{ + return _ComputeHMAC_("SHA384", message, key, hmac); +} + +bool pcrypto::SHA512HMAC( + const ww::types::ByteArray& message, + const ww::types::ByteArray& key, + ww::types::ByteArray& hmac) +{ + return _ComputeHMAC_("SHA512", message, key, hmac); +} + +// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +// +static bool _ComputePasswordBasedKeyDerivation_( + const EVP_MD *hashfunc(void), + const std::string& password, + const ww::types::ByteArray& salt, + const unsigned int iterations, + ww::types::ByteArray& key) +{ + const EVP_MD *md = hashfunc(); + key.resize(EVP_MD_size(md)); + + int ret; + ret = PKCS5_PBKDF2_HMAC( + password.c_str(), password.size(), + salt.data(), salt.size(), + iterations, md, + key.size(), key.data()); + ERROR_IF(ret == 0, "password derivation failed"); + + return true; +} + +bool pcrypto::SHA512PasswordBasedKeyDerivation( + const std::string& password, + const ww::types::ByteArray& salt, + ww::types::ByteArray& key) +{ + return _ComputePasswordBasedKeyDerivation_(EVP_sha512, password, salt, pcrypto::PBDK_Iterations, key); +} diff --git a/identity-contract/identity/crypto/Key.cpp b/identity-contract/identity/crypto/Key.cpp new file mode 100644 index 0000000..17284ed --- /dev/null +++ b/identity-contract/identity/crypto/Key.cpp @@ -0,0 +1,67 @@ +/* Copyright 2025 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Types.h" + +#include "exchange/common/Common.h" + +#include "identity/crypto/Crypto.h" +#include "identity/crypto/Key.h" + +namespace signing = pdo_contracts::crypto::signing; + +// ----------------------------------------------------------------- +// DeriveChildKey +// +// This function is used to derive a child key and chain code from an +// extended key. The extended chain code normally serves as the key +// in the HMAC function. However, to accommodate longer keys, the +// extended chain code is split into segments, each of which is used +// as a key to compute an HMAC.The HMAC is then split into two halves, +// the first half is used as the child key and the second half is used +// as the child chain code. +// ----------------------------------------------------------------- +bool signing::Key::DeriveChildKey( + const ww::types::ByteArray& extended_chain_code, // array of random bytes, EXTENDED_KEY_SIZE + const ww::types::ByteArray& data, // data to be hashed with HMAC + ww::types::ByteArray& child_key, + ww::types::ByteArray& child_chain_code) +{ + // empty out anything that is in the arrays + child_key.resize(0); + child_chain_code.resize(0); + + for (int i = 0; i < extended_chain_code.size() / EXTENDED_CHUNK_SIZE; i++) + { + const size_t seg_start = i * EXTENDED_CHUNK_SIZE; + const size_t seg_end = (i + 1) * EXTENDED_CHUNK_SIZE; + + // Pull out the segment of the chain code to use for this iteration + ww::types::ByteArray chain_code_segment( + extended_chain_code.begin() + seg_start, extended_chain_code.begin() + seg_end); + + // Compute the HMAC, this will give us a chunk that can be split and + // put into a key and the chaincode + ww::types::ByteArray hmac; + ERROR_IF(! CHUNK_HMAC_FUNCTION(data, chain_code_segment, hmac), "HMAC failed"); + ERROR_IF(hmac.size() != 2 * EXTENDED_CHUNK_SIZE, "HMAC returned the wrong size"); + + // Put this chunk into the child key and chain code + child_key.insert(child_key.end(), hmac.begin(), hmac.begin() + EXTENDED_CHUNK_SIZE); + child_chain_code.insert(child_chain_code.end(), hmac.begin() + EXTENDED_CHUNK_SIZE, hmac.end()); + } + + return true; +} diff --git a/identity-contract/identity/crypto/Key.h b/identity-contract/identity/crypto/Key.h new file mode 100644 index 0000000..fe3557a --- /dev/null +++ b/identity-contract/identity/crypto/Key.h @@ -0,0 +1,55 @@ +/* Copyright 2018 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "Cryptography.h" +#include "Types.h" + +#define CHUNK_HMAC_FUNCTION pdo_contracts::crypto::SHA256HMAC +// #define CHUNK_HMAC_FUNCTION ww::crypto::hash::sha256_hmac +#define EXTENDED_CHUNK_SIZE 16 + +namespace pdo_contracts +{ +namespace crypto +{ + namespace signing + { + class Key + { + protected: + EC_KEY* key_; + int curve_; + + + static bool DeriveChildKey( + const ww::types::ByteArray& extended_chain_code, // array of random bytes, EXTENDED_KEY_SIZE + const ww::types::ByteArray& data, // data to be hashed with HMAC + ww::types::ByteArray& child_key, + ww::types::ByteArray& child_chain_code); + + public: + Key(int curve = NID_undef) { + key_ = nullptr; + curve_ = curve; + }; + }; + } +} +} diff --git a/identity-contract/identity/crypto/PrivateKey.cpp b/identity-contract/identity/crypto/PrivateKey.cpp new file mode 100644 index 0000000..bcc0d01 --- /dev/null +++ b/identity-contract/identity/crypto/PrivateKey.cpp @@ -0,0 +1,539 @@ +/* Copyright 2018 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +#include "exchange/common/Common.h" + +#include "identity/crypto/Crypto.h" +#include "identity/crypto/PrivateKey.h" +#include "identity/crypto/PublicKey.h" + +#include "Types.h" + +namespace signing = pdo_contracts::crypto::signing; +namespace crypto = pdo_contracts::crypto; + +// ----------------------------------------------------------------- +// Constructor from numeric key +// ----------------------------------------------------------------- +signing::PrivateKey::PrivateKey(const int curve, const ww::types::ByteArray& numeric_key) : Key(curve) +{ + InitializeFromNumericKey(numeric_key); +} + +// ----------------------------------------------------------------- +// Copy constructor +// ----------------------------------------------------------------- +signing::PrivateKey::PrivateKey(const signing::PrivateKey& privateKey) +{ + InitializeFromPrivateKey(privateKey); +} + +// ----------------------------------------------------------------- +// Move constructor +// ----------------------------------------------------------------- +signing::PrivateKey::PrivateKey(signing::PrivateKey&& privateKey) +{ + // when the privateKey does not have a key associated with it, + // e.g. when privateKey.key_ == nullptr, we simply copy the + // uninitialized state; the alternative is to throw and exception + // with the assumption that uninitialized keys should not be assigned + key_ = privateKey.key_; + curve_ = privateKey.curve_; + + privateKey.key_ = nullptr; +} + +// ----------------------------------------------------------------- +// Constructor from encoded string +// ----------------------------------------------------------------- +signing::PrivateKey::PrivateKey(const std::string& encoded) +{ + Deserialize(encoded); +} + +// ----------------------------------------------------------------- +// Destructor +// ----------------------------------------------------------------- +signing::PrivateKey::~PrivateKey() +{ + ResetKey(); +} + +// ----------------------------------------------------------------- +void signing::PrivateKey::ResetKey(void) +{ + // reset the the key, do not change the curve details + if (key_) + EC_KEY_free(key_); + + key_ = nullptr; + curve_ = NID_undef; +} + +// ----------------------------------------------------------------- +// Custom curve constructor with initial key specified as a bignum +// ----------------------------------------------------------------- +bool signing::PrivateKey::InitializeFromNumericKey( + const ww::types::ByteArray& numeric_key) +{ + int res; + + crypto::BIGNUM_ptr bn_key(BN_bin2bn((const unsigned char*)numeric_key.data(), numeric_key.size(), NULL), BN_free); + ERROR_IF_NULL(bn_key, "Crypto Error (PrivateKey::InitializeFromNumericKey): Could not create bignum"); + + crypto::BN_CTX_ptr b_ctx(BN_CTX_new(), BN_CTX_free); + ERROR_IF_NULL(b_ctx, "Crypto Error (PrivateKey::InitializeFromNumericKey): Cound not create BN context"); + + crypto::BIGNUM_ptr r(BN_new(), BN_free); + ERROR_IF_NULL(r, "Crypto Error (PrivateKey::InitializeFromNumericKey): Cound not create BN"); + + crypto::BIGNUM_ptr o(BN_new(), BN_free); + ERROR_IF_NULL(o, "Crypto Error (PrivateKey::InitializeFromNumericKey): Cound not create BN"); + + // setup the private key + crypto::EC_KEY_ptr private_key(EC_KEY_new(), EC_KEY_free); + ERROR_IF_NULL(private_key, "Crypto Error (PrivateKey::InitializeFromNumericKey): Could not create new EC_KEY"); + + crypto::EC_GROUP_ptr ec_group(EC_GROUP_new_by_curve_name(curve_), EC_GROUP_clear_free); + ERROR_IF_NULL(ec_group, "Crypto Error (PrivateKey::InitializeFromNumericKey): Could not create EC_GROUP"); + + res = EC_KEY_set_group(private_key.get(), ec_group.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::InitializeFromNumericKey): Could not set EC_GROUP"); + + EC_GROUP_get_order(ec_group.get(), o.get(), b_ctx.get()); + + res = BN_mod(r.get(), bn_key.get(), o.get(), b_ctx.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::InitializeFromNumericKey): Bignum modulus failed"); + + res = EC_KEY_set_private_key(private_key.get(), r.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::InitializeFromNumericKey): Could not create new key"); + + // setup the public key + crypto::EC_POINT_ptr public_point(EC_POINT_new(ec_group.get()), EC_POINT_free); + ERROR_IF_NULL(public_point, "Crypto Error (PrivateKey::InitializeFromNumericKey): Could not allocate point"); + + res = EC_POINT_mul(ec_group.get(), public_point.get(), r.get(), NULL, NULL, b_ctx.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::InitializeFromNumericKey): point multiplication failed"); + + res = EC_KEY_set_public_key(private_key.get(), public_point.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::InitializeFromNumericKey): failed to set public key"); + + // complete the sanity check + res = EC_KEY_check_key(private_key.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::InitializeFromNumericKey): invalid key"); + + key_ = private_key.get(); + private_key.release(); + + return true; +} + +// ----------------------------------------------------------------- +// InitializeFromPrivateKey +// ----------------------------------------------------------------- +bool signing::PrivateKey::InitializeFromPrivateKey(const signing::PrivateKey& privateKey) +{ + key_ = nullptr; + curve_ = privateKey.curve_; + + // when the privateKey does not have a key associated with it, + // e.g. when privateKey.key_ == nullptr, we simply copy the + // uninitialized state; the alternative is to throw and exception + // with the assumption that uninitialized keys should not be assigned + if (privateKey.key_ != nullptr) + { + key_ = EC_KEY_dup(privateKey.key_); + ERROR_IF_NULL(key_, "Crypto Error (PrivateKey::InitializeFromPrivateKey): Could not copy private key"); + } + + return true; +} + +// ----------------------------------------------------------------- +// boolean conversion operator, returns true if there is a +// key associated with the object +signing::PrivateKey::operator bool(void) const +{ + return key_ != nullptr; +} + +// ----------------------------------------------------------------- +// assignment operator overload +// throws RuntimeError +signing::PrivateKey& signing::PrivateKey::operator=( + const signing::PrivateKey& privateKey) +{ + if (this == &privateKey) + return *this; + + ResetKey(); + + // when the privateKey does not have a key associated with it, + // e.g. when privateKey.key_ == nullptr, we simply copy the + // uninitialized state; the alternative is to throw and exception + // with the assumption that uninitialized keys should not be assigned + if (privateKey) + key_ = EC_KEY_dup(privateKey.key_); + + curve_ = privateKey.curve_; + + return *this; +} // signing::PrivateKey::operator = + +// ----------------------------------------------------------------- +// Deserialize ECDSA Private Key +// ----------------------------------------------------------------- +bool signing::PrivateKey::Deserialize(const std::string& encoded) +{ + ResetKey(); + + crypto::BIO_ptr bio(BIO_new_mem_buf(encoded.c_str(), -1), BIO_free_all); + ERROR_IF_NULL(bio, "Crypto Error (PrivateKey::Deserialize): Could not create BIO"); + + // generally we would throw a CryptoError if an OpenSSL function fails; however, in this + // case, the conversion really means that we've been given a bad value for the key + // so throw a value error instead + key_ = PEM_read_bio_ECPrivateKey(bio.get(), NULL, NULL, NULL); + ERROR_IF_NULL(key_, "Crypto Error (PrivateKey::Deserialize): Could not deserialize private ECDSA key"); + + curve_ = EC_GROUP_get_curve_name(EC_KEY_get0_group(key_)); + + return true; +} + +// ----------------------------------------------------------------- +// Generate ECDSA private key +// ----------------------------------------------------------------- +bool signing::PrivateKey::Generate() +{ + ResetKey(); + + crypto::EC_KEY_ptr private_key(EC_KEY_new(), EC_KEY_free); + ERROR_IF_NULL(private_key, "Crypto Error (PrivateKey::Generate): Could not create new EC_KEY"); + + crypto::EC_GROUP_ptr ec_group(EC_GROUP_new_by_curve_name(curve_), EC_GROUP_clear_free); + ERROR_IF(ec_group, "Crypto Error (PrivateKey::Generate): Could not create EC_GROUP"); + + int res; + + res = EC_KEY_set_group(private_key.get(), ec_group.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::Generate): Could not set EC_GROUP"); + + res = EC_KEY_generate_key(private_key.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::Generate): Could not generate EC_KEY"); + + key_ = private_key.get(); + private_key.release(); + + return true; +} + +// ----------------------------------------------------------------- +// Derive Digital Signature public key from private key +// ----------------------------------------------------------------- +bool signing::PrivateKey::GetPublicKey(signing::PublicKey& publicKey) const +{ + ERROR_IF_NULL(key_, "Crypto Error (PrivateKey::GetPublicKey): Private key is not initialized"); + + PublicKey key(*this); + publicKey = key; + + return true; +} + +// ----------------------------------------------------------------- +// Serialize ECDSA PrivateKey +// ----------------------------------------------------------------- +bool signing::PrivateKey::Serialize(std::string& encoded) const +{ + ERROR_IF_NULL(key_, "Crypto Error (PrivateKey::Serialize): Private key not initialized"); + + crypto::BIO_ptr bio(BIO_new(BIO_s_mem()), BIO_free_all); + ERROR_IF_NULL(bio, "Crypto Error (PrivateKey::Serialize): Could not create BIO"); + + int res; + + res = PEM_write_bio_ECPrivateKey(bio.get(), key_, NULL, NULL, 0, 0, NULL); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::Serialize) failed to write PEM key"); + + int keylen = BIO_pending(bio.get()); + ww::types::ByteArray pem_str(keylen + 1); + res = BIO_read(bio.get(), pem_str.data(), keylen); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::Serialize): Could not read BIO"); + + pem_str[keylen] = '\0'; + encoded = reinterpret_cast(pem_str.data()); + + return true; +} + +// ----------------------------------------------------------------- +// Computes SHA256 hash of message.data(), signs with ECDSA privkey and +// returns ww::types::ByteArray containing raw binary data +// ----------------------------------------------------------------- +bool signing::PrivateKey::SignMessage( + const ww::types::ByteArray& message, + ww::types::ByteArray& signature, + HashFunctionType hash_function) const +{ + ERROR_IF_NULL(key_, "Crypto Error (PrivateKey::SignMessage): Private key not initialized"); + + // Hash, will throw exception on failure + ww::types::ByteArray hash; + hash_function(message, hash); + + // Then Sign + crypto::ECDSA_SIG_ptr sig(ECDSA_do_sign(hash.data(), hash.size(), key_), ECDSA_SIG_free); + ERROR_IF_NULL(sig, "Crypto Error (PrivateKey::SignMessage): Could not compute ECDSA signature"); + + // These are pointers into the signature and do not need to be free'd after use + const BIGNUM* rc = ECDSA_SIG_get0_r(sig.get()); + ERROR_IF_NULL(rc, "Crypto Error (PrivateKey::SignMessage): bad r value"); + + const BIGNUM* sc = ECDSA_SIG_get0_s(sig.get()); + ERROR_IF_NULL(sc, "Crypto Error (PrivateKey::SignMessage): bad s value"); + + crypto::BIGNUM_ptr s(BN_dup(sc), BN_free); + ERROR_IF_NULL(s, "Crypto Error (PrivateKey::SignMessage): Could not dup BIGNUM for s"); + + crypto::BIGNUM_ptr r(BN_dup(rc), BN_free); + ERROR_IF_NULL(r, "Crypto Error (PrivateKey::SignMessage): Could not dup BIGNUM for r"); + + crypto::BIGNUM_ptr ord(BN_new(), BN_free); + ERROR_IF_NULL(ord, "Crypto Error (PrivateKey::SignMessage): Could not create BIGNUM for ord"); + + crypto::BIGNUM_ptr ordh(BN_new(), BN_free); + ERROR_IF_NULL(ordh, "Crypto Error (PrivateKey::SignMessage): Could not create BIGNUM for ordh"); + + int res; + + res = EC_GROUP_get_order(EC_KEY_get0_group(key_), ord.get(), NULL); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::SignMessage): Could not get order"); + + res = BN_rshift(ordh.get(), ord.get(), 1); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::SignMessage): Could not shft order BN"); + + if (BN_cmp(s.get(), ordh.get()) >= 0) + { + res = BN_sub(s.get(), ord.get(), s.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::SignMessage): Could not sub BNs"); + } + + res = ECDSA_SIG_set0(sig.get(), r.get(), s.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::SignMessage): Could not set r and s"); + + // when we invoke ECDSA_SIG_set0 control of the allocated objects is passed + // back to the signature and released when the signature is released so we + // need to drop control from the unique_ptr objects we've been using + r.release(); + s.release(); + + signature.resize(i2d_ECDSA_SIG(sig.get(), nullptr)); + unsigned char* data = signature.data(); + + res = i2d_ECDSA_SIG(sig.get(), &data); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::SignMessage): Could not convert signatureto DER"); + + return true; +} + +// ----------------------------------------------------------------- +// ----------------------------------------------------------------- +bool signing::PrivateKey::GetNumericKey(ww::types::ByteArray& numeric_key) const +{ + ERROR_IF_NULL(key_, "Crypto Error (PrivateKey::GetNumericKey): Key not initialized"); + + const BIGNUM *bn = EC_KEY_get0_private_key(key_); + ERROR_IF_NULL(bn, "Crypto Error (PrivateKey::GetNumericKey) failed to retrieve the private key"); + + numeric_key.resize(BN_num_bytes(bn)); + int res; + + res = BN_bn2bin(bn, numeric_key.data()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::GetNumericKey) failed to convert bignum"); + + return true; +} + +// ----------------------------------------------------------------- +// ----------------------------------------------------------------- +bool signing::PrivateKey::DeriveHardenedKey( + const ww::types::ByteArray& parent_chain_code, // array of random bytes, EXTENDED_KEY_SIZE + const std::string& path_element, // array of strings, path to the current key + signing::PrivateKey& extended_key, + ww::types::ByteArray& extended_chain_code) const +{ + ERROR_IF_NULL(key_, "Crypto Error (PrivateKey::DeriveHardenedKey): Key not initialized"); + + int res; + + // --------------- Setup the big number context --------------- + + BN_CTX_ptr ctx(BN_CTX_new(), BN_CTX_free); + ERROR_IF_NULL(ctx, "Crypto Error (PrivateKey::DeriveHardenedKey): Failed to create BN context"); + + // --------------- Get curve information --------------- + + const EC_GROUP *ec_group = EC_KEY_get0_group(key_); + + BIGNUM_ptr curve_order_ptr(BN_new(), BN_free); + ERROR_IF_NULL(curve_order_ptr, "Crypto Error (PrivateKey::DeriveHardenedKey): Failed to create curve order bignum"); + + res = EC_GROUP_get_order(ec_group, curve_order_ptr.get(), ctx.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::DeriveHardenedKey): Failed to get curve order"); + + // make sure the chain code is the correct size + ERROR_IF(parent_chain_code.size() != BN_num_bytes(curve_order_ptr.get()), "Invalid parent chain code size"); + + // --------------- Start the derivation process ---------------- + + // First step is to build the data array to be hashed + // BIP: HMAC-SHA512(Key = cpar, Data = 0x00 || ser256(kpar) || ser32(i)). + + const BIGNUM *parent_key_ptr = EC_KEY_get0_private_key(key_); + + // Convert the parent key into a byte array + ww::types::ByteArray parent_key; + ERROR_IF(! GetNumericKey(parent_key), "Crypto Error (PrivateKey::DeriveHardenedKey): Failed to get parent key"); + + ww::types::ByteArray data; + data.push_back(0x00); // this is part of the BIP32 specification for extended keys + data.insert(data.end(), parent_key.begin(), parent_key.end()); + + // Append the current context key to the material to be hashed + size_t path_hash = std::hash{}(path_element); + path_hash = path_hash | 0x80000000; // this is part of the BIP32 specification for hardened keys + auto ptr = reinterpret_cast(&path_hash); + data.insert(data.end(), ptr, ptr + sizeof(size_t)); + + // Next step is to compute the HMAC in order to derive the child key and chain code + // BIP: Split I into two 32-byte sequences, IL and IR. + ww::types::ByteArray child_key; + ww::types::ByteArray child_chain_code; + if (! DeriveChildKey(parent_chain_code, data, child_key, child_chain_code)) + return false; + + // The final step is to add the child key to the parent key to get the next extended key + // BIP: The returned child key ki is parse256(IL) + kpar (mod n). + BIGNUM_ptr child_key_ptr(BN_bin2bn((const unsigned char*)child_key.data(), child_key.size(), NULL), BN_free); + ERROR_IF_NULL(child_key_ptr, "Crypto Error (PrivateKey::DeriveHardenedKey): Failed to create child key bignum"); + + res = BN_mod_add( + child_key_ptr.get(), // destination + child_key_ptr.get(), // value 1 + parent_key_ptr, // value 2 + curve_order_ptr.get(), // modulus + ctx.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::DeriveHardenedKey): Failed to add child key to parent key"); + + // Write the updated child key back to a byte array + res = BN_bn2bin(child_key_ptr.get(), child_key.data()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::DeriveHardenedKey): Failed to convert child key to byte array"); + + // --------------- Create the return values ---------------- + extended_key = signing::PrivateKey(curve_, child_key); + extended_chain_code = child_chain_code; + + return true; +} + +// ----------------------------------------------------------------- +// ----------------------------------------------------------------- +bool signing::PrivateKey::DeriveNormalKey( + const ww::types::ByteArray& parent_chain_code, // array of random bytes, EXTENDED_KEY_SIZE + const std::string& path_element, // array of strings, path to the current key + signing::PrivateKey& extended_key, + ww::types::ByteArray& extended_chain_code) const +{ + ERROR_IF_NULL(key_, "Crypto Error (PrivateKey::DeriveNormalKey): Key not initialized"); + + int res; + + // --------------- Setup the big number context --------------- + + BN_CTX_ptr ctx(BN_CTX_new(), BN_CTX_free); + ERROR_IF_NULL(ctx, "Crypto Error (PrivateKey::DeriveNormalKey): Failed to create BN context"); + + // --------------- Get curve information --------------- + + const EC_GROUP *ec_group = EC_KEY_get0_group(key_); + + BIGNUM_ptr curve_order_ptr(BN_new(), BN_free); + ERROR_IF_NULL(curve_order_ptr, "Crypto Error (PrivateKey::DeriveNormalKey): Failed to create curve order bignum"); + + res = EC_GROUP_get_order(ec_group, curve_order_ptr.get(), ctx.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::DeriveNormalKey): Failed to get curve order"); + + // make sure the chain code is the correct size + ERROR_IF(parent_chain_code.size() != BN_num_bytes(curve_order_ptr.get()), "Invalid parent chain code size"); + + // --------------- Start the derivation process ---------------- + + // First step is to build the data array to be hashed + // BIP: HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)) + + const BIGNUM *parent_key_ptr = EC_KEY_get0_private_key(key_); + + signing::PublicKey public_key; + ERROR_IF(! GetPublicKey(public_key), "Crypto Error (PrivateKey::DeriveNormalKey): Failed to get public key"); + + // Convert the extended public key into a byte array, this is point(kpar) + // Push the public key into the data array + ww::types::ByteArray data; + ERROR_IF(! public_key.GetNumericKey(data), "Crypto Error (PrivateKey::DeriveNormalKey): Failed to get numeric key"); + + // Append the current context key to the material to be hashed, this is ser32(i) + size_t path_hash = std::hash{}(path_element); + path_hash = path_hash & 0x7FFFFFFF; // this is part of the BIP32 specification for normal keys + auto ptr = reinterpret_cast(&path_hash); + data.insert(data.end(), ptr, ptr + sizeof(size_t)); + + // Next step is to compute the HMAC in order to derive the child key and chain code + // BIP: Split I into two 32-byte sequences, IL and IR. + ww::types::ByteArray child_key; + ww::types::ByteArray child_chain_code; + if (! DeriveChildKey(parent_chain_code, data, child_key, child_chain_code)) + return false; + + // The final step is to add the child key to the parent key to get the next extended key + // BIP: The returned child key ki is parse256(IL) + kpar (mod n). + BIGNUM_ptr child_key_ptr(BN_bin2bn((const unsigned char*)child_key.data(), child_key.size(), NULL), BN_free); + ERROR_IF_NULL(child_key_ptr, "Crypto Error (PrivateKey::DeriveNormalKey): Failed to create child key bignum"); + + res = BN_mod_add( + child_key_ptr.get(), // destination + child_key_ptr.get(), // value 2 + parent_key_ptr, // value 1 + curve_order_ptr.get(), // modulus + ctx.get()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::DeriveNormalKey): Failed to add child key to parent key"); + + // Write the updated child key back to a byte array + res = BN_bn2bin(child_key_ptr.get(), child_key.data()); + ERROR_IF(res <= 0, "Crypto Error (PrivateKey::DeriveNormalKey): Failed to convert child key to byte array"); + + // --------------- Create the return values ---------------- + extended_key = signing::PrivateKey(curve_, child_key); + extended_chain_code = child_chain_code; + + return true; +} diff --git a/identity-contract/identity/crypto/PrivateKey.h b/identity-contract/identity/crypto/PrivateKey.h new file mode 100644 index 0000000..b9875e1 --- /dev/null +++ b/identity-contract/identity/crypto/PrivateKey.h @@ -0,0 +1,82 @@ +/* Copyright 2018 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "identity/crypto/Crypto.h" +#include "identity/crypto/Key.h" + +#include "Types.h" + +namespace pdo_contracts +{ +namespace crypto +{ + // ECDSA signature + namespace signing + { + class PublicKey; + + class PrivateKey: public pdo_contracts::crypto::signing::Key + { + friend PublicKey; + + private: + void ResetKey(void); + bool InitializeFromNumericKey(const ww::types::ByteArray& numeric_key); + bool InitializeFromPrivateKey(const PrivateKey& privateKey); + + public: + PrivateKey(const int curve = NID_undef) : Key(curve) {}; + PrivateKey(const int curve, const ww::types::ByteArray& numeric_key); + PrivateKey(const PrivateKey& privateKey); + PrivateKey(PrivateKey&& privateKey); + PrivateKey(const std::string& encoded); + + ~PrivateKey(); + + operator bool() const; + PrivateKey& operator=(const PrivateKey& privateKey); + + bool Generate(void); + bool Deserialize(const std::string& encoded); + bool Serialize(std::string& encoded) const; + + bool GetPublicKey(PublicKey& publicKey) const; + + bool SignMessage( + const ww::types::ByteArray& message, + ww::types::ByteArray& signature, + HashFunctionType hash_function) const; + + bool GetNumericKey(ww::types::ByteArray& numeric_key) const; + + bool DeriveHardenedKey( + const ww::types::ByteArray& parent_chain_code, + const std::string& path_element, + PrivateKey& extended_key, + ww::types::ByteArray& extended_chain_code) const; + + bool DeriveNormalKey( + const ww::types::ByteArray& parent_chain_code, // array of random bytes, EXTENDED_KEY_SIZE + const std::string& path_element, // array of strings, path to the current key + PrivateKey& extended_key, + ww::types::ByteArray& extended_chain_code) const; + }; + } +} +} diff --git a/identity-contract/identity/crypto/PublicKey.cpp b/identity-contract/identity/crypto/PublicKey.cpp new file mode 100644 index 0000000..1ab7357 --- /dev/null +++ b/identity-contract/identity/crypto/PublicKey.cpp @@ -0,0 +1,421 @@ +/* Copyright 2018 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +#include "exchange/common/Common.h" + +#include "identity/crypto/Crypto.h" +#include "identity/crypto/PrivateKey.h" +#include "identity/crypto/PublicKey.h" + +#include "Types.h" + +namespace signing = pdo_contracts::crypto::signing; +namespace crypto = pdo_contracts::crypto; + +// ----------------------------------------------------------------- +// Constructor from numeric key +// ----------------------------------------------------------------- +signing::PublicKey::PublicKey(const int curve, const ww::types::ByteArray& numeric_key) : Key(curve) +{ + InitializeFromNumericKey(numeric_key); +} + +// ----------------------------------------------------------------- +// Constructor from PrivateKey +// ----------------------------------------------------------------- +signing::PublicKey::PublicKey(const signing::PrivateKey& privateKey) +{ + InitializeFromPrivateKey(privateKey); +} + +// ----------------------------------------------------------------- +// Constructor from PublicKey +// ----------------------------------------------------------------- +signing::PublicKey::PublicKey(const signing::PublicKey& publicKey) +{ + InitializeFromPublicKey(publicKey); +} + +// ----------------------------------------------------------------- +// Move constructor +// ----------------------------------------------------------------- +signing::PublicKey::PublicKey(signing::PublicKey&& publicKey) +{ + // when the publicKey does not have a key associated with it, + // e.g. when publicKey.key_ == nullptr, we simply copy the + // uninitialized state; the alternative is to throw and exception + // with the assumption that uninitialized keys should not be assigned + key_ = publicKey.key_; + curve_ = publicKey.curve_; + + publicKey.key_ = nullptr; +} + +// ----------------------------------------------------------------- +// Constructor from encoded string +// ----------------------------------------------------------------- +signing::PublicKey::PublicKey(const std::string& encoded) : Key(NID_undef) +{ + Deserialize(encoded); +} + +// ----------------------------------------------------------------- +// Destructor +signing::PublicKey::~PublicKey() +{ + ResetKey(); +} // signing::PublicKey::~PublicKey + +// ----------------------------------------------------------------- +void signing::PublicKey::ResetKey(void) +{ + // reset the the key, do not change the curve details + if (key_) + EC_KEY_free(key_); + + key_ = nullptr; + curve_ = NID_undef; +} + +// ----------------------------------------------------------------- +// InitializeFromNumericKey +// +// This function initializes the public key from a numeric key. It is +// implemented as a separate function because WASM constructors cannot +// easily throw an exception when an error occurs. +// ----------------------------------------------------------------- +bool signing::PublicKey::InitializeFromNumericKey(const ww::types::ByteArray& numeric_key) +{ + int res; + + crypto::BN_CTX_ptr b_ctx(BN_CTX_new(), BN_CTX_free); + ERROR_IF_NULL(b_ctx, "Crypto Error (PublicKey::InitializeFromNumericKey): Cound not create BN context"); + + EC_GROUP_ptr group(EC_GROUP_new_by_curve_name(curve_), EC_GROUP_clear_free); + ERROR_IF_NULL(group, "Crypto Error (PublicKey::InitializeFromNumericKey): Cound not create group"); + + EC_GROUP_set_point_conversion_form(group.get(), POINT_CONVERSION_COMPRESSED); + + EC_KEY_ptr public_key(EC_KEY_new(), EC_KEY_free); + ERROR_IF_NULL(public_key, "Crypto Error (PublicKey::InitializeFromNumericKey): Cound not create public_key"); + + res = EC_KEY_set_group(public_key.get(), group.get()); + ERROR_IF(res <= 0, "Crypto Error (PublicKey::InitializeFromNumericKey): Could not set EC_GROUP"); + + EC_POINT_ptr point(EC_POINT_new(group.get()), EC_POINT_free); + ERROR_IF_NULL(point, "Crypto Error (PublicKey::InitializeFromNumericKey): Cound not create point"); + + res = EC_POINT_oct2point(group.get(), point.get(), numeric_key.data(), numeric_key.size(), b_ctx.get()); + ERROR_IF(res <= 0, "Crypto Error (PublicKey::InitializeFromNumericKey): Cound not convert octet to point"); + + res = EC_KEY_set_public_key(public_key.get(), point.get()); + ERROR_IF(res <= 0, "Crypto Error (PublicKey::InitializeFromNumericKey): Cound not set public key"); + + key_ = public_key.get(); + public_key.release(); + + return true; +} + +// ----------------------------------------------------------------- +// InitializeFromPrivateKey +// +// Initialize the public key from the private key. This function is +// necessary because WASM does not support constructors that can throw +// exceptions. +// ----------------------------------------------------------------- +bool signing::PublicKey::InitializeFromPrivateKey(const signing::PrivateKey& privateKey) +{ + key_ = nullptr; + curve_ = privateKey.curve_; + + // when the privateKey does not have a key associated with it, + // e.g. when privateKey.key_ == nullptr, we simply copy the + // uninitialized state; the alternative is to throw and exception + // with the assumption that uninitialized keys should not be assigned + if (privateKey) + { + int res; + + crypto::EC_KEY_ptr public_key(EC_KEY_new(), EC_KEY_free); + ERROR_IF_NULL(public_key, "Crypto Error (PublicKey::InitializeFromPrivateKey): Could not create new public EC_KEY"); + + crypto::EC_GROUP_ptr ec_group(EC_GROUP_new_by_curve_name(curve_), EC_GROUP_clear_free); + ERROR_IF_NULL(ec_group, "Crypto Error (PublicKey::InitializeFromPrivateKey): Could not create EC_GROUP"); + + EC_GROUP_set_point_conversion_form(ec_group.get(), POINT_CONVERSION_COMPRESSED); + + crypto::BN_CTX_ptr context(BN_CTX_new(), BN_CTX_free); + ERROR_IF_NULL(context, "Crypto Error (PublicKey::InitializeFromPrivateKey): Could not create new CTX"); + + res = EC_KEY_set_group(public_key.get(), ec_group.get()); + ERROR_IF(res <= 0, "Crypto Error (PublicKey::InitializeFromPrivateKey): Could not set EC_GROUP"); + + const EC_POINT* p = EC_KEY_get0_public_key(privateKey.key_); + ERROR_IF_NULL(p, "Crypto Error (PublicKey::InitializeFromPrivateKey): Could not create new EC_POINT"); + + res = EC_KEY_set_public_key(public_key.get(), p); + ERROR_IF(res <= 0, "Crypto Error (PublicKey::InitializeFromPrivateKey): Could not set public EC_KEY"); + + key_ = public_key.get(); + public_key.release(); + } + + return true; +} + +// ----------------------------------------------------------------- +// InitializeFromPublicKey +// +// This function initializes the public key from another public key. +// ----------------------------------------------------------------- +bool signing::PublicKey::InitializeFromPublicKey(const signing::PublicKey& publicKey) +{ + key_ = nullptr; + curve_ = publicKey.curve_; + + // when the publicKey does not have a key associated with it, + // e.g. when publicKey.key_ == nullptr, we simply copy the + // uninitialized state; the alternative is to throw and exception + // with the assumption that uninitialized keys should not be assigned + if (publicKey) + { + key_ = EC_KEY_dup(publicKey.key_); + ERROR_IF_NULL(key_, "Crypto Error (PublicKey::InitializeFromPublicKey): Could not copy public key"); + } + + return true; +} + +// ----------------------------------------------------------------- +// boolean conversion operator, returns true if there is a +// key associated with the object +signing::PublicKey::operator bool(void) const +{ + return key_ != nullptr; +} + +// ----------------------------------------------------------------- +// assignment operator overload +// ***** RETURN VALUE ***** +// ----------------------------------------------------------------- +signing::PublicKey& signing::PublicKey::operator=( + const signing::PublicKey& publicKey) +{ + if (this == &publicKey) + return *this; + + ResetKey(); + + // when the publicKey does not have a key associated with it, + // e.g. when publicKey.key_ == nullptr, we simply copy the + // uninitialized state; the alternative is to throw and exception + // with the assumption that uninitialized keys should not be assigned + if (publicKey) + key_ = EC_KEY_dup(publicKey.key_); + + curve_ = publicKey.curve_; + + return *this; +} + +// ----------------------------------------------------------------- +// Deserialize Digital Signature Public Key +// ----------------------------------------------------------------- +bool signing::PublicKey::Deserialize(const std::string& encoded) +{ + ResetKey(); + + crypto::BIO_ptr bio(BIO_new_mem_buf(encoded.c_str(), -1), BIO_free_all); + ERROR_IF_NULL(bio, "Crypto Error (PublicKey::Deserialize): Could not create BIO"); + + key_ = PEM_read_bio_EC_PUBKEY(bio.get(), NULL, NULL, NULL); + ERROR_IF_NULL(key_, "Crypto Error (PublicKey::Deserialize): Could not deserialize public ECDSA key"); + + curve_ = EC_GROUP_get_curve_name(EC_KEY_get0_group(key_)); + + return true; +} + +// ----------------------------------------------------------------- +// Serialize Digital Signature Public Key +// ----------------------------------------------------------------- +bool signing::PublicKey::Serialize(std::string& encoded) const +{ + ERROR_IF_NULL(key_, "Crypto Error (PublicKey::Serialize): public key not initialized"); + + crypto::BIO_ptr bio(BIO_new(BIO_s_mem()), BIO_free_all); + ERROR_IF_NULL(bio, "Crypto Error (PublicKey::Serialize): Could not create BIO"); + + int res; + + res = PEM_write_bio_EC_PUBKEY(bio.get(), key_); + ERROR_IF(res <= 0, "Crypto Error (PublicKey::Serialize): Could not serialize key"); + + int keylen = BIO_pending(bio.get()); + + ww::types::ByteArray pem_str(keylen + 1); + + res = BIO_read(bio.get(), pem_str.data(), keylen); + ERROR_IF(res <= 0, "Crypto Error (PublicKey::Serialize): Could not read BIO"); + + pem_str[keylen] = '\0'; + encoded = reinterpret_cast(pem_str.data()); + + return true; +} + +// ----------------------------------------------------------------- +// Verifies SHA256 ECDSA signature of message +// input signature ww::types::ByteArray contains raw binary data +// returns 1 if signature is valid, 0 if signature is invalid and -1 if there is +// an internal error +// ***** FIX HASH ***** +// ----------------------------------------------------------------- +int signing::PublicKey::VerifySignature( + const ww::types::ByteArray& message, + const ww::types::ByteArray& signature, + HashFunctionType hash_function) const +{ + ERROR_IF_NULL(key_, "Crypto Error (PublicKey::VerifySignature): public key not initialized"); + + ww::types::ByteArray hash; + ERROR_IF(! hash_function(message, hash), "Crypto Error (PublicKey::VerifySignature): Could not hash message"); + + // Decode signature B64 -> DER -> ECDSA_SIG, must be null terminated + ERROR_IF(signature.back() != 0, "Crypto Error (PublicKey::VerifySignature): Invalid signature format"); + + const unsigned char* der_SIG = (const unsigned char*)signature.data(); + crypto::ECDSA_SIG_ptr sig( + d2i_ECDSA_SIG(NULL, (const unsigned char**)(&der_SIG), signature.size()), ECDSA_SIG_free); + if (sig == nullptr) + return -1; + + // Verify + // ECDSA_do_verify() returns 1 for a valid sig, 0 for an invalid sig and -1 on error + return ECDSA_do_verify(hash.data(), hash.size(), sig.get(), key_); +} // signing::PublicKey::VerifySignature + +// ----------------------------------------------------------------- +// ----------------------------------------------------------------- +bool signing::PublicKey::GetNumericKey(ww::types::ByteArray& numeric_key) const +{ + ERROR_IF_NULL(key_, "Crypto Error (PublicKey::GetNumericKey): Key not initialized"); + + crypto::BN_CTX_ptr b_ctx(BN_CTX_new(), BN_CTX_free); + ERROR_IF_NULL(b_ctx, "Crypto Error (PublicKey::GetNumericKey): Cound not create BN context"); + + const EC_GROUP *group = EC_KEY_get0_group(key_); + const EC_POINT *point = EC_KEY_get0_public_key(key_); + + int converted_size; + + // this call just returns the size of the buffer that is necessary + converted_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, NULL, 0, b_ctx.get()); + numeric_key.resize(converted_size); + + // this call writes the data to the numeric_key + int res; + res = EC_POINT_point2oct( + group, point, POINT_CONVERSION_COMPRESSED, numeric_key.data(), numeric_key.size(), b_ctx.get()); + ERROR_IF(res <= 0, "Crypto Error (PublicKey::GetNumericKey): Cound not convert point to octet"); + + return true; +} + +// ----------------------------------------------------------------- +// ----------------------------------------------------------------- +bool signing::PublicKey::DerivePublicKey( + const ww::types::ByteArray& parent_chain_code, // array of random bytes, EXTENDED_KEY_SIZE + const std::string& path_element, // path to the current key + signing::PublicKey& extended_key, + ww::types::ByteArray& extended_chain_code) const +{ + ERROR_IF_NULL(key_, "Crypto Error (PublicKey::DerivePublicKey): Key not initialized"); + + int res; + + // --------------- Setup the big number context --------------- + + BN_CTX_ptr ctx(BN_CTX_new(), BN_CTX_free); + ERROR_IF_NULL(ctx, "Crypto Error (PublicKey::DerivePublicKey): Failed to create BN context"); + + // --------------- Get curve information --------------- + + const EC_GROUP *ec_group = EC_KEY_get0_group(key_); + + BIGNUM_ptr curve_order_ptr(BN_new(), BN_free); + ERROR_IF_NULL(curve_order_ptr, "Crypto Error (PublicKey::DerivePublicKey): Failed to create curve order bignum"); + + res = EC_GROUP_get_order(ec_group, curve_order_ptr.get(), ctx.get()); + ERROR_IF(res <= 0, "Crypto Error (PublicKey::DerivePublicKey): Failed to get curve order"); + + // make sure the chain code is the correct size + ERROR_IF(parent_chain_code.size() != BN_num_bytes(curve_order_ptr.get()), "Invalid parent chain code size"); + + // --------------- Start the derivation process ---------------- + + // First step is to build the data array to be hashed + // BIP: HMAC-SHA512(Key = cpar, Data = serP(Kpar) || ser32(i)). + + // Convert the extended public key into a byte array, this is point(kpar) + // Push the public key into the data array + ww::types::ByteArray data; + ERROR_IF(! GetNumericKey(data), "Crypto Error (PublicKey::DerivePublicKey): Failed to get numeric key"); + + // Append the current context key to the material to be hashed, this is ser32(i) + size_t path_hash = std::hash{}(path_element); + path_hash = path_hash & 0x7FFFFFFF; // this is part of the BIP32 specification for normal keys + auto ptr = reinterpret_cast(&path_hash); + data.insert(data.end(), ptr, ptr + sizeof(size_t)); + + // Next step is to compute the HMAC in order to derive the child key and chain code + // BIP: Split I into two 32-byte sequences, IL and IR. + ww::types::ByteArray child_key; + ww::types::ByteArray child_chain_code; + if (! DeriveChildKey(parent_chain_code, data, child_key, child_chain_code)) + return false; + + // The final step is to add the child key to the parent key to get the next extended key + // BIP: Ki is point(parse256(IL)) + Kpar + + const EC_POINT *ec_point = EC_KEY_get0_public_key(key_); + + // convert child_key bytearray to a point + crypto::EC_POINT_ptr child_key_point_ptr(EC_POINT_new(ec_group), EC_POINT_free); + ERROR_IF_NULL(child_key_point_ptr, "Crypto Error (PublicKey::DerivePublicKey): Failed to create child key point"); + + res = EC_POINT_oct2point(ec_group, child_key_point_ptr.get(), child_key.data(), child_key.size(), ctx.get()); + ERROR_IF(res <= 0, "Crypto Error (PublicKey::DerivePublicKey): Failed to convert child key to point"); + + // add the child key point to the parent key point + res = EC_POINT_add(ec_group, child_key_point_ptr.get(), ec_point, child_key_point_ptr.get(), ctx.get()); + + // --------------- Create the return values ---------------- + extended_key.curve_ = curve_; + res = EC_KEY_set_public_key(extended_key.key_, child_key_point_ptr.get()); + ERROR_IF(res <= 0, "Crypto Error (PublicKey::DerivePublicKey): Failed to set public key"); + + extended_chain_code = child_chain_code; + + return true; +} diff --git a/identity-contract/identity/crypto/PublicKey.h b/identity-contract/identity/crypto/PublicKey.h new file mode 100644 index 0000000..1d3a9b6 --- /dev/null +++ b/identity-contract/identity/crypto/PublicKey.h @@ -0,0 +1,73 @@ +/* Copyright 2018 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "identity/crypto/Crypto.h" +#include "identity/crypto/Key.h" + +#include "Types.h" + +namespace pdo_contracts +{ +namespace crypto +{ + namespace signing + { + class PrivateKey; + + class PublicKey: public Key + { + private: + void ResetKey(void); + bool InitializeFromNumericKey(const ww::types::ByteArray& numeric_key); + bool InitializeFromPrivateKey(const PrivateKey& privateKey); + bool InitializeFromPublicKey(const PublicKey& publicKey); + + public: + PublicKey(const int curve = NID_undef) : Key(curve) {}; + PublicKey(const int curve, const ww::types::ByteArray& numeric_key); + PublicKey(const PrivateKey& privateKey); + PublicKey(const PublicKey& publicKey); + PublicKey(PublicKey&& publicKey); + PublicKey(const std::string& encoded); + + ~PublicKey(); + + PublicKey& operator=(const PublicKey& publicKey); + operator bool() const; + + bool Deserialize(const std::string& encoded); + bool Serialize(std::string& encoded) const; + + int VerifySignature( + const ww::types::ByteArray& message, + const ww::types::ByteArray& signature, + HashFunctionType hash_function) const; + + bool GetNumericKey(ww::types::ByteArray& numeric_key) const; + + bool DerivePublicKey( + const ww::types::ByteArray& parent_chain_code, // array of random bytes, EXTENDED_KEY_SIZE + const std::string& path_element, + PublicKey& extended_key, + ww::types::ByteArray& extended_chain_code) const; + + }; + } +} +} diff --git a/identity-contract/methods.cmake b/identity-contract/methods.cmake index 72f72e5..93609de 100644 --- a/identity-contract/methods.cmake +++ b/identity-contract/methods.cmake @@ -26,11 +26,13 @@ LIST(APPEND IDENTITY_INCLUDES ${CMAKE_CURRENT_LIST_DIR}) # --------------------------------------------- # Set up the default source list # --------------------------------------------- -FILE(GLOB IDENTITY_COMMON_SOURCE ${CMAKE_CURRENT_LIST_DIR}/identity/common/*.cpp) -FILE(GLOB IDENTITY_CONTRACT_SOURCE ${CMAKE_CURRENT_LIST_DIR}/identity/contracts/*.cpp) +FILE(GLOB IDENTITY_COMMON_SOURCE ${CMAKE_CURRENT_LIST_DIR}/identity/common/[A-Za-z]*.cpp) +FILE(GLOB IDENTITY_CRYPTO_SOURCE ${CMAKE_CURRENT_LIST_DIR}/identity/crypto/[A-Za-z]*.cpp) +FILE(GLOB IDENTITY_CONTRACT_SOURCE ${CMAKE_CURRENT_LIST_DIR}/identity/contracts/[A-Za-z]*.cpp) SET (IDENTITY_SOURCES) LIST(APPEND IDENTITY_SOURCES ${IDENTITY_COMMON_SOURCE}) +LIST(APPEND IDENTITY_SOURCES ${IDENTITY_CRYPTO_SOURCE}) LIST(APPEND IDENTITY_SOURCES ${IDENTITY_CONTRACT_SOURCE}) # --------------------------------------------- diff --git a/identity-contract/openssl/README.md b/identity-contract/openssl/README.md new file mode 100644 index 0000000..4c73980 --- /dev/null +++ b/identity-contract/openssl/README.md @@ -0,0 +1,16 @@ + + +This directory provides a simple script for building OpenSSL for WASM that will run inside an SGX enclave (meaning that no dependencies on syscalls are acceptable). The script and patches were inspired by other projects including +(openssl-wasm)[https://github.com/jedisct1/openssl-wasm] and (SGX-SSL)[https://github.com/intel/intel-sgx-ssl]. Licenses for both projects are included. + +Note that there are some ongoing dependencies for missing functions. Any application that wants to use the library will likely have to provide definitions for functions such as `getenv`: + +```c +extern "C" char* getenv(const char* name) +{ + return NULL; +} +``` diff --git a/identity-contract/openssl/build.sh b/identity-contract/openssl/build.sh new file mode 100755 index 0000000..02bcaba --- /dev/null +++ b/identity-contract/openssl/build.sh @@ -0,0 +1,121 @@ +#! /bin/bash +# Copyright 2025 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +source ${PDO_HOME}/bin/lib/common.sh + +OPENSSL_VERSION=3.1.8 +PATCH_DIR=${PWD}/patches +OUTPUT_DIR=${PWD}/precompiled + +while getopts "o:p:v:" opt; do + case $opt in + o) + OUTPUT_DIR=$OPTARG ;; + p) + PATCH_DIR=$OPTARG ;; + v) + OPENSSL_VERSION=$OPTARG ;; + \?) + die "Invalid option: -$OPTARG" >&2 ;; + esac +done + +# ----------------------------------------------------------------- +# Get the openssl source code and unpack it +# ----------------------------------------------------------------- +OPENSSL_SOURCE=openssl-${OPENSSL_VERSION} +if [ -d ${OPENSSL_SOURCE} ]; then + say "OpenSSL source code already exists, skipping download" +else + say "Downloading OpenSSL source code" + try wget https://github.com/openssl/openssl/releases/download/${OPENSSL_SOURCE}/${OPENSSL_SOURCE}.tar.gz + try tar zxf openssl-${OPENSSL_VERSION}.tar.gz + try rm -f openssl-${OPENSSL_VERSION}.tar.gz +fi + +# ----------------------------------------------------------------- +# Patch openssl +# ----------------------------------------------------------------- +# The following patches are applied to the source code, we use a dry +# run in order to check if the patch is already applied. If it is not +# then apply it + +pushd ${OPENSSL_SOURCE} + +for patch in ${PATCH_DIR}/*.patch; do + patch -p1 -N --dry-run --silent <"$patch" >/dev/null 2>/dev/null || continue + patch -p1 <"$patch" +done + +# ----------------------------------------------------------------- +# Configure openssl +# ----------------------------------------------------------------- +WASI_TOOLKIT_PATH=/opt/wasi-sdk/bin +if [ ! -d ${WASI_TOOLKIT_PATH} ]; then + say "WASI toolkit not found at ${WASI_TOOLKIT_PATH}, please install it" + exit 1 +fi + +# First set up the paths to the wasi toolkit install +export AR="${WASI_TOOLKIT_PATH}/llvm-ar" +export RANLIB="${WASI_TOOLKIT_PATH}/llvm-ranlib" +export CC="${WASI_TOOLKIT_PATH}/clang" +export CXX="${WASI_TOOLKIT_PATH}/clang++" + +# Now specify the OpenSSL configuration options, these options are mix of selections from SGX SSL and +# from https://github.com/jedisct1/openssl-wasm +EXTRA_CPP_FLAGS="-D_BSD_SOURCE -D_WASI_EMULATED_GETPID -DOPENSSL_SMALL_FOOTPRINT -DNO_SYSLOG" +EXTRA_CPP_FLAGS="${EXTRA_CPP_FLAGS} -Dgetuid=getpagesize -Dgeteuid=getpagesize -Dgetgid=getpagesize -Dgetegid=getpagesize" + +# Disable building tests in the library +DISABLE_TESTS="no-tests no-buildtest-c++ no-external-tests no-unit-test" + +# Disable features; these are inspired by the SGX SSL configuration options +# Earlier versions of openssl can include 'no-atexit' but 3.1.0 requires a +# different workaround +DISABLE_FOR_SGX="no-autoalginit no-cms no-dsa no-err no-filenames no-rdrand no-zlib" + +# Prep for WASM; these are inspired by https://github.com/jedisct1/openssl-wasm +DISABLE_FOR_WASM="no-asm no-async no-egd no-ktls no-module no-posix-io no-secure-memory no-shared no-sock" +DISABLE_FOR_WASM="${DISABLE_FOR_WASM} no-stdio no-threads no-ui-console no-weak-ssl-ciphers" + +# Set up the environment variables for the build +export CROSS_COMPILE="" +export CFLAGS="-Ofast -Werror -Qunused-arguments -Wno-shift-count-overflow" +export CPPFLAGS="${CPPFLAGS} ${EXTRA_CPP_FLAGS}" +export CXXFLAGS="-Werror -Qunused-arguments -Wno-shift-count-overflow" +export LDFLAGS="-s -lwasi-emulated-getpid" +try ./Configure --banner="wasm32-wasi port" ${DISABLE_TESTS} ${DISABLE_FOR_SGX} ${DISABLE_FOR_WASM} wasm32-wasi + +# ----------------------------------------------------------------- +# Build the library +# ----------------------------------------------------------------- +NUM_CORES=$(grep -c '^processor' /proc/cpuinfo) +if [ "$NUM_CORES " == " " ]; then + NUM_CORES=4 +fi + +try make "-j${NUM_CORES}" + +# ----------------------------------------------------------------- +# Install the library and include files +# ----------------------------------------------------------------- +popd + +mkdir -p ${OUTPUT_DIR}/lib +mv ${OPENSSL_SOURCE}/*.a ${OUTPUT_DIR}/lib + +mkdir -p ${OUTPUT_DIR}/include +cp -r ${OPENSSL_SOURCE}/include/openssl ${OUTPUT_DIR}/include diff --git a/identity-contract/openssl/licenses/openssl-wasm b/identity-contract/openssl/licenses/openssl-wasm new file mode 100644 index 0000000..a164d74 --- /dev/null +++ b/identity-contract/openssl/licenses/openssl-wasm @@ -0,0 +1,26 @@ +The following only applies to the build script. +OpenSSL's license can be found in the `openssl/LICENSE.txt` file. + +--- + +MIT License + +Copyright (c) 2023-2024 Frank Denis + +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. diff --git a/identity-contract/openssl/licenses/sgx-ssl b/identity-contract/openssl/licenses/sgx-ssl new file mode 100644 index 0000000..3192a4f --- /dev/null +++ b/identity-contract/openssl/licenses/sgx-ssl @@ -0,0 +1,87 @@ +BSD 3-clause "New" or "Revised" License + +Copyright (C) 2011-2017 Intel Corporation. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of Intel Corporation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +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 +OWNER 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. + +================================================================= + +This project use OpenSSL crypto tests. + +/* ==================================================================== + * Copyright (c) 1998-2017 The OpenSSL Project. 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. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED 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 OpenSSL PROJECT OR + * ITS 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. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ \ No newline at end of file diff --git a/identity-contract/openssl/patches/assert.patch b/identity-contract/openssl/patches/assert.patch new file mode 100644 index 0000000..cbaf55f --- /dev/null +++ b/identity-contract/openssl/patches/assert.patch @@ -0,0 +1,13 @@ +diff --git a/include/openssl/crypto.h.in b/include/openssl/crypto.h.in +index b2d691b..3e74c1d 100644 +--- a/include/openssl/crypto.h.in ++++ b/include/openssl/crypto.h.in +@@ -394,7 +394,6 @@ ossl_noreturn void OPENSSL_die(const char *assertion, const char *file, int line + # ifndef OPENSSL_NO_DEPRECATED_1_1_0 + # define OpenSSLDie(f,l,a) OPENSSL_die((a),(f),(l)) + # endif +-# define OPENSSL_assert(e) \ +- (void)((e) ? 0 : (OPENSSL_die("assertion failed: " #e, OPENSSL_FILE, OPENSSL_LINE), 1)) ++# define OPENSSL_assert(e) + + int OPENSSL_isservice(void); diff --git a/identity-contract/openssl/patches/rand.patch b/identity-contract/openssl/patches/rand.patch new file mode 100644 index 0000000..a72e12d --- /dev/null +++ b/identity-contract/openssl/patches/rand.patch @@ -0,0 +1,26 @@ +diff --git a/crypto/rand/rand_lib.c b/crypto/rand/rand_lib.c +index ce95bf62..0001e7b5 100644 +--- a/crypto/rand/rand_lib.c ++++ b/crypto/rand/rand_lib.c +@@ -320,6 +320,10 @@ const RAND_METHOD *RAND_get_rand_method(void) + int RAND_priv_bytes_ex(OSSL_LIB_CTX *ctx, unsigned char *buf, size_t num, + unsigned int strength) + { ++#ifdef __wasi__ ++ arc4random_buf(buf, num); ++ return 1; ++#endif + EVP_RAND_CTX *rand; + #if !defined(OPENSSL_NO_DEPRECATED_3_0) && !defined(FIPS_MODULE) + const RAND_METHOD *meth = RAND_get_rand_method(); +@@ -349,6 +353,10 @@ int RAND_priv_bytes(unsigned char *buf, int num) + int RAND_bytes_ex(OSSL_LIB_CTX *ctx, unsigned char *buf, size_t num, + unsigned int strength) + { ++#ifdef __wasi__ ++ arc4random_buf(buf, num); ++ return 1; ++#endif + EVP_RAND_CTX *rand; + #if !defined(OPENSSL_NO_DEPRECATED_3_0) && !defined(FIPS_MODULE) + const RAND_METHOD *meth = RAND_get_rand_method(); diff --git a/identity-contract/openssl/patches/ssl_cert.patch b/identity-contract/openssl/patches/ssl_cert.patch new file mode 100644 index 0000000..229104a --- /dev/null +++ b/identity-contract/openssl/patches/ssl_cert.patch @@ -0,0 +1,20 @@ +diff --git a/ssl/ssl_cert.c b/ssl/ssl_cert.c +index 1c4f4529..97725de6 100644 +--- a/ssl/ssl_cert.c ++++ b/ssl/ssl_cert.c +@@ -829,6 +829,7 @@ int SSL_add_file_cert_subjects_to_stack(STACK_OF(X509_NAME) *stack, + return ret; + } + ++#ifndef OPENSSL_NO_POSIX_IO + int SSL_add_dir_cert_subjects_to_stack(STACK_OF(X509_NAME) *stack, + const char *dir) + { +@@ -876,6 +877,7 @@ int SSL_add_dir_cert_subjects_to_stack(STACK_OF(X509_NAME) *stack, + + return ret; + } ++#endif + + static int add_uris_recursive(STACK_OF(X509_NAME) *stack, + const char *uri, int depth) diff --git a/identity-contract/openssl/patches/wasi-config.patch b/identity-contract/openssl/patches/wasi-config.patch new file mode 100644 index 0000000..999a0dd --- /dev/null +++ b/identity-contract/openssl/patches/wasi-config.patch @@ -0,0 +1,21 @@ +diff --git a/Configurations/10-main.conf b/Configurations/10-main.conf +index 46094f59..c3db2695 100644 +--- a/Configurations/10-main.conf ++++ b/Configurations/10-main.conf +@@ -1887,6 +1887,16 @@ my %targets = ( + multilib => "64", + }, + ++ "wasm32-wasi" => { ++ inherit_from => [ "BASE_unix" ], ++ CC => "clang", ++ CXX => "clang++", ++ cflags => add("--target=wasm32-wasi"), ++ cxxflags => add("--target=wasm32-wasi"), ++ lib_cppflags => add("-DL_ENDIAN"), ++ bn_ops => "THIRTY_TWO_BIT", ++ }, ++ + ##### VxWorks for various targets + "vxworks-ppc60x" => { + inherit_from => [ "BASE_unix" ], diff --git a/identity-contract/test/tests/identity.psh b/identity-contract/test/tests/identity.psh index 814d4e7..ff3964c 100755 --- a/identity-contract/test/tests/identity.psh +++ b/identity-contract/test/tests/identity.psh @@ -51,7 +51,7 @@ if -o ${_error_code_} 0 fi ## ================================================================= -echo ${HEADER} test key creation, these should succeed ${ENDC} +echo ${HEADER} test key creation, normal keys, these should succeed ${ENDC} ## ================================================================= identity_wallet_contract register_signing_context -w -f ${_contract_} \ -d "test1 context" --fixed -p "test1" @@ -89,7 +89,45 @@ if -o ${_error_code_} 0 fi ## ================================================================= -echo ${HEADER} test key description, these should succeed ${ENDC} +echo ${HEADER} test key creation, hardened keys, these should succeed ${ENDC} +## ================================================================= +identity_wallet_contract register_signing_context -w -f ${_contract_} \ + -d "#test1 context" --fixed -p "#test1" +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to save signing context test1 ${ENDC} + exit -v ${_error_code_} +fi + +identity_wallet_contract register_signing_context -w -f ${_contract_} \ + -d "#test2 context" --fixed -p "#test2" +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to save signing context test2 ${ENDC} + exit -v ${_error_code_} +fi + +identity_wallet_contract register_signing_context -w -f ${_contract_} \ + -d "#test1.1 context" --fixed -p "#test1" "#test1.1" +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to save signing context test1 test1.1 ${ENDC} + exit -v ${_error_code_} +fi + +identity_wallet_contract register_signing_context -w -f ${_contract_} \ + -d "#test2.1 context" --fixed -p "#test2" "#test2.1" +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to save signing context test2 test2.1 ${ENDC} + exit -v ${_error_code_} +fi + +identity_wallet_contract register_signing_context -w -f ${_contract_} \ + -d "#test3 extensible context" --extensible -p "#test3" +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to save extensible signing context test3 ${ENDC} + exit -v ${_error_code_} +fi + +## ================================================================= +echo ${HEADER} test key description, normal keys, these should succeed ${ENDC} ## ================================================================= identity_wallet_contract describe_signing_context -f ${_contract_} -s _description_ -p "test1" if -o ${_error_code_} 0 @@ -134,7 +172,52 @@ fi echo TEST3 KEY : ${_key_} ## ================================================================= -echo ${HEADER} test key signing/verification, these should succeed ${ENDC} +echo ${HEADER} test key description, hardened keys, these should succeed ${ENDC} +## ================================================================= +identity_wallet_contract describe_signing_context -f ${_contract_} -s _description_ -p "#test1" +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to fetch signing context test1 ${ENDC} + exit -v ${_error_code_} +fi +echo TEST1 DESCRIPTION : ${_description_} + +identity_wallet_contract get_verifying_key -f ${_contract_} -s _key_ -p "#test1" +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to fetch verifying key test1 ${ENDC} + exit -v ${_error_code_} +fi +echo TEST1 KEY : ${_key_} + +identity_wallet_contract describe_signing_context -f ${_contract_} -s _description_ -p "#test1" "#test1.1" +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to fetch signing context test1 test1.1 ${ENDC} + exit -v ${_error_code_} +fi +echo TEST1 TEST1.1 DESCRIPTION : ${_description_} + +identity_wallet_contract get_verifying_key -f ${_contract_} -s _key_ -p "#test1" "#test1.1" +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to fetch verifying key test1 test1.1 ${ENDC} + exit -v ${_error_code_} +fi +echo TEST1 TEST1.1 KEY : ${_key_} + +identity_wallet_contract describe_signing_context -f ${_contract_} -s _description_ -p "#test3" +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to fetch extensible signing context test3 ${ENDC} + exit -v ${_error_code_} +fi +echo TEST3 DESCRIPTION : ${_description_} + +identity_wallet_contract get_verifying_key -f ${_contract_} -s _key_ -p "#test3" +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to fetch verifying key test3 ${ENDC} + exit -v ${_error_code_} +fi +echo TEST3 KEY : ${_key_} + +## ================================================================= +echo ${HEADER} test key signing/verification, normal keys, these should succeed ${ENDC} ## ================================================================= echo Test simple key verification identity_wallet_contract sign -f ${_contract_} -s _sig_ -p test1 -m 'this is a test' @@ -178,6 +261,51 @@ if -o ${_error_code_} 0 fi echo verified: ${_v_} +## ================================================================= +echo ${HEADER} test key signing/verification, hardened keys, these should succeed ${ENDC} +## ================================================================= +echo Test simple key verification +identity_wallet_contract sign -f ${_contract_} -s _sig_ -p '#test1' -m 'this is a test' +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to sign message using signing context #test1 ${ENDC} + exit -v ${_error_code_} +fi + +identity_wallet_contract verify -f ${_contract_} -s _v_ -p '#test1' -m 'this is a test' --signature ${_sig_} +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to verify signature ${ENDC} + exit -v ${_error_code_} +fi +echo verified: ${_v_} + +echo Test second level key verification +identity_wallet_contract sign -f ${_contract_} -s _sig_ -p '#test1' '#test1.1' -m 'this is a test' +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to sign message using signing context #test1 #test1.1 ${ENDC} + exit -v ${_error_code_} +fi + +identity_wallet_contract verify -f ${_contract_} -s _v_ -p '#test1' '#test1.1' -m 'this is a test' --signature ${_sig_} +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to verify signature ${ENDC} + exit -v ${_error_code_} +fi +echo verified: ${_v_} + +echo Test extensible key verification +identity_wallet_contract sign -f ${_contract_} -s _sig_ -p '#test3' '#test3.1' '#test3.1.1' -m 'this is a test' +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to sign message using extensible context #test3 #test3.1 #test3.1.1. ${ENDC} + exit -v ${_error_code_} +fi + +identity_wallet_contract verify -f ${_contract_} -s _v_ -p '#test3' '#test3.1' '#test3.1.1' -m 'this is a test' --signature ${_sig_} +if -o ${_error_code_} 0 + echo ${ERROR} [Error ${_error_code_}] failed to verify signature ${ENDC} + exit -v ${_error_code_} +fi +echo verified: ${_v_} + ## ================================================================= echo ${HEADER} test key generate, these should generate errors ${ENDC} ## =================================================================