Skip to content

Commit

Permalink
Add SHA-384 RSA PKCS#1v1.5 & RSASSA-PSS signature verification functions
Browse files Browse the repository at this point in the history
  • Loading branch information
smlu committed Dec 14, 2023
1 parent 7152abc commit f1a8ace
Show file tree
Hide file tree
Showing 18 changed files with 45,524 additions and 9,704 deletions.
5 changes: 3 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ if ( ACK_BUILD_TESTS )

include (CTest)
enable_testing()
add_test( ack_tests ${CMAKE_BINARY_DIR}/tests/ack_tests )
add_test( ack_gen_tests ${CMAKE_BINARY_DIR}/tests/ack_gen_tests )
add_test( ack_rsa_tests ${CMAKE_BINARY_DIR}/tests/ack_rsa_tests )
add_test( ack_ecc_tests ${CMAKE_BINARY_DIR}/tests/ack_ecc_tests )
endif( ACK_BUILD_TESTS )


message( "No intrinsics................${ACK_NO_INTRINSICS}" )
message( "Building examples............${ACK_BUILD_EXAMPLES}" )
message( "Building tests...............${ACK_BUILD_TESTS}" )
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ The [ack/rsa.hpp](include/ack/rsa.hpp) header file defines the RSA PKCS v1.5 sig
- `assert_rsa_sha1` - fails transaction if RSA signature is not valid for the provided SHA-1 hash.
- `verify_rsa_sha256` - checks if RSA signature is valid for the provided SHA-256 hash.
- `assert_rsa_sha256` - fails transaction if RSA signature is not valid for the provided SHA-256 hash.
- `verify_rsa_sha384` - checks if RSA signature is valid for the provided SHA-384 hash.
- `assert_rsa_sha384` - fails transaction if RSA signature is not valid for the provided SHA-384 hash.
- `verify_rsa_sha512` - checks if RSA signature is valid for the provided SHA-512 hash.
- `assert_rsa_sha512` - fails transaction if RSA signature is not valid for the provided SHA-512 hash.

Expand All @@ -48,6 +50,8 @@ the RSASSA-PSS signature verification functions for *SHA-1*, *SHA-256* and *SHA-
- `assert_rsa_pss_sha1` - fails transaction if RSASSA-PSS MGF1 signature is not valid for the provided SHA-1 hash.
- `verify_rsa_pss_sha256` - checks if RSASSA-PSS MGF1 signature is valid for the provided SHA-256 hash.
- `assert_rsa_pss_sha256` - fails transaction if RSASSA-PSS MGF1 signature is not valid for the provided SHA-256 hash.
- `verify_rsa_pss_sha384` - checks if RSASSA-PSS MGF1 signature is valid for the provided SHA-384 hash.
- `assert_rsa_pss_sha384` - fails transaction if RSASSA-PSS MGF1 signature is not valid for the provided SHA-384 hash.
- `verify_rsa_pss_sha512` - checks if RSASSA-PSS MGF1 signature is valid for the provided SHA-512 hash.
- `assert_rsa_pss_sha512` - fails transaction if RSASSA-PSS MGF1 signature is not valid for the provided SHA-512 hash.

