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

realm / realm-core / 1658

10 Sep 2023 11:58PM UTC coverage: 91.222% (-0.04%) from 91.263%
1658

push

Evergreen

GitHub
Merge pull request #6938 from realm/tg/tls-error-reporting

95840 of 175760 branches covered (0.0%)

142 of 146 new or added lines in 7 files covered. (97.26%)

290 existing lines in 22 files now uncovered.

233460 of 255926 relevant lines covered (91.22%)

6997123.82 hits per line

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

97.11
/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
bool verify_certificate_from_root_cert(const char* root_cert, X509* server_cert)
37
{
310✔
38
    bool verified = false;
310✔
39
    BIO* bio;
310✔
40
    X509* x509;
310✔
41
    EVP_PKEY* pkey;
310✔
42

310✔
43
    bio = BIO_new_mem_buf(const_cast<char*>(root_cert), -1);
310✔
44
    if (!bio)
310✔
45
        goto out;
46

310✔
47
    x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
310✔
48
    if (!x509)
310✔
49
        goto free_bio;
50

310✔
51
    pkey = X509_get_pubkey(x509);
310✔
52
    if (!pkey)
310✔
53
        goto free_x509;
54

310✔
55
    verified = (X509_verify(server_cert, pkey) == 1);
310✔
56

310✔
57
    EVP_PKEY_free(pkey);
310✔
58
free_x509:
310✔
59
    X509_free(x509);
310✔
60
free_bio:
310✔
61
    BIO_free(bio);
310✔
62
out:
310✔
63
    return verified;
310✔
64
}
310✔
65

66
bool verify_certificate_from_root_certs(X509* server_cert, util::Logger* logger)
67
{
2✔
68
    std::size_t num_certs = sizeof(root_certs) / sizeof(root_certs[0]);
2✔
69

2✔
70
    if (logger)
2✔
71
        logger->info("Verifying server SSL certificate using %1 root certificates", num_certs);
2✔
72

2✔
73
    for (std::size_t i = 0; i < num_certs; ++i) {
312✔
74
        const char* root_cert = root_certs[i];
310✔
75
        bool verified = verify_certificate_from_root_cert(root_cert, server_cert);
310✔
76
        if (verified) {
310✔
77
            if (logger)
78
                logger->debug("Server SSL certificate verified using root certificate(%1):\n%2", i, root_cert);
79
            return true;
80
        }
81
    }
310✔
82

2✔
83
    if (logger)
2✔
84
        logger->error("The server certificate was not signed by any root certificate");
2✔
85
    return false;
2✔
86
}
2✔
87

88
#endif // REALM_INCLUDE_CERTS
89

90

91
#if REALM_HAVE_OPENSSL && (OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER))
92

93
// These must be made to execute before main() is called, i.e., before there is
94
// any chance of threads being spawned.
95
struct OpensslInit {
96
    std::unique_ptr<std::mutex[]> mutexes;
97
    OpensslInit();
98
    ~OpensslInit();
99
};
100

101
OpensslInit g_openssl_init;
102

103

104
void openssl_locking_func(int mode, int i, const char*, int)
105
{
106
    if (mode & CRYPTO_LOCK) {
107
        g_openssl_init.mutexes[i].lock();
108
    }
109
    else {
110
        g_openssl_init.mutexes[i].unlock();
111
    }
112
}
113

114

115
OpensslInit::OpensslInit()
116
{
117
    SSL_library_init();
118
    SSL_load_error_strings();
119
    OpenSSL_add_all_algorithms();
120
    std::size_t n = CRYPTO_num_locks();
121
    mutexes.reset(new std::mutex[n]); // Throws
122
    CRYPTO_set_locking_callback(&openssl_locking_func);
123
    /*
124
    #if !defined(SSL_OP_NO_COMPRESSION) && (OPENSSL_VERSION_NUMBER >= 0x00908000L)
125
        null_compression_methods_ = sk_SSL_COMP_new_null();
126
    #endif
127
    */
128
}
129

130

131
OpensslInit::~OpensslInit()
132
{
133
    /*
134
    #if !defined(SSL_OP_NO_COMPRESSION) && (OPENSSL_VERSION_NUMBER >= 0x00908000L)
135
        sk_SSL_COMP_free(null_compression_methods_);
136
    #endif
137
    */
138
    CRYPTO_set_locking_callback(0);
139
    ERR_free_strings();
140
#if OPENSSL_VERSION_NUMBER < 0x10000000L
141
    ERR_remove_state(0);
142
#else
143
    ERR_remove_thread_state(0);
144
#endif
145
    EVP_cleanup();
146
    CRYPTO_cleanup_all_ex_data();
147
    CONF_modules_unload(1);
148
}
149

150
#endif // REALM_HAVE_OPENSSL && (OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER))
151

152
} // unnamed namespace
153

154

