• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / github_pull_request_312964

19 Feb 2025 07:31PM UTC coverage: 90.814% (-0.3%) from 91.119%
github_pull_request_312964

Pull #8071

Evergreen

web-flow
Bump serialize-javascript and mocha

Bumps [serialize-javascript](https://github.com/yahoo/serialize-javascript) to 6.0.2 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together.


Updates `serialize-javascript` from 6.0.0 to 6.0.2
- [Release notes](https://github.com/yahoo/serialize-javascript/releases)
- [Commits](https://github.com/yahoo/serialize-javascript/compare/v6.0.0...v6.0.2)

Updates `mocha` from 10.2.0 to 10.8.2
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v10.2.0...v10.8.2)

---
updated-dependencies:
- dependency-name: serialize-javascript
  dependency-type: indirect
- dependency-name: mocha
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #8071: Bump serialize-javascript and mocha

96552 of 179126 branches covered (53.9%)

212672 of 234185 relevant lines covered (90.81%)

3115802.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.46
/src/realm/sync/network/network_ssl.cpp
1
#include <cstring>
2
#include <mutex>
3

4
#include <realm/string_data.hpp>
5
#include <realm/util/cf_str.hpp>
6
#include <realm/util/features.h>
7
#include <realm/sync/network/network_ssl.hpp>
8

9
#if REALM_HAVE_OPENSSL
10
#include <openssl/conf.h>
11
#include <openssl/x509v3.h>
12
#ifdef _WIN32
13
using osslX509_NAME = X509_NAME; // alias this before including wincrypt.h because it gets clobbered
14
#include <Windows.h>
15
#include <wincrypt.h>
16
#else
17
#include <pthread.h>
18
#endif
19
#elif REALM_HAVE_SECURE_TRANSPORT
20
#include <fstream>
21
#include <vector>
22
#endif
23

24
using namespace realm;
25
using namespace realm::util;
26
using namespace realm::sync::network;
27
using namespace realm::sync::network::ssl;
28

29

30
namespace {
31

32
#if REALM_INCLUDE_CERTS
33

34
const char* root_certs[] = {
35
#include <realm/sync/noinst/root_certs.hpp>
36
};
37

38
void populate_cert_store_with_included_certs(X509_STORE* store, std::error_code& ec)
39
{
2✔
40
    std::size_t num_certs = sizeof(root_certs) / sizeof(root_certs[0]);
2✔
41

42
    for (std::size_t i = 0; i < num_certs; ++i) {
312✔
43
        ERR_clear_error();
310✔
44
        BIO* bio = BIO_new_mem_buf(const_cast<char*>(root_certs[i]), -1);
310✔
45
        if (REALM_UNLIKELY(!bio)) {
310✔
46
            ec = std::error_code(int(ERR_get_error()), openssl_error_category);
×
47
            return;
×
48
        }
×
49

50
        ERR_clear_error();
310✔
51
        X509* cert = PEM_read_bio_X509_AUX(bio, nullptr, nullptr, nullptr);
310✔
52
        BIO_free(bio);
310✔
53
        if (REALM_UNLIKELY(!cert)) {
310✔
54
            ec = std::error_code(int(ERR_get_error()), openssl_error_category);
×
55
            return;
×
56
        }
×
57

58
        ERR_clear_error();
310✔
59
        int ret = X509_STORE_add_cert(store, cert);
310✔
60
        X509_free(cert);
310✔
61
        if (REALM_UNLIKELY(ret != 1)) {
310✔
62
            ec = std::error_code(int(ERR_get_error()), openssl_error_category);
×
63
        }
×
64
    }
310✔
65
}
2✔
66

67
#endif // REALM_INCLUDE_CERTS
68

69
#if REALM_HAVE_OPENSSL && _WIN32
70

71
/// Allow OpenSSL to look up certificates in the Windows Trusted Root Certification Authority list by implementing the
72
/// X509_LOOKUP interface.
73
class CapiLookup {
74
public:
75
    CapiLookup()
76
    {
77
        // Try to open the store in all of these locations sequentially. Many of them might not exist, and the
78
        // CERT_STORE_OPEN_EXISTING_FLAG flag
79
        // will cause CertOpenStore to return null in which case we just move on to the next.
80
        // The order is important - we go from the most likely to the least likely to optimize lookup.
81
        static std::initializer_list<DWORD> store_locations{CERT_SYSTEM_STORE_CURRENT_USER,
82
                                                            CERT_SYSTEM_STORE_LOCAL_MACHINE,
83
                                                            CERT_SYSTEM_STORE_CURRENT_SERVICE,
84
                                                            CERT_SYSTEM_STORE_SERVICES,
85
                                                            CERT_SYSTEM_STORE_USERS,
86
                                                            CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY,
87
                                                            CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY,
88
                                                            CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE};
89

90
        for (DWORD location : store_locations) {
91
            constexpr DWORD flags =
92
                CERT_STORE_READONLY_FLAG | CERT_STORE_SHARE_CONTEXT_FLAG | CERT_STORE_OPEN_EXISTING_FLAG;
93
            HCERTSTORE store = CertOpenStore(CERT_STORE_PROV_SYSTEM_W, 0, NULL, flags | location, L"ROOT");
94
            if (store)
95
                m_stores.push_back(store);
96
        }
97
    }
98

99
    ~CapiLookup()
100
    {
101
        for (auto store : m_stores) {
102
            CertCloseStore(store, 0);
103
        }
104
    }
105

106
    int get_by_subject(X509_LOOKUP* ctx, X509_LOOKUP_TYPE type, const osslX509_NAME* name, X509_OBJECT* ret)
107
    {
108
        if (type != X509_LU_X509)
109
            return 0;
110

111
        // Convert the OpenSSL X509_NAME structure into its ASN.1 representation and construct a CAPI search parameter
112
        CERT_NAME_BLOB capi_name = {0};
113
        capi_name.cbData = i2d_X509_NAME(name, &capi_name.pbData);
114
        int result = 0;
115

116
        for (auto store : m_stores) {
117
            PCCERT_CONTEXT cert =
118
                CertFindCertificateInStore(store, X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_NAME, &capi_name, NULL);
119
            if (!cert)
120
                continue;
121

122
            // Convert the ASN.1 representation of the CAPI certificate into an OpenSSL certificate and add it to the
123
            // OpenSSL store
124
            const unsigned char* encoded_cert_data = cert->pbCertEncoded;
125
            X509* ossl_cert = d2i_X509(NULL, &encoded_cert_data, cert->cbCertEncoded);
126
            result = X509_STORE_add_cert(X509_LOOKUP_get_store(ctx), ossl_cert);
127
            X509_free(ossl_cert);
128
            break;
129
        }
130

131
        OPENSSL_free(capi_name.pbData);
132

133
        // if we previously added a certificate to the store we need to look it up again from the store and return
134
        // that
135
        if (result)
136
            result = get_cached_object(ctx, type, name, ret);
137
        return result;
138
    }
139

140
private:
141
    int get_cached_object(X509_LOOKUP* ctx, X509_LOOKUP_TYPE type, const osslX509_NAME* name, X509_OBJECT* ret)
142
    {
143
        REALM_ASSERT_RELEASE(type == X509_LU_X509);
144

145
        // Loop through the objects already in the store to find the one we just added in get_by_subject.
146
        // retrieve_by_subject returns a cert with refcount 1 but set1_X509 increases it.
147
        // That's why we need to free it after before returning, otherwise it will leak.
148

149
        STACK_OF(X509_OBJECT)* objects = X509_STORE_get0_objects(X509_LOOKUP_get_store(ctx));
150
        X509_OBJECT* tmp = X509_OBJECT_retrieve_by_subject(objects, type, name);
151
        if (!tmp)
152
            return 0;
153

154
        X509* cert = X509_OBJECT_get0_X509(tmp);
155
        int result = X509_OBJECT_set1_X509(ret, cert);
156
        X509_free(cert);
157

158
        return result;
159
    }
160

161
    std::vector<HCERTSTORE> m_stores;
162
};
163

164
void add_windows_certificate_store_lookup(X509_STORE* store)
165
{
166
    X509_LOOKUP_METHOD* capi_lookup = X509_LOOKUP_meth_new("capi");
167

168
    X509_LOOKUP_meth_set_new_item(capi_lookup, [](X509_LOOKUP* ctx) {
169
        auto* data = new CapiLookup();
170
        return X509_LOOKUP_set_method_data(ctx, data);
171
    });
172
    X509_LOOKUP_meth_set_free(capi_lookup, [](X509_LOOKUP* ctx) {
173
        auto* data = reinterpret_cast<CapiLookup*>(X509_LOOKUP_get_method_data(ctx));
174
        delete data;
175
    });
176
    X509_LOOKUP_meth_set_get_by_subject(capi_lookup, [](auto ctx, auto type, auto name, auto ret) {
177
        auto* data = reinterpret_cast<CapiLookup*>(X509_LOOKUP_get_method_data(ctx));
178
        return data->get_by_subject(ctx, type, name, ret);
179
    });
180

181
    X509_STORE_add_lookup(store, capi_lookup);
182
}
183

184
#endif // REALM_HAVE_OPENSSL && _WIN32
185

186
#if REALM_HAVE_OPENSSL && (OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER))
187

188
// These must be made to execute before main() is called, i.e., before there is
189
// any chance of threads being spawned.
190
struct OpensslInit {
191
    std::unique_ptr<std::mutex[]> mutexes;
192
    OpensslInit();
193
    ~OpensslInit();
194
};
195

196
OpensslInit g_openssl_init;
197

198

199
void openssl_locking_func(int mode, int i, const char*, int)
200
{
201
    if (mode & CRYPTO_LOCK) {
202
        g_openssl_init.mutexes[i].lock();
203
    }
204
    else {
205
        g_openssl_init.mutexes[i].unlock();
206
    }
207
}
208

209

210
OpensslInit::OpensslInit()
211
{
212
    SSL_library_init();
213
    SSL_load_error_strings();
214
    OpenSSL_add_all_algorithms();
215
    std::size_t n = CRYPTO_num_locks();
216
    mutexes.reset(new std::mutex[n]); // Throws
217
    CRYPTO_set_locking_callback(&openssl_locking_func);
218
    /*
219
    #if !defined(SSL_OP_NO_COMPRESSION) && (OPENSSL_VERSION_NUMBER >= 0x00908000L)
220
        null_compression_methods_ = sk_SSL_COMP_new_null();
221
    #endif
222
    */
223
}
224

225

226
OpensslInit::~OpensslInit()
227
{
228
    /*
229
    #if !defined(SSL_OP_NO_COMPRESSION) && (OPENSSL_VERSION_NUMBER >= 0x00908000L)
230
        sk_SSL_COMP_free(null_compression_methods_);
231
    #endif
232
    */
233
    CRYPTO_set_locking_callback(0);
234
    ERR_free_strings();
235
#if OPENSSL_VERSION_NUMBER < 0x10000000L
236
    ERR_remove_state(0);
237
#else
238
    ERR_remove_thread_state(0);
239
#endif
240
    EVP_cleanup();
241
    CRYPTO_cleanup_all_ex_data();
242
    CONF_modules_unload(1);
243
}
244

245
#endif // REALM_HAVE_OPENSSL && (OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER))
246

247
} // unnamed namespace
248

249

250
namespace realm {
251
namespace sync {
252
namespace network {
253
namespace ssl {
254

255
ErrorCategory error_category;
256

257

258
const char* ErrorCategory::name() const noexcept
259
{
×
260
    return "realm.sync.network.ssl";
×
261
}
×
262

263

264
std::string ErrorCategory::message(int value) const
265
{
×
266
    switch (Errors(value)) {
×
267
        case Errors::tls_handshake_failed:
×
268
            return "SSL certificate rejected"; // Throws
×
269
    }
×
270
    REALM_ASSERT(false);
×
271
    return {};
×
272
}
×
273

274

275
bool ErrorCategory::equivalent(const std::error_code& ec, int condition) const noexcept
276
{
6✔
277
    switch (Errors(condition)) {
6✔
278
        case Errors::tls_handshake_failed:
6✔
279
#if REALM_HAVE_OPENSSL
6✔
280
            return ec.category() == openssl_error_category;
6✔
281
#elif REALM_HAVE_SECURE_TRANSPORT
282
            return ec.category() == secure_transport_error_category;
283
#else
284
            static_cast<void>(ec);
285
            return false;
286
#endif
287
    }
6✔
288
    return false;
×
289
}
6✔
290

291
} // namespace ssl
292

293

294
OpensslErrorCategory openssl_error_category;
295

296

297
const char* OpensslErrorCategory::name() const noexcept
298
{
6✔
299
    return "openssl";
6✔
300
}
6✔
301

302

303
std::string OpensslErrorCategory::message(int value) const
304
{
18✔
305
    const char* message = "Unknown error";
18✔
306
#if REALM_HAVE_OPENSSL
18✔
307
    if (const char* s = ERR_reason_error_string(value))
18✔
308
        message = s;
18✔
309
#endif
18✔
310
    return util::format("OpenSSL error: %1 (%2)", message, value); // Throws
18✔
311
}
18✔
312

313

314
SecureTransportErrorCategory secure_transport_error_category;
315

316

317
const char* SecureTransportErrorCategory::name() const noexcept
318
{
×
319
    return "securetransport";
×
320
}
×
321

322

323
std::string SecureTransportErrorCategory::message(int value) const
324
{
×
325
    const char* message = "Unknown error";
×
326
#if REALM_HAVE_SECURE_TRANSPORT
327
    std::unique_ptr<char[]> buffer;
328
    auto status = OSStatus(value);
329
    void* reserved = nullptr;
330
    if (auto cf_message = adoptCF(SecCopyErrorMessageString(status, reserved)))
331
        message = cfstring_to_cstring(cf_message.get(), buffer);
332
#endif // REALM_HAVE_SECURE_TRANSPORT
333

334
    return util::format("SecureTransport error: %1 (%2)", message, value); // Throws
×
335
}
×
336

337

338
namespace ssl {
339

340
const char* ProtocolNotSupported::what() const noexcept
341
{
×
342
    return "SSL/TLS protocol not supported";
×
343
}
×
344

345

346
std::error_code Stream::handshake(std::error_code& ec)
347
{
28✔
348
    REALM_ASSERT(!m_tcp_socket.m_read_oper || !m_tcp_socket.m_read_oper->in_use());
28!
349
    REALM_ASSERT(!m_tcp_socket.m_write_oper || !m_tcp_socket.m_write_oper->in_use());
28✔
350
    m_tcp_socket.m_desc.ensure_blocking_mode(); // Throws
28✔
351
    Want want = Want::nothing;
28✔
352
    ssl_handshake(ec, want);
28✔
353
    REALM_ASSERT(want == Want::nothing);
28✔
354
    return ec;
28✔
355
}
28✔
356

357

358
std::error_code Stream::shutdown(std::error_code& ec)
359
{
20✔
360
    REALM_ASSERT(!m_tcp_socket.m_write_oper || !m_tcp_socket.m_write_oper->in_use());
20✔
361
    m_tcp_socket.m_desc.ensure_blocking_mode(); // Throws
20✔
362
    Want want = Want::nothing;
20✔
363
    ssl_shutdown(ec, want);
20✔
364
    REALM_ASSERT(want == Want::nothing);
20✔
365
    return ec;
20✔
366
}
20✔
367

368

369
#if REALM_HAVE_OPENSSL
370

371
void Context::ssl_init()
372
{
108✔
373
    ERR_clear_error();
108✔
374

375
    // Despite the name, SSLv23_method isn't specific to SSLv2 and SSLv3.
376
    // It negotiates with the peer to pick the newest enabled protocol version.
377
    const SSL_METHOD* method = SSLv23_method();
108✔
378

379
    SSL_CTX* ssl_ctx = SSL_CTX_new(method);
108✔
380
    if (REALM_UNLIKELY(!ssl_ctx)) {
108✔
381
        std::error_code ec(int(ERR_get_error()), openssl_error_category);
×
382
        throw std::system_error(ec);
×
383
    }
×
384

385
    // Disable use of older protocol versions (SSLv2 and SSLv3).
386
    // Disable SSL compression by default, as compression is unavailable
387
    // with Apple's Secure Transport API.
388
    long options = 0;
108✔
389
    options |= SSL_OP_NO_SSLv2;
108✔
390
    options |= SSL_OP_NO_SSLv3;
108✔
391
    options |= SSL_OP_NO_COMPRESSION;
108✔
392
    SSL_CTX_set_options(ssl_ctx, options);
108✔
393

394
    m_ssl_ctx = ssl_ctx;
108✔
395
}
108✔
396

397

398
void Context::ssl_destroy() noexcept
399
{
108✔
400
    /*
401
        if (handle_->default_passwd_callback_userdata) {
402
            detail::password_callback_base* callback =
403
       static_cast<detail::password_callback_base*>(handle_->default_passwd_callback_userdata); delete callback;
404
            handle_->default_passwd_callback_userdata = nullptr;
405
        }
406

407
        if (SSL_CTX_get_app_data(handle_)) {
408
            detail::verify_callback_base* callback =
409
       static_cast<detail::verify_callback_base*>(SSL_CTX_get_app_data(handle_)); delete callback;
410
            SSL_CTX_set_app_data(handle_, nullptr);
411
        }
412
    */
413
    SSL_CTX_free(m_ssl_ctx);
108✔
414
}
108✔
415

416

417
void Context::ssl_use_certificate_chain_file(const std::string& path, std::error_code& ec)
418
{
54✔
419
    ERR_clear_error();
54✔
420
    int ret = SSL_CTX_use_certificate_chain_file(m_ssl_ctx, path.c_str());
54✔
421
    if (REALM_UNLIKELY(ret != 1)) {
54✔
422
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
×
423
        return;
×
424
    }
×
425
    ec = std::error_code();
54✔
426
}
54✔
427

428

429
void Context::ssl_use_private_key_file(const std::string& path, std::error_code& ec)
430
{
54✔
431
    ERR_clear_error();
54✔
432
    int type = SSL_FILETYPE_PEM;
54✔
433
    int ret = SSL_CTX_use_PrivateKey_file(m_ssl_ctx, path.c_str(), type);
54✔
434
    if (REALM_UNLIKELY(ret != 1)) {
54✔
435
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
×
436
        return;
×
437
    }
×
438
    ec = std::error_code();
54✔
439
}
54✔
440

441

442
void Context::ssl_use_default_verify(std::error_code& ec)
443
{
2✔
444
#if REALM_USE_SYSTEM_OPENSSL_PATHS
445
    ERR_clear_error();
446
    int ret = SSL_CTX_set_default_verify_paths(m_ssl_ctx);
447
    if (ret != 1) {
448
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
449
        return;
450
    }
451
#endif
452
#ifdef _WIN32
453
    add_windows_certificate_store_lookup(SSL_CTX_get_cert_store(m_ssl_ctx));
454
#endif
455
    ec = std::error_code();
2✔
456
}
2✔
457

458

459
void Context::ssl_use_verify_file(const std::string& path, std::error_code& ec)
460
{
14✔
461
    ERR_clear_error();
14✔
462
    int ret = SSL_CTX_load_verify_locations(m_ssl_ctx, path.c_str(), nullptr);
14✔
463
    if (ret != 1) {
14✔
464
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
×
465
        return;
×
466
    }
×
467

468
    ec = std::error_code();
14✔
469
}
14✔
470

471
#if REALM_INCLUDE_CERTS
472
void Context::ssl_use_included_certificate_roots(std::error_code& ec)
473
{
2✔
474
    X509_STORE* store = SSL_CTX_get_cert_store(m_ssl_ctx);
2✔
475
    populate_cert_store_with_included_certs(store, ec);
2✔
476
}
2✔
477
#endif
478

479
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) && !defined(OPENSSL_IS_BORINGSSL)
480
class Stream::BioMethod {
481
public:
482
    BIO_METHOD* bio_method;
483

484
    BioMethod()
485
    {
6✔
486
        const char* name = "realm::util::Stream::BioMethod";
6✔
487
        bio_method = BIO_meth_new(BIO_get_new_index(), name);
6✔
488
        if (!bio_method)
6✔
489
            throw util::bad_alloc();
×
490

491
        BIO_meth_set_write(bio_method, &Stream::bio_write);
6✔
492
        BIO_meth_set_read(bio_method, &Stream::bio_read);
6✔
493
        BIO_meth_set_puts(bio_method, &Stream::bio_puts);
6✔
494
        BIO_meth_set_gets(bio_method, nullptr);
6✔
495
        BIO_meth_set_ctrl(bio_method, &Stream::bio_ctrl);
6✔
496
        BIO_meth_set_create(bio_method, &Stream::bio_create);
6✔
497
        BIO_meth_set_destroy(bio_method, &Stream::bio_destroy);
6✔
498
        BIO_meth_set_callback_ctrl(bio_method, nullptr);
6✔
499
    }
6✔
500

501
    ~BioMethod()
502
    {
×
503
        BIO_meth_free(bio_method);
×
504
    }
×
505
};
506
#else
507
class Stream::BioMethod {
508
public:
509
    BIO_METHOD* bio_method;
510

511
    BioMethod()
512
    {
513
        bio_method = new BIO_METHOD{
514
            BIO_TYPE_SOCKET,      // int type
515
            nullptr,              // const char* name
516
            &Stream::bio_write,   // int (*bwrite)(BIO*, const char*, int)
517
            &Stream::bio_read,    // int (*bread)(BIO*, char*, int)
518
            &Stream::bio_puts,    // int (*bputs)(BIO*, const char*)
519
            nullptr,              // int (*bgets)(BIO*, char*, int)
520
            &Stream::bio_ctrl,    // long (*ctrl)(BIO*, int, long, void*)
521
            &Stream::bio_create,  // int (*create)(BIO*)
522
            &Stream::bio_destroy, // int (*destroy)(BIO*)
523
            nullptr               // long (*callback_ctrl)(BIO*, int, bio_info_cb*)
524
        };
525
    }
526

527
    ~BioMethod()
528
    {
529
        delete bio_method;
530
    }
531
};
532
#endif
533

534

535
Stream::BioMethod Stream::s_bio_method;
536

537

538
#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER)
539

540
namespace {
541

542
// check_common_name() checks that \param  server_cert constains host_name
543
// as Common Name. The function is used by verify_callback() for
544
// OpenSSL versions before 1.0.2.
545
bool check_common_name(X509* server_cert, const std::string& host_name)
546
{
547
    // Find the position of the Common Name field in the Subject field of the certificate
548
    int common_name_loc = -1;
549
    common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name(server_cert), NID_commonName, -1);
550
    if (common_name_loc < 0)
551
        return false;
552

553
    // Extract the Common Name field
554
    X509_NAME_ENTRY* common_name_entry;
555
    common_name_entry = X509_NAME_get_entry(X509_get_subject_name(server_cert), common_name_loc);
556
    if (!common_name_entry)
557
        return false;
558

559
    // Convert the Common Namefield to a C string
560
    ASN1_STRING* common_name_asn1;
561
    common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
562
    if (!common_name_asn1)
563
        return false;
564

565
    char* common_name_str = reinterpret_cast<char*>(ASN1_STRING_data(common_name_asn1));
566

567
    // Make sure there isn't an embedded NUL character in the Common Name
568
    if (static_cast<std::size_t>(ASN1_STRING_length(common_name_asn1)) != std::strlen(common_name_str))
569
        return false;
570

571
    bool names_equal = (host_name == common_name_str);
572
    return names_equal;
573
}
574

575
// check_common_name() checks that \param  server_cert constains host_name
576
// in the Subject Alternative Name DNS section. The function is used by verify_callback()
577
// for OpenSSL versions before 1.0.2.
578
bool check_san(X509* server_cert, const std::string& host_name)
579
{
580
    STACK_OF(GENERAL_NAME) * san_names;
581

582
    // Try to extract the names within the SAN extension from the certificate
583
    san_names =
584
        static_cast<STACK_OF(GENERAL_NAME)*>(X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
585
    if (!san_names)
586
        return false;
587

588
    int san_names_nb = sk_GENERAL_NAME_num(san_names);
589

590
    bool found = false;
591

592
    // Check each name within the extension
593
    for (int i = 0; i < san_names_nb; ++i) {
594
        const GENERAL_NAME* current_name = sk_GENERAL_NAME_value(san_names, i);
595

596
        if (current_name->type == GEN_DNS) {
597
            // Current name is a DNS name
598
            char* dns_name = static_cast<char*>(ASN1_STRING_data(current_name->d.dNSName));
599

600
            // Make sure there isn't an embedded NUL character in the DNS name
601
            if (static_cast<std::size_t>(ASN1_STRING_length(current_name->d.dNSName)) != std::strlen(dns_name))
602
                break;
603

604
            if (host_name == dns_name) {
605
                found = true;
606
                break;
607
            }
608
        }
609
    }
610

611
    sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
612

613
    return found;
614
}
615

616
} // namespace
617

618
int Stream::verify_callback_using_hostname(int preverify_ok, X509_STORE_CTX* ctx) noexcept
619
{
620
    if (preverify_ok != 1)
621
        return preverify_ok;
622

623
    X509* server_cert = X509_STORE_CTX_get_current_cert(ctx);
624

625
    int err = X509_STORE_CTX_get_error(ctx);
626
    if (err != X509_V_OK)
627
        return 0;
628

629
    int depth = X509_STORE_CTX_get_error_depth(ctx);
630

631
    // We only inspect the certificate at depth = 0.
632
    if (depth > 0)
633
        return preverify_ok;
634

635
    // Retrieve the pointer to the SSL object for this connection.
636
    SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
637

638
    // The stream object is stored as data in the SSL object.
639
    Stream* stream = static_cast<Stream*>(SSL_get_ex_data(ssl, 0));
640

641
    const std::string& host_name = stream->m_host_name;
642

643
    if (check_common_name(server_cert, host_name))
644
        return 1;
645

646
    if (check_san(server_cert, host_name))
647
        return 1;
648

649
    return 0;
650
}
651

652
#endif
653

654

655
void Stream::ssl_set_verify_mode(VerifyMode mode, std::error_code& ec)
656
{
22✔
657
    int mode_2 = 0;
22✔
658
    switch (mode) {
22✔
659
        case VerifyMode::none:
✔
660
            break;
×
661
        case VerifyMode::peer:
22✔
662
            mode_2 = SSL_VERIFY_PEER;
22✔
663
            break;
22✔
664
    }
22✔
665

666
    int rc = SSL_set_ex_data(m_ssl, 0, this);
22✔
667
    if (rc == 0) {
22✔
668
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
×
669
        return;
×
670
    }
×
671

672
#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER)
673
    SSL_set_verify(m_ssl, mode_2, &Stream::verify_callback_using_hostname);
674
#else
675
    // verify_callback is nullptr.
676
    SSL_set_verify(m_ssl, mode_2, nullptr);
22✔
677
#endif
22✔
678
    ec = std::error_code();
22✔
679
}
22✔
680

681

682
void Stream::ssl_set_host_name(const std::string& host_name, std::error_code& ec)
683
{
24✔
684
    // Enable Server Name Indication (SNI) extension
685
#if OPENSSL_VERSION_NUMBER >= 0x10101000L
24✔
686
    {
24✔
687
#ifndef _WIN32
24✔
688
#pragma GCC diagnostic push
24✔
689
#pragma GCC diagnostic ignored "-Wold-style-cast"
24✔
690
#endif
24✔
691
        auto ret = SSL_set_tlsext_host_name(m_ssl, host_name.c_str());
24✔
692
#ifndef _WIN32
24✔
693
#pragma GCC diagnostic pop
24✔
694
#endif
24✔
695
        if (ret == 0) {
24✔
696
            ec = std::error_code(int(ERR_get_error()), openssl_error_category);
×
697
            return;
×
698
        }
×
699
    }
24✔
700
#else
701
    static_cast<void>(host_name);
702
    static_cast<void>(ec);
703
#endif
704

705
    // Enable host name check during certificate validation
706
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)
24✔
707
    {
24✔
708
        X509_VERIFY_PARAM* param = SSL_get0_param(m_ssl);
24✔
709
        X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
24✔
710
        auto ret = X509_VERIFY_PARAM_set1_host(param, host_name.c_str(), host_name.size());
24✔
711
        if (ret == 0) {
24✔
712
            int sys_error = int(ERR_get_error());
×
713
            // BoringSSL can return 0 here without actually pushing on the error stack
714
            REALM_ASSERT_DEBUG(sys_error != 0);
×
715
            ec = std::error_code(sys_error, openssl_error_category);
×
716
            return;
×
717
        }
×
718
    }
24✔
719
#else
720
    static_cast<void>(host_name);
721
    static_cast<void>(ec);
722
#endif
723
}
24✔
724

725
void Stream::ssl_use_verify_callback(const std::function<SSLVerifyCallback>& callback, std::error_code&)
726
{
6✔
727
    m_ssl_verify_callback = &callback;
6✔
728

729
    SSL_set_verify(m_ssl, SSL_VERIFY_PEER, &Stream::verify_callback_using_delegate);
6✔
730
}
6✔
731

732
#ifndef _WIN32
733
#pragma GCC diagnostic push
734
#pragma GCC diagnostic ignored "-Wold-style-cast"
735
#endif
736

737
int Stream::verify_callback_using_delegate(int preverify_ok, X509_STORE_CTX* ctx) noexcept
738
{
10✔
739
    X509* server_cert = X509_STORE_CTX_get_current_cert(ctx);
10✔
740

741
    int depth = X509_STORE_CTX_get_error_depth(ctx);
10✔
742

743
    BIO* bio = BIO_new(BIO_s_mem());
10✔
744
    if (!bio) {
10✔
745
        // certificate rejected if a memory error occurs.
746
        return 0;
×
747
    }
×
748

749
    int ret = PEM_write_bio_X509(bio, server_cert);
10✔
750
    if (!ret) {
10✔
751
        BIO_free(bio);
×
752
        return 0;
×
753
    }
×
754

755
    BUF_MEM* buffer;
10✔
756
    BIO_get_mem_ptr(bio, &buffer);
10✔
757

758
    const char* pem_data = buffer->data;
10✔
759
    std::size_t pem_size = buffer->length;
10✔
760

761
    SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
10✔
762
    Stream* stream = static_cast<Stream*>(SSL_get_ex_data(ssl, 0));
10✔
763
    const std::string& host_name = stream->m_host_name;
10✔
764
    port_type server_port = stream->m_server_port;
10✔
765

766
    REALM_ASSERT(stream->m_ssl_verify_callback);
10✔
767
    const std::function<SSLVerifyCallback>& callback = *stream->m_ssl_verify_callback;
10✔
768

769
    // FIXME: Oops, the callback may throw, but verify_callback_using_delegate()
770
    // is not allowed to throw. It does not seem to be reasonable to deny the
771
    // callback the opportunity of throwing. The right solution seems to be to
772
    // carry an exception across the OpenSSL C-layer using the exception object
773
    // transportation mechanism offered by C++.
774
    bool valid = callback(host_name, server_port, pem_data, pem_size, preverify_ok, depth); // Throws
10✔
775

776
    BIO_free(bio);
10✔
777
    return int(valid);
10✔
778
}
10✔
779

780
#ifndef _WIN32
781
#pragma GCC diagnostic pop
782
#endif
783

784
void Stream::ssl_init()
785
{
122✔
786
    SSL_CTX* ssl_ctx = m_ssl_context.m_ssl_ctx;
122✔
787
    SSL* ssl = SSL_new(ssl_ctx);
122✔
788
    if (REALM_UNLIKELY(!ssl)) {
122✔
789
        std::error_code ec(int(ERR_get_error()), openssl_error_category);
×
790
        throw std::system_error(ec);
×
791
    }
×
792
    SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
122✔
793
#if defined(SSL_MODE_RELEASE_BUFFERS)
122✔
794
    SSL_set_mode(ssl, SSL_MODE_RELEASE_BUFFERS);
122✔
795
#endif
122✔
796

797
    BIO* bio = BIO_new(s_bio_method.bio_method);
122✔
798

799
    if (REALM_UNLIKELY(!bio)) {
122✔
800
        SSL_free(ssl);
×
801
        std::error_code ec(int(ERR_get_error()), openssl_error_category);
×
802
        throw std::system_error(ec);
×
803
    }
×
804

805
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
122✔
806
    BIO_set_data(bio, this);
122✔
807
#else
808
    bio->ptr = this;
809
#endif
810

811
    SSL_set_bio(ssl, bio, bio);
122✔
812
    m_ssl = ssl;
122✔
813
}
122✔
814

815

816
void Stream::ssl_destroy() noexcept
817
{
122✔
818
    SSL_free(m_ssl);
122✔
819
}
122✔
820

821

822
int Stream::bio_write(BIO* bio, const char* data, int size) noexcept
823
{
176,868✔
824
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
176,868✔
825
    Stream& stream = *static_cast<Stream*>(BIO_get_data(bio));
176,868✔
826
#else
827
    Stream& stream = *static_cast<Stream*>(bio->ptr);
828
#endif
829
    Service::Descriptor& desc = stream.m_tcp_socket.m_desc;
176,868✔
830
    std::error_code ec;
176,868✔
831
    std::size_t n = desc.write_some(data, std::size_t(size), ec);
176,868✔
832

833
    BIO_clear_retry_flags(bio);
176,868✔
834
    if (ec) {
176,868✔
835
        if (REALM_UNLIKELY(ec != error::resource_unavailable_try_again)) {
504✔
836
            stream.m_bio_error_code = ec;
6✔
837
            return -1;
6✔
838
        }
6✔
839
        BIO_set_retry_write(bio);
498✔
840
        return -1;
498✔
841
    }
504✔
842
    return int(n);
176,364✔
843
}
176,868✔
844

845

846
int Stream::bio_read(BIO* bio, char* buffer, int size) noexcept
847
{
352,328✔
848
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
352,328✔
849
    Stream& stream = *static_cast<Stream*>(BIO_get_data(bio));
352,328✔
850
#else
851
    Stream& stream = *static_cast<Stream*>(bio->ptr);
852
#endif
853
    Service::Descriptor& desc = stream.m_tcp_socket.m_desc;
352,328✔
854
    std::error_code ec;
352,328✔
855
    std::size_t n = desc.read_some(buffer, std::size_t(size), ec);
352,328✔
856

857
    BIO_clear_retry_flags(bio);
352,328✔
858
    if (ec) {
352,328✔
859
        if (REALM_UNLIKELY(ec == MiscExtErrors::end_of_input)) {
218✔
860
            // This behaviour agrees with `crypto/bio/bss_sock.c` of OpenSSL.
861
            return 0;
4✔
862
        }
4✔
863
        if (REALM_UNLIKELY(ec != error::resource_unavailable_try_again)) {
214✔
864
            stream.m_bio_error_code = ec;
×
865
            return -1;
×
866
        }
×
867
        BIO_set_retry_read(bio);
214✔
868
        return -1;
214✔
869
    }
214✔
870
    return int(n);
352,110✔
871
}
352,328✔
872

873

874
int Stream::bio_puts(BIO* bio, const char* c_str) noexcept
875
{
×
876
    std::size_t size = std::strlen(c_str);
×
877
    return bio_write(bio, c_str, int(size));
×
878
}
×
879

880

881
long Stream::bio_ctrl(BIO*, int cmd, long, void*) noexcept
882
{
536✔
883
    switch (cmd) {
536✔
884
        case BIO_CTRL_EOF:
4✔
885
            return 0;
4✔
886
        case BIO_CTRL_PUSH:
140✔
887
        case BIO_CTRL_POP:
280✔
888
            // Ignoring in alignment with `crypto/bio/bss_sock.c` of OpenSSL.
889
            return 0;
280✔
890
        case BIO_CTRL_FLUSH:
252✔
891
            // Ignoring in alignment with `crypto/bio/bss_sock.c` of OpenSSL.
892
            return 1;
252✔
893
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
×
894
        case BIO_CTRL_GET_KTLS_SEND:
✔
895
        case BIO_CTRL_GET_KTLS_RECV:
✔
896
            return 0;
×
897
#endif
×
898
        default:
✔
899
            REALM_ASSERT_EX(false, "Got BIO_ctrl with unknown command %d", cmd);
×
900
    }
536✔
901
    return 0;
×
902
}
536✔
903

904

905
int Stream::bio_create(BIO* bio) noexcept
906
{
122✔
907
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
122✔
908
    BIO_set_init(bio, 1);
122✔
909
    BIO_set_data(bio, nullptr);
122✔
910
    BIO_clear_flags(bio, 0);
122✔
911
    BIO_set_shutdown(bio, 0);
122✔
912
#else
913
    // In alignment with `crypto/bio/bss_sock.c` of OpenSSL.
914
    bio->init = 1;
915
    bio->num = 0;
916
    bio->ptr = nullptr;
917
    bio->flags = 0;
918
#endif
919
    return 1;
122✔
920
}
122✔
921

922

923
int Stream::bio_destroy(BIO*) noexcept
924
{
122✔
925
    return 1;
122✔
926
}
122✔
927

928

929
#elif REALM_HAVE_SECURE_TRANSPORT
930

931
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // FIXME: Should this be removed at some point?
932

933
void Context::ssl_init() {}
934

935
void Context::ssl_destroy() noexcept
936
{
937
#if REALM_HAVE_KEYCHAIN_APIS
938
    if (m_keychain) {
939
        m_keychain.reset();
940
        unlink(m_keychain_path.data());
941
        m_keychain_path = {};
942
    }
943
#endif
944
}
945

946
// Load certificates and/or keys from the specified PEM file. If keychain is non-null, the items will be
947
// imported into that keychain.
948
util::CFPtr<CFArrayRef> Context::load_pem_file(const std::string& path, SecKeychainRef keychain, std::error_code& ec)
949
{
950
    using util::adoptCF;
951
    using util::CFPtr;
952

953
    std::ifstream file(path);
954
    if (!file) {
955
        // Rely on the open attempt having set errno to a sensible value as ifstream's
956
        // own error reporting gives terribly generic error messages.
957
        ec = make_basic_system_error_code(errno);
958
        return util::CFPtr<CFArrayRef>();
959
    }
960
    std::vector<char> contents{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
961

962
    auto contentsCF = adoptCF(CFDataCreateWithBytesNoCopy(nullptr, reinterpret_cast<const UInt8*>(contents.data()),
963
                                                          contents.size(), kCFAllocatorNull));
964

965
    // If we don't need to import it into a keychain, try to interpret the data
966
    // as a certificate directly. This only works for DER files, so we fall back
967
    // to SecItemImport() on platforms which support that if this fails.
968
    if (keychain == nullptr) {
969
        if (auto certificate = adoptCF(SecCertificateCreateWithData(NULL, contentsCF.get()))) {
970
            auto ref = certificate.get();
971
            return adoptCF(CFArrayCreate(nullptr, const_cast<const void**>(reinterpret_cast<void**>(&ref)), 1,
972
                                         &kCFTypeArrayCallBacks));
973
        }
974

975
        // SecCertificateCreateWithData doesn't tell us why it failed, so just
976
        // report the error code that SecItemImport uses when given something
977
        // that's not a certificate
978
        ec = std::error_code(errSecUnknownFormat, secure_transport_error_category);
979
    }
980

981
    CFArrayRef items = nullptr;
982

983
#if REALM_HAVE_KEYCHAIN_APIS
984
    SecItemImportExportKeyParameters params{};
985
    params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
986

987
    CFPtr<CFStringRef> pathCF = adoptCF(CFStringCreateWithBytes(nullptr, reinterpret_cast<const UInt8*>(path.data()),
988
                                                                path.size(), kCFStringEncodingUTF8, false));
989

990
    SecExternalFormat format = kSecFormatUnknown;
991
    SecExternalItemType itemType = kSecItemTypeUnknown;
992
    if (OSStatus status =
993
            SecItemImport(contentsCF.get(), pathCF.get(), &format, &itemType, 0, &params, keychain, &items)) {
994
        ec = std::error_code(status, secure_transport_error_category);
995
        return util::CFPtr<CFArrayRef>();
996
    }
997
    ec = {};
998
#endif
999

1000
    return adoptCF(items);
1001
}
1002

1003
#if REALM_HAVE_KEYCHAIN_APIS
1004

1005
static std::string temporary_directory()
1006
{
1007
    auto ensure_trailing_slash = [](auto str) {
1008
        return str.back() == '/' ? str : str + '/';
1009
    };
1010

1011
    std::string path;
1012
    path.resize(PATH_MAX);
1013
    std::size_t result = confstr(_CS_DARWIN_USER_TEMP_DIR, &path[0], path.size());
1014
    if (result && result <= path.size()) {
1015
        path.resize(result - 1);
1016
        return ensure_trailing_slash(std::move(path));
1017
    }
1018

1019
    // We failed to retrieve temporary directory from confstr. Fall back to the TMPDIR
1020
    // environment variable if we're not running with elevated privileges, and then to /tmp.
1021
    if (!issetugid()) {
1022
        path = getenv("TMPDIR");
1023
        if (path.size()) {
1024
            return ensure_trailing_slash(std::move(path));
1025
        }
1026
    }
1027
    return "/tmp/";
1028
}
1029

1030

1031
std::error_code Context::open_temporary_keychain_if_needed()
1032
{
1033
    if (m_keychain) {
1034
        return std::error_code();
1035
    }
1036

1037
    std::string path = temporary_directory() + "realm-sync-ssl-XXXXXXXX.keychain";
1038
    int fd = mkstemps(&path[0], std::strlen(".keychain"));
1039
    if (fd < 0) {
1040
        return make_basic_system_error_code(errno);
1041
    }
1042

1043
    // Close and remove the file so that we can create a keychain in its place.
1044
    close(fd);
1045
    unlink(path.data());
1046

1047
    SecKeychainRef keychain = nullptr;
1048
    std::string password = "";
1049
    if (OSStatus status =
1050
            SecKeychainCreate(path.data(), UInt32(password.size()), password.data(), false, nullptr, &keychain))
1051
        return std::error_code(status, secure_transport_error_category);
1052

1053
    m_keychain = adoptCF(keychain);
1054
    m_keychain_path = std::move(path);
1055

1056
    return std::error_code();
1057
}
1058

1059

1060
// Creates an identity from the certificate and private key. The private key must exist in m_keychain.
1061
std::error_code Context::update_identity_if_needed()
1062
{
1063
    // If we've not yet loaded both the certificate and private key there's nothing to do.
1064
    if (!m_certificate || !m_private_key) {
1065
        return std::error_code();
1066
    }
1067

1068
    SecIdentityRef identity = nullptr;
1069
    if (OSStatus status = SecIdentityCreateWithCertificate(m_keychain.get(), m_certificate.get(), &identity)) {
1070
        return std::error_code(status, secure_transport_error_category);
1071
    }
1072

1073
    m_identity = util::adoptCF(identity);
1074
    return std::error_code();
1075
}
1076

1077
#endif // REALM_HAVE_KEYCHAIN_APIS
1078

1079
void Context::ssl_use_certificate_chain_file(const std::string& path, std::error_code& ec)
1080
{
1081
#if !REALM_HAVE_KEYCHAIN_APIS
1082
    static_cast<void>(path);
1083
    ec = make_basic_system_error_code(ENOTSUP);
1084
#else
1085
    auto items = load_pem_file(path, nullptr, ec);
1086
    if (!items) {
1087
        REALM_ASSERT(ec);
1088
        return;
1089
    }
1090

1091
    if (CFArrayGetCount(items.get()) < 1) {
1092
        ec = std::error_code(errSecItemNotFound, secure_transport_error_category);
1093
        return;
1094
    }
1095

1096
    CFTypeRef item = CFArrayGetValueAtIndex(items.get(), 0);
1097
    if (CFGetTypeID(item) != SecCertificateGetTypeID()) {
1098
        ec = std::error_code(errSecItemNotFound, secure_transport_error_category);
1099
        return;
1100
    }
1101

1102
    m_certificate = util::retainCF(reinterpret_cast<SecCertificateRef>(const_cast<void*>(item)));
1103

1104
    // The returned array contains the server certificate followed by the remainder of the certificates in the chain.
1105
    // Remove the server certificate to leave us with an array containing only the remainder of the certificate chain.
1106
    auto certificate_chain = util::adoptCF(CFArrayCreateMutableCopy(nullptr, 0, items.get()));
1107
    CFArrayRemoveValueAtIndex(certificate_chain.get(), 0);
1108
    m_certificate_chain = util::adoptCF(reinterpret_cast<CFArrayRef>(certificate_chain.release()));
1109

1110
    ec = update_identity_if_needed();
1111
#endif
1112
}
1113

1114

1115
void Context::ssl_use_private_key_file(const std::string& path, std::error_code& ec)
1116
{
1117
#if !REALM_HAVE_KEYCHAIN_APIS
1118
    static_cast<void>(path);
1119
    ec = make_basic_system_error_code(ENOTSUP);
1120
#else
1121
    ec = open_temporary_keychain_if_needed();
1122
    if (ec) {
1123
        return;
1124
    }
1125

1126
    auto items = load_pem_file(path, m_keychain.get(), ec);
1127
    if (!items) {
1128
        return;
1129
    }
1130

1131
    if (CFArrayGetCount(items.get()) != 1) {
1132
        ec = std::error_code(errSecItemNotFound, secure_transport_error_category);
1133
        return;
1134
    }
1135

1136
    CFTypeRef item = CFArrayGetValueAtIndex(items.get(), 0);
1137
    if (CFGetTypeID(item) != SecKeyGetTypeID()) {
1138
        ec = std::error_code(errSecItemNotFound, secure_transport_error_category);
1139
        return;
1140
    }
1141

1142
    m_private_key = util::retainCF(reinterpret_cast<SecKeyRef>(const_cast<void*>(item)));
1143
    ec = update_identity_if_needed();
1144
#endif
1145
}
1146

1147
void Context::ssl_use_default_verify(std::error_code&) {}
1148

1149
void Context::ssl_use_verify_file(const std::string& path, std::error_code& ec)
1150
{
1151
#if REALM_HAVE_KEYCHAIN_APIS
1152
    m_trust_anchors = load_pem_file(path, m_keychain.get(), ec);
1153
#else
1154
    m_trust_anchors = load_pem_file(path, nullptr, ec);
1155
#endif
1156

1157
    if (m_trust_anchors && CFArrayGetCount(m_trust_anchors.get())) {
1158
        const void* leaf_certificate = CFArrayGetValueAtIndex(m_trust_anchors.get(), 0);
1159
        m_pinned_certificate =
1160
            adoptCF(SecCertificateCopyData(static_cast<SecCertificateRef>(const_cast<void*>(leaf_certificate))));
1161
    }
1162
    else {
1163
        m_pinned_certificate.reset();
1164
    }
1165
}
1166

1167
void Stream::ssl_init()
1168
{
1169
    SSLProtocolSide side = m_handshake_type == HandshakeType::client ? kSSLClientSide : kSSLServerSide;
1170
    m_ssl = util::adoptCF(SSLCreateContext(nullptr, side, kSSLStreamType));
1171
    if (OSStatus status = SSLSetIOFuncs(m_ssl.get(), Stream::tcp_read, Stream::tcp_write)) {
1172
        std::error_code ec(status, secure_transport_error_category);
1173
        throw std::system_error(ec);
1174
    }
1175
    if (OSStatus status = SSLSetConnection(m_ssl.get(), this)) {
1176
        std::error_code ec(status, secure_transport_error_category);
1177
        throw std::system_error(ec);
1178
    }
1179

1180
    // Require TLSv1 or greater.
1181
    if (OSStatus status = SSLSetProtocolVersionMin(m_ssl.get(), kTLSProtocol1)) {
1182
        std::error_code ec(status, secure_transport_error_category);
1183
        throw std::system_error(ec);
1184
    }
1185

1186
    // Break after certificate exchange to allow for customizing the verification process.
1187
    SSLSessionOption option = m_handshake_type == HandshakeType::client ? kSSLSessionOptionBreakOnServerAuth
1188
                                                                        : kSSLSessionOptionBreakOnClientAuth;
1189
    if (OSStatus status = SSLSetSessionOption(m_ssl.get(), option, true)) {
1190
        std::error_code ec(status, secure_transport_error_category);
1191
        throw std::system_error(ec);
1192
    }
1193

1194
#if REALM_HAVE_KEYCHAIN_APIS
1195
    if (m_ssl_context.m_identity && m_ssl_context.m_certificate_chain) {
1196
        // SSLSetCertificate expects an array containing the identity followed by the identity's certificate chain.
1197
        auto certificates = util::adoptCF(CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks));
1198
        CFArrayInsertValueAtIndex(certificates.get(), 0, m_ssl_context.m_identity.get());
1199

1200
        CFArrayRef certificate_chain = m_ssl_context.m_certificate_chain.get();
1201
        CFArrayAppendArray(certificates.get(), certificate_chain, CFRangeMake(0, CFArrayGetCount(certificate_chain)));
1202

1203
        if (OSStatus status = SSLSetCertificate(m_ssl.get(), certificates.get())) {
1204
            std::error_code ec(status, secure_transport_error_category);
1205
            throw std::system_error(ec);
1206
        }
1207
    }
1208
#endif
1209
}
1210

1211

1212
void Stream::ssl_destroy() noexcept
1213
{
1214
    m_ssl.reset();
1215
}
1216

1217

1218
void Stream::ssl_set_verify_mode(VerifyMode verify_mode, std::error_code& ec)
1219
{
1220
    m_verify_mode = verify_mode;
1221
    ec = std::error_code();
1222
}
1223

1224

1225
void Stream::ssl_set_host_name(const std::string& host_name, std::error_code& ec)
1226
{
1227
    if (OSStatus status = SSLSetPeerDomainName(m_ssl.get(), host_name.data(), host_name.size()))
1228
        ec = std::error_code(status, secure_transport_error_category);
1229
}
1230

1231
void Stream::ssl_use_verify_callback(const std::function<SSLVerifyCallback>&, std::error_code&) {}
1232

1233
void Stream::ssl_handshake(std::error_code& ec, Want& want) noexcept
1234
{
1235
    auto perform = [this]() noexcept {
1236
        return do_ssl_handshake();
1237
    };
1238
    ssl_perform(std::move(perform), ec, want);
1239
}
1240

1241
std::pair<OSStatus, std::size_t> Stream::do_ssl_handshake() noexcept
1242
{
1243
    OSStatus result = SSLHandshake(m_ssl.get());
1244
    if (result != errSSLPeerAuthCompleted) {
1245
        return {result, 0};
1246
    }
1247

1248
    if (OSStatus status = verify_peer()) {
1249
        // When performing peer verification internally, verification failure results in SecureTransport
1250
        // sending a fatal alert to the peer, closing the connection. Sadly SecureTransport has no way
1251
        // to explicitly send a fatal alert when trust evaluation is handled externally. The best we can
1252
        // do is close the connection gracefully.
1253
        SSLClose(m_ssl.get());
1254
        return {status, 0};
1255
    }
1256

1257
    // Verification succeeded. Resume the handshake.
1258
    return do_ssl_handshake();
1259
}
1260

1261

1262
OSStatus Stream::verify_peer() noexcept
1263
{
1264
    switch (m_verify_mode) {
1265
        case VerifyMode::none:
1266
            // Peer verification is disabled.
1267
            return noErr;
1268

1269
        case VerifyMode::peer: {
1270
            SecTrustRef peerTrustRef = nullptr;
1271
            if (OSStatus status = SSLCopyPeerTrust(m_ssl.get(), &peerTrustRef)) {
1272
                return status;
1273
            }
1274

1275
            auto peerTrust = util::adoptCF(peerTrustRef);
1276

1277
            if (m_ssl_context.m_trust_anchors) {
1278
                if (OSStatus status =
1279
                        SecTrustSetAnchorCertificates(peerTrust.get(), m_ssl_context.m_trust_anchors.get())) {
1280
                    return status;
1281
                }
1282
                if (OSStatus status = SecTrustSetAnchorCertificatesOnly(peerTrust.get(), true)) {
1283
                    return status;
1284
                }
1285
            }
1286

1287
            // FIXME: SecTrustEvaluate can block if evaluation needs to fetch missing intermediate
1288
            // certificates or to check revocation using OCSP. Consider disabling these network
1289
            // fetches or doing async trust evaluation instead.
1290
#if __has_builtin(__builtin_available)
1291
            if (__builtin_available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *)) {
1292
                CFErrorRef cfErrorRef;
1293
                if (!SecTrustEvaluateWithError(peerTrust.get(), &cfErrorRef)) {
1294
                    auto cfError = util::adoptCF(cfErrorRef);
1295
                    if (logger && logger->would_log(Logger::Level::debug)) {
1296
                        auto errorStr = util::adoptCF(CFErrorCopyDescription(cfErrorRef));
1297
                        std::unique_ptr<char[]> buffer;
1298
                        logger->debug("SSL peer verification failed: %1",
1299
                                      cfstring_to_cstring(errorStr.get(), buffer));
1300
                    }
1301
                    return errSSLXCertChainInvalid;
1302
                }
1303
            }
1304
            else
1305
#endif
1306
            {
1307
                SecTrustResultType trustResult;
1308
                if (OSStatus status = SecTrustEvaluate(peerTrust.get(), &trustResult)) {
1309
                    return status;
1310
                }
1311

1312
                // A "proceed" result means the cert is explicitly trusted, e.g. "Always Trust" was selected.
1313
                // "Unspecified" means the cert has no explicit trust settings, but is implicitly OK since it
1314
                // chains back to a trusted root. Any other result means the cert is not trusted.
1315
                if (trustResult == kSecTrustResultRecoverableTrustFailure) {
1316
                    // Not trusted.
1317
                    return errSSLXCertChainInvalid;
1318
                }
1319
                if (trustResult != kSecTrustResultProceed && trustResult != kSecTrustResultUnspecified) {
1320
                    return errSSLBadCert;
1321
                }
1322
            }
1323

1324
            if (!m_ssl_context.m_pinned_certificate) {
1325
                // Certificate is trusted!
1326
                return noErr;
1327
            }
1328

1329
            // Verify that the certificate is one of our pinned certificates
1330
            // Loop backwards as the pinned certificate will normally be the last one
1331
            for (CFIndex i = SecTrustGetCertificateCount(peerTrust.get()); i > 0; --i) {
1332
                SecCertificateRef certificate = SecTrustGetCertificateAtIndex(peerTrust.get(), i - 1);
1333
                auto certificate_data = adoptCF(SecCertificateCopyData(certificate));
1334
                if (CFEqual(certificate_data.get(), m_ssl_context.m_pinned_certificate.get())) {
1335
                    return noErr;
1336
                }
1337
            }
1338

1339
            // Although the cerificate is valid, it's not the one we've pinned so reject it.
1340
            return errSSLXCertChainInvalid;
1341
        }
1342
    }
1343
}
1344

1345

1346
std::size_t Stream::ssl_read(char* buffer, std::size_t size, std::error_code& ec, Want& want) noexcept
1347
{
1348
    auto perform = [this, buffer, size]() noexcept {
1349
        return do_ssl_read(buffer, size);
1350
    };
1351
    std::size_t n = ssl_perform(std::move(perform), ec, want);
1352
    if (want == Want::nothing && n == 0 && !ec) {
1353
        // End of input on TCP socket
1354
        SSLSessionState state;
1355
        if (SSLGetSessionState(m_ssl.get(), &state) == noErr && state == kSSLClosed) {
1356
            ec = MiscExtErrors::end_of_input;
1357
        }
1358
        else {
1359
            ec = MiscExtErrors::premature_end_of_input;
1360
        }
1361
    }
1362
    return n;
1363
}
1364

1365
std::pair<OSStatus, std::size_t> Stream::do_ssl_read(char* buffer, std::size_t size) noexcept
1366
{
1367
    std::size_t processed = 0;
1368
    OSStatus result = SSLRead(m_ssl.get(), buffer, size, &processed);
1369
    return {result, processed};
1370
}
1371

1372

1373
std::size_t Stream::ssl_write(const char* data, std::size_t size, std::error_code& ec, Want& want) noexcept
1374
{
1375
    auto perform = [this, data, size]() noexcept {
1376
        return do_ssl_write(data, size);
1377
    };
1378
    std::size_t n = ssl_perform(std::move(perform), ec, want);
1379
    if (want == Want::nothing && n == 0 && !ec) {
1380
        // End of input on TCP socket
1381
        ec = MiscExtErrors::premature_end_of_input;
1382
    }
1383
    return n;
1384
}
1385

1386
std::pair<OSStatus, std::size_t> Stream::do_ssl_write(const char* data, std::size_t size) noexcept
1387
{
1388
    m_last_error = {};
1389

1390
    REALM_ASSERT(size >= m_num_partially_written_bytes);
1391
    data += m_num_partially_written_bytes;
1392
    size -= m_num_partially_written_bytes;
1393

1394
    std::size_t processed = 0;
1395
    OSStatus result = SSLWrite(m_ssl.get(), data, size, &processed);
1396

1397
    if (result != noErr) {
1398
        // Map errors that indicate the connection is closed to broken_pipe, for
1399
        // consistency with OpenSSL.
1400
        if (REALM_LIKELY(result == errSSLWouldBlock)) {
1401
            m_num_partially_written_bytes += processed;
1402
        }
1403
        else if (result == errSSLClosedGraceful || result == errSSLClosedAbort || result == errSSLClosedNoNotify) {
1404
            result = errSecIO;
1405
            m_last_error = error::broken_pipe;
1406
        }
1407
        processed = 0;
1408
    }
1409
    else {
1410
        processed += m_num_partially_written_bytes;
1411
        m_num_partially_written_bytes = 0;
1412
    }
1413

1414
    return {result, processed};
1415
}
1416

1417

1418
bool Stream::ssl_shutdown(std::error_code& ec, Want& want) noexcept
1419
{
1420
    auto perform = [this]() noexcept {
1421
        return do_ssl_shutdown();
1422
    };
1423
    std::size_t n = ssl_perform(std::move(perform), ec, want);
1424
    REALM_ASSERT(n == 0 || n == 1);
1425
    return (n > 0);
1426
}
1427

1428
std::pair<OSStatus, std::size_t> Stream::do_ssl_shutdown() noexcept
1429
{
1430
    SSLSessionState previousState;
1431
    if (OSStatus result = SSLGetSessionState(m_ssl.get(), &previousState)) {
1432
        return {result, false};
1433
    }
1434
    if (OSStatus result = SSLClose(m_ssl.get())) {
1435
        return {result, false};
1436
    }
1437

1438
    // SSLClose returns noErr if it encountered an I/O error. We can still
1439
    // detect such errors if they originated from our underlying tcp_read /
1440
    // tcp_write functions as we'll have set m_last_error in such cases. This
1441
    // allows us to reconstruct the I/O error and communicate it to our caller.
1442
    if (m_last_error) {
1443
        return {errSecIO, false};
1444
    }
1445
    return {noErr, previousState == kSSLClosed};
1446
}
1447

1448

1449
OSStatus Stream::tcp_read(SSLConnectionRef connection, void* data, std::size_t* length) noexcept
1450
{
1451
    return static_cast<Stream*>(const_cast<void*>(connection))->tcp_read(data, length);
1452
}
1453

1454
OSStatus Stream::tcp_read(void* data, std::size_t* length) noexcept
1455
{
1456
    Service::Descriptor& desc = m_tcp_socket.m_desc;
1457
    std::error_code ec;
1458
    std::size_t bytes_read = desc.read_some(reinterpret_cast<char*>(data), *length, ec);
1459

1460
    m_last_operation = BlockingOperation::read;
1461

1462
    // A successful but short read should be treated the same as EAGAIN.
1463
    if (!ec && bytes_read < *length) {
1464
        ec = error::resource_unavailable_try_again;
1465
    }
1466

1467
    *length = bytes_read;
1468
    m_last_error = ec;
1469

1470
    if (ec) {
1471
        if (REALM_UNLIKELY(ec == MiscExtErrors::end_of_input)) {
1472
            return noErr;
1473
        }
1474
        if (ec == error::resource_unavailable_try_again) {
1475
            return errSSLWouldBlock;
1476
        }
1477
        return errSecIO;
1478
    }
1479
    return noErr;
1480
}
1481

1482
OSStatus Stream::tcp_write(SSLConnectionRef connection, const void* data, std::size_t* length) noexcept
1483
{
1484
    return static_cast<Stream*>(const_cast<void*>(connection))->tcp_write(data, length);
1485
}
1486

1487
OSStatus Stream::tcp_write(const void* data, std::size_t* length) noexcept
1488
{
1489
    Service::Descriptor& desc = m_tcp_socket.m_desc;
1490
    std::error_code ec;
1491
    std::size_t bytes_written = desc.write_some(reinterpret_cast<const char*>(data), *length, ec);
1492

1493
    m_last_operation = BlockingOperation::write;
1494

1495
    // A successful but short write should be treated the same as EAGAIN.
1496
    if (!ec && bytes_written < *length) {
1497
        ec = error::resource_unavailable_try_again;
1498
    }
1499

1500
    *length = bytes_written;
1501
    m_last_error = ec;
1502

1503
    if (ec) {
1504
        if (ec == error::resource_unavailable_try_again) {
1505
            return errSSLWouldBlock;
1506
        }
1507
        return errSecIO;
1508
    }
1509
    return noErr;
1510
}
1511

1512

1513
#else // !REALM_HAVE_OPENSSL && !REALM_HAVE_SECURE_TRANSPORT
1514

1515

1516
void Context::ssl_init()
1517
{
1518
    throw ProtocolNotSupported();
1519
}
1520

1521

1522
void Context::ssl_destroy() noexcept {}
1523

1524

1525
void Stream::ssl_init() {}
1526

1527

1528
void Stream::ssl_destroy() noexcept {}
1529

1530

1531
void Context::ssl_use_certificate_chain_file(const std::string&, std::error_code&) {}
1532

1533

1534
void Context::ssl_use_private_key_file(const std::string&, std::error_code&) {}
1535

1536

1537
void Context::ssl_use_default_verify(std::error_code&) {}
1538

1539

1540
void Context::ssl_use_verify_file(const std::string&, std::error_code&) {}
1541

1542

1543
void Stream::ssl_set_verify_mode(VerifyMode, std::error_code&) {}
1544

1545

1546
void Stream::ssl_set_host_name(const std::string&, std::error_code&) {}
1547

1548

1549
void Stream::ssl_use_verify_callback(const std::function<SSLVerifyCallback>&, std::error_code&) {}
1550

1551

1552
void Stream::ssl_handshake(std::error_code&, Want&) noexcept {}
1553

1554

1555
std::size_t Stream::ssl_read(char*, std::size_t, std::error_code&, Want&) noexcept
1556
{
1557
    return 0;
1558
}
1559

1560

1561
std::size_t Stream::ssl_write(const char*, std::size_t, std::error_code&, Want&) noexcept
1562
{
1563
    return 0;
1564
}
1565

1566

1567
bool Stream::ssl_shutdown(std::error_code&, Want&) noexcept
1568
{
1569
    return false;
1570
}
1571

1572
#endif // ! REALM_HAVE_OPENSSL
1573

1574

1575
} // namespace ssl
1576
} // namespace network
1577
} // namespace sync
1578
} // namespace realm
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc