diff options
| author | Jon Bonazza <jonbonazza@gmail.com> | 2020-11-14 04:42:47 -0800 | 
|---|---|---|
| committer | Jon Bonazza <jonbonazza@gmail.com> | 2020-11-26 18:39:56 -0800 | 
| commit | d5925fd52258bfdaddd434e8c610cdcb0a16a0cd (patch) | |
| tree | 573f3d78bb9ff123598eb2b0224e18d5354dcf71 | |
| parent | ed2f84735b0fe6b75c823716426d62e6b80e7ae3 (diff) | |
feat: HMAC support in Crypto APIs
| -rw-r--r-- | core/crypto/crypto.cpp | 47 | ||||
| -rw-r--r-- | core/crypto/crypto.h | 23 | ||||
| -rw-r--r-- | core/register_core_types.cpp | 1 | ||||
| -rw-r--r-- | doc/classes/Crypto.xml | 26 | ||||
| -rw-r--r-- | doc/classes/HMACContext.xml | 88 | ||||
| -rwxr-xr-x | modules/mbedtls/SCsub | 4 | ||||
| -rw-r--r-- | modules/mbedtls/crypto_mbedtls.cpp | 71 | ||||
| -rw-r--r-- | modules/mbedtls/crypto_mbedtls.h | 22 | ||||
| -rw-r--r-- | modules/mbedtls/register_types.cpp | 4 | ||||
| -rw-r--r-- | modules/mbedtls/tests/test_crypto_mbedtls.cpp | 62 | ||||
| -rw-r--r-- | modules/mbedtls/tests/test_crypto_mbedtls.h | 61 | ||||
| -rw-r--r-- | tests/test_crypto.h | 74 | ||||
| -rw-r--r-- | tests/test_main.cpp | 1 | 
13 files changed, 480 insertions, 4 deletions
diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp index d12108bca0..33ba0ba704 100644 --- a/core/crypto/crypto.cpp +++ b/core/crypto/crypto.cpp @@ -65,6 +65,22 @@ void X509Certificate::_bind_methods() {  	ClassDB::bind_method(D_METHOD("load", "path"), &X509Certificate::load);  } +/// HMACContext + +void HMACContext::_bind_methods() { +	ClassDB::bind_method(D_METHOD("start", "hash_type", "key"), &HMACContext::start); +	ClassDB::bind_method(D_METHOD("update", "data"), &HMACContext::update); +	ClassDB::bind_method(D_METHOD("finish"), &HMACContext::finish); +} + +HMACContext *(*HMACContext::_create)() = nullptr; +HMACContext *HMACContext::create() { +	if (_create) { +		return _create(); +	} +	ERR_FAIL_V_MSG(nullptr, "HMACContext is not available when the mbedtls module is disabled."); +} +  /// Crypto  void (*Crypto::_load_default_certificates)(String p_path) = nullptr; @@ -82,6 +98,35 @@ void Crypto::load_default_certificates(String p_path) {  	}  } +PackedByteArray Crypto::hmac_digest(HashingContext::HashType p_hash_type, PackedByteArray p_key, PackedByteArray p_msg) { +	Ref<HMACContext> ctx = Ref<HMACContext>(HMACContext::create()); +	ERR_FAIL_COND_V_MSG(ctx.is_null(), PackedByteArray(), "HMAC is not available witout mbedtls module."); +	Error err = ctx->start(p_hash_type, p_key); +	ERR_FAIL_COND_V(err != OK, PackedByteArray()); +	err = ctx->update(p_msg); +	ERR_FAIL_COND_V(err != OK, PackedByteArray()); +	return ctx->finish(); +} + +// Compares two HMACS for equality without leaking timing information in order to prevent timing attakcs. +// @see: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy +bool Crypto::constant_time_compare(PackedByteArray p_trusted, PackedByteArray p_received) { +	const uint8_t *t = p_trusted.ptr(); +	const uint8_t *r = p_received.ptr(); +	int tlen = p_trusted.size(); +	int rlen = p_received.size(); +	// If the lengths are different then nothing else matters. +	if (tlen != rlen) { +		return false; +	} + +	uint8_t v = 0; +	for (int i = 0; i < tlen; i++) { +		v |= t[i] ^ r[i]; +	} +	return v == 0; +} +  void Crypto::_bind_methods() {  	ClassDB::bind_method(D_METHOD("generate_random_bytes", "size"), &Crypto::generate_random_bytes);  	ClassDB::bind_method(D_METHOD("generate_rsa", "size"), &Crypto::generate_rsa); @@ -90,6 +135,8 @@ void Crypto::_bind_methods() {  	ClassDB::bind_method(D_METHOD("verify", "hash_type", "hash", "signature", "key"), &Crypto::verify);  	ClassDB::bind_method(D_METHOD("encrypt", "key", "plaintext"), &Crypto::encrypt);  	ClassDB::bind_method(D_METHOD("decrypt", "key", "ciphertext"), &Crypto::decrypt); +	ClassDB::bind_method(D_METHOD("hmac_digest", "hash_type", "key", "msg"), &Crypto::hmac_digest); +	ClassDB::bind_method(D_METHOD("constant_time_compare", "trusted", "received"), &Crypto::constant_time_compare);  }  /// Resource loader/saver diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h index 8325f043bf..5cacddaea0 100644 --- a/core/crypto/crypto.h +++ b/core/crypto/crypto.h @@ -67,6 +67,23 @@ public:  	virtual Error save(String p_path) = 0;  }; +class HMACContext : public Reference { +	GDCLASS(HMACContext, Reference); + +protected: +	static void _bind_methods(); +	static HMACContext *(*_create)(); + +public: +	static HMACContext *create(); + +	virtual Error start(HashingContext::HashType p_hash_type, PackedByteArray p_key) = 0; +	virtual Error update(PackedByteArray p_data) = 0; +	virtual PackedByteArray finish() = 0; + +	HMACContext() {} +}; +  class Crypto : public Reference {  	GDCLASS(Crypto, Reference); @@ -88,6 +105,12 @@ public:  	virtual Vector<uint8_t> encrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_plaintext) = 0;  	virtual Vector<uint8_t> decrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_ciphertext) = 0; +	PackedByteArray hmac_digest(HashingContext::HashType p_hash_type, PackedByteArray p_key, PackedByteArray p_msg); + +	// Compares two PackedByteArrays for equality without leaking timing information in order to prevent timing attacks. +	// @see: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy +	bool constant_time_compare(PackedByteArray p_trusted, PackedByteArray p_received); +  	Crypto() {}  }; diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 7e32f215e7..9883ce12a0 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -168,6 +168,7 @@ void register_core_types() {  	ClassDB::register_class<AESContext>();  	ClassDB::register_custom_instance_class<X509Certificate>();  	ClassDB::register_custom_instance_class<CryptoKey>(); +	ClassDB::register_custom_instance_class<HMACContext>();  	ClassDB::register_custom_instance_class<Crypto>();  	ClassDB::register_custom_instance_class<StreamPeerSSL>(); diff --git a/doc/classes/Crypto.xml b/doc/classes/Crypto.xml index b3bbbae94f..1f6cb40cde 100644 --- a/doc/classes/Crypto.xml +++ b/doc/classes/Crypto.xml @@ -73,6 +73,18 @@  	<tutorials>  	</tutorials>  	<methods> +		<method name="constant_time_compare"> +			<return type="bool"> +			</return> +			<argument index="0" name="trusted" type="PackedByteArray"> +			</argument> +			<argument index="1" name="received" type="PackedByteArray"> +			</argument> +			<description> +				Compares two [PackedByteArray]s for equality without leaking timing information in order to prevent timing attacks. +				See [url=https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy]this blog post[/url] for more information. +			</description> +		</method>  		<method name="decrypt">  			<return type="PackedByteArray">  			</return> @@ -147,6 +159,20 @@  				[/codeblocks]  			</description>  		</method> +		<method name="hmac_digest"> +			<return type="PackedByteArray"> +			</return> +			<argument index="0" name="hash_type" type="int" enum="HashingContext.HashType"> +			</argument> +			<argument index="1" name="key" type="PackedByteArray"> +			</argument> +			<argument index="2" name="msg" type="PackedByteArray"> +			</argument> +			<description> +				Generates an [url=https://en.wikipedia.org/wiki/HMAC]HMAC[/url] digest of [code]msg[/code] using [code]key[/code]. The [code]hash_type[/code] parameter is the hashing algorithm that is used for the inner and outer hashes. +				Currently, only [constant HashingContext.HASH_SHA256] and [constant HashingContext.HASH_SHA1] are supported. +			</description> +		</method>  		<method name="sign">  			<return type="PackedByteArray">  			</return> diff --git a/doc/classes/HMACContext.xml b/doc/classes/HMACContext.xml new file mode 100644 index 0000000000..00d528ef8f --- /dev/null +++ b/doc/classes/HMACContext.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="HMACContext" inherits="Reference" version="4.0"> +	<brief_description> +		Used to create an HMAC for a message using a key. +	</brief_description> +	<description> +		The HMACContext class is useful for advanced HMAC use cases, such as streaming the message as it supports creating the message over time rather than providing it all at once. +	[codeblocks] +	[gdscript] +	extends Node +	var ctx = HMACContext.new() + +	func _ready(): +	    var key = "supersecret".to_utf8() +	    var err = ctx.start(HashingContext.HASH_SHA256, key) +	    assert(err == OK) +	    var msg1 = "this is ".to_utf8() +	    var msg2 = "vewy vewy secret".to_utf8() +	    err = ctx.update(msg1) +	    assert(err == OK) +	    err = ctx.update(msg2) +	    assert(err == OK) +	    var hmac = ctx.finish() +	    print(hmac.hex_encode()) + +	[/gdscript] +	[csharp] +	using Godot; +	using System; +	using System.Diagnostics; + +	public class CryptoNode : Node +	{ +	    private HMACContext ctx = new HMACContext(); +	    public override void _Ready() +	    { +	        PackedByteArray key = String("supersecret").to_utf8(); +	        Error err = ctx.Start(HashingContext.HASH_SHA256, key); +	        GD.Assert(err == OK); +	        PackedByteArray msg1 = String("this is ").to_utf8(); +	        PackedByteArray msg2 = String("vewy vew secret").to_utf8(); +	        err = ctx.Update(msg1); +	        GD.Assert(err == OK); +	        err = ctx.Update(msg2); +	        GD.Assert(err == OK); +	        PackedByteArray hmac = ctx.Finish(); +	        GD.Print(hmac.HexEncode()); +	    } +	} + +	[/csharp] +	[/codeblocks] +	[b]Note:[/b] Not available in HTML5 exports. +	</description> +	<tutorials> +	</tutorials> +	<methods> +		<method name="finish"> +			<return type="PackedByteArray"> +			</return> +			<description> +				Returns the resulting HMAC. If the HMAC failed, an empty [PackedByteArray] is returned. +			</description> +		</method> +		<method name="start"> +			<return type="int" enum="Error"> +			</return> +			<argument index="0" name="hash_type" type="int" enum="HashingContext.HashType"> +			</argument> +			<argument index="1" name="key" type="PackedByteArray"> +			</argument> +			<description> +				Initializes the HMACContext. This method cannot be called again on the same HMACContext until [method finish] has been called. +			</description> +		</method> +		<method name="update"> +			<return type="int" enum="Error"> +			</return> +			<argument index="0" name="data" type="PackedByteArray"> +			</argument> +			<description> +				Updates the message to be HMACed. This can be called multiple times before [method finish] is called to append [code]data[/code] to the message, but cannot be called until [method start] has been called. +			</description> +		</method> +	</methods> +	<constants> +	</constants> +</class> diff --git a/modules/mbedtls/SCsub b/modules/mbedtls/SCsub index 5f5d25a3ee..3b1739c6ee 100755 --- a/modules/mbedtls/SCsub +++ b/modules/mbedtls/SCsub @@ -100,3 +100,7 @@ if env["builtin_mbedtls"]:  # Module sources  env_mbed_tls.add_source_files(env.modules_sources, "*.cpp") + +if env["tests"]: +    env_mbed_tls.Append(CPPDEFINES=["TESTS_ENABLED"]) +    env_mbed_tls.add_source_files(env.modules_sources, "./tests/*.cpp") diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp index bec792450a..4ea38ebd60 100644 --- a/modules/mbedtls/crypto_mbedtls.cpp +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -44,6 +44,7 @@  #define PEM_END_CRT "-----END CERTIFICATE-----\n"  #include <mbedtls/debug.h> +#include <mbedtls/md.h>  #include <mbedtls/pem.h>  CryptoKey *CryptoKeyMbedTLS::create() { @@ -186,6 +187,68 @@ Error X509CertificateMbedTLS::save(String p_path) {  	return OK;  } +bool HMACContextMbedTLS::is_md_type_allowed(mbedtls_md_type_t p_md_type) { +	switch (p_md_type) { +		case MBEDTLS_MD_SHA1: +		case MBEDTLS_MD_SHA256: +			return true; +		default: +			return false; +	} +} + +HMACContext *HMACContextMbedTLS::create() { +	return memnew(HMACContextMbedTLS); +} + +Error HMACContextMbedTLS::start(HashingContext::HashType p_hash_type, PackedByteArray p_key) { +	ERR_FAIL_COND_V_MSG(ctx != nullptr, ERR_FILE_ALREADY_IN_USE, "HMACContext already started."); + +	// HMAC keys can be any size. +	ERR_FAIL_COND_V_MSG(p_key.empty(), ERR_INVALID_PARAMETER, "Key must not be empty."); + +	hash_type = p_hash_type; +	mbedtls_md_type_t ht = CryptoMbedTLS::md_type_from_hashtype(p_hash_type, hash_len); + +	bool allowed = HMACContextMbedTLS::is_md_type_allowed(ht); +	ERR_FAIL_COND_V_MSG(!allowed, ERR_INVALID_PARAMETER, "Unsupported hash type."); + +	ctx = memalloc(sizeof(mbedtls_md_context_t)); +	mbedtls_md_init((mbedtls_md_context_t *)ctx); + +	mbedtls_md_setup((mbedtls_md_context_t *)ctx, mbedtls_md_info_from_type((mbedtls_md_type_t)ht), 1); +	int ret = mbedtls_md_hmac_starts((mbedtls_md_context_t *)ctx, (const uint8_t *)p_key.ptr(), (size_t)p_key.size()); +	return ret ? FAILED : OK; +} + +Error HMACContextMbedTLS::update(PackedByteArray p_data) { +	ERR_FAIL_COND_V_MSG(ctx == nullptr, ERR_INVALID_DATA, "Start must be called before update."); + +	ERR_FAIL_COND_V_MSG(p_data.empty(), ERR_INVALID_PARAMETER, "Src must not be empty."); + +	int ret = mbedtls_md_hmac_update((mbedtls_md_context_t *)ctx, (const uint8_t *)p_data.ptr(), (size_t)p_data.size()); +	return ret ? FAILED : OK; +} + +PackedByteArray HMACContextMbedTLS::finish() { +	ERR_FAIL_COND_V_MSG(ctx == nullptr, PackedByteArray(), "Start must be called before finish."); +	ERR_FAIL_COND_V_MSG(hash_len == 0, PackedByteArray(), "Unsupported hash type."); + +	PackedByteArray out; +	out.resize(hash_len); + +	unsigned char *out_ptr = (unsigned char *)out.ptrw(); +	int ret = mbedtls_md_hmac_finish((mbedtls_md_context_t *)ctx, out_ptr); + +	mbedtls_md_free((mbedtls_md_context_t *)ctx); +	memfree((mbedtls_md_context_t *)ctx); +	ctx = nullptr; +	hash_len = 0; + +	ERR_FAIL_COND_V_MSG(ret, PackedByteArray(), "Error received while finishing HMAC"); +	return out; +} +  Crypto *CryptoMbedTLS::create() {  	return memnew(CryptoMbedTLS);  } @@ -199,6 +262,7 @@ void CryptoMbedTLS::initialize_crypto() {  	Crypto::_load_default_certificates = load_default_certificates;  	X509CertificateMbedTLS::make_default();  	CryptoKeyMbedTLS::make_default(); +	HMACContextMbedTLS::make_default();  }  void CryptoMbedTLS::finalize_crypto() { @@ -210,6 +274,7 @@ void CryptoMbedTLS::finalize_crypto() {  	}  	X509CertificateMbedTLS::finalize();  	CryptoKeyMbedTLS::finalize(); +	HMACContextMbedTLS::finalize();  }  CryptoMbedTLS::CryptoMbedTLS() { @@ -313,7 +378,7 @@ PackedByteArray CryptoMbedTLS::generate_random_bytes(int p_bytes) {  	return out;  } -mbedtls_md_type_t CryptoMbedTLS::_md_type_from_hashtype(HashingContext::HashType p_hash_type, int &r_size) { +mbedtls_md_type_t CryptoMbedTLS::md_type_from_hashtype(HashingContext::HashType p_hash_type, int &r_size) {  	switch (p_hash_type) {  		case HashingContext::HASH_MD5:  			r_size = 16; @@ -332,7 +397,7 @@ mbedtls_md_type_t CryptoMbedTLS::_md_type_from_hashtype(HashingContext::HashType  Vector<uint8_t> CryptoMbedTLS::sign(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Ref<CryptoKey> p_key) {  	int size; -	mbedtls_md_type_t type = _md_type_from_hashtype(p_hash_type, size); +	mbedtls_md_type_t type = CryptoMbedTLS::md_type_from_hashtype(p_hash_type, size);  	ERR_FAIL_COND_V_MSG(type == MBEDTLS_MD_NONE, Vector<uint8_t>(), "Invalid hash type.");  	ERR_FAIL_COND_V_MSG(p_hash.size() != size, Vector<uint8_t>(), "Invalid hash provided. Size must be " + itos(size));  	Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS>>(p_key); @@ -350,7 +415,7 @@ Vector<uint8_t> CryptoMbedTLS::sign(HashingContext::HashType p_hash_type, Vector  bool CryptoMbedTLS::verify(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Vector<uint8_t> p_signature, Ref<CryptoKey> p_key) {  	int size; -	mbedtls_md_type_t type = _md_type_from_hashtype(p_hash_type, size); +	mbedtls_md_type_t type = CryptoMbedTLS::md_type_from_hashtype(p_hash_type, size);  	ERR_FAIL_COND_V_MSG(type == MBEDTLS_MD_NONE, false, "Invalid hash type.");  	ERR_FAIL_COND_V_MSG(p_hash.size() != size, false, "Invalid hash provided. Size must be " + itos(size));  	Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS>>(p_key); diff --git a/modules/mbedtls/crypto_mbedtls.h b/modules/mbedtls/crypto_mbedtls.h index e40ca08643..990f8ae578 100644 --- a/modules/mbedtls/crypto_mbedtls.h +++ b/modules/mbedtls/crypto_mbedtls.h @@ -101,12 +101,31 @@ public:  	friend class SSLContextMbedTLS;  }; +class HMACContextMbedTLS : public HMACContext { +private: +	HashingContext::HashType hash_type; +	int hash_len = 0; +	void *ctx = nullptr; + +public: +	static HMACContext *create(); +	static void make_default() { HMACContext::_create = create; } +	static void finalize() { HMACContext::_create = nullptr; } + +	static bool is_md_type_allowed(mbedtls_md_type_t p_md_type); + +	virtual Error start(HashingContext::HashType p_hash_type, PackedByteArray p_key); +	virtual Error update(PackedByteArray p_data); +	virtual PackedByteArray finish(); + +	HMACContextMbedTLS() {} +}; +  class CryptoMbedTLS : public Crypto {  private:  	mbedtls_entropy_context entropy;  	mbedtls_ctr_drbg_context ctr_drbg;  	static X509CertificateMbedTLS *default_certs; -	mbedtls_md_type_t _md_type_from_hashtype(HashingContext::HashType p_hash_type, int &r_size);  public:  	static Crypto *create(); @@ -114,6 +133,7 @@ public:  	static void finalize_crypto();  	static X509CertificateMbedTLS *get_default_certificates();  	static void load_default_certificates(String p_path); +	static mbedtls_md_type_t md_type_from_hashtype(HashingContext::HashType p_hash_type, int &r_size);  	virtual PackedByteArray generate_random_bytes(int p_bytes);  	virtual Ref<CryptoKey> generate_rsa(int p_bytes); diff --git a/modules/mbedtls/register_types.cpp b/modules/mbedtls/register_types.cpp index 84a27c29bd..59abbac8ec 100644 --- a/modules/mbedtls/register_types.cpp +++ b/modules/mbedtls/register_types.cpp @@ -35,6 +35,10 @@  #include "packet_peer_mbed_dtls.h"  #include "stream_peer_mbedtls.h" +#ifdef TESTS_ENABLED +#include "tests/test_crypto_mbedtls.h" +#endif +  void register_mbedtls_types() {  	CryptoMbedTLS::initialize_crypto();  	StreamPeerMbedTLS::initialize_ssl(); diff --git a/modules/mbedtls/tests/test_crypto_mbedtls.cpp b/modules/mbedtls/tests/test_crypto_mbedtls.cpp new file mode 100644 index 0000000000..c5a27aa794 --- /dev/null +++ b/modules/mbedtls/tests/test_crypto_mbedtls.cpp @@ -0,0 +1,62 @@ +/*************************************************************************/ +/*  test_crypto_mbedtls.cpp                                              */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "modules/mbedtls/tests/test_crypto_mbedtls.h" + +#include "modules/mbedtls/crypto_mbedtls.h" +#include "tests/test_macros.h" + +namespace TestCryptoMbedTLS { + +void hmac_digest_test(HashingContext::HashType ht, String expected_hex) { +	CryptoMbedTLS crypto; +	PackedByteArray key = String("supersecretkey").to_utf8_buffer(); +	PackedByteArray msg = String("Return of the MAC!").to_utf8_buffer(); +	PackedByteArray digest = crypto.hmac_digest(ht, key, msg); +	String hex = String::hex_encode_buffer(digest.ptr(), digest.size()); +	CHECK(hex == expected_hex); +} + +void hmac_context_digest_test(HashingContext::HashType ht, String expected_hex) { +	HMACContextMbedTLS ctx; +	PackedByteArray key = String("supersecretkey").to_utf8_buffer(); +	PackedByteArray msg1 = String("Return of ").to_utf8_buffer(); +	PackedByteArray msg2 = String("the MAC!").to_utf8_buffer(); +	Error err = ctx.start(ht, key); +	CHECK(err == OK); +	err = ctx.update(msg1); +	CHECK(err == OK); +	err = ctx.update(msg2); +	CHECK(err == OK); +	PackedByteArray digest = ctx.finish(); +	String hex = String::hex_encode_buffer(digest.ptr(), digest.size()); +	CHECK(hex == expected_hex); +} +} // namespace TestCryptoMbedTLS diff --git a/modules/mbedtls/tests/test_crypto_mbedtls.h b/modules/mbedtls/tests/test_crypto_mbedtls.h new file mode 100644 index 0000000000..7b1e062239 --- /dev/null +++ b/modules/mbedtls/tests/test_crypto_mbedtls.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/*  test_crypto_mbedtls.h                                                */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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.                */ +/*************************************************************************/ + +#ifndef TEST_CRYPTO_MBEDTLS_H +#define TEST_CRYPTO_MBEDTLS_H + +#include "core/crypto/hashing_context.h" + +#include "tests/test_macros.h" + +namespace TestCryptoMbedTLS { + +void hmac_digest_test(HashingContext::HashType ht, String expected_hex); + +TEST_CASE("[CryptoMbedTLS] HMAC digest") { +	// SHA-256 +	hmac_digest_test(HashingContext::HashType::HASH_SHA256, "fe442023f8a7d36a810e1e7cd8a8e2816457f350a008fbf638296afa12085e59"); + +	// SHA-1 +	hmac_digest_test(HashingContext::HashType::HASH_SHA1, "a0ac4cd68a2f4812c355983d94e8d025afe7dddf"); +} + +void hmac_context_digest_test(HashingContext::HashType ht, String expected_hex); + +TEST_CASE("[HMACContext] HMAC digest") { +	// SHA-256 +	hmac_context_digest_test(HashingContext::HashType::HASH_SHA256, "fe442023f8a7d36a810e1e7cd8a8e2816457f350a008fbf638296afa12085e59"); + +	// SHA-1 +	hmac_context_digest_test(HashingContext::HashType::HASH_SHA1, "a0ac4cd68a2f4812c355983d94e8d025afe7dddf"); +} +} // namespace TestCryptoMbedTLS + +#endif // TEST_CRYPTO_MBEDTLS_H diff --git a/tests/test_crypto.h b/tests/test_crypto.h new file mode 100644 index 0000000000..9e219ceec9 --- /dev/null +++ b/tests/test_crypto.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/*  test_crypto.h                                                        */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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.                */ +/*************************************************************************/ + +#ifndef TEST_CRYPTO_H +#define TEST_CRYPTO_H + +#include "core/crypto/crypto.h" +#include "tests/test_macros.h" +#include <stdio.h> + +namespace TestCrypto { + +class _MockCrypto : public Crypto { +	virtual PackedByteArray generate_random_bytes(int p_bytes) { return PackedByteArray(); } +	virtual Ref<CryptoKey> generate_rsa(int p_bytes) { return nullptr; } +	virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after) { return nullptr; } + +	virtual Vector<uint8_t> sign(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Ref<CryptoKey> p_key) { return Vector<uint8_t>(); } +	virtual bool verify(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Vector<uint8_t> p_signature, Ref<CryptoKey> p_key) { return false; } +	virtual Vector<uint8_t> encrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_plaintext) { return Vector<uint8_t>(); } +	virtual Vector<uint8_t> decrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_ciphertext) { return Vector<uint8_t>(); } +	virtual PackedByteArray hmac_digest(HashingContext::HashType p_hash_type, PackedByteArray p_key, PackedByteArray p_msg) { return PackedByteArray(); } +}; + +PackedByteArray raw_to_pba(const uint8_t *arr, size_t len) { +	PackedByteArray pba; +	pba.resize(len); +	for (size_t i = 0; i < len; i++) { +		pba.set(i, arr[i]); +	} +	return pba; +} + +TEST_CASE("[Crypto] PackedByteArray constant time compare") { +	const uint8_t hm1[] = { 144, 140, 176, 38, 88, 113, 101, 45, 71, 105, 10, 91, 248, 16, 117, 244, 189, 30, 238, 29, 219, 134, 82, 130, 212, 114, 161, 166, 188, 169, 200, 106 }; +	const uint8_t hm2[] = { 80, 30, 144, 228, 108, 38, 188, 125, 150, 64, 165, 127, 221, 118, 144, 232, 45, 100, 15, 248, 193, 244, 245, 34, 116, 147, 132, 200, 110, 27, 38, 75 }; +	PackedByteArray p1 = raw_to_pba(hm1, sizeof(hm1) / sizeof(hm1[0])); +	PackedByteArray p2 = raw_to_pba(hm2, sizeof(hm2) / sizeof(hm2[0])); +	_MockCrypto crypto; +	bool equal = crypto.constant_time_compare(p1, p1); +	CHECK(equal); +	equal = crypto.constant_time_compare(p1, p2); +	CHECK(!equal); +} +} // namespace TestCrypto + +#endif // TEST_CRYPTO_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index c9bf9ab5d5..5e897e3b99 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -39,6 +39,7 @@  #include "test_color.h"  #include "test_command_queue.h"  #include "test_config_file.h" +#include "test_crypto.h"  #include "test_curve.h"  #include "test_expression.h"  #include "test_gradient.h"  |