mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Merge pull request #1823 from RustPython/coolreader18/stdlib-ssl
Add the ssl stdlib module
This commit is contained in:
15 files changed
+4643
-19
No files matched your search
@@ -5,6 +5,9 @@ on:
|
||||
|
||||
name: CI
|
||||
|
||||
env:
|
||||
VCPKGRS_DYNAMIC: 1
|
||||
|
||||
jobs:
|
||||
rust_tests:
|
||||
name: Run rust tests
|
||||
@@ -15,8 +18,16 @@ jobs:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Convert symlinks to hardlink (windows only)
|
||||
run: powershell.exe scripts/symlinks-to-hardlinks.ps1
|
||||
- name: Cache Windows
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
key: vcpkg
|
||||
path: 'C:/vcpkg'
|
||||
if: runner.os == 'Windows'
|
||||
- name: Set up the Windows environment
|
||||
run: |
|
||||
powershell.exe scripts/symlinks-to-hardlinks.ps1
|
||||
powershell.exe scripts/install-openssl.ps1
|
||||
if: runner.os == 'Windows'
|
||||
- name: Cache cargo dependencies
|
||||
uses: actions/cache@v1
|
||||
@@ -40,8 +51,16 @@ jobs:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Convert symlinks to hardlink (windows only)
|
||||
run: powershell.exe scripts/symlinks-to-hardlinks.ps1
|
||||
- name: Cache Windows
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
key: vcpkg
|
||||
path: 'C:/vcpkg'
|
||||
if: runner.os == 'Windows'
|
||||
- name: Set up the Windows environment
|
||||
run: |
|
||||
powershell.exe scripts/symlinks-to-hardlinks.ps1
|
||||
powershell.exe scripts/install-openssl.ps1
|
||||
if: runner.os == 'Windows'
|
||||
- name: Cache cargo dependencies
|
||||
uses: actions/cache@v1
|
||||
|
||||
Generated
+108
@@ -402,6 +402,17 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b94d2eb97732ec84b4e25eaf37db890e317b80e921f168c82cb5282473f8151"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.8",
|
||||
"quote 1.0.2",
|
||||
"syn 1.0.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.12"
|
||||
@@ -596,6 +607,21 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
@@ -1008,6 +1034,28 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca565a7df06f3d4b485494f25ba05da1435950f4dc263440eda7a6fa9b8e36e4"
|
||||
dependencies = [
|
||||
"derivative",
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffa5a33ddddfee04c0283a7653987d634e880347e96b5b2ed64de07efb59db9d"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2 1.0.8",
|
||||
"quote 1.0.2",
|
||||
"syn 1.0.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.3.1"
|
||||
@@ -1020,6 +1068,33 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "973293749822d7dd6370d6da1e523b0d1db19f06c459134c658b2a4261378b52"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"lazy_static 1.4.0",
|
||||
"libc",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordermap"
|
||||
version = "0.3.5"
|
||||
@@ -1106,6 +1181,15 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10d4b51f154c8a7fb96fd6dad097cb74b863943ec010ac94b9fd1be8861fe1e"
|
||||
dependencies = [
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.11"
|
||||
@@ -1516,6 +1600,7 @@ dependencies = [
|
||||
"flame",
|
||||
"flamer",
|
||||
"flate2",
|
||||
"foreign-types",
|
||||
"gethostname",
|
||||
"getrandom",
|
||||
"hex",
|
||||
@@ -1538,7 +1623,10 @@ dependencies = [
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"num_cpus",
|
||||
"num_enum",
|
||||
"once_cell",
|
||||
"openssl",
|
||||
"openssl-sys",
|
||||
"paste",
|
||||
"pwd",
|
||||
"rand 0.7.3",
|
||||
@@ -1551,6 +1639,7 @@ dependencies = [
|
||||
"rustpython-derive",
|
||||
"rustpython-parser",
|
||||
"rustyline",
|
||||
"schannel",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha-1",
|
||||
@@ -1618,6 +1707,16 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "039c25b130bd8c1321ee2d7de7fde2659fa9c2744e4bb29711cfc852ea53cd19"
|
||||
dependencies = [
|
||||
"lazy_static 1.4.0",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
@@ -1914,6 +2013,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.11.2"
|
||||
|
||||
Vendored
+2266
File diff suppressed because it is too large.
Load diff
Vendored
+1240
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,8 @@
|
||||
# From the Actix Web windows workflow:
|
||||
# https://github.com/actix/actix-web/blob/master/.github/workflows/windows.yml
|
||||
vcpkg integrate install
|
||||
vcpkg install openssl:x64-windows
|
||||
Copy-Item C:\vcpkg\installed\x64-windows\bin\libcrypto-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libcrypto.dll
|
||||
Copy-Item C:\vcpkg\installed\x64-windows\bin\libssl-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libssl.dll
|
||||
Get-ChildItem C:\vcpkg\installed\x64-windows\bin
|
||||
Get-ChildItem C:\vcpkg\installed\x64-windows\lib
|
||||
+8
-1
@@ -368,8 +368,15 @@ fn run_rustpython(vm: &VirtualMachine, matches: &ArgMatches) -> PyResult<()> {
|
||||
} else if let Some(module) = matches.value_of("m") {
|
||||
run_module(&vm, module)?;
|
||||
} else if let Some(filename) = matches.value_of("script") {
|
||||
run_script(&vm, scope, filename)?
|
||||
run_script(&vm, scope.clone(), filename)?;
|
||||
if matches.is_present("inspect") {
|
||||
shell::run_shell(&vm, scope)?;
|
||||
}
|
||||
} else {
|
||||
println!(
|
||||
"Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}",
|
||||
crate_version!()
|
||||
);
|
||||
shell::run_shell(&vm, scope)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,11 +41,6 @@ fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> ShellExecResul
|
||||
}
|
||||
|
||||
pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
|
||||
println!(
|
||||
"Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}",
|
||||
crate_version!()
|
||||
);
|
||||
|
||||
let mut repl = Readline::new(helper::ShellHelper::new(vm, scope.clone()));
|
||||
let mut full_input = String::new();
|
||||
|
||||
|
||||
+6
-1
@@ -70,6 +70,8 @@ paste = "0.1"
|
||||
base64 = "0.11"
|
||||
is-macro = "0.1"
|
||||
result-like = "^0.2.1"
|
||||
foreign-types = "0.3"
|
||||
num_enum = "0.4"
|
||||
|
||||
flame = { version = "0.2", optional = true }
|
||||
flamer = { version = "0.3", optional = true }
|
||||
@@ -89,6 +91,8 @@ subprocess = "0.2.2"
|
||||
num_cpus = "1"
|
||||
socket2 = { version = "0.3", features = ["unix"] }
|
||||
rustyline = "6.0"
|
||||
openssl = "0.10"
|
||||
openssl-sys = "0.9"
|
||||
|
||||
[target.'cfg(not(any(target_arch = "wasm32", target_os = "redox")))'.dependencies]
|
||||
dns-lookup = "1.0"
|
||||
@@ -97,10 +101,11 @@ libz-sys = "1.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.7"
|
||||
schannel = "0.1"
|
||||
|
||||
[target."cfg(windows)".dependencies.winapi]
|
||||
version = "0.3"
|
||||
features = ["winsock2", "handleapi", "ws2def", "std", "winbase"]
|
||||
features = ["winsock2", "handleapi", "ws2def", "std", "winbase", "wincrypt"]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
@@ -40,10 +40,15 @@ impl TryFromObject for PyByteInner {
|
||||
elements: k.try_value().unwrap()
|
||||
}),
|
||||
l @ PyList => l.get_byte_inner(vm),
|
||||
obj => Err(vm.new_type_error(format!(
|
||||
"a bytes-like object is required, not {}",
|
||||
obj.class()
|
||||
))),
|
||||
obj => {
|
||||
let iter = vm.get_method_or_type_error(obj.clone(), "__iter__", || {
|
||||
format!("a bytes-like object is required, not {}", obj.class())
|
||||
})?;
|
||||
let iter = PyIterable::from_method(iter);
|
||||
Ok(PyByteInner {
|
||||
elements: iter.iter(vm)?.collect::<PyResult<_>>()?,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -330,8 +335,8 @@ impl PyByteInner {
|
||||
where
|
||||
F: Fn(&[u8], &[u8]) -> bool,
|
||||
{
|
||||
let r = PyByteInner::try_from_object(vm, other)
|
||||
.map(|other| op(&self.elements, &other.elements));
|
||||
let r = PyBytesLike::try_from_object(vm, other)
|
||||
.map(|other| other.with_ref(|other| op(&self.elements, other)));
|
||||
PyComparisonValue::from_option(r.ok())
|
||||
}
|
||||
|
||||
|
||||
@@ -631,6 +631,17 @@ macro_rules! multi_args_frozenset {
|
||||
|
||||
#[pyimpl(flags(BASETYPE))]
|
||||
impl PyFrozenSet {
|
||||
pub fn from_iter(
|
||||
vm: &VirtualMachine,
|
||||
it: impl IntoIterator<Item = PyObjectRef>,
|
||||
) -> PyResult<Self> {
|
||||
let mut inner = PySetInner::default();
|
||||
for elem in it {
|
||||
inner.add(&elem, vm)?;
|
||||
}
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
#[pyslot]
|
||||
fn tp_new(
|
||||
cls: PyClassRef,
|
||||
|
||||
@@ -31,7 +31,7 @@ use crate::function::{single_or_tuple_any, OptionalArg, PyFuncArgs};
|
||||
use crate::pyhash;
|
||||
use crate::pyobject::{
|
||||
Either, IdProtocol, IntoPyObject, ItemProtocol, PyClassImpl, PyContext, PyIterable,
|
||||
PyObjectRef, PyRef, PyResult, PyValue, TryIntoRef, TypeProtocol,
|
||||
PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TryIntoRef, TypeProtocol,
|
||||
};
|
||||
use crate::vm::VirtualMachine;
|
||||
|
||||
@@ -1351,6 +1351,14 @@ impl IntoPyObject for &String {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromObject for std::ffi::CString {
|
||||
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
|
||||
let s = PyStringRef::try_from_object(vm, obj)?;
|
||||
Self::new(s.as_str().to_owned())
|
||||
.map_err(|_| vm.new_value_error("embedded null character".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct SplitArgs {
|
||||
#[pyarg(positional_or_keyword, default = "None")]
|
||||
|
||||
@@ -919,6 +919,13 @@ pub struct PyIterable<T = PyObjectRef> {
|
||||
}
|
||||
|
||||
impl<T> PyIterable<T> {
|
||||
pub fn from_method(method: PyObjectRef) -> Self {
|
||||
PyIterable {
|
||||
method,
|
||||
_item: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over this sequence of objects.
|
||||
///
|
||||
/// This operation may fail if an exception is raised while invoking the
|
||||
|
||||
@@ -55,6 +55,8 @@ mod select;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod signal;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod ssl;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod subprocess;
|
||||
#[cfg(windows)]
|
||||
mod winapi;
|
||||
@@ -123,6 +125,7 @@ pub fn get_module_inits() -> HashMap<String, StdlibInitFunc> {
|
||||
);
|
||||
modules.insert("signal".to_owned(), Box::new(signal::make_module));
|
||||
modules.insert("select".to_owned(), Box::new(select::make_module));
|
||||
modules.insert("_ssl".to_owned(), Box::new(ssl::make_module));
|
||||
modules.insert("_subprocess".to_owned(), Box::new(subprocess::make_module));
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
modules.insert("zlib".to_owned(), Box::new(zlib::make_module));
|
||||
|
||||
+64
-1
@@ -37,7 +37,7 @@ mod c {
|
||||
pub use winapi::shared::ws2def::*;
|
||||
pub use winapi::um::winsock2::{
|
||||
SD_BOTH as SHUT_RDWR, SD_RECEIVE as SHUT_RD, SD_SEND as SHUT_WR, SOCK_DGRAM, SOCK_RAW,
|
||||
SOCK_RDM, SOCK_STREAM, SOL_SOCKET, SO_BROADCAST, SO_REUSEADDR, *,
|
||||
SOCK_RDM, SOCK_STREAM, SOL_SOCKET, SO_BROADCAST, SO_REUSEADDR, SO_TYPE, *,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -208,6 +208,10 @@ impl PySocket {
|
||||
fn close(&self) {
|
||||
self.sock.replace(invalid_sock());
|
||||
}
|
||||
#[pymethod]
|
||||
fn detach(&self) -> RawSocket {
|
||||
into_sock_fileno(self.sock.replace(invalid_sock()))
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn fileno(&self) -> RawSocket {
|
||||
@@ -278,6 +282,50 @@ impl PySocket {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn getsockopt(
|
||||
&self,
|
||||
level: i32,
|
||||
name: i32,
|
||||
buflen: OptionalArg<i32>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let fd = sock_fileno(&self.sock()) as _;
|
||||
let buflen = buflen.unwrap_or(0);
|
||||
if buflen == 0 {
|
||||
let mut flag: libc::c_int = 0;
|
||||
let mut flagsize = std::mem::size_of::<libc::c_int>() as _;
|
||||
let ret = unsafe {
|
||||
c::getsockopt(
|
||||
fd,
|
||||
level,
|
||||
name,
|
||||
&mut flag as *mut libc::c_int as *mut _,
|
||||
&mut flagsize,
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
Err(convert_sock_error(vm, io::Error::last_os_error()))
|
||||
} else {
|
||||
Ok(vm.new_int(flag))
|
||||
}
|
||||
} else {
|
||||
if buflen <= 0 || buflen > 1024 {
|
||||
return Err(vm.new_os_error("getsockopt buflen out of range".to_owned()));
|
||||
}
|
||||
let mut buf = vec![0u8; buflen as usize];
|
||||
let mut buflen = buflen as _;
|
||||
let ret =
|
||||
unsafe { c::getsockopt(fd, level, name, buf.as_mut_ptr() as *mut _, &mut buflen) };
|
||||
buf.truncate(buflen as usize);
|
||||
if ret < 0 {
|
||||
Err(convert_sock_error(vm, io::Error::last_os_error()))
|
||||
} else {
|
||||
Ok(vm.ctx.new_bytes(buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn setsockopt(
|
||||
&self,
|
||||
@@ -348,6 +396,20 @@ impl PySocket {
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for PySocketRef {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
<Socket as io::Read>::read(&mut self.sock.borrow_mut(), buf)
|
||||
}
|
||||
}
|
||||
impl io::Write for PySocketRef {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
<Socket as io::Write>::write(&mut self.sock.borrow_mut(), buf)
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
<Socket as io::Write>::flush(&mut self.sock.borrow_mut())
|
||||
}
|
||||
}
|
||||
|
||||
struct Address {
|
||||
host: PyStringRef,
|
||||
port: u16,
|
||||
@@ -609,6 +671,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
"SO_REUSEADDR" => ctx.new_int(c::SO_REUSEADDR),
|
||||
"TCP_NODELAY" => ctx.new_int(c::TCP_NODELAY),
|
||||
"SO_BROADCAST" => ctx.new_int(c::SO_BROADCAST),
|
||||
"SO_TYPE" => ctx.new_int(c::SO_TYPE),
|
||||
});
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
|
||||
@@ -0,0 +1,879 @@
|
||||
use super::socket::PySocketRef;
|
||||
use crate::exceptions::PyBaseExceptionRef;
|
||||
use crate::function::OptionalArg;
|
||||
use crate::obj::objbytearray::PyByteArrayRef;
|
||||
use crate::obj::objbyteinner::PyBytesLike;
|
||||
use crate::obj::objstr::PyStringRef;
|
||||
use crate::obj::{objtype::PyClassRef, objweakref::PyWeak};
|
||||
use crate::pyobject::{
|
||||
Either, IntoPyObject, ItemProtocol, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue,
|
||||
};
|
||||
use crate::types::create_type;
|
||||
use crate::VirtualMachine;
|
||||
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::fmt;
|
||||
|
||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||
use openssl::{
|
||||
asn1::{Asn1Object, Asn1ObjectRef},
|
||||
error::ErrorStack,
|
||||
nid::Nid,
|
||||
ssl::{self, SslContextBuilder, SslOptions, SslVerifyMode},
|
||||
x509::{self, X509Ref, X509},
|
||||
};
|
||||
|
||||
mod sys {
|
||||
#![allow(non_camel_case_types, unused)]
|
||||
use libc::{c_char, c_double, c_int, c_long, c_void};
|
||||
pub use openssl_sys::*;
|
||||
extern "C" {
|
||||
pub fn OBJ_txt2obj(s: *const c_char, no_name: c_int) -> *mut ASN1_OBJECT;
|
||||
pub fn OBJ_nid2obj(n: c_int) -> *mut ASN1_OBJECT;
|
||||
pub fn TLS_server_method() -> *const SSL_METHOD;
|
||||
pub fn TLS_client_method() -> *const SSL_METHOD;
|
||||
pub fn SSL_CTX_get_verify_mode(ctx: *const SSL_CTX) -> c_int;
|
||||
pub fn X509_get_default_cert_file_env() -> *const c_char;
|
||||
pub fn X509_get_default_cert_file() -> *const c_char;
|
||||
pub fn X509_get_default_cert_dir_env() -> *const c_char;
|
||||
pub fn X509_get_default_cert_dir() -> *const c_char;
|
||||
pub fn SSL_CTX_set_post_handshake_auth(ctx: *mut SSL_CTX, val: c_int);
|
||||
pub fn RAND_add(buf: *const c_void, num: c_int, randomness: c_double);
|
||||
pub fn RAND_pseudo_bytes(buf: *const u8, num: c_int) -> c_int;
|
||||
pub fn X509_STORE_get0_objects(ctx: *mut X509_STORE) -> *mut stack_st_X509_OBJECT;
|
||||
pub fn X509_OBJECT_free(a: *mut X509_OBJECT);
|
||||
pub fn SSL_is_init_finished(ssl: *const SSL) -> c_int;
|
||||
pub fn X509_get_version(x: *const X509) -> c_long;
|
||||
}
|
||||
|
||||
pub enum stack_st_X509_OBJECT {}
|
||||
|
||||
pub type X509_LOOKUP_TYPE = c_int;
|
||||
pub const X509_LU_NONE: X509_LOOKUP_TYPE = 0;
|
||||
pub const X509_LU_X509: X509_LOOKUP_TYPE = 1;
|
||||
pub const X509_LU_CRL: X509_LOOKUP_TYPE = 2;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct X509_OBJECT {
|
||||
pub r#type: X509_LOOKUP_TYPE,
|
||||
pub data: X509_OBJECT_data,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub union X509_OBJECT_data {
|
||||
pub ptr: *mut c_char,
|
||||
pub x509: *mut X509,
|
||||
pub crl: *mut X509_CRL,
|
||||
pub pkey: *mut EVP_PKEY,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: upstream this into rust-openssl
|
||||
foreign_types::foreign_type! {
|
||||
type CType = sys::X509_OBJECT;
|
||||
fn drop = sys::X509_OBJECT_free;
|
||||
|
||||
pub struct X509Object;
|
||||
pub struct X509ObjectRef;
|
||||
}
|
||||
|
||||
impl X509ObjectRef {
|
||||
fn x509(&self) -> Option<&X509Ref> {
|
||||
let ptr = self.as_ptr();
|
||||
let ty = unsafe { (*ptr).r#type };
|
||||
if ty == sys::X509_LU_X509 {
|
||||
Some(unsafe { X509Ref::from_ptr((*ptr).data.x509) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl openssl::stack::Stackable for X509Object {
|
||||
type StackType = sys::stack_st_X509_OBJECT;
|
||||
}
|
||||
|
||||
#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive, PartialEq)]
|
||||
#[repr(i32)]
|
||||
enum SslVersion {
|
||||
Ssl2,
|
||||
Ssl3 = 1,
|
||||
Tls,
|
||||
Tls1,
|
||||
// TODO: Tls1_1, Tls1_2 ?
|
||||
TlsClient = 0x10,
|
||||
TlsServer,
|
||||
}
|
||||
|
||||
#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
|
||||
#[repr(i32)]
|
||||
enum CertRequirements {
|
||||
None,
|
||||
Optional,
|
||||
Required,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum SslServerOrClient {
|
||||
Client,
|
||||
Server,
|
||||
}
|
||||
|
||||
unsafe fn ptr2obj(ptr: *mut sys::ASN1_OBJECT) -> Option<Asn1Object> {
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(Asn1Object::from_ptr(ptr))
|
||||
}
|
||||
}
|
||||
fn txt2obj(s: &CStr, no_name: bool) -> Option<Asn1Object> {
|
||||
unsafe { ptr2obj(sys::OBJ_txt2obj(s.as_ptr(), if no_name { 1 } else { 0 })) }
|
||||
}
|
||||
fn nid2obj(nid: Nid) -> Option<Asn1Object> {
|
||||
unsafe { ptr2obj(sys::OBJ_nid2obj(nid.as_raw())) }
|
||||
}
|
||||
fn obj2txt(obj: &Asn1ObjectRef, no_name: bool) -> Option<String> {
|
||||
unsafe {
|
||||
let no_name = if no_name { 1 } else { 0 };
|
||||
let ptr = obj.as_ptr();
|
||||
let buflen = sys::OBJ_obj2txt(std::ptr::null_mut(), 0, ptr, no_name);
|
||||
assert!(buflen >= 0);
|
||||
if buflen == 0 {
|
||||
return None;
|
||||
}
|
||||
let mut buf = vec![0u8; buflen as usize];
|
||||
let ret = sys::OBJ_obj2txt(buf.as_mut_ptr() as *mut libc::c_char, buflen, ptr, no_name);
|
||||
assert!(ret >= 0);
|
||||
let s = String::from_utf8(buf)
|
||||
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
|
||||
Some(s)
|
||||
}
|
||||
}
|
||||
|
||||
type PyNid = (libc::c_int, String, String, Option<String>);
|
||||
fn obj2py(obj: &Asn1ObjectRef) -> PyNid {
|
||||
let nid = obj.nid();
|
||||
(
|
||||
nid.as_raw(),
|
||||
nid.short_name().unwrap().to_owned(),
|
||||
nid.long_name().unwrap().to_owned(),
|
||||
obj2txt(obj, true),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn ssl_enum_certificates(store_name: PyStringRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
use crate::obj::objset::PyFrozenSet;
|
||||
use schannel::{cert_context::ValidUses, cert_store::CertStore, RawPointer};
|
||||
use winapi::um::wincrypt;
|
||||
// TODO: check every store for it, not just 2 of them:
|
||||
// https://github.com/python/cpython/blob/3.8/Modules/_ssl.c#L5603-L5610
|
||||
let open_fns = [CertStore::open_current_user, CertStore::open_local_machine];
|
||||
let stores = open_fns
|
||||
.iter()
|
||||
.filter_map(|open| open(store_name.as_str()).ok())
|
||||
.collect::<Vec<_>>();
|
||||
let certs = stores.iter().map(|s| s.certs()).flatten().map(|c| {
|
||||
let cert = vm.ctx.new_bytes(c.to_der().to_owned());
|
||||
let enc_type = unsafe {
|
||||
let ptr = c.as_ptr() as wincrypt::PCCERT_CONTEXT;
|
||||
(*ptr).dwCertEncodingType
|
||||
};
|
||||
let enc_type = match enc_type {
|
||||
wincrypt::X509_ASN_ENCODING => vm.new_str("x509_asn".to_owned()),
|
||||
wincrypt::PKCS_7_ASN_ENCODING => vm.new_str("pkcs_7_asn".to_owned()),
|
||||
other => vm.new_int(other),
|
||||
};
|
||||
let usage = match c.valid_uses()? {
|
||||
ValidUses::All => vm.new_bool(true),
|
||||
ValidUses::Oids(oids) => {
|
||||
PyFrozenSet::from_iter(vm, oids.into_iter().map(|oid| vm.new_str(oid)))
|
||||
.unwrap()
|
||||
.into_ref(vm)
|
||||
.into_object()
|
||||
}
|
||||
};
|
||||
Ok(vm.ctx.new_tuple(vec![cert, enc_type, usage]))
|
||||
});
|
||||
let certs = certs
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| super::os::convert_io_error(vm, e))?;
|
||||
Ok(vm.ctx.new_list(certs))
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct Txt2ObjArgs {
|
||||
#[pyarg(positional_or_keyword)]
|
||||
txt: CString,
|
||||
#[pyarg(positional_or_keyword, default = "false")]
|
||||
name: bool,
|
||||
}
|
||||
fn ssl_txt2obj(args: Txt2ObjArgs, vm: &VirtualMachine) -> PyResult<PyNid> {
|
||||
txt2obj(&args.txt, !args.name)
|
||||
.as_deref()
|
||||
.map(obj2py)
|
||||
.ok_or_else(|| {
|
||||
vm.new_value_error(format!("unknown object '{}'", args.txt.to_str().unwrap()))
|
||||
})
|
||||
}
|
||||
|
||||
fn ssl_nid2obj(nid: libc::c_int, vm: &VirtualMachine) -> PyResult<PyNid> {
|
||||
nid2obj(Nid::from_raw(nid))
|
||||
.as_deref()
|
||||
.map(obj2py)
|
||||
.ok_or_else(|| vm.new_value_error(format!("unknown NID {}", nid)))
|
||||
}
|
||||
|
||||
fn ssl_get_default_verify_paths() -> (String, String, String, String) {
|
||||
macro_rules! convert {
|
||||
($f:ident) => {
|
||||
CStr::from_ptr(sys::$f()).to_string_lossy().into_owned()
|
||||
};
|
||||
}
|
||||
unsafe {
|
||||
(
|
||||
convert!(X509_get_default_cert_file_env),
|
||||
convert!(X509_get_default_cert_file),
|
||||
convert!(X509_get_default_cert_dir_env),
|
||||
convert!(X509_get_default_cert_dir),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn ssl_rand_status() -> i32 {
|
||||
unsafe { sys::RAND_status() }
|
||||
}
|
||||
|
||||
fn ssl_rand_add(string: Either<PyStringRef, PyBytesLike>, entropy: f64) {
|
||||
let f = |b: &[u8]| {
|
||||
for buf in b.chunks(libc::c_int::max_value() as usize) {
|
||||
unsafe { sys::RAND_add(buf.as_ptr() as *const _, buf.len() as _, entropy) }
|
||||
}
|
||||
};
|
||||
match string {
|
||||
Either::A(s) => f(s.as_str().as_bytes()),
|
||||
Either::B(b) => b.with_ref(f),
|
||||
}
|
||||
}
|
||||
|
||||
fn ssl_rand_bytes(n: i32, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
|
||||
if n < 0 {
|
||||
return Err(vm.new_value_error("num must be positive".to_owned()));
|
||||
}
|
||||
let mut buf = vec![0; n as usize];
|
||||
openssl::rand::rand_bytes(&mut buf)
|
||||
.map(|()| buf)
|
||||
.map_err(|e| convert_openssl_error(vm, e))
|
||||
}
|
||||
|
||||
fn ssl_rand_pseudo_bytes(n: i32, vm: &VirtualMachine) -> PyResult<(Vec<u8>, bool)> {
|
||||
if n < 0 {
|
||||
return Err(vm.new_value_error("num must be positive".to_owned()));
|
||||
}
|
||||
let mut buf = vec![0; n as usize];
|
||||
let ret = unsafe { sys::RAND_pseudo_bytes(buf.as_mut_ptr(), n) };
|
||||
match ret {
|
||||
0 | 1 => Ok((buf, ret == 1)),
|
||||
_ => Err(convert_openssl_error(vm, ErrorStack::get())),
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "_SSLContext")]
|
||||
struct PySslContext {
|
||||
ctx: RefCell<SslContextBuilder>,
|
||||
check_hostname: bool,
|
||||
}
|
||||
|
||||
impl fmt::Debug for PySslContext {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("_SSLContext")
|
||||
}
|
||||
}
|
||||
|
||||
impl PyValue for PySslContext {
|
||||
fn class(vm: &VirtualMachine) -> PyClassRef {
|
||||
vm.class("_ssl", "_SSLContext")
|
||||
}
|
||||
}
|
||||
|
||||
#[pyimpl(flags(BASETYPE))]
|
||||
impl PySslContext {
|
||||
fn builder(&self) -> RefMut<SslContextBuilder> {
|
||||
self.ctx.borrow_mut()
|
||||
}
|
||||
fn ctx(&self) -> Ref<ssl::SslContextRef> {
|
||||
Ref::map(self.ctx.borrow(), |ctx| unsafe {
|
||||
&**(ctx as *const SslContextBuilder as *const ssl::SslContext)
|
||||
})
|
||||
}
|
||||
fn ptr(&self) -> *mut sys::SSL_CTX {
|
||||
self.ctx.borrow().as_ptr()
|
||||
}
|
||||
|
||||
#[pyslot]
|
||||
fn tp_new(cls: PyClassRef, proto_version: i32, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
|
||||
let proto = SslVersion::try_from(proto_version)
|
||||
.map_err(|_| vm.new_value_error("invalid protocol version".to_owned()))?;
|
||||
let method = match proto {
|
||||
SslVersion::Ssl2 => todo!(),
|
||||
SslVersion::Ssl3 => todo!(),
|
||||
SslVersion::Tls => ssl::SslMethod::tls(),
|
||||
SslVersion::Tls1 => todo!(),
|
||||
// TODO: Tls1_1, Tls1_2 ?
|
||||
SslVersion::TlsClient => unsafe { ssl::SslMethod::from_ptr(sys::TLS_client_method()) },
|
||||
SslVersion::TlsServer => unsafe { ssl::SslMethod::from_ptr(sys::TLS_server_method()) },
|
||||
};
|
||||
let mut builder =
|
||||
SslContextBuilder::new(method).map_err(|e| convert_openssl_error(vm, e))?;
|
||||
|
||||
let check_hostname = proto == SslVersion::TlsClient;
|
||||
builder.set_verify(if check_hostname {
|
||||
SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT
|
||||
} else {
|
||||
SslVerifyMode::NONE
|
||||
});
|
||||
|
||||
let mut options = SslOptions::ALL & !SslOptions::DONT_INSERT_EMPTY_FRAGMENTS;
|
||||
if proto != SslVersion::Ssl2 {
|
||||
options |= SslOptions::NO_SSLV2;
|
||||
}
|
||||
if proto != SslVersion::Ssl3 {
|
||||
options |= SslOptions::NO_SSLV3;
|
||||
}
|
||||
options |= SslOptions::NO_COMPRESSION;
|
||||
options |= SslOptions::CIPHER_SERVER_PREFERENCE;
|
||||
options |= SslOptions::SINGLE_DH_USE;
|
||||
options |= SslOptions::SINGLE_ECDH_USE;
|
||||
builder.set_options(options);
|
||||
|
||||
let mode = ssl::SslMode::ACCEPT_MOVING_WRITE_BUFFER | ssl::SslMode::AUTO_RETRY;
|
||||
builder.set_mode(mode);
|
||||
|
||||
unsafe { sys::SSL_CTX_set_post_handshake_auth(builder.as_ptr(), 0) };
|
||||
|
||||
builder
|
||||
.set_session_id_context(b"Python")
|
||||
.map_err(|e| convert_openssl_error(vm, e))?;
|
||||
|
||||
PySslContext {
|
||||
ctx: RefCell::new(builder),
|
||||
check_hostname,
|
||||
}
|
||||
.into_ref_with_type(vm, cls)
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn set_ciphers(&self, cipherlist: PyStringRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let ciphers = cipherlist.as_str();
|
||||
if ciphers.contains('\0') {
|
||||
return Err(vm.new_value_error("embedded null character".to_owned()));
|
||||
}
|
||||
self.builder().set_cipher_list(ciphers).map_err(|_| {
|
||||
vm.new_exception_msg(ssl_error(vm), "No cipher can be selected.".to_owned())
|
||||
})
|
||||
}
|
||||
|
||||
#[pyproperty]
|
||||
fn verify_mode(&self) -> i32 {
|
||||
let mode = unsafe { sys::SSL_CTX_get_verify_mode(self.ptr()) };
|
||||
let mode =
|
||||
SslVerifyMode::from_bits(mode).expect("bad SSL_CTX_get_verify_mode return value");
|
||||
if mode == SslVerifyMode::NONE {
|
||||
CertRequirements::None.into()
|
||||
} else if mode == SslVerifyMode::PEER {
|
||||
CertRequirements::Optional.into()
|
||||
} else if mode == SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT {
|
||||
CertRequirements::Required.into()
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
#[pyproperty(setter)]
|
||||
fn set_verify_mode(&self, cert: i32, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let cert_req = CertRequirements::try_from(cert)
|
||||
.map_err(|_| vm.new_value_error("invalid value for verify_mode".to_owned()))?;
|
||||
let mode = match cert_req {
|
||||
CertRequirements::None if self.check_hostname => {
|
||||
return Err(vm.new_value_error(
|
||||
"Cannot set verify_mode to CERT_NONE when check_hostname is enabled."
|
||||
.to_owned(),
|
||||
))
|
||||
}
|
||||
CertRequirements::None => SslVerifyMode::NONE,
|
||||
CertRequirements::Optional => SslVerifyMode::PEER,
|
||||
CertRequirements::Required => SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT,
|
||||
};
|
||||
self.builder().set_verify(mode);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn set_default_verify_paths(&self, vm: &VirtualMachine) -> PyResult<()> {
|
||||
self.builder()
|
||||
.set_default_verify_paths()
|
||||
.map_err(|e| convert_openssl_error(vm, e))
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn load_verify_locations(
|
||||
&self,
|
||||
args: LoadVerifyLocationsArgs,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
if args.cafile.is_none() && args.capath.is_none() && args.cadata.is_none() {
|
||||
return Err(
|
||||
vm.new_type_error("cafile, capath and cadata cannot be all omitted".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(cadata) = args.cadata {
|
||||
let cert = match cadata {
|
||||
Either::A(s) => {
|
||||
if !s.as_str().is_ascii() {
|
||||
return Err(vm.new_type_error("Must be an ascii string".to_owned()));
|
||||
}
|
||||
X509::from_pem(s.as_str().as_bytes())
|
||||
}
|
||||
Either::B(b) => b.with_ref(X509::from_der),
|
||||
};
|
||||
let cert = cert.map_err(|e| convert_openssl_error(vm, e))?;
|
||||
let ctx = self.ctx();
|
||||
let store = ctx.cert_store();
|
||||
let ret = unsafe { sys::X509_STORE_add_cert(store.as_ptr(), cert.as_ptr()) };
|
||||
if ret <= 0 {
|
||||
return Err(convert_openssl_error(vm, ErrorStack::get()));
|
||||
}
|
||||
}
|
||||
|
||||
if args.cafile.is_some() || args.capath.is_some() {
|
||||
let ret = unsafe {
|
||||
sys::SSL_CTX_load_verify_locations(
|
||||
self.ptr(),
|
||||
args.cafile
|
||||
.as_ref()
|
||||
.map_or_else(std::ptr::null, |cs| cs.as_ptr()),
|
||||
args.capath
|
||||
.as_ref()
|
||||
.map_or_else(std::ptr::null, |cs| cs.as_ptr()),
|
||||
)
|
||||
};
|
||||
if ret != 1 {
|
||||
let errno = std::io::Error::last_os_error().raw_os_error().unwrap();
|
||||
let err = if errno != 0 {
|
||||
super::os::errno_err(vm)
|
||||
} else {
|
||||
convert_openssl_error(vm, ErrorStack::get())
|
||||
};
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn get_ca_certs(&self, binary_form: OptionalArg<bool>, vm: &VirtualMachine) -> PyResult {
|
||||
use openssl::stack::StackRef;
|
||||
let binary_form = binary_form.unwrap_or(false);
|
||||
let certs = unsafe {
|
||||
let stack = sys::X509_STORE_get0_objects(self.ctx().cert_store().as_ptr());
|
||||
assert!(!stack.is_null());
|
||||
StackRef::<X509Object>::from_ptr(stack)
|
||||
};
|
||||
let certs = certs
|
||||
.iter()
|
||||
.filter_map(|cert| {
|
||||
let cert = cert.x509()?;
|
||||
Some(cert_to_py(vm, cert, binary_form))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(vm.ctx.new_list(certs))
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn _wrap_socket(
|
||||
zelf: PyRef<Self>,
|
||||
args: WrapSocketArgs,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PySslSocket> {
|
||||
let ssl = {
|
||||
let ptr = zelf.ptr();
|
||||
let ctx = unsafe { ssl::SslContext::from_ptr(ptr) };
|
||||
let ssl = ssl::Ssl::new(&ctx).map_err(|e| convert_openssl_error(vm, e))?;
|
||||
std::mem::forget(ctx);
|
||||
ssl
|
||||
};
|
||||
|
||||
let mut stream = ssl::SslStreamBuilder::new(ssl, args.sock.clone());
|
||||
|
||||
let socket_type = if args.server_side {
|
||||
stream.set_accept_state();
|
||||
SslServerOrClient::Server
|
||||
} else {
|
||||
stream.set_connect_state();
|
||||
SslServerOrClient::Client
|
||||
};
|
||||
|
||||
// TODO: use this
|
||||
let _ = args.session;
|
||||
|
||||
Ok(PySslSocket {
|
||||
ctx: zelf,
|
||||
stream: RefCell::new(Some(stream)),
|
||||
socket_type,
|
||||
server_hostname: args.server_hostname,
|
||||
owner: RefCell::new(args.owner.as_ref().map(PyWeak::downgrade)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
// #[allow(dead_code)]
|
||||
struct WrapSocketArgs {
|
||||
#[pyarg(positional_or_keyword)]
|
||||
sock: PySocketRef,
|
||||
#[pyarg(positional_or_keyword)]
|
||||
server_side: bool,
|
||||
#[pyarg(positional_or_keyword, default = "None")]
|
||||
server_hostname: Option<PyStringRef>,
|
||||
#[pyarg(keyword_only, default = "None")]
|
||||
owner: Option<PyObjectRef>,
|
||||
#[pyarg(keyword_only, default = "None")]
|
||||
session: Option<PyObjectRef>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct LoadVerifyLocationsArgs {
|
||||
#[pyarg(positional_or_keyword, default = "None")]
|
||||
cafile: Option<CString>,
|
||||
#[pyarg(positional_or_keyword, default = "None")]
|
||||
capath: Option<CString>,
|
||||
#[pyarg(positional_or_keyword, default = "None")]
|
||||
cadata: Option<Either<PyStringRef, PyBytesLike>>,
|
||||
}
|
||||
|
||||
#[pyclass(name = "_SSLSocket")]
|
||||
struct PySslSocket {
|
||||
ctx: PyRef<PySslContext>,
|
||||
stream: RefCell<Option<ssl::SslStreamBuilder<PySocketRef>>>,
|
||||
socket_type: SslServerOrClient,
|
||||
server_hostname: Option<PyStringRef>,
|
||||
owner: RefCell<Option<PyWeak>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for PySslSocket {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("_SSLSocket")
|
||||
}
|
||||
}
|
||||
|
||||
impl PyValue for PySslSocket {
|
||||
fn class(vm: &VirtualMachine) -> PyClassRef {
|
||||
vm.class("_ssl", "_SSLSocket")
|
||||
}
|
||||
}
|
||||
|
||||
#[pyimpl]
|
||||
impl PySslSocket {
|
||||
fn stream_builder(&self) -> ssl::SslStreamBuilder<PySocketRef> {
|
||||
self.stream.replace(None).unwrap()
|
||||
}
|
||||
fn stream(&self) -> RefMut<ssl::SslStream<PySocketRef>> {
|
||||
RefMut::map(self.stream.borrow_mut(), |b| {
|
||||
let b = b.as_mut().unwrap();
|
||||
unsafe { &mut *(b as *mut ssl::SslStreamBuilder<_> as *mut ssl::SslStream<_>) }
|
||||
})
|
||||
}
|
||||
fn set_stream(&self, stream: ssl::SslStream<PySocketRef>) {
|
||||
let prev = self
|
||||
.stream
|
||||
.replace(Some(unsafe { std::mem::transmute(stream) }));
|
||||
debug_assert!(prev.is_none());
|
||||
}
|
||||
|
||||
#[pyproperty]
|
||||
fn owner(&self) -> Option<PyObjectRef> {
|
||||
self.owner.borrow().as_ref().and_then(PyWeak::upgrade)
|
||||
}
|
||||
#[pyproperty(setter)]
|
||||
fn set_owner(&self, owner: PyObjectRef) {
|
||||
*self.owner.borrow_mut() = Some(PyWeak::downgrade(&owner))
|
||||
}
|
||||
#[pyproperty]
|
||||
fn server_side(&self) -> bool {
|
||||
self.socket_type == SslServerOrClient::Server
|
||||
}
|
||||
#[pyproperty]
|
||||
fn context(&self) -> PyRef<PySslContext> {
|
||||
self.ctx.clone()
|
||||
}
|
||||
#[pyproperty]
|
||||
fn server_hostname(&self) -> Option<PyStringRef> {
|
||||
self.server_hostname.clone()
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn peer_certificate(
|
||||
&self,
|
||||
binary: OptionalArg<bool>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<PyObjectRef>> {
|
||||
let binary = binary.unwrap_or(false);
|
||||
let init_finished = unsafe { sys::SSL_is_init_finished(self.stream().ssl().as_ptr()) } != 0;
|
||||
if !init_finished {
|
||||
return Err(vm.new_value_error("handshake not done yet".to_owned()));
|
||||
}
|
||||
self.stream()
|
||||
.ssl()
|
||||
.peer_certificate()
|
||||
.map(|cert| cert_to_py(vm, &cert, binary))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn do_handshake(&self, vm: &VirtualMachine) -> PyResult<()> {
|
||||
// Either a stream builder or a mid-handshake stream from WANT_READ or WANT_WRITE
|
||||
let mut handshaker: Either<_, ssl::MidHandshakeSslStream<_>> =
|
||||
Either::A(self.stream_builder());
|
||||
loop {
|
||||
let handshake_result = match handshaker {
|
||||
Either::A(s) => s.handshake(),
|
||||
Either::B(s) => s.handshake(),
|
||||
};
|
||||
match handshake_result {
|
||||
Ok(stream) => {
|
||||
self.set_stream(stream);
|
||||
return Ok(());
|
||||
}
|
||||
Err(ssl::HandshakeError::SetupFailure(e)) => {
|
||||
return Err(convert_openssl_error(vm, e))
|
||||
}
|
||||
Err(ssl::HandshakeError::WouldBlock(s)) => handshaker = Either::B(s),
|
||||
Err(ssl::HandshakeError::Failure(s)) => {
|
||||
return Err(convert_ssl_error(vm, s.into_error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn write(&self, data: PyBytesLike, vm: &VirtualMachine) -> PyResult<usize> {
|
||||
data.with_ref(|b| self.stream().ssl_write(b))
|
||||
.map_err(|e| convert_ssl_error(vm, e))
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn read(&self, n: usize, buffer: OptionalArg<PyByteArrayRef>, vm: &VirtualMachine) -> PyResult {
|
||||
if let OptionalArg::Present(buffer) = buffer {
|
||||
let mut buf = buffer.borrow_value_mut();
|
||||
let n = self
|
||||
.stream()
|
||||
.ssl_read(&mut buf.elements)
|
||||
.map_err(|e| convert_ssl_error(vm, e))?;
|
||||
Ok(vm.new_int(n))
|
||||
} else {
|
||||
let mut buf = vec![0u8; n];
|
||||
buf.truncate(n);
|
||||
Ok(vm.ctx.new_bytes(buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ssl_error(vm: &VirtualMachine) -> PyClassRef {
|
||||
vm.class("_ssl", "SSLError")
|
||||
}
|
||||
|
||||
fn convert_openssl_error(vm: &VirtualMachine, err: ErrorStack) -> PyBaseExceptionRef {
|
||||
let cls = ssl_error(vm);
|
||||
match err.errors().first() {
|
||||
Some(e) => {
|
||||
// let no = "unknown";
|
||||
// let msg = format!(
|
||||
// "openssl error code {}, from library {}, in function {}, on line {}, with reason {}, and extra data {}",
|
||||
// e.code(), e.library().unwrap_or(no), e.function().unwrap_or(no), e.line(),
|
||||
// e.reason().unwrap_or(no), e.data().unwrap_or("none"),
|
||||
// );
|
||||
// TODO: map the error codes to code names, e.g. "CERTIFICATE_VERIFY_FAILED", just requires a big hashmap/dict
|
||||
let msg = e.to_string();
|
||||
vm.new_exception_msg(cls, msg)
|
||||
}
|
||||
None => vm.new_exception_empty(cls),
|
||||
}
|
||||
}
|
||||
fn convert_ssl_error(vm: &VirtualMachine, e: ssl::Error) -> PyBaseExceptionRef {
|
||||
match e.into_io_error() {
|
||||
Ok(io_err) => super::os::convert_io_error(vm, io_err),
|
||||
Err(e) => convert_openssl_error(vm, e.ssl_error().unwrap().clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn cert_to_py(vm: &VirtualMachine, cert: &X509Ref, binary: bool) -> PyResult {
|
||||
if binary {
|
||||
cert.to_der()
|
||||
.map(|b| vm.ctx.new_bytes(b))
|
||||
.map_err(|e| convert_openssl_error(vm, e))
|
||||
} else {
|
||||
let dict = vm.ctx.new_dict();
|
||||
|
||||
let name_to_py = |name: &x509::X509NameRef| {
|
||||
name.entries()
|
||||
.map(|entry| {
|
||||
let txt = match obj2txt(entry.object(), false) {
|
||||
Some(s) => vm.new_str(s),
|
||||
None => vm.get_none(),
|
||||
};
|
||||
let data = vm.new_str(entry.data().as_utf8()?.to_owned());
|
||||
Ok(vm.ctx.new_tuple(vec![vm.ctx.new_tuple(vec![txt, data])]))
|
||||
})
|
||||
.collect::<Result<_, _>>()
|
||||
.map(|list| vm.ctx.new_tuple(list))
|
||||
.map_err(|e| convert_openssl_error(vm, e))
|
||||
};
|
||||
|
||||
dict.set_item("subject", name_to_py(cert.subject_name())?, vm)?;
|
||||
dict.set_item("issuer", name_to_py(cert.issuer_name())?, vm)?;
|
||||
|
||||
let version = unsafe { sys::X509_get_version(cert.as_ptr()) };
|
||||
dict.set_item("version", vm.new_int(version), vm)?;
|
||||
|
||||
let serial_num = cert
|
||||
.serial_number()
|
||||
.to_bn()
|
||||
.and_then(|bn| bn.to_hex_str())
|
||||
.map_err(|e| convert_openssl_error(vm, e))?;
|
||||
dict.set_item("serialNumber", vm.new_str(serial_num.to_owned()), vm)?;
|
||||
|
||||
dict.set_item("notBefore", vm.new_str(cert.not_before().to_string()), vm)?;
|
||||
dict.set_item("notAfter", vm.new_str(cert.not_after().to_string()), vm)?;
|
||||
|
||||
if let Some(names) = cert.subject_alt_names() {
|
||||
let san = names
|
||||
.iter()
|
||||
.filter_map(|gen_name| {
|
||||
if let Some(email) = gen_name.email() {
|
||||
Some(vm.ctx.new_tuple(vec![
|
||||
vm.new_str("email".to_owned()),
|
||||
vm.new_str(email.to_owned()),
|
||||
]))
|
||||
} else if let Some(dnsname) = gen_name.dnsname() {
|
||||
Some(vm.ctx.new_tuple(vec![
|
||||
vm.new_str("DNS".to_owned()),
|
||||
vm.new_str(dnsname.to_owned()),
|
||||
]))
|
||||
} else if let Some(ip) = gen_name.ipaddress() {
|
||||
Some(vm.ctx.new_tuple(vec![
|
||||
vm.new_str("IP Address".to_owned()),
|
||||
vm.new_str(String::from_utf8_lossy(ip).into_owned()),
|
||||
]))
|
||||
} else {
|
||||
// TODO: convert every type of general name:
|
||||
// https://github.com/python/cpython/blob/3.6/Modules/_ssl.c#L1092-L1231
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
dict.set_item("subjectAltName", vm.ctx.new_tuple(san), vm)?;
|
||||
};
|
||||
|
||||
Ok(dict.into_object())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_version_info(mut n: i64) -> (u8, u8, u8, u8, u8) {
|
||||
let status = (n & 0xF) as u8;
|
||||
n >>= 4;
|
||||
let patch = (n & 0xFF) as u8;
|
||||
n >>= 8;
|
||||
let fix = (n & 0xFF) as u8;
|
||||
n >>= 8;
|
||||
let minor = (n & 0xFF) as u8;
|
||||
n >>= 8;
|
||||
let major = (n & 0xFF) as u8;
|
||||
(major, minor, fix, patch, status)
|
||||
}
|
||||
|
||||
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
openssl::init();
|
||||
let ctx = &vm.ctx;
|
||||
let ssl_error = create_type(
|
||||
"SSLError",
|
||||
&vm.ctx.types.type_type,
|
||||
&vm.ctx.exceptions.os_error,
|
||||
);
|
||||
let module = py_module!(vm, "_ssl", {
|
||||
"_SSLContext" => PySslContext::make_class(ctx),
|
||||
"_SSLSocket" => PySslSocket::make_class(ctx),
|
||||
"SSLError" => ssl_error,
|
||||
"txt2obj" => ctx.new_function(ssl_txt2obj),
|
||||
"nid2obj" => ctx.new_function(ssl_nid2obj),
|
||||
"get_default_verify_paths" => ctx.new_function(ssl_get_default_verify_paths),
|
||||
"RAND_status" => ctx.new_function(ssl_rand_status),
|
||||
"RAND_add" => ctx.new_function(ssl_rand_add),
|
||||
"RAND_bytes" => ctx.new_function(ssl_rand_bytes),
|
||||
"RAND_pseudo_bytes" => ctx.new_function(ssl_rand_pseudo_bytes),
|
||||
|
||||
// Constants
|
||||
"OPENSSL_VERSION" => ctx.new_str(openssl::version::version().to_owned()),
|
||||
"OPENSSL_VERSION_NUMBER" => ctx.new_int(openssl::version::number()),
|
||||
"OPENSSL_VERSION_INFO" => parse_version_info(openssl::version::number()).into_pyobject(vm).unwrap(),
|
||||
"PROTOCOL_SSLv2" => ctx.new_int(SslVersion::Ssl2 as u32),
|
||||
"PROTOCOL_SSLv3" => ctx.new_int(SslVersion::Ssl3 as u32),
|
||||
"PROTOCOL_SSLv23" => ctx.new_int(SslVersion::Tls as u32),
|
||||
"PROTOCOL_TLS" => ctx.new_int(SslVersion::Tls as u32),
|
||||
"PROTOCOL_TLS_CLIENT" => ctx.new_int(SslVersion::TlsClient as u32),
|
||||
"PROTOCOL_TLS_SERVER" => ctx.new_int(SslVersion::TlsServer as u32),
|
||||
"PROTOCOL_TLSv1" => ctx.new_int(SslVersion::Tls1 as u32),
|
||||
"OP_NO_SSLv2" => ctx.new_int(sys::SSL_OP_NO_SSLv2),
|
||||
"OP_NO_SSLv3" => ctx.new_int(sys::SSL_OP_NO_SSLv3),
|
||||
"OP_NO_TLSv1" => ctx.new_int(sys::SSL_OP_NO_TLSv1),
|
||||
// "OP_NO_TLSv1_1" => ctx.new_int(sys::SSL_OP_NO_TLSv1_1),
|
||||
// "OP_NO_TLSv1_2" => ctx.new_int(sys::SSL_OP_NO_TLSv1_2),
|
||||
"OP_NO_TLSv1_3" => ctx.new_int(sys::SSL_OP_NO_TLSv1_3),
|
||||
"OP_CIPHER_SERVER_PREFERENCE" => ctx.new_int(sys::SSL_OP_CIPHER_SERVER_PREFERENCE),
|
||||
"OP_SINGLE_DH_USE" => ctx.new_int(sys::SSL_OP_SINGLE_DH_USE),
|
||||
"OP_NO_TICKET" => ctx.new_int(sys::SSL_OP_NO_TICKET),
|
||||
// #ifdef SSL_OP_SINGLE_ECDH_USE
|
||||
// "OP_SINGLE_ECDH_USE" => ctx.new_int(sys::SSL_OP_SINGLE_ECDH_USE),
|
||||
// #endif
|
||||
// #ifdef SSL_OP_NO_COMPRESSION
|
||||
// "OP_NO_COMPRESSION" => ctx.new_int(sys::SSL_OP_NO_COMPRESSION),
|
||||
// #endif
|
||||
"HAS_TLS_UNIQUE" => ctx.new_bool(true),
|
||||
"CERT_NONE" => ctx.new_int(CertRequirements::None as u32),
|
||||
"CERT_OPTIONAL" => ctx.new_int(CertRequirements::Optional as u32),
|
||||
"CERT_REQUIRED" => ctx.new_int(CertRequirements::Required as u32),
|
||||
"VERIFY_DEFAULT" => ctx.new_int(0),
|
||||
// "VERIFY_CRL_CHECK_LEAF" => sys::X509_V_FLAG_CRL_CHECK,
|
||||
// "VERIFY_CRL_CHECK_CHAIN" => sys::X509_V_FLAG_CRL_CHECK|sys::X509_V_FLAG_CRL_CHECK_ALL,
|
||||
// "VERIFY_X509_STRICT" => X509_V_FLAG_X509_STRICT,
|
||||
"SSL_ERROR_ZERO_RETURN" => ctx.new_int(sys::SSL_ERROR_ZERO_RETURN),
|
||||
"SSL_ERROR_WANT_READ" => ctx.new_int(sys::SSL_ERROR_WANT_READ),
|
||||
"SSL_ERROR_WANT_WRITE" => ctx.new_int(sys::SSL_ERROR_WANT_WRITE),
|
||||
// "SSL_ERROR_WANT_X509_LOOKUP" => ctx.new_int(sys::SSL_ERROR_WANT_X509_LOOKUP),
|
||||
"SSL_ERROR_SYSCALL" => ctx.new_int(sys::SSL_ERROR_SYSCALL),
|
||||
"SSL_ERROR_SSL" => ctx.new_int(sys::SSL_ERROR_SSL),
|
||||
"SSL_ERROR_WANT_CONNECT" => ctx.new_int(sys::SSL_ERROR_WANT_CONNECT),
|
||||
// "SSL_ERROR_EOF" => ctx.new_int(sys::SSL_ERROR_EOF),
|
||||
// "SSL_ERROR_INVALID_ERROR_CODE" => ctx.new_int(sys::SSL_ERROR_INVALID_ERROR_CODE),
|
||||
// TODO: so many more of these
|
||||
"ALERT_DESCRIPTION_DECODE_ERROR" => ctx.new_int(sys::SSL_AD_DECODE_ERROR),
|
||||
"ALERT_DESCRIPTION_ILLEGAL_PARAMETER" => ctx.new_int(sys::SSL_AD_ILLEGAL_PARAMETER),
|
||||
"ALERT_DESCRIPTION_UNRECOGNIZED_NAME" => ctx.new_int(sys::SSL_AD_UNRECOGNIZED_NAME),
|
||||
});
|
||||
|
||||
extend_module_platform_specific(&module, vm);
|
||||
|
||||
module
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn extend_module_platform_specific(module: &PyObjectRef, vm: &VirtualMachine) {
|
||||
let ctx = &vm.ctx;
|
||||
extend_module!(vm, module, {
|
||||
"enum_certificates" => ctx.new_function(ssl_enum_certificates),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn extend_module_platform_specific(_module: &PyObjectRef, _vm: &VirtualMachine) {}
|
||||
Reference in new issue
Block a user