diff --git a/openssl-sys/src/handwritten/types.rs b/openssl-sys/src/handwritten/types.rs index a03a878305..3323ade7e3 100644 --- a/openssl-sys/src/handwritten/types.rs +++ b/openssl-sys/src/handwritten/types.rs @@ -346,6 +346,8 @@ pub enum X509_LOOKUP_METHOD {} pub enum X509_NAME {} +pub enum X509_PUBKEY {} + cfg_if! { if #[cfg(any(ossl110, libressl270))] { pub enum X509_STORE {} diff --git a/openssl-sys/src/handwritten/x509.rs b/openssl-sys/src/handwritten/x509.rs index f5e3c24289..dbfedefb45 100644 --- a/openssl-sys/src/handwritten/x509.rs +++ b/openssl-sys/src/handwritten/x509.rs @@ -152,6 +152,13 @@ extern "C" { len: *mut c_uint, ) -> c_int; + pub fn X509_pubkey_digest( + data: *const X509, + type_: *const EVP_MD, + md: *mut c_uchar, + len: *mut c_uint, + ) -> c_int; + pub fn X509_REQ_sign(x: *mut X509_REQ, pkey: *mut EVP_PKEY, md: *const EVP_MD) -> c_int; } @@ -356,6 +363,8 @@ const_ptr_api! { extern "C" { pub fn X509_REQ_set_pubkey(req: *mut X509_REQ, pkey: *mut EVP_PKEY) -> c_int; pub fn X509_REQ_get_pubkey(req: *mut X509_REQ) -> *mut EVP_PKEY; + #[cfg(ossl110)] + pub fn X509_REQ_get_X509_PUBKEY(req: *mut X509_REQ) -> *mut X509_PUBKEY; pub fn X509_REQ_get_extensions(req: *mut X509_REQ) -> *mut stack_st_X509_EXTENSION; } const_ptr_api! { @@ -435,6 +444,46 @@ const_ptr_api! { } } +extern "C" { + pub fn X509_PUBKEY_new() -> *mut X509_PUBKEY; + pub fn X509_PUBKEY_free(a: *mut X509_PUBKEY); + #[cfg(ossl300)] + pub fn X509_PUBKEY_dup(a: *const X509_PUBKEY) -> *mut X509_PUBKEY; + #[cfg(ossl300)] + pub fn X509_PUBKEY_eq(a: *const X509_PUBKEY, b: *const X509_PUBKEY) -> c_int; + pub fn d2i_X509_PUBKEY( + a: *mut *mut X509_PUBKEY, + in_: *mut *const c_uchar, + len: c_long, + ) -> *mut X509_PUBKEY; + #[cfg(ossl300)] + pub fn X509_PUBKEY_new_ex(libctx: *mut OSSL_LIB_CTX, propq: *const c_char) -> *mut X509_PUBKEY; + pub fn X509_PUBKEY_set(x: *mut *mut X509_PUBKEY, pkey: *mut EVP_PKEY) -> c_int; + + pub fn X509_PUBKEY_set0_param( + pub_: *mut X509_PUBKEY, + aobj: *mut ASN1_OBJECT, + ptype: c_int, + pval: *mut c_void, + penc: *mut c_uchar, + penclen: c_int, + ) -> c_int; +} +const_ptr_api! { + extern "C" { + pub fn i2d_X509_PUBKEY(a: #[const_ptr_if(ossl300)] X509_PUBKEY, out: *mut *mut c_uchar) -> c_int; + pub fn X509_PUBKEY_get0_param( + ppkalg: *mut *mut ASN1_OBJECT, + pk: *mut *const c_uchar, + ppklen: *mut c_int, + pa: *mut *mut X509_ALGOR, + pub_: #[const_ptr_if(ossl300)] X509_PUBKEY, + ) -> c_int; + pub fn X509_PUBKEY_get(key: #[const_ptr_if(ossl300)] X509_PUBKEY) -> *mut EVP_PKEY; + pub fn X509_PUBKEY_get0(key: #[const_ptr_if(ossl300)] X509_PUBKEY) -> *mut EVP_PKEY; + } +} + extern "C" { #[cfg(any(ossl110, libressl281))] pub fn X509_CRL_get_REVOKED(crl: *mut X509_CRL) -> *mut stack_st_X509_REVOKED; @@ -447,6 +496,8 @@ extern "C" { #[cfg(ossl110)] pub fn X509_get0_extensions(req: *const X509) -> *const stack_st_X509_EXTENSION; + #[cfg(ossl110)] + pub fn X509_get_X509_PUBKEY(x: *const X509) -> *mut X509_PUBKEY; pub fn X509_CRL_set_version(crl: *mut X509_CRL, version: c_long) -> c_int; } diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index 97242ff4d8..e14d8fff4b 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -9,7 +9,7 @@ use cfg_if::cfg_if; use foreign_types::{ForeignType, ForeignTypeRef, Opaque}; -use libc::{c_int, c_long, c_uint, c_void}; +use libc::{c_int, c_long, c_uchar, c_uint, c_void}; use std::cmp::{self, Ordering}; use std::convert::{TryFrom, TryInto}; use std::error::Error; @@ -539,6 +539,15 @@ impl X509Ref { } } + #[corresponds(X509_get_X509_PUBKEY)] + #[cfg(ossl110)] + pub fn x509_pubkey(&self) -> Result<&X509PubkeyRef, ErrorStack> { + unsafe { + let key = cvt_p(ffi::X509_get_X509_PUBKEY(self.as_ptr()))?; + Ok(X509PubkeyRef::from_ptr(key)) + } + } + /// Returns a digest of the DER representation of the certificate. #[corresponds(X509_digest)] pub fn digest(&self, hash_type: MessageDigest) -> Result { @@ -565,6 +574,27 @@ impl X509Ref { self.digest(hash_type).map(|b| b.to_vec()) } + /// Returns a digest of the DER representation of the public key in the certificate. + #[corresponds(X509_pubkey_digest)] + pub fn pubkey_digest(&self, hash_type: MessageDigest) -> Result { + unsafe { + let mut digest = DigestBytes { + buf: [0; ffi::EVP_MAX_MD_SIZE as usize], + len: ffi::EVP_MAX_MD_SIZE as usize, + }; + let mut len = ffi::EVP_MAX_MD_SIZE as c_uint; + cvt(ffi::X509_pubkey_digest( + self.as_ptr(), + hash_type.as_ptr(), + digest.buf.as_mut_ptr() as *mut _, + &mut len, + ))?; + digest.len = len as usize; + + Ok(digest) + } + } + /// Returns the certificate's Not After validity period. #[corresponds(X509_getm_notAfter)] pub fn not_after(&self) -> &Asn1TimeRef { @@ -1352,6 +1382,97 @@ impl fmt::Debug for X509NameEntryRef { } } +foreign_type_and_impl_send_sync! { + type CType = ffi::X509_PUBKEY; + fn drop = ffi::X509_PUBKEY_free; + + /// The SubjectPublicKeyInfo of an `X509` certificate. + pub struct X509Pubkey; + /// Reference to `X509Pubkey`. + pub struct X509PubkeyRef; +} + +impl X509Pubkey { + from_der! { + /// Deserializes a DER-encoded X509 SubjectPublicKeyInfo. + /// + /// This corresponds to [`d2i_X509_PUBKEY`]. + /// + /// [`d2i_X509_PUBKEY`]: https://www.openssl.org/docs/manmaster/crypto/d2i_X509_PUBKEY.html + from_der, + X509Pubkey, + ffi::d2i_X509_PUBKEY + } + + /// Build a X509Pubkey from the public key. + /// + /// This corresponds to [`X509_PUBKEY_set`]. + /// + /// [`X509_PUBKEY_set`]: https://www.openssl.org/docs/manmaster/crypto/X509_PUBKEY_set.html + pub fn from_pubkey(key: &PKeyRef) -> Result + where + T: HasPublic, + { + let mut p = ptr::null_mut(); + unsafe { + cvt(ffi::X509_PUBKEY_set(&mut p as *mut _, key.as_ptr()))?; + } + Ok(X509Pubkey(p)) + } +} + +impl X509PubkeyRef { + /// Copies the X509 SubjectPublicKeyInfo to a new `X509Pubkey`. + #[corresponds(X509_PUBKEY_dup)] + #[cfg(ossl300)] + pub fn to_owned(&self) -> Result { + unsafe { cvt_p(ffi::X509_PUBKEY_dup(self.as_ptr())).map(|n| X509Pubkey::from_ptr(n)) } + } + + to_der! { + /// Serializes the X509 SubjectPublicKeyInfo to DER-encoded. + /// + /// This corresponds to [`i2d_X509_PUBKEY`]. + /// + /// [`i2d_X509_PUBKEY`]: https://www.openssl.org/docs/manmaster/crypto/i2d_X509_PUBKEY.html + to_der, + ffi::i2d_X509_PUBKEY + } + + /// Returns the public key of the X509 SubjectPublicKeyInfo. + /// + /// This corresponds to [`X509_PUBKEY_get"] + /// + /// [`X509_PUBKEY_get`]: https://www.openssl.org/docs/manmaster/crypto/X509_PUBKEY_get.html + pub fn public_key(&self) -> Result, ErrorStack> { + unsafe { + let key = cvt_p(ffi::X509_PUBKEY_get(self.as_ptr()))?; + Ok(PKey::from_ptr(key)) + } + } + + /// Get the encoded bytes of the X509 SubjectPublicKeyInfo. + /// + /// This corresponds to ['X509_PUBKEY_get0_param'] + /// + /// ['X509_PUBKEY_get0_param']: https://www.openssl.org/docs/man3.0/man3/X509_PUBKEY_get0_param.html + pub fn encoded_bytes(&self) -> Result<&[u8], ErrorStack> { + unsafe { + let mut pk = ptr::null_mut() as *const c_uchar; + let mut pkt_len: c_int = 0; + cvt(ffi::X509_PUBKEY_get0_param( + ptr::null_mut(), + &mut pk as *mut _, + &mut pkt_len as *mut _, + ptr::null_mut(), + self.as_ptr(), + ))?; + + Ok(slice::from_raw_parts(pk, pkt_len as usize)) + } + } +} + /// A builder used to construct an `X509Req`. pub struct X509ReqBuilder(X509Req); @@ -1579,6 +1700,19 @@ impl X509ReqRef { } } + /// Returns the X509Pubkey of the certificate request. + /// + /// This corresponds to [`X509_REQ_get_X509_PUBKEY"] + /// + /// [`X509_REQ_get_X509_PUBKEY`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_get_X509_PUBKEY.html + #[cfg(ossl110)] + pub fn x509_pubkey(&self) -> Result<&X509PubkeyRef, ErrorStack> { + unsafe { + let key = cvt_p(ffi::X509_REQ_get_X509_PUBKEY(self.as_ptr()))?; + Ok(X509PubkeyRef::from_ptr(key)) + } + } + /// Check if the certificate request is signed using the given public key. /// /// Returns `true` if verification succeeds. diff --git a/openssl/src/x509/tests.rs b/openssl/src/x509/tests.rs index a4a3de970c..adc610f314 100644 --- a/openssl/src/x509/tests.rs +++ b/openssl/src/x509/tests.rs @@ -25,7 +25,8 @@ use crate::x509::X509PurposeRef; #[cfg(ossl110)] use crate::x509::{CrlReason, X509Builder}; use crate::x509::{ - CrlStatus, X509Crl, X509Extension, X509Name, X509Req, X509StoreContext, X509VerifyResult, X509, + CrlStatus, X509Crl, X509Extension, X509Name, X509Pubkey, X509Req, X509StoreContext, + X509VerifyResult, X509, }; #[cfg(ossl110)] @@ -265,6 +266,37 @@ fn test_subject_alt_name_iter() { assert!(subject_alt_names_iter.next().is_none()); } +#[test] +fn test_x509_pubkey() { + let cert = include_bytes!("../../test/certv3.pem"); + let cert = X509::from_pem(cert).unwrap(); + + let pubkey_digest = cert.pubkey_digest(MessageDigest::sha1()).unwrap(); + + let pkey = cert.public_key().unwrap(); + let x_pkey = X509Pubkey::from_pubkey(&pkey).unwrap(); + + let xbytes = x_pkey.encoded_bytes().unwrap(); + let hashed = crate::hash::hash(MessageDigest::sha1(), xbytes).unwrap(); + + assert_eq!(pubkey_digest.as_ref(), hashed.as_ref()); +} + +#[test] +#[cfg(ossl110)] +fn test_x509_pubkey_ref() { + let cert = include_bytes!("../../test/certv3.pem"); + let cert = X509::from_pem(cert).unwrap(); + + let pkey = cert.public_key().unwrap(); + let x_pkey = X509Pubkey::from_pubkey(&pkey).unwrap(); + let xbytes = x_pkey.encoded_bytes().unwrap(); + + let y_pkey = cert.x509_pubkey().unwrap(); + let ybytes = y_pkey.encoded_bytes().unwrap(); + assert_eq!(xbytes, ybytes); +} + #[test] fn test_aia_ca_issuer() { // With AIA