summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Bonazza <jonbonazza@gmail.com>2020-11-14 04:42:47 -0800
committerJon Bonazza <jonbonazza@gmail.com>2020-11-26 18:39:56 -0800
commitd5925fd52258bfdaddd434e8c610cdcb0a16a0cd (patch)
tree573f3d78bb9ff123598eb2b0224e18d5354dcf71
parented2f84735b0fe6b75c823716426d62e6b80e7ae3 (diff)
feat: HMAC support in Crypto APIs
-rw-r--r--core/crypto/crypto.cpp47
-rw-r--r--core/crypto/crypto.h23
-rw-r--r--core/register_core_types.cpp1
-rw-r--r--doc/classes/Crypto.xml26
-rw-r--r--doc/classes/HMACContext.xml88
-rwxr-xr-xmodules/mbedtls/SCsub4
-rw-r--r--modules/mbedtls/crypto_mbedtls.cpp71
-rw-r--r--modules/mbedtls/crypto_mbedtls.h22
-rw-r--r--modules/mbedtls/register_types.cpp4
-rw-r--r--modules/mbedtls/tests/test_crypto_mbedtls.cpp62
-rw-r--r--modules/mbedtls/tests/test_crypto_mbedtls.h61
-rw-r--r--tests/test_crypto.h74
-rw-r--r--tests/test_main.cpp1
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"