native-ossl 0.1.1

Native Rust idiomatic bindings to OpenSSL
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
//! TLS — `SSL_CTX`, `SSL`, and `SSL_SESSION` wrappers.
//!
//! # Types
//!
//! | Type           | Owned / Shared | Description                               |
//! |----------------|---------------|-------------------------------------------|
//! | [`SslCtx`]     | Shared (Clone) | TLS context — configuration, certs, keys  |
//! | [`Ssl`]        | Exclusive      | Per-connection TLS object                 |
//! | [`SslSession`] | Shared (Clone) | Resumable TLS session handle              |
//!
//! # Protocol version
//!
//! `SSL_CTX_set_min_proto_version` / `SSL_CTX_set_max_proto_version` are C macros
//! that expand to `SSL_CTX_ctrl(ctx, 123 / 124, version, NULL)`.  This module
//! calls `SSL_CTX_ctrl` directly since bindgen cannot expose C macros as functions.
//!
//! # SNI hostname
//!
//! `SSL_set_tlsext_host_name` is a C macro expanding to
//! `SSL_ctrl(s, 55, 0, name)`.  Use [`Ssl::set_hostname`] to set the SNI extension.
//!
//! # BIO ownership
//!
//! `SSL_set_bio` transfers ownership of the supplied `BIO*` pointers to the `SSL`
//! object.  [`Ssl::set_bio_duplex`] accepts a single [`crate::bio::Bio`] for the
//! common case where the same BIO serves as both read and write channel (e.g. the
//! output of `BIO_new_bio_pair`).

use crate::bio::Bio;
use crate::error::ErrorStack;
use crate::pkey::{HasPrivate, Pkey};
use crate::x509::X509;
use native_ossl_sys as sys;
use std::ffi::CStr;

// ── TLS version ───────────────────────────────────────────────────────────────

/// TLS protocol version selector.
///
/// Passed to [`SslCtx::set_min_proto_version`] and [`SslCtx::set_max_proto_version`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TlsVersion {
    /// TLS 1.2 (`0x0303`)
    Tls12 = 0x0303,
    /// TLS 1.3 (`0x0304`)
    Tls13 = 0x0304,
}

// ── Verify mode ───────────────────────────────────────────────────────────────

/// Certificate verification mode flags.
///
/// Combine with bitwise OR using [`SslVerifyMode::or`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SslVerifyMode(i32);

impl SslVerifyMode {
    /// Do not verify the peer certificate (`SSL_VERIFY_NONE`).
    pub const NONE: Self = SslVerifyMode(0x00);
    /// Verify the peer certificate (`SSL_VERIFY_PEER`).
    pub const PEER: Self = SslVerifyMode(0x01);
    /// Fail if the peer does not present a certificate (`SSL_VERIFY_FAIL_IF_NO_PEER_CERT`).
    pub const FAIL_IF_NO_PEER_CERT: Self = SslVerifyMode(0x02);

    /// Combine two mode values with bitwise OR.
    #[must_use]
    pub fn or(self, other: Self) -> Self {
        SslVerifyMode(self.0 | other.0)
    }
}

// ── SSL I/O error ─────────────────────────────────────────────────────────────

/// Error returned by non-blocking SSL I/O operations.
///
/// `WantRead` / `WantWrite` indicate that the operation should be retried
/// after the underlying BIO becomes ready.  For in-memory BIO pairs, retrying
/// immediately after driving the peer is sufficient.
#[derive(Debug)]
pub enum SslIoError {
    /// Retry after data arrives on the read BIO (`SSL_ERROR_WANT_READ`).
    WantRead,
    /// Retry after the write BIO drains (`SSL_ERROR_WANT_WRITE`).
    WantWrite,
    /// The peer closed the connection cleanly (`SSL_ERROR_ZERO_RETURN`).
    ZeroReturn,
    /// Underlying I/O error; see [`ErrorStack`] for details (`SSL_ERROR_SYSCALL`).
    Syscall(ErrorStack),
    /// OpenSSL protocol error (`SSL_ERROR_SSL`).
    Ssl(ErrorStack),
    /// Unexpected error code returned by `SSL_get_error`.
    Other(i32),
}

