Skip to content

Commit 2000932

Browse files
committed
SSL MemoryBIO
1 parent 0609a97 commit 2000932

File tree

2 files changed

+174
-1
lines changed

2 files changed

+174
-1
lines changed

Lib/ssl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
import _ssl # if we can't import it, let the error propagate
9999

100100
from _ssl import OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_INFO, OPENSSL_VERSION
101-
from _ssl import _SSLContext, SSLSession #, MemoryBIO
101+
from _ssl import _SSLContext, SSLSession, MemoryBIO
102102
from _ssl import (
103103
SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError,
104104
SSLSyscallError, SSLEOFError, SSLCertVerificationError

stdlib/src/ssl.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,29 @@ mod _ssl {
836836

837837
Ok(py_ssl_socket)
838838
}
839+
840+
#[pymethod]
841+
fn _wrap_bio(_zelf: PyRef<Self>, _args: WrapBioArgs, vm: &VirtualMachine) -> PyResult {
842+
// TODO: Implement BIO-based SSL wrapping
843+
// This requires refactoring PySslSocket to support both socket and BIO modes
844+
Err(vm.new_not_implemented_error(
845+
"_wrap_bio is not yet implemented in RustPython".to_owned(),
846+
))
847+
}
848+
}
849+
850+
#[derive(FromArgs)]
851+
#[allow(dead_code)] // Fields will be used when _wrap_bio is fully implemented
852+
struct WrapBioArgs {
853+
incoming: PyRef<PySSLMemoryBIO>,
854+
outgoing: PyRef<PySSLMemoryBIO>,
855+
server_side: bool,
856+
#[pyarg(any, default)]
857+
server_hostname: Option<PyStrRef>,
858+
#[pyarg(named, default)]
859+
owner: Option<PyObjectRef>,
860+
#[pyarg(named, default)]
861+
session: Option<PyObjectRef>,
839862
}
840863

841864
#[derive(FromArgs)]
@@ -1300,6 +1323,156 @@ mod _ssl {
13001323
}
13011324
}
13021325

