Browse Source

Merge pull request #48869 from tavurth/feature/hmac-port

Backport HMACContext to 3.x
Rémi Verschelde 4 years ago
parent
commit
2660fafcc0

+ 47 - 0
core/crypto/crypto.cpp

@@ -65,6 +65,22 @@ void X509Certificate::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("load", "path"), &X509Certificate::load);
 	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
 /// Crypto
 
 
 void (*Crypto::_load_default_certificates)(String p_path) = nullptr;
 void (*Crypto::_load_default_certificates)(String p_path) = nullptr;
@@ -82,6 +98,35 @@ void Crypto::load_default_certificates(String p_path) {
 	}
 	}
 }
 }
 
 
+PoolByteArray Crypto::hmac_digest(HashingContext::HashType p_hash_type, PoolByteArray p_key, PoolByteArray p_msg) {
+	Ref<HMACContext> ctx = Ref<HMACContext>(HMACContext::create());
+	ERR_FAIL_COND_V_MSG(ctx.is_null(), PoolByteArray(), "HMAC is not available witout mbedtls module.");
+	Error err = ctx->start(p_hash_type, p_key);
+	ERR_FAIL_COND_V(err != OK, PoolByteArray());
+	err = ctx->update(p_msg);
+	ERR_FAIL_COND_V(err != OK, PoolByteArray());
+	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(PoolByteArray p_trusted, PoolByteArray p_received) {
+	const uint8_t *t = p_trusted.read().ptr();
+	const uint8_t *r = p_received.read().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() {
 void Crypto::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("generate_random_bytes", "size"), &Crypto::generate_random_bytes);
 	ClassDB::bind_method(D_METHOD("generate_random_bytes", "size"), &Crypto::generate_random_bytes);
 	ClassDB::bind_method(D_METHOD("generate_rsa", "size"), &Crypto::generate_rsa);
 	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("verify", "hash_type", "hash", "signature", "key"), &Crypto::verify);
 	ClassDB::bind_method(D_METHOD("encrypt", "key", "plaintext"), &Crypto::encrypt);
 	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("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);
 }
 }
 
 
 Crypto::Crypto() {
 Crypto::Crypto() {

+ 23 - 0
core/crypto/crypto.h

@@ -68,6 +68,23 @@ public:
 	virtual Error save(String p_path) = 0;
 	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, PoolByteArray p_key) = 0;
+	virtual Error update(PoolByteArray p_data) = 0;
+	virtual PoolByteArray finish() = 0;
+
+	HMACContext() {}
+};
+
 class Crypto : public Reference {
 class Crypto : public Reference {
 	GDCLASS(Crypto, Reference);
 	GDCLASS(Crypto, Reference);
 
 
@@ -89,6 +106,12 @@ public:
 	virtual Vector<uint8_t> encrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_plaintext) = 0;
 	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;
 	virtual Vector<uint8_t> decrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_ciphertext) = 0;
 
 
+	PoolByteArray hmac_digest(HashingContext::HashType p_hash_type, PoolByteArray p_key, PoolByteArray p_msg);
+
+	// Compares two PoolByteArrays 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(PoolByteArray p_trusted, PoolByteArray p_received);
+
 	Crypto();
 	Crypto();
 };
 };
 
 

+ 1 - 0
core/register_core_types.cpp