155
namespace realm {
156
namespace sync {
157
namespace network {
158
namespace ssl {
159

160
ErrorCategory error_category;
161

162

163
const char* ErrorCategory::name() const noexcept
164
{
×
165
    return "realm.sync.network.ssl";
×
166
}
×
167

168

169
std::string ErrorCategory::message(int value) const
170
{
×
171
    switch (Errors(value)) {
×
NEW
172
        case Errors::tls_handshake_failed:
×
173
            return "SSL certificate rejected"; // Throws
×
174
    }
×
175
    REALM_ASSERT(false);
×
176
    return {};
×
177
}
×
178

179

180
bool ErrorCategory::equivalent(const std::error_code& ec, int condition) const noexcept
181
{
10✔
182
    switch (Errors(condition)) {
10✔
183
        case Errors::tls_handshake_failed:
10✔
184
#if REALM_HAVE_OPENSSL
6✔
185
            return ec.category() == openssl_error_category;
6✔
186
#elif REALM_HAVE_SECURE_TRANSPORT
187
            return ec.category() == secure_transport_error_category;
4✔
188
#else
189
            static_cast<void>(ec);
190
            return false;
191
#endif
UNCOV
192
    }
×
UNCOV
193
    return false;
×
UNCOV
194
}
×
195

196
} // namespace ssl
197

198

199
OpensslErrorCategory openssl_error_category;
200

201

202
const char* OpensslErrorCategory::name() const noexcept
203
{
6✔
204
    return "openssl";
6✔
205
}
6✔
206

207

208
std::string OpensslErrorCategory::message(int value) const
209
{
18✔
210
    const char* message = "Unknown error";
18✔
211
#if REALM_HAVE_OPENSSL
18✔
212
    if (const char* s = ERR_reason_error_string(value))
18✔
213
        message = s;
18✔
214
#endif
18✔
215
    return util::format("OpenSSL error: %1 (%2)", message, value); // Throws
18✔
216
}
18✔
217

218

219
SecureTransportErrorCategory secure_transport_error_category;
220

221

222
const char* SecureTransportErrorCategory::name() const noexcept
UNCOV
223
{
×
UNCOV
224
    return "securetransport";
×
UNCOV
225
}
×
226

227

228
std::string SecureTransportErrorCategory::message(int value) const
229
{
4✔
230
    std::string message = "Unknown error";
4✔
231
#if REALM_HAVE_SECURE_TRANSPORT
4✔
232
#if __has_builtin(__builtin_available)
4✔
233
    if (__builtin_available(iOS 11.3, macOS 10.3, tvOS 11.3, watchOS 4.3, *)) {
4✔
234
        auto status = OSStatus(value);
4✔
235
        void* reserved = nullptr;
4✔
236
        if (auto cf_message = adoptCF(SecCopyErrorMessageString(status, reserved)))
4✔
237
            message = cfstring_to_std_string(cf_message.get());
4✔
238
    }
4✔
239
#endif // __has_builtin(__builtin_available)
4✔
240
#endif // REALM_HAVE_SECURE_TRANSPORT
4✔
241

242
    return util::format("SecureTransport error: %1 (%2)", message, value); // Throws
4✔
243
}
4✔
244

245

246
namespace ssl {
247

248
const char* ProtocolNotSupported::what() const noexcept
UNCOV
249
{
×
UNCOV
250
    return "SSL/TLS protocol not supported";
×
UNCOV
251
}
×
252

253

254
std::error_code Stream::handshake(std::error_code& ec)
255
{
52✔
256
    REALM_ASSERT(!m_tcp_socket.m_read_oper || !m_tcp_socket.m_read_oper->in_use());
52!
257
    REALM_ASSERT(!m_tcp_socket.m_write_oper || !m_tcp_socket.m_write_oper->in_use());
52✔
258
    m_tcp_socket.m_desc.ensure_blocking_mode(); // Throws
52✔
259
    Want want = Want::nothing;
52✔
260
    ssl_handshake(ec, want);
52✔
261
    REALM_ASSERT(want == Want::nothing);
52✔
262
    return ec;
52✔
263
}
52✔
264

265

266
std::error_code Stream::shutdown(std::error_code& ec)
267
{
40✔
268
    REALM_ASSERT(!m_tcp_socket.m_write_oper || !m_tcp_socket.m_write_oper->in_use());
40✔
269
    m_tcp_socket.m_desc.ensure_blocking_mode(); // Throws
40✔
270
    Want want = Want::nothing;
40✔
271
    ssl_shutdown(ec, want);
40✔
272
    REALM_ASSERT(want == Want::nothing);
40✔
273
    return ec;
40✔
274
}
40✔
275

276

277
#if REALM_HAVE_OPENSSL
278

279
void Context::ssl_init()
280
{
108✔
281
    ERR_clear_error();
108✔
282

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

108✔
287
    SSL_CTX* ssl_ctx = SSL_CTX_new(method);
108✔
288
    if (REALM_UNLIKELY(!ssl_ctx)) {
108✔
289
        std::error_code ec(int(ERR_get_error()), openssl_error_category);
290
        throw std::system_error(ec);
291
    }
292

108✔
293
    // Disable use of older protocol versions (SSLv2 and SSLv3).
108✔
294
    // Disable SSL compression by default, as compression is unavailable
108✔
295
    // with Apple's Secure Transport API.
108✔
296
    long options = 0;
108✔
297
    options |= SSL_OP_NO_SSLv2;
108✔
298
    options |= SSL_OP_NO_SSLv3;
108✔
299
    options |= SSL_OP_NO_COMPRESSION;
108✔
300
    SSL_CTX_set_options(ssl_ctx, options);
108✔
301

108✔
302
    m_ssl_ctx = ssl_ctx;
108✔
303
}
108✔
304

305

306
void Context::ssl_destroy() noexcept
307
{
108✔
308
    /*
108✔
309
        if (handle_->default_passwd_callback_userdata) {
108✔
310
            detail::password_callback_base* callback =
108✔
311
       static_cast<detail::password_callback_base*>(handle_->default_passwd_callback_userdata); delete callback;
108✔
312
            handle_->default_passwd_callback_userdata = nullptr;
108✔
313
        }
108✔
314

108✔
315
        if (SSL_CTX_get_app_data(handle_)) {
108✔
316
            detail::verify_callback_base* callback =
108✔
317
       static_cast<detail::verify_callback_base*>(SSL_CTX_get_app_data(handle_)); delete callback;
108✔
318
            SSL_CTX_set_app_data(handle_, nullptr);
108✔
319
        }
108✔
320
    */
108✔
321
    SSL_CTX_free(m_ssl_ctx);
108✔
322
}
108✔
323

324

325
void Context::ssl_use_certificate_chain_file(const std::string& path, std::error_code& ec)
326
{
54✔
327
    ERR_clear_error();
54✔
328
    int ret = SSL_CTX_use_certificate_chain_file(m_ssl_ctx, path.c_str());
54✔
329
    if (REALM_UNLIKELY(ret != 1)) {
54✔
330
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
331
        return;
332
    }
333
    ec = std::error_code();
54✔
334
}
54✔
335

336

337
void Context::ssl_use_private_key_file(const std::string& path, std::error_code& ec)
338
{
54✔
339
    ERR_clear_error();
54✔
340
    int type = SSL_FILETYPE_PEM;
54✔
341
    int ret = SSL_CTX_use_PrivateKey_file(m_ssl_ctx, path.c_str(), type);
54✔
342
    if (REALM_UNLIKELY(ret != 1)) {
54✔
343
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
344
        return;
345
    }
346
    ec = std::error_code();
54✔
347
}
54✔
348

349

350
void Context::ssl_use_default_verify(std::error_code& ec)
351
{
2✔
352
    ERR_clear_error();
2✔
353
    int ret = SSL_CTX_set_default_verify_paths(m_ssl_ctx);
2✔
354
    if (ret != 1) {
2✔
355
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
356
        return;
357
    }
358
    ec = std::error_code();
2✔
359
}
2✔
360

361

362
void Context::ssl_use_verify_file(const std::string& path, std::error_code& ec)
363
{
14✔
364
    ERR_clear_error();
14✔
365
    int ret = SSL_CTX_load_verify_locations(m_ssl_ctx, path.c_str(), nullptr);
14✔
366
    if (ret != 1) {
14✔
367
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
368
        return;
369
    }
370

14✔
371
    ec = std::error_code();
14✔
372
}
14✔
373

374
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
375
class Stream::BioMethod {
376
public:
377
    BIO_METHOD* bio_method;
378

379
    BioMethod()
380
    {
6✔
381
        const char* name = "realm::util::Stream::BioMethod";
6✔
382
        bio_method = BIO_meth_new(BIO_get_new_index(), name);
6✔
383
        if (!bio_method)
6✔
384
            throw util::bad_alloc();
385

6✔
386
        BIO_meth_set_write(bio_method, &Stream::bio_write);
6✔
387
        BIO_meth_set_read(bio_method, &Stream::bio_read);
6✔
388
        BIO_meth_set_puts(bio_method, &Stream::bio_puts);
6✔
389
        BIO_meth_set_gets(bio_method, nullptr);
6✔
390
        BIO_meth_set_ctrl(bio_method, &Stream::bio_ctrl);
6✔
391
        BIO_meth_set_create(bio_method, &Stream::bio_create);
6✔
392
        BIO_meth_set_destroy(bio_method, &Stream::bio_destroy);
6✔
393
        BIO_meth_set_callback_ctrl(bio_method, nullptr);
6✔
394
    }
6✔
395

396
    ~BioMethod()
397
    {
398
        BIO_meth_free(bio_method);
399
    }
400
};
401
#else
402
class Stream::BioMethod {
403
public:
404
    BIO_METHOD* bio_method;
405

406
    BioMethod()
407
    {
408
        bio_method = new BIO_METHOD{
409
            BIO_TYPE_SOCKET,      // int type
410
            nullptr,              // const char* name
411
            &Stream::bio_write,   // int (*bwrite)(BIO*, const char*, int)
412
            &Stream::bio_read,    // int (*bread)(BIO*, char*, int)
413
            &Stream::bio_puts,    // int (*bputs)(BIO*, const char*)
414
            nullptr,              // int (*bgets)(BIO*, char*, int)
415
            &Stream::bio_ctrl,    // long (*ctrl)(BIO*, int, long, void*)
416
            &Stream::bio_create,  // int (*create)(BIO*)
417
            &Stream::bio_destroy, // int (*destroy)(BIO*)
418
            nullptr               // long (*callback_ctrl)(BIO*, int, bio_info_cb*)
419
        };
420
    }
421

422
    ~BioMethod()
423
    {
424
        delete bio_method;
425
    }
426
};
427
#endif
428

429

430
Stream::BioMethod Stream::s_bio_method;
431

432

433
#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER)
434

435
namespace {
436

437
// check_common_name() checks that \param  server_cert constains host_name
438
// as Common Name. The function is used by verify_callback() for
439
// OpenSSL versions before 1.0.2.
440
bool check_common_name(X509* server_cert, const std::string& host_name)
441
{
442
    // Find the position of the Common Name field in the Subject field of the certificate
443
    int common_name_loc = -1;
444
    common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name(server_cert), NID_commonName, -1);
445
    if (common_name_loc < 0)
446
        return false;
447

448
    // Extract the Common Name field
449
    X509_NAME_ENTRY* common_name_entry;
450
    common_name_entry = X509_NAME_get_entry(X509_get_subject_name(server_cert), common_name_loc);
451
    if (!common_name_entry)
452
        return false;
453

454
    // Convert the Common Namefield to a C string
455
    ASN1_STRING* common_name_asn1;
456
    common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
457
    if (!common_name_asn1)
458
        return false;
459

460
    char* common_name_str = reinterpret_cast<char*>(ASN1_STRING_data(common_name_asn1));
461

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

466
    bool names_equal = (host_name == common_name_str);
467
    return names_equal;
468
}
469

470
// check_common_name() checks that \param  server_cert constains host_name
471
// in the Subject Alternative Name DNS section. The function is used by verify_callback()
472
// for OpenSSL versions before 1.0.2.
473
bool check_san(X509* server_cert, const std::string& host_name)
474
{
475
    STACK_OF(GENERAL_NAME) * san_names;
476

477
    // Try to extract the names within the SAN extension from the certificate
478
    san_names =
479
        static_cast<STACK_OF(GENERAL_NAME)*>(X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
480
    if (!san_names)
481
        return false;
482

483
    int san_names_nb = sk_GENERAL_NAME_num(san_names);
484

485
    bool found = false;
486

487
    // Check each name within the extension
488
    for (int i = 0; i < san_names_nb; ++i) {
489
        const GENERAL_NAME* current_name = sk_GENERAL_NAME_value(san_names, i);
490

491
        if (current_name->type == GEN_DNS) {
492
            // Current name is a DNS name
493
            char* dns_name = static_cast<char*>(ASN1_STRING_data(current_name->d.dNSName));
494

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

499
            if (host_name == dns_name) {
500
                found = true;
501
                break;
502
            }
503
        }
504
    }
505

506
    sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
507

508
    return found;
509
}
510

511
} // namespace
512

513
int Stream::verify_callback_using_hostname(int preverify_ok, X509_STORE_CTX* ctx) noexcept
514
{
515
    if (preverify_ok != 1)
516
        return preverify_ok;
517

518
    X509* server_cert = X509_STORE_CTX_get_current_cert(ctx);
519

520
    int err = X509_STORE_CTX_get_error(ctx);
521
    if (err != X509_V_OK)
522
        return 0;
523

524
    int depth = X509_STORE_CTX_get_error_depth(ctx);
525

526
    // We only inspect the certificate at depth = 0.
527
    if (depth > 0)
528
        return preverify_ok;
529

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

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

536
    const std::string& host_name = stream->m_host_name;
537

538
    if (check_common_name(server_cert, host_name))
539
        return 1;
540

541
    if (check_san(server_cert, host_name))
542
        return 1;
543

544
    return 0;
545
}
546

547
#endif
548

549

550
void Stream::ssl_set_verify_mode(VerifyMode mode, std::error_code& ec)
551
{
22✔
552
    int mode_2 = 0;
22✔
553
    switch (mode) {
22✔
554
        case VerifyMode::none:
555
            break;
556
        case VerifyMode::peer:
22✔
557
            mode_2 = SSL_VERIFY_PEER;
22✔
558
            break;
22✔
559
    }
22✔
560

22✔
561
    int rc = SSL_set_ex_data(m_ssl, 0, this);
22✔
562
    if (rc == 0) {
22✔
563
        ec = std::error_code(int(ERR_get_error()), openssl_error_category);
564
        return;
565
    }
566

22✔
567
#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER)
568
    SSL_set_verify(m_ssl, mode_2, &Stream::verify_callback_using_hostname);
569
#else
570
    // verify_callback is nullptr.
22✔
571
    SSL_set_verify(m_ssl, mode_2, nullptr);
22✔
572
#endif
22✔
573
    ec = std::error_code();
22✔
574
}
22✔
575

576

577
void Stream::ssl_set_host_name(const std::string& host_name, std::error_code& ec)
578
{
24✔
579
    // Enable Server Name Indication (SNI) extension
24✔
580
#if OPENSSL_VERSION_NUMBER >= 0x10101000L
24✔
581
    {
24✔
582
#ifndef _WIN32
24✔
583
#pragma GCC diagnostic push
24✔
584
#pragma GCC diagnostic ignored "-Wold-style-cast"
24✔
585
#endif
24✔
586
        auto ret = SSL_set_tlsext_host_name(m_ssl, host_name.c_str());
24✔
587
#ifndef _WIN32
24✔
588
#pragma GCC diagnostic pop
24✔
589
#endif
24✔
590
        if (ret == 0) {
24✔
591
            ec = std::error_code(int(ERR_get_error()), openssl_error_category);
592
            return;
593
        }
594
    }
24✔
595
#else
596
    static_cast<void>(host_name);
597
    static_cast<void>(ec);
598
#endif
599

24✔
600
    // Enable host name check during certificate validation
24✔
601
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)
24✔
602
    {
24✔
603
        X509_VERIFY_PARAM* param = SSL_get0_param(m_ssl);
24✔
604
        X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
24✔
605
        auto ret = X509_VERIFY_PARAM_set1_host(param, host_name.c_str(), 0);
24✔
606
        if (ret == 0) {
24✔
607
            ec = std::error_code(int(ERR_get_error()), openssl_error_category);
608
            return;
609
        }
610
    }
24✔
611
#else
612
    static_cast<void>(host_name);
613
    static_cast<void>(ec);
614
#endif
615
}
24✔
616

617
void Stream::ssl_use_verify_callback(const std::function<SSLVerifyCallback>& callback, std::error_code&)
618
{
6✔
619
    m_ssl_verify_callback = &callback;
6✔
620

6✔
621
    SSL_set_verify(m_ssl, SSL_VERIFY_PEER, &Stream::verify_callback_using_delegate);
6✔
622
}
6✔
623

624
#ifndef _WIN32
625
#pragma GCC diagnostic push
626
#pragma GCC diagnostic ignored "-Wold-style-cast"
627
#endif
628

629
#if REALM_INCLUDE_CERTS
630
void Stream::ssl_use_included_certificates(std::error_code&)
631
{
2✔
632
    REALM_ASSERT(!m_ssl_verify_callback);
2✔
633

2✔
634
    SSL_set_verify(m_ssl, SSL_VERIFY_PEER, &Stream::verify_callback_using_root_certs);
2✔
635
}
2✔
636

637
int Stream::verify_callback_using_root_certs(int preverify_ok, X509_STORE_CTX* ctx)
638
{
2✔
639
    if (preverify_ok)
2✔
640
        return 1;
641

2✔
642
    X509* server_cert = X509_STORE_CTX_get_current_cert(ctx);
2✔
643

2✔
644
    SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
2✔
645
    Stream* stream = static_cast<Stream*>(SSL_get_ex_data(ssl, 0));
2✔
646
    REALM_ASSERT(stream);
2✔
647

2✔
648
    util::Logger* logger = stream->logger;
2✔
649

2✔
650
    const std::string& host_name = stream->m_host_name;
2✔
651
    port_type server_port = stream->m_server_port;
2✔
652

2✔
653
    if (logger && logger->would_log(util::Logger::Level::debug)) {
2✔
654
        BIO* bio = BIO_new(BIO_s_mem());
2✔
655
        if (bio) {
2✔
656
            int ret = PEM_write_bio_X509(bio, server_cert);
2✔
657
            if (ret) {
2✔
658
                BUF_MEM* buffer;
2✔
659
                BIO_get_mem_ptr(bio, &buffer);
2✔
660

2✔
661
                const char* pem_data = buffer->data;
2✔
662
                std::size_t pem_size = buffer->length;
2✔
663

2✔
664
                logger->debug("Verifying server SSL certificate using root certificates, "
2✔
665
                              "host name = %1, server port = %2, certificate =\n%3",
2✔
666
                              host_name, server_port, StringData{pem_data, pem_size});
2✔
667
            }
2✔
668
            BIO_free(bio);
2✔
669
        }
2✔
670
    }
2✔
671

2✔
672
    bool valid = verify_certificate_from_root_certs(server_cert, logger);
2✔
673
    if (!valid && logger) {
2✔
674
        logger->error("server SSL certificate rejected using root certificates, "
2✔
675
                      "host name = %1, server port = %2",
2✔
676
                      host_name, server_port);
2✔
677
    }
2✔
678

2✔
679
    return int(valid);
2✔
680
}
2✔
681
#endif
682

683
int Stream::verify_callback_using_delegate(int preverify_ok, X509_STORE_CTX* ctx) noexcept
684
{
10✔
685
    X509* server_cert = X509_STORE_CTX_get_current_cert(ctx);
10✔
686

10✔
687
    int depth = X509_STORE_CTX_get_error_depth(ctx);
10✔
688

10✔
689
    BIO* bio = BIO_new(BIO_s_mem());
10✔
690
    if (!bio) {
10✔
691
        // certificate rejected if a memory error occurs.
692
        return 0;
693
    }
694

10✔
695
    int ret = PEM_write_bio_X509(bio, server_cert);
10✔
696
    if (!ret) {
10✔
697
        BIO_free(bio);
698
        return 0;
699
    }
700

10✔
701
    BUF_MEM* buffer;
10✔
702
    BIO_get_mem_ptr(bio, &buffer);
10✔
703

10✔
704
    const char* pem_data = buffer->data;
10✔
705
    std::size_t pem_size = buffer->length;
10✔
706

10✔
707
    SSL* ssl = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
10✔
708
    Stream* stream = static_cast<Stream*>(SSL_get_ex_data(ssl, 0));
10✔
709
    const std::string& host_name = stream->m_host_name;
10✔
710
    port_type server_port = stream->m_server_port;
10✔
711

10✔
712
    REALM_ASSERT(stream->m_ssl_verify_callback);
10✔
713
    const std::function<SSLVerifyCallback>& callback = *stream->m_ssl_verify_callback;
10✔
714

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

10✔
722
    BIO_free(bio);
10✔
723
    return int(valid);
10✔
724
}
10✔
725

726
#ifndef _WIN32
727
#pragma GCC diagnostic pop
728
#endif
729

730
void Stream::ssl_init()
731
{
122✔
732
    SSL_CTX* ssl_ctx = m_ssl_context.m_ssl_ctx;
122✔
733
    SSL* ssl = SSL_new(ssl_ctx);
122✔
734
    if (REALM_UNLIKELY(!ssl)) {
122✔
735
        std::error_code ec(int(ERR_get_error()), openssl_error_category);
736
        throw std::system_error(ec);
737
    }
738
    SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
122✔
739
#if defined(SSL_MODE_RELEASE_BUFFERS)
122✔
740
    SSL_set_mode(ssl, SSL_MODE_RELEASE_BUFFERS);
122✔
741
#endif
122✔
742

122✔
743
    BIO* bio = BIO_new(s_bio_method.bio_method);
122✔
744

122✔
745
    if (REALM_UNLIKELY(!bio)) {
122✔
746
        SSL_free(ssl);
747
        std::error_code ec(int(ERR_get_error()), openssl_error_category);
748
        throw std::system_error(ec);
749
    }
750

122✔
751
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
122✔
752
    BIO_set_data(bio, this);
122✔
753
#else
754
    bio->ptr = this;
755
#endif
756

122✔
757
    SSL_set_bio(ssl, bio, bio);
122✔
758
    m_ssl = ssl;
122✔
759
}
122✔
760

761

762
void Stream::ssl_destroy() noexcept
763
{
122✔
764
    SSL_free(m_ssl);
122✔
765
}
122✔
766

767

768
int Stream::bio_write(BIO* bio, const char* data, int size) noexcept
769
{
177,316✔
770
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
177,316✔
771
    Stream& stream = *static_cast<Stream*>(BIO_get_data(bio));
177,316✔
772
#else
773
    Stream& stream = *static_cast<Stream*>(bio->ptr);
774
#endif
775
    Service::Descriptor& desc = stream.m_tcp_socket.m_desc;
177,316✔
776
    std::error_code ec;
177,316✔
777
    std::size_t n = desc.write_some(data, std::size_t(size), ec);
177,316✔
778

177,316✔
779
    BIO_clear_retry_flags(bio);
177,316✔
780
    if (ec) {
177,316✔
781
        if (REALM_UNLIKELY(ec != error::resource_unavailable_try_again)) {
340✔
782
            stream.m_bio_error_code = ec;
6✔
783
            return -1;
6✔
784
        }
6✔
785
        BIO_set_retry_write(bio);
334✔
786
        return -1;
334✔
787
    }
334✔
788
    return int(n);
176,976✔
789
}
176,976✔
790

791

792
int Stream::bio_read(BIO* bio, char* buffer, int size) noexcept
793
{
353,682✔
794
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
353,682✔
795
    Stream& stream = *static_cast<Stream*>(BIO_get_data(bio));
353,682✔
796
#else
797
    Stream& stream = *static_cast<Stream*>(bio->ptr);
798
#endif
799
    Service::Descriptor& desc = stream.m_tcp_socket.m_desc;
353,682✔
800
    std::error_code ec;
353,682✔
801
    std::size_t n = desc.read_some(buffer, std::size_t(size), ec);
353,682✔
802

353,682✔
803
    BIO_clear_retry_flags(bio);
353,682✔
804
    if (ec) {
353,682✔
805
        if (REALM_UNLIKELY(ec == MiscExtErrors::end_of_input)) {
188✔
806
            // This behaviour agrees with `crypto/bio/bss_sock.c` of OpenSSL.
4✔
807
            return 0;
4✔
808
        }
4✔
809
        if (REALM_UNLIKELY(ec != error::resource_unavailable_try_again)) {
184✔
810
            stream.m_bio_error_code = ec;
811
            return -1;
812
        }
813
        BIO_set_retry_read(bio);
184✔
814
        return -1;
184✔
815
    }
184✔
816
    return int(n);
353,494✔
817
}
353,494✔
818

819

820
int Stream::bio_puts(BIO* bio, const char* c_str) noexcept
821
{
822
    std::size_t size = std::strlen(c_str);
823
    return bio_write(bio, c_str, int(size));
824
}
825

826

827
long Stream::bio_ctrl(BIO*, int cmd, long, void*) noexcept
828
{
536✔
829
    switch (cmd) {
536✔
830
        case BIO_CTRL_EOF:
4✔
831
            return 0;
4✔
832
        case BIO_CTRL_PUSH:
280✔
833
        case BIO_CTRL_POP:
280✔
834
            // Ignoring in alignment with `crypto/bio/bss_sock.c` of OpenSSL.
280✔
835
            return 0;
280✔
836
        case BIO_CTRL_FLUSH:
280✔
837
            // Ignoring in alignment with `crypto/bio/bss_sock.c` of OpenSSL.
252✔
838
            return 1;
252✔
839
        default:
280✔
840
            REALM_ASSERT_EX(false, "Got BIO_ctrl with unknown command %d", cmd);
841
    }
536✔
842
    return 0;
536✔
843
}
536✔
844

845

846
int Stream::bio_create(BIO* bio) noexcept
847
{
122✔
848
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
122✔
849
    BIO_set_init(bio, 1);
122✔
850
    BIO_set_data(bio, nullptr);
122✔
851
    BIO_clear_flags(bio, 0);
122✔
852
    BIO_set_shutdown(bio, 0);
122✔
853
#else
854
    // In alignment with `crypto/bio/bss_sock.c` of OpenSSL.
855
    bio->init = 1;
856
    bio->num = 0;
857
    bio->ptr = nullptr;
858
    bio->flags = 0;
859
#endif
860
    return 1;
122✔
861
}
122✔
862

863

864
int Stream::bio_destroy(BIO*) noexcept
865
{
122✔
866
    return 1;
122✔
867
}
122✔
868

869

870
#elif REALM_HAVE_SECURE_TRANSPORT
871

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

874
void Context::ssl_init() {}
104✔
875

876
void Context::ssl_destroy() noexcept
877
{
104✔
878
#if REALM_HAVE_KEYCHAIN_APIS
104✔
879
    if (m_keychain) {
104✔
880
        m_keychain.reset();
52✔
881
        unlink(m_keychain_path.data());
52✔
882
        m_keychain_path = {};
52✔
883
    }
52✔
884
#endif
104✔
885
}
104✔
886

887
// Load certificates and/or keys from the specified PEM file. If keychain is non-null, the items will be
888
// imported into that keychain.
889
util::CFPtr<CFArrayRef> Context::load_pem_file(const std::string& path, SecKeychainRef keychain, std::error_code& ec)
890
{
118✔
891
    using util::adoptCF;
118✔
892
    using util::CFPtr;
118✔
893

894
    std::ifstream file(path);
118✔
895
    if (!file) {
118✔
896
        // Rely on the open attempt having set errno to a sensible value as ifstream's
897
        // own error reporting gives terribly generic error messages.
898
        ec = make_basic_system_error_code(errno);
899
        return util::CFPtr<CFArrayRef>();
900
    }
901
    std::vector<char> contents{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
118✔
902

903
    auto contentsCF = adoptCF(CFDataCreateWithBytesNoCopy(nullptr, reinterpret_cast<const UInt8*>(contents.data()),
118✔
904
                                                          contents.size(), kCFAllocatorNull));
118✔
905

906
    // If we don't need to import it into a keychain, try to interpret the data
907
    // as a certificate directly. This only works for DER files, so we fall back
908
    // to SecItemImport() on platforms which support that if this fails.
909
    if (keychain == nullptr) {
118✔
910
        if (auto certificate = adoptCF(SecCertificateCreateWithData(NULL, contentsCF.get()))) {
66✔
911
            auto ref = certificate.get();
2✔
912
            return adoptCF(CFArrayCreate(nullptr, const_cast<const void**>(reinterpret_cast<void**>(&ref)), 1,
2✔
913
                                         &kCFTypeArrayCallBacks));
2✔
914
        }
2✔
915

916
        // SecCertificateCreateWithData doesn't tell us why it failed, so just
917
        // report the error code that SecItemImport uses when given something
918
        // that's not a certificate
919
        ec = std::error_code(errSecUnknownFormat, secure_transport_error_category);
64✔
920
    }
64✔
921

922
    CFArrayRef items = nullptr;
116✔
923

924
#if REALM_HAVE_KEYCHAIN_APIS
116✔
925
    SecItemImportExportKeyParameters params{};
116✔
926
    params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
116✔
927

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

931
    SecExternalFormat format = kSecFormatUnknown;
116✔
932
    SecExternalItemType itemType = kSecItemTypeUnknown;
116✔
933
    if (OSStatus status =
116✔
934
            SecItemImport(contentsCF.get(), pathCF.get(), &format, &itemType, 0, &params, keychain, &items)) {
935
        ec = std::error_code(status, secure_transport_error_category);
936
        return util::CFPtr<CFArrayRef>();
937
    }
938
    ec = {};
116✔
939
#endif
116✔
940

941
    return adoptCF(items);
116✔
942
}
116✔
943

944
#if REALM_HAVE_KEYCHAIN_APIS
945

946
static std::string temporary_directory()
947
{
52✔
948
    auto ensure_trailing_slash = [](auto str) {
52✔
949
        return str.back() == '/' ? str : str + '/';
52✔
950
    };
52✔
951

952
    std::string path;
52✔
953
    path.resize(PATH_MAX);
52✔
954
    std::size_t result = confstr(_CS_DARWIN_USER_TEMP_DIR, &path[0], path.size());
52✔
955
    if (result && result <= path.size()) {
52✔
956
        path.resize(result - 1);
52✔
957
        return ensure_trailing_slash(std::move(path));
52✔
958
    }
52✔
959

960
    // We failed to retrieve temporary directory from confstr. Fall back to the TMPDIR
961
    // environment variable if we're not running with elevated privileges, and then to /tmp.
962
    if (!issetugid()) {
×
963
        path = getenv("TMPDIR");
964
        if (path.size()) {
×
965
            return ensure_trailing_slash(std::move(path));
966
        }
967
    }
968
    return "/tmp/";
969
}
970

971

972
std::error_code Context::open_temporary_keychain_if_needed()
973
{
52✔
974
    if (m_keychain) {
52✔
975
        return std::error_code();
976
    }
977

978
    std::string path = temporary_directory() + "realm-sync-ssl-XXXXXXXX.keychain";
52✔
979
    int fd = mkstemps(&path[0], std::strlen(".keychain"));
52✔
980
    if (fd < 0) {
52✔
981
        return make_basic_system_error_code(errno);
982
    }
983

984
    // Close and remove the file so that we can create a keychain in its place.
985
    close(fd);
52✔
986
    unlink(path.data());
52✔
987

988
    SecKeychainRef keychain = nullptr;
52✔
989
    std::string password = "";
52✔
990
    if (OSStatus status =
52✔
991
            SecKeychainCreate(path.data(), UInt32(password.size()), password.data(), false, nullptr, &keychain))
992
        return std::error_code(status, secure_transport_error_category);
993

994
    m_keychain = adoptCF(keychain);
52✔
995
    m_keychain_path = std::move(path);
52✔
996

997
    return std::error_code();
52✔
998
}
52✔
999

1000

1001
// Creates an identity from the certificate and private key. The private key must exist in m_keychain.
1002
std::error_code Context::update_identity_if_needed()
1003
{
104✔
1004
    // If we've not yet loaded both the certificate and private key there's nothing to do.
1005
    if (!m_certificate || !m_private_key) {
104✔
1006
        return std::error_code();
52✔
1007
    }
52✔
1008

1009
    SecIdentityRef identity = nullptr;
52✔
1010
    if (OSStatus status = SecIdentityCreateWithCertificate(m_keychain.get(), m_certificate.get(), &identity)) {
52✔
1011
        return std::error_code(status, secure_transport_error_category);
1012
    }
1013

1014
    m_identity = util::adoptCF(identity);
52✔
1015
    return std::error_code();
52✔
1016
}
52✔
1017

1018
#endif // REALM_HAVE_KEYCHAIN_APIS
1019

1020
void Context::ssl_use_certificate_chain_file(const std::string& path, std::error_code& ec)
1021
{
52✔
1022
#if !REALM_HAVE_KEYCHAIN_APIS
1023
    static_cast<void>(path);
1024
    ec = make_basic_system_error_code(ENOTSUP);
1025
#else
1026
    auto items = load_pem_file(path, nullptr, ec);
52✔
1027
    if (!items) {
52✔
1028
        REALM_ASSERT(ec);
×
1029
        return;
1030
    }
1031

1032
    if (CFArrayGetCount(items.get()) < 1) {
52✔
1033
        ec = std::error_code(errSecItemNotFound, secure_transport_error_category);
1034
        return;
1035
    }
1036

1037
    CFTypeRef item = CFArrayGetValueAtIndex(items.get(), 0);
52✔
1038
    if (CFGetTypeID(item) != SecCertificateGetTypeID()) {
52✔
1039
        ec = std::error_code(errSecItemNotFound, secure_transport_error_category);
1040
        return;
1041
    }
1042

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

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

1051
    ec = update_identity_if_needed();
52✔
1052
#endif
52✔
1053
}
52✔
1054

1055

1056
void Context::ssl_use_private_key_file(const std::string& path, std::error_code& ec)
1057
{
52✔
1058
#if !REALM_HAVE_KEYCHAIN_APIS
1059
    static_cast<void>(path);
1060
    ec = make_basic_system_error_code(ENOTSUP);
1061
#else
1062
    ec = open_temporary_keychain_if_needed();
52✔
1063
    if (ec) {
52✔
1064
        return;
1065
    }
1066

1067
    auto items = load_pem_file(path, m_keychain.get(), ec);
52✔
1068
    if (!items) {
52✔
1069
        return;
1070
    }
1071

1072
    if (CFArrayGetCount(items.get()) != 1) {
52✔
1073
        ec = std::error_code(errSecItemNotFound, secure_transport_error_category);
1074
        return;
1075
    }
1076

1077
    CFTypeRef item = CFArrayGetValueAtIndex(items.get(), 0);
52✔
1078
    if (CFGetTypeID(item) != SecKeyGetTypeID()) {
52✔
1079
        ec = std::error_code(errSecItemNotFound, secure_transport_error_category);
1080
        return;
1081
    }
1082

1083
    m_private_key = util::retainCF(reinterpret_cast<SecKeyRef>(const_cast<void*>(item)));
52✔
1084
    ec = update_identity_if_needed();
52✔
1085
#endif
52✔
1086
}
52✔
1087

1088
void Context::ssl_use_default_verify(std::error_code&) {}
2✔
1089

1090
void Context::ssl_use_verify_file(const std::string& path, std::error_code& ec)
1091
{
14✔
1092
#if REALM_HAVE_KEYCHAIN_APIS
14✔
1093
    m_trust_anchors = load_pem_file(path, m_keychain.get(), ec);
14✔
1094
#else
1095
    m_trust_anchors = load_pem_file(path, nullptr, ec);
1096
#endif
1097

1098
    if (m_trust_anchors && CFArrayGetCount(m_trust_anchors.get())) {
14✔
1099
        const void* leaf_certificate = CFArrayGetValueAtIndex(m_trust_anchors.get(), 0);
14✔
1100
        m_pinned_certificate =
14✔
1101
            adoptCF(SecCertificateCopyData(static_cast<SecCertificateRef>(const_cast<void*>(leaf_certificate))));
14✔
1102
    }
14✔
1103
    else {
1104
        m_pinned_certificate.reset();
1105
    }
1106
}
14✔
1107

1108
void Stream::ssl_init()
1109
{
114✔
1110
    SSLProtocolSide side = m_handshake_type == HandshakeType::client ? kSSLClientSide : kSSLServerSide;
62✔
1111
    m_ssl = util::adoptCF(SSLCreateContext(nullptr, side, kSSLStreamType));
114✔
1112
    if (OSStatus status = SSLSetIOFuncs(m_ssl.get(), Stream::tcp_read, Stream::tcp_write)) {
114✔
1113
        std::error_code ec(status, secure_transport_error_category);
1114
        throw std::system_error(ec);
1115
    }
1116
    if (OSStatus status = SSLSetConnection(m_ssl.get(), this)) {
114✔
1117
        std::error_code ec(status, secure_transport_error_category);
1118
        throw std::system_error(ec);
1119
    }
1120

1121
    // Require TLSv1 or greater.
1122
    if (OSStatus status = SSLSetProtocolVersionMin(m_ssl.get(), kTLSProtocol1)) {
114✔
1123
        std::error_code ec(status, secure_transport_error_category);
1124
        throw std::system_error(ec);
1125
    }
1126

1127
    // Break after certificate exchange to allow for customizing the verification process.
1128
    SSLSessionOption option = m_handshake_type == HandshakeType::client ? kSSLSessionOptionBreakOnServerAuth
114✔
1129
                                                                        : kSSLSessionOptionBreakOnClientAuth;
62✔
1130
    if (OSStatus status = SSLSetSessionOption(m_ssl.get(), option, true)) {
114✔
1131
        std::error_code ec(status, secure_transport_error_category);
1132
        throw std::system_error(ec);
1133
    }
1134

1135
#if REALM_HAVE_KEYCHAIN_APIS
114✔
1136
    if (m_ssl_context.m_identity && m_ssl_context.m_certificate_chain) {
114✔
1137
        // SSLSetCertificate expects an array containing the identity followed by the identity's certificate chain.
1138
        auto certificates = util::adoptCF(CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks));
62✔
1139
        CFArrayInsertValueAtIndex(certificates.get(), 0, m_ssl_context.m_identity.get());
62✔
1140

1141
        CFArrayRef certificate_chain = m_ssl_context.m_certificate_chain.get();
62✔
1142
        CFArrayAppendArray(certificates.get(), certificate_chain, CFRangeMake(0, CFArrayGetCount(certificate_chain)));
62✔
1143

1144
        if (OSStatus status = SSLSetCertificate(m_ssl.get(), certificates.get())) {
62✔
1145
            std::error_code ec(status, secure_transport_error_category);
1146
            throw std::system_error(ec);
1147
        }
1148
    }
62✔
1149
#endif
114✔
1150
}
114✔
1151

1152

1153
void Stream::ssl_destroy() noexcept
1154
{
114✔
1155
    m_ssl.reset();
114✔
1156
}
114✔
1157

1158

1159
void Stream::ssl_set_verify_mode(VerifyMode verify_mode, std::error_code& ec)
1160
{
16✔
1161
    m_verify_mode = verify_mode;
16✔
1162
    ec = std::error_code();
16✔
1163
}
16✔
1164

1165

1166
void Stream::ssl_set_host_name(const std::string& host_name, std::error_code& ec)
1167
{
18✔
1168
    if (OSStatus status = SSLSetPeerDomainName(m_ssl.get(), host_name.data(), host_name.size()))
18✔
1169
        ec = std::error_code(status, secure_transport_error_category);
1170
}
18✔
1171

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

1174
void Stream::ssl_handshake(std::error_code& ec, Want& want) noexcept
1175
{
258✔
1176
    auto perform = [this]() noexcept {
258✔
1177
        return do_ssl_handshake();
258✔
1178
    };
258✔
1179
    ssl_perform(std::move(perform), ec, want);
258✔
1180
}
258✔
1181

1182
std::pair<OSStatus, std::size_t> Stream::do_ssl_handshake() noexcept
1183
{
300✔
1184
    OSStatus result = SSLHandshake(m_ssl.get());
300✔
1185
    if (result != errSSLPeerAuthCompleted) {
300✔
1186
        return {result, 0};
252✔
1187
    }
252✔
1188

1189
    if (OSStatus status = verify_peer()) {
48✔
1190
        // When performing peer verification internally, verification failure results in SecureTransport
1191
        // sending a fatal alert to the peer, closing the connection. Sadly SecureTransport has no way
1192
        // to explicitly send a fatal alert when trust evaluation is handled externally. The best we can
1193
        // do is close the connection gracefully.
1194
        SSLClose(m_ssl.get());
6✔
1195
        return {status, 0};
6✔
1196
    }
6✔
1197

1198
    // Verification succeeded. Resume the handshake.
1199
    return do_ssl_handshake();
42✔
1200
}
42✔
1201

1202

1203
OSStatus Stream::verify_peer() noexcept
1204
{
48✔
1205
    switch (m_verify_mode) {
48✔
1206
        case VerifyMode::none:
32✔
1207
            // Peer verification is disabled.
1208
            return noErr;
32✔
1209

1210
        case VerifyMode::peer: {
16✔
1211
            SecTrustRef peerTrustRef = nullptr;
16✔
1212
            if (OSStatus status = SSLCopyPeerTrust(m_ssl.get(), &peerTrustRef)) {
16✔
1213
                return status;
1214
            }
1215

1216
            auto peerTrust = util::adoptCF(peerTrustRef);
16✔
1217

1218
            if (m_ssl_context.m_trust_anchors) {
16✔
1219
                if (OSStatus status =
14✔
1220
                        SecTrustSetAnchorCertificates(peerTrust.get(), m_ssl_context.m_trust_anchors.get())) {
1221
                    return status;
1222
                }
1223
                if (OSStatus status = SecTrustSetAnchorCertificatesOnly(peerTrust.get(), true)) {
14✔
1224
                    return status;
1225
                }
1226
            }
16✔
1227

1228
            // FIXME: SecTrustEvaluate can block if evaluation needs to fetch missing intermediate
1229
            // certificates or to check revocation using OCSP. Consider disabling these network
1230
            // fetches or doing async trust evaluation instead.
1231
#if __has_builtin(__builtin_available)
16✔
1232
            if (__builtin_available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0, *)) {
16✔
1233
                CFErrorRef cfErrorRef;
16✔
1234
                if (!SecTrustEvaluateWithError(peerTrust.get(), &cfErrorRef)) {
16✔
1235
                    auto cfError = util::adoptCF(cfErrorRef);
4✔
1236
                    if (logger) {
4✔
1237
                        auto errorStr = util::adoptCF(CFErrorCopyDescription(cfErrorRef));
4✔
1238
                        logger->debug("SSL peer verification failed: %1", cfstring_to_std_string(errorStr.get()));
4✔
1239
                    }
4✔
1240
                    return errSSLXCertChainInvalid;
4✔
1241
                }
4✔
1242
            }
1243
            else
1244
#endif
1245
            {
1246
                SecTrustResultType trustResult;
1247
                if (OSStatus status = SecTrustEvaluate(peerTrust.get(), &trustResult)) {
×
1248
                    return status;
1249
                }
1250

1251
                // A "proceed" result means the cert is explicitly trusted, e.g. "Always Trust" was selected.
1252
                // "Unspecified" means the cert has no explicit trust settings, but is implicitly OK since it
1253
                // chains back to a trusted root. Any other result means the cert is not trusted.
1254
                if (trustResult == kSecTrustResultRecoverableTrustFailure) {
×
1255
                    // Not trusted.
1256
                    return errSSLXCertChainInvalid;
1257
                }
1258
                if (trustResult != kSecTrustResultProceed && trustResult != kSecTrustResultUnspecified) {
×
1259
                    return errSSLBadCert;
1260
                }
1261
            }
12✔
1262

1263
            if (!m_ssl_context.m_pinned_certificate) {
12✔
1264
                // Certificate is trusted!
1265
                return noErr;
1266
            }
1267

1268
            // Verify that the certificate is one of our pinned certificates
1269
            // Loop backwards as the pinned certificate will normally be the last one
1270
            for (CFIndex i = SecTrustGetCertificateCount(peerTrust.get()); i > 0; --i) {
16✔
1271
                SecCertificateRef certificate = SecTrustGetCertificateAtIndex(peerTrust.get(), i - 1);
14✔
1272
                auto certificate_data = adoptCF(SecCertificateCopyData(certificate));
14✔
1273
                if (CFEqual(certificate_data.get(), m_ssl_context.m_pinned_certificate.get())) {
14✔
1274
                    return noErr;
10✔
1275
                }
10✔
1276
            }
14✔
1277

1278
            // Although the cerificate is valid, it's not the one we've pinned so reject it.
1279
            return errSSLXCertChainInvalid;
2✔
1280
        }
12✔
1281
    }
48✔
1282
}
48✔
1283

1284

1285
std::size_t Stream::ssl_read(char* buffer, std::size_t size, std::error_code& ec, Want& want) noexcept
1286
{
215,996✔
1287
    auto perform = [this, buffer, size]() noexcept {
215,988✔
1288
        return do_ssl_read(buffer, size);
215,988✔
1289
    };
215,988✔
1290
    std::size_t n = ssl_perform(std::move(perform), ec, want);
215,996✔
1291
    if (want == Want::nothing && n == 0 && !ec) {
215,996✔
1292
        // End of input on TCP socket
1293
        SSLSessionState state;
1294
        if (SSLGetSessionState(m_ssl.get(), &state) == noErr && state == kSSLClosed) {
×
1295
            ec = MiscExtErrors::end_of_input;
1296
        }
1297
        else {
1298
            ec = MiscExtErrors::premature_end_of_input;
1299
        }
1300
    }
1301
    return n;
215,996✔
1302
}
215,996✔
1303

1304
std::pair<OSStatus, std::size_t> Stream::do_ssl_read(char* buffer, std::size_t size) noexcept
1305
{
215,998✔
1306
    std::size_t processed = 0;
215,998✔
1307
    OSStatus result = SSLRead(m_ssl.get(), buffer, size, &processed);
215,998✔
1308
    return {result, processed};
215,998✔
1309
}
215,998✔
1310

1311

1312
std::size_t Stream::ssl_write(const char* data, std::size_t size, std::error_code& ec, Want& want) noexcept
1313
{
134,722✔
1314
    auto perform = [this, data, size]() noexcept {
134,732✔
1315
        return do_ssl_write(data, size);
134,732✔
1316
    };
134,732✔
1317
    std::size_t n = ssl_perform(std::move(perform), ec, want);
134,722✔
1318
    if (want == Want::nothing && n == 0 && !ec) {
134,722✔
1319
        // End of input on TCP socket
1320
        ec = MiscExtErrors::premature_end_of_input;
1321
    }
1322
    return n;
134,722✔
1323
}
134,722✔
1324

1325
std::pair<OSStatus, std::size_t> Stream::do_ssl_write(const char* data, std::size_t size) noexcept
1326
{
134,734✔
1327
    m_last_error = {};
134,734✔
1328

1329
    REALM_ASSERT(size >= m_num_partially_written_bytes);
134,734✔
1330
    data += m_num_partially_written_bytes;
134,734✔
1331
    size -= m_num_partially_written_bytes;
134,734✔
1332

1333
    std::size_t processed = 0;
134,734✔
1334
    OSStatus result = SSLWrite(m_ssl.get(), data, size, &processed);
134,734✔
1335

1336
    if (result != noErr) {
134,734✔
1337
        // Map errors that indicate the connection is closed to broken_pipe, for
1338
        // consistency with OpenSSL.
1339
        if (REALM_LIKELY(result == errSSLWouldBlock)) {
2,336✔
1340
            m_num_partially_written_bytes += processed;
2,332✔
1341
        }
2,332✔
1342
        else if (result == errSSLClosedGraceful || result == errSSLClosedAbort || result == errSSLClosedNoNotify) {
4✔
1343
            result = errSecIO;
2✔
1344
            m_last_error = error::broken_pipe;
2✔
1345
        }
2✔
1346
        processed = 0;
2,336✔
1347
    }
2,336✔
1348
    else {
132,398✔
1349
        processed += m_num_partially_written_bytes;
132,398✔
1350
        m_num_partially_written_bytes = 0;
132,398✔
1351
    }
132,398✔
1352

1353
    return {result, processed};
134,734✔
1354
}
134,734✔
1355

1356

1357
bool Stream::ssl_shutdown(std::error_code& ec, Want& want) noexcept
1358
{
26✔
1359
    auto perform = [this]() noexcept {
26✔
1360
        return do_ssl_shutdown();
26✔
1361
    };
26✔
1362
    std::size_t n = ssl_perform(std::move(perform), ec, want);
26✔
1363
    REALM_ASSERT(n == 0 || n == 1);
26✔
1364
    return (n > 0);
26✔
1365
}
26✔
1366

1367
std::pair<OSStatus, std::size_t> Stream::do_ssl_shutdown() noexcept
1368
{
26✔
1369
    SSLSessionState previousState;
26✔
1370
    if (OSStatus result = SSLGetSessionState(m_ssl.get(), &previousState)) {
26✔
1371
        return {result, false};
1372
    }
1373
    if (OSStatus result = SSLClose(m_ssl.get())) {
26✔
1374
        return {result, false};
1375
    }
1376

1377
    // SSLClose returns noErr if it encountered an I/O error. We can still
1378
    // detect such errors if they originated from our underlying tcp_read /
1379
    // tcp_write functions as we'll have set m_last_error in such cases. This
1380
    // allows us to reconstruct the I/O error and communicate it to our caller.
1381
    if (m_last_error) {
26✔
1382
        return {errSecIO, false};
2✔
1383
    }
2✔
1384
    return {noErr, previousState == kSSLClosed};
24✔
1385
}
24✔
1386

1387

1388
OSStatus Stream::tcp_read(SSLConnectionRef connection, void* data, std::size_t* length) noexcept
1389
{
397,104✔
1390
    return static_cast<Stream*>(const_cast<void*>(connection))->tcp_read(data, length);
397,104✔
1391
}
397,104✔
1392

1393
OSStatus Stream::tcp_read(void* data, std::size_t* length) noexcept
1394
{
397,082✔
1395
    Service::Descriptor& desc = m_tcp_socket.m_desc;
397,082✔
1396
    std::error_code ec;
397,082✔
1397
    std::size_t bytes_read = desc.read_some(reinterpret_cast<char*>(data), *length, ec);
397,082✔
1398

1399
    m_last_operation = BlockingOperation::read;
397,082✔
1400

1401
    // A successful but short read should be treated the same as EAGAIN.
1402
    if (!ec && bytes_read < *length) {
397,082✔
1403
        ec = error::resource_unavailable_try_again;
40✔
1404
    }
40✔
1405

1406
    *length = bytes_read;
397,082✔
1407
    m_last_error = ec;
397,082✔
1408

1409
    if (ec) {
397,082✔
1410
        if (REALM_UNLIKELY(ec == MiscExtErrors::end_of_input)) {
982✔
1411
            return noErr;
8✔
1412
        }
8✔
1413
        if (ec == error::resource_unavailable_try_again) {
974✔
1414
            return errSSLWouldBlock;
974✔
1415
        }
974✔
1416
        return errSecIO;
1417
    }
1418
    return noErr;
396,100✔
1419
}
396,100✔
1420

1421
OSStatus Stream::tcp_write(SSLConnectionRef connection, const void* data, std::size_t* length) noexcept
1422
{
231,446✔
1423
    return static_cast<Stream*>(const_cast<void*>(connection))->tcp_write(data, length);
231,446✔
1424
}
231,446✔
1425

1426
OSStatus Stream::tcp_write(const void* data, std::size_t* length) noexcept
1427
{
231,440✔
1428
    Service::Descriptor& desc = m_tcp_socket.m_desc;
231,440✔
1429
    std::error_code ec;
231,440✔
1430
    std::size_t bytes_written = desc.write_some(reinterpret_cast<const char*>(data), *length, ec);
231,440✔
1431

1432
    m_last_operation = BlockingOperation::write;
231,440✔
1433

1434
    // A successful but short write should be treated the same as EAGAIN.
1435
    if (!ec && bytes_written < *length) {
231,440✔
1436
        ec = error::resource_unavailable_try_again;
1,236✔
1437
    }
1,236✔
1438

1439
    *length = bytes_written;
231,440✔
1440
    m_last_error = ec;
231,440✔
1441

1442
    if (ec) {
231,440✔
1443
        if (ec == error::resource_unavailable_try_again) {
98,574✔
1444
            return errSSLWouldBlock;
98,566✔
1445
        }
98,566✔
1446
        return errSecIO;
8✔
1447
    }
8✔
1448
    return noErr;
132,866✔
1449
}
132,866✔
1450

1451

1452
#else // !REALM_HAVE_OPENSSL && !REALM_HAVE_SECURE_TRANSPORT
1453

1454

1455
void Context::ssl_init()
1456
{
1457
    throw ProtocolNotSupported();
1458
}
1459

1460

1461
void Context::ssl_destroy() noexcept {}
1462

1463

1464
void Stream::ssl_init() {}
1465

1466

1467
void Stream::ssl_destroy() noexcept {}
1468

1469

1470
void Context::ssl_use_certificate_chain_file(const std::string&, std::error_code&) {}
1471

1472

1473
void Context::ssl_use_private_key_file(const std::string&, std::error_code&) {}
1474

1475

1476
void Context::ssl_use_default_verify(std::error_code&) {}
1477

1478

1479
void Context::ssl_use_verify_file(const std::string&, std::error_code&) {}
1480

1481

1482
void Stream::ssl_set_verify_mode(VerifyMode, std::error_code&) {}
1483

1484

1485
void Stream::ssl_set_host_name(const std::string&, std::error_code&) {}
1486

1487

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

1490

1491
void Stream::ssl_handshake(std::error_code&, Want&) noexcept {}
1492

1493

1494
std::size_t Stream::ssl_read(char*, std::size_t, std::error_code&, Want&) noexcept
1495
{
1496
    return 0;
1497
}
1498

1499

1500
std::size_t Stream::ssl_write(const char*, std::size_t, std::error_code&, Want&) noexcept
1501
{
1502
    return 0;
1503
}
1504

1505

1506
bool Stream::ssl_shutdown(std::error_code&, Want&) noexcept
1507
{
1508
    return false;
1509
}
1510

1511
#endif // ! REALM_HAVE_OPENSSL
1512

1513

1514
} // namespace ssl
1515
} // namespace network
1516
} // namespace sync
1517
} // 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

© 2026 Coveralls, Inc