diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 475bd97e..e7a15f7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,6 +65,10 @@ jobs: key: clippy-target-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }} - name: Run clippy run: cargo clippy --all --all-targets + - name: Check docs + run: cargo doc --no-deps -p boring -p boring-sys --features rpk,pq-experimental,underscore-wildcards + env: + DOCS_RS: 1 test: name: Test runs-on: ${{ matrix.os }} diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 62dec155..fe369a6f 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,3 +1,21 @@ +4.18.0 +- 2025-05-29 Add set_verify_param +- 2025-05-28 Add support for X509_STORE_CTX_get0_untrusted +- 2025-06-02 Add X509VerifyParamRef::copy_from (#361) +- 2025-06-02 Fix X509VerifyContextRef::set_verify_param (#358) +- 2025-06-02 Ensure we call X509_STORE_CTX_cleanup on error path too (#360) +- 2025-06-02 Add mutable ex_data APIs for X509StoreContext +- 2025-06-02 Add X509StoreContextRef::init_without_cleanup +- 2025-06-04 Rename to reset_with_context_data +- 2025-06-05 Avoid panicking in error handling +- 2025-06-05 Don't unwrap when Result can be returned instead +- 2025-06-04 Make X509Store shareable between contexts +- 2025-06-05 Sprinkle #[must_use] (#368) +- 2025-06-05 Expose SSL_set1_groups to Efficiently Set Curves on SSL Session (#346) +- 2025-06-09 Upgrade bindgen to v0.72.0 +- 2025-06-13 Expose PKey::raw_{private,public}_key (#364) +- 2025-06-10 Don't link binaries on docs.rs +- 2025-06-11 Use cargo:warning for warnings 4.17.0 - 2025-05-27 Revert "feat(x509): Implement `Clone` for `X509Store` (#339)" (#353) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 9c144d0f..84b67d70 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -468,6 +468,24 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec { } fn ensure_patches_applied(config: &Config) -> io::Result<()> { + if config.env.assume_patched || config.env.path.is_some() { + println!( + "cargo:warning=skipping git patches application, provided\ + native BoringSSL is expected to have the patches included" + ); + return Ok(()); + } else if config.env.source_path.is_some() + && (config.features.rpk + || config.features.pq_experimental + || config.features.underscore_wildcards) + { + panic!( + "BORING_BSSL_ASSUME_PATCHED must be set when setting + BORING_BSSL_SOURCE_PATH and using any of the following + features: rpk, pq-experimental, underscore-wildcards" + ); + } + let mut lock_file = LockFile::open(&config.out_dir.join(".patch_lock"))?; let src_path = get_boringssl_source_path(config); let has_git = src_path.join(".git").exists(); @@ -551,25 +569,6 @@ fn built_boring_source_path(config: &Config) -> &PathBuf { static BUILD_SOURCE_PATH: OnceLock = OnceLock::new(); BUILD_SOURCE_PATH.get_or_init(|| { - if config.env.assume_patched { - println!( - "cargo:warning=skipping git patches application, provided\ - native BoringSSL is expected to have the patches included" - ); - } else if config.env.source_path.is_some() - && (config.features.rpk - || config.features.pq_experimental - || config.features.underscore_wildcards) - { - panic!( - "BORING_BSSL_ASSUME_PATCHED must be set when setting - BORING_BSSL_SOURCE_PATH and using any of the following - features: rpk, pq-experimental, underscore-wildcards" - ); - } else { - ensure_patches_applied(config).unwrap(); - } - let mut cfg = get_boringssl_cmake_config(config); let num_jobs = std::env::var("NUM_JOBS").ok().or_else(|| { @@ -660,6 +659,7 @@ fn get_cpp_runtime_lib(config: &Config) -> Option { fn main() { let config = Config::from_env(); + ensure_patches_applied(&config).unwrap(); if !config.env.docs_rs { emit_link_directives(&config); } diff --git a/boring-sys/src/lib.rs b/boring-sys/src/lib.rs index 8463fe1f..6f027919 100644 --- a/boring-sys/src/lib.rs +++ b/boring-sys/src/lib.rs @@ -20,6 +20,7 @@ use std::os::raw::{c_char, c_int, c_uint, c_ulong}; clippy::useless_transmute, clippy::derive_partial_eq_without_eq, clippy::ptr_offset_with_cast, + unpredictable_function_pointer_comparisons, // TODO: remove Eq/PartialEq in v5 dead_code )] mod generated { diff --git a/boring/src/derive.rs b/boring/src/derive.rs index 701d48a3..1952118d 100644 --- a/boring/src/derive.rs +++ b/boring/src/derive.rs @@ -51,7 +51,6 @@ impl<'a> Deriver<'a> { /// /// It can be used to size the buffer passed to [`Deriver::derive`]. #[corresponds(EVP_PKEY_derive)] - /// [`EVP_PKEY_derive`]: https://www.openssl.org/docs/man1.0.2/crypto/EVP_PKEY_derive_init.html pub fn len(&mut self) -> Result { unsafe { let mut len = 0; diff --git a/boring/src/error.rs b/boring/src/error.rs index 4809c0fc..2295c2c4 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -15,7 +15,8 @@ //! Err(e) => println!("Parsing Error: {:?}", e), //! } //! ``` -use libc::{c_char, c_uint}; +use libc::{c_char, c_int, c_uint}; +use openssl_macros::corresponds; use std::borrow::Cow; use std::error; use std::ffi::CStr; @@ -34,7 +35,8 @@ pub struct ErrorStack(Vec); impl ErrorStack { /// Pops the contents of the OpenSSL error stack, and returns it. - #[allow(clippy::must_use_candidate)] + #[corresponds(ERR_get_error_line_data)] + #[must_use = "Use ErrorStack::clear() to drop the error stack"] pub fn get() -> ErrorStack { let mut vec = vec![]; while let Some(err) = Error::get() { @@ -44,6 +46,7 @@ impl ErrorStack { } /// Pushes the errors back onto the OpenSSL error stack. + #[corresponds(ERR_put_error)] pub fn put(&self) { for error in self.errors() { error.put(); @@ -55,6 +58,14 @@ impl ErrorStack { pub(crate) fn internal_error(err: impl error::Error) -> Self { Self(vec![Error::new_internal(err.to_string())]) } + + /// Empties the current thread's error queue. + #[corresponds(ERR_clear_error)] + pub(crate) fn clear() { + unsafe { + ffi::ERR_clear_error(); + } + } } impl ErrorStack { @@ -80,7 +91,9 @@ impl fmt::Display for ErrorStack { write!( fmt, "[{}]", - err.reason_internal().unwrap_or("unknown reason") + err.reason_internal() + .or_else(|| err.library()) + .unwrap_or("unknown reason") )?; } Ok(()) @@ -101,7 +114,7 @@ impl From for fmt::Error { } } -/// An error reported from OpenSSL. +/// A detailed error reported as part of an [`ErrorStack`]. #[derive(Clone)] pub struct Error { code: c_uint, @@ -117,7 +130,8 @@ static BORING_INTERNAL: &CStr = c"boring-rust"; impl Error { /// Pops the first error off the OpenSSL error stack. - #[allow(clippy::must_use_candidate)] + #[must_use = "Use ErrorStack::clear() to drop the error stack"] + #[corresponds(ERR_get_error_line_data)] pub fn get() -> Option { unsafe { ffi::init(); @@ -150,6 +164,7 @@ impl Error { } /// Pushes the error back onto the OpenSSL error stack. + #[corresponds(ERR_put_error)] pub fn put(&self) { unsafe { ffi::ERR_put_error( @@ -179,7 +194,10 @@ impl Error { } } - /// Returns the raw OpenSSL error code for this error. + /// Returns a raw OpenSSL **packed** error code for this error, which **can't be reliably compared to any error constant**. + /// + /// Use [`Error::library_code()`] and [`Error::reason_code()`] instead. + /// Packed error codes are different than [SSL error codes](crate::ssl::ErrorCode). #[must_use] pub fn code(&self) -> c_uint { self.code @@ -201,27 +219,17 @@ impl Error { } } - /// Returns the raw OpenSSL error constant for the library reporting the - /// error. + /// Returns the raw OpenSSL error constant for the library reporting the error (`ERR_LIB_{name}`). + /// + /// Error [reason codes](Error::reason_code) are not globally unique, but scoped to each library. #[must_use] - pub fn library_code(&self) -> libc::c_int { + pub fn library_code(&self) -> c_int { ffi::ERR_GET_LIB(self.code) } - /// Returns the name of the function reporting the error. - #[must_use] + /// Returns `None`. Boring doesn't use function codes. pub fn function(&self) -> Option<&'static str> { - if self.is_internal() { - return None; - } - unsafe { - let cstr = ffi::ERR_func_error_string(self.code); - if cstr.is_null() { - return None; - } - let bytes = CStr::from_ptr(cstr as *const _).to_bytes(); - str::from_utf8(bytes).ok() - } + None } /// Returns the reason for the error. @@ -237,9 +245,14 @@ impl Error { } } - /// Returns the raw OpenSSL error constant for the reason for the error. + /// Returns [library-specific](Error::library_code) reason code corresponding to some of the `{lib}_R_{reason}` constants. + /// + /// Reason codes are ambiguous, and different libraries reuse the same numeric values for different errors. + /// + /// For `ERR_LIB_SYS` the reason code is `errno`. `ERR_LIB_USER` can use any values. + /// Other libraries may use [`ERR_R_*`](ffi::ERR_R_FATAL) or their own codes. #[must_use] - pub fn reason_code(&self) -> libc::c_int { + pub fn reason_code(&self) -> c_int { ffi::ERR_GET_REASON(self.code) } @@ -256,6 +269,8 @@ impl Error { } /// Returns the line in the source file which encountered the error. + /// + /// 0 if unknown #[allow(clippy::unnecessary_cast)] #[must_use] pub fn line(&self) -> u32 { @@ -294,20 +309,19 @@ impl Error { impl fmt::Debug for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let mut builder = fmt.debug_struct("Error"); - builder.field("code", &self.code()); - if let Some(library) = self.library() { - builder.field("library", &library); + builder.field("code", &self.code); + if !self.is_internal() { + if let Some(library) = self.library() { + builder.field("library", &library); + } + builder.field("library_code", &self.library_code()); + if let Some(reason) = self.reason() { + builder.field("reason", &reason); + } + builder.field("reason_code", &self.reason_code()); + builder.field("file", &self.file()); + builder.field("line", &self.line()); } - builder.field("library_code", &self.library_code()); - if let Some(function) = self.function() { - builder.field("function", &function); - } - if let Some(reason) = self.reason() { - builder.field("reason", &reason); - } - builder.field("reason_code", &self.reason_code()); - builder.field("file", &self.file()); - builder.field("line", &self.line()); if let Some(data) = self.data() { builder.field("data", &data); } @@ -321,7 +335,7 @@ impl fmt::Display for Error { fmt, "{}\n\nCode: {:08X}\nLoc: {}:{}", self.reason_internal().unwrap_or("unknown TLS error"), - self.code(), + &self.code, self.file(), self.line() ) diff --git a/boring/src/rsa.rs b/boring/src/rsa.rs index 9fb48dd3..391ccbae 100644 --- a/boring/src/rsa.rs +++ b/boring/src/rsa.rs @@ -413,7 +413,6 @@ impl Rsa { /// `n` is the modulus common to both public and private key. /// `e` is the public exponent. #[corresponds(RSA_new)] - /// [`RSA_set0_key`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_set0_key.html pub fn from_public_components(n: BigNum, e: BigNum) -> Result, ErrorStack> { unsafe { let rsa = cvt_p(ffi::RSA_new())?; @@ -472,7 +471,6 @@ impl RsaPrivateKeyBuilder { /// `n` is the modulus common to both public and private key. /// `e` is the public exponent and `d` is the private exponent. #[corresponds(RSA_new)] - /// [`RSA_set0_key`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_set0_key.html pub fn new(n: BigNum, e: BigNum, d: BigNum) -> Result { unsafe { let rsa = cvt_p(ffi::RSA_new())?; diff --git a/boring/src/sign.rs b/boring/src/sign.rs index 5c15231a..054327a1 100644 --- a/boring/src/sign.rs +++ b/boring/src/sign.rs @@ -478,7 +478,7 @@ impl<'a> Verifier<'a> { match r { 1 => Ok(true), 0 => { - ErrorStack::get(); // discard error stack + ErrorStack::clear(); // discard error stack Ok(false) } _ => Err(ErrorStack::get()), @@ -500,7 +500,7 @@ impl<'a> Verifier<'a> { match r { 1 => Ok(true), 0 => { - ErrorStack::get(); + ErrorStack::clear(); Ok(false) } _ => Err(ErrorStack::get()), diff --git a/boring/src/ssl/async_callbacks.rs b/boring/src/ssl/async_callbacks.rs index 93226b48..e4bdf846 100644 --- a/boring/src/ssl/async_callbacks.rs +++ b/boring/src/ssl/async_callbacks.rs @@ -11,7 +11,7 @@ use std::pin::Pin; use std::sync::LazyLock; use std::task::{ready, Context, Poll, Waker}; -/// The type of futures to pass to [`SslContextBuilderExt::set_async_select_certificate_callback`]. +/// The type of futures to pass to [`SslContextBuilder::set_async_select_certificate_callback`]. pub type BoxSelectCertFuture = ExDataFuture>; /// The type of callbacks returned by [`BoxSelectCertFuture`] methods. @@ -25,19 +25,19 @@ pub type BoxPrivateKeyMethodFuture = pub type BoxPrivateKeyMethodFinish = Box Result>; -/// The type of futures to pass to [`SslContextBuilderExt::set_async_get_session_callback`]. +/// The type of futures to pass to [`SslContextBuilder::set_async_get_session_callback`]. pub type BoxGetSessionFuture = ExDataFuture>; /// The type of callbacks returned by [`BoxSelectCertFuture`] methods. pub type BoxGetSessionFinish = Box Option>; -/// The type of futures to pass to [`SslContextBuilderExt::set_async_custom_verify_callback`]. +/// The type of futures to pass to [`SslContextBuilder::set_async_custom_verify_callback`]. pub type BoxCustomVerifyFuture = ExDataFuture>; /// The type of callbacks returned by [`BoxCustomVerifyFuture`] methods. pub type BoxCustomVerifyFinish = Box Result<(), SslAlert>>; -/// Convenience alias for futures stored in [`Ssl`] ex data by [`SslContextBuilderExt`] methods. +/// Convenience alias for futures stored in [`Ssl`] ex data by [`SslContextBuilder`] methods. /// /// Public for documentation purposes. pub type ExDataFuture = Pin + Send>>; @@ -123,7 +123,7 @@ impl SslContextBuilder { /// /// # Safety /// - /// The returned [`SslSession`] must not be associated with a different [`SslContext`]. + /// The returned [`SslSession`] must not be associated with a different [`SslContextBuilder`]. pub unsafe fn set_async_get_session_callback(&mut self, callback: F) where F: Fn(&mut SslRef, &[u8]) -> Option + Send + Sync + 'static, diff --git a/boring/src/ssl/error.rs b/boring/src/ssl/error.rs index da1bdcd1..6f8589c6 100644 --- a/boring/src/ssl/error.rs +++ b/boring/src/ssl/error.rs @@ -1,16 +1,20 @@ use crate::ffi; use crate::x509::X509VerifyError; use libc::c_int; +use openssl_macros::corresponds; use std::error; use std::error::Error as StdError; +use std::ffi::CStr; use std::fmt; use std::io; use crate::error::ErrorStack; use crate::ssl::MidHandshakeSslStream; -/// An error code returned from SSL functions. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +/// `SSL_ERROR_*` error code returned from SSL functions. +/// +/// This is different than [packed error codes](crate::error::Error). +#[derive(Copy, Clone, PartialEq, Eq)] pub struct ErrorCode(c_int); impl ErrorCode { @@ -50,16 +54,52 @@ impl ErrorCode { /// An error occurred in the SSL library. pub const SSL: ErrorCode = ErrorCode(ffi::SSL_ERROR_SSL); + /// Wrap an `SSL_ERROR_*` error code. + /// + /// This is different than [packed error codes](crate::error::Error). #[must_use] + #[inline] + #[cfg_attr(debug_assertions, track_caller)] pub fn from_raw(raw: c_int) -> ErrorCode { - ErrorCode(raw) + let code = ErrorCode(raw); + debug_assert!( + raw < 64 || code.description().is_some(), + "{raw} is not an SSL_ERROR_* code" + ); + code } + /// An `SSL_ERROR_*` error code. + /// + /// This is different than [packed error codes](crate::error::Error). #[allow(clippy::trivially_copy_pass_by_ref)] #[must_use] pub fn as_raw(&self) -> c_int { self.0 } + + #[corresponds(SSL_error_description)] + pub fn description(self) -> Option<&'static str> { + unsafe { + let msg = ffi::SSL_error_description(self.0); + if msg.is_null() { + return None; + } + CStr::from_ptr(msg).to_str().ok() + } + } +} + +impl fmt::Display for ErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} ({})", self.description().unwrap_or("error"), self.0) + } +} + +impl fmt::Debug for ErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } } #[derive(Debug)] @@ -68,7 +108,7 @@ pub(crate) enum InnerError { Ssl(ErrorStack), } -/// An SSL error. +/// A general SSL error, based on [`SSL_ERROR_*` error codes](ErrorCode). #[derive(Debug)] pub struct Error { pub(crate) code: ErrorCode, @@ -76,6 +116,7 @@ pub struct Error { } impl Error { + /// An `SSL_ERROR_*` error code. #[must_use] pub fn code(&self) -> ErrorCode { self.code @@ -96,6 +137,7 @@ impl Error { } } + /// Stack of [library-specific errors](crate::error::Error), if available. #[must_use] pub fn ssl_error(&self) -> Option<&ErrorStack> { match self.cause { @@ -131,26 +173,27 @@ impl From for Error { impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match self.code { - ErrorCode::ZERO_RETURN => fmt.write_str("the SSL session has been shut down"), + let msg = match self.code { + ErrorCode::ZERO_RETURN => "the SSL session has been shut down", ErrorCode::WANT_READ => match self.io_error() { - Some(_) => fmt.write_str("a nonblocking read call would have blocked"), - None => fmt.write_str("the operation should be retried"), + Some(_) => "a nonblocking read call would have blocked", + None => "the operation should be retried", }, ErrorCode::WANT_WRITE => match self.io_error() { - Some(_) => fmt.write_str("a nonblocking write call would have blocked"), - None => fmt.write_str("the operation should be retried"), + Some(_) => "a nonblocking write call would have blocked", + None => "the operation should be retried", }, ErrorCode::SYSCALL => match self.io_error() { - Some(err) => write!(fmt, "{err}"), - None => fmt.write_str("unexpected EOF"), + Some(err) => return err.fmt(fmt), + None => "unexpected EOF", }, ErrorCode::SSL => match self.ssl_error() { - Some(e) => write!(fmt, "{e}"), - None => fmt.write_str("unknown BoringSSL error"), + Some(err) => return err.fmt(fmt), + None => "unknown BoringSSL error", }, - ErrorCode(code) => write!(fmt, "unknown error code {code}"), - } + ErrorCode(code) => return code.fmt(fmt), + }; + fmt.write_str(msg) } } diff --git a/boring/src/stack.rs b/boring/src/stack.rs index d5635359..5a95d98c 100644 --- a/boring/src/stack.rs +++ b/boring/src/stack.rs @@ -192,14 +192,14 @@ impl StackRef { } #[must_use] - pub fn iter(&'_ self) -> Iter<'_, T> { + pub fn iter(&self) -> Iter<'_, T> { Iter { stack: self, idxs: 0..self.len(), } } - pub fn iter_mut(&'_ mut self) -> IterMut<'_, T> { + pub fn iter_mut(&mut self) -> IterMut<'_, T> { IterMut { idxs: 0..self.len(), stack: self, diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index 0abc1b79..ee3ce1c9 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -167,11 +167,10 @@ impl X509StoreContextRef { /// * `cert_chain` - The certificates chain. /// * `with_context` - The closure that is called with the initialized context. /// - /// This corresponds to [`X509_STORE_CTX_init`] before calling `with_context` and to - /// [`X509_STORE_CTX_cleanup`] after calling `with_context`. + /// Calls [`X509_STORE_CTX_cleanup`] after calling `with_context`. /// - /// [`X509_STORE_CTX_init`]: https://www.openssl.org/docs/man1.0.2/crypto/X509_STORE_CTX_init.html /// [`X509_STORE_CTX_cleanup`]: https://www.openssl.org/docs/man1.0.2/crypto/X509_STORE_CTX_cleanup.html + #[corresponds(X509_STORE_CTX_init)] pub fn init( &mut self, trust: &store::X509StoreRef, @@ -816,7 +815,7 @@ impl X509 { if ffi::ERR_GET_LIB(err) == ffi::ERR_LIB_PEM.0.try_into().unwrap() && ffi::ERR_GET_REASON(err) == ffi::PEM_R_NO_START_LINE { - ffi::ERR_clear_error(); + ErrorStack::clear(); break; } @@ -1287,10 +1286,7 @@ pub struct X509ReqBuilder(X509Req); impl X509ReqBuilder { /// Returns a builder for a certificate request. - /// - /// This corresponds to [`X509_REQ_new`]. - /// - ///[`X509_REQ_new`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_new.html + #[corresponds(X509_REQ_new)] pub fn new() -> Result { unsafe { ffi::init(); @@ -1299,10 +1295,7 @@ impl X509ReqBuilder { } /// Set the numerical value of the version field. - /// - /// This corresponds to [`X509_REQ_set_version`]. - /// - ///[`X509_REQ_set_version`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_set_version.html + #[corresponds(X509_REQ_set_version)] pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> { unsafe { cvt(ffi::X509_REQ_set_version(self.0.as_ptr(), version.into())).map(|_| ()) } } @@ -1460,10 +1453,7 @@ impl X509ReqRef { } /// Returns the public key of the certificate request. - /// - /// This corresponds to [`X509_REQ_get_pubkey"] - /// - /// [`X509_REQ_get_pubkey`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_get_pubkey.html + #[corresponds(X509_REQ_get_pubkey)] pub fn public_key(&self) -> Result, ErrorStack> { unsafe { let key = cvt_p(ffi::X509_REQ_get_pubkey(self.as_ptr()))?; @@ -1474,10 +1464,7 @@ impl X509ReqRef { /// Check if the certificate request is signed using the given public key. /// /// Returns `true` if verification succeeds. - /// - /// This corresponds to [`X509_REQ_verify"]. - /// - /// [`X509_REQ_verify`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_verify.html + #[corresponds(X509_REQ_verify)] pub fn verify(&self, key: &PKeyRef) -> Result where T: HasPublic, @@ -1486,8 +1473,7 @@ impl X509ReqRef { } /// Returns the extensions of the certificate request. - /// - /// This corresponds to [`X509_REQ_get_extensions"] + #[corresponds(X509_REQ_get_extensions)] pub fn extensions(&self) -> Result, ErrorStack> { unsafe { let extensions = cvt_p(ffi::X509_REQ_get_extensions(self.as_ptr()))?;