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 | |