diff options
author | Steven Fackler <sfackler@gmail.com> | 2017-01-13 19:38:12 -0800 |
---|---|---|
committer | Steven Fackler <sfackler@gmail.com> | 2017-01-14 21:09:38 -0800 |
commit | 920ab0d6fb60c17077f43d7f08ad3ff391201689 (patch) | |
tree | 2ede3415426f622fe2aff78eaa70a3d64f35a403 /openssl/src | |
parent | 9942643ab6fbdecb0561fcdc08565d4f154865b3 (diff) | |
download | rust-openssl-920ab0d6fb60c17077f43d7f08ad3ff391201689.zip |
OCSP functionality
Diffstat (limited to 'openssl/src')
-rw-r--r-- | openssl/src/asn1.rs | 20 | ||||
-rw-r--r-- | openssl/src/bn.rs | 14 | ||||
-rw-r--r-- | openssl/src/crypto.rs | 60 | ||||
-rw-r--r-- | openssl/src/lib.rs | 6 | ||||
-rw-r--r-- | openssl/src/ocsp.rs | 274 | ||||
-rw-r--r-- | openssl/src/ssl/mod.rs | 144 | ||||
-rw-r--r-- | openssl/src/ssl/tests/mod.rs | 45 | ||||
-rw-r--r-- | openssl/src/string.rs | 74 | ||||
-rw-r--r-- | openssl/src/x509/mod.rs | 20 | ||||
-rw-r--r-- | openssl/src/x509/store.rs | 35 | ||||
-rw-r--r-- | openssl/src/x509/tests.rs | 13 |
11 files changed, 627 insertions, 78 deletions
diff --git a/openssl/src/asn1.rs b/openssl/src/asn1.rs index c0a23591..d177885e 100644 --- a/openssl/src/asn1.rs +++ b/openssl/src/asn1.rs @@ -1,5 +1,5 @@ use ffi; -use libc::c_long; +use libc::{c_long, c_char}; use std::fmt; use std::ptr; use std::slice; @@ -7,9 +7,21 @@ use std::str; use {cvt, cvt_p}; use bio::MemBio; -use crypto::CryptoString; use error::ErrorStack; use types::{OpenSslType, OpenSslTypeRef}; +use string::OpensslString; + +type_!(Asn1GeneralizedTime, Asn1GeneralizedTimeRef, ffi::ASN1_GENERALIZEDTIME, ffi::ASN1_GENERALIZEDTIME_free); + +impl fmt::Display for Asn1GeneralizedTimeRef { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + unsafe { + let mem_bio = try!(MemBio::new()); + try!(cvt(ffi::ASN1_GENERALIZEDTIME_print(mem_bio.as_ptr(), self.as_ptr()))); + write!(f, "{}", str::from_utf8_unchecked(mem_bio.get_buf())) + } + } +} type_!(Asn1Time, Asn1TimeRef, ffi::ASN1_TIME, ffi::ASN1_TIME_free); @@ -42,7 +54,7 @@ impl Asn1Time { type_!(Asn1String, Asn1StringRef, ffi::ASN1_STRING, ffi::ASN1_STRING_free); impl Asn1StringRef { - pub fn as_utf8(&self) -> Result<CryptoString, ErrorStack> { + pub fn as_utf8(&self) -> Result<OpensslString, ErrorStack> { unsafe { let mut ptr = ptr::null_mut(); let len = ffi::ASN1_STRING_to_UTF8(&mut ptr, self.as_ptr()); @@ -50,7 +62,7 @@ impl Asn1StringRef { return Err(ErrorStack::get()); } - Ok(CryptoString::from_raw_parts(ptr, len as usize)) + Ok(OpensslString::from_ptr(ptr as *mut c_char)) } } diff --git a/openssl/src/bn.rs b/openssl/src/bn.rs index a8bb9619..01c0b428 100644 --- a/openssl/src/bn.rs +++ b/openssl/src/bn.rs @@ -6,8 +6,8 @@ use std::{fmt, ptr}; use std::ops::{Add, Div, Mul, Neg, Rem, Shl, Shr, Sub, Deref}; use {cvt, cvt_p, cvt_n}; -use crypto::CryptoString; use error::ErrorStack; +use string::OpensslString; use types::{OpenSslType, OpenSslTypeRef}; #[cfg(ossl10x)] @@ -484,12 +484,12 @@ impl BigNumRef { /// # use openssl::bn::BigNum; /// let s = -BigNum::from_u32(12345).unwrap(); /// - /// assert_eq!(&*s.to_dec_str().unwrap(), "-12345"); + /// assert_eq!(&**s.to_dec_str().unwrap(), "-12345"); /// ``` - pub fn to_dec_str(&self) -> Result<CryptoString, ErrorStack> { + pub fn to_dec_str(&self) -> Result<OpensslString, ErrorStack> { unsafe { let buf = try!(cvt_p(ffi::BN_bn2dec(self.as_ptr()))); - Ok(CryptoString::from_null_terminated(buf)) + Ok(OpensslString::from_ptr(buf)) } } @@ -499,12 +499,12 @@ impl BigNumRef { /// # use openssl::bn::BigNum; /// let s = -BigNum::from_u32(0x99ff).unwrap(); /// - /// assert_eq!(&*s.to_hex_str().unwrap(), "-99FF"); + /// assert_eq!(&**s.to_hex_str().unwrap(), "-99FF"); /// ``` - pub fn to_hex_str(&self) -> Result<CryptoString, ErrorStack> { + pub fn to_hex_str(&self) -> Result<OpensslString, ErrorStack> { unsafe { let buf = try!(cvt_p(ffi::BN_bn2hex(self.as_ptr()))); - Ok(CryptoString::from_null_terminated(buf)) + Ok(OpensslString::from_ptr(buf)) } } } diff --git a/openssl/src/crypto.rs b/openssl/src/crypto.rs index ce83cbae..49029318 100644 --- a/openssl/src/crypto.rs +++ b/openssl/src/crypto.rs @@ -1,59 +1,5 @@ -use libc::{c_char, c_int, c_void}; -use std::fmt; -use std::ffi::CStr; -use std::slice; -use std::ops::Deref; -use std::str; +use string::OpensslString; -pub struct CryptoString(&'static str); +#[deprecated(note = "renamed to OpensslString", since = "0.9.7")] +pub type CryptoString = OpensslString; -impl Drop for CryptoString { - fn drop(&mut self) { - unsafe { - CRYPTO_free(self.0.as_ptr() as *mut c_void, - concat!(file!(), "\0").as_ptr() as *const c_char, - line!() as c_int); - } - } -} - -impl Deref for CryptoString { - type Target = str; - - fn deref(&self) -> &str { - self.0 - } -} - -impl CryptoString { - pub unsafe fn from_raw_parts(buf: *mut u8, len: usize) -> CryptoString { - let slice = slice::from_raw_parts(buf, len); - CryptoString(str::from_utf8_unchecked(slice)) - } - - pub unsafe fn from_null_terminated(buf: *mut c_char) -> CryptoString { - let slice = CStr::from_ptr(buf).to_bytes(); - CryptoString(str::from_utf8_unchecked(slice)) - } -} - -impl fmt::Display for CryptoString { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self.0, f) - } -} - -impl fmt::Debug for CryptoString { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self.0, f) - } -} - -#[cfg(not(ossl110))] -#[allow(non_snake_case)] -unsafe fn CRYPTO_free(buf: *mut c_void, _: *const c_char, _: c_int) { - ::ffi::CRYPTO_free(buf); -} - -#[cfg(ossl110)] -use ffi::CRYPTO_free; diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index dc1d794f..9138896b 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -35,18 +35,20 @@ pub mod error; pub mod hash; pub mod memcmp; pub mod nid; +pub mod ocsp; pub mod pkcs12; pub mod pkcs5; pub mod pkey; pub mod rand; -pub mod types; pub mod rsa; pub mod sign; pub mod ssl; +pub mod stack; +pub mod string; pub mod symm; +pub mod types; pub mod version; pub mod x509; -pub mod stack; #[cfg(any(ossl102, ossl110))] mod verify; diff --git a/openssl/src/ocsp.rs b/openssl/src/ocsp.rs new file mode 100644 index 00000000..bba5c561 --- /dev/null +++ b/openssl/src/ocsp.rs @@ -0,0 +1,274 @@ +use ffi; +use libc::{c_int, c_long, c_ulong}; +use std::ptr; +use std::mem; + +use {cvt, cvt_p}; +use asn1::Asn1GeneralizedTimeRef; +use error::ErrorStack; +use hash::MessageDigest; +use stack::StackRef; +use types::OpenSslTypeRef; +use x509::store::X509StoreRef; +use x509::{X509, X509Ref}; + +bitflags! { + pub flags Flag: c_ulong { + const FLAG_NO_CERTS = ffi::OCSP_NOCERTS, + const FLAG_NO_INTERN = ffi::OCSP_NOINTERN, + const FLAG_NO_CHAIN = ffi::OCSP_NOCHAIN, + const FLAG_NO_VERIFY = ffi::OCSP_NOVERIFY, + const FLAG_NO_EXPLICIT = ffi::OCSP_NOEXPLICIT, + const FLAG_NO_CA_SIGN = ffi::OCSP_NOCASIGN, + const FLAG_NO_DELEGATED = ffi::OCSP_NODELEGATED, + const FLAG_NO_CHECKS = ffi::OCSP_NOCHECKS, + const FLAG_TRUST_OTHER = ffi::OCSP_TRUSTOTHER, + const FLAG_RESPID_KEY = ffi::OCSP_RESPID_KEY, + const FLAG_NO_TIME = ffi::OCSP_NOTIME, + } +} + +pub const RESPONSE_STATUS_SUCCESSFUL: OcspResponseStatus = + OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_SUCCESSFUL); +pub const RESPONSE_STATUS_MALFORMED_REQUEST: OcspResponseStatus = + OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_MALFORMEDREQUEST); +pub const RESPONSE_STATUS_INTERNAL_ERROR: OcspResponseStatus = + OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_INTERNALERROR); +pub const RESPONSE_STATUS_TRY_LATER: OcspResponseStatus = + OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_TRYLATER); +pub const RESPONSE_STATUS_SIG_REQUIRED: OcspResponseStatus = + OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_SIGREQUIRED); +pub const RESPONSE_STATUS_UNAUTHORIZED: OcspResponseStatus = + OcspResponseStatus(ffi::OCSP_RESPONSE_STATUS_UNAUTHORIZED); + +pub const CERT_STATUS_GOOD: OcspCertStatus = OcspCertStatus(ffi::V_OCSP_CERTSTATUS_GOOD); +pub const CERT_STATUS_REVOKED: OcspCertStatus = OcspCertStatus(ffi::V_OCSP_CERTSTATUS_REVOKED); +pub const CERT_STATUS_UNKNOWN: OcspCertStatus = OcspCertStatus(ffi::V_OCSP_CERTSTATUS_UNKNOWN); + +pub const REVOKED_STATUS_NO_STATUS: OcspRevokedStatus = + OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_NOSTATUS); +pub const REVOKED_STATUS_UNSPECIFIED: OcspRevokedStatus = + OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_UNSPECIFIED); +pub const REVOKED_STATUS_KEY_COMPROMISE: OcspRevokedStatus = + OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_KEYCOMPROMISE); +pub const REVOKED_STATUS_CA_COMPROMISE: OcspRevokedStatus = + OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_CACOMPROMISE); +pub const REVOKED_STATUS_AFFILIATION_CHANGED: OcspRevokedStatus = + OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_AFFILIATIONCHANGED); +pub const REVOKED_STATUS_SUPERSEDED: OcspRevokedStatus = + OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_SUPERSEDED); +pub const REVOKED_STATUS_CESSATION_OF_OPERATION: OcspRevokedStatus = + OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_CESSATIONOFOPERATION); +pub const REVOKED_STATUS_CERTIFICATE_HOLD: OcspRevokedStatus = + OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_CERTIFICATEHOLD); +pub const REVOKED_STATUS_REMOVE_FROM_CRL: OcspRevokedStatus = + OcspRevokedStatus(ffi::OCSP_REVOKED_STATUS_REMOVEFROMCRL); + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct OcspResponseStatus(c_int); + +impl OcspResponseStatus { + pub fn from_raw(raw: c_int) -> OcspResponseStatus { + OcspResponseStatus(raw) + } + + pub fn as_raw(&self) -> c_int { + self.0 + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct OcspCertStatus(c_int); + +impl OcspCertStatus { + pub fn from_raw(raw: c_int) -> OcspCertStatus { + OcspCertStatus(raw) + } + + pub fn as_raw(&self) -> c_int { + self.0 + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct OcspRevokedStatus(c_int); + +impl OcspRevokedStatus { + pub fn from_raw(raw: c_int) -> OcspRevokedStatus { + OcspRevokedStatus(raw) + } + + pub fn as_raw(&self) -> c_int { + self.0 + } +} + +pub struct Status<'a> { + /// The overall status of the response. + pub status: OcspCertStatus, + /// If `status` is `CERT_STATUS_REVOKED`, the reason for the revocation. + pub reason: OcspRevokedStatus, + /// If `status` is `CERT_STATUS_REVOKED`, the time at which the certificate was revoked. + pub revocation_time: Option<&'a Asn1GeneralizedTimeRef>, + /// The time that this revocation check was performed. + pub this_update: &'a Asn1GeneralizedTimeRef, + /// The time at which this revocation check expires. + pub next_update: &'a Asn1GeneralizedTimeRef, +} + +impl<'a> Status<'a> { + /// Checks validity of the `this_update` and `next_update` fields. + /// + /// The `nsec` parameter specifies an amount of slack time that will be used when comparing + /// those times with the current time to account for delays and clock skew. + /// + /// The `maxsec` parameter limits the maximum age of the `this_update` parameter to prohibit + /// very old responses. + pub fn check_validity(&self, nsec: u32, maxsec: Option<u32>) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OCSP_check_validity(self.this_update.as_ptr(), + self.next_update.as_ptr(), + nsec as c_long, + maxsec.map(|n| n as c_long).unwrap_or(-1))) + .map(|_| ()) + } + } +} + +type_!(OcspBasicResponse, OcspBasicResponseRef, ffi::OCSP_BASICRESP, ffi::OCSP_BASICRESP_free); + +impl OcspBasicResponseRef { + /// Verifies the validity of the response. + /// + /// The `certs` parameter contains a set of certificates that will be searched when locating the + /// OCSP response signing certificate. Some responders to not include this in the response. + pub fn verify(&self, + certs: &StackRef<X509>, + store: &X509StoreRef, + flags: Flag) + -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OCSP_basic_verify(self.as_ptr(), certs.as_ptr(), store.as_ptr(), flags.bits())) + .map(|_| ()) + } + } + + /// Looks up the status for the specified certificate ID. + pub fn find_status<'a>(&'a self, id: &OcspCertIdRef) -> Option<Status<'a>> { + unsafe { + let mut status = ffi::V_OCSP_CERTSTATUS_UNKNOWN; + let mut reason = ffi::OCSP_REVOKED_STATUS_NOSTATUS; + let mut revocation_time = ptr::null_mut(); + let mut this_update = ptr::null_mut(); + let mut next_update = ptr::null_mut(); + + let r = ffi::OCSP_resp_find_status(self.as_ptr(), + id.as_ptr(), + &mut status, + &mut reason, + &mut revocation_time, + &mut this_update, + &mut next_update); + if r == 1 { + let revocation_time = if revocation_time.is_null() { + None + } else { + Some(Asn1GeneralizedTimeRef::from_ptr(revocation_time)) + }; + Some(Status { + status: OcspCertStatus(status), + reason: OcspRevokedStatus(status), + revocation_time: revocation_time, + this_update: Asn1GeneralizedTimeRef::from_ptr(this_update), + next_update: Asn1GeneralizedTimeRef::from_ptr(next_update), + }) + } else { + None + } + } + } +} + +type_!(OcspCertId, OcspCertIdRef, ffi::OCSP_CERTID, ffi::OCSP_CERTID_free); + +impl OcspCertId { + /// Constructs a certificate ID for certificate `subject`. + pub fn from_cert(digest: MessageDigest, + subject: &X509Ref, + issuer: &X509Ref) + -> Result<OcspCertId, ErrorStack> { + unsafe { + cvt_p(ffi::OCSP_cert_to_id(digest.as_ptr(), subject.as_ptr(), issuer.as_ptr())) + .map(OcspCertId) + } + } +} + +type_!(OcspResponse, OcspResponseRef, ffi::OCSP_RESPONSE, ffi::OCSP_RESPONSE_free); + +impl OcspResponse { + /// Creates an OCSP response from the status and optional body. + /// + /// A body should only be provided if `status` is `RESPONSE_STATUS_SUCCESSFUL`. + pub fn create(status: OcspResponseStatus, + body: Option<&OcspBasicResponseRef>) + -> Result<OcspResponse, ErrorStack> { + unsafe { + ffi::init(); + + cvt_p(ffi::OCSP_response_create(status.as_raw(), + body.map(|r| r.as_ptr()).unwrap_or(ptr::null_mut()))) + .map(OcspResponse) + } + } + + from_der!(OcspResponse, ffi::d2i_OCSP_RESPONSE); +} + +impl OcspResponseRef { + to_der!(ffi::i2d_OCSP_RESPONSE); + + /// Returns the status of the response. + pub fn status(&self) -> OcspResponseStatus { + unsafe { + OcspResponseStatus(ffi::OCSP_response_status(self.as_ptr())) + } + } + + /// Returns the basic response. + /// + /// This will only succeed if `status()` returns `RESPONSE_STATUS_SUCCESSFUL`. + pub fn basic(&self) -> Result<OcspBasicResponse, ErrorStack> { + unsafe { + cvt_p(ffi::OCSP_response_get1_basic(self.as_ptr())).map(OcspBasicResponse) + } + } +} + +type_!(OcspRequest, OcspRequestRef, ffi::OCSP_REQUEST, ffi::OCSP_REQUEST_free); + +impl OcspRequest { + pub fn new() -> Result<OcspRequest, ErrorStack> { + unsafe { + ffi::init(); + + cvt_p(ffi::OCSP_REQUEST_new()).map(OcspRequest) + } + } + + from_der!(OcspRequest, ffi::d2i_OCSP_REQUEST); +} + +impl OcspRequestRef { + to_der!(ffi::i2d_OCSP_REQUEST); + + pub fn add_id(&mut self, id: OcspCertId) -> Result<&mut OcspOneReqRef, ErrorStack> { + unsafe { + let ptr = try!(cvt_p(ffi::OCSP_request_add0_id(self.as_ptr(), id.as_ptr()))); + mem::forget(id); + Ok(OcspOneReqRef::from_ptr_mut(ptr)) + } + } +} + +type_!(OcspOneReq, OcspOneReqRef, ffi::OCSP_ONEREQ, ffi::OCSP_ONEREQ_free); diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index f412ca93..2fc7605a 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -97,14 +97,14 @@ use ec::EcKeyRef; #[cfg(any(all(feature = "v101", ossl101), all(feature = "v102", ossl102)))] use ec::EcKey; use x509::{X509StoreContextRef, X509FileType, X509, X509Ref, X509VerifyError, X509Name}; -use x509::store::X509StoreBuilderRef; +use x509::store::{X509StoreBuilderRef, X509StoreRef}; #[cfg(any(ossl102, ossl110))] use verify::X509VerifyParamRef; use pkey::PKeyRef; use error::ErrorStack; use types::{OpenSslType, OpenSslTypeRef}; use util::Opaque; -use stack::Stack; +use stack::{Stack, StackRef}; mod error; mod connector; @@ -217,6 +217,22 @@ bitflags! { } } +#[derive(Copy, Clone)] +pub struct StatusType(c_int); + +impl StatusType { + pub fn from_raw(raw: c_int) -> StatusType { + StatusType(raw) + } + + pub fn as_raw(&self) -> c_int { + self.0 + } +} + +/// An OSCP status. +pub const STATUS_TYPE_OCSP: StatusType = StatusType(ffi::TLSEXT_STATUSTYPE_ocsp); + lazy_static! { static ref INDEXES: Mutex<HashMap<TypeId, c_int>> = Mutex::new(HashMap::new()); static ref SSL_INDEXES: Mutex<HashMap<TypeId, c_int>> = Mutex::new(HashMap::new()); @@ -475,6 +491,37 @@ unsafe extern fn raw_tmp_ecdh_ssl<F>(ssl: *mut ffi::SSL, } } +unsafe extern fn raw_tlsext_status<F>(ssl: *mut ffi::SSL, _: *mut c_void) -> c_int + where F: Fn(&mut SslRef) -> Result<bool, ErrorStack> + Any + 'static + Sync + Send +{ + let ssl_ctx = ffi::SSL_get_SSL_CTX(ssl as *const _); + let callback = ffi::SSL_CTX_get_ex_data(ssl_ctx, get_callback_idx::<F>()); + let callback = &*(callback as *mut F); + + let ssl = SslRef::from_ptr_mut(ssl); + let ret = callback(ssl); + + if ssl.is_server() { + match ret { + Ok(true) => ffi::SSL_TLSEXT_ERR_OK, + Ok(false) => ffi::SSL_TLSEXT_ERR_NOACK, + Err(_) => { + // FIXME reset error stack + ffi::SSL_TLSEXT_ERR_ALERT_FATAL + } + } + } else { + match ret { + Ok(true) => 1, + Ok(false) => 0, + Err(_) => { + // FIXME reset error stack + -1 + } + } + } +} + /// The function is given as the callback to `SSL_CTX_set_next_protos_advertised_cb`. /// /// It causes the parameter `out` to point at a `*const c_uchar` instance that @@ -887,6 +934,31 @@ impl SslContextBuilder { unsafe { X509StoreBuilderRef::from_ptr_mut(ffi::SSL_CTX_get_cert_store(self.as_ptr())) } } + /// Sets the callback dealing with OCSP stapling. + /// + /// On the client side, this callback is responsible for validating the OCSP status response + /// returned by the server. The status may be retrieved with the `SslRef::ocsp_status` method. + /// A response of `Ok(true)` indicates that the OCSP status is valid, and a response of + /// `Ok(false)` indicates that the OCSP status is invalid and the handshake should be + /// terminated. + /// + /// On the server side, this callback is resopnsible for setting the OCSP status response to be + /// returned to clients. The status may be set with the `SslRef::set_ocsp_status` method. A + /// response of `Ok(true)` indicates that the OCSP status should be returned to the client, and + /// `Ok(false)` indicates that the status should not be returned to the client. + pub fn set_status_callback<F>(&mut self, callback: F) -> Result<(), ErrorStack> + where F: Fn(&mut SslRef) -> Result<bool, ErrorStack> + Any + 'static + Sync + Send + { + unsafe { + let callback = Box::new(callback); + ffi::SSL_CTX_set_ex_data(self.as_ptr(), + get_callback_idx::<F>(), + Box::into_raw(callback) as *mut c_void); + let f: unsafe extern fn (_, _) -> _ = raw_tlsext_status::<F>; + cvt(ffi::SSL_CTX_set_tlsext_status_cb(self.as_ptr(), Some(f)) as c_int).map(|_| ()) + } + } + pub fn build(self) -> SslContext { let ctx = SslContext(self.0); mem::forget(self); @@ -951,6 +1023,22 @@ impl SslContextRef { } } } + + /// Returns the certificate store used for verification. + pub fn cert_store(&self) -> &X509StoreRef { + unsafe { + X509StoreRef::from_ptr(ffi::SSL_CTX_get_cert_store(self.as_ptr())) + } + } + + pub fn extra_chain_certs(&self) -> &StackRef<X509> { + unsafe { + let mut chain = ptr::null_mut(); + ffi::SSL_CTX_get_extra_chain_certs(self.as_ptr(), &mut chain); + assert!(!chain.is_null()); + StackRef::from_ptr(chain) + } + } } pub struct CipherBits { @@ -1378,6 +1466,49 @@ impl SslRef { } } } + + /// Sets the status response a client wishes the server to reply with. + pub fn set_status_type(&mut self, type_: StatusType) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_set_tlsext_status_type(self.as_ptr(), type_.as_raw()) as c_int).map(|_| ()) + } + } + + /// Returns the server's OCSP response, if present. + pub fn ocsp_status(&self) -> Option<&[u8]> { + unsafe { + let mut p = ptr::null_mut(); + let len = ffi::SSL_get_tlsext_status_ocsp_resp(self.as_ptr(), &mut p); + + if len < 0 { + None + } else { + Some(slice::from_raw_parts(p as *const u8, len as usize)) + } + } + } + + /// Sets the OCSP response to be returned to the client. + pub fn set_ocsp_status(&mut self, response: &[u8]) -> Result<(), ErrorStack> { + unsafe { + assert!(response.len() <= c_int::max_value() as usize); + let p = try!(cvt_p(ffi::CRYPTO_malloc(response.len() as _, + concat!(file!(), "\0").as_ptr() as *const _, + line!() as c_int))); + ptr::copy_nonoverlapping(response.as_ptr(), p as *mut u8, response.len()); + cvt(ffi::SSL_set_tlsext_status_ocsp_resp(self.as_ptr(), + p as *mut c_uchar, + response.len() as c_long) as c_int) + .map(|_| ()) + } + } + + /// Determines if this `Ssl` is configured for server-side or client-side use. + pub fn is_server(&self) -> bool { + unsafe { + compat::SSL_is_server(self.as_ptr()) != 0 + } + } } unsafe impl Sync for Ssl {} @@ -1745,9 +1876,8 @@ mod compat { use ffi; use libc::c_int; - pub use ffi::{SSL_CTX_get_options, SSL_CTX_set_options}; - pub use ffi::{SSL_CTX_clear_options, SSL_CTX_up_ref}; - pub use ffi::SSL_SESSION_get_master_key; + pub use ffi::{SSL_CTX_get_options, SSL_CTX_set_options, SSL_CTX_clear_options, SSL_CTX_up_ref, + SSL_SESSION_get_master_key, SSL_is_server}; pub unsafe fn get_new_idx(f: ffi::CRYPTO_EX_free) -> c_int { ffi::CRYPTO_get_ex_new_index(ffi::CRYPTO_EX_INDEX_SSL_CTX, @@ -1839,4 +1969,8 @@ mod compat { pub fn dtls_method() -> *const ffi::SSL_METHOD { unsafe { ffi::DTLSv1_method() } } + + pub unsafe fn SSL_is_server(s: *mut ffi::SSL) -> c_int { + (*s).server + } } diff --git a/openssl/src/ssl/tests/mod.rs b/openssl/src/ssl/tests/mod.rs index 14bb2f71..349c7a4d 100644 --- a/openssl/src/ssl/tests/mod.rs +++ b/openssl/src/ssl/tests/mod.rs @@ -16,9 +16,11 @@ use tempdir::TempDir; use dh::Dh; use hash::MessageDigest; +use ocsp::{OcspResponse, RESPONSE_STATUS_UNAUTHORIZED}; use ssl; use ssl::{SslMethod, HandshakeError, SslContext, SslStream, Ssl, ShutdownResult, - SslConnectorBuilder, SslAcceptorBuilder, Error, SSL_VERIFY_PEER, SSL_VERIFY_NONE}; + SslConnectorBuilder, SslAcceptorBuilder, Error, SSL_VERIFY_PEER, SSL_VERIFY_NONE, + STATUS_TYPE_OCSP}; use x509::{X509StoreContext, X509, X509Name, X509_FILETYPE_PEM}; #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))] use x509::verify::X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS; @@ -1393,7 +1395,48 @@ fn active_session() { let mut buf = vec![0; len + 1]; let copied = session.master_key(&mut buf); assert_eq!(copied, len); +} + +#[test] +fn status_callbacks() { + static CALLED_BACK_SERVER: AtomicBool = ATOMIC_BOOL_INIT; + static CALLED_BACK_CLIENT: AtomicBool = ATOMIC_BOOL_INIT; + + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + let guard = thread::spawn(move || { + let stream = listener.accept().unwrap().0; + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_certificate_file(&Path::new("test/cert.pem"), X509_FILETYPE_PEM).unwrap(); + ctx.set_private_key_file(&Path::new("test/key.pem"), X509_FILETYPE_PEM).unwrap(); + ctx.set_status_callback(|ssl| { + CALLED_BACK_SERVER.store(true, Ordering::SeqCst); + let response = OcspResponse::create(RESPONSE_STATUS_UNAUTHORIZED, None).unwrap(); + let response = response.to_der().unwrap(); + ssl.set_ocsp_status(&response).unwrap(); + Ok(true) + }).unwrap(); + let ssl = Ssl::new(&ctx.build()).unwrap(); + ssl.accept(stream).unwrap(); + }); + + let stream = TcpStream::connect(("127.0.0.1", port)).unwrap(); + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_status_callback(|ssl| { + CALLED_BACK_CLIENT.store(true, Ordering::SeqCst); + let response = OcspResponse::from_der(ssl.ocsp_status().unwrap()).unwrap(); + assert_eq!(response.status(), RESPONSE_STATUS_UNAUTHORIZED); + Ok(true) + }); + let mut ssl = Ssl::new(&ctx.build()).unwrap(); + ssl.set_status_type(STATUS_TYPE_OCSP).unwrap(); + ssl.connect(stream).unwrap(); + + assert!(CALLED_BACK_SERVER.load(Ordering::SeqCst)); + assert!(CALLED_BACK_CLIENT.load(Ordering::SeqCst)); + + guard.join().unwrap(); } fn _check_kinds() { diff --git a/openssl/src/string.rs b/openssl/src/string.rs new file mode 100644 index 00000000..37d44d16 --- /dev/null +++ b/openssl/src/string.rs @@ -0,0 +1,74 @@ +use ffi; +use libc::{c_char, c_void}; +use std::fmt; +use std::ffi::CStr; +use std::ops::Deref; +use std::str; + +use types::{OpenSslType, OpenSslTypeRef}; +use stack::Stackable; + +type_!(OpensslString, OpensslStringRef, c_char, free); + +impl OpensslString { + #[deprecated(note = "use from_ptr", since = "0.9.7")] + pub unsafe fn from_raw_parts(buf: *mut u8, _: usize) -> OpensslString { + OpensslString::from_ptr(buf as *mut c_char) + } + + #[deprecated(note = "use from_ptr", since = "0.9.7")] + pub unsafe fn from_null_terminated(buf: *mut c_char) -> OpensslString { + OpensslString::from_ptr(buf) + } +} + +impl fmt::Display for OpensslString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +impl fmt::Debug for OpensslString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl Stackable for OpensslString { + type StackType = ffi::stack_st_OPENSSL_STRING; +} + +impl Deref for OpensslStringRef { + type Target = str; + + fn deref(&self) -> &str { + unsafe { + let slice = CStr::from_ptr(self.as_ptr()).to_bytes(); + str::from_utf8_unchecked(slice) + } + } +} + +impl fmt::Display for OpensslStringRef { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +impl fmt::Debug for OpensslStringRef { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +#[cfg(not(ossl110))] +unsafe fn free(buf: *mut c_char) { + ::ffi::CRYPTO_free(buf as *mut c_void); +} + +#[cfg(ossl110)] +unsafe fn free(buf: *mut c_char) { + ::ffi::CRYPTO_free(buf as *mut c_void, + concat!(file!(), "\0").as_ptr() as *const c_char, + line!() as ::libc::c_int); +} diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index d90cee22..e75dcf5d 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -20,6 +20,7 @@ use error::ErrorStack; use ffi; use nid::Nid; use types::{OpenSslType, OpenSslTypeRef}; +use string::OpensslString; use stack::{Stack, StackRef, Stackable}; #[cfg(ossl10x)] @@ -415,6 +416,25 @@ impl X509Ref { } } + /// Returns the list of OCSP responder URLs specified in the certificate's Authority Information + /// Access field. + pub fn ocsp_responders(&self) -> Result<Stack<OpensslString>, ErrorStack> { + unsafe { + cvt_p(ffi::X509_get1_ocsp(self.as_ptr())).map(|p| Stack::from_ptr(p)) + } + } + + /// Checks that this certificate issued `subject`. + pub fn issued(&self, subject: &X509Ref) -> Result<(), X509VerifyError> { + unsafe { + let r = ffi::X509_check_issued(self.as_ptr(), subject.as_ptr()); + match X509VerifyError::from_raw(r as c_long) { + Some(e) => Err(e), + None => Ok(()), + } + } + } + to_pem!(ffi::PEM_write_bio_X509); to_der!(ffi::i2d_X509); } diff --git a/openssl/src/x509/store.rs b/openssl/src/x509/store.rs index 01eb0e2f..dd08a49b 100644 --- a/openssl/src/x509/store.rs +++ b/openssl/src/x509/store.rs @@ -1,13 +1,33 @@ use ffi; use std::mem; -use cvt; +use {cvt, cvt_p}; use error::ErrorStack; use types::OpenSslTypeRef; use x509::X509; type_!(X509StoreBuilder, X509StoreBuilderRef, ffi::X509_STORE, ffi::X509_STORE_free); +impl X509StoreBuilder { + /// Returns a builder for a certificate store. + /// + /// The store is initially empty. + pub fn new() -> Result<X509StoreBuilder, ErrorStack> { + unsafe { + ffi::init(); + + cvt_p(ffi::X509_STORE_new()).map(X509StoreBuilder) + } + } + + /// Constructs the `X509Store`. + pub fn build(self) -> X509Store { + let store = X509Store(self.0); + mem::forget(self); + store + } +} + impl X509StoreBuilderRef { /// Adds a certificate to the certificate store. pub fn add_cert(&mut self, cert: X509) -> Result<(), ErrorStack> { @@ -17,4 +37,17 @@ impl X509StoreBuilderRef { cvt(ffi::X509_STORE_add_cert(self.as_ptr(), ptr)).map(|_| ()) } } + + /// Load certificates from their default locations. + /// + /// These locations are read from the `SSL_CERT_FILE` and `SSL_CERT_DIR` + /// environment variables if present, or defaults specified at OpenSSL + /// build time otherwise. + pub fn set_default_paths(&mut self) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::X509_STORE_set_default_paths(self.as_ptr())).map(|_| ()) + } + } } + +type_!(X509Store, X509StoreRef, ffi::X509_STORE, ffi::X509_STORE_free); diff --git a/openssl/src/x509/tests.rs b/openssl/src/x509/tests.rs index 0843b19f..f89b7267 100644 --- a/openssl/src/x509/tests.rs +++ b/openssl/src/x509/tests.rs @@ -132,7 +132,7 @@ fn test_nid_values() { assert_eq!(email.data().as_slice(), b"test@example.com"); let friendly = subject.entries_by_nid(nid::FRIENDLYNAME).next().unwrap(); - assert_eq!(&*friendly.data().as_utf8().unwrap(), "Example"); + assert_eq!(&**friendly.data().as_utf8().unwrap(), "Example"); } #[test] @@ -186,3 +186,14 @@ fn test_stack_from_pem() { assert_eq!(certs[1].fingerprint(MessageDigest::sha1()).unwrap().to_hex(), "c0cbdf7cdd03c9773e5468e1f6d2da7d5cbb1875"); } + +#[test] +fn issued() { + let cert = include_bytes!("../../test/cert.pem"); + let cert = X509::from_pem(cert).unwrap(); + let ca = include_bytes!("../../test/root-ca.pem"); + let ca = X509::from_pem(ca).unwrap(); + + ca.issued(&cert).unwrap(); + cert.issued(&cert).err().unwrap(); +} |