Expand Down
4 changes: 2 additions & 2 deletions include/ack/c/powm.h
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ extern "C" {
*
* CT: value or length of x does not leak.
*/
static uint32_t br_i32_bit_length(uint32_t *x, size_t xlen)
static uint32_t br_i32_bit_length(const uint32_t *x, size_t xlen)
{
uint32_t tw, twk;

Expand Down Expand Up @@ -881,7 +881,7 @@ extern "C" {
* @return 0 if num < modulus, 1 if num >= modulus
*/
static int greater_equal_modulus(const struct rsa_public_key *key,
uint32_t num[])
const uint32_t num[])
{
int i;

Expand Down
6 changes: 3 additions & 3 deletions include/ack/mgf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ namespace ack {
/**
* Mask generation function - MGF1.
* Implementation based on RFC 8017 appendix-B.2.1: https://datatracker.ietf.org/doc/html/rfc8017#appendix-B.2.1
* @param hash - ref to t the hash function to use in the process
* @param hashfunc - ref to t the hash function to use in the process
* @param mgf_seed - the bytes view of MGF seed
* @param mask - the output mask
* @param copy - ref to the function which is used for copying final data to `mask`
*/
template<typename HashF, typename CopyF = void(*)(std::span<byte_t>, const bytes_view, size_t) >
void mgf1(HashF&& hash, const bytes_view mgf_seed, std::span<byte_t> mask,
void mgf1(HashF&& hashfunc, const bytes_view mgf_seed, std::span<byte_t> mask,
CopyF copy = [](auto dst, auto src, auto len) { memcpy( dst.data(), src.data(), len ); })
{
uint32_t counter = 0;
Expand All @@ -34,7 +34,7 @@ namespace ack {
buf[mgf_seed.size() + 2] = ((counter) >> 8) & 0xff;
buf[mgf_seed.size() + 3] = (counter) & 0xff;

auto digest = hash( reinterpret_cast<const char*>( buf ), buf_len )
auto digest = hashfunc( bytes_view{ buf, buf_len } )
.extract_as_byte_array();

const size_t copied = std::min<std::size_t>( digest.size(), out_len );
Expand Down
103 changes: 93 additions & 10 deletions include/ack/rsa.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <ack/mgf.hpp>
#include <ack/bigint.hpp>
#include <ack/public_key.hpp>
#include <ack/sha.hpp>
#include <ack/types.hpp>
#include <ack/utils.hpp>

Expand Down Expand Up @@ -38,12 +39,35 @@ namespace ack {
constexpr size_t pkcs1_v1_5_t_sha256_size = sha256_digest_info_prefix.size() + sizeof(hash256);
static_assert( pkcs1_v1_5_t_sha256_size == 51 );

constexpr auto sha384_digest_info_prefix = std::array<byte_t, 19> {
0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30
};
constexpr size_t pkcs1_v1_5_t_sha384_size = sha384_digest_info_prefix.size() + sizeof(hash384);
static_assert( pkcs1_v1_5_t_sha384_size == 67 );

constexpr auto sha512_digest_info_prefix = std::array<byte_t, 19> {
0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40
};
constexpr size_t pkcs1_v1_5_t_sha512_size = sha512_digest_info_prefix.size() + sizeof(hash512);
static_assert( pkcs1_v1_5_t_sha512_size == 83 );

template<typename HashF>
inline auto eosiohash(const bytes_view& msg, HashF&& hashfunc) {
return hashfunc( reinterpret_cast<const char*>( msg.data() ), msg.size() );
}

inline hash160 eosiosha1(const bytes_view& msg) {
return eosiohash( msg, eosio::sha1 );
}

inline hash256 eosiosha256(const bytes_view& msg) {
return eosiohash( msg, eosio::sha256 );
}

inline hash512 eosiosha512(const bytes_view& msg) {
return eosiohash( msg, eosio::sha512 );
}

/**
* Functions check if signature representative s <= n - 1.
* @note function expects s to bi positive integer.
Expand Down Expand Up @@ -235,20 +259,21 @@ namespace ack {
* Implementation based on RFC 8017 sec. 8.1.2: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1.2
*
* @note The DB mask is generated by using MGF1 function.
* The same hash function - `HashT` is used to generate digest H' and MGF1 mask.
* The same hash function - `HashF` is used to generate digest H' and MGF1 mask.
*
* @param pub_key - RSA-PSS public key
* Note: If salt length is not provided through key the hash length is used.
* @param digest - Digest to verify
* @param signature - RSASSA-PSS signature
* @param hash - Hash function which was used to produce `digest`.
* @param hashfunc - Hash function which was used to produce `digest`.
*
* @return true if signature is valid, else false.
*/
template<size_t HLen, typename HashT>
[[nodiscard]] static bool rsassa_pss_mgf1_verify(const rsa_pss_public_key_view& pub_key, const hash_t<HLen>& digest, const bytes_view& signature, HashT&& hash)
template<size_t HLen, typename HashF>
[[nodiscard]] static bool rsassa_pss_mgf1_verify(const rsa_pss_public_key_view& pub_key, const hash_t<HLen>& digest, const bytes_view signature, HashF&& hashfunc)
{
using HDT = typename std::invoke_result_t<HashT, const char*, size_t>;
// using HDT = typename std::invoke_result_t<HashT, const char*, size_t>;
using HDT = typename std::invoke_result_t<HashF, const bytes_view&>;
static_assert( std::is_same_v<hash_t<HLen>, HDT> );

// 1. Length check
Expand Down Expand Up @@ -287,7 +312,7 @@ namespace ack {
auto h = bytes_view( &em[dblen], HLen );

// Unmask DB
mgf1( hash, h, db, [](auto dst, auto src, auto len){
mgf1( hashfunc, h, db, [](auto dst, auto src, auto len){
memxor( dst, src, len );
});

Expand All @@ -312,7 +337,7 @@ namespace ack {
memcpy( &m[8 + HLen], salt.data(), slen );

// Calculate H' and compare H' to H
const auto h2 = hash( reinterpret_cast<const char*>( m.data() ), m.size() )
const auto h2 = hashfunc( m )
.extract_as_byte_array();
return memcmp( h.data(), h2.data(), HLen ) == 0;
}
Expand Down Expand Up @@ -377,6 +402,36 @@ namespace ack {
eosio::check( verify_rsa_sha256( pub_key, digest, signature ), error );
}

/**
* Verifies a RSA PKCS1 v1.5 SHA-384 signature.
* @note The decrypted signature is verified following the RFC8017 spec.
* https://tools.ietf.org/html/rfc8017#section-8.2.2
*
* @param pub_key - RSA public key
* @param digest - SHA-384 digest to verify
* @param signature - RSA PKCS1 v1.5 signature
*
* @return false if verification has failed, true if succeeds
*/
[[nodiscard]] inline bool verify_rsa_sha384(const rsa_public_key_view& pub_key, const hash384& digest, const bytes_view& signature) {
return rsassa_pkcs1_v1_5_verify<detail::pkcs1_v1_5_t_sha384_size>( pub_key, signature, [&](std::span<byte_t>&& t) {
rsa_1_5_t_generator( t, detail::sha384_digest_info_prefix, digest );
});
}

/**
* Asserts if verification of RSA PKCS1 v1.5 SHA-384 signature fails.
* @note For implementation details see verify_rsa_sha384.
*
* @param pub_key - RSA public key
* @param digest - SHA-384 digest to verify
* @param signature - RSA PKCS1 v1.5 signature
* @param error - error message to use when verification fails
*/
inline void assert_rsa_sha384(const rsa_public_key_view& pub_key, const hash384& digest, const bytes_view& signature, const char* error) {
eosio::check( verify_rsa_sha384( pub_key, digest, signature ), error );
}

/**
* Verifies a RSA PKCS1 v1.5 SHA-512 signature.
* @note The decrypted signature is verified following the RFC8017 spec.
Expand Down Expand Up @@ -419,7 +474,7 @@ namespace ack {
* @return false if verification has failed, true if succeeds
*/
[[nodiscard]] inline bool verify_rsa_pss_sha1(const rsa_pss_public_key_view& pub_key, const hash160& digest, const bytes_view& signature) {
return rsassa_pss_mgf1_verify( pub_key, digest, signature, eosio::sha1 );
return rsassa_pss_mgf1_verify( pub_key, digest, signature, detail::eosiosha1 );
}

/**
Expand Down Expand Up @@ -447,7 +502,7 @@ namespace ack {
* @return false if verification has failed, true if succeeds
*/
[[nodiscard]] inline bool verify_rsa_pss_sha256(const rsa_pss_public_key_view& pub_key, const hash256& digest, const bytes_view& signature) {
return rsassa_pss_mgf1_verify( pub_key, digest, signature, eosio::sha256 );
return rsassa_pss_mgf1_verify( pub_key, digest, signature, detail::eosiosha256 );
}

/**
Expand All @@ -463,6 +518,34 @@ namespace ack {
eosio::check( verify_rsa_pss_sha256( pub_key, digest, signature ), error );
}

/**
* Verifies RSASSA-PSS MGF1 SHA-384 signature
* @note The decrypted signature is verified following the RFC8017 spec.
* https://datatracker.ietf.org/doc/html/rfc8017#section-8.1.2
*
* @param pub_key - RSA-PSS public key
* @param digest - SHA-384 digest to verify
* @param signature - RSASSA-PSS MGF1 SHA-384 signature
*
* @return false if verification has failed, true if succeeds
*/
[[nodiscard]] inline bool verify_rsa_pss_sha384(const rsa_pss_public_key_view& pub_key, const hash384& digest, const bytes_view& signature) {
return rsassa_pss_mgf1_verify( pub_key, digest, signature, sha384 );
}

/**
* Asserts if verification of RSASSA-PSS MGF1 SHA-384 signature fails.
* @note For implementation details see verify_rsa_pss_sha384.
*
* @param pub_key - RSA-PSS public key
* @param digest - SHA-384 digest to verify
* @param signature - RSASSA-PSS MGF1 SHA-384 signature
* @param error - error message to use when verification fails
*/
inline void assert_rsa_pss_sha384(const rsa_pss_public_key_view& pub_key, const hash384& digest, const bytes_view& signature, const char* error) {
eosio::check( verify_rsa_pss_sha384( pub_key, digest, signature ), error );
}

/**
* Verifies RSASSA-PSS MGF1 SHA-512 signature
* @note The decrypted signature is verified following the RFC8017 spec.
Expand All @@ -475,7 +558,7 @@ namespace ack {
* @return false if verification has failed, true if succeeds
*/
[[nodiscard]] inline bool verify_rsa_pss_sha512(const rsa_pss_public_key_view& pub_key, const hash512& digest, const bytes_view& signature) {
return rsassa_pss_mgf1_verify( pub_key, digest, signature, eosio::sha512 );
return rsassa_pss_mgf1_verify( pub_key, digest, signature, detail::eosiosha512 );
}

/**
Expand Down
33 changes: 27 additions & 6 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,36 @@ project( ack_tests )

find_package( cdt REQUIRED )

set( ACK_TEST_SOURCES
${PROJECT_SOURCE_DIR}/main.cpp
set( ACK_TESTS_SRC_DIR
${PROJECT_SOURCE_DIR}/src
)

add_native_executable( ${PROJECT_NAME} ${ACK_TEST_SOURCES} )
target_include_directories( ${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include )
target_include_directories( ${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/../include )
# ack_gen_tests
add_native_executable( ack_gen_tests ${ACK_TESTS_SRC_DIR}/ack_gen_tests.cpp )
target_include_directories( ack_gen_tests PRIVATE ${PROJECT_SOURCE_DIR}/include )
target_include_directories( ack_gen_tests PRIVATE ${PROJECT_SOURCE_DIR}/../include )

target_compile_definitions( ${PROJECT_NAME} PRIVATE
target_compile_definitions( ack_gen_tests PRIVATE
ACK_NO_INTRINSICS=1
$<$<BOOL:${ACK_ENABLE_DEBUG_LOG}>:ACK_ENABLE_DEBUG_LOG=1>
)

# ack_ecc_tests
add_native_executable( ack_ecc_tests ${ACK_TESTS_SRC_DIR}/ack_ecc_tests.cpp )
target_include_directories( ack_ecc_tests PRIVATE ${PROJECT_SOURCE_DIR}/include )
target_include_directories( ack_ecc_tests PRIVATE ${PROJECT_SOURCE_DIR}/../include )

target_compile_definitions( ack_ecc_tests PRIVATE
ACK_NO_INTRINSICS=1
$<$<BOOL:${ACK_ENABLE_DEBUG_LOG}>:ACK_ENABLE_DEBUG_LOG=1>
)

# ack_rsa_tests
add_native_executable( ack_rsa_tests ${ACK_TESTS_SRC_DIR}/ack_rsa_tests.cpp )
target_include_directories( ack_rsa_tests PRIVATE ${PROJECT_SOURCE_DIR}/include )
target_include_directories( ack_rsa_tests PRIVATE ${PROJECT_SOURCE_DIR}/../include )

target_compile_definitions( ack_rsa_tests PRIVATE
ACK_NO_INTRINSICS=1
$<$<BOOL:${ACK_ENABLE_DEBUG_LOG}>:ACK_ENABLE_DEBUG_LOG=1>
)
Loading

0 comments on commit f1a8ace

Please sign in to comment.