impl std::fmt::Display for SslIoError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::WantRead => write!(f, "SSL want read"),
            Self::WantWrite => write!(f, "SSL want write"),
            Self::ZeroReturn => write!(f, "SSL zero return (peer closed)"),
            Self::Syscall(e) => write!(f, "SSL syscall error: {e}"),
            Self::Ssl(e) => write!(f, "SSL error: {e}"),
            Self::Other(code) => write!(f, "SSL error code {code}"),
        }
    }
}

impl std::error::Error for SslIoError {}

// ── ShutdownResult ────────────────────────────────────────────────────────────

/// Result of [`Ssl::shutdown`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShutdownResult {
    /// First shutdown stage sent; call `shutdown` again to complete.
    Sent,
    /// Bidirectional shutdown complete.
    Complete,
}

// ── SslSession ────────────────────────────────────────────────────────────────

/// A TLS session handle (`SSL_SESSION*`).
///
/// Cloneable via `SSL_SESSION_up_ref`.  Pass to [`Ssl::set_session`] to enable
/// session resumption.
pub struct SslSession {
    ptr: *mut sys::SSL_SESSION,
}

// SAFETY: `SSL_SESSION` is reference-counted.
unsafe impl Send for SslSession {}
unsafe impl Sync for SslSession {}

impl Clone for SslSession {
    fn clone(&self) -> Self {
        unsafe { sys::SSL_SESSION_up_ref(self.ptr) };
        SslSession { ptr: self.ptr }
    }
}

impl Drop for SslSession {
    fn drop(&mut self) {
        unsafe { sys::SSL_SESSION_free(self.ptr) };
    }
}

// ── SslCtx ────────────────────────────────────────────────────────────────────

/// TLS context (`SSL_CTX*`).
///
/// Holds shared configuration such as certificates, private keys, and verify
/// settings.  Multiple [`Ssl`] objects can be created from the same `SslCtx`.
///
/// Cloneable via `SSL_CTX_up_ref`; wrapping in `Arc<SslCtx>` is safe.
pub struct SslCtx {
    ptr: *mut sys::SSL_CTX,
}

// SAFETY: `SSL_CTX` is reference-counted.
unsafe impl Send for SslCtx {}
unsafe impl Sync for SslCtx {}

impl Clone for SslCtx {
    fn clone(&self) -> Self {
        unsafe { sys::SSL_CTX_up_ref(self.ptr) };
        SslCtx { ptr: self.ptr }
    }
}

impl Drop for SslCtx {
    fn drop(&mut self) {
        unsafe { sys::SSL_CTX_free(self.ptr) };
    }
}

