Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 109 additions & 54 deletions stdlib/src/ssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,50 @@ use openssl::{
error::ErrorStack,
nid::Nid,
ssl::{self, SslContextBuilder, SslOptions, SslVerifyMode},
x509::{self, X509Object, X509Ref, X509},
x509::{self, X509Ref, X509},
};
use std::convert::TryFrom;
use std::ffi::CStr;
use std::fmt;
use std::io::{Read, Write};
use std::time::Instant;

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 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;
#[cfg(ossl111)]
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_get_version(x: *const X509) -> c_long;
pub fn SSLv3_method() -> *const SSL_METHOD;
pub fn TLSv1_method() -> *const SSL_METHOD;
pub fn COMP_get_type(meth: *const COMP_METHOD) -> i32;
use openssl_sys as sys;

mod bio {
//! based off rust-openssl's private `bio` module

use super::*;

use libc::c_int;
use std::marker::PhantomData;

pub struct MemBioSlice<'a>(*mut sys::BIO, PhantomData<&'a [u8]>);

impl<'a> Drop for MemBioSlice<'a> {
fn drop(&mut self) {
unsafe {
sys::BIO_free_all(self.0);
}
}
}

impl<'a> MemBioSlice<'a> {
pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> {
openssl::init();

assert!(buf.len() <= c_int::max_value() as usize);
let bio = unsafe { sys::BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int) };
if bio.is_null() {
return Err(ErrorStack::get());
}

Ok(MemBioSlice(bio, PhantomData))
}

pub fn as_ptr(&self) -> *mut sys::BIO {
self.0
}
}
}

