Skip to content

Commit

Permalink
Merge pull request #43536 from jonbonazza/hmac
Browse files Browse the repository at this point in the history
feat: HMAC support in Crypto APIs
  • Loading branch information
Faless authored Dec 3, 2020
2 parents dab7cc0 + d5925fd commit 502ff74
Show file tree
Hide file tree
Showing 13 changed files with 480 additions and 4 deletions.
47 changes: 47 additions & 0 deletions core/crypto/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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
Expand Down
23 changes: 23 additions & 0 deletions core/crypto/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ class X509Certificate : public Resource {
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);

Expand All @@ -88,6 +105,12 @@ class Crypto : public Reference {
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() {}
};

Expand Down
1 change: 1 addition & 0 deletions core/register_core_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>();

Expand Down
26 changes: 26 additions & 0 deletions doc/classes/Crypto.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
88 changes: 88 additions & 0 deletions doc/classes/HMACContext.xml
Original file line number Diff line number Diff line change
@@ -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>
4 changes: 4 additions & 0 deletions modules/mbedtls/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -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")
71 changes: 68 additions & 3 deletions modules/mbedtls/crypto_mbedtls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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() {
Expand All @@ -210,6 +274,7 @@ void CryptoMbedTLS::finalize_crypto() {
}
X509CertificateMbedTLS::finalize();
CryptoKeyMbedTLS::finalize();
HMACContextMbedTLS::finalize();
}

CryptoMbedTLS::CryptoMbedTLS() {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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);
Expand Down
Loading

0 comments on commit 502ff74

Please sign in to comment.