impl SslCtx {
    /// Create a new TLS context accepting any role (client or server).
    ///
    /// Uses the generic `TLS_method()`.  Call [`SslCtx::new_client`] or
    /// [`SslCtx::new_server`] for role-specific method selection.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `SSL_CTX_new` fails.
    pub fn new() -> Result<Self, ErrorStack> {
        let method = unsafe { sys::TLS_method() };
        let ptr = unsafe { sys::SSL_CTX_new(method) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(SslCtx { ptr })
    }

    /// Create a new TLS context optimised for client connections (`TLS_client_method`).
    ///
    /// # Errors
    pub fn new_client() -> Result<Self, ErrorStack> {
        let method = unsafe { sys::TLS_client_method() };
        let ptr = unsafe { sys::SSL_CTX_new(method) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(SslCtx { ptr })
    }

    /// Create a new TLS context optimised for server connections (`TLS_server_method`).
    ///
    /// # Errors
    pub fn new_server() -> Result<Self, ErrorStack> {
        let method = unsafe { sys::TLS_server_method() };
        let ptr = unsafe { sys::SSL_CTX_new(method) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(SslCtx { ptr })
    }

    /// Set the minimum acceptable TLS protocol version.
    ///
    /// Internally calls `SSL_CTX_ctrl(ctx, 123 /*SSL_CTRL_SET_MIN_PROTO_VERSION*/, version, NULL)`.
    ///
    /// # Errors
    pub fn set_min_proto_version(&self, ver: TlsVersion) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_ctrl(self.ptr, 123, ver as i64, std::ptr::null_mut()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Set the maximum acceptable TLS protocol version.
    ///
    /// Internally calls `SSL_CTX_ctrl(ctx, 124 /*SSL_CTRL_SET_MAX_PROTO_VERSION*/, version, NULL)`.
    ///
    /// # Errors
    pub fn set_max_proto_version(&self, ver: TlsVersion) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_ctrl(self.ptr, 124, ver as i64, std::ptr::null_mut()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Set the peer certificate verification mode.
    ///
    /// Wraps `SSL_CTX_set_verify(ctx, mode, NULL)`.
    pub fn set_verify(&self, mode: SslVerifyMode) {
        unsafe { sys::SSL_CTX_set_verify(self.ptr, mode.0, None) };
    }

    /// Set the allowed cipher list (TLS 1.2 and below).
    ///
    /// `list` uses OpenSSL cipher string syntax (e.g. `c"HIGH:!aNULL:!MD5"`).
    ///
    /// # Errors
    pub fn set_cipher_list(&self, list: &CStr) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_set_cipher_list(self.ptr, list.as_ptr()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Set the allowed TLS 1.3 ciphersuites.
    ///
    /// `list` uses OpenSSL ciphersuite syntax (e.g. `c"TLS_AES_256_GCM_SHA384"`).
    ///
    /// # Errors
    pub fn set_ciphersuites(&self, list: &CStr) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_set_ciphersuites(self.ptr, list.as_ptr()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Load a certificate into the context.
    ///
    /// For a server, this is the certificate that will be presented to clients.
    ///
    /// # Errors
    pub fn use_certificate(&self, cert: &X509) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_use_certificate(self.ptr, cert.as_ptr()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Load a private key into the context.
    ///
    /// The key must correspond to the certificate loaded via [`SslCtx::use_certificate`].
    ///
    /// # Errors
    pub fn use_private_key<T: HasPrivate>(&self, key: &Pkey<T>) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_use_PrivateKey(self.ptr, key.as_ptr()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Verify that the loaded certificate and private key are consistent.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the key/certificate pair is invalid or not loaded.
    pub fn check_private_key(&self) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_check_private_key(self.ptr) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Load the system default CA certificate store for verification.
    ///
    /// # Errors
    pub fn set_default_verify_paths(&self) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_set_default_verify_paths(self.ptr) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Disable TLS session caching on this context.
    pub fn disable_session_cache(&self) {
        // SSL_CTRL_SET_SESS_CACHE_MODE = 44, SSL_SESS_CACHE_OFF = 0
        unsafe { sys::SSL_CTX_ctrl(self.ptr, 44, 0, std::ptr::null_mut()) };
    }

    /// Create a new [`Ssl`] connection object from this context.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `SSL_new` fails.
    pub fn new_ssl(&self) -> Result<Ssl, ErrorStack> {
        let ptr = unsafe { sys::SSL_new(self.ptr) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(Ssl { ptr })
    }
}

// ── Ssl ───────────────────────────────────────────────────────────────────────

/// Per-connection TLS object (`SSL*`).
///
/// Has exclusive ownership over its state; no `Clone`.  BIOs passed to
/// [`Ssl::set_bio_duplex`] or [`Ssl::set_bio`] are owned by the `Ssl` thereafter.
pub struct Ssl {
    ptr: *mut sys::SSL,
}

// SAFETY: `SSL*` is not thread-safe for concurrent access, but `Ssl` has
// exclusive ownership, so `Send` is safe for moving between threads.
unsafe impl Send for Ssl {}

impl Drop for Ssl {
    fn drop(&mut self) {
        unsafe { sys::SSL_free(self.ptr) };
    }
}

impl Ssl {
    /// Set a single duplex BIO for both reading and writing.
    ///
    /// Transfers ownership of `bio` to the `SSL` object; do not use `bio`
    /// afterwards.  Suitable for `BIO_new_bio_pair` endpoints.
    ///
    /// When `rbio == wbio` (same pointer), OpenSSL only increments the
    /// reference count once, so the single reference in `bio` is correct.
    pub fn set_bio_duplex(&mut self, bio: Bio) {
        let ptr = bio.as_ptr();
        // Prevent our Drop from calling BIO_free — SSL now owns this BIO.
        std::mem::forget(bio);
        // Passing the same pointer for rbio and wbio: OpenSSL increments
        // the ref count only once when rbio == wbio.
        unsafe { sys::SSL_set_bio(self.ptr, ptr, ptr) };
    }

    /// Set separate read and write BIOs.
    ///
    /// Transfers ownership of both `rbio` and `wbio` to the `SSL` object.
    pub fn set_bio(&mut self, rbio: Bio, wbio: Bio) {
        let rbio_ptr = rbio.as_ptr();
        let wbio_ptr = wbio.as_ptr();
        // Prevent our Drop from calling BIO_free on each — SSL now owns them.
        std::mem::forget(rbio);
        std::mem::forget(wbio);
        unsafe { sys::SSL_set_bio(self.ptr, rbio_ptr, wbio_ptr) };
    }

    /// Set the SNI hostname extension sent during the TLS handshake.
    ///
    /// Call before [`Self::connect`] on client connections to enable SNI.
    /// `hostname` must be a NUL-terminated ASCII/UTF-8 hostname.
    ///
    /// `SSL_set_tlsext_host_name` is a C macro expanding to
    /// `SSL_ctrl(s, 55 /*SSL_CTRL_SET_TLSEXT_HOSTNAME*/, 0 /*TLSEXT_NAMETYPE_host_name*/, name)`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the control call fails.
    pub fn set_hostname(&mut self, hostname: &CStr) -> Result<(), ErrorStack> {
        let rc = unsafe {
            sys::SSL_ctrl(
                self.ptr,
                55, // SSL_CTRL_SET_TLSEXT_HOSTNAME
                0,  // TLSEXT_NAMETYPE_host_name
                // OpenSSL declares parg as *mut c_void but uses it as const here.
                hostname.as_ptr() as *mut std::os::raw::c_void,
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Set this SSL object to operate in client (connect) mode.
    ///
    /// Required before calling [`Self::do_handshake`] if neither [`Self::connect`] nor
    /// [`Self::accept`] will be used.
    pub fn set_connect_state(&mut self) {
        unsafe { sys::SSL_set_connect_state(self.ptr) };
    }

    /// Set this SSL object to operate in server (accept) mode.
    pub fn set_accept_state(&mut self) {
        unsafe { sys::SSL_set_accept_state(self.ptr) };
    }

    /// Initiate a client-side TLS handshake (`SSL_connect`).
    ///
    /// Returns `Ok(())` on success, [`SslIoError::WantRead`] / [`SslIoError::WantWrite`]
    /// when the operation must be retried after more data is available.
    ///
    /// # Errors
    pub fn connect(&mut self) -> Result<(), SslIoError> {
        let rc = unsafe { sys::SSL_connect(self.ptr) };
        if rc == 1 {
            return Ok(());
        }
        Err(self.ssl_io_error(rc))
    }

    /// Accept an incoming TLS connection (`SSL_accept`).
    ///
    /// Returns `Ok(())` on success, [`SslIoError::WantRead`] / [`SslIoError::WantWrite`]
    /// on non-blocking retry.
    ///
    /// # Errors
    pub fn accept(&mut self) -> Result<(), SslIoError> {
        let rc = unsafe { sys::SSL_accept(self.ptr) };
        if rc == 1 {
            return Ok(());
        }
        Err(self.ssl_io_error(rc))
    }

    /// Drive the TLS handshake in either role (`SSL_do_handshake`).
    ///
    /// The role must have been set via [`Self::set_connect_state`] or [`Self::set_accept_state`]
    /// (or implicitly by [`Self::connect`] / [`Self::accept`]).
    ///
    /// # Errors
    pub fn do_handshake(&mut self) -> Result<(), SslIoError> {
        let rc = unsafe { sys::SSL_do_handshake(self.ptr) };
        if rc == 1 {
            return Ok(());
        }
        Err(self.ssl_io_error(rc))
    }

    /// Read decrypted application data (`SSL_read_ex`).
    ///
    /// Returns the number of bytes written into `buf` on success.
    ///
    /// # Errors
    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, SslIoError> {
        let mut readbytes: usize = 0;
        let rc = unsafe {
            sys::SSL_read_ex(
                self.ptr,
                buf.as_mut_ptr().cast(),
                buf.len(),
                std::ptr::addr_of_mut!(readbytes),
            )
        };
        if rc == 1 {
            return Ok(readbytes);
        }
        Err(self.ssl_io_error(rc))
    }

    /// Write application data (`SSL_write_ex`).
    ///
    /// Returns the number of bytes consumed from `buf` on success.
    ///
    /// # Errors
    pub fn write(&mut self, buf: &[u8]) -> Result<usize, SslIoError> {
        let mut written: usize = 0;
        let rc = unsafe {
            sys::SSL_write_ex(
                self.ptr,
                buf.as_ptr().cast(),
                buf.len(),
                std::ptr::addr_of_mut!(written),
            )
        };
        if rc == 1 {
            return Ok(written);
        }
        Err(self.ssl_io_error(rc))
    }

    /// Send a TLS close-notify alert (`SSL_shutdown`).
    ///
    /// Returns [`ShutdownResult::Sent`] after the first shutdown stage and
    /// [`ShutdownResult::Complete`] after a bidirectional shutdown.  Call
    /// twice on a non-blocking connection to complete the exchange.
    ///
    /// # Errors
    ///
    /// Returns `Err` on a fatal error during shutdown.
    pub fn shutdown(&mut self) -> Result<ShutdownResult, ErrorStack> {
        let rc = unsafe { sys::SSL_shutdown(self.ptr) };
        match rc {
            1 => Ok(ShutdownResult::Complete),
            0 => Ok(ShutdownResult::Sent),
            _ => Err(ErrorStack::drain()),
        }
    }

    /// Return the peer's certificate, or `None` if unavailable.
    ///
    /// The returned certificate has its reference count incremented, so it
    /// outlives `self`.
    #[must_use]
    pub fn peer_certificate(&self) -> Option<X509> {
        let ptr = unsafe { sys::SSL_get0_peer_certificate(self.ptr) };
        if ptr.is_null() {
            return None;
        }
        // get0 → no ownership; increment ref count to produce an owned X509.
        unsafe { sys::X509_up_ref(ptr) };
        Some(unsafe { X509::from_ptr(ptr) })
    }

    /// Get an owned reference to the current session (`SSL_get1_session`).
    ///
    /// Returns `None` if no session is established.  The session can be passed
    /// to [`Self::set_session`] on a new `Ssl` for resumption.
    #[must_use]
    pub fn get1_session(&self) -> Option<SslSession> {
        let ptr = unsafe { sys::SSL_get1_session(self.ptr) };
        if ptr.is_null() {
            None
        } else {
            Some(SslSession { ptr })
        }
    }

    /// Set a previously obtained session for resumption (`SSL_set_session`).
    ///
    /// Call before the handshake.
    ///
    /// # Errors
    pub fn set_session(&mut self, session: &SslSession) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_set_session(self.ptr, session.ptr) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Translate a non-positive SSL I/O return code into an [`SslIoError`].
    fn ssl_io_error(&self, ret: i32) -> SslIoError {
        let err = unsafe { sys::SSL_get_error(self.ptr, ret) };
        match err {
            2 => SslIoError::WantRead,
            3 => SslIoError::WantWrite,
            5 => SslIoError::Syscall(ErrorStack::drain()),
            6 => SslIoError::ZeroReturn,
            _ => {
                let stack = ErrorStack::drain();
                if stack.errors().next().is_none() {
                    SslIoError::Other(err)
                } else {
                    SslIoError::Ssl(stack)
                }
            }
        }
    }
}

// ── Tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use crate::pkey::{KeygenCtx, Pkey, Private, Public};
    use crate::x509::{X509Builder, X509NameOwned};

    // ── Helpers ───────────────────────────────────────────────────────────────

    /// Generate a fresh Ed25519 key pair.
    fn make_ed25519_key() -> (Pkey<Private>, Pkey<Public>) {
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let priv_key = kgen.generate().unwrap();
        let pub_key = Pkey::<Public>::from(priv_key.clone());
        (priv_key, pub_key)
    }

    /// Build a self-signed Ed25519 certificate valid for 1 day.
    fn make_self_signed_cert(priv_key: &Pkey<Private>, pub_key: &Pkey<Public>) -> X509 {
        let mut name = X509NameOwned::new().unwrap();
        name.add_entry_by_txt(c"CN", b"test").unwrap();

        X509Builder::new()
            .unwrap()
            .set_version(2)
            .unwrap()
            .set_serial_number(1)
            .unwrap()
            .set_not_before_offset(0)
            .unwrap()
            .set_not_after_offset(86400)
            .unwrap()
            .set_subject_name(&name)
            .unwrap()
            .set_issuer_name(&name)
            .unwrap()
            .set_public_key(pub_key)
            .unwrap()
            .sign(priv_key, None)
            .unwrap()
            .build()
    }

    /// Drive a pair of SSL objects to a completed handshake using an in-memory
    /// BIO pair.  Returns `(client, server)` after the handshake.
    ///
    /// `BIO_new_bio_pair` creates two linked BIOs:
    ///   - data written to bio1 → readable from bio2  (client→server)
    ///   - data written to bio2 → readable from bio1  (server→client)
    fn do_handshake_pair(mut client: Ssl, mut server: Ssl) -> Result<(Ssl, Ssl), SslIoError> {
        let mut client_bio: *mut sys::BIO = std::ptr::null_mut();
        let mut server_bio: *mut sys::BIO = std::ptr::null_mut();
        let rc = unsafe {
            sys::BIO_new_bio_pair(
                std::ptr::addr_of_mut!(client_bio),
                0,
                std::ptr::addr_of_mut!(server_bio),
                0,
            )
        };
        assert_eq!(rc, 1, "BIO_new_bio_pair failed");

        // Transfer ownership to the SSL objects.  Since rbio == wbio for each,
        // OpenSSL only takes one ref, matching the single ref from BIO_new_bio_pair.
        let client_bio_obj = unsafe { Bio::from_ptr_owned(client_bio) };
        let server_bio_obj = unsafe { Bio::from_ptr_owned(server_bio) };
        client.set_bio_duplex(client_bio_obj);
        server.set_bio_duplex(server_bio_obj);

        // Alternate between client and server until both complete (up to 20 steps).
        let mut client_done = false;
        let mut server_done = false;

        for _ in 0..20 {
            if !client_done {
                match client.connect() {
                    Ok(()) => client_done = true,
                    Err(SslIoError::WantRead | SslIoError::WantWrite) => {}
                    Err(e) => return Err(e),
                }
            }
            if !server_done {
                match server.accept() {
                    Ok(()) => server_done = true,
                    Err(SslIoError::WantRead | SslIoError::WantWrite) => {}
                    Err(e) => return Err(e),
                }
            }
            if client_done && server_done {
                return Ok((client, server));
            }
        }
        Err(SslIoError::Other(-1))
    }

    // ── SslCtx construction ───────────────────────────────────────────────────

    #[test]
    fn ctx_new_variants() {
        SslCtx::new().unwrap();
        SslCtx::new_client().unwrap();
        SslCtx::new_server().unwrap();
    }

    #[test]
    fn ctx_clone() {
        let ctx = SslCtx::new().unwrap();
        let _clone = ctx.clone();
    }

    #[test]
    fn ctx_proto_version() {
        let ctx = SslCtx::new().unwrap();
        ctx.set_min_proto_version(TlsVersion::Tls12).unwrap();
        ctx.set_max_proto_version(TlsVersion::Tls13).unwrap();
    }

    #[test]
    fn ctx_verify_mode() {
        let ctx = SslCtx::new().unwrap();
        ctx.set_verify(SslVerifyMode::NONE);
        ctx.set_verify(SslVerifyMode::PEER);
        ctx.set_verify(SslVerifyMode::PEER.or(SslVerifyMode::FAIL_IF_NO_PEER_CERT));
    }

    #[test]
    fn ctx_cipher_list() {
        let ctx = SslCtx::new().unwrap();
        ctx.set_cipher_list(c"HIGH:!aNULL").unwrap();
    }

    // ── Certificate / key loading ─────────────────────────────────────────────

    #[test]
    fn ctx_load_cert_and_key() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        let ctx = SslCtx::new_server().unwrap();
        ctx.use_certificate(&cert).unwrap();
        ctx.use_private_key(&priv_key).unwrap();
        ctx.check_private_key().unwrap();
    }

    // ── In-memory TLS handshake ───────────────────────────────────────────────

    #[test]
    fn tls13_handshake_ed25519() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        // Server context: present our self-signed cert.
        let server_ctx = SslCtx::new_server().unwrap();
        server_ctx.set_min_proto_version(TlsVersion::Tls13).unwrap();
        server_ctx.set_max_proto_version(TlsVersion::Tls13).unwrap();
        server_ctx.use_certificate(&cert).unwrap();
        server_ctx.use_private_key(&priv_key).unwrap();
        server_ctx.check_private_key().unwrap();
        server_ctx.disable_session_cache();

        // Client context: do not verify the server cert (self-signed test cert).
        let client_ctx = SslCtx::new_client().unwrap();
        client_ctx.set_min_proto_version(TlsVersion::Tls13).unwrap();
        client_ctx.set_max_proto_version(TlsVersion::Tls13).unwrap();
        client_ctx.set_verify(SslVerifyMode::NONE);
        client_ctx.disable_session_cache();

        let client_ssl = client_ctx.new_ssl().unwrap();
        let server_ssl = server_ctx.new_ssl().unwrap();

        let (mut client, mut server) =
            do_handshake_pair(client_ssl, server_ssl).expect("TLS 1.3 handshake failed");

        // Exchange a small message.
        let msg = b"hello TLS 1.3";
        let n = client.write(msg).unwrap();
        assert_eq!(n, msg.len());

        let mut buf = [0u8; 64];
        let n = server.read(&mut buf).unwrap();
        assert_eq!(&buf[..n], msg);

        // Server replies.
        let reply = b"world";
        server.write(reply).unwrap();
        let n = client.read(&mut buf).unwrap();
        assert_eq!(&buf[..n], reply);
    }

    #[test]
    fn tls12_handshake() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        let server_ctx = SslCtx::new_server().unwrap();
        server_ctx.set_min_proto_version(TlsVersion::Tls12).unwrap();
        server_ctx.set_max_proto_version(TlsVersion::Tls12).unwrap();
        server_ctx.use_certificate(&cert).unwrap();
        server_ctx.use_private_key(&priv_key).unwrap();
        server_ctx.check_private_key().unwrap();
        server_ctx.disable_session_cache();

        let client_ctx = SslCtx::new_client().unwrap();
        client_ctx.set_min_proto_version(TlsVersion::Tls12).unwrap();
        client_ctx.set_max_proto_version(TlsVersion::Tls12).unwrap();
        client_ctx.set_verify(SslVerifyMode::NONE);
        client_ctx.disable_session_cache();

        let client_ssl = client_ctx.new_ssl().unwrap();
        let server_ssl = server_ctx.new_ssl().unwrap();

        let (mut client, mut server) =
            do_handshake_pair(client_ssl, server_ssl).expect("TLS 1.2 handshake failed");

        // Verify data exchange works after handshake.
        client.write(b"tls12").unwrap();
        let mut buf = [0u8; 16];
        let n = server.read(&mut buf).unwrap();
        assert_eq!(&buf[..n], b"tls12");
    }

    #[test]
    fn peer_certificate_after_handshake() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        // Re-encode cert to DER for comparison.
        let cert_der = cert.to_der().unwrap();

        let server_ctx = SslCtx::new_server().unwrap();
        server_ctx.use_certificate(&cert).unwrap();
        server_ctx.use_private_key(&priv_key).unwrap();
        server_ctx.disable_session_cache();

        // Client requests peer cert verification (peer cert = server cert).
        let client_ctx = SslCtx::new_client().unwrap();
        client_ctx.set_verify(SslVerifyMode::NONE);
        client_ctx.disable_session_cache();

        let (client, _server) =
            do_handshake_pair(client_ctx.new_ssl().unwrap(), server_ctx.new_ssl().unwrap())
                .unwrap();

        // The client should have the server's certificate.
        let peer_cert = client.peer_certificate().expect("no peer certificate");
        let peer_der = peer_cert.to_der().unwrap();
        assert_eq!(peer_der, cert_der, "peer cert DER mismatch");
    }

    #[test]
    fn shutdown_sequence() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        let server_ctx = SslCtx::new_server().unwrap();
        server_ctx.use_certificate(&cert).unwrap();
        server_ctx.use_private_key(&priv_key).unwrap();
        server_ctx.disable_session_cache();

        let client_ctx = SslCtx::new_client().unwrap();
        client_ctx.set_verify(SslVerifyMode::NONE);
        client_ctx.disable_session_cache();

        let (mut client, mut server) =
            do_handshake_pair(client_ctx.new_ssl().unwrap(), server_ctx.new_ssl().unwrap())
                .unwrap();

        // Client sends close_notify → Sent.
        let r = client.shutdown().unwrap();
        assert_eq!(r, ShutdownResult::Sent);

        // Server receives close_notify; its first shutdown call returns Sent.
        let r = server.shutdown().unwrap();
        assert_eq!(r, ShutdownResult::Sent);

        // Client receives server's close_notify → Complete.
        let r = client.shutdown().unwrap();
        assert_eq!(r, ShutdownResult::Complete);
    }

    #[test]
    fn verify_mode_bits() {
        let both = SslVerifyMode::PEER.or(SslVerifyMode::FAIL_IF_NO_PEER_CERT);
        assert_eq!(both.0, 0x03);
    }
}