1326+
#[pyattr]
1327+
#[pyclass(module = "ssl", name = "MemoryBIO")]
1328+
#[derive(PyPayload)]
1329+
struct PySSLMemoryBIO {
1330+
bio: *mut sys::BIO,
1331+
eof_written: AtomicCell<bool>,
1332+
}
1333+
1334+
impl fmt::Debug for PySSLMemoryBIO {
1335+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1336+
f.pad("MemoryBIO")
1337+
}
1338+
}
1339+
1340+
impl Drop for PySSLMemoryBIO {
1341+
fn drop(&mut self) {
1342+
if !self.bio.is_null() {
1343+
unsafe {
1344+
sys::BIO_free_all(self.bio);
1345+
}
1346+
}
1347+
}
1348+
}
1349+
1350+
unsafe impl Send for PySSLMemoryBIO {}
1351+
unsafe impl Sync for PySSLMemoryBIO {}
1352+
1353+
// OpenSSL BIO helper functions
1354+
// These are typically macros in OpenSSL, implemented via BIO_ctrl
1355+
const BIO_CTRL_PENDING: libc::c_int = 10;
1356+
const BIO_CTRL_SET_EOF: libc::c_int = 2;
1357+
1358+
#[allow(non_snake_case)]
1359+
unsafe fn BIO_ctrl_pending(bio: *mut sys::BIO) -> usize {
1360+
unsafe { sys::BIO_ctrl(bio, BIO_CTRL_PENDING, 0, std::ptr::null_mut()) as usize }
1361+
}
1362+
1363+
#[allow(non_snake_case)]
1364+
unsafe fn BIO_set_mem_eof_return(bio: *mut sys::BIO, eof: libc::c_int) -> libc::c_int {
1365+
unsafe {
1366+
sys::BIO_ctrl(
1367+
bio,
1368+
BIO_CTRL_SET_EOF,
1369+
eof as libc::c_long,
1370+
std::ptr::null_mut(),
1371+
) as libc::c_int
1372+
}
1373+
}
1374+
1375+
#[allow(non_snake_case)]
1376+
unsafe fn BIO_clear_retry_flags(bio: *mut sys::BIO) {
1377+
unsafe {
1378+
sys::BIO_clear_flags(bio, sys::BIO_FLAGS_RWS | sys::BIO_FLAGS_SHOULD_RETRY);
1379+
}
1380+
}
1381+
1382+
impl Constructor for PySSLMemoryBIO {
1383+
type Args = ();
1384+
1385+
fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult {
1386+
unsafe {
1387+
let bio = sys::BIO_new(sys::BIO_s_mem());
1388+
if bio.is_null() {
1389+
return Err(vm.new_memory_error("failed to allocate BIO".to_owned()));
1390+
}
1391+
1392+
sys::BIO_set_retry_read(bio);
1393+
BIO_set_mem_eof_return(bio, -1);
1394+
1395+
PySSLMemoryBIO {
1396+
bio,
1397+
eof_written: AtomicCell::new(false),
1398+
}
1399+
.into_ref_with_type(vm, cls)
1400+
.map(Into::into)
1401+
}
1402+
}
1403+
}
1404+
1405+
#[pyclass(with(Constructor))]
1406+
impl PySSLMemoryBIO {
1407+
#[pygetset]
1408+
fn pending(&self) -> usize {
1409+
unsafe { BIO_ctrl_pending(self.bio) }
1410+
}
1411+
1412+
#[pygetset]
1413+
fn eof(&self) -> bool {
1414+
let pending = unsafe { BIO_ctrl_pending(self.bio) };
1415+
pending == 0 && self.eof_written.load()
1416+
}
1417+
1418+
#[pymethod]
1419+
fn read(&self, size: OptionalArg<i32>, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
1420+
unsafe {
1421+
let avail = BIO_ctrl_pending(self.bio).min(i32::MAX as usize) as i32;
1422+
let len = size.unwrap_or(-1);
1423+
let len = if len < 0 || len > avail { avail } else { len };
1424+
1425+
if len == 0 {
1426+
return Ok(Vec::new());
1427+
}
1428+
1429+
let mut buf = vec![0u8; len as usize];
1430+
let nbytes = sys::BIO_read(self.bio, buf.as_mut_ptr() as *mut _, len);
1431+
1432+
if nbytes < 0 {
1433+
return Err(convert_openssl_error(vm, ErrorStack::get()));
1434+
}
1435+
1436+
buf.truncate(nbytes as usize);
1437+
Ok(buf)
1438+
}
1439+
}
1440+
1441+
#[pymethod]
1442+
fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<i32> {
1443+
if self.eof_written.load() {
1444+
return Err(vm.new_exception_msg(
1445+
ssl_error(vm),
1446+
"cannot write() after write_eof()".to_owned(),
1447+
));
1448+
}
1449+
1450+
data.with_ref(|buf| unsafe {
1451+
if buf.len() > i32::MAX as usize {
1452+
return Err(
1453+
vm.new_overflow_error(format!("string longer than {} bytes", i32::MAX))
1454+
);
1455+
}
1456+
1457+
let nbytes = sys::BIO_write(self.bio, buf.as_ptr() as *const _, buf.len() as i32);
1458+
if nbytes < 0 {
1459+
return Err(convert_openssl_error(vm, ErrorStack::get()));
1460+
}
1461+
1462+
Ok(nbytes)
1463+
})
1464+
}
1465+
1466+
#[pymethod]
1467+
fn write_eof(&self) {
1468+
self.eof_written.store(true);
1469+
unsafe {
1470+
BIO_clear_retry_flags(self.bio);
1471+
BIO_set_mem_eof_return(self.bio, 0);
1472+
}
1473+
}
1474+
}
1475+
13031476
#[pyclass(with(Comparable))]
13041477
impl PySslSession {
13051478
#[pygetset]

0 commit comments

Comments
 (0)