Merge pull request #48144 from Faless/crypto/3.3_encryption_stable

This commit is contained in:
Rémi Verschelde 2021-04-27 13:46:49 +02:00 committed by GitHub
commit 2d57df60f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 565 additions and 21 deletions

116
core/crypto/aes_context.cpp Normal file
View File

@ -0,0 +1,116 @@
/*************************************************************************/
/* aes_context.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/aes_context.h"
Error AESContext::start(Mode p_mode, PoolByteArray p_key, PoolByteArray p_iv) {
ERR_FAIL_COND_V_MSG(mode != MODE_MAX, ERR_ALREADY_IN_USE, "AESContext already started. Call 'finish' before starting a new one.");
ERR_FAIL_COND_V_MSG(p_mode < 0 || p_mode >= MODE_MAX, ERR_INVALID_PARAMETER, "Invalid mode requested.");
// Key check.
int key_bits = p_key.size() << 3;
ERR_FAIL_COND_V_MSG(key_bits != 128 && key_bits != 256, ERR_INVALID_PARAMETER, "AES key must be either 16 or 32 bytes");
// Initialization vector.
if (p_mode == MODE_CBC_ENCRYPT || p_mode == MODE_CBC_DECRYPT) {
ERR_FAIL_COND_V_MSG(p_iv.size() != 16, ERR_INVALID_PARAMETER, "The initialization vector (IV) must be exactly 16 bytes.");
iv.resize(0);
iv.append_array(p_iv);
}
// Encryption/decryption key.
if (p_mode == MODE_CBC_ENCRYPT || p_mode == MODE_ECB_ENCRYPT) {
ctx.set_encode_key(p_key.read().ptr(), key_bits);
} else {
ctx.set_decode_key(p_key.read().ptr(), key_bits);
}
mode = p_mode;
return OK;
}
PoolByteArray AESContext::update(PoolByteArray p_src) {
ERR_FAIL_COND_V_MSG(mode < 0 || mode >= MODE_MAX, PoolByteArray(), "AESContext not started. Call 'start' before calling 'update'.");
int len = p_src.size();
ERR_FAIL_COND_V_MSG(len % 16, PoolByteArray(), "The number of bytes to be encrypted must be multiple of 16. Add padding if needed");
PoolByteArray out;
out.resize(len);
const uint8_t *src_ptr = p_src.read().ptr();
uint8_t *out_ptr = out.write().ptr();
switch (mode) {
case MODE_ECB_ENCRYPT: {
for (int i = 0; i < len; i += 16) {
Error err = ctx.encrypt_ecb(src_ptr + i, out_ptr + i);
ERR_FAIL_COND_V(err != OK, PoolByteArray());
}
} break;
case MODE_ECB_DECRYPT: {
for (int i = 0; i < len; i += 16) {
Error err = ctx.decrypt_ecb(src_ptr + i, out_ptr + i);
ERR_FAIL_COND_V(err != OK, PoolByteArray());
}
} break;
case MODE_CBC_ENCRYPT: {
Error err = ctx.encrypt_cbc(len, iv.write().ptr(), p_src.read().ptr(), out.write().ptr());
ERR_FAIL_COND_V(err != OK, PoolByteArray());
} break;
case MODE_CBC_DECRYPT: {
Error err = ctx.decrypt_cbc(len, iv.write().ptr(), p_src.read().ptr(), out.write().ptr());
ERR_FAIL_COND_V(err != OK, PoolByteArray());
} break;
default:
ERR_FAIL_V_MSG(PoolByteArray(), "Bug!");
}
return out;
}
PoolByteArray AESContext::get_iv_state() {
ERR_FAIL_COND_V_MSG(mode != MODE_CBC_ENCRYPT && mode != MODE_CBC_DECRYPT, PoolByteArray(), "Calling 'get_iv_state' only makes sense when the context is started in CBC mode.");
PoolByteArray out;
out.append_array(iv);
return out;
}
void AESContext::finish() {
mode = MODE_MAX;
iv.resize(0);
}
void AESContext::_bind_methods() {
ClassDB::bind_method(D_METHOD("start", "mode", "key", "iv"), &AESContext::start, DEFVAL(PoolByteArray()));
ClassDB::bind_method(D_METHOD("update", "src"), &AESContext::update);
ClassDB::bind_method(D_METHOD("get_iv_state"), &AESContext::get_iv_state);
ClassDB::bind_method(D_METHOD("finish"), &AESContext::finish);
BIND_ENUM_CONSTANT(MODE_ECB_ENCRYPT);
BIND_ENUM_CONSTANT(MODE_ECB_DECRYPT);
BIND_ENUM_CONSTANT(MODE_CBC_ENCRYPT);
BIND_ENUM_CONSTANT(MODE_CBC_DECRYPT);
BIND_ENUM_CONSTANT(MODE_MAX);
}
AESContext::AESContext() {
mode = MODE_MAX;
}

68
core/crypto/aes_context.h Normal file
View File

@ -0,0 +1,68 @@
/*************************************************************************/
/* aes_context.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 AES_CONTEXT_H
#define AES_CONTEXT_H
#include "core/crypto/crypto_core.h"
#include "core/reference.h"
class AESContext : public Reference {
GDCLASS(AESContext, Reference);
public:
enum Mode {
MODE_ECB_ENCRYPT,
MODE_ECB_DECRYPT,
MODE_CBC_ENCRYPT,
MODE_CBC_DECRYPT,
MODE_MAX
};
private:
Mode mode;
CryptoCore::AESContext ctx;
PoolByteArray iv;
protected:
static void _bind_methods();
public:
Error start(Mode p_mode, PoolByteArray p_key, PoolByteArray p_iv = PoolByteArray());
PoolByteArray update(PoolByteArray p_src);
PoolByteArray get_iv_state();
void finish();
AESContext();
};
VARIANT_ENUM_CAST(AESContext::Mode);
#endif // AES_CONTEXT_H

View File

@ -44,8 +44,11 @@ CryptoKey *CryptoKey::create() {
} }
void CryptoKey::_bind_methods() { void CryptoKey::_bind_methods() {
ClassDB::bind_method(D_METHOD("save", "path"), &CryptoKey::save); ClassDB::bind_method(D_METHOD("save", "path", "public_only"), &CryptoKey::save, DEFVAL(false));
ClassDB::bind_method(D_METHOD("load", "path"), &CryptoKey::load); ClassDB::bind_method(D_METHOD("load", "path", "public_only"), &CryptoKey::load, DEFVAL(false));
ClassDB::bind_method(D_METHOD("is_public_only"), &CryptoKey::is_public_only);
ClassDB::bind_method(D_METHOD("save_to_string", "public_only"), &CryptoKey::save_to_string, DEFVAL(false));
ClassDB::bind_method(D_METHOD("load_from_string", "string_key", "public_only"), &CryptoKey::load_from_string, DEFVAL(false));
} }
X509Certificate *(*X509Certificate::_create)() = NULL; X509Certificate *(*X509Certificate::_create)() = NULL;
@ -80,6 +83,10 @@ 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);
ClassDB::bind_method(D_METHOD("generate_self_signed_certificate", "key", "issuer_name", "not_before", "not_after"), &Crypto::generate_self_signed_certificate, DEFVAL("CN=myserver,O=myorganisation,C=IT"), DEFVAL("20140101000000"), DEFVAL("20340101000000")); ClassDB::bind_method(D_METHOD("generate_self_signed_certificate", "key", "issuer_name", "not_before", "not_after"), &Crypto::generate_self_signed_certificate, DEFVAL("CN=myserver,O=myorganisation,C=IT"), DEFVAL("20140101000000"), DEFVAL("20340101000000"));
ClassDB::bind_method(D_METHOD("sign", "hash_type", "hash", "key"), &Crypto::sign);
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);
} }
Crypto::Crypto() { Crypto::Crypto() {
@ -98,7 +105,12 @@ RES ResourceFormatLoaderCrypto::load(const String &p_path, const String &p_origi
} else if (el == "key") { } else if (el == "key") {
CryptoKey *key = CryptoKey::create(); CryptoKey *key = CryptoKey::create();
if (key) if (key)
key->load(p_path); key->load(p_path, false);
return key;
} else if (el == "pub") {
CryptoKey *key = CryptoKey::create();
if (key)
key->load(p_path, true);
return key; return key;
} }
return NULL; return NULL;
@ -108,6 +120,7 @@ void ResourceFormatLoaderCrypto::get_recognized_extensions(List<String> *p_exten
p_extensions->push_back("crt"); p_extensions->push_back("crt");
p_extensions->push_back("key"); p_extensions->push_back("key");
p_extensions->push_back("pub");
} }
bool ResourceFormatLoaderCrypto::handles_type(const String &p_type) const { bool ResourceFormatLoaderCrypto::handles_type(const String &p_type) const {
@ -120,7 +133,7 @@ String ResourceFormatLoaderCrypto::get_resource_type(const String &p_path) const
String el = p_path.get_extension().to_lower(); String el = p_path.get_extension().to_lower();
if (el == "crt") if (el == "crt")
return "X509Certificate"; return "X509Certificate";
else if (el == "key") else if (el == "key" || el == "pub")
return "CryptoKey"; return "CryptoKey";
return ""; return "";
} }
@ -133,7 +146,8 @@ Error ResourceFormatSaverCrypto::save(const String &p_path, const RES &p_resourc
if (cert.is_valid()) { if (cert.is_valid()) {
err = cert->save(p_path); err = cert->save(p_path);
} else if (key.is_valid()) { } else if (key.is_valid()) {
err = key->save(p_path); String el = p_path.get_extension().to_lower();
err = key->save(p_path, el == "pub");
} else { } else {
ERR_FAIL_V(ERR_INVALID_PARAMETER); ERR_FAIL_V(ERR_INVALID_PARAMETER);
} }
@ -149,7 +163,10 @@ void ResourceFormatSaverCrypto::get_recognized_extensions(const RES &p_resource,
p_extensions->push_back("crt"); p_extensions->push_back("crt");
} }
if (key) { if (key) {
p_extensions->push_back("key"); if (!key->is_public_only()) {
p_extensions->push_back("key");
}
p_extensions->push_back("pub");
} }
} }
bool ResourceFormatSaverCrypto::recognize(const RES &p_resource) const { bool ResourceFormatSaverCrypto::recognize(const RES &p_resource) const {

View File

@ -31,6 +31,7 @@
#ifndef CRYPTO_H #ifndef CRYPTO_H
#define CRYPTO_H #define CRYPTO_H
#include "core/crypto/hashing_context.h"
#include "core/reference.h" #include "core/reference.h"
#include "core/resource.h" #include "core/resource.h"
@ -46,8 +47,11 @@ protected:
public: public:
static CryptoKey *create(); static CryptoKey *create();
virtual Error load(String p_path) = 0; virtual Error load(String p_path, bool p_public_only = false) = 0;
virtual Error save(String p_path) = 0; virtual Error save(String p_path, bool p_public_only = false) = 0;
virtual String save_to_string(bool p_public_only = false) = 0;
virtual Error load_from_string(String p_string_key, bool p_public_only = false) = 0;
virtual bool is_public_only() const = 0;
}; };
class X509Certificate : public Resource { class X509Certificate : public Resource {
@ -80,6 +84,11 @@ public:
virtual Ref<CryptoKey> generate_rsa(int p_bytes) = 0; virtual Ref<CryptoKey> generate_rsa(int p_bytes) = 0;
virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after) = 0; virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after) = 0;
virtual Vector<uint8_t> sign(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Ref<CryptoKey> p_key) = 0;
virtual bool verify(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Vector<uint8_t> p_signature, Ref<CryptoKey> p_key) = 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;
Crypto(); Crypto();
}; };

View File

@ -145,6 +145,16 @@ Error CryptoCore::AESContext::decrypt_ecb(const uint8_t p_src[16], uint8_t r_dst
return ret ? FAILED : OK; return ret ? FAILED : OK;
} }
Error CryptoCore::AESContext::encrypt_cbc(size_t p_length, uint8_t r_iv[16], const uint8_t *p_src, uint8_t *r_dst) {
int ret = mbedtls_aes_crypt_cbc((mbedtls_aes_context *)ctx, MBEDTLS_AES_ENCRYPT, p_length, r_iv, p_src, r_dst);
return ret ? FAILED : OK;
}
Error CryptoCore::AESContext::decrypt_cbc(size_t p_length, uint8_t r_iv[16], const uint8_t *p_src, uint8_t *r_dst) {
int ret = mbedtls_aes_crypt_cbc((mbedtls_aes_context *)ctx, MBEDTLS_AES_DECRYPT, p_length, r_iv, p_src, r_dst);
return ret ? FAILED : OK;
}
// CryptoCore // CryptoCore
String CryptoCore::b64_encode_str(const uint8_t *p_src, int p_src_len) { String CryptoCore::b64_encode_str(const uint8_t *p_src, int p_src_len) {
int b64len = p_src_len / 3 * 4 + 4 + 1; int b64len = p_src_len / 3 * 4 + 4 + 1;

View File

@ -91,6 +91,8 @@ public:
Error set_decode_key(const uint8_t *p_key, size_t p_bits); Error set_decode_key(const uint8_t *p_key, size_t p_bits);
Error encrypt_ecb(const uint8_t p_src[16], uint8_t r_dst[16]); Error encrypt_ecb(const uint8_t p_src[16], uint8_t r_dst[16]);
Error decrypt_ecb(const uint8_t p_src[16], uint8_t r_dst[16]); Error decrypt_ecb(const uint8_t p_src[16], uint8_t r_dst[16]);
Error encrypt_cbc(size_t p_length, uint8_t r_iv[16], const uint8_t *p_src, uint8_t *r_dst);
Error decrypt_cbc(size_t p_length, uint8_t r_iv[16], const uint8_t *p_src, uint8_t *r_dst);
}; };
static String b64_encode_str(const uint8_t *p_src, int p_src_len); static String b64_encode_str(const uint8_t *p_src, int p_src_len);

View File

@ -34,6 +34,7 @@
#include "core/class_db.h" #include "core/class_db.h"
#include "core/compressed_translation.h" #include "core/compressed_translation.h"
#include "core/core_string_names.h" #include "core/core_string_names.h"
#include "core/crypto/aes_context.h"
#include "core/crypto/crypto.h" #include "core/crypto/crypto.h"
#include "core/crypto/hashing_context.h" #include "core/crypto/hashing_context.h"
#include "core/engine.h" #include "core/engine.h"
@ -159,6 +160,7 @@ void register_core_types() {
// Crypto // Crypto
ClassDB::register_class<HashingContext>(); ClassDB::register_class<HashingContext>();
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<Crypto>(); ClassDB::register_custom_instance_class<Crypto>();

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="AESContext" inherits="Reference" version="3.2">
<brief_description>
Interface to low level AES encryption features.
</brief_description>
<description>
This class provides access to AES encryption/decryption of raw data. Both AES-ECB and AES-CBC mode are supported.
[codeblock]
extends Node
var aes = AESContext.new()
func _ready():
var key = "My secret key!!!" # Key must be either 16 or 32 bytes.
var data = "My secret text!!" # Data size must be multiple of 16 bytes, apply padding if needed.
# Encrypt ECB
aes.start(AESContext.MODE_ECB_ENCRYPT, key.to_utf8())
var encrypted = aes.update(data.to_utf8())
aes.finish()
# Decrypt ECB
aes.start(AESContext.MODE_ECB_DECRYPT, key.to_utf8())
var decrypted = aes.update(encrypted)
aes.finish()
# Check ECB
assert(decrypted == data.to_utf8())
var iv = "My secret iv!!!!" # IV must be of exactly 16 bytes.
# Encrypt CBC
aes.start(AESContext.MODE_CBC_ENCRYPT, key.to_utf8(), iv.to_utf8())
encrypted = aes.update(data.to_utf8())
aes.finish()
# Decrypt CBC
aes.start(AESContext.MODE_CBC_DECRYPT, key.to_utf8(), iv.to_utf8())
decrypted = aes.update(encrypted)
aes.finish()
# Check CBC
assert(decrypted == data.to_utf8())
[/codeblock]
</description>
<tutorials>
</tutorials>
<methods>
<method name="finish">
<return type="void">
</return>
<description>
Close this AES context so it can be started again. See [method start].
</description>
</method>
<method name="get_iv_state">
<return type="PoolByteArray">
</return>
<description>
Get the current IV state for this context (IV gets updated when calling [method update]). You normally don't need this funciton.
Note: This function only makes sense when the context is started with [constant MODE_CBC_ENCRYPT] or [constant MODE_CBC_DECRYPT].
</description>
</method>
<method name="start">
<return type="int" enum="Error">
</return>
<argument index="0" name="mode" type="int" enum="AESContext.Mode">
</argument>
<argument index="1" name="key" type="PoolByteArray">
</argument>
<argument index="2" name="iv" type="PoolByteArray" default="PoolByteArray( )">
</argument>
<description>
Start the AES context in the given [code]mode[/code]. A [code]key[/code] of either 16 or 32 bytes must always be provided, while an [code]iv[/code] (initialization vector) of exactly 16 bytes, is only needed when [code]mode[/code] is either [constant MODE_CBC_ENCRYPT] or [constant MODE_CBC_DECRYPT].
</description>
</method>
<method name="update">
<return type="PoolByteArray">
</return>
<argument index="0" name="src" type="PoolByteArray">
</argument>
<description>
Run the desired operation for this AES context. Will return a [PoolByteArray] containing the result of encrypting (or decrypting) the given [code]src[/code]. See [method start] for mode of operation.
Note: The size of [code]src[/code] must be a multiple of 16. Apply some padding if needed.
</description>
</method>
</methods>
<constants>
<constant name="MODE_ECB_ENCRYPT" value="0" enum="Mode">
AES electronic codebook encryption mode.
</constant>
<constant name="MODE_ECB_DECRYPT" value="1" enum="Mode">
AES electronic codebook decryption mode.
</constant>
<constant name="MODE_CBC_ENCRYPT" value="2" enum="Mode">
AES cipher blocker chaining encryption mode.
</constant>
<constant name="MODE_CBC_DECRYPT" value="3" enum="Mode">
AES cipher blocker chaining decryption mode.
</constant>
<constant name="MODE_MAX" value="4" enum="Mode">
Maximum value for the mode enum.
</constant>
</constants>
</class>

View File

@ -5,7 +5,7 @@
</brief_description> </brief_description>
<description> <description>
The Crypto class allows you to access some more advanced cryptographic functionalities in Godot. The Crypto class allows you to access some more advanced cryptographic functionalities in Godot.
For now, this includes generating cryptographically secure random bytes, and RSA keys and self-signed X509 certificates generation. More functionalities are planned for future releases. For now, this includes generating cryptographically secure random bytes, RSA keys and self-signed X509 certificates generation, asymmetric key encryption/decryption, and signing/verification.
[codeblock] [codeblock]
extends Node extends Node
@ -21,12 +21,48 @@
# Save key and certificate in the user folder. # Save key and certificate in the user folder.
key.save("user://generated.key") key.save("user://generated.key")
cert.save("user://generated.crt") cert.save("user://generated.crt")
# Encryption
var data = "Some data"
var encrypted = crypto.encrypt(key, data.to_utf8())
# Decryption
var decrypted = crypto.decrypt(key, encrypted)
# Signing
var signature = crypto.sign(HashingContext.HASH_SHA256, data.sha256_buffer(), key)
# Verifying
var verified = crypto.verify(HashingContext.HASH_SHA256, data.sha256_buffer(), signature, key)
# Checks
assert(verified)
assert(data.to_utf8() == decrypted)
[/codeblock] [/codeblock]
[b]Note:[/b] Not available in HTML5 exports. [b]Note:[/b] Not available in HTML5 exports.
</description> </description>
<tutorials> <tutorials>
</tutorials> </tutorials>
<methods> <methods>
<method name="decrypt">
<return type="PoolByteArray">
</return>
<argument index="0" name="key" type="CryptoKey">
</argument>
<argument index="1" name="ciphertext" type="PoolByteArray">
</argument>
<description>
Decrypt the given [code]ciphertext[/code] with the provided private [code]key[/code].
[b]Note[/b]: The maximum size of accepted ciphertext is limited by the key size.
</description>
</method>
<method name="encrypt">
<return type="PoolByteArray">
</return>
<argument index="0" name="key" type="CryptoKey">
</argument>
<argument index="1" name="plaintext" type="PoolByteArray">
</argument>
<description>
Encrypt the given [code]plaintext[/code] with the provided public [code]key[/code].
[b]Note[/b]: The maximum size of accepted plaintext is limited by the key size.
</description>
</method>
<method name="generate_random_bytes"> <method name="generate_random_bytes">
<return type="PoolByteArray"> <return type="PoolByteArray">
</return> </return>
@ -68,6 +104,34 @@
[/codeblock] [/codeblock]
</description> </description>
</method> </method>
<method name="sign">
<return type="PoolByteArray">
</return>
<argument index="0" name="hash_type" type="int" enum="HashingContext.HashType">
</argument>
<argument index="1" name="hash" type="PoolByteArray">
</argument>
<argument index="2" name="key" type="CryptoKey">
</argument>
<description>
Sign a given [code]hash[/code] of type [code]hash_type[/code] with the provided private [code]key[/code].
</description>
</method>
<method name="verify">
<return type="bool">
</return>
<argument index="0" name="hash_type" type="int" enum="HashingContext.HashType">
</argument>
<argument index="1" name="hash" type="PoolByteArray">
</argument>
<argument index="2" name="signature" type="PoolByteArray">
</argument>
<argument index="3" name="key" type="CryptoKey">
</argument>
<description>
Verify that a given [code]signature[/code] for [code]hash[/code] of type [code]hash_type[/code] against the provided public [code]key[/code].
</description>
</method>
</methods> </methods>
<constants> <constants>
</constants> </constants>

View File

@ -11,13 +11,34 @@
<tutorials> <tutorials>
</tutorials> </tutorials>
<methods> <methods>
<method name="is_public_only" qualifiers="const">
<return type="bool">
</return>
<description>
Return [code]true[/code] if this CryptoKey only has the public part, and not the private one.
</description>
</method>
<method name="load"> <method name="load">
<return type="int" enum="Error"> <return type="int" enum="Error">
</return> </return>
<argument index="0" name="path" type="String"> <argument index="0" name="path" type="String">
</argument> </argument>
<argument index="1" name="public_only" type="bool" default="false">
</argument>
<description> <description>
Loads a key from [code]path[/code] ("*.key" file). Loads a key from [code]path[/code]. If [code]public_only[/code] is [code]true[/code], only the public key will be loaded.
[b]Note[/b]: [code]path[/code] should should be a "*.pub" file if [code]public_only[/code] is [code]true[/code], a "*.key" file otherwise.
</description>
</method>
<method name="load_from_string">
<return type="int" enum="Error">
</return>
<argument index="0" name="string_key" type="String">
</argument>
<argument index="1" name="public_only" type="bool" default="false">
</argument>
<description>
Loads a key from the given [code]string[/code]. If [code]public_only[/code] is [code]true[/code], only the public key will be loaded.
</description> </description>
</method> </method>
<method name="save"> <method name="save">
@ -25,8 +46,20 @@
</return> </return>
<argument index="0" name="path" type="String"> <argument index="0" name="path" type="String">
</argument> </argument>
<argument index="1" name="public_only" type="bool" default="false">
</argument>
<description> <description>
Saves a key to the given [code]path[/code] (should be a "*.key" file). Saves a key to the given [code]path[/code]. If [code]public_only[/code] is [code]true[/code], only the public key will be saved.
[b]Note[/b]: [code]path[/code] should should be a "*.pub" file if [code]public_only[/code] is [code]true[/code], a "*.key" file otherwise.
</description>
</method>
<method name="save_to_string">
<return type="String">
</return>
<argument index="0" name="public_only" type="bool" default="false">
</argument>
<description>
Returns a string containing the key in PEM format. If [code]public_only[/code] is [code]true[/code], only the public key will be included.
</description> </description>
</method> </method>
</methods> </methods>

View File

@ -50,7 +50,7 @@ CryptoKey *CryptoKeyMbedTLS::create() {
return memnew(CryptoKeyMbedTLS); return memnew(CryptoKeyMbedTLS);
} }
Error CryptoKeyMbedTLS::load(String p_path) { Error CryptoKeyMbedTLS::load(String p_path, bool p_public_only) {
ERR_FAIL_COND_V_MSG(locks, ERR_ALREADY_IN_USE, "Key is in use"); ERR_FAIL_COND_V_MSG(locks, ERR_ALREADY_IN_USE, "Key is in use");
PoolByteArray out; PoolByteArray out;
@ -66,35 +66,77 @@ Error CryptoKeyMbedTLS::load(String p_path) {
} }
memdelete(f); memdelete(f);
int ret = mbedtls_pk_parse_key(&pkey, out.read().ptr(), out.size(), NULL, 0); int ret = 0;
if (p_public_only) {
ret = mbedtls_pk_parse_public_key(&pkey, out.read().ptr(), out.size());
} else {
ret = mbedtls_pk_parse_key(&pkey, out.read().ptr(), out.size(), NULL, 0);
}
// We MUST zeroize the memory for safety! // We MUST zeroize the memory for safety!
mbedtls_platform_zeroize(out.write().ptr(), out.size()); mbedtls_platform_zeroize(out.write().ptr(), out.size());
ERR_FAIL_COND_V_MSG(ret, FAILED, "Error parsing private key '" + itos(ret) + "'."); ERR_FAIL_COND_V_MSG(ret, FAILED, "Error parsing key '" + itos(ret) + "'.");
public_only = p_public_only;
return OK; return OK;
} }
Error CryptoKeyMbedTLS::save(String p_path) { Error CryptoKeyMbedTLS::save(String p_path, bool p_public_only) {
FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE); FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE);
ERR_FAIL_COND_V_MSG(!f, ERR_INVALID_PARAMETER, "Cannot save CryptoKeyMbedTLS file '" + p_path + "'."); ERR_FAIL_COND_V_MSG(!f, ERR_INVALID_PARAMETER, "Cannot save CryptoKeyMbedTLS file '" + p_path + "'.");
unsigned char w[16000]; unsigned char w[16000];
memset(w, 0, sizeof(w)); memset(w, 0, sizeof(w));
int ret = mbedtls_pk_write_key_pem(&pkey, w, sizeof(w)); int ret = 0;
if (p_public_only) {
ret = mbedtls_pk_write_pubkey_pem(&pkey, w, sizeof(w));
} else {
ret = mbedtls_pk_write_key_pem(&pkey, w, sizeof(w));
}
if (ret != 0) { if (ret != 0) {
memdelete(f); memdelete(f);
memset(w, 0, sizeof(w)); // Zeroize anything we might have written. mbedtls_platform_zeroize(w, sizeof(w)); // Zeroize anything we might have written.
ERR_FAIL_V_MSG(FAILED, "Error writing key '" + itos(ret) + "'."); ERR_FAIL_V_MSG(FAILED, "Error writing key '" + itos(ret) + "'.");
} }
size_t len = strlen((char *)w); size_t len = strlen((char *)w);
f->store_buffer(w, len); f->store_buffer(w, len);
memdelete(f); memdelete(f);
memset(w, 0, sizeof(w)); // Zeroize temporary buffer. mbedtls_platform_zeroize(w, sizeof(w)); // Zeroize temporary buffer.
return OK; return OK;
} }
Error CryptoKeyMbedTLS::load_from_string(String p_string_key, bool p_public_only) {
int ret = 0;
if (p_public_only) {
ret = mbedtls_pk_parse_public_key(&pkey, (unsigned char *)p_string_key.utf8().get_data(), p_string_key.utf8().size());
} else {
ret = mbedtls_pk_parse_key(&pkey, (unsigned char *)p_string_key.utf8().get_data(), p_string_key.utf8().size(), NULL, 0);
}
ERR_FAIL_COND_V_MSG(ret, FAILED, "Error parsing key '" + itos(ret) + "'.");
public_only = p_public_only;
return OK;
}
String CryptoKeyMbedTLS::save_to_string(bool p_public_only) {
unsigned char w[16000];
memset(w, 0, sizeof(w));
int ret = 0;
if (p_public_only) {
ret = mbedtls_pk_write_pubkey_pem(&pkey, w, sizeof(w));
} else {
ret = mbedtls_pk_write_key_pem(&pkey, w, sizeof(w));
}
if (ret != 0) {
mbedtls_platform_zeroize(w, sizeof(w));
ERR_FAIL_V_MSG("", "Error saving key '" + itos(ret) + "'.");
}
String s = String::utf8((char *)w);
return s;
}
X509Certificate *X509CertificateMbedTLS::create() { X509Certificate *X509CertificateMbedTLS::create() {
return memnew(X509CertificateMbedTLS); return memnew(X509CertificateMbedTLS);
} }
@ -229,6 +271,7 @@ Ref<CryptoKey> CryptoMbedTLS::generate_rsa(int p_bytes) {
int ret = mbedtls_pk_setup(&(out->pkey), mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)); int ret = mbedtls_pk_setup(&(out->pkey), mbedtls_pk_info_from_type(MBEDTLS_PK_RSA));
ERR_FAIL_COND_V(ret != 0, NULL); ERR_FAIL_COND_V(ret != 0, NULL);
ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(out->pkey), mbedtls_ctr_drbg_random, &ctr_drbg, p_bytes, 65537); ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(out->pkey), mbedtls_ctr_drbg_random, &ctr_drbg, p_bytes, 65537);
out->public_only = false;
ERR_FAIL_COND_V(ret != 0, NULL); ERR_FAIL_COND_V(ret != 0, NULL);
return out; return out;
} }
@ -277,3 +320,75 @@ PoolByteArray CryptoMbedTLS::generate_random_bytes(int p_bytes) {
mbedtls_ctr_drbg_random(&ctr_drbg, out.write().ptr(), p_bytes); mbedtls_ctr_drbg_random(&ctr_drbg, out.write().ptr(), p_bytes);
return out; return out;
} }
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;
return MBEDTLS_MD_MD5;
case HashingContext::HASH_SHA1:
r_size = 20;
return MBEDTLS_MD_SHA1;
case HashingContext::HASH_SHA256:
r_size = 32;
return MBEDTLS_MD_SHA256;
default:
r_size = 0;
ERR_FAIL_V_MSG(MBEDTLS_MD_NONE, "Invalid hash type.");
}
}
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);
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);
ERR_FAIL_COND_V_MSG(!key.is_valid(), Vector<uint8_t>(), "Invalid key provided.");
ERR_FAIL_COND_V_MSG(key->is_public_only(), Vector<uint8_t>(), "Invalid key provided. Cannot sign with public_only keys.");
size_t sig_size = 0;
unsigned char buf[MBEDTLS_MPI_MAX_SIZE];
Vector<uint8_t> out;
int ret = mbedtls_pk_sign(&(key->pkey), type, p_hash.ptr(), size, buf, &sig_size, mbedtls_ctr_drbg_random, &ctr_drbg);
ERR_FAIL_COND_V_MSG(ret, out, "Error while signing: " + itos(ret));
out.resize(sig_size);
copymem(out.ptrw(), buf, sig_size);
return out;
}
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);
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);
ERR_FAIL_COND_V_MSG(!key.is_valid(), false, "Invalid key provided.");
return mbedtls_pk_verify(&(key->pkey), type, p_hash.ptr(), size, p_signature.ptr(), p_signature.size()) == 0;
}
Vector<uint8_t> CryptoMbedTLS::encrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_plaintext) {
Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS> >(p_key);
ERR_FAIL_COND_V_MSG(!key.is_valid(), Vector<uint8_t>(), "Invalid key provided.");
uint8_t buf[1024];
size_t size;
Vector<uint8_t> out;
int ret = mbedtls_pk_encrypt(&(key->pkey), p_plaintext.ptr(), p_plaintext.size(), buf, &size, sizeof(buf), mbedtls_ctr_drbg_random, &ctr_drbg);
ERR_FAIL_COND_V_MSG(ret, out, "Error while encrypting: " + itos(ret));
out.resize(size);
copymem(out.ptrw(), buf, size);
return out;
}
Vector<uint8_t> CryptoMbedTLS::decrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_ciphertext) {
Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS> >(p_key);
ERR_FAIL_COND_V_MSG(!key.is_valid(), Vector<uint8_t>(), "Invalid key provided.");
ERR_FAIL_COND_V_MSG(key->is_public_only(), Vector<uint8_t>(), "Invalid key provided. Cannot decrypt using a public_only key.");
uint8_t buf[2048];
size_t size;
Vector<uint8_t> out;
int ret = mbedtls_pk_decrypt(&(key->pkey), p_ciphertext.ptr(), p_ciphertext.size(), buf, &size, sizeof(buf), mbedtls_ctr_drbg_random, &ctr_drbg);
ERR_FAIL_COND_V_MSG(ret, out, "Error while decrypting: " + itos(ret));
out.resize(size);
copymem(out.ptrw(), buf, size);
return out;
}

View File

@ -44,15 +44,19 @@ class CryptoKeyMbedTLS : public CryptoKey {
private: private:
mbedtls_pk_context pkey; mbedtls_pk_context pkey;
int locks; int locks = 0;
bool public_only = true;
public: public:
static CryptoKey *create(); static CryptoKey *create();
static void make_default() { CryptoKey::_create = create; } static void make_default() { CryptoKey::_create = create; }
static void finalize() { CryptoKey::_create = NULL; } static void finalize() { CryptoKey::_create = NULL; }
virtual Error load(String p_path); virtual Error load(String p_path, bool p_public_only);
virtual Error save(String p_path); virtual Error save(String p_path, bool p_public_only);
virtual String save_to_string(bool p_public_only);
virtual Error load_from_string(String p_string_key, bool p_public_only);
virtual bool is_public_only() const { return public_only; };
CryptoKeyMbedTLS() { CryptoKeyMbedTLS() {
mbedtls_pk_init(&pkey); mbedtls_pk_init(&pkey);
@ -105,6 +109,7 @@ 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();
@ -116,6 +121,10 @@ public:
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);
virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after); virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after);
virtual Vector<uint8_t> sign(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Ref<CryptoKey> p_key);
virtual bool verify(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Vector<uint8_t> p_signature, Ref<CryptoKey> p_key);
virtual Vector<uint8_t> encrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_plaintext);
virtual Vector<uint8_t> decrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_ciphertext);
CryptoMbedTLS(); CryptoMbedTLS();
~CryptoMbedTLS(); ~CryptoMbedTLS();