@@ -162,6 +162,7 @@ void register_core_types() {
 	ClassDB::register_class<AESContext>();
 	ClassDB::register_class<AESContext>();
 	ClassDB::register_custom_instance_class<X509Certificate>();
 	ClassDB::register_custom_instance_class<X509Certificate>();
 	ClassDB::register_custom_instance_class<CryptoKey>();
 	ClassDB::register_custom_instance_class<CryptoKey>();
+	ClassDB::register_custom_instance_class<HMACContext>();
 	ClassDB::register_custom_instance_class<Crypto>();
 	ClassDB::register_custom_instance_class<Crypto>();
 	ClassDB::register_custom_instance_class<StreamPeerSSL>();
 	ClassDB::register_custom_instance_class<StreamPeerSSL>();
 
 

+ 26 - 0
doc/classes/Crypto.xml

@@ -39,6 +39,18 @@
 	<tutorials>
 	<tutorials>
 	</tutorials>
 	</tutorials>
 	<methods>
 	<methods>
+		<method name="constant_time_compare">
+			<return type="bool">
+			</return>
+			<argument index="0" name="trusted" type="PoolByteArray">
+			</argument>
+			<argument index="1" name="received" type="PoolByteArray">
+			</argument>
+			<description>
+				Compares two [PoolByteArray]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">
 		<method name="decrypt">
 			<return type="PoolByteArray">
 			<return type="PoolByteArray">
 			</return>
 			</return>
@@ -104,6 +116,20 @@
 				[/codeblock]
 				[/codeblock]
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="hmac_digest">
+			<return type="PoolByteArray">
+			</return>
+			<argument index="0" name="hash_type" type="int" enum="HashingContext.HashType">
+			</argument>
+			<argument index="1" name="key" type="PoolByteArray">
+			</argument>
+			<argument index="2" name="msg" type="PoolByteArray">
+			</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">
 		<method name="sign">
 			<return type="PoolByteArray">
 			<return type="PoolByteArray">
 			</return>
 			</return>

+ 85 - 0
doc/classes/HMACContext.xml

@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="HMACContext" inherits="Reference" version="3.4">
+	<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.
+	[codeblock]
+	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())
+	[/codeblock]
+		And in C# we can use the following.
+	[codeblock]
+	using Godot;
+	using System;
+	using System.Diagnostics;
+
+	public class CryptoNode : Node
+	{
+	    private HMACContext ctx = new HMACContext();
+	    public override void _Ready()
+	    {
+	        PoolByteArray key = String("supersecret").to_utf8();
+	        Error err = ctx.Start(HashingContext.HASH_SHA256, key);
+	        GD.Assert(err == OK);
+	        PoolByteArray msg1 = String("this is ").to_utf8();
+	        PoolByteArray msg2 = String("vewy vew secret").to_utf8();
+	        err = ctx.Update(msg1);
+	        GD.Assert(err == OK);
+	        err = ctx.Update(msg2);
+	        GD.Assert(err == OK);
+	        PoolByteArray hmac = ctx.Finish();
+	        GD.Print(hmac.HexEncode());
+	    }
+	}
+	[/codeblock]
+	[b]Note:[/b] Not available in HTML5 exports.
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+		<method name="finish">
+			<return type="PoolByteArray">
+			</return>
+			<description>
+				Returns the resulting HMAC. If the HMAC failed, an empty [PoolByteArray] 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="PoolByteArray">
+			</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="PoolByteArray">
+			</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>

+ 99 - 0
main/tests/test_crypto.cpp

