| 1 | /**************************************************************************/ |
| 2 | /* crypto.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "crypto.h" |
| 32 | |
| 33 | #include "core/config/engine.h" |
| 34 | #include "core/io/certs_compressed.gen.h" |
| 35 | #include "core/io/compression.h" |
| 36 | |
| 37 | /// Resources |
| 38 | |
| 39 | CryptoKey *(*CryptoKey::_create)() = nullptr; |
| 40 | CryptoKey *CryptoKey::create() { |
| 41 | if (_create) { |
| 42 | return _create(); |
| 43 | } |
| 44 | return nullptr; |
| 45 | } |
| 46 | |
| 47 | void CryptoKey::_bind_methods() { |
| 48 | ClassDB::bind_method(D_METHOD("save" , "path" , "public_only" ), &CryptoKey::save, DEFVAL(false)); |
| 49 | ClassDB::bind_method(D_METHOD("load" , "path" , "public_only" ), &CryptoKey::load, DEFVAL(false)); |
| 50 | ClassDB::bind_method(D_METHOD("is_public_only" ), &CryptoKey::is_public_only); |
| 51 | ClassDB::bind_method(D_METHOD("save_to_string" , "public_only" ), &CryptoKey::save_to_string, DEFVAL(false)); |
| 52 | ClassDB::bind_method(D_METHOD("load_from_string" , "string_key" , "public_only" ), &CryptoKey::load_from_string, DEFVAL(false)); |
| 53 | } |
| 54 | |
| 55 | X509Certificate *(*X509Certificate::_create)() = nullptr; |
| 56 | X509Certificate *X509Certificate::create() { |
| 57 | if (_create) { |
| 58 | return _create(); |
| 59 | } |
| 60 | return nullptr; |
| 61 | } |
| 62 | |
| 63 | void X509Certificate::_bind_methods() { |
| 64 | ClassDB::bind_method(D_METHOD("save" , "path" ), &X509Certificate::save); |
| 65 | ClassDB::bind_method(D_METHOD("load" , "path" ), &X509Certificate::load); |
| 66 | ClassDB::bind_method(D_METHOD("save_to_string" ), &X509Certificate::save_to_string); |
| 67 | ClassDB::bind_method(D_METHOD("load_from_string" , "string" ), &X509Certificate::load_from_string); |
| 68 | } |
| 69 | |
| 70 | /// TLSOptions |
| 71 | |
| 72 | Ref<TLSOptions> TLSOptions::client(Ref<X509Certificate> p_trusted_chain, const String &p_common_name_override) { |
| 73 | Ref<TLSOptions> opts; |
| 74 | opts.instantiate(); |
| 75 | opts->trusted_ca_chain = p_trusted_chain; |
| 76 | opts->common_name = p_common_name_override; |
| 77 | opts->verify_mode = TLS_VERIFY_FULL; |
| 78 | return opts; |
| 79 | } |
| 80 | |
| 81 | Ref<TLSOptions> TLSOptions::client_unsafe(Ref<X509Certificate> p_trusted_chain) { |
| 82 | Ref<TLSOptions> opts; |
| 83 | opts.instantiate(); |
| 84 | opts->trusted_ca_chain = p_trusted_chain; |
| 85 | if (p_trusted_chain.is_null()) { |
| 86 | opts->verify_mode = TLS_VERIFY_NONE; |
| 87 | } else { |
| 88 | opts->verify_mode = TLS_VERIFY_CERT; |
| 89 | } |
| 90 | return opts; |
| 91 | } |
| 92 | |
| 93 | Ref<TLSOptions> TLSOptions::server(Ref<CryptoKey> p_own_key, Ref<X509Certificate> p_own_certificate) { |
| 94 | Ref<TLSOptions> opts; |
| 95 | opts.instantiate(); |
| 96 | opts->server_mode = true; |
| 97 | opts->own_certificate = p_own_certificate; |
| 98 | opts->private_key = p_own_key; |
| 99 | opts->verify_mode = TLS_VERIFY_NONE; |
| 100 | return opts; |
| 101 | } |
| 102 | |
| 103 | void TLSOptions::_bind_methods() { |
| 104 | ClassDB::bind_static_method("TLSOptions" , D_METHOD("client" , "trusted_chain" , "common_name_override" ), &TLSOptions::client, DEFVAL(Ref<X509Certificate>()), DEFVAL(String())); |
| 105 | ClassDB::bind_static_method("TLSOptions" , D_METHOD("client_unsafe" , "trusted_chain" ), &TLSOptions::client_unsafe, DEFVAL(Ref<X509Certificate>())); |
| 106 | ClassDB::bind_static_method("TLSOptions" , D_METHOD("server" , "key" , "certificate" ), &TLSOptions::server); |
| 107 | } |
| 108 | |
| 109 | /// HMACContext |
| 110 | |
| 111 | void HMACContext::_bind_methods() { |
| 112 | ClassDB::bind_method(D_METHOD("start" , "hash_type" , "key" ), &HMACContext::start); |
| 113 | ClassDB::bind_method(D_METHOD("update" , "data" ), &HMACContext::update); |
| 114 | ClassDB::bind_method(D_METHOD("finish" ), &HMACContext::finish); |
| 115 | } |
| 116 | |
| 117 | HMACContext *(*HMACContext::_create)() = nullptr; |
| 118 | HMACContext *HMACContext::create() { |
| 119 | if (_create) { |
| 120 | return _create(); |
| 121 | } |
| 122 | ERR_FAIL_V_MSG(nullptr, "HMACContext is not available when the mbedtls module is disabled." ); |
| 123 | } |
| 124 | |
| 125 | /// Crypto |
| 126 | |
| 127 | void (*Crypto::_load_default_certificates)(String p_path) = nullptr; |
| 128 | Crypto *(*Crypto::_create)() = nullptr; |
| 129 | Crypto *Crypto::create() { |
| 130 | if (_create) { |
| 131 | return _create(); |
| 132 | } |
| 133 | ERR_FAIL_V_MSG(nullptr, "Crypto is not available when the mbedtls module is disabled." ); |
| 134 | } |
| 135 | |
| 136 | void Crypto::load_default_certificates(String p_path) { |
| 137 | if (_load_default_certificates) { |
| 138 | _load_default_certificates(p_path); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | PackedByteArray Crypto::hmac_digest(HashingContext::HashType p_hash_type, PackedByteArray p_key, PackedByteArray p_msg) { |
| 143 | Ref<HMACContext> ctx = Ref<HMACContext>(HMACContext::create()); |
| 144 | ERR_FAIL_COND_V_MSG(ctx.is_null(), PackedByteArray(), "HMAC is not available without mbedtls module." ); |
| 145 | Error err = ctx->start(p_hash_type, p_key); |
| 146 | ERR_FAIL_COND_V(err != OK, PackedByteArray()); |
| 147 | err = ctx->update(p_msg); |
| 148 | ERR_FAIL_COND_V(err != OK, PackedByteArray()); |
| 149 | return ctx->finish(); |
| 150 | } |
| 151 | |
| 152 | // Compares two HMACS for equality without leaking timing information in order to prevent timing attacks. |
| 153 | // @see: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy |
| 154 | bool Crypto::constant_time_compare(PackedByteArray p_trusted, PackedByteArray p_received) { |
| 155 | const uint8_t *t = p_trusted.ptr(); |
| 156 | const uint8_t *r = p_received.ptr(); |
| 157 | int tlen = p_trusted.size(); |
| 158 | int rlen = p_received.size(); |
| 159 | // If the lengths are different then nothing else matters. |
| 160 | if (tlen != rlen) { |
| 161 | return false; |
| 162 | } |
| 163 | |
| 164 | uint8_t v = 0; |
| 165 | for (int i = 0; i < tlen; i++) { |
| 166 | v |= t[i] ^ r[i]; |
| 167 | } |
| 168 | return v == 0; |
| 169 | } |
| 170 | |
| 171 | void Crypto::_bind_methods() { |
| 172 | ClassDB::bind_method(D_METHOD("generate_random_bytes" , "size" ), &Crypto::generate_random_bytes); |
| 173 | ClassDB::bind_method(D_METHOD("generate_rsa" , "size" ), &Crypto::generate_rsa); |
| 174 | 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" )); |
| 175 | ClassDB::bind_method(D_METHOD("sign" , "hash_type" , "hash" , "key" ), &Crypto::sign); |
| 176 | ClassDB::bind_method(D_METHOD("verify" , "hash_type" , "hash" , "signature" , "key" ), &Crypto::verify); |
| 177 | ClassDB::bind_method(D_METHOD("encrypt" , "key" , "plaintext" ), &Crypto::encrypt); |
| 178 | ClassDB::bind_method(D_METHOD("decrypt" , "key" , "ciphertext" ), &Crypto::decrypt); |
| 179 | ClassDB::bind_method(D_METHOD("hmac_digest" , "hash_type" , "key" , "msg" ), &Crypto::hmac_digest); |
| 180 | ClassDB::bind_method(D_METHOD("constant_time_compare" , "trusted" , "received" ), &Crypto::constant_time_compare); |
| 181 | } |
| 182 | |
| 183 | /// Resource loader/saver |
| 184 | |
| 185 | Ref<Resource> ResourceFormatLoaderCrypto::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { |
| 186 | String el = p_path.get_extension().to_lower(); |
| 187 | if (el == "crt" ) { |
| 188 | X509Certificate *cert = X509Certificate::create(); |
| 189 | if (cert) { |
| 190 | cert->load(p_path); |
| 191 | } |
| 192 | return cert; |
| 193 | } else if (el == "key" ) { |
| 194 | CryptoKey *key = CryptoKey::create(); |
| 195 | if (key) { |
| 196 | key->load(p_path, false); |
| 197 | } |
| 198 | return key; |
| 199 | } else if (el == "pub" ) { |
| 200 | CryptoKey *key = CryptoKey::create(); |
| 201 | if (key) { |
| 202 | key->load(p_path, true); |
| 203 | } |
| 204 | return key; |
| 205 | } |
| 206 | return nullptr; |
| 207 | } |
| 208 | |
| 209 | void ResourceFormatLoaderCrypto::get_recognized_extensions(List<String> *p_extensions) const { |
| 210 | p_extensions->push_back("crt" ); |
| 211 | p_extensions->push_back("key" ); |
| 212 | p_extensions->push_back("pub" ); |
| 213 | } |
| 214 | |
| 215 | bool ResourceFormatLoaderCrypto::handles_type(const String &p_type) const { |
| 216 | return p_type == "X509Certificate" || p_type == "CryptoKey" ; |
| 217 | } |
| 218 | |
| 219 | String ResourceFormatLoaderCrypto::get_resource_type(const String &p_path) const { |
| 220 | String el = p_path.get_extension().to_lower(); |
| 221 | if (el == "crt" ) { |
| 222 | return "X509Certificate" ; |
| 223 | } else if (el == "key" || el == "pub" ) { |
| 224 | return "CryptoKey" ; |
| 225 | } |
| 226 | return "" ; |
| 227 | } |
| 228 | |
| 229 | Error ResourceFormatSaverCrypto::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) { |
| 230 | Error err; |
| 231 | Ref<X509Certificate> cert = p_resource; |
| 232 | Ref<CryptoKey> key = p_resource; |
| 233 | if (cert.is_valid()) { |
| 234 | err = cert->save(p_path); |
| 235 | } else if (key.is_valid()) { |
| 236 | String el = p_path.get_extension().to_lower(); |
| 237 | err = key->save(p_path, el == "pub" ); |
| 238 | } else { |
| 239 | ERR_FAIL_V(ERR_INVALID_PARAMETER); |
| 240 | } |
| 241 | ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save Crypto resource to file '" + p_path + "'." ); |
| 242 | return OK; |
| 243 | } |
| 244 | |
| 245 | void ResourceFormatSaverCrypto::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const { |
| 246 | const X509Certificate *cert = Object::cast_to<X509Certificate>(*p_resource); |
| 247 | const CryptoKey *key = Object::cast_to<CryptoKey>(*p_resource); |
| 248 | if (cert) { |
| 249 | p_extensions->push_back("crt" ); |
| 250 | } |
| 251 | if (key) { |
| 252 | if (!key->is_public_only()) { |
| 253 | p_extensions->push_back("key" ); |
| 254 | } |
| 255 | p_extensions->push_back("pub" ); |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | bool ResourceFormatSaverCrypto::recognize(const Ref<Resource> &p_resource) const { |
| 260 | return Object::cast_to<X509Certificate>(*p_resource) || Object::cast_to<CryptoKey>(*p_resource); |
| 261 | } |
| 262 | |