Expand Down Expand Up @@ -119,9 +136,17 @@ fn obj2txt(obj: &Asn1ObjectRef, no_name: bool) -> Option<String> {
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);
let buflen = buflen as usize;
let mut buf = Vec::<u8>::with_capacity(buflen + 1);
let ret = sys::OBJ_obj2txt(
buf.as_mut_ptr() as *mut libc::c_char,
buf.capacity() as _,
ptr,
no_name,
);
assert!(ret >= 0);
// SAFETY: set_len is safe when capacity is enoguh and all values are already initialized
buf.set_len(buflen);
Comment thread
coolreader18 marked this conversation as resolved.
Resolved
String::from_utf8(buf)
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned())
};
Expand Down Expand Up @@ -243,7 +268,7 @@ fn _ssl_rand_pseudo_bytes(n: i32, vm: &VirtualMachine) -> PyResult<(Vec<u8>, boo
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) };
let ret = unsafe { sys::RAND_bytes(buf.as_mut_ptr(), n) };
match ret {
0 | 1 => Ok((buf, ret == 1)),
_ => Err(convert_openssl_error(vm, ErrorStack::get())),
Expand Down Expand Up @@ -277,7 +302,6 @@ impl SlotConstructor for PySslContext {
let method = match proto {
// SslVersion::Ssl3 => unsafe { ssl::SslMethod::from_ptr(sys::SSLv3_method()) },
SslVersion::Tls => ssl::SslMethod::tls(),
SslVersion::Tls1 => unsafe { ssl::SslMethod::from_ptr(sys::TLSv1_method()) },
// TODO: Tls1_1, Tls1_2 ?
SslVersion::TlsClient => ssl::SslMethod::tls_client(),
SslVersion::TlsServer => ssl::SslMethod::tls_server(),
Expand Down Expand Up @@ -461,22 +485,22 @@ impl PySslContext {
}

if let Some(cadata) = args.cadata {
let cert = match cadata {
let certs = match cadata {
Either::A(s) => {
if !s.as_str().is_ascii() {
if !s.is_ascii() {
return Err(vm.new_type_error("Must be an ascii string".to_owned()));
}
X509::from_pem(s.as_str().as_bytes())
X509::stack_from_pem(s.as_str().as_bytes())
}
Either::B(b) => b.with_ref(X509::from_der),
Either::B(b) => b.with_ref(x509_stack_from_der),
};
let cert = cert.map_err(|e| convert_openssl_error(vm, e))?;
let ret = self.exec_ctx(|ctx| {
let store = ctx.cert_store();
unsafe { sys::X509_STORE_add_cert(store.as_ptr(), cert.as_ptr()) }
});
if ret <= 0 {
return Err(convert_openssl_error(vm, ErrorStack::get()));
let certs = certs.map_err(|e| convert_openssl_error(vm, e))?;
let mut ctx = self.builder();
let store = ctx.cert_store_mut();
for cert in certs {
store
.add_cert(cert)
.map_err(|e| convert_openssl_error(vm, e))?;
}
}

Expand Down Expand Up @@ -511,22 +535,17 @@ impl PySslContext {

#[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.exec_ctx(|ctx| 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))
self.exec_ctx(|ctx| {
let certs = ctx
.cert_store()
.objects()
.iter()
.filter_map(|obj| obj.x509())
.map(|cert| cert_to_py(vm, cert, binary_form))
.collect::<Result<Vec<_>, _>>()?;
Ok(vm.ctx.new_list(certs))
})
}

#[pymethod]
Expand Down Expand Up @@ -949,19 +968,30 @@ fn ssl_error(vm: &VirtualMachine) -> PyTypeRef {
vm.class("_ssl", "SSLError")
}

#[track_caller]
fn convert_openssl_error(vm: &VirtualMachine, err: ErrorStack) -> PyBaseExceptionRef {
let cls = ssl_error(vm);
match err.errors().last() {
Some(e) => {
let caller = std::panic::Location::caller();
let (file, line) = (caller.file(), caller.line());
let file = file
.rsplit_once(&['/', '\\'][..])
.map_or(file, |(_, basename)| basename);
// TODO: map the error codes to code names, e.g. "CERTIFICATE_VERIFY_FAILED", just requires a big hashmap/dict
let errstr = e.reason().unwrap_or("unknown error");
let msg = format!("{} (_ssl.c:{})", errstr, e.line());
let msg = if let Some(lib) = e.library() {
format!("[{}] {} ({}:{})", lib, errstr, file, line)
} else {
format!("{} ({}:{})", errstr, file, line)
};
let reason = sys::ERR_GET_REASON(e.code());
vm.new_exception(cls, vec![vm.ctx.new_int(reason), vm.ctx.new_utf8_str(msg)])
}
None => vm.new_exception_empty(cls),
}
}
#[track_caller]
fn convert_ssl_error(
vm: &VirtualMachine,
e: impl std::borrow::Borrow<ssl::Error>,
Expand Down Expand Up @@ -992,6 +1022,33 @@ fn convert_ssl_error(
vm.new_exception_msg(cls, msg.to_owned())
}

fn x509_stack_from_der(der: &[u8]) -> Result<Vec<X509>, ErrorStack> {
unsafe {
openssl::init();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does openssl::init() is designed to be called whenever this function is called?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of rust-openssl's functions call init() at the start of the function, e.g. X509::stack_from_pem's first line calls it, since it's invalid to use the openssl api without calling it. Obviously we wouldn't get to the point since we call it in make_module but I figured since it's a more generic function it would be good to follow their convention.

let bio = bio::MemBioSlice::new(der)?;

let mut certs = vec![];
loop {
let r = sys::d2i_X509_bio(bio.as_ptr(), std::ptr::null_mut());
if r.is_null() {
let err = sys::ERR_peek_last_error();
if sys::ERR_GET_LIB(err) == sys::ERR_LIB_ASN1
&& sys::ERR_GET_REASON(err) == sys::ASN1_R_HEADER_TOO_LONG
{
sys::ERR_clear_error();
break;
}

return Err(ErrorStack::get());
} else {
certs.push(X509::from_ptr(r));
}
}

Ok(certs)
}
}

type CipherTuple = (&'static str, &'static str, i32);

fn cipher_to_tuple(cipher: &ssl::SslCipherRef) -> CipherTuple {
Expand Down Expand Up @@ -1019,9 +1076,7 @@ fn cert_to_py(vm: &VirtualMachine, cert: &X509Ref, binary: bool) -> PyResult {

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.ctx.new_int(version), vm)?;
dict.set_item("version", vm.ctx.new_int(cert.version()), vm)?;

let serial_num = cert
.serial_number()
Expand Down
7 changes: 7 additions & 0 deletions vm/pylib-crate/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() {
if cfg!(windows) {
if let Ok(real_path) = std::fs::read_to_string("Lib") {
println!("rustc-env:win_lib_path={:?}", real_path);
}
}
}
7 changes: 6 additions & 1 deletion vm/pylib-crate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
//! common way to use this crate is to just add the `"freeze-stdlib"` feature to `rustpython-vm`,
//! in order to automatically include the python part of the standard library into the binary.

pub const LIB_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/Lib");
// windows needs to read the symlink out of `Lib` as git turns it into a text file,
// so build.rs sets this env var
pub const LIB_PATH: &str = match option_env!("win_lib_path") {
Some(s) => s,
None => concat!(env!("CARGO_MANIFEST_DIR"), "/Lib"),
};

#[cfg(feature = "compiled-bytecode")]
use rustpython_bytecode::FrozenModule;
Expand Down