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

realm / realm-core / 2214

10 Apr 2024 11:21PM UTC coverage: 91.813% (-0.8%) from 92.623%
2214

push

Evergreen

web-flow
Add missing availability checks for SecCopyErrorMessageString (#7577)

This requires iOS 11.3 and we currently target iOS 11.

94848 of 175770 branches covered (53.96%)

7 of 22 new or added lines in 2 files covered. (31.82%)

1815 existing lines in 77 files now uncovered.

242945 of 264608 relevant lines covered (91.81%)

6136478.37 hits per line

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

96.81
/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
#ifdef _WIN32
11
#include <Windows.h>
12
#else
13
#include <pthread.h>
14
#endif
15
#include <openssl/conf.h>
16
#include <openssl/x509v3.h>
17
#elif REALM_HAVE_SECURE_TRANSPORT
18
#include <fstream>
19
#include <vector>
20
#endif
21

22
using namespace realm;
23
using namespace realm::util;
24
using namespace realm::sync::network;
25
using namespace realm::sync::network::ssl;
26

27

28
namespace {
29

30
#if REALM_INCLUDE_CERTS
31

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

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

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

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

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

65
#endif // REALM_INCLUDE_CERTS
66

67

68
#if REALM_HAVE_OPENSSL && (OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER))
69

70
// These must be made to execute before main() is called, i.e., before there is
71
// any chance of threads being spawned.
72
struct OpensslInit {
73
    std::unique_ptr<std::mutex[]> mutexes;
74
    OpensslInit();
75
    ~OpensslInit();
76
};
77

78
OpensslInit g_openssl_init;
79

80

81
void openssl_locking_func(int mode, int i, const char*, int)
82
{
83
    if (mode & CRYPTO_LOCK) {
84
        g_openssl_init.mutexes[i].lock();
85
    }
86
    else {
87
        g_openssl_init.mutexes[i].unlock();
88
    }
89
}
90

91

92
OpensslInit::OpensslInit()
93
{
94
    SSL_library_init();
95
    SSL_load_error_strings();
96
    OpenSSL_add_all_algorithms();
97
    std::size_t n = CRYPTO_num_locks();
98
    mutexes.reset(new std::mutex[n]); // Throws
99
    CRYPTO_set_locking_callback(&openssl_locking_func);
100
    /*
101
    #if !defined(SSL_OP_NO_COMPRESSION) && (OPENSSL_VERSION_NUMBER >= 0x00908000L)
102
        null_compression_methods_ = sk_SSL_COMP_new_null();
103
    #endif
104
    */
105
}
106

107

108
OpensslInit::~OpensslInit()
109
{
110
    /*
111
    #if !defined(SSL_OP_NO_COMPRESSION) && (OPENSSL_VERSION_NUMBER >= 0x00908000L)
112
        sk_SSL_COMP_free(null_compression_methods_);
113
    #endif
114
    */
115
    CRYPTO_set_locking_callback(0);
116
    ERR_free_strings();
117
#if OPENSSL_VERSION_NUMBER < 0x10000000L
118
    ERR_remove_state(0);
119
#else
120
    ERR_remove_thread_state(0);
121
#endif
122
    EVP_cleanup();
123
    CRYPTO_cleanup_all_ex_data();
124
    CONF_modules_unload(1);
125
}
126

127
#endif // REALM_HAVE_OPENSSL && (OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER))
128

129
} // unnamed namespace
130

131

132
namespace realm {
133
namespace sync {
134
namespace network {
135
namespace ssl {
136

137
ErrorCategory error_category;
138

139

140
const char* ErrorCategory::name() const noexcept
141
{
×
142
    return "realm.sync.network.ssl";
×
143
}
×
144

145

146
std::string ErrorCategory::message(int value) const
147
{
×
148
    switch (Errors(value)) {
×
149
        case Errors::tls_handshake_failed:
×
150
            return "SSL certificate rejected"; // Throws
×
151
    }
×
152
    REALM_ASSERT(false);
×
153
    return {};
×
154
}
×
155

156

157
bool ErrorCategory::equivalent(const std::error_code& ec, int condition) const noexcept
158
{
10✔
159
    switch (Errors(condition)) {
10✔
160
        case Errors::tls_handshake_failed:
10✔
161
#if REALM_HAVE_OPENSSL
6✔
162
            return ec.category() == openssl_error_category;
6✔
163
#elif REALM_HAVE_SECURE_TRANSPORT
164
            return ec.category() == secure_transport_error_category;
4✔
165
#else
166
            static_cast<void>(ec);
167
            return false;
168
#endif
169
    }
×
170
    return false;
×
171
}
×
172

173
} // namespace ssl
174

175

176
OpensslErrorCategory openssl_error_category;
177

178

179
const char* OpensslErrorCategory::name() const noexcept
180
{
6✔
181
    return "openssl";
6✔
182
}
6✔
183

184

185
std::string OpensslErrorCategory::message(int value) const
186
{
18✔
187
    const char* message = "Unknown error";
18✔
188
#if REALM_HAVE_OPENSSL
18✔
189
    if (const char* s = ERR_reason_error_string(value))
18✔
190
        message = s;
18✔
191
#endif
18✔
192
    return util::format("OpenSSL error: %1 (%2)", message, value); // Throws
18✔
193
}
18✔
194

195

196
SecureTransportErrorCategory secure_transport_error_category;
197

198

199
const char* SecureTransportErrorCategory::name() const noexcept
200
{
×
201
    return "securetransport";
×
202
}
×
203

204

205
std::string SecureTransportErrorCategory::message(int value) const
206
{
4✔
207
    const char* message = "Unknown error";
4✔
208
#if REALM_HAVE_SECURE_TRANSPORT
4✔
209
    if (__builtin_available(iOS 11.3, macOS 10.3, tvOS 11.3, watchOS 4.3, *)) {
4✔
210
        auto status = OSStatus(value);
4✔
211
        void* reserved = nullptr;
4✔
212
        std::unique_ptr<char[]> buffer;
4✔
213
        if (auto cf_message = adoptCF(SecCopyErrorMessageString(status, reserved)))
4✔
214
            message = cfstring_to_cstring(cf_message.get(), buffer);
4✔
215
    }
4✔
216
#endif // REALM_HAVE_SECURE_TRANSPORT
4✔
217

218
    return util::format("SecureTransport error: %1 (%2)", message, value); // Throws
4✔
219
}
4✔
220

221

222
namespace ssl {
223

224
const char* ProtocolNotSupported::what() const noexcept
UNCOV
225
{
×
UNCOV
226
    return "SSL/TLS protocol not supported";
×
UNCOV
227
}
×
228

229

230
std::error_code Stream::handshake(std::error_code& ec)
231
{
52✔
232
    REALM_ASSERT(!m_tcp_socket.m_read_oper || !m_tcp_socket.m_read_oper->in_use());
52!
233
    REALM_ASSERT(!m_tcp_socket.m_write_oper || !m_tcp_socket.m_write_oper->in_use());
52✔
234
    m_tcp_socket.m_desc.ensure_blocking_mode(); // Throws
52✔
235
    Want want = Want::nothing;
52✔
236
    ssl_handshake(ec, want);
52✔
237
    REALM_ASSERT(want == Want::nothing);
52✔
238
    return ec;
52✔
239
}
52✔
240

241

242
std::error_code Stream::shutdown(std::error_code& ec)
243
{
40✔
244
    REALM_ASSERT(!m_tcp_socket.m_write_oper || !m_tcp_socket.m_write_oper->in_use());
40✔
245
    m_tcp_socket.m_desc.ensure_blocking_mode(); // Throws
40✔
246
    Want want = Want::nothing;
40✔
247
    ssl_shutdown(ec, want);
40✔
248
    REALM_ASSERT(want == Want::nothing);
40✔
249
    return ec;
40✔
250
}
40✔
251

252

253
#if REALM_HAVE_OPENSSL
254

255
void Context::ssl_init()
256
{
108✔
257
    ERR_clear_error();
108✔
258

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

108✔
263
    SSL_CTX* ssl_ctx = SSL_CTX_new(method);
108✔
264
    if (REALM_UNLIKELY(!ssl_ctx)) {
108✔
265
        std::error_code ec(int(ERR_get_error()), openssl_error_category);
266
        throw std::system_error(ec);
267
    }
268

108✔
269
    // Disable use of older protocol versions (SSLv2 and SSLv3).
108✔
270
    // Disable SSL compression by default, as compression is unavailable
108✔
271
    // with Apple's Secure Transport API.
108✔
272
    long options = 0;
108✔
273
    options |= SSL_OP_NO_SSLv2;
108✔
274
    options |= SSL_OP_NO_SSLv3;
108✔
275
    options |= SSL_OP_NO_COMPRESSION;
108✔
276
    SSL_CTX_set_options(ssl_ctx, options);
108✔
277

108✔
278
    m_ssl_ctx = ssl_ctx;
108✔
279
}
108✔
280

281

282
void Context::ssl_destroy() noexcept
283
{
108✔
284
    /*
108✔
285
        if (handle_->default_passwd_callback_userdata) {
108✔
286
            detail::password_callback_base* callback =
108✔
287
       static_cast<detail::password_callback_base*>(handle_->default_passwd_callback_userdata); delete callback;
108✔
288
            handle_->default_passwd_callback_userdata = nullptr;
108✔
289
        }
108✔
290

108✔
291
        if (SSL_CTX_get_app_data(handle_)) {
108✔
292
            detail::verify_callback_base* callback =
108✔
293
       static_cast<detail::verify_callback_base*>(SSL_CTX_get_app_data(handle_)); delete callback;
108✔
294
            SSL_CTX_set_app_data(handle_, nullptr);
108✔
295
        }
108✔
296
    */
108✔
297
    SSL_CTX_free(m_ssl_ctx);
108✔
298
}
108✔
299

300

301
void Context::ssl_use_certificate_chain_file(const std::string& path, std::error_code& ec)
302
{
54✔
303
    ERR_clear_error();
54✔
304
    int ret = SSL_CTX_use_certificate_chain_file(m_ssl_ctx, path.c_str());
54✔
305
    if (REALM_UNLIKELY(ret != 1)) {
54✔
306
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
307
        return;
308
    }
309
    ec = std::error_code();
54✔
310
}
54✔
311

312

313
void Context::ssl_use_private_key_file(const std::string& path, std::error_code& ec)
314
{
54✔
315
    ERR_clear_error();
54✔
316
    int type = SSL_FILETYPE_PEM;
54✔
317
    int ret = SSL_CTX_use_PrivateKey_file(m_ssl_ctx, path.c_str(), type);
54✔
318
    if (REALM_UNLIKELY(ret != 1)) {
54✔
319
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
320
        return;
321
    }
322
    ec = std::error_code();
54✔
323
}
54✔
324

325

326
void Context::ssl_use_default_verify(std::error_code& ec)
327
{
2✔
328
#if REALM_USE_SYSTEM_OPENSSL_PATHS
329
    ERR_clear_error();
330
    int ret = SSL_CTX_set_default_verify_paths(m_ssl_ctx);
331
    if (ret != 1) {
332
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
333
        return;
334
    }
335
#endif
336
    ec = std::error_code();
2✔
337
}
2✔
338

339

340
void Context::ssl_use_verify_file(const std::string& path, std::error_code& ec)
341
{
14✔
342
    ERR_clear_error();
14✔
343
    int ret = SSL_CTX_load_verify_locations(m_ssl_ctx, path.c_str(), nullptr);
14✔
344
    if (ret != 1) {
14✔
345
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
346
        return;
347
    }
348

14✔
349
    ec = std::error_code();
14✔
350
}
14✔
351

352
#if REALM_INCLUDE_CERTS
353
void Context::ssl_use_included_certificate_roots(std::error_code& ec)
354
{
2✔
355
    X509_STORE* store = SSL_CTX_get_cert_store(m_ssl_ctx);
2✔
356
    populate_cert_store_with_included_certs(store, ec);
2✔
357
}
2✔
358
#endif
359

360
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) && !defined(OPENSSL_IS_BORINGSSL)
361
class Stream::BioMethod {
362
public:
363
    BIO_METHOD* bio_method;
364

365
    BioMethod()
366
    {
6✔
367
        const char* name = "realm::util::Stream::BioMethod";
6✔
368
        bio_method = BIO_meth_new(BIO_get_new_index(), name);
6✔
369
        if (!bio_method)
6✔
370
            throw util::bad_alloc();
371

6✔
372
        BIO_meth_set_write(bio_method, &Stream::bio_write);
6✔
373
        BIO_meth_set_read(bio_method, &Stream::bio_read);
6✔
374
        BIO_meth_set_puts(bio_method, &Stream::bio_puts);
6✔
375
        BIO_meth_set_gets(bio_method, nullptr);
6✔
376
        BIO_meth_set_ctrl(bio_method, &Stream::bio_ctrl);
6✔
377
        BIO_meth_set_create(bio_method, &Stream::bio_create);
6✔
378
        BIO_meth_set_destroy(bio_method, &Stream::bio_destroy);
6✔
379
        BIO_meth_set_callback_ctrl(bio_method, nullptr);
6✔
380
    }
6✔
381

382
    ~BioMethod()
383
    {
384
        BIO_meth_free(bio_method);
385
    }
386
};
387
#else
388
class Stream::BioMethod {
389
public:
390
    BIO_METHOD* bio_method;
391

392
    BioMethod()
393
    {
394
        bio_method = new BIO_METHOD{
395
            BIO_TYPE_SOCKET,      // int type
396
            nullptr,              // const char* name
397
            &Stream::bio_write,   // int (*bwrite)(BIO*, const char*, int)
398
            &Stream::bio_read,    // int (*bread)(BIO*, char*, int)
399
            &Stream::bio_puts,    // int (*bputs)(BIO*, const char*)
400
            nullptr,              // int (*bgets)(BIO*, char*, int)
401
            &Stream::bio_ctrl,    // long (*ctrl)(BIO*, int, long, void*)
402
            &Stream::bio_create,  // int (*create)(BIO*)
403
            &Stream::bio_destroy, // int (*destroy)(BIO*)
404
            nullptr               // long (*callback_ctrl)(BIO*, int, bio_info_cb*)
405
        };
406
    }
407

408
    ~BioMethod()
409
    {
410
        delete bio_method;
411
    }
412
};
413
#endif
414

415

416
Stream::BioMethod Stream::s_bio_method;
417

418

419
#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER)
420

421
namespace {
422

423
// check_common_name() checks that \param  server_cert constains host_name
424
// as Common Name. The function is used by verify_callback() for
425
// OpenSSL versions before 1.0.2.
426
bool check_common_name(X509* server_cert, const std::string& host_name)
427
{
428
    // Find the position of the Common Name field in the Subject field of the certificate
429
    int common_name_loc = -1;
430
    common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name(server_cert), NID_commonName, -1);
431
    if (common_name_loc < 0)
432
        return false;
433

434
    // Extract the Common Name field
435
    X509_NAME_ENTRY* common_name_entry;
436
    common_name_entry = X509_NAME_get_entry(X509_get_subject_name(server_cert), common_name_loc);
437
    if (!common_name_entry)
438
        return false;
439

440
    // Convert the Common Namefield to a C string
441
    ASN1_STRING* common_name_asn1;
442
    common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
443
    if (!common_name_asn1)
444
        return false;
445

446
    char* common_name_str = reinterpret_cast<char*>(ASN1_STRING_data(common_name_asn1));
447

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

452
    bool names_equal = (host_name == common_name_str);
453
    return names_equal;
454
}
455

456
// check_common_name() checks that \param  server_cert constains host_name
457
// in the Subject Alternative Name DNS section. The function is used by verify_callback()
458
// for OpenSSL versions before 1.0.2.
459
bool check_san(X509* server_cert, const std::string& host_name)
460
{
461
    STACK_OF(GENERAL_NAME) * san_names;
462

463
    // Try to extract the names within the SAN extension from the certificate
464
    san_names =
465
        static_cast<STACK_OF(GENERAL_NAME)*>(X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
466
    if (!san_names)
467
        return false;
468

469
    int san_names_nb = sk_GENERAL_NAME_num(san_names);
470

471
    bool found = false;
472

473
    // Check each name within the extension
474
    for (int i = 0; i < san_names_nb; ++i) {
475
        const GENERAL_NAME* current_name = sk_GENERAL_NAME_value(san_names, i);
476

477
        if (current_name->type == GEN_DNS) {
478
            // Current name is a DNS name
479
            char* dns_name = static_cast<char*>(ASN1_STRING_data(current_name->d.dNSName));
480

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

485
            if (host_name == dns_name) {
486
                found = true;
487
                break;
488
            }
489
        }
490
    }
491

492
    sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
493

494
    return found;
495
}
496

497
} // namespace
498

499
int Stream::verify_callback_using_hostname(int preverify_ok, X509_STORE_CTX* ctx) noexcept
500
{
501
    if (preverify_ok != 1)
502
        return preverify_ok;
503

504
    X509* server_cert = X509_STORE_CTX_get_current_cert(ctx);
505

506
    int err = X509_STORE_CTX_get_error(ctx);
507
    if (err != X509_V_OK)
508
        return 0;
509

510
    int depth = X509_STORE_CTX_get_error_depth(ctx);
511

512
    // We only inspect the certificate at depth = 0.
513
    if (depth > 0)
514
        return preverify_ok;
515

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

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

522
    const std::string& host_name = stream->m_host_name;
523

524
    if (check_common_name(server_cert, host_name))
525
        return 1;
526

527
    if (check_san(server_cert, host_name))
528
        return 1;
529

530
    return 0;
531
}
532

533
#endif
534

535

536
void Stream::ssl_set_verify_mode(VerifyMode mode, std::error_code& ec)
537
{
22✔
538
    int mode_2 = 0;
22✔
539
    switch (mode) {
22✔
540
        case VerifyMode::none:
541
            break;
542
        case VerifyMode::peer:
22✔
543
            mode_2 = SSL_VERIFY_PEER;
22✔
544
            break;
22✔
545
    }
22✔
546

22✔
547
    int rc = SSL_set_ex_data(m_ssl, 0, this);
22✔
548
    if (rc == 0) {
22✔
549
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
550
        return;
551
    }
552

22✔
553
#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER)
554
    SSL_set_verify(m_ssl, mode_2, &Stream::verify_callback_using_hostname);
555
#else
556
    // verify_callback is nullptr.
22✔
557
    SSL_set_verify(m_ssl, mode_2, nullptr);
22✔
558
#endif
22✔
559
    ec = std::error_code();
22✔
560
}
22✔
561

562

563
void Stream::ssl_set_host_name(const std::string& host_name, std::error_code& ec)
564
{
24✔
565
    // Enable Server Name Indication (SNI) extension
24✔
566
#if OPENSSL_VERSION_NUMBER >= 0x10101000L
24✔
567
    {
24✔
568
#ifndef _WIN32
24✔
569
#pragma GCC diagnostic push
24✔
570
#pragma GCC diagnostic ignored "-Wold-style-cast"
24✔
571
#endif
24✔
572
        auto ret = SSL_set_tlsext_host_name(m_ssl, host_name.c_str());
24✔
573
#ifndef _WIN32
24✔
574
#pragma GCC diagnostic pop
24✔
575
#endif
24✔
576
        if (ret == 0) {
24✔
577
            ec = std::error_code(int(ERR_get_error()), openssl_error_category);
578
            return;
579
        }
580
    }
24✔
581
#else
582
    static_cast<void>(host_name);
583
    static_cast<void>(ec);
584
#endif
585

24✔
586
    // Enable host name check during certificate validation
24✔
587
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)
24✔
588
    {
24✔
589
        X509_VERIFY_PARAM* param = SSL_get0_param(m_ssl);
24✔
590
        X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
24✔
591
        auto ret = X509_VERIFY_PARAM_set1_host(param, host_name.c_str(), host_name.size());
24✔
592
        if (ret == 0) {
24✔
593
            int sys_error = int(ERR_get_error());
594
            // BoringSSL can return 0 here without actually pushing on the error stack
595
            REALM_ASSERT_DEBUG(sys_error != 0);
596
            ec = std::error_code(sys_error, openssl_error_category);
597
            return;
598
        }
599
    }
24✔
600
#else
601
    static_cast<void>(host_name);
602
    static_cast<void>(ec);
603
#endif
604
}
24✔
605

606
void Stream::ssl_use_verify_callback(const std::function<SSLVerifyCallback>& callback, std::error_code&)
607
{
6✔
608
    m_ssl_verify_callback = &callback;
6✔
609

6✔
610
    SSL_set_verify(m_ssl, SSL_VERIFY_PEER, &Stream::verify_callback_using_delegate);
6✔
611
}
6✔
612

613
#ifndef _WIN32
614
#pragma GCC diagnostic push
615
#pragma GCC diagnostic ignored "-Wold-style-cast"
616
#endif
617

618
int Stream::verify_callback_using_delegate(int preverify_ok, X509_STORE_CTX* ctx) noexcept
619
{
10✔
620
    X509* server_cert = X509_STORE_CTX_get_current_cert(ctx);
10✔
621

10✔
622
    int depth = X509_STORE_CTX_get_error_depth(ctx);
10✔
623

10✔
624
    BIO* bio = BIO_new(BIO_s_mem());
10✔
625
    if (!bio) {
10✔
626
        // certificate rejected if a memory error occurs.
627
        return 0;
628
    }
629

10✔
630
    int ret = PEM_write_bio_X509(bio, server_cert);
10✔
631
    if (!ret) {
10✔
632
        BIO_free(bio);
633
        return 0;
634
    }
635

10✔
636
    BUF_MEM* buffer;
10✔
637
    BIO_get_mem_ptr(bio, &buffer);
10✔
638

10✔
639
    const char* pem_data = buffer->data;
10✔
640
    std::size_t pem_size = buffer->length;
10✔
641

10✔
642
    SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
10✔
643
    Stream* stream = static_cast<Stream*>(SSL_get_ex_data(ssl, 0));
10✔
644
    const std::string& host_name = stream->m_host_name;
10✔
645
    port_type server_port = stream->m_server_port;
10✔
646

10✔
647
    REALM_ASSERT(stream->m_ssl_verify_callback);
10✔
648
    const std::function<SSLVerifyCallback>& callback = *stream->m_ssl_verify_callback;
10✔
649

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

10✔
657
    BIO_free(bio);
10✔
658
    return int(valid);
10✔
659
}
10✔
660

661
#ifndef _WIN32
662
#pragma GCC diagnostic pop
663
#endif
664

665
void Stream::ssl_init()
666
{
122✔
667
    SSL_CTX* ssl_ctx = m_ssl_context.m_ssl_ctx;
122✔
668
    SSL* ssl = SSL_new(ssl_ctx);
122✔
669
    if (REALM_UNLIKELY(!ssl)) {
122✔
670
        std::error_code ec(int(ERR_get_error()), openssl_error_category);
671
        throw std::system_error(ec);
672
    }
673
    SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
122✔
674
#if defined(SSL_MODE_RELEASE_BUFFERS)
122✔
675
    SSL_set_mode(ssl, SSL_MODE_RELEASE_BUFFERS);
122✔
676
#endif
122✔
677

122✔
678
    BIO* bio = BIO_new(s_bio_method.bio_method);
122✔
679

122✔
680
    if (REALM_UNLIKELY(!bio)) {
122✔
681
        SSL_free(ssl);
682
        std::error_code ec(int(ERR_get_error()), openssl_error_category);
683
        throw std::system_error(ec);
684
    }
685

122✔
686
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
122✔
687
    BIO_set_data(bio, this);
122✔
688
#else
689
    bio->ptr = this;
690
#endif
691

122✔
692
    SSL_set_bio(ssl, bio, bio);
122✔
693
    m_ssl = ssl;
122✔
694
}
122✔
695

696

697
void Stream::ssl_destroy() noexcept
698
{
122✔
699
    SSL_free(m_ssl);
122✔
700
}
122✔
701

702

703
int Stream::bio_write(BIO* bio, const char* data, int size) noexcept
704
{
176,546✔
705
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
176,546✔
706
    Stream& stream = *static_cast<Stream*>(BIO_get_data(bio));
176,546✔
707
#else
708
    Stream& stream = *static_cast<Stream*>(bio->ptr);
709
#endif
710
    Service::Descriptor& desc = stream.m_tcp_socket.m_desc;
176,546✔
711
    std::error_code ec;
176,546✔
712
    std::size_t n = desc.write_some(data, std::size_t(size), ec);
176,546✔
713

176,546✔
714
    BIO_clear_retry_flags(bio);
176,546✔
715
    if (ec) {
176,546✔
716
        if (REALM_UNLIKELY(ec != error::resource_unavailable_try_again)) {
394✔
717
            stream.m_bio_error_code = ec;
6✔
718
            return -1;
6✔
719
        }
6✔
720
        BIO_set_retry_write(bio);
388✔
721
        return -1;
388✔
722
    }
388✔
723
    return int(n);
176,152✔
724
}
176,152✔
725

726

727
int Stream::bio_read(BIO* bio, char* buffer, int size) noexcept
728
{
351,960✔
729
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
351,960✔
730
    Stream& stream = *static_cast<Stream*>(BIO_get_data(bio));
351,960✔
731
#else
732
    Stream& stream = *static_cast<Stream*>(bio->ptr);
733
#endif
734
    Service::Descriptor& desc = stream.m_tcp_socket.m_desc;
351,960✔
735
    std::error_code ec;
351,960✔
736
    std::size_t n = desc.read_some(buffer, std::size_t(size), ec);
351,960✔
737

351,960✔
738
    BIO_clear_retry_flags(bio);
351,960✔
739
    if (ec) {
351,960✔
740
        if (REALM_UNLIKELY(ec == MiscExtErrors::end_of_input)) {
184✔
741
            // This behaviour agrees with `crypto/bio/bss_sock.c` of OpenSSL.
4✔
742
            return 0;
4✔
743
        }
4✔
744
        if (REALM_UNLIKELY(ec != error::resource_unavailable_try_again)) {
180✔
745
            stream.m_bio_error_code = ec;
746
            return -1;
747
        }
748
        BIO_set_retry_read(bio);
180✔
749
        return -1;
180✔
750
    }
180✔
751
    return int(n);
351,776✔
752
}
351,776✔
753

754

755
int Stream::bio_puts(BIO* bio, const char* c_str) noexcept
756
{
757
    std::size_t size = std::strlen(c_str);
758
    return bio_write(bio, c_str, int(size));
759
}
760

761

762
long Stream::bio_ctrl(BIO*, int cmd, long, void*) noexcept
763
{
536✔
764
    switch (cmd) {
536✔
765
        case BIO_CTRL_EOF:
4✔
766
            return 0;
4✔
767
        case BIO_CTRL_PUSH:
280✔
768
        case BIO_CTRL_POP:
280✔
769
            // Ignoring in alignment with `crypto/bio/bss_sock.c` of OpenSSL.
280✔
770
            return 0;
280✔
771
        case BIO_CTRL_FLUSH:
280✔
772
            // Ignoring in alignment with `crypto/bio/bss_sock.c` of OpenSSL.
252✔
773
            return 1;
252✔
774
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
280✔
775
        case BIO_CTRL_GET_KTLS_SEND:
280✔
776
        case BIO_CTRL_GET_KTLS_RECV:
777
            return 0;
778
#endif
779
        default:
780
            REALM_ASSERT_EX(false, "Got BIO_ctrl with unknown command %d", cmd);
781
    }
536✔
782
    return 0;
536✔
783
}
536✔
784

785

786
int Stream::bio_create(BIO* bio) noexcept
787
{
122✔
788
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
122✔
789
    BIO_set_init(bio, 1);
122✔
790
    BIO_set_data(bio, nullptr);
122✔
791
    BIO_clear_flags(bio, 0);
122✔
792
    BIO_set_shutdown(bio, 0);
122✔
793
#else
794
    // In alignment with `crypto/bio/bss_sock.c` of OpenSSL.
795
    bio->init = 1;
796
    bio->num = 0;
797
    bio->ptr = nullptr;
798
    bio->flags = 0;
799
#endif
800
    return 1;
122✔
801
}
122✔
802

803

804
int Stream::bio_destroy(BIO*) noexcept
805
{
122✔
806
    return 1;
122✔
807
}
122✔
808

809

810
#elif REALM_HAVE_SECURE_TRANSPORT
811

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

814
void Context::ssl_init() {}
104✔
815

816
void Context::ssl_destroy() noexcept
817
{
104✔
818
#if REALM_HAVE_KEYCHAIN_APIS
104✔
819
    if (m_keychain) {
104✔
820
        m_keychain.reset();
52✔
821
        unlink(m_keychain_path.data());
52✔
822
        m_keychain_path = {};
52✔
823
    }
52✔
824
#endif
104✔
825
}
104✔
826

827
// Load certificates and/or keys from the specified PEM file. If keychain is non-null, the items will be
828
// imported into that keychain.
829
util::CFPtr<CFArrayRef> Context::load_pem_file(const std::string& path, SecKeychainRef keychain, std::error_code& ec)
830
{
118✔
831
    using util::adoptCF;
118✔
832
    using util::CFPtr;
118✔
833

834
    std::ifstream file(path);
118✔
835
    if (!file) {
118✔
836
        // Rely on the open attempt having set errno to a sensible value as ifstream's
837
        // own error reporting gives terribly generic error messages.
838
        ec = make_basic_system_error_code(errno);
839
        return util::CFPtr<CFArrayRef>();
840
    }
841
    std::vector<char> contents{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
118✔
842

843
    auto contentsCF = adoptCF(CFDataCreateWithBytesNoCopy(nullptr, reinterpret_cast<const UInt8*>(contents.data()),
118✔
844
                                                          contents.size(), kCFAllocatorNull));
118✔
845

846
    // If we don't need to import it into a keychain, try to interpret the data
847
    // as a certificate directly. This only works for DER files, so we fall back
848
    // to SecItemImport() on platforms which support that if this fails.
849
    if (keychain == nullptr) {
118✔
850
        if (auto certificate = adoptCF(SecCertificateCreateWithData(NULL, contentsCF.get()))) {
66✔
851
            auto ref = certificate.get();
2✔
852
            return adoptCF(CFArrayCreate(nullptr, const_cast<const void**>(reinterpret_cast<void**>(&ref)), 1,
2✔
853
                                         &kCFTypeArrayCallBacks));
2✔
854
        }
2✔
855

856
        // SecCertificateCreateWithData doesn't tell us why it failed, so just
857
        // report the error code that SecItemImport uses when given something
858
        // that's not a certificate
859
        ec = std::error_code(errSecUnknownFormat, secure_transport_error_category);
64✔
860
    }
64✔
861

862
    CFArrayRef items = nullptr;
116✔
863

864
#if REALM_HAVE_KEYCHAIN_APIS
116✔
865
    SecItemImportExportKeyParameters params{};
116✔
866
    params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
116✔
867

868
    CFPtr<CFStringRef> pathCF = adoptCF(CFStringCreateWithBytes(nullptr, reinterpret_cast<const UInt8*>(path.data()),
116✔
869
                                                                path.size(), kCFStringEncodingUTF8, false));
116✔
870

871
    SecExternalFormat format = kSecFormatUnknown;
116✔
872
    SecExternalItemType itemType = kSecItemTypeUnknown;
116✔
873
    if (OSStatus status =
116✔
874
            SecItemImport(contentsCF.get(), pathCF.get(), &format, &itemType, 0, &params, keychain, &items)) {
875
        ec = std::error_code(status, secure_transport_error_category);
876
        return util::CFPtr<CFArrayRef>();
877
    }
878
    ec = {};
116✔
879
#endif
116✔
880

881
    return adoptCF(items);
116✔
882
}
116✔
883

884
#if REALM_HAVE_KEYCHAIN_APIS
885

886
static std::string temporary_directory()
887
{
52✔
888
    auto ensure_trailing_slash = [](auto str) {
52✔
889
        return str.back() == '/' ? str : str + '/';
52✔
890
    };
52✔
891

892
    std::string path;
52✔
893
    path.resize(PATH_MAX);
52✔
894
    std::size_t result = confstr(_CS_DARWIN_USER_TEMP_DIR, &path[0], path.size());
52✔
895
    if (result && result <= path.size()) {
52✔
896
        path.resize(result - 1);
52✔
897
        return ensure_trailing_slash(std::move(path));
52✔
898
    }
52✔
899

900
    // We failed to retrieve temporary directory from confstr. Fall back to the TMPDIR
901
    // environment variable if we're not running with elevated privileges, and then to /tmp.
902
    if (!issetugid()) {
×
903
        path = getenv("TMPDIR");
904
        if (path.size()) {
×
905
            return ensure_trailing_slash(std::move(path));
906
        }
907
    }
908
    return "/tmp/";
909
}
910

911

912
std::error_code Context::open_temporary_keychain_if_needed()
913
{
52✔
914
    if (m_keychain) {
52✔
915
        return std::error_code();
916
    }
917

918
    std::string path = temporary_directory() + "realm-sync-ssl-XXXXXXXX.keychain";
52✔
919
    int fd = mkstemps(&path[0], std::strlen(".keychain"));
52✔
920
    if (fd < 0) {
52✔
921
        return make_basic_system_error_code(errno);
922
    }
923

924
    // Close and remove the file so that we can create a keychain in its place.
925
    close(fd);
52✔
926
    unlink(path.data());
52✔
927

928
    SecKeychainRef keychain = nullptr;
52✔
929
    std::string password = "";
52✔
930
    if (OSStatus status =
52✔
931
            SecKeychainCreate(path.data(), UInt32(password.size()), password.data(), false, nullptr, &keychain))
932
        return std::error_code(status, secure_transport_error_category);
933

934
    m_keychain = adoptCF(keychain);
52✔
935
    m_keychain_path = std::move(path);
52✔
936

937
    return std::error_code();
52✔
938
}
52✔
939

940

941
// Creates an identity from the certificate and private key. The private key must exist in m_keychain.
942
std::error_code Context::update_identity_if_needed()
943
{
104✔
944
    // If we've not yet loaded both the certificate and private key there's nothing to do.
945
    if (!m_certificate || !m_private_key) {
104✔
946
        return std::error_code();
52✔
947
    }
52✔
948

949
    SecIdentityRef identity = nullptr;
52✔
950
    if (OSStatus status = SecIdentityCreateWithCertificate(m_keychain.get(), m_certificate.get(), &identity)) {
52✔
951
        return std::error_code(status, secure_transport_error_category);
952
    }
953

954
    m_identity = util::adoptCF(identity);
52✔
955
    return std::error_code();
52✔
956
}
52✔
957

958
#endif // REALM_HAVE_KEYCHAIN_APIS
959

960
void Context::ssl_use_certificate_chain_file(const std::string& path, std::error_code& ec)
961
{
52✔
962
#if !REALM_HAVE_KEYCHAIN_APIS
963
    static_cast<void>(path);
964
    ec = make_basic_system_error_code(ENOTSUP);
965
#else
966
    auto items = load_pem_file(path, nullptr, ec);
52✔
967
    if (!items) {
52✔
968
        REALM_ASSERT(ec);
×
969
        return;
970
    }
971

972
    if (CFArrayGetCount(items.get()) < 1) {
52✔
973
        ec = std::error_code(errSecItemNotFound, secure_transport_error_category);
974
        return;
975
    }
976

977
    CFTypeRef item = CFArrayGetValueAtIndex(items.get(), 0);
52✔
978
    if (CFGetTypeID(item) != SecCertificateGetTypeID()) {
52✔
979
        ec = std::error_code(errSecItemNotFound, secure_transport_error_category);
980
        return;
981
    }
982

983
    m_certificate = util::retainCF(reinterpret_cast<SecCertificateRef>(const_cast<void*>(item)));
52✔
984

985
    // The returned array contains the server certificate followed by the remainder of the certificates in the chain.
986
    // Remove the server certificate to leave us with an array containing only the remainder of the certificate chain.
987
    auto certificate_chain = util::adoptCF(CFArrayCreateMutableCopy(nullptr, 0, items.get()));
52✔
988
    CFArrayRemoveValueAtIndex(certificate_chain.get(), 0);
52✔
989
    m_certificate_chain = util::adoptCF(reinterpret_cast<CFArrayRef>(certificate_chain.release()));
52✔
990

991
    ec = update_identity_if_needed();
52✔
992
#endif
52✔
993
}
52✔
994

995

996
void Context::ssl_use_private_key_file(const std::string& path, std::error_code& ec)
997
{
52✔
998
#if !REALM_HAVE_KEYCHAIN_APIS
999
    static_cast<void>(path);
1000
    ec = make_basic_system_error_code(ENOTSUP);
1001
#else
1002
    ec = open_temporary_keychain_if_needed();
52✔
1003
    if (ec) {
52✔
1004
        return;
1005
    }
1006

1007
    auto items = load_pem_file(path, m_keychain.get(), ec);
52✔
1008
    if (!items) {
52✔
1009
        return;
1010
    }
1011

1012
    if (CFArrayGetCount(items.get()) != 1) {
52✔
1013
        ec = std::error_code(errSecItemNotFound, secure_transport_error_category);
1014
        return;
1015
    }
1016

1017
    CFTypeRef item = CFArrayGetValueAtIndex(items.get(), 0);
52✔
1018
    if (CFGetTypeID(item) != SecKeyGetTypeID()) {
52✔
1019
        ec = std::error_code(errSecItemNotFound, secure_transport_error_category);
1020
        return;
1021
    }
1022

1023
    m_private_key = util::retainCF(reinterpret_cast<SecKeyRef>(const_cast<void*>(item)));
52✔
1024
    ec = update_identity_if_needed();
52✔
1025
#endif
52✔
1026
}
52✔
1027

1028
void Context::ssl_use_default_verify(std::error_code&) {}
2✔
1029

1030
void Context::ssl_use_verify_file(const std::string& path, std::error_code& ec)
1031
{
14✔
1032
#if REALM_HAVE_KEYCHAIN_APIS
14✔
1033
    m_trust_anchors = load_pem_file(path, m_keychain.get(), ec);
14✔
1034
#else
1035
    m_trust_anchors = load_pem_file(path, nullptr, ec);
1036
#endif
1037

1038
    if (m_trust_anchors && CFArrayGetCount(m_trust_anchors.get())) {
14✔
1039
        const void* leaf_certificate = CFArrayGetValueAtIndex(m_trust_anchors.get(), 0);
14✔
1040
        m_pinned_certificate =
14✔
1041
            adoptCF(SecCertificateCopyData(static_cast<SecCertificateRef>(const_cast<void*>(leaf_certificate))));
14✔
1042
    }
14✔
1043
    else {
1044
        m_pinned_certificate.reset();
1045
    }
1046
}
14✔
1047

1048
void Stream::ssl_init()
1049
{
114✔
1050
    SSLProtocolSide side = m_handshake_type == HandshakeType::client ? kSSLClientSide : kSSLServerSide;
62✔
1051
    m_ssl = util::adoptCF(SSLCreateContext(nullptr, side, kSSLStreamType));
114✔
1052
    if (OSStatus status = SSLSetIOFuncs(m_ssl.get(), Stream::tcp_read, Stream::tcp_write)) {
114✔
1053
        std::error_code ec(status, secure_transport_error_category);
1054
        throw std::system_error(ec);
1055
    }
1056
    if (OSStatus status = SSLSetConnection(m_ssl.get(), this)) {
114✔
1057
        std::error_code ec(status, secure_transport_error_category);
1058
        throw std::system_error(ec);
1059
    }
1060

1061
    // Require TLSv1 or greater.
1062
    if (OSStatus status = SSLSetProtocolVersionMin(m_ssl.get(), kTLSProtocol1)) {
114✔
1063
        std::error_code ec(status, secure_transport_error_category);
1064
        throw std::system_error(ec);
1065
    }
1066

1067
    // Break after certificate exchange to allow for customizing the verification process.
1068
    SSLSessionOption option = m_handshake_type == HandshakeType::client ? kSSLSessionOptionBreakOnServerAuth
114✔
1069
                                                                        : kSSLSessionOptionBreakOnClientAuth;
62✔
1070
    if (OSStatus status = SSLSetSessionOption(m_ssl.get(), option, true)) {
114✔
1071
        std::error_code ec(status, secure_transport_error_category);
1072
        throw std::system_error(ec);
1073
    }
1074

1075
#if REALM_HAVE_KEYCHAIN_APIS
114✔
1076
    if (m_ssl_context.m_identity && m_ssl_context.m_certificate_chain) {
114✔
1077
        // SSLSetCertificate expects an array containing the identity followed by the identity's certificate chain.
1078
        auto certificates = util::adoptCF(CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks));
62✔
1079
        CFArrayInsertValueAtIndex(certificates.get(), 0, m_ssl_context.m_identity.get());
62✔
1080

1081
        CFArrayRef certificate_chain = m_ssl_context.m_certificate_chain.get();
62✔
1082
        CFArrayAppendArray(certificates.get(), certificate_chain, CFRangeMake(0, CFArrayGetCount(certificate_chain)));
62✔
1083

1084
        if (OSStatus status = SSLSetCertificate(m_ssl.get(), certificates.get())) {
62✔
1085
            std::error_code ec(status, secure_transport_error_category);
1086
            throw std::system_error(ec);
1087
        }
1088
    }
62✔
1089
#endif
114✔
1090
}
114✔
1091

1092

1093
void Stream::ssl_destroy() noexcept
1094
{
114✔
1095
    m_ssl.reset();
114✔
1096
}
114✔
1097

1098

1099
void Stream::ssl_set_verify_mode(VerifyMode verify_mode, std::error_code& ec)
1100
{
16✔
1101
    m_verify_mode = verify_mode;
16✔
1102
    ec = std::error_code();
16✔
1103
}
16✔
1104

1105

1106
void Stream::ssl_set_host_name(const std::string& host_name, std::error_code& ec)
1107
{
18✔
1108
    if (OSStatus status = SSLSetPeerDomainName(m_ssl.get(), host_name.data(), host_name.size()))
18✔
1109
        ec = std::error_code(status, secure_transport_error_category);
1110
}
18✔
1111

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

1114
void Stream::ssl_handshake(std::error_code& ec, Want& want) noexcept
1115
{
264✔
1116
    auto perform = [this]() noexcept {
264✔
1117
        return do_ssl_handshake();
264✔
1118
    };
264✔
1119
    ssl_perform(std::move(perform), ec, want);
264✔
1120
}
264✔
1121

1122
std::pair<OSStatus, std::size_t> Stream::do_ssl_handshake() noexcept
1123
{
306✔
1124
    OSStatus result = SSLHandshake(m_ssl.get());
306✔
1125
    if (result != errSSLPeerAuthCompleted) {
306✔
1126
        return {result, 0};
258✔
1127
    }
258✔
1128

1129
    if (OSStatus status = verify_peer()) {
48✔
1130
        // When performing peer verification internally, verification failure results in SecureTransport
1131
        // sending a fatal alert to the peer, closing the connection. Sadly SecureTransport has no way
1132
        // to explicitly send a fatal alert when trust evaluation is handled externally. The best we can
1133
        // do is close the connection gracefully.
1134
        SSLClose(m_ssl.get());
6✔
1135
        return {status, 0};
6✔
1136
    }
6✔
1137

1138
    // Verification succeeded. Resume the handshake.
1139
    return do_ssl_handshake();
42✔
1140
}
42✔
1141

1142

1143
OSStatus Stream::verify_peer() noexcept
1144
{
48✔
1145
    switch (m_verify_mode) {
48✔
1146
        case VerifyMode::none:
32✔
1147
            // Peer verification is disabled.
1148
            return noErr;
32✔
1149

1150
        case VerifyMode::peer: {
16✔
1151
            SecTrustRef peerTrustRef = nullptr;
16✔
1152
            if (OSStatus status = SSLCopyPeerTrust(m_ssl.get(), &peerTrustRef)) {
16✔
1153
                return status;
1154
            }
1155

1156
            auto peerTrust = util::adoptCF(peerTrustRef);
16✔
1157

1158
            if (m_ssl_context.m_trust_anchors) {
16✔
1159
                if (OSStatus status =
14✔
1160
                        SecTrustSetAnchorCertificates(peerTrust.get(), m_ssl_context.m_trust_anchors.get())) {
1161
                    return status;
1162
                }
1163
                if (OSStatus status = SecTrustSetAnchorCertificatesOnly(peerTrust.get(), true)) {
14✔
1164
                    return status;
1165
                }
1166
            }
16✔
1167

1168
            // FIXME: SecTrustEvaluate can block if evaluation needs to fetch missing intermediate
1169
            // certificates or to check revocation using OCSP. Consider disabling these network
1170
            // fetches or doing async trust evaluation instead.
1171
#if __has_builtin(__builtin_available)
16✔
1172
            if (__builtin_available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *)) {
16✔
1173
                CFErrorRef cfErrorRef;
16✔
1174
                if (!SecTrustEvaluateWithError(peerTrust.get(), &cfErrorRef)) {
16✔
1175
                    auto cfError = util::adoptCF(cfErrorRef);
4✔
1176
                    if (logger && logger->would_log(Logger::Level::debug)) {
4✔
1177
                        auto errorStr = util::adoptCF(CFErrorCopyDescription(cfErrorRef));
2✔
1178
                        std::unique_ptr<char[]> buffer;
2✔
1179
                        logger->debug("SSL peer verification failed: %1",
2✔
1180
                                      cfstring_to_cstring(errorStr.get(), buffer));
2✔
1181
                    }
2✔
1182
                    return errSSLXCertChainInvalid;
4✔
1183
                }
4✔
1184
            }
1185
            else
1186
#endif
1187
            {
1188
                SecTrustResultType trustResult;
1189
                if (OSStatus status = SecTrustEvaluate(peerTrust.get(), &trustResult)) {
×
1190
                    return status;
1191
                }
1192

1193
                // A "proceed" result means the cert is explicitly trusted, e.g. "Always Trust" was selected.
1194
                // "Unspecified" means the cert has no explicit trust settings, but is implicitly OK since it
1195
                // chains back to a trusted root. Any other result means the cert is not trusted.
1196
                if (trustResult == kSecTrustResultRecoverableTrustFailure) {
×
1197
                    // Not trusted.
1198
                    return errSSLXCertChainInvalid;
1199
                }
1200
                if (trustResult != kSecTrustResultProceed && trustResult != kSecTrustResultUnspecified) {
×
1201
                    return errSSLBadCert;
1202
                }
1203
            }
12✔
1204

1205
            if (!m_ssl_context.m_pinned_certificate) {
12✔
1206
                // Certificate is trusted!
1207
                return noErr;
1208
            }
1209

1210
            // Verify that the certificate is one of our pinned certificates
1211
            // Loop backwards as the pinned certificate will normally be the last one
1212
            for (CFIndex i = SecTrustGetCertificateCount(peerTrust.get()); i > 0; --i) {
16✔
1213
                SecCertificateRef certificate = SecTrustGetCertificateAtIndex(peerTrust.get(), i - 1);
14✔
1214
                auto certificate_data = adoptCF(SecCertificateCopyData(certificate));
14✔
1215
                if (CFEqual(certificate_data.get(), m_ssl_context.m_pinned_certificate.get())) {
14✔
1216
                    return noErr;
10✔
1217
                }
10✔
1218
            }
14✔
1219

1220
            // Although the cerificate is valid, it's not the one we've pinned so reject it.
1221
            return errSSLXCertChainInvalid;
2✔
1222
        }
12✔
1223
    }
48✔
1224
}
48✔
1225

1226

1227
std::size_t Stream::ssl_read(char* buffer, std::size_t size, std::error_code& ec, Want& want) noexcept
1228
{
217,644✔
1229
    auto perform = [this, buffer, size]() noexcept {
217,632✔
1230
        return do_ssl_read(buffer, size);
217,632✔
1231
    };
217,632✔
1232
    std::size_t n = ssl_perform(std::move(perform), ec, want);
217,644✔
1233
    if (want == Want::nothing && n == 0 && !ec) {
217,644✔
1234
        // End of input on TCP socket
1235
        SSLSessionState state;
1236
        if (SSLGetSessionState(m_ssl.get(), &state) == noErr && state == kSSLClosed) {
×
1237
            ec = MiscExtErrors::end_of_input;
1238
        }
1239
        else {
1240
            ec = MiscExtErrors::premature_end_of_input;
1241
        }
1242
    }
1243
    return n;
217,644✔
1244
}
217,644✔
1245

1246
std::pair<OSStatus, std::size_t> Stream::do_ssl_read(char* buffer, std::size_t size) noexcept
1247
{
217,610✔
1248
    std::size_t processed = 0;
217,610✔
1249
    OSStatus result = SSLRead(m_ssl.get(), buffer, size, &processed);
217,610✔
1250
    return {result, processed};
217,610✔
1251
}
217,610✔
1252

1253

1254
std::size_t Stream::ssl_write(const char* data, std::size_t size, std::error_code& ec, Want& want) noexcept
1255
{
135,490✔
1256
    auto perform = [this, data, size]() noexcept {
135,506✔
1257
        return do_ssl_write(data, size);
135,506✔
1258
    };
135,506✔
1259
    std::size_t n = ssl_perform(std::move(perform), ec, want);
135,490✔
1260
    if (want == Want::nothing && n == 0 && !ec) {
135,490✔
1261
        // End of input on TCP socket
1262
        ec = MiscExtErrors::premature_end_of_input;
1263
    }
1264
    return n;
135,490✔
1265
}
135,490✔
1266

1267
std::pair<OSStatus, std::size_t> Stream::do_ssl_write(const char* data, std::size_t size) noexcept
1268
{
135,504✔
1269
    m_last_error = {};
135,504✔
1270

1271
    REALM_ASSERT(size >= m_num_partially_written_bytes);
135,504✔
1272
    data += m_num_partially_written_bytes;
135,504✔
1273
    size -= m_num_partially_written_bytes;
135,504✔
1274

1275
    std::size_t processed = 0;
135,504✔
1276
    OSStatus result = SSLWrite(m_ssl.get(), data, size, &processed);
135,504✔
1277

1278
    if (result != noErr) {
135,504✔
1279
        // Map errors that indicate the connection is closed to broken_pipe, for
1280
        // consistency with OpenSSL.
1281
        if (REALM_LIKELY(result == errSSLWouldBlock)) {
3,250✔
1282
            m_num_partially_written_bytes += processed;
3,246✔
1283
        }
3,246✔
1284
        else if (result == errSSLClosedGraceful || result == errSSLClosedAbort || result == errSSLClosedNoNotify) {
4✔
1285
            result = errSecIO;
2✔
1286
            m_last_error = error::broken_pipe;
2✔
1287
        }
2✔
1288
        processed = 0;
3,250✔
1289
    }
3,250✔
1290
    else {
132,254✔
1291
        processed += m_num_partially_written_bytes;
132,254✔
1292
        m_num_partially_written_bytes = 0;
132,254✔
1293
    }
132,254✔
1294

1295
    return {result, processed};
135,504✔
1296
}
135,504✔
1297

1298

1299
bool Stream::ssl_shutdown(std::error_code& ec, Want& want) noexcept
1300
{
26✔
1301
    auto perform = [this]() noexcept {
26✔
1302
        return do_ssl_shutdown();
26✔
1303
    };
26✔
1304
    std::size_t n = ssl_perform(std::move(perform), ec, want);
26✔
1305
    REALM_ASSERT(n == 0 || n == 1);
26✔
1306
    return (n > 0);
26✔
1307
}
26✔
1308

1309
std::pair<OSStatus, std::size_t> Stream::do_ssl_shutdown() noexcept
1310
{
26✔
1311
    SSLSessionState previousState;
26✔
1312
    if (OSStatus result = SSLGetSessionState(m_ssl.get(), &previousState)) {
26✔
1313
        return {result, false};
1314
    }
1315
    if (OSStatus result = SSLClose(m_ssl.get())) {
26✔
1316
        return {result, false};
1317
    }
1318

1319
    // SSLClose returns noErr if it encountered an I/O error. We can still
1320
    // detect such errors if they originated from our underlying tcp_read /
1321
    // tcp_write functions as we'll have set m_last_error in such cases. This
1322
    // allows us to reconstruct the I/O error and communicate it to our caller.
1323
    if (m_last_error) {
26✔
1324
        return {errSecIO, false};
2✔
1325
    }
2✔
1326
    return {noErr, previousState == kSSLClosed};
24✔
1327
}
24✔
1328

1329

1330
OSStatus Stream::tcp_read(SSLConnectionRef connection, void* data, std::size_t* length) noexcept
1331
{
398,554✔
1332
    return static_cast<Stream*>(const_cast<void*>(connection))->tcp_read(data, length);
398,554✔
1333
}
398,554✔
1334

1335
OSStatus Stream::tcp_read(void* data, std::size_t* length) noexcept
1336
{
398,544✔
1337
    Service::Descriptor& desc = m_tcp_socket.m_desc;
398,544✔
1338
    std::error_code ec;
398,544✔
1339
    std::size_t bytes_read = desc.read_some(reinterpret_cast<char*>(data), *length, ec);
398,544✔
1340

1341
    m_last_operation = BlockingOperation::read;
398,544✔
1342

1343
    // A successful but short read should be treated the same as EAGAIN.
1344
    if (!ec && bytes_read < *length) {
398,544✔
1345
        ec = error::resource_unavailable_try_again;
56✔
1346
    }
56✔
1347

1348
    *length = bytes_read;
398,544✔
1349
    m_last_error = ec;
398,544✔
1350

1351
    if (ec) {
398,544✔
1352
        if (REALM_UNLIKELY(ec == MiscExtErrors::end_of_input)) {
2,450✔
1353
            return noErr;
8✔
1354
        }
8✔
1355
        if (ec == error::resource_unavailable_try_again) {
2,442✔
1356
            return errSSLWouldBlock;
2,442✔
1357
        }
2,442✔
1358
        return errSecIO;
1359
    }
1360
    return noErr;
396,094✔
1361
}
396,094✔
1362

1363
OSStatus Stream::tcp_write(SSLConnectionRef connection, const void* data, std::size_t* length) noexcept
1364
{
236,634✔
1365
    return static_cast<Stream*>(const_cast<void*>(connection))->tcp_write(data, length);
236,634✔
1366
}
236,634✔
1367

1368
OSStatus Stream::tcp_write(const void* data, std::size_t* length) noexcept
1369
{
236,626✔
1370
    Service::Descriptor& desc = m_tcp_socket.m_desc;
236,626✔
1371
    std::error_code ec;
236,626✔
1372
    std::size_t bytes_written = desc.write_some(reinterpret_cast<const char*>(data), *length, ec);
236,626✔
1373

1374
    m_last_operation = BlockingOperation::write;
236,626✔
1375

1376
    // A successful but short write should be treated the same as EAGAIN.
1377
    if (!ec && bytes_written < *length) {
236,626✔
1378
        ec = error::resource_unavailable_try_again;
1,788✔
1379
    }
1,788✔
1380

1381
    *length = bytes_written;
236,626✔
1382
    m_last_error = ec;
236,626✔
1383

1384
    if (ec) {
236,626✔
1385
        if (ec == error::resource_unavailable_try_again) {
103,890✔
1386
            return errSSLWouldBlock;
103,890✔
1387
        }
103,890✔
1388
        return errSecIO;
2,147,483,647✔
1389
    }
2,147,483,647✔
1390
    return noErr;
132,744✔
1391
}
132,744✔
1392

1393

1394
#else // !REALM_HAVE_OPENSSL && !REALM_HAVE_SECURE_TRANSPORT
1395

1396

1397
void Context::ssl_init()
1398
{
1399
    throw ProtocolNotSupported();
1400
}
1401

1402

1403
void Context::ssl_destroy() noexcept {}
1404

1405

1406
void Stream::ssl_init() {}
1407

1408

1409
void Stream::ssl_destroy() noexcept {}
1410

1411

1412
void Context::ssl_use_certificate_chain_file(const std::string&, std::error_code&) {}
1413

1414

1415
void Context::ssl_use_private_key_file(const std::string&, std::error_code&) {}
1416

1417

1418
void Context::ssl_use_default_verify(std::error_code&) {}
1419

1420

1421
void Context::ssl_use_verify_file(const std::string&, std::error_code&) {}
1422

1423

1424
void Stream::ssl_set_verify_mode(VerifyMode, std::error_code&) {}
1425

1426

1427
void Stream::ssl_set_host_name(const std::string&, std::error_code&) {}
1428

1429

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

1432

1433
void Stream::ssl_handshake(std::error_code&, Want&) noexcept {}
1434

1435

1436
std::size_t Stream::ssl_read(char*, std::size_t, std::error_code&, Want&) noexcept
1437
{
1438
    return 0;
1439
}
1440

1441

1442
std::size_t Stream::ssl_write(const char*, std::size_t, std::error_code&, Want&) noexcept
1443
{
1444
    return 0;
1445
}
1446

1447

1448
bool Stream::ssl_shutdown(std::error_code&, Want&) noexcept
1449
{
1450
    return false;
1451
}
1452

1453
#endif // ! REALM_HAVE_OPENSSL
1454

1455

1456
} // namespace ssl
1457
} // namespace network
1458
} // namespace sync
1459
} // 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