@@ -0,0 +1,99 @@
+/*************************************************************************/
+/*  test_crypto.cpp                                                      */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 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 "core/crypto/crypto.h"
+#include "core/os/os.h"
+
+namespace TestCrypto {
+
+class _MockCrypto : public Crypto {
+	virtual PoolByteArray generate_random_bytes(int p_bytes) { return PoolByteArray(); }
+	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 PoolByteArray hmac_digest(HashingContext::HashType p_hash_type, PoolByteArray p_key, PoolByteArray p_msg) { return PoolByteArray(); }
+};
+
+PoolByteArray raw_to_pba(const uint8_t *arr, size_t len) {
+	PoolByteArray pba;
+	pba.resize(len);
+	for (size_t i = 0; i < len; i++) {
+		pba.set(i, arr[i]);
+	}
+	return pba;
+}
+
+bool test_PoolByteArray_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 };
+	PoolByteArray p1 = raw_to_pba(hm1, sizeof(hm1) / sizeof(hm1[0]));
+	PoolByteArray p2 = raw_to_pba(hm2, sizeof(hm2) / sizeof(hm2[0]));
+	_MockCrypto crypto;
+	bool equal = crypto.constant_time_compare(p1, p1);
+	bool ok = true;
+	ok = ok && equal;
+	equal = crypto.constant_time_compare(p1, p2);
+	ok = ok && !equal;
+	return ok;
+}
+
+typedef bool (*TestFunc)();
+
+TestFunc test_funcs[] = {
+	test_PoolByteArray_constant_time_compare,
+	nullptr
+};
+
+MainLoop *test() {
+	int count = 0;
+	int passed = 0;
+
+	while (true) {
+		if (!test_funcs[count]) {
+			break;
+		}
+		bool pass = test_funcs[count]();
+		if (pass) {
+			passed++;
+		}
+		OS::get_singleton()->print("\t%s\n", pass ? "PASS" : "FAILED");
+
+		count++;
+	}
+	OS::get_singleton()->print("\n");
+	OS::get_singleton()->print("Passed %i of %i tests\n", passed, count);
+	return nullptr;
+}
+
+} // namespace TestCrypto

+ 41 - 0
main/tests/test_crypto.h

@@ -0,0 +1,41 @@
+/*************************************************************************/
+/*  test_crypto.h                                                        */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 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/os/main_loop.h"
+
+namespace TestCrypto {
+
+MainLoop *test();
+}
+
+#endif

+ 5 - 0
main/tests/test_main.cpp

@@ -36,6 +36,7 @@
 
 
 #include "test_astar.h"
 #include "test_astar.h"
 #include "test_basis.h"
 #include "test_basis.h"
+#include "test_crypto.h"
 #include "test_gdscript.h"
 #include "test_gdscript.h"
 #include "test_gui.h"
 #include "test_gui.h"
 #include "test_math.h"
 #include "test_math.h"
@@ -109,6 +110,10 @@ MainLoop *test_main(String p_test, const List<String> &p_args) {
 		return TestShaderLang::test();
 		return TestShaderLang::test();
 	}
 	}
 
 
+	if (p_test == "crypto") {
+		return TestCrypto::test();
+	}
+
 	if (p_test == "gd_tokenizer") {
 	if (p_test == "gd_tokenizer") {
 		return TestGDScript::test(TestGDScript::TEST_TOKENIZER);
 		return TestGDScript::test(TestGDScript::TEST_TOKENIZER);
 	}
 	}

+ 68 - 3
modules/mbedtls/crypto_mbedtls.cpp

@@ -44,6 +44,7 @@
 #define PEM_END_CRT "-----END CERTIFICATE-----\n"
 #define PEM_END_CRT "-----END CERTIFICATE-----\n"
 
 
 #include <mbedtls/debug.h>
 #include <mbedtls/debug.h>
+#include <mbedtls/md.h>
 #include <mbedtls/pem.h>
 #include <mbedtls/pem.h>
 
 
 CryptoKey *CryptoKeyMbedTLS::create() {
 CryptoKey *CryptoKeyMbedTLS::create() {
@@ -192,6 +193,68 @@ Error X509CertificateMbedTLS::save(String p_path) {
 	return OK;
 	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, PoolByteArray 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.read().ptr(), (size_t)p_key.size());
+	return ret ? FAILED : OK;
+}
+
+Error HMACContextMbedTLS::update(PoolByteArray 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.read().ptr(), (size_t)p_data.size());
+	return ret ? FAILED : OK;
+}
+
+PoolByteArray HMACContextMbedTLS::finish() {
+	ERR_FAIL_COND_V_MSG(ctx == nullptr, PoolByteArray(), "Start must be called before finish.");
+	ERR_FAIL_COND_V_MSG(hash_len == 0, PoolByteArray(), "Unsupported hash type.");
+
+	PoolByteArray out;
+	out.resize(hash_len);
+
+	unsigned char *out_ptr = (unsigned char *)out.write().ptr();
+	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, PoolByteArray(), "Error received while finishing HMAC");
+	return out;
+}
+
 Crypto *CryptoMbedTLS::create() {
 Crypto *CryptoMbedTLS::create() {
 	return memnew(CryptoMbedTLS);
 	return memnew(CryptoMbedTLS);
 }
 }
@@ -205,6 +268,7 @@ void CryptoMbedTLS::initialize_crypto() {
 	Crypto::_load_default_certificates = load_default_certificates;
 	Crypto::_load_default_certificates = load_default_certificates;
 	X509CertificateMbedTLS::make_default();
 	X509CertificateMbedTLS::make_default();
 	CryptoKeyMbedTLS::make_default();
 	CryptoKeyMbedTLS::make_default();
+	HMACContextMbedTLS::make_default();
 }
 }
 
 
 void CryptoMbedTLS::finalize_crypto() {
 void CryptoMbedTLS::finalize_crypto() {
@@ -216,6 +280,7 @@ void CryptoMbedTLS::finalize_crypto() {
 	}
 	}
 	X509CertificateMbedTLS::finalize();
 	X509CertificateMbedTLS::finalize();
 	CryptoKeyMbedTLS::finalize();
 	CryptoKeyMbedTLS::finalize();
+	HMACContextMbedTLS::finalize();
 }
 }
 
 
 CryptoMbedTLS::CryptoMbedTLS() {
 CryptoMbedTLS::CryptoMbedTLS() {
@@ -320,7 +385,7 @@ PoolByteArray CryptoMbedTLS::generate_random_bytes(int p_bytes) {
 	return out;
 	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) {
 	switch (p_hash_type) {
 		case HashingContext::HASH_MD5:
 		case HashingContext::HASH_MD5:
 			r_size = 16;
 			r_size = 16;
@@ -339,7 +404,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) {
 Vector<uint8_t> CryptoMbedTLS::sign(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Ref<CryptoKey> p_key) {
 	int size;
 	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(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));
 	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);
 	Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS>>(p_key);
@@ -357,7 +422,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) {
 bool CryptoMbedTLS::verify(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Vector<uint8_t> p_signature, Ref<CryptoKey> p_key) {
 	int size;
 	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(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));
 	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);
 	Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS>>(p_key);

+ 21 - 1
modules/mbedtls/crypto_mbedtls.h

@@ -101,12 +101,31 @@ public:
 	friend class SSLContextMbedTLS;
 	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, PoolByteArray p_key);
+	virtual Error update(PoolByteArray p_data);
+	virtual PoolByteArray finish();
+
+	HMACContextMbedTLS() {}
+};
+
 class CryptoMbedTLS : public Crypto {
 class CryptoMbedTLS : public Crypto {
 private:
 private:
 	mbedtls_entropy_context entropy;
 	mbedtls_entropy_context entropy;
 	mbedtls_ctr_drbg_context ctr_drbg;
 	mbedtls_ctr_drbg_context ctr_drbg;
 	static X509CertificateMbedTLS *default_certs;
 	static X509CertificateMbedTLS *default_certs;
-	mbedtls_md_type_t _md_type_from_hashtype(HashingContext::HashType p_hash_type, int &r_size);
 
 
 public:
 public:
 	static Crypto *create();
 	static Crypto *create();
@@ -114,6 +133,7 @@ public:
 	static void finalize_crypto();
 	static void finalize_crypto();
 	static X509CertificateMbedTLS *get_default_certificates();
 	static X509CertificateMbedTLS *get_default_certificates();
 	static void load_default_certificates(String p_path);
 	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 PoolByteArray generate_random_bytes(int p_bytes);
 	virtual PoolByteArray generate_random_bytes(int p_bytes);
 	virtual Ref<CryptoKey> generate_rsa(int p_bytes);
 	virtual Ref<CryptoKey> generate_rsa(int p_bytes);