Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Backport AESContext, RSA public keys, encryption, decryption, sign, and verify. #48144

Merged
merged 6 commits into from
Apr 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions core/crypto/aes_context.cpp
Original file line number Diff line number Diff line change
@@ -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 changes: 68 additions & 0 deletions core/crypto/aes_context.h
Original file line number Diff line number Diff line change
@@ -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
29 changes: 23 additions & 6 deletions core/crypto/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ CryptoKey *CryptoKey::create() {
}

void CryptoKey::_bind_methods() {
ClassDB::bind_method(D_METHOD("save", "path"), &CryptoKey::save);
ClassDB::bind_method(D_METHOD("load", "path"), &CryptoKey::load);
ClassDB::bind_method(D_METHOD("save", "path", "public_only"), &CryptoKey::save, DEFVAL(false));
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;
Expand Down Expand Up @@ -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_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("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() {
Expand All @@ -98,7 +105,12 @@ RES ResourceFormatLoaderCrypto::load(const String &p_path, const String &p_origi
} else if (el == "key") {
CryptoKey *key = CryptoKey::create();
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 NULL;
Expand All @@ -108,6 +120,7 @@ void ResourceFormatLoaderCrypto::get_recognized_extensions(List<String> *p_exten

p_extensions->push_back("crt");
p_extensions->push_back("key");
p_extensions->push_back("pub");
}

bool ResourceFormatLoaderCrypto::handles_type(const String &p_type) const {
Expand All @@ -120,7 +133,7 @@ String ResourceFormatLoaderCrypto::get_resource_type(const String &p_path) const
String el = p_path.get_extension().to_lower();
if (el == "crt")
return "X509Certificate";
else if (el == "key")
else if (el == "key" || el == "pub")
return "CryptoKey";
return "";
}
Expand All @@ -133,7 +146,8 @@ Error ResourceFormatSaverCrypto::save(const String &p_path, const RES &p_resourc
if (cert.is_valid()) {
err = cert->save(p_path);
} 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 {
ERR_FAIL_V(ERR_INVALID_PARAMETER);
}
Expand All @@ -149,7 +163,10 @@ void ResourceFormatSaverCrypto::get_recognized_extensions(const RES &p_resource,
p_extensions->push_back("crt");
}
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 {
Expand Down
13 changes: 11 additions & 2 deletions core/crypto/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#ifndef CRYPTO_H
#define CRYPTO_H

#include "core/crypto/hashing_context.h"
#include "core/reference.h"
#include "core/resource.h"

Expand All @@ -46,8 +47,11 @@ class CryptoKey : public Resource {

public:
static CryptoKey *create();
virtual Error load(String p_path) = 0;
virtual Error save(String p_path) = 0;
virtual Error load(String p_path, bool p_public_only = false) = 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 {
Expand Down Expand Up @@ -80,6 +84,11 @@ class Crypto : public Reference {
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 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();
};

Expand Down
10 changes: 10 additions & 0 deletions core/crypto/crypto_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ Error CryptoCore::AESContext::decrypt_ecb(const uint8_t p_src[16], uint8_t r_dst
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
String CryptoCore::b64_encode_str(const uint8_t *p_src, int p_src_len) {
int b64len = p_src_len / 3 * 4 + 4 + 1;
Expand Down
2 changes: 2 additions & 0 deletions core/crypto/crypto_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class CryptoCore {
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 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);
Expand Down
2 changes: 2 additions & 0 deletions core/register_core_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "core/class_db.h"
#include "core/compressed_translation.h"
#include "core/core_string_names.h"
#include "core/crypto/aes_context.h"
#include "core/crypto/crypto.h"
#include "core/crypto/hashing_context.h"
#include "core/engine.h"
Expand Down Expand Up @@ -159,6 +160,7 @@ void register_core_types() {

// Crypto
ClassDB::register_class<HashingContext>();
ClassDB::register_class<AESContext>();
ClassDB::register_custom_instance_class<X509Certificate>();
ClassDB::register_custom_instance_class<CryptoKey>();
ClassDB::register_custom_instance_class<Crypto>();
Expand Down
Loading